有序集合在工作中的应用场景有很多,例如“
排行榜:可以将用户的得分作为有序集合的分支,用户的ID作为成员,通过有序集合的排名功能可以得到用户的排名信息。例如:
zadd hot 1e aid 1 99 aid 2 95 aid 3 9 aid 4
用户关注集合:可以将用户的关注数作为有序集合的分值,用户ID作为成员,通过分值的排序可以得到关注数最多的用户。例如:
zadd user_1_friends 17 zhangsan 18 lisi
商品价格排序:可以将商品价格作为有序集合的分值,商品ID作为成员,通过有序集合的排序功能可以得到价格从高到低的商品列表
zadd prize 10 apple 20 banana
默认情况,使用setnx lock true实现分布式锁会存在以下问题:
解决方法:
解决死锁问题:设置超时时间
set lock true ex 10 nx
解决锁误删问题:锁误删可以通过将锁标识存储到 Redis 中来解决,删除之前先判断锁归属(也就是将线程 id 存储到分布式的value 值内,删除之前先判断锁 value 值是否等于当前线程 id),如果属于你的锁再删除,否则不删除就可以,这就解决了锁误删的问题
通用解决方案:以上问题有一个通用的解决方案,那就是使用 Redisson 架来实现 Redis 分布式锁,这样可以解决死锁问题,也可以解决锁误删、不可重入和无法自动续期的问题了
Redisson?是一个在 Redis 的基础上实现的 Java 驻内存数据网格客户端(In-Memory Data Grid)。它不仅提供了一系列的 redis 常用数据结构命令服务,还提供了许多分布式服务,例如分布式锁、分布式对象、分布式集合、分布式远程服务、分布式调度任务服务等等。
使用 Redisson 实现分布式锁的步骤如下:
Redisson 实现的分布式锁有以下优点:
Redisson 中的看门狗机制是一种自动延长锁有效期的机制。当一个线程获取了一个锁,但是还没有执行完毕,看门狗会自动帮助线程延长锁的超时时间,防止锁因为超时而被释放?
具体来说,Redisson 的看门狗机制工作流程如下:?
?当一个线程成功获取锁后,如果没有指定锁的租赁时间(lease time),那么看门狗会启动一个定时任务,每隔 lockWatchdogTimeout / 3 的时间,就会自动将锁的有效期延长到 lockWatchdogTimeout。?
如果线程在执行过程中一直没有释放锁,那么看门狗会一直帮助线程续期,直到线程执行完毕并释放锁。?
如果线程在获取锁时指定了租赁时间,那么看门狗就不会启动,锁会在租赁时间结束后自动释放。??
这种机制可以有效防止因线程执行时间过长而导致的锁提前释放,同时也避免了因线程突然宕机导致的死锁。默认情况下,看门狗的续期时间是 30 秒,但是可以通过修改 Config.lockWatchdogTimeout 来另行指定
RedLock 是一种基于 Redis 实现的分布式锁算法。它的主要优点和缺点,以及是否推荐使用,如下:?
优点:?
互斥性:在任何时候,只能有一个客户端能够持有
避免死锁:当客户端拿到锁后,即使客户端崩溃或者因为其他原因无法释放锁,锁也会因为设置了失效时间而自动被释放
高可靠性:相比于普通的基于单个 Redis 实例的分布式锁,RedLock 算法在多个 Redis 实例之间实现分布式锁,从而提高了锁的可靠性
缺点:?
时钟偏移:RedLock 算法依赖于各个 Redis 节点的系统时钟是准确同步的,如果节点之间的时钟存在较大的偏移,可能导致锁的获取和释放出现问题
资源需求:需要部署多台 Redis 机器
极端情况下的问题:在极端情况下,可能会出现两个线程同时获得锁的情况
关于是否推荐使用 RedLock,这取决于具体的使用场景。RedLock 提供了一种在多个 Redis 实例之间实现分布式锁的可靠方法。然而,如果你的应用不需要一个高度可靠的分布式锁,或者你的 Redis 环境只有一个实例,那么使用 RedLock 可能就没有必要了。此外,还需要考虑到 RedLock 的资源需求和可能的时钟偏移问题。总的来说,如果你的应用需要一个高度可靠的分布式锁,并且你能够管理多个 Redis 实例,那么 RedLock 是一个值得考虑的选项。但是,有一些观点认为 Redisson 的 RedLock 实现并不推荐使用,因此在选择使用 RedLock 之前,建议详细研究其可能的问题和限制
布隆过滤器(Bloom Filter)是一种数据结构,它实际上是由一个很长的二进制向量(位数组)和一系列随机映射函数(哈希函数)组成
特点:
高效地插入和查询:布隆过滤器的增加和查询元素的时间复杂度为O(K),其中K为哈希函数的个数
占用内存小:布隆过滤器的空间复杂度为O(m),不会随着元素增加而增加
有一定的误识别率:布隆过滤器返回的结果是概率性的,而不是非常准确的理论情况下添加到集合中的元素越多,误报的可能性就越大
删除困难:存放在布隆过滤器的数据不容易删除
底层实现原理:
当一个元素加入布隆过滤器中的时候,会使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。根据得到的哈希值,在位数组中把对应下标的值置为1
当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:对给定元素再次进行相同的哈希计算;得到值之后判断位数组中的每个元素是否都为1,如果值都为1,那么说明这个值在布隆过滤器中,如果存在一个值不为1,说明该元素不在布隆过滤器中
布隆过滤器的底层实现原理是基于位数组和哈希函数的,因此,它的底层实现原理也可以看作是位数组和哈希函数的应用
?
在 Redis 中实现布隆过滤器主要有以下两种方式:?
使用 Bitmaps:Redis 提供了一套 Bitmaps 命令,可以用来操作位数组,这是布隆过滤器的基础。例如,setbit 命令可以用来设置位数组中的某一位,getbit 命令可以用来获取位数组中的某一位,bitcount 命令可以用来获取位数组中值为 1 的位的个数
使用 Redisson:Redisson 是一个在 Java 程序中操作 Redis 的库,它提供了布隆过滤器的实现。例如,可以使用 redisson.getBloomFilter("filterName") 来获取一个布隆过滤器
具体的实现步骤如下:?
首先,需要在 Redis 中创建一个位数组,这可以通过 Redis 的 setbit 命令来实现
然后,当需要添加一个元素到布隆过滤器时,可以通过多个哈希函数,将元素哈希到位数组的不同位置,然后将这些位置的值设置为 1
当需要查询一个元素是否存在于布隆过滤器时,可以通过相同的哈希函数,将元素哈希到位数组的相同位置,然后检查这些位置的值是否都为 1。如果都为 1,则认为元素可能存在于布隆过滤器;如果有任何一个位置的值为 0,则认为元素一定不存在于布隆过滤器
除了Redis,还有其他一些方法可以实现布隆过滤器:?
使用Bitmaps:Bitmaps是一种可以用来操作位数组的数据结构,这是布隆过滤器的基础。例如,setbit命令可以用来设置位数组中的某一位,getbit命令可以用来获取位数组中的某一位,bitcount命令可以用来获取位数组中值为1的位的个数
使用Java库Redisson:Redisson是一个在Java程序中操作Redis的库,它提供了布隆过滤器的实现。例如,可以使用redisson.getBloomFilter("filterName")来获取一个布隆过滤器?
使用Guava库:Guava是Google提供的一个Java库,它也提供了布隆过滤器的实现?
这些方法和Redis实现布隆过滤器的主要区别在于底层实现和使用的技术栈。例如,Redis使用的是内存数据存储和处理,而Guava则是在Java应用程序的JVM内存中实现布隆过滤器。此外,Redis提供的布隆过滤器可以在分布式环境中使用,而Guava提供的布隆过滤器则主要用于单机环境
Redis存储的数据不会丢失,保证数据不丢失的手段主要有以下两个
Redis 中过期的键值对不会立即删除,而是使用以下手段来删除过期键的
惰性删除(Lazy Expire): Redis 不会主动地、周期性地检查和除所有过期的键。惰性删除是指在 Redis 访问某个键值时,才会检查该键是否已经过期,如果已过期,则返回 NULL,并同时删除它
定期删除(Periodic Expire): 每隔一段时间检查一次数据库,随机删除一些过期键。定期删除在2redis.conf 配置文件中配置,如下图所示:
hz 等于 10 表示每秒钟删除 10 次,也就是每 100 毫秒执行一次定期删除
之所以要采用这两种方式来删除是为了保证清除过期数据,不影响 Redis 整体的执行效率