redis 一个开源的,基于内存的结构化数据存储媒介,可以作为数据库,缓存,消息队列等。
数据类型 有String hash list set sortedset 位图,hyperloglogs等 。

Redis 主要的功能都是基于单线程模型实现的,它是使用一个线程来服务所有的客户端的请求,同事 redis 也采用了 非阻塞式IO,并精细化地优化各种命令的算法时间复杂度,这些说明了什么嗯?

  • redis 是线程安全的,因为只有一个线程,其所有的操作都是原子性的,不会因为并发产生数据异常
  • reids 的速度非常快,因为使用非阻塞IO ,IO可以多路复用,并且大部分的命令的算法时间复杂度都是O(1)
  • 注意 使用高耗时的命令是非常危险的,这会占用唯一的一个线程的大量处理时间,导致所有的请求都被拖慢,比如说时间复杂度为O(n) keys 这个命令 ,是严格禁止在生成环境下使用的。

关于 key 的 一些注意事项

  • 不要使用过长的 key ,过长的 key 消耗内存不说,还会让查询的效率降低
  • key可读性也要注意,不能太短,也不能随意 设置key
  • 最好使用同一规范来设计 key,比如 " object-type?attr" ,这样设计出来的key 可能是 “ user:10001”,“commnet?reply-to”
  • redis 允许的最大 key 长度是 512 MB,value 的长度限制也是 512MB。

String
String 是 Redis 的基础数据类型,redis 没有 int Float ,Boolean 等数据类型的概念,所有的基本类型在Redis 都可以通过String 体现
String的 常用命令

  • set 为一个 key 设置 value,可以配合 ex/px 参数指定key 的有效期,通过nx/xx 参数对key 是否存在的情况进行区别操作,时间复杂度为 O(1)
  • get 获取 key 对应的 value 时间复杂度为 O(1)
  • getset 为一个key 设置value 并返回key 的原 value ,时间复杂度为 O(1)
  • mset 为 多个 key 设置 value ,时间复杂度为 O(n)
  • msetnx 同 mset ,如果指定的key 中有任意一个已经存在,则不进行任何操作,时间复杂度为 O(n)

List
redis 的 list 是链表型的数据结构,可以使用 lpush、rpush、lpop、rpop 等命令在List 的两端执行插入和移除元素操作, List 而已支持在特定的 index 上插入和读取元素的功能,但是其时间复杂度就比较高里的,谨慎使用。

  • lpush 向指定 List 的左侧(也就是头部)插入一个或者多个元素,返回插入一户list 的长度,时间复杂度为 O(n),n 为插入元素的数量
  • rpush 向 List 的的右侧,也就是尾部,插入一个或者多个元素
  • lpop 是从 List 的头部移除一个元素,就是取最后面添加的元素,并返回该元素,时间复杂度为 O(1)
  • rpop 从 List 的尾部移除一个元素并返回
  • lpushxrpushxlpush rpush 类似,区别就是,如果 操作的 key 不存在,则不会进行任务号的操作
  • llen 是返回 指定list 的长度,时间复杂度为 O(1)
  • lrange 返回指定 list 中 指定范围的元素,时间复杂度为 O(n), 这个命令要控制获取 list 元素的个数,太大会导致 redis 延迟响应,另外对于不可预知长度的list 禁止 lrange key 0 -1 这样的遍历操作。
  • 注意 谨慎使用 命令 lindex 返回指定的下标的元素,如果下标越界 返回 nil, index 数值是回环的,也就是说 -1 代表最后一个位置,-2 代表倒水第二个位置,该命令的时间复杂度为 O(n) ; lset 命令将指定 list 指定 index 的元素设置为 value ,如果下标越界,则返回错误,其时间复杂度为 O(n),操作 首尾的话 复杂度为 O(1); linsert 向指定 List 中指定元素之前或者之后添加一个新元素,并返回操作后的list长度,如果指定的元素不存在,返回 -1,如果指定的 key 不存在,则不进行任何操作 ,时间复杂度也为 O(n)

Hash
哈希表,redis 的hash 和传统的 hash 一样,是一种 field-value 的数据结构,Hash 适合用于表现对象类型的数据, hash 中的 field 对应 对象的 field 即可。
Hash 的优点包括

  1. 可以实现二元查询
  2. 比起将整个对象序列化后作为String 存储,hash 能够有效的叫声网络传输的消耗
  3. 当使用 hash 维护一个集合的时候,提供了比 List 效率更高的随机访问命令

与Hash 相关常用的命令

  • hset 将key 对应的 Hash 中的 field 设置为 value ,如果该 hash 不存在,则会自动创建一个 时间复杂度为 O(1)
  • hget 返回指定Hash 中 field 字段的值,时间复杂度为 O(1)
  • hmset/hmget 和 hset 和 hget 类似,可以批量操作同一个key 下的多个 field,jedis 中你可以 使用一个key 存储一个 map对象进去,其时间复杂度是 O(n),n为一次操作的 field 的数量
  • hsetnxhset 如果 field 存在,则不会进行任何的操作,O(1) 复杂度
  • hexists 是否存在 field,存在返回 -1 ,不存在返回0 O(1)复杂度
  • hdel 删除指定 hash 中的 field (一个或者 多个),时间复杂度为O(n),n为操作的 field 数量
  • hincrbyincrby ,对指定Hash 中的 field 进行 incrby 操作,O(1) 复杂度
    需要谨慎使用的命令
  • hgetall 返回指定hash中所有的 field-value,返回结果是数组,数组中field 和value 交替出现,O(n) 复杂度
  • hkeys/hvals 返回指定 Hash 中 所有的 field/value O(n) 复杂度。
    上面 3 个命令都会将 hahs 整个遍历,尽量使用 hscan 进行游标式遍历。

Set
redis set 是无序的,不可重复的String 集合
与 set 相关常用命令

  • sadd 向指定的 set 中添加一个或者多个 member ,如果指定的set 不存在,自会自动创建一个,时间复杂度为 O(n),n 为添加的个数
  • srem 从指定的 set 移除 1 个或者多个 member,时间复杂度为O(n), n为返回的member个数
  • srandmember 从指定的set 随机返回 1 个或者多个 member ,时间复杂度为 O(n), n为返回的member 的个数
  • spop 从指定的set 移除并返回 n个 member 时间复杂度为 O(n),n为移除的member的个数
  • scard 返回指定 set 中 member的格式 ,时间复杂度为O(1)
  • sismember 判断指定的 value 是否存在于指定的 set 中,时间复杂度为O(1)
  • smove 将指定的member 从一个 set 移动到另外的 set

慎用的set 命令

smembers 返回指定 hash 中所有的 member ,O(n)
sunion / sunionstore 计算多个 set 的并集并返回/存储到另外一个 set 中,O(n)
sinter/sinterstore 计算多个set 的交集并返回/ 存储至另外一个set 中,O(n)
sdiff / sdiffstore  计算 1 个 set 与 1 个或者多个 set 的并集并返回 
存储到另外的 set 中,O(n)

上面的几个命令计算量大,特别是在 set 尺寸不可知的情况下,避免使用,可以考虑 sscan 命令遍历获取相关的 set 的全部 member 。

其他命令

exists 判断是否存在某个 key  存在 返回 1,不存在 返回 0  O(1)
 del 删除指定 key 及其对应的  value ,时间复杂度为 O(n) , n 为删除 key 的数量
 expire/pexpire 为一个 key 设置有效期,单位为 秒 或者 毫秒,时间复杂度为 O(1)
 TTL/PTTL  返回一个 key 的有效时间,单位为秒或者毫秒。O(1)
 rename  /  renamenx 将 key 重命名为 newkey,使用 rename 时候,如果 newkey 已经
 存在其值会被覆盖 ,使用 renamenx 时,如果 newkey 已经存在,则不会进行任何的操作。O(1)
 type 返回 key 的类型,O(1)
 config get  获得 redis 某配置项当前的值,可以使用 * 通配符 O(1)
 config set 为 redis 某配置项设置新值,时间复杂度为 O(1)
 config rewrite 让 redis 重新加载 redis.conf 的配置

数据持久化
redis 提供了将数据定期自动持久化至硬盘的能力, 包括 RDB 和 AOF 两种方案,两种方案分别有其长处和短板,可以配合起来同时执行,确保数据的稳定性。

必须使用数据持久化吗
redis 数据持久化是可以关闭的。如果仅仅当做缓存来用,可以关闭,毕竟这里的树都是其他拷贝
但通常来说,任然建议至少开始 RDB 方式的数据持久化,因为

  • RDB 几乎不损耗redis的性能, rdb 进行持久化的时候, redis 主进程唯一需要做得事情是 fork 出一个子进程 ,所有的 持久化工作都是有子进程完成
  • redis 无论什么原因挂掉之后,重启事能自动恢复到上一次RDB快照中记录的数据,这省去了手工从其他数据源同步数据的过程,二期要比其他任何的数据恢复方式都要快

RDB
采用RDB 的持久化方式,redis 会定期保存数据快照到一个 rdb 文件中,并在启动时自动加载 rdb文件,恢复之前保存的数据,可以在 redis.conf 中配置 redis 进行快照保存的时机

save [ seconds ] [changes]
意思是在 seconds 秒内如果发生了 changes 次数据修改,则进行一次 RDB 快照保存 
eg.  save 60 100

在 conf 文件中 可以配置多条 save 策略

save 900 1 
save 300 10 
save 60 10000

还可以通过手工 bgsave 命令触发 rdb 快照保存

下面说说 rdb 的优点

  • 对性能影响小 子进程 操作 rdb
  • 每次生成一个完整的 rdb 文件,作为非常可靠的灾难恢复手段
  • rdb文件恢复数据比 aof 快很多

RDB 缺点是

  • 快照是定期生成的,所以可能干好在 两次 rdb 之间 redis 挂了,那么这个自从上一次到这一次之间的数据,或多或少地会丢失部分数据
  • 如果数据集非常的大 而且 CPU 不够强,Redis 是 fork 子进程时可能会消耗相对较长的时间,因此影响这期间客户端的请求。

AOF
采用 aof 持久化方式,redis 会把每一次的写请求都放在一个日志文件里,redis 重启的时候,会把 aof 文件中的记录的所有的写操作顺序的执行一遍,确保数据恢复到最新
aof 默认是关闭的,需要开启进行如下配置

appendonly yes

aof 提供了 3 中 fsync 配置 always 、everysec 、no

appendfsync no 不进行 fsync 将 flush 文件的时机交给 os 决定,速度最快
appendfsync always 每写入一条日志就进行一次的 fsync 操作,数据安全性最高,单数速度最慢
appendfsync 折中的做法,交给后台线程每秒 fsync 一次

随着 aof 不断的记录写操作日子,必定会出现一些无用的日志,例如某个时间点执行了 set key1 “abc” ,在此之后又执行了 set key1 “bcd”,那么第一条命令显然是没有用的,大量的无用日志会使得 aof 文件过大,也会让数据恢复的时间加长。基于这种情况,redis 提供了 aof rewrite 功能,只保留能够把书恢复到最新状态的最小写操作集。
aof rewrite 命令 可以通过 bgrewriteaof 命令触发,也可以通过 redis 定期自动执行。

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min--size 64mb

两行的含义是  reids 在每次 aof  rewrite 时,会记录rewrite 后 aof 的大小,当 aof 日志
	在该基础上增长了 100% 后,自动进行 aof rewrite 操作,同时如果增长的大小没有达到 64mb
	 则不胡 rewrite

aof 的优点

  • 安全性很高, appendfsync always 时候,任何写入的数据都不会丢失,但是这个策略一般不会使用,太慢了
  • aof 在发生断电的时候也不会损坏,即使出现某条日志写入一半的情况,也可以使用 redis-check-aof 工具轻松修复
  • aof 文件易读

aof 的缺点

  • aof 的文件通常都比 rdb 文件大
  • 性能消耗比 rdb 高
  • 数据恢复比 rdb 慢

内存管理与数据淘汰机制

最大内存的设置
默认情况下 32 位 OS 中, redis 最大使用 3 GB 内存, 64 位 中没有限制
使用 redis 时候,要对存储的数据大小有一个预估,并为其设置最大的使用内存,否则 64 位 OS 中 redis 会无限制地占用内存。

maxmemory 100mb
通过这个参数配置 redis 使用的最大内存