有序集合(sorted sets)使用示例代码如下 。
RSortedSet<Student> studentSortedSet = redisson.getSortedSet("studentSortedSet");studentSortedSet.add(jack);studentSortedSet.add(tom);// 通过key获取valueredisson.getSortedSet("studentSortedSet");
布隆过滤器布隆过滤器(Bloom Filter)是1970年由布隆提出的 。它实际上是一个很长的二进制向量和一系列随机映射函数 。布隆过滤器可以用于检索一个元素是否在一个集合中 。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难 。更多布隆过滤器的内容,请通过搜索引擎了解更多 。
Redission提供了布隆过滤器的实现,可以直接使用,示例代码如下 。
RBloomFilter seqIdBloomFilter = redisson.getBloomFilter("seqId");// 初始化预期插入的数据量为10000000和期望误差率为0.01seqIdBloomFilter.tryInit(10000000, 0.01);// 插入部分数据seqIdBloomFilter.add("123");seqIdBloomFilter.add("456");seqIdBloomFilter.add("789");// 判断是否存在System.out.println(seqIdBloomFilter.contains("123"));System.out.println(seqIdBloomFilter.contains("789"));System.out.println(seqIdBloomFilter.contains("100"));
分布式锁Redission提供了强大的分布式锁实现,使用简单、安全 。下面模拟多线程竞争分布式锁,示例代码如下 。
for (int i = 0; i < 5; i++) {new Thread(new Runnable() {@Overridepublic void run() {final String lockKey = "abc";RLock lock = redisson.getLock(lockKey);boolean hasLocked = lock.tryLock();System.out.println(Thread.currentThread().getName() + ":" + hasLocked);}}).start();}
Redisson分布式锁解决方案使用Redis实现分布式锁,一般的实现是使用setnx命令,但是这种实现方式在高并发且并发安全控制非常高的情况是有问题的,下面从三个方面分析这些问题 。
•不具备可重入性
在执行setnx命令时,通常采用业务上指定的名称作为key名,用时间或随机值作为value来实现 。这样的实现方式不具备追踪请求线程的能力,同时也不具备统计重入次数的能力,甚至有些实现方式都不具备操作的原子性 。当遇到业务上需要在多个地方用到同样一个锁的时候,很显然使用不具有可重入的锁会很容易发生死锁的现象 。特别是在有递归逻辑的场景里,发生死锁的几率会更高 。Java并发工具包里的Lock对象和sychronized语块都具有可重入性,对于经常使用这些工具的人来说,往往会很容易忽略setnx的这个缺陷 。
•不支持续约
在分布式环境中,为了保证锁的活性和避免程序宕机造成的死锁现象,分布式锁往往会引入一个失效时间,超过这个时间则认为自动解锁 。这样的设计前提是开发人员对这个自动解锁时间的力度有一个很好的把握,太短了可能会出现任务没做完锁就失效了,而太长了在出现程序宕机或业务节点挂掉时,其它节点需要等很长时间才能恢复,而难以保证业务的SLA 。setnx的设计缺乏一个延续有效期的续约机制,无法保证业务能够先工作做完再解锁,也不能确保在某个程序宕机或业务节点挂掉的时候,其它节点能够很快的恢复业务处理能力 。
•不具备阻塞的能力
平常大家多少都接触过的锁,由于加锁策略(Locking Strategy)的差别,使得每种锁都有各自不同的特性 。但是在通常情况下这些锁都具备两个共性:一是互斥性,二是阻塞性 。互斥性是指在任何时刻最多只能有一个线程获得通行的资格 。阻塞性是指的在有竞争的情况下,未获取到资源的线程会停止继续操作,直到成功获取到资源或取消操作 。很显然setnx命令只提供了互斥的特性,却没有提供阻塞的能力 。虽然在业务代码里可以引入自旋机制来进行再次获取,但这仅仅是把原本应该在锁里实现的功能搬到了业务代码里,通过增加业务代码的复杂程度来简化锁的实现似乎显得有点南辕北辙 。
Redisson的分布式锁在满足以上三个基本要求的同时还增加了线程安全的特点 。利用Redis的Hash结构作为储存单元,将业务指定的名称作为key,将随机UUID和线程ID作为field,最后将加锁的次数作为value来储存 。同时UUID作为锁的实例变量保存在客户端 。将UUID和线程ID作为标签在运行多个线程同时使用同一个锁的实例时,仍然保证了操作的独立性,满足了线程安全的要求 。
加锁时通过Lua脚本先检查锁是否存在,如不存在则创建hash相关字段并设定过期时间后返回,这表示加锁成功 。如果该hash字段已经存在,再检查随机字段和线程id是否一致 。如果一致则递增value的值并重新更新过期时间后返回,此时表示同一节点同一线程再次成功加锁,从而保证了可重入性 。如果hash存在且字段不一致,说明其他节点或线程已经拥有了这个锁 。因此Lua脚本返回这个hash的当前有效期 。当结果返回到该客户端后,如果加锁成功,则通过线程池依照设定好的参数定时执行续约,最后通知请求线程继续后续操作 。如果加锁没有成功,则监听一个以这个key为后缀的pubsub频道,直到收到解锁消息后再次重试 。
推荐阅读
- 前端杂谈:浅聊GMS
- 如何恢复微信聊天记录?简单几步,轻松搞定 快速恢复微信聊天记录
- 网线也有高低?聊聊网线的差别
- 国人开源了一款超好用的 Redis 客户端,真香
- 这可能是目前最好看的Redis可视化管理客户端了
- 浅谈ARP地址解析协议
- Redis源码剖析之SDS
- 手写Redis分布式锁
- 知其然更要知其所以然,聊聊SQLite软件架构
- RediSearch 2.0 GA 发布,高性能全文搜索引擎