概述
Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
Oinone平台默认使用dubbo-v2.7.22
版本,本文以该版本为例进行描述。
基本概念
Dubbo在注册provider/consumer
时使用Netty
作为RPC调用的核心服务,其具备客户端/服务端(C/S)
的基本特性。即:provider
作为服务端
,consumer
作为客户端
。
客户端
通过服务中心
发现有服务可被调用时,将通过服务中心
提供的服务端
调用信息,连接服务端
并发起请求,从而实现远程调用。
服务注册(绑定Host/Port)
JAVA程序启动时,需要将provider
的信息注册到服务中心
,并在当前环境为Netty
服务开启Host/Port
监听,以实现服务注册
功能。
在下文中,我们通过绑定Host/Port
表示Netty
服务的访问地址,通过注册Host/Port
表示客户端的访问地址。
使用yaml配置绑定Host/Port
PS:该配置可在多种环境中通用,改变部署方式无需修改此配置。
dubbo:
protocol:
name: dubbo
# host: 0.0.0.0
port: -1
假设当前环境的可用IP为192.168.1.100
以上配置将使得Netty
服务默认绑定在0.0.0.0:20880
地址,服务注册地址为192.168.1.100:20880
客户端
将通过192.168.1.100:20880
调用服务端服务
若发生20880端口占用,则自动向后查找可用端口。如20881、20882等等
若当前可用端口为20881,则以上配置将使得Netty
服务默认绑定在0.0.0.0:20881
地址,服务注册地址为192.168.1.100:20881
使用环境变量配置注册Host/Port
当服务端被放置在容器环境中时,由于容器环境的特殊性,其内部的网络配置相对于宿主机而言是独立的。因此为保证客户端可以正常调用服务端,还需在容器中配置环境变量,以确保客户端可以通过指定的注册Host/Port
进行访问。
以下示例为体现无法使用20880端口的情况,将宿主机可访问端口从20880改为20881。
DUBBO_IP_TO_REGISTRY=192.168.1.100
DUBBO_PORT_TO_REGISTRY=20881
假设当前宿主机环境的可用IP为192.168.1.100
以上配置将使得Netty
服务默认绑定在0.0.0.0:20881
地址,服务注册地址为192.168.1.100:20881
客户端
将通过192.168.1.100:20881
调用服务端服务
使用docker/docker-compose启动
需添加端口映射,将20881端口映射至宿主机20881端口。(此处容器内的端口发生变化,若需要了解具体原因,可参考题外话章节)
docker-run
IP=192.168.1.100
docker run -d --name designer-allinone-full \
-e DUBBO_IP_TO_REGISTRY=$IP \
-e DUBBO_PORT_TO_REGISTRY=20881 \
-p 20881:20881 \
docker-compose
services:
backend:
container_name: designer-backend
image: harbor.oinone.top/oinone/designer-backend-v5.0
restart: always
environment:
DUBBO_IP_TO_REGISTRY: 192.168.1.100
DUBBO_PORT_TO_REGISTRY: 20881
ports:
- 20881:20881 # dubbo端口
使用kubernetes启动
工作负载(Deployment)
kind: Deployment
apiVersion: apps/v1
spec:
replicas: 1
template:
spec:
containers:
- name: designer-backend
image: harbor.oinone.top/oinone/designer-backend-v5.0
ports:
- name: dubbo
containerPort: 20881
protocol: TCP
env:
- name: DUBBO_IP_TO_REGISTRY
value: "192.168.1.100"
- name: DUBBO_PORT_TO_REGISTRY
value: "20881"
服务(Services)
kind: Service
apiVersion: v1
spec:
type: NodePort
ports:
- name: dubbo
protocol: TCP
port: 20881
targetPort: dubbo
nodePort: 20881
PS:此处的targetPort
为对应Deployment#spec. template.spec.containers.ports.name
配置的端口名称。若未配置,可使用20881
直接指定对应容器的端口号。
使用kubernetes其他暴露服务方式
在Kubernetes中部署服务,有多种配置方式均可用暴露服务。上述配置仅用于通过Service/NodePort
将20881
端口暴露至宿主机,其他服务可用通过任意Kubernetes节点IP进行调用。
若其他服务也在Kubernetes中进行部署,则可以通过Service/Service
方式进行调用。将DUBBO_IP_TO_REGISTRY
配置为${serviceName}.${namespace}
即可。
若其他服务无法直接访问Kubernetes的master服务,则可以通过Ingress/Service
方式进行调用。将DUBBO_IP_TO_REGISTRY
配置为Ingress
可解析域名即可。
Dubbo调用链路图解
PS: Consumer
的绑定Host/Port
是其作为Provider
使用的,下面所有图解仅演示单向的调用链路。
名词解释
- Provider: 服务提供者(JVM)
- Physical Machine Provider: 服务提供者所在物理机
- Provider Container: 服务提供者所在容器
- Kubernetes Service: Kubernetes Service资源类型
- Consumer: 服务消费者(JVM)
- Registration Center: 注册中心;可以是
zookeeper
、nacos
等。 - bind: 服务
绑定Host/Port
到指定ip:port
。 - registry: 服务注册;
注册Host/Port
到注册中心的信息。 - discovery: 服务发现;
注册Host/Port
到消费者的信息。 - invoke: 服务调用;消费者通过注册中心提供的提供者信息向提供者发起服务调用。
- forward: 网络转发;通常在容器环境需要进行必要的网络转发,以使得服务调用可以到达服务提供者。
物理机/物理机
调用链路
PS: 此处虚线部分表示提供者部署在物理机上,并不存在真实的网络处理。
容器/物理机
调用链路
PS: 此处虚线部分表示提供者部署在容器中,并不存在真实的网络处理。
Kubernetes/物理机
(Service/NodePort模式)调用链路
PS: 此处虚线部分表示提供者部署在容器中,并不存在真实的网络处理。
题外话
在dubbo-v2.7.22
源码中,作者发现Host/Port
的获取方式并不对等,这里目前不太清楚是dubbo
设计如此还是作者对dubbo
设计理解不足。
-
现象:
DUBBO_IP_TO_REGISTRY
配置与dubbo.protocol.host
无关。DUBBO_PORT_TO_REGISTRY
配置优先级高于dubbo.protocol.port
配置。
-
作者理解:
- 客户端向服务端发起请求时,应使用
注册Host/Port
进行调用,只要该访问地址可以与服务端连通,则远程调用就可以正常运行。 注册Host/Port
与绑定Host/Port
应支持完全独立配置。当注册Host/Port
与绑定Host/Port
均被配置时,注册Host
与绑定Host
是独立生效的,但绑定Port
却强制使用了注册Port
。(这一点也是经常在容器环境中无法正常调用的主要原因)
- 客户端向服务端发起请求时,应使用
常用配置
yaml配置
dubbo:
application:
name: pamirs-test
version: 1.0.0
registry:
address: zookeeper://127.0.0.1:2181
# group: demo
# timeout: 5000
protocol:
name: dubbo
# host: 0.0.0.0
port: -1
serialization: pamirs
payload: 104857600
scan:
base-packages: pro.shushi
cloud:
subscribed-services:
- dubbo.registry.address: 注册中心地址
- dubbo.registry.group: 全局group配置
- dubbo.registry.timeout: 全局超时时间配置
- dubbo.protocol.name: 协议名称
- dubbo.protocol.host: 绑定主机IP配置;默认:0.0.0.0
- dubbo.protocol.port: 绑定主机端口配置;-1表示自动获取可用端口;默认:20880
- dubbo.protocol.serialization: 序列化配置;Oinone平台必须使用
pamirs
作为序列化方式。 - dubbo.protocol.payload: RPC调用数据大小限制;单位:字节(byte)
- dubbo.scan.base-packages: provider/consumer扫描包路径
- dubbo.cloud.subscribed-services: 多提供者配置;示例中该参数配置为空是为了避免启动时的警告日志,一般无需配置。
环境变量配置
DUBBO_IP_TO_REGISTRY=127.0.0.1
DUBBO_PORT_TO_REGISTRY=20880
- DUBBO_IP_TO_REGISTRY:注册Host配置
- DUBBO_PORT_TO_REGISTRY:注册Port配置
源码参考
- org.apache.dubbo.config.ServiceConfig#findConfigedHosts
private String findConfigedHosts(ProtocolConfig protocolConfig,
List<URL> registryURLs,
Map<String, String> map) {
boolean anyhost = false;
String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND);
if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) {
throw new IllegalArgumentException("Specified invalid bind ip from property:" + DUBBO_IP_TO_BIND + ", value:" + hostToBind);
}
// if bind ip is not found in environment, keep looking up
if (StringUtils.isEmpty(hostToBind)) {
hostToBind = protocolConfig.getHost();
if (provider != null && StringUtils.isEmpty(hostToBind)) {
hostToBind = provider.getHost();
}
if (isInvalidLocalHost(hostToBind)) {
anyhost = true;
logger.info("No valid ip found from environment, try to get local host.");
hostToBind = getLocalHost();
}
}
map.put(BIND_IP_KEY, hostToBind);
// registry ip is not used for bind ip by default
String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY);
if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException(
"Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
} else if (StringUtils.isEmpty(hostToRegistry)) {
// bind ip is used as registry ip by default
hostToRegistry = hostToBind;
}
map.put(ANYHOST_KEY, String.valueOf(anyhost));
return hostToRegistry;
}
- org.apache.dubbo.config.ServiceConfig#findConfigedPorts
private Integer findConfigedPorts(ProtocolConfig protocolConfig,
String name,
Map<String, String> map, int protocolConfigNum) {
Integer portToBind = null;
// parse bind port from environment
String port = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_BIND);
portToBind = parsePort(port);
// if there's no bind port found from environment, keep looking up.
if (portToBind == null) {
portToBind = protocolConfig.getPort();
if (provider != null && (portToBind == null || portToBind == 0)) {
portToBind = provider.getPort();
}
final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
if (portToBind == null || portToBind == 0) {
portToBind = defaultPort;
}
if (portToBind <= 0) {
portToBind = getRandomPort(name);
if (portToBind == null || portToBind < 0) {
portToBind = getAvailablePort(defaultPort);
putRandomPort(name, portToBind);
}
}
}
// registry port, not used as bind port by default
String key = DUBBO_PORT_TO_REGISTRY;
if (protocolConfigNum > 1) {
key = getProtocolConfigId(protocolConfig).toUpperCase() + "_" + key;
}
String portToRegistryStr = getValueFromConfig(protocolConfig, key);
Integer portToRegistry = parsePort(portToRegistryStr);
if (portToRegistry != null) {
portToBind = portToRegistry;
}
// save bind port, used as url's key later
map.put(BIND_PORT_KEY, String.valueOf(portToBind));
return portToBind;
}
- org.apache.dubbo.config.ServiceConfig#getValueFromConfig
private String getValueFromConfig(ProtocolConfig protocolConfig, String key) {
String protocolPrefix = protocolConfig.getName().toUpperCase() + "_";
String value = ConfigUtils.getSystemProperty(protocolPrefix + key);
if (StringUtils.isEmpty(value)) {
value = ConfigUtils.getSystemProperty(key);
}
return value;
}
- org.apache.dubbo.common.utils.ConfigUtils#getSystemProperty
public static String getSystemProperty(String key) {
String value = System.getenv(key);
if (StringUtils.isEmpty(value)) {
value = System.getProperty(key);
}
return value;
}
Oinone社区 作者:张博昊原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/16028.html
访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验