grpc
概括
个高性能、开源的通用RPC框架 (A high-performance, open-source universal RPC framework)
- 多语言:语言中立,支持多种语言。
- 轻量级、高性能:序列化支持 PB(Protocol Buffer)和 JSON,PB 是一种语言无关的高性能序列化框架。
- 可插拔
- IDL:基于文件定义服务,通过 proto3 工具生成指定语言的数据结构、服务端接口以及客户端 Stub。
- 设计理念
- 移动端:基于标准的 HTTP2 设计,支持双向流、消息头压缩、单 TCP 的多路复用、服务端推送等特性,这些特性使得 gRPC 在移动端设备上更加省电和节省网络流量。 -服务而非对象、消息而非引用:促进微服务的系统间粗粒度消息交互设计理念。 -负载无关的:不同的服务需要使用不同的消息类型和编码,例如 protocol buffers、JSON、XML 和 Thrift。
- 流:Streaming API。
- 阻塞式和非阻塞式:支持异步和同步处理在客户端和服务端间交互的消息序列。
- 元数据交换:常见的横切关注点,如认证或跟踪,依赖数据交换。
- 标准化状态码:客户端通常以有限的方式响应 API 调用返回的错误。
不要过早关注性能问题,先统一标准化。
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
helloworld/helloworld.proto
grpc HealthCheck
gRPC 有一个标准的健康检测协议,在 gRPC 的所有语言实现中基本都提供了生成代码和用于设置运行状态的功能。
主动健康检查 health check,可以在服务提供者服务不稳定时,被消费者所感知,临时从负载均衡中摘除,减少错误请求。当服务提供者重新稳定后,health check 成功,重新加入到消费者的负载均衡,恢复请求。health check,同样也被用于外挂方式的容器健康检测,或者流量检测(k8s liveness & readiness)。
两个节点之间也是需要一种检测机制,两个服务(peer)之间网络产生抖动,可以使用
HealthCheck
手动从注册中心将服务踢掉。服务注册
平滑发布
- 外置服务调用 Provider
health check
接口,发现接口不通就不会注册,发现通就会注册,外挂有个旁者帮你去注册。
一般服务启动会进行一些数据初始化,环境初始化,等所有完成,然后将接口的 health check
接口状态改为可用,这时候docker 容器就可以放流量进来了,这就是常用的外挂方式。
总结:服务注册
- 服务内部自己注册
- 外挂注册, docker 启动有个脚本
entrypoint
,entrypoint
自动帮我们做服务注册的逻辑,他什么时候知道可以注册呢?他就是内部调用我们的 provider grpchealth check
接口判断状态,状态好了就放流量进来,没好就等你服务启动成功
平滑下线
- 如果我们这时候有个新功能需要上线,这时候需要先下线走
滚动更新
。
- k8s 发送 kill 进程id
- go main 拦截
sigterm
信号,先告诉服务发现注册中心服务要注销,然后服务发现通知其他服务将自己本地的负载均衡连接池 close 掉,并且流量不要再发送过去了。但是服务发现通知其他 consumer 需要一定的同步时间,所以下一步我们将自己服务的health check
接口标记为下线
状态。 - 最终我们一般要等到 2 个服务发现心跳周期,这是最差的情况,一般是实时的退出。
总结:平滑发布流程
- 收到一个
kill pid
信号 - 向
服务发现
或注册中心
发送一个注销请求把自己注销掉 - 把自己的
health check
接口标记为 失败,自己进入到一个优雅退出的状态 - 我们使用
http
或者grpc
的shutdown
接口,shutdown
接受入参 context 作为参数,我们把 context 的超时时间设置为两个服务发现心跳时间。 - 发现进程一直退不出,强制 kill
本章学习技能点
- 滚动更新
- 平滑发布
- 平滑下线
- graceful 优雅发布
- entrypoint docker 外挂脚本
服务发现
客户端发现
一个服务实例被启动时,它的网络地址会被写到注册表上;当服务实例终止时,再从注册表中删除;这个服务实例的注册表通过心跳机制动态刷新;客户端使用一个负载均衡算法,去选择一个可用的服务实例,来响应这个请求。
直连,比服务端服务发现少一次网络跳转,Consumer 需要内置特定的服务发现客户端和发现逻辑。
客户端发现
服务端发现
客户端通过负载均衡器向一个服务发送请求,这个负载均衡器会查询服务注册表,并将请求路由到可用的服务实例上。服务实例在服务注册表上被注册和注销(Consul Template+Nginx,kubernetes+etcd)。
Consumer 无需关注服务发现具体细节,只需知道服务的 DNS 域名即可,支持异构语言开发,需要基础设施支撑,多了一次网络跳转,可能有性能损失。
会经过一个集中负载均衡器,这种模式叫做服务端发现
会遇到一个问题就是 NGINX 一台肯定不行,扛不住这么大的量,然后会有 NGINX 集群,这时候就需要在 NGINX 集群前面加一个 lvs, 因为 NGINX 集群是随便可能加的,所以需要 lvs, 对使用方来说只有一个 lvs 的 IP 就够了
服务端发现
服务发现
微服务的核心是去中心化,我们使用客户端发现模式。
服务发现
早期我们使用最熟悉的 Zookeeper 作为服务发现,但是实际场景是海量服务发现和注册,服务状态可以弱一致, 需要的是 AP 系统。
遇到的问题:
- 分布式协调服务(要求任何时刻对 ZooKeeper的访问请求能得到一致的数据,从而牺牲可用性)。
- 网络抖动或网络分区会导致的 master 节点因为其他节点失去联系而重新选举或超过半数不可用导致服务注册发现瘫痪。
- 大量服务长连接导致性能瓶颈。
我们参考了 Eureka 实现了自己的 AP 发现服务,试想两个场景,牺牲一致性,最终一致性的情况:
- 注册的事件延迟
- 注销的事件延迟
- 通过 Family(app_id) 和 Addr(IP:Port) 定位实例,除此之外还可以附加更多的元数据:权重、染色标签、集群等。
app_id: 使用三段式命名,business.service.xxx
- Provider 注册后定期(30s)心跳一次,注册, 心跳,下线都需要进行同步,注册和下线需要进行长轮询推送。
新启动节点,需要 load cache,JVM 预热。 故障时,Provider 不建议重启和发布。
- Consumer 启动时拉取实例,发起30s长轮询。
故障时,需要 client 侧 cache 节点信息。
- Server 定期(60s) 检测失效(90s)的实例,失效则剔除。短时间里丢失了大量的心跳连接(15分钟内心跳低于期望值*85%),开启自我保护,保留过期服务不删除。
本章学习技能点
- lvs
- router/load balancer (
ribbon
) - 客户端发现
- 服务端发现
本章相关链接
https://github.com/bilibili/discovery
https://www.jianshu.com/p/4828f90674b3
https://www.jianshu.com/p/27a742e349f7