理解 Redis

Posted by AceKei on January 13, 2019

Redis

官网:http://www.redis.cn/

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis 支持的数据类型

字符串类型

String

1
2
set key value
get key

String 是最基本的类型,可以存储任何数据,一个键最大能存储 512M

散列类型

hash

1
2
hset key field value
hget key field 

散列类型的键值也是一种字典结构,存储了字段 field 和字段值的映射,但字段值只能是字符串,不支持其他数据类型,也就是说不能嵌套。

一个hash键可以包含最多2^32 - 1 个字段。

hash类型适合存储对象:使用对象类别和ID构成键名,使用字段表示对象的属性,而字段值表示存储的属性值。就类似Java中 bean 。key对于 bean 名,fiel 对应变量, value 对应变量的值。

列表类型

list(双向)

1
2
3
4
lpush key value [value...]
rpush key value [value...]
lpop key
rpop key

list 存储一个有序的字符串列表,常用的操作是向列表两端添加元素或者获得列表的某一个片段。

列表内部是基于 双向链表 实现,所以向列表两端添加元素的时间复杂度为 O(1)。

适用场景:获取最新的数据;或者记录日志,可以保证新加入的速度不受已经存在的日志数量的影响,等很少访问中间元素的应用。

集合类型

set

1
2
sadd key member [member...]
srem key member [member...]

在集合中,每个元素是不同的,并且没有顺序,一个set键最多可以存储 2^32 - 1 个字符串。

集合和列表的区别

  set list
存储内容 最多 2^32 - 1 个字符串 最多 2^32 - 1 个字符串
有序性
唯一性

有序集合

zset

1
zadd key score member [score member...]

时间复杂度 O(log N)

zset 是在set 和 list 的基础上,为每个 key 增加一个 score(分数),通过 score 进行排序。

Redis 持久化

RDB

Redis DataBase
核心函数是rdbSave(生成RDB文件)和rdbLoad(从文件加载到内容)。

1
2
3
graph LR
内存中的数据-(rdbSave)->磁盘的RDB文件
磁盘的RDB文件-(rdbLoad)->内存中的数据

AOF

append-only file

1
2
3
graph LR
服务器-(flushAppendOnlyFile)->磁盘的AOF文件

每当执行服务器的任务,或者函数 flushAppendOnlyFile 被调用时,就会触发:
aof写入/保存
write:将aof_buf中的缓冲写入AOF文件中。
save:调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

比较:

  • AOF 文件比 RDB 文件更新频繁,优先级比较高。
  • AOF 比 RDB 更安全,但是也更大。
  • RDB 的性能比 AOF 好。
  • 如果配置了这2种方式的持久化,优先使用 AOF 。

Redis 分布式锁

使用 setnx 命令来争抢锁,当抢到之后,使用 expire 来为锁加一个过期时间。

缓存穿透

查询的时候,是根据 key 去查询的,如果不存在该 key 对应 value 时,就会去后台系统(例如数据库)查询。当一些请求重复查询不存在的 key 时,如果请求量过大,那么就会对 后台系统 造成很大的压力,有可能会导致 后台系统 奔溃。

避免:

  1. 为查询的结果为空也进行缓冲,可以把有效时间设置比平时的 key 短。
  2. 采用 布隆过滤器 BloomFilter 。

布隆过滤器类似一个 HashSet ,用于快速判断某个元素是否存在于集合中。布隆过滤器的关键在于 hash 算法 和 容器的大小。可以使用 Guava 里面提供的 BloomFilter 直接实现。
在查询到空结果的时候,把当前的 key 存放到 布隆过滤器 中,以便下次直接过滤掉。

  1. 使用互斥锁排队

当查询到 key 对应的 value 为空时,可以先加锁,然后从数据库查找,接着存储到redis中,最后再释放锁。其他线程获取锁失败的话,则等待后重试。

缓存雪崩

当 缓存服务器重启期间,或者大量的缓冲在某一个时间段失效,而在这段期间,如果有大量的请求,就会给 后台系统 带来很大的压力。

避免:

  1. 在缓存失效后,通过加锁或者队列来控制 访问 后台系统 的线程数量
  2. 使用二级缓存,一级缓存 设置的有效时间较短,二级缓存有效时间较长。
  3. 不同的key,设置不同的有效时间。

Redis的缓存失效策略

redis 提供 6种数据淘汰策略:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据

注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据, 后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。

使用策略规则:

  1. 如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru。
  2. 如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random。

三种数据淘汰策略:

ttl和random比较容易理解,实现也会比较简单。主要是Lru最近最少使用淘汰策略,设计上会对key 按失效时间排序,然后取最先失效的key进行淘汰。