客户端服务
事实速查
- Channel.Init()是线程不安全的。
- Channel.CallMethod()是线程安全的,一个Channel可以被所有线程同时使用。
- Channel可以分配在栈上。
- Channel在发送异步请求后可以析构。
- 没有krpc::Client这个类。
Channel
Client指发起请求的一端,在krpc中没有对应的实体,取而代之的是krpc::Channel,它代表和一台或一组服务器的交互通道,Client和Channel在角色上的差别在实践中并不重要,你可以把Channel视作Client。
Channel可以被所有线程共用,你不需要为每个线程创建独立的Channel,也不需要用锁互斥。不过Channel的创建和Init并不是线程安全的,请确保在Init成功后再被多线程访问,在没有线程访问后再析构。
一些RPC实现中有ClientManager的概念,包含了Client端的配置信息和资源管理。krpc不需要这些,以往在ClientManager中配置的线程数、长短连接等等要么被加入了krpc::ChannelOptions,要么可以通过gflags全局配置,这么做的好处:
- 方便。你不需要在创建Channel时传入ClientManager,也不需要存储ClientManager。否则不少代码需要一层层地传递ClientManager,很麻烦。gflags使一些全局行为的配置更加简单。
- 共用资源。比如server和channel可以共用后台线程。(kthread的工作线程)
- 生命周期。析构ClientManager的过程很容易出错,现在由框架负责则不会有问题。
就像大部分类那样,Channel必须在Init之后才能使用,options为NULL时所有参数取默认值,如果你要使用非默认值,这么做就行了:
krpc::ChannelOptions options; // 包含了默认值
options.xxx = yyy;
...
channel.Init(..., &options);
注意Channel不会修改options,Init结束后不会再访问options。所以options一般就像上面代码中那样放栈上。Channel.options()可以获得channel在使用的所有选项。
Init函数分为连接一台服务器和连接服务集群。
连接一台服务器
// options为NULL时取默认值
int Init(EndPoint server_addr_and_port, const ChannelOptions* options);
int Init(const char* server_addr_and_port, const ChannelOptions* options);
int Init(const char* server_addr, int port, const ChannelOptions* options);
这类Init连接的服务器往往有固定的ip地址,不需要命名服务和负载均衡,创建起来相对轻量。但是请勿频繁创建使用域名的Channel。这需要查询dns,可能最多耗时10秒(查询DNS的默认超时)。重用它们。
合法的“server_addr_and_port”:
- 127.0.0.1:80
- www.foo.com:8765
- localhost:9000
- [::1]:8080 # IPV6
- unix:path.sock # Unix domain socket
不合法的"server_addr_and_port":
- 127.0.0.1:90000 # 端口过大
- 10.39.2.300:8000 # 非法的ip
关于IPV6和Unix domain socket的使用,详见 EndPoint。
连接服务集群
int Init(const char* naming_service_url,
const char* load_balancer_name,
const ChannelOptions* options);
这类Channel需要定期从naming_service_url指定的命名服务中获得服务器列表,并通过load_balancer_name指定的负载均衡算法选择出一台机器发送请求。
你不应该在每次请求前动态 地创建此类(连接服务集群的)Channel。因为创建和析构此类Channel牵涉到较多的资源,比如在创建时得访问一次命名服务,否则便不知道有哪些服务器可选。由于Channel可被多个线程共用,一般也没有必要动态创建。
当load_balancer_name为NULL或空时,此Init等同于连接单台server的Init,naming_service_url应该是"ip:port"或"域名:port"。你可以通过这个Init函数统一Channel的初始化方式。比如你可以把naming_service_url和load_balancer_name放在配置文件中,要连接单台server时把load_balancer_name置空,要连接服务集群时则设置一个有效的算法名称。
命名服务
命名服务把一个名字映射为可修改的机器列表,在client端的位置如下:
有了命名服务后client记录的是一个名字,而不是每一台下游机器。而当下游机器变化时,就只需要修改命名服务中的列表,而不需要逐台修改每个上游。这个过程也常被称为“解耦上下游”。当然在具体实现上,上游会记录每一台下游机器,并定期向命名服务请求或被推送最新的列表,以避免在RPC请求时才去访问命名服务。使用命名服务一般不会对访问性能造成影响,对命名服务的压力也很小。
naming_service_url的一般形式是"protocol://service_name"
bns://<bns-name>
BNS是百度内常用的命名服务,比如bns://rdev.matrix.all,其中"bns"是protocol,"rdev.matrix.all"是service-name。相关一个gflag是-ns_access_interval:
如果BNS中显示不为空,但Channel却说找不到服务器,那么有可能BNS列表中的机器状态位(status)为非0,含义为机器不可用,所以不会被加入到server候选集中.状态位可通过命令行查看:
get_instance_by_service [bns_node_name] -s
file://<path>
服务器列表放在path所在的文件里,比如"file://conf/machine_list"中的“conf/machine_list”对应一个文件:
- 每行是一台服务器的地址。
- #之后的是注释会被忽略
- 地址后出现的非注释内容被认为是tag,由一个或多个空格与前面的地址分隔,相同的地址+不同的tag被认为是不同的实例。
- 当文件更新时, krpc会重新加载。
# 此行会被忽略
10.24.234.17:8080 tag1 # 这是注释,会被忽略
10.24.234.17:8090 tag2 # 此行和上一行被认为是不同的实例
10.24.234.18:8080
10.24.234.19:8080
优点: 易于修改,方便单测。
缺点: 更新时需要修改每个上游的列表文件,不适合线上部署。
list://<addr1>,<addr2>...
服务器列表直接跟在list://之后,以逗号分隔,比如"list://db-bce-81-3-186.db01:7000,m1-bce-44-67-72.m1:7000,cp01-rd-cos-006.cp01:7000"中有三个地址。
地址后可以声明tag,用一个或多个空格分隔,相同的地址+不同的tag被认为是不同的实例。
优点: 可在命令行中直接配置,方便单测。
缺点: 无法在运行时修改,完全不能用于线上部署。
http://<url>
连接一个域名下所有的机器, 例如http://www.baidu.com:80 ,注意连接单点的Init(两个参数)虽然也可传入域名,但只会连接域名下的一台机器。
优点: DNS的通用性,公网内网均可使用。
缺点: 受限于DNS的格式限制无法传递复杂的meta数据,也无法实现通知机制。
https://<url>
和http前缀类似,只是会自动开启SSL。
consul://<service-name>
通过consul获取服务名称为service-name的服务列表。consul的默认地址是localhost:8500,可通过gflags设置-consul_agent_addr来修改。consul的连接超时时间默认是200ms,可通过-consul_connect_timeout_ms来修改。
默认在consul请求参数中添加stale和passing(仅返回状态为passing的服务列表),可通过gflags中-consul_url_parameter改变consul请求参数。
除了对consul的首次请求,后续对consul的请求都采用long polling的方式,即仅当服务列表更新或请求超时后consul才返回结果,这里超时时间默认为60s,可通过-consul_blocking_query_wait_secs来设置。
若consul返回的服务列表响应格式有错误,或者列表中所有服务都因为地址、端口等关键字段缺失或无法解析而被过滤,consul naming server会拒绝更新服务列表,并在一段时间后(默认500ms,可通过-consul_retry_interval_ms设置)重新访问consul。
如果consul不可访问,服务可自动降级到file naming service获取服务列表。此功能默认关闭,可通过设置-consul_enable_degrade_to_file_naming_service来打开。服务列表文件目录通过-consul _file_naming_service_dir来设置,使用service-name作为文件名。该文件可通过consul-template生成,里面会保存consul不可用之前最新的下游服务节点。当consul恢复时可自动恢复到consul naming service。
nacos://<service-name>
NacosNamingService使用Open-Api定时从nacos获取服务列表。 NacosNamingService支持简单鉴权。
<service-name>是一个http uri query,具体参数参见/nacos/v1/ns/instance/list文档。
注意:<service-name>需要urlencode。
nacos://serviceName=test&groupName=g&namespaceId=n&clusters=c&healthyOnly=true
NacosNamingService拉取列表的时间间隔为/nacos/v1/ns/instance/listapi返回的cacheMillis。
NacosNamingService只支持整形的权重值。
| GFlags | 描述 | 默认值 |
|---|---|---|
| nacos_address | nacos http url | "" |
| nacos_service_discovery_path | nacos服务发现路径 | "/nacos/v1/ns/instance/list" |
| nacos_service_auth_path | nacos登陆路径 | "/nacos/v1/auth/login" |
| nacos_service_timeout_ms | 连接nacos超时时间(毫秒) | 200 |
| nacos_username | 用户名(urlencode编码) | "" |
| nacos_password | 密码(urlencode编码) | "" |
| nacos_load_balancer | nacos集群的负载均衡 | "rr" |
更多命名服务
用户可以通过实现krpc::NamingService来对接更多命名服务,具体见这里