簡易RPC框架-上下文

来源:http://www.cnblogs.com/ASPNET2008/archive/2017/10/29/7749242.html
-Advertisement-
Play Games

上下文 記的學英語的時候,總是不記的某個詞是什麼意思,然後就看不下去了,只能問周圍的同學或者老師或者去查詞典,他們的建議是通過上下文去推測這個詞大概的意思,反正我那會上學時沒理解,所以英文一直比較差。 現在英語水平也沒提高多少,儘管有點領會。 後來慢慢理解了一些,因為有些詞有很多種意思,放在某個場景 ...


上下文

記的學英語的時候,總是不記的某個詞是什麼意思,然後就看不下去了,只能問周圍的同學或者老師或者去查詞典,他們的建議是通過上下文去推測這個詞大概的意思,反正我那會上學時沒理解,所以英文一直比較差。

現在英語水平也沒提高多少,儘管有點領會。

後來慢慢理解了一些,因為有些詞有很多種意思,放在某個場景下可能是一個意思,放在另外一個場景下又是其它的意思,這裡不舉例子了,上文有一定的相似度。

RPC客戶端上下文

客戶端由於是一個獨立的環境,所以可以認為它處於一個屬於自己的上下文中,與其它的端隔離。在上下文中可以指定一些公共的參數來提供給介面使用,比如:

RPC版本號
客戶端請求ID
各類自定義的公共參數

RPC服務端上下文

服務端同理,也處於一個屬於自己的上下文中,與其它端隔離。

RPC上下文的作用

基本線程級別的訪問,讓客戶端或者服務端能夠像訪問本地變數一樣訪問RPC框架級別的變數。比如我們想將客戶端的一個請求ID傳遞給服務端,這個請求ID作用於所有介面,比如RPC的調用鏈追蹤,有兩種方式:

介面中增加請求ID參數

這個方案顯然是不能接受的,因為需要改的介面過多。

介面不改的情況下,在RPC框架中提供一個上下文,其中包含請求ID

這個方案顯然成本最小,比如這種樣調用:RpcContext.getRequestId();

 

  • 上圖中的Context是指RPC框架級變數的一個本地副本,為了簡化調用複雜度。
  • 上圖中的Invocation是RPC框架級的變數,它與上面提到的Context相互配合,做到調用端與框架本身的解耦合

實現

定義上下文對象

在RpcContext對象中增加一個map類型的參數對象,可以存放任意擴展的參數。

public class RpcContext {

    private Map<String,Object> contextParameters;

    public Map<String, Object> getContextParameters() {
        return contextParameters;
    }

    public void setContextParameters(Map<String, Object> contextParameters) {
        this.contextParameters = contextParameters;
    }

    private static final ThreadLocal<RpcContext> rpcContextThreadLocal=new ThreadLocal<RpcContext>(){
        @Override
        protected RpcContext initialValue() {
            RpcContext context= new RpcContext();
            context.setContextParameters(Maps.newHashMap());
            return context;
        }
    };

    public static RpcContext getContext() {
        return rpcContextThreadLocal.get();
    }

    public static void removeContext() {
        rpcContextThreadLocal.remove();
    }

    private RpcContext() {
    }
}

RPC請求對象中增加上下文參數

RpcRequest增加如下欄位,用於服務端調用。

private Map<String,Object> contextParameters;

RpcInvocation介面中增加上下文參數

在後續新增加的過濾器使用。

private Map<String,Object> contextParameters;

客戶端代理

RpcProxy在組裝RpcRequest對象時,從RpcContext中獲取最新的參數傳遞給RpcReuest,從而傳遞給服務端。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        RpcRequest request = new RpcRequest();
        //...

        request.setContextParameters(RpcContext.getContext().getContextParameters());

        //...
    }

客戶端上下文過濾器

主要作用就是從本線線程變數中獲取參數傳遞給RpcInvocation。

註解上的order屬性文章後面詳細介紹

@ActiveFilter(group = {ConstantConfig.CONSUMER},order = -1000)
public class ClientContextFilter implements RpcFilter {

    private final static Logger logger = LoggerFactory.getLogger(ClientContextFilter.class);

    @Override
    public Object invoke(RpcInvoker invoker, RpcInvocation invocation) {
        Map<String,Object> contextParameters=invocation.getContextParameters();
        if(null==contextParameters){
            contextParameters= Maps.newHashMap();
        }
        Map<String,Object> contextParametersFromRpcContext= RpcContext.getContext().getContextParameters();
        if(null!=contextParametersFromRpcContext) {
            contextParameters.putAll(contextParametersFromRpcContext);
        }
        Object rpcResponse=invoker.invoke(invocation);
        logger.info("ClientContextFilter.invoke end");
        return rpcResponse;
    }
}

服務端上下文過濾器

服務端上下文過濾器與客戶端的作用相反,是從RpcInvocation中獲取參數傳遞給本地線程變數RpcContext,後面在執行服務端方法時就可以方便的通過RpcContext獲取指定變數。

@ActiveFilter(group = {ConstantConfig.PROVIDER},order = -1000)
public class ServerContextFilter implements RpcFilter {

    private final static Logger logger = LoggerFactory.getLogger(ServerContextFilter.class);

    @Override
    public Object invoke(RpcInvoker invoker, RpcInvocation invocation) {
        Map<String,Object> contextParameters=invocation.getContextParameters();
        RpcContext.getContext().setContextParameters(contextParameters);
        Object rpcResponse=invoker.invoke(invocation);
        logger.info("ServerContextFilter.invoke end");
        return rpcResponse;
    }
}

過濾器排序

因為我們的RpcContext是個本地線程變數,而且Rpc服務端是多線程處理業務,所以需要在請求結束後及時的清理掉相關本地線程變數信息。這就需要清理上下文的過濾動作在最後執行,否則有會出現服務端方法還沒有執行就被清空了參數。創建一個工具類,專門用來處理獲取客戶端以及服務端過濾器。

過濾器註解增加排序屬性

增加order欄位,升級排列。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface ActiveFilter {
    String[] group() default {};
    String[] value() default {};
    int order() default 999999999;
}

過濾器工具類

創建ActiveFilterUtil,包含下麵兩個函數。

過濾器排序函數

獲取特定註解的類,然後根據註解上的排序屬性升序排序。

private static List<Object> getActiveFilter(){
    List<Object> rpcFilterList= Lists.newArrayList();
    Map<String, Object> rpcFilterMapObject = ApplicationContextUtils.getApplicationContext().getBeansWithAnnotation(ActiveFilter.class);
    if (null!=rpcFilterMapObject) {
        rpcFilterList = Lists.newArrayList(rpcFilterMapObject.values());
        Collections.sort(rpcFilterList, new Comparator<Object>() {
            @Override
            public int compare(Object o1, Object o2) {
                ActiveFilter activeFilterO1 = o1.getClass().getAnnotation(ActiveFilter.class);
                ActiveFilter activeFilterO2 = o2.getClass().getAnnotation(ActiveFilter.class);
                return activeFilterO1.order() > activeFilterO2.order() ? 1 : -1;
            }
        });
    }
    return rpcFilterList;
}

獲取RPC過濾器列表

提供給客戶端以及服務端的一個協助方法,便於客戶端以及服務構建過濾器職責鏈。

public static Map<String,RpcFilter> getFilterMap(boolean isServer){
    List<Object> rpcFilterList=getActiveFilter();
    Map<String,RpcFilter> filterMap=new HashMap<>();
    for (Object filterBean : rpcFilterList) {
        Class<?>[] interfaces = filterBean.getClass().getInterfaces();
        ActiveFilter activeFilter=filterBean.getClass().getAnnotation(ActiveFilter.class);
        String includeFilterGroupName=!isServer?ConstantConfig.CONSUMER:ConstantConfig.PROVIDER;
        if(null!=activeFilter.group()&& Arrays.stream(activeFilter.group()).filter(p->p.contains(includeFilterGroupName)).count()==0){
            continue;
        }
        for(Class<?> clazz:interfaces) {
            if(clazz.isAssignableFrom(RpcFilter.class)){
                filterMap.put(filterBean.getClass().getName(),(RpcFilter) filterBean);
            }
        }
    }
    return filterMap;
}

客戶端以及服務端初始化

獲取過濾器map的邏輯改為調用上面ActiveFilterUtil.getFilterMap方法。

使用示例

客戶端設置參數

設置一個RPC版本號的參數。

@RequestMapping("/{productId}")
    public Product getById(@PathVariable final long productId) throws UnknownHostException {
        RpcContext.getContext().addContextParameter("rpc-version","1.0");
        //...
    }

服務端獲取參數

簡單的在服務端列印出RPC版本號。

 logger.info("get context parameter from server,rpc-version={}",String.valueOf(RpcContext.getContext().getContextParameter("rpc-version")));

輸出日誌如下:

本文源碼

https://github.com/jiangmin168168/jim-framework

文中代碼是依賴上述項目的,如果有不明白的可下載源碼


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 多線程使用共用變數,就會涉及到一個線程同步的問題。那如何解決呢? 1) 重構程式,移除多線程的共用變數,讓一個線程只訪問一個自有變數 2) 使用原子操作,一個操作只占用一個量子時間,一次完成,只有噹噹前操作完成之後,其他線程才能進行操作。這樣可以避免使用獨占鎖,避免死鎖。... ...
  • 上一章主要介紹了Xuan.UWP.Framework.ImageLib的基本用法,這一章具體來看些Xuan.UWP.Framework.ImageLib的使用。 一、首先看下Xuan.UWP.Framework.ImageLib中最主要的class之一的ImageLoader。 可以看到ImageL ...
  • 無法嵌入互操作類型“Microsoft.Office.Interop.Word.ApplicationClass”。請改用適用的介面。 ...
  • 目前的問題V5.7沒有完成的需求是不是應該包含在V5.7.2中?V5.7.2發版的時候也會帶上V5.7還沒有發版但是已經開發完成的需求,怎麼區分哪些是V5.7已經發版的需求,哪些是沒有發版但是已經開發完成的需求?需求開發完成並且測試完畢後,產品經理如何發現待驗證的需求?需求的管理和追蹤流程狀態欄位+... ...
  • 系統環境操作系統:CentOS 6.9redis版本:redis-4.0.2安裝步驟1,安裝預環境運行以下命令安裝預環境。[root@redis02 redis-4.0.2]# yum -y install gcc make2,下載redis源代碼文件並解壓縮下載完redis源代碼後,運行以下命令進... ...
  • 這節課主要是學習關於Bootstrap樣式的應用以及對上節做的UI界面進行改造,下邊總結一下所學到的知識點! 一、關於Bootstrap樣式的介紹 Bootstrap,來自Twitter,是目前很受歡迎的前端框架。Bootstrap 是基於 HTML、CSS、JavaScript 的,它簡潔靈活,使 ...
  • 前面的文章中為My Blog加入了文章的管理功能(ASP.NET沒有魔法——ASP.NET MVC使用Area開發一個管理模塊),但是管理功能應該只能由“作者”來訪問,那麼要如何控制用戶的訪問許可權?也就是當用戶訪問管理功能時需要對用戶進行身份驗證,對於用戶來說身份驗證也就是登錄,即提供一個登錄界面, ...
  • 準備一下,近段時間,需要把Excel的數據導入資料庫中。引用命名空間: 你可以寫一個方法,是去讀取Excel文檔的方法,返回DataSet數據集: private DataSet ImportExcelToDataSet(string virtualPath, string sqlQueryStat ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...