38人参与 • 2025-11-25 • Redis
在分布式系统中,多个进程或服务常常需要访问共享资源,为了保证数据的一致性和系统的稳定性,需要引入分布式锁机制。同时,为了提高系统的性能和响应速度,缓存策略也是必不可少的。
redis凭借其原子性操作、内存存储、过期机制和分布式特性,成为实现分布式锁和缓存策略的理想选择。
在单服务环境下,使用synchronized关键字可以保证线程安全,但在分布式系统中,多个节点访问同一个公共资源时,synchronized就无法发挥作用。
分布式锁能够确保在任意时刻,只有一个客户端能持有锁,防止多个客户端同时对共享资源进行操作,从而保证数据的一致性。
setnx + expire方案
原理:
setnx是set if not exists的简写,即当指定的键不存在时,为其设置值。setnx命令尝试获取锁,如果返回1,表示获取成功,再使用expire命令为锁设置一个过期时间,防止锁忘记释放导致死锁。代码示例(java):
import redis.clients.jedis.jedis;
public class redislockexample {
public static void main(string[] args) {
jedis jedis = new jedis("localhost", 6379);
string key = "resource_lock";
string value = "lock_value";
int expiretime = 100; // 过期时间,单位秒
if (jedis.setnx(key, value) == 1) {
jedis.expire(key, expiretime);
try {
// 业务代码
system.out.println("获取锁成功,执行业务逻辑");
} catch (exception e) {
e.printstacktrace();
} finally {
jedis.del(key);
system.out.println("释放锁");
}
} else {
system.out.println("获取锁失败");
}
jedis.close();
}
}
- **缺点**:`setnx`和`expire`两个命令不是原子操作,如果在执行完`setnx`后,进程崩溃或重启,`expire`命令未执行,锁将无法释放,导致其他客户端永远无法获取锁。
setnx + value(系统时间 + 过期时间)方案
原理:
setnx的value值里。如果加锁失败,拿出value值校验是否过期。value存入redis。getset命令尝试获取锁。代码示例(java):
import redis.clients.jedis.jedis;
public class redislockwithtimeexample {
public static void main(string[] args) {
jedis jedis = new jedis("localhost", 6379);
string key = "resource_lock";
long expiretime = 10000; // 过期时间,单位毫秒
long expires = system.currenttimemillis() + expiretime;
string expiresstr = string.valueof(expires);
if (jedis.setnx(key, expiresstr) == 1) {
system.out.println("获取锁成功");
} else {
string currentvaluestr = jedis.get(key);
if (currentvaluestr != null && long.parselong(currentvaluestr) < system.currenttimemillis()) {
string oldvaluestr = jedis.getset(key, expiresstr);
if (oldvaluestr != null && oldvaluestr.equals(currentvaluestr)) {
system.out.println("获取锁成功");
} else {
system.out.println("获取锁失败,其他线程已更新锁");
}
} else {
system.out.println("获取锁失败,锁未过期");
}
}
jedis.close();
}
}
- **缺点**:过期时间是客户端自己生成的,依赖系统时间,要求分布式环境下每个客户端的时间必须同步。锁过期时,并发多个客户端同时请求,都执行`getset`,最终只有一个客户端加锁成功,但该客户端锁的过期时间可能被别的客户端覆盖。且该锁没有保存持有者的唯一标识,可能被别的客户端释放。
使用lua脚本方案
原理:
setnx和expire两条指令的原子操作。代码示例(java):
import redis.clients.jedis.jedis;
import java.util.collections;
public class redislockwithluaexample {
public static void main(string[] args) {
jedis jedis = new jedis("localhost", 6379);
string key = "resource_lock";
string value = "lock_value";
int expiretime = 100; // 过期时间,单位秒
string luascript = "if redis.call('setnx', keys[1], argv[1]) == 1 then " +
"redis.call('expire', keys[1], argv[2]) " +
"else " +
"return 0 " +
"end";
object result = jedis.eval(luascript, collections.singletonlist(key),
collections.singletonlist(value + "," + expiretime));
if (result.equals(1l)) {
system.out.println("获取锁成功");
try {
// 业务代码
} catch (exception e) {
e.printstacktrace();
} finally {
jedis.del(key);
system.out.println("释放锁");
}
} else {
system.out.println("获取锁失败");
}
jedis.close();
}
}
set的扩展命令(set ex px nx)方案
原理:
set指令有扩展参数[ex seconds][px milliseconds][nx|xx],其中ex表示设置键的过期时间,单位为秒;px表示设置键的过期时间,单位为毫秒;nx表示只有键不存在时才能设置成功。代码示例(java):
import redis.clients.jedis.jedis;
public class redissetlockexample {
public static void main(string[] args) {
jedis jedis = new jedis("localhost", 6379);
string key = "resource_lock";
string value = "lock_value";
int expiretime = 100; // 过期时间,单位秒
string result = jedis.set(key, value, "nx", "ex", expiretime);
if ("ok".equals(result)) {
system.out.println("获取锁成功");
try {
// 业务代码
} catch (exception e) {
e.printstacktrace();
} finally {
jedis.del(key);
system.out.println("释放锁");
}
} else {
system.out.println("获取锁失败");
}
jedis.close();
}
}
开源框架redisson方案
原理:
代码示例(java):
import org.redisson.redisson;
import org.redisson.api.rlock;
import org.redisson.api.redissonclient;
import org.redisson.config.config;
public class redissonlockexample {
public static void main(string[] args) {
config config = new config();
config.usesingleserver().setaddress("redis://localhost:6379");
redissonclient redissonclient = redisson.create(config);
rlock lock = redissonclient.getlock("resource_lock");
try {
boolean islocked = lock.trylock(10, 30, java.util.concurrent.timeunit.seconds);
if (islocked) {
system.out.println("获取锁成功");
// 业务代码
} else {
system.out.println("获取锁失败");
}
} catch (interruptedexception e) {
e.printstacktrace();
} finally {
lock.unlock();
redissonclient.shutdown();
}
}
}
工作原理:
代码示例(java):
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.stereotype.service;
@service
public class userservicecacheaside {
@autowired
private redistemplate<string, object> redistemplate;
@autowired
private userrepository userrepository;
private static final string cache_key_prefix = "user:";
private static final long cache_expiration = 30; // 缓存过期时间(分钟)
public user getuserbyid(long userid) {
string cachekey = cache_key_prefix + userid;
// 1. 查询缓存
user user = (user) redistemplate.opsforvalue().get(cachekey);
// 2. 缓存命中,直接返回
if (user != null) {
return user;
}
// 3. 缓存未命中,查询数据库
user = userrepository.findbyid(userid).orelse(null);
// 4. 将数据库结果写入缓存(设置过期时间)
if (user != null) {
redistemplate.opsforvalue().set(cachekey, user, cache_expiration, java.util.concurrent.timeunit.minutes);
}
return user;
}
public void updateuser(user user) {
// 1. 先更新数据库
userrepository.save(user);
// 2. 再删除缓存
string cachekey = cache_key_prefix + user.getid();
redistemplate.delete(cachekey);
}
}
优缺点分析
适用场景:
redis在实现分布式锁和缓存策略方面具有显著的优势。通过多种分布式锁实现方案,可以满足不同场景下对锁的要求,保证共享资源的原子性访问。同时,合理的缓存策略能够提高系统的性能和响应速度,解决缓存穿透、雪崩和击穿等问题。在实际应用中,应根据具体的业务需求和系统特点,选择合适的分布式锁实现方案和缓存策略,以构建高效、稳定的分布式系统。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论