Redis 缓存相关
一 Redis可缓存的数据类型
String,List,Hash,Set,ZSet
二 Redis常见的命令
2.1 String类型的命令
- GET -获取key值,存在返回value,不存在返回特殊字符:nil
- INCR -将 key 中储存的数字值增一。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
这是一个针对字符串的操作,因为 Redis 没有专用的整数类型,所以 key 内储存的字符串被解释为十进制 64 位有符号整数来执行 INCR 操作。
- INCRBY 将 key 所储存的值加上增量 increment 。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。DECR 和DESCBY 相反,减量。
//INCRBY key increment redis> SET rank 50 OK redis> INCRBY rank 20 (integer) 70
-
MSET mset key value [key value ...] 同时设置一个或多个 key-value 对,是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置,某些给定 key 被更新而另一些给定 key 没有改变的情况,不可能发生。总是返回 OK (因为 MSET 不可能失败)。
jedis.mset(key,value,key1,value1,key2,value2);
同理MSETNX设置的 key都不存在时,才添加
jedis.msetnx(key,value,key,value,key,value);
-
MGET 获取值MGET时候就比较简单,获取多个key的z
jedis.mget(key,key,key);
- SET
- SETEX 设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。成功返回OK;
jedis.set("key", "value", "EX", seconds); 等同于 jedis.setex("key",seconds,"value");
类似于
jedis.set(key,value); jedis.expire(key,1000);//设置生存时间1000s
不同之处是, SETEX 是一个原子性(atomic)操作,关联值和设置生存时间两个动作会在同一时间内完成,该命令在 Redis 用作缓存时。
- SETPX 设置键的过期时间为 millisecond 毫秒,SET key value PX millisecond 效果等同于 PSETEX key millisecond value ,成功返回OK;
jedis.set("key", "value", "PX", milliseconds); 等同于 jedis.psetex("key",milliseconds,"value");
-
SETNX 『SET if Not eXists』只在键不存在时,才对键进行设置操作。 成功返回1,失败0 。
XX :只在键已经存在时,才对键进行设置操作,成功返回1,失败0。
jedis.set("key", "value", "NX");等同于 jedis.setnx("key", "value"); jedis.set("key", "value", "XX");
EX 、PX和 NX、XX组合使用
jedis.set(key,value,"NX","EX",1000);//不存在时才添加,成功返回1,不成功返回0,过期时间 1000s
jedis.set(key,value,"XX","PX",10000);//存在时才添加,成功返回1,不成功返回0,过期时间 10000ms
2.2 Hash(hash表)常见命令
- HDEL HDEL key field [field ...] 删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
# 删除单个域 redis> HDEL abbr a (integer) 1 # 删除不存在的域 redis> HDEL abbr not-exists-field (integer) 0 # 删除多个域 redis> HDEL abbr b c (integer) 2
- HEXISTS HEXISTS key field 查看哈希表 key 中,给定域 field 是否存在。
redis> HEXISTS phone myphone (integer) 0 redis> HSET phone myphone nokia-1110 (integer) 1 redis> HEXISTS phone myphone (integer) 1
- HGET 返回哈希表 key 中,一个或多个给定域的值。
HMGET key field [field ...]
redis> HMSET pet dog "doudou" cat "nounou" # 一次设置多个域 OK redis> HMGET pet dog cat fake_pet # 返回值的顺序和传入参数的顺序一样 1) "doudou" 2) "nounou" 3) (nil) # 不存在的域返回nil值
- HKEYS 返回哈希表 key 中的所有域
redis> HMSET website google www.google.com yahoo www.yahoo.com OK redis> HKEYS website 1) "google" 2) "yahoo" # 空哈希表/key不存在 redis> EXISTS fake_key (integer) 0 redis> HKEYS fake_key (empty list or set)
- HMGET HMGET key field [field ...]返回哈希表 key 中,一个或多个给定域的值。如果给定的域不存在于哈希表,那么返回一个 nil 值。
redis> HMSET pet dog "doudou" cat "nounou" # 一次设置多个域 OK redis> HMGET pet dog cat fake_pet # 返回值的顺序和传入参数的顺序一样 1) "doudou" 2) "nounou" 3) (nil) # 不存在的域返回nil值
# HMSET key field value [field value ...] redis> HMSET website google www.google.com yahoo www.yahoo.com OK redis> HGET website google "www.google.com" redis> HGET website yahoo "www.yahoo.com"
redis> HSET website google "www.g.cn" # 设置一个新域 (integer) 1 #返回值 redis> HSET website google "www.google.com" # 覆盖一个旧域 (integer) 0 #返回值
- HSETNX 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在。若域 field 已经存在,该操作无效。如果 key 不存在,一个新哈希表被创建并执行 HSETNX 命令。
redis> HSETNX nosql key-value-store redis (integer) 1 #返回值 redis> HSETNX nosql key-value-store redis # 操作无效,域 key-value-store 已存在 (integer) 0 #返回值
2.3 List(列表)常用命令
- LLEN LLEN key返回列表 key 的长度。如果 key 不存在,则 key 被解释为一个空列表,返回 0 .如果 key 不是列表类型,返回一个错误。
# 空列表 redis> LLEN job (integer) 0 # 非空列表 redis> LPUSH job "cook food" (integer) 1 redis> LPUSH job "have lunch" (integer) 2 redis> LLEN job (integer) 2
- LPOP LPOP key 移除并返回列表 key 的头元素。
redis> LLEN course (integer) 0 redis> RPUSH course algorithm001 (integer) 1 redis> RPUSH course c++101 (integer) 2 redis> LPOP course # 移除头元素 "algorithm001"
-
LPUSH LPUSH key value [value ...] 将一个或多个值 value 插入到列表 key 的表头,如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表头: 比如说,对空列表 mylist 执行命令 LPUSH mylist a b c ,列表的值将是 c b a ,这等同于原子性地执行 LPUSH mylist a 、 LPUSH mylist b 和 LPUSH mylist c 三个命令。
如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。当key 存在但不是列表类型时,返回一个错误。
# 加入单个元素 redis> LPUSH languages python (integer) 1 #返回执行 LPUSH 命令后,列表的长度。 # 加入重复元素 redis> LPUSH languages python (integer) 2 #返回执行 LPUSH 命令后,列表的长度。 redis> LRANGE languages 0 -1 # 列表允许重复元素 1) "python" 2) "python" # 加入多个元素 redis> LPUSH mylist a b c (integer) 3 redis> LRANGE mylist 0 -1 1) "c" 2) "b" 3) "a"
count 的值可以是以下几种:
count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
count = 0 : 移除表中所有与 value 相等的值。
# 先创建一个表,内容排列是 # morning hello morning helllo morning redis> LPUSH greet "morning" (integer) 1 redis> LPUSH greet "hello" (integer) 2 redis> LPUSH greet "morning" (integer) 3 redis> LPUSH greet "hello" (integer) 4 redis> LPUSH greet "morning" (integer) 5 redis> LRANGE greet 0 4 # 查看所有元素 1) "morning" 2) "hello" 3) "morning" 4) "hello" 5) "morning" redis> LREM greet 2 morning # 移除从表头到表尾,最先发现的两个 morning (integer) 2 # 两个元素被移除 redis> LLEN greet # 还剩 3 个元素 (integer) 3 redis> LRANGE greet 0 2 1) "hello" 2) "hello" 3) "morning" redis> LREM greet -1 morning # 移除从表尾到表头,第一个 morning (integer) 1 redis> LLEN greet # 剩下两个元素 (integer) 2 redis> LRANGE greet 0 1 1) "hello" 2) "hello" redis> LREM greet 0 hello # 移除表中所有 hello (integer) 2 # 两个 hello 被移除 redis> LLEN greet (integer) 0
- LSET LSET key index value 将列表 key 下标为 index 的元素的值设置为 value 。当 index 参数超出范围,或对一个空表( key 不存在)进行 LSET 时,返回一个错误。操作成功返回 ok ,否则返回错误信息。。
# 对空列表(key 不存在)进行 LSET redis> EXISTS list (integer) 0 redis> LSET list 0 item (error) ERR no such key # 对非空列表进行 LSET redis> LPUSH job "cook food" (integer) 1 redis> LRANGE job 0 0 #查看列表key指定区间(开始索引-结束索引)间的元素 1) "cook food" redis> LSET job 0 "play game" OK redis> LRANGE job 0 0 1) "play game" # index 超出范围 redis> LLEN list # 列表长度为 1 (integer) 1 redis> LSET list 3 'out of range' (error) ERR index out of range
- LTRIM LRANGE key start stop 返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
redis> RPUSH fp-language lisp (integer) 1 redis> LRANGE fp-language 0 0 1) "lisp" redis> RPUSH fp-language scheme (integer) 2 redis> LRANGE fp-language 0 1 1) "lisp" 2) "scheme"
- RPOP RPOP key 移除并返回列表 key 的尾元素。 当 key 不存在时,返回 nil 。
redis> RPUSH mylist "one" (integer) 1 redis> RPUSH mylist "two" (integer) 2 redis> RPUSH mylist "three" (integer) 3 redis> RPOP mylist # 返回被弹出的元素 "three" redis> LRANGE mylist 0 -1 # 列表剩下的元素 1) "one" 2) "two"
- RPUSH RPUSH key value [value ...]将一个或多个值 value 插入到列表 key 的表尾(最右边)。如果有多个 value 值,那么各个 value 值按从左到右的顺序依次插入到表尾:比如对一个空列表 mylist 执行 RPUSH mylist a b c ,得出的结果列表为 a b c ,等同于执行命令 RPUSH mylist a 、 RPUSH mylist b 、 RPUSH mylist c 。如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。当 key 存在但不是列表类型时,返回一个错误。执行 RPUSH 操作后,表的长度。
# 添加单个元素 redis> RPUSH languages c (integer) 1 # 添加重复元素 redis> RPUSH languages c (integer) 2 redis> LRANGE languages 0 -1 # 列表允许重复元素 1) "c" 2) "c" # 添加多个元素 redis> RPUSH mylist a b c (integer) 3 redis> LRANGE mylist 0 -1 1) "a" 2) "b" 3) "c
2.4 Set(集合)常用命令
格式: sadd name value
Redis的Set是string类型的无序集合。
jedis.sadd(key,value,value,value);
2.5 ZSet(有序集合)命令
zset(sorted set:有序集合) 格式: zadd name score value
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
2.6 Key常用命令
- DEL DEL key [key ...]删除给定的一个或多个 key 。不存在的 key 会被忽略。
# 删除单个 key redis> SET name huangz OK redis> DEL name (integer) 1 # 删除一个不存在的 key redis> EXISTS phone (integer) 0 redis> DEL phone # 失败,没有 key 被删除 (integer) 0 # 同时删除多个 key redis> SET name "redis" OK redis> SET type "key-value store" OK redis> SET website "redis.com" OK redis> DEL name type website (integer) 3
- EXISTS EXISTS key 检查给定 key 是否存在。
redis> SET db "redis" OK redis> EXISTS db (integer) 1 redis> DEL db (integer) 1 redis> EXISTS db (integer) 0
- EXPIRE EXPIRE key seconds 为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
redis> SET cache_page "www.google.com" OK redis> EXPIRE cache_page 30 # 设置过期时间为 30 秒 (integer) 1 redis> TTL cache_page # 查看剩余生存时间 (integer) 23 redis> EXPIRE cache_page 30000 # 更新过期时间 (integer) 1 redis> TTL cache_page (integer) 29996
-
KEYS KEYS pattern 查找所有符合给定模式 pattern 的 key 。
KEYS * 匹配数据库中所有 key 。
KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
KEYS h*llo 匹配 hllo 和 heeeeello 等。
KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。
特殊符号用 \ 隔开
KEYS 的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,如果你需要从一个数据集中查找特定的 key ,你最好还是用 Redis 的集合结构(set)来代替。
redis> MSET one 1 two 2 three 3 four 4 # 一次设置 4 个 key OK redis> KEYS *o* 1) "four" 2) "two" 3) "one" redis> KEYS t?? 1) "two" redis> KEYS t[w]* 1) "two" redis> KEYS * # 匹配数据库内所有 key 1) "four" 2) "three" 3) "two" 4) "one"
SORT key 返回键值从小到大排序的结果。
SORT key DESC 返回键值从大到小排序的结果。
假设 today_cost 列表保存了今日的开销金额, 那么可以用 SORT 命令对它进行排序:
# 开销金额列表 redis> LPUSH today_cost 30 1.5 10 8 (integer) 4 # 排序 redis> SORT today_cost 1) "1.5" 2) "8" 3) "10" 4) "30" # 逆序排序 redis 127.0.0.1:6379> SORT today_cost DESC 1) "30" 2) "10" 3) "8" 4) "1.5"
- TTL TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。
# key 存在,但没有设置剩余生存时间 redis> SET key value OK redis> TTL key (integer) -1 # 有剩余生存时间的 key redis> EXPIRE key 10086 (integer) 1 redis> TTL key (integer) 10084
三 Redis常见问题
3.1 什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?
Redis持久化就是将内存中的数据以文件的方式写入到磁盘中去。Redis有两种持久化方式:RDB(默认) 和 AOF
RDB:RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。
RDB是 Redis DataBase的缩写,功能核心函数有两个:
- rdbSave:将内存中的数据以RDB文件的方式写入到磁盘中。
- rdbLoad:将磁盘中的RDB文件加载到内存中。
另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不 是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。
AOF
AOF是Append-only file的缩写
每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作
aof 写入保存:
- WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
- SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
存储结构:
内容是redis通讯协议(RESP )格式的命令文本存储。
比较
aof文件、更新频率高,比rdb更安全也更大、但性能没有rdb好,如果两个都配了优先加载AOF
什么是RESP?有什么特点?
RESP 是redis客户端和服务端之前使用的一种通讯协议;
RESP 的特点:实现简单、快速解析、可读性好
3.2 Redis有哪些架构模式?各自的特点又是什么?
特点:比较简单,缺点:内存有限,处理能力有限,无法高可用
Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。
特点:有主从角色,主和从数据一样,降低了master的读取压力
问题:写操作是在master上进行的,由master同步给slave,master写压力较大
Redis sentinel (发音:sentɪnl-森忒no)是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:
- 监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
- 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。
特点:监控各个节点 ,保证高可用,一旦有服务器出现问题,会自动故障迁移
缺点:主从模式,切换需要时间丢数据,没有解决 master 写的压力
Twemproxy 是一个 Twitter 开源的一个 redis 和 memcache 快速/轻量级代理服务器; Twemproxy 是一个快速的单线程代理程序,支持 Memcached ASCII 协议和 redis 协议。
特点:1、多种 hash 算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins
2、支持失败节点自动删除
3、后端 Sharding 分片逻辑对业务透明,业务方的读写方式和操作单个 Redis 一致
缺点:增加了新的 proxy,需要维护其高可用。
failover 逻辑需要自己实现,其本身不能支持故障的自动转移可扩展性差,进行扩缩容都需要手动干预
从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
特点:
1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本
5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。
缺点:
1、资源隔离性较差,容易出现相互影响的情况。
2、数据通过异步复制,不保证数据的强一致性
3.3 什么是一致性哈希算法?什么是哈希槽?
这两个问题篇幅过长 网上找了两个解锁的不错的文章
https://www.cnblogs.com/lpfuture/p/5796398.html
https://blog.****.net/z15732621582/article/details/79121213
3.4 使用过Redis分布式锁么,它是怎么实现的?
先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放
如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?
set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!
set(key,value,"NX","EX",1000);
3.5 使用过Redis做异步队列么,你是怎么用的?有什么缺点?
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
缺点:在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
能不能生产一次消费多次呢?
使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
3.6 什么是缓存穿透?如何避免?什么是缓存雪崩?何如避免?
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
1:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
2:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免?
1:在缓存失效后,控制读数据库和写缓存的线程个数,比如某个key,只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。