近日, ServiceComb 开源了该项目旗下的 Go 语言微服务框架ServiceComb Go-chassis
(以下简称Go-chassis
)。作为 ServiceComb 生态中的重要一环,Go-chassis 将和已经开源的 Apache ServiceComb(incubating) Java-chassis 一道,为用户带来良好的框架级多语言微服务编程体验。同时,ServiceComb Go-chassis
未来也会和ServiceComb Java-chassis
一样,捐献到 Apache 基金会中。
什么是 chassis?
Chassis,直译成中文是底盘的意思,它是一种微服务模式,由微服务大师 Chris Richardson提出。在这种模式中,用户并不需要自己去处理构建微服务过程中外部配置、日志、健康检查、分布式追踪等那些横切关注点( crosscutting concern ),而是将他们交给专门的框架来处理。用户可以更聚焦业务逻辑本身,简单、快速的开发微服务。
Apache ServiceComb Go-chassis 简介
Go-chassis 是一个用 Go 语言编写的微服务快速开发框架,采用插件化设计,将各种功能模块定义为接口,实现功能“可插拔”的同时也带来了非常好的扩展性;用户可以定制开发自己的通讯协议、处理链,对接自己的配置中心、服务发现中心等。Go-chassis
的模块及工作流程如上图所示,不同协议请求进入到各协议 Server,Server 将具体的协议请求转换为Invocation
统一抽象模型,并传入Handler chain
( 框架内默认内置了大部分微服务治理所需的 handler),最终再进入Transport handler
,使用具体的协议客户端传输到目标。
目前,Go-chassis 支持的特性包括但不限于:
- 服务发现(配合
ServiceComb
现有的ServiceCenter
) - 动态配置管理:
- 负载均衡
- 流量控制(服务端与客户端)
- 熔断容错
- 分布式追踪
- Metrics
- 日志
- 路由策略
在通讯协议方面,Go-chassis 原生支持 REST/Http 和 Highway 协议,其中 Highway 是 ServiceComb 开发的、基于 RPC 的高性能通讯协议。
使用 Go-chassis 开发体质指数应用(BMI)
下面,我们将以简单的体质指数应用(BMI)为例,说明如何用 Go-chassis 快速开发微服务。 BMI 是国际上常用的衡量人体胖瘦程度的一个标准,我们开发的 BMI 应用主要包含两个微服务:
- 体质指数计算器(calculator):负责处理运算事务。
- 体质指数界面(web-app):提供用户界面及网关服务。
两个微服务之间的关系如下图所示,它们之间通过REST
方式进行通信。
准备工作
在开始开发之前,请确保开发环境上已经完成以下配置
- 安装 git
- 安装 Go 1.8+
- 安装 docker
- 下载 ServiceComb go-chassis
go get https://github.com/ServiceComb/go-chassis
- 运行 Service Center
在 ServiceComb 微服务框架中,Service Center 提供服务注册及服务发现功能,可直接使用 docker 运行。
docker pull servicecomb/service-center docker run -d -p 30100:30100 servicecomb/service-center:latest
体质指数计算器 (calculator) 开发
体质指数计算器 (calculator) 提供运算服务,分为具体业务逻辑、服务注册与配置,启动框架三部分:
- 具体业务逻辑
定义服务结构体
type CalculateBmi struct { }
编写计算体质指数(BMI)函数,该函数根据公式 \(BMI=\frac{weight}{height^2}\) 进行实现:
func (c *CalculateBmi) BMIIndex(height, weight float64) (float64, error) { if height <= 0 || weight <= 0 { return 0, fmt.Errorf("Arugments must be above 0") } heightInMeter := height / 100 bmi := weight / (heightInMeter * heightInMeter) return bmi, nil }
编写 handler 函数,其中restful.Context
必须作为入参传入。restful.Context
中包含了对http
调用的Request
和Response
的常用操作,Read
开头的为对Request
的读取,包括ReadEntity
、ReadHeader
、ReadPathParameter
、ReadBodyParameter
、ReadRequest
等;而Writer
开头的为对Response
的写入操作,包括WriteHeader
、WriteJson
、WriteError
等。
import ( rf "github.com/ServiceComb/go-chassis/server/restful" ) …… func (c *CalculateBmi) Calculate(b *rf.Context) { …… // 定义返回结构体,此处省略 heightStr := b.ReadQueryParameter("height") weightStr := b.ReadQueryParameter("weight") var height, weight, bmi float64 var err error …… // 字符串与浮点数类型转换,此处省略 if bmi, err = c.BMIIndex(height, weight); err != nil { errorResponse.Error = err.Error() b.WriteHeaderAndJson(http.StatusBadRequest, errorResponse, "application/json") return } result.Result = bmi result.CallTime = time.Now().String() b.WriteJson(result, "application/json") }
指定对应的 URL 路由
func (c *CalculateBmi) URLPatterns() []rf.Route { return []rf.Route{ {http.MethodGet, "/calculator/bmi", "Calculate"}, } }
- 服务注册与配置
完成业务逻辑代码的编写之后,需要将业务逻辑注册到Go-chassis
框架,注册时可以同时指定微服务的名称、ID 等属性。
chassis.RegisterSchema("rest",&CalculateBmi{})
除了在代码中指定的部分属性外,更多的属性是通过配置文件来进行配置。配置文件包括chassis.yaml
和microservice.yaml
,放置于代码目录下的conf
文件夹内。其中,chassis.yaml
中配置的是微服务的公共属性,如公共的AppId
信息,使用的注册中心类型信息、地址信息,服务的协议、监听地址、注册发现地址、传输协议信息等;microservice.yaml
配置的是微服务的私有属性,包括服务名、版本等。
chassis.yaml
APPLICATION_ID: bmi #应用 ID cse: service: registry: address: http://127.0.0.1:30100 #注册中心(ServiceCenter)的地址 protocols: rest: listenAddress: 0.0.0.0:8080 #微服务的监听地址
microservice.yaml
service_description: name: calculator #微服务名称 version: 0.0.1 #微服务版本号
启动框架和服务
import ( "github.com/ServiceComb/go-chassis" "github.com/ServiceComb/go-chassis/core/lager" ) …… if err := chassis.Init(); err != nil { // 初始化框架 lager.Logger.Error("init failed", err) return } chassis.Run() // 运行微服务
体质指数界面 (web-app) 开发
开发完后端的calculator
服务之后,需要开发提供给用户的交互界面,主要分为前端静态界面、请求转发、服务启动入口三个部分。
- 前端静态界面
前端静态界面使用了 Bootstrap 开发,并通过golang
官方库中http.ServeFile
将前端静态页面展示出来。
func BmiPageHandler(w http.ResponseWriter, r *http.Request) { http.ServeFile(w,r,"external/index.html") }
请求转发
体质指数界面(web-app
)微服务收到前端界面发过来的请求时,通过core.NewRestInvoker()
将请求转发到calculator
服务。在转发调用的过程中,用户并不需要感知calculator
服务具体的地址和端口,服务发现的过程由 go-chassis 框架自动完成。
func BmiRequestHandler(w http.ResponseWriter, r *http.Request) { queries := r.URL.Query() heightStr := queries.Get("height") weightStr := queries.Get("weight") requestURI := fmt.Sprintf("cse://calculator/bmi?height=%s&weight=%s", heightStr, weightStr) restInvoker := core.NewRestInvoker() req, _ := rest.NewRequest("GET", requestURI) resp, _ := restInvoker.ContextDo(context.TODO(), req) w.Header().Set("content-type", "application/json") w.WriteHeader(resp.GetStatusCode()) w.Write(resp.ReadBody()) }
- 服务配置与启动
体质指数界面(web-app
)服务的配置文件同样包括chassis.yaml
和microservice.yaml
两个yaml
文件,具体内容如下:
chassis.yaml
APPLICATION_ID: bmi #应用 ID cse: service: registry: address: http://127.0.0.1:30100 #注册中心(ServiceCenter)的地址
microservice.yaml
service_description: name: web-app #微服务名称 version: 0.0.1 #微服务版本号
与calculator
服务不同,体质指数界面(web-app
)在Go-chassis
框架内是一个消费者类型的服务,故只需调用chassis.Init()
对Go-chassis
框架进行初始化。
func main() { http.HandleFunc("/", BmiPageHandler) http.HandleFunc("/calculator/bmi", BmiRequestHandler) if err := chassis.Init(); err != nil { lager.Logger.Error("Init fail", err) return } port := flag.String("port", "8889", "Port web-app will listen") address := flag.String("address", "0.0.0.0", "Address web-app will listen") fullAddress := fmt.Sprintf("%s:%s", *address, *port) http.ListenAndServe(fullAddress, nil) }
界面效果
通过上述步骤,体质指数(BMI)应用已经开发完毕,可以通过http://localhost:8889
访问该应用,届时将能看到如下界面,并可输入身高和体重信息验证服务是否正常运行。
使用Go-chassis 进行微服务治理
除了基础的业务逻辑开发外, Go-chassis
提供了丰富的微服务治理功能。下面将在刚刚开发的体质指数应用(BMI)基础上,介绍如何使用Go-chassis
进行微服务治理。
负载均衡
当对体质指数计算器(calculator
)进行水平扩展时,需要将请求均衡地分发到多个体质指数计算器(calculator
)上。Go-chassis
的负载均衡功能包括负载均衡策略和负载均衡过滤器两种方式。微服务实例在经过过滤器中制定的规则初筛之后,根据策略算法选出一个合适的实例进行下一步处理。Go-chassis
内置了RoundRobin
,Random
,SessionStickiness
,WeightedResponse
四种策略,其中默认使用的策略为RoundRobin
。此外,无论是负载均衡策略还是负载均衡过滤器,开发者都可以根据自己的需求进行定制。
- 开启
负载均衡功能默认开启,不需要另外配置。若用户希望使用RoundRobin
之外的策略,可以在配置文件chassis.yaml
中进行指定。
cse: loadbalance: # 全局负载均衡配置 strategy: name: Random microserviceA: # 微服务级别的负载均衡配置 strategy: name: SessionStickiness
对体质指数计算器(calculator
)进行水平拓展时,用户只需复制原有代码,并在配置文件中修改监听端口,使得新的实例在另一个端口运行即可。
- 微服务实例的本地缓存
为了便于区分不同的运行实例,在体质指数计算器(calculator
)的实现中新增了返回实例 ID 的代码。
import ( "github.com/ServiceComb/go-chassis/core/registry" ) …… items := registry.SelfInstancesCache.Items() for microServiceID, _ := range items { instanceID, exist := registry.SelfInstancesCache.Get(microServiceID) if exist { result.InstanceId = instanceID.([]string)[0] } }
Go-chassis 框架初始化时,会将注册中心中的实例缓存到本地,并每 30 秒同步一次注册中心和本地缓存的数据(可配置)。代码中使用了本地缓存的数据来获取实例 ID。
- 验证
上述操作完成后,在界面上点击 Submit 按钮,可以发现实例 ID 交替变换。
流量控制
- 开启
流量控制机制通过控制数据传输速率来避免微服务过载运行。用户可以进行服务端流量控制,限制接收处理请求的频率,在体质指数计算器(calulator
)的chassis.yaml
中可进行服务端流量控制的配置:
cse: handler: chain: Provider: default: ratelimiter-provider #添加服务段流量控制的 handler flowcontrol: Provider: qps: enabled: true # 是否启用流量控制 limit: web-app: 1 # key 为指定要限制的微服务调用者名称,此处 web-app 为调用 calculator 的微服务的名称;value 为每秒允许的请求数。
也可以进行消费端流量控制,限制发往指定微服务的请求的频率。在体质指数界面(web-app
)中的chassis.yaml
中配置:
cse: handler: chain: Consumer: default: ratelimiter-consumer #添加消费者段流量控制的 handler flowcontrol: Consumer qps: enabled: true # 是否启用流量控制 limit: calculator: 1 # key 为指定要限制的请求发往的微服务的名称;value 为每秒允许的请求数
验证
访问 http://localhost:8889 ,在身高和体重的输入框中输入正数,尝试在 1 秒内多次点击 Submit 按钮。此时,能看到所有的请求将被后台延迟处理,处理的频率为 1 次 / 秒。
熔断与降级
- 开启
熔断与降级主要用于解决或缓解服务雪崩的情况,即个别微服务表现异常时,系统能对其进行处理,从而避免资源的耗尽。用户可以在服务端体质指数计算器(calculator
)的chassis.yaml
文件中指明使用服务治理的处理链及指定熔断和容错策略:
cse: handler: chain: Provider: default: bizkeeper-provider circuitBreaker: Provider: calculator: requestVolumeThreshold: 3 #10s 内发生错误的阈值,超出阈值触发熔断 fallback: Provider: enabled: true fallbackpolicy: Provider: policy: returnnull #出错后的处理策略
- 验证
- 使服务进入熔断状态。访问 http://localhost:8889 ,在身高或体重的输入框中输入一个负数,连续点击三次或以上 Submit 按钮,此时在网页下方能看到类似左图的界面。
- 验证服务处于熔断状态。在身高和体重的输入框中输入正数,再次点击 Submit 按钮,此时看到的界面依然是类似左图的界面。同时在 体质指数计算器 (
calculator
) 后台运行日志中也能看到circuit open
的日志。 - 验证服务恢复正常。约 5 秒后 ,在身高和体重的输入框中输入正数,点击 Submit 按钮,此时界面显示正常。
分布式调用链追踪
开启
分布式调用链追踪用于有效地监控微服务的网络延时并可视化微服务中的数据流转。启用分布式调用链追踪,需要在体质指数计算器( calculator
) 的 chassis.yaml
文件中添加如下配置,指定追踪数据上报的开关以及上报地址、上报类型等。
cse: handler: chain: Provider: default: tracing-provider tracing: enabled: true collectorType: zipkin collectorTarget: http://localhost:9411/api/v1/spans
同样的,在体质指数界面(web-app
)中的chassis.yaml
中进行如下配置:
cse: handler: chain: Provider: default: tracing-consumer tracing: enabled: true collectorType: zipkin collectorTarget: http://localhost:9411/api/v1/spans
此外,还需要运行Zipkin
分布式追踪服务,可以采取Docker
方式:
docker pull openzipkin/zipkin docker run -d -p 9411:9411 openzipkin/zipkin
验证
首先访问 http://localhost:8889
,在身高和体重栏处输入正数,并点击 Submit 按钮。然后访问 http://localhost:9411
,进入Zipkin
查看分布式调用追踪情况,可得下方界面。
Metrics
- 开启
监控数据能直观的显示微服务应用的性能数据,帮助更好的进行微服务治理、制定弹性伸缩策略等。Go-chassis
支持输出Prometheus
格式的监控数据,提供的数据包括 Golang 运行时的数据、微服务进程的内存占用、CPU 占用情况等。要使用监控功能,需要在体质指数计算器(calculator
) 的chassis.yaml
文件中添加下列内容:
cse: metrics: apiPath: /metrics enable: true enableGoRuntimeMetrics: true #是否开启 go runtime 检测
- 验证
首先访问 http://localhost:8889 ,在身高和体重栏处输入正数,并点击 Submit 按钮。然后访问 http://localhost:8080/metrics (其中 8080 为体质指数计算器(calculator
)服务端监听的端口),可以得到下图所示的监控数据:
加入ServiceComb 社区
本文介绍了 ServiceComb Go-chassis
的基本特性以及如何使用ServiceComb Go-chassis
开发体质指数应用(BMI)以及进行微服务治理。整个示例的源代码可以从 Github 上获取。同时,作为一个新开源的项目,ServiceComb Go-chassis
诚挚的欢迎大家一起参与社区讨论,贡献代码,共同努力打造“最好用的 Go 语言微服务框架”。
- ServiceComb Go-chassis github 地址: https://github.com/ServiceComb/go-chassis
- ServiceComb 官方网站: http://servicecomb.incubator.apache.org/
- ServiceComb 邮件列表:dev-subscribe@servicecomb.incubator.apache.org
感谢徐川对本文的审校。
评论