自研API 網關 - 媲美美團這套Shepherd網關架構!

作者:小傅哥

博客:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!😄

我說:"很多互聯網大廠,很少基於 SpringMVC 模塊對外提供 WEB 服務的 HTTP 接口!" 一下炸窩了,你說,哪個廠不用,你說。還,還不用 SpringMVC 我天天用! 哈哈哈,好在我最近閱讀到了美團的這篇技術文章《百億規模API網關服務Shepherd的設計與實現》

他說:在沒有Shepherd API網關之前,美團業務研發人員如果要將內部服務輸出爲對外的HTTP API接口。通常要搭建一個Web應用,用於完成基礎的鑑權、限流、監控日誌、參數校驗、協議轉換等工作,同時需要維護代碼邏輯、基礎組件的升級,研發效率相對比較低。此外,每個Web應用都需要維護機器、配置、數據庫等,資源利用率也非常差。

他說:美團內部一些業務線苦於沒有現成的解決方案,根據自身業務特點,研發了業務相關的API網關。放眼業界,亞馬遜、阿里巴巴、騰訊等公司也都有成熟的API網關解決方案。

他說的我說的,是同一個事情。並且我們所設計的API網關架構模型也都是類似的! 類似的架構設計,並不會讓我多驚訝。因爲API網關所實現的目標一致,在同一目標下如果研發思考高度一致,那麼就不需要太多技術認知對其。—— 所以,少和臭棋簍子下棋!

接下來,小傅哥就給大家分享下。兩套API網關的架構設計,以及你該怎麼學習才能掌握這些技術技能和提高這些技術認知。

一、技術設計與實現

API網關來說,我們可以先抽象出一個最簡單的模型來理解。它的核心目標是統一提供 HTTP 請求服務,也就是說你可以在不需要額外開發 WEB 應用的前提下,對外把自身的服務通過 HTTP 請求協議暴漏出去。因爲在互聯網大廠中,各個微服務系統的交互主要以 RPC 爲主,同時爲了提供帶有基礎功能(鑑權、監控、限流等) HTTP 服務,所以有了API網關服務。

這就是一套最基礎的API網關設計模型結構,從左到右,從 HTTP 請求到協議轉換處理,調用到具體的 RPC 服務。而 RPC 服務的接口變化由 SDK 上報到註冊中心,註冊中心再通知給協議轉換服務。有了這樣一個基礎認知以後,我們在來講解幾個重要的核心模塊設計和實現,包括;整體架構、註冊中心、服務發現、協議轉換。

1. 整體架構

這裏有2張API網關架構圖,一張是美團技術團隊的,一張是小傅哥設計的。

1.1 API網關架構圖-美團

Shepherd API 網關的數據面也就是 Shepherd 服務端。一次完整的API請求,可能是從移動應用、Web應用,合作伙伴或內部系統發起,經過Nginx負載均衡系統後,到達服務端。服務端集成了一系列的基礎功能組件和業務自定義組件,通過泛化調用請求後端RPC服務、HTTP服務、函數服務或服務編排服務,最後返回響應結果。

注意:美團的這張技術架構圖圖應該是簡化的,整體架構並不會比小傅哥設計的簡單。

1.2 API網關架構圖-小傅哥

這是一整套API網關的核心通信模型結構圖,以API網關算力的多套服務註冊到網關中心開始,拉取RPC應用接口並完成映射HTTP調用操作。最終允許用戶通過 Nginx 訪問和路徑重寫的負載均衡管理,調用到具體的網關算力中執行協議解析和RPC接口的泛化調用並最終返回結果數據。

2. 註冊中心

API 網關爲什麼要有一個註冊中心呢?

其實這個註冊中心,最核心管理就是 RPC 接口映射成一個 HTTP 請求地址,並把這個信息下發給對應的協議轉發服務上進行使用。

  • 如圖所示,api-gateway-core 是最核心的通信層。那麼它還需要把註冊的網關接口在通信核心服務中啓動起來。那麼怎麼啓動呢?
  • 這個啓動過程首先來自於 api-gateway-sdk 向 api-gateway-center 推送註冊接口,之後在通過網關引擎 api-gateway-engin 拉取接口並在本地服務完成註冊。最後再調用到網關接口時,則是通過 api-gateway-core 調用到對應的 RPC 應用中。
  • 那麼 api-gateway-sdk 並不是主要工程,沒有它的是可以通過 api-gateway-admin 配置。所以 在整個流程中 api-gateway-center、api-gateway-core 是兩個核心工程,能更好的串聯流程。

3. 服務發現

什麼叫服務發現呢?發現誰呢?

服務發現,發現的是用於註冊到API網關注冊中心的 RPC 服務,通過 SDK 配置的方式,採集到 RPC 服務中的接口信息。因爲這些接口的定義如果都是手動配置到API網關注冊中心,那麼就會非常麻煩。所以通過 SDK 採集的方式進行自動註冊,當有接口變更的時候也會及時的變更接口信息。

  • 在 api-gateway-center 工程中添加 Redis 發佈消息模塊,並提供應用服務註冊後的事件通知操作。這個通知只會通知給對應的網關算力服務,不會全局通知。
  • 在 api-gateway-assist 工程中開發 Redis 訂閱消息模塊,當收到註冊中心的消息推送時,則根據系統的標識信息進行拉取服務。注意這裏你可以進行細化,只把變更的信息一條條推送給網關注冊,減少接口的拉取
  • 在 api-gateway-sdk 工程中添加對網關注冊中心接口的調用,當所有的服務註冊完成後,調用接口進行通知。

4. 協議轉換

這是最核心的服務!

所有的 HTTP 請求協議轉發,到最終的 RPC 泛化調用,這些操作都在這一個服務中完成。而整個這一塊服務的實現,其實就是一套會話模型的架構分層設計。

一次網絡請求經過 Netty 處理可以分爲三段;消息接收、請求鑑權、消息處理。這樣就由原來我們只是在接收消息後直接把消息協議轉換後請求到 RPC 服務,轉換爲多添加二層來處理簡單的消息接收和請求鑑權。這裏的請求鑑權就是基於引入的 Shrio + JWT 完成。

二、內容結構和目錄

當你需要學習編程知識,提高編程思維和編碼能力的階段時候,你需要看到什麼資料?🤔 不知道大家是否有想過這樣一個問題。

每當我看到那些非常牛皮的架構或者框架的時候,我就希望把他們喫透,並拿捏成自己的知識體系。但往往這些框架源碼有太多的繁枝末節,也因爲隨着不斷的需求迭代,讓一些旁路細節流程的大量代碼掩蓋了核心流程。所以當你想學習時候,往往也是有心無力,根本不知道從哪開始。

現在我來爲你鋪路!

爲了解決這樣的學習問題,小傅哥把一個API網關項目,以不斷接需求迭代的視角,一點點漸進式的完成整套代碼開發。那麼這樣就可以讓大家有清晰的學習編碼路線,把一整套這樣的東西學習完成。—— 跟着小傅哥學習,你可以不浪費時間少走彎路、目標明確的把技術學習到手。

三、設計模式與編碼

每每在公司經歷一個大項目時,其實不只是看重這塊業務場景,還看重對應這套項目的架構和編碼,跟着各路大牛提升經驗。可能就這樣一個項目的學習,就能把一個人的編碼思維提升到一個新的臺階。

那麼小傅哥再做這套架構和編碼時,特別注重整體的架構設計和編碼實現。接下來我給大家舉例看看這套代碼中的代碼實現。

1. 會話模型

源碼cn.bugstack.gateway.core.session.defaults.DefaultGatewaySessionFactory

public class DefaultGatewaySessionFactory implements GatewaySessionFactory {

    private final Configuration configuration;

    public DefaultGatewaySessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public GatewaySession openSession(String uri) {
        // 獲取數據源連接信息:這裏把 Dubbo、HTTP 抽象爲一種連接資源
        DataSourceFactory dataSourceFactory = new UnpooledDataSourceFactory();
        dataSourceFactory.setProperties(configuration, uri);
        DataSource dataSource = dataSourceFactory.getDataSource();
        // 創建執行器
        Executor executor = configuration.newExecutor(dataSource.getConnection());
        // 創建會話:DefaultGatewaySession
        return new DefaultGatewaySession(configuration, uri, executor);
    }

    public Configuration getConfiguration() {
        return configuration;
    }

}
  • 會話模型是網關算力中非常重要的一環,所有的 HTTP 請求都可以被抽象爲會話模型。通過會話模型封裝出 HTTP 到 RPC 的處理,中間再通過執行器和RPC抽象的數據源進行銜接。

2. 抽象模板

源碼cn.bugstack.gateway.core.socket.BaseHandler

public abstract class BaseHandler<T> extends SimpleChannelInboundHandler<T> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, T msg) throws Exception {
        session(ctx, ctx.channel(), msg);
    }

    protected abstract void session(ChannelHandlerContext ctx, final Channel channel, T request);

}
  • 網關會話中還需要協議的處理,而協議的接收、解析、轉換,就需要通過 Netty 實現的 Socket 服務來封裝。通過爲了更好的體現出會話的結構,這裏小傅哥通過一個抽象類模板,定義出 session 方法。—— 好的代碼,就是好的文檔。有了這樣的約定,也就不需要太多的口口相傳。

3. 映射代理

源碼cn.bugstack.gateway.core.bind.MapperProxyFactory

public class MapperProxyFactory {

    private String uri;

    public MapperProxyFactory(String uri) {
        this.uri = uri;
    }

    private final Map<String, IGenericReference> genericReferenceCache = new ConcurrentHashMap<>();

    public IGenericReference newInstance(GatewaySession gatewaySession) {
        return genericReferenceCache.computeIfAbsent(uri, k -> {
            HttpStatement httpStatement = gatewaySession.getConfiguration().getHttpStatement(uri);
            // 泛化調用
            MapperProxy genericReferenceProxy = new MapperProxy(gatewaySession, uri);
            // 創建接口
            InterfaceMaker interfaceMaker = new InterfaceMaker();
            interfaceMaker.add(new Signature(httpStatement.getMethodName(), Type.getType(String.class), new Type[]{Type.getType(String.class)}), null);
            Class<?> interfaceClass = interfaceMaker.create();
            // 代理對象
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(Object.class);
            // IGenericReference 統一泛化調用接口
            // interfaceClass    根據泛化調用註冊信息創建的接口,建立 http -> rpc 關聯
            enhancer.setInterfaces(new Class[]{IGenericReference.class, interfaceClass});
            enhancer.setCallback(genericReferenceProxy);
            return (IGenericReference) enhancer.create();
        });
    }

}
  • 爲了更好的銜接 HTTP 請求的地址【/wg/activity/sayHi】與 RPC 服務的映射關係,這裏我們像 ORM 框架一樣做了 bind 綁定關係。有了這樣一層綁定關係的抽象設計,就會變得非常好維護代碼實現關係。—— 代碼就是一塊磚頭🧱,怎麼搭建擺放,那是設計師的能力體現。

4. 領域驅動

不只是代碼,小傅哥也希望各個實現的工程結構也是乾淨整潔的。永遠不是使用設計模式耽誤時間,是不具備這樣的經驗的人員耽誤時間。不是現在耽誤開發時間,就是耽誤以後的迭代時間。

  • 舉例:如何設計出領域驅動的四層架構,會用 DDD 其實 DDD 也就沒那麼難。駕馭不了才難。
  • 同時到處都能看到設計模式的身影,用設計模式的思想解決各類場景實現問題。

四、技術項目與生態

其實小傅哥所構建的是一整套項目生態,以API網關所提供的HTTP服務爲樞紐,銜接星球中的各類項目進行組合構建。目前星球中包括;4個業務項目3個組件項目,它們可以被如下關係結構展示;

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