SOFABolt 源码分析15 - 双工通信机制的设计

image.png

SOFABolt 提供了双工通信能力,使得不仅客户端可以调用服务端,服务端也可以主动调用客户端(当然,客户端也就需要可以注册 UserProcessor 的功能)。

SOFABolt 四种调用模式:oneway / sync / future / callback
SOFABolt 三种调用链路:addr / url / connection,
注意:在整个调用过程中,调用链路会发生如下转化:addr -> url -> connection

如上图所示,整个调用辅助类包含:

  • BaseRemoting:提供了四种基本调用模式的方法(基于 Connection 的,值得注意的是,Connection 也是三种调用链路最底层的),这四种调用模式也提供了基本的调用模板;
  • RpcRemoting
  • 实现了 RpcProtocol 的初始化
  • 实现了基于 addr 链路的四种基本调用模式模板(内部实际调用基于 url 链路模式)
  • 提供了将请求对象 Object 封装为 RemotingCommand 的方法
  • RpcClientRemoting
  • 实现了基于 url 链路的四种基本调用模式模板(内部实际调用基于 connection 链路模式,根据 url 调用建连接口创建 connection)
  • 提供了建连操作
  • RpcServerRemoting

实现了基于 url 链路的四种基本调用模式模板(内部实际调用基于 connection 链路模式,但是不会根据 url 创建 connection,只会从连接管理器根据 url 获取连接 - 所以要想使用基于 url 链路的功能,必须开启服务端连接管理功能,而基于 addr 链路的方式底层又是转化为 url 链路方式,所以基于 addr 链路的功能,也必须开启服务端连接管理功能

注意:

  • 客户端调用服务端会主动建连,服务端调用客户端不会主动建连
  • 服务端想使用基于 url 链路或者基于 addr 链路的调用功能,必须开启服务端连接管理功能(实际上还需要保存 addr 链路地址,通常通过 UserProcessor.handleRequest 中的 BizContext 来获取 remoteAddr 并存储在 UserProcessor 中)
  • 服务端如果没有开启服务端连接管理功能,只能通过 connection 链路进行调用,此时要保存好连接建立好的时候创建的 connection 对象(通常使用 ConnectionEventType.CONNECT 的连接事件处理器做这件事)

一、使用姿势

1.1、基于 addr 链路模式

服务端

public class MyServer {
    public static void main(String[] args) throws RemotingException, InterruptedException {
        RpcServer server= new RpcServer(8888);
        MyServerUserProcessor serverUserProcessor = new MyServerUserProcessor();
        server.registerUserProcessor(serverUserProcessor);
        // 打开服务端连接管理功能
        server.switches().turnOn(GlobalSwitch.SERVER_MANAGE_CONNECTION_SWITCH);

        if (server.start()) {
            System.out.println("server start success!");
            // 模拟去其他事情
            Thread.sleep(10000);
            MyRequest request = new MyRequest();
            request.setReq("hi, bolt-client");
            // 向 serverUserProcessor 存储的 RemoteAddr 发起请求
            MyResponse resp = (MyResponse)server.invokeSync(serverUserProcessor.getRemoteAddr(), request, 10000);
            System.out.println(resp.getResp());
        } else {
            System.out.println("server start fail!");
        }
    }
}

=========================== 服务端业务逻辑处理器 ===========================
public class MyServerUserProcessor extends SyncUserProcessor<MyRequest> {
    // 存储 client 端地址,用于发起远程调用
    private String remoteAddr;

    @Override
    public Object handleRequest(BizContext bizCtx, MyRequest request) throws Exception {
        remoteAddr = bizCtx.getRemoteAddress(); // 此处也可以存储 Connection:bizCtx.getConnection();
        MyResponse response = new MyResponse();
        if (request != null) {
            System.out.println(request);
            response.setResp("from server -> " + request.getReq());
        }

        return response;
    }

    @Override
    public String interest() {
        return MyRequest.class.getName();
    }

    public String getRemoteAddr() {
        return remoteAddr;
    }
}

客户端

public class MyClient {
    private static RpcClient client;
    private static CountDownLatch latch = new CountDownLatch(1);

    public static void start() {
        client = new RpcClient();
        // 注册业务逻辑处理器
        client.registerUserProcessor(new MyClientUserProcessor());
        client.init();
    }

    public static void main(String[] args) throws RemotingException, InterruptedException {
        MyClient.start();
        MyRequest request = new MyRequest();
        request.setReq("hello, bolt-server");
        MyResponse response = (MyResponse) client.invokeSync("127.0.0.1:8888", request, 300 * 1000);
        System.out.println(response);
        latch.await();
    }
}

=========================== 客户端业务逻辑处理器 ===========================
public class MyClientUserProcessor extends SyncUserProcessor<MyRequest> {
    @Override
    public Object handleRequest(BizContext bizCtx, MyRequest request) throws Exception {
        MyResponse response = new MyResponse();
        if (request != null) {
            System.out.println(request);
            response.setResp("from client -> " + request.getReq());
        }
        return response;
    }

    @Override
    public String interest() {
        return MyRequest.class.getName();
    }
}

1.2、基于 connection 链路模式

服务端

public class MyServer {
    public static void main(String[] args) throws RemotingException, InterruptedException {
        RpcServer server= new RpcServer(8888);
        MyServerUserProcessor serverUserProcessor = new MyServerUserProcessor();
        server.registerUserProcessor(serverUserProcessor);
        // 创建并注册 ConnectionEventType.CONNECT 连接事件处理器
        MyCONNECTEventProcessor connectEventProcessor = new MyCONNECTEventProcessor();
        server.addConnectionEventProcessor(ConnectionEventType.CONNECT, connectEventProcessor);
        if (server.start()) {
            System.out.println("server start success!");
            // 模拟去其他事情
            Thread.sleep(10000);
            MyRequest request = new MyRequest();
            request.setReq("hi, bolt-client");
            // 向 connectEventProcessor 存储的 connection 发起请求
            MyResponse resp = (MyResponse)server.invokeSync(connectEventProcessor.getConnection(), request, 10000);
            System.out.println(resp.getResp());
        } else {
            System.out.println("server start fail!");
        }
    }
}

=========================== 连接事件处理器 ===========================
public class MyCONNECTEventProcessor implements ConnectionEventProcessor {
    // 存储连接,用于服务端向客户端发起远程通信
    private Connection connection;

    @Override
    public void onEvent(String remoteAddr, Connection conn) {
        this.connection = conn;
        System.out.println("hello, " + remoteAddr);
    }

    public Connection getConnection() {
        return connection;
    }
}

客户端

同 1.1

二、源码解析

建连

======================================== RpcServer ========================================
    protected void doInit() {
        // 开启服务端连接管理功能
        if (this.switches().isOn(GlobalSwitch.SERVER_MANAGE_CONNECTION_SWITCH)) {
            this.connectionEventHandler = new RpcConnectionEventHandler(switches());
            // 与客户端一样,创建连接管理器,并设置到 connectionEventHandler 中
            this.connectionManager = new DefaultConnectionManager(new RandomSelectStrategy());
            this.connectionEventHandler.setConnectionManager(this.connectionManager);
            this.connectionEventHandler.setConnectionEventListener(this.connectionEventListener);
        } else {
            this.connectionEventHandler = new ConnectionEventHandler(switches());
            this.connectionEventHandler.setConnectionEventListener(this.connectionEventListener);
        }
        initRpcRemoting();
        ...
        this.bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel channel) {
                ...
                createConnection(channel);
            }

            private void createConnection(SocketChannel channel) {
                Url url = addressParser.parse(RemotingUtil.parseRemoteAddress(channel));
                // 如果开启了连接管理功能,则新建 Connection 并加入 连接管理器;
                // 如果没有开启,则直接新建 Connection
                if (switches().isOn(GlobalSwitch.SERVER_MANAGE_CONNECTION_SWITCH)) {
                    connectionManager.add(new Connection(channel, url), url.getUniqueKey());
                } else {
                    new Connection(channel, url);
                }
                // 发布建连事件,此时进行连接的保存
                channel.pipeline().fireUserEventTriggered(ConnectionEventType.CONNECT);
            }
        });
    }

======================================== DefaultConnectionManager ========================================
    public void add(Connection connection, String poolKey) {
        ConnectionPool pool = null;
        // 如果有 ConnectionPool 直接获取,没有就创建一个新的
        // 与客户端不同的是,该 add 方法只创建一个空的 ConnectionPool,不会初始化连接;而客户端会初始化连接
        // 连接是否初始化,取决于 ConnectionPoolCall 构造器参数
        pool = this.getConnectionPoolAndCreateIfAbsent(poolKey, new ConnectionPoolCall());
        // 连接加入连接池
        pool.add(connection);
    }

    private class ConnectionPoolCall implements Callable<ConnectionPool> {
        // 是否初始化连接
        private boolean whetherInitConnection;
        private Url     url;

        // 默认构造器:只创建 pool,不建连(服务端用的这个)
        public ConnectionPoolCall() {
            this.whetherInitConnection = false;
        }

        // 创建 pool + 建连(客户端用的这个)
        public ConnectionPoolCall(Url url) {
            this.whetherInitConnection = true;
            this.url = url;
        }

        @Override
        public ConnectionPool call() throws Exception {
            // 创建 pool
            final ConnectionPool pool = new ConnectionPool(connectionSelectStrategy);
            if (whetherInitConnection) {
                // 建连
                doCreate(this.url, pool, this.getClass().getSimpleName(), 1);
            }
            return pool;
        }
    }

调用

======================================== RpcRemoting ========================================
    public Object invokeSync(String addr, Object request, InvokeContext invokeContext, int timeoutMillis) {
        // addr => url
        Url url = this.addressParser.parse(addr);
        // 以 url 链路方式进行调用
        return this.invokeSync(url, request, invokeContext, timeoutMillis);
    }

======================================== RpcServerRemoting ========================================
    public Object invokeSync(Url url, Object request, InvokeContext invokeContext, int timeoutMillis) {
        // 直接从 connectionManager 获取连接(仅仅是获取,不建连)
        Connection conn = this.connectionManager.get(url.getUniqueKey());
        // 检查连接
        this.connectionManager.check(conn);
        // 以 connection 链路方式进行调用
        return this.invokeSync(conn, request, invokeContext, timeoutMillis);
    }

    public Connection DefaultConnectionManager#get(String poolKey) {
        // 获取连接池,如果没有返回null,否则从连接池获取一个 connection。整个过程只是单纯的获取,不做建连操作
        ConnectionPool pool = this.getConnectionPool(this.connTasks.get(poolKey));
        return null == pool ? null : pool.get();
    }
======================================== RpcClientRemoting ========================================
    public Object invokeSync(Url url, Object request, InvokeContext invokeContext, int timeoutMillis) {
        // 获取连接,如果没有就建连
        final Connection conn = getConnectionAndInitInvokeContext(url, invokeContext);
        // 检查连接
        this.connectionManager.check(conn);
        // 以 connection 链路方式进行调用
        return this.invokeSync(conn, request, invokeContext, timeoutMillis);
    }

======================================== RpcRemoting ========================================
    public Object invokeSync(Connection conn, Object request, InvokeContext invokeContext, int timeoutMillis) {
        // 构造请求统一体
        RemotingCommand requestCommand = toRemotingCommand(request, conn, invokeContext, timeoutMillis);
        // 预处理 invokeContext
        preProcessInvokeContext(invokeContext, requestCommand, conn);
        // 发起调用,返回响应统一体
        ResponseCommand responseCommand = (ResponseCommand) super.invokeSync(conn, requestCommand, timeoutMillis);
        // 设置 invokeContext 到响应统一体
        responseCommand.setInvokeContext(invokeContext);
        // 从响应统一体解析出真正的响应消息
        Object responseObject = RpcResponseResolver.resolveResponseObject(responseCommand, RemotingUtil.parseRemoteAddress(conn.getChannel()));
        return responseObject;
    }

======================================== BaseRemoting ========================================
    protected RemotingCommand invokeSync(Connection conn, RemotingCommand request, int timeoutMillis) {
        // 创建 InvokeFuture
        final InvokeFuture future = createInvokeFuture(request, request.getInvokeContext());
        // 将 InvokeFuture 添加到 Connection 的 InvokeFutureMap 映射中
        conn.addInvokeFuture(future);
        // 发起 netty 请求
        conn.getChannel().writeAndFlush(request);
        // 阻塞等待响应结果
        RemotingCommand response = future.waitResponse(timeoutMillis);
        // 被唤醒后返回响应结果
        return response;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266