公式文書については、https://www.sofastack.tech/projects/sofa-bolt/sofa-bolt-handbook/を参照してください。
短い本の高品質のブログ投稿:https://www.jianshu.com/p/f2b8a2099323
目次
同期要求プロセッサの抽象クラスSyncUserProcessor
非同期リクエストプロセッサの抽象クラスAsyncUserProcessor
ConnectionEventProcessor接続イベントインターフェイスクラス
CONNECTEventProcessor接続イベントハンドラー
DISCONNECTEventProcessor切断イベントハンドラー
2種類のプロセッサ
Sofa-boltは、ユーザー要求プロセッサと接続イベントプロセッサの2種類のプロセッサを提供します。
ユーザー要求プロセッサは、主に要求データに応答するために使用されます。たとえば、サーバーは、要求を受信した後、特定の応答メッセージをクライアントに送信します。
接続イベントハンドラーは、主に、接続、クローズ、接続例外などのイベントを監視して、関連する操作を実行するために使用されます。
ユーザーリクエストハンドラ
SyncUserProcessorとAsyncUserProcessorの2つのユーザーリクエストプロセッサが提供されています。両者の違いは、前者のニーズは、現在の処理スレッドで戻り値の形式で処理結果を返すようにすることである。後者は現在のスレッドまたは非同期スレッドで呼び出すことができるAsyncContextスタブ、有している sendResponse
のが処理結果を返します。
継承関係のクラス図は次のとおりです。
例では、次の2つのカテゴリを参照できます。
同期要求プロセッサの抽象クラス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();
}
この抽象クラスには2つのhandleRequest()メソッドが定義されていることがわかります。1つは戻り値あり、もう1つは戻り値なしです。戻り値のないメソッドは直接例外処理を行い、非同期要求がサポートされていないことを示します。したがって、同期メソッドを使用するユーザーの要求については、戻り値を使用してhandleRequest()を実装する必要があります。具体的な例については、公式の例であるカスタムユーザー同期ハンドラーを参照してください。
以下は、重複したhandleRequest()のインターセプトされた部分コードであり、デフォルトのサーバー応答が返されることがわかります。
// ~~~ 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;
}
ソースコードを見ると、ここでアダプタモードが使用されていることがわかります
その中で、AbstractUserProcessorはインターフェイスのアダプタと見なすことができます(デフォルトの実装はインターフェイスのメソッドごとに行われるため、4つの最下位レベルの抽象化は実装に必要なメソッドのみを実装できます)。 it UserProcessorAdapter
非同期リクエストプロセッサの抽象クラス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();
}
この抽象クラスには2つのhandleRequest()メソッドが定義されていることがわかります。1つは戻り値あり、もう1つは戻り値なしです。戻り値を持つメソッドは直接例外処理を行い、同期要求がサポートされていないことを示します。 。したがって、非同期モードでのユーザーの要求については、戻り値なしでhandleRequest()を実装する必要があります。具体的な例については、公式の例を参照してください:カスタムユーザー非同期ハンドラー
以下は、handleRequest()を書き換えるコードのインターセプトされた部分です。AsyncContextスタブがあり、現在のスレッド、または非同期スレッド、非同期スレッドにあり、sendResponse
メソッドを呼び出し て処理結果。
@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);
}
}
}
接続イベントハンドラ
接続イベント(ConnectionEventType.CONNECT)と切断イベント(ConnectionEventType.CLOSE)の2種類のイベント監視が提供されます。ユーザーは独自のイベントハンドラーを作成して、クライアントまたはサーバーに登録できます。クライアントとサーバーの両方が、それぞれの接続確立イベントと切断イベントを監視できます。
次の列挙型クラスを見てください。このクラスには、接続、接続障害、クローズ、および例外の4つのイベントが含まれています。
/**
* 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接続イベントインターフェイスクラス
/**
* 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接続イベントハンドラー
以下は、接続イベントハンドラーのソースコードです。
/**
* 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;
}
}
主に現在の接続とリモートアクセスアドレスを取得し、接続ステータスをtrueに設定するために、onEvent()メソッドが実装されていることがわかります。さらに、接続するかどうか、接続数を取得する、接続を取得する、リモートアクセスのアドレスを取得するなどのメソッドも提供します。
では、onEvent()はいつ呼び出されるのかという質問があります。
次のように、クライアントが起動するコードを見てみましょう。
@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");
}
}
connectionManagerが初期化されると、connectionEventListenerリスナーが渡されることがわかります。
DISCONNECTEventProcessor切断イベントハンドラー
このプロセッサの原理は前のものと似ているので、あまり紹介しません。