一、缓存
作为Key-Value形态的内存数据库,Redis 最先会被想到的应用场景便是作为数据缓存。目前这几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力,而使用 Redis 缓存数据也非常简单,只需要通过string类型将序列化后的对象存起来即可,不过也有一些需要注意的地方:
(一)必须保证不同对象的 key 不会重复,并且使 key 尽量短,一般使用类名(表名)加主键拼接而成。
(二)选择一个优秀的序列化方式也很重要,目的是提高序列化的效率和减少内存占用。
(三)缓存内容与数据库的一致性,这里一般有两种做法:
①只在数据库查询后将对象放入缓存,如果对象发生了修改或删除操作,直接清除对应缓存(或设为过期)。
②在数据库新增和查询后将对象放入缓存,修改后更新缓存,删除后清除对应缓存(或设为过期)。
二、排行榜
很多网站目前都有排行榜的应用,如淘宝的年度/每日销量榜单、商品按时间的上新排行榜等。利用Redis的zset结构能实现各种复杂的排行榜应用。比如使用zset和一个计算热度的算法便可以轻松打造一个热度排行榜,zrevrangebyscore可以得到以分数倒序排列的序列,zrank可以得到一个成员在该排行榜的位置(是分数正序排列时的位置,如果要获取倒序排列时的位置需要用zcard-zrank)。
三、计数器
什么是计数器,如文章的阅读量、微博点赞数、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。我们可以先写入Redis再定时同步到数据库,Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。
计数功能应该是最适合 Redis 的使用场景之一了,因为它高频率读写的特征可以完全发挥 Redis 作为内存数据库的高效。在 Redis 的数据结构中,string、hash和sorted set都提供了incr方法用于原子性的自增操作,下面举例说明一下它们各自的使用场景:
(1)如果应用需要显示每天的注册用户数,便可以使用string作为计数器,设定一个名为REGISTERED_COUNT_TODAY的 key,并在初始化时给它设置一个到凌晨 0 点的过期时间,每当用户注册成功后便使用incr命令使该 key 增长 1,同时当每天凌晨 0 点后,这个计数器都会因为 key 过期使值清零。
(2)每条微博都有点赞数、评论数、转发数和浏览数四条属性,这时用hash进行计数会更好,将该计数器的 key 设为weibo:weibo_id,hash的 field 为like_number、comment_number、forward_number和view_number,在对应操作后通过hincrby使hash 中的 field 自增。
(3)如果应用有一个发帖排行榜的功能,选择sorted set更合适,将集合的 key 设为POST_RANK。当用户发帖后,使用zincrby将该用户 id 的 score 增长 1。sorted set会重新进行排序,用户所在排行榜的位置也就会得到实时的更新。
四、限流
在某些秒杀活动中,瞬间会有大量的用户请求涌入,而且大概率也会出现同一用户不断刷新页面或者其他操作来发送大量请求的情况,这时候我们就可以采取限流措施。那如何实现限流呢?我们可以利用Redis的incr方法,以访问者的ip和其他信息作为key,访问一次增加一次计数,当同一用户的访问次数超过我们预先设定的次数则返回提示信息(比如提示用户操作过于频繁,一定时间之后再重新操作等等)。
五、分布式会话
集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当在应用增多且相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。
六、分布式锁
在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,在实际应用中要考虑的细节会更多。
在 Redis 2.6.12 版本开始,string的set命令增加了一些参数:
(一)EX:设置键的过期时间(单位为秒)
(二)PX:设置键的过期时间(单位为毫秒)
(三)NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
(四)XX :只在键已经存在时,才对键进行设置操作。
由于这个操作是原子性的,可以简单地以此实现一个分布式的锁,例如:
set lock_key locked NX EX 1
如果这个操作返回false,说明 key 的添加不成功,也就是当前有人在占用这把锁。而如果返回true,则说明得了锁,便可以继续进行操作,并且在操作后通过del命令释放掉锁。并且即使程序因为某些原因并没有释放锁,由于设置了过期时间,该锁也会在 1 秒后自动释放,不会影响到其他程序的运行。
这里更推荐大家使用 redisson 第三方库来实现分布式锁。
七、消息队列
消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。
另外Redis 中list的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列(生产者 / 消费者模型)。消息的生产者只需要通过lpush将消息放入 list,消费者便可以通过rpop取出该消息,并且可以保证消息的有序性。如果需要实现带有优先级的消息队列也可以选择sorted set。而pub/sub功能也可以用作发布者 / 订阅者模型的消息。无论使用何种方式,由于 Redis 拥有持久化功能,也不需要担心由于服务器故障导致消息丢失的情况。
List提供了两个阻塞的弹出操作:blpop/brpop,可以设置超时时间:
blpop:blpop key1 timeout 移除并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
brpop:brpop key1 timeout 移除并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
上面的操作其实就是java的阻塞队列
队列:先进先除:rpush blpop,左头右尾,右边进入队列,左边出队列
栈:先进后出:rpush brpop
八、抽奖
利用set结构的无序性,通过 Spop( Redis Spop 命令用于移除集合中的指定 key 的一个或多个随机元素,移除后会返回移除的元素。 ) 随机获得值。
redis> SADD myset "one"
(integer) 1
redis> SADD myset "two"
(integer) 1
redis> SADD myset "three"
(integer) 1
redis> SPOP myset
"one"
redis> SMEMBERS myset
1) "three"
2) "two"
redis> SADD myset "four"
(integer) 1
redis> SADD myset "five"
(integer) 1
redis> SPOP myset 3
1) "five"
2) "four"
3) "two"
redis> SMEMBERS myset
1) "three"
redis>
九、点赞、签到
假如一个用户进行了评论,我们想给他点赞,他的ID是t1001,用户ID是u3001。
那么我们可以用 like:t1001 来维护这条评论的所有点赞用户。
//点赞这条微博
sadd like:t1001 u3001
//取消点赞
srem like:t1001 u3001
//是否点赞
sismember like:t1001 u3001
//点赞的所有用户
smembers like:t1001
//点赞数
scard like:t1001
这不比直接去数据库查询操作上更简单、效率更高?
十、显示最新的项目列表
比如说,当我们想要在网页上列出用户的最新20条评论,在最新的评论边上我们有一个“显示全部”的链接,点击后就可以获得更多的评论。
Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。
即每次新评论发表时,我们会将它的ID添加到一个Redis列表。可以限定列表的长度为1000
LPUSH latest.comments
在Redis中我们的最新ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过1000个ID,因此我们的获取ID函数会一直询问Redis。只有在超出了这个范围的时候,才需要去访问数据库。