1. 与memcached的区别:

  1. Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等;
  2. Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储;
  3. 虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘;
  4. 过期策略–memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10;
  5. 分布式–设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从;
  6. 存储数据安全–memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化);
  7. 灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复;
  8. Redis支持数据的备份,即master-slave模式的数据备份;
  9. memcached使用的是串行+多线程+锁,redis使用的是单线程+多路IO复用

2. Redis如何避免并发问题 – IO多路复用

redis是单线程(6改为多线程,但本质还是单线程)+多路IO复用技术

多路复用是指:使用一个线程来检查多个文件描述符的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)

多路复用简单点说就是给请求监视效果:
当有多个请求访问redis的时候,可以把这些请求都加一个监视效果,监视这个请求是否准备完毕,如果某个请求准备完毕,直接处理这个请求,处理完成当前请求后再处理其他请求。
没有阻塞的状态,一直在处理请求。

2.1 阻塞IO、非阻塞IO、IO多路复用区别:

  • 其中select和poll的区别是poll没有数量限制;
  • epoll相当于给请求加上一个标识符,通过标识符来判断它是否准备完毕

3. 适用场景:

4. redis五大类型简介及应用:

4.1 基本操作

  1. 选择库:select index
    例:选择一号库:select 1
  2. 查看所有键:keys *(生产不能用,太耗时)
  3. 判断某个键是否存在:exists key
  4. 查看键的类型:type key
  5. 删除某个键:del key
  6. 为键值设置过期时间(单位秒):expire key seconds
    例:expire a 10
  7. 查看键剩余过期时间:ttl key
    -1表示永不过期,-2表示已经过期
  8. 查看当前数据库数量:dbsize
  9. 清空当前库:Flushdb
  10. 通杀全部库:Flushall

4.2 String类型:

4.2.1 简介:

  1. string是Redis最基本的类型,可以理解成与memcached一模一样的类型,一个key对应一个value
  2. string类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象
  3. 个redis中字符串value最多可以是512M

4.2.2 命令:

  1. get key:获取key的value
  2. set key value:设置k-v值
  3. append key value:在key的值后面追加value,返回追加后的value的长度
  4. strlen key:获取key的值长度
  5. setnx key value:如果在key不存在时设置值,如果存在不设置值
    如果key存在返回0,如果key不存在返回1
  6. incr key :将key中储存的数字值+1,只能对数字值操作,否则报错,如果为空,新增值为1
  7. decr key:将key中储存的数字值-1,只能对数字值操作,如果为空,新增值为-1
  8. incrby/decrby key 步长:将key中储存的值增减自定义步长
  9. mset key1 value1 key2 value2:一次性设置一个或多个键值对
  10. mget key1 key2 key3:一次获取一个或多个value
  11. msetnx key1 value1 key2 value2:同时设置一个或多个键值对,当且仅当给定的key都不存在
  12. getrange key 起始位置 结束位置:获得范围内的值,包前包后,不修改原值
  13. setrange key 起始位置 value:用value从起始位置开始覆写key的字符串值
  14. setex key 过期时间 value:设置键值的同时设置过期时间,单位秒
  15. getset key value:以新换旧,设置新值同时获得旧值

4.2.3 应用:

4.2.3.1 缓存功能:

部分数据第一次查询查询数据库,查询完后存入redis中,后续再获取可以从redis中获取

4.2.3.2 验证码:

网站登录中常有验证码,我们可以用此数据类型,手机号作为key,验证码作为value存储在redis中,设置过期时间,后续如果用户输入验证码,我们从redis中取值对比,如果过期则无效

set 13030303300 123456
4.2.3.3 数字计数:

比如帖子有点赞数,可以以帖子的id作为key,点赞总数作为value; 还比如访问量等,用户每次访问,访问总数可以加一,记录在redis中; 抖音的关注数,当大V注册抖音的时候,关注数会在非常短的时间内增加,这里我们可以用redis记录,一段时间后同步到mysql等数据库中;

user-id:10086:fans → 123456
user-id:10086:blogs → 999
user-id:10086:likes →  888
4.2.3.4 存储对象:

以json形式存储,常见key=id value=json格式数据,如商品id为key,商品信息为value

  1. 我的项目中的应用:

    1. 生成卷子时,卷子对象的json数据存到redis中,首次进入直接到redis中获取
    2. 定时任务给每个用户生成每日推荐,视频组成json列表存放
{"id":10086,"name":"哈哈哈哈","fans":123456,"blogs":999, "likes":888}
4.2.3.5 共享session:(分布式唯一id)

如我们第一次访问 https://editor.csdn.net这个域名,可能会对应这个IP 112.14.111.222的服务器,然后第二次访问,IP可能会变为112.13.121.219的服务器;负载均衡,一个域名对应多个服务器,将访问量分担到其他的服务器,这样很大程度的减轻了每个服务器上访问量

因为服务器都会有自己的会话session会导致用户每次刷新网页又要重新登录,为了解决这个问题,我们用redis将用户session集中管理,每次获取用户更新或查询登录信息都直接从redis中集中获取

这里的本质还是将某一个东西存入redis缓存中,和缓存功能类似,描述的是不同的应用场景

负载均衡:把众多的访问量分担到其他的服务器上,让每个服务器的压力减少

4.2.3.6 分布式锁

适用场景:在一个集群环境下,多个web应用时对同一个商品进行抢购和减库存操作时,可能出现超卖时会用到分布式锁

setnx key value  //存入一个不存在的键值对,如果key不存在,同set;若存在,则不做任何操作

语法:SETNX key value
功能:当且仅当 key 不存在,将 key 的值设为 value ,并返回1;
若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

4.3 list类型:

4.3.1 简介:

  • 单键多值
  • Redis列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)
  • 底层是双向链表,对双端的操作性能很高,通过索引下标的操作中间的节点性能会较差
  • 单个元素可以储存4GB数据

4.3.2 命令:

  1. lpush/rpush key value1 value2 ...:从左边/右边插入一个或多个值(先进先出
  2. lpop/rpop key :从左边/右边弹出一个数据,弹出后数据就不在list中了
  3. rpoplpush key1 key2:从key1列表右边弹出一个值插入到key2的左边。
  4. lrange key start stop:按照索引下标获得元素(从左到右)
    从头到尾:lrange key 0 -1
  5. llen key:获得列表长度
  6. lindex key index:获取index位置的元素(不弹出,从左往右)
    lindex key -1:获取最后一个元素
  7. linsert key before/after value newvalue:在value的前/后面插入newvalue
  8. lrem key n value:从左边删除n个指定value(从左往右)

4.3.3 应用:

参考:https://blog.csdn.net/yaoyaochengxian/article/details/120401152

4.3.3.1 微信抢红包

关于微信抢红包,每个人应该都用过,我们今天就来聊聊这个抢红包的技术实现。
像微信抢红包的高峰期一般是在年底公司开年会和春节2个时间段,高峰的并发量是在几千万以上。

高峰的抢红包有3大特点:

  1. 包红包的人多:也就是创建红包的任务比较多,即红包系统是以单个红包的任务来区分,特点就是在高峰期红包任务多。
  2. 抢红包的人更多:当你发红包出去后,是几十甚至几百人来抢你的红包,即单红包的请求并发量大。
  3. 抢红包体验:当你发现红包时,要越快抢到越开心,所以要求抢红包的响应速度要快,一般1秒响应。

微信抢红包的技术实现原理

  • 包红包:

    • 先把金额拆解为小金额的红包,例如 总金额100元,发20个,用户在点保存的时候,就自动拆解为20个随机小红包。
    • 这里的存储就是个难题,多个金额(例如20个小金额的红包)如何存储?采用set、list、还是hash?set不能存储相同的值,也就无法用在金额相同的红包分发中;
    • 代码:
public long setRedpacket(int total, int count) {//拆解红包Integer[] packet= this.splitRedPacket(total,count);//为红包生成全局唯一idlong n=idGenerator .incrementId();//采用list存储红包String key=RED_PACKET_KEY+n;this.redisTemplate.opsForList().leftPushAll(key,packet);//设置3天过期this.redisTemplate.expire(key,3, TimeUnit.DAYS);log.info("拆解红包{}={}",key,packet);return n;
}
  • 抢红包:

    • 高并发的抢红包时核心的关键技术,就是控制各个小红包的原子性。
    • 例如 20个红包在500人的群里被抢,20个红包被抢走一个的同时要红包的库存减1,即剩下19个。
    • 在整个过程中抢走一个 和 红包库存减1个 是一个原子操作。
    • 那数据类型符合 “抢走一个 和 红包库存减1个 是一个原子操作” 采用set、list、还是hash?相对来说。list更加合适
    • list的pop操作弹出一个元素的同时会自动从队列里面剔除该元素,它是一个原子性操作。
    • 代码:
public int rob(long redid,long userid) {//第一步:验证该用户是否抢过Object packet=this.redisTemplate.opsForHash().get(RED_PACKET_CONSUME_KEY+redid,String.valueOf(userid));if(packet==null){//第二步:从list队列,弹出一个红包Object obj=this.redisTemplate.opsForList().leftPop(RED_PACKET_KEY+redid);if(obj!=null){//第三步:抢到红包存起来this.redisTemplate.opsForHash().put(RED_PACKET_CONSUME_KEY+redid,String.valueOf(userid),obj);log.info("用户={}抢到{}",userid,obj);//TODO 异步把数据落地到数据库上return (Integer) obj;}//-1 代表抢完return -1;}//-2 该用户代表已抢return -2;
}
4.3.3.2 商品列表

聚划算商品页:https://ju.taobao.com/

这张页面的特点:

  1. 数据量少,才13页
  2. 高并发,请求量大。

像聚划算这种高并发的功能,绝对不可能用数据库的!
一般的做法是先把数据库中的数据抽取到redis里面。采用定时器,来定时缓存。
这张页面的特点,数据量不多,才13页。最大的特点就要支持分页。redis的 list数据结构天然支持这种高并发的分页查询功能。
具体的技术方案采用list 的lpush 和 lrange来实现。

代码:(此处直接用redis命令模拟存商品和获取商品代码)

## 先用定时器把数据刷新到list中
127.0.0.1:6379> lpush jhs p1 p2 p3 p4 p5 p6 p7 p8 p9 p10
(integer) 10
## 用lrange来实现分页
127.0.0.1:6379> lrange jhs 0 5
1) "p10"
2) "p9"
3) "p8"
4) "p7"
5) "p6"
6) "p5"
127.0.0.1:6379> lrange jhs 6 10
1) "p4"
2) "p3"
3) "p2"
4) "p1"
4.3.3.3 PV阅读量
  • 并发量低的情况:通常情况我们使用redisTemplate.opsForValue().increment(postId,num)就可以实现阅读量功能了。
  • 并发量高的情况:假如每天有10万篇文章,每篇文章有10万次点击阅读;那么就需要10亿次自增操作,一天12小时高峰的话,平摊下来,Redis的QPS需要达到50多万,这就导致Redis服务器CPU必然达到了100%。

二级缓存的高并发微信文章的阅读量PV技术方案

  1. 一级缓存定时器:

    • JVM的缓存Map: Map<Long, Map<Long,Object>> pvMap=new ConcurrentHashMap<>();
    • pvMap中的key存储的是时间块的值, Map<Long,Object>是在时间块内增加的阅读量。存储时间块内所有文章的点击量;
    • 一级缓存定时器:将定时5分钟,将5分钟阅读量map放入Redis的List;
    • List的value值为每隔时间块的阅读量map;
    • 目的:是避免直接与Redis的阅读量计数器进行交互,分摊redis的并发量到本地的JVM,给JVM降压;
  2. 二级缓存定时器
    • 定时将List的值拿出来,遍历每个map,先将数据插入数据库,再来修改文章阅读量计数器的值,使用incr;
    • 目的:就是实现数据同步

这种方式利用了队列的特点;

具体实现:

  1. 模仿点击阅读量操作;
public class InitPVTask {@Autowiredprivate RedisTemplate redisTemplate;@PostConstructpublic void initPV(){log.info("启动模拟大量PV请求 定时器..........");new Thread(()->runArticlePV()).start();}/*** 模拟大量PV请求*/public void runArticlePV() {while (true){this.batchAddArticle();try {//5秒执行一次Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}}/*** 对1000篇文章,进行模拟请求PV*/public void   batchAddArticle() {for (int i = 0; i < 1000; i++) {this.addPV(new Integer(i));}}/***那如何切割时间块呢? 如何把当前的时间切入时间块中?* 例如,我们要计算“小时块”,先把当前的时间转换为为毫秒的时间戳,然后除以一个小时,* 即当前时间T/1000*60*60=小时key,然后用这个小时序号作为key。* 例如:* 2020-01-12 15:30:00=1578814200000毫秒 转换小时key=1578814200000/1000*60*60=438560* 2020-01-12 15:59:00=1578815940000毫秒 转换小时key=1578815940000/1000*60*60=438560* 2020-01-12 16:30:00=1578817800000毫秒 转换小时key=1578817800000/1000*60*60=438561* 剩下的以此类推** 每一次PV操作时,先计算当前时间是那个时间块,然后存储Map中。*/public void addPV(Integer id) {//生成环境:时间块为5分钟//long m5=System.currentTimeMillis()/(1000*60*5);//为了方便测试 改为1分钟 时间块long m1=System.currentTimeMillis()/(1000*60*1);Map<Integer,Integer> mMap=Constants.PV_MAP.get(m1);if (CollectionUtils.isEmpty(mMap)){mMap=new ConcurrentHashMap();mMap.put(id,new Integer(1));//<1分钟的时间块,Map<文章Id,访问量>>Constants.PV_MAP.put(m1, mMap);}else {//通过文章id 取出浏览量Integer value=mMap.get(id);if (value==null){mMap.put(id,new Integer(1));}else{mMap.put(id,value+1);}}}
}
  1. 一级缓存定时器
public class OneCacheTask {@Autowiredprivate RedisTemplate redisTemplate;@PostConstructpublic void cacheTask(){log.info("启动定时器:一级缓存消费..........");new Thread(()->runCache()).start();}/*** 一级缓存定时器消费* 定时器,定时(5分钟)从jvm的map把时间块的阅读pv取出来,* 然后push到reids的list数据结构中,list的存储的书为Map<文章id,访问量PV>即每个时间块的pv数据*/public void runCache() {while (true){this.consumePV();try {//间隔1.5分钟 执行一遍Thread.sleep(90000);} catch (InterruptedException e) {e.printStackTrace();}log.info("消费一级缓存,定时刷新..............");}}public void consumePV(){//为了方便测试 改为1分钟 时间块long m1=System.currentTimeMillis()/(1000*60*1);Iterator<Long> iterator= Constants.PV_MAP.keySet().iterator();while (iterator.hasNext()){//取出map的时间块Long key=iterator.next();//小于当前的分钟时间块key ,就消费if (key<m1){//先pushMap<Integer,Integer> map=Constants.PV_MAP.get(key);//push到reids的list数据结构中,list的存储的书为Map<文章id,访问量PV>即每个时间块的pv数据this.redisTemplate.opsForList().leftPush(Constants.CACHE_PV_LIST,map);//后removeConstants.PV_MAP.remove(key);log.info("push进{}",map);}}}
}
  1. 二级缓存定时器消费
public class TwoCacheTask {@Autowiredprivate RedisTemplate redisTemplate;@PostConstructpublic void cacheTask(){log.info("启动定时器:二级缓存消费..........");new Thread(()->runCache()).start();}/*** 二级缓存定时器消费* 定时器,定时(6分钟),从redis的list数据结构pop弹出Map<文章id,访问量PV>,弹出来做了2件事:* 第一件事:先把Map<文章id,访问量PV>,保存到数据库* 第二件事:再把Map<文章id,访问量PV>,同步到redis缓存的计数器incr。*/public void runCache() {while (true){while (this.pop()){}try {//间隔2分钟 执行一遍Thread.sleep(1000*60*2);} catch (InterruptedException e) {e.printStackTrace();}log.info("消费二级缓存,定时刷新..............");}}public boolean pop(){//从redis的list数据结构pop弹出Map<文章id,访问量PV>ListOperations<String, Map<Integer,Integer>> operations= this.redisTemplate.opsForList();Map<Integer,Integer> map= operations.rightPop(Constants.CACHE_PV_LIST);log.info("弹出pop={}",map);if (CollectionUtils.isEmpty(map)){return false;}// 第一步:先存入数据库// TODO: 插入数据库//第二步:同步redis缓存for (Map.Entry<Integer,Integer> entry:map.entrySet()){//            log.info("key={},value={}",entry.getKey(),entry.getValue());String key=Constants.CACHE_ARTICLE+entry.getKey();//调用redis的increment命令long n=this.redisTemplate.opsForValue().increment(key,entry.getValue());
//            log.info("key={},pv={}",key, n);}return true;}
}
  1. 查看浏览量
@GetMapping(value = "/view")
public String view(Integer id) {String key= Constants.CACHE_ARTICLE+id;//调用redis的get命令String n=this.stringRedisTemplate.opsForValue().get(key);log.info("key={},阅读量为{}",key, n);return n;
}
4.3.3.4 推送帖子

前置条件:

发微博、帖子、文章push消息:

  • 用户发微博,帖子时,先将数据插入DB,和Redis,再推送到个人主页List,和粉丝List;
  • 当用户访问个人主页的时候,显示的是自己发过的微博,帖子或者文章等;使用List存储自己发过的微博,一页10页微博,并且可以进行分页查询;
  • 当用户查看关注列表的时候,显示的是自己关注的人发的微博,文章等;
  • 这就意味着:当用户发微博,首先推送到自己的个人主页List,再推送到粉丝的关注列表List;


当大明星发微博时,就会有大量粉丝来查询明星的个人主页;只能查Redis,不能查DB;不然直接夸了。

  1. 基于push技术,实现微博个人列表
/*** push到个人主页*/
public void pushHomeList(Integer userId,Integer postId){String key= Constants.CACHE_MY_POST_BOX_LIST_KEY+userId;this.redisTemplate.opsForList().leftPush(key,postId);
}/*** 获取个人主页列表*/
public PageResult<Content> homeList(Integer userId,int page, int size){PageResult<Content> pageResult=new PageResult();List<Integer> list=null;long start = (page - 1) * size;long end = start + size - 1;try {String key= Constants.CACHE_MY_POST_BOX_LIST_KEY+userId;//1.查询用户的总数int total=this.redisTemplate.opsForList().size(key).intValue();pageResult.setTotal(total);//2.采用redis list数据结构的lrange命令实现分页查询。list = this.redisTemplate.opsForList().range(key, start, end);//3.去拿明细List<Content> contents=this.getContents(list);pageResult.setRows(contents);}catch (Exception e){log.error("异常",e);}return pageResult;
}protected List<Content> getContents(List<Integer> list){List<Content> contents=new ArrayList<>();//发布内容的keyList<String> hashKeys=new ArrayList<>();hashKeys.add("id");hashKeys.add("content");hashKeys.add("userId");HashOperations<String, String ,Object> opsForHash=redisTemplate.opsForHash();for (Integer id:list){String hkey= Constants.CACHE_CONTENT_KEY+id;List<Object> clist=opsForHash.multiGet(hkey,hashKeys);//redis没有去db找if (clist.get(0)==null && clist.get(1)==null){Content obj=this.contentMapper.selectByPrimaryKey(id);contents.add(obj);}else{Content content=new Content();content.setId(clist.get(0)==null?0:Integer.valueOf(clist.get(0).toString()));content.setContent(clist.get(1)==null?"":clist.get(1).toString());content.setUserId(clist.get(2)==null?0:Integer.valueOf(clist.get(2).toString()));contents.add(content);}}return contents;
}
  1. 基于push技术,实现微博关注列表:发一条微博,批量推送给所有粉丝
/*** 发一条微博,批量推送给所有粉丝*/
private void pushFollower(int userId,int postId){SetOperations<String, Integer> opsForSet = redisTemplate.opsForSet();//读取粉丝集合String followerkey=Constants.CACHE_KEY_FOLLOWER+userId;//千万不能取set集合的所有数据,如果数据量大的话,会卡死// Set<Integer> sets= opsForSet.members(followerkey);Cursor<Integer> cursor = opsForSet.scan(followerkey, ScanOptions.NONE);try{while (cursor.hasNext()){//拿出粉丝的useridInteger object = cursor.next();String key= Constants.CACHE_MY_ATTENTION_BOX_LIST_KEY+object;this.redisTemplate.opsForList().leftPush(key,postId);}}catch (Exception ex){log.error("",ex);}finally {try {cursor.close();} catch (IOException e) {log.error("",e);}}
}
  1. 查看关注列表
 /*** 获取关注列表*/
public PageResult<Content> attentionList(Integer userId,int page, int size){PageResult<Content> pageResult=new PageResult();List<Integer> list=null;long start = (page - 1) * size;long end = start + size - 1;try {String key= Constants.CACHE_MY_ATTENTION_BOX_LIST_KEY+userId;//1.设置总数int total=this.redisTemplate.opsForList().size(key).intValue();pageResult.setTotal(total);//2.采用redis,list数据结构的lrange命令实现分页查询。list = this.redisTemplate.opsForList().range(key, start, end);//3.去拿明细数据List<Content> contents=this.getContents(list);pageResult.setRows(contents);}catch (Exception e){log.error("异常",e);}return pageResult;
}

优化:

优化方案采用:限定个人和关注list的长度为1000,即,
发微博的时候,往个人和关注list push完成后,把list的长度剪切为1000,
具体的技术方案采用list 的ltrim命令来实现。
LTRIM key start end
截取队列指定区间的元素,其余元素都删除

微博个人和关注列表的性能优化

//性能优化,截取前1000条
if(this.redisTemplate.opsForList().size(key)>1000){this.redisTemplate.opsForList().trim(key,0,1000);
}

4.4 set

4.4.1 简介:

  • Set对外提供的功能与list类似,是一个列表的功能。特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这是list所不能提供的。

  • Set是String类型的无序集合底层其实是一个value为null的hash表,所以添加删除查询的复杂度都是O(1)

  • 类似java的set,可以用来求交集并集等

4.4.2 命令:

  1. sadd key value1 value2.。。:将一个或多member元素加入到集合key中,如果有元素已经存在在key中,则会被忽略
  2. smembers key:取出 该集合的所有值
  3. simember key value:判断集合key中是否包含value,有返回1,没有返回0
  4. scard key:返回元素个数
  5. srem key value1 value2:删除集合中的一个或多个value
  6. spop key:从集合中随机弹出一个值(弹出后不再在 列表中)
  7. srandmember key n:从集合中随机取出n个值,不会从集合中删除
  8. sinter key1 key2:返回2个集合的交集
  9. sunion key1 key2 :返回2个集合的并集
  10. sdiff key1 key2:返回2个集合的差集(返回key1中有,key2中没有的)

4.4.3 应用:

4.4.3.1 淘宝黑名单
  1. 黑名单过滤器业务场景分析
    淘宝的商品评价功能,不是任何人就能评价的,有一种职业就是差评师,差评师就是勒索敲诈商家。这种差评师在淘宝里面就被设置了黑名单,即使购买了商品,也评价不了。
  2. 解决的技术方案
    黑名单过滤器除了针对上文说的淘宝评价,针对用户黑名单外,其实还有ip黑名单、设备黑名单等。在高并发的情况下,通过数据库过滤明显不符合要求,一般的做法都是通过Redis的set来实现的。

    1. 步骤1:先把数据库的黑名单数据同步到redis的set集合中。
    2. 步骤2:评价的时候验证是否为黑名单,通过sismember命令来实现。
4.4.3.2 京东京豆抽奖
  1. 京东京豆抽奖的业务场景分析
    可以无线抽,奖品是无限的,不同奖品的概率是不一样的;

  2. 京东京豆抽奖的技术方案(我项目中用的hash存放数据、随机数进行摇号,因为中签率不一样)
    京豆抽奖一般是采用redis的set集合来操作的,那为什么是set集合适用于抽奖呢?

    1. set集合的特点是元素不重复 存放1个 5个 10个京豆 谢谢参与
    2. set集合支持随机读取
    3. 具体的技术方案是采用set集合的srandmember命令来实现,随机返回set的一个元素
4.4.3.4 支付宝抽奖

思考一个问题:支付宝的抽奖 和 京东京豆的抽奖有什么区别????

  1. 京豆抽奖:奖品是可以重复,例如抽5京豆可以再抽到5京豆,即京豆是无限量抽。
  2. 支付宝抽奖: 奖品不能重复抽,例如1万人抽1台华为手机;再给大家举一个熟悉的例子:
    例如公司年会,抽中奖品的人,下一轮就不能重复抽取,不然就会重复中奖。
  • 技术方案和京东的京豆类似,但是不同的是
    京东的京豆用了srandmember命令,即随机返回set的一个元素
  • 支付宝的抽奖要用spop命令,即随机返回并删除set中一个元素
    为什么呢?
  • 因为支付宝的奖品有限,不能重复抽,故抽奖完后,必须从集合中剔除中奖的人。
    再举个每个人都参与过的例子,年会抽奖,你公司1000人,年会抽奖3等奖500名100元,2等奖50名1000元,1等奖10名10000元,
    在抽奖的设计中就必须把已中奖的人剔除,不然就会出现重复中奖的概率。

这里如果考虑上中签率,可以初始化的时候,往列表中放入n个奖品(包括无奖),n可以自定义,主要考虑:

  1. 总抽奖人数
  2. 奖品中奖率
  3. 奖品数量
4.4.3.5 微博榜单与qq群的随机展示

  1. 随机展示业务场景分析
    为什么要随机展示?因为展示的区域有限啊,在那么小的地方展示全部数据是不可能的,通常的做法就是随机展示一批数据,然后用户点击“换一换”按钮,再随机展示另一批
  2. 随机展示的redis技术方案
    随机展示的原因就是区域有限,而区域有限的地方通常就是首页或频道页,这些位置通常都是访问量并发量非常高的,一般是不可能采用数据库来实现的,通常都是Redis来实现。
    redis的实现技术方案:

    • 步骤1:先把数据准备好,把所有需要展示的内容存入redis的Set数据结构中。
    • 步骤2:通过srandmember命令随机拿一批数据出来。
4.4.3.6 帖子点赞
  1. 微博点赞业务场景分析:
    梳理点赞的业务场景,它有2个接口:

    1. 点赞或取消点赞,用户点击功能
    2. 查看帖子信息:通过用户id 和帖子id,查看该帖子的点赞数、该用户是否点赞状态。
  2. 微博点赞的技术方案:
    点赞的关键技术就是要判断该用户是否点赞,已重复点赞的不允许再点赞,即过滤重复,虽然业务不复杂,可以采用数据库直接实现。但是对于微博这种高并发的场景,不可能查数据库的,一般是缓存,即redis

    1. 点赞或取消点赞,用户点击功能采用的是redis的set数据结构,key=like:postid value={userid}
    2. 采用scard命令,查看点赞总数,采用sismember命令,判断是否点赞
  3. 存在的问题:

    1. 这种方法只能记录帖子、评论的点赞数量和人数,不能记住流水
    2. 列表太多
# 采用sadd命令,添加点赞
127.0.0.1:6379> sadd like:1000 101
(integer) 1
127.0.0.1:6379> sadd like:1000 102
(integer) 1
127.0.0.1:6379> sadd like:1000 103
(integer) 1
127.0.0.1:6379> smembers like:1000
1) "101"
2) "102"
3) "103" # 采用srem命令,取消点赞
127.0.0.1:6379> srem like:1000 101
(integer) 1
127.0.0.1:6379> smembers like:1000
1) "102"
2) "103"# 获取点赞总数
127.0.0.1:6379> smembers like:1000
1) "102"
2) "103"
127.0.0.1:6379> scard like:1000
(integer) 2# 判断是否点赞
127.0.0.1:6379> smembers like:1000
1) "102"
2) "103"
127.0.0.1:6379> sismember like:1000 102
(integer) 1
127.0.0.1:6379> sismember like:1000 101
(integer) 0
4.4.3.7 关注与粉丝
  1. 微博关注与粉丝的业务场景分析
    我关注了雷军:我就是雷军的粉丝follower;
    雷军被阿甘关注:雷军就是阿甘的关注followee;
  2. 微博关注与粉丝的redis技术方案
    技术方案:每个用户都有2个集合:关注集合和粉丝集合
    例如 我关注了雷军,做了2个动作

    1. 把我的userid加入雷军的粉丝follower集合set
    2. 把雷军的userid加入我的关注followee集合set
  3. 集合key设计
    我的关注集合 key=follower:我的userid
    雷军的粉丝集合 key=follower:雷军userid
4.4.3.8 微关系计算

  1. 计算好友关系业务场景分析
    微博微关系:
    共同关注:是计算出我和雷军共同关注的人有哪些?
    我关注的人也关注他:是计算出我关注的人群中,有哪些人同时和我一样关注了雷军
  2. 计算好友关系的redis技术方案
    思考题:如果是采用数据库来实现用户的关系,一般SQL怎么写? 例如 阿甘关注10个人,雷军关注100个人,让你计算2人的共同关注那些人?

    • SQL的写法,一般是采用in 或 not in 来实现。但是对于互联网高并发的系统来说,in not in 明显不适合。
    • 一般的做法是采用redis的set集合来实现。
      Redis Set数据结构,非常适合存储好友、关注、粉丝、感兴趣的人的集合。然后采用set的命令就能得出我们想要的数据。

      1. sinter命令:获得A和B两个用户的共同好友
      2. sismember命令:判断C是否为B的好友
      3. scard命令:获取好友数量

4.5 hash:

4.5.1 简介

  • hash是一个键值对集合
  • 是一个string类型的field和value的映射表,hash特别适合用于存储对象
  • 类似java中的map<String,object>
  • 用户ID为key,存储的内容包括:姓名:小红,年龄:15.。。。
  • Redis的hash结构更适合存储写频率高的Java对象。
  • Redis只能对key进行设置过期时间,不能对key的field设置过期时间,这是值得注意的点;

4.5.2 命令:

  1. hset key field value:给集合key中的field键赋值value
    相当于key指向Map<field, value>
  2. hget key field:从集合key中获取field键的值
  3. hmset key1 field1 value1 field2 value2...:批量设置hset值
  4. hexists key field:查看key集合中field键是否存在
  5. hkeys key:累出该hash集合的所有field
  6. hvals key:列出该hash集合的所有value
  7. hincrby key field increment:为集合key的field键的值加上增量increment
  8. hsetnx key field value:将集合key的field键的值设置为value,当且仅当field不存在,添加成功返回1,失败返回0

4.5.3 应用:

https://blog.csdn.net/yaoyaochengxian/article/details/120234850

4.5.3.1 本质上是存储java对象

redis存储java对象常用String,那为什么还要用hash来存储?

Redis存储java对象,一般是String 或 Hash 两种,那到底什么时候用String ? 什么时候用hash ?

  • String的存储通常用在频繁读操作,它的存储格式是json,即把java对象转换为json,然后存入redis
  • Hash的存储场景应用在频繁写操作,即:当对象的某个属性频繁修改时,不适用string+json的数据结构,因为不灵活,每次修改都需要把整个对象转换为json存储。
    如果采用hash,就可以针对某个属性单独修改,不用序列号去修改整个对象。例如,商品的库存、价格、关注数、评价数经常变动时,就使用存储hash结果。
4.5.3.2 短连接

你们应该收到淘宝的短信:

【天猫】有优惠啦!黄皮金煌芒果(水仙芒)带箱10斤49.8元!
核薄无丝很甜喔!购买: c.tb.cn/c.ZzhFZ0 急鲜丰 退订回TD

这个 c.tb.cn/c.ZzhFZ0 就是短链接;
打开IE,输入 c.tb.cn/c.ZzhFZ0 就转变为如下一大坨

https://h5.m.taobao.com/ecrm/jump-to-app.html?scm=20140608.2928562577.LT_ITEM.1699166744&target_url=
http%3A%2F%2Fh5.m.taobao.com%2Fawp%2Fcore%2Fdetail.htm%3Fid%3D567221004504%26scm=20140607.2928562577.
LT_ITEM.1699166744&spm=a313p.5.1cfl9ch.947174560063&short_name=c.ZzhFZ0&app=chrome

短链接就是把普通网址,转换成比较短的网址。

短链接有什么好处?

  • 节省网址长度,便于社交化传播。
  • 方便后台跟踪点击量、统计。

《短链接转换器》的原理

  • 长链接转换为短链接
    实现原理:长链接转换为短链接加密串key,然后存储于redis的hash结构中。
  • 重定向到原始的url
    实现原理:通过加密串key到redis找出原始url,然后重定向出去
4.5.3.3 购物车

登录淘宝后,逛淘宝时,点击商品加入购物车时,购物车中就会有一件对应的商品;

  1. 往购物车加入2件商品。采用hash数据结果,key=cart:user:用户id
127.0.0.1:6379> hset cart:user:1000 101 1
(integer) 1
127.0.0.1:6379> hset cart:user:1000 102 1
(integer) 1
127.0.0.1:6379> hgetall cart:user:1000
1) "101"
2) "1"
3) "102"
4) "1"
  1. 修改购物车的数据,为某件商品添加数量
127.0.0.1:6379> hincrby cart:user:1000 101 1
(integer) 2
127.0.0.1:6379> hincrby cart:user:1000 102 10
(integer) 11
127.0.0.1:6379> hgetall cart:user:1000
1) "101"
2) "2"
3) "102"
4) "11"
  1. 统计购物车有多少件商品
127.0.0.1:6379> hlen cart:user:1000
(integer) 2
  1. 删除购物车某件商品
127.0.0.1:6379> hdel cart:user:1000 102
(integer) 1
127.0.0.1:6379> hgetall cart:user:1000
1) "101"
2) "2"

京东购物车
京东在未登录的情况下,用户点击商品加入购物车后,购物车中自动就有了商品的信息,当用户退出重新进入网站后,再次点开购物车,商品还是存在的;
也就是京东网站使用Cookie机制为未登录的用户提供一个购物车ID;
当用户登录后,会将未登录时的购物车与登录后的购物车进行合并;

4.5.3.4 帖子点赞
  • 使用set存放帖子点赞存在的问题:

    1. 这种方法只能记录帖子、评论的点赞数量和人数,不能记住流水
    2. 列表太多
  • redis设计:

    • 所谓点赞记录缓存即“是否做了点赞这件事”,最终将持久化到数据库的点赞关系表上,用于表示某个用户是否已经点赞了某个作品。这里储存的是一种行为,或者称之为关系。
    • 而点赞数量缓存即缓存某一个作品现在有多少点赞数。它缓存的是一个数字,并不能表示哪个用户点赞了哪个表。这储存的是一种数据。
    • redis的hash可以指定一个Key,因此我们使用likeRecord和likeCount两个key来区分上述两种缓存。
      • 用户点赞一条数据,设置状态为0,并且更新被点赞内容的likeCount+1
      • 用户取消点赞一条数据,设置状态为1,并且更新被点赞内容的likeCount-1
  • 键值对设计

    • 点赞信息:

      • key:likeRecord
      • field: (String) 浏览信息id和点赞用户id拼接而成, 分隔符为::
      • value: (HashMap) 存储点赞状态(0: 点赞 1:取消点赞)和更新时间的时间戳
      • 即键值对为: "浏览信息id::点赞用户id" value: {status: Integer, updateTime: long}
    • 点赞数量
      • field: (String) 浏览信息id
      • value: (Integer) 点赞数量
  • 操作步骤:

    • 点赞:

    • 取消点赞:

  • 持久化:使用定时任务,定时加redis中的数据写入到数据库汇总

4.6 zset(sorted set)

4.6.1 简介:

Redis有序集合和普通集合set很相似,是一个没有重复元素的字符串集合。

不同之处时有序集合的每个成员都关联了一个评分(score),这个评分被用来按照最低分到最高分的方式排序集合种的成员。集合的成员是唯一的,但是分数可以重复

因为元素是有序的,所有可以很快的根据评分或者次序来获取一个范围的元素。

访问有序集合的中间元素也是非常快的,因为你能够使用有序集合作为一个没有重复成员的智能列表。

最经典的应用就是排行榜。

4.6.2 命令:

  1. zadd key score1 value1 score2 value2...:将一个或多个member元素及其score添加到有序集合key中
  2. zrange key start end (withscores):返回有续集key中,下标在start,stop之间的元素
    带withscores,可以让分数一起和值返回到结果集
  3. zrangebyscore/zrevrangebyscore key min max (withscores) (limit offset count):返回有续集key中,所有score值介于【min,max】的成员。有序集按score值递增/递减排序
  4. zincrby key increment value:为元素的score加上增量
  5. zrem key value:删除集合中指定元素
  6. zcount key min max:统计【min,max】之间元素个数
  7. zrank key value:返回该值在集合中的排名,从0开始

4.6.3 应用:

4.6.3.1 排行榜

技术模拟思路:
采用26个英文字母来实现排行,随机为每个字母生成一个随机数作为score

实现:

  1. 先初始化1个月的历史数据
@Service
@Slf4j
public class InitService {@Autowiredprivate RedisTemplate redisTemplate;/*** 先初始化1个月的历史数据*/public void init30day(){//计算当前的小时keylong hour=System.currentTimeMillis()/(1000*60*60);//初始化近30天,每天24个keyfor(int i=1;i<24*30;i++){//倒推过去30天String  key=Constants.HOUR_KEY+(hour-i);this.initMember(key);System.out.println(key);}}/***初始化某个小时的key*/public void initMember(String key) {Random rand = new Random();//采用26个英文字母来实现排行,随机为每个字母生成一个随机数作为scorefor(int i = 1;i<=26;i++){this.redisTemplate.opsForZSet().add(key,String.valueOf((char)(96+i)),rand.nextInt(10));}}}
  1. 定时5秒钟,模拟微博的热度刷新(例如模拟点赞 收藏 评论的热度值更新)

@Service
@Slf4j
public class TaskService {@Autowiredprivate RedisTemplate redisTemplate;/***2. 定时5秒钟,模拟微博的热度刷新(例如模拟点赞 收藏 评论的热度值更新)* 3. 定时1小时合并统计 天、周、月的排行榜。*/@PostConstructpublic void init(){log.info("启动初始化 ..........");
//        2. 定时5秒钟,模拟微博的热度刷新(例如模拟点赞 收藏 评论的热度值更新)new Thread(()->this.refreshDataHour()).start();
//        3. 定时1小时合并统计 天、周、月的排行榜。new Thread(()->this.refreshData()).start();}/***采用26个英文字母来实现排行,随机为每个字母生成一个随机数作为score*/public void refreshHour(){//计算当前的小时keylong hour=System.currentTimeMillis()/(1000*60*60);//为26个英文字母来实现排行,随机为每个字母生成一个随机数作为scoreRandom rand = new Random();for(int i = 1;i<=26;i++){//redis的ZINCRBY 新增这个积分值this.redisTemplate.opsForZSet().incrementScore(Constants.HOUR_KEY+hour,String.valueOf((char)(96+i)),rand.nextInt(10));}}/***刷新当天的统计数据*/public void refreshDay(){long hour=System.currentTimeMillis()/(1000*60*60);List<String> otherKeys=new ArrayList<>();//算出近24小时内的keyfor(int i=1;i<23;i++){String  key=Constants.HOUR_KEY+(hour-i);otherKeys.add(key);}//把当前的时间key,并且把后推23个小时,共计近24小时,求出并集存入Constants.DAY_KEY中//redis ZUNIONSTORE 求并集this.redisTemplate.opsForZSet().unionAndStore(Constants.HOUR_KEY+hour,otherKeys,Constants.DAY_KEY);//设置当天的key 40天过期,不然历史数据浪费内存for(int i=0;i<24;i++){String  key=Constants.HOUR_KEY+(hour-i);this.redisTemplate.expire(key,40, TimeUnit.DAYS);}log.info("天刷新完成..........");}/***刷新7天的统计数据*/public void refreshWeek(){long hour=System.currentTimeMillis()/(1000*60*60);List<String> otherKeys=new ArrayList<>();//算出近7天内的keyfor(int i=1;i<24*7-1;i++){String  key=Constants.HOUR_KEY+(hour-i);otherKeys.add(key);}//把当前的时间key,并且把后推24*7-1个小时,共计近24*7小时,求出并集存入Constants.WEEK_KEY中this.redisTemplate.opsForZSet().unionAndStore(Constants.HOUR_KEY+hour,otherKeys,Constants.WEEK_KEY);log.info("周刷新完成..........");}/***刷新30天的统计数据*/public void refreshMonth(){long hour=System.currentTimeMillis()/(1000*60*60);List<String> otherKeys=new ArrayList<>();//算出近30天内的keyfor(int i=1;i<24*30-1;i++){String  key=Constants.HOUR_KEY+(hour-i);otherKeys.add(key);}//把当前的时间key,并且把后推24*30个小时,共计近24*30小时,求出并集存入Constants.MONTH_KEY中this.redisTemplate.opsForZSet().unionAndStore(Constants.HOUR_KEY+hour,otherKeys,Constants.MONTH_KEY);log.info("月刷新完成..........");}/***定时1小时合并统计 天、周、月的排行榜。*/public void refreshData(){while (true){//刷新当天的统计数据this.refreshDay();
//            刷新7天的统计数据this.refreshWeek();
//            刷新30天的统计数据this.refreshMonth();//TODO 在分布式系统中,建议用xxljob来实现定时try {Thread.sleep(1000*60*60);} catch (InterruptedException e) {e.printStackTrace();}}}/***定时5秒钟,模拟微博的热度刷新(例如模拟点赞 收藏 评论的热度值更新)*/public void refreshDataHour(){while (true){this.refreshHour();//TODO 在分布式系统中,建议用xxljob来实现定时try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
  1. 定时1小时合并统计 天、周、月的排行榜。
@RestController
@Slf4j
public class Controller {@Autowiredprivate RedisTemplate redisTemplate;@GetMapping(value = "/getHour")public Set getHour() {long hour=System.currentTimeMillis()/(1000*60*60);//ZREVRANGE 返回有序集key中,指定区间内的成员,降序。Set<ZSetOperations.TypedTuple<Integer>> rang= this.redisTemplate.opsForZSet().reverseRangeWithScores(Constants.HOUR_KEY+hour,0,30);return rang;}@GetMapping(value = "/getDay")public Set getDay() {Set<ZSetOperations.TypedTuple<Integer>> rang= this.redisTemplate.opsForZSet().reverseRangeWithScores(Constants.DAY_KEY,0,30);return rang;}@GetMapping(value = "/getWeek")public Set getWeek() {Set<ZSetOperations.TypedTuple<Integer>> rang= this.redisTemplate.opsForZSet().reverseRangeWithScores(Constants.WEEK_KEY,0,30);return rang;}@GetMapping(value = "/getMonth")public Set getMonth() {Set<ZSetOperations.TypedTuple<Integer>> rang= this.redisTemplate.opsForZSet().reverseRangeWithScores(Constants.MONTH_KEY,0,30);return rang;}
}

一般情况下,我们浏览各大网站时,点击某篇文章,博客,帖子,其阅读量就会+1,或者点击 点赞按钮,又或是评论数量;
这些都会根据热度算法,计算其热度;

4.6.3.2 微博推送
  • 场景:每个用户都有一个关注微博列表List,和个人微博列表List;
    明星发表一条微博、如果使用 Redis List数据结构, 就需要先获取明星的粉丝集合,再将微博的ID发送到粉丝的List,如果粉丝的用户量不大,就几十万,还是勉强可以支撑的。适合中小型并发。但是像大明星,粉丝数量几千万,将明星微博的ID推送Push到粉丝的关注List,这个过程太耗时间,会直接把服务器给卡死了。
    而且明星实时在线粉丝数量可能只有百分之一,也就是几十万。push操作短时间内看来相当于做了无用功;
  • 替代方案:使用zset pull 推送,每个用户都有一个关注微博列表Zset , 和个人列表Zset;
    当用户登录后,主动去查询关注用户的微博;并将他们的微博放到自己的关注微博列表Zset里面;

PULL 与PUSH的差别

  • push : 每次用户发微博都要异步推送给每个粉丝的关注列表;
  • pull :每个粉丝查看关注微博列表,都需要主动去关注人的个人微博列表下拉取,再存储到自己的关注微博列表里;


选择pull 方式, 需要自己去关注人的个人微博列表下拉取最新微博,这种方式可以通过客户端定时轮询服务端,查询最新的微博;

pull 技术方案

  1. 用户发微博、先写入DB、再写入Redis, 使用Hash数据结构存储微博 、 key = post::id
  2. 异步推送到个人微博列表Zset;

为什么个人列表和关注列表采用zset集合?

  • 拉取微博是根据刷新时间t进行过滤的,使用List集合的话,只能用将微博的发表时间和id一起转化为JSON字符串(或者将将ID写入list),再写入List, 获取数据时,需要根据时间进行过滤,就意味着,我们并不知道微博的发表时间,只能根据List的微博ID拉取全部微博JSON字符串,再获取发表时间,再进行过滤,太麻烦了,效率也很低;
  • 使用Zset集合,将发表时间作为score, id作为value,查询时,根据score进行排序,进行过滤,进行分页查询,比List方便一点;

基于pull技术,实现微博个人列表

  1. 将数据存放到数据库
  2. 将数据存放到个人微博列表
@Slf4j
@Service
public class PullContentService extends ContentService{/*** 用户发微博*/public void post(Content obj){Content temp=this.addContent(obj);this.addMyPostBox(temp);}/*** 发布微博的时候,加入到我的个人列表*/public void addMyPostBox(Content obj){String key= Constants.CACHE_MY_POST_BOX_ZSET_KEY+obj.getUserId();//按秒为单位long score=obj.getCreateTime().getTime()/1000;this.redisTemplate.opsForZSet().add(key,obj.getId(),score);}/*** 获取个人列表*/public PageResult<Content> homeList(Integer userId, int page, int size){PageResult<Content> pageResult=new PageResult();List<Integer> list=new ArrayList<>();long start = (page - 1) * size;long end = start + size - 1;try {String key= Constants.CACHE_MY_POST_BOX_ZSET_KEY+userId;//1.设置总数long total=this.redisTemplate.opsForZSet().zCard(key);pageResult.setTotal(total);//2.分页查询//redis ZREVRANGESet<ZSetOperations.TypedTuple<Integer>> rang= this.redisTemplate.opsForZSet().reverseRangeWithScores(key,start,end);for (ZSetOperations.TypedTuple<Integer> obj:rang){list.add(obj.getValue());log.info("个人post集合value={},score={}",obj.getValue(),obj.getScore());}//3.去拿明细数据List<Content> contents=this.getContents(list);pageResult.setRows(contents);}catch (Exception e){log.error("异常",e);}return pageResult;}}

基于pull技术,实现微博关注列表

  1. 拿到个人关注的用户列表
  2. 获取到个人关注的用户的近两天发布的微博列表,并将这些数据存放到个人关注微博列表,时间作为score(只取前1000条数据)
/*** 刷新拉取用户关注列表* 用户第一次刷新或定时刷新 触发*/
private void refreshAttentionBox(int userId){//获取刷新的时间String refreshkey=Constants.CACHE_REFRESH_TIME_KEY+userId;Long ago=(Long) this.redisTemplate.opsForValue().get(refreshkey);//如果时间为空,取2天前的时间if (ago==null){//当前时间long now=System.currentTimeMillis()/1000;//当前时间减去2天ago=now-60*60*24*2;}//提取该用户的关注列表String followerkey=Constants.CACHE_KEY_FOLLOWEE+userId;Set<Integer> sets= redisTemplate.opsForSet().members(followerkey);log.debug("用户={}的关注列表={}",followerkey,sets);//当前时间long now=System.currentTimeMillis()/1000;String attentionkey= Constants.CACHE_MY_ATTENTION_BOX_ZSET_KEY+userId;for (Integer id:sets){//去关注人的个人主页,拿最新微博String key= Constants.CACHE_MY_POST_BOX_ZSET_KEY+id;Set<ZSetOperations.TypedTuple<Integer>> rang= this.redisTemplate.opsForZSet().rangeByScoreWithScores(key,ago,now);if(!CollectionUtils.isEmpty(rang)){//加入我的关注post集合 就是通过上次刷新时间计算出最新的微博,写入关注zset集合;再更新刷新时间this.redisTemplate.opsForZSet().add(attentionkey,rang);}}//关注post集合 只留1000个//计算post集合,总数long count=this.redisTemplate.opsForZSet().zCard(attentionkey);//如果大于1000,就剔除多余的postif(count>1000){long end=count-1000;//redis ZREMRANGEBYRANKthis.redisTemplate.opsForZSet().removeRange(attentionkey,0,end);}long days=this.redisTemplate.getExpire(attentionkey,TimeUnit.DAYS);if(days<10){//设置30天过期this.redisTemplate.expire(attentionkey,30,TimeUnit.DAYS);}this.redisTemplate.opsForValue().set(refreshkey,now);
}

Redis(三) -- redis简介、各数据类型应用相关推荐

  1. 你不知道的redis三-Redis的持久化机制

    目录 一.持久化 二.RDB持久化方式 2.1 手动触发 2.2 RDB持久化命令 2.3 恢复和异常流程演示 2.4 RDB持久化的优缺点 三.AOF持久化 3.1 AOF持久化原理 3.2 AOF ...

  2. Redis三种特殊数据类型

    Redis三种特殊数据类型 geospatial 地理位置 朋友的定位, 附近的人, 打车的距离计算 Redis的Geo在Redis3.2版本就推出了.这个功能可以推算地理位置信息, 两地之间的距离, ...

  3. Redis学习 - NoSQL简介、redis安装、redis基础知识、数据类型、持久化、订阅发布、主从复制、哨兵模式、缓存击穿和雪崩

    学习视频地址:https://www.bilibili.com/video/BV1S54y1R7SB 完结撒花,感谢狂神 文章目录 1. NoSQL 1.1 单机Mysql的演进 1.2 当今企业架构 ...

  4. 跟着狂神学Redis(NoSql+环境配置+五大数据类型+三种特殊类型+Hyperloglog+Bitmap+事务+Jedis+SpringBoot整合+Redis持久化+...)

    跟着狂神学Redis 狂神聊Redis 学习方式:不是为了面试和工作学习!仅仅是为了兴趣!兴趣才是最好的老师! 基本的理论先学习,然后将知识融汇贯通! 狂神的Redis课程安排: nosql 讲解 阿 ...

  5. Redis三种特殊数据类型——Geospatial地理空间

    Redis三种特殊数据类型:bitmaps位图.hyperloglog基数统计 和 geospatial 地理空间 一.介绍 Redis GEO 用于存储地理位置信息,并对存储的信息进行操作,该功能在 ...

  6. Redis -- 01 【简介,特点,搭建,shell,数据类型】

    1.Redis的简介 Redis中文官网链接: 1.1.NoSql NoSql 是Not-Only Sql的简写,泛指非关系型数据库 关系型数据库不太适合存储非结构化的大数据(现在的非结构化的数据占比 ...

  7. 【一篇入魂】redis快速入门—部署、数据类型、持久化、事务、集群

    Redis简介 简单介绍 Redis:开源.免费.非关系型数据库.K-V数据库.内存数据库,支持持久化.事务和备份,集群(支持16个库)等高可用功能.并且性能极高(可以达到100000+的QPS),易 ...

  8. 缓存服务——Redis集群简介

    文章目录 缓存服务--Redis集群 一.Redis简介 1.Redis软件获取和帮助 2.Redis特性 3.企业缓存数据库解决方案对比 4.Redis应用场景 二.Redis基本部署 1.编译安装 ...

  9. Redis安装和简介

    CENTOS7下安装REDIS 官网:https://redis.io/download 第一步:解压压缩包 [root@hd01 opt]# tar -zxf redis-5.0.14.tar.gz ...

最新文章

  1. tomcat 深度优化
  2. OpenCV访问Mat图像中每个像素的值
  3. Android MIPI转LVDS显示屏调试之--- SD65DSI84概述(2)
  4. Git的学习与使用(五)——Git 创建仓库
  5. css中属性兼容性写法,CSS3兼容属性和标准属性的书写顺序
  6. python数据展示平台_Python获取各大地图平台经纬度数据,哪家的数据最准确?
  7. Windows下更改pip镜像源
  8. sealos kubernetes(k8s)高可用安装教程
  9. 模板:Prime最小生成树堆优化 + Dijkstra单源最短路堆优化
  10. ajax返回list前台遍历_List、set集合接口分析
  11. STM32学习笔记之一(初窥STM32)
  12. 【电源】开关电源、线性稳压电源
  13. FISCO BCOS 区块链 设置交易最晚处理区块高度
  14. RemObjects Elements多用途软件开发
  15. ppt导出pdf后非矢量图图片失真的解决办法
  16. 《认知心理学》思维导图
  17. linux去掉锁屏密码,安卓手机锁屏密码清除方法 android7.0锁屏密码怎么去除
  18. 163电子邮箱怎么注册申请?手机号注册电子邮箱的小技巧
  19. JavaScript前端数据呈现——Table会拐弯的column,数据分多列展示
  20. AutoCAD 系统变量

热门文章

  1. java怎么返回数组_java方法怎么返回数组
  2. 读书也享受时间的复利
  3. [高可用系列|keepalived]Keepalived详解
  4. 人性趋利——天下熙熙,皆为利来
  5. CSS实现渐变圆角边框
  6. 统计学习方法 | K 近邻法
  7. 运维决定价值 四川农信社如何支撑4万员工的IT管理?
  8. 低代码开发平台有哪些 【最新】低代码开发平台排名
  9. 什么是hard code
  10. 文件系统管理源码(MxsDoc)