所谓单体架构,就是只有一台服务器,所有的系统、程序、服务、应用都安装在这一台服务器上。比如一个 bbs 系统,它用到的数据库,它需要存储的图片和文件等,统统都部署在同一台服务器上。
单体架构的好处就是简单,相对便宜。一般在互联网早期、或创业型团队,都有经历过单体架构。通过配置升级来应对并发量过高的情况,比如把 CPU/内存升配成 64 核、128G内存。
当服务器负载过高时,拆分和单独部署应用服务器和数据库服务器。
当网站积累的内容多了,另外用户和访问量也越来越大了。每天的 UV 过万,PV 过十万时,服务器的负载会在单体架构下变得越来越高。
当网站的访问量越来越大,顶配服务器能支撑的并发量也是有极限的。这时候,就要对架构进行升级了,最简单的方式,就是从单体架构升级为垂直架构:把应用和数据库分开来部署。
网站有源源不断的访问,数据库的压力越来越大,经常出现拥堵和慢查询。此时可以考虑增加数据库的服务器数量。
比较常见的是使用数据库主从模式,可以是一主一从或者一主多从。一主一从就是一个主库加上一个从库,使用两台服务器。
主库负责全部的数据写入请求,从库只能用来查询,分散主库的查询压力。类似的一主多从,就是增加多个从库,这样通过增加从库的服务器资源,来提高查询的性能。
注意:
存在的问题:数据库主从同步会有延时:一条数据成功写入了主库,但是马上读取从库,因为同步延时,这条数据还没有从主库同步到从库,于是查询无结果。
这时便需要对从库延时进行监控,以 MySQL 为例,在从库上执行 show slave status;
语句查看从库的状态,Slave_IO_Running
和Slave_SQL_Running
都需要是 Yes。Seconds_Behind_Master
的延迟程度,数值越大说明延时越长。除了时刻关注从库的延时情况,在程序方面,也需要有一些策略性的调整。比如不要把全部查询都改为读从库,优先把对时间更新不敏感的数据改为读从库。
系统的并发量也同时增加,单台应用服务器的负载太高,考虑对服务器进行扩容,从一台服务器水平扩容成多个应用服务器。
把应用系统在每台服务器上都部署一套,然后再部署一个负载均衡服务器,比如 Nginx 来作为统一的接入层。
在分布式系统中,依赖于本地的文件存在一个问题:上传的图片和文件只保存在本地的文件系统上,其他的服务器上并没有这些文件,所以就会出现 404。比较简单的方法,就是在每一台应用服务器上都挂载同一个 nfs,并且配置一样的目录。这种方式,文件还是集中式的读写,只是通过网络来进行读写,不再是本地的IO了。另外也可搭建额外的 OSS 集群,同时引入 CDN 优化等。
在分布式架构下,正常网站的支撑基本达到 20-30万的 PV。
数据库的压力依然很大,慢查询越来越多,到达瓶颈了,此时便引入分布式缓存服务,解放数据库。
在这个阶段,数据库面对庞大并发压力,再对数据库服务器通过水平、垂直扩容的收益已经不大了。可引入分布式缓存服务。比如:使用Redis集群。
一般认为,MySQL 服务单机可以支持 1000 的并发,Redis 服务单机可以支持10万的并发。利用缓存,可以把一些耗时几百毫秒的慢查询的结果缓存起来,缓存查询耗时只有1-2ms,性能提升了上百倍。
这便有新存在一些问题,比如:数据库与缓存的数据一致性问题(内容较多,此处省略2万字)。终于,在这种系统架构下,已经完全可以应对百万用户,每天几百万的 PV 的情况了。
当数据规模越来越大,数据量飞速增长,超过千万规模,只要涉及到数据库的查询、批量更新等操作时,又会变得很慢,再次成为整个系统的瓶颈。针对数据规模的不断增长,需要再一次升级数据库架构,考虑对数据库及数据表做水平拆分和垂直拆分。
单表的数据量太大,考虑对数据表进行分表,比如让单张表的数据规模控制在百万的规模。
数据库的分库方法跟分表的方法类似,同样是考虑用水平拆分或者垂直拆分。
当应用越来越多,越来越复杂时,考虑将应用拆分为多个子服务,需要注意垂直拆分的服务边界。
对一个大的应用系统进行垂直拆分,而服务拆分的依据就是各个服务的数据尽量独立。比如:在博客系统中,可以考虑把文章系统、评论系统、图片文件系统、等进行拆分。
早期的 SOA架构,即面向服务架构,也是把一个大的系统进行很多的独立的服务化拆分。微服务架构其实和 SOA 架构类似,是在 SOA 上做的升华。微服务架构重点强调的一个是"业务需要彻底的组件化和服务化",原有的单个业务系统会拆分为多个可以独立开发、设计、运行的小应用。这样的小应用和其他各个应用之间,相互去协作通信,来完成一个交互和集成。
微服务太多,管理难度太大。通过 K8S 等技术,可以轻松管理成千上万的微服务,管理上百万核的服务器资源。同时可以让开发、测试、发布的周期变得更短。
云原生将进一步帮助我们提高开发、运维效率和灵活性,提高系统和产品的可用性。不再受到资源和地域的限制,可以快速在全球几十个地区把服务部署、运行起来。
系统中用到的 MySQL 集群、Redis集群、分布式存储、消息队列、Elasticsearch、Prometheus、分布式日志系统、实时计算服务等。
要管理和维护这么多的大型分布式系统,既要优化提高服务的性能,又要保证系统的高可靠可用性,这里需要投入的人力资源和服务器资源,都会是非常巨大的。如果对于云原生的资源管控不好,成本方面控制不好,可以选择混合云的方式。
服务发现是微服务架构的灵魂,就是把服务的信息注册到一个配置中心,而且从配置中心,可以找到服务的更多信息。这里的配置中心,也叫做注册中心,管理着所有的服务信息。注册中心是所有微服务创建、启动、运行、销毁、互相调用都绕不过去的中心服务。
注册中心的功能
服务注册
。服务启动的时候,把服务名称和服务的访问方法等信息注册到注册中心。
服务查询
。通过服务名称查找到调用服务的访问方式。健康检查
。让注册中心随时掌握服务的运行状况。当服务的实例出现异常时,进行告警、服务自动重启等。注册中心本身还是一个分布式的存储系统,作为一个配置中心,除了要保存服务信息,可能还会有各个微服务的自定义配置信息。对于注册中心来说,最大的挑战是系统的可靠性和数据的一致性。所以,所有的注册中心都是部署在多台服务器上。而且,每台机器上都会保存一份完整的配置信息。并且,多台服务器之间的数据同步,为了保证数据的强一致性,需要实现一些复杂的一致性协议。
服务发现过程:
一些服务发现的实现方案:Apache Zookeeper、Etcd、Consul。
为应对线上突发情况,如某个目标服务出现 Bug,性能和并发能力急速下降,需要采取各种措施来保证系统的可用性。
调用方主动中断请求的连接。在调用 API 的时候,一般会根据目标服务预估的接口延时来配置这个 TCP 超时时间。如 API 大部分情况都是100ms 以内就可以返回,那么,就可以把 TCP 请求超时设置为 500ms 或者1s。如果不设置超时,那么当目标服务出现异常,无法正常的快速响应时,那源服务与目标服务的所有请求就会无限的等待,TCP 连接数也就会越来越多,对于系统的资源开销也就越来
越大。
限制请求的最大并发数。如目标服务最大可以支持 100QPS,那就不能让源服务超过这个并发请求进入。
常用算法:
暂时把调用目标服务的请求快速返回,不允许再重复调用目标服务。因为当目标服务在某段时间内出现大量的异常情况,这时候如果继续请求目标服务,大概率也还是返回异常结果。
熔断只是暂时停止调用,过一段时间后又会放一部分请求进来,看看后端服务是否恢复正常了。如一分钟内,服务的超时请求、500、503 等响应超过 100 次时,触发降级或者熔断。降级或者熔断 3 分钟后,尝试连续 10 次请求到后端服务,如果都正常返回了,则恢复。如果没有全部正常返回,则继续保持降级或者熔断状态,下一个 3 分钟后再来尝试恢复。
类似熔断的场景。目标服务出现大量异常,这时候的响应不要像熔断那么简单粗暴,而是要有更加友好的响应。主要时为了当服务异常时,又想让服务看上去是正常的,有正常的返回。
降级常用的方法:
按种类隔离/用户隔离等方式隔离不同的依赖调用,避免服务之间相互影响。如源服务 A/B 都直接调用目标服务 C。源服务 A 出现突发的高并发来请求目标服务 C。导致目标服务 C 出现拥堵,那么,这时候也就会影响源服务 B 的请求。
对服务进行管理、监控和控制,以确保其满足业务需求和合规要求。
服务治理包括以下方面:
简单来讲,负载均衡的作用就是把所有请求合理的分配到每一个服务实例上,避免某个服务实例一直处于负载过高的情况。
另外还有应用一致性哈希算法的负载均衡策略等。
负载均衡的实现方案,常见的一般在客户端、DNS、网关,另外也有在各种中间件组件处,如数据库代理,缓存代理等。