The processor of sofa-bolt source code interpretation

 

For official documents, please refer to: https://www.sofastack.tech/projects/sofa-bolt/sofa-bolt-handbook/

Short book high-quality blog post: https://www.jianshu.com/p/f2b8a2099323

table of Contents

Two types of processors

User request handler

Sync request processor abstract class SyncUserProcessor

Asynchronous request processor abstract class AsyncUserProcessor

Connection event handler

ConnectionEventProcessor connection event interface class

CONNECTEventProcessor connection event handler

DISCONNECTEventProcessor disconnect event handler


Two types of processors

Sofa-bolt provides two types of processors: user request processor and connection event processor .

The user request processor is mainly used to respond to the request data, for example, the server sends a specific response message to the client after receiving the request.

The connection event handler is mainly used to monitor events such as connection, close, and connection exception, so as to perform some related operations.

User request handler

Two user request processors are provided, SyncUserProcessor and AsyncUserProcessor . The difference between the two is that the former needs to return the processing result in the form of return value in the current processing thread; while the latter, there is an AsyncContext stub, which can be in the current thread or in the asynchronous thread, calling the  sendResponse method to return the processing result.

The inheritance relationship class diagram is as follows:

Examples can refer to the following two categories:

Sync request processor abstract class SyncUserProcessor

/**
 * 继承这个类去处理用户用同步的方式定义的请求
 * Extends this to process user defined request in SYNC way.<br>
 * 如果你想用异步的方式处理请求,请拓展AsyncUserProcessor处理器
 * If you want process request in ASYNC way, please extends {@link AsyncUserProcessor}.
 *
 * @author xiaomin.cxm
 * @version $Id: SyncUserProcessor.java, v 0.1 May 19, 2016 2:47:21 PM xiaomin.cxm Exp $
 */
public abstract class SyncUserProcessor<T> extends AbstractUserProcessor<T> {
    /**
     * @see com.alipay.remoting.rpc.protocol.UserProcessor#handleRequest(com.alipay.remoting.BizContext, java.lang.Object)
     */
    @Override
    public abstract Object handleRequest(BizContext bizCtx, T request) throws Exception;

    /**
     * unsupported here!
     *
     * @see com.alipay.remoting.rpc.protocol.UserProcessor#handleRequest(com.alipay.remoting.BizContext, com.alipay.remoting.AsyncContext, java.lang.Object)
     */
    @Override
    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, T request) {
        throw new UnsupportedOperationException(
            "ASYNC handle request is unsupported in SyncUserProcessor!");
    }

    /**
     * @see com.alipay.remoting.rpc.protocol.UserProcessor#interest()
     */
    @Override
    public abstract String interest();
}

You can see that there are two handleRequest() methods defined in this abstract class, one with return value and one without return value, and the method without return value directly gives an exception handling, prompting that asynchronous requests are not supported. Therefore, for the user's request using the synchronous method, it is necessary to implement the handleRequest() with a return value. For specific examples, please refer to the official example: Custom User Synchronization Handler

The following is the intercepted partial code of duplicate handleRequest(), we can see that a default server response is returned.

    // ~~~ override methods

    @Override
    public Object handleRequest(BizContext bizCtx, RequestBody request) throws Exception {
        logger.warn("Request received:" + request + ", timeout:" + bizCtx.getClientTimeout()
                    + ", arriveTimestamp:" + bizCtx.getArriveTimestamp());
        if (bizCtx.isRequestTimeout()) {
            String errMsg = "Stop process in server biz thread, already timeout!";
            processTimes(request);
            logger.warn(errMsg);
            throw new Exception(errMsg);
        }

        this.remoteAddr = bizCtx.getRemoteAddress();

        //test biz context get connection
        Assert.assertNotNull(bizCtx.getConnection());
        Assert.assertTrue(bizCtx.getConnection().isFine());

        Long waittime = (Long) bizCtx.getInvokeContext().get(InvokeContext.BOLT_PROCESS_WAIT_TIME);
        Assert.assertNotNull(waittime);
        if (logger.isInfoEnabled()) {
            logger.info("Server User processor process wait time {}", waittime);
        }

        latch.countDown();
        logger.warn("Server User processor say, remote address is [" + this.remoteAddr + "].");
        Assert.assertEquals(RequestBody.class, request.getClass());
        processTimes(request);
        if (!delaySwitch) {
            return RequestBody.DEFAULT_SERVER_RETURN_STR;
        }
        try {
            Thread.sleep(delayMs);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return RequestBody.DEFAULT_SERVER_RETURN_STR;
    }

Looking at the source code, we can see that the adapter mode is used here

Among them, AbstractUserProcessor can be regarded as an adapter of an interface (a default implementation is made for each method of the interface, so that the four lowest-level abstractions can only implement the methods they need to implement), so I prefer to name it UserProcessorAdapter

Asynchronous request processor abstract class AsyncUserProcessor

/**
 * Extends this to process user defined request in ASYNC way.<br>
 * If you want process request in SYNC way, please extends {@link SyncUserProcessor}.
 *
 * @author xiaomin.cxm
 * @version $Id: AsyncUserProcessor.java, v 0.1 May 16, 2016 8:18:03 PM xiaomin.cxm Exp $
 */
public abstract class AsyncUserProcessor<T> extends AbstractUserProcessor<T> {
    /**
     * unsupported here!
     *
     * @see com.alipay.remoting.rpc.protocol.UserProcessor#handleRequest(com.alipay.remoting.BizContext, java.lang.Object)
     */
    @Override
    public Object handleRequest(BizContext bizCtx, T request) throws Exception {
        throw new UnsupportedOperationException(
            "SYNC handle request is unsupported in AsyncUserProcessor!");
    }

    /**
     * @see com.alipay.remoting.rpc.protocol.UserProcessor#handleRequest(com.alipay.remoting.BizContext, com.alipay.remoting.AsyncContext, java.lang.Object)
     */
    @Override
    public abstract void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, T request);

    /**
     * @see com.alipay.remoting.rpc.protocol.UserProcessor#interest()
     */
    @Override
    public abstract String interest();
}

You can see that there are two handleRequest() methods defined in this abstract class, one with a return value and one without a return value, and the method with a return value directly gives an exception handling, prompting that the synchronous request is not supported. Therefore, for the user's request in asynchronous mode, it is necessary to implement handleRequest() without a return value. For specific examples, please refer to the official example: Custom User Asynchronous Handler

The following is the intercepted part of the code that rewrites handleRequest(). We can see that there is an AsyncContext stub, which can be in the current thread, or in the asynchronous thread, in the asynchronous thread, calling the  sendResponse method to return the processing result.

    @Override
    public void handleRequest(BizContext bizCtx, AsyncContext asyncCtx, RequestBody request) {
        this.asyncExecutor.execute(new InnerTask(bizCtx, asyncCtx, request));
    }

    class InnerTask implements Runnable {
        private BizContext   bizCtx;
        private AsyncContext asyncCtx;
        private RequestBody  request;

        public InnerTask(BizContext bizCtx, AsyncContext asyncCtx, RequestBody request) {
            this.bizCtx = bizCtx;
            this.asyncCtx = asyncCtx;
            this.request = request;
        }

        public void run() {
            logger.warn("Request received:" + request);
            remoteAddr = bizCtx.getRemoteAddress();
            latch.countDown();
            logger.warn("Server User processor say, remote address is [" + remoteAddr + "].");
            Assert.assertEquals(RequestBody.class, request.getClass());
            processTimes(request);
            if (isException) {
                this.asyncCtx.sendResponse(new IllegalArgumentException("Exception test"));
            } else if (isNull) {
                this.asyncCtx.sendResponse(null);
            } else {
                if (!delaySwitch) {
                    this.asyncCtx.sendResponse(RequestBody.DEFAULT_SERVER_RETURN_STR);
                    return;
                }
                try {
                    Thread.sleep(delayMs);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.asyncCtx.sendResponse(RequestBody.DEFAULT_SERVER_RETURN_STR);
            }
        }
    }

Connection event handler

Two types of event monitoring are provided, connection event (ConnectionEventType.CONNECT) and disconnection event (ConnectionEventType.CLOSE). Users can create their own event handlers and register them to the client or server. Both the client and the server can monitor their respective connection establishment and disconnection events.

Take a look at the following enumeration class, which contains four events, connection, connection failure, close, and exception.

/**
 * Event triggered by connection state.
 * 
 * @author jiangping
 * @version $Id: ConnectionEventType.java, v 0.1 Mar 4, 2016 8:03:27 PM tao Exp $
 */
public enum ConnectionEventType {
    CONNECT, CONNECT_FAILED, CLOSE, EXCEPTION;
}

ConnectionEventProcessor connection event interface class

/**
 * Process connection events.
 * @author jiangping
 * @version $Id: ConnectionEventProcessor.java, v 0.1 Mar 5, 2016 11:01:07 AM tao Exp $
 */
public interface ConnectionEventProcessor {
    /**
     * Process event.<br>
     * 
     * @param remoteAddress remoting connection
     * @param connection Connection
     */
    void onEvent(String remoteAddress, Connection connection);
}

CONNECTEventProcessor connection event handler

The following is the source code of the connection event handler

/**
 * ConnectionEventProcessor for ConnectionEventType.CONNECT
 * 
 * @author xiaomin.cxm
 * @version $Id: CONNECTEventProcessor.java, v 0.1 Apr 8, 2016 10:58:48 AM xiaomin.cxm Exp $
 */
public class CONNECTEventProcessor implements ConnectionEventProcessor {

    private AtomicBoolean  connected    = new AtomicBoolean();
    private AtomicInteger  connectTimes = new AtomicInteger();
    private Connection     connection;
    private String         remoteAddr;
    private CountDownLatch latch        = new CountDownLatch(1);

    @Override
    public void onEvent(String remoteAddr, Connection conn) {
        Assert.assertNotNull(remoteAddr);
        doCheckConnection(conn);
        this.remoteAddr = remoteAddr;
        this.connection = conn;
        connected.set(true);
        connectTimes.incrementAndGet();
        latch.countDown();
    }

    /**
     * do check connection
     * @param conn
     */
    private void doCheckConnection(Connection conn) {
        Assert.assertNotNull(conn);
        Assert.assertNotNull(conn.getPoolKeys());
        Assert.assertTrue(conn.getPoolKeys().size() > 0);
        Assert.assertNotNull(conn.getChannel());
        Assert.assertNotNull(conn.getUrl());
        Assert.assertNotNull(conn.getChannel().attr(Connection.CONNECTION).get());
    }

    public boolean isConnected() throws InterruptedException {
        latch.await();
        return this.connected.get();
    }

    public int getConnectTimes() throws InterruptedException {
        latch.await();
        return this.connectTimes.get();
    }

    public Connection getConnection() throws InterruptedException {
        latch.await();
        return this.connection;
    }

    public String getRemoteAddr() throws InterruptedException {
        latch.await();
        return this.remoteAddr;
    }

    public void reset() {
        this.connectTimes.set(0);
        this.connected.set(false);
        this.connection = null;
    }
}

You can see that the onEvent() method is implemented, mainly to get the current connection and remote access address, and then set the connection status to true. In addition, it also provides methods such as whether to connect, get the number of connections, get the connection, and get the address of remote access.

So there is a question, when is onEvent() called?

Let's take a look at the code that the client starts, as follows

    @Override
    public void startup() throws LifeCycleException {
        super.startup();

        for (UserProcessor<?> userProcessor : userProcessors.values()) {
            if (!userProcessor.isStarted()) {
                userProcessor.startup();
            }
        }

        if (this.addressParser == null) {
            this.addressParser = new RpcAddressParser();
        }

        ConnectionSelectStrategy connectionSelectStrategy = option(BoltGenericOption.CONNECTION_SELECT_STRATEGY);
        if (connectionSelectStrategy == null) {
            connectionSelectStrategy = new RandomSelectStrategy(switches());
        }
        this.connectionManager = new DefaultClientConnectionManager(connectionSelectStrategy,
            new RpcConnectionFactory(userProcessors, this), connectionEventHandler,
            connectionEventListener, switches()); //创建默认的连接管理器
        this.connectionManager.setAddressParser(this.addressParser);
        this.connectionManager.startup();
        this.rpcRemoting = new RpcClientRemoting(new RpcCommandFactory(), this.addressParser,
            this.connectionManager);
        this.taskScanner.add(this.connectionManager);
        this.taskScanner.startup();

        if (switches().isOn(GlobalSwitch.CONN_MONITOR_SWITCH)) {
            if (monitorStrategy == null) {
                connectionMonitor = new DefaultConnectionMonitor(new ScheduledDisconnectStrategy(),
                    this.connectionManager);
            } else {
                connectionMonitor = new DefaultConnectionMonitor(monitorStrategy,
                    this.connectionManager);
            }
            connectionMonitor.startup();
            logger.warn("Switch on connection monitor");
        }
        if (switches().isOn(GlobalSwitch.CONN_RECONNECT_SWITCH)) {
            reconnectManager = new ReconnectManager(connectionManager);
            reconnectManager.startup();

            connectionEventHandler.setReconnector(reconnectManager);
            logger.warn("Switch on reconnect manager");
        }
    }

We see that a connectionEventListener listener is passed when the connectionManager is initialized

DISCONNECTEventProcessor disconnect event handler

The principle of this processor is similar to the previous one, so I won’t introduce it too much.

 

Guess you like

Origin blog.csdn.net/qq_34050399/article/details/115232386