基于CSE的微服務工程實踐-多微服務框架演進

      網友投稿 1047 2025-04-02

      開發團隊選擇同樣的開發框架能夠更好的進行經驗積累和知識共享,從而提高開發效率。在實際項目中,這個過程經常被打破。團隊需要根據用戶需求的變化,選擇更好的開發框架來解決面對的新問題。每個開發團隊都不得不采取“持續迭代演進”的方法,來改造舊系統,開發新系統。


      在[單體應用微服務改造實踐]( https://bbs.huaweicloud.com/blogs/17ad483f325f11e9bd5a7ca23e93a891)中,分享了一種單體應用“持續迭代”改造微服務的方法。本文結合在改造過程中多微服務框架并存問題,描述一下使用CSE的開發實踐。

      本文假設應用已經采用網關搭建可持續演進的架構,如下圖。這個架構由多種開發框架構建的基于REST的微服務組成。

      本文提供的示例項目代碼托管在[github](https://github.com/huaweicse/cse-java-chassis-samples/tree/master/multi-framework) 。

      1????? 網關轉發規則開發

      CSE提供的edge service默認提供的轉發規則對于采用CSE開發的微服務最簡單,功能也最強大。根據配置的URL規則,將請求轉發到對應的微服務,只是網關核心功能之一。當edge service將請求轉發到CSE的微服務的時候,還具備如下功能:

      ·???????? 實例發現和動態更新。網關可以通過微服務名稱從服務中心發現實例列表,并在實例列表變化的時候,自動更新。

      ·???????? 實例隔離和重試。當微服務存在多個實例的時候,如果一個實例出現故障,那么可以進行一次重試,將請求轉發到其他實例;如果一個實例多次出現故障,需要在后續請求中將該實例隔離一段時間,避免頻繁失敗。

      ·???????? 支持灰度發布。網關可以結合請求參數規則,或者版本規則,將請求轉發到符合規則的實例上面去。比如將請求參數count>20的請求轉發給v2版本的實例,count<=20的請求轉發給v1版本的實例。

      ·???????? 支持AZ親和。網關需要結合微服務部署的數據中心信息,將請求轉發給就近的數據中心,以提高效率。

      ·???????? 支持流量控制。可以針對微服務、微服務的某個接口進行流量控制。

      ·???????? 自動按照接口兼容性轉發。這個場景指的是如果微服務存在多個版本,比如v1存在接口/a/b/c,v2新增了接口/x/y/z,那么當用戶請求/x/y/z的時候,能夠自動將請求轉發給v2,而不需要做任何配置。

      ·???????? 支持故障注入。故障注入用于模擬故障,這個在開發測試階段比較有用。通過模擬延時等情況,能夠測試網關在異常情況下是否正常工作。

      基于CSE的微服務工程實踐-多微服務框架演進

      上面列的功能是edge service的部分功能,這些功能都是開箱即用,無需用戶開發的。此外,CSE提供handler的機制,還實現了其他大量的開箱即用功能,在上述列表中沒有給出。下面的例子實現了一個edge service轉發給CSE服務的Dispatcher,CSE也默認提供了幾個Dispatcher,本例子參考了默認的Dispatcher代碼,定制了轉發的URL。

      public?class?DefaultCseDispatcher?extends?AbstractEdgeDispatcher?{ ??@Override ??public?void?init(Router?router)?{ ????//?Dispatcher?patterns.?This?dispatcher?only?forward?requests?to?store. ????router.routeWithRegex("/store/(.*)").handler(CookieHandler.create()); ????router.routeWithRegex("/store/(.*)").handler(createBodyHandler()); ????router.routeWithRegex("/store/(.*)").failureHandler(this::onFailure).handler(this::onRequest); ??} ? ??protected?void?onRequest(RoutingContext?context)?{ ????Map?pathParams?=?context.pathParams(); ????String?microserviceName?=?"store"; ????String?path?=?"/"?+?pathParams.get("param0"); ? ????EdgeInvocation?edgeInvocation?=?new?EdgeInvocation(); ????edgeInvocation.init(microserviceName,?context,?path,?httpServerFilters); ????edgeInvocation.edgeInvoke(); ??} ? ??@Override ??public?int?getOrder()?{ ????return?10000; ??} }

      Edge service可以通過Dispatcher擴展,將請求轉發給非CSE開發的REST服務。并且提供了一些擴展API供開發者使用,使用這些擴展API,能夠使用CSE提供的部分功能。

      其中:

      ·???????? DiscoveryTree 集成了實例發現和動態更新、實例隔離、AZ親和等功能。

      ·???????? LoadbalanceHandler 集成了重試、灰度發布等功能。

      下面例子展示了將請求轉發給auth服務。

      public?class?GenericHttpWithDiscoveryDispatcher?extends?AbstractEdgeDispatcher?{ ??private?static?Logger?LOGGER?=?LoggerFactory.getLogger(GenericHttpWithDiscoveryDispatcher.class); ? ??private?static?Vertx?vertx?=?VertxUtils.getOrCreateVertxByName("transport",?null); ? ??private?static?HttpClient?httpClient?=?vertx.createHttpClient(new?HttpClientOptions()); ? ??private?DiscoveryTree?discoveryTree?=?new?DiscoveryTree(); ? ??private?LoadbalanceHandler?loadbalanceHandler; ? ??class?RetriableHandler?implements?Handler?{ ????private?RoutingContext?context; ? ????private?String?path; ? ????private?Buffer?data; ? ????private?boolean?isRetry?=?false; ? ????public?RetriableHandler(RoutingContext?context,?String?path)?{ ??????this.context?=?context; ??????this.path?=?path; ??????this.context.response().setChunked(true); ????} ? ????@Override ????public?void?handle(Invocation?invocation,?AsyncResponse?asyncResponse)?throws?Exception?{ ??????URIEndpointObject?endpoint?=?(URIEndpointObject)?invocation.getEndpoint().getAddress(); ??????HttpClientRequest?clietRequest?= ??????????httpClient.request(context.request().method(), ??????????????endpoint.getPort(), ??????????????endpoint.getHostOrIp(), ??????????????"/"?+?path?+?"?"?+?context.request().query(), ??????????????clientResponse?->?{ ????????????????context.response().setStatusCode(clientResponse.statusCode()); ????????????????VertxHttpHeaders?headers?=?new?VertxHttpHeaders(); ????????????????clientResponse.headers().forEach(entry?->?{ ??????????????????headers.add(entry.getKey(),?entry.getValue()); ????????????????}); ????????????????context.response().headers().setAll(headers); ????????????????clientResponse.handler(data?->?{ ??????????????????context.response().write(data); ????????????????}); ????????????????clientResponse.endHandler((v)?->?{ ??????????????????context.response().end(); ??????????????????asyncResponse.success("OK"); ????????????????}); ??????????????}); ??????clietRequest.headers().setAll(context.request().headers()); ??????clietRequest.exceptionHandler(e?->?{ ????????asyncResponse.consumerFail(e); ??????}); ? ??????if?(!isRetry)?{ ????????//?data?can?not?be?read?twice,?so?cache?it?in?retry? ????????this.context.request().handler(d?->?{ ??????????clietRequest.write(d); ??????????data?=?d; ????????}); ? ????????context.request().endHandler((v)?->?{ ??????????clietRequest.end(); ????????}); ? ????????isRetry?=?true; ??????}?else?{ ????????if?(data?!=?null)?{ ??????????clietRequest.write(data); ????????} ????????clietRequest.end(); ??????} ????} ??} ? ??public?GenericHttpWithDiscoveryDispatcher()?{ ????discoveryTree.addFilter(new?IsolationDiscoveryFilter()); ????discoveryTree.addFilter(new?ServerDiscoveryFilter()); ????discoveryTree.sort(); ????loadbalanceHandler?=?new?LoadbalanceHandler(discoveryTree); ??} ? ??@Override ??public?int?getOrder()?{ ????return?10001; ??} ? ??@Override ??public?void?init(Router?router)?{ ????//?Dispatcher?patterns.?This?dispatcher?only?forward?requests?to?auth. ????String?regex?=?"/auth/(.*)"; ????router.routeWithRegex(regex).failureHandler(this::onFailure).handler(this::onRequest); ??} ? ??protected?void?onRequest(RoutingContext?context)?{ ????Map?pathParams?=?context.pathParams(); ? ????String?microserviceName?=?"auth"; ????String?path?=?pathParams.get("param0"); ? ????Invocation?invocation?= ????????new?NonSwaggerInvocation(RegistryUtils.getAppId(),?microserviceName,?"0+",?new?RetriableHandler(context,?path)); ????try?{ ??????loadbalanceHandler.handle(invocation,?resp?->?{ ????????if?(resp.isFailed())?{ ??????????context.response().setStatusCode(resp.getStatusCode()); ??????????context.response().write(((Exception)?resp.getResult()).getMessage()); ??????????context.response().end(); ????????} ??????}); ????}?catch?(Exception?e)?{ ??????LOGGER.error("",?e); ????} ??} ? }

      由于auth服務采用Spring MVC開發,該服務在注冊的時候,不會生成契約信息。因此上面列出的幾個功能是不具備的:

      ·???????? 不支持流量控制。

      ·???????? 不支持自動按照接口兼容性轉發。

      ·???????? 不支持故障注入。

      此外,CSE通過Handler擴展提供的其他開箱即用功能,也是不具備的。

      最后一個服務就是將請求轉發給user了。user服務沒有向服務中心注冊,因此沒有微服務信息,也沒有契約信息。需要在代碼里面寫上實例列表和自己定義負載均衡。

      public?class?GenericHttpDispatcher?extends?AbstractEdgeDispatcher?{ ??private?static?Logger?LOGGER?=?LoggerFactory.getLogger(GenericHttpDispatcher.class); ? ??private?static?Vertx?vertx?=?VertxUtils.getOrCreateVertxByName("transport",?null); ? ??private?static?HttpClient?httpClient?=?vertx.createHttpClient(new?HttpClientOptions()); ? ??public?GenericHttpDispatcher()?{ ??} ? ??@Override ??public?int?getOrder()?{ ????return?10001; ??} ? ??@Override ??public?void?init(Router?router)?{ ????//?Dispatcher?patterns.?This?dispatcher?only?forward?requests?to?user. ????String?regex?=?"/user/(.*)"; ????router.routeWithRegex(regex).failureHandler(this::onFailure).handler(this::onRequest); ??} ? ??protected?void?onRequest(RoutingContext?context)?{ ????Map?pathParams?=?context.pathParams(); ? ????String?path?=?pathParams.get("param0"); ? ????HttpClientRequest?clietRequest?= ????????httpClient.request(context.request().method(), ????????????//?hard?coded?ip/port?here.?can?use?configurations.? ????????????9093, ????????????"localhost", ????????????"/"?+?path?+?"?"?+?context.request().query(), ????????????clientResponse?->?{ ??????????????context.response().setStatusCode(clientResponse.statusCode()); ??????????????VertxHttpHeaders?headers?=?new?VertxHttpHeaders(); ??????????????clientResponse.headers().forEach(entry?->?{ ????????????????headers.add(entry.getKey(),?entry.getValue()); ??????????????}); ??????????????context.response().headers().setAll(headers); ??????????????clientResponse.handler(data?->?{ ????????????????context.response().write(data); ??????????????}); ??????????????clientResponse.endHandler((v)?->?{ ????????????????context.response().end(); ??????????????}); ????????????}); ???? ? ????clietRequest.headers().setAll(context.request().headers()); ????clietRequest.exceptionHandler(e?->?{ ??????LOGGER.error("",?e); ????}); ????context.request().handler(data?->?{ ??????clietRequest.write(data); ????}); ????context.request().endHandler((v)?->?{ ??????clietRequest.end();? ????}); ??} }

      網關給user服務轉發的過程中,沒有上面列舉的所有服務治理能力,只是一個單純的HTTP轉發器。

      2????? CSE訪問第三方服務

      在edge service轉發給CSE服務的過程中,介紹了一組治理能力。這組治理能力在CSE服務調用其他CSE的服務的時候,也是具備的,而且這組能力不僅體現在調用者(consumer),提供者(provider)處理請求的時候,也會走handler鏈,從而具備流控、隔離等能力。 CSE之間的微服務調用非常簡單,包括RestTemplate和RPC兩種開發模式,這里不詳細介紹,開發者可以參考開發指南進行學習。

      CSE也提供了調用第三方服務的能力,通過使用CSE提供的第三方能力,這些治理能力對于調用者(consumer)一端,也是全部具備的。因此在CSE服務中調用其他第三方服務的時候,應該盡可能使用CSE提供的第三方訪問能力,而不是使用普通的HTTP client。

      使用CSE的http client的原理是首選通過接口的方式聲明需要訪問的服務端的契約。

      @RequestMapping(path?=?"/") public?interface?AuthService?{ ??@GetMapping(path?=?"/oauth/token") ??Token?auth(@RequestParam(name?=?"username")?String?username,?@RequestParam(name?=?"password")?String?password, ??????@RequestParam(name?=?"grant_type")?String?grant_type, ??????@RequestParam(name?=?"scope")?String?scope, ??????@RequestParam(name?=?"client_id")?String?clientId, ??????@RequestParam(name?=?"client_secret")?String?clientSecret); }

      然后本地模擬注冊一個在服務中心注冊的實例。

      @Component public?class?ThirdPartyRegistry?implements?BootListener?{ ? ??@Override ??public?void?onBootEvent(BootEvent?event)?{ ????if?(event.getEventType()?==?EventType.AFTER_REGISTRY)?{ ??????DiscoveryTree?discoveryTree?=?new?DiscoveryTree(); ??????discoveryTree.addFilter(new?IsolationDiscoveryFilter()); ??????discoveryTree.addFilter(new?ServerDiscoveryFilter()); ??????discoveryTree.sort(); ? ??????DiscoveryContext?context?=?new?DiscoveryContext(); ??????Invocation?invocation?= ??????????new?NonSwaggerInvocation("default",?"auth",?"0+",?null); ??????context.setInputParameters(invocation); ??????VersionedCache?serversVersionedCache?=?discoveryTree.discovery(context, ??????????"default", ??????????"auth", ??????????"0+"); ??????List?servers?=?serversVersionedCache.data(); ??????List?instances?=?new?ArrayList<>(servers.size()); ??????servers.forEach(item?->?{ ????????MicroserviceInstance?instance?=?new?MicroserviceInstance(); ????????List?endpoints?=?item.getInstance().getEndpoints(); ????????List?endpointsWithoutPrefix?=?new?ArrayList<>(endpoints.size()); ????????endpoints.forEach(e?->?{ ??????????System.out.println(e); ??????????endpointsWithoutPrefix.add(e.substring(0,?e.indexOf("?"))); ????????}); ????????instance.setEndpoints(endpointsWithoutPrefix); ????????instances.add(instance); ??????}); ??????RegistryUtils.getServiceRegistry() ??????????.registerMicroserviceMapping( ??????????????"authStub", ??????????????"1.1.1", ??????????????instances, ??????????????AuthService.class); ????} ??} }

      最后就可以按照CSE訪問CSE服務一樣訪問這個第三方服務。

      @RpcReference(microserviceName?=?"authStub",?schemaId="authStub") ??private?AuthService?authService; ? ??@GetMapping("/auth") ??public?Token?auth()?{ ????return?authService.auth("user_1",?"123456",?"password",?"read",?"client_2",?"123456"); ??}

      3????? 第三方服務訪問CSE

      第三方訪問CSE可以按照原生的第三方HTTP Client API來訪問。 這個治理能力等同于第三方提供的功能,通常不包含治理能力。 這里不在詳細描述。

      4????? 總結

      通過上面的例子,看到了CSE在處理多種REST開發框架并存情況下,網關轉發和服務之間調用需要采用的技術。這些技術在微服務持續演進過程中會經常用到。但是微服務演進的最終目的是選擇一套技術來解決問題,應該避免架構選型的多樣化,這無疑會增加學習和維護的成本。同時可以看到CSE在服務治理能力方面的優勢,這些優勢對于微服務的可靠運行提供了非常強大的保障,這個才是CSE作為一個微服務框架核心重要的部分。

      CSE Edge Service

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:C語言: 字符數組與字符串總結
      下一篇:wps文檔中怎么插入好看的背景色?
      相關文章
      国产成+人+综合+亚洲专| 亚洲视频在线观看免费视频| 亚洲黄色在线观看视频| 亚洲色成人中文字幕网站 | 亚洲精品国精品久久99热| 亚洲AV无码国产一区二区三区| 2019亚洲午夜无码天堂| 亚洲制服丝袜第一页| 亚洲精品福利你懂| 午夜在线a亚洲v天堂网2019| 日韩亚洲国产高清免费视频| 中文字幕亚洲精品无码| 亚洲乱妇老熟女爽到高潮的片| 亚洲日韩一中文字暮| 亚洲精品V天堂中文字幕| 亚洲av乱码一区二区三区按摩| 亚洲1区2区3区精华液| 国产成人亚洲精品电影| 国产亚洲人成在线影院| 亚洲国产成人久久综合野外| 亚洲乱亚洲乱少妇无码| 国产自偷亚洲精品页65页| 亚洲级αV无码毛片久久精品| 亚洲国产精品VA在线观看麻豆| 亚洲av福利无码无一区二区| 久久亚洲私人国产精品vA | 精品久久久久久亚洲中文字幕| 青草久久精品亚洲综合专区| 国内成人精品亚洲日本语音 | 亚洲美女人黄网成人女| 亚洲依依成人精品| 亚洲色最新高清av网站| 爱情岛亚洲论坛在线观看| 亚洲午夜精品第一区二区8050| 亚洲精品高清无码视频| 久久精品国产亚洲av影院| 亚洲AV无码国产精品色| 亚洲av无码专区在线观看下载| 亚洲精品专区在线观看| 久久精品国产亚洲麻豆| 亚洲麻豆精品果冻传媒|