分布式锁主要用于解决多服务器之间的并发问题。Redis通过SETNX
命令实现分布式锁,确保同一时间只有一个线程可以获取锁。
获取锁
使用SETNX
命令设置锁,并设置一个过期时间,避免死锁。
String lockKey = "lock:" + key;
if (redis.setnx(lockKey, "1", 30) == 1) { // 尝试获取锁,超时30秒
// 执行业务逻辑
} else {
// 等待锁释放
Thread.sleep(100);
}
释放锁
使用DEL
命令释放锁。
redis.del(lockKey);
watchdog
(看门狗)机制watchdog
是Redisson提供的一种自动续期机制,用于防止锁因为超时而被误释放。它通过后台线程定期向Redis发送续期请求。
1、自动续期:
当一个线程获取锁后,watchdog
会启动一个后台线程,定期向Redis发送续期请求。
2、续期频率:
续期频率通常设置为锁超时时间的一半。例如,如果锁的超时时间是30秒,watchdog
会每15秒发送一次续期请求。
3、释放锁:
当业务逻辑执行完成后,watchdog
会自动停止续期,并释放锁。
代码示例
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
public class DistributedLockExample {
public static void main(String[] args) {
// 配置Redisson
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取锁
RLock lock = redisson.getLock("lockKey");
try {
// 获取锁,自动续期
lock.lock();
// 执行业务逻辑
System.out.println("执行业务逻辑...");
Thread.sleep(10000); // 模拟长时间的业务逻辑
} finally {
// 释放锁
lock.unlock();
}
// 关闭Redisson
redisson.shutdown();
}
}
实际应用场景
假设我们有一个库存扣减的场景,需要确保同一时间只有一个线程可以扣减库存:
public void deductStock(String productId, int quantity) {
String lockKey = "lock:stock:" + productId;
RLock lock = redisson.getLock(lockKey);
try {
lock.lock();
// 查询库存
int currentStock = database.getStock(productId);
if (currentStock >= quantity) {
// 扣减库存
database.updateStock(productId, currentStock - quantity);
System.out.println("库存扣减成功");
} else {
System.out.println("库存不足");
}
} finally {
lock.unlock();
}
}
分布式锁支持同一线程的重入,避免死锁。Redisson默认支持可重入锁,允许多次获取和释放同一把锁。
public void nestedLockExample() {
String lockKey = "lock:nested";
RLock lock = redisson.getLock(lockKey);
try {
lock.lock();
System.out.println("第一次获取锁");
// 再次获取锁
lock.lock();
System.out.println("第二次获取锁");
} finally {
lock.unlock();
System.out.println("第一次释放锁");
lock.unlock();
System.out.println("第二次释放锁");
}
}
主从复制用于解决Redis的高并发问题,通过主节点负责写操作,从节点负责读操作,提高系统的读写能力。
全量同步是指从节点一次性同步主节点的所有数据。
操作步骤
REPLCONF
和offset
。BGSAVE
生成RDB文件并发送给从节点。增量同步是指主节点将未同步的操作发送给从节点。
操作步骤
REPLCONF
和offset
。offset
判断需要同步的内容。# 主节点配置
slaveof <主节点IP> <主节点端口>
策略 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
全量同步 | 数据一致性高 | 初始同步时间长 | 第一次同步或数据不一致时 |
增量同步 | 同步速度快 | 依赖之前的同步状态 | 数据部分不一致时 |
哨兵模式用于解决Redis的高可用问题。当主节点出现故障时,哨兵会选举一个从节点升级为主节点。
# 配置哨兵
sentinel monitor <主节点名称> <主节点IP> <主节点端口> <哨兵数量>
sentinel down-after-milliseconds <主节点名称> 5000
sentinel parallel-syncs <主节点名称> 1
sentinel failover-timeout <主节点名称> 60000
分片集群用于解决高并发写操作和海量数据存储问题。通过将数据分布到多个主节点,提高系统的读写能力。
Redis集群有16384个槽,每个Key通过哈希算法映射到一个槽。这种设计确保数据均匀分布,提高集群的扩展性。
每个主节点负责一部分槽,数据通过槽分布到不同的节点。这种分片机制允许集群处理更大的数据量和更高的并发请求。
主节点通过PING
相互监督,确保高可用性。如果某个主节点出现故障,其他节点可以快速检测并进行故障转移。
epoll
机制高效处理多个连接。IO多路复用通过一个线程监控多个socket
,当某个socket
有请求时,线程会立即处理该请求。Redis使用epoll
模式实现IO多路复用,显著提高了性能。
Redis 6.0引入多线程处理命令,命令的接受和转发改为多线程方式,而核心数据结构操作仍保持单线程。
// Redis配置
redis {
threads 8
io-threads 4
io-threads-do-reads yes
}
策略 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
单线程模型 | 简化并发控制 | 无法充分利用多核CPU | 通用场景 |
IO多路复用 | 高效处理多个连接 | 实现复杂 | 高并发场景 |
多线程优化 | 提高命令处理速度 | 仅适用于部分命令 | Redis 6.0及以上版本 |
还有部分内容参考之前写的Redis面试问题缓存相关详解-CSDN博客