微服务的架构原理(随堂笔记)|青训营

89 阅读11分钟

微服务架构原理及原理实践

架构及介绍

由于需求复杂性的多样化,在互联网发展和硬件设施发展,开发人员的急剧增加,计算机理论的急速发展所诞生的一种新型架构。

架构一览:

Screenshot 2023-08-21 194259.png

组成:

Screenshot 2023-08-21 192859.png

模拟说明:

将HDFS模拟成只有一个NameNode实例(NameNode Service层)和多个DataNode实例(DataNode Service层)的微服务架构。 具体来说,NameNode是负责管理文件系统命名空间和元数据的主节点,而DataNode则负责存储和处理实际的文件块数据。

这种架构存在的缺点:

  1. 单点故障:由于只有一个NameNode实例,如果该实例发生故障,则整个系统将无法继续提供服务。
  2. 性能瓶颈:在这种架构下,所有的文件操作都需要经过NameNode。这意味着所有的元数据操作(如创建、删除、重命名等)都会产生对NameNode的请求,这可能会导致性能瓶颈。
  3. 扩展性受限:由于只有一个NameNode实例,其所能处理的文件系统大小和文件数目有限。一旦达到了极限,就无法再扩展文件系统的容量和性能。

因而一般的微服务架构具有多个实例。

通信对比

在单体架构中,所有的功能模块都运行在同一个应用程序中,它们可以直接调用彼此的函数。这种内部调用方式非常高效和简单,因为函数之间的调用是在内存中完成的,没有网络开销。

相比之下,在微服务架构中,不同的功能模块被拆分成独立的服务,并以分布式的方式部署在不同的主机上。因此,微服务之间的通信需要通过网络进行,而不再是简单的函数调用。(通信方式以Http协议和RPC为常见

场景模拟

调用HTTP服务时动态获取目标服务的地址 hardcode(硬编码的方式): client:grpc.NewClient("10.23..45.67.8080"),因为是固定的地址(写死)而服务的地址是动态的,不支持动态的配置管理 DNS(Domain Name System)(动态解析获取) net.Dial("b.service.org:8080")

  • 本地的DNS存在缓存,将保存解析的结果,用户获得结果会是缓存的旧结果

  • 实例负载不均衡问题,

  • 不支持服务实例的探活检查,即使某个服务实例已经停止运行或不可用,DNS仍然会返回它的IP地址,客户端无法直接感知到服务的实际可用性。

  • 域名无法配置端口,域名解析只返回IP地址,而无法直接指定端口号

解决思路:

  1. 添加服务注册中心用于记录各个服务实例的信息,包括服务名称、IP地址和端口号等。服务实例启动时,将自己的信息注册到服务注册中心,以便其他服务或客户端能够发现和调用它。
  2. 当需要调用某个服务时,可以通过调用服务注册中心的接口进行查询。使用 addrs = svc_reg.find("service.b") 的方式获取服务B的地址列表。 在获取到服务地址列表后,使用随机算法从中选择一个地址。使用 addrs[random(n)] 的方式随机选择一个地址,其中n为地址列表的长度。
  3. 进行服务调用:选定了服务地址后,使用网络库(如net.Dial())建立与该地址的连接,并进行服务调用。使用 net.Dial(addrs[random(n)]) 的方式建立与选定地址的连接。

服务实例的上线和下线,下线会首先去服务状态中心删除记录; 上线添加实例,会进行health check发送请求测试,确认该实例能够正常运作,再在服务注册中心进行注册

流量特征:

  1. 负载均衡:将流量均匀地分发到多个服务实例上,以提高系统的可扩展性和稳定性。
  2. 网关:位于客户端和服务端之间的中间层,它提供一些共享的功能,如路由、鉴权、限流、日志记录等。网关可以集中处理流量,并将请求转发给相应的服务。
  3. 内网通信采用RPC:在微服务架构中,服务之间的通信通常采用远程过程调用(RPC)方式。RPC允许服务之间进行函数调用,使得服务能够在分布式环境下进行协同工作。内网通信多数采用RPC可以提供更高的性能和效率,同时也能够支持各种编程语言和协议。
  4. 网状调用链路:一个服务可能会依赖多个其他服务,而这些服务又可能依赖其他服务,形成了复杂的服务依赖关系,网状调用链路可以监控和追踪整个调用链上的请求流动,帮助发现和解决潜在的问题和性能瓶颈。

核心服务治理:

服务发布(deployment):服务升级运行新代码的过程 要想实现服务发布,就得克服以下难点: 服务不可用(对来自a服务的请求,b服务不响应了,用不了了),服务抖动(不稳定处理,能跑但是跑的不好的情况),服务回滚(新版本bug了,切换旧版本,记忆回溯

克服途径:

  1. 蓝绿部署(Blue-Green Deployment):蓝绿部署是指在生产环境中同时维护两个版本的服务实例,一个是当前稳定版本(蓝色),另一个是新版本(绿色)。在流量低的时候,可以将一部分流量切换到新版本上进行测试和验证。具体特点如下:

    • 简单稳定:蓝绿部署相对简单,只需要两个完全独立的环境来支撑两个版本的服务,并且在切换过程中不会影响用户的访问。
    • 需要两倍资源:由于需要同时维护两个版本的服务实例,因此蓝绿部署需要消耗两倍的资源,包括服务器、网络等。
  2. 灰度发布(Canary Release):灰度发布是指逐步将新版本的服务实例引入线上环境,逐渐将流量切换到新版本上,以进行测试和验证。具体特点如下:

    • 经济化节省资源:相比蓝绿部署,灰度发布只需要逐渐引入新版本的服务实例,可以根据流量来动态分配资源,节省了资源的使用。
    • 缓慢但安全:由于逐个节点添加和调试新版本,灰度发布相对比较缓慢,但能够有效控制风险,并及时发现和解决问题,确保新版本的稳定性。

总之就是蓝绿部署适用于对服务的稳定性要求较高,且具备足够的资源支持的场景,毕竟成本高但是收益也是在那里的,而灰度发布适用于经济化利用的场景,测试虽然慢但是稳定。

流量治理: 可以基于地区集群实例请求等维度,对端到端流量的路由路径进行调配 例如:

  • 繁荣地区与次繁荣地区,
  • 稳定的服务和测试中的服务,
  • 新的运行实体和旧的运行实体,
  • 寻常的请求(大量)和测试的请求(少量)

负载均衡 负责分配请求在每个下有实例的分布,目的就是不能给一个实例过多压力,而其他实例反而没用上,强调资源的利用

常见的策略

阿巴阿巴

  1. 轮询(Round Robin):按照顺序依次分配给后端服务器。
  2. 随机(Random):随机地分配给后端服务器。
  3. 哈希(Hash)(又是你哈希):根据请求的某个属性(如客户端IP地址或URL)计算哈希值,并将请求分发到对应的后端服务器。
  4. 最少连接(Least Connection):将请求分配给当前连接数最少的后端服务器。

稳定性治理

(意外太多还是太难维持线上服务了)

解决方式

  • 限流(设置请求次数限制)
  • 熔断(响应端出了点问题,先断掉,过段时间再看看)
  • 过载保护(压力过大,先断掉请求,喘口气)
  • 降级(过载了,砍掉一些业务,但还是要完成主要业务)

治理实践

重试的意义

检测本地函数会出现的异常:

  1. 非法参数异常。
  2. 内存溢出异常
  3. 空指针异常
  4. 边界异常
  5. 系统崩溃、死循环与程序异常退出

检测远程函数的异常(由于服务器的不可抗力因素,也许下一次就会成功):

  1. 抖动
  2. 超载,
  3. 宕机,
  4. 调度超时
  5. 熔断,
  6. 限流

通过解决这些异常情况,减低错误率减低长尾延时容忍暂时性错误避开下游故障实例

常见问题

  1. 幂等性,指对同一操作的多次执行所产生的影响与执行一次的影响相同。例如,在数据库写操作中使用唯一键或乐观锁来确保幂等性。这样即使请求出现重复或重试,也不会对系统产生副作用。(必须要确保的性质,多次重试带来的副作用会宕机的~)
  2. 重试风暴(雪崩效应),指的是分布式系统中的一个故障扩散现象,当某个服务故障时,其上游服务会发起大量重试请求,导致上游服务负载过高、不可用,最终形成级联故障。是指数级增长警告
  3. 超时设置:在远程函数调用中,超时设置是为了避免请求无限等待或占用系统资源。通过设置适当的超时时间,可以在一定时间内得到响应,或者在超时后进行相应的错误处理。

解决方式

  1. 限制重试比例:可以设置一个阈值,确保重试请求的比例不超过该阈值。例如,可以配置一个百分比,表示每个请求中最多允许的重试比例。如果超过了这个比例,可以拒绝继续进行重试,或者采取其他处理方式,如返回错误信息或进行回退操作。
  2. 防止链路重试:可以针对每一层或每个服务设置重试次数的上限,以避免整个链路出现重试风暴。当达到重试次数上限时,可以快速失败,并告知上游服务请求失败,从而减轻链路上的重试压力。
  3. Hedged Requests(并行请求):对于可能超时的请求,可以同时向多个下游发送相同的请求,并等待先到达的响应。这样可以提高整体的请求成功率,避免单个请求的超时影响整体性能。当第一个响应到达时,可以取消其他请求。
  4. 重试效果验证:可以使用for循环来实现重试逻辑,并结合接入重试组件,例如断路器(Circuit Breaker)、熔断器(Fallback)等。通过不断重试和监控重试结果,可以验证重试机制的有效性和性能。

总结:

微服务架构通过将一个大型应用拆分成一系列更小、独立的服务,每个服务专注于特定的业务功能。这种方式使得每个服务能够独立运行、部署和扩展,从而提高系统的可伸缩性和灵活性。

通过微服务架构,可以方便地根据流量需求来进行服务的分配与负载均衡,从而实现利用率的提升。例如,可以通过服务注册与发现机制(如Consul、Eureka)来动态地将请求流量分配到不同的服务实例上,根据实时的负载情况来进行动态调整,确保资源的合理利用。

此外,重试机制在微服务架构中也扮演着重要的角色。由于每个服务都是独立部署和运行的,可能会面临网络延迟、故障或其他异常情况。通过重试机制,当请求遇到错误或超时时,可以自动地进行重试,以期最终获得成功的响应。同时,重试机制还能帮助排查服务运行中出现的异常情况,通过记录和监控重试请求的结果,可以及时发现并修复问题,提高系统的稳定性和可靠性。

总之,微服务架构的部署独立性、流量分配灵活性以及重试机制的使用,确实为系统的可扩展性、稳定性和故障排查提供了方便和有效的手段。但同时也需要注意合理设置重试策略,避免过多的重试导致资源浪费或性能下降。