点一下关注吧!!!非常感谢!!持续更新!!!
🚀 AI 篇持续更新中!(长期更新)
AI 炼丹日志-30-新发布【1T 万亿】参数量大模型!Kimi‑K2 开源大模型解读与实践,持续打造实用 AI 工具指南!📐🤖
💻 Java 篇正式开启!(300 篇)
目前 2025 年 07 月 21 日更新到:
Java-77 深入浅出 RPC Dubbo 负载均衡全解析:策略、配置与自定义实现实战 MyBatis 已完结,Spring 已完结,Nginx 已完结,Tomcat 已完结,分布式服务正在更新!深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300 篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT 案例 详解
章节内容
上节我们完成了:
Redis 缓存相关的概念
缓存穿透、缓存击穿、数据不一致性等
HotKey、BigKey 等问题
针对上述问题提出一些解决方案
乐观锁深入解析
基本原理
乐观锁是一种并发控制机制,其核心思想是 CAS(Compare And Swap,比较并交换)。这种锁机制假设多个事务或线程在大多数情况下不会产生冲突,因此不需要加锁,而是通过版本控制来实现并发控制。
实现特点
非互斥性:乐观锁不会阻塞其他线程的访问,各线程可以同时读取数据
无锁等待:避免了传统锁机制中线程排队等待锁释放的资源消耗
重试机制:当检测到数据被修改时,操作会失败并需要重试,这可能导致一定性能开销
典型工作流程
读取数据时记录版本号或时间戳
修改数据前再次检查版本号是否变化
如果版本一致则提交修改并更新版本
如果版本不一致则丢弃当前修改并重试
应用场景
高并发读操作:系统读取远多于写入时特别适用
分布式系统:减少跨节点锁带来的性能问题
电商库存管理:多个用户同时抢购同一商品时
版本控制系统:如 Git 的合并冲突处理
优劣分析
优势:
响应速度快,适合低冲突场景
不会导致死锁问题
系统吞吐量通常较高
劣势:
冲突率高时重试开销大
需要应用程序处理重试逻辑
不保证操作一定成功
技术实现示例
在 Java 中,AtomicInteger 等原子类就是基于 CAS 实现的乐观锁机制:
AtomicInteger counter = new AtomicInteger(0);
counter.compareAndSet(expectedValue, newValue); // 典型的CAS操作
复制代码
在数据库层面,可以通过版本号字段实现乐观锁:
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE id = 123 AND version = 5 -- 确保版本未变
复制代码
Watch 实现
Redis 乐观锁实现详解
Redis 的 WATCH
命令是实现乐观锁的关键机制,它允许我们在执行事务前监视一个或多个键,如果在事务执行期间这些键的值被修改,整个事务就会被取消。以下是更为详细的实现步骤和说明:
监控阶段
使用 WATCH
命令监控指定的键(如:WATCH my_counter
)
这个键可以是一个计数器、库存量或任何需要原子性更新的值
监控会持续到事务执行完成或取消
获取当前值
通过 GET
命令获取被监控键的当前值(如:GET my_counter
)
将这个值存储在客户端本地以备后续计算使用
事务准备阶段
使用 MULTI
命令开始一个事务块
在事务中执行修改操作(如:INCR my_counter
或 SET my_counter new_value
)
可以包含多个操作命令,它们将作为一个原子单元执行
事务执行阶段
使用 EXEC
命令执行事务
如果在 WATCH
和 EXEC
之间键的值未被修改,事务会成功执行
如果键的值被其他客户端修改,事务会返回 (nil)
表示执行失败
失败处理
当事务失败时,应该重新尝试整个流程(从 WATCH
开始)
通常需要设置最大重试次数以避免无限循环
应用场景举例:
电商系统中的库存扣减
分布式环境下的计数器
抢购系统中的商品限量购买
代码示例(伪代码):
WATCH inventory
current = GET inventory
if current > 0:
MULTI
DECR inventory
EXEC
else:
UNWATCH
复制代码
wacth 实现
暂时就先忽略编码规范的内容,就先实现即可。具体编写逻辑如下:
public class Test02 {
public static void main(String[] args) {
String redisKey = "lock";
ExecutorService executor = Executors.newFixedThreadPool(20);
try {
Jedis jedis = new Jedis("h121.wzk.icu", 6379);
jedis.del(redisKey);
jedis.set(redisKey, "0");
jedis.close();
} catch (Exception e) {
e.printStackTrace();
}
for (int i = 0; i < 300; i ++) {
executor.execute(() -> {
Jedis jedis = null;
try {
jedis = new Jedis("h121.wzk.icu", 6379);
jedis.watch(redisKey);
String redisValue = jedis.get(redisKey);
int value = Integer.valueOf(redisValue);
String userInfo = UUID.randomUUID().toString();
if (value < 20) {
Transaction tx = jedis.multi();
tx.incr(redisKey);
List<Object> list = tx.exec();
if (list != null && !list.isEmpty()) {
System.out.println("获取锁成功, 用户信息: " + userInfo + " 成功人数: " + (value + 1));
}
} else {
System.out.println("秒杀结束!");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != jedis) {
jedis.close();
}
}
});
}
executor.shutdown();
}
}
复制代码
运行之后,会看到已经在进行争抢了:
获取锁成功, 用户信息: e6e06770-f274-4d89-8369-65babc2e3073 成功人数: 1
获取锁成功, 用户信息: 2cc2803b-085e-47ee-9fe6-4bbe1f694fd5 成功人数: 2
获取锁成功, 用户信息: 525ad22c-abb2-4f94-868a-cca981f9d768 成功人数: 3
获取锁成功, 用户信息: 9af67396-798e-4e09-b524-6ddc5e1673ec 成功人数: 4
···省略
秒杀结束!
获取锁成功, 用户信息: dba287f8-65f0-4da8-a131-05304164b3aa 成功人数: 18
秒杀结束!
获取锁成功, 用户信息: 05c5c5f9-f9cd-48b3-a266-c4ff3f256814 成功人数: 20
秒杀结束!
复制代码
SETNX
setnx 详细介绍
基本概念
setnx(SET if Not eXists)是 Redis 提供的一个原子性操作命令,用于实现分布式锁。当且仅当 key 不存在时,将 key 的值设为 value;若 key 已经存在,则不做任何操作。
应用场景
1. 共享资源互斥
在分布式系统中,当多个进程/服务需要互斥地访问某个共享资源时(如数据库中的某条记录、文件系统中的某个文件等),可以使用 setnx 实现互斥访问。
示例场景:
电商系统中的库存扣减
秒杀系统中的商品抢购
支付系统的订单处理
2. 共享资源串行化
当需要对共享资源的访问进行有序控制时,setnx 可以确保同一时刻只有一个客户端能够操作该资源,其他客户端必须等待。
3. 单应用中的锁
在单进程多线程环境下,可以使用:
synchronized(Java 内置锁)
ReentrantLock(可重入锁)
但在分布式环境下,这些单机锁机制无法满足需求。
4. 分布式应用中的锁
在分布式系统中,由于涉及多个进程(可能部署在不同机器上),需要使用分布式锁来解决:
实现原理
Redis 的 setnx 命令特别适合实现分布式锁,这是因为:
Redis 的单线程特性保证了命令执行的原子性
setnx 操作本身是原子的,不存在竞态条件
通过设置 key 的过期时间可以避免死锁
典型实现方式
获取锁:SETNX lock_key unique_value
设置过期时间:EXPIRE lock_key timeout
释放锁:通过 Lua 脚本保证原子性删除
注意事项:
需要确保设置值和设置过期时间是原子操作
每个客户端应该使用唯一的 value 来标识自己
需要合理设置锁的超时时间
与其他方案的对比
相比 Zookeeper 等分布式协调服务实现的分布式锁,基于 Redis 的 setnx 实现:
优点:性能更高,实现简单
缺点:可靠性略低,需要处理锁续期等问题
SETNX 实现
获取锁方式 1 SET
public boolean getLock(String lockKey,String requestId,int expireTime) {
// NX:保证互斥性
// hset 原子性操作 只要lockKey有效 则说明有进程在使用分布式锁
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
if("OK".equals(result)) {
return true;
}
return false;
}
复制代码
获取锁方式 2 SETNX
public boolean getLock(String lockKey,String requestId,int expireTime) {
Long result = jedis.setnx(lockKey, requestId);
if(result == 1) {
// 成功设置 进程down 永久有效 别的进程就无法获得锁
jedis.expire(lockKey, expireTime);
return true;
}
return false;
}
复制代码
释放锁方式 1 del
注意,当调用 del 方法时候,如果这把锁已经不属于当前客户端了,比如已经过期了,而别的人拿到了这把锁,此时删除就会导致释放掉了别人的锁。
public static void releaseLock(String lockKey,String requestId) {
if (requestId.equals(jedis.get(lockKey))) {
jedis.del(lockKey);
}
}
复制代码
释放锁方式 2 lua
public static boolean releaseLock(String lockKey, String requestId) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return
redis.call('del', KEYS[1]) else return 0 end";
Object result = jedis.eval(script, Collections.singletonList(lockKey),
Collections.singletonList(requestId));
if (result.equals(1L)) {
return true;
}
return false;
}
复制代码
Redisson 分布式锁
Redisson 介绍
Redisson 是一个基于 Redis 实现的 Java 驻内存数据网格(In-Memory Data Grid)框架。它提供了丰富的分布式 Java 对象和服务,包括分布式集合、分布式锁、分布式服务等高级功能,使开发者能够轻松构建分布式系统。
核心特性
基于 Redis 实现:Redisson 完全兼容 Redis 协议,可以直接连接 Redis 服务器
分布式服务:提供了分布式锁、分布式集合、分布式原子变量等常用分布式服务
高性能:基于 NIO 的 Netty 框架实现,具有优秀的网络通信性能
支持多种 Redis 部署模式:包括单节点、哨兵、集群等多种部署方式
技术架构
Redisson 采用 Netty 作为底层通信框架,通过异步非阻塞 I/O 实现高性能的网络通信。其核心架构包括:
连接管理器:管理 Redis 连接池
命令执行器:处理 Redis 命令的发送和响应
编解码器:负责数据的序列化和反序列化
监听器:处理 Redis 的订阅/发布消息
典型应用场景
分布式锁:解决分布式环境下的资源竞争问题
分布式集合:如分布式 Map、Set、List 等
分布式限流:控制系统的访问频率
分布式发布订阅:实现跨服务的消息通信
// 使用示例
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取分布式锁
RLock lock = redisson.getLock("myLock");
try {
lock.lock();
// 业务逻辑处理
} finally {
lock.unlock();
}
复制代码
Redisson 通过丰富的 API 和稳定的性能,成为 Java 开发者在分布式系统中常用的工具库之一。
添加依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.7.0</version>
</dependency>
复制代码
配置 Redisson
public class RedissonManager {
private static final Config CONFIG = new Config();
private static Redisson redisson = null;
static {
CONFIG
.useClusterServers()
.setScanInterval(2000)
.addNodeAddress("redis://h121.wzk.icu:6379")
.addNodeAddress("redis://h122.wzk.icu:6379")
.addNodeAddress("redis://h123.wzk.icu:6379");
redisson = (Redisson) Redisson.create(CONFIG);
}
public static Redisson getRedisson() {
return redisson;
}
}
复制代码
获取与释放锁
public class DistributedRedisLock {
private static Redisson redisson = RedissonManager.getRedisson();
private static final String LOCK_TITLE = "redisLock_";
public static boolean acquire(String lockName) {
String key = LOCK_TITLE + lockName;
RLock rLock = redisson.getLock(key);
rLock.lock(3, TimeUnit.SECONDS);
return true;
}
public static void release(String lockName) {
String key = LOCK_TITLE + lockName;
RLock rLock = redisson.getLock(key);
rLock.unlock();
}
}
复制代码
业务使用
public String discount() throws IOException{
String key = "lock001";
// 加锁
DistributedRedisLock.acquire(key);
// 执行具体业务逻辑
dosoming
// 释放锁
DistributedRedisLock.release(key);
// 返回结果
return soming;
}
复制代码
实现原理
分布式锁特性
互斥性:任意时刻,只能有一个客户端获取锁,不能同时有两个客户端获取到锁。
同一性:锁只能被持有该锁客户端删除,不能由其他客户端删除
可重入性:持有某个客户端可持续对该锁加锁 实现锁的续租
容错性:超过生命周期会自动进行释放,其他客户端可以获取到锁
常见分布式锁对比
评论