以淘宝作为例,介绍从一百个并发到千万级并发情况下服务端的架构的演进过程,同时列举出每个演进阶段会遇到的相关技术,让大家对架构的演进有一个整体的认知,文章最后汇总了一些架构设计的原则。
网站的初期,我们经常会在单机上跑我们所有的程序和软件。此时我们使用一个容器,如tomcat、jetty、jboos,然后直接使用JSP/servlet技术,或者使用一些开源的框架如maven+spring+struct+hibernate、maven+spring+springmvc+mybatis; 最后再选择一个数据库管理系统来存储数据,如mysql、sqlserver、oracle,然后通过JDBC进行数据库的连接和操作。
以淘宝为例子,在网站最初的时候,应用数量与用户数量都比较少,可以把Tomcat和数据库部署在同一台服务器上。浏览器往www.taobao.com
发起请求时,首先经过DNS服务器(域名系统)把域名转换成实际IP地址10.102.4.1
,浏览器转而访问该IP对应的Tomcat。
单体架构在规模比较小的情况下工作情况良好,但是随着系统规模的扩大,它暴露出来的问题也越来越多,主要有以下几点:
- 复杂性逐渐变高 比如有的项目有几十万行代码,各个模块之间区别比较模糊,逻辑比较混乱,代码越多复杂性越高,越难解决遇到的问题。
- 技术债务逐渐上升 公司的人员流动是再正常不过的事情,有的员工在离职之前,疏于代码质量的自我管束,导致留下来很多坑。由于单体项目代码量庞大的惊人,留下的坑很难被发觉,这就给新来的员工带来很大的烦恼,人员流动越大所留下的坑越多,也就是所谓的技术债务越来越多。
- 部署速度逐渐变慢 这个就很好理解了,单体架构模块非常多,代码量非常庞大,导致部署项目所花费的时间越来越多,曾经有的项目启动就要一二十分钟,这是多么恐怖的事情啊,启动几次项目一天的时间就过去了,留给开发者开发的时间就非常少了。
- 阻碍技术创新 比如以前的某个项目使用struts2写的,由于各个模块之间有着千丝万缕的联系,代码量大,逻辑不够清楚,如果现在想用spring mvc来重构这个项目将是非常困难的,付出的成本将非常大,所以更多的时候公司不得不硬着头皮继续使用老的struts2架构,这就阻碍了技术的创新。
- 无法按需伸缩 比如说用户模块是CPU密集型的模块,而订单模块是IO密集型的模块,假如我们要提升订单模块的性能,比如加大内存、增加硬盘,但是由于所有的模块都在一个架构下,因此我们在扩展订单模块的性能时不得不考虑其它模块的因素,因为我们不能因为扩展某个模块的性能而损害其它模块的性能,从而无法按需进行伸缩。
随着网站的上线,访问量逐步上升,服务器的负载慢慢提高,在服务器还没有超载的时候,我们应该就要做好准备,提升网站的负载能力。假如我们代码层面已难以优化,在不提高单台机器的性能的情况下,增加机器是一个不错的方式,不仅可以有效地提高系统的负载能力,而且性价比高。
增加的机器用来做什么呢?此时我们可以把数据库,web服务器拆分开来,这样不仅提高了单台机器的负载能力,也提高了容灾能力。
应用服务器与数据库分开后的架构如下图所示: web服务器(Tomcat)和数据库分别独占服务器资源,显著地提高两者各自的性能。
随着用户数量的增长,并发读写数据库成为了性能的瓶颈。
第二次演进:引入本地缓存和分部式缓存在Tomcat服务器或JVM中增加本地缓存,并在外部增加分布式缓存,缓存热门商品信息或热门商品的HTML页面等。通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。其中涉及的技术包括:使用Memcached作为本地缓存,使用Redis作为分布式缓存,这会涉及到缓存一致性、缓存穿透/击穿、缓存雪崩、热点数据集中失效等问题。 具体和缓存方案:
-
后台应用层和数据库层的缓存 随着访问量的增加,逐渐出现了许多用户访问同一部分内容的情况,对于这些比较热门的内容,没必要每次都从数据库读取。我们可以使用缓存技术,例如可以使用google的开源缓存技术guava或者使用memcacahe作为应用层的缓存,也可以使用redis作为数据库层的缓存。 另外,在某些场景下,关系型数据库并不是很适合,例如我想做一个“每日输入密码错误次数限制”的功能,思路大概是在用户登录时,如果登录错误,则记录下该用户的IP和错误次数,那么这个数据要放在哪里呢? 假如放在内存中,那么显然会占用太大的内容;假如放在关系型数据库中,那么既要建立数据库表,还要建立对应的java bean,还要写SQL等等。而分析一下我们要存储的数据,无非就是类似{ip:errorNumber}这样的key:value数据。对于这种数据,我们可以用NOSQL数据库来代替传统的关系型数据库。
-
页面缓存 除了数据缓存,还有页面缓存。比如使用HTML5的localstroage或者cookie。
-
优点:
- 减轻数据库的压力
- 大幅度提高访问速度
-
缺点:
- 需要维护缓存服务器
- 提高了编码的复杂性
值得一提的是:缓存集群的调度算法不同与上面提到的应用服务器和数据库。最好采用“一致性哈希算法”,这样才能提高命中率。
缓存虽然抗住了大部分的访问请求,但是随着用户数量的增长,并发的压力还是主要落在单机的Tomcat上,响应逐渐变慢。
第三次演进:引入反向代理和负载均衡随着访问量继续增加,单台应用服务器已经无法满足需求了。在假设数据库服务器没有压力的情况下,我们可以把应用服务器从一台变成了两台甚至多台,把用户的请求分散到不同的服务器中,从而提高负载能力。
多台应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务。
在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。此处假设Tomcat最多支持100个并发,Nginx最多支持50000个并发,那么理论上Nginx把请求分发到500个Tomcat上,就能抗住50000个并发。其中涉及的技术包括:Nginx、HAProxy,两者都是工作在网络第七层(最高层、应用层)的反向代理软件,主要支持HTTP协议,还会涉及Session共享,文件上传、下载的问题。 系统演变到这里,有可能会出现下面四个问题:
- 用户的请求由谁来转发到到具体的应用服务器
- 有什么转发的算法
- 应用服务器如何返回用户的请求
- 用户如果每次访问到的服务器不一样,那么如何维护session的一致性
解决方案:
-
第一个问题即是负载均衡的问题,一般有5种解决方案:
- http重定向。HTTP重定向就是应用层的请求转发。用户的请求其实已经到了HTTP重定向负载均衡服务器,服务器根据算法要求用户重定向,用户收到重定向请求后,再次请求真正的集群 优点:简单。 缺点:性能较差。
- DNS域名解析负载均衡。DNS域名解析负载均衡就是在用户请求DNS服务器,获取域名对应的IP地址时,DNS服务器直接给出负载均衡后的服务器IP。 优点:交给DNS,不用我们去维护负载均衡服务器。 缺点:当一个应用服务器挂了,不能及时通知DNS,而且DNS负载均衡的控制权在域名服务商那里,网站无法做更多的改善和更强大的管理。
- 反向代理服务器。在用户的请求到达反向代理服务器时(已经到达网站机房),由反向代理服务器根据算法转发到具体的服务器。常用的apache,nginx都可以充当反向代理服务器。 优点:部署简单。 缺点:代理服务器可能成为性能的瓶颈,特别是一次上传非常大的文件。
- IP层负载均衡。在请求到达负载均衡器后,负载均衡器通过修改请求的目的IP地址,从而实现请求的转发,做到负载均衡。 优点:性能更好。 缺点:负载均衡器的宽带成为瓶颈。
- 数据链路层负载均衡。在请求到达负载均衡器后,负载均衡器通过修改请求的mac地址,从而做到负载均衡。与IP负载均衡不一样的是,当请求访问完服务器之后,直接返回客户。而无需再经过负载均衡器。
-
第二个问题即是集群调度算法问题,常见的调度算法有以下10种:
- rr 轮询调度算法。顾名思义,轮询分发请求。 优点:实现简单 缺点:不考虑每台服务器的处理能力
- wrr 加权调度算法。我们给每个服务器设置权值weight,负载均衡调度器根据权值调度服务器,服务器被调用的次数跟权值成正比。 优点:考虑了服务器处理能力的不同
- sh 原地址散列:提取用户IP,根据散列函数得出一个key,再根据静态映射表,查出对应的value,即目标服务器IP。如果目标机器超负荷,则返回空。
- dh 目标地址散列:同上,只是现在用提取的是目标地址的IP来做哈希。 优点:以上两种算法都能实现同一个用户访问同一个服务器。
- lc 最少连接。优先把请求转发给连接数少的服务器。 优点:使得集群中各个服务器的负载更加均匀。
- wlc 加权最少连接。在lc的基础上,为每台服务器加上权值。算法为:(活动连接数*256+非活动连接数)÷权重 ,计算出来的值小的服务器优先被选择。 优点:可以根据服务器的能力分配请求。
- sed 最短期望延迟。其实sed跟wlc类似,区别是不考虑非活动连接数。算法为:(活动连接数+1)*256÷权重,同样计算出来的值小的服务器优先被选择。
- nq 永不排队。改进的sed算法。我们想一下什么情况下才能“永不排队”,那就是服务器的连接数为0的时候,那么假如有服务器连接数为0,均衡器直接把请求转发给它,无需经过sed的计算。
- LBLC 基于局部性的最少连接。均衡器根据请求的目的IP地址,找出该IP地址最近被使用的服务器,把请求转发之;若该服务器超载,最采用最少连接数算法。
- LBLCR 带复制的基于局部性的最少连接。均衡器根据请求的目的IP地址,找出该IP地址最近使用的“服务器组”。注意,并不是具体某个服务器,然后采用最少连接数从该组中挑出具体的某台服务器出来,把请求转发之。若该服务器超载,那么根据最少连接数算法,在集群的非本服务器组的服务器中,找出一台服务器出来,加入本服务器组,然后把请求转发之。
-
第三个问题是集群模式问题,一般3种解决方案:
- NAT:负载均衡器接收用户的请求,转发给具体服务器,服务器处理完请求返回给均衡器,均衡器再重新返回给用户。
- DR:负载均衡器接收用户的请求,转发给具体服务器,服务器处理完请求后直接返回给用户。需要系统支持IP Tunneling协议,难以跨平台。
- TUN:同上,但无需IP Tunneling协议,跨平台性好,大部分系统都可以支持。
-
第四个问题是session问题,一般有以下4种解决方案:
- Session Sticky。session sticky就是把同一个用户在某一个会话中的请求,都分配到固定的某一台服务器中,这样我们就不需要解决跨服务器的session问题了,常见的算法有ip_hash法,即上面提到的两种散列算法。 优点:实现简单。 缺点:应用服务器重启则session消失。
- Session Replication。session replication就是在集群中复制session,使得每个服务器都保存有全部用户的session数据。 优点:减轻负载均衡服务器的压力,不需要实现ip_hasp算法来转发请求。 缺点:复制时宽带开销大,访问量大的话session占用内存大且浪费。
- Session数据集中存储:session数据集中存储就是利用数据库来存储session数据,实现了session和应用服务器的解耦。 优点:相比session replication的方案,集群间对于宽带和内存的压力减少了很多。 缺点:需要维护存储session的数据库。
- Cookie Base:cookie base就是把session存在cookie中,有浏览器来告诉应用服务器我的session是什么,同样实现了session和应用服务器的解耦。 优点:实现简单,基本免维护。 缺点:cookie长度限制,安全性低,宽带消耗。
值得一提的是:
- nginx目前支持的负载均衡算法有wrr、sh(支持一致性哈希)、fair(本人觉得可以归结为lc)。但nginx作为均衡器的话,还可以一同作为静态资源服务器。
- keepalived+ipvsadm比较强大,目前支持的算法有:rr、wrr、lc、wlc、lblc、sh、dh
- keepalived支持集群模式有:NAT、DR、TUN
- nginx本身并没有提供session同步的解决方案,而apache则提供了session共享的支持。
虽然反向代理使应用服务器可以支持的并发量大大增加,但是并发量的增加也意味着更多请求穿透到数据库,单机的数据库最终会称为性能瓶颈。
第四次演进:数据库读写分离上面我们总是假设数据库负载正常,但随着访问量的的提高,数据库的负载也在慢慢增大。那么可能有人马上就想到跟应用服务器一样,把数据库一份为二再负载均衡即可。
但对于数据库来说,并没有那么简单。假如我们简单的把数据库一分为二,然后对于数据库的请求,分别负载到A机器和B机器,那么显而易见会造成两台数据库数据不统一的问题。那么对于这种情况,我们可以先考虑使用读写分离的方式。 把数据库划分为读库和写库,读库可以有多个,通过同步机制把写库的数据同步到读库,对于需要查询最新写入数据的场景,可以在缓存中多写一份,通过缓存获得最新数据。其中涉及的技术包括Mycat,它是数据库中间件,可通过它来组织数据库的读写分离和分库分表,客户端通过它来访问下层数据库,还会涉及数据同步,数据一致性的问题。
这个结构变化后也会带来两个问题:
- 主从数据库之间数据同步问题
- 应用对于数据源的选择问题 解决问题方案:
- 我们可以使用MYSQL自带的master+slave的方式实现主从复制。
- 采用第三方数据库中间件,例如mycat。mycat是从cobar发展而来的,而cobar是阿里开源的数据库中间件,后来停止开发。mycat是国内比较好的mysql开源数据库分库分表中间件。
随着业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库资源,相互影响性能。
第五次演进:数据库水平拆分与垂直拆分我们的网站演进到现在,交易、商品、用户的数据都还在同一个数据库中。尽管采取了增加缓存,读写分离的方式,但随着数据库的压力继续增加,数据库的瓶颈越来越突出,此时,我们可以有数据垂直拆分和水平拆分两种选择。
数据库垂直拆分(按业务分库)垂直拆分的意思是把数据库中不同的业务数据拆分道不同的数据库中,比如把电商项目中的交易、商品、用户的数据分开。 把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低。对于访问量大的业务,可以部署更多的服务器来支撑。
- 优点:
- 解决了原来把所有业务放在一个数据库中的压力问题。
- 可以根据业务的特点进行更多的优化
- 缺点: 需要维护多个数据库
- 问题:
- 需要考虑原来跨业务的事务
- 跨数据库的join
- 解决问题方案:
- 应该在应用层尽量避免跨数据库的事物,如果非要跨数据库,尽量在代码中控制。
- 可以通过第三方应用来解决,如上面提到的mycat,mycat提供了丰富的跨库join方案,详情可参考mycat官方文档。
随着用户数量的增长,单机的写库会逐渐达到性能瓶颈。
水平拆分(把大表拆分为小表(分表))数据水平拆分就是把同一个表中的数据拆分到两个甚至多个数据库中。产生数据水平拆分的原因是某个业务的数据量或者更新量到达了单个数据库的瓶颈,这时就可以把这个表拆分到两个或更多个数据库中。 比如针对评论数据,可以按照商品的ID进行Hash,路由到对应的表中存储;针对支付记录,可以按照支付的小时创建表,每个小时表继续拆分为小表,使用用户ID或记录编号来路由数据。只要实时操作的表数据量足够小,请求能够足够均匀地分发到多台服务器上的小表,那数据库就能通过水平扩展的方式来提升性能。其中前面提到的Mycat也支持在大表拆分为小表的情况下进行访问控制。 这种做法显著地增加了数据库运维的难度,对DBA的要求较高。当数据库设计到这种结构时,已经可以称为分布式数据库,但是这只是一个逻辑的数据库整体,数据库里不同的组成部分是由不同的组件单独来实现的,比如分库分表的管理和请求分发由Mycat实现,SQL的解析由单机的数据库实现,读写分离可能由网关和消息队列来实现,查询结果的汇总可能由数据库接口层来实现等,这种架构其实是
MPP
(大规模并行处理)架构的一类实现。
目前开源和商用都已经有不少MPP数据库,开源中比较流行的有Greenplum、TiDB、Postgresql XC、HAWQ等,商用的如南大通用的GBase、睿帆科技的雪球DB、华为的LibraA等,不同的MPP数据库的侧重点也不一样,比如TiDB侧重于分布式OLTP场景,Greenplum侧重于分布式OLAP场景,这些MPP数据库基本都提供了类似Postgresql、Oracle、MySQL那样的SQL标准支持能力,能把一个查询解析为分布式的执行计划分发到每台机器上并行执行,最终由数据库本身汇总数据进行返回,也提供了注入权限管理、分库分表、事务、数据副本等能力,并且大多能够支持100个节点以上的集群,大大降低了数据 库运维的成本,并且使数据库也能够水平扩展。
- 优点: 如果我们能克服以上问题,那么我们将能够很好地应对数据量及写入量增长的情况。
- 问题:
- 访问用户信息(假设用户表进行了水平拆分)的应用系统需要解决SQL路由的问题,因为现在用户信息分在了两个数据库中,需要在进行数据操作时了解需要操作的数据在哪里。
- 主键的处理也变得不同,例如原来自增字段,现在不能简单地继续使用了。
- 如果需要分页,就麻烦了。
- 解决问题方案:
- 可以通过可以解决第三方中间件,如mycat。mycat可以通过SQL解析模块对我们的SQL进行解析,再根据我们的配置,把请求转发到具体的某个数据库。
- 可以通过UUID保证唯一或自定义ID方案来解决。
- mycat也提供了丰富的分页查询方案,比如先从每个数据库做分页查询,再合并数据做一次分页查询等等。
虽然数据库和Tomcat都能够水平扩展,可以支撑的并发量大幅提升,但是随着用户量的增长,最终单机的Nginx会成为性能上的瓶颈。
第七次演进:使用LVS或F5来使多个Nginx负载均衡由于性能瓶颈在Nginx,因此无法通过两层的Nginx来实现多个Nginx的负载均衡。LVS和F5是工作在网络第四层的负载均衡解决方案,其中:
- LVS是软件,运行在操作系统内核态,可对TCP请求或更高层级的网络协议进行转发,因此支持的协议更丰富,并且性能也远高于Nginx,可假设单机的LVS可支持几十万个并发的请求转发;
- F5是一种负载均衡硬件,与LVS提供的能力类似,性能比LVS更高,但价格昂贵。
由于LVS是单机版的软件,若LVS所在服务器宕机则会导致整个后端系统都无法访问,因此需要有备用节点。可使用keepalived软件模拟出虚拟IP,然后把虚拟IP绑定到多台LVS服务器上,浏览器访问虚拟IP时,会被路由器重定向到真实的LVS服务器,当主LVS服务器宕机时,keepalived软件会自动更新路由器中的路由表,把虚拟IP重定向到另外一台正常的LVS服务器,从而达到LVS服务器高可用的效果。 此处需要注意的是,上图中从Nginx层到Tomcat层这样画并不代表全部Nginx都转发请求到全部的Tomcat,在实际使用时,可能会是几个Nginx下面接一部分的Tomcat,这些Nginx之间通过keepalived实现高可用,其他的Nginx接另外的Tomcat,这样接入的Tomcat数量就能成倍的增加。 由于LVS也是单机的,随着并发数量增长到几十万时,LVS服务器最终会达到性能瓶颈,此时用户数量达到千万甚至上亿级别,用户分布在不同的地区,与服务器机房距离不同,导致了访问的延迟会明显不同。
在DNS服务器中可配置一个域名对应多个IP地址,每个IP地址对应到不同的机房里的虚拟IP。当用户访问www.taobao.com时,DNS服务器会使用轮询策略或其他策略,来选择某个IP供用户访问。此方式能实现机房间的负载均衡。至此,系统可做到机房级别的水平扩展,千万级到亿级的并发量都可通过增加机房来解决,系统入口处的请求并发量不再是问 题。 随着数据的丰富程度和业务的发展,检索、分析等需求越来越丰富,单单依靠数据库无法解决如此丰富的需求。
当数据库中的数据多到一定规模的时候,数据库就不适用于复杂查询了,往往只能满足普通查询的场景。对于统计报表的场景,在数据量大时不一定能跑出结果,而且在跑复杂查询时会导致其他查询变慢,对于全文检索、可变数据结构等场景,数据库天生不适用。因此需要针对特定的场景,引入合适的解决方案。
如对于海量文件的存储,可以通过分布式文件系统HDFS解决;对于KEY-VALUE类型的数据,可以通过HBase和Redis等方案解决;对于全文检索场景,可以通过搜索引擎,比如ElasticSearch解决;对于多维分析场景,可以通过Kylin或Druid等方案解决。
当然,引入更多的组件同时会提高系统的复杂度,不同的组件保存的数据需要同步,需要考虑数据一致性的问题,需要有更多的运维手段来管理这些组件等。 搜索引擎最大的优点是它能够大大提高查询速度。 引入搜索引擎后也会带来以下的开销:
- 带来大量的维护工作,我们需要自己实现索引的构建过程,设计全量/增加的构建方式来应对非实时与实时的查询需求。
- 发需要维护搜索引擎集群
搜索引擎并不能替代数据库,他解决了某些场景下的“读”的问题,是否引入搜索引擎,需要综合考虑整个系统的需求。
引入更多组件解决了丰富的需求,业务维度能够极大扩充,但是随之而来的是一个应用中包含了太多的业务代码,业务的升 级迭代变得困难。
第十次演进:大应用拆分为小应用随着业务的发展,业务越来越多,应用越来越大。我们需要考虑如何避免让应用越来越臃肿。这就需要把应用拆开,从一个应用变为俩个甚至更多。比如,我们可以把用户、商品、交易拆分开。变成“用户、商品”和“用户,交易”两个子系统。 按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代。这时候应用之间可能会涉及到一些公共配置,可以通过分布式配置中心Zookeeper来解决。
- 问题: 这样拆分后,可能会有一些相同的代码,如用户相关的代码,商品和交易都需要用户信息,所以在两个系统中都保留差不多的操作用户信息的代码。如何保证这些代码可以复用是一个需要解决的问题。
- 解决问题: 通过走服务化(微服务)的路线来解决
但是不同的应用之间可能存在共用的模块,由应用单独管理会导致相同的代码存在多份,导致公共功能在升级时全部应用代码都要跟着升级。
第十一次演进:重复的功能抽离成微服务如用户管理、订单、支付、鉴权等功能在多个应用中都存在,那么可以把这些功能的代码单独抽取出来形成一个单独的服务来管理,这样的服务就是所谓的微服务。应用和服务之间通过HTTP、TCP或RPC请求等多种方式来访问公共服务,每个单独的服务都可以由单独的团队来管理。此外,可以通过Dubbo、SpringCloud等框架实现服务治理、限流、熔断、降级等功能,提高服务的稳定性和可用性。
- 优点:
- 相同的代码不会散落在不同的应用中了,这些实现放在了各个服务中心,使代码得到更好的维护。
- 我们把对数据库的交互放在了各个服务中心,让”前端“的web应用更注重与浏览器交互的工作。
- 问题: 如何进行远程的服务调用
- 解决方法: 我们可以通过下面的引入消息中间件来解决。开源消息中间件有阿里的dubbo,可以搭配Google开源的分布式程序协调服务zookeeper实现服务器的注册与发现。
但是由于不同服务的接口访问方式不同,应用代码需要适配多种访问方式才能使用服务。此外,应用访问服务,服务之间也可能相互访问,调用链将会变得非常复杂,逻辑变得混乱。
第十二次演进:引入企业服务总线ESB屏蔽服务接口的访问差异通过ESB统一进行访问协议转换,应用统一通过ESB来访问后端服务,服务与服务之间也通过ESB来相互调用,以此降低系统的耦合程度。这种单个应用拆分为多个应用,公共服务单独抽取出来来管理,并使用企业消息总线来解除服务之间耦合问题的架构,就是所谓的SOA
(面向服务)架构。 SAO架构与微服务架构容易混淆,因为表现形式十分相似。个人理解,微服务架构更多是指把系统里的公共服务抽取出来单独运维管理的;而SOA架构则是指一种拆分服务并使服务接口访问变得统一的架构思想,SOA架构中包含了微服务的思想。 随着业务不断发展,应用和服务都会不断变多,应用和服务的部署变得复杂,同一台服务器上部署多个服务还要解决运行环境冲突的问题。此外,对于如大促这类需要动态扩缩容的场景,需要水平扩展服务的场景,就需要在新增的服务器上准备运行环境,部署服务等,运维将变得十分困难。
目前最流行的容器化技术是Docker,最流行的容器管理服务是Kubernetes(K8S),应用/服务可以打包为Docker镜像,通过K8S来动态分发和部署镜像。 Docker镜像可理解为一个能运行你的应用/服务的最小的操作系统,里面放着应用/服务的运行代码,运行环境根据实际的需要设置好。把整个“操作系统”打包为一个镜像后,就可以分发到需要部署相关服务的机器上,直接启动Docker镜像就可以把服务启起来,使服务的部署和运维变得简单。 在大促的之前,可以在现有的机器集群上划分出服务器来启动Docker镜像,增强服务的性能,大促过后就可以关闭镜像,对机器上的其他服务不造成影响(在之前,服务运行在新增机器上需要修改系统配置来适配服务,这会导致机器上其他服务需要的运行环境被破坏)。 虽然使用容器化技术后服务动态扩缩问题得以解决,但是机器还是需要公司自身来管理,在非大促的时候,还是需要闲置着大量的机器资源来应对大促,机器自身成本和运维成本都极高,资源利用率低。
系统可部署到公有云上,利用公有云的海量机器资源,解决动态硬件资源的问题,在大促的时间段里,在云平台中临时申请更多的资源,结合Docker和K8S来快速部署服务,在大促结束后释放资源,真正做到按需付费,资源利用率大大提高,同时大大降低了运维成本。 所谓的云平台,就是把海量机器资源,通过统一的资源管理,抽象为一个资源整体,在之上可按需动态申请硬件资源(如CPU、内存、网络等),并且之上提供通用的操作系统,提供常用的技术组件(如Hadoop技术栈,MPP数据库等)供用户使用,甚至提供开发好的应用,用户不需要关心应用内部使用了什么技术,就能够解决需求(如音视频转码服务、邮件服务、个人博客等)。 在云平台中会涉及如下几个概念:
- IaaS:基础设施即服务。对应于上面所说的机器资源统一为资源整体,可动态申请硬件资源的层面。
- PaaS:平台即服务。对应于上面所说的提供常用的技术组件,方便系统的开发与维护。
- SaaS:软件即服务。对应于上面所说的提供开发好的应用或服务,按功能或性能要求付费。
至此,上面所提到的从高并发访问问题,到服务的架构和系统实施的层面都有了各自的解决方案。但是同时也应该意识到,在上面的介绍中,其实是有意地忽略了诸如跨机房数据同步、分布式事务实现等等的实际问题,这些问题的解决方案可以去查找相关的资料去了解。
架构设计的总结 架构的调整是否必须按照上述演变路径进行?不是的,以上所说的架构演变顺序只是针对某个侧面进行单独的改进,在实际场景中,可能同一时间会有几个问题需要解决,或者可能先达到瓶颈的是另外的方面,这时候就应该按照实际问题实际解决。比如在政府类的服务,并发量不大但业务可能很丰富的场景,高并发就不是重点解决的问题,此时优先需要的可能会是丰富需求的解决方案。
对于将要实施的系统,架构应该设计到什么程度?对于单次实施并且性能指标明确的系统,架构设计到能够支持系统的性能指标要求就足够了,但是,要留有扩展架构的接口以备不时之需。 对于不断发展的系统,比如电商平台,就应该设计到能满足下一阶段用户量和性能指标要求的程度,并根据业务的增长不断地迭代升级架构,以支持更高的并发量和更丰富的业务。
服务端架构和大数据架构有什么区别?所谓的大数据,其实是海量数据采集、清洗转换、数据存储、数据分析、数据服务等场景解决方案的一个统称。在每一个场景都包含了多种可选的技术,比如:数据采集有Flume、Sqoop、Kettle等,数据存储有分布式文件系统HDFS、FastDFS、NoSQL数据库HBase、MongoDB等,数据分析有Spark技术栈、机器学习算法等。 总的来说大数据架构就是根据业务的需求,整合各种大数据组件组合而成的架构,一般会提供分布式存储、分布式计算、多维分析、数据仓库、机器学习算法等能力。而服务端架构则更多指的是应用组织层面上的架构,底层能力往往是由大数据架构来提供的。
有没有一些架构设计的原则?- N+1设计:系统中的每个组件都应做到没有单点故障。
- 回滚设计:确保系统可以向前兼容,在系统升级时应能有办法回滚版本。
- 禁用设计:应该提供控制具体功能是否可用的配置,在系统出现故障的情况下能够快速下线功能。
- 监控设计:在设计阶段就要考虑监控的手段。
- 多活数据中心设计:若系统需要极高的高可用,应考虑在多地实施数据中心进行多活,至少在一个机房断电的情况下系统依然可用。
- 采用成熟的技术:刚开发的或开源的技术往往存在很多隐藏的Bug,出了问题没有商业支持可能会是一个灾难。
- 资源隔离设计:避免单一业务占用全部资源。
- 架构应提供水平扩展的能力:系统只有做到能水平扩展,才能有效避免性能瓶颈。
- 非核心则购买:如果非核心功能的开发需要占用大量的研发资源才能解决,应考虑购买成熟的产品。
- 使用商用硬件:商用硬件能有效降低硬件故障的几率。
- 快速迭代:系统应该快速开发小功能模块,尽快上线进行验证,早日发现问题,大大降低系统交付的风险。
- 无状态设计:服务接口应该做成无状态的,当前接口的访问不依赖接口上次访问的状态。