Redis - Must Know

105 阅读4分钟

Data Type

redis has 5 data type, and every type has different bottom implements:

image.png

image.png

Encoding

SKIPLIST

See my another article: juejin.cn/post/716830…

Why choose skip list rather than AVL tree or Red-Black Tree, the redis author's answer is that:

1) They are not very memory intensive. It’s up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees. 
2) A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees. 
3) They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.

Type

ZSET

When the amount of data is small, zset uses encoding_ziplist. Otherwise zset uses encoding_skiplist to store data.

业务常见问题

缓存穿透

有大量的key不在缓存中, 这时大量的key都会打在DB上

解决方案: 可以使用布隆过滤器

布隆过滤器可以通过多个散列函数判断一个key在不在向量里(如果至少有一个散列的值是0, 那就说明这个值一锭不在), 所以它解决的是一个不存在的key就不用再请求DB了, 直接返回就好了

缓存击穿(热key问题)

一个热点key, 过期了, 那么瞬时就会有大量请求打到DB上

solve problem: 不设置过期时间, 每次访问刷新过期时间

缓存雪崩

同一时刻大量的缓存key同时失效, 就会有大量的key打到DB上

solve problem: 过期时间随机设置

大key问题

Order

string

SETNX

SETEX key value is the same as SET key value NX

SETEX

SETEX key seconds value is the same as SET key value EX seconds

Transaction

MULTI & EXEC

127.0.0.1:6379> multi

OK

127.0.0.1:6379(TX)> set key003 value003

QUEUED

127.0.0.1:6379(TX)> set key004 value004

QUEUED

127.0.0.1:6379(TX)> get key003

QUEUED

127.0.0.1:6379(TX)> get key004

QUEUED

127.0.0.1:6379(TX)> exec

1) OK

2) OK

3) "value003"

4) "value004"

redis 事务不是原子性的:

  1. 如果是语法错误,EXEC时会直接报错, 不会执行
  2. 如果是运行时错误, EXEC会提交到server并且执行, 但是只有有问题的那一个命令会报错, 其他前后命令都会正常执行

Lua脚本

redis can run Lua script

Lua脚本的一大作用是提供多个命令的原子性操作, 因为redis会将lua脚本作为整体执行

Example of Lua script:
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 value1 vauel2

1) "key1"

2) "key2"

3) "value1"

4) "vauel2"

redis.call

执行redis命令,出错了会直接返回

127.0.0.1:6379> eval "return redis.call('GET',KEYS[1])" 1 "key001"

"val001new"

等同于:

127.0.0.1:6379> GET key001

"val001new"

但lua脚本也不是原子性, 其中一个命令失败了, 之前的命令也不会回滚, 但之后的命令不会再运行(different from the MULTI/EXEC)

Application

Distributed Lock

使用SETNX命令

执行SETNX key unique_value EX seconds 成功了则取锁, 不成功则需要业务自己重试; 释放锁则调用DEL key

另外unique_value需要每个client唯一, 这样确保谁加锁谁解锁; EX防止一直不释放锁

实际工程中会用lua脚本做CAD(Compare and Delete)来解锁:

if redis.call("GET",KEYS[1]) == ARGV[1] then
  redis.call("DEL",KEYS[1])
  return 1
else
  return 0
end

Bad Case:

在集群情况下(一主多从), clientA从master中取到锁后, master宕机了, 此时key还没有同步到follower中, 该follower升级为master后clientB也从新master中取到锁, 这就有问题了

High Concurrency & Availability

Master-Salve

单机redis也就几万QPS

一主多从: 通过水平扩展horizental scaling, 可以无限扩张读, 但还是受限于一台机器(master)

If you wanna Vertical Scale, you need redis cluster to solve it.

一主多从使用RDB文件同步:

  1. 从节点主动请求主节点, 主库fork新进程并开始dump rdb, 同时将期间的新命令缓存下来
  2. 将rdb发送给从节点, 从库重放RDB到内存
  3. 然后主节点再将缓存的新命令发送给从节点, 从库重放

Sentinel

artificial operation to make a salve become master is too slow, so we need a mechanism to accelerate the process. The sentinel cluster is doing for this.

Practice

brew install redis

brew services start redis
brew services restart redis
brew services stop redis
redis-cli

REF

  1. redis official book: redis.io/commands/se…
  2. redis book blog: redisdoc.com/string/set.…
  3. About Lua Script: https://tech.byte_dance.net/articles/7155464065656045604?from=lark_all_search
  4. https://byte_dance.fei_shu.cn/wiki/wikcn3jnPjUyzYo22uM5vw3tbWd