实现Redis 的内存扩展功能。
Redis集群最少需要三台主服务器,三台从服务器。端口号分别为:8001~8006, 创建8001实例,并编辑redis.conf文件,修改port为8001。
第一步:创建8001实例,修改redis.conf配置文件,打开cluster-enable yes
第二步:复制8001,创建8002~8006实例, 注意端口修改 。
第三步:启动所有的实例
第四步:创建Redis集群 --cluster-replicas 1 表示每个节点的副本数为1。cluster 从节点默认不可写不可读仅仅是备份。
# 创建集群 ./redis-cli --cluster create 127.0.0.1:8001 127.0.0.1:8002 127.0.0.1:8003 127.0.0.1:8004 127.0.0.1:8005 127.0.0.1:8006 --cluster-replicas 1 # 连接到其中一个客户端 ./redis-cli –h 127.0.0.1 –p 8001 –c # 查看集群状态 127.0.0.1:8006> cluster info # 查看集群中的节点 cluster nodes
下图是6个节点,无备份的cluster集群。
所有的redis节点彼此互联( PING-PONG机制 ),内部使用二进制协议优化传输速度和带宽.
客户端与Redis节点直连,不需要中间Proxy层,直接连接任意一个Master节点
redis-cluster把所有的物理节点映射到 [0-16383] slot 上,cluster 负责维护node→ slot→ value,根据公式HASH_SLOT=CRC16(key) mod 16384,计算出映射到哪个分片上,然后Redis会去相应的节点进行操作。 实现扩容功能。
节点的fail是通过集群中超过半数的节点检测失效时才生效
当一个主节点不可用时,该机器的备份节点转为主节点,若无备份节点。
Redis 集群中内置了 16384个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis
先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。 机器的哈希槽变动时,响应数据也应一起变动。
什么时候整个集群不可用(cluster_state:fail)?
优点:
缺点:
当集群中节点通过错误检测机制发现某个节点处于fail状态时,会执行主从切换。Redis 还提供了手动切换的方法,即通过执行 cluster failover 命令
假设A发生故障,主A的A1会执行切换,切换完成后A1变为主A1,此时主A1会出现单点题。
在周期性调度函数 clusterCron中会定期检查如下条件
具体漂移过程
当集群需要扩容时需要向集群中添加新的节点。
我们首先创建一个新的redis实例,端口为8007。其他配置同上。
启动7007节点,然后使用redis-cli将新的节点添加到集群中。
./redis-cli ƕcluster add-node 新节点的ip:端口 现有集群ip:端口
我们可以看到新的节点已经添加到集群中了,但是新的节点中并没有任何槽在其上,也就意味着,不可能有数据放到这个节点上。那么我们需要将一部分槽移动到新的节点上。
./redis-cli ƕcluster reshard 192.168.133.22:8001 --cluster-from f3ab5f5c53b82b8a9df24bfd4a8191224cf63597,faa53cbb6bae288d7f3a19fa55f18d7e77c9 7f6d, d33eab99055e05440a6ba1dd314efb0a25274fb1 --cluster-to 90dcd8e79fd753d24c35ef6ec63e65d79f56600a 3000
ƕcluster-from:表示slot目前所在的节点的node ID,多个ID用逗号分隔
ƕcluster-to:表示需要新分配节点的node ID(貌似每次只能分配一个)
ƕcluster-slots:分配的slot数量
我们向集群中添加了一个新的节点7007,如果只有一个主节点的情况下,高可用性会下降。那么我们就应该给这个节点增加一个备份节点提高可用性。我们再添加一个节点7008,按照之前的配置方法配置,然后启动。
./redis-cli --cluster add-node 192.168.133.22:8008 192.168.133.22:8007 -- cluster-slave --cluster-master-id 90dcd8e79fd753d24c35ef6ec63e65d79f56600a
--
cluster-master-id:表示slave对应的master的node ID下线节点192.168.23.129:7007(master)/192.168.23.129:7008(slave)
./redis-cli ƕcluster del-node 192.168.133.22:8008 5e8924bef3cfb0a07838d45da77e9a2e0e61d7b9
del-node后面跟着集群中的任意节点的ip及端口号(主要目的是连接到集群,ip:port) 和node ID
reshard子命令前面已经介绍过了,这里需要注意的一点是,由于我们的集群一共有四个主节点,而每次reshard只能写一个目的节点,因此以上命令需要执行三次( ƕcluster-to对应不同的目的节点)。
– cluster-yes:不回显需要迁移的slot,直接迁移。
./redis-cli --cluster reshard 192.168.133.22:8007 --cluster-from 90dcd8e79fd753d24c35ef6ec63e65d79f56600a --cluster-to f3ab5f5c53b82b8a9df24bfd4a8191224cf63597 --cluster-slots 3000 --cluster-yes
./redis-cli ƕcluster del-node 192.168.133.22:8007 90dcd8e79fd753d24c35ef6ec63e65d79f56600
Redis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。
Redis对象有5种类型;无论是哪种类型,Redis都不会直接存储,而是通过redisObject对象进行存储。
redisObject对象非常重要,Redis对象的类型、内部编码、内存回收、共享对象等功能,都需要
redisObject支持,下面将通过redisObject的结构来说明它是如何起作用的。
Redis没有直接使用C字符串(即以空字符’\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。
下面以最简单的字符串类型来进行说明。
info memory
记录的是由 操作系统分配 的 Redis进程内存 和Redis内存中无法再被jemalloc分配的 内存碎片 (单位是字节)。
used_memory和used_memory_rss的区别:
前者是从Redis角度得到的量,后者是从操作系统角度得到的量。二者之所以有所不同,一方面是因为内存碎片和Redis进程运行需要占用内存,使得前者可能比后者小。
内存碎片比率 ,该值是used_memory_rss / used_memory的比值。
mem_fragmentation_ratio一般大于1,且该值越大,内存碎片比例越大。
mem_fragmentation_ratio<1:(刚运行时redis内存中无数据 使比例小于1,可查看长期后的数据)
mem_fragmentation_ratio值计算为进程的内存驻留集大小(RSS,由OS测量)与Redis使用分配器分配的总字节数之间的比率。 现在,如果使用libc分配更多内存(与jemalloc,tcmalloc相比),或者在基准测试期间系统上的某些其他进程使用了更多内存,则可以通过操作系统交换Redis内存。它会减少RSS(因为Redis内存的一部分不再存在于主内存中)。由此产生的碎裂率将小于1。 换句话说,只有当您确定操作系统没有交换Redis内存时,此比率才有意义(如果不是这样,那么无论如何都会出现性能问题)。
一般来说,mem_fragmentation_ratio在1.03左右是比较健康的状态(对于jemalloc来说);刚开始的mem_fragmentation_ratio值很大,是因为还没有向Redis中存入数据,Redis进程本身运行的内存使得used_memory_rss 比used_memory大得多。
我们应该根据实际的业务情况,对键值设置合理的过期时间,这样 Redis 会帮你自动清除过期的键值对,以节约对内存的占用,以避免键值过多的堆积,频繁的触发内存淘汰策略。
Redis 有四个不同的命令可以用于设置键的生存时间(键可以存在多久)或过期时间(键什么时候会被删除) :
lazy free 特性是 Redis 4.0 新增的一个非常使用的功能,它可以理解为惰性删除或延迟删除。意思是在删除的时候提供异步延时释放键值的功能,把键值释放操作放 IO(Background I/O) 单独的子线程处理中,以减少删除删除对 Redis 主线程的阻塞,可以有效地避免删除 big key 时带来的性能和可用性问题。
lazy free 对应了 4 种场景,默认都是关闭的:
lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no slave-lazy-flush no
建议开启其中的 lazyfree-lazy-eviction、lazyfree-lazy-expire、lazyfree-lazy-server-del
等配置,这样就可以有效的提高主线程的执行效率。
redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。
最佳设置: 是物理内存的75% ,写操作比较多 60%
# 最大缓存 maxmemory 1048576 maxmemory 1048576B maxmemory 1000KB maxmemory 100MB maxmemory 1GB
redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。
maxmemory-policy voltile-lru,支持热配置 内存淘汰策略在 Redis 4.0 之后有 8 种:
在 Redis 4.0 版本中又新增了 2 种淘汰策略:
其中 allkeys-xxx 表示从所有的键值中淘汰数据,而 volatile-xxx 表示从设置了过期键的键值中淘汰数据。
我们可以根据实际的业务情况进行设置,默认的淘汰策略不淘汰任何数据,在新增时会报错。
Redis 绝大多数读写命令的时间复杂度都在 O(1) 到 O(N) 之间,在官方文档对每命令都有时间复杂度说明
其中 O(1) 表示可以安全使用的,而 O(N) 就应该当心了,N 表示不确定,数据越大查询的速度可能会越慢。因为 Redis 只用一个线程来做数据查询,如果这些指令耗时很长,就会阻塞 Redis,造成大量延时。
要避免 O(N) 命令对 Redis 造成的影响,可以从以下几个方面入手改造:
多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题。单线程模式下,可以方便地进行调试和测试。
一旦受到网络请求就会在内存中快速处理,由于绝大多数的操作都是纯内存的,所以处理的速度会非常地快。也就是说在单线程模式下,即使连接的网络处理很多,因为有IO多路复用,依然可以在高速的内存处理中得到忽略。
读请求11W+ 写请求8.7W+。
多线程能够充分利用CPU的资源,但对于Redis来说,由于基于内存速度那是相当的高,能达到在一秒内处理10万个用户请求,如果一秒十万还不能满足,那我们就可以使用Redis分片的技术来交给不同的Redis服务器。这样的做法避免了在同一个 Redis 服务中引入大量的多线程操作。
总结:基于内存而且使用多路复用技术,单线程速度很快,又保证了多线程的特点。因为没有必要使用多线程。
FD是一个文件描述符,意思是表示当前文件处于可读、可写还是异常状态。使用 I/O 多路复用机制同时监听多个文件描述符的可读和可写状态。 你可以理解为具有了多线程的特点。
总结:
我们可以使用 slowlog 功能找出最耗时的 Redis 命令进行相关的优化,以提升 Redis 的运行
速度,慢查询有两个重要的配置项:
我们可以使用 slowlog get n
来获取相关的慢查询日志,再找到这些慢查询对应的业务进行相关的优化。
Redis 过期键值删除使用的是贪心策略,它每秒会进行 10 次过期扫描,此配置可在 redis.conf 进行配置,默认值是 hz 10,Redis 会随机抽取 20 个值,删除这 20 个键中过期的键,如果过期 key 的比例超过 25% ,重复执行此流程。
如果在大型系统中有大量缓存在同一时间同时过期,那么会导致 Redis 循环多次持续扫描删除过期字典,直到过期字典中过期键值被删除的比较稀疏为止,而在整个执行过程会导致 Redis 的读写出现明显的卡顿,卡顿的。另一种原因是内存管理器需要频繁回收内存页,因此也会消耗一定的 CPU。
Pipeline (管道技术) 是客户端提供的一种批处理技术
可以批量执行一组指令,一次性返回全部结果,可以减少频繁的请求应答。
在客户端的使用上我们除了要尽量使用 Pipeline 的技术外,还需要注意要尽量使用 Redis 连接池,而不是频繁创建销毁 Redis 连接,这样就可以减少网络传输次数和减少了非必要调用指令。
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
Redis 分布式架构有重要的手段:
Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内,计算公式:slot = CRC16(key) & 16383,每一个节点负责维护一部分槽以及槽所映射的键值数据。这样 Redis 就可以把读写压力从一台服务器,分散给多台服务器了,因此性能会有很大的提升。
Linux kernel 在 2.6.38 内核增加了 Transparent Huge Pages (THP) 特性 ,支持大内存页 2MB分配,默认开启。
当开启了 THP 时,fork 的速度会变慢,fork 之后每个内存页从原来 4KB 变为 2MB,会大幅增加重写期间父进程内存消耗。同时每次写命令引起的复制内存页单位放大了 512 倍,会拖慢写操作的执行时间,导致大量写操作慢查询。例如简单的 incr 命令也会出现在慢查询中,因此 Redis 建议将此特性进行禁用,禁用方法如下:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
为了使机器重启后 THP 配置依然生效,可以在 /etc/rc.local 中追加 echo never >
/sys/kernel/mm/transparent_hugepage/enabled