<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Cloud 框架優(yōu)雅關(guān)機和重啟

          共 6845字,需瀏覽 14分鐘

           ·

          2023-07-10 06:31

          65be33ff2ed4271b2983485fba0c1da8.webp

          背景

          我們編寫的Web項目部署之后,經(jīng)常會因為需要進(jìn)行配置變更或功能迭代而重啟服務(wù),單純的kill -9 pid的方式會強制關(guān)閉進(jìn)程,這樣就會導(dǎo)致服務(wù)端當(dāng)前正在處理的請求失敗,那有沒有更優(yōu)雅的方式來實現(xiàn)關(guān)機或重啟呢?

          優(yōu)雅停機

          在項目正常運行的過程中,如果直接不加限制的重啟可能會發(fā)生一下問題

          1. 項目重啟(關(guān)閉)時,調(diào)用方可能會請求到已經(jīng)停掉的項目,導(dǎo)致拒絕連接錯誤(503),調(diào)用方服務(wù)會緩存一些服務(wù)列表導(dǎo)致,服務(wù)列表依然還有已經(jīng)關(guān)閉的項目實例信息

          2. 項目本身存在一部分任務(wù)需要處理,強行關(guān)閉導(dǎo)致這部分?jǐn)?shù)據(jù)丟失,比如內(nèi)存隊列、線程隊列、MQ 關(guān)閉導(dǎo)致重復(fù)消費

          為了解決上面出現(xiàn)的問題,提供以下解決方案:

          1. 關(guān)于問題 1 采用將需要重啟的項目實例,提前 40s 從 nacos 上剔除,然后再重啟對應(yīng)的項目,保證有 40s 的時間可以用來服務(wù)發(fā)現(xiàn)刷新實例信息,防止調(diào)用方將請求發(fā)送到該項目

          2. 使用 Spring Boot 提供的優(yōu)雅停機選項,再次預(yù)留一部分時間

          3. 使用 shutdonwhook 完成自定的關(guān)閉操作

          一、主動將服務(wù)剔除

          該方案主要考慮因為服務(wù)下線的瞬間,如果 Nacos 服務(wù)剔除不及時,導(dǎo)致仍有部分請求轉(zhuǎn)發(fā)到該服務(wù)的情況

          在項目增加一個接口,同時在準(zhǔn)備關(guān)停項目前執(zhí)行 stop 方法,先主動剔除這個服務(wù),shell 改動如下:

          run.sh

                function?stop()??
          {??
          ????echo?"Stop?service?please?waiting...."??
          ????echo?"deregister."??
          ????curl?-X?POST?"127.0.0.1:${SERVER_PORT}/discovery/deregister"??
          ????echo?""??
          ????echo?"deregister?[${PROJECT}]?then?sleep?40?seconds."??
          ????#?這里?sleep?40?秒,因為?Nacos?默認(rèn)的拉取新實例的時間為?30s,?如果調(diào)用方不修改的化,這里應(yīng)該最短為?30s
          ????#?考慮到已經(jīng)接收的請求還需要一定的時間進(jìn)行處理,這里預(yù)留?10s,?如果?10s?還沒處理完預(yù)留的請求,調(diào)用方肯定也超時了??
          ????#?所以這里是?30?+?10?=?40sleep?40??
          ????kill?-s?SIGTERM?${PID}?
          ????if?[?$??-eq?0?];then??
          ????????echo?"Stop?service?done."
          ????else??
          ????????echo?"Stop?service??failed!"??
          ????fi
          }

          在項目中增加 /discovery/deregister 接口

          Spring Boot MVC 版本

                import?lombok.extern.slf4j.Slf4j;??
          import?org.springframework.beans.factory.annotation.Autowired;??
          import?org.springframework.cloud.client.serviceregistry.Registration;??
          import?org.springframework.cloud.client.serviceregistry.ServiceRegistry;??
          import?org.springframework.context.annotation.Lazy;??
          import?org.springframework.web.bind.annotation.PostMapping;??
          import?org.springframework.web.bind.annotation.RequestMapping;??
          import?org.springframework.web.bind.annotation.RestController;??

          @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")??
          @RestController??
          @RequestMapping("discovery")??
          @Slf4j??
          public?class?DeregisterInstanceController?{??

          ????@Autowired
          ????@Lazy??
          ????private?ServiceRegistry?serviceRegistry;??

          ????@Autowired??
          ????@Lazy??
          ????private?Registration?registration;??


          ????@PostMapping("deregister")??
          ????public?ResultVO<String>?deregister()?{??
          ????????log.info("deregister?serviceName:{},?ip:{},?port:{}",??
          ????????registration.getServiceId(),??
          ????????registration.getHost(),??
          ????????registration.getPort());??
          ????????try?{??
          ????????????serviceRegistry.deregister(registration);??
          ????????}?catch?(Exception?e)?{??
          ????????????log.error("deregister?from?nacos?error",?e);??
          ????????????return?ResultVO.failure(e.getMessage());??
          ????????}??
          ????????????return?ResultVO.success();??
          ????}
          }

          Spring Cloud Gateway

          通過使用 GatewayFilter 方式來處理

                package?com.br.zeus.gateway.filter;??

          import?static?org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted;??

          import?com.alibaba.fastjson.JSON;??
          import?com.br.zeus.gateway.entity.RulesResult;??
          import?lombok.extern.slf4j.Slf4j;??
          import?org.springframework.beans.factory.annotation.Autowired;??
          import?org.springframework.cloud.client.serviceregistry.Registration;??
          import?org.springframework.cloud.client.serviceregistry.ServiceRegistry;??
          import?org.springframework.cloud.gateway.filter.GatewayFilter;??
          import?org.springframework.cloud.gateway.filter.GatewayFilterChain;??
          import?org.springframework.context.annotation.Lazy;??
          import?org.springframework.core.Ordered;??
          import?org.springframework.core.io.buffer.DataBuffer;??
          import?org.springframework.http.HttpStatus;??
          import?org.springframework.http.MediaType;??
          import?org.springframework.http.server.reactive.ServerHttpResponse;??
          import?org.springframework.stereotype.Component;??
          import?org.springframework.web.server.ServerWebExchange;??
          import?reactor.core.publisher.Mono;??

          @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection")??
          @Component??
          @Slf4j??
          public?class?DeregisterInstanceGatewayFilter?implements?GatewayFilter,?Ordered?{??

          ????@Autowired??
          ????@Lazy??
          ????private?ServiceRegistry?serviceRegistry;??

          ????@Autowired??
          ????@Lazy??
          ????private?Registration?registration;??

          ????public?DeregisterInstanceGatewayFilter()?{??
          ????????log.info("DeregisterInstanceGatewayFilter?啟用");??
          ????}??

          ????@Override??
          ????public?Mono<Void>?filter(ServerWebExchange?exchange,?GatewayFilterChain?chain)?{??
          ????????if?(isAlreadyRouted(exchange))?{??
          ????????????return?chain.filter(exchange);??
          ????????}??

          ????????log.info("deregister?serviceName:{},?ip:{},?port:{}",??
          ????????registration.getServiceId(),??
          ????????registration.getHost(),??
          ????????registration.getPort());??

          ????????RulesResult?result?=?new?RulesResult();??
          ????????try?{??
          ????????????serviceRegistry.deregister(registration);??
          ????????????result.setSuccess(true);??
          ????????}?catch?(Exception?e)?{??
          ????????????log.error("deregister?from?nacos?error",?e);??
          ????????????result.setSuccess(false);??
          ????????????result.setMessage(e.getMessage());??
          ????????}??

          ????????ServerHttpResponse?response?=?exchange.getResponse();??
          ????????response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);??
          ????????DataBuffer?bodyDataBuffer?=?response.bufferFactory().wrap(JSON.toJSONBytes(result));??
          ????????response.setStatusCode(HttpStatus.OK);??
          ????????return?response.writeWith(Mono.just(bodyDataBuffer));??
          ????}??

          ????@Override??
          ????public?int?getOrder()?{??
          ????????return?0;??
          ????}??
          }

          在路由配置時,增加接口和過濾器的關(guān)系

                .route("DeregisterInstance",?r?->?r.path("/discovery/deregister")??
          ????.filters(f?->?f.filter(deregisterInstanceGatewayFilter))??
          ????.uri("https://example.com"))
          二、Spring Boot 自帶的優(yōu)雅停機方案

          要求 Spring Boot 的版本大于等于 2.3

          在配置文件中增加如下配置:

          application.yaml

                server:??
          ????shutdown:?graceful
          spring:
          ??lifecycle:
          ????timeout-per-shutdown-phase:?10s

          當(dāng)使用 server.shutdown=graceful 啟用時,在 web 容器關(guān)閉時,web 服務(wù)器將不再接收新請求,并將等待活動請求完成的緩沖期。使用 timeout-per-shutdown-phase 配置最長等待時間,超過該時間后關(guān)閉

          三、使用 ShutdownHook
                public?class?MyShutdownHook?{
          ????public?static?void?main(String[]?args)?{
          ????????//?創(chuàng)建一個新線程作為ShutdownHook
          ????????Thread?shutdownHook?=?new?Thread(()?->?{
          ????????????System.out.println("ShutdownHook?is?running...");
          ????????????//?執(zhí)行清理操作或其他必要的任務(wù)
          ????????????//?1.?關(guān)閉?MQ
          ????????????//?2.?關(guān)閉線程池
          ????????????//?3.?保存一些數(shù)據(jù)
          ????????});

          ????????//?注冊ShutdownHook
          ????????Runtime.getRuntime().addShutdownHook(shutdownHook);

          ????????//?其他程序邏輯
          ????????System.out.println("Main?program?is?running...");

          ????????//?模擬程序執(zhí)行
          ????????try?{
          ????????????Thread.sleep(5000);?//?假設(shè)程序運行5秒鐘
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}

          ????????//?當(dāng)程序退出時,ShutdownHook將被觸發(fā)執(zhí)行
          ????}
          }


          瀏覽 145
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩视频一区 | 成人福利免费视频 | 欧美成人免费专区精品高清 | 国产区视频在线观看 | 玖玖视频这里只有精品 |