乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      你的接口,真的能承受高并發(fā)嗎?

       oldzhoua 2019-04-10


      作者:肥朝

      出處:公眾號【肥朝】


      前言

      本篇主要講解的是前陣子的一個壓測問題.那么就直接開門見山

      可能有的朋友不并不知道 forceTransactionTemplate這個是干嘛的,首先這里先普及一下,在Java中,我們一般開啟事務(wù)就有三種方式

      • XML中根據(jù)service及方法名配置切面,來開啟事務(wù)(前幾年用的頻率較高,現(xiàn)在基本很少用)

      • @Transactional注解開啟事務(wù)(使用頻率最高)

      • 采用spring的事務(wù)模板(截圖中的方式,幾乎沒什么人用)

      我們先不糾結(jié)為什么使用第三種,后面在講 事務(wù)傳播機(jī)制的時候我會專門介紹,我們聚焦一下主題,你現(xiàn)在只要知道,那個是開啟事務(wù)的意思就行了.我特意用紅色和藍(lán)色把日志代碼圈起來,意思就是,進(jìn)入方法的時候打印日志,然后開啟事務(wù)后,再打印一個日志.一波壓測之后,發(fā)現(xiàn)接口頻繁超時,數(shù)據(jù)一致壓不上去.我們查看日志如下:

      我們發(fā)現(xiàn).這兩個日志輸出時間間隔,竟然用了接近5秒!開個事務(wù)為何用了5秒? 事出反常必有妖!

      如何切入解決問題

      線上遇到高并發(fā)的問題,由于一般高并發(fā)問題重現(xiàn)難度比較大,所以一般肥朝都是采用眼神編譯,九淺一深靜態(tài)看源碼的方式來分析.具體可以參考本地可跑,上線就崩?慌了!.但是考慮到肥朝公眾號仍然有小部分新關(guān)注的粉絲尚未掌握分析問題的技巧,本篇就再講一些遇到此類問題的一些常見分析方式,不至于遇到問題時, 慌得一比!

      好在這個并發(fā)問題的難度并不大,本篇案例排查非常適合小白入門,我們可以通過本地模擬場景重現(xiàn),將問題范圍縮小,從而逐步定位問題.

      本地重現(xiàn)

      首先我們可以準(zhǔn)備一個并發(fā)工具類,通過這個工具類,可以在本地環(huán)境模擬并發(fā)場景.手機(jī)查看代碼并不友好,但是沒關(guān)系,以下代碼均是給你復(fù)制粘貼進(jìn)項目重現(xiàn)問題用的, 并不是給你手機(jī)上看的.至于這個工具類為什么能模擬并發(fā)場景,由于這個工具類的代碼 全是JDK中的代碼,核心就是 CountDownLatch類,這個原理你根據(jù)我提供的關(guān)鍵字對著你喜歡的搜索引擎搜索即可.

      CountDownLatchUtil.java

      1. 1 public class CountDownLatchUtil {

      2. 2

      3. 3    private CountDownLatch start;

      4. 4    private CountDownLatch end;

      5. 5    private int pollSize = 10;

      6. 6

      7. 7    public CountDownLatchUtil() {

      8. 8        this(10);

      9. 9    }

      10. 10

      11. 11    public CountDownLatchUtil(int pollSize) {

      12. 12        this.pollSize = pollSize;

      13. 13        start = new CountDownLatch(1);

      14. 14        end = new CountDownLatch(pollSize);

      15. 15    }

      16. 16

      17. 17    public void latch(MyFunctionalInterface functionalInterface) throws InterruptedException {

      18. 18        ExecutorService executorService = Executors.newFixedThreadPool(pollSize);

      19. 19        for (int i = 0; i < pollSize; i++) {

      20. 20            Runnable run = new Runnable() {

      21. 21                @Override

      22. 22                public void run() {

      23. 23                    try {

      24. 24                        start.await();

      25. 25                        functionalInterface.run();

      26. 26                    } catch (InterruptedException e) {

      27. 27                        e.printStackTrace();

      28. 28                    } finally {

      29. 29                        end.countDown();

      30. 30                    }

      31. 31                }

      32. 32            };

      33. 33            executorService.submit(run);

      34. 34        }

      35. 35

      36. 36        start.countDown();

      37. 37        end.await();

      38. 38        executorService.shutdown();

      39. 39    }

      40. 40

      41. 41    @FunctionalInterface

      42. 42    public interface MyFunctionalInterface {

      43. 43        void run();

      44. 44    }

      45. 45}

      HelloService.java

      1. 1 public interface HelloService {

      2. 2

      3. 3    void sayHello(long timeMillis);

      4. 4

      5. 5}

      HelloServiceImpl.java

      1. 1 @Service

      2. 2 public class HelloServiceImpl implements HelloService {

      3. 3

      4. 4    private final Logger log = LoggerFactory.getLogger(HelloServiceImpl.class);

      5. 5

      6. 6    @Transactional

      7. 7    @Override

      8. 8    public void sayHello(long timeMillis) {

      9. 9        long time = System.currentTimeMillis() - timeMillis;

      10. 10        if (time > 5000) {

      11. 11            //超過5秒的打印日志輸出

      12. 12            log.warn('time : {}', time);

      13. 13        }

      14. 14        try {

      15. 15            //模擬業(yè)務(wù)執(zhí)行時間為1s

      16. 16            Thread.sleep(1000);

      17. 17        } catch (Exception e) {

      18. 18            e.printStackTrace();

      19. 19        }

      20. 20    }

      21. 21}

      HelloServiceTest.java

      1. 1 @RunWith(SpringRunner.class)

      2. 2 @SpringBootTest

      3. 3 public class HelloServiceTest {

      4. 4

      5. 5    @Autowired

      6. 6    private HelloService helloService;

      7. 7

      8. 8    @Test

      9. 9    public void testSayHello() throws Exception {

      10. 10        long currentTimeMillis = System.currentTimeMillis();

      11. 11        //模擬1000個線程并發(fā)

      12. 12        CountDownLatchUtil countDownLatchUtil = new CountDownLatchUtil(1000);

      13. 13        countDownLatchUtil.latch(() -> {

      14. 14            helloService.sayHello(currentTimeMillis);

      15. 15        });

      16. 16    }

      17. 17

      18. 18 }

      我們從本地調(diào)試的日志中,發(fā)現(xiàn)了大量超過5s的接口,并且還有一些規(guī)律,肥朝特地用不同顏色的框框給大家框起來

      為什么這些時間,都是5個為一組,且每組數(shù)據(jù)相差是1s左右呢?

      真相大白

      @Transactional的核心代碼如下(后續(xù)我會專門一個系列分析這部分源碼,關(guān)注肥朝以免錯過核心內(nèi)容).這里簡單說就是 retVal=invocation.proceedWithInvocation()方法會去獲取數(shù)據(jù)庫連接.

      1. 1 if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {

      2. 2    // Standard transaction demarcation with getTransaction and commit/rollback calls.

      3. 3    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

      4. 4    Object retVal = null;

      5. 5    try {

      6. 6        // This is an around advice: Invoke the next interceptor in the chain.

      7. 7        // This will normally result in a target object being invoked.

      8. 8        retVal = invocation.proceedWithInvocation();

      9. 9    }

      10. 10    catch (Throwable ex) {

      11. 11        // target invocation exception

      12. 12        completeTransactionAfterThrowing(txInfo, ex);

      13. 13        throw ex;

      14. 14    }

      15. 15    finally {

      16. 16        cleanupTransactionInfo(txInfo);

      17. 17    }

      18. 18    commitTransactionAfterReturning(txInfo);

      19. 19    return retVal;

      20. 20}

      然后肥朝為了更好的演示這個問題,將數(shù)據(jù)庫連接池(本篇用的是Druid)的參數(shù)做了以下設(shè)置

      1. 1 //初始連接數(shù)

      2. 2 spring.datasource.initialSize=1

      3. 3 //最大連接數(shù)

      4. 4 spring.datasource.maxActive=5

      由于最大連接數(shù)是5.所以當(dāng)1000個線程并發(fā)進(jìn)來的時候,你可以想象是一個隊伍有1000個人排隊,最前面的5個,拿到了連接,并且執(zhí)行業(yè)務(wù)時間為1秒.那么隊伍中剩下的995個人,就在門外等候.等這5個執(zhí)行完的時候.釋放了5個連接,依次向后的5個人又進(jìn)來,又執(zhí)行1秒的業(yè)務(wù)操作.通過簡單的小學(xué)數(shù)學(xué),都可以計算出最后5個執(zhí)行完,需要多長時間.通過這里分析,你就知道,為什么上面的日志輸出,是5秒為一組了,并且每組間隔為1s了.

      怎么解決

      看過肥朝源碼實戰(zhàn)的粉絲都知道,肥朝從來不耍流氓,凡是拋出問題,都會相應(yīng)給出 其中一種解決方案.當(dāng)然方案 沒有最優(yōu)只有更優(yōu)!

      比如看到這里有的朋友可能會說,你最大連接數(shù)設(shè)置得 就像平時贊賞肥朝的金額一樣小,如果設(shè)置大一點,自然就不會有問題了.當(dāng)然這里為了方便向大家演示問題,設(shè)置了最大連接數(shù)是5.正常生產(chǎn)的連接數(shù)是要根據(jù)業(yè)務(wù)特點和不斷壓測才能得出合理的值,當(dāng)然肥朝也了解到,部分同學(xué)公司機(jī)器的配置,竟然比不過市面上的 千元手機(jī)!!!

      但是其實當(dāng)時壓測的時候,數(shù)據(jù)庫的最大連接數(shù)設(shè)置的是200,并且當(dāng)時的壓測壓力并不大.那為什么還會有這個問題呢?那么仔細(xì)看前面的代碼

      其中這個 校驗的代碼是RPC調(diào)用,該接口的同事并沒有像肥朝一樣 值得托付終身般的高度可靠,導(dǎo)致耗時時間較長,從而導(dǎo)致后續(xù)線程獲取數(shù)據(jù)庫連接等待的時間過長.你再根據(jù)前面說的小學(xué)數(shù)學(xué)來算一下就很容易明白該壓測問題出現(xiàn)的原因.

      敲黑板劃重點

      之前肥朝就反復(fù)說過,遇到問題,要經(jīng)過深度思考.比如這個問題,我們能得到什么拓展性的思考呢?我們來看一下之前一位粉絲的面試經(jīng)歷

      其實他面試遇到的這個問題,和我們這個壓測問題基本是同一個問題,只不過面試官的結(jié)論其實并不夠準(zhǔn)確.我們來一起看一下阿里巴巴的開發(fā)手冊

      那么什么樣叫做濫用呢?其實肥朝認(rèn)為,即使這個方法經(jīng)常調(diào)用,但是都是單表insert、update操作,執(zhí)行時間非常短,那么承受較大并發(fā)問題也不大.關(guān)鍵是,這個事務(wù)中的所有方法調(diào)用,是否是有意義的,或者說,事務(wù)中的方法是否是真的要事務(wù)保證,才是關(guān)鍵.因為部分同學(xué),在一些比較傳統(tǒng)的公司,做的多是 能用就行的CRUD工作,很容易一個service方法,就直接打上事務(wù)注解開始事務(wù),然后在一個事務(wù)中,進(jìn)行大量和事務(wù)一毛錢關(guān)系都沒有的無關(guān)耗時操作,比如文件IO操作,比如查詢校驗操作等.例如本文中的 業(yè)務(wù)校驗就完全沒必要放在事務(wù)中.平時工作中沒有相應(yīng)的實戰(zhàn)場景,加上并沒有關(guān)注肥朝的公眾號,對原理源碼真實實戰(zhàn)場景一無所知.面試稍微一問原理就喊痛,面試官也只好換個方向再繼續(xù)深入!

      通過這個經(jīng)歷我們又有什么拓展性的思考呢?因為問題是永遠(yuǎn)解決不完的,但是我們可以通過不斷的思考,把這個問題壓榨出更多的價值!我們再來看一下阿里規(guī)范手冊

      用大白話概括就是,盡量減少鎖的粒度.并且盡量避免在鎖中調(diào)用RPC方法,因為RPC方法涉及網(wǎng)絡(luò)因素,他的調(diào)用時間存在很大的不可控,很容易就造成了占用鎖的時間過長.

      其實這個和我們這個壓測問題是一樣的.首先你本地事務(wù)中調(diào)用RPC既不能起到事務(wù)作用(RPC需要分布式事務(wù)保證),但是又會因為RPC不可控因素導(dǎo)致數(shù)據(jù)庫連接占用時間過長.從而引起接口超時.當(dāng)然我們也可以通過 APM工具來梳理接口的耗時拓?fù)?將此類問題在壓測前就暴露.

      -更多文章-

      微服務(wù)架構(gòu)之「 服務(wù)注冊 」

      你知道Java的四種引用類型嗎?

      企業(yè)微服務(wù)中臺落地實踐和思想之我見

      支付寶架構(gòu)師眼中的高并發(fā)架構(gòu)

      超詳細(xì)的Guava RateLimiter限流原理解析

      微服務(wù)架構(gòu)·基礎(chǔ)篇

      數(shù)據(jù)庫中間件詳解 | 珍藏版

      -關(guān)注我-

      感謝搓一下“在看

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多