Surging實踐經驗


背景

在去年9月份的時候,我入職一家做航空軟件產品的公司。當時公司部門領導決定構建一個技術平台(或稱為技術中台),通過該技術平台進而孵化各個業務系統。說白了就是需要通過一個分布式框架或是微服務框架提高應用系統的性能和並發處理能力、業務處理能力。
當時現有的系統是在 .net framework平台上搭建的簡單的單體應用,並不具備可用性、擴展性、易用性等。
我在入職前也學習過一些微服務相關的知識,並通過搜索引擎了解了.net平台下的一些微服務框架和分布式架構。在對比不同技術框架的背景后,我決定使用surging作為公司的技術平台。原因在於:

  • Surging的設計思想和理念更符合微服務的架構思想,通過dotnetty框架實現的RPC通信,內置了服務治理保證通信的可靠性。
  • 通過向服務注冊中心(Zookeeper、Consul)注冊相關元數據來管理集群的服務命令、路由信息、緩存中間件等數據,服務注冊不需要進行額外的處理
  • Surging內置了負載均衡算法。
  • Surging支持多種協議的通信方式,並且支持ws服務主機與一般服務(Http、TCP)主機之間直接通過RPC進行通信。
  • 服務之間的調用很方便,作者提供了基於ServiceProxyProvider、和基於ServiceProxyFactory的rpc調用方式,簡單易用。
  • 模塊化設計,很方便的對模塊進行擴展。
  • 支持事件總線,通過消息對象實現的時效件縱向的適配可以實現發布訂閱的交互模式。
  • .net core 跨平台,性能更高。

架構維護

由於作者一直在維護surging,而且我們也需要對surging的一些模塊進行調整,也需要擴展一些surging的組件包,所以我們在使用surging的過程中是直接獲取源代碼后,在公司維護一份自己的源碼,然后打包成nuget包,並發布到內部的nuget服務,通過內部的nuget對surging組件進行分發。
在獲取surging源碼后,我對surging進行了如下調整:

  1. 根據公司要求,對名稱空間和包名稱進行了調整。
  2. 對異常處理進行了重構
  3. 將消息返回的數據結果名稱重命名為Data,統一了消息返回碼。
  4. 修改了默認的json序列化器,默認使用camelCame風格
  5. 重構了簽發token的方法(使用jose-jwt組件)
  6. 支持通過RpcContext設置token的payload和獲取payload,通過擴展RpcContextSession獲取運行時登錄用戶
  7. 擴展了Dapper、Domain、Validation、Schedule(基於Quartz的分布式任務調度)等組件包
  8. swagger文檔支持jwt token驗證
  9. 新增surging打包腳本等等
  10. 現在surging的demo案例和內部的開發者文檔

如果你在使用surging的過程中,對surging源碼較為熟悉,並希望對surging進行一定的調整、擴展自己公司的一些組件的時候,您可以通過社區獲取surging的源代碼,並在公司的代碼庫維護自己的分支。但是需要對作者對源碼的修改要及時了解和熟悉。
nuget服務的搭建可以使用nuget官方提供的nuget.server或是nexus 。
對架構的維護可能是一個持續的和長久的過程,你可以通過企業內部的需求和作者對框架的調整對技術框架持續的進行調整和維護。在對surging進行調整維護后,就通過通過打包腳本進行打包發布到內部的nuget服務。

業務框架

構建微服務主機

由於在構建每個微服務主機的代碼和配置文件都是一致的,無法就是對配置文件的一些配置項進行調整,所以可以將構建微服務主機的代碼和配置文件抽象出來,統一放置在Shared目錄中,再在項目文件中通過import進入即可。
如何將公共的腳本、配置文件、屬性抽象出來,可以參考:https://github.com/surging-cloud/Surging.Hero/tree/develop/hero/src/Shared 。
如何構建主機呢?Surging通過ServiceHostBuilder來構建微服務主機,在構建主機過程中,可以添加一些服務組件或是指定相應的配置文件。構建主機的代碼如下:

需要注意的是可以通過SurgingServiceEngine來指定surging服務引擎掃描的業務組件的目錄。以及也可以通過Startup注入相應的服務或是制定配置文件。

var host = new ServiceHostBuilder()
                 .RegisterServices(builder =>
                 {
                     builder.AddMicroService(option =>
                      {
                          option.AddServiceRuntime()
                           .AddClientProxy()
                           .AddRelateServiceRuntime()
                           .AddConfigurationWatch()
                           .AddServiceEngine(typeof(SurgingServiceEngine))
                           ;

                          builder.Register(p => new CPlatformContainer(ServiceLocator.Current));
                      });
                 })
                 .ConfigureLogging(loggging =>
                 {
                     loggging.AddConfiguration(
                         AppConfig.GetSection("Logging"));
                 })
                 .UseServer(options => { })
                 .UseConsoleLifetime()
                 .Configure(build =>
                 {
#if DEBUG
                     build.AddCacheFile("${cachePath}|/app/configs/cacheSettings.json", optional: false, reloadOnChange: true);
                     build.AddCPlatformFile("${surgingPath}|/app/configs/surgingSettings.json", optional: false, reloadOnChange: true);
                     build.AddEventBusFile("${eventBusPath}|/app/configs/eventBusSettings.json", optional: false);
                     build.AddConsulFile("${consulPath}|/app/configs/consul.json", optional: false, reloadOnChange: true);


#else
                    build.AddCacheFile("${cachePath}|configs/cacheSettings.json", optional: false, reloadOnChange: true);                      
                    build.AddCPlatformFile("${surgingPath}|configs/surgingSettings.json", optional: false,reloadOnChange: true);                    
                    build.AddEventBusFile("configs/eventBusSettings.json", optional: false);
                    build.AddConsulFile("configs/consul.json", optional: false, reloadOnChange: true);
#endif
                 })
                 .UseProxy()
                 .UseStartup<Startup>()
                 .Build();

            using (host.Run())
            {
                Console.WriteLine($"服務主機啟動成功{DateTime.Now}。");
            }

 

業務框架的分層

一般地,我會將每個微服務組件分為如下幾層:

1. Host

用於構建微服務主機和服務寄宿,一般我會直接引用Application層,托管應用服務本身。

2. IApplication 應用接口層

  • 用於定義應用接口,每個應用接口都應當繼承IServiceKey,Surging通過應用接口生成服務條目(ServiceEntry)
  • 使用ServiceBundle特性來標識路由模板。
  • 可以使用ServiceCommand來對Action進行注解,該元數據會被注冊到服務注冊中心,在RPC通信過程中,通過ServiceCommand注解的元數據實現服務治理。該特性可不需要配置,可以通過SurgingSettings.json統一指定相關的配置,如果配置了ServiceCommand特性,會優先選擇特性指定的配置值。
  • 可以通過Service特性指定Action的一些元數據。
  • 應用接口層除了定義應用接口之外,還需要定義相關的DTO對象。
  • 應用接口層可以被其他微服務組件應用或是通過nuget進行分發,通過IServiceProxyFactory創建應用接口的代理,從而實現RPC通信。

3. Application 應用層

  • 應用層主要是實現業務流程和輸入輸出判斷,不實現復雜的業務邏輯
  • 應用層的應用需要實現應用接口定義的接口,並繼承ProxyServiceBase,基類ProxyServiceBase提供了一些通用的方法。

4. Domain 領域層

  • 領域層主要是實現具體的業務邏輯

5. Domian.Shared

  • 定義微服務組件通用的值類型(model或是枚舉類型),可被其他微服務組件引用

容器化服務和服務編排

服務容器化

docker是一款優秀的容器引擎產品。將服務容器化,能夠最大化的發揮微服務的體驗性。能夠讓開發者感受到docker構建一次,處處運行的魅力所在。所以我強烈推薦在開發過程中,使用docker容器化服務組件,使用docker-compose編排微服務組件。
vs對docker-compose進行開發調試提供了非常友好的體驗性。
一般地,會在服務組件的Host層提供Dockerfile用於構建docker鏡像。如下的dockerfile提供了微服務組件的編譯、構建等過程。

FROM microsoft/dotnet:2.2.0-runtime AS base
WORKDIR /app
ARG rpc_port=100
ARG http_port=8080
ARG ws_port=96
ENV TZ=Asia/Shanghai 
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 
EXPOSE ${rpc_port} ${http_port} ${ws_port}

FROM microsoft/dotnet:2.2-sdk AS build
WORKDIR /src
COPY . .
ARG sln_name
RUN dotnet restore ${sln_name} && \
    dotnet build --no-restore -c Release ${sln_name}

FROM build AS publish
ARG host_workdir
WORKDIR ${host_workdir}
RUN dotnet publish --no-restore -c Release -o /app

FROM base AS final
ARG host_name
ENV host_name=${host_name}
COPY --from=publish /app .
ENTRYPOINT dotnet ${host_name}

 

服務編排

使用docker-compose編排微服務組件,一般的,使用docker-compose.yml定義鏡像構建的上下文、指定網絡、鏡像名稱、掛載的目錄等,通過docker-compose.override.yml來指定配置文件的環境變量,.env來設置環境變量的值,通過docker-compose.vs.debug.yml來指定調試過程中的相關設置(部署中可不指定該編排文件)。

需要注意的是,surging在開發過程中,基礎服務也通過docker-compose來編排和啟動,且必須在開發和調試前啟動基礎服務。基礎服務和surging服務組件指定的網絡必須同一個。

基礎服務編排如下所示:

由於開發過程中的基礎服務並沒有考慮到高可用,在生產環境中建議基礎服務集群化。

version: '3.7'

services:
  consul:
    image: consul:latest
    ports:
      - "8400:8400"
      - "8500:8500"
      - "8600:8600"
      - "8600:8600/udp"
    command: "agent -server -bootstrap-expect 1 -ui -client 0.0.0.0"
    networks:
      - surging_hero_service_net
  redis:
    image: redis:latest
    ports:
      - "6379:6379"
    networks:
      - surging_hero_service_net
  rabbitmq:
    image: rabbitmq:management
    environment:
      RABBITMQ_ERLANG_COOKIE: "SWQOKODSQALRPCLNMEQG"
      RABBITMQ_DEFAULT_USER: "rabbitmq"
      RABBITMQ_DEFAULT_PASS: "rabbitmq"
      RABBITMQ_DEFAULT_VHOST: "/"
    ports:
      - "15672:15672"
      - "5672:5672"
    networks:
      - surging_hero_service_net
      
networks:
  surging_hero_service_net: 
    driver: bridge
    name: surging_hero_service_net
    ipam:
      driver: default
      config:
      - subnet: 172.22.0.1/16

 

微服務組件的編排請參考: https://github.com/surging-cloud/Surging.Hero/tree/develop/hero/docker-compose/surging.hero

開發與調試

其實在開發過程中,由於業務模塊的不同,責任人不同,開發團隊不同,開發者擁有的權限不同,業務模塊的代碼有可能放到不同的git倉庫。建議將微服務服務組件的應用接口層和Domian.Shared可以發布的企業內部的nuget服務。其他微服務組件可以通過nuget服務引用應用接口層和Domian.Shared組件。
如果源代碼都放到一個git倉庫中,也可以建立多個解決方案或是docker-compose編排文件項目來編排不同的服務,方便開發和調試。

常見問題

首次使用docker-compose進行調試服務時,由於vs會從網絡上下載vsdbg組件,由於網絡原因,一般都會比較慢,開發者可以從其他同事的電腦的家目錄下拷貝vsdbg到本機,重新打開vs,然后再進行調試。

Devops

業務流程

在開發過程中,我們使用Jenkins實現持續集成和部署。整個流程如下所述:

  1. 開發者編寫業務代碼或修復完bug后,提交代碼,push到遠程倉庫,並發起pr請求,請求合並到develop分支。
  2. 當代碼審核通過后,合並到develop分支后,通過設置gitlab或是giteewebhook,觸發jenkins執行構建。或是通過設置Jenkins的定時任務檢測代碼庫變化,當代碼庫變化后,jenkins獲取最新代碼,執行構建操作(由於當時我們Jenkins部署的環境是內網,gitee無法訪問公司內網,所以無法設置webhook)
  3. Jenkins通過預先設置好的命令和腳本執行構建打包程序。本質上是執行docker-compose build打包docker鏡像,當完成構建和打包docker鏡像后,然后將鏡像推送到企業內部的docker鏡像倉庫。
  4. 之后,jenkins通過Jenkins SSH插件將部署腳本拷貝到k8集群的master節點,通過ssh插件在k8s master節點執行部署命令。完成后,微服務集群將自動部署到指定的k8s集群中。

整個devops流程如下所述(但是我們沒有與釘釘做集成):
devops

注意事項

  1. 企業內部的docker倉庫除了可以使用harbor搭建之外,還可以使用nexus。推薦使用nexus作為倉庫管理服務,因為nexus除了支持docker鏡像倉庫之外,還支持nuget包、npm等格式的包管理。
  2. 建議企業內部在構建業務平台時,根據業務模塊划分主題,一個主題對應一個數據庫,一個git倉庫,一個項目組,多個相關的微服務組件,一個Jenkins構建項目。每個主題獨立的進行持續集成與部署。
  3. 建議基礎服務consul、rabbitmq、redis考慮集群。

產品交付和部署

  1. 一般的,我們通過docker鏡像完成產品交付與部署。可以通過編寫部署腳本在k8s集群或是通過rancher進行部署。
  2. 可以使用k8s或是rancher提供的Dashborad進行容器和服務的監控和管理。

體會

  1. surging的設計思想是無疑正確的。相比於市面上其他的.net微服務框架或是分布式框架,無論是服務治理還是內部通信機制,服務引擎設置,主機寄宿均有獨到之處。(abp vnext的微服務框架通過內部網關Ocelot進行通信,完全違反的去中心化設計,而且性能也相對較差的多)
  2. 在使用surging的過程中,也遇到了一些問題或是bug(例如:1.首次訪問性能較差;2.服務實例無法支持同時擴展),在反饋到github社區或是請求作者協助,都能夠得到及時反饋。目前作者已經即將完成對surging2.0的開發,相信會有更優秀的體驗。
  3. 在開發和測試、部署和產品交付中推薦將服務容器化,推薦使用linux作為部署服務器。
  • 最后

    • 如果你對surging感興趣,可以在github上對surging關注。
    • 如果你對如何使用surging落地開發,您可以在github上關注surging.hero
      • surging.hero是一個使用surging作為開發框架的權限管理平台。目前項目剛剛開始,歡迎各位開發者加入,如果您想加入surging.hero的開發或是願意為surging的生態做出貢獻,歡迎加入surging-cloud社區。
      • 如果你希望加入surging-cloud社區,可以將你的github賬號通過email到:1029765111@qq.com,並備注`申請加入 surging-cloud社區 即可。
      • 如果您對surging.hero感興趣並希望加入surging.hero的開發,也可以申請加入qq群:713943626
    • 如果大家對surging確實感興趣,后期我有時間的話,可以寫一些我使用surging的經驗或是對源碼的理解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM