容器型数据结构的通用规则
list set hash zset 这四种数据结构是容器型数据结构,有两条通用规则:create if not exists
:如果容器不存在,那就创建一个,再进行操作。 drop if no elements
:如果容器里的元素没有了,那么立即删除容器,释放内存。
过期时间
所有数据结构都可以设置过期时间到期自动删除相应的对象,如果已设置过期时间,然后你再去修改它,它的过期时间会消失。
常用命令:expire
key 时间单位 ttl
key
千帆竟发一一分布式锁
为了解决分布式锁命令: set key true ex 5 nx (设置5秒)
setnx expire 组合在一起的原子指令,它就是分布式锁的奥义所在
为了解决超时问题 :是将 set 指令的 value 参数设置为一个随机数,释放锁时先匹配随机数是否一致,然后再删除 key ,这是为了确保当前线程占有的锁不会被其他线程释放,除非这个锁是因为过期了而被服务器自动释放的。为了匹配 value 和删除 key 不是 一个原子操作,需要使用 Lua 脚本来处理了,因为 Lua 脚本可以保证连续多个指令的原子性执行。这只是一个相对安全的方案,如果超时了,当前线程逻辑没有这些完,其他线程也会进入。
可重入性:可重入性是指线程在持有锁的情况下再次请求加锁,如果一个锁支持同一个线程的多次加锁,那么这个锁就是可重入的。
缓兵之计一一延时队列
异步消息队列:Redis 的 (列表) 数据结构常用来作为异步消息队列使用,例如:用 rpush 和 lpush 操作人队列,用 lpop和 rpop 操作出队列.可以支持多个生产者和多个消费者并发进出消息,每个消费者拿到的消息都是不同的列表元素。
队列空了怎么办: 如果队列空了,客户端就会陷入 pop 的死循环,空轮询不但拉高了客户端的 CPU 消耗, Redis的 QPS 也会被拉高,Redis 慢查询可能会显著增多。解决方案: 使用 sleep睡个 1s 就可以了.
阻塞读: 阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦数据到来,则立刻 醒过来。消息的延迟几乎为零。用 blpop/brpop
替代前面的 lpop rpop
解决空闲连接的问题 :闲置过久,服务器一般会主动断开连接,减少闲置资源、占用。这个时候 blpop/brpop 会抛出异常,如编写捕获异常则需重试。
锁冲突处理 :采用下3种策略来处理加锁失败。1. 直接抛出异常,通知用户稍后重试。 2. sleep 会儿,然后再重试。 3. 将请求转移至延时队列,过一会儿再试。(这种方式比较适合异步消息处理,将当前冲突的请求扔到另一个队列延后处理以避开冲突。)
延时队列的实现 :延时队列可以通过 Redis 的 zset (有序列表)来实现。我们将消息序列化成一个字符串作为 zset 的 value ,这个消息的到期处理时间作为 score ,然后用多个线程轮询 zset 获取到期的任务进行处理。
Redis 的 zrem 方法是多线程多进程争抢任务的关键,它的返回值决定了当前实例有没有抢到任务,因为 loop 方法可能会被多个线程、多个进程调用,同一个任务可能会被多个进程、多个线程抢到,要通过 zrem 来决定唯一的属主。
进一步优化:,同一个任务可能会被多个进程取到之后再使用 zrem 进行争抢, 那些没抢到的进程都自取了一次任务,这是浪费。可以考虑使用 lua scripting
来优化一下,将 zrangebyscore 和 zrem 一同挪到服务器端进行原子化操作。