前言
这是一个老生常谈的话题,也是一个纷乱繁复的话题,我尝试着去找到一个恰当的切入点,我发现我找不到,因为这个话题他可以写一本书,他包含的知识点真的太多太多,我情不自禁的梳理了一遍,却发现我渐渐学习和掌握的每一个知识点,正好对应了这个时代网站架构的变迁。
网站架构为什么会发生变迁?当网站提供的服务满足不了用户需求的时候,我们就需要做出改变。怎么去改变,从用户体验角度看,我们不可能强制限制用户的需求,只能通过提升我们提供的服务来满足日益增长的用户。所以,网站架构发生变迁的根本都源于用户需求的不断增加。
正文
本文从不同用户需求的层次来剖析网站架构的变迁。
我们先从搭建一个简单的电商网站开始,假设初期的用户数很少,网站的uv只有100,pv只有1000,网站的业务也极其简单,这种情况下,网站是不用考虑任何的性能瓶颈的,我们可以用一个简单的web应用程式和一个数据库然后部署到一台单独的物理机器即可。
应用数据分离
随着岁月的变迁,网站的用户数慢慢开始积累,网站的业务复杂度也慢慢开始增加,这时候我们发现一台物理机器处理用户请求的响应时间和cpu使用率开始增加,io吞吐量开始下降,这台物理机器的性能已经出现了瓶颈,我们需要提升物理服务器的性能,当然也可以增大物理内存增加cpu,但是这种方式并不能从根本上解决问题,我们一般会采用另一种方式,将应用服务器、数据库服务器、图片文件服务器分离,并根据各自服务器自身特性和用途来配置不同的硬件,来达到很好的性能效果。
服务器集群改善应用性能
服务器分离是一个好的开端,我们发现单台服务器解决不了的问题,可以通过多台服务器来协同解决,这样看来问题似乎就简洁清晰许多了。通常而言,某种物事在质量上的提升是比较困难的,但是如果能从数量上来改变从而达到质变的效果,那就会简单很多。就这样,集群的概念出现了。随着用户规模的不断扩大,单台应用服务器无法处理用户请求的时候,可以通过应用服务器集群来改善应用服务器的性能。其原理也非常简单,就是在应用服务器前面通过负载均衡调度用户请求,将用户请求分发到多个服务器节点上去。常用的负载均衡软件有LVS、nginx、HAProxy等,LVS分发路径优于nginx和HAProxy,性能会略高一些。使用应用服务器集群也需要考虑因为集群环境带来的一些问题:
-
比如session的管理,多台服务器之间如何使session保持一致?
目前主流的做法是使用基于cache(redis)对session做集中化的存储来实现session的共享。
-
再比如分布式环境如何实现临界资源(同步互斥)的访问?
在单机环境下,我们可以很方便的使用synchronized或者lock来实现临界资源的访问,但是分布式集群环境,这种方式就无效了,目前主流的做法是使用zookeeper来实现分布式锁。
-
分布式系统难以解决却又非常重要的一个问题,分布式事务的挑战
我们往往并不是去直接保证分布式系统事务的强一致性,而是保证数据的最终一致性,换句话说,我们往往允许系统在非常短的时间内出现数据不一致的情况,我们只需要保证最终数据一致即可。
除了以上非常常见的问题之外,分布式系统较之单机系统还会有非常的问题需要去解决,如果系统业务依赖时序,如何保证每个节点的时钟完全一致?分布式系统的提出,除了用于提升应用服务器性能之外,也保证了整个系统的可靠性,那么如何去避免出现单点故障?出现单点故障如何做到自动恢复等等。分布式系统极大的改善和提高了应用服务器的性能,但是较之单机系统也在一定程度上面增加了业务的复杂度,依稀还记得哪位大牛曾经说过,很好的分布式系统设计,就是尽量不要使用分布式。看似有点矛盾,有点装x,但是我觉得真正道出了分布式系统和单机系统设计取舍的一种平衡点。
数据库分库分表读写分离
应用服务器的性能瓶颈似乎看起来已经解决了,通过应用服务器集群来改善服务器的性能在不考虑业务复杂度的情况下是一个完美的解决方案。但是,对于整个系统来说,数据库访问同样面临着严重的性能问题。有了应用服务器集群的经验,我们很自然的想到数据库集群解决方案。Sharding并不是一门新的技术,只是跟应用服务器集群一样,从质量上无法提升单机数据库服务的性能时,可以通过sharding将请求分散到不同的数据库服务节点上去。Sharding的方式分水平切分和垂直切分。
-
垂直分库,如果网站的业务都可以切分的足够独立,把不同业务的数据放到不同的数据库存储会是一个非常不错的方案,即使其中某个业务的数据库出现了问题,也不会影响到其他的业务的正常运行,看似是个非常不错的分库方案,但是在实际的生产环境下,业务之间或多或少总会有些关联,而且也不能解决单表数据暴涨的问题,所以在实际操作中除非业务之间彼此非常独立,否则我们往往会选择水平分库分表的方案。
-
水平分库分表,水平分库分表是分布式系统最常见的数据库切分方式。通过水平分库分表以后,每个库的库表结构都不发生改变,我们假设分库分表之前,数据库用户表有一亿条记录,现在通过水平分为4个库,每个库分25张表,那么经过分库分表以后,新的数据库的用户表只会有(1亿/4/25=100w)100w条记录。分库分表的策略有很多种,最常见的就是根据主键id直接取模,然后散列到不同的库表中,当然分表的纬度也有很多种,并不一定会根据id取模,如果我们选择分表的纬度是用户名,因为用户名可能是字符串,就不能通过直接取模来进行散列,可以先hash然后再取模散列。水平分库分表可以很好的解决数据库访问的性能瓶颈,和分布式系统设计一样,也会大大增加业务系统的复杂度,下面就分库分表以后常见的一些问题来做阐述:
-
分库分表以后如何生成全局唯一的主键id?
单库单表情况下,我们通常直接采用自增长的方式来生成唯一的主键id,但是分库分表以后这种方式就不可用了。我们通常会用一个独立的应用程式来提供一个专门生成全局唯一id的服务,有兴趣的朋友可以参考Twitter的解决方案。
-
假设用户表分库分表的方式是按照主键id取模的方式,怎样根据用户名或者手机号或者邮箱来查询用户?
根据不同纬度来查询的问题,这个一般没有很好的解决方案,只能通过根据主表来建立纬度附属表(查询字段和主键id的映射表)来避免查询的时候扫库扫表。即,通过建立用户名或者手机号码或者邮箱与主键id的映射表,先通过映射表查询到主键id,然后根据主键id查询到具体的记录。
-
如果用户表分库分表的策略是按照主键id mod 4的方式,有一天,我们发现4个库不再够用了,要多增加一个库的时候怎么办?如果用id mod 5,我们会发现之前的数据都散列到了错误的库。
解决这个问题可以考虑使用一致性hash算法作为分库分表的策略,一致性hash是一个环形hash算法,增加或者删除任何节点,只会影响该节点上一个或者下一个节点之间的数据,这样就可以保证增加或者删除节点带来的数据迁移影响最小。当然现在阿里巴巴提供的DRDS服务已经可以支持自动化水平拆分,在线平滑扩缩容,透明读写分离等等。
-
分库分表以后联表查询变的艰难无比,分页排序都会变的非常复杂。
水平分库分表以后做连表查询已经不可能了,只能放弃联合查询,通过多次子查询然后通过程序去内存中关联或者将相关信息存入缓存,每次更新数据库的时候同步更新缓存。分页排序的处理方式也是类似。
以上同样只是分库分表带来的诸多问题的冰山一角,跟应用服务器集群类似,既然享受到分库分表带给我们的诸多便利和性能上的极大改善,就得容忍它所有的瑕疵和缺陷。除了分库分表之外,还可以根据业务特性来做主从备份读写分离,因为往往用户80%以上的操作都是查询数据库的操作,所以我们可以采取一主多从,读写分离的方式来提升数据库的性能,用户的写操作依然访问主库,但是占据绝大多数部分的查询操作就可以通过访问多个从库来实现。对于海量的数据,频繁访问关系型数据库已经是下下策,对于海量数据的查询,可以使用内存数据库(mogodb)、redis缓存来存储,或者直接使用搜索引擎(solr、elasticsearch)来实现。
SOA和微服务架构
当我们采用了应用服务器集群和数据库集群以后,整个系统的稳定性和可靠性得到了质的提升,但是往往这时候我们会发现业务系统已经变得臃肿不堪,模块跟模块之间互相依赖,高度耦合,每次修正小的bug和增加新的功能,都会把整个系统在所有的应用服务器集群内部重新发布,并且应用启动的时间会越来越长,生产效率会受到极大的影响。这时候,我们应该会想到,需要对应用进行拆分了。
就以本文中的电商网站为例,我们可以根据不同的业务对整个系统进行拆分,登录子系统(sso单点登录)、用户中心子系统、商品子系统、购物子系统、支付子系统、评论子系统、客服子系统等等,拆分以后的子系统作为单独的服务独立部署更新和迭代,各个子系统做好去中心化的负载均衡避免出现单点故障。这就是面向服务的架构,也就是本文中的SOA架构。微服务的模式跟SOA在我看来并没有本质上的区别,只是微服务乐于采用简单轻量的协议,比如rest,而不是ws,微服务的架构其实是一个不包含ws服务的SOA。微服务的有点是显而易见的,通过分解巨大单体式应用从而解决了复杂的业务带来的一系列的问题,单个服务非常容易开发维护和迭代更新,微服务可以独立部署,很多情况下即使坏掉,也不会影响整个业务系统。微服务的架构有很多值得探讨和思考的问题,比如服务注册和发现、服务治理、服务部署以及用合适的策略去分解一个巨大的单体应用等等。
笔者接触微服务的时间并不长,但是有幸也在具体的项目中实施过。服务的注册发现以及服务治理采用的阿里开源的Dubbo框架。不得不说Dubbo是笔者目前为止使用过阿里巴巴开源出来很好的框架,没有之一。Dubbo可以通过服务管理和监控中心,在web界面上对所有的服务进行管理,包括服务之间的负载均衡、访问控制、权重调节、服务降级等等,对于集群容错、故障重试等都只需要配置重试策略即可,重试的过程对使用者透明。Dubbo可以跟Spring框架无缝集成,对于java开发者来说,配置开发极其方便。当然,Dubbo框架并不是一个跨语言的框架,它只能对基于java开发的服务进行服务管理和监控,如果构建服务是通过其他语言进行开发的,可以选择类似的产品比如Netflix Hystrix。
在架构变迁的历程中,技术的更替当然远远不止以上的变革,比如多级缓存、cdn技术、页面静态化、使用消息队列进行异步处理、数据仓库进行数据清洗转换、使用kafka、spark进行大数据实时计算和分析等等等等,总体说来构建一个复杂的应用真的很难,在设计之初我们需要尽可能的考虑到系统的可伸缩性,当然我们同时需要避免过度设计和重度设计带来的高成本,尽量使用一些优秀的框架,Stop Trying to Reinvent the Wheel!
关于:中科研拓
深圳市中科研拓科技有限公司专注提供软件外包、app开发、智能硬件开发、O2O电商平台、手机应用程序、大数据系统、物联网项目等开发外包服务,十年研发经验,上百成功案例,中科院软件外包合作企业。通过IT技术实现创造客户和社会的价值,致力于为用户提供很好的软件解决方案。联系电话400-0316-532,邮箱sales@zhongkerd.com,网址www.zhongkerd.com