[Dubbo开发]Dubbo日志插件实现(未打包)

Posted 圣所SANCTUARY

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Dubbo开发]Dubbo日志插件实现(未打包)相关的知识,希望对你有一定的参考价值。

本文需要实现的是一个Dubbo的日志插件,日志插件的原理如上图所示。

一、原理

简单的Dubbo生产者和消费者实现服务调用的原理为:

①生产者在注册中心上注册服务;

②消费者在注册中心上订阅服务;

③一旦建立了订阅,消费者和生产者将进行点对点的通信;

此时会产生一个问题:如果作为第三方需要对服务的调用过程进行日志记录(有实际生产需求),那么将失去对调用服务的控制。

于是,在Dubbo简单生产者和消费者的基础上,增加一个日志服务器(本质上也是一个Dubbo生产者),并使用Dubbo拦截器实现日志的记录功能,实现的原理为:

④消费者向生产者发送request请求之前,先由拦截器(filter)向日志服务器发送一条日志,将请求的信息,诸如接口、方法名、参数等信息记录到日志服务器上;

⑤消费者对生产者发送request请求;

⑥生产者对消费者的请求进行响应(Response),当然前提是网络连接畅通,生产者服务可以正常被调用;

⑦在消费者收到响应之后,由拦截器(filter)再向日志服务器发送一条日志,将返回的信息,诸如返回值等信息记录到日志服务器上。

以上就是以Dubbo拦截器方式实现的日志插件的原理。

二、实现

接下来是具体的实现过程:

消费者端的文件目录结构

FilterDesc.java

package com.mohrss.service;
/*
 * Class: FilterDesc
 * Function:拦截对象
 */
public class FilterDesc {
	private String interfaceName ;//接口名
    private String methodName ;//方法名
    private Object[] args ;//参数
    
    public FilterDesc(){
    	
    }

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getArgs() {
        return args;
    }

    public void setArgs(Object[] args) {
        this.args = args;
    }
}

LogFilter.java

package com.mohrss.service;

import com.alibaba.dubbo.rpc.*;
import com.alibaba.dubbo.rpc.service.GenericService;
import com.alibaba.fastjson.JSON;
/*
 * Class:LogFilter
 * Function:日志拦截器
 */
public class LogFilter implements Filter {
	private static final FilterDesc filterReq = new FilterDesc();
	private static final FilterDesc filterRsp = new FilterDesc();
	private LogService ls = null;
	public LogFilter(){
		ls = (LogService)SpringContextUtil.getApplicationContext().getBean("testLogService");
	}

	public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
		try{
			//request部分
			filterReq.setInterfaceName(invocation.getInvoker().getInterface().getName());
			filterReq.setMethodName(invocation.getMethodName());
			filterReq.setArgs(invocation.getArguments());
			ls.printLog("Dubbo请求数据" + JSON.toJSONString(filterReq));
			
			
			Result result = invoker.invoke(invocation);
			
			if(GenericService.class != invoker.getInterface() && result.hasException()){
				ls.printLog("Dubbo执行异常"+result.getException().toString());
				
			}else{
				ls.printLog("Dubbo执行成功");
				
				filterRsp.setInterfaceName(invocation.getMethodName());
				filterRsp.setMethodName(invocation.getMethodName());
				filterRsp.setArgs(new Object[]{result.getValue()});
				ls.printLog("Dubbo返回数据" + JSON.toJSONString(filterRsp));
			}
			
			return result;
		}
		catch(RuntimeException e){
			ls.printLog("Dubbo未知异常" + RpcContext.getContext().getRemoteHost()
					+ ".service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
					+ ", exception: " + e.getClass().getName() + ": " + e.getMessage());
			throw e;
		}
	}
}

LogService.java(由日志服务器提供的接口文件,实际中应该是打包成jar包)

package com.mohrss.service;

public interface LogService {
	public void printLog(String log);
}

ProviderService.java(由生产者提供的接口文件,实际中应该是打包成jar包)

package com.mohrss.service;

public interface ProviderService {
	public void sayHello();
	public int calc(int a, int b);
}

SpringContextUtil.java

package com.mohrss.service;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/*
 * Class: SpringContextUtil
 * Function:用于获得当前Spring上下文环境的工具类
 */
public class SpringContextUtil implements ApplicationContextAware {

	// Spring应用上下文环境  
    private static ApplicationContext context;  
  
    /** 
     * 实现ApplicationContextAware接口的回调方法。设置上下文环境 
     *  
     * @param applicationContext 
     */  
    public void setApplicationContext(ApplicationContext applicationContext) {  
        SpringContextUtil.context = applicationContext;  
    }  
  
    /** 
     * @return ApplicationContext 
     */  
    public static ApplicationContext getApplicationContext() {  
        return context;  
    }  
  
    public static Object getBean(String name) throws BeansException {  
        return context.getBean(name);  
    }  

}

com.alibaba.dubbo.rpc.filter(拦截器配置文件)

logFilter=com.mohrss.service.LogFilter

dubbo-consumer.xml(消费者自己的dubbo配置文件)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
        http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://code.alibabatech.com/schema/dubbo  
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd ">   
         
    <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 -->  
    <!-- <dubbo:application name="consumer" />   --> 
    
    <!-- 使用multicast广播注册中心暴露发现服务地址 -->  
    <!-- <dubbo:registry  protocol="zookeeper" address="127.0.0.1:2181" />  -->      
    
    <!-- 生成远程服务代理,可以和本地bean一样使用testProviderService -->  
    <!-- 对谁拦截,就给谁加filter -->
    <dubbo:reference id="testProviderService" interface="com.mohrss.service.ProviderService" filter="logFilter" async="true"/>
     
    <!-- 引入外部插件的配置文件 -->
	<import resource="classpath:filter.xml" />
</beans>

dubbo.properties(更加规范的配置文件,可以避免经常修改dubbo-consumer.xml文件,文件名必须叫dubbo.properties,不然会加载不到)

# Consumer application name
dubbo.application.name=consumer
# Zookeeper registry address
dubbo.registry.protocol=zookeeper
dubbo.registry.address=127.0.0.1:2181

filter.xml(插件的配置文件,因为是作为插件使用,所以单独放在filter.xml中,在dubbo-consumer.xml里用import resource引用)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
        http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://code.alibabatech.com/schema/dubbo  
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd ">   
    <bean id="springContextUtil" class="com.mohrss.service.SpringContextUtil" />
    
    <dubbo:reference id="testLogService" interface="com.mohrss.service.LogService" /> 
</beans>

TestConsumerService.java 

package com.mohrss.consumer;

import java.io.IOException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mohrss.service.ProviderService;
/*
 * Class: TestConsumerService
 * Function: 程序入口,访问生产者,被拦截的对象
 */
public class TestConsumerService {

    public static void main(String[] args) {
    	//读取xml配置文件
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext(new String[]{"dubbo-consumer.xml"});
        context.start();

        ProviderService testService = (ProviderService) context.getBean("testProviderService");
        testService.calc(1, 2);
        testService.calc(5, 6);
        testService.sayHello();
        testService.calc(7, 8);
        
        try {
            System.in.read();
        } catch (IOException e) {       
            e.printStackTrace();
        }  
    }
}

 

 生产者代码实现参考之前的博客:https://www.cnblogs.com/Vivianwang/p/9408493.html

 日志服务器文件目录:

 

LogService.java

package com.mohrss.service;

public interface LogService {
	public void printLog(String log);
}

LogServiceImpl.java

package com.mohrss.service.impl;

import org.springframework.stereotype.Service;

import com.mohrss.service.LogService;

@Service("logService")
public class LogServiceImpl implements LogService {
	public void printLog(String log){
		System.out.println(log);
	}
}

dubbo-log.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"  
	xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
        http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://code.alibabatech.com/schema/dubbo  
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        http://www.springframework.org/schema/context  
        http://www.springframework.org/schema/context/spring-context-3.0.xsd ">   
        
	<!--用注解方式实现bean-->
	<context:component-scan base-package="com.mohrss.service">  
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>   
	</context:component-scan>
    
    <!-- 提供方应用信息,用于计算依赖关系 -->  
    <dubbo:application name="logservice"  />    
    
    <!-- 使用zookeeper注册中心暴露服务地址  配置后spring管理-->  
    <dubbo:registry address="zookeeper://127.0.0.1:2181" />     
    
    <!-- 用dubbo协议在20881端口暴露日志服务 -->  
    <dubbo:protocol name="dubbo" port="20881" />  
    
    <!-- 声明需要暴露的服务接口 -->  
    <dubbo:service interface="com.mohrss.service.LogService" ref="logService" />  
</beans>

TestLogService.java

package com.mohrss.service;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestLogService {
	public static void main(String[] args) {
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext(new String[]{"dubbo-log.xml"});
        context.start();
        System.out.println("日志服务已经注册成功!");
        try {
            System.in.read();//让此程序一直跑,表示一直提供服务
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  

 注:原创博客,转载请注明。

 

 

 

以上是关于[Dubbo开发]Dubbo日志插件实现(未打包)的主要内容,如果未能解决你的问题,请参考以下文章

从ExtensionLoader理解Dubbo扩展机制

通过Jenkins自动构建dubbo服务时的问题汇总

Lolttery项目开发日志 - dubbo初战

dubbo-admin打包和zookper安装

Dubbo 压测插件的实现——基于 Gatling

dubbo案例