🌏【架构师指南】分布式技术知识点总结(中)
架构蓝图
整个架构是分层的分布式的架构,纵向包括 CDN,负载均衡/反向代理,web 应用,业务层,基础服务层,数据存储层。水平方向包括对整个平台的配置管理部署和监控。
剖析架构
CDN
CDN 系统能够实时地根据网络流量和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决 Internet 网络拥挤的状况,提高用户访问网站的响应速度。
对于大规模电子商务平台一般需要建 CDN 做网络加速,大型平台如淘宝、京东都采用自建 CDN,中小型的企业可以采用第三方 CDN 厂商合作,如蓝汛、网宿、快网等。
当然在选择 CDN 厂商时,需要考虑经营时间长短,是否有可扩充的带宽资源、灵活的流量和带宽选择、稳定的节点、性价比。
负载均衡、反向代理
大型的平台包括很多个业务域,不同的业务域有不同的集群,可以用 DNS 做域名解析的分发或轮询,DNS 方式实现简单,但是因存在 cache 而缺乏灵活性;
一般基于商用的硬件 F5、NetScaler 或者开源的软负载 LVS 在 4 层做分发,当然会采用做冗余(比如 LVS+keepalived)的考虑,采取主备方式。
4 层分发到业务集群上后,会经过 web 服务器如 nginx 或者 HAProxy 在 7 层做负载均衡或者反向代理分发到集群中的应用节点。
选择哪种负载,需要综合考虑各种因素(是否满足高并发高性能,Session 保持如何解决,负载均衡的算法如何,支持压缩,缓存的内存消耗);下面基于几种常用的负载均衡软件做个介绍。
LVS,工作在 4 层 ,支持多种转发方式(NAT、DR、FullNAT、IP Tunneling),其中 DR 模式支持通过广域网进行负
载均衡。支持双机热备(Keepalived 或者 Heartbeat)。对网络环境的依赖性比较高。
Nginx 工作在 7 层,事件驱动的、异步非阻塞的架构、支持多进程的高并发的负载均衡器/反向代理软件。可以针对域名、目录结构、正则规则针对 http 做一些分流。通过端口检测到服务器内部的故障,比如根据服务器处理网页返回的状态码、超时等等,并且会把返回错误的请求重新提交到另一个节点,不过其中缺点就是不支持 url 来检测。对于 session sticky,可以基于 ip hash 的算法来实现,通过基于 cookie 的扩展 nginx-sticky-module 支持 session sticky。
HAProxy 支持 4 层和 7 层做负载均衡,支持 session 的会话保持,cookie 的引导;支持后端 url 方式的检测;负载均衡的算法比较丰富,有 RR、权重等。对于图片,需要有单独的域名,独立或者分布式的图片服务器或者如 mogileFS,可以图片服务器之上加 varnish 做图片缓存。
应用接入层
应用层运行在 jboss 或者 tomcat 容器中,代表独立的系统,比如前端购物、用户自主服务、后端系统等协议接口,HTTP、JSON,可以采用 servlet3.0(异步化 servlet),提高整个系统的吞吐量,http 请求经过 Nginx,通过负载均衡算法分到到 App 的某一节点,这一层层扩容起来比较简单。
除了利用 cookie 保存少量用户部分信息外(cookie 一般不能超过 4K 的大小),对于接入层应用,保存有用户相关的 session 数据,但是有些反向代理或者负载均衡不支持对 session sticky 支持不是很好或者对接入的可用性要求比较高(app 接入节点宕机,session 随之丢失),这就需要考虑 session 的集中式存储,使得应用接入层无状态化,同时系统用户变多的时候,就可以通过增加更多的应用节点来达到水平扩展的目的。
Session 的集中式存储,需要满足以下几点要求:
a、高效的通讯协议
b、session 的分布式缓存,支持节点的伸缩,数据的冗余备份以及数据的迁移
c、session 过期的管理
业务服务
代表某一领域的业务提供的服务,对于电商而言,领域有用户、商品、订单、红包、支付业务等等,不同的领域提供不同的服务。
这些不同的领域构成一个个模块,良好的模块划分和接口设计非常重要,一般是参考高内聚、接口收敛的原则,这样可以提高整个系统的可用性。当然可以根据应用规模的大小,模块可以部署在一起,对于大规模的应用,一般是独立部署的。
高并发
业务层对外协议以 NIO 的 RPC 方式暴露,可以采用比较成熟的 NIO 通讯框架,如 netty、mina
高可用
为了提高模块服务的可用性,一个模块部署在多个节点做冗余,并自动进行负载转发和失效转移;
最初可以利用 VIP+heartbeat 方式,目前系统有一个单独的组件 HA,利用 zookeeper 实现(比原来方案的优点)
一致性、事务:
对于分布式系统的一致性,尽量满足可用性,一致性可以通过校对来达到最终一致的状态。
基础服务中间件
通信组件
通信组件用于业务系统内部服务之间的调用,在大并发的电商平台中,需要满足高并发高吞吐量的要求。
整个通信组件包括客户端和服务端两部分。
客户端和服务器端维护的是长连接,可以减少每次请求建立连接的开销,在客户端对于每个服务器定义一个连接池,初始化连接后,可以并发连接服务端进行 RPC 操作,连接池中的长连接需要心跳维护,设置请求超时时间。
对于长连接的维护过程可以分两个阶段,一个是发送请求过程,另外一个是接收响应过程。
发送请求时,若发生 IOException,则把该连接标记失效。
接收响应时,服务端返回 SocketTimeoutException,如果设置了超时时间,那么就直接返回异常,清除当前连接中那些超时的请求。否则继续发送心跳包(因为可能是丢包,超过 pingInterval 间隔时间就发送 ping 操作)
若 ping 不通(发送 IOException),则说明当前连接是有问题的,那么就把当前连接标记成已经失效;
若 ping 通,则说明当前连接是可靠的,继续进行读操作。失效的连接会从连接池中清除掉。
每个连接对于接收响应来说都以单独的线程运行,客户端可以通过同步(wait,notify)方式或者异步进行 rpc 调用,序列化采用更高效的 hession 序列化方式。
服务端采用事件驱动的 NIO 的 MINA 框架/Netty 框架,支撑高并发高吞吐量的请求。
路由 Router
在大多数的数据库切分解决方案中,为了提高数据库的吞吐量,首先是对不同的表进行垂直切分到不同的数据库中,然后当数据库中一个表超过一定大小时,需要对该表进行水平切分,这里也是一样,这里以用户表为例,对于访问数据库客户端来讲,需要根据用户的 ID,定位到需要访问的数据;
数据切分算法
根据用户的 ID 做 hash 操作,一致性 Hash,这种方式存在失效数据的迁移问题,迁移时间内服务不可用,维护路由表,路由表中存储用户和 sharding 的映射关系,sharding 分为 leader 和 replica,分别负责写和读。
这样每个 biz 客户端都需要保持所有 sharding 的连接池,这样有个缺点是会产生全连接的问题;
解决方法是 sharding 的切分提到业务服务层进行,每个业务节点只维护一个 shard 的连接即可。
路由组件的实现是这样的(可用性、高性能、高并发)
基于性能方面的考虑,采用 MongoDB 中维护用户 id 和 shard 的关系,为了保证可用性,搭建 replicatset 集群。
biz 的 sharding 和数据库的 sharding 是一一对应的,只访问一个数据库 sharding.
biz 业务注册节点到 zookeeper 上/bizs/shard/下。
router 监听 zookeeper 上/bizs/下节点状态,缓存在线 biz 在 router 中。
client 请求 router 获取 biz 时,router 首先从 mongodb 中获取用户对应的 shard,router 根据缓存的内容通过 RR 算法获取 biz 节点。
为了解决 router 的可用性和并发吞吐量问题,对 router 进行冗余,同时 client 监听 zookeeper 的/routers 节点并缓存在线 router 节点列表。
HA
传统实现 HA 的做法一般是采用虚拟 IP 漂移,结合 Heartbeat、keepalived 等实现 HA,
Keepalived 使用 vrrp 方式进行数据包的转发,提供 4 层的负载均衡,通过检测 vrrp 数据包来切换,做冗余热备更加适合与 LVS 搭配。Linux Heartbeat 是基于网络或者主机的服务的高可用,HAProxy 或者 Nginx 可以基于 7 层进行数据包的转发,因此 Heatbeat 更加适合做 HAProxy、Nginx,包括业务的高可用。
分布式的集群中,可以用 zookeeper 做分布式的协调,实现集群的列表维护和失效通知,客户端可以选择 hash 算法或者 roundrobin 实现负载均衡;对于 master-master 模式、master-slave 模式,可以通过 zookeeper 分布式锁的机制来支持。
消息中心
对于平台各个系统之间的异步交互,是通过 MQ 组件进行的。
在设计消息服务组件时,需要考虑消息一致性、持久化、可用性、以及完善的监控体系。
业界开源的消息中间件有很多,现在以 RabbitMQ、kafka 介绍。
RabbitMQ,遵循 AMQP 协议,由内在高并发的 erlang 语言开发;
kafka 是 Linkedin 于 2010 年 12 月份开源的消息发布订阅系统,主要用于处理活跃的流式数据,大数据量的数据处理上。
RabbitMQ 采用的是这种方式,对消息一致性要求比较高的场合需要有应答确认机制,包括生产消息和消费消息的过程;不过因网络等原理导致的应答缺失,可能会导致消息的重复,这个可以在业务层次根据幂等性进行判断过滤;
kafka 分布式消息中间件就是这种方式。还有一种机制是消费端从 broker 拉取消息时带上 LSN 号,从 broker 中某个 LSN 点批量拉取消息,这样无须应答机制。
消息的在 broker 中的存储,根据消息的可靠性的要求以及性能方面的综合衡量,可以在内存中,可以持久化存储上。
对于可用性和高吞吐量的要求,集群和主备模式都可以在实际的场景应用的到。
RabbitMQ 解决方案中有普通的集群和可用性更高的 mirror queue 方式。
kafka 采用 zookeeper 对集群中的 broker、consumer 进行管理,可以注册 topic 到 zookeeper 上;
通过 zookeeper 的协调机制,producer 保存对应 topic 的 broker 信息,可以随机或者轮询发送到 broker 上;
并且 producer 可以基于语义指定分片,消息发送到 broker 的某分片上。
总体来讲,RabbitMQ 用在实时的对可靠性要求比较高的消息传递上。kafka 主要用于处理活跃的流式数据,大数据量的数据处理上。
Cache&Buffer
Cache 系统
在一些高并发高性能的场景中,使用 cache 可以减少对后端系统的负载,承担可大部分读的压力,可以大大提高系统的吞吐量,比如通常在数据库存储之前增加 cache 缓存。
但是引入 cache 架构不可避免的带来一些问题,cache 命中率的问题, cache 失效引起的抖动,cache 和存储的一致性。
cache 中的数据相对于存储来讲,毕竟是有限的,比较理想的情况是存储系统的热点数据,这里可以用一些常见的算法 LRU 等等淘汰老的数据;随着系统规模的增加,单个节点 cache 不能满足要求,就需要搭建分布式 Cache;为了解决单个节点失效引起的抖动 ,分布式 cache 一般采用一致性 hash 的解决方案,大大减少因单个节点失效引起的抖动范围;而对于可用性要求比较高的场景,每个节点都是需要有备份的。数据在 cache 和存储上都存有同一份备份,必然有一致性的问题,一致性比较强的,在更新数据库的同时,更新数据库 cache。对于一致性要求不高的,可以去设置缓存失效时间的策略。
Memcached 作为高速的分布式缓存服务器,协议比较简单,基于 libevent 的事件处理机制。
Cache 系统在平台中用在 router 系统的客户端中,热点的数据会缓存在客户端,当数据访问失效时,才去访问 router 系统。
当然目前更多的利用内存型的数据库做 cache,比如 Redis、mongodb;redis 比 memcache 有丰富的数据操作的 API;redis 和 mongodb 都对数据进行了持久化,而 memcache 没有这个功能,因此 memcache 更加适合在关系型数据库之上的数据的缓存。
Buffer 系统
用在高速的写操作的场景中,平台中有些数据需要写入数据库,并且数据是分库分表的,但对数据的可靠性不是那么高,为了减少对数据库的写压力,可以采取批量写操作的方式。
开辟一个内存区域,当数据到达区域的一定阀值时如 80%时,在内存中做分库梳理工作(内存速度还是比较快的),后分库批量 flush。
日志收集
在整个交易过程中,会产生大量的日志,这些日志需要收集到分布式存储系统中存储起来,以便于集中式的查询和分析处理。
日志系统需具备三个基本组件,分别为 agent(封装数据源,将数据源中的数据发送给 collector),collector(接收多个 agent 的数据,并进行汇总后导入后端的 store 中),store(中央存储系统,应该具有可扩展性和可靠性,应该支持当前非常流行的 HDFS)。
在设计或者对日志收集系统做技术选型时,通常需要具有以下特征:
a、 应用系统和分析系统之间的桥梁,将他们之间的关系解耦
b、 分布式可扩展,具有高的扩展性,当数据量增加时,可以通过增加节点水平扩展,日志收集系统是可以伸缩的,在系统的各个层次都可伸缩,对数据的处理不需要带状态,伸缩性方面也比较容易实现。
c、 近实时性,在一些时效性要求比较高的场景中,需要可以及时的收集日志,进行数据分析;一般的日志文件都会定时或者定量的进行 rolling,所以实时检测日志文件的生成,及时对日志文件进行类似的 tail 操作,并支持批量发送提高传输效率;批量发送的时机需要满足消息数量和时间间隔的要求。
d、 容错性,Scribe 在容错方面的考虑是,当后端的存储系统 crash 时,scribe 会将数据写到本地磁盘上,当存储系统恢复正常后,scribe 将日志重新加载到存储系统中。FlumeNG 通过 Sink Processor 实现负载均衡和故障转移。多个 Sink 可以构成一个 Sink Group。一个 Sink Processor 负责从一个指定的 Sink Group 中激活一个 Sink。Sink Processor 可以通过组中所有 Sink 实现负载均衡;也可以在一个 Sink 失败时转移到另一个。
e、 事务支持,Scribe 没有考虑事务的支持。通常提取发送消息都是批量操作的,消息的确认是对一批数据的确认,这样可以大大提高数据发送的效率。
f、 可恢复性,FlumeNG 的 channel 根据可靠性的要求的不同,可以基于内存和文件持久化机制,基于内存的数据传输的销量比较高,但是在节点宕机后,数据丢失,不可恢复;而文件持久化宕机是可以恢复的。
g、 数据的定时定量归档,数据经过日志收集系统归集后,一般存储在分布式文件系统如 Hadoop,为了便于对数据进行后续的处理分析,需要定时(TimeTrigger)或者定量(SizeTrigger 的 rolling 分布式系统的文件。
数据同步
在交易系统中,通常需要进行异构数据源的同步,通常有数据文件到关系型数据库,数据文件到分布式数据库,关系型数据库到分布式数据库等。数据在异构源之间的同步一般是基于性能和业务的需求,
在数据同步的设计中需要综合考虑吞吐量、容错性、可靠性、一致性的问题
同步有实时增量数据同步和离线全量数据区分,下面从这两个维度来介绍一下,
实时增量一般是 Tail 文件来实时跟踪文件变化,批量或者多线程往数据库导出,这种方式的架构类似于日志收集框架。这种方式需要有确认机制,包括两个方面。
一个方面是 Channel 需要给 agent 确认已经批量收到数据记录了,发送 LSN 号给 agent,这样在 agent 失效恢复时,可以从这个 LSN 点开始 tail;当然对于允许少量的重复记录的问题(发生在 channel 给 agent 确认的时,agent 宕机并未受到确认消息),需要在业务场景中判断。
另外一个方面是 sync 给 channel 确认已经批量完成写入到数据库的操作,这样 channel 可以删除这部分已经 confirm 的消息。
基于可靠性的要求,channel 可以采用文件持久化的方式。
离线全量遵循空间间换取时间,分而治之的原则,尽量的缩短数据同步的时间,提高同步的效率。
需要对源数据比如 MySQL 进行切分,多线程并发读源数据,多线程并发批量写入分布式数据库比如 HBase,利用 channel 作为读写之间的缓冲,实现更好的解耦,channel 可以基于文件存储或者内存。
对于源数据的切分,如果是文件可以根据文件名称设置块大小来切分。
对于关系型数据库,由于一般的需求是只离线同步一段时间的数据(比如凌晨把当天的订单数据同步到 HBase),所以需要在数据切分时(按照行数切分),会多线程扫描整个表(及时建索引,也要回表),对于表中包含大量的数据来讲,IO 很高,效率非常低;这里解决的方法是对数据库按照时间字段(按照时间同步的)建立分区,每次按照分区进行导出。
版权声明: 本文为 InfoQ 作者【李浩宇/Alex】的原创文章。
原文链接:【http://xie.infoq.cn/article/4d20871eb7723efc144dc474d】。文章转载请联系作者。
评论