@TOC
微服务治理框架(C 版)详细设计
概述
gRPC 是一款高性能、开源的 RPC 框架,产自 Google,基于 ProtoBuf 序列化协议进行开发,支持多种语言(C 、Golang、Python、Java等) gRPC 对 HTTP/2 协议的支持使其在 Android、IOS 等客户端后端服务的开发领域具有良好的前景。 gRPC 提供了一种简单的方法来定义服务,同时客户端可以充分利用 HTTP2 stream 的特性,从而有助于节省带宽、降低 TCP 的连接次数、节省CPU的使用等。
(1)服务端:服务端需要实现.proto中定义的方法,并启动一个gRPC服务器用于处理客户端请求。gRPC反序列化到达的请求,执行服务方法,序列化服务端响应并发送给客户端。
(2)客户端:客户端本地有一个实现了服务端一样方法的对象,gRPC中称为桩或者存根,其他语言中更习惯称为客户端。客户调用本地存根的方法,将参数按照合适的协议封装并将请求发送给服务端,并接收服务端的响应。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OnrdYDL7-1616151589083)(https://raw.githubusercontent.com/grpc-nebula/grpc-nebula/master/images/grpc_transport.png)]
通信模型示意图
服务端
1. 服务端读取配置
- 1.1 原理分析
2. Provider注册设计说明
- 2.5代码修改思路 与原生代码耦合处: 需要在server启动的时候获取服务端口,服务名和方法名,并调用注册接口,此处会影响原生代码。而且同步和异步调用服务启动不同,需要分别获取和添加。
3 服务关闭时,自动注销zk中的Provider信息
4 服务流量控制
5. 服务端配置信息监听
6 服务过时打印告警日志
7 服务注册支持配置自定义IP与端口
- 场景描述
使用场景1:针对利用Nginx做grpc反向代理的场景,服务提供者可以通过配置文件将Nginx的地址注册到Zookeeper
使用场景2:针对服务器跨网段调用时会被映射为另一个IP的场景,应允许服务将自身地址配置为一个映射的IP
- 实现思路
在配置文件,增加自定义IP与端口的配置信息
代码语言:javascript复制# 可选,类型string,说明:服务注册时指定的IP,优先级高于common.localhost.ip参数
# common.service.ip=
# 可选,类型int,说明:服务注册时指定的端口
# common.service.port=
在服务端向注册中心进行注册时,会将服务真实的IP与端口添加到real.ip
和real.port
参数中,如果配置了自定义的IP与端口,则使用该配置的IP与端口对服务进行注册;如果未配置,则使用真实的ip与端口进行注册;无论是否有配置,真实的IP与端口都将添加到real.ip
与real.port
参数中。
- 相关代码
涉及到的模块与代码:
代码语言:javascript复制orientsec-provider 模块:
修改 provider_registry()
添加 orientsec_add_property_into_url_inner()
客户端
1 读取本地的配置文件信息
2. 客户端启动时,注册客户端信息
3. 客户端关闭时,注销客户端信息
4. 基于zookeeper的NameResolver
5. 监听服务端信息
6. 实现客户端对路由规则的解析方法,用来过滤服务端列表
- 6.1原理分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LIPmpPH6-1616151589085)(https://raw.githubusercontent.com/grpc-nebula/grpc-nebula/master/images/workflow.png “consumer workflow”)]
7. 支持路由规则可以设置为IP段、项目
多条路由规则工作原理:
代码语言:javascript复制流程如上流程图所示, 如果多条规则,grpc-c会一条一条的匹配, 对于每一条,客户端先看 =>前面的匹配条件,是不是限制本身的,不是的话,跳过;
是的话,继续看=>后面的 服务端IP过滤条件,对provider 列表中的provider进行遍历过滤,如果是限制访问的,置黑名单标志位。
多条规则是 “与” 的工作模式,有一条限制了访问,就不能访问了。
8. 监听路由规则,获取可访问的服务列表
9. 监听服务端权重信息
10. 监听服务端是否过时
11. 客户端监听配置信息的更新
12. 限制客户端对某个服务每秒钟的请求次数(Requests Per Second)
13. 两种负载均衡模式
- 13.1 原理分析 支持两种模式:一种是“请求负载均衡”,另一种是“连接负载均衡”。 “请求负载均衡”指的是每次调用服务端都调用负载均衡算法选择一个服务端。“连接负载均衡”指的是,创建通道(Channel)后第一次调用选择服务端之后,一直复用与之前已选定的服务端建立的连接。 默认情况下为“连接负载均衡”。
- 13.2 实现思路 客户端每次调用服务端时,grpc-c中都会调用 start_pick_locked 方法选择subchannel,及选择服务端发送数据,首先判断当前客户端的负载均衡模式,原生的流程是根据DNS等域名解析器解析出提供服务的endpoint(IP port),然后根据这些endpoint创建一一对应的subchannel,根据原生的算法进行挑选合适的服务提供端进行调用,并传输数据。如果负载均衡模式为“请求负载均衡”,先调用负载均衡算法重新选择服务端,然后再继续原来的流程。
14. 四种负载均衡算法
14.1原理分析 框架支持以下四种负载均衡算法: (1) 随机算法 pick_first 实现原理:数据集合下标随机数 (2) 轮询算法 round_robin 实现原理:数据集合下标加1,取余运算 (3) 加权轮询算法 weight_round_robin 实现原理:采用ngix的平滑加权轮询算法。 (4) 一致性Hash算法 consistent_hash 实现原理:采用MD5算法来将对应的key哈希到一个具有232)-1的数字空间中。同时,引入虚拟机器节点,解决数据分配不均衡的问题。
14.2实现思路
配置文件:
代码语言:javascript复制consumer.loadbalance.mode=request
consumer.default.loadbalance=pick_first
配置文件: consumer.loadbalance.mode=request consumer.default.loadbalance=round_robin
配置文件:
代码语言:javascript复制consumer.loadbalance.mode=request
consumer.default.loadbalance=weight_round_robin
//可选,类型int,缺省值100,说明:服务provider权重,是服务provider的容量,在负载均衡基于权重的选择算法中用到
provider.weight= 400
Note: 根据经验或者服务器性能对所有服务器进行权重估算,处理能力越强,权重(黑体加粗数值)越大。算法会根据配置的权重比进行任务分配,权重越大,被调用的次数越多。
配置文件:
代码语言:javascript复制consumer.loadbalance.mode=request
consumer.default.loadbalance=consistent_hash
consumer.consistent.hash.arguments=name,no
15. grpc断线重连指数退避算法支持参数配置功能
- 15.1原理分析 当grpc连接到服务端发生失败时,通常希望不要立即重试(以避免泛滥的网络流量或大量的服务请求),而是做某种形式的指数退避算法。 相关参数: (1)INITIAL_BACKOFF (第一次失败重试前等待的时间) (2)MAX_BACKOFF (失败重试等待时间上限) (3)MULTIPLIER (下一次失败重试等待时间乘以的倍数) (4)JITTER (随机抖动因子) 其中MAX_BACKOFF的值为120,单位秒,参数值目前是直接“硬编码”在框架中的,为了优化系统性能,支持不同的义务系统配置不同的参数值,将该参数的取值修改为可配置的。
- 15.2实现思路 在配置文件“dfzq-grpc-config.properties”增加如下参数,修改程序增加对这些配置参数的读取。在框架调用指数退避算法时,参数值优先使用配置文件中的数值:
consumer.backoff.maxsecond =120
16. 服务容错
- 16.1 原理分析
配置服务调用出错后自动重试次数后,可以启用服务容错功能,当调用某个服务端出错后,框架自动尝试切换到提供相同服务的服务端再次发起请求。
调用某个服务端,如果连续5次请求出错,自动切换到提供相同服务的新服务端。(5这个数值支持配置)
调用某个服务端,如果连续5次请求出错,如果此时没有其他服务端,增加一个惩罚连接时间(例如60s)。
- 16.2实现思路
定义一个客户端调用服务端出现错误的数据集合:
代码语言:javascript复制/**
* 各个【客户端对应服务提供者】服务调用失败次数
* key值为:consumerId@IP:port
* value值为: 失败次数
* 其中consumerId指的是客户端在zk上注册的URL的字符串形式,@是分隔符,IP:port指的是服务提供者的IP和端口
*/
ConcurrentHashMap<String, AtomicInteger> requestFailures = new ConcurrentHashMap<>();
当客户端调用服务端抛出异常时,将错误次数累加到以上的数据集合中。当客户端调用同一个服务端失败达到5次时,进行以下处理:
如果服务端个数大于1,将出错的服务端从客户端内存中的服务端候选列表中移除,然后重新选择一个服务端;
如果服务端个数为1,先记录一下当前的时间,然后出错的服务端从客户端内存中的服务端候选列表中移除。
如果服务端个数为0,但是注册中心上服务端个数大于0,并且当前时间与从内存中删除服务端的时间差大于惩罚时间时,将注册中心上服务端列表更新到客户端内存中,然后调用负载均衡算法重新选择服务端。
- 16.3相关代码
涉及到的模块与代码:
代码语言:javascript复制orientsec-consumer 模块:
新增 failover_utils类
新增 record_provider_failure()方法
修改 BlockingUnaryCallImpl()方法嵌入调用接口
17. 支持失败服务恢复到服务端列表时间自定义配置
- 17.1原理分析
调用某个服务端,如果连续出错5次(5次内有一次调用成功,会重置失败次数,以达到连续的效果;5这个数值支持配置),会把该服务从服务端列表中摘除该服务端节点,通过FATAL ERROR信息的日志记录服务调用失败的相关情况;被移除的服务在10分钟后(时间支持配置),自动恢复到服务端列表中。
- 17.2实现思路
在配置文件,增加服务恢复时间的配置
代码语言:javascript复制# 可选,类型integer,缺省值5,说明:连续多少次请求出错,自动切换到提供相同服务的新服务器
# consumer.switchover.threshold=5
# 可选,类型int,说明:服务端节点调用失败被移除请求列表后,经过多长时间将该服务端节点重新添加回服务端候选列表
# 单位毫秒,默认值600000,即600秒,即10分钟
# consumer.service.recoveryMilliseconds=600000
服务调用失败时,比较当前失败服务的调用次数,如果服务端失败达到5次时,进行以下处理:
(1)将该服务从服务端列表中移除,并通过FATAL ERROR信息的日志进行输出;
(2)通过一个延迟执行的线程,在10分钟后,将该服务恢复到服务端列表中;
(3)重置该服务的失败次数,并重选服务提供者。
- 17.3相关代码
涉及到的模块与代码:
代码语言:javascript复制orientsec-grpc-core 模块:
修改 com.orientsec.grpc.consumer.ErrorNumberUtil#recordInvokeInfo
修改 com.orientsec.grpc.consumer.ErrorNumberUtil#removeCurrentProvider
新增 com.orientsec.grpc.consumer.ErrorNumberUtil#resetFailTimes
18. 服务调用出错后支持自动重试
- 18.1原理分析
当服务调用出错时,可通过配置的重试次数进行重试,调用重试次数的配置支持到服务级别以及服务方法级别;重试次数配置优先级如下:方法级别 > 服务级别 > 默认重试配置
- 18.2实现思路
在配置文件,增加服务调用重试次数的相关配置,具体如下:
代码语言:javascript复制# 可选,类型int,缺省值0,0表示不进行重试,说明:服务调用出错后自动重试次数
# consumer.default.retries=0
# 可选,类型int,说明:指定服务名称的服务调用出错后,自动重试次数,[]中配置指定的服务名称
# consumer.default.retries[helloworld.Greeter]=0
# 可选,类型int,说明:指定服务的方法调用出错后,自动重试次数,[]中配置指定服务名称及方法名
# 最小可到指定到方法名
# consumer.default.retries[helloworld.Greeter.sayHello]=0
当某一服务在调用出错时,框架会进行调用重试,重试的次数根据配置来确定。在进行重试时,会根据当前出错服务的方法、服务名、默认配置来选择重试次数;获取重试次数的优先级:方法级别 > 服务级别 > 默认重试配置,确认重试次数后,会进行服务调用重试。
例:当前服务名:helloworld.Greeter,方法名为sayHello。当sayHello方法调用出错时,优先从配置文件获取consumer.default.retries[helloworld.Greeter.sayHello]属性值作为重试次数进行调用重试;如果未配置,则获取consumer.default.retries[helloworld.Greeter]属性值,若该属性也未配置,则取consumer.default.retries的配置作为重试次数。
- 18.3相关代码
涉及到的模块与代码:
代码语言:javascript复制orientsec-consumer 模块:
修改 BlockingUnaryCall() 判断调用结果,失败的话重新调用
系统组件
1. HTTP/2
- 1.1 概念 • 消息(Message):由一个或多个帧组合而成,例如请求和响应; • 流(Stream):存在于连接中的一个虚拟通道,流可以承载双向消息,每个流都有一个唯一的整数ID; • 帧(Frame):HTTP/2通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流; • 连接(Connection):与 HTTP/1 相同,都是指对应的 TCP 连接;
- 1.2 特征 • 多路复用、乱序收发:可以乱序收发数据报文,不用使用单步:发1->收1 或者流水线:发1->发2->收2->收1 的流程,提高效率; • Header压缩:不用花大量篇幅重复发送常用header,采用发送增量的方法,由客户端和服务器端共同维护一个字典; • stream优先级:可以在一个连接上,为不同stream设置不同优先级; • 服务器推送:提前发送需要的资源;
2. Grpc-c 工作流程
- 2.1 发送流程 • 解析地址:client消息发送给gRpc,然后resolver解析域名,并获取到目标服务器地址列表; • 负载均衡:客户端基于负载均衡算法,从连接服务器列表中找出一个目标服务器; • 连接:如果到目标服务器已有连接,则使用已有连接,访问目标服务器;如果没有可用连接,则创建HTTP/2连接; • 编码:对请求消息使用 Protobuf做序列化,通过 HTTP/2 Stream 发送给 gRPC 服务端;
- 2.2 接收流程 • 编码:接收到服务端响应之后,使用Protobuf 做反序列化; • 回调:回调 GrpcFuture 的 set(Response) 方法,唤醒阻塞的客户端调用线程,获取 RPC 响应。
- 2.3 流程示意图 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tt6KT3xZ-1616151589087)(https://raw.githubusercontent.com/grpc-nebula/grpc-nebula/master/images/index.png “HTTP/2 workflow”)]
3. 支持Zookeeper开启ACL
- 3.1原理分析
zookeeper是一个高效的分布式协调服务,他暴露了一些共用服务,比如命名、配置、管理、同步控制、群组服务等我们可以使用zk来实现比如打成共识,集群管理、leader选举等。
什么是ACL,Zookeeper作为一个分布式协调框架,其内部存储的都是一些关乎分布式系统运行时状态的元数据,尤其是设计到了一些分布式锁,Master选举和协调应用场景,有效的保障Zookeeper中的数据安全,ZookeeperZK提供了三种模式,权限模式,授权对象,权限权限模式:Scheme 开发人员最多使用的如下四种权限模式
- IP: ip模式通过ip地址,来进行权限控制
- Digest: digest是最常用的权限控制模式,zk会对形成的权限标识先后进行两次编码处理,分别是SHA-1加密 算法、BASE64编码。
- World:World是一值最开放的权限控制模式、这种模式可以看做为特殊的Digest,他仅仅是一个标识而已
- Super:超级用户模式,在超级用户模式下可以ZK进行操作 权限: 权限就是指那些通过权限检测后可以允许执行的操作,在ZK中,对数据的操作权限分为以下五大类: CREATE、Delete、READ、WRITE、ADMIN permission: zookeeper目前支持下面一些权限:
- CREATE©: 创建权限,可以在在当前node下创建child node
- DELETE(d): 删除权限,可以删除当前的node
- READ®: 读权限,可以获取当前node的数据,可以list当前node所有的child nodes
- WRITE(w): 写权限,可以向当前node写数据
- ADMIN(a): 管理权限,可以设置当前node的permission
综合考虑,digest权限控制方案比较适合grpc框架,因此采用这种方案进行访问控制。
如果配置了zookeeper访问控制用户名和密码,那么在创建Zookeeper Client时,增加ACL验证数据。即客户端和服务端访问zookeeper时,需要进行ACL验证。验证失败的情况,无法正常访问服务。
4. 主备切换
- 4.1 使用场景
多个服务端提供服务的时候,能够区分主服务器和备服务器。当主服务器可用时客户端只能调用主服务器,不能调用备服务器;当所有主服务器不可用时,客户端自动切换到备服务器进行服务调用;当主服务器恢复时,客户端自动切换到主服务器进行服务调用。
- 4.2 实现思路
给服务端添加一个master属性,用来标识服务端是主服务器还是备服务器,master等于true表示主服务器,master等于false表示备服务器,master缺省时为true。
当客户端启动时,首先根据服务名获取所有的服务端列表,然后根据每个服务端的master属性进行筛选操作:
(1) 当服务端列表中全部都是主服务器的时候,服务端列表不发生变化
(2) 当服务端列表中全部都是备服务器的时候,服务端列表不发生变化
(3) 当服务端列表中既有主服务器也有备服务器的时候,将备服务器从服务列表中移除出去,只保留主服务器
同时,客户端监听注册中心中服务端主备属性的变化,一旦监听到变化,重新获取服务端列表,并进行以上筛选操作。
- 4.3 相关代码
涉及到的模块和代码:
代码语言:javascript复制orientsec-provider 模块:
增加provider的master属性
orientsec-consumer 模块:
修改 consumer_query_providers 函数
增加 master 和 online 属性的判断和解析
5. 支持优先级的服务分组
- 5.1 使用场景
- 场景1:服务分组。当服务集群非常大时,客户端不必与每个服务节点建立连接,通过对服务分组,一个客户端只与一个服务组连接。
- 场景2:业务隔离。例如服务端部署在三台服务器上,分别提供给三种业务的客户端。该场景下,可以将三台服务器配置不同的分组,然后不同业务的客户端配置各自的服务端分组。这样即使其中一种业务的客户端调用频繁导致服务端响应时间边长,也不会影响其它两种业务的客户端。
- 场景3:多机房支持。例如某证券公司在上海有两个机房A和B,在深圳有一个机房C,三个机房都对外提供服务;每个机房都划分为两个分组,即三个机房共有6个分组,分别为A1、A2、B1、B2、C1、C2。在上海地区的客户端要调用证券公司的服务时,可以优先调用A1、A2两个分组的服务端;如果A1、A2分组的服务端不可用,调用B1、B2两个分组的服务端;如果B1、B2分组的服务端也不可用,调用C1、C2两个分组的服务端。
- 5.2 实现思路
服务端添加一个group属性,用来标识服务端的服务分组,group缺省值为空,表示没有服务分组。客户端也添加一个group属性,用来标识当前客户端可以调用的服务端分组。
当客户端启动时,首先根据服务名获取所有的服务端列表,然后根据客户端的group属性和每个服务端的group属性,对服务端列表进行筛选操作:
(1) 当客户端group属性为空的时候,服务列表不发生变化
(2) 当客户端group属性不为空的时候,首先获取高优先级分组的服务端,如果获取不到,再获取优先级低的服务端。只要某个优先级分组的服务端获取到,就将获取到的服务端作为客户端的服务端列表。如果所有的优先级的分组服务端都没有获取到,客户端报错,提示找不到服务端。
同时,客户端监听注册中心中服务端和客户端分组的变化,一旦监听到变化,重新获取服务端列表,并进行以上筛选操作。 客户端与服务端允许对指定服务名的分组进行单独配置,配置项如下所示:
代码语言:javascript复制# 客户端consumer 在中括号[]中配置指定服务的服务名
consumer.invoke.group[helloworld.Greeter]=A1
# 服务端provider 在中括号[]中配置指定服务的服务名
provider.group[helloworld.Greeter]=B1
指定服务名的配置方式优先级高于未指定服务名的配置方式。
例:服务名为A的服务进行注册时,如果同时配置了group与group[A]两个属性,优先取group[A]的属性值作为服务的分组信息,同时如果有服务名为B的服务进行注册时,因为没有配置group[B]这个属性,所以会取group的属性值作为服务的分组信息。
- 5.3 相关代码
涉及到的模块和代码:
代码语言:javascript复制orientsec-consumer 模块:
新增 orientsec_grpc_consumer_control_group.cc
修改 consumer_query_providers 函数
增加group属性的比对和解析
6. 实现系统内部grpc服务与系统外部grpc服务的区分
- 6.1使用场景
支持同一项目不同类型的grpc服务具有不同的可见性。
项目中可能会包括两类grpc服务,对于内部项目组件间grpc调用服务,此类服务并不对外暴露,因此应该避免外部项目可见;对于项目对外提供的grpc服务则需要允许外部系统可见。
- 6.2实现思路
公共注册中心参数配置包括:注册中心集群地址、注册根路径、digest模式ACL的用户名、digest模式ACL的密码。参数名称如下:
代码语言:javascript复制zookeeper.host.server (zookeeper.host.server和zookeeper.private.host.server至少配置一个参数)
common.root (可选参数,默认值/Application/grpc)
zookeeper.acl.username(可选参数)
zookeeper.acl.password(可选参数)
私有注册中心参数配置包括:注册中心集群地址、注册根路径、digest模式ACL的用户名、digest模式ACL的密码。参数名称如下:
代码语言:javascript复制zookeeper.private.host.server (zookeeper.host.server和zookeeper.private.host.server至少配置一个参数)
zookeeper.private.root (可选参数,默认值/Application/grpc)
zookeeper.private.acl.username(可选参数)
zookeeper.private.acl.password(可选参数)
如果服务端所有的服务都是公共服务(外部服务),只需要配置“公共注册中心参数”。
如果服务端所有的服务都是私有服务(内部服务),只需要配置“私有注册中心参数”。
如果服务端同时存在公共服务、私有服务,“公共注册中心参数”和“私有注册中心参数”都需要配置。
如果客户端只调用公共服务(外部服务),只需要配置“公共注册中心参数”。
如果客户端只调用私有服务(内部服务),只需要配置“私有注册中心参数”。
如果客户端同时调用公共服务、私有服务,“公共注册中心参数”和“私有注册中心参数”都需要配置。
如果系统存在私有服务(内部服务),需要配置哪些服务属于私有服务、哪些服务属于公共服务。
参数public.service.list
表示公共服务名称列表,多个服务名称之间以英文逗号分隔。该参数可选,如果不配置,表示所有服务都是公共服务。
参数private.service.list
表示私有服务名称列表,多个服务名称之间以英文逗号分隔。该参数可选,如果不配置,将公共服务名称列表之外的服务都视为私有服务。
公共服务向公共注册中心上注册,私有服务向私有注册中心上注册。
给服务端增加一个服务类型(service.type
)(公共/私有)的属性,服务类型根据服务所在的注册中心来判断,在公共注册中心上的服务为共有服务,在私有注册中心上的服务为私有服务。
- 注册中心管理方案
公共注册中心集群、私有注册中心集群分开管理。
公共注册中心集群服务注册路径统一为/Application/grpc
,并且不设置访问控制权限。
私有注册中心集群服务注册路径为/Application/grpc/private/xxx
,xxx表示应用(或开发团队)。区分内外部服务的应用首先需要向zookeeper管理员申请私有注册中心的服务注册路径。
对于私有注册中心集群,不同应用(或开发团队)申请不同的注册路径,zookeeper管理员给不同注册路径设置不同访问控制权限(digest模式)。
对于私有注册中心集群,为了方便服务治理平台管理注册中心,zookeeper管理员将服务治理平台服务器的IP地址列表配置到每个私有注册路径节点的ACL中,实现服务治理平台可以免密访问私有注册中心集群。
- 6.3相关代码
涉及到的模块和代码:
代码语言:javascript复制orientsec-registry 模块:
修改 orientsec_grpc_registry_zk_intf_init()
修改 registry(url_t* url)
新增 zk_prov_reg_init()
新增 zk_cons_reg_init()
7. 支持注册中心断线自动重连最长时间配置
- 7.1原理分析
控制zookeeper的断线重连时间
- 7.2实现思路
配置文件中增加zookeeper断线重连最长时间配置项。
代码语言:javascript复制# 可选,类型int,缺省值30,单位天,即缺省值30天,说明:ZK断线重连最长时间
# zookeeper.retry.time=30
修改创建Zookeeper Client的代码,根据配置的重连最长时间计算重连的次数,创建重试指定次数的重试策略(RetryNTimes),在创建Zookeeper Client选用该重试策略并启用。
- 7.3相关代码
涉及到的模块与代码:
代码语言:javascript复制orientsec-registry 模块:
修改 zk_conn_watcher_g()
8. 注册中心容灾、降级
- 8.1功能描述 容灾:注册中心不可用时服务端和客户端可以正常启动,注册中心恢复后注册信息需要自动注册到注册中心 降级:客户端可以通过配置文件指定服务端地址,此时即使注册中心不可用,客户端也可以访问服务端;这种情况下,注册中心即使恢复,也不会再去访问注册中心获取最新的服务列表
- 8.2实现思路 (1)服务端启动时,将自动向zk注册Provider信息的任务代码提取到一个新的线程 (2)客户端启动时,将自动向zk注册Consumer信息的任务代码提取到一个新的线程 (3)获取配置文件中(service.server.list)提供服务的服务器地址列表:如果不为null,将服务提供者存入allProviders、serviceProviderMap中,客户端进行服务调用(忽略注册中心);否则客户端注册zk,获取zk的服务提供者列表进行服务调用 (4)特别注意:一旦配置service.server.list参数,客户端运行过程中,即使注册中心恢复可用,框架也不会访问注册中心。如果需要从配置中心查找服务端信息,需要注释掉该参数,并重启客户端应用。
- 8.3配置方法
在配置文件“dfzq-grpc-config.properties”增加如下配置:
代码语言:javascript复制# 可选,类型string,说明:该参数用来手动指定提供服务的服务器地址列表。
# 使用场合: 在zookeeper注册中心不可用时,通过该参数指定服务器的地址;如果有多个服务,需要配置多个参数。
# 特别注意: 一旦配置该参数,客户端运行过程中,即使注册中心恢复可用,框架也不会访问注册中心。
# 如果需要从配置中心查找服务端信息,需要注释掉该参数,并重启客户端应用。
# xxx表示客户端调用的服务名称
# service.server.list[xxx]=10.45.0.100:50051
service.server.list[xxx]=10.45.0.100:50051,10.45.0.101:50051,10.45.0.102:50051
- 8.4相关代码
涉及到的模块和代码:
代码语言:javascript复制新增 obtain_appointed_provider_list()
新增 init_provider_from_appointed_list()
新增 init_provider_by_host_port()
修改 orientsec_grpc_consumer_register()
9. 打印log到指定文件
demo-sync-client.cpp:
代码语言:javascript复制int main(int argc, char** argv) {
// Instantiate the client. It requires a channel, out of which the actual RPCs
// are created. This channel models a connection to an endpoint (in this case,
// localhost at port 50051). We indicate that the channel isn't authenticated
// (use of InsecureChannelCredentials()).
//gpr_set_log_verbosity(GPR_LOG_SEVERITY_INFO);
//gpr_set_log_verbosity(GPR_LOG_SEVERITY_ERROR);
//gpr_set_log_verbosity(GPR_LOG_SEVERITY_DEBUG);
// set log output target
gpr_set_log_target(GPR_LOG_WIN_TO_FILE);
gpr_thd_id thd_id;
//开启n线程并发调用
for (int i = 0; i < 1; i ) {
gpr_thd_new(&thd_id, multiple, NULL, NULL);
}
getchar();
return 0;
}
默认输出到std,即不调用上述API(gpr_set_log_target).
调用如上API,相当于打开log开关,默认会将error log 输出到 client.exe 同目录下 //logs//nebula.log 文件中,方便调查问题。
10. 程序健壮性
当服务端与zookeeper断开连接、服务注册信息丢失后,如果客户端与服务端连接正常,那么客户端与服务端依然可以正常通信。