高并发系统分布式服务方案

服务拆分

一体化架构的痛点

一体化架构的一些缺陷,这主要体现在以下几个方面:

  • 在技术层面上,数据库连接数可能成为系统的瓶颈:

    因为你的系统是按照一体化架构部署的,在部署结构上没有分层,应用服务器直接连接数据库,那么当前端请求量增加,部署的应用服务器扩容,数据库的连接数也会大增。

  • 一体化架构增加了研发的成本抑制了研发效率的提升:

    当如此多的小团队共同维护一套代码和一个系统时,在配合的过程中就会出现问题,例如交流、代码冲突等问题。

  • 对于系统的运维也会有很大的影响:

    当你的系统扩充到几十万行甚至上百万行代码的时候,一次构建的过程包括编译、单元测试、打包和上传到正式环境,花费的时间可能达到十几分钟,并且任何小的修改,都需要构建整个项目,上线变更的过程非常不灵活。

微服务化

例如最开始的社区业务系统,对数据库进行了垂直分库分为用户库、内容库和互动库。但是由于系统是一体化的,每个模块都会直接与数据库相连接。

image-20230507152009593

其实可以把各个模块的逻辑部署成一个单独的服务,这样就可以直接服务之间相互调用。(也可以将公共服务拆分为单独的服务,增加重用性)

image-20230507152017332

服务拆分原则:

  • 做到单一服务内部功能的高内聚和低耦合。
  • 你需要关注服务拆分的粒度,先粗略拆分再逐渐细化。
  • 拆分的过程,要尽量避免影响产品的日常功能迭代。
  • 服务接口的定义要具备可扩展性,例如参数个数问题最好用封装类。

微服务化的问题以及解决方案

微服务化之后,原有单一系统被拆分成多个子服务,无论在开发还是运维上都会引入额外的问题。

  1. 服务调用问题:

    服务接口的调用不再是同一进程内的方法调用而是跨进程的网络调用,这会增加接口响应时间的增加。此时我们就要选择高效的服务调用框架,同时接口调用方需要知道服务部署在哪些机器的哪个端口上,这些信息需要存储在一个分布式一致性的存储中,于是就需要引入服务注册中心以及RPC框架

    远程调用的要点:

    • 选择高性能的 I/O 模型,这里我推荐使用同步多路 I/O 复用模型;
    • 调试网络参数,这里面有一些经验值的推荐。比如将 tcp_nodelay 设置为 true,也有一些参数需要在运行中来调试,比如接受缓冲区和发送缓冲区的大小,客户端连接请求缓冲队列的大小(back log)等等;
    • 序列化协议依据具体业务来选择。如果对性能要求不高可以选择 JSON,否则可以从 Thrift 和 Protobuf 中选择其一。

    服务注册中心要点:

    • 注册中心可以让我们动态地变更 RPC 服务的节点信息,对于动态扩缩容,故障快速恢复,以及服务的优雅关闭都有重要的意义;
    • 心跳机制是一种常见的探测服务状态的方式,你在实际的项目中也可以考虑使用;
    • 我们需要对注册中心中管理的节点提供一些保护策略,避免节点被过度摘除导致的服务不可用
  2. 服务治理问题:

    多个服务之间有着错综复杂的依赖关系。一个服务会依赖多个其它服务也会被多个服务所依赖,那么一旦被依赖的服务的性能出现问题产生大量的慢请求,就会导致依赖服务的工作线程池中的线程被占满,依赖的服务也会出现性能问题。

    为了避免发生这种情况,我们需要引入服务治理体系针对出问题的服务采用熔断、降级、限流、超时控制的方法,使问题被限制在单一服务中,保护服务网络中的其它服务不受影响。

  3. 服务监控问题:

    整体系统一旦出现故障,很可能外在的表现是所有服务在同一时间都出现了问题,你在问题定位时很难确认哪一个服务是源头,这就需要引入分布式追踪工具,以及更细致的服务端监控报表。

    一个接口响应时间慢,一般是出在跨网络的调用上,比如说请求数据库、缓存或者依赖的第三方服务。

负载均衡问题

在微服务架构中我们也会启动多个服务节点承接从用户端到应用服务器的请求,自然会需要一个负载均衡服务器作为流量的入口,实现流量的分发。

负载均衡服务器的种类

负载均衡服务大体上可以分为两大类:

  1. 一类是代理类的负载均衡服务;
  2. 另一类是客户端负载均衡服务。

代理类的负载均衡服务

代理类的负载均衡服务以单独的服务方式部署,所有的请求都要先经过负载均衡服务,在负载均衡服务中选出一个合适的服务节点后,再由负载均衡服务调用这个服务节点来实现流量的分发。

image-20230511112508717

比较著名的实现有 LVS、Nginx:

一般会同时部署 LVS 和 Nginx 来做 HTTP 应用服务的负载均衡。在入口处部署 LVS 将流量分发到多个 Nginx 服务器上,再由 Nginx 服务器分发到应用服务器上

  • LVS:

    LVS 在 OSI 网络模型中的第四层,传输层工作,所以 LVS 又可以称为四层负载;LVS 是在网络栈的四层做请求包的转发,请求包转发之后,由客户端和后端服务直接建立连接,后续的响应包不会再经过 LVS 服务器,能够承担更高的并发。

  • Nginx:

    Nginx 运行在 OSI 网络模型中的第七层,应用层,所以又可以称它为七层负载;Nginx 虽然比 LVS 的性能差很多,但也可以承担每秒几万次的请求,并且它在配置上更加灵活,还可以感知后端服务是否出现问题

不过这两个负载均衡服务适用于普通的 Web 服务,对于微服务架构来说,它们是不合适的。因为微服务架构中的服务节点存储在注册中心里,使用 LVS 就很难和注册中心交互获取全量的服务节点列表。

所以,我们会使用另一类的负载均衡服务,客户端负载均衡服务,也就是把负载均衡的服务内嵌在 RPC 客户端中。

客户端负载均衡服务

它一般和客户端应用部署在一个进程中,提供多种选择节点的策略,最终为客户端应用提供一个最佳的、可用的服务端节点。这类服务一般会结合注册中心来使用,注册中心提供服务节点的完整列表,客户端拿到列表之后使用负载均衡服务的策略选取一个合适的节点,然后将请求发到这个节点上。

image-20230511113004856

常见的负载均衡策略

  • 静态策略:也就是说负载均衡服务器在选择服务节点时,不会参考后端服务的实际运行的状态;
    • 轮询的策略(RoundRobin,RR):按照顺序依次轮询
    • 带有权重的轮询策略:在轮询的过程中考虑权重大小
    • Nginx 提供了 ip_hash 和 url_hash 算法;
    • LVS 提供了按照请求的源地址和目的地址做 Hash 的策略;
    • Dubbo 也提供了随机选取策略以及一致性 Hash 的策略。
  • 动态策略:也就是说负载均衡服务器会依据后端服务的一些负载特性,来决定要选择哪一个服务节点。
    • Dubbo 提供的 LeastAcive 策略,就是优先选择活跃连接数最少的服务;
    • Spring Cloud 全家桶中的 Ribbon 提供了 WeightedResponseTimeRule 是使用响应时间给每个服务节点计算一个权重,然后依据这个权重,来给调用方分配服务节点。

在实际开发中,优先考虑使用动态的策略。

节点故障问题

对于微服务化架构来说,服务节点会定期地向注册中心发送心跳包,这样注册中心就能够知晓服务节点是否故障,也就可以确认传递给负载均衡服务的节点一定是可用的

对于 Nginx 来说,就需要用到开源的 Nginx 模块nginx_upstream_check_module了,这个模块可以让 Nginx 定期地探测后端服务的一个指定的接口,然后根据返回的状态码来判断服务是否还存活。

配置如下:

1
2
3
4
5
6
7
upstream server {
server 192.168.1.1:8080;
server 192.168.1.2:8080;
check interval=3000 rise=2 fall=5 timeout=1000 type=http default_down=true;//检测间隔为3秒,检测超时时间是1秒,使用http协议。如果连续失败次数达到5次就认为服务不可用;如果连续成功次数达到2次,则认为服务可用。后端服务刚启动时状态是不可用的
check_http_send "GET /health_check HTTP/1.0\r\n\r\n"; //检测URL
check_http_expect_alive http_2xx; //检测返回状态码为200时认为检测成功
}

Nginx 按照上面的方式配置之后,你的业务服务器也要实现一个“/health_check”的接口,在这个接口中返回的 HTTP 状态码,这个返回的状态码可以存储在配置中心中,这样在变更状态码时,就不需要重启服务了。