Sofa-Bolt研究笔记

153 阅读3分钟

为什么要研究Sofa-Bolt

  • 1.用于做远程桌面控制的基础框架,实现远程桌面鼠标键盘事件的透传
  • 2.用于IM,sofa-bolt本身支持连接管理,可根据接收方id找到接收方的channel,进而实现发送消息。
  • 3.实现分布式集群中间件状态同步工具,比如Raft协议实现
  • 4.用于云盒指令透传执行,云盒为指令执行方实体域sofa client,服务器为下发指令方实体域为sofa server,通过sofa-bolt的rpc能力,可以轻易的实现指令的同步、异步执行;完美适配我们的云盒指令“透传->执行->响应”的场景,不过缺陷是集群的实现需要自己基于zookeeper或其他注册中心实现。
  • 5:想基于sofa-bolt实现设备协议对接还是挺困难的,毕竟设备的协议格式不可能按照sofa-bolt的协议定义,而sofa-bolt想调整协议实现需要修改太多。

SOFA-BOLT性能优化技巧

1.修改Netty源码中的ByteToMessageDecoder.channelRead方法将管道消息传递改为支持批量
//Netty源码,修改之前
for (int i = 0; i < numElements; i ++) {
    ctx.fireChannelRead(msgs.getUnsafe(i));
}
//Sofa-Bolt源码,修改之后
if (size == 1) {
    ctx.fireChannelRead(out.get(0));
} else {
    ArrayList<Object> ret = new ArrayList<Object>(size);
    for (int i = 0; i < size; i++) {
        ret.add(out.get(i));
    }
    ctx.fireChannelRead(ret);
}
在RpcCommandHandler.handleCommand方法中,要对传递过来的消息进行判断是否需要批量还是单个处理
private void handle(final RemotingContext ctx, final Object msg) {
    try {
        if (msg instanceof List) {
            final Runnable handleTask = new Runnable() {
                @Override
                public void run() {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Batch message! size={}", ((List<?>) msg).size());
                    }
                    for (final Object m : (List<?>) msg) {
                        RpcCommandHandler.this.process(ctx, m);
                    }
                }
            };
            if (RpcConfigManager.dispatch_msg_list_in_default_executor()) {
                // If msg is list ,then the batch submission to biz threadpool can save io thread.
                // See com.alipay.remoting.decoder.ProtocolDecoder
                processorManager.getDefaultExecutor().execute(handleTask);
            } else {
                handleTask.run();
            }
        } else {
            process(ctx, msg);
        }
    } catch (final Throwable t) {
        processException(ctx, msg, t);
    }
}

2.反序列机制的优化

sofabolt默认在IO线程里只序列化className。其余的数据都由业务线程进行反序列化,以最大化的利用 IO 线程处理连接的能力。同时,SOFABolt 也提供了更多场景的下的反序列化时机,例如 IO 密集型的业务,为了防止大量上下文切换,就可以直接在 IO 线程处理所有任务,包括业务逻辑。同时也提供业务线程池隔离的场景,此时 IO 线程在反序列化 ClassName 的基础上,再反序列化 header,剩下的交有业务线程池。不可谓不灵活。

3.创建连接池的无锁设计

public class RunStateRecordedFutureTask<V> extends FutureTask<V> {
    private AtomicBoolean hasRun = new AtomicBoolean();

    public RunStateRecordedFutureTask(Callable<V> callable) {
        super(callable);
    }

    @Override
    public void run() {
        this.hasRun.set(true);
        super.run();
    }

    public V getAfterRun() throws InterruptedException, ExecutionException,
                          FutureTaskNotRunYetException, FutureTaskNotCompleted {
        if (!hasRun.get()) {
            throw new FutureTaskNotRunYetException();
        }

        if (!isDone()) {
            throw new FutureTaskNotCompleted();
        }

        return super.get();
    }
}
private ConnectionPool getConnectionPoolAndCreateIfAbsent(String poolKey,
                                                          Callable<ConnectionPool> callable)
                                                                                            throws RemotingException,
                                                                                            InterruptedException {
    RunStateRecordedFutureTask<ConnectionPool> initialTask;
    ConnectionPool pool = null;

    int retry = Constants.DEFAULT_RETRY_TIMES;

    int timesOfResultNull = 0;
    int timesOfInterrupt = 0;

    for (int i = 0; (i < retry) && (pool == null); ++i) {
        initialTask = this.connTasks.get(poolKey);
        if (null == initialTask) {
            RunStateRecordedFutureTask<ConnectionPool> newTask = new RunStateRecordedFutureTask<ConnectionPool>(
                callable);
            initialTask = this.connTasks.putIfAbsent(poolKey, newTask);
            if (null == initialTask) {
                initialTask = newTask;
                initialTask.run();
            }
        }
        try {
            pool = initialTask.get();
            if (null == pool) {
                if (i + 1 < retry) {
                    timesOfResultNull++;
                    continue;
                }
                this.connTasks.remove(poolKey);
                String errMsg = "Get future task result null for poolKey [" + poolKey
                                + "] after [" + (timesOfResultNull + 1) + "] times try.";
                throw new RemotingException(errMsg);
            }
        } catch (InterruptedException e) {
            if (i + 1 < retry) {
                timesOfInterrupt++;
                continue;// retry if interrupted
            }
            this.connTasks.remove(poolKey);
            logger
                .warn(
                    "Future task of poolKey {} interrupted {} times. InterruptedException thrown and stop retry.",
                    poolKey, (timesOfInterrupt + 1), e);
            throw e;
        } catch (ExecutionException e) {
            // DO NOT retry if ExecutionException occurred
            this.connTasks.remove(poolKey);

            Throwable cause = e.getCause();
            if (cause instanceof RemotingException) {
                throw (RemotingException) cause;
            } else {
                FutureTaskUtil.launderThrowable(cause);
            }
        }
    }
    return pool;
}