分布式锁
1. 分布式锁要解决的问题
随着业务的发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程,多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的javaAPI并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源访问,这就是分布式锁要解决的问题
2. 解决方案
- 基于数据库实现分布式锁
- 基于缓存(redis等)
- 基于zookeeper
本章我们基于性能最高redis来学习
3. 使用redis实现分布式锁
- 使用setnx上锁,通过del可以释放锁
- 锁一直没有释放,可以设置key的过期时间,自动释放
- 如果在上锁的时候,服务器突然断电,就设置不了过期时间了。其实我们可以边上锁边设置过期时间
set user 10 nx ex 20 nx 表示上锁 ex 表示设置过期时间 20
UUID防止误删锁
问题的出现
解决的思路
我们给锁一个标识(UUID),当Redis需要删除锁时,需要先和uuid对比,相同才能删
java代码实现
package com.google.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Controller; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; import java.util.concurrent.TimeUnit; @RestController public class RedisTestController { @Autowired private RedisTemplate redisTemplate; @GetMapping("/redisTest") public void testLock() { //获取uuid作为锁的标识符 String uuid = UUID.randomUUID().toString(); //1.获取锁,setnx Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS); if (lock) { //2.如果获取到了锁,将num加1 Object value = redisTemplate.opsForValue().get("num"); if(value.equals(null)){ return; } int num = Integer.parseInt(value + ""); //3.将num值加1 redisTemplate.opsForValue().set("num", ++num); String lockUuid = (String)redisTemplate.opsForValue().get("lock"); if(lockUuid.equals(uuid)){ //如果锁的标识相同,则释放锁 //4.释放锁 redisTemplate.delete("lock"); } System.out.println("不为空"); } else { try { //如果没有获取到锁,就等0.1秒再尝试获取锁 Thread.sleep(100); testLock(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
lua脚本保证锁的原子性
锁的实现需要同时满足一下四个条件:
- 互斥性。在任意时刻,只有一个客户能持有锁
- 不会发生死锁,即使有一个客户端在持有锁的期间崩溃,而没有主动释放锁,也能保证后续其他客户能加锁。
- 加锁和解锁必须是同一个客户端,客户端自己不能把别人的锁给解了
- 加锁解锁必须具有原子性
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决