1. 简介
String是redis最基本的类型,可以理解为Memcached一样的类型,一个key对应一个value。
String类型是二进制安全的,意思是redis的String可以包含任何数据。比如ipg图片或者序列化的对象。
String类型是Redis最基本的数据结构,一个redis中字符串value最多可以是512M
。
2. 常用Api可以查阅如下链接分档
https://www.runoob.com/redis/redis-strings.html
3. 案例
del key
删除某个key 返回删除的条数
append key
相当于java里面的append 在指定key的value后面追加
strlen key
返回指定key的长度
下面这个必须value必须是数字
Incr key
在原来的数字key基础上自增
Decr key
在原来的数字key基础上自减
Incrby key 3
在原来key基础上加3
decrby key 3
在原来key基础上减3
getrange key 开始索引 结束索引
获取key的value值的指定区间索引,-1为全部
setrange key 开始索引 值
获取key的value并且从开始索引开始将值覆盖
setnx
如果不存在则set ,存在则忽视 使用这个是防止我们使用set 时覆盖原有的值
设置成功返回1,设置失败返回0
setex key 过期时间 value
设置值的同时设置过期时间
mset k1 v1 k2 v2 ..
说白了等同于moreset 一次性设置多个值
mget k1 k2 k3 k4 k5 ..
说白了等同于moreget 一次性获取多个key的值
msetnx k1 v1 k2 v2 ..
注意 这是一次性设置多个值当这些值不存在时,但凡一个存在则设置不成功,返回0 设置成功则返回1
getset key value
先获取key并返回,再将value设置,覆盖
4. 分布式锁
4.1 锁的可靠性
为了确保分布式锁可用,我们至少要确保锁的可靠性,要满足一下四个条件:
- 互斥性,在任意时刻,只能有一个客户端(或者说业务请求)获得锁,并且也只能由该客户端请求解锁成功。
- 避免死锁,即使获取了锁的客户端崩溃没有释放锁,也要保证锁正常过期,后续的客户端能正常加锁。
- 容错性,只要大部分Redis节点可用,客户端就能正常加锁。
- 自旋重试,获取不到锁时,不要直接返回失败,而是支持一定的周期自旋重试,设置一个总的超时时间,当过了超时时间以后还没有获取到锁则返回失败。(这一点很重要,我发现网上很多方案并没有把这个功能加上,只尝试一次加锁请求失败就返回了,加了自旋重试更好一些
4.2 过期时间设定
-
锁的过期时间 (EXPIRE_TIME)
太短可能过早的释放锁,造成数据安全问题。太长的话,如果客户端挂掉,会长时间无法释放锁,导致其他客户端锁请求阻塞或者失败(这种场景太少见)
我们一般会预估一下加锁需要进行的操作最长耗时,然后在最长耗时基础上再加一个buffer的时间来确定。(buffer比例多少不确定,这个自行判断吧)需要保证锁在任务执行完之前不会过期。 -
自旋间隔时间 (WAIT_INTERVAL)
适当间隔就好,一般是50~100ms -
获取锁的超时时间 (ACCQUIRE_TIME_OUT)
在激烈的竞争环境下,超时时间设置太短会导致失败次数显著增加。建议至少设置成和锁的过期时间一样。
4.3 有效期续约(看门狗)
设置有效期也是为了防止死锁
1, 如何设置有效期,这个有效期如何维护?
redisson就是利用守护线程设计的看门狗,来定时的刷新有效期
;
注:守护线程通过调用接口实现设置,java.lang.Thread#setDaemon(boolean on),参数boolean类型,true则是守护线程,false则不是守护线程;
Watch Dog 机制其实就是一个后台定时任务线程,获取锁成功之后,会将持有锁的线程放入到一个 RedissonLock.EXPIRATION_RENEWAL_MAP里面,然后每隔 10 秒 (internalLockLeaseTime / 3) 检查一下,如果客户端 还持有锁 key(判断客户端是否还持有 key,其实就是遍历EXPIRATION_RENEWAL_MAP 里面线程 id 然后根据线程 id 去 Redis 中查,如果存在就会延长 key 的时间),那么就会不断的延长锁 key 的生存时间。
如果服务宕机了,Watch Dog 机制线程也就没有了,此时就不会延长 key 的过期时间,到了 30s 之后就会自动过期了,其他线程就可以获取到锁。
引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.8</version>
</dependency>
查看看门狗默认配置是30s
4.4 redis分布式锁设计成可重入锁思路
Java 中的 ReentrantLock 是如何实现的?
ReentrantLock 实现的思路:
- 锁标识:通过AQS的state变量作为锁标识,利用Java的CAS保证多线程竞争锁时的线程安全问题
- 队列:未竞争到锁的线程进入AQS的队列并挂起,等待解锁时被唤醒(或者超时)
Redisson 是如何做的呢?
利用Redis的发布订阅,加上Java的Semaphore
-
锁标识:Hash 数据结构,key 为锁的名字,filed 当前竞争锁成功线程的"唯一标识",value 重入次数
-
队列:所有竞争锁失败的线程,会订阅当前锁的解锁事件,利用 Semaphore 实现线程的挂起和唤醒
流程:
- 获取锁
- 如果获取锁失败,订阅解锁事件
-
尝试获取锁
-
判断是否超时
-
等待解锁消息释放信号量
(此时每个Java客户端都可能会有多个线程被挂起,但是只有一个线程会被唤醒) -
判断是否超时
-