分布式服务治理 --Dubbo

Dubbo 是什么

dubbo 是一个分布式的服务框架,提供高性能的以及透明化的 RPC 远程服务调用解决方法,以及 SOA 服务治理方案。
Dubbo 的核心部分:

  • 远程通信
  • 集群容错
  • 服务的自动发现
  • 负载均衡

Dubbo 架构

核心角色

  • Provider
  • Consumer
  • Registry
  • Monitor
  • Container

dubbo 在 zk 上的节点结构图


url 是个临时节点,为什么?
因为 url 会随着机器和 ip 的变化而变化,当服务挂掉之后,url 也会随之删掉,从而保证了宕机的服务一定是不可用的。
而其他的信息,包括接口定义、配置等等,都是固定的是不会产生变化的,所以是永久节点。

常用配置

启动服务检查

如果提供方没有启动的时候,默认会去检测所依赖的服务是否正常提供服务
如果 check 为 false,表示启动的时候不去检查。当服务出现循环依赖的时候,check 设置成 false
dubbo:reference 属性: check 默认值是 true 、false

  • dubbo:consumer check=”false” 没有服务提供者的时候,报错
  • dubbo:registry check=false 注册订阅失败报错

多协议支持

dubbo 支持的协议:dubbo、RMI、hessian、webService、http、thrift 等等

协议描述连接使用场景
dubbo 传输:mina、netty、grizzy
序列化:dubbo、hessian2、java、json
dubbo 缺省采用单一长连
接和 NIO 异步通讯
1. 传入传出参数数据包较小
2. 消费者 比提供者多
3. 常规远程服务方法调用
4. 不适合传送大数据量的服务,比如文件、传视频
RMI 传输:java rmi
序列化:java 标准序列化
连接个数:多连接
连接方式:短连接
传输协议:TCP/IP
传输方式:BIO
1. 常规 RPC 调用
2. 与原 RMI 客户端互操作
3. 可传文件
4. 不支持防火墙穿透
hession 传输:Serverlet 容器
序列化:hessian 二进制序列化
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
1. 提供者比消费者多
2. 可传文件
3. 跨语言传输
http 传输:servlet 容器
序列化:表单序列化
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
1. 提供者多余消费者
2. 数据包混合
webService 传输:HTTP
序列化:SOAP 文件序列化
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
1. 系统集成
2. 跨语言调用
thrift 与 thrift RPC 实现集成,并在基础上修改了报文头长连接、NIO 异步传输


同时支持多协议,并且可以针对某个服务,使用单独的某个协议

多注册中心支持

dubbo 支持的注册中心
并且可以针对不同的服务注册到不同的注册中心上。

多版本支持

通过使用 version 字段控制多版本

异步调用

async="true" 表示接口异步返回
hessian 协议,使用 async 异步回调会报错

主机绑定

获取服务 host 的四个途径:

  1. 获取 protocol 中的 host 配置的地址
  2. 通过 InetAddress.getLocalHost().getHostAddress() 获取 localhost 中配置的地址
  3. 通过 socket 发起连接请求注册中心,获取注册中心中的地址
  4. hostToBind = getLocalHost(); 获取本地地址
1
2
3
4
hostToBind = protocolConfig.getHost();
if (provider != null && StringUtils.isEmpty(hostToBind)) {
hostToBind = provider.getHost();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if (NetUtils.isInvalidLocalHost(host)) {
anyhost = true;
try {
host = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
logger.warn(e.getMessage(), e);
}
if (NetUtils.isInvalidLocalHost(host)) {
if (registryURLs != null && registryURLs.size() > 0) {
for (URL registryURL : registryURLs) {
try {
Socket socket = new Socket();
try {
SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
socket.connect(addr, 1000);
host = socket.getLocalAddress().getHostAddress();
break;
} finally {
try {
socket.close();
} catch (Throwable e) {}
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
if (NetUtils.isInvalidLocalHost(host)) {
host = NetUtils.getLocalHost();
}
}

服务只订阅

作为服务提供方和消费方,我依赖注册中心中的其他服务,但是我有不希望我的服务暴露给其他的服务(一般情况下,会在测试阶段会使用)

这样的话,我就只会订阅其他的提供方,但是我本身不会注册到注册中心。

服务只注册

和上面类似,只注册到注册中心,但是我不订阅其他的提供方
一般情况下,在只提供服务的模块会使用,不订阅我用不到且不需要的服务。

1
<dubbo:registry subscribe="false"/>

负载均衡

在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用。可以自行扩展负载均衡策略

具体源码:

Random 随机负载(默认)

随机,按权重设置随机概率。
在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

RoundRobin 轮训负载

轮循,按公约后的权重设置轮循比率。

问题

存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

LeastActive 最少活跃调用负载

最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

ConsistentHash 一致性 Hash 负载

一致性 Hash,相同参数的请求总是发到同一提供者。
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。

连接超时 timeout

默认超时时间:int DEFAULT_TIMEOUT = 1000;
必须要设置服务的处理的超时时间

集群容错

  • failover 失败的时候自动切换并重试其他服务器。 通过 retries=2。 来设置重试次数
  • failfast 快速失败,只发起一次调用;比如:写操作(比如因为某一台服务超时,触发了重试的操作,但是超时的服务器其实执行成功了,那么就会触发两次写操作,会出现问题)、非幂等请求
  • failsafe 失败安全。 出现异常时,直接忽略异常,通常用在于主业务无关的操作,比如:日志记录等等
  • failback 失败自动恢复。 后台记录失败请求,定时重发
  • forking 并行调用多个服务器,只要一个成功就返回。只能应用在读请求,但是会浪费服务器资源,因为需要调用所有服务
  • broadcast 广播调用所有提供者,逐个调用。其中一台报错就会返回异常

配置的优先级

消费端优先最高 – 服务端

服务的最佳实践

分包

  1. 服务接口、请求服务模型、异常信息都放在 api 里面,符合重用发布等价原则,共同重用原则
  2. api 里面放入 spring 的引用配置。 也可以放在模块的包目录下。

颗粒度

  1. 尽可能把接口设置成粗粒度,每个服务方法代表一个独立的功能,而不是某个功能的步骤。否则就会涉及到分布式事务
  2. 服务接口建议以业务场景为单位划分。并对相近业务做抽象,防止接口暴增
  3. 不建议使用过于抽象的通用接口 T T <泛型>, 接口没有明确的语义,带来后期的维护

版本

  1. 每个接口都应该定义版本,为后续的兼容性提供前瞻性的考虑 version
  2. 建议使用两位版本号,因为第三位版本号表示的兼容性升级,只有不兼容时才需要变更服务版本
  3. 当接口做到不兼容升级的时候,先升级一半或者一台提供者为新版本, 再将消费全部升级新版本,然后再将剩下的一半提供者升级新版本
  4. 预发布环境

推荐用法

在 provider 端尽可能配置 consumer 端的属性

比如 timeout、retires、线程池大小、LoadBalance,
因为这个服务的性能状态只有这个服务自己知道

配置 dubbo 缓存文件


当因为网络波动问题,短暂实践内不能访问注册中心或者获取服务列表的时候,就会使用本地的缓存文件
本地的缓存文件会缓存:

  • 注册中心的列表
  • 服务提供者列表

源码

功能模块

dubbo 2.5.3 版本:

  • dubbo-clustr 与集群和负载相关
  • dubbo-common 公共工具类和逻辑
  • dubbo-config 与 spring 配置的集成
  • dubbo-demo demo
  • dubbo-filter 过滤器:验证和缓存
  • dubbo-registry 注册中心模块
  • dubbo-remoting 远程通讯方式
  • dubbo-rpc 远程通讯协议
  • dubbo-simple 简单的监控中心
  • dubbo-test 测试代码

入口代码在哪里

dubbo 是基于 spring 的配置扩展实现的,需要实现两个功能

  • NamespaceHandler: 注册 BeanDefinitionParser, 利用它来解析
  • BeanDefinitionParser: 解析配置文件的元素

主要看 dubbo-confg 中 dubbo-config-spring 模块
基于 spring 的配置扩展,首先 spring 会默认加载 jar 包下 / META-INF/spring.handlers 找到对应的 NamespaceHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.config.spring.schema;

import org.apache.dubbo.common.Version;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ConsumerConfig;
import org.apache.dubbo.config.MetadataReportConfig;
import org.apache.dubbo.config.MetricsConfig;
import org.apache.dubbo.config.ModuleConfig;
import org.apache.dubbo.config.MonitorConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.ProviderConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.SslConfig;
import org.apache.dubbo.config.spring.ConfigCenterBean;
import org.apache.dubbo.config.spring.ReferenceBean;
import org.apache.dubbo.config.spring.ServiceBean;
import org.apache.dubbo.config.spring.beans.factory.config.ConfigurableSourceBeanMetadataElement;
import org.apache.dubbo.config.spring.context.DubboBootstrapApplicationListener;
import org.apache.dubbo.config.spring.context.DubboLifecycleComponentApplicationListener;

import com.alibaba.spring.util.AnnotatedBeanDefinitionRegistryUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.w3c.dom.Element;

import static com.alibaba.spring.util.AnnotatedBeanDefinitionRegistryUtils.registerBeans;

/**
* DubboNamespaceHandler
*
* @export
*/
public class DubboNamespaceHandler extends NamespaceHandlerSupport implements ConfigurableSourceBeanMetadataElement {

static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}

@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("config-center", new DubboBeanDefinitionParser(ConfigCenterBean.class, true));
registerBeanDefinitionParser("metadata-report", new DubboBeanDefinitionParser(MetadataReportConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("metrics", new DubboBeanDefinitionParser(MetricsConfig.class, true));
registerBeanDefinitionParser("ssl", new DubboBeanDefinitionParser(SslConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}

/**
* Override {@link NamespaceHandlerSupport#parse(Element, ParserContext)} method
*
* @param element {@link Element}
* @param parserContext {@link ParserContext}
* @return
* @since 2.7.5
*/
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionRegistry registry = parserContext.getRegistry();
registerAnnotationConfigProcessors(registry);
registerApplicationListeners(registry);
BeanDefinition beanDefinition = super.parse(element, parserContext);
setSource(beanDefinition);
return beanDefinition;
}

/**
* Register {@link ApplicationListener ApplicationListeners} as a Spring Bean
*
* @param registry {@link BeanDefinitionRegistry}
* @see ApplicationListener
* @see AnnotatedBeanDefinitionRegistryUtils#registerBeans(BeanDefinitionRegistry, Class[])
* @since 2.7.5
*/
private void registerApplicationListeners(BeanDefinitionRegistry registry) {
registerBeans(registry, DubboLifecycleComponentApplicationListener.class);
registerBeans(registry, DubboBootstrapApplicationListener.class);
}

/**
* Register the processors for the Spring Annotation-Driven features
*
* @param registry {@link BeanDefinitionRegistry}
* @see AnnotationConfigUtils
* @since 2.7.5
*/
private void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(registry);
}
}

在看到 ReferenceBean、ServiceBean 会触发 spring 的扩展点,注意看他们继承的接口

1
2
3
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, BeanNameAware,
ApplicationEventPublisherAware {
1
2
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
ApplicationContextAware, InitializingBean, DisposableBean {
  • initializingBean: spring 初始化 bean 的时候,如果 bean 实现了 InitializingBean 接口,会自动调用 afterPropertiesSet 方法。
  • DisposableBean: 在对象销毁的时候,会去调用 DisposableBean 的 destroy 方法
  • ApplicationContextAware: aware 接口以为感知到,意思就是能够获取到 Aware 单词前面的东西,因此这个接口是获取 applicationContext 上下文的
  • ApplicationListener: spring 事件触发回调
  • BeanNameAware: 和 ApplicationContextAware 一样,bean 初始化完后调用,获取 Aware 前面的东西,获取 beanName 的

服务的发布:ServiceBean

核心的发布服务的代码,ServiceConfig 中的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void doExportUrls() {
ServiceRepository repository = ApplicationModel.getServiceRepository();
ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
repository.registerProvider(
getUniqueServiceName(),
ref,
serviceDescriptor,
this,
serviceMetadata
);

List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);

for (ProtocolConfig protocolConfig : protocols) {
String pathKey = URL.buildKey(getContextPath(protocolConfig)
.map(p -> p + "/" + path)
.orElse(path), group, version);
// In case user specified path, register service one more time to map it to path.
repository.registerService(pathKey, interfaceClass);
// TODO, uncomment this line once service key is unified
serviceMetadata.setServiceKey(pathKey);
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
1
2
3
4
5
6
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// ...
// 发布服务
Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
URL url = invoker.getUrl();

// export service.
String key = serviceKey(url);
DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
exporterMap.put(key, exporter);

//export an stub service for dispatching event
Boolean isStubSupportEvent = url.getParameter(STUB_EVENT_KEY, DEFAULT_STUB_EVENT);
Boolean isCallbackservice = url.getParameter(IS_CALLBACK_SERVICE, false);
if (isStubSupportEvent && !isCallbackservice) {
String stubServiceMethods = url.getParameter(STUB_EVENT_METHODS_KEY);
if (stubServiceMethods == null || stubServiceMethods.length() == 0) {
if (logger.isWarnEnabled()) {
logger.warn(new IllegalStateException("consumer [" + url.getParameter(INTERFACE_KEY) +
"], has set stubproxy support event ,but no stub methods founded."));
}

}
}
// 开启服务
openServer(url);
optimizeSerialization(url);

return exporter;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected final Map<String, ProtocolServer> serverMap = new ConcurrentHashMap<>();
private void openServer(URL url) {
// find server.
String key = url.getAddress();
//client can export a service which's only for server to invoke
boolean isServer = url.getParameter(IS_SERVER_KEY, true);
if (isServer) {
ProtocolServer server = serverMap.get(key);
if (server == null) {
synchronized (this) {
server = serverMap.get(key);
if (server == null) {
// 把发布的服务url缓存下来
serverMap.put(key, createServer(url));
}
}
} else {
// server supports reset, use together with override
server.reset(url);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private ProtocolServer createServer(URL url) {
url = URLBuilder.from(url)
// send readonly event when server closes, it's enabled by default
.addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
// enable heartbeat by default
.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
.addParameter(CODEC_KEY, DubboCodec.NAME)
.build();
String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);

if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
throw new RpcException("Unsupported server type: " + str + ", url: " + url);
}

ExchangeServer server;
try {
// 主要做通讯方式的绑定
server = Exchangers.bind(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
}

str = url.getParameter(CLIENT_KEY);
if (str != null && str.length() > 0) {
Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
if (!supportedTypes.contains(str)) {
throw new RpcException("Unsupported client type: " + str);
}
}

return new DubboProtocolServer(server);
}

在 Transporters 类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static RemotingServer bind(URL url, ChannelHandler... handlers) throws RemotingException {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
if (handlers == null || handlers.length == 0) {
throw new IllegalArgumentException("handlers == null");
}
ChannelHandler handler;
if (handlers.length == 1) {
handler = handlers[0];
} else {
handler = new ChannelHandlerDispatcher(handlers);
}
return getTransporter().bind(url, handler);
}

其中 bind 方法针对不同的方式有不同的实现

服务的引入:ReferenceBean

1

1

Main 是怎么启动的

首先查看入口源码:

1
2
3
public static void main(String[] args) {
Main.main(args);
}


dubbo 实现了这些容器,我们主要看看如何使用 spring 的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.container.spring;

import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ConfigUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.container.Container;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
* SpringContainer. (SPI, Singleton, ThreadSafe)
*
* The container class implementation for Spring
*/
public class SpringContainer implements Container {

public static final String SPRING_CONFIG = "dubbo.spring.config";
public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
private static final Logger logger = LoggerFactory.getLogger(SpringContainer.class);
static ClassPathXmlApplicationContext context;

public static ClassPathXmlApplicationContext getContext() {
return context;
}

@Override
public void start() {
String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
if (StringUtils.isEmpty(configPath)) {
configPath = DEFAULT_SPRING_CONFIG;
}
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"), false);
context.refresh();
context.start();
}

@Override
public void stop() {
try {
if (context != null) {
context.stop();
context.close();
context = null;
}
} catch (Throwable e) {
logger.error(e.getMessage(), e);
}
}

}

当我们去 debug 的时候

可以看到 args 中的参数是 spring

然后回去找到 dubbo 的 SPI 文件去加载相应的 spring 容器类

log 容器

首先我们在资源文件中直接添加 log4j.properties 就可以使用 log4j 的日志框架,是怎么做到的呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.common.logger.log4j;

import org.apache.dubbo.common.logger.Level;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerAdapter;

import org.apache.log4j.Appender;
import org.apache.log4j.FileAppender;
import org.apache.log4j.LogManager;

import java.io.File;
import java.util.Enumeration;

public class Log4jLoggerAdapter implements LoggerAdapter {

private File file;

@SuppressWarnings("unchecked")
public Log4jLoggerAdapter() {
try {
org.apache.log4j.Logger logger = LogManager.getRootLogger();
if (logger != null) {
Enumeration<Appender> appenders = logger.getAllAppenders();
if (appenders != null) {
while (appenders.hasMoreElements()) {
Appender appender = appenders.nextElement();
if (appender instanceof FileAppender) {
FileAppender fileAppender = (FileAppender) appender;
String filename = fileAppender.getFile();
file = new File(filename);
break;
}
}
}
}
} catch (Throwable t) {
}
}

private static org.apache.log4j.Level toLog4jLevel(Level level) {
if (level == Level.ALL) {
return org.apache.log4j.Level.ALL;
}
if (level == Level.TRACE) {
return org.apache.log4j.Level.TRACE;
}
if (level == Level.DEBUG) {
return org.apache.log4j.Level.DEBUG;
}
if (level == Level.INFO) {
return org.apache.log4j.Level.INFO;
}
if (level == Level.WARN) {
return org.apache.log4j.Level.WARN;
}
if (level == Level.ERROR) {
return org.apache.log4j.Level.ERROR;
}
// if (level == Level.OFF)
return org.apache.log4j.Level.OFF;
}

private static Level fromLog4jLevel(org.apache.log4j.Level level) {
if (level == org.apache.log4j.Level.ALL) {
return Level.ALL;
}
if (level == org.apache.log4j.Level.TRACE) {
return Level.TRACE;
}
if (level == org.apache.log4j.Level.DEBUG) {
return Level.DEBUG;
}
if (level == org.apache.log4j.Level.INFO) {
return Level.INFO;
}
if (level == org.apache.log4j.Level.WARN) {
return Level.WARN;
}
if (level == org.apache.log4j.Level.ERROR) {
return Level.ERROR;
}
// if (level == org.apache.log4j.Level.OFF)
return Level.OFF;
}

@Override
public Logger getLogger(Class<?> key) {
return new Log4jLogger(LogManager.getLogger(key));
}

@Override
public Logger getLogger(String key) {
return new Log4jLogger(LogManager.getLogger(key));
}

@Override
public Level getLevel() {
return fromLog4jLevel(LogManager.getRootLogger().getLevel());
}

@Override
public void setLevel(Level level) {
LogManager.getRootLogger().setLevel(toLog4jLevel(level));
}

@Override
public File getFile() {
return file;
}

@Override
public void setFile(File file) {

}

}

然后 dubbo 会根据配置的不同,使用不同的策略