理论上redis是可以取代数据库应用到各种场景,一些优秀的图书的文章也都有具体的例子来说明如何实现,所以笔者不再重复讲这些,读者可以自行网上搜索。在这里笔者只说几种日常中经常用到的方案的设计思路。
限流
- 方案A:把访问用户的特征码比如IP做为KEY存储,每次访问时用INCR来递增,并在第一次访问时创建KEY并用EXPIRE限制过期时间。当用户在此时间内访问超过了设置的值或时间过期会自动删除。注意上述的第一次访问的设置是在一个事务中执行的。
- 方案B:方案A存在这样一个问题,第1分种最后一秒和第2分种第一秒可以做大量的操作,实际效果是用户在整个2分钟内访问了200次,但这200次发生在2秒内,不够精确。所以应该对每个用户使用一个LIST类型来记录他最近100次访问时间,一旦LIST中元素超过10个,就判断时间最早的元素距现在是否小于1分钟,如果是就限制访问,如果否就记录最新的时间,删除最早的时间
搜索实现
需要使用集合以及有序集合的交集、并集和差集操作指定符合要求的元素,其实就是倒排索引的实现。
队列实现
一种方式是使用LPUSH和RPOP来实现一简单的,另一种是实现生产消费模式的队列:brpop key [key...] timeout:从key队列中取元素,当超过了等待时间后key队列没有收到新元素就返回nil,如果timeout=0则会一直阻塞。但它一次也只取走一个元素,所以在程序实现时需要放在一个loop中。blpop:它和brpop的区别是从左边取。如果指定多个key时,会自动检测哪个key中有新元素加入,这样实现了优先级队列。
如果把所有需要在未来执行的任务都igdlk到有序集合里面,并将任务的执行时间设置为分值,另外再使用一个进程来查找有序集合里面是否存在可以立即被执行的任务,如果有的话就从有序集合里面移除那个任务,并将它添加到适当的任务队列里面,这就构建了一个延迟队列。
锁
除了常规的认知外,还要处理以下几类问题:下面1,3出现的频率比较高。
- 持有锁的进程操作时间过长,导致锁提前被释放,而进程本身并不清楚,也有可能错误的释放了锁;
- 持有锁的进程崩溃了,其它线程想要取得锁,但不知道被谁锁了,导致时间浪费;
- 多个线程同时取得锁。
分布式锁
- 获取锁的时候,使用 setnx(SETNX key val:当且仅当 key 不存在时,set 一个 key为 val 的字符串,返回 1;若 key 存在,则什么都不做,返回 0)加锁,锁的 value值为一个随机生成的 UUID,在释放锁的时候进行判断。并使用 expire 命令为锁添加一个超时时间,超过该时间则自动释放锁;
- 获取锁的时候调用 setnx,如果返回 0,则该锁正在被别人使用,返回 1 则成功获取锁。 还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
- 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放;