当前位置: 首页 > 工具软件 > Motan > 使用案例 >

Motan服务的启动

甄坚白
2023-12-01

Motan是新浪于2016年开源的一个RPC框架,类似的已有的RPC框架有像阿里开源的Dubbo,还有后来在此基础上做二次开源的当当网的dubbox。
Motan分为服务提供方和服务调用方,服务提供方发布服务,服务调用方调用服务。
看这样一个简单的例子:
1、创建一个接口,并且简单实现:

public interface FooService {
    public String hello(String name);
}
public class FooServiceImpl implements FooService {
    public String hello(String name) {
        System.out.println("Invoke RPC service method, "+name);
        return "Hello, "+name;
    }
}

2、配置xm

<?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:motan="http://api.weibo.com/schema/motan"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
   http://api.weibo.com/schema/motan http://api.weibo.com/schema/motan.xsd">
    <bean id="serviceImpl" class="quickstart.FooServiceImpl" />
    <motan:registry address="127.0.0.1:2181" regProtocol="zookeeper" name="myzk"></motan:registry>
    <motan:service interface="quickstart.FooService" ref="serviceImpl" registry="myzk" group="myMotanService1" export="8080"/>

</beans>

xml的配置也很简单易懂:
先定义一个id为serviceImpl的类,也就是我们的服务具体实现,然后将服务声明为zookeeper协议,并且注册到本地2181端口上,最后将服务实现发布成服务,暴露的端口为8080.
3、启动本地zk,端口默认为2181.运行服务器程序:

public class ServerZK {
    public static void main(String[] args) {

        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:motanServer.xml");
        MotanSwitcherUtil.setSwitcherValue(MotanConstants.REGISTRY_HEARTBEAT_SWITCHER,true);
        System.out.println("Server start...");
    }
}

运行输出信息:Server start…
服务发布成功了吗?可以使用zkClient查看目录:
/motan/myMotanService1/quickstart.FooService/server
发现有可用服务:
[192.168.0.6:8080]
运行main方法之后,这发生了什么?
观察我们的xml配置,有两点值得说明:
1、该xml使用了名为【motan】的Namespace
2、bean和registry的配置都被使用在了service配置中
对于第一点,由于是和Spring结合使用,所以motan命名空间肯定是会有相关联的NamespaceHandler进行解析的,也一定可以在motan的jar包中找到对应的xsd约束(在motan-core.jar包中就可以找到)。
根据一个不成文的约定(很多命名空间都是这样的,像Spring自带的aop命名空间,解析的Handler就是AOPNamespaceHandler),查找MotanNamespaceHandler类。
这个类负责来解析xml到Motan所需要使用到的类,具体过程很繁琐而无味。

service被解析成了ServiceConfigBean这个类,查看这个类,发现它的方法签名:

public class ServiceConfigBean<T> extends ServiceConfig<T>
        implements
        BeanPostProcessor,
        BeanFactoryAware,
        InitializingBean,
        DisposableBean,
        ApplicationListener<ContextRefreshedEvent> 

实现了Spring暴露的扩展接口。
最后一个是ApplicationListener,这个类使得我们的ServiceConfigBean类监听了Spring的ContextRefreshEvent,在ApplicationContext加载时(此处是ClasspathXmlApplicationContext)的finishRefresh方法触发监听器,调用ServiceConfigBean的onApplicationEvent方法。
可以看到,这个方法调用了export方法,也就是发布服务的方法。

========分割线==========
实现InitializingBean的类,需要实现方法:

public void afterPropertiesSet() throws Exception {

        // 注意:basicConfig需要首先配置,因为其他可能会依赖于basicConfig的配置
        checkAndConfigBasicConfig();
        checkAndConfigExport();
        checkAndConfigRegistry();

        // 等spring初始化完毕后,再export服务
        // export();
    }

三个方法调用,分别加载了basicService;
checkAndConfigExport检查是否有export配置(如果没有,则用basicService中配置的export配置);
checkAndConfigRegistry保证发布的服务能有合适的registry,如果service配置了就使用该配置,如果没有,则取basicService中配置的,如果basicService中没有配置则从MotanNamespaceHandler.registryDefineNames中去加载(以防没有成功生成对象),如果以上的操作都没有获取到,则使用默认的local配置。

讲了这么一堆,现在才刚刚开始进入本节的主题,终于要开始发布服务了。

public void onApplicationEvent(ContextRefreshedEvent event) {
        if (!getExported().get()) {
            export();
        }
    }

每个ServiceConfigBean都有一个exported布尔值,表示该Service是不是已经被发布过。如果发布过,就不再发布。
接下来进入发布的方法,export():

public synchronized void export() {
        //在同步块中再次检查是不是已经被发布过了
        //此处的exported变量是J.C.U包中的原子变量,可以保证在多个线程之间及时可见
        if (exported.get()) {
            LoggerUtil.warn(String.format("%s has already been expoted, so ignore the export request!", interfaceClass.getName()));
            return;
        }
        //① 检查 方法是不是在接口中存在
        checkInterfaceAndMethods(interfaceClass, methods);
        //② 加载注册地址列表
        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()));
            }
            // ④ 在注册的地址上,按协议和端口 进行服务发布
            doExport(protocolConfig, port, registryUrls);
        }
        //⑤ 发布之后的一些操作
        afterExport();
    }

几个主要的步骤都被抽成方法,嗯,所以这个方法看上去还是挺简洁易懂的。
1、检查方法是不是在接口中存在
由于moton发布service的时候可以指定的方法,所以需要在export之前,确认指定的接口是不是存在该方法。

2、生成注册地址列表

protected List<URL> loadRegistryUrls() {
        List<URL> registryList = new ArrayList<URL>();
        if (registries != null && !registries.isEmpty()) {
            //遍历registry配置信息
            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>();
                //将registry的各个属性,以键值对的形式存储在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内部可能包含多个注册中心地址
                //将地址和参数构造成一个motan服务地址,类似于下面的格式:
                // zookeeper://127.0.0.1:2181/com.weibo.api.motan.registry.RegistryService?group=default_rpc

                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;
    }

3、生成协议和端口的键值对
一个service可以按多个protocol提供服务,不同protocol使用不同port 利用export来设置protocol和port,格式如下:
protocol1:port1,protocol2:port2
4、发布服务
Motan使用Netty做为底层的服务发布框架。
对于发布服务来说,doExport方法重点在下面的两句:

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

        exporters.add(configHandler.export(interfaceClass, ref, urls));

ConfigHandler是一个很重要的对象,连接了配置和协议。
实现为SimpleConfigHandler,它主要提供如下几个方面的功能:
1、提供服务集群模块,可以增加过滤器
2、将service关联到具体的Protocol对象,后者是提供Netty服务的对象
3、提供注册和取消注册的功能
4、提供服务的注销功能
这个具体以后还会再提到。
configHandler的export方法将返回Export对象,该例子中使用的的”DefaultRpcExporter”实现;
Exporter是Motan中一个很重要的对象,它通过SPI配置创建EndpointFactory对象,并通过createServer方法暴露服务。
这部分的内容,之后还会详细看。

-EOF-

 类似资料: