网站的风格与布局的设计,官方网站旗舰店,体检营销型网站,第三方仓储配送公司文章目录 字符串缓存计数共享Session限速 哈希缓存 列表消息队列文章列表栈队列有限集合 集合标签抽奖社交需求 有序集合排行榜系统 字符串
缓存
#xff08;1#xff09;使用原生字符类型缓存 优点#xff1a;简单直观#xff0c;每个属性都支持更新操作 缺点#xff1… 文章目录 字符串缓存计数共享Session限速 哈希缓存 列表消息队列文章列表栈队列有限集合 集合标签抽奖社交需求 有序集合排行榜系统 字符串
缓存
1使用原生字符类型缓存 优点简单直观每个属性都支持更新操作 缺点占用过多的键、内存占用量较大同时用户内聚性比较差。 2使用序列化字符串类型缓存 优点简化编程如果合理地使用序列化可以提高内存的利用效率。 缺点序列化和反序列化有一定的开销同时每次更新属性都需要把全部数据取出来反序列化更新后在序列化存到Redis中。
计数
许多应用都会使用Redis作为计数的基础工具。它可以实现快速计数、查询缓存等功能同时数据可以异步落到其它数据源。比如视频播放系统使用Redis作为视频播放计数的基础组件用户每播放一次视频相应的视频播放数就会自增1。
long incrVideoCounter(long id){key video:playCount: id;return redis.incr(key);
}图解mysql专栏count( * )这么慢我该怎么办?
Redis计数和数据库同步的问题
以InnoDB为存储引擎的数据库由于MVCC机制count(*)计数时需要一行一行的读取判断是否对当前事务可见 然后计数加1或者不加。所以当数据量很大时执行count(*)的效率很慢。
假设某个网站有这样一个页面要显示数据库中某张操作记录表的总数同时还要显示最新的100条记录。由于count(*)的效率很慢我们可以用Redis来计数如果这张操作记录表插入了一行Redis计数就加1删除了一行Redis计数减1。
我们用一张表模拟两个事务的操作过程事务B负责这个页面的业务查询即获取Redis计数和操作表中最新的100条记录事务A向表中插入一条记录Redis计数加1
由于多线程中线程的执行顺序是不确定的如果事务B的代码在t2时刻执行则查询到底100条记录里面有最新的插入记录但redis计数却没变。
时刻事务A事务Bt0t1向操作表中插入一条记录t2读取Redis计数获取表中最新的100条记录t3Redis记录加1
如果事务A变化一下添加记录的执行过程先去Redis计数加1再向操作表中执行插入操作。事务B在t2时刻执行此时该页面显示的情况是Redis的计数变了但是查询到的100行记录里面没有最新的改变
时刻事务A事务Bt0t1Redis记录加1t2读取Redis计数获取表中最新的100条记录t3向操作表中插入一条记录
以上两种情况对于事物B来说查计数值和“最近的100条记录”看到的结构逻辑上是不一致的。
共享Session
查看我写的另一篇博客 什么是Redis共享Session
限速
很多应用出了安全考虑会在每次进行登录的时候让用户输入手机验证码从而确定是否是用户本人。但是为了短信验证接口不被频繁访问会限制用户每分钟获取验证码的频率例如一分钟不能超过5次。
此功能可以通过Redis来实现
phoneNum 173xxxxxxxx;
key shortMsg:limit: phoneNum;
// 将这个key的生命周期设置为60s也就是1分钟
// key初始时的次数是1
isExists redis.set(key,1,EX 60,NX);
if(isExists ! null || redis.incr(key) 5){// 第一次创建这个键// 或者// 不是第一次创建键但 key 对应的次数小于等于5// 通过
}else{// 限速
}哈希
缓存
优点简单直观如果合理可以减少内存空间的使用。 缺点要控制哈希在ziplist和hashtable两种内部编码的转换hashtable会消耗更多内存。
列表
消息队列
如图所示Redis的lpushbrpop命令组合即可实现阻塞队列生产者使用lpush命令从列表左侧插入元素多个消费者使用brpop命令阻塞式地抢列表尾部的元素。
文章列表
每个用户有属于自己文章列表现需要分页展示文章列表。此时可以考虑使用列表因为列表是索引有序的可以支持按照索引范围获取元素。 1每篇文章用哈希类型存储例如每篇文章有一些属性title、time、author、content、isbn、money等
hmset acticle:1 title xx time 1476536196 content xxxx ...
...
hmset acticle:k title xx time 1476536196 content xxxx ...使用类似这样的命令构造k篇文章的数据
2向用户添加文字列表
lpush user:1:acticles article:1 article:3 ...
...
lpush user:n:acticles article:2 article:4每个用户有自己的一个文章列表 3分页获取用户文章列表例如通过下面的伪代码获取用户id1的前10篇文章
articles lrange user:1:articles 0 9
for (article : articles){hgetall {article}
}使用列表类型保存或获取文章列表会存在两个问题
如果每次分页获取的文章数目较多需要执行多次hgetall操作此时可以考虑使用Pipeline批量获取。或者考虑将文章数据序列化为字符串类型使用mget来获取。分页获取文章列表时lrange命令在列表的两端性能比较好但如果列表较大lrange获取列表中间元素性能会变差此时可以考虑将列表做二级拆分。或者使用Redis quciklist内部编码实现。
栈
lpush lpop Stack队列
lpush rpop Queue有限集合
lpush ltrim Capped Collection集合
标签
一个用户可能对娱乐、体育比较感兴趣另一个用户可能对历史、新闻比较感兴趣这些兴趣点就是标签。有了标签之后我们可以得到
喜欢同一个标签的人站在标签角度两个或多个用户共同喜欢的标签站在用户角度
这些数据对于用户体验以及增强用户黏度比较重要。
例如一个电子商务的网站会对不同标签的用户做不同类型的推荐比如对数码产品感兴趣的人在各个页面或通过邮件的形式给他们推荐最新的数码产品通常会为网站带来更多的收益。
下面使用集合类型实现标签的若干功能 1给用户添加标签
SADD user:1:tags tag1 tag2 tag3
SADD user:2:tags tag2 tag4 tag5
SADD user:3:tags tag1 tag3 tag5
SADD user:4:tags tag2 tag3 tag4
SADD user:5:tags tag1 tag4 tag5
SADD user:6:tags tag3 tag4 tag5在redis种可以使用lua脚本一次性执行这命令
EVAL
redis.call(SADD, user:1:tags, tag1, tag2, tag3)
redis.call(SADD, user:2:tags, tag2, tag4, tag5)
redis.call(SADD, user:3:tags, tag1, tag3, tag5)
redis.call(SADD, user:4:tags, tag2, tag3, tag4)
redis.call(SADD, user:5:tags, tag1, tag4, tag5)
redis.call(SADD, user:6:tags, tag3, tag4, tag5)02 给标签添加用户
SADD tag1:users user:1 user:3 user:5
SADD tag2:users user:1 user:2 user:4
SADD tag3:users user:1 user:3 user:4
SADD tag4:users user:2 user:4 user:5 user:6
SADD tag5:users user:2 user:3 user:5 user:6在redis种可以使用lua脚本一次性执行这命令
EVAL
redis.call(SADD, tag1:users, user:1, user:3, user:5)
redis.call(SADD, tag2:users, user:1, user:2, user:4)
redis.call(SADD, tag3:users, user:1, user:3, user:4)
redis.call(SADD, tag4:users, user:2, user:4, user:5, user:6)
redis.call(SADD, tag5:users, user:2, user:3, user:5, user:6)03删除用户下标签
srem user:1:tags tag1 tag24删除标签下的用户
srem tag1:users user:1
srem tag2:users user:11和2尽量放在一个事务中执行。 3和4尽量放在一个事务中执行。 5获取喜欢同一个标签的人 smembers tag1:users6获取user:1与user:2共同喜欢的标签
sinter user:1:tags user:2:tags抽奖
spop/srandmember Random item社交需求
sadd sinter Social Graph有序集合
排行榜系统
比如视频网站需要对用户上传的视频做排行榜榜单的维度可能是多方面的可能按照以下几个维度排名
时间播放数量获得的赞数…
以获得的赞数为例记录每天用户上传的视频的排行榜。
1视频添加用户赞数 例如用户mike上传了一个视频并获得了3个赞可以使用有序集合的zadd和zincrby功能
zadd user:ranking:2024_03_12 3 mike如果之后再获得一个赞就可以使用zincrby
zincrby user:ranking:2024_03_12 1 mike2将用户移除榜单 由于各种原因如用户注销、用户作弊需要将用户删除此时需要将用户从榜单中删除掉可以使用zrem。例如删除榜单中的tom用户
zrem user:ranking:2024_03_12 tom3显示榜单Top10 为了模拟这个功能我用lua脚本创建了20条模拟数据
EVAL
redis.call(ZADD, user:ranking:2024_03_12, 3, mike)
redis.call(ZADD, user:ranking:2024_03_12, 5, jack)
redis.call(ZADD, user:ranking:2024_03_12, 7, tom)
redis.call(ZADD, user:ranking:2024_03_12, 9, curt)
redis.call(ZADD, user:ranking:2024_03_12, 11, dexter)
redis.call(ZADD, user:ranking:2024_03_12, 13, bert)
redis.call(ZADD, user:ranking:2024_03_12, 15, christian)
redis.call(ZADD, user:ranking:2024_03_12, 17, cecil)
redis.call(ZADD, user:ranking:2024_03_12, 19, charles)
redis.call(ZADD, user:ranking:2024_03_12, 21, bill)
redis.call(ZADD, user:ranking:2024_03_12, 23, cathy)
redis.call(ZADD, user:ranking:2024_03_12, 25, crystal)
redis.call(ZADD, user:ranking:2024_03_12, 27, elaine)
redis.call(ZADD, user:ranking:2024_03_12, 29, ellie)
redis.call(ZADD, user:ranking:2024_03_12, 31, hortensia)
redis.call(ZADD, user:ranking:2024_03_12, 33, kit)
redis.call(ZADD, user:ranking:2024_03_12, 35, lori)
redis.call(ZADD, user:ranking:2024_03_12, 37, marian)
redis.call(ZADD, user:ranking:2024_03_12, 39, lesley)
redis.call(ZADD, user:ranking:2024_03_12, 41, thirza)0根据或赞数排序取前或赞数量前10个用户可以使用zrevrange命令从高到地返回成员 zrevrange user:ranking:2024_03_12 0 94展示用户信息、用户分数以及用户排名
假设用户信息保存再哈希类型中用户分数和用户排名可以用zscore和zrank两个命令
hgetall user:info:tom
zscore user:ranking:2024_03_12 mike
zrank user:ranking:2024_03_12 mike