Redis代理与集群的总结报告

【1】redis-cluster

【1.1】基本情况

RedisCluster是redis的分布式解决方案,支持分片;

Redis3.0版本之前,可以通过Redis Sentinel(哨兵)来实现高可用(HA);

从3.0版本后,官方推出了Redis Cluster,它的主要用途是实现数据分片(Data Sharding)与高可用

【1.2】基本原理

(1)集群连接、tcp通信情况

每个 Redis Cluster 节点都需要打开两个 TCP 连接。用于服务客户端的普通Redis TCP端口,例如6379,加上集群数据通信端口加上10000得到的端口,所以在示例中为16379。

(2)集群分片情况

Redis 集群使用了 哈希分区的 "虚拟槽分区" 方式(槽:slot),即哈希槽的概念

   所有的键根据 哈希函数(CRC16[KEY]&16383) 即 HASH_SLOT(key)= CRC16(key) % 16384

   映射到 0~16383槽内,共16384个槽位。每个节点维护部分槽及槽锁映射的键值数据。

Redis 集群有16384个哈希槽(slot),当需要在 Redis 集群中放置一个 key-value 时,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。

    

解耦数据与节点的关系,节点自身维护槽映射关系,分布式存储。

(3)集群key 数据分片情况

(1)只要涉及到单个命令执行(或整个事务,或 Lua 脚本执行)的所有键都属于同一个哈希槽。用户可以使用称为散列标签的概念强制多个键成为同一个散列槽的一部分。

(2)edis Cluster 规范中记录了哈希标签,但要点是如果键中的 {} 括号之间有一个子字符串,则只有字符串内的内容会被哈希;

因此例如this{foo}key和another{foo}key 保证在同一个哈希槽中, 并且可以在具有多个键作为参数的命令中一起使用。

(4)集群主从模式

1》分片:集群分片功能是任意个主节点都可以实现的,只要所有主节点的slot 一共是 16384个,且均可用;

可以没有从节点,但这样没有故障转移;任意一个主节点挂掉则整个集群不可用;

2》故障转移:就一个主库至少有一个以上的从库,这样实现故障转移,从库顶替主库;

如果某个节点的主从均宕机,则进群不可用;

(5)Redis 集群一致性问题

1》主从引起数据丢失:因为redis 主从是异步,所以无法完全保证数据不丢失 ;同步复制 通过wait实现,但性能影响极大,且在一些非常复杂的情况中也会丢失数据;

2》网络分区引起数据丢失:

以我们的6个节点集群为例,由A、B、C、A1、B1、C1组成,有3个master和3个slave。还有一个客户端,我们将其称为 Z1。

分区发生后,可能在分区的一侧有 A、C、A1、B1、C1,而在另一侧有 B 和 Z1。

(6)集群连接访问问题

0》请求重定向概念

集群模式下,Redis接收任何键相关命令时首先计算键对应的槽,再根据槽找出所对应的节点,如果节点是自身,则处理键命令;

否则回复MOVED重定向错误,通知客户端请求正确的节点。这个过程称为MOVED重定向。

1》当所有节点正常,无操作;

1、客户端发送请求到连接的集群的任意实例

2、连接实例根据hash相关算法找到slot=》在集群映射信息中,跟据slot找到对应的redis 目标实例;

如果目标实例就是自己,则直接处理请求返回数据;否则返回该实例信息给客户端让其重定向请求到这个新实例

3、客户端根据 2 返回的信息,发起请求到 slot所在的目标实例

4、目标实例获取数据返回给客户端

2》客户端在redis集群中请求正在迁移的slot的流程

 

【1.3】优劣

RedisCluster的优势:

》去中心化

》去中间件

》集群中任意节点平等,任意节点可以根据使用自动转发跳转到其他节点,从而获得全局的数据。

》水平扩展

RedisCluster的缺陷:

      》键的批量操作支持有限,比如mset,mget,如果多个键映射在不同的槽,就不支持了

      》不支持多数据库,只有0,select 0

      》复制结构只支持单层结构,不支持树形结构(即不能级联复制了)

      》键事务支持有限,当多个Key分部在不同节点时无法使用事务,同一节点是支持事务的

      》键是数据分区的最小粒度,不能将一个很大的键值对 映射到不同的节点

【1.4】功能性测试

  1. 前置条件:为实现故障转移+分片;至少3主3从,以便投票,即使偶数节点,也只有奇数节点有投票权;比如4个主节点,则只有3个节点有投票权
  2. 故障转移:主库挂掉,从库会自动故障转移;手动故障转移,在从库使用 cluster failover
  3. 使用跳转:根据hash key值,增删查改对应的跳转到相应主实例上的不同slot中去;
  4. 增加节点:若要节点可用需要从集群现有主节点中迁移slot过去;
    1. 不会自动rebalance,需要命令操作
    2. 迁移过程中会针对该slot中的Key一个一个迁移,迁移前后都可以访问,该key正在被迁移时无法访问
  5. 删除节点:
    1. 从节点可以直接删除,
    2. 主节点则需要迁移到所有slot 到集群其他主节点后,才能删除;不会自动rebalance,需要命令操作
    3. 主节点被删除后,主节点下的所有从节点会自动挂到其他主节点上去
  6. 集群分片slot:由于将哈希槽从一个节点移动到另一个节点不需要停止操作,因此添加和删除节点或更改节点持有的哈希槽百分比不需要任何停机时间。
    1. reshard交互式迁移:迁移多少个slot,从source迁移到destination
    2. reshard命令式迁移:直接指定从source迁移到destination,迁移多少个slot
    3. rebalance自动平衡分片迁移:可以自动平均的把slot分给所有的主节点;集群创建时就默认是它自动均分;也可以设置权重分配slot
    4. cluster setslot手动迁移:可以手动指点迁移某个固定slot
  7. 集群可用性:任意一个 slot 不存在或无法使用,则整个集群不可用;
    1. 比较常见的问题就是某一个没有从库的主库挂掉,导致该主库的所有槽不可用;则整个集群不可用;

【1.5】Redis集群参数功能性测试

#cluster

cluster-enabled yes

#-- 允许实例以集群模式启动,允许加入集群

cluster-config-file nodes-${port}.conf

#-- 每个加入群集的实例都会有一个,持久化的记录节点状态变化等信息

cluster-node-timeout 15000

#-- ms,节点响应超时,影响主从故障转移,影响网络分区主不可写入时间

cluster-replica-validity-factor 10

#-- 这个系数是 超时事件* + 集群心跳检查时间;例如:假设 A=cluster-node-timeout=30B=cluster-replica-validity-factor=10C=repl-ping=10,则 A*B+C = 30*10+10 = 310;

# 与主库断开310秒的从库不参与故障转移

cluster-require-full-coverage yes

# --当为no时: 如果请求过来发现集群无法处理(比如某个主实例挂了又没有副本顶上,造成缺失部分hash),则可以重新分配hash 覆盖原有数据;

# yes 则必须所有hash ok 状态集群才能访问

cluster-migration-barrier 1

# -- 迁移壁垒,即当某个主实例下没有副本,同时又有某个主实例下有超过1个以上的副本,则至少保留一个副本,多的则可以迁移到没有副本的实例下做副本

cluster-replica-no-failover no

# -- 副本是否故障转移,如果为 yes 则副本无法顶替主实例自动故障转移(可以手动)

cluster-allow-reads-when-down no

# --6.0参数,当集群不可用时,是否允许集群读访问(为 yes 时,只能对现有 key 读,但不能增删查改)

【2】redis代理 prodixy

【2.1】常见redis 代理对比与选择

(1)功能性对比

    

这里我们选择了 predixy;

(2)性能对比

 

Prodixy作者:性能测试报告 2017https://blog.csdn.net/rebaic/article/details/76384028

网易云信camellia:性能测试测试报告 2020https://github.com/netease-im/camellia/blob/master/docs/redis-proxy/performance-report-8.md

【2.2】predixy 功能、特点

  • 支持Redis Sentinel,可配置一组或者多组redis
  • 支持Redis集群
  • 支持redis多种类型的命令,包括blpop、brpop、brpoplpush
  • 支持扫描命令,无论是单个redis还是多个redis实例都支持
  • 多键命令支持:mset/msetnx/mget/del/unlink/touch/exists
  • 支持redis的多数据库,即可以使用select命令
  • 支持事务,当前验证于Redis Sentinel下组redis组可用
  • 支持脚本,包括命令:script load、eval、evalsha
  • 支持发布订阅机制,也即Pub/Sub系列命令
  • 多数据中心支持,删除支持
  • 扩展的AUTH命令,部分的读、写、管理权限控制机制,健空间限制机制
  • 日志可按级别输出,记录日志被io实时记录
  • 日志文件可以按时间、大小自动切分
  • 大量的统计信息,包括CPU、内存、请求、响应等信息

延迟监控信息,可以看到整体延迟,分秒还原事件延迟

【2.3】predixy 结合 redis-cluster 功能性测试

  • 支持redis多种类型的命令,包括blpop、brpop、brpoplpush、hget、hset
  • 支持权限,口令
  • 日志文件可以按时间、大小自动切分
  • 可以读写分离,也可以主从一起负载均衡,也可以从节点读负载均衡
  • 监控IO延迟
  • 所连接的集群增节点可以自动发现;删节点无法自动发现,会一直在错误日志中告警表示连不上该节点;但不影响正常分发命令

 

【3】性能测试

【3.1】redis 与 redis 多线程

单线程:

    Redis的单线程指的是"其网络IO和键值对读写是由一个线程完成的"。

也就是只有网络请求模块和数据操作模块是单线程的,而其他持久化存储模块、集群支撑模块等是多线程的。

多线程:

现在很多服务器都是多个CPU核的,但是对于Redis来说,因为使用了单线程,在一次数据操作的过程中,有大量的CPU时间片是耗费在了网络IO的同步处理上的,没有充分的发挥出多核的优势。

如果能采用多线程,使得网络处理的请求并发进行,就可以大大的提升性能。多线程除了可以减少由于网络 I/O 等待造成的影响,还可以充分利用 CPU 的多核优势。

所以,Redis 6.0采用多个IO线程来处理网络请求,网络请求的解析可以由其他线程完成,然后把解析后的请求交由主线程进行实际的内存读写。提升网络请求处理的并行度,进而提升整体性能。

但是,Redis 的多 IO 线程只是用来处理网络请求的,对于读写命令,Redis 仍然使用单线程来处理。

那么,在引入多线程之后,如何解决并发带来的线程安全问题呢?

"Redis 6.0的多线程只用来处理网络请求,而数据的读写还是单线程"的原因。

 

Redis 6.0 只有在网络请求的接收和解析,以及请求后的数据通过网络返回给时,使用了多线程。

而数据读写操作还是由单线程来完成的,所以,这样就不会出现并发问题了。

 

1,1个线程,也就是传统的单线程模式,get 操作的QPS可以达到7.3W左右(效率好的机器一般应该可以达到9-10W)

2,2个线程,get 操作的QPS可以达到15-16W左右,相比单线程有100%+的提升

3,4个线程,与2线程相比,会有30%左右的提高,get的QPS可以达到19-20W左右;但是已经没有从1个线程到2个线程翻一倍的提升了

4,6个线程,与4线程相比,没有明显的提升,对于SET操作,QPS从4线程到6线程,8线程开始没有出现明显的差异

5,8个线程,与4线程和6线程相比,8线程下大概有0-10%的提升

6,10个线程,相比效率最高的8线程,此时性能反倒是开始下降了,与4线程或者6线程的效率相当

因此在本机环境下,io-threads 4设置成2或者4个都ok,最多不超过8个,超出后性能反而会下降,同时也不能超出cpu的个数,正如配置文件中注释中说的,至少要留出一个CPU。

【3.2】redis-cluster 与 redis-cluster 多线程

单线程客户端压测--thread 1

1.当正常客户端访问时,所有连接会被均匀分布在不同的主库上

2.所有的get 会相对均匀的分散在不同的主库上

3.所有的set lpush 等操作根据hash算法分布在不同的主库上,压测有规律,所以并不一定会用到所有主库

 

多线程客户端压测--thread 4

1.当正常客户端访问时,压测多线程会使得连接相对集中的访问某个主库,导致最终 25% 25% 50% 这种分配情况

2.所有的get 会根据连接比例均匀的分散在不同的主库上

3.所有的set lpush 等操作根据hash算法和连接比例分布在不同的主库上,压测有规律,所以并不一定会用到所有主库

 

性能对比,连接在250左右的时候,4线程最高性能比单线程高了30%,再往上升 也没什么增长了

【3.3】predixy代理测试

在代理 WorkerThreads 3 时最佳;

在常规测试下 workerThreads 2-4区别不大

它的多线程指的是,predixy 发起多个线程,去连接集群节点;感觉和多个client连接没什么区别;

举例:

假如外部连接到代理,100个连接;代理开了4线程;

那么有50个连接的请求被分配到A节点,前面说了代理的4线程就相当于代理弄了4个连接连到集群节点;

所以这里就4个连接来承接50个连接的请求,最终整理好后由这4个连接来对集群节点发起请求操作

 

【3.4】单实例、集群、代理+集群 三者对比

项目

单实例

集群

代理+集群

性能对比

性能可以说无论时候时候都是最高
4线程时 可以飙升260%左右

与单实例一样的性能
4线程时,最大可以飙升30%左右

1、1线程和4线程没什么区别

cd /data/redis/bin/ && ./redis-benchmark -h 192.168.191.82 -p 8004 -d 3 -q -c 200 -n 10000000 --threads 1 -t get,set,lpush

2、tcp连接 受网络限制 开6个客户端运行压测命令,与开3个客户端运行压测命令CPU使用率差不多,都在60-100%左右,且若单机tcp为3W,6个客户端的压测脚本qps等则每个为5-6K左右

3、本地发起压测连接代理predixy,3个客户端运行压测命令则 predixy的CPU使用率即到了 250%-300%,且如果单个客户端是4W,则压测的每个本地客户端均是4W;由此可知网络情况对其影响还是非常大

 

这种网络下的tcp单线程压测时

在1连接的时候,性能为30-50%

在10连接的时候,性能为50%左右

在30连接的时候,性能为60-75%

在60连接的时候,性能为80%左右

在120连接的时候,性能为90%-100左右

超过240连接时,性能高于其他,在20%以内

压测多线程 --threads 参数对其没有意义,都一样

 

原文地址:https://www.cnblogs.com/gered/p/15210798.html