一個學習式的mogoLink框架介紹

 

 

https://github.com/penkee/mogoLink

mogoLink是我於2016年開始設計的一個rpc框架,當時只是接觸了Netty技術,覺得它非常適合做rpc框架的底層通訊。對於編解碼產品,選用的是谷歌的protoBuf,不過苦於它的schema和每個類都得靜態編譯問題無法解決,然後耽擱下來。而那時候各大公司尚未服務化改造,故開發此框架經驗不足,考慮不周。

今年公司開展服務化工作,此時又接觸了dubbo,spring cloud相關書籍,系統介紹了設計rpc的理論基礎。故而有重新維護此項目。

那麼設計一個rpc框架,需要注意哪些地方呢?

  1. socket通訊框架:一般這個框架目前也只有netty開源的,上手快,性能也槓槓的
  2. 編解碼技術:java序列化、谷歌的protobuf、facebook thift、jboss marshaling、kryo、json、hession、aryo
  3. 粘包處理:消息定長、消息尾加分隔符、消息頭加表示長度的字段、其他複雜的應用層協議
  4. 服務註冊中心:分佈式系統用的,管理可用的服務地址
  5. 負載均衡算法:客戶端用的,分散請求不同的服務器
  6. 限流熔斷處理:服務端用,防止請求過多,造成服務中斷的
  7. 監控系統:監控服務調用次數,耗時,客戶端連接數等信息
  8. 日誌跟蹤系統:由於會級聯調用多層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的接口,spring自動注入成工廠類產生的代理對象,當調用服務方法時,則代碼執行代理的請求遠程的服務

<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;
    }
}
  • channel連接池

 一個客戶端如果只連接一個channel,那豈不是暴殄天物。一個channel是同步阻塞的,所有這裏每個客戶端要有一個channel連接池。本系統採用是apache的common-pool工具類,來維護channel連接。

  • 粘包問題

本框架採用消息頭加消息長度字段的方式實現,具體的類是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);
    }

如果能確定編碼後不包含分割符,也是可以用分隔符處理的,更節約。

  • 消息體的定義:方法支持重載,故需要方法參數的class名
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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章