来到了Redis系列的最后一篇,在前三篇中,介绍了Redis的数据类型及底层实现,持久化,集群分区,事务,缓存淘汰策略,HA哨兵机制等内容,其实关于Redis最重要的是其应用场景,只有知道Redis在什么场景下使用才是第一步。在这篇文章中,我们就来讨论一下Redis典型的应用场景。
缓存
提到redis,我们第一想到的应用场景肯定是缓存,因为redis是基于内存的<K,V>数据库,具有很强大的读写性能。当我们出现读多写少的热点数据时,缓存会起到非常非常重要的作用。如何保证缓存的一致性是很重要的。
热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存,毕竟强大的QPS和极强的稳定性不是所有类似工具都有的,而且相比于memcached还提供了丰富的数据类型可以使用,另外,内存中的数据也提供了AOF和RDB等持久化机制可以选择,要冷、热的还是忽冷忽热的都可选。
结合具体应用需要注意一下:很多人用spring的AOP来构建redis缓存的自动生产和清除,过程可能如下:
-
Select 数据库前查询redis,有的话使用redis数据,放弃select 数据库,没有的话,select 数据库,然后将数据插入redis
-
update或者delete数据库钱,查询redis是否存在该数据,存在的话先删除redis中数据,然后再update或者delete数据库中的数据
上面这种操作,如果并发量很小的情况下基本没问题,但是高并发的情况请注意下面场景:
为了update先删掉了redis中的该数据,这时候另一个线程执行查询,发现redis中没有,瞬间执行了查询SQL,并且插入到redis中一条数据,回到刚才那个update语句,这个悲催的线程压根不知道刚才那个该死的select线程犯了一个弥天大错!于是这个redis中的错误数据就永远的存在了下去,直到下一个update或者delete。
<k,v>数据库
首先Redis就是一个基于<k,v>的内存数据库,当然可以当做数据库来使用。比如:在单点登录系统中(SSO),我们就可以用Redis中存储共享Session,并设置相应的过期时间,这样就可以实现高性能的SSO。
队列–分布式消息队列
任务队列:顾名思义,就是“传递消息的队列”。与任务队列进行交互的实体有两类,一类是
生产者(producer),另一类则是
消费者(consumer)。生产者将需要处理的任务放入任务队列中,而消费者则不断地从任务独立中读入任务信息并执行。
任务队列的好处:
- 松耦合。生产者和消费者只需按照约定的任务描述格式,进行编写代码。
- 易于扩展。多消费者模式下,消费者可以分布在多个不同的服务器中,由此降低单台服务器的负载。
由于Redis有提供了队列(列表)的数据类型,我们可以把他用作分布式队列。
实现任务队列,只需让生产者将任务使用LPUSH加入到某个键中,然后另一个消费者不断地使用RPOP命令从该键中取出任务即可。
** **
将Redis用作日志收集器:
实际上还是一个队列,多个端点将日志信息写入Redis,然后一个worker统一将所有日志写到磁盘。
同时,由于Redis有订阅发布机制,所以它可以实现一些消息队列比如:Rocket MQ,Active MQ的一些功能。
关于Redis的订阅/发布模式:
“发布/订阅”(publish/subscribe)模式同样可以实现进程间通信,==订阅者可以订阅一个或多个频道(channel),而发布者可以向指定的频道发送消息,所有订阅次频道的订阅者都会收到次消息。==
命令实践
**PUBLISH **
将信息 message 发送到指定的频道 channel。返回收到消息的客户端数量。
**SUBSCRIBE **
订阅给指定频道的信息。
一旦客户端进入订阅状态,客户端就只可接受订阅相关的命令SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE和PUNSUBSCRIBE除了这些命令,其他命令一律失效。
**UNSUBSCRIBE **
取消订阅指定的频道,如果不指定,则取消订阅所有的频道。
**PSUBSCRIBE **
订阅给定的模式(patterns)。
**PUNSUBSCRIBE **
可以退订指定的规则,如果没有参数会退订所有的规则。
可参考:redis订阅与发布
- 相当于消息系统,ActiveMQ,RocketMQ等工具类似,但是个人觉得简单用一下还行,如果对于数据一致性要求高的话还是用RocketMQ等专业系统。
- 由于redis把数据添加到队列是返回添加元素在队列的第几位,所以可以做判断用户是第几个访问这种业务
- 队列不仅可以把并发请求变成串行,并且还可以做队列或者栈使用
单线程机制的应用
由于Redis是单线程机制,所以不会出现并发问题,所以可以将其作为:计数器,产生唯一ID,做秒杀系统。
计数器:
诸如统计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且100%毫秒级性能!爽。
命令:INCRBY。
产生唯一ID:在分布式系统中,如何产生唯一的商品id,订单id等等是很重要的,所以我们可以通过Redis的单线程计数器来产生唯一的ID,并且能保证性能。
**做秒杀系统:**在秒杀系统中,如何对抗高并发与解决超卖问题是两个关键问题。我们可以先将商品放入Redis中,在Redis中单线程的用DECR命令判断是否超卖了,从而将很少的流量放入到数据库端,达到限流与超卖问题。
分布式锁
分布式锁在很多应用场景下是非常有效的手段,比如当运行在多个机器上的不同进程需要访问同一个竞争资源的时候,那么就会涉及到进程对资源的加锁和释放,这样才能保证数据的安全访问。分布式锁实现的方案有很多,比如基于ZooKeeper实现、或者基于Mysql实现等等,今天我们来一起看看如何基于Redis实现分布式锁服务。
对于分布式锁的目标,我们必须首先明确三点:
- 任何一个时间点必须只能够有一个客户端拥有锁。
- 不能够有死锁,也就是最终客户端都能够获得锁,尽管可能会经历失败。
- 错误容忍性要好,只要有大部分的Redis实例存活,客户端就应该能够获得锁。
理解了上面我们列出的三个点,我们来分析一下一般的基于Redis实现的分布式锁:
使用Redis实现锁最简单的办法是创建一个key,利用key的唯一性(如果有一个客户端创建成功了这个key,那么其他客户端就无法获得),且这个key通常有有限的存活时间,这一点可以利用Redis的过期时间特性,所以锁最终会被释放掉,当客户端需要释放资源的时候,客户端delete这个key即可。
So far so good!但是有个单点问题,假如Redis master挂掉怎么办,因此我们需要加个slave,当master挂掉的时候可以切换到slave。这又带来了新的问题,由于Redis的复制是异步的,因此我们不能保证同时只有一个客户端获得锁(这就是为什么zk集群可以稳定的,因为zk自身保证一致性)。
这个模型有很显然的竞态:
- Client在Master上面获得了锁。
- master在数据同步到slave之前挂掉了。
- slave升级成为master。
- Client B申请了同样的资源的锁,成功了!
在特定条件下这种情况是会发生的,当出现多个客户端同时获得锁的时候,我们就认为可以这种锁方案是不可靠的。
基于上边的分析,我们可以很简单地构建一个分布式锁,但是对于Redis集群来说,这个方案是不稳定的,可能会出现多个客户端获得锁的情况。所以如何实现Redis的分布式锁:基于RedLock算法实现:
在分布式环境下,假设我们有N个master,这些节点都是独立的,因此我们没有配置复制策略。上面我们已经学会了如何在单机环境下获取锁和释放锁,我们假设的更具体一些,N=5,为了能获取锁,客户端的步骤为:
- 获取当前系统的时间,以毫秒为单位。
- 顺序的获取N个Redis实例上的锁,在每个实例中都用同样的key和value。在步骤2中,客户端需要一个比过期时间小很多的超时时间,例如,如果自动过期时间为10s,那么超时时间大概是5~50ms,这样可以避免客户端一直被阻塞,而不能继续请求下一个实例。
- 客户端每次都要计算已经过去了多长时间,使用的时间是否小于key自动过期的时间同时又获取了至少3个实例的锁。如果是,那么我们认为客户端此次获取锁成功。
- 如果锁被获取了,锁的过期时间必须要减去获取锁花费的时间。
- 如果当前客户端获取锁失败,客户端需要释放所有之前获取到的锁。
位操作(去重,统计在线人数等)
用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。
想想一下腾讯10亿用户,要几个毫秒内查询到某个用户是否在线,你能怎么做?千万别说给每个用户建立一个key,然后挨个记(你可以算一下需要的内存会很恐怖,而且这种类似的需求很多,腾讯光这个得多花多少钱。。)好吧。这里要用到位操作——使用setbit、getbit、bitcount命令。
redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示我们上面例子里面的用户id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统,上面我说的几个场景也就能够实现。用到的命令是:setbit、getbit、bitcount.
因为字符串的存储都是基于位的,所以可以通过setbit来设置为,并且通过bitcount来统计位中为1的个数。
redis 的setbit,getbit用法
排行榜/top n
可以用Sorted Sets这种数据类型来做排行榜,根据分数排行,实现高性能排行。利用sorted sets 还有很多应用场景,只要涉及到排序的,比如按照用户投票时间来排序等等场景。
求共同关注/共同还有
在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
Set
是集合,是String
类型的无序集合,set是通过hashtable实现的,概念和数学中个的集合基本类似,可以交集,并集,差集等等,
set
中的元素是没有顺序的。
sadd,spop,smembers,sunion命令。
一些好的应用场景的文章:
Redis应用场景(主要是基于数据类型说的)
Redis的7个应用场景
Redis专栏
基于Redis实现分布式锁
将redis做分布式队列
基于Redis的秒杀系统
Redis实现热词搜索
Redis实现分布式锁与队列
Redis实现session共享(SSO)
基于Redis实现分布式协调服务
分布式锁