Bloqueio distribuído + problema de cache distribuído
- No modo de microsserviço, vários serviços idênticos precisam manter a consistência dos dados para o banco de dados. Neste momento, ele precisa evoluir de um bloqueio local para um bloqueio distribuído.
- Esta postagem do blog continuamente levanta questões e resolve ideias de uma forma avançada, melhora o código passo a passo e realiza a função de bloqueio distribuída com alta confiabilidade.
Use o comando redis set com o parâmetro NX (não existe) para realizar o bloqueio distribuído
NX: somente quando não existe, pode ser definido; conjunto bem-sucedido retornará OK, retorno nulo sem sucesso
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
Fase dois independente mais o tempo de expiração do bloqueio distribuído
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
if(aBoolean){
//加锁成功 执行业务
//2、设置过期时间
stringRedisTemplate.expire("lock",30, TimeUnit.SECONDS);
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
Fase três bloqueio atômico e tempo de expiração definido
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock",30,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//删除锁
stringRedisTemplate.delete("lock");
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
Fase 4 excluir bloqueio para permissão uuid correspondência
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
String lock = stringRedisTemplate.opsForValue().get("lock");
if(uuid.equals(lock)){
//删除自己的锁
stringRedisTemplate.delete("lock");
}
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
Fase 5 do script Lua excluir a operação atômica do bloqueio
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = this.getDataFromDB();
//获取值 + 对比 + 删除 必须是原子操作 lua脚本解锁
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1])" +
"else " +
" return 0 " +
"end";
Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
return dataFromDB;
}else {
//加锁失败 重试 自旋
return getCatalogJsonFromDBWithRedisLock();
}
}
Resultado final da fase 6
Independentemente de o negócio ser concluído corretamente, exclua os bloqueios estabelecidos por você
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
//1、占分布式锁。去redis占坑 并设置过期时间 必须是同步的 原子的
String uuid = UUID.randomUUID().toString();
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock",uuid,300,TimeUnit.SECONDS);
if(aBoolean){
//加锁成功 执行业务
Map<String, List<Catelog2Vo>> dataFromDB = null;
try {
dataFromDB = this.getDataFromDB();
}finally {
//获取值 + 对比 + 删除 必须是原子操作 lua脚本解锁
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then " +
" return redis.call('del', KEYS[1])" +
"else " +
" return 0 " +
"end";
Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList("lock"), uuid);
}
return dataFromDB;
}else {
//加锁失败 重试 自旋
//睡眠
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCatalogJsonFromDBWithRedisLock();
}
}
Use a estrutura Redisson para implementar bloqueios distribuídos
Se você usar o redis manualmente para implementar bloqueios distribuídos, precisará escrever instruções de bloqueio atômico e script lua de desbloqueio atômico antes de cada processo de negócios, o que é muito complicado, e a estrutura de uso de bloqueios distribuídos Redisson pode ser mais conveniente de implementar. A camada inferior de Reddison implementa o pacote JUC Portanto, pode ser perfeitamente conectado ao uso do JUC.
Introduzir dependências
<!--引入redis的分布式锁 分布式对象框架redison-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
Configurar o cliente redissonClient
@Configuration
public class MyRedissonConfig {
/**
* 所有对Redisson的使用 都是通过RedissonClient对象
* @return
* @throws IOException
*/
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() throws IOException{
//1、创建配置 redisson包下的config
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.231.1:6379");
//2、根据config创建出RedissonClient示例
return Redisson.create(config);
}
}
Use bloqueios de redisson para implementar bloqueios distribuídos
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedissonLock() {
//1、占分布式锁。redisson
/**
* 锁的名字 决定了锁的粒度,越细越快
* 锁的粒度约定: 具体缓存的是某个数据 例如 11号商品 product-11-lock
*/
RLock lock = redissonClient.getLock("catalogJson-lock");
lock.lock();
Map<String, List<Catelog2Vo>> dataFromDB = null;
try{
//加锁成功 执行业务
dataFromDB = this.getDataFromDB();
}finally{
lock.unlock();
}
return dataFromDB;
}