轻量级Rpc框架设计--motan源码解析四:Netty服务暴露

一, 服务暴露

motan框架通过实现ApplicationListener<ContextRefreshedEvent>接口的onApplicationEvent()方法, 执行服务暴露操作.

@Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
    	// 首先检查, 是否已经exported服务
        if (!getExported().get()) {
            export();
        }
    }

1.1 export()方法进行服务暴露

服务暴露过程源码如下, 下面针对该源码, 进行解析:

public synchronized void export() {
    	// 再次判断, 该服务是否已经exported服务
        if (exported.get()) {
            LoggerUtil.warn(String.format("%s has already been expoted, so ignore the export request!", interfaceClass.getName()));
            return;
        }
        checkInterfaceAndMethods(interfaceClass, methods);
        // 很核心的一步, 将xml配置文件registry标签配置的内容, 转换成List<URL>对象
        // 但是URL的parameters属性, 去掉了下面这四项参数:
        // defaultParameters.remove("protocol");
        // defaultParameters.remove("host");
        // defaultParameters.remove("port");
        // defaultParameters.remove("path");
        List<URL> registryUrls = loadRegistryUrls();
        if (registryUrls == null || registryUrls.size() == 0) {
            throw new IllegalStateException("Should set registry config for service:" + interfaceClass.getName());
        }
        Map<String, Integer> protocolPorts = getProtocolAndPort();
        // 从这里可以看出, 
        for (ProtocolConfig protocolConfig : protocols) {
            Integer port = protocolPorts.get(protocolConfig.getId());
            if (port == null) {
                throw new MotanServiceException(String.format("Unknow port in service:%s, protocol:%s", interfaceClass.getName(),
                        protocolConfig.getId()));
            }
            // 参数protocolConfig : <motan:protocol name="motan" default="true" requestTimeout="220" minWorkerThread="20" maxWorkerThread="800" maxContentLength="1048576" maxServerConnection="80000" isDefault="true" id="demoMotan" />
    		// 参数port : 8001
    		// 参数registryUrls : [zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc], 已经去掉了上面的四项的结果
            doExport(protocolConfig, port, registryUrls);
        }
        afterExport();
    }

1.1.1 首先再次判断, 服务是否已经启动, 正在对外暴露服务.

1.1.2 checkInterfaceAndMethods(interfaceClass, methods);

类似于dubbo的服务暴露时的对接口中的方法进行配置:

<dubbo:service>

        <dubbo:method></dubbo:method>

</dubbo:service>

同样的motan也提供了类似于这样基于method级别的配置方式:

<motan:service>

         <motan:method></motan:method>

</motan:service>

基于这样的配置, checkInterfaceAndMethods(interfaceClass, methods);这行代码的意思就是, check校验该method方法是否在待进行服务暴露接口的接口定义中.

1.1.3 List<URL> registryUrls = loadRegistryUrls(); 很关键的一行代码, 生成了URL核心对象

 protected List<URL> loadRegistryUrls() {
        List<URL> registryList = new ArrayList<URL>();
        if (registries != null && !registries.isEmpty()) {
            for (RegistryConfig config : registries) {
                String address = config.getAddress();
                if (StringUtils.isBlank(address)) {
                    address = NetUtils.LOCALHOST + ":" + MotanConstants.DEFAULT_INT_VALUE;
                }
                Map<String, String> map = new HashMap<String, String>();
                // 将config对象转换成map类型
                config.appendConfigParams(map);
                map.put(URLParamType.application.getName(), getApplication());
                map.put(URLParamType.path.getName(), RegistryService.class.getName());
                map.put(URLParamType.refreshTimestamp.getName(), String.valueOf(System.currentTimeMillis()));

                // 设置默认的registry protocol,parse完protocol后,需要去掉该参数
                if (!map.containsKey(URLParamType.protocol.getName())) {
                    if (address.contains("://")) {
                        map.put(URLParamType.protocol.getName(), address.substring(0, address.indexOf("://")));
                    }
                    map.put(URLParamType.protocol.getName(), MotanConstants.REGISTRY_PROTOCOL_LOCAL);
                }
                // address内部可能包含多个注册中心地址
                List<URL> urls = UrlUtils.parseURLs(address, map);
                if (urls != null && !urls.isEmpty()) {
                    for (URL url : urls) {
                        url.removeParameter(URLParamType.protocol.getName());
                        registryList.add(url);
                    }
                }
            }
        }
        return registryList;
    }

这行代码时特别关键的一步, , 将xml配置文件<motan:registry>标签配置的内容, 通过其封装类RegistryConfig, 转换成List<URL>对象, 当前URL核心对象中现在封装了<motan:registry>标签配置的ip:port, 以及一些其他的参数, 比如application, path, refreshTimestamp等.

最终执行结果为:[zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc], 很显然就是得到了xml文件中<motan:registry>标签配置的zk地址及端口号, 以及默认生成一个group.

1.1.4 Map<String, Integer> protocolPorts = getProtocolAndPort(); // 从export中获取协议名称及服务暴露的端口号

<motan:basicService  export="demoMotan:8002"> // 

<motan:service export="demoMotan:8001">

上面两端配置文件都配置export, 若同时存在的话, 将以motan:service中的配置为主, 即覆盖<motan:basicService>中export的配置内容, 存在相同的配置项时, 优先级是<motan:service> 大于 <motan:basicService>.

所以这行代码的执行结果就是: {demoMotan=8001}或者{demoMotan=8002}

1.1.5 从ProtocolConfig封装类中获取xml配置文件中, 关于Protocol的其他配置配置内容:

<motan:protocol id="demoMotan" default="true" name="motan"
                    requestTimeout="220" maxServerConnection="80000" maxContentLength="1048576"
                    maxWorkerThread="800" minWorkerThread="20"/>
for (ProtocolConfig protocolConfig : protocols) {
            Integer port = protocolPorts.get(protocolConfig.getId());
            if (port == null) {
                throw new MotanServiceException(String.format("Unknow port in service:%s, protocol:%s", interfaceClass.getName(),
                        protocolConfig.getId()));
            }
            // 参数protocolConfig : <motan:protocol name="motan" default="true" requestTimeout="220" minWorkerThread="20" maxWorkerThread="800" maxContentLength="1048576" maxServerConnection="80000" isDefault="true" id="demoMotan" />
    		// 参数port : 8001
    		// 参数registryUrls : [zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc], 已经去掉了上面的四项的结果
            doExport(protocolConfig, port, registryUrls);
        }

1.2 传入经过进一步处理后的protocolConfig对象, 待暴露的port端口号, 注册中心registryUrl封装对象, 传入给doExport()方法进行服务暴露.

 private void doExport(ProtocolConfig protocolConfig, int port, List<URL> registryURLs) {
        String protocolName = protocolConfig.getName();
        if (protocolName == null || protocolName.length() == 0) {
            protocolName = URLParamType.protocol.getValue();
        }
        String hostAddress = host;
        if (StringUtils.isBlank(hostAddress) && basicServiceConfig != null) {
            hostAddress = basicServiceConfig.getHost();
        }
        if (NetUtils.isInvalidLocalHost(hostAddress)) {
            hostAddress = getLocalHostAddress(registryURLs);
        }
        Map<String, String> map = new HashMap<String, String>();
        map.put(URLParamType.nodeType.getName(), MotanConstants.NODE_TYPE_SERVICE);
        map.put(URLParamType.refreshTimestamp.getName(), String.valueOf(System.currentTimeMillis()));
        // 按顺序进行config 参数append and override,按照configs出现的顺序,后面的会覆盖前面的相同名称的参数, 即优先级:serviceConfig > extConfig > basicServiceConfig > protocolConfig
        // collectConfigParams()方法之前的map:
        // {
        //		nodeType=service, 	
        //		refreshTimestamp=1543822818150
        // }
        collectConfigParams(map, protocolConfig, basicServiceConfig, extConfig, this);
        // collectConfigParams()方法之后的map : 
        // {
		// 		maxContentLength=1048576, 
		// 		module=motan-demo-rpc, 
		// 		nodeType=service, 
		// 		accessLog=false, 
		// 		minWorkerThread=20, 
		// 		protocol=motan, 
		// 		isDefault=true, 
		// 		application=myMotanDemo, 
		// 		maxWorkerThread=800, 
		// 		shareChannel=true, 
		// 		refreshTimestamp=1543822818150, 
		// 		id=com.weibo.api.motan.config.springsupport.ServiceConfigBean, // 这里着重说下这个属性的来历, spring在解析标签时, 假如某个标签没有id或者是name, 那么就使用该标签的属性包装类的全限定名作为id
		// 		export=demoMotan:8001, 
		// 		requestTimeout=200, 
		// 		maxServerConnection=80000, 
		// 		group=motan-demo-rpc
		// }
        collectMethodConfigParams(map, this.getMethods());
        URL serviceUrl = new URL(protocolName, hostAddress, port, interfaceClass.getName(), map);
        if (serviceExists(serviceUrl)) {
            LoggerUtil.warn(String.format("%s configService is malformed, for same service (%s) already exists ", interfaceClass.getName(),
                    serviceUrl.getIdentity()));
            throw new MotanFrameworkException(String.format("%s configService is malformed, for same service (%s) already exists ",
                    interfaceClass.getName(), serviceUrl.getIdentity()), MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
        }
        List<URL> urls = new ArrayList<URL>();
        // injvm 协议只支持注册到本地,其他协议可以注册到local、remote
        if (MotanConstants.PROTOCOL_INJVM.equals(protocolConfig.getId())) {
            URL localRegistryUrl = null;
            for (URL ru : registryURLs) {
                if (MotanConstants.REGISTRY_PROTOCOL_LOCAL.equals(ru.getProtocol())) {
                    localRegistryUrl = ru.createCopy();
                    break;
                }
            }
            if (localRegistryUrl == null) {
                localRegistryUrl =
                        new URL(MotanConstants.REGISTRY_PROTOCOL_LOCAL, hostAddress, MotanConstants.DEFAULT_INT_VALUE,
                                RegistryService.class.getName());
            }
            urls.add(localRegistryUrl);
        } else {
            for (URL ru : registryURLs) {
                urls.add(ru.createCopy());
            }
        }
        for (URL u : urls) {
            u.addParameter(URLParamType.embed.getName(), StringTools.urlEncode(serviceUrl.toFullStr()));
            registereUrls.add(u.createCopy());
        }
        // MotanConstants.DEFAULT_VALUE 对应 SimpleConfigHandler
        ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);
        // 步骤一. 该configHandler.export(interfaceClass, ref, urls)将返回一个export对象
        // 步骤二. exporters.add()则是缓存步骤一的export对象
        // urls = [zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc]
        // xxz
        Exporter<T> export2 = configHandler.export(interfaceClass, ref, urls);
        exporters.add(export2);
        //exporters.add(configHandler.export(interfaceClass, ref, urls));
        initLocalAppInfo(serviceUrl);
    }

① collectConfigParams(map, protocolConfig, basicServiceConfig, extConfig, this);

② collectMethodConfigParams(map, this.getMethods());

③ 判断协议类型

④ jdk的spi机制获取执行SimpleConfigHandler对象进行服务暴露.

1.2.1 collectConfigParams(map, protocolConfig, basicServiceConfig, extConfig, this); 

从protocolConfig, basicServiceConfig, extConfig包装对象以及this = serviceConfig对象中, 获取参数, 写入到map.

1.2.2 collectMethodConfigParams(map, this.getMethods());

再从<motan:method>对应的List<MethodConfig>封装对象list中, 获取接口method级别配置参数, 写入map

1.2.3 判断协议类型

if (MotanConstants.PROTOCOL_INJVM.equals(protocolConfig.getId())) {
            URL localRegistryUrl = null;
            for (URL ru : registryURLs) {
                if (MotanConstants.REGISTRY_PROTOCOL_LOCAL.equals(ru.getProtocol())) {
                    localRegistryUrl = ru.createCopy();
                    break;
                }
            }
            if (localRegistryUrl == null) {
                localRegistryUrl =
                        new URL(MotanConstants.REGISTRY_PROTOCOL_LOCAL, hostAddress, MotanConstants.DEFAULT_INT_VALUE,
                                RegistryService.class.getName());
            }
            urls.add(localRegistryUrl);
        } else {
            for (URL ru : registryURLs) {
                urls.add(ru.createCopy());
            }
        }

上面的代码很简单, 根据不通的条件实现不同的逻辑, 仔细点阅读基本上没有问题.

1.2.4 jdk的spi机制获取执行SimpleConfigHandler对象进行服务暴露

首先不懂什么是spi机制的, 可以查看本人以前针对jdk的spi机制写过的一篇博客:JDK Spi机制

motan框架相当于扩展了JDk的spi机制, 在motan中如果想要使用Spi机制, 暴露出来的接口需要使用@Spi注解进行标记, 该注解还

可以指定其接口的实现类在创建对象时, 是单例还是多例.

接着, 在其实现类上需要使用@SpiMeta注解, 用来标记该实现类的Spi名称, 根据该名称从META-INF/services/实现类列表文件, 

获取一个具体的该类的具体实现类, 完成对应的业务逻辑处理.

以这里的SimpleConfigHandler为例: 这里的MotanConstants.DEFAULT_VALUE = default

ConfigHandler configHandler = ExtensionLoader.getExtensionLoader(ConfigHandler.class).getExtension(MotanConstants.DEFAULT_VALUE);

① ConfigHandler接口

@Spi(scope = Scope.SINGLETON)
public interface ConfigHandler {

@Spi注解标记的, 其实现类是单实例创建

② SimpleConfigHandler实现类的创建

@SpiMeta(name = MotanConstants.DEFAULT_VALUE)
public class SimpleConfigHandler implements ConfigHandler {

@SpiMeta注解标记的, 该实现类的Spi名称为MotanConstants.DEFAULT_VALUE = default

所以根据项目的Spi名称同时为default的话, 那么motan利用spi机制就能创建出一个针对该接口的具体的实现类.

③ SimpleConfigHandler调用export()方法进行服务暴露.

configHandler.export(interfaceClass, ref, urls);

进入该方法的具体实现逻辑:

public <T> Exporter<T> export(Class<T> interfaceClass, T ref, List<URL> registryUrls) {
    	// serviceStr = motan://192.168.99.1:8001/com.weibo.motan.demo.service.MotanDemoService?maxContentLength=1048576&
    	//				module=motan-demo-rpc&nodeType=service&accessLog=false&minWorkerThread=20&protocol=motan&isDefault=true&
    	//				application=myMotanDemo&maxWorkerThread=800&shareChannel=true&refreshTimestamp=1544514033379&
    	//				id=com.weibo.api.motan.config.springsupport.ServiceConfigBean&export=demoMotan:8001&requestTimeout=200&
    	//				maxServerConnection=80000&group=motan-demo-rpc&
        String serviceStr = StringTools.urlDecode(registryUrls.get(0).getParameter(URLParamType.embed.getName()));
        // 解析serviceStr字符串, 转换成URL对象
        URL serviceUrl = URL.valueOf(serviceStr);
        // export service
        // protocolName = motan
        String protocolName = serviceUrl.getParameter(URLParamType.protocol.getName(), URLParamType.protocol.getValue());
        // 利用protocol decorator来增加filter特性
        Protocol protocol = new ProtocolFilterDecorator(ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(protocolName));
        // 很关键, 初始化一个DefaultProvider对象, server端用于执行反射调用, 执行业务逻辑代码, 并且将结果回写到客户端
        Provider<T> provider = new DefaultProvider<T>(ref, serviceUrl, interfaceClass);
        Exporter<T> exporter = protocol.export(provider, serviceUrl);
        // register service
        register(registryUrls, serviceUrl);
        return exporter;
    }

Protocol protocol = new ProtocolFilterDecorator(ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(protocolName));

这里的ProtocolFilterDecorator类可以理解成对DefaultRpcProtocol类的一种处理功能的能力增强, 比如, 根据一些其他额外的参数配置, 判断是否需要为该Protocol添加上这一功能, 例如, 根据xml配置文件中的accessLog参数进行判断, 是够需要增加accesslog.

所以最终服务暴露会调用DefaultRpcProtocol类的export()方法进行服务暴露.

④ DefaultRpcProtocol类调用export()方法进行服务暴露

public <T> Exporter<T> export(Provider<T> provider, URL url) {
        if (url == null) {
            throw new MotanFrameworkException(this.getClass().getSimpleName() + " export Error: url is null",
                    MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
        }
        if (provider == null) {
            throw new MotanFrameworkException(this.getClass().getSimpleName() + " export Error: provider is null, url=" + url,
                    MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
        }
        String protocolKey = MotanFrameworkUtil.getProtocolKey(url);
        synchronized (exporterMap) {
            Exporter<T> exporter = (Exporter<T>) exporterMap.get(protocolKey);
            if (exporter != null) {
                throw new MotanFrameworkException(this.getClass().getSimpleName() + " export Error: service already exist, url=" + url,
                        MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
            }
            exporter = createExporter(provider, url);
            exporter.init();
            exporterMap.put(protocolKey, exporter);
            LoggerUtil.info(this.getClass().getSimpleName() + " export Success: url=" + url);
            return exporter;
        }

步骤一, 首先exporter = createExporter(provider, url); 创建一个exporter用于暴露服务的实例对象:

protected <T> Exporter<T> createExporter(Provider<T> provider, URL url) {
    	// new创建一个DefaultRpcExporter对象时, 即在该类的构造方法中就启动netty server完成了服务的暴露
        return new DefaultRpcExporter<T>(provider, url);
    }

该createExporter()方法会初始化一个DefaultRpcExporter对象, 在初始化的过程中, 执行DefaultRpcExporter类的构造方法最终会生成一个NettyServer对象, 如下:

DefaultRpcExporter类的构造方法.

 public DefaultRpcExporter(Provider<T> provider, URL url) {
            super(provider, url);
            ProviderMessageRouter requestRouter = initRequestRouter(url);
            String param1 = URLParamType.endpointFactory.getName();
            String param2 = URLParamType.endpointFactory.getValue();
            String result = url.getParameter(param1, param2);
            // 利用spi机制实例化一个NettyEndpointFactory对象
            endpointFactory =ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(result);
            //endpointFactory =
            //        ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(
            //                url.getParameter(URLParamType.endpointFactory.getName(), URLParamType.endpointFactory.getValue()));
            // 使用NettyEndpointFactory对象创建一个netty server
            server = endpointFactory.createServer(url, requestRouter);
        }

endpointFactory =ExtensionLoader.getExtensionLoader(EndpointFactory.class).getExtension(result);

再次利用Spi机制生成一个NettyEndpointFactory对象, 这里的result = motan, 根据Spi名称为"motan", 框架很容易就能生成这个NettyEndpointFactory实例对象.

接着NettyEndpointFactory类调用createServer()方法, 生成一个NettyServer对象,

endpointFactory.createServer(url, requestRouter);  // 返回一个NettyServer对象, 后面用于开启一个netty server服务, 对外提供服务, 接口client端的请求.

步骤二: DefaultRpcExporter实例对象调用init()方法, open开启netty server服务.

步骤一中执行exporter = createExporter(provider, url);方法, 返回了一个DefaultRpcExporter实例对象, 调用init()方法, 开启netty 服务, exporter.init();

步骤三, 查看init方法的实现

public synchronized void init() {
        if (init) {
            LoggerUtil.warn(this.getClass().getSimpleName() + " node already init: " + desc());
            return;
        }
        boolean result = doInit();
        if (!result) {
            LoggerUtil.error(this.getClass().getSimpleName() + " node init Error: " + desc());
            throw new MotanFrameworkException(this.getClass().getSimpleName() + " node init Error: " + desc(),
                    MotanErrorMsgConstant.FRAMEWORK_INIT_ERROR);
        } else {
            LoggerUtil.info(this.getClass().getSimpleName() + " node init Success: " + desc());
            init = true;
            available = true;
        }
    }

可以看到该方法又调用了doInit()方法, 开启netty server服务, 熟悉设计模式的朋友, 阅读源码时, 看到这里其实一眼就能看出来, 这里采用了模板方法模式(简单说一句), 接着我们就来查看, doInit()方式是怎么开启一个Netty Server服务的.

步骤四, doInit()开启一个Netty Server服务

protected boolean doInit() {
        	// 开启一个netty server 服务, 监听客户端请求
            boolean result = server.open();
            return result;
        }

可以看到, 代码很简单, 就是调用了NettyServer类的open()方法, 开启netty服务:

public synchronized boolean open() {
		if (isAvailable()) {
			LoggerUtil.warn("NettyServer ServerChannel already Open: url=" + url);
			return true;
		}
		LoggerUtil.info("NettyServer ServerChannel start Open: url=" + url);
		initServerBootstrap();
		serverChannel = bootstrap.bind(new InetSocketAddress(url.getPort()));
		state = ChannelState.ALIVE;
		StatsUtil.registryStatisticCallback(this);
		LoggerUtil.info("NettyServer ServerChannel finish Open: url=" + url);
		return state.isAliveState();
	}

initServerBootstrap(); // 初始化netty server服务配置

serverChannel = bootstrap.bind(new InetSocketAddress(url.getPort()));//从URL对象中取出配置文件中配置的服务暴露端口号, bind()绑定该端口号, netty服务端将监听该端口号, 接收客户端请求.

至此, motan框架就通过启动netty server绑定xml配置的服务暴露端口号, 进行服务暴露, 接收client端客户请求, 处理客户端请求.

二, 总结:

motan框架在进行服务暴露时, 主要是利用Jdk的spi机制加载合适的处理类, 完成对应的功能. 接着就是运用到了一些设计模式,  所以大家再看这一部分代码时, 会觉得绕来绕去的, 其实motan服务暴露过程的代码编写, 是一种特别好的松耦合设计, 以插件的形式根据不同的条件, 加载不同的配置类, 完成自己的某一项功能, 这是一种特别好的编程设计思想, 值得我们每一位编程人员学习借鉴.

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值