Redis经典案例场景

微博底下显示最新的评论

像微博这些一个热门帖子就有其他的消息量,所以使用redis来进行数据的浏览和操作是很好的选择

  • 存储模型设计
    redis中存储数据的模型主要是list和hash,list用于有序地将评论id存储,hash则是根据id来存储评论内容。
  • 数据id的设计
    所有的评论都存储于MySQL的一张表中
    对于存储评论id的list来说,因为需要指定是哪一条微博,所以key的名称应该要有该条微博的id -> weibo:66:comments,而这个key对应的值是每条评论的id -> $commentId
    而对于存储评论内容的hash结构,因为是存储着每条评论的具体内容,所以每个键值对就是 id -> 内容的形式,key需要指定是这条微博 -> weibo:66 :comments:content
    value的值则是按照评论id和评论内容来存储 $commentId – $commentContent

当用户评论之后

  1. 存入Mysql数据库,获取到的评论id
  2. 评论id通过push进redis的list中,并且将评论内容转成json的格式存放进redis的hash中
  3. 删除该条微博5000条之后的评论(完整的评论数据存在于MySQL数据库中)
    伪代码:
public void addComment(Comment comment){
    Long commentId = commentMappper.insert(comment); 
    String listKey = "weibo:"+comment.getWeiboId+"comments";
    String hashKey = "weibo:"+comment.getWeiboId+":comment:content";
    jedis.lpush(listKey,commentId.toString());
    jedis.hset(hashKey,commentId.toString,comment.getConent());
    //只要最新的5000条数据存在redis中
    List ids = jedis.lrange(listKey,5000,-1);
    for(String id : ids){
        jedis.hdel(hashKey,id);
    }
    jedis.ltrim(listKey,0,5000);
}

当有用户查看时

  1. 按照分页数据从list中取出对应的评论id
  2. 如果没有查到数据(用户需要5000条之后的数据),或者需要查询的数据较小,则从mysql中查出,否则从redis中查询
    伪代码:
public List queryComments(Long weiboId,QueryObject qo){
    String listKey = "weibo:"+weiboId+"comments";
    String hashKey = "weibo:"+weiboId+":comment:content";
    Long start = (qo.getCurrentPage - 1) * qo.getPageSize;
    Long end = start + qo.getPageSize - 1; // qo.getCurrentSize()*qo.getPageSize()-1;
    List ids = jedis.lrange(listkey,start,end);
    List result = new ArrayList<>();
    if(ids.size

当有用户删除评论时

  1. 将评论id获取,删除redis的数据,list和hash都要
  2. 删除mysql中的数据
    伪代码:
public void delete(Long weiboId,Long commentId){
    String listKey = "weibo:"+weiboId+"comments";
    tring hashKey = "weibo:"+weiboId+":comment:content";
    jedis.lrem(listKey,commentId,1);
    jedis.hdel(hashKey,commentId);

    commentMapper.delete(Long commentId);
}

最近浏览功能

当一个需求是获取最近的浏览功能的时候,其实这个数据模型就应该马上出来,起码是有序对的存储,所以使用redis的list和zset都可以实现,使用list的时候只需要简单地保存就可以了,但是需要注意如果相近的两天浏览了相同商品,那么这个商品的浏览记录应该是最近的,也就是我们要删除之前的。同时,我们还需要设置(因为redis的过期删除是删除整个key,所以这里的有效时间是整个浏览历史的有效时间),而且浏览记录需要设置一个数量

  • 存储模型设计
    主要存储商品的id和所对应的用户的信息即可,商品的具体内容可以从mysql数据库中查出来。
  • 数据id的设计
    每一个key对应一个用户的浏览记录key -> MemeberRecentGoods:66 ,所存储的值就是商品的id即可

存储用户浏览的商品

  1. 避免重复,先删除之前关于这条商品id的浏览记录(如果是用zset来做,就不需要,因为zset会自己去重,所以设置最新的时间大小即可)
  2. 存入对应的list
  3. 只保留最近的60个
  4. 重新给这个key设置时间
    伪代码:
public void addMemeberRecentGoods(Long memberId,Long templateId){
    String key = "MemeberRecentGoods:"+memberId;
    jedis.lrem(key,1,templateId.toString());
    jedis.lpush(key,templated);
    jedis.ltrim(key,0,59);
    jedis.expire(key,60*60*24*30);
}

读取用户最近浏览的商品

  1. 根据用户的id,分页信息取出商品id
  2. 将总页数,商品信息等数据封装进map保存
    伪代码:
public Map queryMemberRecentGoods(Long memberId, QueryObject qo){
    String key = "MemeberRecentGoods:"+memberId;
    Long start = (qo.getCurrentPage - 1) * qo.getPageSize;
    Long end = start + qo.getPageSize - 1; // qo.getCurrentSize()*qo.getPageSize()-1;
    //获取总共条数,算出有多少页用于前台显示
    Long totalCount = jedis.llen(key);
    Integer totalPage = totalCount/pageSize == 0 ? totalCount/pageSize : totalCount/pageSize + 1;
    List ids = jedis.lrange(key,start,end);
    //查询出商品的信息(可以保存在数据库,也可以保存在redis)
    List goods = recentGoodsService.get(ids);
    Map map = new HashMap<>();
    map.put("goods",goods);
    map.put("totalCount",totalCount);
    return map;
}

实现日排行、周排行

很多应用都有排行榜的功能,排行榜有两个特点,其一是按积分排名,其二就是需要有时间约束,排行榜是有时效性的,如果排行榜没有时效,那始终都是老用户在榜首,这对新用户并不公平,使用redis可以很高效地实现这个功能

  • 存储模型设计
    因为需要用到积分排行,其实就是按照内容进行排行,使用ZSet可以做到,在这个应用中,每天都有一个排行榜
  • 数据id设计
    zset所对应的key需要指定日期,而用户id作为成员 key -> rank:20190115 member ->
    key所对应的值就是数字

增加积分

伪代码:

public void addScore(String nowDate, Long userId, Integer score){
    String key = "rank:"+nowDate;
    jedis.zincrby(key,score,userId);
}

获取每日排行

选出前10名展示出来
伪代码:

public List getDailyRankings(String nowDate){
    String key = "rank:"+nowDate;
    return jedis.zrevreange(key,0,9);
}

获取每周排行

利用zset的并集实现一周的日排行相加,从而选出周排行
伪代码:

public List getWeeklyRankings(String[] keys){
    String weeklyRankings = "weeklyRankings";
    jedis.zunionstore(weeklyRankings,2,keys[0],keys[1],keys[2],keys[3],keys[4],keys[5],keys[6],"weights",1,1,1,1,1,1,1);
    return jedis.zrevrange(weeklyRankings,0,9);
}

实现文章按点击量排名

文章点击量的排行,其实主要是利用了redis的单线程自加来实现的

  • 存储模型设计
    一个用来存储文章,一个用来存储该文章的点赞数,利用hash来存储文章内容,使用一个zset存储文章的点击量
  • 数据id设计
    需要指定到某一个人的文章,所以对于文章的存储,使用hash来存储某个用户的哪篇文章,需要用户id以及文章id
    而构造一个zset的存储空间来存储每篇文章的点赞数,并且利用zset来进行点赞后的加一操作

存储文章

HSET user:00001 “article:000001” “文章”
HSET user:00002 “article:000001” “文章”

文章排序

ZADD article:score 0 user:00001:artical:0000001
ZADD article:score 0 user:00002:artical:0000001

用户点赞

ZINCRBY article:score 1 user:00001:artical:0000001
ZINCRBY article:score 1 user:00002:artical:0000001

根据点击量获取文章列表

ZREVRANGEWITHSCORES article:score 0 -1

上亿用户登录统计

  • 存储模型设计
    因为有上亿的用户量,所以可以将每个用户按照每个位来存入,每一天都有一个bitmap,每个bitmap的每一位对应着一个用户的当天的登录状态值

  • 数据id设计
    每天的bitmap的key设置成当天日期,比如login_20181206,而用户的id就和位数对应,比如王五的id为8,则bitset login_20181206 8 1

需求:获取最近三天都登录的用户

bitop and login_last_3_day login_20181206 login_20181205 login_20181204
(其实就是取三天的二进制位进行与运算)

需求:获取最近7天曾经登录的用户:(最近7天有一天登录即可)

bitop or login_last_7_day login_20181206 login_20181205 login_20181204 login_20181203 login_20181204 login_20181205 login_20181206

需求:获取最近7天都没有登录的用户

bitop not nerver_login_last_7_day login_last_7_day

需求:统计某一用户当前月的登录天数

方法一:

使用bitmap存储所有用户的当天的登录情况,在java代码中循环30次取得每一天某一个用户的状态,并统计

方法二:

使用bitmap存储某一个用户的这个月的登录情况,1号是第0位,2号是第1位,以此类推,只需要8个字节就能解决某一个用户一个月的登录情况,取出来的时候只需要使用bitcount来查询即可

知乎回答的踩赞功能

  • 存储模型设计
    利用String或者hash来存储回答的内容,利用zset来存储每一个回答的分数,每条回答都有相对应的2个bitmap,一个用来当做用户的赞表,一个用来当做用户的踩表。
  • 数据id设计
    回答的id需要设计到回答者的id以及对应评论的id还有自己的id;
    在zset中,以article:00001:answer:score作为key,需要绑定对应的文章id,回答的id作为member来存储每个回答的分数;
    bitMap中需要知道当前回答的id,里面存储的是每个用户对这条回答的行为,1表示点了赞或者是点了踩,0表示没有操作

实现

  1. 当李四看到某个回答,要显示他是否支持或者反对(假设李四id为6)

第一次进来肯定还没有支持或反对. =====>结果为0,说明没有点赞.接着判断是否反对
getbit answer:166:user:oppose 6 =====>结果为0,说明没有反对.

  1. 此时李四对这个答案点赞.
    先根据权重算出分数:1*2 = 2
    zincrby answer:score 2 166
    此时id=166号的答案就增加了两分.同时要标记李四已经对这个答案点赞了.

第二次进来,已经点赞了
getbit answer:166:user:goods 6 =====>结果为1,说明对这个答案是点赞了,返回前台,显示支持效果。(已经知道点赞就没有去查询是否点反对了)

  1. 不小心点了支持,完成了上面的动作,这时候我需要点反对。
    我们需要做的动作是:
    3.1 判断是否点赞了?(防止前台绕过)
    getbit answer:166:user:goods 6 ===>结果为1,说明点赞了,继续后面操作.
    3.2 在 answer:166:user:goods把该用户对于的位修改成0,表示没有点赞.
    setbit answer:166:user:goods 6 0
    3.3 id=166的答案要先减去之前点赞所加的分数.
    3.4 Id=166的答案要减去该用户点赞需要扣的分数.(3,4步的分数可以一起统计后再扣除)
    score = 12 + 0.52 = 3
    zincrby answer:score -3 166

第三次进来,此时点击了反对
getbit answer:166:user:goods 6 =====>结果为0,说明没有点赞.接着判断是否反对
getbit answer:166:user:oppose 6 =====>结果为1,说明点了反对,返回前台,显示反对的效果.

  • 按照分数对答案进行排序:zrevrange answer:score 0 -1
  • 点击id=166的答案显示该答案的点赞数:bitcount answer:166:user:goods

你可能感兴趣的:(Redis)