Web应用架构演进
项目起步阶段
在项目起步阶段,系统访问量不大,业务比较单一,且需求紧,需要快速上线,这个时候一般瀑布式开发,单系统、单库、单缓存集群。
业务范围扩大,根据业务边界拆分
在业务范围扩大过程中,单系统会逐渐变得臃肿庞杂,业务耦合越来越严重,改动小可能影响会很大,有时候可能因为一处细小的错误导致全站宕机。
当前阶段最直接的办法就是根据业务边界进行拆分,每个业务模块由专门的开发人员进行维护,做到专而精。
访问量级增大,系统性能考虑提上日程
在系统的访问量级变大之后,对于系统性能方面考虑就要提上日程了,以前可能单库就搞定的事情现在需要分库分表了,以前把量全部往数据库打都不要紧的现在需要考虑哪些数据需要做预热了,以前很多的非核心逻辑在主流程同步执行的需要考虑异步去执行了。而且在访问量这么大的条件下,对于系统的稳定性要求会更高,以前挂1分钟可能都没请求进来,现在挂1分钟可能是上百万请求异常。
该系统中由于存在需要根据多字段去检索信息,所以会有一个索引库的概念,通过在索引库去查询信息所在分库,但是这种模型对于读能力是可以水平扩展的,而写能力则是有单点瓶颈,有待优化。
多机房容灾
上一步完成之后,对于单机房的可用性已经有一定的保障,要做到服务的持续可用,还得继续发展做网络分区,保证一个网络分区下的整个集群全部挂掉,另外的网络分区也可继续提供服务。
在介绍网络分区之前,先来介绍下CAP定理,在分布式系统中,不可能同时满足三点,C,consistence数据一致性,A,availability高可用性,P,partition tolerance容忍网络分区;如果有了网络分区,要么选择不同分区都可以写数据,丧失部分数据一致性能力,通过最终一致性保证不同分区下数据一致;要么选择只有一个分区在写数据,丧失写的高可用。
先来看看国内使用较多的模式,两地三中心,同城两个机房可同时提供服务,写数据可一个机房写也可以两个机房都写再同步,异地机房做冷备,一般来说同城机房之间50公里的光纤距离,数据同步延时在1~2ms,是一个可接受的范围,而异地机房同步延时可能超过100ms;选择这种方式整体来说就会简单一些。一个异地机房就这么一直处于空闲状态总觉得代价挺大的,完全部署了一套却长期派不上用场,而且一旦主机房出问题了,冷备机房长期未使用,对于这个机房是否能完全接管下来还是个未知数;那么接下来又做了一些改进,把部分只读流量引导到异地机房;多了只读请求后,就需要考虑一个问题,如果用户刚注册完去访问用户信息,结果用户被路由到异地机房,数据还没同步过来,发现用户不存在了怎么处理,可能的一种做法是发现没有数据了就跑去源机房取一次试试。
异地只读这种方式在某些业务场景下发现能拎出来的只读业务很难抽象并区分开,这样的话异地只读的机房还是意义不太大,那再做好一点就要往异地多活的方向发展了,每个城市最少一个机房,每个机房都会有读写流量,允许多点写;那么这开始考虑的问题就要多太多了,举两个例子,
1、机房A和B现在分别接管百分之五十流量,现在想把A机房流量再切一部分到B机房,那么就可能存在在A机房写入了一些数据,再到B机房读取发现数据不一致了,怎么做到切换过程完全一致
2、如果用户现在机房A注册了,机房A某个时间点挂了,流量切到机房B,用户又使用这个信息注册了一次,但是密码可能换了一个,这个时候机房A又恢复了,怎么对这个数据做恢复
类似这种细节点应该还有很多,这里还缺少实战经验,就不做太多介绍了,也希望后续能够有机会参与到异地多活的设计与开发,
再回过头来看看,虽然细节点有许多要考虑的,做异地多活的话还有一些基本原则是比较明确的,首先对一个用户做路由的时候,不能说第一次访问给路由到A机房,第二次访问又给路由到B机房,这个对一致性要求太高,所以这里需要把路由策略做到同一个用户请求每次访问路由到一处;然后因为延时问题如果存在多次跨机房调用对于用户体验造成严重影响,需要保证同机房流量闭环;另外对于多中心数据同步的延时一定要有感知,对于数据的一致性需要有一定的校验措施。
那么异地多活做完之后呢,好像系统已经很强大了,就算机房光缆被挖断也不用担心了。
服务化
1、 什么是服务化
服务拆分
服务治理
2、 服务化可以带来什么
业务职责清晰
- 模块复用
- 自主测试及上线
- 独立伸缩
- 故障隔离
系统性能
压测介绍
线下压测
单机压测
线上压测
全链路压测
逐渐引导流量到一台机器
线上监控(CAT)
线下调优环境
线上同等配置机器及数据量
调优目标
TPS、平均响应时间、最大响应时间、错误率
CPU、内存使用率
稳定
内存趋势在每一个最高点或最低点都是呈现上升趋势,长期压会引发OOM,需要排查内存占用对象。
对于稳定的系统压测,走势应该如下图
调优举例
Proxool导致finalizer线程阻塞
见下图中T4CPreparedStatement和proxool.WrappedStatement,占比较高,详情见http://blog.csdn.net/hzzhoushaoyu/article/details/51052773
Tomcat session使内存爆满
上图中可看见有tomcat的StandardSession占比也较高,这边当时遇到这个问题的场景是说我们封装了session放到redis中,但是在封装过程中错误调用了原生的request.getSession导致产生大量session对象,默认session超时30分钟,30分钟内无法被释放,如果持续生成会导致OOM。
Drools的高并发使用HashMap导致假死
详情见http://coolshell.cn/articles/9606.html
IO密集型服务做异步化处理
读多写少,使用分布式缓存及预热处理
系统稳定性
SLA
SLA,Service Level Agreement
SLA体现在对容量(QPS)、性能(RT)、程度(分布情况;可⽤用性、出错率)的约束
SLA保障是服务稳定性的生命线和服务架构升级的动力
高可用
系统无单点,不仅包括软件的部署以及机房的电源、交换机等硬件设备。
强依赖转弱依赖
梳理系统核心流程,将非核心依赖转为弱依赖
防御式编程
防御式编程核心是除了自身系统,对上游和下游系统的调用量、稳定性和服务质量都持质疑态度;
一旦上游或下游服务出现任何问题,通过预设策略保证自身系统的稳定。
客户端超时,调用任何外部服务时需要设定合理的超时时间,避免因为外部服务故障引起自身服务的一系列故障
服务端超时,对于在等待队列中的任务,如果客户端已经超过等待时间则抛弃请求,避免引发雪崩
熔断,过载保护措施;熔断策略和恢复策略
服务降级,对弱依赖服务做好开关或多策略切换机制,保证在对方服务挂掉时屏蔽服务调用或切换其他执行策略
资源池隔离
不同业务使用的资源池隔离使用:数据库、缓存、连接池、线程池
防刷和限流
限制恶意提交
控制请求频次;请求数据特征;限制分为降低频次和限制一定时间使用 针对客户端接口通过动态库加签及自定义协议等策略提高刷接口门槛 针对网页接口抓取机器特征及用户行为做风险判断
针对服务端服务能力限制接口请求总量
削峰以及真/假排队
通过发送消息的方式,后端服务根据能力接收消息处理的方式进行削峰填谷。
假排队是描述系统对于用户的先进先出无特别感知,只要用户请求进来满足可进入条件即可放入。
真排队描述用户必须根据先来后到的顺序进行排队进入。
多机房容灾
同城双活
两地三中心
异地只读
异地多活
灰度发布
通过灰度发布验证新功能,减小影响范围
系统监控
业务监控
订单量,支付成功率;各渠道支付成功量、各渠道支付成功率;PC/H5支付成功量;收银台访问量
业务监控最能反应当前系统运行状态,如果业务数据突然减少很多,必然出现问题。
业务告警,通过日志中ERROR和FATAL进行邮件、短信通知
应用监控
调用频次,各接口TPS,平均响应时长,最长响应时长,http status
系统监控
CPU、内存、负载、网络带宽、IO、磁盘空间、服务端口
全链路监控系统
通过全局唯一traceID从业务入口带到底端,然后通过日志埋点写入到大数据平台进行分析展示。
业务可扩展性
业务边界
说大一点的层面,会员、众筹这些商品系统,在下单前都是在各自系统下维护,包括可能存在购物车、商品详情等,下单后准备支付的流程都在钱包系统,支付完再由钱包通知回各商品系统,如果有实体发货会有另外的系统做维护配送信息等。
再往小层面,各业务子系统,比如钱包目前存在银行卡业务、账户余额业务、收银台支付业务、充值业务等,剥离完各自业务系统,后续有业务需求变更时,就只用根据业务的职责去修改对应系统。
如果都做在一起,那后续需求变更的时候很可能就是往哪里做最快就往哪里做了,长期下去会导致各业务之间耦合严重;剥离到不同业务系统可以从根源上杜绝这个问题。业务分层
在对业务边界有了明确区分后,还需要对这些业务系统做一个良好的分层,每一层仅依赖本层和下层。