redis5种数据类型:1、redis字符串(String);2、字符串列表(list)3、有序字符串集合(sorted set)4、哈希(hash)5、字符串集合(set)
实现分布式锁要满足3点:多进程可见,互斥,可重入。(redis的hash结构实现:hash散列值(hash的key必须是唯一的))
重入锁:----实现方式:使用hash进行存储,key数为(字符串+线程id),value为重入次数。
加锁时重入次数加1,解锁时重入次数减1。
也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。换一种说法:同一个线程再次进入同步代码时,可以使用自己已获取到的锁。可重入锁可以避免因同一线程中多次获取锁而导致死锁发生。像synchronized就是一个重入锁,它是通过moniter函数记录当前线程信息来实现的。实现可重入锁需要考虑两点:
获取锁:首先尝试获取锁,如果获取失败,判断这个锁是否是自己的,如果是则允许再次获取, 而且必须记录重复获取锁的次数。
释放锁:释放锁不能直接删除了,因为锁是可重入的,如果锁进入了多次,在内层直接删除锁, 导致外部的业务在没有锁的情况下执行,会有安全问题。因此必须获取锁时累计重入的次数,释放时则减去重入次数,如果减到0,则可以删除锁。
重入锁可以避免因同一线程中多次获取锁而导致死锁发生。
3种Redis分布式锁的对比 - 吴磊的 - 博客园
1、redis集群扩容缩容实现
在Redis Sentinel模式中,每个节点需要保存全量数据,冗余比较多,而在Redis Cluster模式中,每个分片只需要保存一部分的数据,对于内存数据库来说,还是要尽量的减少冗余。在数据量太大的情况下,故障恢复需要较长时间。
总结:redis cluster集群新增节点,槽范围移动的时候,槽分区(实际存放数据的地方)也会做相应的移动。
redis集群环境搭建、扩容、缩容原理_芭蕉扇-CSDN博客_redis扩容机制
2、redis怎么做到数据隔离(对于不同的微服务看到不容的数据)
3、分布式锁
在分布式场景中,分布式锁经常选用redis或者ZK来实现,在使用redis时,应注意的问题。
使用分布式锁时需要解决的问题:
1、锁需要具备唯一性
问题讲解:分布式场景下为了保证同一变量在分布式环境中的唯一性,需要对同一资源加锁。
解决方案:使用redis命令setnx(set if not exist),即只能被一个客户端操作成功,如果redis实例存在唯一键(key),如果再想在该键(key)上设置值,就会被拒绝。在锁操作完了后,需要进行锁释放>del lockkey #释放锁。
2、 死锁(因操作客户端挂掉,锁无法释放。----引入原子性的超时时间)
问题讲解: redis释放锁需要客户端的操作,如果此时客户端突然挂了,就没有释放锁的操作了,也意味着其他客户端想要重新加锁,却加不了的问题。
解决方案:,需要在加锁的同时,给锁加上超时时间。
>setnx lockkey true #加锁操作 >expire lockkey 5 #给锁加上超时时间
因为setnx和expire需要两个命令来完成操作,也就是需要两次RTT操作,如果在setnx和expire两次命令之间,客户端突然挂掉,这时又无法释放锁,且又回到了死锁的问题.
进一步优化: 使用set扩展命令,set lockkey true ex 5 nx命令可以一次性完成setnx和expire两个操作,也就是解决了原子性问题.( 锁的创建和设置锁超时时间需要具备原子性)
>set lockkey true ex 5 nx #加锁,过期时间5s-----
... do something critical ...
>del lockkey
3、锁的超时问题(任务超时--使用lua删除锁再重建锁,保证原子性)
问题讲解: 虽然给锁加上了超时时间,但是客户端并不能一定在超时时间之内完成定时任务,所以,即使当前客户端没有完成任务,此时又会有其他的客户端设置锁成功,此时同一资源将会面临多个客户端同时操作的问题.
解决方案:客户端可以在锁设置成功之后,进行定时任务,在锁超时之前使用lua脚本删除锁并重新设置锁和超时时间。当然,这里为什么会使用lua来完成操作呢,其实和上面的原子性问题一样,在删除锁和重新设置锁和锁的超时时间之间,可能面临其他的客户端将锁资源占有,而lua具有原子性的特性,删除锁和重新加锁这两个操作要么都完成,要么都不完成.(具体方案以及代码后续补充)
Redis 允许将 Lua 脚本传到 Redis 服务器中执行, 脚本内可以调用大部分 Redis 命令, 且 Redis 保证脚本的原子性:(
)
local key = "rate.limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]
local is_exists = redis.call("EXISTS", key)
if is_exists == 1 then
if redis.call("INCR", key) > limit then
return 0
else
return 1
end
else
redis.call("SET", key, 1)
redis.call("EXPIRE", key, expire_time)
return 1
end
Java调用:
private boolean accessLimit(String ip, int limit, int timeout, Jedis connection) throws IOException {
List keys = Collections.singletonList(ip);
List argv = Arrays.asList(String.valueOf(limit), String.valueOf(timeout));
return 1 == (long) connection.eval(loadScriptString("script.lua"), keys, argv);
}
// 加载Lua代码
private String loadScriptString(String fileName) throws IOException {
Reader reader = new InputStreamReader(Client.class.getClassLoader().getResourceAsStream(fileName));
return CharStreams.toString(reader);
}
4、锁的可重入问题
问题讲解: 拥有锁的客户端想要再次获得锁
解决方案:我们可以选择使用lua脚本的方案,将锁重新删除和设置.
5、 集群下分布式锁的问题以及需要考虑的其他问题
问题讲解:
这一问题是在redis集群方案时会出现的.事实上,现在为了保证redis的高可用和访问性能,都会设置redis的主节点和从节点,主节点负责写操作,从节点负责读操作,也就意味着,我们所有的锁都要写在主redis服务器实例中,如果主redis服务器宕机,资源释放(在没有加持久化时候,如果加了持久化,这一问题会更加复杂),此时redis主节点的数据并没有复制到从服务器,此时,其他客户端就会趁机获取锁,而之前拥有锁的客户端可能还在对资源进行操作,此时又会出现多客户端对同一资源进行访问和操作的问题.
解决方案:后续补充
6、 默认的是非阻塞锁,怎么实现阻塞锁:
Redis 实现分布式阻塞锁_sui_feng_piao_guo的博客-CSDN博客_redis 阻塞锁
redis 阻塞锁 和非阻塞锁 lua 脚本 - 走看看
Redis监听 集群阻塞锁_一抹春色的博客-CSDN博客