005.Redis在一个购物网站中的应用

Token

书中这块设计描述的有点乱,整理候结果如下:

  1. 书中记录Token采用的是Hash结构,而我们生产中使用的是String结构。书中用一个类型为Hash,键名为login:的数据结构来存放登录用户的Token,当用户请求时,使用hget login: token来判断Cookie中得令牌是否登录。

  2. 当用户登录候,我们还需要在login:下为该用户创建一个键值对,键为token,值为userId。同时我们还要在recent:中使用zadd recent: token timestamp记录令牌最后一次出现的时间。

  3. 针对用户的浏览数据,我们使用zdd viewed:token item timestamp记录用户的浏览数据。在此操作的同时,我们还需要使用zremrangebyrank viewed:token 0 -26移除旧的记录,只保留用户最近浏览过的25个商品。(大数据时代,这种应用应该比较低吧)

  4. 为了限制会话数据的数量,只保存最新的1000万个会话。使用一个循环去清除会话,该循环每次执行的逻辑为:

  • 判断recent:的长度是否超过了限制,如果超过了则执行清理,否则不进行清理
  • 调用zrange 0 end_index-1获取需要删除的令牌的id(end_index = min(size - LIMIT, 100)
  • 得到需要删除的viewed:键(因为登录令牌已经需要删除了,这些viewed:键也没有任何意义了)
  • 调用delete session_keys批量删除viewed:
  • 调用hdel login: tokens批量删除令牌
  • 调用zrem recent: tokens批量删除最近浏览记录
  1. 另外的解决方案:和我们目前的项目一样,使用string接口,加上过期时间,实现Token的自动清理。但是使用这种方案,没有办法限制会话的数量在1000万之内,并且也没有办法对废弃的购物车进行任何处理了。

购物车

数据结构:

Redis类型:Hash
键:card:session
值:商品Id
值的值:商品的数量

流程设计:

  1. 添加购物车时的逻辑:

    • 判断用户传入的数量,如果大于零,调用hset card:session item cound,如果小于零,调用hrem card:session item

    • Token清理部分用的清理函数也需要定时清理掉相关的card:session。

网页缓存

数据结构:

Redis类型:string
键:cache:hash(request) // 这个地方书上说的并不好,这个最好是全局唯一的请求类名 + 方法名
值:缓存的内容

数据缓存

方案设计:

为了应对促销活动带来的大量负载,需要对数据进行缓存,具体的做法是:编写一个持续运行的守护进程函数,让这个函数将指定的数据行缓存到Redis里面,并不定期地对这些缓存进行更新。缓存函数会将数据行编码为JSON字段并存储在Redis的字典里面,其中,数据列的名字被映射为JSON字典的键,而数据行的值则被映射为JSON字典的值。

(实际开发中,似乎没有这么做过,我们更多做的事接口级的缓存,这个应该数据数据级的,MyBatis的二级缓存应该和这个原理差不多。)

2021-10-26-17-23-01

程序使用了两个有序集合来记录应该在何时对缓存进行更新:第一个有序集合为调度有序集合,它的成员为数据行的行Id,而分值则是一个时间戳,这个时间戳记录了应该在何时将指定的数据行缓存到Redis里面;第二个有序集合为延时有序集合,它的成员也是数据行的行ID,而分值则记录了指定数据行的缓存需要每隔多少秒更新一次。

为了让缓存函数定期地缓存数据行,程序首先需要将行ID和给定的延迟值添加到延迟有序集合里面,然后再将行ID和当前时间的时间戳添加到调度有序集合里面。实际执行缓存操作的函数需要用到数据行的延迟值,如果某个数据行的延迟值不存在,那么程序将取消对这个数据行的调度。如果我们想要移除某个数据行已有的缓存,并且让缓存函数不再缓存那个数据行,那么只需要把那个数据行的延迟值设置为小于或等于0就可以了。

(这个地方我已经看出来了,我的想法和作者的并不一致)

数据结构:

Redis类型:zset
键:schedule:
成员:row_id
分值:待更新的时间

Redis类型:zset
键:delay:
成员:row_id
分值:延时

流程设计:

  1. 将需要进行缓存的row_id和缓存的delay值放到delay:中,同时在schedule:中放一份,放入schedule:时用的是当前的时间戳,这样确保了之后一定会被缓存。

  2. 负责缓存数据行的函数会尝试读取调度有序集合的第一个元素以及该元素的分值,如果调度集合中没有元素,或者所指定的时间戳未来临,则函数休眠50毫秒,然后重新检查。

  3. 当函数发现一个需要立即进行更新的数据行时,缓存函数会检查这个数据行的延迟值,如果该数据行的延时值小于或者等于0 ,那么缓存函数会从延时集合和调度集合里面移除这个数据行的Id,并从缓存中删除这个数据行已有的缓存,然后再重新检查,对于延迟值大于0的数据行来说,缓存函数会从数据库里面取出这些行,将它们编码为JSON格式并存储到Redis里面,然后更新这些行的调度时间。

网页分析

目标:

  1. 记录用户常访问的商品Id。
  2. 确保数据不会两级分化严重(一部分数据经常被访问,一部分数据从来不会被访问)。
  3. 调整缓存函数,让其仅缓存榜上拥有的数据(避免全量缓存)

实现:

  1. 用户浏览商品时,调用zincreby viewed: item -1,让经常被浏览的商品的分值降低,这样能够拍到前面。
  2. 定期修改viewed:有序集合的长度(调用zremrangebyrank viewed: 0, -20001),并调用zinterstore vieded {'viewed': .5},让浏览次数降低为原来的一半(这块表示怀疑,因为前面登录的时候,增加的是-1。)