Loading

Redis

date: 2020-09-18 17:07:00
updated: 2020-09-25 11:03:00

Redis

参考网址

1. 概述

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

  • Redis是线程安全的(因为只有一个线程),其所有操作都是原子的,不会因并发产生数据异常
  • Redis的速度非常快(因为使用非阻塞式IO,且大部分命令的算法时间复杂度都是O(1))
  • 使用高耗时的Redis命令是很危险的,会占用唯一的一个线程的大量处理时间,导致所有的请求都被拖慢。(例如时间复杂度为O(N)的KEYS命令,严格禁止在生产环境中使用)

2. 数据结构

2.1 Key-Value

任何二进制序列都可以作为Redis的Key使用(例如普通的字符串或一张JPEG图片)。

  • 不要使用过长的Key。例如使用一个1024字节的key就不是一个好主意,不仅会消耗更多的内存,还会导致查找的效率降低
  • Key短到缺失了可读性也是不好的,例如"u1000flw"比起"user:1000:followers"来说,节省了寥寥的存储空间,却引发了可读性和可维护性上的麻烦
  • 最好使用统一的规范来设计Key,比如"object-type🆔attr",以这一规范设计出的Key可能是"user:1000"或"comment🔢reply-to"
  • Redis允许的最大Key长度是512MB(对Value的长度限制也是512MB)

2.2 String

Redis的基础数据类型只有 String。

与 String 相关的常用命令:

  • SET :为一个key设置value,可以设置过期时间,-1为永久有效,时间复杂度O(1)
  • GET :获取某个key对应的value,时间复杂度O(1)
  • GETSET :为一个key设置value,并返回该key的原value,时间复杂度O(1)
  • MSET :为多个key设置value,时间复杂度O(N) -> 参数为 Map<byte[], byte[]>
    • 底层实现的时候还是for循环一个个set进去,mset和set的效率差别在是否需要销毁连接,通过pipeline的方式可以一次性发送过去多条指令,但是通过连接池实现时连接会一直存在,此时的效率差距就不是很大了
    • 由于底层是for循环,那么根据数据的key,计算出槽位,按照槽位来分组,通过 ParallelStream 来并行处理相同槽位的数据,可以提高上下行效率
  • MSETNX :同MSET,如果指定的key中有任意一个已存在,则不进行任何操作,时间复杂度O(N)
  • MGET :获取多个key对应的value,时间复杂度O(N)

虽然Redis的基本数据类型只有String,但Redis可以把String作为整型或浮点型数字来使用,主要体现在INCR、DECR类的命令上:

  • INCR :将key对应的value值自增1,并返回自增后的值。只对可以转换为整型的String数据起作用。时间复杂度O(1)
  • INCRBY :将key对应的value值自增指定的整型数值,并返回自增后的值。只对可以转换为整型的String数据起作用。时间复杂度O(1)
  • DECR/DECRBY :同INCR/INCRBY,自增改为自减。

INCR/DECR系列命令要求操作的value类型为String,并可以转换为64位带符号的整型数字,否则会返回错误。也就是说,进行INCR/DECR系列命令的value,必须在[-2^63 ~ 2^63 - 1]范围内。

2.3 其他存储形式

3. 数据持久化

3.1 RDB

采用RDB持久方式,Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。

可以在配置文件中配置多个Redis进行快照保存的时机

save [seconds] [changes]
意为在[seconds]秒内如果发生了[changes]次数据修改,则进行一次RDB快照保存
  • RDB的优点:
    • 对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
    • 每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。
    • 使用RDB文件进行数据恢复比使用AOF要快很多。
  • RDB的缺点:
    • 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据。
    • 如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间(长至1秒),影响这期间的客户端请求。

3.2 AOF

采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。

缺点:恢复速度慢,因为恢复的时候需要遍历日记文件;对一个key不断进行操作,记录多次,有冗余,针对这一点可以对日志冗余的部分进行合并重写

4. 内存管理

提前预估设置最大内存使用大小,避免无限制占用内存。

在内存占用达到了maxmemory后,再向Redis写入数据时,Redis会:

  • 根据配置的数据淘汰策略尝试淘汰数据,释放空间
  • 如果没有数据可以淘汰,或者没有配置数据淘汰策略,那么Redis会对所有写请求返回错误,但读请求仍然可以正常执行

在为Redis设置maxmemory时,需要注意:

  • 如果采用了Redis的主从同步,主节点向从节点同步数据时,会占用掉一部分内存空间,如果maxmemory过于接近主机的可用内存,导致数据同步时内存不足。所以设置的maxmemory不要过于接近主机可用的内存,留出一部分预留用作主从同步。

5. 数据淘汰机制

Redis 提供5种数据淘汰策略:

  • volatile-lru:使用LRU算法进行数据淘汰(淘汰上次使用时间最早的,且使用次数最少的key),只淘汰设定了有效期的key
  • allkeys-lru:使用LRU算法进行数据淘汰,所有的key都可以被淘汰
  • volatile-random:随机淘汰数据,只淘汰设定了有效期的key
  • allkeys-random:随机淘汰数据,所有的key都可以被淘汰
  • volatile-ttl:淘汰剩余有效期最短的key

6. PipeLining

比起多次传输数据不如一次性传输多个数据 => mset 一次性传输一个map的key-value的效率要高于多次set传输key-value的效率 => 前提是set返回的结果并不是迫切需要的

7. 性能调优

尽管Redis是一个非常快速的内存数据存储媒介,也并不代表Redis不会产生性能问题。前文中提到过,Redis采用单线程模型,所有的命令都是由一个线程串行执行的,所以当某个命令执行耗时较长时,会拖慢其后的所有命令,这使得Redis对每个任务的执行效率更加敏感。

针对Redis的性能优化,主要从下面几个层面入手:

  • 最初的也是最重要的,确保没有让Redis执行耗时长的命令
  • 使用pipelining将连续执行的命令组合执行
  • 操作系统的Transparent huge pages功能必须关闭:
    echo never > /sys/kernel/mm/transparent_hugepage/enabled
  • 如果在虚拟机中运行Redis,可能天然就有虚拟机环境带来的固有延迟。可以通过./redis-cli --intrinsic-latency 100命令查看固有延迟。同时如果对Redis的性能有较高要求的话,应尽可能在物理机上直接部署Redis。
  • 检查数据持久化策略
  • 考虑引入读写分离机制

8. 长耗时命令

避免使用 O(N) 的指令。

9. 为什么只有16384个槽

参考

客户端请求的key,根据 HASH_SLOT=CRC16(key) mod 16384 计算出需要映射到哪一个分片上,然后 redis 会到相应的节点进行操作。

CRC16 算法产生的hash值有16bit,该算法可以产生2^16-=65536个值。换句话说,值是分布在0~65535之间。那作者在做mod运算的时候,为什么不mod65536,而选择mod16384?

作者回答

The reason is:
Normal heartbeat packets carry the full configuration of a node, that can be replaced in an idempotent way with the old in order to update an old config. This means they contain the slots configuration for a node, in raw form, that uses 2k of space with16k slots, but would use a prohibitive 8k of space using 65k slots.
At the same time it is unlikely that Redis Cluster would scale to more than 1000 mater nodes because of other design tradeoffs.
So 16k was in the right range to ensure enough slots per master with a max of 1000 maters, but a small enough number to propagate the slot configuration as a raw bitmap easily. Note that in small clusters the bitmap would be hard to compress because when N is small the bitmap would have slots/N bits set that is a large percentage of bits set.

当两个节点meet之后,会定期进行通信,交换数据信息。这个数据信息包括节点id、ip、端口号以及本节点负责的槽位信息等等。在消息头里最占空间的是 myslots[CLUSTER_SLOTS/8],大小为 16384÷8÷1024=2kb,myslots 是一个char数组,每一位代表一个槽,该位为1表示这个槽属于这个节点。

数据消息体里会携带一定数量的其他节点信息用于交换 => 节点数量越多,消息体内容越大。

∴ 如果槽位过多,那么消息体占的内容越大,65536个槽对应8k,浪费带宽;同时也不会有太多的节点,16384足够用

posted @ 2020-10-22 11:26  猫熊小才天  阅读(83)  评论(0编辑  收藏  举报