分布式服务治理--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会根据配置的不同,使用不同的策略