这是我参与「第五届青训营」伴学笔记创作活动的第 5 天
微服务架构介绍
系统架构演变历史
为什么系统架构需要演进?
-
互联网爆炸性发展
- 用户规模指数级增长
- 需求复杂
-
硬件设置的快速发展
-
需要复杂性的多样化
-
开发人员的急剧增加
-
计算机理论及技术的发展
总的来说就是老的系统架构无法满足新的需求
演变历史
单体架构->垂直应用架构->分布式架构->SOA架构->微服务架构
单体架构
all in one process
把所有的逻辑放在一个项目里面
优势:
- 性能最高
- 冗余小
劣势:
- debug困难:当一个项目由上万人同时研发,有几十个上百个模块的时候,就已经无法再debug了
- 模块相互影响:假设广告管理模块有一个bug,可能就会导致整个项目崩溃
- 模块分工、开发流程:当该项目有几十人或上百人共同开发的时候,假设一个功能模块开发受阻,其他模块也会受到阻碍
垂直应用架构
思想:按照业务线垂直划分
如图的项目可以分为:电商业务线,后台业务线,广告业务线
每个业务线又回到了单体结构
优势:
- 业务独立开发维护
劣势:
- 不同业务存在冗余
- 每个业务还是单体
分布式架构
思想:抽出业务无关的公共模块
本项目抽出了与业务无关的服务层,达到逻辑服用的功能
优势:
- 业务无关的独立服务:公共服务的去冗余,实现复用
劣势
- 服务模块bug可导致全站瘫痪
- 调用关系复杂:如图可知,模块对公共服务的调用是N:N的,那么就导致了单个服务的bug导致整个项目瘫痪
- 不同服务冗余:服务本身仍存在冗余,无法去除
SOA架构(Service Oriented Architecture)
思想:面向服务
可以看到,相比分布式架构多了一个服务注册中心,通过服务注册中心解耦
优势
- 服务注册
劣势
- 整个系统设计是中心化的
- 需要从上至下设计
- 重构困难
微服务架构
思想:彻底的服务化
优势
- 开发效率
- 业务独立设计
- 自下而上
- 故障隔离
劣势
- 治理、运维难度
- 观测挑战
- 安全性
- 分布式系统
微服务架构概览
Actor:用户终端
网关:处理外界流量
链路追踪和监控:微服务架构本身是一个非常庞大的分布式系统,在此基础上进行问题定位,需要同一台的平台、能力来支持
微服务架构核心要素
-
服务治理
- 服务注册
- 服务发现
- 负载均衡
- 扩缩容
- 流量治理
- 稳定性治理
- ......
-
可观测性
- 日志采集
- 日志分析
- 监控打点
- 监控大盘
- 异常报警
- 链路追踪:微服务是链式调用,跨主机的链路追踪提供问题排查
- ......
-
安全
- 身份验证
- 认证授权
- 访问令牌
- 审计
- 传输加密
- 黑产攻击
- ......
微服务架构原理及特征
基本概念
在上图中,一段代码通过编译、部署,生成了一个服务,服务里有一个集群,集群中包含多个实例
-
服务(service) :一组具有相同逻辑的运行实体。(运行同一段代码的多个实例)
-
实例(instance) :一个服务中,每个运行实体即为一个实例。
-
实例与进程的关系:实例与进程之间没有必然对应关系,可以一个实例可以对应一个或多个进程(反之不常见)
-
集群(cluster):通常指服务内部的逻辑划分,包含多个实例。
-
常见的实例承载形式:进程、VM、k8s pod……
-
有状态/无状态服务:服务的实例是否存储了可持久化的数据(例如磁盘文件)
如果把HDFS看作一组微服务,由于NameNode和DataNode运行不同的代码,因此被分为两个服务
NameNode是一个单实例服务,而DataNode服务具有多个实例
服务间通信
对于单体服务,不同模块通信只是简单的函数调用
对于微服务,服务间通信息意味着网络传输,包括HTTP,gRPC、Thrift等
在企业内部,HTTP和ROC协议的使用是比较广泛的
在上图中,一个服务包含若干个实例,服务与服务之间通过网络传输进行通信
服务注册与发现
在代码层面,如何制定调用一个目标服务的地址(ip:port)?
hardcode
hardcode(硬编码,将本应写在配置文件中的内容在程序中写死)
//Service A wants to call service B
client = grpc.NewClient("10.23.45.67:8080")
存在什么问题?
不太可能指定一个固定的IP地址,这种地址是动态变化的
而且一个服务包含多个实例,指定一个IP地址显然是不够的
如上图所示,服务B中的三个实例运行同一份代码
服务A通过IP地址显然只调用了服务B的一个实例,这样是不够的,这样其他两个实例就会很轻松了,不能让他们得逞
还能采用什么中间形式来表达服务?
DNS
如图所示,服务A通过域名调用服务B,服务B的三个IP地址通过NameService登记到域名当中去,这样当服务B的IP地址发生变化时,只需修改域名记录即可,不会出现只返回一个实例的问题
存在什么问题?
- 本地DNS存在缓存,导致延时:本地DNS有缓存机制,当IP地址发生变化需要刷新
- 负载均衡问题:DNS仍然可能只返回第一个IP,仍然存在过载问题
- 不支持服务实例的探活检查
- 域名无法配置端口:这就导致了实例的数量是有一定上限的
服务注册中心
解决思路:新增一个统一的服务注册中心,用于存储服务名到服务实例的映射(简单理解为哈希表或map)
在上图中服务A中的代码
//调用服务注册中心拿到服务B的地址
adds = svc_reg.find("service.b")
//调用一个负载均衡算法random随机调用其中一个实例
net.Dial(addsrs[random(n)])
服务的上线和下线
假设已经有了一个服务注册中心,两个服务,各有三个实例,3:3的网络通信
若管理员发现服务B的实例3有问题,需要下线,如何实现
若直接下线,则发过来的请求就会失败,造成线上产品故障
基于服务发现的技术会执行一下操作
首先管理员告诉注册中心,把实例3的记录删掉
服务A会不断刷新服务发现
过几分钟后,实例A就不会再去调用服务B的实例3
这个时候再去删除实例3就已经是安全的了
这个时候服务B的压力就会比较大,需要上线新的实例
先添加实例到服务B,执行健康检查(测试进程能否接受TCP建连请求)
检查OK之后,再去向注册中心添加实例记录
注册之后,服务A还是会不断地刷新服务发现,这时发现服务发现的第三个实例出现了,流量恢复
流量特征
前面基于组件的维度,我们了解了微服务的架构包含服务、实例、服务注册中心等部分
接下来从上图流量的维度观察微服务的结构
首先是终端用户,可能还涉及到CDN
流量进入右侧Internet企业内网环境之后,首先会经过负载均衡(Load Balance),API网关,最后在分配到各个服务上去
这里要注意区分流量是通过HTTP传输还是RPC传输
HTTP是一个文本协议,传输的是文本,性能不高,而RPC是通过二进制的形式传输,无论是信息传输还是编解码效率都是很高的,因此内部之间的通信采用RPC协议
Offline Training Job通常做推荐工作,在执行推荐系统、机器学习等训练模型训练的同时,需要调用一些其他的模块
- 统一网关入口
- 内网通信多数采用RPC
- 网状调用链路