Redis因为其丰富的数据结构,在高并发场景下经常作为缓存使用。
在高并发流量下,我们要在业务层解决流量截断工作,让更少的请求到达redis缓存这一层。主要做法是:
比如redis中有n个资源待抢,部署了m台业务服务器,每台业务服务进程内部会有一个计数器,初始值为n,用户每发送一个请求,计数器减1,如果计数器值>0,则继续访问redis抢占资源(一般用decr命令);如果计数器值<=0,则不会再访问redis,直接返回抢占失败。这样到达redis的请求数目只剩下n*m个,极大地减少了redis的压力。
一. 发红包
用redis队列。
用户发红包时,输入金额m和红包个数n。后台程序接收到这两个参数后,将m分成随机的n份,将这n个数写入到redis队列中。
用户抢红包时,访问redis队列,每抢一个就lpop出一个金额,直到队列为空。
为了减少redis的压力,用上面的方法做流量截断工作。
二. 商品秒杀系统
用redis的DECR命令。
Redis Decr 命令将 key 中储存的数字值减一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECR 操作。
思路:
- 商品信息在秒杀活动开启之前, 将其从关系型数据库(比如mysql)里加载到redis里面去。用set key counter命令对应商品的秒杀总数。
- 活动开始后, 秒杀系统只需要调用DECR 商品id命令, 返回的数据就是数字减一之后的值, 如果大于等于0, 就代表抢到了一件商品, 将该商品加入购物车即可(加入购物车调用现有的接口即可), 如果小于0, 则代表商品都抢完了, 则秒杀失败, 前端做相应的展示提示。
这只是最基本的设计思路,由于秒杀请求量会特别大,所以对于超过秒杀数的请求,我们可以直接返回,没有必要再进行redis操作。
比如某商品10件物品待秒,假设有10台业务服务器,接收到秒杀请求后,每台业务服务器在进程内部保存一个计数器,设置初始值=待秒商品总数,每来一个请求,减一次计数器,计数器>0的,继续访问redis,计数器<=0的,直接返回秒杀结束页面,这样经过第一步的处理只剩下10台*10个=1000个请求。
三. 积分排行榜
Redis 提供了 sorted set 有序集合数据结构,高效的插入和删除性能,适用于需实时排序的场景。
比如我们要做一个积分排名系统,积分可以奖励(增加积分),也可以惩罚(扣除积分)。
- 用zadd命令新增玩家
127.0.0.1:6379> zadd game 0 user1
(integer) 1
表示,排行榜名称为game,玩家user1赋予初始积分为0。
2. 用zincrby命令增减玩家分数
127.0.0.1:6379> ZINCRBY game 3 user1
"3"
127.0.0.1:6379> ZINCRBY game -2 user2
"8"
- zscore命令查看玩家分数
127.0.0.1:6379> zscore game user2
"8"
127.0.0.1:6379> zscore game user1
"3"
- zrevrange命令查看排行榜
由于排行榜一般是按照分数由高到低排序的,所以我们使用zrevrange,而命令zrange是按照分数由低到高排序。
起始位置和结束位置都是以0开始的索引,且都包含在内。如果结束位置为-1则查看范围为整个排行榜。
带上withscores则会返回玩家分数。
127.0.0.1:6379> zrevrange game 0 -1 withscores
1) "user2"
2) "8"
3) "user1"
4) "3"
- zrem移除某个玩家
127.0.0.1:6379> zrem game user2
(integer) 1
- 删除排行榜
del game
四. 分布式锁
分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题,而分布式锁,就是解决了分布式系统中控制共享资源访问的问题。与单体应用不同的是,分布式系统中竞争共享资源的最小粒度从线程升级成了进程。
由此可见分布式锁的目的其实很简单,就是为了保证多台服务器在执行某一段代码时保证只有一台服务器执行。
为了保证分布式锁的可用性,至少要确保锁的实现要同时满足以下几点:
(1) 互斥性。在任何时刻,保证只有一个客户端持有锁。
(2) 不能出现死锁。如果在一个客户端持有锁的期间,这个客户端崩溃了,也要保证后续的其他客户端可以上锁。
(3) 保证上锁和解锁都是同一个客户端。
Redis实现分布式锁主要利用Redis的setnx命令。setnx是SET if not exists(如果不存在,则 SET)的简写。
127.0.0.1:6379> setnx lock value1 #在键lock不存在的情况下,将键key的值设置为value1
(integer) 1
127.0.0.1:6379> setnx lock value2 #试图覆盖lock的值,返回0表示失败
(integer) 0
127.0.0.1:6379> get lock #获取lock的值,验证没有被覆盖
"value1"
127.0.0.1:6379> del lock #删除lock的值,删除成功
(integer) 1
127.0.0.1:6379> setnx lock value2 #再使用setnx命令设置,返回0表示成功
(integer) 1
127.0.0.1:6379> get lock #获取lock的值,验证设置成功
"value2"
上面这几个命令就是最基本的用来完成分布式锁的命令。
加锁:使用setnx key value命令,如果key不存在,设置value(加锁成功)。如果已经存在lock(也就是有客户端持有锁了),则设置失败(加锁失败)。
解锁:使用del命令,通过删除键值释放锁。释放锁之后,其他客户端可以通过setnx命令进行加锁。