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 同时将多个 field-value (域-值)对设置到哈希表 key 中。此命令会覆盖哈希表中已存在的域。

    如果 key 不存在,一个空哈希表被创建并执行 HMSET 操作。

# 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"
  • HSET  将哈希表 key 中的域 field 的值设为 value 。如果 key 不存在,一个新的哈希表被创建并进行 HSET 操作。如果域 field 已经存在于哈希表中,旧值将被覆盖。
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"
  • LREM LREM key count value根据参数 count 的值,移除列表中与参数 value 相等的元素。REM 命令总是返回 0 。

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 最简单的 SORT 使用方法是 SORT key 和 SORT key DESC :

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"
  • TTTTL 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的缩写,功能核心函数有两个:

  1. rdbSave:将内存中的数据以RDB文件的方式写入到磁盘中。
  2. rdbLoad:将磁盘中的RDB文件加载到内存中。

另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不 是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。

AOF

AOF是Append-only file的缩写

每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作

aof 写入保存:

  1. WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
  2. 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 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:

  1. 监控(Monitoring):    Sentinel  会不断地检查你的主服务器和从服务器是否运作正常。
  2. 提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
  3. 自动故障迁移(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.csdn.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,设置不同的过期时间,让缓存失效的时间点尽量均匀。

 

参考链接:https://www.cnblogs.com/jasontec/p/9699242.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章