Redisson 不仅仅是分布式锁

Redisson简介

Redisson是一个使用Java编写的开源框架,它提供了一系列分布式数据结构和服务,这些服务能够使Java应用具备分布式应用所需的特性和能力。Redisson的主要目标是将Redis作为Java应用程序的数据存储和缓存层,并且尽可能地利用Redis平台提供的高并发、高可用、高性能、高可扩展性等特点。

Redisson提供的特性包括:

  1. 完善的Redis客户端功能,支持单节点、集群、哨兵模式的自动切换,
  2. 提供了完整的Java原生API,支持所有Redis支持的数据结构以及扩展的的分布式数据结构,
  3. 提供了线程安全的同步/异步Redis操作,支持高性能并发操作,
  4. 提供了分布式实现的超时等待、信号量、倒计时器、锁、消息队列、异步执行等功能。

Redisson的优势

Redisson使用Java编写,易于集成和使用,同时又具有以下几个优势:

  1. 支持Java的所有数据结构:除了Redis原生支持的数据结构外,Redisson还提供了Java数组、Java集合、Java映射、原子变量、可重入锁等数据结构的分布式实现。
  2. 线程安全:Redisson提供了线程安全的同步/异步Redis操作,保证多线程安全。
  3. 高性能:Redisson底层采用Netty网络框架实现,采用序列化机制提高传输效率,支持高并发、高性能的场景。
  4. 易于部署和管理:Redisson支持多种模式,如单节点、主从复制、哨兵、Redis Cluster等,易于部署和管理。

Redisson的数据结构

Redisson支持的数据结构包括:

  1. Redis原生类型:String、List、Set、SortedSet、Hash等。
  2. Java类型:Java数组、Java集合、Java映射、原子变量等。
  3. Redisson特定数据类型:

1. 原子变量

Redisson提供了一系列原子变量,包括AtomicLong、AtomicDouble、AtomicBoolean等。原子变量支持单节点和分布式环境下的原子操作,可以实现计数器、状态标记等功能。

2. 分布式锁

Redisson提供了分布式锁的实现,支持公平锁、非公平锁、可重入锁、读写锁等。

3. 分布式队列

Redisson提供了分布式队列的实现,支持阻塞队列、无界队列、延迟队列等。

4. 分布式集合

Redisson提供了分布式集合的实现,支持HashSet、TreeSet、LinkedHashSet等。

5. 分布式映射

Redisson提供了分布式映射的实现,支持HashMap、TreeMap、LinkedHashMap等。

6. 分布式消息

Redisson提供了分布式消息的实现,支持发布订阅模式、点对点模式等。

Redisson的分布式锁

分布式锁是实现分布式并发控制的一种常见方式,通过在不同进程之间共享同一个锁来避免多个并发进程竞争同一资源时出现的数据竞争问题。Redisson提供了完整的分布式锁的实现,包括公平锁、非公平锁、可重入锁、读写锁等。下面我们分别来介绍这几种锁的特点:

1. 公平锁

公平锁是指在多个线程竞争同一个锁时,按照先来先得的原则获取锁。Redisson实现的公平锁是基于Redis的brpoplpush命令实现的,当一个线程获取锁失败时,会将该线程加入到队列的尾部,并在头部等待锁释放。当锁被释放时,将队列头的线程唤醒,让其获取锁,从而保证了先来先得的原则。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取公平锁对象
RLock fairLock = redisson.getFairLock("my_fair_lock");

// 尝试获取锁,如果获取不到则阻塞等待
fairLock.lock();

try {
    // 这里编写需要被锁保护的操作
} finally {
    // 释放锁
    fairLock.unlock();
}

在上面的示例中,首先获取了 Redisson 实例,然后通过它获取了名为 "my_fair_lock" 的公平锁对象。接下来使用 lock() 方法获取锁,并在需要加锁的代码块中执行操作。在最后使用 unlock() 方法释放锁。

需要注意的是,在 Redisson 中,默认的锁都是公平锁,即所有请求锁的线程会按照请求的顺序依次获取锁,而非公平锁则是无序的,在高并发情况下可能会产生饥饿现象。因此,公平锁通常比非公平锁更可靠,但在某些场景下可能会导致性能问题。

2. 非公平锁

非公平锁是指在多个线程竞争同一个锁时,没有优先级规则,只要锁未被占用,任何线程都可以随机获取锁。Redisson实现的非公平锁是基于Redis的setnx命令实现的,通过setnx命令可以实现原子性的获取锁,但是由于非公平锁没有优先级规则,可能导致某些线程一直获取不到锁,从而产生饥饿现象。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取非公平锁对象
RLock unfairLock = redisson.getLock("my_unfair_lock");

// 尝试获取锁,如果获取不到则阻塞等待
unfairLock.lock();

try {
    // 这里编写需要被锁保护的操作
} finally {
    // 释放锁
    unfairLock.unlock();
}

需要注意的是,在 Redisson 中,默认的锁都是公平锁,即所有请求锁的线程会按照请求的顺序依次获取锁,而非公平锁则是无序的,在高并发情况下可能会产生饥饿现象。因此,非公平锁需要谨慎使用,需要根据具体场景进行选择。

3. 可重入锁

可重入锁是指同一个线程可以多次获取同一个锁而不会死锁。在分布式环境下,可重入锁需要支持跨进程、跨JVM的可重入。Redisson实现的可重入锁是基于Redis的分布式对象实现的,通过在锁上维护一个线程标识和计数器,来实现同一个线程多次获取锁时计数器增加,释放锁时计数器减少的功能。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取可重入锁对象
RReentrantLock reentrantLock = redisson.getReentrantLock("my_reentrant_lock");

// 尝试获取锁,如果获取不到则阻塞等待
reentrantLock.lock();

try {
    // 这里编写需要被锁保护的操作

    // 在可重入锁内可以再次获取锁
    reentrantLock.lock();
    try {
        // 这里编写需要被锁保护的操作
    } finally {
        reentrantLock.unlock();
    }
} finally {
    // 释放锁
    reentrantLock.unlock();
}

在上面的示例中,首先获取了 Redisson 实例,然后通过它获取了名为 "my_reentrant_lock" 的可重入锁对象。接下来使用 lock() 方法获取锁,并在需要加锁的代码块中执行操作。在最后使用 unlock() 方法释放锁。

需要注意的是,在 Redisson 中,可重入锁可以多次被同一线程获取,使用计数器记录次数,但需要注意在每次获取锁后必须在 finally 块中释放锁,否则会导致死锁。此外,如果需要在一段时间内自动释放锁(如程序异常等情况),可以使用带有超时时间的 tryLock() 方法或者 lockInterruptibly() 方法。

4. 读写锁

读写锁是指在多线程对同一个资源进行读和写的场景下,通过加锁和解锁来实现共享资源的访问控制。读写锁分为读锁和写锁,当一个线程获取写锁时,其他线程无法获取读锁和写锁,当一个线程获取读锁时,其他线程可以获取读锁,但无法获取写锁。Redisson实现的读写锁是基于Redis CAS命令实现的,通过实现不同的加锁策略来达到读写锁的目的。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取读写锁对象
RReadWriteLock rwLock = redisson.getReadWriteLock("my_rwlock");

// 写操作加锁
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
    // 执行写操作
} finally {
    writeLock.unlock();
}

// 读操作加锁
RLock readLock = rwLock.readLock();
readLock.lock();
try {
    // 执行读操作
} finally {
    readLock.unlock();
}

在上面的示例中,首先获取了 Redisson 实例,然后通过它获取了名为 "my_rwlock" 的读写锁对象。接下来分别使用 writeLock()readLock() 方法来获取写锁和读锁,然后在需要加锁的代码块中执行 lock() 方法获取锁,执行完操作后使用 unlock() 方法释放锁。

需要注意的是,在执行写操作时必须先获取写锁,而在执行读操作时则可以获取读锁。这是由于 Redisson 读写锁是弱化了可重入性的,即同一线程无法重复获取锁,因此在执行嵌套的写操作时会发生死锁。另外,在使用 Redisson 读写锁时也需要注意避免出现死锁的情况。

Redisson的分布式队列

分布式队列是指将数据存储到某种队列中,并且可以被多个应用程序或进程同时访问。Redisson提供了阻塞队列、无界队列、延迟队列等多种分布式队列的实现,下面我们分别来介绍这几种队列的特点:

1. 阻塞队列

阻塞队列是指当队列为空或者队列已满时,读和写的线程会被阻塞直到队列可用为止。Redisson实现的阻塞队列是基于Redis的brpoplpush命令实现的,通过在列表尾部插入数据,在列表头部弹出数据的方式来实现阻塞队列的功能。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取阻塞队列对象
RBlockingQueue queue = redisson.getBlockingQueue("my_blocking_queue");

// 向队列中添加元素,如果队列已满则阻塞等待
queue.put("element");

// 从队列中取出元素,如果队列为空则阻塞等待
Object element = queue.take();

2. 无界队列

无界队列是指队列的大小没有限制,可以一直存储数据。Redisson实现的无界队列是基于Redis的list命令实现的,通过在列表尾部插入数据,在列表头部弹出数据的方式来实现无界队列的功能。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取无界队列对象
RQueue queue = redisson.getQueue("my_unbounded_queue");

// 向队列中添加元素
queue.offer("element");

// 从队列中取出元素
Object element = queue.poll();

3. 延迟队列

延迟队列是指在队列中存储带有过期时间的数据,在过期时间到达时自动从队列中移除。Redisson实现的延迟队列是基于Redis的zset命令实现的,通过将数据存储到zset中,并设置过期时间作为score,通过定时任务轮询zset来实现延迟队列的功能。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取延迟队列对象
RDelayedQueue<String> delayedQueue = redisson.getDelayedQueue(redisson.getQueue("my_delayed_queue"), new StringCodec());

// 添加延迟元素到队列中,第二个参数是过期时间,单位为秒
delayedQueue.offer("element", 10, TimeUnit.SECONDS);

// 在过期时间到达后,从队列中取出过期元素
String element = delayedQueue.take();

Redisson的分布式集合

分布式集合是指将数据存储在某种集合中,并且可以被多个应用程序或进程同时访问。Redisson提供了HashSet、TreeSet、LinkedHashSet等分布式集合的实现,下面我们分别来介绍这几种集合的特点:

1. HashSet

HashSet是一种无序、不可重复的集合。Redisson实现的HashSet是基于Redis的hash命令实现的,通过在hash数据结构中存储key-value对来实现HashSet的功能。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取 Hash 集合对象
RMap<String, Object> map = redisson.getMap("my_hash_set");

// 向集合中添加元素
map.put("key", "value");

// 删除集合中的元素
Object value = map.remove("key");

2. TreeSet

TreeSet是一种有序、不可重复的集合。Redisson实现的TreeSet是基于Redis的zset命令实现的,通过将元素存储到zset中,并按照score进行排序来实现TreeSet的功能。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取 SortedSet 集合对象
RSortedSet<Integer> set = redisson.getSortedSet("my_tree_set");

// 向集合中添加元素
set.add(1);
set.add(2);
set.add(3);

// 取出排名前三的元素
Collection<Integer> topThree = set.entryRange(0, 2);

3. LinkedHashSet

LinkedHashSet是一种有序、不可重复的集合。Redisson实现的LinkedHashSet是基于Redis的list命令实现的,通过在列表中存储元素,并按照插入顺序进行排序来实现LinkedHashSet的功能。

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取 LinkedSet 集合对象
RList<Object> list = redisson.getList("my_linked_set");

// 向集合中添加元素
list.add("element1");
list.add("element2");
list.add("element3");

// 获取集合中的元素
Object element = list.get(0);

Redisson的调度器

Redisson提供了分布式任务调度的功能,可以支持固定频率执行、延迟执行、定时执行等多种任务调度模式。Redisson的调度器是基于Redis的zset命令实现的,通过将任务存储到zset中,并将执行时间作为score,通过定时任务轮询zset来实现任务调度的功能。

固定频率执行任务

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取任务调度器对象
RScheduledExecutorService executorService = redisson.getExecutorService("my_executor_service");

// 提交周期性任务,任务每隔 5 秒执行一次
RFuture<Void> future = executorService.scheduleAtFixedRateAsync(new Runnable() {
    @Override
    public void run() {
        // 这里编写需要执行的任务逻
    }
}, 0, 5, TimeUnit.SECONDS);

Redisson的可靠订阅

Redisson提供了可靠订阅的功能,支持订阅主题、模式匹配、按照参数订阅等模式。Redisson的可靠订阅是基于Redis的pub/sub命令实现的,通过向Redis发送subscribe/psubscribe命令来实现订阅功能,通过向Redis发送unsubscribe/punsubscribe命令来取消订阅。在使用可靠订阅时,Redisson会启动一个线程用于接收订阅的消息,并通过回调函数将消息推送给应用程序。

以上就是Redisson框架的基本特点和使用方式,如果您想了解更多内容或者深入学习Redisson,请参考Redisson的官方文档。

发布消息

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取主题对象
RTopic<String> topic = redisson.getTopic("my_topic", new StringCodec());

// 发布消息
topic.publish("hello world");

订阅消息

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取主题对象
RTopic<String> topic = redisson.getTopic("my_topic", new StringCodec());

// 订阅消息
int listenerId = topic.addListener(new MessageListener<String>() {
    @Override
    public void onMessage(String channel, String message) {
        // 处理收到的消息
    }
});

// 取消订阅
topic.removeListener(listenerId);

其他

分布式限流器

基于 Redis 的计数器

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取计数器对象
RAtomicLong counter = redisson.getAtomicLong("my_counter");

// 每次请求时增加计数器的值
counter.incrementAndGet();

// 如果计数器的值超过阈值,则限制请求
if (counter.get() > MAX_REQUESTS_PER_SECOND) {
    throw new RateLimitException("Too many requests");
}

基于 Redis 的令牌桶

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取令牌桶对象
RTopic<Set<String>> topic = redisson.getTopic("my_token_bucket", new StringCodec());

// 定义令牌桶容量和令牌生成速率
int capacity = 100;
double rate = 10;

// 启动令牌桶生成器
TokenBucketGenerator generator = new TokenBucketGenerator(topic, capacity, rate);
generator.start();

// 在请求时获取令牌,如果获取不到则限制请求
if (!generator.tryAcquire()) {
    throw new RateLimitException("Too many requests");
}

分布式对象存储

上传文件

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取 Bucket 对象
RBucket<byte[]> bucket = redisson.getBucket("my_bucket");

// 读取文件内容
byte[] content = Files.readAllBytes(Paths.get("/path/to/my/file"));

// 上传文件到 Redis
bucket.set(content);

下载文件

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取 Bucket 对象
RBucket<byte[]> bucket = redisson.getBucket("my_bucket");

// 从 Redis 中下载文件
byte[] content = bucket.get();

// 将文件内容写入本地文件
Files.write(Paths.get("/path/to/my/file"), content);

存储对象

// 定义对象类
public class MyClass {
    private String name;
    private int age;

    // 省略 getter 和 setter
}

// 获取 Redisson 实例
RedissonClient redisson = Redisson.create();

// 获取对象存储对象
RMap<String, MyClass> map = redisson.getMap("my_data_store");

// 存储对象到 Redis
MyClass data = new MyClass();
data.setName("John Doe");
data.setAge(30);
map.put("key", data);

// 从 Redis 中读取对象
MyClass result = map.get("key");
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 一、Redisson概述 什么是Redisson? Redisson是一个在Redis的基础上实现的Java驻内存...
    Java码农阅读 2,815评论 0 37
  • redis官网对分布式锁的说法: 一、Redisson 是什么? 如果你之前是在用 Redis 的话,那使用 Re...
    陈二狗想吃肉阅读 1,522评论 1 3
  • [TOC] 前言 突然觉得想要安稳的度过一生简直可以称之为臆想,想想历史上的盛世,大都不过三四十年,如何能保证自己...
    程柏阅读 416评论 1 0
  • 特别说明: 本人平时混迹于 B 站,不咋回复这里的评论,有问题可以到 B 站视频评论区留言找我视频地址: http...
    撸帝阅读 2,932评论 0 43
  • 简述 使用分布式锁的目的: 提高效率:使用分布式锁避免不同节点重复性的操作,比如:推送、定时任务等 保证正确性:避...
    Jerry_06ed阅读 554评论 0 4