https://github.com/penkee/mogoLink
mogoLinkは、2016年に設計を開始したrpcフレームワークです。当時、私はNettyテクノロジーに触れただけで、rpcフレームワークの基盤となる通信に非常に適していると感じました。コーデック製品には、GoogleのprotoBufが選択されましたが、スキーマと各クラスの静的コンパイルの問題が解決できず、遅延しました。当時、大手企業はまだ変革に取り組んでいなかったため、このフレームワークの開発経験が不十分であり、検討も不十分でした。
今年からサービスを開始し、ダボやスプリングクラウド関連の本と触れ合い、rpcの設計の理論的根拠を体系的に紹介しました。したがって、このプロジェクトは再保守されています。
では、rpcフレームワークを設計するときに何に注意を払う必要がありますか?
- ソケット通信フレームワーク:一般的に、このフレームワークは現在、nettyによってのみオープンソース化されており、すぐに開始でき、優れたパフォーマンスを発揮します。
- コーディングおよびデコードテクノロジー:Javaシリアル化、Googleのprotobuf、facebook thift、jbossマーシャリング、kryo、json、hession、aryo
- スティッキーパケット処理:メッセージ固定長、メッセージテールとセパレータ、メッセージヘッダーと長さフィールド、およびその他の複雑なアプリケーション層プロトコル
- サービスレジストリ:利用可能なサービスアドレスを管理するために分散システムによって使用されます
- 負荷分散アルゴリズム:クライアントが使用し、リクエストをさまざまなサーバーに分散します
- 電流制限ヒューズ処理:過剰な要求がサービスの中断を引き起こすのを防ぐためにサーバー側で使用されます
- 監視システム:サービスコールの数、時間のかかる、クライアント接続およびその他の情報を監視します
- ログ追跡システム:rpcの複数のレイヤーがカスケードで呼び出されるため、コールチェーンのログ収集システムを追跡するために一意の識別子を生成する必要があります
上の写真は本システムの設計フローチャートです。ラフですが、はっきりと見える程度で十分です。
- FutureObjectクラスは、将来のオブジェクトを取得するために使用されます。nettyはメッセージを非同期で受信するため、このオブジェクトは通信ツールとしてのみ使用できます。サーバーがメッセージを受信してこのオブジェクトに入れると、リスナーはすぐにオブジェクトを取得します。それ以外の場合はタイムアウトになります。
/**
* @brief 获取未来对象
* @details (必填)
* @author 彭堃
* @date 2016年8月26日下午5:56:29
*/
public class FutureObject<T> {
private T value;
public T get(long outTime) {
if (value == null) {
synchronized (this) {
try {
this.wait(outTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return value;
}
public void set(T value) {
this.value = value;
synchronized (this) {
this.notify();
}
}
}
-
クライアントプロキシメソッド:このようにして、@ Autowirdのすべてのインターフェイスは、ファクトリクラスによって生成されたプロキシオブジェクトに自動的に挿入されます。サービスメソッドが呼び出されると、コードはリモートサービスのプロキシリクエストを実行します。
<bean id="userInfoRemoteService" class="com.eastorm.mogolink.client.proxy.ProxyFactory">
<constructor-arg name="className" value="com.eastorm.mogolink.demo.client.service.api.IUserInfoService" />
</bean>
/**
* 创建动态代理对象
* 动态代理不需要实现接口,但是需要指定接口类型
* @author 慕容恪
*/
public class ProxyFactory implements FactoryBean {
private static final Logger logger = LoggerFactory.getLogger(ProxyFactory.class);
private String className;
public ProxyFactory(String className){
this.className=className;
}
public Object getProxyInstance() throws ClassNotFoundException {
Class target=Class.forName(className);
return Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//无需代理的父类方法
if("toString".equals(method.getName())){
return method.invoke(proxy,args);
}
BaseMessage req=new BaseMessage();
UUID uuid = UUID.randomUUID();
req.setRequestId(uuid.toString());
List<MethodParam> paramTypes=new ArrayList<>();
if(args!=null&&args.length>0){
for (Object arg : args) {
paramTypes.add(new MethodParam(arg.getClass().getName(),arg));
}
}
req.setParamTypes(paramTypes);
req.setServiceName(className);
req.setMethod(method.getName());
long s=System.currentTimeMillis();
ClientMsgHandler handler= ClientStarter.getHandler();
if(handler==null){
logger.info("请求失败req={},耗时:{}ms",req.getRequestId(),System.currentTimeMillis()-s);
return null;
}
// Request and get the response.
BaseMessage resMsg = handler.getData(req);
if(resMsg!=null&&resMsg.getCode().equals(ServiceCodeEnum.SUCCCESS.getId())){
logger.info("req={},耗时:{}ms",resMsg.getRequestId(),System.currentTimeMillis()-s);
return resMsg.getReturnData();
}else{
logger.info("失败req={},耗时:{}ms",req.getRequestId(),System.currentTimeMillis()-s);
}
return null;
}
});
}
@Override
public Object getObject() throws Exception {
return getProxyInstance();
}
@Override
public Class<?> getObjectType() {
Class target= null;
try {
target = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return target;
}
@Override
public boolean isSingleton() {
return true;
}
}
- チャネル接続プール
クライアントが1つのチャネルにのみ接続する場合、それは暴力的なことではありません。チャネルは同期的にブロックされるため、各クライアントにはチャネル接続プールが必要です。このシステムは、apacheのcommon-poolツールを使用してチャネル接続を維持します。
- 粘着性のあるバッグの問題
このフレームワークは、メッセージヘッダーをメッセージ長フィールドに追加することによって実装されます。特定のクラスは、nettyに付属するLengthFieldBasedFrameDecoderです。kryoエンコーダーに行ヘッダー長フィールドを追加しました。
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, BaseMessage baseMessage, ByteBuf byteBuf) throws Exception {
byte[] data= messageCodec.serialize(baseMessage);
byteBuf.writeShort(data.length);
byteBuf.writeBytes(data);
}
エンコード後に区切り文字が含まれていないと判断できる場合は、より経済的な区切り文字で処理することもできます。
- メッセージ本文の定義:メソッドはオーバーロードをサポートしているため、メソッドパラメーターのクラス名が必要です
ublic class BaseMessage {
private String requestId;
private String code;
private String msg;
/**
* 返回的信息
*/
private Object returnData;
/**
* 服务名
*/
private String serviceName;
/**
* 方法
*/
private String method;
/**
* 方法的参数类型
* 用来转型
*/
private List<MethodParam> paramTypes;
}