应用系统架构演变初探
背景
近几年的互联网创业风潮持续在高涨中,所涉及的行业从涵盖了社交、资讯、电商、生活服务等方方面面。其中也涌现不少优秀的APP,而这些产品或平台的特点都包含了"快速",即更新快,迭代快的特性。
然而作为一名软件工程师的角度,按以前软件工程的理论来说,系统在设计初期应考虑更多的复杂度、良好的扩展性,尽可能达到以不变应万变的结果,而这些快速变更的新秀产品,在系统架构上如何做到灵活扩展、快速演进的呢? 这便是以下要开始探讨的内容。
一、初定系统架构
在最开始的时候,产品经理(或项目经理)找到你,在唠唠叨叨叙述完整个系统的功能需求后,要求你一个月内就开发出来。
经理:
"领导那边催得要命,一个月内必须上线!"
"这些功能都很常规呢,需要开发这么久吗!"
你:
...
当然,以上的场景稍显夸张,但在互联网思维方式的APP开发场景下,这样的节奏其实很常见。
研发经理都是任劳任怨的角儿,于是乎,大脑开始高速运转:
"这么短的的时间" .. "好吧,先不考虑性能问题了!"
"单元测试也可以省了,做好自测就可以,能省下不少时间呢"
经过一轮挣扎和思考,研发经理推断出了产品的第一代系统架构:
![](http://images.cnitblog.com/blog2015/242916/201503/281910008028605.jpg)
单点,以满足功能为主,简单、轻量级架构。
数据流
来自多种场景的的http请求直接由单个server接入、完成业务逻辑处理、输出计算结果。
组件介绍
1 App Server,应用系统的服务端节点,可以有很多种选择,以Java系列为例:
tomcat
最流行不过的j2ee容器,官网 http://tomcat.apache.org/
jetty
一款更为轻量级的servlet容器,轻量小巧,提供可编程server的实现(容易嵌入),在continuation机制也有比较良好的表现。
详细参见 http://www.eclipse.org/jetty/
playframework
此前最为喜爱的一款非主流应用服务器,应该是首个java版本的"ruby on rails"的实现。其摒弃了j2ee繁重的各种理念枷锁,让编程变得更加简单、可依赖。
playframework框架目前已经主推2.x (同时支持java和scala)版本,然而学习曲线相较1.2.x版本更加陡峭,建议可从1.2.5版本开始入门使用。
值得一提的是该框架内置了netty以支持http请求的接入及处理。
netty是一款基于NIO实现的轻量级http服务器,
关于netty的实现原理可参考: http://stonexmx.blog.163.com/blog/static/122158587201061331614536/
play框架的官网:https://www.playframework.com/
2 mysql,流行的开源数据库,应用基于JPA/JDBC进行访问,以实现数据持久化
3 memcached,
高速的内存服务器系统,支持KV数据流的快速读写,用作数据缓存及加速访问
二、演变的开始
好的,经过一个月的努力,系统上线了。
运营也开始干活,紧接着是对线上各种问题的紧急修复。
系统渐渐有了一些活跃度,在几个月内,产品经理和研发经理开始听到了一些抱怨:
"系统怎么老挂啊,动不动就访问不了,这什么破玩意啊" "我这都已经换成wifi了,图片怎么还是一直加载不了呢?" .....
所谓积少成怨,团队终于顶不住压力,下决心开始优化系统。针对上述的问题,分析如下:
1 可用性;
![]()
单点发生崩溃,整个系统直接不可用,所有用户将受到影响;
JVM的内存溢出,系统进程意外终止都可能导致这样的问题。
2 带宽;
应用系统在初期设计时更多的考虑了网络调用、html动态页面的输出,而针对图片的访问并未做过多考虑,因此文件资源访问很快出现了瓶颈。
研发经理决定将架构优化如下:
![](http://images.cnitblog.com/blog2015/242916/201503/281926054425409.jpg)
负载均衡
使用nginx提供应用服务器的负载均衡,提供两个应用服务器节点,保证单点崩溃时而系统仍然可用。
文件访问分离
使用单独的web服务器(nginx)用作文件访问服务器,所有的静态图片地址将指向独立域名。
三、加速进化
继上一次优化之后,用户的抱怨已经明显减少,产品经理和领导都对此次工作表示满意。
研发经理开始感受到了前所未有的成就感。
然而好景不长,在几个月后公司重点引入了营销团队。这是相当给力的团队,在短短的时间内,在各大网络平台布置了多个入口。
各种推广活动、事件营销接踵而来。最可观的如在微信朋友圈的一次事件营销直接引入了好几倍的用户量。
这样的情况在互联网产品的发展轨迹上至少也可以称得上一次小小的爆发式增长了。
当然,领导、产品经理都开心和兴奋起来了。但这次,研发经理内心开始发愁了:
1 访问压力;
一个server的并发处理能力很容易达到上限,一旦超过负载阈值,将直接导致访问时长加大,甚至系统崩溃、无法访问等情形;
2 数据存取;
数据量的扩充,单个数据库表不断增大,查询速度将产生下降;
此外数据库的单点也成为隐患
3 缓存空间;
与数据库同理,为加速更多数据的访问,需要提供更大容量缓存空间;
这次,研发经理将系统架构做了一次较为彻底的改进:
![](http://images.cnitblog.com/blog2015/242916/201503/281928464278656.png)
负载均衡
增加负载均衡的服务器节点,提高整体处理能力;
数据库扩展
采取水平切分的方式,实现基于Shard的分布式数据库集群;
memcached集群
开启多个memcached节点,组成高可用的缓存集群,同时可支持更大的缓存空间
其中对于服务端程序改造最大的在于分布式Shard数据的实现,一般可从数据量较大的局部表开始切分,在数据存取上加入水平分库的机制;
当然此时的数据迁移工作也是一个重点。
四、未来的展望
至此,系统优化已经告一段落。当前的后台架构已经处在一个表现稳定,容易扩展的一个状态。
性能指标方面大致已经可以达到百万级用户量,日PV过百万,甚至还可能更好。
然而这还仅仅是一个雏形,应用系统的场景是多种多样的,在未来需要解决的问题往往不仅仅这些,比如:
1 系统存在全文搜索、或是标签式搜索的场景,为了提高搜索性能,你可能会引入sphinx中间件;
sphinx的中文版本为coreseek(可支持中文全文搜索),。
2 为了提高事务处理的效率,你需要队列服务器来解决,这个可以是ttserver + worker的模式;
3 频繁的处理缓存数据的解析在开发上会比较麻烦,你还可以更换redis来直接支持数据结构式的缓存...
redis为一款支持数据结构缓存及数据持久化的优秀的nosql中间件,
官网地址:http://redis.io/
备注
架构设计需秉承的"适用"原则,不同业务场景所采取的架构都会有些差异,
此处仅仅讨论最常见的扩展思路,关于读写分离、数据主备等做法在此不做讨论。
本文所提及的故事情节均属杜撰,如有雷同,实属巧合。