集群设计的套路
了解了不少开源的有集群特性的软件,就像消息队列设计的时候一样,虽然有很多类型的消息队列,但设计时候需要解决的问题都是差不多的,如持久化、慢消费者的的问题、事务提交、订阅发布模式支持、负载均衡等,同样集群也有很多共性的问题,这里简单总结下。
套路&角色
- 集群设计的目的,就是设计更强大的软件功能,存储多、响应延迟低、吞吐量大。按照功能,可以简单分为分布式计算(spark、hadoop)、分布式应用(zk、yarn)、分布式存储(ceph、Hbase、mongdb、kafka、fastdfs、HDFS)、分布式缓存(Redis),分类并不严格不必计较,还有像ElasticSearch这类,又有存储又有计算。区分比较明显的是有没有状态,与存储相关的分布式存储与分布式缓存是有状态的,集群出问题的时候处理要小心,而在集群重建时,一般很费时间而且不稳定。
- 集群中的分工,集群的架构很像现实中的公司,很多集群的设计都是尽量简化,否则定位维护起来相当困难。一般来说,是有很多的工作节点(底层员工),处于最底层的干简单重复劳动的逻辑节点,而且集群的伸缩说的大部分就是这一部分的伸缩。
- 工作节点也会分组,干的工作稍有不同,但也差不太多,就像员工会有自己的项目组,不同项目组中做的事情稍有不同,像spark中各个节点计算任务不同、Redis、Hbase、ES中的存储要shard分片,分组的最大目的是并发,提高效率。
- 另外就是管理的角色,任务如何分配,人员离职入职后如何在分工,一般而言,集群都会有各自的leader,但这个leader一般会指派任务,客户来了单子来了,指派给其他人去做,自己工作尽量少,关键位置尽量稳定。
- 集群的节点组织分层一般三层或者两层,现在公司不都也是扁平化么,这样比较高效。而且集群中还有个优势,同一层还能伸缩扩展。
无状态
- 无状态当然是最好的,新节点加入后可以直接工作。当然是有条件的,像偏逻辑计算,也就是CPU和内存是瓶颈的分布式应用可以做成无状态的,像spark中的计算节点。一般大型的网站,应用服务器是设计成无状态的,也是现在Restful这么火的原因。
- 无状态也能够更好地进行负载均衡,比较理想的是根据各个无状态节点负载情况进行均衡。
- 设计上,很多为了达到“无状态”会把与存储相关的服务器单独出去,例如建立单独的session服务器集群,也有的利用Restful、客户的状态来达到这个效果。通常而言,无状态的设计是比较麻烦的。
负载均衡
- 集群最大的特定就是性能好,负载均衡一定少不了。均衡机制与缓存一样,到处可见。用户请求的前端nginx负载均衡、存储系统中多个分片的负载均衡、多个Redis副本提供的读负载均衡,磁盘上的RAID0也是两块盘的负载均衡,还有ceph中的数据条带化实现的写负载均衡。
- 在集群内部,使用集群的专业客户端最好,很多客户端也实现了部分的负载均衡机制,在客户端上配置一个或多个提供服务器的地址,其中部分不可用的时候会自动更新状态。否则像web服务,虽然有DNS负载均衡,但是单个IP上的压力还是很大的,需要使用lvs、nginx等专业的负载均衡。如果有专业客户端(大多数都所有),集群内部就不要给自己在引入单点问题了。
一致性
- 这个问题解决上,ceph自己实现了paxos算法,很多其他的开源都直接使用zk,像Hbase、Kafka。
备份
- 一般存储的集群会有多份的存储,很多默认的是两份或三份。写入的时候,有的对可用性比较高,要求必须写完两份才返回ceph,有的写一份就返回fastdfs,有的可以配置kafka。备份的时候会分成小的文件块在不同的node上,这里要注意,一个机器能部署很多node,写不同副本的node上不要再一个机器上,否则机器挂了就全丢了。
自动恢复
- 自动恢复是集群一个基本的属性,存储系统中,kafka,fastdfs,es都是自动进行数据备份与恢复的。
- 在备份的时候,都会有个leader,如果需要三份数据,A、B、C、D那么会从中找出一份leader 假如是A,数据先写入leader,然后由A写入后面的B、C和D,在kafka、fastdfs、es中都是这样的,而不是为了追求效率,写入A后,A写入B,然后B和A并发各自写一个C和D。这样设计主要还是为了简化,防止形成写的回路。
- 虽然有几份数据,但并不是所有的集群,副本都提供给用户读服务的,例如kafka中,所有的client的端只与主的partition进行读写操作,其他的就是做数据备份用的。而对读要求高的缓存Redis会用副本来提高读取的吞吐量。
缓存
- 很多集群并不提供缓存的特性,而是交给上层自己实现,因为需求不一样,热数据也不好判断。像fastdfs可以自己加一层nginx在nginx上利用缓存的插件来实现。
- ceph有自己的缓存设计,而且可以把SSD当成机械硬盘的缓存。
集群管理
- 一般来说都会选举出一个节点,或者有专门的节点例如HBase的Hmaster来进行一些全局化的操作。
- 如果操作不重,可以一个工作多个standby的方式(HBase),如果操作比较繁忙,可以对等集群(fastdfs的tracker提供路由查询),最后,厉害的来了,在ceph中没有这样的管理节点,而是把管理的功能分散到集群中的各个节点和客户端中,更加智能。
分片(shard)
- 非常常用的手段,多数用在存储中,一般都是通过计算hash的方式计算出几个shard的地址,在处理用户请求的时候有时也会根据用户的ip或用户id等其他信息计算hashcode在进行负载均衡。
- 对于存储系统,像ES和Redis这样,如果已经存在了大量数据被分片,虽然数据增长在分片,那集群重建就非常麻烦。在Redis中有个解决方式要预分片,就是把将来可能需要的情况也考虑进去,虽然目前四个节点能用大概一两年,但是可以直接分配32个或更多的分片(实例),虽然也是在四个物理节点上,但是将来加机器的时候,直接迁移部分Redis的逻辑节点就可以。
伸缩
- 集群的扩展性上,在增加节点后一般都是自动的均衡的,像ceph会自动把其他节点上的数据拷贝过去.
- 也有因为数据不好迁移,而通过负载均衡策略,把新的更多的存储数据写到新加入的节点例如fastdfs。
批量处理
- 为了提高吞吐量而设计的,很多集群因为要求实时性比较高这类设计没有考虑。而像注重吞吐量的集群如kafka,在写、读的时候,都会缓存一段时间,牺牲写实时性而增加吞吐量。
- 在Redis中为了提高吞吐量也有批量的操作。
协议问题
- 集群内部,几乎全都是私有的二进制协议,主要是从效率上来考虑。
- 为了去中心化,有的集群使用了gossip协议例如Redis Cluster这种集群方式,其他的集群像fastdfs与ceph的协议也有类似的功能。
IO与CPU问题
很多情况下,集群中IO(网络和磁盘)是比CPU紧张的,尤其是在偏存储的系统中,有三个例子:
- es的数据备份的时候,主分片给其他分片拷贝的是文档而不是通过分析体积更大的索引,其他的分片得到文档后会在进行一次分片。
- Hbase中的磁盘上列式存储其中一个优点就是压缩率高,存储的都是连续的相似的东西,在存储到磁盘上的时候都是经过压缩的数据,读取到内存中会在进行解压操作。
- Spark中也有类似的,Spark中的RDD,在节点失败后,不会给其他节点同步计算了一半的结果和数据,而是分成小的计算单元,失败了就重新计算一次,简化同步保存中间结果的过程。