点一下关注吧!!!非常感谢!!持续更新!!!
🚀 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 inventorycurrent = GET inventoryif current > 0: MULTI DECR inventory EXECelse: 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;}
复制代码
实现原理
分布式锁特性
互斥性:任意时刻,只能有一个客户端获取锁,不能同时有两个客户端获取到锁。
同一性:锁只能被持有该锁客户端删除,不能由其他客户端删除
可重入性:持有某个客户端可持续对该锁加锁 实现锁的续租
容错性:超过生命周期会自动进行释放,其他客户端可以获取到锁
常见分布式锁对比
评论