閑話高并發的那些神話,看京東架構師如何把它拉下神壇

轉載:閑話高并發的那些神話,看京東架構師如何把它拉下神壇

高并發也算是這幾年的熱門詞匯了,尤其在互聯網圈,開口不聊個高并發問題,都不好意思出門。高并發有那么邪乎嗎?動不動就千萬并發、億級流量,聽上去的確挺嚇人。但仔細想想,這么大的并發與流量不都是通過路由器來的嗎?

?

>>>>

0x00 一切源自網卡

高并發的流量通過低調的路由器進入我們系統,第一道關卡就是網卡,網卡怎么抗住高并發?這個問題壓根就不存在,千萬并發在網卡看來,一樣一樣的,都是電信號,網卡眼里根本區分不出來你是千萬并發還是一股洪流,所以衡量網卡牛不牛都說帶寬,從來沒有并發量的說法。

網卡位于物理層和鏈路層,最終把數據傳遞給網絡層(IP層),在網絡層有了IP地址,已經可以識別出你是千萬并發了,所以搞網絡層的可以自豪的說,我解決了高并發問題,可以出來吹吹牛了。誰沒事搞網絡層呢?主角就是路由器,這玩意主要就是玩兒網絡層。

>>>>

0x01 一頭霧水

非專業的我們,一般都把網絡層(IP層)和傳輸層(TCP層)放到一起,操作系統提供,對我們是透明的,很低調、很靠譜,以至于我們都把他忽略了。

吹過的牛是從應用層開始的,應用層一切都源于Socket,那些千萬并發最終會經過傳輸層變成千萬個Socket,那些吹過的牛,不過就是如何快速處理這些Socket。處理IP層數據和處理Socket究竟有啥不同呢?

>>>>

0x02 沒有連接,就沒用等待

最重要的一個不同就是IP層不是面向連接的,而Socket是面向連接的,IP層沒有連接的概念,在IP層,來一個數據包就處理一個,不用瞻前也不用顧后;而處理Socket,必須瞻前顧后,Socket是面向連接的,有上下文的,讀到一句我愛你,激動半天,你不前前后后地看看,就是瞎激動了。

你想前前后后地看明白,就要占用更多的內存去記憶,就要占用更長的時間去等待;不同連接要搞好隔離,就要分配不同的線程(或者協程)。所有這些都解決好,貌似還是有點難度的。

>>>>

0x03 感謝操作系統

操作系統是個好東西,在Linux系統上,所有的IO都被抽象成了文件,網絡IO也不例外,被抽象成Socket,但是Socket還不僅是一個IO的抽象,它同時還抽象了如何處理Socket,最著名的就是select和epoll了,知名的nginx、netty、redis都是基于epoll搞的,這仨家伙基本上是在千萬并發領域必備神技。

但是多年前,Linux只提供了select的,這種模式能處理的并發量非常小,而epoll是專為高并發而生的,感謝操作系統。不過操作系統沒有解決高并發的所有問題,只是讓數據快速地從網卡流入我們的應用程序,如何處理才是老大難。

操作系統的使命之一就是最大限度的發揮硬件的能力,解決高并發問題,這也是最直接、最有效的方案,其次才是分布式計算。前面我們提到的nginx、netty、redis都是最大限度發揮硬件能力的典范。如何才能最大限度的發揮硬件能力呢?

>>>>0x04 核心矛盾

要最大限度的發揮硬件能力,首先要找到核心矛盾所在。我認為,這個核心矛盾從計算機誕生之初直到現在,幾乎沒有發生變化,就是CPU和IO之間的矛盾。

CPU以摩爾定律的速度野蠻發展,而IO設備(磁盤,網卡)卻乏善可陳。龜速的IO設備成為性能瓶頸,必然導致CPU的利用率很低,所以提升CPU利用率幾乎成了發揮硬件能力的代名詞。

>>>>

0x05 中斷與緩存

CPU與IO設備的協作基本都是以中斷的方式進行的,例如讀磁盤的操作,CPU僅僅是發一條讀磁盤到內存的指令給磁盤驅動,之后就立即返回了,此時CPU可以接著干其他事情,讀磁盤到內存本身是個很耗時的工作,等磁盤驅動執行完指令,會發個中斷請求給CPU,告訴CPU任務已經完成,CPU處理中斷請求,此時CPU可以直接操作讀到內存的數據。

中斷機制讓CPU以最小的代價處理IO問題,那如何提高設備的利用率呢?答案就是緩存。

操作系統內部維護了IO設備數據的緩存,包括讀緩存和寫緩存,讀緩存很容易理解,我們經常在應用層使用緩存,目的就是盡量避免產生讀IO。

寫緩存應用層使用的不多,操作系統的寫緩存,完全是為了提高IO寫的效率。操作系統在寫IO的時候會對緩存進行合并和調度,例如寫磁盤會用到電梯調度算法。

>>>>

0x06 高效利用網卡

高并發問題首先要解決的是如何高效利用網卡。網卡和磁盤一樣,內部也是有緩存的,網卡接收網絡數據,先存放到網卡緩存,然后寫入操作系統的內核空間(內存),我們的應用程序則讀取內存中的數據,然后處理。

除了網卡有緩存外,TCP/IP協議內部還有發送緩沖區和接收緩沖區以及SYN積壓隊列、accept積壓隊列。

這些緩存,如果配置不合適,則會出現各種問題。例如在TCP建立連接階段,如果并發量過大,而nginx里面socket的backlog設置的值太小,就會導致大量連接請求失敗。

如果網卡的緩存太小,當緩存滿了后,網卡會直接把新接收的數據丟掉,造成丟包。當然如果我們的應用讀取網絡IO數據的效率不高,會加速網卡緩存數據的堆積。如何高效讀取網絡數據呢?目前在Linux上廣泛應用的就是epoll了。

操作系統把IO設備抽象為文件,網絡被抽象成了Socket,Socket本身也是一個文件,所以可以用read/write方法來讀取和發送網絡數據。在高并發場景下,如何高效利用Socket快速讀取和發送網絡數據呢?

要想高效利用IO,就必須在操作系統層面了解IO模型,在《UNIX網絡編程》這本經典著作里,總結了五種IO模型,分別是阻塞式IO,非阻塞式IO,多路復用IO,信號驅動IO和異步IO。

>>>>

0x07 阻塞式IO

我們以讀操作為例,當我們調用read方法讀取Socket上的數據時,如果此時Socket讀緩存是空的(沒有數據從Socket的另一端發過來),操作系統會把調用read方法的線程掛起,直到Socket讀緩存里有數據時,操作系統再把該線程喚醒。

當然,在喚醒的同時,read方法也返回了數據。我理解所謂的阻塞,就是操作系統是否會掛起線程。

>>>>

0x08 非阻塞式IO

而對于非阻塞式IO,如果Socket的讀緩存是空的,操作系統并不會把調用read方法的線程掛起,而是立即返回一個EAGAIN的錯誤碼,在這種情景下,可以輪詢read方法,直到Socket的讀緩存有數據則可以讀到數據,這種方式的缺點非常明顯,就是消耗大量的CPU。

>>>>

0x09 多路復用IO

對于阻塞式IO,由于操作系統會掛起調用線程,所以如果想同時處理多個Socket,就必須相應地創建多個線程,線程會消耗內存,增加操作系統進行線程切換的負載,所以這種模式不適合高并發場景。有沒有辦法較少線程數呢?

非阻塞IO貌似可以解決,在一個線程里輪詢多個Socket,看上去可以解決線程數的問題,但實際上這個方案是無效的,原因是調用read方法是一個系統調用,系統調用是通過軟中斷實現的,會導致進行用戶態和內核態的切換,所以很慢。

但是這個思路是對的,有沒有辦法避免系統調用呢?有,就是多路復用IO。

在Linux系統上select/epoll這倆系統API支持多路復用IO,通過這兩個API,一個系統調用可以監控多個Socket,只要有一個Socket的讀緩存有數據了,方法就立即返回,然后你就可以去讀這個可讀的Socket了,如果所有的Socket讀緩存都是空的,則會阻塞,也就是將調用select/epoll的線程掛起。

所以select/epoll本質上也是阻塞式IO,只不過他們可以同時監控多個Socket。

>>>>

0x0A select和epoll的區別

為什么多路復用IO模型有兩個系統API?我分析原因是,select是POSIX標準中定義的,但是性能不夠好,所以各個操作系統都推出了性能更好的API,如Linux上的epoll、Windows上的IOCP。

至于select為什么會慢,大家比較認可的原因有兩點,一點是select方法返回后,需要遍歷所有監控的Socket,而不是發生變化的Ssocket,還有一點是每次調用select方法,都需要在用戶態和內核態拷貝文件描述符的位圖(通過調用三次copy_from_user方法拷貝讀、寫、異常三個位圖)。epoll可以避免上面提到的這兩點。

>>>>

0x0B Reactor多線程模型

在Linux操作系統上,性能最為可靠、穩定的IO模式就是多路復用,我們的應用如何能夠利用好多路復用IO呢?經過前人多年實踐總結,搞了一個Reactor模式,目前應用非常廣泛,著名的Netty、Tomcat NIO就是基于這個模式。

Reactor的核心是事件分發器和事件處理器,事件分發器是連接多路復用IO和網絡數據處理的中樞,核心就是監聽Socket事件(select/epoll_wait),然后將事件分發給事件處理器,事件分發器和事件處理器都可以基于線程池來做。

需要重點提一下的是,在Socket事件中主要有兩大類事件,一個是連接請求,另一個是讀寫請求,連接請求成功處理之后會創建新的Socket,讀寫請求都是基于這個新創建的Socket。

所以在網絡處理場景中,實現Reactor模式會稍微有點繞,但是原理沒有變化。具體實現可以參考Doug Lea的《Scalable IO in Java》(http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf)

Reactor原理圖

>>>>

0x0C Nginx多進程模型

Nginx默認采用的是多進程模型,Nginx分為Master進程和Worker進程,真正負責監聽網絡請求并處理請求的只有Worker進程,所有的Worker進程都監聽默認的80端口,但是每個請求只會被一個Worker進程處理。

這里面的玄機是:每個進程在accept請求前必須爭搶一把鎖,得到鎖的進程才有權處理當前的網絡請求。每個Worker進程只有一個主線程,單線程的好處是無鎖處理,無鎖處理并發請求,這基本上是高并發場景里面的最高境界了。(參考http://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf)

數據經過網卡、操作系統、網絡協議中間件(Tomcat、Netty等)重重關卡,終于到了我們應用開發人員手里,我們如何處理這些高并發的請求呢?我們還是先從提升單機處理能力的角度來思考這個問題。

>>>>0x0D 突破木桶理論

據經過網卡、操作系統、中間件(Tomcat、Netty等)重重關卡,終于到了我們應用開發人員手里,我們如何處理這些高并發的請求呢?

我們還是先從提升單機處理能力的角度來思考這個問題,在實際應用的場景中,問題的焦點是如何提高CPU的利用率(誰叫它發展的最快呢),木桶理論講最短的那根板決定水位,那為啥不是提高短板IO的利用率,而是去提高CPU的利用率呢?

這個問題的答案是在實際應用中,提高了CPU的利用率往往會同時提高IO的利用率。當然在IO利用率已經接近極限的條件下,再提高CPU利用率是沒有意義的。我們先來看看如何提高CPU的利用率,后面再看如何提高IO的利用率。

>>>>0x0E 并行與并發

提升CPU利用率目前主要的方法是利用CPU的多核進行并行計算,并行和并發是有區別的,在單核CPU上,我們可以一邊聽MP3,一邊Coding,這個是并發,但不是并行,因為在單核CPU的視野,聽MP3和Coding是不可能同時進行的。

只有在多核時代,才會有并行計算。并行計算這東西太高級,工業化應用的模型主要有兩種,一種是共享內存模型,另外一種是消息傳遞模型。

>>>>

0x0F 多線程設計模式

對于共享內存模型,其原理基本都來自大師Dijkstra在半個世紀前(1965)的一篇論文《Cooperating sequential processes》,這篇論文提出了大名鼎鼎的概念信號量,Java里面用于線程同步的wait/notify也是信號量的一種實現。

大師的東西看不懂,學不會也不用覺得丟人,畢竟大師的嫡傳子弟也沒幾個。東洋有個叫結城浩的總結了一下多線程編程的經驗,寫了本書叫《JAVA多線程設計模式》,這個還是挺接地氣(能看懂)的。下面簡單介紹一下。

1. Single Threaded Execution

這個模式是把多線程變成單線程,多線程在同時訪問一個變量時,會發生各種莫名其妙的問題,這個設計模式直接把多線程搞成了單線程,于是安全了,當然性能也就下來了。最簡單的實現就是利用synchronized將存在安全隱患的代碼塊(方法)保護起來。在并發領域有個臨界區(criticalsections)的概念,我感覺和這個模式是一回事。

2. Immutable Pattern

如果共享變量永遠不變,那就多個線程訪問就沒有任何問題,永遠安全。這個模式雖然簡單,但是用的好,能解決很多問題。

3. Guarded Suspension Patten

這個模式其實就是等待-通知模型,當線程執行條件不滿足時,掛起當前線程(等待),當條件滿足時,喚醒所有等待的線程(通知),在Java語言里利用synchronized,wait/notifyAll可以很快實現一個等待通知模型。結城浩將這個模式總結為多線程版的If,我覺得非常貼切。

4. Balking

這個模式和上個模式類似,不同點是當線程執行條件不滿足時直接退出,而不是像上個模式那樣掛起。這個用法最大的應用場景是多線程版的單例模式,當對象已經創建了(不滿足創建對象的條件)就不用再創建對象(退出)。

5. Producer-Consumer

生產者-消費者模式,全世界人都知道。我接觸的最多的是一個線程處理IO(如查詢數據庫),一個(或者多個)線程處理IO數據,這樣IO和CPU就都能成分利用起來。如果生產者和消費者都是CPU密集型,再搞生產者-消費者就是自己給自己找麻煩了。

6. Read-Write Lock

讀寫鎖解決的讀多寫少場景下的性能問題,支持并行讀,但是寫操作只允許一個線程做。如果寫操作非常非常少,而讀的并發量非常非常大,這個時候可以考慮使用寫時復制(copy on write)技術,我個人覺得應該單獨把寫時復制單獨作為一個模式。

7. Thread-Per-Message

就是我們經常提到的一請求一線程。

8. Worker Thread

一請求一線程的升級版,利用線程池解決線程的頻繁創建、銷毀導致的性能問題。BIO年代Tomcat就是用的這種模式。

9. Future

當你調用某個耗時的同步方法很心煩,想同時干點別的事情,可以考慮用這個模式,這個模式的本質是個同步變異步的轉換器。同步之所以能變異步,本質上是啟動了另外一個線程,所以這個模式和一請求一線程還是多少有點關系的。

10. Two-Phase Termination

這個模式能解決優雅地終止線程的需求。

11. Thread-Specific Storage

線程本地存儲,避免加鎖、解鎖開銷的利器,C#里面有個支持并發的容器ConcurrentBag就是采用了這個模式,這個星球上最快的數據庫連接池HikariCP借鑒了ConcurrentBag的實現,搞了個Java版的,有興趣的同學可以參考。

12. Active Object(這個不講也罷)

這個模式相當于降龍十八掌的最后一掌,綜合了前面的設計模式,有點復雜,個人覺得借鑒的意義大于參考實現。

最近國人也出過幾本相關的書,但總體還是結城浩這本更能經得住推敲。基于共享內存模型解決并發問題,主要問題就是用好鎖,但是用好鎖,還是有難度的,所以后來又有人搞了消息傳遞模型,這個后面再聊。

基于共享內存模型解決并發問題,主要問題就是用好鎖,但是用好鎖,還是有難度的,所以后來又有人搞了消息傳遞模型。

>>>>

0x10 消息傳遞模型

共享內存模型難度還是挺大的,而且你沒有辦法從理論上證明寫的程序是正確的,我們總一不小心就會寫出來個死鎖的程序來,每當有了問題,總會有大師出來,于是消息傳遞(Message-Passing)模型橫空出世(發生在上個世紀70年代),消息傳遞模型有兩個重要的分支,一個是Actor模型,一個是CSP模型。

>>>>

0x11 Actor模型

Actor模型因為Erlang聲名鵲起,后來又出現了Akka。在Actor模型里面,沒有操作系統里所謂進程、線程的概念,一切都是Actor,我們可以把Actor想象成一個更全能、更好用的線程。

在Actor內部是線性處理(單線程)的,Actor之間以消息方式交互,也就是不允許Actor之間共享數據,沒有共享,就無需用鎖,這就避免了鎖帶來的各種副作用。

Actor的創建和new一個對象沒有啥區別,很快、很小,不像線程的創建又慢又耗資源;Actor的調度也不像線程會導致操作系統上下文切換(主要是各種寄存器的保存、恢復),所以調度的消耗也很小。

Actor還有一個有點爭議的優點,Actor模型更接近現實世界,現實世界也是分布式的、異步的、基于消息的、尤其Actor對于異常(失敗)的處理、自愈、監控等都更符合現實世界的邏輯。

但是這個優點改變了編程的思維習慣,我們目前大部分編程思維習慣其實是和現實世界有很多差異的(這個回頭再細說),一般來講,改變我們思維習慣的事情,阻力總是超乎我們的想象。

>>>>

0x12 CSP模型

Golang在語言層面支持CSP模型,CSP模型和Actor模型的一個感官上的區別是在CSP模型里面,生產者(消息發送方)和消費者(消息接收方)是完全松耦合的,生產者完全不知道消費者的存在,但是在Actor模型里面,生產者必須知道消費者,否則沒辦法發送消息。

CSP模型類似于我們在多線程里面提到的生產者-消費者模型,核心的區別我覺得在于CSP模型里面有類似綠色線程(green thread)的東西,綠色線程在Golang里面叫做協程,協程同樣是個非常輕量級的調度單元,可以快速創建而且資源占用很低。

Actor在某種程度上需要改變我們的思維方式,而CSP模型貌似沒有那么大動靜,更容易被現在的開發人員接受,都說Golang是工程化的語言,在Actor和CSP的選擇上,也可以看到這種體現。

>>>>

0x13 多樣世界

除了消息傳遞模型,還有事件驅動模型、函數式模型。事件驅動模型類似于觀察者模式,在Actor模型里面,消息的生產者必須知道消費者才能發送消息,而在事件驅動模型里面,事件的消費者必須知道消息的生產者才能注冊事件處理邏輯。

Akka里消費者可以跨網絡,事件驅動模型的具體實現如Vertx里,消費者也可以訂閱跨網絡的事件,從這個角度看,大家都在取長補短。



作者:meng_philip123
鏈接:https://www.jianshu.com/p/51523ecae414
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。

轉載于:https://www.cnblogs.com/DreamRecorder/p/9242911.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/279995.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/279995.shtml
英文地址,請注明出處:http://en.pswp.cn/news/279995.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c# Clone方法

clone是深拷貝,copy是淺拷貝,如果是值類型的話是沒什么區別的,如果是引用類型的話深拷貝拷貝的事整個對象的數據,而淺拷貝僅僅拷貝對象的引用。因為類的實例是引用類型,要想用原有的類中的實例的數據的話,既…

使用MyQ打開車庫門時如何接收警報

Chamberlain’s MyQ technology is great for opening and closing your garage door remotely with your smartphone, but you can also receive alerts whenever your garage door opens and closes (as well as receive alerts when it’s been open for an extended amount…

踏實工作,實現價值

工作,為實現自我價值 若想在漫長的職場生涯中穩步高升,首先要踏踏實實,專心致志、充滿激情的去完成工作中的每一項任務,無論工作是繁重的還是瑣碎的,都要嚴格要求自己全身心的去完成。而不是一味的抱怨,一味…

mac 防火墻禁止程序聯網_如何允許應用程序通過Mac的防火墻進行通信

mac 防火墻禁止程序聯網If you use a Mac, chances are you might not even realize that OS X comes with a firewall. This firewall helps ensure unauthorized app and services can’t contact your computer, and prevents intruders from sniffing out your Mac on a ne…

WPF-22 基于MVVM員工管理-02

我們接著上一節,這節我們實現crud操作,我們在EmployeeViewMode類中新增如下成員,并在構造函數中初始化該成員code snippetpublic EmployeeViewMode() {employeeService new EmployeeService();BindData();Employee new Employee();AddComma…

linux 3

-- Linux -- 開心的一天 vi   所有的 unix like 系統都會內置 vi 文本編輯器 vim  較多使用的,可以主動的以字體顏色辨別語法的正確性,方便程序設計 vi/vim 的使用 -- 命令模式(Command mode) 輸入模式(Insert mode&#x…

從零開始搭建一個簡單的ui自動化測試框架02(pytest+selenium+allure)

二、先搭一個架子 在我還是小白連py語法都不太熟悉的時候,經常在網上看關于自學ui自動化測試的博客,最熟悉的套路莫過于先給你介紹一下selenium的各個api,然后寫一套代碼去登陸微博或者百度什么的,但我今天不愿意這么寫&#xff0…

DML語言DDL

DML(data manipulation language): 它們是SELECT、UPDATE、INSERT、DELETE,就象它的名字一樣,這4條命令是用來對數據庫里的數據進行操作的語言 。 DDL(data definition language): D…

什么是Adobe Lightroom,我需要它嗎?

Adobe Photoshop Lightroom confuses a lot of new photographers. It has Photoshop in the name, but it isn’t Photoshop? What gives? Adobe Photoshop Lightroom使許多新攝影師感到困惑。 它的名稱是Photoshop,但不是Photoshop嗎? 是什么賦予了&…

jquery中的serializeArray方法的使用

轉載于:https://blog.51cto.com/11871779/2359556

新冠病毒中招|第一天

感染新冠病毒后具體如何治療,需要根據感染者病情輕重情況而定,嚴重的要聽醫生的。昨天是我個人感染奧密克戎毒株第一天,今天跟大家分享一下我的情況。我昨天感覺渾身無力,也有點發燒37.9,我就吃了感冒藥睡了一上午&…

機器學習(一)—— 線性回歸

機器學習(一)—— 線性回歸 目錄 0. 回歸(Regression)的由來 1. 回歸關系 2. 線性回歸的整體思路 (1)根據數據提出假設模型 (2)求解參數 1)梯度下降法 2)正規…

Java EE啟示錄

前言 最近的這段時間一直在學習Java EE,剛剛完成了從0到1的蛻變,所以順便整理一下我所了解到的Java EE,給剛入門學習的新人一些頭緒,而所謂“啟示錄”,就是這個意思。 一.Java EE是什么? Java EE&#xff0…

又到年末“團建”!某企業員工吐槽:這真是一場噩夢……

這是頭哥侃碼的第270篇原創2022年即將結束,很多公司又到了一年一度的年末團建。前天晚上,之前的同事找我聊天,說他們公司因為最近疫情的原因,準備把年末“團建”放到春節后進行。但是計劃的時間是2月份的某個周末,并且…

天梯 L2 這是二叉搜索樹嗎?

L2-004 這是二叉搜索樹嗎? (25 分)一棵二叉搜索樹可被遞歸地定義為具有下列性質的二叉樹:對于任一結點, 其左子樹中所有結點的鍵值小于該結點的鍵值;其右子樹中所有結點的鍵值大于等于該結點的鍵值&#xf…

三星筆記本進入BIOS后找不到U盤啟動項/快速啟動鍵F12沒有反應

分析:BIOS開啟了 Fast Bios Mode 解決方法: 開機按F2進入BIOS設置,選擇Advanced菜單下Fast Bios Mode,設置為 Disabled,按F10鍵保存退出,重啟時按F12鍵即可進入快速啟動界面選擇。或按F2進入BIOS設置&#…

IP別名與多網卡綁定(RHEL5/RHEL6)

RHEL6的網絡管理與RHEL5的有比較大的改變。雖然在RHEL5、6中均安裝有NetworkManager,在RHEL5中2、3、4、5級別中默認是不啟用的。但在RHEL6中,默認是啟用的,NetworkManager會一直監控網卡狀態,修改網卡參數立即生效不用重啟服務。…

基于.NetCore開發博客項目 StarBlog - (25) 圖片接口與文件上傳

1前言上傳文件的接口設計有兩種風格,一種是整個項目只設置一個接口用來上傳,然后其他需要用到文件的地方,都只存一個引用ID;另一種是每個需要文件的地方單獨管理各自的文件。這倆各有優劣吧,本項目中選擇的是后者的風格…

pta7-7旅游規劃(dijkstra算法)

題目鏈接:https://pintia.cn/problem-sets/1101307589335527424/problems/1101314114762387456 題意:給n給城市,m條公路,公路是雙向的,起點S,終點D,并給出每條公路連接的兩個city的編號以及路費…

context:annotation-config vs context:component-scan

<context:annotation-config> 用來注入已經在上下文注冊的bean&#xff0c;無論bean是定義在XML中還是被 package scanning。 <context:component-scan>僅scans packages 去注冊應用上線文中的Bean。 example&#xff1a; Lets start with a basic setup of three …