赞
踩
随着分布式系统的广泛应用,解决分布式锁、分布式数据结构、分布式任务调度等问题变得尤为关键。Redisson,作为一个基于Redis的Java驻内存数据网格(In-Memory Data Grid)和操作库,提供了丰富的分布式服务和工具,能够极大地简化分布式应用的开发。本文将介绍Redisson的应用,探讨如何利用Redisson提高分布式系统的效率和可靠性。
Redis是一款内存高速缓存数据库;
数据模型为:key - value,非关系型数据库使用的存储数据的格式;
可持久化:将内存数据在写入之后按照一定格式存储在磁盘文件中,宕机、断电后可以重启redis时读取磁盘中文件恢复缓存数据;
分布式:当前任务被多个节点切分处理,叫做分布式处理一个任务。单个服务器内存,磁盘空间有限,无法处理海量的缓存数据,必须支持分布式的结构;
SpringBoot 2.x引入Redis缓存
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
样例:采用Redis缓存。
Map<Long, List<Catelog2Vo>> listMap = null;
String category= (String) redisUtils.get("Category");
if(StringUtils.isEmpty(category)){
listMap = getCategoryJsonFromDb();
String s = JSON.toJSONString(listMap);
redisUtils.set("Category", s);
}else {
listMap = JSON.parseObject(category, new TypeReference<Map<Long, List<Catelog2Vo>>>(){});
}
return listMap;
指查询一个一定不存在的数据,由于缓存是不命中,将查询数据库,但是数据库也无此记录。我们没有将查询的null写入缓存,导致不存在的数据每次请求数据存储层,失去缓存的意义。
风险:利用不存在的数据进行攻击,数据库压力瞬时增大,最终导致崩溃
解决方法:null结果缓存,并加入短暂过期时间 或者 采用布隆过滤器
对上诉样例改造:
if(listMap != null){
String s = JSON.toJSONString(listMap);
redisUtils.set("CatalogJson", s, 24*60*60);
}
else{
redisUtils.set("CatalogJson", null, 24*60*60);
}
总结:通过设置过期时间和缓存null来解决缓存穿透问题,但是还是会遇到高并发导致服务器雪崩问题
指我们设置的缓存时key采用相同的过期时间,导致缓存在某一时刻同时失效,请求全部转换到存储层DB,存储层瞬间压力过重导致雪崩。
解决方法:原有的失效时间基础上增加一个随机值,比如1-10分钟随机,这样每一个缓存的过期时间的重复率就会降低,就能大概率避免集体失效的事件。
对上诉代码改造:
int randomTime = (int)(1+Math.random()*(10-1+1));//随机数要自己改造
if(listMap != null){
String s = JSON.toJSONString(listMap);
redisUtils.set("CatalogJson", s, 24*60*60 + randomTime);
}
else{
redisUtils.set("CatalogJson", null, 24*60*60 + randomTime);
}
总结:解决了缓存穿透和缓存雪崩问题,但是还是存在某个时间点出现的缓存击穿问题
缓存击穿:对于一些设置过期时间的key,如何这些key可能会在某些时间点被高并发访问,是一个非常热点的数据,如果这个key在大量请求同时进来钱正好失效,那么所有的key数据查询到落到存储层DB。我们称为缓存击穿。
解决方法: 加锁,大量并发只让一个去查,其他人等待,查到以后释放锁。其他人获取到锁,先查询缓存,就有数据了,不用去存储层查询。
本地锁只锁当前进程,无法完全锁住高并发。
synchronized (this){
//保证锁的原子性
}
加锁保证原子性:
String uuid = UUID.randomUUID().toString();
boolean lock = redisTemplate.opsForValue().setIfAbsent("category_lock", uuid, 120, TimeUnit.SECONDS);
删锁保证原子性:采用lua脚本
String script ="if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Long lockInt = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("category_lock"), uuid);
通过以上redis在分布式操作的限制,我们可以通过Redission 来简化分布式应用的开发难度。
官网: https://redisson.org/
github: https://github.com/redisson/redisson#quick-start
首先,需要在项目中引入Redisson的依赖。可以通过Maven或Gradle等构建工具进行引入。以下是一个基本的Maven依赖配置:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.6</version> <!-- 请使用最新版本 -->
</dependency>
在使用Redisson之前,需要配置Redisson的连接信息,包括Redis服务器地址、端口号等。通常,这些配置信息可以通过Config对象进行设置。以下是一个简单的配置示例:
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
分布式锁是在分布式环境中保障资源访问安全的关键。Redisson提供了简单易用的分布式锁实现。以下是一个简单的分布式锁的应用示例:
RLock lock = redisson.getLock("myLock");
try {
lock.lock();
// 执行临界区代码
} finally {
lock.unlock();
}
Redisson支持多种分布式集合,如分布式List、Set、Map等。通过这些集合,可以方便地在分布式环境中进行数据共享和操作。以下是一个分布式Set的示例:
RSet<String> set = redisson.getSet("mySet");
set.add("element1");
set.add("element2");
Redisson还提供了分布式任务调度的功能,可以方便地在分布式环境中调度任务。以下是一个简单的任务调度的示例:
RScheduledExecutorService executorService = redisson.getExecutorService("myExecutorService");
RFuture<Void> future = executorService.schedule(() -> {
// 执行任务逻辑
}, 10, TimeUnit.SECONDS);
Redisson还支持分布式对象的操作,如分布式AtomicLong、CountDownLatch等。通过这些对象,可以实现更复杂的分布式场景。以下是一个分布式AtomicLong的示例:
RAtomicLong atomicLong = redisson.getAtomicLong("myAtomicLong");
atomicLong.incrementAndGet();
通过以上简单的示例,我们可以看到Redisson提供了丰富而强大的API,极大地简化了分布式应用的开发难度。在实际应用中,还可以结合Redisson的监控和统计功能,更好地了解分布式系统的运行状况。然而,使用Redisson时需注意配置合理的Redis集群,以及合理设计分布式锁的范围,确保系统的性能和可靠性。Redisson的广泛应用为分布式系统的开发提供了便利,使得开发者可以更专注于业务逻辑的实现,而不用过多关注分布式环境的复杂性。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。