高并发电子商务网站技术架构

2017/3/7
我自己的大型B2B和B2C网站原来也是用Hibernate,但是后来不得不换成mybatis, 
第一是用Hibernate 由于它封装得太高了,很多东西是隐式进行的,经常引起问题,很难定位。毕竟凡事有利必有弊; 
第二大型网站肯定不是一个数据库,这点Hibernate是很麻烦的,用Jdbc或Mybatis 可以轻松应付之,我自己写的shard分库框架目前就是支持mybatis和Jdbc Template。 
另,觉得割舍不了Hibernate的iteyer,其实也是建议直接再用Hibernate,待遇到痛苦时,再换,这样体会会更深些  
我的技术选型和onecan的类似,区别在于: 
1.缓存:我采用ehcache+memcached结合的方式,ehcache做JVM本地缓存,memcached做进程外全局缓存,即由本地缓存和全局缓存构成系统的二级缓存; 
2.数据库上,你用单数据库肯定是不行的。我的平台是划分为100多个库,早期我采用淘宝的amoeba(陈师儒兄写的)分库技术(其实是一个分库中间件,通过一台代理amoeba实现对后端mysql集群的透明化代理。后来发现问题多多,另一个是中间件方案虽然使用简单,但不够灵活,不能做多数据库事务,所以弃之。不得以自己写了一个基于Java的分库框架,即Shard,在应用层直接通过Shard操作数据库集群; 
3.全文索引,我们采用Solr,不过目前想把它换成ElasticSearch,因为Solr的全文索引同步比较慢,延时是一个很大的问题,ES做得好些。 
4.任务调度你这里没有讲,其实这块对于大型网站是很重要的,我是基于Quautz自己写了一个全局任务调度框架,相当于任务调度云的方式。如每天晚上汇总数据,定期迁移数据等就可以很好地使用任务调度来完成。 
5.编码生成:凡是商城或应用系统,肯定是要有一个编码生成的框架,如单据号,商品编号等,要求是全局唯一,规则可自定义。这个我是基于Spring Expression写了一个全局的编码框架。称为codeman,后面我也拟把它开源出来; 
6.开放平台:如果你的商城要允许多终端接入,如iphone,android,PC客户端,或者第三方,则一定要有一条服务总线,如淘宝的TOP。这个原来是用Spring MVC直接写的,后来发现新增功能太麻烦,开发效率太低了,因此我就基于Spring MVC框架的设计思路和TOP的应用模型写了一个Rop框架,这个已经开源的,参见我这个帖子: http://www.iteye.com/topic/1121252 
7.NoSQL和mySQL结合,mySQL毕竟是关系型的,对于高并发的数据,我们是放到mogonDB中的,这个数据库的压力会小很多。 
8.日志的记录:大型网站的日志记录是非常重要的,是审计,问题定位的依据。原来早期,我直接把日志记录到MySQL中,日志很大,数据库压力大,后来把它直接异步到Elastic Search中,不但可以全文检索,并发性大时也没有问题; 
此外,对日志编写了一些分析引擎,可以从日志中发现关键的问题,即时报警。 
9.会话管理的问题:由于应用服务节点很多,因此弃用Web应用服务器本身的Session功能,直接自己编写了一个全局会话管理功能,以实现全局统一的会话管理。 
10.图片服务器独立,每张图片只保存一张物理的,其实不同规格的图片动态生成并放到内存中; 
11.项目采用敏捷开发,DDT,Maven等。
 
一、 设计理念
 
1. 空间换时间
 
1) 多级缓存,静态化
 
客户端页面缓存( http header 中包含 Expires/Cache of Control , last modified(304 , server 不返回 body ,客户端可以继续用 cache ,减少流量 ) , ETag )
 
反向代理缓存
 
应用端的缓存 (memcache)
 
内存数据库
 
Buffer 、 cache 机制(数据库,中间件等)
 
2) 索引
 
哈希、 B 树、倒排、 bitmap
 
哈希索引适合综合数组的寻址和链表的插入特性,可以实现数据的快速存取。
 
B 树索引适合于查询为主导的场景,避免多次的 IO ,提高查询的效率。
 
倒排索引实现单词到文档映射关系的最佳实现方式和最有效的索引结构,广泛用在搜索领域。
 
Bitmap 是一种非常简洁快速的数据结构,他能同时使存储空间和速度最优化(而不必空间换时间),适合于海量数据的的计算场景。
 
2. 并行与分布式计算
 
1) 任务切分、分而治之 (MR)
 
在大规模的数据中,数据存在一定的局部性的特征,利用局部性的原理将海量数据计算的问题分而治之。
 
MR 模型是无共享的架构,数据集分布至各个节点。处理时,每个节点就近读取本地存储的数据处理 (map) ,将处理后的数据进行合并 (combine) 、排序 (shuffle and sort) 后再分发 ( 至 reduce 节点 ) ,避免了大量数据的传输,提高了处理效率。
 
2) 多进程、多线程并行执行 (MPP)
 
并行计算( Parallel Computing )是指同时使用多种计算资源解决计算问题的过程,是提高计算机系统计算速度和处理能力的一种有效手段。它的基本思想是用多个处理器 / 进程 / 线程来协同求解同一问题,即将被求解的问题分解成若干个部分,各部分均由一个独立的处理机来并行计算。
 
和 MR 的区别在于,它是基于问题分解的,而不是基于数据分解。
 
3. 多维度的可用
 
1) 负载均衡、容灾、备份
 
随着平台并发量的增大,需要扩容节点进行集群,利用负载均衡设备进行请求的分发;负载均衡设备通常在提供负载均衡的同时,也提供失效检测功能;同时为了提高可用性,需要有容灾备份,以防止节点宕机失效带来的不可用问题;备份有在线的和离线备份,可以根据失效性要求的不同,进行选择不同的备份策略。
 
2) 读写分离
 
读写分离是对数据库来讲的,随着系统并发量的增大,提高数据访问可用性的一个重要手段就是写数据和读数据进行分离;当然在读写分离的同时,需要关注数据的一致性问题;对于一致性的问题,在分布式的系统 CAP 定量中,更多的关注于可用性。
 
3) 依赖关系
 
平台中各个模块之间的关系尽量是低耦合的,可以通过相关的消息组件进行交互,能异步则异步,分清楚数据流转的主流程和副流程,主副是异步的,比如记录日志可以是异步操作的,增加整个系统的可用性。
 
当然在异步处理中,为了确保数据得到接收或者处理,往往需要确认机制 (confirm 、 ack) 。
 
但是有些场景中,虽然请求已经得到处理,但是因其他原因 ( 比如网络不稳定 ) ,确认消息没有返回,那么这种情况下需要进行请求的重发,对请求的处理设计因重发因素需要考虑幂等性。
 
4) 监控
 
监控也是提高整个平台可用性的一个重要手段,多平台进行多个维度的监控;模块在运行时候是透明的,以达到运行期白盒化。
 
4. 伸缩
 
1) 拆分
 
拆分包括对业务的拆分和对数据库的拆分。
 
系统的资源总是有限的,一段比较长的业务执行如果是一竿子执行的方式,在大量并发的操作下,这种阻塞的方式,无法有效的及时释放资源给其他进程执行,这样系统的吞吐量不高。
 
需要把业务进行逻辑的分段,采用异步非阻塞的方式,提高系统的吞吐量。
 
随着数据量和并发量的增加,读写分离不能满足系统并发性能的要求,需要对数据进行切分,包括对数据进行分库和分表。这种分库分表的方式,需要增加对数据的路由逻辑支持。
 
2) 无状态
 
对于系统的伸缩性而言,模块最好是无状态的,通过增加节点就可以提高整个的吞吐量。
 
5. 优化资源利用
 
1) 系统容量有限
 
系统的容量是有限的,承受的并发量也是有限的,在架构设计时,一定需要考虑流量的控制,防止因意外攻击或者瞬时并发量的冲击导致系统崩溃。在设计时增加流控的措施,可考虑对请求进行排队,超出预期的范围,可以进行告警或者丢弃。
 
2) 原子操作与并发控制
 
对于共享资源的访问,为了防止冲突,需要进行并发的控制,同时有些交易需要有事务性来保证交易的一致性,所以在交易系统的设计时,需考虑原子操作和并发控制。
 
保证并发控制一些常用高性能手段有,乐观锁、 Latch 、 mutex 、写时复制、 CAS 等;多版本的并发控制 MVCC 通常是保证一致性的重要手段,这个在数据库的设计中经常会用到。
 
3) 基于逻辑的不同,采取不一样的策略
 
平台中业务逻辑存在不同的类型,有计算复杂型的,有消耗 IO 型的,同时就同一种类型而言,不同的业务逻辑消耗的资源数量也是不一样的,这就需要针对不同的逻辑采取不同的策略。
 
针对 IO 型的,可以采取基于事件驱动的异步非阻塞的方式,单线程方式可以减少线程的切换引起的开销,或者在多线程的情况下采取自旋 spin 的方式,减少对线程的切换 ( 比如 oracle latch 设计 ) ;对于计算型的,充分利用多线程进行操作。
 
同一类型的调用方式,不同的业务进行合适的资源分配,设置不同的计算节点数量或者线程数量,对业务进行分流,优先执行优先级别高的业务。
 
4) 容错隔离
 
系统的有些业务模块在出现错误时,为了减少并发下对正常请求的处理的影响,有时候需要考虑对这些异常状态的请求进行单独渠道的处理,甚至暂时自动禁止这些异常的业务模块。
 
有些请求的失败可能是偶然的暂时的失败 ( 比如网络不稳定 ) ,需要进行请求重试的考虑。
 
5) 资源释放
 
系统的资源是有限的,在使用资源时,一定要在最后释放资源,无论是请求走的是正常路径还是异常的路径,以便于资源的及时回收,供其他请求使用。
 
在设计通信的架构时,往往需要考虑超时的控制。
 
二、 静态架构蓝图
 
 
整个架构是分层的分布式的架构,纵向包括 CDN,负载均衡/反向代理,web应用,业务层,基础服务层,数据存储层。水平方向包括对整个平台的配置管理部署和监控。
 
三、 剖析架构
 
CDN 系统能够实时地根据 网络流量 和各节点的连接、负载状况以及到用户的距离和响应时间等综合信息将用户的请求重新导向离用户最近的服务节点上。其目的是使用户可就近取得所需内容,解决  Internet 网络拥挤 的状况,提高用户访问网站的响应速度。
 
对于大规模电子商务平台一般需要建 CDN 做网络加速,大型平台如淘宝、京东都采用自建 CDN ,中小型的企业可以采用第三方 CDN 厂商合作 ,如蓝汛、网宿、快网等。
 
当然在选择 CDN 厂商时,需要考虑经营时间长短,是否有可扩充的带宽资源、灵活的流量和带宽选择、稳定的节点、性价比。
 
2. 负载均衡、反向代理
 
一个大型的平台包括很多个业务域,不同的业务域有不同的集群,可以用 DNS 做域名解析的分发或轮询, DNS 方式实现简单,但是因存在 cache 而缺乏灵活性;一般基于 商用的硬件 F5 、NetScaler或者开源的软负载 lvs 在 4 层做分发,当然会采用做冗余 ( 比如 lvs+keepalived) 的考虑,采取主备方式。
 
4 层分发到业务集群上后,会经过 web 服务器如 nginx 或者 HAProxy 在 7 层做负载均衡或者反向代理分发到集群中的应用节点。
 
选择哪种负载,需要综合考虑各种因素(是否满足高并发高性能,Session保持如何解决,负载均衡的算法如何,支持压缩,缓存的内存消耗);下面基于几种常用的负载均衡软件做个介绍。
 
LVS ,工作在 4 层, Linux 实现的高性能高并发、可伸缩性、可靠的的负载均衡器,支持多种转发方式 (NAT 、 DR 、 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 做图片缓存。
 
3. App 接入
 
应用层运行在 jboss 或者 tomcat 容器中,代表独立的系统,比如前端购物、用户自主服务、后端系统等
 
协议接口, HTTP 、 JSON
 
可以采用 servlet3.0, 异步化 servlet, 提高整个系统的吞吐量
 
http 请求经过 Nginx ,通过负载均衡算法分到到 App 的某一节点,这一层层扩容起来比较简单。
 
除了利用 cookie 保存少量用户部分信息外 ( cookie 一般不能超过 4K 的大小 ) ,对于 App 接入层,保存有用户相关的 session 数据,但是有些反向代理或者负载均衡不支持对 session sticky 支持不是很好或者对接入的可用性要求比较高 (app 接入节点宕机, session 随之丢失 ) ,这就需要考虑 session 的集中式存储,使得 App 接入层无状态化,同时 系统用户变多的时候,就可以通过增加更多的应用节点来达到水平扩展的目的。
 
Session 的集中式存储,需要满足以下几点要求:
 
a 、高效的通讯协议
 
b 、 session 的分布式缓存,支持节点的伸缩,数据的冗余备份以及数据的迁移
 
c 、 session 过期的管理
 
4. 业务服务
 
代表某一领域的业务提供的服务,对于电商而言,领域有用户、商品、订单、红包、支付业务等等,不同的领域提供不同的服务,
 
这些不同的领域构成一个个模块,良好的模块划分和接口设计非常重要,一般是参考高内聚、接口收敛的原则,
 
这样可以提高整个系统的可用性。当然可以根据应用规模的大小,模块可以部署在一起,对于大规模的应用,一般是独立部署的。
 
高并发:
 
业务层对外协议以 NIO 的 RPC 方式暴露,可以采用比较成熟的 NIO 通讯框架,如 netty 、 mina
 
可用性:
 
为了提高模块服务的可用性,一个模块部署在多个节点做冗余,并自动进行负载转发和失效转移 ;
 
最初可以利用 VIP+heartbeat 方式,目前系统有一个单独的组件 HA, 利用 zookeeper 实现 ( 比原来方案的优点 )
 
一致性、事务:
 
对于分布式系统的一致性,尽量满足可用性,一致性可以通过校对来达到最终一致的状态。
 
5. 基础服务中间件
 
1) 通信组件
 
通信组件用于业务系统内部服务之间的调用,在大并发的电商平台中,需要满足高并发高吞吐量的要求。
 
整个通信组件包括客户端和服务端两部分。
 
客户端和服务器端维护的是长连接,可以减少每次请求建立连接的开销,在客户端对于每个服务器定义一个连接池,初始化连接后,可以并发连接服务端进行 rpc 操作,连接池中的长连接需要心跳维护,设置请求超时时间。
 
对于长连接的维护过程可以分两个阶段,一个是发送请求过程,另外一个是接收响应过程。在发送请求过程中,若发生 IOException ,则把该连接标记失效。接收响应时,服务端返回 SocketTimeoutException,如果设置了超时时间,那么就直接返回异常,清除当前连接中那些超时的请求。否则继续发送心跳包 ( 因为可能是丢包,超过 pingInterval 间隔时间就发送 ping 操作 ) ,若 ping 不通 ( 发送 IOException) ,则说明当前连接是有问题的,那么就把当前连接标记成已经失效;若 ping 通,则说明当前连接是可靠的,继续进行读操作。失效的连接会从连接池中清除掉。
 
每个连接对于接收响应来说都以单独的线程运行,客户端可以通过同步 (wait,notify) 方式或者异步进行 rpc 调用,
 
序列化采用更高效的 hession 序列化方式。
 
服务端采用事件驱动的 NIO 的 MINA 框架,支撑高并发高吞吐量的请求。
 
 
2) 路由 Router
 
在大多数的数据库切分解决方案中,为了提高数据库的吞吐量,首先是对不同的表进行垂直切分到不同的数据库中,
 
然后当数据库中一个表超过一定大小时,需要对该表进行水平切分,这里也是一样,这里以用户表为例;
 
对于访问数据库客户端来讲,需要根据用户的 ID ,定位到需要访问的数据;
 
数据切分算法,
 
根据用户的 ID 做 hash 操作,一致性 Hash ,这种方式存在失效数据的迁移问题,迁移时间内服务不可用
 
维护路由表,路由表中存储用户和 sharding 的映射关系 ,sharding 分为 leader 和 replica ,分别负责写和读
 
这样每个 biz 客户端都需要保持所有 sharding 的连接池,这样有个缺点是会产生全连接的问题;
 
一种解决方法是 sharding 的切分提到业务服务层进行,每个业务节点只维护一个 shard 的连接即可。
 
见图( router )
 
 
路由组件的实现是这样的(可用性、高性能、高并发)
 
基于性能方面的考虑,采用 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 的做法一般是采用虚拟 IP 漂移,结合 Heartbeat 、 keepalived 等实现 HA ,
 
Keepalived 使用 vrrp 方式进行数据包的转发,提供 4 层的负载均衡,通过检测 vrrp 数据包来切换,做冗余热备更加适合与 LVS 搭配。 Linux Heartbeat 是基于网络或者主机的服务的高可用, HAProxy 或者 Nginx 可以基于 7 层进行数据包的转发,因此 Heatbeat 更加适合做 HAProxy 、 Nginx ,包括业务的高可用。
 
在分布式的集群中,可以用 zookeeper 做分布式的协调,实现集群的列表维护和失效通知,客户端可以选择 hash 算法或者 roudrobin 实现负载均衡;对于 master-master 模式、 master-slave 模式,可以通过 zookeeper 分布式锁的机制来支持。
 
4) 消息 Message
 
对于平台各个系统之间的异步交互,是通过 MQ 组件进行的。
 
在设计消息服务组件时,需要考虑消息一致性、持久化、可用性、以及完善的监控体系。
 
业界开源的消息中间件主要 RabbitMQ 、 kafka 有两种,
 
RabbitMQ, 遵循 AMQP 协议,由内在高并发的 erlanng 语言开发 ;kafka 是 Linkedin 于 2010 年 12 月份开源的消息发布订阅系统 , 它主要用于处理活跃的流式数据 , 大数据量的数据处理上。
 
对消息一致性要求比较高的场合需要有应答确认机制,包括生产消息和消费消息的过程;不过因网络等原理导致的应答缺失,可能会导致消息的重复,这个可以在业务层次根据幂等性进行判断过滤; RabbitMQ 采用的是这种方式。还有一种机制是消费端从 broker 拉取消息时带上 LSN 号,从 broker 中某个 LSN 点批量拉取消息,这样无须应答机制, kafka 分布式消息中间件就是这种方式。
 
消息的在 broker 中的存储,根据消息的可靠性的要求以及性能方面的综合衡量,可以在内存中,可以持久化到存储上。
 
对于可用性和高吞吐量的要求,集群和主备模式都可以在实际的场景应用的到。 RabbitMQ 解决方案中有普通的集群和可用性更高的 mirror queue 方式。  kafka 采用 zookeeper 对集群中的 broker 、 consumer 进行管理,可以注册 topic 到 zookeeper 上;通过 zookeeper 的协调机制, producer 保存对应 topic 的 broker 信息,可以随机或者轮询发送到 broker 上;并且 producer 可以基于语义指定分片,消息发送到 broker 的某分片上。
 
总体来讲,RabbitMQ用在实时的对可靠性要求比较高的消息传递上。 kafka 主要用于处理活跃的流式数据 , 大数据量的数据处理上。
 
5) 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。
 
6) 搜索
 
在电子商务平台中搜索是一个非常的重要功能,主要有搜索词类目导航、自动提示和搜索排序功能。
 
开源的企业级搜索引擎主要有 lucene, sphinx,这里不去论述哪种搜索引擎更好一些,不过选择搜索引擎除了基本的功能需要支持外,非功能方面需要考虑以下两点:
 
a、 搜索引擎是否支持分布式的索引和搜索,来应对海量的数据,支持读写分离,提高可用性
 
b、 索引的实时性
 
c、 性能
 
Solr 是基于 lucene 的高性能的全文搜索服务器,提供了比 lucene 更为丰富的查询语言,可配置可扩展,对外提供基于 http 协议的 XML/JSON 格式的接口。
 
从 Solr4 版本开始提供了 SolrCloud 方式来支持分布式的索引,自动进行 sharding 数据切分;通过每个 sharding 的 master-slave(leader 、 replica) 模式提高搜索的性能;利用 zookeeper 对集群进行管理,包括 leader 选举等等,保障集群的可用性。
 
Lucene 索引的 Reader 是基于索引的 snapshot 的,所以必须在索引 commit 的后,重新打开一个新的 snapshot ,才能搜索到新添加的内容;而索引的 commit 是非常耗性能的,这样达到实时索引搜索效率就比较低下。
 
对于索引搜索实时性, Solr4 的之前解决方案是结合文件全量索引和内存增量索引合并的方式,参见下图。
 
 
Solr4 提供了 NRT softcommit 的解决方案, softcommit 无需进行提交索引操作,就可以搜素到最新对索引的变更,不过对索引的变更并没有 sync commit 到硬盘存储上,若发生意外导致程序非正常结束,未 commit 的数据会丢失,因此需要定时的进行 commit 操作。
 
平台中对数据的索引和存储操作是异步的,可以大大提高可用性和吞吐量;只对某些属性字段做索引操作,存储数据的标识 key ,减少索引的大小;数据是存储在分布式存储 HBase  中的, HBase 对二级索引搜索支持的不好,然而可以结合 Solr 搜索功能进行多维度的检索统计。
 
索引数据和 HBase 数据存储的一致性,也就是如何保障 HBase 存储的数据都被索引过,可以采用 confirm 确认机制,通过在索引前建立待索引数据队列,在数据存储并索引完成后,从待索引数据队列中删除数据。
 
7) 日志收集
 
在整个交易过程中,会产生大量的日志,这些日志需要收集到分布式存储系统中存储起来,以便于集中式的查询和分析处理。
 
日志系统需具备三个基本组件,分别为 agent (封装数据源,将数据源中的数据发送给 collector ), collector (接收多个 agent 的数据,并进行汇总后导入后端的 store 中), store (中央存储系统,应该具有可扩展性和可靠性,应该支持当前非常流行的 HDFS )。
 
开源的日志收集系统业界使用的比较多的是 cloudera 的 Flume 和 facebook 的 Scribe ,其中 Flume 目前的版本 FlumeNG 对 Flume 从架构上做了较大的改动。
 
在设计或者对日志收集系统做技术选型时,通常需要具有以下特征:
 
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 没有考虑事务的支持。
 
Flume 通过应答确认机制实现事务的支持,参见下图,
 
 
通常提取发送消息都是批量操作的,消息的确认是对一批数据的确认,这样可以大大提高数据发送的效率。
 
f、 可恢复性
 
FlumeNG 的 channel 根据可靠性的要求的不同,可以基于内存和文件持久化机制,基于内存的数据传输的销量比较高,但是在节点宕机后,数据丢失,不可恢复;而文件持久化宕机是可以恢复的。
 
g、 数据的定时定量归档
 
数据经过日志收集系统归集后,一般存储在分布式文件系统如 Hadoop ,为了便于对数据进行后续的处理分析,需要定时 (TimeTrigger) 或者定量 (SizeTrigger 的 rolling 分布式系统的文件。
 
8) 数据同步
 
在交易系统中,通常需要进行异构数据源的同步,通常有数据文件到关系型数据库,数据文件到分布式数据库,关系型数据库到分布式数据库等。数据在异构源之间的同步一般是基于性能和业务的需求,数据存储在本地文件中一般是基于性能的考虑,文件是顺序存储的,效率还是比较高的;数据同步到关系型数据一般是基于查询的需求;而分布式数据库是存储越来越多的海量数据的,而关系型数据库无法满足大数据量的存储和查询请求。
 
在数据同步的设计中需要综合考虑吞吐量、容错性、可靠性、一致性的问题
 
同步有实时增量数据同步和离线全量数据区分,下面从这两个维度来介绍一下,
 
实时增量一般是 Tail 文件来实时跟踪文件变化,批量或者多线程往数据库导出 , 这种方式的架构类似于日志收集框架。这种方式需要有确认机制,包括两个方面。
 
一个方面是Channel 需要给 agent 确认已经批量收到数据记录了,发送 LSN 号给 agent ,这样在 agent 失效恢复时,可以从这个 LSN 点开始 tail ;当然对于允许少量的重复记录的问题 ( 发生在 channel 给 agent 确认的时, agent 宕机并未受到确认消息 ) ,需要在业务场景中判断。
 
另外一个方面是 sync 给 channel 确认已经批量完成写入到数据库的操作,这样 channel 可以删除这部分已经 confirm 的消息。
 
基于可靠性的要求, channel 可以采用文件持久化的方式。
 
参见下图
 
 
离线全量遵循空间间换取时间,分而治之的原则,尽量的缩短数据同步的时间,提高同步的效率。
 
需要对源数据比如 mysql 进行切分,多线程并发读源数据,多线程并发批量写入分布式数据库比如 HBase, 利用 channel 作为读写之间的缓冲,实现更好的解耦, channel 可以基于文件存储或者内存。参见下图:
 
 
对于源数据的切分,如果是文件可以根据文件名称设置块大小来切分。
 
对于关系型数据库,由于一般的需求是只离线同步一段时间的数据 ( 比如凌晨把当天的订单数据同步到 HBase) ,所以需要在数据切分时 ( 按照行数切分 ) ,会多线程扫描整个表 ( 及时建索引,也要回表 ) ,对于表中包含大量的数据来讲, IO 很高,效率非常低;这里解决的方法是对数据库按照时间字段 ( 按照时间同步的 ) 建立分区,每次按照分区进行导出。
 
9) 数据分析
 
从传统的基于关系型数据库并行处理集群、用于内存计算近实时的,到目前的基于 hadoop 的海量数据的分析,数据的分析在大型电子商务网站中应用非常广泛,包括 流量统计、推荐引擎、趋势分析、用户行为分析、数据挖掘分类器、分布式索引等 等。
 
并行处理集群有商业的 EMC Greenplum , Greenplum 的架构采用了 MPP( 大规模并行处理 ) , 基于 postgresql 的大数据量存储的分布式数据库。
 
内存计算方面有 SAP 的 HANA ,开源的 nosql 内存型的数据库 mongodb 也支持 mapreduce 进行数据的分析。
 
海量数据的离线分析目前互联网公司大量的使用 Hadoop , Hadoop 在可伸缩性、健壮性、计算性能和成本上具有无可替代的优势,事实上已成为当前互联网企业主流的大数据分析平台
 
Hadoop 通过 MapReuce 的分布式处理框架,用于处理大规模的数据,伸缩性也非常好;但是 MapReduce 最大的不足是不能满足实时性的场景,主要用于离线的分析。
 
基于 MapRduce 模型编程做数据的分析,开发上效率不高,位于 hadoop 之上 Hive 的出现使得数据的分析可以类似编写 sql 的方式进行, sql 经过语法分析、生成执行计划后最终生成 MapReduce 任务进行执行,这样大大提高了开发的效率,做到以 ad-hoc( 计算在 query 发生时 ) 方式进行的分析。
 
基于 MapReduce 模型的分布式数据的分析都是离线的分析,执行上都是暴力扫描,无法利用类似索引的机制;开源的 Cloudera Impala 是基于 MPP 的并行编程模型的,底层是 Hadoop 存储的高性能的实时分析平台,可以大大降低数据分析的延迟。
 
目前 Hadoop 使用的版本是 Hadoop1.0 ,一方面原有的 MapReduce 框架存在 JobTracker 单点的问题,另外一方面 JobTracker 在做资源管理的同时又做任务的调度工作,随着数据量的增大和 Job 任务的增多,明显存在 可扩展性、内存消耗、线程模型、可靠性和性能上的缺陷瓶颈; Hadoop2.0 yarn 对整个框架进行了重构,分离了资源管理和任务调度,从架构设计上解决了这个问题。
 
参考Yarn 的架构
 
10) 实时计算
 
在互联网领域,实时计算被广泛实时监控分析、流控、风险控制等领域。电商平台系统或者应用对日常产生的大量日志和异常信息,需要经过实时过滤、分析,以判定是否需要预警;
 
同时需要对系统做自我保护机制,比如对模块做流量的控制,以防止非预期的对系统压力过大而引起的系统瘫痪,流量过大时,可以采取拒绝或者引流等机制;有些业务需要进行风险的控制,比如彩票中有些业务需要根据系统的实时销售情况进行限号与放号。
 
原始基于单节点的计算,随着系统信息量爆炸式产生以及计算的复杂度的增加,单个节点的计算已不能满足实时计算的要求,需要进行多节点的分布式的计算,分布式实时计算平台就出现了。
 
这里所说的实时计算,其实是流式计算,概念前身其实是 CEP 复杂事件处理,相关的开源产品如 Esper ,业界分布式的流计算产品 Yahoo S4,Twitter storm 等,以 storm 开源产品使用最为广泛。
 
对于实时计算平台,从架构设计上需要考虑以下几个因素:
 
1、 伸缩性
 
随着业务量的增加,计算量的增加,通过增加节点处理,就可以处理。
 
2、 高性能、低延迟
 
从数据流入计算平台数据,到计算输出结果,需要性能高效且低延迟,保证消息得到快速的处理,做到实时计算。
 
3、 可靠性
 
保证每个数据消息得到一次完整处理。
 
4、 容错性
 
系统可以自动管理节点的宕机失效,对应用来说,是透明的。
 
Twitter 的 Storm 在以上这几个方面做的比较好,下面简介一下 Storm 的架构。
 
 
整个集群的管理是通过 zookeeper 来进行的。
 
客户端提交拓扑到 nimbus 。
 
Nimbus 针对该拓扑建立本地的目录根据 topology 的配置计算 task ,分配 task ,在 zookeeper 上建立 assignments 节点存储 task 和 supervisor 机器节点中 woker 的对应关系 。
 
在 zookeeper 上创建 taskbeats 节点来监控 task 的心跳;启动 topology 。
 
Supervisor 去 zookeeper 上获取分配的 tasks ,启动多个 woker 进行,每个 woker 生成 task ,一个 task 一个线程;根据 topology 信息初始化建立 task 之间的连接 ;Task 和 Task 之间是通过 zeroMQ 管理的; 之 后整个拓扑运行起来。
 
Tuple 是流的基本处理单元,也就是一个消息, Tuple 在 task 中流转, Tuple 的发送和接收过程如下:
 
发送 Tuple , Worker 提供了一个 transfer 的功能,用于当前 task 把 tuple 发到到其他的 task 中。以目的 taskid 和 tuple 参数,序列化 tuple 数据并放到 transfer queue 中。
 
在 0.8 版本之前,这个 queue 是 LinkedBlockingQueue , 0.8 之后是 DisruptorQueue 。
 
在 0.8 版本之后,每一个 woker 绑定一个 inbound transfer queue 和 outbond queue , inbound queue 用于接收 message , outbond queue 用于发送消息。
 
发送消息时,由单个线程从 transferqueue 中拉取数据,把这个 tuple 通过 zeroMQ 发送到其他的 woker 中。
 
接收 Tuple , 每个 woker 都会监听 zeroMQ 的 tcp 端口来接收消息,消息放到 DisruptorQueue 中后,后从 queue 中获取 message(taskid,tuple) ,根据目的 taskid,tuple 的值路由到 task 中执行。每个 tuple 可以 emit 到 direct steam 中,也可以发送到 regular stream 中,在 Reglular 方式下,由 Stream Group ( stream id-->component id -->outbond tasks )功能完成当前 tuple 将要发送的 Tuple 的目的地。
 
通过以上分析可以看到, Storm 在伸缩性、容错性、高性能方面的从架构设计的角度得以支撑;同时在可靠性方面, Storm 的 ack 组件利用异或 xor 算法在不失性能的同时,保证每一个消息得到完整处理的同时。 
 
11) 实时推送
 
实时推送的应用场景非常多,比如系统的监控动态的实时曲线绘制,手机消息的推送, web 实时聊天等。
 
实时推送有很多技术可以实现,有 Comet 方式,有 websocket 方式等。
 
Comet 基于服务器长连接的“服务器推”技术,包含两种:
 
Long Polling :服务器端在接到请求后挂起,有更新时返回连接即断掉,然后客户端再发起新的连接
 
Stream 方式 :  每次服务端数据传送不会关闭连接,连接只会在通信出现错误时,或是连接重建时关闭(一些防火墙常被设置为丢弃过长的连接, 服务器端可以设置一个超时时间, 超时后通知客户端重新建立连接,并关闭原来的连接)。
 
Websocket :长连接,全双工通信
 
是 Html5  的一种新的协议。它实现了浏览器与服务器的双向通讯。 webSocket API  中,浏览器和服务器端只需要通过一个握手的动作,便能形成浏览器与客户端之间的快速双向通道,使得数据可以快速的双向传播。
 
Socket.io 是一个 NodeJS websocket 库,包括客户端的 JS 和服务端的的 nodejs ,用于快速构建实时的 web 应用。
 
12) 推荐引擎
 
待补充
 
6. 数据存储
 
数据库存储大体分为以下几类,有关系型(事务型)的数据库,以 oracle 、 mysql 为代表,有 keyvalue 数据库,以 redis 和 memcached db 为代表,有文档型数据库如 mongodb ,有列式 分布式数据库以 HBase , cassandra,dynamo 为代表,还 有其他的图形数据库、对象数据 库、 xml 数据库等。 每种类型的数据库应用的业务领域是不一样的,下面从内存型、关系型、分布式三个维度针对相关的产品做性能可用性等方面的考量分析。
 
1) 内存型数据库
 
内存型的数据库,以高并发高性能为目标,在事务性方面没那么严格,以开源 nosql 数据库 mongodb 、 redis 为例
 
Ø Mongodb
 
通信方式
 
多线程方式,主线程监听新的连接,连接后,启动新的线程做数据的操作( IO 切换)。
 
数据结构
 
 
数据库 -->collection-->record
 
MongoDB 在数据存储上按命名空间来划分,一个 collection 是一个命名空间,一个索引也是一个命名空间。
 
同一个命名空间的数据被分成很多个 Extent , Extent 之间使用双向链表连接。
 
在每一个 Extent 中,保存了具体每一行的数据,这些数据也是通过双向链接连接的。
 
每一行数据存储空间不仅包括数据占用空间,还可能包含一部分附加空间,这使得在数据 update 变大后可以不移动位置。
 
索引以 BTree 结构实现。
 
如果你开启了 jorunaling 日志,那么还会有一些文件存储着你所有的操作记录 。
 
持久化存储
 
MMap 方式把文件地址映射到内存的地址空间,直接操作内存地址空间就可以操作文件,不用再调用 write,read 操作,性能比较高。
 
mongodb 调用 mmap 把磁盘中的数据映射到内存中的,所以必须有一个机制时刻的刷数据到硬盘才能保证可靠性,多久刷一次是与 syncdelay 参数相关的。
 
journal (进行恢复用)是 Mongodb 中的 redo log ,而 Oplog 则是负责复制的 binlog 。如果打开 journal ,那么即使断电也只会丢失 100ms 的数据,这对大多数应用来说都可以容忍了。从 1.9.2+ , mongodb 都会默认打开 journal 功能,以确保数据安全。而且 journal 的刷新时间是可以改变的, 2-300ms 的范围 , 使用  --journalCommitInterval  命令。 Oplog 和数据刷新到磁盘的时间是 60s ,对于复制来说,不用等到 oplog 刷新磁盘,在内存中就可以直接复制到 Sencondary 节点。
 
事务支持
 
Mongodb 只支持对单行记录的原子操作
 
HA 集群
 
用的比较多的是 Replica Sets ,采用选举算法,自动进行 leader 选举,在保证可用性的同时,可以做到强一致性要求。
 
 
当然对于大量的数据, mongodb 也提供了数据的切分架构 Sharding 。
 
Ø Redis
 
丰富的数据结构,高速的响应速度,内存操作
 
通信方式
 
因都在内存操作,所以逻辑的操作非常快,减少了 CPU 的切换开销,所以为单线程的模式(逻辑处理线程和主线程是一个)。
 
reactor 模式,实现自己的多路复用 NIO 机制( epoll , select , kqueue 等)
 
单线程处理多任务
 
数据结构
 
hash+bucket 结构,当链表的长度过长时,会采取迁移的措施(扩展原来两倍的 hash 表,把数据迁移过去, expand+rehash )
 
持久化存储
 
a 、全量持久化 RDB (遍历 redisDB, 读取 bucket 中的 key,value ), save 命令阻塞主线程, bgsave 开启子进程进行 snapshot 持久化操作,生成 rdb 文件。
 
在 shutdown 时,会调用 save 操作
 
数据发生变化,在多少秒内触发一次 bgsave
 
sync , master 接受 slave 发出来的命令
 
b 、增量持久化( aof 类似 redolog ),先写到日志 buffer, 再 flush 到日志文件中( flush 的策略可以配置的,而已单条,也可以批量),只有 flush 到文件上的,才真正返回客户端。
 
要定时对 aof 文件和 rdb 文件做合并操作(在快照过程中,变化的数据先写到 aof buf 中等子进程完成快照 < 内存 snapshot> 后,再进行合并 aofbuf 变化的部分以及全镜像数据)。
 
在高并发访问模式下, RDB 模式使服务的性能指标出现明显的抖动, aof 在性能开销上比 RDB 好,但是恢复时重新加载到内存的时间和数据量成正比。
 
集群 HA
 
通用的解决方案是主从备份切换,采用 HA 软件,使得失效的主 redis 可以快速的切换到从 redis 上。主从数据的同步采用复制机制,该场景可以做读写分离。
 
目前在复制方面,存在的一个问题是在遇到网络不稳定的情况下, Slave 和 Master 断开(包括闪断)会导致 Master 需要将内存中的数据全部重新生成 rdb 文件(快照文件),然后传输给 Slave 。 Slave 接收完 Master 传递过来的 rdb 文件以后会将自身的内存清空,把 rdb 文件重新加载到内存中。这种方式效率比较低下,在后面的未来版本 Redis2.8 作者已经实现了部分复制的功能。
 
2) 关系型数据库
 
关系型数据库在满足并发性能的同时,也需要满足事务性,以 mysql 数据库为例,讲述架构设计原理,在性能方面的考虑,以及如何满足可用性的需求。
 
Ø mysql 的架构原理 (innodb)
 
在架构上, mysql 分为 server 层和存储引擎层。
 
Server 层的架构对于不同的存储引擎来讲都是一样的 , 包括连接 / 线程处理、查询处理 (parser 、 optimizer) 以及其他系统任务。存储引擎层有很多种, mysql 提供了存储引擎的插件式结构,支持多种存储引擎,用的最广泛的是 innodb 和 myisamin ; inodb 主要面向 OLTP 方面的应用,支持事务处理, myisam 不支持事务,表锁,对 OLAP 操作速度快。
 
以下主要针对 innodb 存储引擎做相关介绍。
 
 
在线程处理方面, Mysql 是多线程的架构,由一个 master 线程,一个锁监控线程,一个错误监控线程,和多个 IO 线程组成。并且对一个连接会开启一个线程进行服务。 io 线程又分为节省随机 IO 的 insert buffer ,用于事务控制的类似于 oracle 的 redo log ,以及多个 write ,多个 read 的硬盘和内存交换的 IO 线程。
 
在内存分配方面,包括 innodb buffer pool  ,以及 log buffer 。其中 innodb buffer pool 包括 insert buffer 、 datapage 、 index page 、数据字典、自适应 hash 。 L og buffer 用于缓存事务日志,提供性能。
 
在数据结构方面, innodb 包括表空间、段、区、页 / 块,行。索引结构是 B +tree 结构,包括二级索引和主键索引,二级索引的叶子节点是主键 PK ,根据主键索引的叶子节点指向存储的数据块。 这种 B+ 树存储结构可以更好的满足随机查询操作 IO 要求,分为数据页和二级索引页,修改二级索引页面涉及到随机操作,为了提高写入时的性能,采用 insert buffer 做顺序的写入,再由后台线程以一定频率将多个插入合并到二级索引页面。为了保证数据库的一致性 ( 内存和硬盘数据文件 ) ,以及缩短实例恢复的时间,关系型数据库还有一个 checkpoint 的功能,用于把内存 buffer 中之前的脏页按照比例 ( 老的 LSN) 写入磁盘,这样 redolog 文件的 LSN 以前的日志就可以被覆盖了,进行循环使用;在失效恢复时,只需要从日志中 LSN 点进行恢复即可。
 
在事务特性支持上,关系型数据库需要满足 ACID 四个特性,需要根据不同的事务并发和数据可见性要求,定义了不同的事务隔离级别,并且离不开对资源争用的锁机制,要避免产生死锁, mysql 在 Server 层和存储引擎层做并发控制,主要体现在读写锁,根据锁粒度不同,有各个级别的锁 ( 表锁、行锁、页锁、 MVCC) ;基于提高并发性能的考虑,使用多版本并发控制 MVCC 来支持事务的隔离,并基于 undo 来实现,在做事务回滚时,也会用到 undo 段。 mysql  用 redolog 来保证数据的写入的性能和失效恢复,在修改数据时只需要修改内存,再把修改行为记录到事务日志中 ( 顺序 IO) ,不用每次将数据修改本身持久化到硬盘 ( 随机 IO) ,大大提高性能。
 
在可靠性方面, innodb 存储引擎提供了两次写机制 double writer 用于防止在 flush 页面到存储上出现的错误,解决磁盘 half-writern 的问题。
 
Ø 对于高并发高性能的 mysql 来讲,可以在多个维度进行性能方面的调优。
 
a 、硬件级别,
 
日志和数据的存储,需要分开,日志是顺序的写,需要做 raid1+0 ,并且用 buffer-IO ;数据是离散的读写,走 direct IO 即可,避免走文件系统 cache 带来的开销。
 
存储能力, SAS 盘 raid 操作( raid 卡缓存,关闭读 cache ,关闭磁盘 cache ,关闭预读,只用 writeback buffer ,不过需要考虑充放电的问题),当然如果数据规模不大,数据的存储可以用高速的设备, Fusion IO 、 SSD 。
 
对于数据的写入,控制脏页刷新的频率,对于数据的读取,控制 cache hit 率;因此而估算系统需要的 IOPS ,评估需要的硬盘数 量 (fusion io 上到 IOPS  在 10w 以上,普通的硬盘 150) 。
 
Cpu 方面,单实例关闭 NUMA , mysql 对多核的支持不是太好,可以对多实例进行 CPU 绑定。
 
b、操作系统级别,
 
内核以及 socket 的优化,网络优化 bond 、文件系统、 IO 调度
 
innodb 主要用在 OLTP 类应用,一般都是 IO 密集型的应用,在提高 IO 能力的基础上,充分利用 cache 机制。需要考虑的内容有,
 
在保证系统可用内存的基础上,尽可能的扩大 innodb buffer pool ,一般设置为物理内存的 3/4
 
文件系统的使用,只在记录事务日志的时候用文件系统的 cache ;尽量避免 mysql 用到 swap (可以将 vm.swappiness=0 ,内存紧张时,释放文件系统 cache )
 
IO 调度优化, 减少不必要的阻塞,降低随机 IO 访问的延时 (CFQ 、 Deadline 、 NOOP)
 
c、 server 以及存储引擎级别(连接管理、网络管理、 table 管理、日志)
 
包括 cache/buffer 、 Connection 、 IO
 
d、应用级别(比如索引的考虑, schema 的优化适当冗余;优化 sql 查询导致的 CPU 问题和内存问题 ,减少锁的范围,减少回表扫描,覆盖索引)
 
Ø 在高可用实践方面,
 
支持 master-master 、 master-slave 模式, master-master 模式是一个作为主负责读写,另外一个作为 standby 提供灾备, maser-slave 是一个作为主提供写操作,其他几个节点作为读操作,支持读写分离。
 
对于节点主备失效检测和切换,可以采用 HA 软件,当然也可以从更细粒度定制的角度,采用 zookeeper 作为集群的协调服务。
 
对于分布式的系统来讲,数据库主备切换的一致性始终是一个问题,可以有以下几种方式:
 
a 、集群方式,如 oracle 的 rack ,缺点是比较复杂
 
b 、共享 SAN 存储方式,相关的数据文件和日志文件都放在共享存储上,优点是主备切换时数据保持一致,不会丢失,但由于备机有一段时间的拉起,会有短暂的不可用状态
 
c 、主备进行数据同步的方式,常见的是日志的同步,可以保障热备,实时性好,但是切换时,可能有部分数据没有同步过来,带来了数据的一致性问题。可以在操作主数据库的同时,记录操作日志,切换到备时,会和操作日志做个 check ,补齐未同步过来的数据;
 
d 、 还有一种做法是备库切换到主库的 regolog 的存储上,保证数据 不丢失。
 
数据库主从复制的效率在 mysql 上不是太高,主要原因是事务是严格保持顺序的,索引 mysql 在复制方面包括日志 IO 和 relog log 两个过程都是单线程的串行操作,在数据复制优化方面,尽量减少 IO 的影响。不过到了 Mysql5.6 版本,可以支持在不同的库上的并行复制。
 
Ø 基于不同业务要求的存取方式
 
平台业务中,不同的业务有不同的存取要求,比如典型的两大业务用户和订单,用户一般来讲总量是可控的,而订单是不断地递增的,对于用户表首先采取分库切分,每个 sharding 做一主多读,同样对于订单因更多需求的是用户查询自己的订单,也需要按照用户进行切分订单库,并且支持一主多读。
 
在硬件存储方面,对于事务日志因是顺序写,闪存的优势比硬盘高不了多少,所以采取电池保护的写缓存的 raid 卡存储;对于数据文件,无论是对用户或者订单都会存在大量的随机读写操作,当然加大内存是一个方面,另外可以采用高速的 IO 设备闪存,比如 PCIe 卡  fusion-io 。使用闪存也适合在单线程的负载中,比如主从复制,可以对从节点配置 fusion-IO 卡,降低复制的延迟。
 
对于订单业务来讲,量是不断递增的, PCIe 卡存储容量比较有限,并且订单业务的热数据只有最近一段时间的 ( 比如近 3 个月的 ) ,对此这里列两种解决方案,一种是 flashcache 方式,采用基于闪存和硬盘存储的开源混合存储方式,在闪存中存储热点的数据。另外一种是可以定期把老的数据导出到分布式数据库 HBase 中,用户在查询订单列表是近期的数据从 mysql 中获取,老的数据可以从 HBase 中查询,当然需要 HBase 良好的 rowkey 设计以适应查询需求。
 
3) 分布式数据库
 
对于数据的高并发的访问,传统的关系型数据库提供读写分离的方案,但是带来的确实数据的一致性问题提供的数据切分的方案;对于越来越多的海量数据,传统的数据库采用的是分库分表,实现起来比较复杂,后期要不断的进行迁移维护;对于高可用和伸缩方面,传统数据采用的是主备、主从、多主的方案,但是本身扩展性比较差,增加节点和宕机需要进行数据的迁移。对于以上提出的这些问题,分布式数据库 HBase 有一套完善的解决方案,适用于高并发海量数据存取的要求。
 
Ø HBase
 
基于列式的高效存储降低 IO 
通常的查询不需要一行的全部字段,大多数只需要几个字段 
对与面向行的存储系统,每次查询都会全部数据取出,然后再从中选出需要的字段 
面向列的存储系统可以单独查询某一列,从而大大降低 IO 
提高压缩效率 
同列数据具有很高的相似性,会增加压缩效率 
Hbase 的很多特性,都是由列存储决定的
 
高性能
 
LSM Tree
 
适合高速写的场景
 
 
强一致的数据访问
 
MVCC
 
HBase 的一致性数据访问是通过 MVCC 来实现的。
 
HBase 在写数据的过程中,需要经过好几个阶段,写 HLog ,写 memstore ,更新 MVCC;
 
只有更新了 MVCC ,才算真正 memstore 写成功,其中事务的隔离需要有 mvcc 的来控制,比如读数据不可以获取别的线程还未提交的数据。
 
高可靠
 
HBase 的数据存储基于 HDFS ,提供了冗余机制。
 
Region 节点的宕机,对于内存中的数据还未 flush 到文件中,提供了可靠的恢复机制。
 
 
可伸缩,自动切分,迁移
 
通过 Zookeeper 定位目标 Region Server ,最后定位 Region 。 
 
Region Server 扩容,通过将自身发布到 Master , Master 均匀分布。
 
可用性
 
存在单点故障, Region Server 宕机后,短时间内该 server 维护的 region 无法访问,等待 failover 生效。 
 
通过 Master 维护各 Region Server 健康状况和 Region 分布。
 
多个 Master , Master 宕机有 zookeeper 的 paxos 投票机制选取下一任 Master 。 Master 就算全宕机,也不影响 Region 读写。 Master 仅充当一个自动运维角色。
 
HDFS 为分布式存储引擎,一备三,高可靠, 0 数据丢失。
 
HDFS 的 namenode 是一个 SPOF 。
 
为避免单个region 访问过于频繁,单机压力过大,提供了 split 机制
 
HBase 的写入是 LSM-TREE 的架构方式,随着数据的 append , HFile 越来越多, HBase 提供了 HFile 文件进行 compact ,对过期数据进行清除,提高查询的性能。
 
Schema free
 
HBase 没有像关系型数据库那样的严格的 schema ,可以自由的增加和删除 schema 中的字段。
 
HBase 分布式数据库,对于二级索引支持的不太好,目前只支持在 rowkey 上的索引,所以 rowkey 的设计对于查询的性能来讲非常关键。
 
7. 管理与部署配置
 
统一的配置库
 
部署平台
 
8. 监控、统计
 
大型分布式系统涉及各种设备,比如网络交换机,普通 PC 机,各种型号的网卡,硬盘,内存等等,还有应用业务层次的监控,数量非常多的时候,出现错误的概率也会变大,并且有些监控的时效性要求比较高,有些达到秒级别;在大量的数据流中需要过滤异常的数据,有时候也对数据会进行上下文相关的复杂计算,进而决定是否需要告警。因此监控平台的性能、吞吐量、已经可用性就比较重要,需要规划统一的一体化的监控平台对系统进行各个层次的监控。
 
平台的数据分类
 
应用业务级别:应用事件、业务日志、审计日志、请求日志、异常、请求业务 metrics 、性能度量
 
系统级别: CPU 、内存、网络、 IO
 
时效性要求
 
阀值,告警:
 
实时计算:
 
近实时分钟计算
 
按小时、天的离线分析
 
实时查询
 
架构
 
节点中 Agent 代理可以接收日志、应用的事件以及通过探针的方式采集数据, agent 采集数据的一个原则是和业务应用的流程是异步隔离的,不影响交易流程。
 
数据统一通过 collector 集群进行收集,按照数据的不同类型分发到不同的计算集群进行处理;有些数据时效性不是那么高,比如按小时进行统计,放入 hadoop 集群;有些数据是请求流转的跟踪数据,需要可以查询的,那么就可以放入 solr 集群进行索引;有些数据需要进行实时计算的进而告警的,需要放到 storm 集群中进行处理。
 
数据经过计算集群处理后,结果存储到 Mysql 或者 HBase 中。
 
监控的 web 应用可以把监控的实时结果推送到浏览器中,也可以提供 API 供结果的展现和搜索。