文章目录
- 一、基础命令
- 1.1 通用命令
- 1.2 String
- 1.3 Hash
- 1.4 List
- 1.5 Set
- 1.6 SortedSet
- 二、Redis 和数据库的数据一致性
- 三、缓存穿透
- 四、缓存雪崩
- 五、缓存击穿
一、基础命令
1.1 通用命令
KEYS pattern
查找所有符合给定模式 pattern 的 key,其中*
匹配零个或多个字符,?
匹配一个字符。DEL key [key ...]
删除给定的一个或多个 key。EXISTS key
检查给定 key 是否存在。EXPIRE key seconds
为给定 key 设置生存时间,生存时间为 0 时该 key 过期,它会被自动删除。TTL key
以秒为单位,返回给定 key 的剩余生存时间。
1.2 String
SET key value [EX seconds] [PX milliseconds] [NX|XX]
将字符串值 value 关联到 key。如果 key 已经持有其他值,SET
就覆写旧值,无视类型。对于某个原本带有生存时间(TTL)的键来说, 当SET
命令成功在这个键上执行时, 这个键原有的TTL
将被清除。GET key
返回 key 所关联的字符串值。如果 key 不存在那么返回特殊值nil
。如果 key 储存的值不是字符串类型,返回一个错误,因为GET
只能用于处理字符串值。MSET key value [key value ...]
同时设置一个或多个 key-value 对。MSET
是一个原子性(atomic)操作,所有给定 key 都会在同一时间内被设置。MGET key [key ...]
返回一个或多个给定 key 的值,如果给定的 key 里面有某个 key 不存在,那么这个 key 返回nil
。INCR key
将 key 中储存的字符串解释为十进制 64 位有符号整数并加一。如果 key 不存在,那么 key 的值会先被初始化为 0,然后再执行INCR
操作。如果值包含错误的类型,或字符串类型的值不能解释为数字,那么返回一个错误。INCRBY key increment
将 key 所储存的值加上增量 increment,具体实现与INCR
类似。HINCRBYFLOAT key field increment
将 key 所储存的值加上浮点数增量 increment,INCRBYFLOAT
的计算结果最多只能表示小数点的后十七位。SETNX key value
即 set if not exists,当且仅当 key 不存在时将 key 的值设为 value,若给定的 key 已经存在,则SETNX
不做任何动作。SETEX key seconds value
即 set with expire,将值 value 关联到 key,并将 key 的生存时间设为 seconds。如果 key 已经存在,SETEX
命令将覆写旧值。
1.3 Hash
HSET key field value
将哈希表 key 中的域 field 的值设为 value。如果 key 不存在,一个新的哈希表被创建并进行HSET
操作。如果域 field 已经存在于哈希表中,旧值将被覆盖。HGET key field
返回哈希表 key 中给定域 field 的值。HMSET key field value [field value ...]
同时将多个 field-value 对设置到哈希表 key 中。HMGET key field [field ...]
返回哈希表 key 中,一个或多个给定域的值。如果给定的域不存在于哈希表,那么返回一个nil
值。HGETALL key
返回哈希表 key 中所有的域和值。HKEYS key
返回哈希表 key 中的所有域。HVALS key
返回哈希表 key 中所有域的值。HINCRBY key field increment
为哈希表 key 中的域 field 的值加上增量 increment。增量也可以为负数,相当于对给定域进行减法操作。HSETNX key field value
将哈希表 key 中的域 field 的值设置为 value,当且仅当域 field 不存在。
1.4 List
LPUSH key value [value ...]
将一个或多个值 value 依次插入到列表 key 的表头(左侧)。LPOP key
移除并返回列表 key 的头元素。RPUSH key value [value ...]
将一个或多个值 value 依次插入到列表 key 的表尾(右侧)。RPOP key
移除并返回列表 key 的尾元素。LRANGE key start stop
返回列表 key 中指定区间内的元素,区间以偏移量 start 和 stop 指定。BLPOP key [key ...] timeout
是LPOP
命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被BLPOP
命令阻塞,直到等待超时或发现可弹出元素为止。BRPOP key [key ...] timeout
是RPOP
命令的阻塞版本,当给定列表内没有任何元素可供弹出的时候,连接将被BRPOP
命令阻塞,直到等待超时或发现可弹出元素为止。
1.5 Set
SADD key member [member ...]
将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。SREM key member [member ...]
移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略。SCARD key
返回集合 key 中元素的数量。SISMEMBER key member
判断 member 元素是否集合 key 的成员。SMEMBERS key
返回集合 key 中的所有成员,不存在的 key 被视为空集合。SINTER key [key ...]
返回一个集合的全部成员,该集合是所有给定集合的交集。SDIFF key [key ...]
返回一个集合的全部成员,该集合是所有给定集合之间的差集。SUNION key [key ...]
返回一个集合的全部成员,该集合是所有给定集合的并集。
1.6 SortedSet
ZADD key score member [[score member] [score member] ...]
将一个或多个 member 元素及其 score 值加入到有序集 key 当中。score 值可以是整数值或双精度浮点数。ZREM key member [member ...]
移除有序集 key 中的一个或多个成员,不存在的成员将被忽略。ZSCORE key member
返回有序集 key 中,成员 member 的 score 值。如果 member 元素不是有序集 key 的成员或 key 不存在,返回nil
。ZRANK key member
返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。ZCARD key
返回有序集 key 的基数。ZCOUNT key min max
返回有序集 key 中,score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max)的成员的数量。ZINCRBY key increment member
为有序集 key 的成员 member 的 score 值加上增量 increment,increment 可以为负。ZRANGE key start stop [WITHSCORES]
返回有序集 key 中,指定区间内的成员。其中成员的位置按 score 值递增来排序。ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
返回有序集 key 中,所有 score 值介于 min 和 max 之间的成员。
二、Redis 和数据库的数据一致性
在并发场景下,如果只有读操作并不会发生数据不一致,但如果存在写操作,不管是先更新数据库还是先更新缓存,只要两次更新操作中夹杂了其他线程的完整更新操作都会导致最终的数据不一致。举两个例子来说就是 线程 A 更新缓存 -> 线程 B 更新缓存 -> 线程 B 更新数据库 -> 线程 A 更新数据库
和 线程 A 更新数据库 -> 线程 B 更新数据库 -> 线程 B 更新缓存 -> 线程 A 更新缓存
,以上两种情况下,线程 B 的完整更新均使得线程 A 的更新成为了部分更新,因此发生了最终的数据不一致。
旁路缓存策略(Cache Aside)能在一定程度上解决数据不一致的问题,该策略主要分为读策略和写策略两个部分:
- 写策略:先更新数据库中的数据,再删除缓存中的数据。
- 读策略:如果读取的数据命中了缓存,则直接返回数据。如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。
对于写策略,主要有两个注意事项:
- 首先是删除缓存还是更新缓存。如果采用更新缓存,那么每次更新数据库都要对应更新缓存,但这些数据并不一定会被查询,此时会导致大量的无效写操作。而删除缓存只需要让缓存失效,开销更小,同时还能在一定程度上避免
线程 A 更新数据库 -> 线程 B 更新数据库 -> 线程 B 更新缓存(删除) -> 线程 A 更新缓存(删除)
的数据不一致问题。 - 其次是先操作数据库还是先操作缓存。其实,不管是先操作谁都有可能发生数据不一致(见下图),不过由于缓存的写入通常要远远快于数据库的写入,因此先操作数据库发生数据不一致的可能性非常小。
基于旁路缓存策略,可以通过加锁将更新数据库和删除缓存整合为一个原子操作从而保证强一致性,但很明显这会影响性能。同时,也可以在写入缓存时指定 TTL,这样哪怕发生数据不一致也能在缓存失效后得到解决。
此外,如果采用先删除缓存再更新数据库,通过延迟双删(在更新数据库后延迟一定时间再删除一次缓存)也可以在一定程度上解决数据不一致的问题。因此,任何一种缓存更新方案都不能说是绝对的最优解,具体用哪种方案就要看对性能和一致性的取舍了。
三、缓存穿透
缓存穿透是指客户端请求的数据在缓存和数据库中都不存在,这样缓存永远不会生效,所有的请求都会访问数据库。
常见的解决方案有三种:
- 限制非法请求:可以在 API 入口处对请求进行判断,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
- 缓存空对象:缓存空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。优点是实现简单、维护方便,缺点是会产生额外的内存消耗已以及短期的数据不一致。
- 布隆过滤器:布隆过滤器通过对数据取模在位图数组中相应位置进行标记,访问数据前只需要判断对应位置是否被标记即可。因此布隆过滤器能够快速判断数据是否存在,从而过滤请求。优点是内存占用少,不过实现复杂而且存在误判的可能。
四、缓存雪崩
缓存雪崩是指在同一时段大量的缓存数据同时失效或者 Redis 故障宕机,导致大量请求到达数据库,为数据库带来巨大压力。
解决大量的缓存数据同时失效:
- 均匀设置过期时间:在对缓存数据设置过期时间时,给过期时间加上一个随机数,尽量避免数据在同一时间过期。
- 对缓存构建加锁:业务线程如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据并更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
- 后台缓存更新:缓存设置为永久有效,业务线程也不再负责更新缓存,将更新缓存的工作交由后台线程定时执行。在业务刚上线的时候,我们可以提前把数据缓起来,而不是等待用户访问才来触发缓存构建,这就是所谓的缓存预热,后台更新缓存的机制很适合完成这个工作。
解决 Redis 故障宕机:
- 服务熔断、降级或请求限流机制:因为 Redis 故障宕机而导致缓存雪崩问题时,我们可以启用服务熔断,暂停业务应用对缓存服务的访问,直接返回错误,或者启用服务降级,只提供默认值或简化的服务而不是完全失败,从而降低对数据库的访问压力,等到 Redis 恢复正常后,再允许业务应用访问缓存服务。服务熔断机制保护了数据库,但是暂停了业务访问,为了减少对业务的影响,我们可以启用请求限流机制,只将少部分请求发送到数据库进行处理,其余请求就在入口直接被拒绝,等到 Redis 恢复并把缓存预热完后,再解除请求限流。
- 构建 Redis 缓存高可靠集群:可以搭建 Redis 集群,如果 Redis 缓存的主节点故障宕机,从节点可以切换成为主节点,继续提供缓存服务,避免了由于 Redis 故障宕机而导致的缓存雪崩问题。
五、缓存击穿
缓存击穿也叫热点 key 问题,是指一个被高并发访问并且缓存重建业务较为复杂的 key 突然失效了,无数的请求同时访问数据库并重建缓存。
常见的解决方案有三种:
- 互斥锁:保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。这种方法可以保证数据一致性但是互斥锁会影响性能。
- 逻辑过期:不显式地为热点 key 指定 TTL,而是由专门地异步线程在热点 key 逻辑过期前进行缓存更新。这种方法性能较好但是不能保证数据地强一致性。
参考:
http://doc.redisfans.com/index.html
https://www.xiaolincoding.com/redis