java 商城系统架构之*四篇:构建高并发高可用的电商平台架构实践

2020-01-05 浏览次数:285

一、设计理念
注:这里只讲概念,尽量不讲技术!但是会推荐一些。本博客虽然是基于java语言,但是适用于任何其他大型架构系统。
1、空间换时间
1)多级缓存、静态化
关于缓存方面,可以做:客户端页面缓存、反向代理缓存、应用端的缓存、内存数据库以及做前端静态化
对于页面缓存,比如京东较狠,图片*存储在客户端,改图片、css、js等的话,必须要改名字,要不然会hit到旧文件。
对于前端静态化,web的话可以做静态页面,对于app、cs架构等同样适用
2) 索引
一般对于关系型数据库查询,需要用到索引。
2、并行与分布式计算
1)任务切分、分而治之(MR)
在大规模的数据中,数据存在一定的局部性的特征,利用局部性的原理将海量数据计算的问题分而治之。
MR模型是无共享的架构,数据集分布至各个节点。处理时,每个节点就近读取本地存储的数据处理(map),将处理后的数据进行合并(combine)、排序(shuffle and sort)后再分发(至reduce节点),避免了大量数据的传输,提高了处理效率。
 
2)多进程、多线程并行执行(MPP)
并行计算(Parallel Computing)是指同时使用多种计算资源解决计算问题的过程,是提高计算机系统计算速度和处理能力的一种有效手段。它的基本思想是用多个处理器/进程/线程来协同求解同一问题,即将被求解的问题分解成若干个部分,各部分均由一个独立的处理机来并行计算。
和MR的区别在于,它是基于问题分解的,而不是基于数据分解。
3、多维度的高可用
1)负载均衡、容灾、备份
随着平台并发量的增大,需要扩容节点进行集群,利用负载均衡设备进行请求的分发;负载均衡设备通常在提供负载均衡的同时,也提供失效检测功能;同时为了提高可用性,需要有容灾备份,以防止节点宕机失效带来的不可用问题;备份有在线的和离线备份,可以根据失效性要求的不同,进行选择不同的备份策略。
2)读写分离、分库分表
读写分离是对数据库来讲的,随着系统并发量的增大,提高数据访问可用性的一个重要手段就是写数据和读数据进行分离;当然在读写分离的同时,需要关注数据的一致性问题;对于一致性的问题,在分布式的系统CAP定量中,更多的关注于可用性。
根据本博主经验,对于单表数据量**过300万,就可以做分库分表,不推荐使用第三方,推荐大家自己写一套,其实就是改造下ORM端,自己改造有几个优势,1、学习成本低,2、后期维护不依赖于第三方,3、横向扩展容易。博主的公司分库分表是自己写的,是**过300万就会自动建库、切分。
首先需要做垂直切分,也就是字段非常多,改成字段在20个以内的表。当然这个并不是必须,为的是分库分表操作起来更简单,很多人对这个有误解,垂直并不是必须要做的。我们公司还有表是100来个字段,数据量在5千万以上,并没有拆分。
3)依赖关系
平台中各个模块之间的关系尽量是低耦合的,可以通过相关的消息组件进行交互,能异步则异步,分清楚数据流转的主流程和副流程,主副是异步的,比如记录日志、邮件、短信、公告等可以是异步操作的,增加整个系统的可用性。
当然在异步处理中,为了确保数据得到接收或者处理,往往需要确认机制(confirm、ack)。
但是有些场景中,虽然请求已经得到处理,但是因其他原因(比如网络不稳定),确认消息没有返回,那么这种情况下需要进行请求的重发,对请求的处理设计因重发因素需要考虑幂等性。
4) 监控
监控也是提高整个平台可用性的一个重要手段,多平台进行多个维度的监控;模块在运行时候是透明的,以达到运行期白盒化。
4、伸缩
1) 拆分
拆分包括对业务的拆分和对数据库的拆分。
系统的资源总是有限的,一段比较长的业务执行如果是一竿子执行的方式,在大量并发的操作下,这种阻塞的方式,无法有效的及时释放资源给其他进程执行,这样系统的吞吐量不高。
需要把业务进行逻辑的分段,采用异步非阻塞的方式,提高系统的吞吐量。
随着数据量和并发量的增加,读写分离不能满足系统并发性能的要求,需要对数据进行切分,包括对数据进行分库和分表。这种分库分表的方式,需要增加对数据的路由逻辑支持。
2)无状态
对于系统的伸缩性而言,模块较好是无状态的,通过增加节点就可以提高整个的吞吐量。
注:无状态其实就是一些逻辑上不需要用户登录状态的。。。比如查看web系统首页、新闻页面、以及模块的局部,比如访问京东的详情页,Top部分需要用户登录状态、价格优惠部分需要用户信息才能做出优惠,这几个模块就是有状态。
有状态也可以做横向扩展。。。。
5、优化资源利用
1)系统容量有限
系统的容量是有限的,承受的并发量也是有限的,在架构设计时,一定需要考虑流量的控制,防止因意外攻击或者瞬时并发量的冲击导致系统崩溃。在设计时增加流控的措施,可考虑对请求进行排队,**出预期的范围,可以进行告警或者丢弃。
2)原子操作与并发控制
对于共享资源的访问,为了防止冲突,需要进行并发的控制,同时有些交易需要有事务性来保证交易的一致性,所以在交易系统的设计时,需考虑原子操作和并发控制。
保证并发控制一些常用高性能手段有,乐观锁、Latch、mutex、写时复制、CAS等;多版本的并发控制MVCC通常是保证一致性的重要手段,这个在数据库的设计中经常会用到。
3)基于逻辑的不同,采取不一样的策略
平台中业务逻辑存在不同的类型,有计算复杂型的,有消耗IO型的,同时就同一种类型而言,不同的业务逻辑消耗的资源数量也是不一样的,这就需要针对不同的逻辑采取不同的策略。
针对IO型的,可以采取基于事件驱动的异步非阻塞的方式,单线程方式可以减少线程的切换引起的开销,或者在多线程的情况下采取自旋spin的方式,减少对线程的切换(比如oracle latch设计);对于计算型的,充分利用多线程进行操作。
同一类型的调用方式,不同的业务进行合适的资源分配,设置不同的计算节点数量或者线程数量,对业务进行分流,**执行**级别高的业务。
4)容错隔离
系统的有些业务模块在出现错误时,为了减少并发下对正常请求的处理的影响,有时候需要考虑对这些异常状态的请求进行单独渠道的处理,甚至暂时自动禁止这些异常的业务模块。
有些请求的失败可能是偶然的暂时的失败(比如网络不稳定),需要进行请求重试或者请求转移到其他机器的考虑。
 
5)资源释放
系统的资源是有限的,在使用资源时,一定要在最后释放资源,无论是请求走的是正常路径还是异常的路径,以便于资源的及时回收,供其他请求使用。
在设计通信的架构时,往往需要考虑**时的控制。
 
二、 静态架构蓝图
 
整个架构是分层的分布式的架构,纵向包括CDN,负载均衡/反向代理,web应用,业务层,基础服务层,数据存储层。水平方向包括对整个平台的配置管理部署和监控。
 
三、 剖析架构
1、CDN
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的连接即可。
  
路由组件的实现是这样的(可用性、高性能、高并发)
基于性能方面的考虑,采用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节点列表。
 
3) HA
传统实现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更加适合在关系型数据库之上的数据的缓存。
 
 
 
强一致的数据访问
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供结果的展现和搜索。




redpigmall.b2b168.com/m/
联系我们

在线客服: 4407509

联系人:周庆达

联系电话: 17503009512