type
status
date
slug
summary
tags
category
icon
password
 

什么是 chassis?

Chassis,是一种微服务模式。在这种模式中,用户并不需要自己去处理构建微服务过程中外部配置、日志、健康检查、分布式追踪等,而是将他们交给专门的框架来处理。用户可以更聚焦业务逻辑本身,简单、快速的开发微服务。
阅读此文,你可以得到什么?
chassis 运行时做了什么chassis 运行时的隐藏操作。chassis 设计思路的一些理解

Go-Chassis 是什么?

Go-Chassis 是一个go语言的微服务开发框架,采用插件化设计,原生提供了可插拔的注册发现,加密解密,调用链追踪等组件。协议也是插件化的,支持http和grpc,也支持开发者定制私有协议, 开发者只需要专注于实现云原生应用即可。
云原生应用,基于云服务开发或者针对云服务开发部署的应用。
notion image
上图是go-chassis 的架构图,可以看出配置管理(Archaius)、服务注册(Registry)、Metrics、日志(Logger)都是独立的组件,分布式追踪、负载均衡、限流等都是以中间件(Handler Chain)的方式实现的。一个请求进来后会先通过server 转换成chassis invoker,然后经过Handler Chain,最后由Transport 转换成对应协议的response返回。
此篇文章主要关注go-chassis 启动过程时做了什么,以及做这些事情的用途。

一个例子

首先从 hello world 开始, 目录结构如下:
chassis.yaml 内容为:
microservice.yaml 内容为:
main.go
先来看一下这段代码具体做了什么。
  • 11~27 声明了 一个 RestFulHello struct,这个struct 有两个方法 Sayhi 和 URLPatterns,其中URLPatterns 返回一个 Route 列表。 这段代码声明了一个http handler 和 对应的路由,那具体为什么这么写等下再做说明。
  • 30行 chassis.RegisterSchema("rest", &RestFulHello{}) 将前面声明的 RestFulHello 注册到 "rest" 服务。
    • 这里内部只是简单的使用传入的参数创建一个 chassis.Schema 然后append到 chassis.schemas 中。
  • 31行 chassis运行前的初始化工作。
  • 35行 运行chassis 服务。
notion image
执行 go run rest/main.go 运行代码,会发现启动失败,日志输出内容为:
通过日志可以看到两个问题:
  1. 为什么添加了配置还会提示配置找不到?
  1. 为什么配置没有加载成功插件却可以安装成功?

chassis init

下图是chassis init 的执行流程:
notion image

配置初始化

首先看一下chassis 初始化的过程中配置是如何加载的。
notion image
查看 config.Init() 代码可以看到 配置目录是通过 fileutil.RouterConfigPath() 来获取的,目录初始化方法为:
如果使用 ChassisHome 环境变量指定应用目录,chassis 运行时,会从该目录下的 ChassisHome/conf/ 目录中读取配置
也可以使用 ChassisConfDir 直接指定配置目录,ChassisConfDir 优先级高于 ChassisHome/conf
chassis 使用 archaius 来管理配置,archaius 初始化时,会从文件、环境变量、命令行、内存中初始化配置。
从代码可以看出,global config 和 microservice config 是必须要有的,
global config 对应 conf_path/chassis.yaml
microservice config 对应 conf_path/microservice.yaml
接下来读出配置后,给初始化runtime 的值:
runtime 中的数据可以认为是运行时的全局变量
archaius 也支持从配置中心读取配置,通过这种方式,chassis 也提供了运行时配置热加载的功能。
对于第二个问题,为什么插件会先于配置安装?

插件初始化

notion image
从图中可以看出init 做了预先初始化了很多的插件,比如 client、provider、server、log、router rule、register、load balance、service discover、treporter等,并且chassis init 方法中并没有做显式的初始化调用。通过查看代码会发现,这个步骤是使用各自的init 方法自动执行的,类似这样:
之所以隐式加载是因为 chassis 是插件式设计,使用 init 方式加载插件,可以做到对插件的即插即用,需要使用的插件只需要在代码中添加包的import 即可,比如加载grpc 插件,只需要在main.go 中添加
从这一系列插件安装方式也能看出,对于chassis 来说,注册中心,协议,负载均衡等都是插件,这也就意味着这些插件都是可替换的,方便二次开发。
以上两个问题现在都解决了,现在执行以下命令运行服务:

初始化handler chain

Handler是微服务在运行过程中在框架层面里的一个最小处理单元。go chassis通过handler和handler的组装实现组件化的运行模型架构。其基本的使用方式就是实现接口、注册逻辑:
Handler 定义非常简单,实现了Handler 接口就可以认为创建了一个Handler。
使用RegisterHandler 函数将添加到HandlerFuncMap 中即可在CreateHandler 调用时使用。
对于chassis 来说,协议转换,权限验证,全链路追踪等都可以认为是一个handler(中间件),这里会从配置中读取声明的handler,并且初始化。请求调用时,会按照配置文件中的定义的顺序进入handler进行处理。
notion image
在服务初始化的过程中,go-chassis 会根据配置文件中的定义加载需要的handler,handler 分为provider、consumer和 default 三种,配置内容示例如下:
如果配置了非default 的type,服务启动的时候只会执行此特定的handler,比如上述配置,handler 只会执行 jwt,而忽略tracing-provider
这是因为chassis 使用map存储 handler chain,map 的key 为 chainType+chainName, default 也是一种chainType,如果name(即chain type)有值则使用对应的 chain,否则使用default。

初始化 server

notion image
初始化的前提是服务已经加载,加载的步骤在init 之前就已经通过 init 方法载入了。
这里初始化的是配置文件中 protocols 指定的服务。
这里会从 *var* serverPlugins = make(*map*[string]NewFunc) 读取server,所以在初始化时需要先安装server 对应的插件
chassis 会 默认安装rest 插件,对于grpc 需要首先指定
服务的Listen Advertise 优先级最高,如果 Advertise 和 Listen 都没有配置,使用默认配置。
初始化 server options,其中chainName 如果Provider 配置了对应 protocol name 的值,则使用protocol name。

其它

几个初始化外,init 还包括 register、configcenter、router、contorl、tracing、metric、reporter、熔断器、事件监听等就不再细说了。
为止,chassis 所需要的初始化步骤已经结束,接下来就是 服务运行的步骤。

chassis run

首先看一下 chassis.Run() 启动的整体流程
notion image
chassis 运行主要分为三个动作:
  1. 根据schema 找到服务,将对应的handle func 使用 handler chain 封装
  1. 启动服务,将服务注册到服务中心
  1. 监听退出信号
这里使用rest 服务作为例子看一下 chassis 启动服务的时候做了哪些操作。

服务注册

首先回顾一下hello world 代码:
RestFulHello ,其中有一个 URLPatterns() []Route 方法,实现了 Router 接口。
Router 定义
notion image
启动的服务注册流程中包含了将schemas 中所有Router 取出遍历,调用 WrapHandlerChain() 函数,这个函数主要做了以下工作:
  1. 取出 Route 中 ResourceFunc (即real handler func)
  1. 将 HttpRequest 转换成 chassis Invocation,
  1. 将Invocation 再添加回 request 中添加到 handler chain 中
  1. 返回一个闭包函数。
最后会把使用 WrapHandlerChain 封装后的handler 注册到go-restful 框架中。
响应请求时,调用关系类似以下操作:
为什么需要转换成统一的invocation?
不同协议请求进入到对应的Server,Server将具体的协议请求转换为Invocation统一抽象模型,并传入Handler chain,由于handler根据统一模型Invocation进行处理,不必每个协议开发出来都自己开发一套治理。处理链可通过配置更新,再进入Transport handler,使用目标微服务的协议客户端传输到目标。
这种方式实际上真正提供业务处理的还是各个server 插件,chassis 只是中间商,可以对request 和 response 做它想要的处理,比如限流,熔断,路由更新等。
notion image
  1. 接收到协议请求后,由各协议Server转为统一的Invocation模型
  1. Invocation进入处理链处理
  1. 处理结束后,进入具体的业务处理逻辑

信号监听

当服务需要关闭或重启时,应当处理完当前的请求或者设置为超时,而不是粗暴的断开链接,chassis 这里使用了信号监听的方式来处理关闭信号。
notion image
这里使用go信号通知机制通过往一个channel中发送os.Signal实现的。创建一个os.Signal channel,然后使用signal.Notify注册要接收的信号,chassis 关注以下信号:
信号
动作
说明
SIGHUP
1
Term
终端控制进程结束(终端连接断开)
SIGINT
2
Term
用户发送INTR字符(Ctrl+C)触发
SIGQUIT
3
Core
用户发送QUIT字符(Ctrl+/)触发
SIGILL
4
Core
非法指令(程序错误、试图执行数据段、栈溢出等)
SIGTRAP
5
Core
Trap指令触发(如断点,在调试器中使用)
SIGABRT
6
Core
调用abort函数触发
SIGTERM
15
Term
结束程序(可以被捕获、阻塞或忽略)
接收到信号后,首先判断是否注册到服务中心,如果注册,停掉心跳发送,退出注册,然后调用 server.Shutdown() 来优雅退出。
go http Server 从1.8 之后支持优雅退出。
具体实现可以参考此文章:http://xiaorui.cc/archives/5803

总结

这篇文章介绍了 chassis 服务启动的过程,主要介绍了init 中 配置 、插件、handler chain 、server 的初始化流程,然后分析了服务启动时做了哪些操作以及对服务退出的处理。

参考链接

  1. 使用ServiceComb Go-chassis构建微服务
  1. Pattern: Microservice chassis
  1. Linux Signal及Golang中的信号处理
  1. 源码分析golang http shutdown优雅退出的原理
  1. Go语言微服务开发框架实践-go chassis
七十二堂写作课:夏丏尊叶圣陶教你写文章(读书笔记)Mac 删除 Microsoft AutoUpdate.app
Gusibi
Gusibi
后端开发,擅长使用 Python 和 Golang,现在面向AI编程。热爱探索新互联网产品,曾写过小程序。正在学习 Flutter,热衷于技术的探索与进步。
公告
type
status
date
slug
summary
tags
category
icon
password
🎉欢迎访问🎉
-- 感谢您的支持 ---
👏欢迎更新体验👏
 
🤺
关于我