前边我们有记录Springboot2/3整合redis的方式。是基于Springboot默认集成的Lettuce客户端实现的。
今天我们在项目中集成的redission是在Lettuce之上构建的redis客户端。
Redisson:一个在Jedis和Lettuce之上构建的Redis客户端。提供了一系列分布式Java对象和服务,比如:分布式锁、原子变量、计数器等。Redisson意在通过高层次的抽象使得开发者能够更容易地利用Redis提供的各种功能。
Redisson通过封装底层的Redis命令,使得在Java代码中操作分布式数据结构就像操作本地数据结构一样自然。如果你的应用程序需要分布式数据类型或者锁,Redisson可能是最佳选择。
Redisson提供的以下的主要功能:
分布式对象:
1) 分布式集合(Set、SortedSet、List)
2) 分布式映射(Map)
3) 分布式队列(Queue、Deque)
4) 分布式锁(Lock)
5) 分布式计数器(AtomicLong)
分布式限流:
1) 令牌桶算法(Rate Limiter)
2) 漏桶算法(Rate Limiter)
分布式发布订阅:
1) 发布订阅模式(Pub-Sub)
2) 消息监听器容器(Message Listener Container)
分布式锁和同步:
1) 可重入锁(ReentrantLock)
2) 公平锁(FairLock)
3) 联锁(MultiLock)
4) 红锁(RedLock)
5) 读写锁(ReadWriteLock)
6) 信号量(Semaphore)
7) 闭锁(CountDownLatch)
8) 栅栏(CyclicBarrier)
分布式服务和任务调度:
1) 远程服务(Remote Service)
2) 分布式任务调度器(Task Scheduler)
3) 分布式延迟队列(Delayed Queue)
分布式地理空间索引(Geospatial Index):
1) 地理位置存储
2) 地理位置搜索
分布式布隆过滤器(Bloom Filter)和可布隆过滤器(Bloom Filter)。
分布式缓存:
1) 对Redis进行本地缓存
2) Spring缓存注解支持
分布式连接池:
1) 支持连接池管理和维护
Redis 集群和哨兵支持:
1) 支持Redis集群模式
2) 支持Redis哨兵模式
3) 对于使用Redis集群部署的场景,Redisson可以自动识别和操作集群中的多个节点,保证数据的高可用性和扩展性。而对于使用Redis哨兵模式部署的场景,Redisson可以监控并切换到可用的主从节点,实现高可靠性和容错能力。
Spring 集成:
1) 与Spring框架的无缝集成
2) 支持Spring缓存注解
功能比较多,我这里就不深入研究了,先研究在Springboot框架中集成。
下边我记录一下我在项目中整合redission的全过程。
老规矩,先放一下redission官网:
一:添加POM依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis链接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- redission -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.24.3</version>
</dependency>
二:YML配置
1 :这里的配置主要是参照官方文档,我的redis是哨兵模式,官方文档如下:
我的配置如下所示:
(1):application.yml:
spring:
data:
redis:
# 超时时间
timeout: 10000
# 使用的数据库索引,默认是0
database: 0
#host: 39.99.144.212
#port: 6379
# 密码
password: camellia
###################以下为red1s哨兵增加的配置###########################
sentinel:
master: mymaster # 哨兵主节点名称,自定义
nodes: 39.99.144.212:26379,39.99.144.212:26380,39.99.144.212:26381
password: camellia
##################以下为]ettuce连接池增加的配置###########################
lettuce:
pool:
max-active: 100 #连接池最大连接数(使用负值表示没有限制)
max-idle: 100 #连接池中的最大空闲连接
min-idle: 50 #连接池中的最小空闲连接
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
# 使用redission配置
redisson:
file: classpath:redisson.yml
(2):redisson.yml
spring:
redis:
redisson:
config:
sentinelServersConfig:
idleConnectionTimeout: 10000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
failedSlaveReconnectionInterval: 3000
failedSlaveNodeDetector: !<org.redisson.client.FailedConnectionDetector> {}
password: xxxxxxxxx
lockWatchdogTimeout: 60000 # 配置对象看门狗超时时间(以毫秒为单位)
subscriptionsPerConnection: 5
clientName: null
loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
slaveConnectionMinimumIdleSize: 24
slaveConnectionPoolSize: 64
masterConnectionMinimumIdleSize: 24
masterConnectionPoolSize: 64
readMode: "SLAVE"
subscriptionMode: "SLAVE"
sentinelAddresses:
- "redis://127.0.0.1:26379"
- "redis://127.0.0.1:26380"
- "redis://127.0.0.1:26381"
masterName: "master"
database: 0
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.Kryo5Codec> {}
transportMode: "NIO"
2 :如果你不想使用yml配置,你也可以使用配置类对redission配置
redission.java
package com.modules.redis.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class RedissonConfig {
/**
* 所有对Redisson的使用都是通过RedissonClient对象
*
* @return
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
// 创建配置 指定redis地址及节点信息
Config config = new Config();
// SingleServerConfig 配置方式
//config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(1).setPassword("123456");
List<String> sentinelNodes = new ArrayList<>();
sentinelNodes.add("redis:// 127.0.0.1:26379");
sentinelNodes.add("redis:// 127.0.0.1:26380");
sentinelNodes.add("redis:// 127.0.0.1:26381");
config.useSentinelServers().setPassword("xxxxxxxx").setDatabase(0).setMasterName("xxxxxx").setSentinelAddresses(sentinelNodes);
// 根据config创建出RedissonClient实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}//*/
}
上边的两种配置方式二选一即可。
三:测试一下
// =============================================================
// redission 框架测试
@Autowired
private RedissonClient redissonClient;
@Resource
private ArticleDao articleDao;
/**
* 分布式锁案详解
* http://127.0.0.1:8001/java/redissonLockLock?lock=redissionlocklock
*/
@GetMapping("redissonLockLock")
public void redissonLockLock(String lock)
{
RLock rLock = redissonClient.getLock(lock);
// 使用多线程来模拟多用户并发操作
for (int i = 0; i < 20; i++)
{
final int temp = i;
new Thread(() -> {
System.out.println("开始创建订单:"+temp);
// 生成唯一uuid
String uuid = UUID.randomUUID().toString();
// 获取锁,并设置锁的自动释放时间。
try
{
// 上锁
//waitTime:等待获取锁的最大时间量; leaseTime:锁的自动释放时间(每个线程占用锁的最大时间); unit:时间单位。
// tryLock(10, 30, TimeUnit.SECONDS):此行代码尝试在10秒内获得锁,如果成功,锁将在30秒后自动释放。
boolean tryLock = rLock.tryLock(30000, 10, TimeUnit.MILLISECONDS);
if (tryLock)
{
// 开启看门狗模式,锁到期自动续期
rLock.lock(15, TimeUnit.MILLISECONDS);
// 模拟执行业务逻辑
log.error("开始执行业务逻辑......");
// 获取到锁,做对应的处理。
System.out.println("获取到锁!"+temp);
String username = "redis";
// 将记录写入数据库,这一步可以换成其他操作
String ip = "0.0.0.0";
Browse browse = new Browse();
browse.setUsername(username.toString());
browse.setArticleTitle("test:"+temp);
browse.setIp(ip);
browse.setIsWeixin("0");
int res = articleDao.addBrowse(browse);
System.out.println("redis写入状态:"+res+" - "+temp);
log.error("业务逻辑执行完成......");
}
else
{
log.error("锁已存在......");
}
}
catch (InterruptedException e)
{
//throw new RuntimeException(e);
System.out.println("出错了!");
}
finally
{
// 解锁报错:attempt to unlock lock, not locked by current thread by node id: 544b01e7-babb-4223-b855-00baab04f050 thread-id: 666
// 解决:https://segmentfault.com/a/1190000042576825?sort=newest
if (rLock.isLocked() && rLock.isHeldByCurrentThread())
{
rLock.unlock();
log.error("释放锁完成……");
}
else
{
log.error("锁已过期......");
}
}
}).start();
}
}
/**
* Redisson操作布隆过滤器
* 解决方案:缓存穿透
* 测试:http://127.0.0.1:8001/java/redissonBloomFilter?bloomKey=redissionFilter
*
* @param bloomKey
*/
@GetMapping("redissonBloomFilter")
public void redissonBloomFilter(String bloomKey) {
RBloomFilter<String> rBloomFilter = redissonClient.getBloomFilter(bloomKey);
// 初始化布隆过滤器,初始化预期插入的数据量为200,期望误差率为0.01
rBloomFilter.tryInit(200, 0.01);
// 插入数据
rBloomFilter.add("guanchao");
rBloomFilter.add("时间里的");
rBloomFilter.add("guanchao.site");
// 设置过期时间
rBloomFilter.expire(60, TimeUnit.SECONDS);
// 判断是否存在
boolean victory = rBloomFilter.contains("guanchao");
boolean forward = rBloomFilter.contains("site");
System.out.println(victory); //true
System.out.println(forward); //false
}
/**
* Redisson实现限流
* 具体可以查看接口的限流应用
* 测试:http://127.0.0.1:8001/java/redissonRateLimiter?rateKey=redissionRate
*
* @param rateKey
*/
@GetMapping("redissonRateLimiter")
public String redissonRateLimiter(String rateKey)
{
RRateLimiter rateLimiter = redissonClient.getRateLimiter(rateKey);
//创建限流器,设置限流策略,例如每秒钟不超过10个请求
rateLimiter.trySetRate(RateType.OVERALL, 1, 1, RateIntervalUnit.SECONDS);
// 允许请求次数
String availCount = "-1";
// 4. 尝试获取许可证,超时时间为0表示不等待
if (rateLimiter.tryAcquire(0, 1, TimeUnit.SECONDS))
{
availCount = StrUtil.toString(rateLimiter.availablePermits());
// 如果获取到许可证,执行业务逻辑
System.out.println("请求被允许:"+availCount);
}
else
{
// 如果无法获取到许可证,执行其他逻辑或者抛出异常
System.out.println("请求被限流");
}
return availCount;
}
/**
* Redisson的可重入锁操作
* 测试:http://127.0.0.1:8001/java/redissonLock?lockKey=redissionLock
*
* @param lockKey
* @return
*/
@GetMapping("redissonLock")
public String redissonLock(String lockKey)
{
RLock rLock = redissonClient.getLock(lockKey);
for (int i = 0; i < 5; i++)
{
new Thread(() -> {
rLock.lock();
try
{
System.out.println(Thread.currentThread() + "-" + System.currentTimeMillis() + "-" + "获取了锁");
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
rLock.unlock();
}
}).start();
}
return "success";
}
/**
* Redisson消息发布订阅操作
* 消息监听器:详见TopicListener.java
* 测试:http://127.0.0.1:8001/java/redissonTopic?topicKey=redissionTopic
*
* @param topicKey
*/
@GetMapping("redissonTopic")
public String redissonTopic(String topicKey)
{
RTopic rTopic = redissonClient.getTopic(topicKey);
String msgId = UUID.fastUUID().toString();
Map<Object, Object> map = new HashMap();
map.put("id", msgId);
map.put("msg", "消息:发布订阅测试");
long victory = rTopic.publish(map);
System.out.println("victory:"+victory);
return StrUtil.toString(victory);
}
/**
* Redisson操作Queue
* 测试:http://127.0.0.1:8001/java/redissonQueue?queueKey=redissionQueue
*
* @param queueKey
*/
@GetMapping("redissonQueue")
public void redissonQueue(String queueKey) {
RQueue<String> rQueue = redissonClient.getQueue(queueKey);
// 向队列中添加值
rQueue.add("guanchao.site-queue1-111111");
rQueue.add("guanchao.site-queue2-222222");
// 设置过期时间
rQueue.expire(30, TimeUnit.SECONDS);
// 取值
String value = rQueue.poll();
System.out.println("value = " + value);
// 删除数据,刚刚读取的数据
rQueue.remove();
// 获取队列信息
RQueue<Object> queueValue = redissonClient.getQueue(queueKey);
System.out.println("queueValue = " + queueValue);
}
/**
* Redisson操作map
* 测试:http://127.0.0.1:8001/java/redissionMap?mapKey=redissionMap
*
* @param mapKey
*/
@GetMapping("redissionMap")
public void redissionMap(String mapKey) {
// 创建Map对象
RMap<String, String> map = redissonClient.getMap(mapKey);
// 添加键值对
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
// 设置过期时间
map.expire(30, TimeUnit.SECONDS);
// 获取值
String value = map.get("key1");
System.out.println(value);
// 删除键值对
String removedValue = map.remove("key2");
System.out.println(removedValue);
// 获取Map大小
int size = map.size();
System.out.println(size);
}
/**
* Redisson操作Set
* 测试:http://127.0.0.1:8001/java/redissonSet?setKey=redissionSet
*
* @param setKey
*/
@GetMapping("redissonSet")
public void redissonSet(String setKey)
{
RSet<Object> set = redissonClient.getSet(setKey);
// 添加数据
set.add("guanchao.site-set1");
set.add("guanchao.site-set2");
// 设置过期时间
set.expire(30, TimeUnit.SECONDS);
System.out.println(set);
//通过key取value值
RSet<Object> setValue = redissonClient.getSet(setKey);
System.out.println("setValue = " + setValue);
}
/**
* Redisson操作List
* 测试:http://127.0.0.1:8001/java/redissonList?listKey=redissionList
*/
@GetMapping("redissonList")
public void redissonList(String listKey) {
RList<String> list = redissonClient.getList(listKey);
// 使用add方法向List中添加元素
list.add("guanchao.site-list1");
list.add("guanchao.site-list2");
list.add("guanchao.site-list3");
// list 设置过期时间
list.expire(30, TimeUnit.SECONDS);
// 获取List中的元素
String s = list.get(0);
System.out.println("s = " + s);
// 获取列表长度
int size = list.size();
System.out.println("size = " + size);
// 获取List中的元素
Object object = redissonClient.getList(listKey).get(1);
System.out.println("object = " + object);
}
/**
* 通用对象桶,我们用来存放任类型的对象
* Redisson将Redis中的字符串数据结构封装成了RBucket,
* 通过RedissonClient的getBucket(key)方法获取一个RBucket对象实例,
* 通过这个实例可以设置value或设置value和有效期。
* 测试:http://127.0.0.1:8001/java/redissonBucket?key=xk&value=xxkfz
*
* @return
*/
@GetMapping("redissonBucket")
public String redissonBucket(String key, String value) {
RBucket<String> rBucket = redissonClient.getBucket(key);
rBucket.set(value, 30, TimeUnit.SECONDS);
return redissonClient.getBucket(key).get().toString();
}
以上是我全部的测试代码,不太建议使用redission做发布订阅操作。我这里也只是演示了发布的代码。订阅的部分并没有演示。
操作起来相对来说还是比较容易的。
四:简单封装一个redission工具类
闲着没啥事,寻思封装一个redissionUtils工具类叭,用着更方便点。
但是,封装完了发现,还不如直接用,我TM……
写都写了,记录一下叭:
package com.modules.redis.utils;
import jakarta.annotation.Resource;
import org.redisson.api.*;
import org.redisson.client.codec.StringCodec;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
//@Component
public class RedissionUtils
{
/**
* 构造函数
*/
public RedissionUtils() {}
/**
* 默认缓存时间
*/
private static final Long DEFAULT_EXPIRED = 32000L;
/**
* 自动装配redisson client对象
*/
@Resource
private RedissonClient redissonClient;
/**
* 用于操作key
* @return RKeys 对象
*/
public RKeys getKeys() {
return redissonClient.getKeys();
}
/**
* 移除缓存
*
* @param key
*/
public void delete(String key) {
redissonClient.getBucket(key).delete();
}
/**
* 获取getBuckets 对象
*
* @return RBuckets 对象
*/
public RBuckets getBuckets() {
return redissonClient.getBuckets();
}
/**
* 读取缓存中的字符串,永久有效
*
* @param key 缓存key
* @return 字符串
*/
public String getStr(String key) {
RBucket<String> bucket = redissonClient.getBucket(key);
return bucket.get();
}
/**
* 缓存字符串
*
* @param key
* @param value
*/
public void setStr(String key, String value) {
RBucket<String> bucket = redissonClient.getBucket(key);
bucket.set(value);
}
/**
* 缓存带过期时间的字符串
*
* @param key 缓存key
* @param value 缓存值
* @param expired 缓存过期时间,long类型,必须传值
*/
public void setStr(String key, String value, long expired) {
RBucket<String> bucket = redissonClient.getBucket(key, StringCodec.INSTANCE);
bucket.set(value, expired <= 0L ? DEFAULT_EXPIRED : expired, TimeUnit.SECONDS);
}
/**
* string 操作,如果不存在则写入缓存(string方式,不带有redisson的格式信息)
*
* @param key 缓存key
* @param value 缓存值
* @param expired 缓存过期时间
*/
public Boolean setIfAbsent(String key, String value, long expired) {
RBucket<String> bucket = redissonClient.getBucket(key, StringCodec.INSTANCE);
return bucket.trySet(value, expired <= 0L ? DEFAULT_EXPIRED : expired, TimeUnit.SECONDS);
}
/**
* 如果不存在则写入缓存(string方式,不带有redisson的格式信息),永久保存
*
* @param key 缓存key
* @param value 缓存值
*/
public Boolean setIfAbsent(String key, String value) {
RBucket<String> bucket = redissonClient.getBucket(key, StringCodec.INSTANCE);
return bucket.trySet(value);
}
/**
* 判断缓存是否存在
*
* @param key
* @return true 存在
*/
public Boolean isExists(String key) {
return redissonClient.getBucket(key).isExists();
}
/**
* 获取RList对象
*
* @param key RList的key
* @return RList对象
*/
public <T> RList<T> getList(String key) {
return redissonClient.getList(key);
}
/**
* 获取RMapCache对象
*
* @param key
* @return RMapCache对象
*/
public <K, V> RMapCache<K, V> getMap(String key) {
return redissonClient.getMapCache(key);
}
/**
* 获取RSET对象
*
* @param key
* @return RSET对象
*/
public <T> RSet<T> getSet(String key) {
return redissonClient.getSet(key);
}
/**
* 获取RScoredSortedSet对象
*
* @param key
* @param <T>
* @return RScoredSortedSet对象
*/
public <T> RScoredSortedSet<T> getScoredSortedSet(String key)
{
return redissonClient.getScoredSortedSet(key);
}
}
以上大概就是Springboot3整合redission的基本过程以及简单使用。
详细的使用,后边会记录。
有好的建议,请在下方输入你的评论。