概要
结合实际生产应用出现的一些问题:
- 新加功能不知放在哪个项目模块合适,项目间模块的界限很难明确;
- 代码越来越臃肿,代码耦合维护越加困难,职责不清晰,改一个地方可能涉及跨项目模块的修改;
- 功能叠加,代码库越来越庞大,发布流程变得更久;
- 开发整理旧逻辑比较费力,如果疏漏逻辑会带来一定的安全隐患;
对庞大的单体应用或者业务领域进行拆分,分而治之来降低系统的复杂性和可维护性,目前是比较常见的做法,比如对移动联盟中marketing业务的拆分。
下面分析下微服务。
微服务
了解微服务之前,我们先看下几个概念:
- 单一职责::“把因相同原因而变化的东西聚合到一起,而把因不同原因而变化的东西分离开来。” (Robert C. Martin对单一职责原则(SingleResponsibility Principle,http://programmer.97things.oreilly.com/wiki/index.php/ The SingleResponsibility Principle)的论述)
- RPC:在分布式计算,远程过程调用(Remote Procedure Call,简称 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC是一种服务器-客户端(Client/Server)模式,经典实现是一个通过发送请求-接受回应进行信息交互的系统。
微服务遵循的就是单一职责原则,把业务相关的聚合在一起,不相关的拆分开,高内聚松耦合,同时通过业务的界限来划定服务的界限。相对于简单的拆分是微服务拆分的粒度更细,通过一系列松耦合、自治的微服务来构建微服务应用,每个微服务只专注做好一件事(功能)。
微服务的自治:每个微服务高内聚,只负责一个功能;服务之间通过网络调用进行通信,避免紧耦合;服务可以彼此间独立进行修改和部署,而不影响其他服务。
自治性使得我们开发人员需要独立的对服务进行开发、部署、扩容,对服务的业务、运维等指标的反馈带来一定的运维挑战。
微服务拆分
微服务拆分目的:对业务领域进行拆分,划定微服务的范围,分而治之降低系统的复杂性和可维护性,解耦业务,使项目间的职责更加明确,方便维护扩展。
微服务拆分规范:
- 梳理当前各项目的业务模块(当前项目按照垂直拆分粒度比较大的服务),梳理耦合部分业务逻辑,保证各个服务职责明确。
- 基于当前移动联盟业务梳理进行业务、可扩展、可靠性拆分:
- 业务拆分:系统中的业务模块按照职责范围识别出来,每个单独的业务模块拆分为一个独立的服务;
- 可扩展拆分:将系统中的业务模块按照稳定性排序,将已经成熟和改动不大的服务拆分为稳定服务,将经常变化和迭代的服务拆分为变动服务;
- 可靠性拆分:将系统中的业务模块按照优先级排序,将可靠性要求高的核心服务和可靠性要求低的非核心服务拆分开来,然后重点保证核心服务的高可用。我理解比如k8s点击的ingress、pod隔离;
- 两者结合,首先按当前拆分项目整合职责,后考虑把比较独立的相关业务抽离出来;
服务契约
每个微服务对外暴露一个契约,契约定义服务期望接受和返回的信息,客户端和服务端遵从的规范。
契约规范化,同时客户端、服务端通过RPC协议进行通信,可以是不同平台不同语言间进行通信,主要的RPC协议有:
- http
- http2
- gRPC(http2)
- socket
目前比较成熟的就是谷歌出品的gRPC,gRPC 使用protocol buffers作为它的接口定义语言(IDL)和底层的消息交换格式。
gRPC客户端与服务端通信流程:
实施
目前的微服务框架主流: go micro、 go kit等。
微服务不是银弹,不能说一下子切成微服务指望解决所有问题,首先开发时间成本高,其次开发阶段会导致业务停滞,然后上线切换,新系统问题会多。早期还是运用微服务拆分的理念来约束项目服务边界,使用gRPC来进行RPC调用,来降低项目间的耦合性,后面在考虑某块业务拆分。
整理当前项目模块间的耦合关系:
项目工程化-目录结构规范:
讨论项目工程化规范能够在团队中约束规范化,也利于后期重构工作。
- micro // 服务
- cmd
└── service // 对内的微服务,仅接受来自内部其他服务或者网关的请求,比如暴露了 gRPC 接口只对内服务。
└── group.go // 如提供对内的渠道组服务
└── api // 对外暴露服务,接受来自用户的请求,比如暴露了 HTTP/gRPC 接口
└── job // 流式任务处理的服务。
└── cron // 定时任务
└── main.go
- api // API 定义的目录,使用 grpc 存放 proto 文件及生成代码文件
└── product_name // 产品名称,如推广分析-渠道管理
└── app_name // 应用名称,如渠道组
# └── v1 // 版本号
└── group.proto
└── group_pb.go
└── group_grpc_pb.go
└── client.go // 客户端指定连接方式
└── README.md // 使用说明,如跨语言PHP如何接入
- pkg
└── service // 逻辑层
└── repository // 数据处理层
需要解决文件及生成代码文件包引用问题,版本升级带来的版本管理问题:
- 代码指定版本目录:优点可以显式展示变更,缺点目录结构会变得臃肿,对外开放会暴露核心代码;
- sub module:实现复杂,只能更新不能往后;
- api层抽成单独库:对外开放安全,比较独立灵活,通过打标签来管理版本。
倾向第三种,比较灵活。
protobuf规范
protobuf定义及生成序列化、gRPC、客户端代码的结构需要规范化。
syntax = "proto3";
package group;
option go_package = "./api/channel-manage/group";
service Group {
rpc GetAccountList (GroupRequest) returns (GroupReply) {}
}
message GroupRequest {
string Source = 1;
string AccountIDs =2;
}
message GroupInfo {
int64 ID = 1;
string Source = 2;
string AccountID =3;
}
message GroupReply {
repeated GroupInfo GroupInfo = 1;
}
外部调用 使用http:restful风格,还需要做接口对外暴露处理,对输入输出的proto定义规范;项目中需要用的时候再提前调研下。
部署
流水线自动部署
调试
服务之间通过protouf协议通信,二进制编码,性能优越,但可读性不友好,寻找可视化的调试工具。
- 命令行操作:grpcurl,evans
- pc端操作:bloomrpc
参考其他团队整理文章 https://tech.qimao.com/grpc/ ,bloomrpc挺方便。
链路追踪
全链路跟踪设计
尽量少写代码
一个好的全链路跟踪系统不需要用户编写很多跟踪代码。最理想的情况是你不需要任何代码,让框架或库负责处理它,当然这比较困难。 全链路跟踪分成三个跟踪级别:
- 跨进程跟踪 (cross-process)(调用另一个微服务)
- 数据库跟踪
- 进程内部的跟踪 (in-process)(在一个函数内部的跟踪)
https://segmentfault.com/a/1190000020450845
https://codeup.aliyun.com/qimao/go-contrib/leo/blob/main/trace/global.go
https://codeup.aliyun.com/qimao/go-contrib/leo/blob/main/grpc/client.go
待整理:
-
微服务部署· 滚动更新策略:代理服务器负载均衡 + grpc平滑关闭
- 服务监控、链路追踪、多点故障定位、故障恢复
- 服务监控可以主动发现系统中的薄弱环节加以优化、重构:当出现故障时,比如服务出现资源、网络瓶颈等情况,如何能够及时感知(调用了哪个服务、输入输出信息),迅速定位问题,找到有效的解决方案。;
- 通过业务指标、应用日志、运维指标和基础设施指标来了解服务行为;
- php客户端调用调研,环境、规范整理
- 提供对外接口,http结合gin
- 服务发现
·
Reference
RPC https://zh.wikipedia.org/wiki/%E9%81%A0%E7%A8%8B%E9%81%8E%E7%A8%8B%E8%AA%BF%E7%94%A8
Go工程化-项目目录结构 https://lailin.xyz/post/go-training-week4-project-layout.html
gRPC调试 https://tech.qimao.com/grpc/
bloomrpc: https://github.com/uw-labs/bloomrpc
sub moduke https://git-reference.readthedocs.io/zh_CN/latest/Git-Tools/Submodules/
gRPC PHP https://grpc.io/docs/languages/php/quickstart/
基于 protobuf 自动生成 gin 代码 https://lailin.xyz/post/go-training-week4-protoc-gen-go-gin.html
gRPC的平滑关闭和服务摘流 https://cloud.tencent.com/developer/article/1816510 https://pkg.go.dev/google.golang.org/grpc#Server.GracefulStop
nginx grpc streaming负载均衡的排坑和思考 http://xiaorui.cc/archives/5970
https://zhuanlan.zhihu.com/p/358295000
http://xiaorui.cc/archives/5970
http://nginx.org/en/docs/http/ngx_http_grpc_module.html
https://www.cnblogs.com/gao88/p/12010917.html
在使用nginx proxy_pass upstream的时候,需要配置keepalive,不然nginx做负载均衡转发一律会按照短连接处理。 没想到grpc upstream也要配置keepalive。
https://segmentfault.com/a/1190000016637212
https://blog.csdn.net/kevin_tech/article/details/115436043
https://golang2.eddycjy.com/posts/ch3/05-call-grpc/#352-grpcdial-%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88
带入gRPC:分布式链路追踪 gRPC + Opentracing + Zipkin https://cloud.tencent.com/developer/article/1683454·