尽管Redis是一个内存数据库,但它支持两种持久化机制:RDB(快照持久化)和AOF(追加文件),这两种机制可以将数据写入磁盘,从而避免因进程退出而导致的数据丢失。
RDB持久化是将当前内存中的数据生成快照并保存到硬盘的过程。就像拍照一样,RDB记录的是某一时刻内存中数据的状态。
SAVE
:在主线程中执行,会导致阻塞,建议在非高峰期使用。BGSAVE
:创建子进程进行快照,主线程不阻塞,适合线上环境。除了手动触发外,Redis还可以在以下情况下自动生成RDB文件:
DEBUG RELOAD
命令时,会自动触发RDB操作。SHUTDOWN
命令时,如果未开启AOF持久化功能,会自动执行bgsave。优点:
缺点:
AOF持久化以日志的方式记录每次写操作,重启时通过执行AOF文件中的命令来恢复数据。AOF的优点是可以实现更高的实时性。
AOF的工作流程主要包括命令写入、文件同步、文件重写和重启加载。
always
:每次写命令后立即同步。everysec
:每秒同步一次(默认设置)。no
:由操作系统决定何时同步,性能较高但不安全。优点:
缺点:
在Redis中,RDB(快照持久化)和AOF(追加文件)各有优缺点。RDB适合快速恢复,但可能会丢失最后几秒的数据,而AOF则可以提供更高的数据安全性,但在恢复时速度较慢。通过混合使用这两种持久化机制,可以有效地结合它们的优势,提高数据恢复的效率和安全性。
在一个电商系统中,用户下单时需要频繁地对订单数据进行写操作。为了确保数据的安全性和快速恢复,系统采用了混合持久化的方式。
开启混合持久化:
aof-use-rdb-preamble yes
,表示启用混合持久化。触发RDB快照:
生成AOF文件:
系统崩溃后的恢复:
快速恢复:通过在AOF文件开头包含RDB快照数据,系统可以快速加载最近的快照,减少重启时的恢复时间。
数据安全性:AOF文件记录了所有的写操作,能够提供更高的实时性,确保即使在系统崩溃后也能恢复到最近的状态。
资源利用:混合持久化可以减少AOF文件的大小,因为在AOF中首先加载RDB快照,后续只需记录增量数据,降低了存储和I/O开销。
灵活性:用户可以根据业务需求调整RDB和AOF的结合方式,以适应不同的场景和性能要求。
分布式锁是一种用于控制多个分布式系统中对共享资源的访问的机制。在使用Redis实现分布式锁时,确保锁的互斥性和安全性是非常重要的。
Redis可以通过SETNX
命令实现分布式锁。该命令表示“如果不存在则设置”,可以用来实现互斥。
客户端1尝试加锁成功,客户端2尝试加锁失败:
SETNX lock_key 1 # 客户端1加锁,成功则返回1
通过DEL
命令释放锁:
DEL lock_key # 客户端1释放锁
为避免死锁,可以在加锁时设置一个过期时间,例如10秒:
SET lock_key 1 EX 10 NX # 设置锁,过期时间为10秒
这样,即使客户端异常,锁也会在10秒后自动释放。
加锁和设置过期时间是两条命令,可能会出现网络问题导致第二条命令未执行。为了解决这个问题,可以使用Redis 2.6.12版本后扩展的SET
命令:
SET lock_key 1 EX 10 NX # 原子性地加锁并设置过期时间
在释放锁时,需检查锁是否仍归自己持有。可以使用Lua脚本来确保原子性:
lua
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
以下是使用Jedis实现分布式锁的Java代码示例,代码中包含详细注释以帮助理解:
java
package com.hmm.redis.lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* 分布式锁的实现
*/
@Component
public class RedisDistLock implements Lock {
// 锁的过期时间
private static final int LOCK_TIME = 5000; // 5秒
private static final String LOCK_NAMESPACE = "lock:"; // 锁的命名空间
// Lua脚本,用于安全释放锁
private static final String RELEASE_LOCK_LUA =
"if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) end return 0";
// 线程本地变量,存储每个线程的唯一ID
private ThreadLocal lockerId = new ThreadLocal<>();
// 当前持有锁的线程
private Thread ownerThread;
private String lockName = "lock"; // 锁的名称
@Autowired
private JedisPool jedisPool; // Redis连接池
@Override
public void lock() {
// 循环尝试获取锁,直到成功
while (!tryLock()) {
try {
Thread.sleep(100); // 休眠100毫秒后重试
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public boolean tryLock() {
Thread currentThread = Thread.currentThread(); // 获取当前线程
// 如果当前线程已经持有锁,直接返回true
if (ownerThread == currentThread) {
return true;
}
Jedis jedis = null;
try {
jedis = jedisPool.getResource(); // 从连接池获取Redis连接
String id = UUID.randomUUID().toString(); // 生成唯一ID
SetParams params = new SetParams().px(LOCK_TIME).nx(); // 设置过期时间和NX参数
// 尝试加锁
if ("OK".equals(jedis.set(LOCK_NAMESPACE + lockName, id, params))) {
lockerId.set(id); // 设置线程本地变量
ownerThread = currentThread; // 设置当前线程为锁的持有者
return true; // 加锁成功
}
} finally {
if (jedis != null) {
jedis.close(); // 关闭Redis连接
}
}
return false; // 加锁失败
}
@Override
public void unlock() {
// 检查当前线程是否为锁的持有者
if (ownerThread != Thread.currentThread()) {
throw new RuntimeException("Attempt to release a lock that is not owned!");
}
Jedis jedis = null;
try {
jedis = jedisPool.getResource(); // 从连接池获取Redis连接
// 执行Lua脚本释放锁
Long result = (Long) jedis.eval(RELEASE_LOCK_LUA,
Arrays.asList(LOCK_NAMESPACE + lockName), // 锁的名称
Arrays.asList(lockerId.get())); // 当前线程的唯一ID
if (result != 0) {
System.out.println("Lock released successfully!"); // 释放成功
} else {
System.out.println("Failed to release lock!"); // 释放失败
}
} finally {
if (jedis != null) {
jedis.close(); // 关闭Redis连接
}
lockerId.remove(); // 清除线程本地变量
ownerThread = null; // 清除持有者线程
}
}
// 其他未实现的方法省略
}
如果业务逻辑执行时间超过锁的过期时间,就可能导致锁失效。为了解决这个问题,可以引入一个“看门狗”线程,定期检查锁的有效性并续期。
看门狗线程会在锁快到期时重新设置过期时间,以确保锁不会提前失效。
Redisson是一个Redis客户端,提供了封装好的分布式锁实现,简化了分布式锁的使用。以下是Redisson的使用示例:
java
RLock lock = redisson.getLock("lock");
lock.lock(10, TimeUnit.SECONDS); // 加锁,10秒后自动解锁
Redlock是一种在多个Redis实例上实现分布式锁的方案,适用于高可用场景。其主要步骤包括:
Redlock的安全性体现在以下几个方面: