微服务架构原理|青训营笔记

136 阅读10分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记

1. 前言

本篇笔记主要通过通过系统架构的演变历史介绍微服务架构原理及特征。

主要介绍了:系统架构为什么需要演进,以及不同架构之间的演进过程,每种架构存在的问题及其优势。

2. 系统架构

为什么系统架构需要演进?

  • 互联网的爆炸性发展

  • 硬件设施的快速发展

  • 需求复杂性的多样化

  • 开发人员的急剧增加

  • 计算机理论及技术的发展

架构演变历史

  • 单体架构

    All in one process

    image.png

    优势:

    1. 性能最高

      • 没有额外开销,是纯粹的程序内部的开销
    2. 冗余小

    劣势:

    1. debug困难

      • 日常开发中,代码量稍大一点的程序,如果上万名的开发人员向一个程序中塞东西的话,就无法进行debug操作
    2. 模块相互影响

      • 以电商项目为例,假设广告管理有一个bug。bug本身可能只会影响广告管理出,同时还可能会导致整个项目崩溃
    3. 模块分工、开发流程

      • 单体架构涉及到的人员过多的话,模块分工以及开发流程都会变得额外复杂

      • 例如一个人开发一个功能,一天上线一个小功能。如果一群人开发一个小功能,其中一个环节卡住就会影响整体开发流程

  • 垂直应用架构

    按照业务线垂直划分

    image.png

    优势:

    1. 业务独立开发维护

    劣势:

    1. 不同业务存在冗余(重复工作多)

    2. 每个业务还是单体(仍存在单体结构缺陷)

  • 分布式架构

    抽出业务无关的公共模块

    image.png

    优势:

    1. 业务无关的独立服务(去冗余,公共部分的复用)

    劣势:

    1. 服务模块bug可导致全站瘫痪

      • 如果用户服务出问题,会导致全站瘫痪
    2. 调用关系复杂

    3. 不同服务冗余

  • SOA架构

    面向服务

    • 多了一个注册中心,业务和服务之间会有一个较为清晰的划分

    • 通过服务注册中心进行一个解耦

    image.png

    优势:

    1. 服务注册、发现

    劣势:

    1. 整个系统设计是中心化的

    2. 需要从上至下设计

    3. 重构困难

  • 微服务架构

    彻底的服务化

    image.png

    优势:

    1. 开发效率

      • 微服务架构中开发新功能,只需要按照一定约定把功能做好,部署到线上就完成了。服务之间的调用关系可以借助微服务架构的基础能力
    2. 业务独立设计

    3. 自下而上

    4. 故障隔离

    在设计用户服务的时候,不需要关心商品服务如何,而是自下而上的以微服务的形式搭起来

    劣势:

    微服务架构本身就是一种分布式系统,有着分布式的相关问题

    1. 治理、运维难度

    2. 观测挑战

    3. 安全性

    4. 分布式系统

3. 微服务架构

概览

image.png

除了服务本身之外,在用户终端之间还有网关。

两边还有服务配置和治理以及链路追踪和监控两大组件,是因为:

  • 服务配置和治理

    • 微服务架构中,本身服务是一等公民的话,相应的针对服务的各种配置是需要一个基础化的能力来支持的。这时服务配置和治理的相关功能。
  • 链路追踪和监控

    • 微服务架构是一个庞大的分布式系统,进行问题定位和系统需要,统一的平台层面的能力进行支持

    • 无法对每一个服务,像单体架构一样对每一个日志进行查询的古老方法。必须要平台级的链路追踪和监控的功能

核心要素

  • 服务治理

    • 服务注册

    • 服务发现

    • 负载均衡

    • 扩缩容

    • 流量治理

    • 稳定性治理

  • 可观测性

    以日志为例

    微服务架构有很多服务,每个服务有相应的日志。在查问题的时候,不可能每个服务都看到,不现实。

    服务架构中需要有一个服务采集和分析的能力

    • 日志采集

    • 日志分析

    • 监控打点

    • 监控大盘

    • 异常报警

    • 链路追踪

      • 在传统的程序中,有一个功能以函数的形式调用某个模块,如果发现模块有问题,可以根据调用栈查询问题。在微服务架构中,这种功能就变成了几个、几十个服务的链式调用。这种调用和传统的调用是不一样的,整个功能点可能是跨越了几十台机器才形成一个完整的功能。

      • 我们必须要对链路的追踪提供相应的能力,才能够进行问题的常规排查

  • 安全

    微服务架构中有大量的服务的话,服务之间的调用要有一个认证授权的过程,否则会出现一些非预期的问题

    • 身份验证

    • 认证授权

    • 访问令牌

    • 审计

    • 传输加密

    • 黑产攻击

4. 微服务架构原理及特征

基本概念

image.png

上图code base为一份代码,经过编译和运行就形成了 service。内部有多个实例

cluster为集群,一个服务中有大量实例,每个实例本身有不同逻辑。通常在服务内部,会对不同逻辑进行逻辑的划分。每个逻辑划分就是一个集群,一个集群会包含若干实例

  • 服务(service)

    • 一组具有相同逻辑的运行实体(一个服务是运行同一份代码的多个实例)

    • 相同逻辑:一个服务的代码是一样的,也就是说,一个服务运行同一份代码

    • 运行实体:实例

  • 实例(instance)

    • 一个服务中,每个运行实体即为一个实例
  • 实例与进程的关系

    • 常规开发中接触比较多的是进程的概念

    • 实例与进程之间没有必然对应关系,可以一个实例对应一个或多个进程(反之不常见)

  • 集群(cluster)

    • 通常指服务内部的逻辑划分,包含多个实例
  • 常见实例承载形式

    • 实例不仅限于具体的形式,只需要满足运行的特征即可

    • 进程、VM、k8s pod ......

  • 有状态/无状态服务

    • 服务的实例是否存储了可持久化的数据(例如磁盘文件)

    • 例如一个存储的服务,它是有状态的;一个代理的服务 jm/,

如果把HDFS(分布式文件系统)看作一组微服务

image.png

  • NameNode和DateNode看作是两个服务。(一个服务应该是具有相同逻辑的,即运行同一份代码)

  • NameNode的服务是一个但实例的服务(一般的服务都有多个实例)

服务间通信

对于单体服务,不同模块通信只是简单的函数调用(调用者传递参数给被调用者,被调用者返回一定的信息)

对于微服务,服务间通信意味着网络传输(不同的实例,直观来说就是不同的机器,会涉及到网络传输)

image.png

  • 企业内部HTTP和RPC应用广泛

  • 每个服务有多个实例,服务与服务之间通过网络传输进行通信

服务注册与发现

问题:在代码层面,如何指定调用一个目标服务的地址(ip port)?

// Setvice A wants to call service B
client := grpc NewClient("10.23.45.67:8080")
  • hashcode?

    上述方式在微服务调用过程中会存在什么问题?

    • 目标地址固定

      在正式的微服务中,地址会动态变化,地址不能写死

    • 只返回同一个实例

      一个服务中会有多个实例,这里一个实例明显不行

    image.png

    上图,一个服务的所有实例会运行同一份代码,指定固定的地址之后,服务B只有一个实例会收到请求

  • 使用DNS代替固定ip地址行不行?

    • 不指定IP:PORT,指定域名

    • 调用b.service.org的域名,端口是8080;服务B注册了一个域名,通过增加不同域名的record,将服务B三个不同的地址登记进去

    这样也行得通:

    1. IP如果发生变化,那么改变DNS的记录就行

    2. 解决了只返回同一个实例的问题,一个域名可以注册多个不同域名的IP

    image.png

    核心问题:

    • 本地DNS存在缓存,导致延时(缓存机制:修改一个域名,在本地的缓存有点久,需要刷新)

    • 负载均衡问题(服务B注册了三个IP,实际上返回哪一个IP存在概率,大部分情况会选择第一个,因为第一个IP存在,也可以访问到)

    • 不支持服务实例的探活检查(可以配置不存在的IP,只是访问会出错)

    • 域名无法配置端口(服务B的IP问题解决了,端口问题还是没有解决。一个服务只能占用一个端口的话,能上线运行的服务大概有几万个)

  • 服务注册及发现.

    SOA服务架构中有一个服务注册及发现的概念

    image.png

    解决思路:

    • 新增一个统一的服务注册中心,用于存储服务名到服务实例的映射。简单来说就是一个hash表或map

    • Service Registry中,对于服务B来说,注册了三个地址(ip:port)。解决了DNS的端口问题;服务A的同一份代码,代码首先会调用注册中心的一个调用,拿到服务B的真实的当前的地址。之后应用最简单的负载均衡算法(radom)进行调用

服务实例上线及下线过程

image.png

上图中有一个3对3的网络调用,服务的注册中心也ready了,这个时候服务B的第三个实例有问题,需要进行下线操作

如果直接将服务下线,会面临流量问题,大量的请求将会使服务崩溃

基于服务注册与发现机制如何解决?

管理员想要下线服务B的实例3,首先在服务注册中心将服务B的第三个实例删除。

image.png

这样过一段几秒,服务A就不会调用第三个实例,因为服务A会不断刷新的服务发现的记录。过了一段时间之后会发现第三个实例没了,此时只会达到服务B的前两个实例。

image.png

这时候在下线第三个实例就是安全的,因为此时已经没有流量了

这时会出现新的问题:服务B的其余两个实例的压力会增加

此时需要额外添加一个实例,来缓解压力

image.png

添加的步骤与下线的步骤相反:

  • 在服务发现之前,先将服务启动(暂时不需要改动注册中心),如果强制加入一条记录,也会造成一部分流量的失败。

  • 此时将实例加好,同时还注意health check健康检查(进程或实例需要跑起来,还需要试一试,是否真的可以处理请求,最简单的是端口起码需要在监听)可能会发送一个TCP的建连请求试一试.

  • 健康检查通过之后,就可以认为这个实例大概OK,此时在将这个实例注册到注册中心中。

  • 注册之后,服务A通过不断刷新进行服务发现,这时就会发现第三个实例,之后流量就会恢复了

后记

通过「第三届青训营 -后端场」课程的学习,自己了解到软件架构演进至今都有哪些形态?它们分别解决了什么问题?仍然存在什么问题?同时加深了自己对微服务架构的认识。