Redis: 分布式锁的多种实现 / 布隆过滤器的使用

前述

从前段时间深入的学习了高并发相关的资料后,初始时便觉得很多细节操作大可不必,但后来经过一段时间的沉淀后才得以明了。之所以考虑很多细节,那是因为在高并发的场景下任何形式的小问题都将被无限放大以致于酿成严重后果,很多的方案估计也都是遇到风险后的优化而来的。

分布式锁

本质来讲,分布式锁就是多个客户端所需要的锁由Redis提供,而锁的实现方法也很简单,无非是设置一个值为标记位,由于Redis是单线程处理,也无需考虑Redis的操作在高并发下的影响。

v1.0

如此,便得到了第一个版本。大概思路为,如果当前Redis中不存在某个值,那么便创建(标记),在执行完毕后将其删除就好。

顺序如下:

  1. setnx lock true // setnx: set if not exists
  2. do something..
  3. del lock

但如果有客户端在获取到锁之后产生了一些问题导致无法去释放锁,那么其他的客户端便无法获得锁了。所以在此之上需要设置一个过期时间,即保证锁一定能被释放。不过并不能直接使用expire去做时间限制,因为其不具备原子性。

v1.1

起初想要实现一个复合原子性的时间限制衍生了很多的第三方插件,不过经过Redis的更新,加入了一个set指令的拓展解决了这个需求。

set lock true ex 5 nx  // 即限制5秒必定释放

v1.2

如果用户A获得锁后由于各方面的影响导致其操作的时间超过了过期时间,那么锁将由于时间限制而被释放。而此时倘若用户B拿到了锁。在B拿到锁后正在执行的时候,A操作完毕后执行了del指令。这也就意味着A释放了B的锁,自此不仅这两个用户受到影响,也可能导致很严重的级联反应。

自此又要对之前的方案作出限制: 在标记的时候随机生成一个数字,释放的同时判定value是否相同,只有这样才允许释放锁。这样便可以控制问题规模。

  1. tag = 随机数()
  2. set lock tag ex 5 nx // 将tag设为值
  3. do something..
  4. delifequals("lock", tag) // 只有tag相同时才允许删除

其中delifequals()并非Redis所提供的方法,主要含义为在释放锁时增加一层判断tag是否相同的逻辑。不过由于其不具备原子性,此时的解决方案是通过Lua脚本来实现这一业务。

其脚本内容为:

if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

而执行Lua脚本只需将脚本内容加上双引号,以字符串为格式作为eval指令的参数便好。

布隆过滤器

总的来说,布隆过滤器的主要用处在于快速的从庞大数据中去判定是否存在某个数据。一般为了完成这样的任务的普遍做法是从头往后进行遍历从而判断,又有预先将数据存入到map之中随后进行判断。

但此两者当面临很大的数据集时会消耗大量的性能与时间,那么有没有更优的选择呢?便是布隆过滤器。

简而言之,布隆过滤器主要思路主要是: 将所有的数据通过几种不同且分布均匀的哈希函数得到多个位图对应的索引值,其后把对应的索引值都设为1,这样便构造好了过滤器所需要的数据集。

当用户传来一个数据需要判定是否存在于其中时,只需与前述相同的将其映射到一个个位图索引之上,最后再去判定是否有一个索引值为0,如果存在,则判定其数据并非存在。但值得注意的是,布隆过滤器是存在误差的。其误差的概率由哈希函数的数量、位图长度两者而决定。

综上所述,布隆过滤器本身是一种解决问题的思路,所以并不是局限在Redis的一种工具。因此这篇博文以两个方面去实现,分别是本地实现和Redis实现。

本地使用Guava实现

代办..

容量受限制 // jvm tomcat jedis 单机

多个应用存在多个布隆过滤器 同步复杂

Redis使用RedisBloom插件实现

代办..

笔记留存

分布式锁.jpg

# Redis