最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

提交gRPC-spring-boot-starter項目bug修復的pr說明

 更新時間:2022年02月22日 09:54:24   作者:kl  
這篇文章主要介紹了這篇文章主要為大家介紹了gRPC-spring-boot-starter項目提交bug修復的pr的原因說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助

前言

為了更好的說明給gRPC-spring-boot-starter項目提交bug修復的pr的原因,解答作者的問題。以博文的形式記錄了整個過程的上下文,目前pr未合并還在溝通處理中,希望此博文可以更清楚描述問題

pr地址:https://github.com/yidongnan/grpc-spring-boot-starter/pull/454

gRPC-spring-boot-starter是什么?

這是一個spring-boot-starter項目,用來在spring boot框架下,快速便捷的使用grpc技術,開箱即用。它提供如下等功能特性:

  • 在 spring boot 應用中,通過@GrpcService自動配置并運行一個嵌入式的 gRPC 服務。
  • 使用@GrpcClient自動創(chuàng)建和管理您的 gRPC Channels 和 stubs
  • 支持Spring Cloud(向ConsulEurekaNacos注冊服務并獲取 gRPC 服務端信息)
  • 支持Spring Sleuth作為分布式鏈路跟蹤解決方案(如果brave-instrument-grpc存在)
  • 支持全局和自定義的 gRPC 服務端/客戶端攔截器
  • 支持Spring-Security
  • 支持metric (基于micrometer/actuator)
  • 也適用于 (non-shaded) grpc-netty

選型gRPC-spring-boot-starter

博主新入職公司接手的項目采用grpc做微服務通訊框架,項目底層框架采用的spring boot,然后grpc的使用是純手工配置的,代碼寫起來比較繁瑣, 而且這種繁瑣的模板化代碼充斥在每個采用了grpc的微服務項目里。所以技術選型后找到了gRPC-spring-boot-starter 這個開源項目,這個項目代碼質量不錯,非常規(guī)范,文檔也比較齊全。但是鑒于之前工作經驗遇到過開源項目的問題(博主選型的原則,如果有合適的輪子,就摸透這個輪子,然后基于這個輪子二開,沒有就自己造一個輪子),而且一般解決周期比較長,所以 最后,我們沒有直接采用他們的發(fā)行包,而是fork了項目后,打算自己維護。正因為如此,才為后面迅速解決問題上線成為可能。也驗證了二開這個選擇是正確的。

bug出現,grpc未優(yōu)雅下線

風風火火重構了所有代碼,全部換成gRPC-spring-boot-starter后就上線了,上線后一切都非常好,但是項目在第二次需求上線投產時發(fā)生了一些問題。 這個時候還不確定是切換grpc實現導致的問題,現象就是,線上出現了大量的請求異常。上線完成后,異常就消失了。后面每次滾動更新都會出現類似的異常。 這個時候就很容易聯系到是否切換grpc實現后,grpc未優(yōu)雅下線,導致滾動更新時,大量的進行中的請求未正常處理,導致這部分流量異常?因為我們線上 流量比較大,幾乎每時每刻都有大量請求,所以我們要求線上服務必須支持無縫滾動更新。如果流量比較小,這個問題可能就不會暴露出來,這也解釋了之前和同事討論的點,為什么這么明顯的問題沒有被及早的發(fā)現。不過都目前為止,這一切都只是猜測,真相繼續(xù)往下。

定位bug,尋找真實原因

有了上面的猜測,直接找到了gRPC-spring-boot-starter管理維護GrpcServer生命周期的類GrpcServerLifecycle,這個類實現了spring的SmartLifecycle接口,這個接口是用來注冊SpringContextShutdownHook的鉤子用的,它的實現如下:

@Slf4j
public class GrpcServerLifecycle implements SmartLifecycle {
    private static AtomicInteger serverCounter = new AtomicInteger(-1);
    private volatile Server server;
    private volatile int phase = Integer.MAX_VALUE;
    private final GrpcServerFactory factory;
    public GrpcServerLifecycle(final GrpcServerFactory factory) {
        this.factory = factory;
    }
    @Override
    public void start() {
        try {
            createAndStartGrpcServer();
        } catch (final IOException e) {
            throw new IllegalStateException("Failed to start the grpc server", e);
        }
    }
    @Override
    public void stop() {
        stopAndReleaseGrpcServer();
    }
    @Override
    public void stop(final Runnable callback) {
        stop();
        callback.run();
    }
    @Override
    public boolean isRunning() {
        return this.server != null && !this.server.isShutdown();
    }
    @Override
    public int getPhase() {
        return this.phase;
    }
    @Override
    public boolean isAutoStartup() {
        return true;
    }
    /**
     * Creates and starts the grpc server.
     *
     * @throws IOException If the server is unable to bind the port.
     */
    protected void createAndStartGrpcServer() throws IOException {
        final Server localServer = this.server;
        if (localServer == null) {
            this.server = this.factory.createServer();
            this.server.start();
            log.info("gRPC Server started, listening on address: " + this.factory.getAddress() + ", port: "
                    + this.factory.getPort());
            // Prevent the JVM from shutting down while the server is running
            final Thread awaitThread = new Thread(() -> {
                try {
                    this.server.awaitTermination();
                } catch (final InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }, "grpc-server-container-" + (serverCounter.incrementAndGet()));
            awaitThread.setDaemon(false);
            awaitThread.start();
        }
    }
    /**
     * Initiates an orderly shutdown of the grpc server and releases the references to the server. This call does not
     * wait for the server to be completely shut down.
     */
    protected void stopAndReleaseGrpcServer() {
        final Server localServer = this.server;
        if (localServer != null) {
            localServer.shutdown();
            this.server = null;
            log.info("gRPC server shutdown.");
        }
    }
}

也就是說當spring容器關閉時,會觸發(fā)ShutdownHook,進而關閉GrpcServer服務,問題就出現在這里,從stopAndReleaseGrpcServer()方法可知,Grpc進行shudown()后,沒有進行任何操作,幾乎瞬時就返回了,這就導致了進程在收到kill命令時,Grpc的服務會被瞬間回收掉,而不會等待執(zhí)行中的處理完成,這個判斷可以從shutdown()的文檔描述中進一步得到確認,如:

/**
   * Initiates an orderly shutdown in which preexisting calls continue but new calls are rejected.
   * After this call returns, this server has released the listening socket(s) and may be reused by
   * another server.
   *
   * <p>Note that this method will not wait for preexisting calls to finish before returning.
   * {@link #awaitTermination()} or {@link #awaitTermination(long, TimeUnit)} needs to be called to
   * wait for existing calls to finish.
   *
   * @return {@code this} object
   * @since 1.0.0
   */
  public abstract Server shutdown();

文檔指出,調用shutdown()后,不在接收新的請求流量,進行中的請求會繼續(xù)處理完成,但是請注意,它不會等待現有的調用請求完成,必須使用awaitTermination()方法等待請求完成,也就是說,這里處理關閉的邏輯里,缺少了awaitTermination()等待處理中的請求完成的邏輯。

模擬環(huán)境,反復驗證

驗證方法:

這個場景的問題非常容易驗證,只需要在server端模擬業(yè)務阻塞耗時長一點,然后kill掉java進程,看程序是否會立刻被kill。正常優(yōu)雅下線關閉的話,會等待阻塞的時間后進程kill。否則就會出現不管業(yè)務阻塞多長時間,進程都會立馬kill。

驗證定位的bug

先驗證下是否如上面所說,不加awaitTermination()時,進程是否立馬就死了。直接使用gRPC-spring-boot-starter里自帶的demo程序,在server端的方法里加上如下模擬業(yè)務執(zhí)行耗時的代碼:

@GrpcService public class GrpcServerService extends SimpleGrpc.SimpleImplBase {
    @Override
  public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
        HelloReply reply = HelloReply._newBuilder_().setMessage("Hello ==> " \+ req.getName()).build();
 try {
            System._err_.println("收到請求,阻塞等待");
  TimeUnit._MINUTES_.sleep(1);
  System._err_.println("阻塞完成,請求結束");
  } catch (InterruptedException e) {
            e.printStackTrace();
  }
        responseObserver.onNext(reply);
  responseObserver.onCompleted();
  }
}

上面代碼模擬的執(zhí)行一分鐘的方法,然后觸發(fā)grpc client調用。接著找到server端的進程號,直接kill掉。發(fā)現進程確實立馬就kill了。繼續(xù)加大阻塞的時間,從一分鐘加大到六分鐘,重復測試,還是立馬就kill掉了,沒有任何的等待。

驗證修復后的效果

先將上面的代碼修復下,正確的關閉邏輯應該如下,在Grpc發(fā)出shutdown指令后,阻塞等待所有請求正常結束,同時,這里阻塞也會夯住主進程不會里面掛掉。

protected void stopAndReleaseGrpcServer() {
        final Server localServer = this.server;
        if (localServer != null) {
            localServer.shutdown();
            try {
                this.server.awaitTermination();
            } catch (final InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            this.server = null;
            log.info("gRPC server shutdown.");
        }
    }

同樣,如上述步驟驗證,當kill掉java進程后,此時java進程并沒有立馬就被kill,而是被awaitTermination()阻塞住了線程,直到業(yè)務方法中模擬的業(yè)務阻塞結束后,java進程才被kill掉,這正是我們想要達到的優(yōu)雅下線關閉的效果。被kill時的,線程堆棧如下:

即使被kill了,還是能打印如下的日志【阻塞完成,請求結束】,進一步驗證了修復后確實解決了問題:

以上就是提交gRPC-spring-boot-starter項目bug修復的pr說明的詳細內容,更多關于gRPC-spring-boot-starter項目提交bug修復pr說明的資料請關注腳本之家其它相關文章!

相關文章

  • Java實現電影院訂票系統代碼

    Java實現電影院訂票系統代碼

    這篇文章主要介紹了Java實現電影院訂票系統代碼,代碼實現了界面類登錄注冊類,用戶類等,具有一定參考價值,需要的朋友可以參考下。
    2017-11-11
  • 關于HttpServletRequest獲取POST請求Body參數的3種方式

    關于HttpServletRequest獲取POST請求Body參數的3種方式

    這篇文章主要介紹了關于HttpServletRequest獲取POST請求Body參數的3種方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • 計算一個Java對象占用字節(jié)數的方法

    計算一個Java對象占用字節(jié)數的方法

    這篇文章主要介紹了計算一個Java對象占用字節(jié)數的方法,較為詳細的分析了Java中各類對象所占用的字節(jié)數,需要的朋友可以參考下
    2015-01-01
  • Maven Plugin的@Mojo和@Execute的具體使用

    Maven Plugin的@Mojo和@Execute的具體使用

    本文主要介紹了Maven Plugin的@Mojo和@Execute的具體使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • SpringBoot攔截器讀取流后不能再讀取的問題

    SpringBoot攔截器讀取流后不能再讀取的問題

    這篇文章主要介紹了SpringBoot攔截器讀取流后不能再讀取的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • SpringBoot之自定義Banner詳解

    SpringBoot之自定義Banner詳解

    這篇文章主要介紹了SpringBoot之自定義Banner詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2021-09-09
  • 圖文詳解Java中的字節(jié)輸入與輸出流

    圖文詳解Java中的字節(jié)輸入與輸出流

    在Java中所有數據都是使用流讀寫的,流是一組有序的數據序列,將數據從一個地方帶到另一個地方,這篇文章主要給大家介紹了關于Java中字節(jié)輸入與輸出流的相關資料,需要的朋友可以參考下
    2021-08-08
  • Spring使用xml方式整合第三方框架流程詳解

    Spring使用xml方式整合第三方框架流程詳解

    這篇文章主要介紹了Spring使用xml方式整合第三方框架流程,Spring會在應用上下文中為某個bean尋找其依賴的bean,Spring中bean有三種裝配機制,分別是:在xml中顯式配置、在java中顯式配置、隱式的bean發(fā)現機制和自動裝配
    2023-02-02
  • java面試常見模式問題---單例模式

    java面試常見模式問題---單例模式

    單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式
    2021-06-06
  • springboot下添加全局異常處理和自定義異常處理的過程解析

    springboot下添加全局異常處理和自定義異常處理的過程解析

    在spring項目中,優(yōu)雅處理異常,好處是可以將系統產生的全部異常統一捕獲處理,自定義的異常也由全局異常來捕獲,如果涉及到validator參數校驗器使用全局異常捕獲也是較為方便,這篇文章主要介紹了springboot下添加全局異常處理和自定義異常處理,需要的朋友可以參考下
    2023-12-12

最新評論

离岛区| 搜索| 利川市| 琼中| 渝北区| 化州市| 奉新县| 沈丘县| 鹰潭市| 铁力市| 德阳市| 丰镇市| 梁平县| 台中县| 广宁县| 延长县| 榆社县| 周宁县| 惠东县| 永善县| 扎鲁特旗| 乌兰县| 友谊县| 郑州市| 三台县| 太白县| 鸡东县| 界首市| 遂平县| 德阳市| 兴安县| 陆川县| 确山县| 翁源县| 保康县| 崇左市| 监利县| 宿州市| 宁城县| 潞城市| 紫云|