Motan 学习笔记(一) - 初识 Motan

简介

Motan 是微博开源的 RPC 框架。作为一个 RPC 框架,motan 和其它的 RPC 实现(如 Dubbo)一样,基于以下的架构提供 RPC 调用:

RPC 成员间交互

motan 官方给出的架构图中并没有加入 manager,可能是因为 manager 可以单独部署,并不是必要的功能吧。

motan 它有如下的核心功能:

Motan 核心功能

项目模块

项目模块

demo

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- GAV... -->

    <properties>
        <motan.version>1.1.3</motan.version>
        <java.source.jdk>1.8</java.source.jdk>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.22</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>com.weibo</groupId>
            <artifactId>motan-core</artifactId>
            <version>${motan.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.weibo</groupId>
            <artifactId>motan-transport-netty</artifactId>
            <version>${motan.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 这里我们与 Spring IOC 容器集成,需要使用这个包实现命名空间等 -->
        <dependency>
            <groupId>com.weibo</groupId>
            <artifactId>motan-springsupport</artifactId>
            <version>${motan.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>com.weibo</groupId>
            <artifactId>motan-registry-zookeeper</artifactId>
            <version>${motan.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>log4j</artifactId>
                    <groupId>log4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>slf4j-log4j12</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- 实现 classpath 包括编译生成的 Async 服务接口所在的包 -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>build-helper-maven-plugin</artifactId>
                <version>3.0.0</version>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>add-source</goal>
                        </goals>
                        <configuration>
                            <sources>
                                <source>${project.build.directory}/generated-sources/annotations</source>
                            </sources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            
            <!-- 编译插件,插手 maven 生命周期的 compile 阶段,
                 绑定 generate-sources,触发上面插件 build-helper-maven-plugin 的动作,
                 如果不定义这个 execution,就需要编译两次,第一次会失败 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>${java.source.jdk}</source>
                    <target>${java.source.jdk}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
                <executions>
                    <execution>
                        <id>process-annotations</id>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <!-- 为 true 时,JVM 参数 -DmotanGeneratePath=xxx 无效 -->
                            <fork>true</fork>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Bean

@Data
public class MyTransportBean implements Serializable {
    private static final long serialVersionUID = -7721510972575436714L;
    private String msg;
    private Date date;
}

服务接口

@MotanAsync
public interface MyMotanService {
    MyTransportBean getTrans(MyTransportBean transBean) throws Exception;
}

服务实现

public class MyMotanServiceImpl implements MyMotanService{
    @Override
    public MyTransportBean getTrans(MyTransportBean transBean) throws Exception {
        System.out.println(transBean);

        transBean.setMsg("message from server");
        Thread.sleep(1000);
        transBean.setDate(new Date());

        System.out.println(transBean);
        return transBean;
    }
}

服务提供方

public class MotanServer {
    public static void main(String[] args) {
        new ClassPathXmlApplicationContext("motan-server.xml");
        // 开关,用于启动 motan 服务与注册中心之间的心跳检测
        MotanSwitcherUtil.setSwitcherValue(MotanConstants.REGISTRY_HEARTBEAT_SWITCHER, true);
        System.out.println("Server started.");
    }
}

服务调用方

public class MotanClient {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("motan-client.xml");
        MyMotanServiceAsync service = (MyMotanServiceAsync) context.getBean("remoteService");
        try {
            MyTransportBean bean = new MyTransportBean();
            bean.setMsg("message from client");
            bean.setDate(new Date());

            // 同步调用
            System.out.println(service.getTrans(bean));

            // 异步调用
            System.out.println(service.getTransAsync(bean).getValue());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果

在 2181 端口启动 Zookeeper,先启动 MotanServer:

MotanServer

启动 MotanClient:

MotanClient

调用成功。

分析

观察 ZK 服务提供者节点:

ls /motan/default_rpc/com.cdf.motan.rpc.MyMotanService/server

ZK 中的服务提供者

观察 ZK 服务消费者节点:

ls /motan/default_rpc/com.cdf.motan.rpc.MyMotanService/client

ZK 中的服务调用者

查看 ZK 端口占用:

lsof -i |head -1;lsof -i|grep $(jps |grep QuorumPeerMain |awk '{print $1}')

ZK 端口占用

UPDATE:49546 是分配给 ZK 的集群 Leader 竞选所用端口号,eforward 和下面的 teradataordbms 分别代表 2181 和 8002,由于当时 lsof 命令没加 -n 避免域名解析所以出现了这样的情况,不用关心。

可以看到,ZK 与 56455 和 56460 分别建立了 TCP 连接。

我们查看这两个端口的占用情况:

lsof -i |head -1;lsof -i|grep -E "56455|56460"

与 ZK 通信的进程

发现是 5903 和 5905 两个 JVM 进程占用的,这两个进程分别是 MotanServer 和 MotanClient:

jps 查看 JVM Process

分别查看这两个进程的端口占用,除了与 IDE(这里是 IntelliJ IDEA) 有关的 1212 进程外,服务提供者和服务调用者都与 2181 端口的 Zookeeper 保持了 TCP 长连接,且服务提供者在 8002 端口暴露服务,故还需要监听 8002 端口:

MotanServer 和 MotanClient 端口占用

而其它三个端口是 Consumer 直连 Provider 所使用的 Socket 端口,这也印证了简介中的架构图。