一.命令
String
set name xiaoming
get name
exprie name 5 #5秒过期
setex name 5 xiaohong
setnx name #不存在时设置
set age 10
incr age #对于数字可以增加1
incrby age 10
#批量操作
mset name xiaoming age 10
mget name age
List
左右添加、左右删除
lpush books java python c++
lpop books
rpush c
rpop books
lindex books 1 #获取下标为1的元素(下标从0开始)
lindex books -1 #获取倒数第一个元素
注意下标从右往左计数。另外没有rindex命令。
lrange books 0 -1 #获取0到倒数第一个元素,即全部元素
ltrim books 1 -1 #保留下标1到最后一个元素,删除其它元素。即删除第一个元素
ltrim books -1 -1 #保留最后一个元素
ltrim books 2 3 #保留下标2到下标3的元素
底层结构:quicklist,用双向指针连接的zipList
Hash
hset nums one 1 #添加值
hget nums one #获取值
hgetall nums #获取所有键值对
hmset nums two 2 three 3 #批量添加
hmget nums one two #批量获取
hincrby nums one 1 #对value为int的键值对进行加法操作
底层结构:数组+链表
SET
sadd books python #添加元素
smembers books #列出所有元素
sismember books java #判断元素是否存在
scard books #列出set中元素个数
spop books #弹出一个元素。弹出规则?
底层结构:基于Hash,value设置为null
ZSET
是Redis独有的数据结构。一方面它是一个set,保证了内部value的唯一性。另一方面它可以给每一个value赋一个score,代表这个value的排序权重。按score从小到大排序。
#添加元素时要带着一个权重值
zadd books 9.0 "think in java"
zadd books 8.9 "java concurrency"
zadd books 8.6 "java cookbook"
zrange books 0 -1 #按score从小到大排序列出
zrevrange books 0 -1 #按score逆序排列
zcard books #获取元素个数
zscore books "think in java" #获取元素的score
zrank books "think in java" #获取位次,从0开始,低到高排序
zrangebyscore books 0 8.91 #获取分值区间内的元素
zrangebyscore books -inf 8.91 withscore #-inf表示负无穷,withscore表示列出分数
zrem books "java concurrency" #删除value
底层结构:跳表
通用规则
对于list、hash、set、zset四种容器类型,有两个通用规则:
(1)create if not exists
(2)drop if no elements
过期时间
所有类型的元素都可以使用expire设置过期时间,只能对数据整体设置,例如不能对hash中的某个key单独设置过期时间。
删除命令
所有类型都可以用del进行删除
二.使用Jedis操作Redis
引入依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.5.1</version>
</dependency>
连接Redis
/**
* 连接Redis
* */
public static Jedis connectJedis() {
Jedis jedis = new Jedis("localhost");
return jedis;
}
执行命令
通过Jedis对象执行命令
//Jedis中的方法和Redis命令基本是一一对应的
Jedis jedis = ConnectRedis.connectJedis();
jedis.set("name","xiaoming");
jedis.get("name");
jedis.hmset(key,hashmap);
思考:存储结构体信息改用hash还是string?
根据使用场景决定,原则是尽可能地减少查询次数。
每次使用是只使用其中的某个字段呢(hash)?还是需要全部字段呢(String)?
Hash不需要进行序列化、反序列化,是否可以承受这部分的性能损失?
三.分布式锁
版本1:
setnx lock true
//执行业务逻辑
del lock
使用setnx抢占锁,只有第一个执行成功的能抢到锁。
最后使用del删除锁。
问题:如果执行业务逻辑时出现异常,删除操作没有正常执行,则其它进程无法抢占到锁。
版本2:
setnx lock true
expire lock 5
//执行业务逻辑
del lock
给锁加上一个过期时间,即使任务没有执行完也会在过期后释放锁。
问题:抢占锁和设置过期时间是两条语句,如果执行完第一条语句后,程序崩溃无法加入过期时间,就会有版本1一样的问题。
版本3:
使用一条指令,同时执行setnx和expire
set lock true ex 5 nx
//执行业务逻辑
del lock
将抢占锁和设置过期时间在一条语句中执行,避免了上述问题。锁一定会被释放。
问题:如果过期时间到了,但是业务代码没有执行完成,锁会释放掉,这时其它进程有可能抢占到锁导致并发执行问题。
Redission怎么实现分布式锁的?是否解决了上述问题?redLock是什么?
Redission在获取到锁后,会启动一个看门狗线程,不断判断任务是否还持有锁,持有的话进行续期。默认是每次续期30秒钟。避免出现任务还每执行完,但锁已经到期的情况。
那会不会出现机器宕机,没有释放锁的情况呢?不会因为机器宕机了,续期的线程也不会执行了,到时间自然就释放锁了。正常执行完时,unlock时也会停止看门狗线程。
仍然存在的问题:设置锁到master,这时进行主从复制,但master宕机了,从机变为主机但没有设置的锁信息。之后来加锁的进程可能成功,造成锁被获得了两遍。
解决:使用多台master,不适用master-salve结构。RedLock算法,向所有机器发送set lock true ex 5 nx命令只有半数以上机器设置成功时,才算加锁成功。执行完成后使用del删除。