分布式架构按照拆分的原则,将应用分配到不同的物理资源上,以此获得高性能和高可用。
而这种拆分势必要解决:将数据分散在多个节点上,又要确保数据存储一致性。
本文基于Redis 主从模式上去做阐述。
1、Redis-Server:Redis服务端,用于存储用户数据的,此处就一个master节点【IP: 100.100.100.1:6379】
2、MySQL数据库,用于存储用户信息、Redis-Server的集群ID、拓扑结构及角色信息等: 此处是用json串示例:{"master":{"role":"master","port":"6379","slot-start":0,"slot-end":16383,"host":"100.100.201.1","shard":"shard1"}}
说明:当Redis-Server主从集群创建好后,MySQL数据库就存有这些信息,包括不限于集群ID、拓扑结构等。
3、Redis-Client:redis客户端【IP: 192.168.168.1】,用于模拟客户端请求,可以用官方的sdk去写一段demo,也可以用redis-benchmark代替;
服务端Redis-Server就是个单节点,自然Redis-Client所有连接都与该节点建立,并且所有的读写都请求到该节点上。
具体逻辑:
这种模式简单,易实现,但当Redis-Client特别多的时候,那么Redis-Server在建立连接数都需要耗服务端资源。而往往Redis-Client特别多的时候,伴随着高并发请求,那么对Redis-Server处理正常的请求的CPU计算、各种Buffer以及节点网络IO都是一个挑战。
另外,还有很重要的一点,当master节点遇到故障(比如节点自身宕机),那么服务端将无法提供服务,若之前没有做数据持久化则有丢数据风险。即高可用不满足。
所以,这种单节点单主的模式,实际当中使用较少。仅适用于规模不大、并发量不高、对高可用也没啥要求的场景。
由于单节点单主模式无法满足日渐高涨的并发请求以及故障备份数据事项(高可用),所以考虑在单主之下挂多个副本,依次来满足故障数据备份。同时可以通过配置来完成“写主读从”,从而提升整个集群的吞吐。另外,多副本,可以分散到更多的物理节点上,这样客户端根据配置也可以“实现就近访问”。
假设 在一套环境的多节点上(可以不同地域不同机房,只要网络能通信即可)有如下部署:
1、Redis-Server:Redis服务端,用于存储用户数据的,此处采用一主两从。有个master角色的节点【IP: 100.100.100.1:6379】,其下挂2个slave节点【IP: 200.100.201.1:6379 和 200.100.202.1:6379】;【Redis挂从slaveof成功后会自动进行主从同步。有关Redis主从同步逻辑本文不再叙述,后续会有其他文章】
2、MySQL数据库,用于存储用户信息、Redis-Server的集群ID、拓扑结构及角色信息等: 此处是用json串示例:{"master":{"role":"master","port":"6379","slot-start":0,"slot-end":16383,"host":"100.100.201.1","shard":"shard1"},"slave":[{"port":"6379","slot-start":0,"slot-end":16383,"host":"200.100.201.1","shard":"shard1","slave":"slave1"},{"port":"6379","slot-start":0,"slot-end":16383,"host":"200.100.202.1","shard":"shard1","slave":"slave2"}]}
3、Redis-Client:redis客户端【IP: 192.168.168.1】,用于模拟客户端请求;
整体逻辑与之前单节点单主模式一样,都是webConsole创建集群,集群信息存储到MySQL中,客户端去取并解析加载到本地内存,再按将请求发送到Redis-Server上。
一般都是写请求都是到master角色上,由redis-server自己完成主从同步数据。
说明:redis.conf配置文件中,可以将从slave支持处理写请求,但那样主从数据一致性控制较难,不仅加大系统复杂度,也从一定程度上加大系统性能损耗。故业界一般都是写请求由master来控制。
补充:Jedis客户端SDK,可以通过请求增加参数来要求服务端主从强一致性。对于示例中有两副本,可以要求主从同步几个从再返回给客户端。该逻辑也不在此描述。本文默认是弱一致性,即满足BASE理论中的“最终一致性”,即master写成功后,就立即返回,主从同步走异步。
master主节点当然可以完成读请求处理。
但为提高整个集群的服务读写吞吐,也可以将读请求到slave从节点上,即完成“读写隔离”\”写主读从”。
这就要求redis-client在处理请求时按一定规则将请求打到对应的slave从节点上。常见的有轮巡策略、负载均衡、就近访问等。
对于一些实时性不要求不是很高的场景,推荐使用【写主读从】
补充:对于读请求到slave从节点上,将可能会有一定的数据延迟,这取决于主从之间同步信息情况以及网络情况。网络稳定的情况下,这个延迟时间窗口会很小,
1、数据备份恢复:
2、读写分离,slave可以抗读流量
上述单主主从模式下的集群,在处理高并发时可能不是那么理想,当redis-client比较多,就需要建立多个TCP连接,对单主是一种挑战。并且伴随请求频繁时,对于master主节点CPU处理以及网络IO都有压力。
所以,此时可以将单主模式替换为多主,采用shard来管理。
shard及分片,也有文献资料也称之为分区。
shard网上有资料可以按范围划分、也有哈希分区划分。
本质就是用一个hash函数将key转换为一个数字,比如使用crc32 hash函数。对key foobar执行crc32(foobar)会输出类似93024922的整数。
对这个整数取模,将其转化为0-3之间的数字,就可以将这个整数映射到4个Redis实例中的一个了(服务端有4个不同分区的实例)。93024922 % 4 = 2,就是说key foobar应该被存到R2实例中。
注意:取模操作是取除的余数,通常在多种编程语言中用%操作符实现。
1、每个shard上存储的数据,是按key的哈希函数映射规定存放的
2、Redis官方的规定一个集群总计有16384个slot槽,即要确保部署到环境里的集群集群所有shard上实例slot的范围数必然是0~16383,没有少slot编号,也不可出现某个槽位号同时出现在不同shard里。
说明:源生redis 出于心跳包携带节点配置占用空间16k插槽,而cluster不太可能有1000多个主,故出于 设计均衡考虑2k空间=2^14 bit = 16384个槽
3、每个redis-server负责的slot范围,可以在redis.conf里配置,必须是连续的,且最少一个slot。每个槽位起始即结束在redis-server启动后就固定,实例存活期间内不可变更;
理论上最多可以有16384个分片。但实际当中,有1w个shard就会影响性能。不仅仅是因为服务端节点资源的缘故,也因为redis-client需要给每个shard上redis-server建立TCP连接,至少是master实例,一个redis-client端是没法建立这么多连接的;所以,不能随心所欲切分很多分片shard。一般需要根据实际场景及业务流量来划分shard数,正常情况下不会超过128个分片。
4、一个集群内对于多个shard上负责的slot槽位数可以不均衡。
但这种一般需要考虑到实际特殊业务场景,比如:一个集群规划好两个分片,但第二个分片上的靠后的十个个槽位存储大key或key的个数特别多,导致这几个槽位上存储占用量都比其他所有槽位加起来都要多。
Ø 对于这种特殊场景,只要启动之前能明确,就可以修改redis.conf配置再重启。
这种精确化切分shard里对应slot,能够更好的利用每个redis-server的内存空间,同时对于Redis-server的cpu计算也有利。因为多shard,自然整个集群的网络IO也多种保障。
本文为简单,采用两个shard。Redis-Server如下图所示:
说明:
补充:对于官方集群版,则每个shard的redis-server会存留其他shard的拓扑信息.
与单主主从相似,有以下两点不同:
1、此处是多个redis-client,每个client之间不通信,每个client都会从MySQL数据库里获取集群拓扑信息并解析加载到本地内存。json串示意:{"master":[{"port":"6379","slot-start":0,"slot-end":8192,"host":"200.100.201.1","shard":"shard1","master":"master1"},{"port":"6379","slot-start":8193,"slot-end":16383,"host":"200.100.201.2","shard":"shard2","master":"master2"}],"salve":[{"slave":"slave1","port":"6379","slot-start":0,"slot-end":8192,"host":"200.100.201.1","shard":"shard1"},{"slave":"slave2","port":"6379","slot-start":0,"slot-end":8192,"host":"200.100.202.1","shard":"shard1"},{"slave":"slave3","port":"6379","slot-start":8193,"slot-end":16383,"host":"200.100.201.3","shard":"shard2"},{"slave":"slave4","port":"6379","slot-start":8193,"slot-end":16383,"host":"200.100.202.4","shard":"shard2"}]}
2、redis-client向服务端redis-server每次发起请求前都会对key进行哈希函数值计算,以便知晓key在哪个shard上,然后将请求直接直接打到该shard。服务端接收到后,该shard上redis-server会计算key的哈希函数值是否在本节点的slot范围内:是,则继续处理,不是直接返回客户端Error信息。
说明:本文示例的是主从版,对于官方集群版,会直接将请求拒绝处理,并返回给客户端新的服务端地址【key映射的slot所在的分片的节点上】,由客户端完成retry处理;
注意:无论哪种模式,redis-server与redis-client所使用的哈希函数一定要一致,才能确保client请求经过网络路由到同一实例。
master采用多shard之后,在提高性能同时,也充分利用了多节点上的网络IO、内存空间、节点连接数压力、Socket IO等,从而能更好的提升性能。需要注意的是:划分多个shard的考虑事项。
而多slave,除了备份数据之外,就是单节点故障,即侧重高可用方面。当然对slave去抗读流量也是提高整个集群的高并发处理事项。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。