详解Spring Cache使用Redisson分布式锁解决缓存击穿问题(详解九章算法的作者是谁)

网友投稿 399 2022-08-10


详解Spring Cache使用Redisson分布式锁解决缓存击穿问题(详解九章算法的作者是谁)

目录1 什么是缓存击穿2 为什么要使用分布式锁3 什么是Redisson4 Spring Boot集成Redisson4.1 添加maven依赖4.2 配置yml4.3 配置RedissonConfig5 使用Redisson的分布式锁解决缓存击穿

1 什么是缓存击穿

一份热点数据,它的访问量非常大。在其缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。

2 为什么要使用分布式锁

在项目中,当共享资源出现竞争情况的时候,为了防止出现并发问题,我们一般会采用锁机制来控制。在单机环境下,可以使用synchronized或Lock来实现;但是在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个jvm中),所以需要一个让所有进程都能访问到的锁来实现,比如mysql、redis、zookeeper。

3 什么是Redisson

Redisson是一个在Redis的基础上实现的java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

4 Spring Boot集成Redisson

4.1 添加maven依赖

不再需要spring-boot-starter-data-redis依赖,但是都添加也不会报错

org.redisson

redisson-spring-boot-starter

3.17.0

4.2 配置yml

spring:

datasource:

username: xx

password: xxxxxx

driver-class-name: com.mysql.cj.jdbc.Driver

url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=CTT

cache:

type: redis

redis:

databashttp://e: 0

port: 6379 # Redis服务器连接端口

host: localhost # Redis服务器地址

password: xxxxxx # Redis服务器连接密码(默认为空)

timeout: 5000 # 超时时间

4.3 配置RedissonConfig

import com.fasterxml.jackson.annotation.jsonAutoDetect;

import com.fasterxml.jackson.annotation.PropertyAccessor;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.redisson.Redisson;

import org.redisson.api.RedissonClient;

import org.redisson.config.Config;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.cache.CacheManager;

import org.springframework.cache.annotation.EnableCaching;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.redis.cache.RedisCacheConfiguration;

import org.springframework.data.redis.cache.RedisCacheManager;

import org.springframework.data.redis.connection.RedisConnectionFactory;

import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

import org.springframework.data.redis.serializer.RedisSerializationContext;

import org.springframework.data.redis.serializer.RedisSerializer;

import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

import java.util.Random;

@EnableCaching

@Configuration

public class RedissonConfig {

@Value("${spring.redis.host}")

private String host;

@Value("${spring.redis.port}")

private String port;

@Value("${spring.redis.password}")

private String password;

@Bean(destroyMethod = "shutdown") // bean销毁时关闭Redisson实例,但不关闭Redis服务

public RedissonClient redisson() {

//创建配置

Config config = new Config();

/**

* 连接哨兵:config.useSentinelServers().setMasterName("myMaster").addSentinelAddress()

* 连接集群: config.useClusterServers().addNodeAddress()

*/

config.useSingleServer()

.setAddress("redis://" + host + ":" + port)

.setPassword(password)

.setTimeout(5000);

//根据config创建出RedissonClient实例

return Redisson.create(config);

}

@Bean

public CacheManager RedisCacheManager(RedisConnectionFactory factory) {

RedisSerializer redisSerializer = new StringRedisSerializer();

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

// 解决查询缓存转换异常的问题

ObjectMapper om = new ObjectMapper();

om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

/**

* 新版本中om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)已经被废弃

* 建议替换为om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL)

*/

om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

jackson2JsonRedisSerializer.setObjectMapper(om);

// 配置序列化解决乱码的问题

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()

// 设置缓存过期时间 为解决缓存雪崩,所以将过期时间加随机值

.entryTtl(Duration.ofSeconds(60 * 60 + new Random().nextInt(60 * 10)))

// 设置key的序列化方式

.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))

// 设置value的序列化方式

.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));

// .disableCachingNullValues(); //为防止缓存击穿,所以允许缓存null值

RedisCacheManager cacheManager = RedisCacheManager.builder(factory)

.cacheDefaults(config)

// 启用RedisCache以将缓存 put/evict 操作与正在进行的 Spring 管理的事务同步

.transactionAware()

.build();

return cacheManager;

}

}

5 使用Redisson的分布式锁解决缓存击穿

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.company.dubbodemo.entity.User;

import com.company.dubbodemo.mapper.UserMapper;

import com.company.dubbodemo.service.UserService;

import org.redisson.api.RLock;

import org.redisson.api.RedissonCzrrrYPItlient;

import org.springframework.cache.annotation.Cacheable;

import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service

public class UserServiceImpl extends ServiceImpl

implements UserService {

@Resource

private RedissonClient redissonClient;

@Resource

private UserMapper userMapper;

@Override

// 一定要设置sync = true开启异步,否则会导致多个线程同时获取到锁

@Cacheable(cacheNames = "user", key = "#id", sync = true)

public User findById(Long id) {

/**

*

* 加了@Cacheable之后方法体执行说明缓存中不存在所查询的数据

* 获取一把锁,只要锁的名字一样,就是同一把锁

*/

/**

* 注意: 如果设置了lock.lock(10,TimeUnit.SECONDS) 锁过期不会自动续期

* 1、如果我们传递了锁的过期时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间

* 2、如果没有指定锁的超时时间,就使用30000L(LockWatchdogTimeout 看门狗的默认时间)

* 可通过RedissonConfig-->getRedissonClient()-->config.setLockWatchdogTimeout()设置看门狗时间

* 只要占锁成功就会启动一个定时任务【就会重新给锁设置过期时间,新的时间就是看门狗的默认时间】,每隔10s都会自动续期,续期成30s

* 看门狗机制

* 1、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s。不用担心因为业务时间长,锁自动过期被删除

* 2、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除

*

*/

RLock lock = redissonClient.getLock("redissonClient-lock");

// 对第一个线程执行方法体的线程加锁,加了@Cacheable,方法执行之后会将方法的返回值存入缓存,下一个线程直接读取缓存

lock.lock();

User user = new User();

try {

user = userMapper.selectById(id);

} catch (Exception e) {

e.printStackTrace();

} finally {

lock.unlock();

}

return user;

}

}


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:MyBatis拦截器动态替换表名的方法详解(mybatis拦截器获取表名)
下一篇:使用spring(使用spring可以实现声明式事务吗)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~