🐼前言
我们来想一想关于服务器的高并发问题,如果我们做一个服务器,假如我们自已实现了一台服务器,部署到了我的机器上,发现,如果访问量是1w以内,机器还勉强能跑,但是如果访问量再多,机器就宕机了,思考一下,我们的服务器和像腾讯,百度,这样的大厂能够完成功能的服务器,最本质的区别是什么?解决人的问题,你能够被一个人访问你的服务器,完成功能,这不值得骄傲,但是如果你的服务器能够服务几千万乃至上亿的用户,并且保定稳定性,又谈何容易。下面我们基于以⼀个 "电⼦商务" 应⽤为例,来谈谈从⼀百个到千万级并发情况下服务端的架构的演进过程。
🐼单机架构
假设我们现在只有一台服务器,我们是一个小公司。这个服务器要负责所有的工作,其中要实现的业务就是电子商务,包括三个核心模块:用户,商品,交易,以及对应的数据库服务,当用户访问时商品服务时,假设我们的引用服务是使用cpp-httplib写的服务器,就会执行到商品的模块,然后根据需求,向数据库服务进行读/写,并且我们的应用服务和数据库服务都是在一台主机上的,如图:

这种做法带来的问题:由于我们的硬件资源有限,比如CPU,内存,网卡,硬盘...,当用户量一上来,也就是每收到一个请求,都会消耗这些资源的一小部分,如果同一时刻,请求量多了,就可能导致硬件资源不足导致服务器处理的请求时间变长,甚至不够用了~
不过其实,这种单机架构对于并发量不是很大,绝大多数公司,都使用的这种架构模式,因为现在的计算机硬件,已经非常成熟了,即使是一台计算机,也能支持挺高的高并发的~
我们继续来说,如果我们真的遇到这种服务器不够用的场景,需要怎么处理?
1.开源:也就是简单粗暴的增加更多的硬件资源,比如再多搞几台计算机行不行。
2.节流:也就是软件上的优化,比如你这个程序是不是哪里没做好,数据结构没有用对?哪里访问耗时,这都需要性能测试工具帮我们测出来,然后再进行对症下药
假设我们使用开源,那么一旦引入多台主机,我们的系统,就可谓称的上是分布式了!
🐼分布式带来的问题
有老的程序员会说,能用单机解决的问题,那么你就不要搞分布式。为啥嘞?你想想,当你的主机数量增加,是不是意味着你维护的成本变高了,系统的复杂度就会大大提高了,那么你的时间不就浪费啦?还有你的年终奖。哈哈~所以,引入分布式系统往往是无奈之举~
🐼应用数据分离架构
随着用户量的增加,我们面临到了性能压力。由于预算很紧张,我们发现,基于上面的架构,我们的应用服务很吃CPU和内存,里面有很多处理业务的功能。数据库服务,要更多的存储空间,更快的访问速度,这就很吃硬盘。于是我们将应用服务和数据库服务分开,应用服务我们就多一些CPU和内存,数据库服务我们就可以配置更大的硬盘,甚至是SSD硬盘。这样自由DIY,达到更大的性价比~

和之前架构的主要区别在于将数据库服务独⽴部署在同⼀个数据中心的其他服务器上,应⽤服务通过网络来访问数据。
🐼应用服务集群架构
现在随着我们的用户量再次增多,这种应用数据分离架构已经远远跟不上我们的用户请求了。
我们现在有两种方案:
垂直扩展 / 纵向扩展 Scale Up。通过购买性能更优、价格更⾼的应⽤服务器来应对更多的流量。这种⽅案的优势在于完全不需要对系统软件做任何的调整;但劣势也很明显:硬件性能和价格的增长关系是非线性的,意味着选择性能 2 倍的硬件可能需要花费超过 4 倍的价格,其次硬件性能提升是有明显上限的。
水平扩展 / 横向扩展 Scale Out。通过调整软件架构,增加应⽤层硬件,将用户流量分担到不同的应用层服务器上,来提升系统的承载能⼒。这种⽅案的优势在于成本相对较低,并且提升的上限空间也很大。但劣势是带给系统更多的复杂性,需要技术团队有更丰富的经验。
这就需要引入⼀个新的组件 ⸺ 负载均衡:为了解决用户流量向哪台应用服务器分发的问题,需要⼀个专门的系统组件做流量分发。负载均衡就像一个公司的领导一样,负责把任务分配给组员 ,
关于一些负载均衡的常见算法:
Round-Robin 轮询算法。即非常公平地将请求依次分给不同的应⽤服务器,1,2,3,4,5....1,2,3,4,5这样分配
Weight-Round-Robin 轮询算法。为不同的服务器(比如性能不同)赋予不同的权重(weight),
能者多劳。或者计算最小的负载值,然后分配给它
⼀致哈希散列算法。这种适用于一个客户的多次请求,通过计算用户的特征值(比如 IP 地址)得到哈希值,根据哈希结果做分发,优点是确保来自相同用户的请求总是被分给指定的服务器。也就是我们平时遇到的专项客户经理服务。
一些负载均衡器的软件:Nginx、HAProxy、LVS、F5 等
因此,这次我们多增加了几台应用服务器节点,并且有一个负载均衡服务器来分配调度
这样用户的请求,会首先到达负载均衡服务器/网关服务器。假设现在有一万个请求,那么有两个服务器,就可以按照负载均衡的方式,让每个服务器承担5000,这有点像我们的"多线程",只不过多线程的目的是为了最大程度的利用我们的资源,这里和多线程类似,就是平摊总任务的~
可是现在有个问题,就是负载均衡器,看起来不就承担了所有请求,那么它能抗的住吗?其实负载均衡器所要完成的任务,是远远小于应用服务器的,因为它只负责派发任务嘛~具体的任务还得应用服务器来做~如果真的遇到了负载均衡器扛不住了,那么只能引入更多的负载均衡器,多个机房咯~
🐼读写分离 / 主从分离架构
随着我们上面的讨论,增加应用服务器,确实能够处理更多的请求了,可是别忘了,你这些应用服务器最后都是要访问数据库的啊,要是存储服务器扛不住了,罢工了怎么办?开源或者节流嘛!
那我们能不能按照上面引入多台应用服务器的方式,来引入多台存储服务器呢?答案是否定的,因为数据库服务有其特殊性:如果将数据分散到各台服务器之后,数据的⼀致性将⽆法得到保障,不能你这边写,它那边也写吧,同一张票卖出去两份的例子,这就是麻烦的事情。
正确的做法是,保留⼀个主要的数据库作为写⼊数据库,其他的数据库作为从属数据库。从库的所有数据全部来⾃主库的数据,经过同步后,从库可以维护着与主库⼀致的数据。这样就完成了读写分离,从服务器可以有很多个。
这样我们就分担数据库的压⼒,我们可以将写数据请求全部交给主库处理,但读请求分散到各个从库中。可是主服务器抗的住吗?如果只有一台写服务器。由于⼤部分的系统中,读写请求都是不成⽐例的,例如 100 次读 1 次写,读的频率是要比写高的!如图:

🐼引入缓存 ⸺ 冷热分离架构
数据库天然有个问题,就是访问速度太慢了!而如果我们能够将高频率被访问的数据加载到内存中,那么访问速度就大大提高了。我们将高频率被访问的数据称为热点数据,低频率被访问的数据称为冷数据,通过冷热数据区分,把热点数据放到缓存中,增加访问速度。redis就是做这个的,用于分布式缓存。
回答个问题:
✅你不是要提高效率吗?那为什么我们不把所有的数据都放到缓存中呢?为什么仅仅是放少量数据?
因为你放到缓存中这需要很大的成本,没钱啊。缓存的快,就要付出代价,那么就是缓存很小!
相比小伙伴们听过2 8 原则,就是20%的人掌握了这世界80%的财富。放到这里也是如此,20%的数据能够提供80%的访问量。
所以我们借助分布式缓存服务器来提高MySQL访问速度。如果你再缓存服务器没读到,那么再去存储服务器去读吧!所以缓存服务器就是为存储服务器附中前行的,减少了存储服务器的访问。

🐼垂直分库分表
引入分布式系统,不仅要面对更高的请求量,并发量,同时也要面对更多的数据量。
有没有一种可能就是一台服务器存储不下数据了呢?当然可能!尽管有的服务器的存储资源高达10T以上,但是还是架不住比如短视频等这些大文件,占空间的资源。
如果一台主机存不下,那么我们只好分多台主机存储咯。我们可以针对数据库进行进一步的拆分,分库分表。本来一个主机上有多个database,现在我们给每一个主机一个database,这样,每个数据库存储服务器存储了一个或者数据库的一部分,如果表太大放不下,那么我们也可以进行分表。如图:

🐼业务拆分 ⸺ 微服务
之前的应用服务器,一个服务器里面可能做了很多复杂的业务,这就导致一个服务器的程序变得很复杂,不管是服务器还是我们的程序,我们始终要做到解耦,为了更方便的维护,我们将一个复杂的服务器,拆分为功能更单一,数据更集中 ,更多,更小的服务,这个我们就称为微服务!
这不就是低耦合,高内聚的思想嘛!微服务,导致了服务器的种类和数量增加了,如图:

不过,有没有考虑到这样一个问题,就是我们把整个应用服务拆分为多个微服务,此时微服务和微服务之间是不是通过网络来通信的,通过网络通信,不是慢嘛!确实,网络的速率有可能还不如硬盘的速率,不过,随着,现在万兆网卡的出现,网卡的速率已经比SSD固定硬盘的速率还要优秀了。
再回答一个问题:通过微服务,其实也变相解决了人的问题,举个例子:将整个应用服务分解为了多个微服务,应用服务就是我们这个公司的业务,而每个微服务其实就是一个部门所负责的服务,
通过划分组织结构,划分为多个组,进行分工,根据不同的业务需求来划分出不同的人,所以,微服务,可以帮助我们解决人的问题。而我们所做的所有技术,其实都在围绕业务!
微服务的优势:
解决了"人"的问题
便于功能的复用,因为我们已经将一个大业务拆分开来了需要小业务,便于其他小业务的直接调用。
可以给不同的服务进行不同的部署。甚⾄可以把⼀些类似用户管理、安全管理、数据采集等业务提成公共服务。
总之,如果业务遇到了问题,我们就去解决问题,从开源和节流入手,进行自由DIY操作。如果把我们上述所有架构DIY到一起,如图:

上述这个演化过程,仅仅是一个粗略的过程。对于具体的公司,可能演化为不同的架构,具体是什么,是跟业务密切相关的,而上述的所有技术,仅仅是为业务服务的。
而我们所引入的分布式结构,其实归根结底的本质就是为了引入更多的硬件资源,同时也带来了挑战 。