Docker 為什么這么牛?
點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)
1. Docker出現(xiàn)的背景
在平常的研發(fā)和項(xiàng)目場(chǎng)景中,以下情況普遍存在:
個(gè)人開發(fā)環(huán)境 為了做大數(shù)據(jù)相關(guān)項(xiàng)目,需要安裝一套CDH集群,常見的做法是在自己電腦里搭建3臺(tái)與CDH版本對(duì)應(yīng)的虛擬機(jī),把CDH集群裝起來后,考慮到以后很有可能還要使用一個(gè)干凈的CDH集群,為了避免以后重復(fù)安裝環(huán)境,通常會(huì)對(duì)整套CDH集群做一個(gè)備份,這樣電腦里就有6個(gè)虛擬機(jī)鏡像了。另外,后面在學(xué)習(xí)其他技術(shù)時(shí),比如學(xué)習(xí)Ambari大數(shù)據(jù)集群,那么為了不破壞已有的虛擬機(jī)環(huán)境,又要重新搭建3臺(tái)虛擬機(jī),本機(jī)磁盤很快被一大堆的虛擬機(jī)鏡像占滿。 公司內(nèi)部開發(fā)環(huán)境 公司里往往會(huì)以小團(tuán)隊(duì)的方式來做項(xiàng)目,一般由運(yùn)維部門從他們管理的服務(wù)器資源中分配出虛擬機(jī)供團(tuán)隊(duì)內(nèi)部開發(fā)測(cè)試使用。 比如做一個(gè)與機(jī)器學(xué)習(xí)相關(guān)的項(xiàng)目: 1)小明在運(yùn)維部門分配的虛擬機(jī)上搭建了一套Ambari集群,拿來跑大數(shù)據(jù)相關(guān)業(yè)務(wù) 開發(fā)/測(cè)試/現(xiàn)場(chǎng)環(huán)境 研發(fā)人員在開發(fā)環(huán)境里寫好了代碼做好測(cè)試后,提交給測(cè)試部門,測(cè)試人員在測(cè)試環(huán)境跑起來發(fā)現(xiàn)有BUG,研發(fā)人員說在開發(fā)環(huán)境沒這個(gè)BUG,和測(cè)試人員多次扯皮解決BUG后發(fā)布版本,發(fā)到現(xiàn)場(chǎng)在生產(chǎn)環(huán)境部署后,又發(fā)現(xiàn)有BUG,這下輪到工程人員和測(cè)試人員扯皮。有時(shí)候?yàn)榱思嫒萏厥獾默F(xiàn)場(chǎng)環(huán)境,還需要對(duì)代碼進(jìn)行定制化修改,拉出分支,這樣導(dǎo)致了每次到現(xiàn)場(chǎng)升級(jí)都是一場(chǎng)噩夢(mèng) 升級(jí)或遷移項(xiàng)目 在每次發(fā)版本要升級(jí)到現(xiàn)場(chǎng)時(shí),如果現(xiàn)場(chǎng)起了多個(gè)tomcat應(yīng)用,那么需要對(duì)每個(gè)tomcat都先停掉,替換war包,然后再起起來,輪流著做,不僅繁瑣而且很容易出錯(cuò),如果遇到升級(jí)后出現(xiàn)嚴(yán)重BUG,還要手工做回退。另外,如果項(xiàng)目想上云,那么在云上部署后要重新進(jìn)行一輪測(cè)試,如果后面考慮還云廠商,可能相同的測(cè)試還要再進(jìn)行一次(比如更換了數(shù)據(jù)存儲(chǔ)組件),費(fèi)時(shí)費(fèi)力。
總結(jié)以上列舉的所有場(chǎng)景,他們存在的一個(gè)共同的問題是:沒有一種既能夠屏蔽操作系統(tǒng)差異,又能夠以不降低性能的方式來運(yùn)行應(yīng)用的技術(shù),來解決環(huán)境依賴的問題。Docker應(yīng)運(yùn)而生。
2. Docker是什么

Namespace和CGroup技術(shù)實(shí)現(xiàn)環(huán)境隔離和資源控制,其中Namespace是Linux提供的一種內(nèi)核級(jí)別環(huán)境隔離的方法,能使一個(gè)進(jìn)程和該進(jìn)程創(chuàng)建的子進(jìn)程的運(yùn)行空間都與Linux的超級(jí)父進(jìn)程相隔離,注意Namespace只能實(shí)現(xiàn)運(yùn)行空間的隔離,物理資源還是所有進(jìn)程共用的,為了實(shí)現(xiàn)資源隔離,Linux系統(tǒng)提供了CGroup技術(shù)來控制一個(gè)進(jìn)程組群可使用的資源(如CPU、內(nèi)存、磁盤IO等),把這兩種技術(shù)結(jié)合起來,就能構(gòu)造一個(gè)用戶空間獨(dú)立且限定了資源的對(duì)象,這樣的對(duì)象稱為容器。Linux Container是Linux系統(tǒng)提供的容器化技術(shù),簡(jiǎn)稱LXC,它結(jié)合Namespace和CGroup技術(shù)為用戶提供了更易用的接口來實(shí)現(xiàn)容器化。LXC僅為一種輕量級(jí)的容器化技術(shù),它僅能對(duì)部分資源進(jìn)行限制,無法做到諸如網(wǎng)絡(luò)限制、磁盤空間占用限制等。dotCloud公司結(jié)合LXC和以下列出的技術(shù)實(shí)現(xiàn)了Docker容器引擎,相比于LXC,Docker具備更加全面的資源控制能力,是一種應(yīng)用級(jí)別的容器引擎。
Chroot:該技術(shù)能在container里構(gòu)造完整的Linux文件系統(tǒng); Veth:該技術(shù)能夠在主機(jī)上虛擬出一張網(wǎng)卡與container里的eth0網(wǎng)卡進(jìn)行橋接,實(shí)現(xiàn)容器與主機(jī)、容器之間的網(wǎng)絡(luò)通信; UnionFS:聯(lián)合文件系統(tǒng),Docker利用該技術(shù)“Copy on Write”的特點(diǎn)實(shí)現(xiàn)容器的快速啟動(dòng)和極少的資源占用,后面會(huì)專門介紹該文件系統(tǒng); Iptables/netfilter:通過這兩個(gè)技術(shù)實(shí)現(xiàn)控制container網(wǎng)絡(luò)訪問策略; TC:該技術(shù)主要用來做流量隔離,限制帶寬; Quota:該技術(shù)用來限制磁盤讀寫空間的大??; Setrlimit:該技術(shù)用來限制container中打開的進(jìn)程數(shù),限制打開的文件個(gè)數(shù)等
也正是因?yàn)镈ocker依賴Linux內(nèi)核的這些技術(shù),至少使用3.8或更高版本的內(nèi)核才能運(yùn)行Docker容器,官方建議使用3.10以上的內(nèi)核版本。
3. 與傳統(tǒng)虛擬化技術(shù)的區(qū)別

直接運(yùn)行在物理硬件之上。如基于內(nèi)核的KVM虛擬機(jī),這種虛擬化需要CPU支持虛擬化技術(shù); 運(yùn)行在另一個(gè)操作系統(tǒng)。如VMWare和VitrualBox等虛擬機(jī)。
4. Docker基本概念

Docker主要有如下幾個(gè)概念:
引擎:創(chuàng)建和管理容器的工具,通過讀取鏡像來生成容器,并負(fù)責(zé)從倉(cāng)庫(kù)拉取鏡像或提交鏡像到倉(cāng)庫(kù)中; 鏡像:類似于虛擬機(jī)鏡像,一般由一個(gè)基本操作系統(tǒng)環(huán)境和多個(gè)應(yīng)用程序打包而成,是創(chuàng)建容器的模板; 容器:可看作一個(gè)簡(jiǎn)易版的Linxu系統(tǒng)環(huán)境(包括root用戶權(quán)限、進(jìn)程空間、用戶空間和網(wǎng)絡(luò)空間等)以及運(yùn)行在其中的應(yīng)用程序打包而成的盒子; 倉(cāng)庫(kù):集中存放鏡像文件的場(chǎng)所,分為公共倉(cāng)庫(kù)和私有倉(cāng)庫(kù),目前最大的公共倉(cāng)庫(kù)是官方提供的Docker Hub,此外國(guó)內(nèi)的阿里云、騰訊云等也提供了公共倉(cāng)庫(kù); 宿主機(jī):運(yùn)行引擎的操作系統(tǒng)所在服務(wù)器。
5. Docker與虛擬機(jī)、Git、JVM的類比
為了讓大家對(duì)Docker有更直觀的認(rèn)識(shí),下面分別進(jìn)行三組類比:


Docker的倉(cāng)庫(kù)思想與Git是相同的。

當(dāng)然,正如Java中如果應(yīng)用代碼使用了JDK10的新特性,基于JDK8就無法運(yùn)行一樣,如果容器內(nèi)的應(yīng)用使用了4.18版本的內(nèi)核特性,那么在CentOS7(內(nèi)核版本為3.10)啟動(dòng)容器時(shí),雖然容器能夠啟動(dòng),但里面應(yīng)用的功能是無法正常運(yùn)行的,除非把宿主機(jī)的操作系統(tǒng)內(nèi)核升級(jí)到4.18版本。
6. Docker鏡像文件系統(tǒng)

UnionFS可以把多個(gè)物理位置獨(dú)立的目錄(也叫分支)內(nèi)容聯(lián)合掛載到同一個(gè)目錄下,UnionFS允許控制這些目錄的讀寫權(quán)限,此外對(duì)于只讀的文件和目錄,它具有“Copy on Write(寫實(shí)復(fù)制)”的特點(diǎn),即如果對(duì)一個(gè)只讀的文件進(jìn)行修改,在修改前會(huì)先把文件復(fù)制一份到可寫層(可能是磁盤里的一個(gè)目錄),所有的修改操作其實(shí)都是對(duì)這個(gè)文件副本進(jìn)行修改,原來的只讀文件并不會(huì)變化。其中一個(gè)使用UnionFS的例子是:Knoppix,一個(gè)用于Linux演示、光盤教學(xué)和商業(yè)產(chǎn)品演示的Linux發(fā)行版,它就是把一個(gè)CD/DVD和一個(gè)存在在可讀寫設(shè)備(例如U盤)聯(lián)合掛載,這樣在演示過程中任何對(duì)CD/DVD上文件的改動(dòng)都會(huì)在被應(yīng)用在U盤上,不改變?cè)瓉淼腃D/DVD上的內(nèi)容。
UnionFS有很多種,其中Docker中常用的是AUFS,這是UnionFS的升級(jí)版,除此之外還有DeviceMapper、Overlay2、ZFS和 VFS等。Docker鏡像的每一層默認(rèn)存放在/var/lib/docker/aufs/diff目錄中,當(dāng)用戶啟動(dòng)一個(gè)容器時(shí),Docker引擎首先在/var/lib/docker/aufs/diff中新建一個(gè)可讀寫層目錄,然后使用UnionFS把該可讀寫層目錄和指定鏡像的各層目錄聯(lián)合掛載到/var/lib/docker/aufs/mnt里的一個(gè)目錄中(其中指定鏡像的各層目錄都以只讀方式掛載),通過LXC等技術(shù)進(jìn)行環(huán)境隔離和資源控制,使容器里的應(yīng)用僅依賴mnt目錄中對(duì)應(yīng)的掛載目錄和文件運(yùn)行起來。
利用UnionFS寫實(shí)復(fù)制的特點(diǎn),在啟動(dòng)一個(gè)容器時(shí), Docker引擎實(shí)際上只是增加了一個(gè)可寫層和構(gòu)造了一個(gè)Linux容器,這兩者都幾乎不消耗系統(tǒng)資源,因此Docker容器能夠做到秒級(jí)啟動(dòng),一臺(tái)服務(wù)器上能夠啟動(dòng)上千個(gè)Docker容器,而傳統(tǒng)虛擬機(jī)在一臺(tái)服務(wù)器上啟動(dòng)幾十個(gè)就已經(jīng)非常吃力了,而且虛擬機(jī)啟動(dòng)很慢,這是Docker相比于傳統(tǒng)虛擬機(jī)的兩個(gè)巨大的優(yōu)勢(shì)。
當(dāng)應(yīng)用只是直接調(diào)用了內(nèi)核功能來運(yùn)作的情況下,應(yīng)用本身就能直接作為最底層的層來構(gòu)建鏡像,但因?yàn)槿萜鞅旧頃?huì)隔絕環(huán)境,因此容器內(nèi)部是無法訪問宿主機(jī)里文件的(除非指定了某些目錄或文件映射到容器內(nèi)),這種情況下應(yīng)用代碼就只能使用內(nèi)核的功能。但是Linux內(nèi)核僅提供了進(jìn)程管理、內(nèi)存管理、文件系統(tǒng)管理等一些基礎(chǔ)且底層的管理功能,在實(shí)際的場(chǎng)景中,幾乎所有軟件都是基于操作系統(tǒng)來開發(fā)的,因此往往都需要依賴操作系統(tǒng)的軟件和運(yùn)行庫(kù)等,如果這些應(yīng)用的下一層直接是內(nèi)核,那么應(yīng)用將無法運(yùn)行。所以實(shí)際上應(yīng)用鏡像往往底層都是基于一個(gè)操作系統(tǒng)鏡像來補(bǔ)足運(yùn)行依賴的。
Docker中的操作系統(tǒng)鏡像,與平常安裝系統(tǒng)時(shí)用的ISO鏡像不同。ISO鏡像里包含了操作系統(tǒng)內(nèi)核及該發(fā)行版系統(tǒng)包含的所有目錄和軟件,而Docker中的操作系統(tǒng)鏡像,不包含系統(tǒng)內(nèi)核,僅包含系統(tǒng)必備的一些目錄(如/etc /proc等)和常用的軟件和運(yùn)行庫(kù)等,可把操作系統(tǒng)鏡像看作內(nèi)核之上的一個(gè)應(yīng)用,一個(gè)封裝了內(nèi)核功能,并為用戶編寫的應(yīng)用提供運(yùn)行環(huán)境的工具。應(yīng)用基于這樣的鏡像構(gòu)建,就能夠利用上相應(yīng)操作系統(tǒng)的各種軟件的功能和運(yùn)行庫(kù),此外,由于應(yīng)用是基于操作系統(tǒng)鏡像來構(gòu)建的,就算換到另外的服務(wù)器,只要操作系統(tǒng)鏡像中被應(yīng)用使用到的功能能適配宿主機(jī)的內(nèi)核,應(yīng)用就能正常運(yùn)行,這就是一次構(gòu)建到處運(yùn)行的原因。
下圖形象的表現(xiàn)出了鏡像和容器的關(guān)系:


7. Docker基礎(chǔ)操作系統(tǒng)

以上系統(tǒng)鏡像分別適用于不同的場(chǎng)景:
BusyBox:一個(gè)極簡(jiǎn)版的Linux系統(tǒng),集成了100多種常用Linux命令,大小不到2MB,被稱為“Linux系統(tǒng)的瑞士軍刀”,適用于簡(jiǎn)單測(cè)試場(chǎng)景; Alpine:一個(gè)面向安全的輕型Linux發(fā)行版系統(tǒng),比BusyBox功能更完善,大小不到5MB,是官網(wǎng)推薦的基礎(chǔ)鏡像,由于其包含了足夠的基礎(chǔ)功能和體積較小,在生產(chǎn)環(huán)境中最常用; Debian/Ubuntu:Debian系列操作系統(tǒng),功能完善,大小約170MB,適合研發(fā)環(huán)境; CentOS/Fedora:都是基于Redhat的Linux發(fā)行版,企業(yè)級(jí)服務(wù)器常用操作系統(tǒng),穩(wěn)定性高,大小約200MB,適合生產(chǎn)環(huán)境使用。
8. Docker持久化存儲(chǔ)
根據(jù)前面介紹的容器UnionFS寫實(shí)復(fù)制的特點(diǎn),可知在容器里增加、刪除或修改文件,其實(shí)都是對(duì)可寫層里的文件副本進(jìn)行了操作。在容器關(guān)閉后,該可寫層也會(huì)被刪除,對(duì)容器的所有修改都會(huì)失效,因此需要解決容器內(nèi)文件持久化的問題。Docker提供了兩種方案來實(shí)現(xiàn):
把宿主機(jī)文件系統(tǒng)里的目錄映射到容器內(nèi)的目錄, 如下圖所示。如此一來,容器內(nèi)在該目錄里創(chuàng)建的所有文件,都存儲(chǔ)到宿主機(jī)的對(duì)應(yīng)目錄中,在關(guān)閉容器后,宿主機(jī)的目錄依然存在,再次啟動(dòng)容器時(shí)還能讀取到之前創(chuàng)建的文件,因此實(shí)現(xiàn)了容器的文件持久化。當(dāng)然同時(shí)要明白,如果是對(duì)鏡像自帶文件進(jìn)行了修改,由于鏡像是只讀的,該修改操作無法在關(guān)閉容器時(shí)保存下來,除非在修改了文件后構(gòu)建一個(gè)新的鏡像。

把多臺(tái)宿主機(jī)的磁盤目錄通過網(wǎng)絡(luò)聯(lián)合為共享存儲(chǔ),然后把共享存儲(chǔ)中的特定目錄映射給特定的容器, 如下圖所示。這樣容器在重啟時(shí),還是能讀取到關(guān)閉前創(chuàng)建的文件。生產(chǎn)環(huán)境中常用NFS作為共享存儲(chǔ)方案。

9. Docker鏡像制作方法
鏡像制作方法有兩種:
通過正在運(yùn)行的容器生成新鏡像

這種方式比較簡(jiǎn)單,但無法直觀的設(shè)置環(huán)境變量、監(jiān)聽端口等內(nèi)容,適合在簡(jiǎn)單使用的場(chǎng)景運(yùn)用。
通過Dockerfile文件來生成新鏡像

FROM ubuntu/14.04 # 基礎(chǔ)鏡像
MAINTAINER guest # 制作者簽名
RUN apt-get install openssh-server -y # 安裝ssh服務(wù)
RUN mkdir /var/run/sshd # 創(chuàng)建目錄
RUN useradd -s /bin/bash -m -d /home/guest guest # 創(chuàng)建用戶
RUN echo ‘guest:123456’| chpasswd # 修改用戶密碼
ENV RUNNABLE_USER_DIR /home/guest # 設(shè)置環(huán)境變量
EXPOSE 22 # 容器內(nèi)默認(rèn)開啟的端口
CMD ["/usr/sbin/sshd -D"] # 啟動(dòng)容器時(shí)自動(dòng)啟動(dòng)ssh服務(wù)
Docker引擎可以根據(jù)以上Dockerfile定義的步驟,構(gòu)造出一個(gè)帶有ssh服務(wù)的Ubuntu鏡像。
10. Docker的使用場(chǎng)景
Docker作為一種輕量級(jí)的虛擬化方案,應(yīng)用場(chǎng)景十分豐富,下面收集了一些常見的場(chǎng)景:
作為輕量級(jí)虛擬機(jī)使用 可以使用Ubuntu等系統(tǒng)鏡像創(chuàng)建容器,當(dāng)作虛擬機(jī)來使用,相比于傳統(tǒng)虛擬機(jī),啟動(dòng)速度更快,資源占用更少,單機(jī)可以啟動(dòng)大量的操作系統(tǒng)容器,方便進(jìn)行各種測(cè)試; 作為云主機(jī)使用 結(jié)合Kubernetes這樣的容器管理系統(tǒng),可以在大量服務(wù)器上動(dòng)態(tài)分配和管理容器,在公司內(nèi)部,甚至可以取代VMWare這樣的虛擬機(jī)管理平臺(tái),使用Docker容器作為云主機(jī)使用; 應(yīng)用服務(wù)打包 在Web應(yīng)用服務(wù)開發(fā)場(chǎng)景,可以把Java運(yùn)行環(huán)境、Tomcat服務(wù)器打包為一個(gè)基礎(chǔ)鏡像,在修改了代碼包后加入到基礎(chǔ)鏡像來構(gòu)建一個(gè)新的鏡像,能很方便的升級(jí)服務(wù)和控制版本; 容器云平臺(tái)CaaS Docker的出現(xiàn),使得很多云平臺(tái)供應(yīng)商開始提供容器云的服務(wù),簡(jiǎn)稱容器即服務(wù)CaaS,以下對(duì)比一下IaaS、PaaS和SaaS: IaaS(基礎(chǔ)設(shè)施即服務(wù)):提供虛擬機(jī)或者其他基礎(chǔ)資源作為服務(wù)提供給用戶。用戶可以從供應(yīng)商那里獲得虛擬機(jī)或者存儲(chǔ)等資源來裝載相關(guān)的應(yīng)用,同時(shí)這些基礎(chǔ)設(shè)施的繁瑣的管理工作將由IaaS供應(yīng)商來處理。其主要的用戶是企業(yè)的系統(tǒng)管理員和運(yùn)維人員; 持續(xù)集成和持續(xù)部署 互聯(lián)網(wǎng)行業(yè)提倡敏捷開發(fā),持續(xù)集成部署CI/CD便是最典型的開發(fā)模式。使用Docker容器云平臺(tái),就能實(shí)現(xiàn)從代碼編寫完成推送到Git/SVN后,自動(dòng)觸發(fā)后端CaaS平臺(tái)將代碼下載、編譯并構(gòu)建成測(cè)試Docker鏡像,再替換測(cè)試環(huán)境容器服務(wù),自動(dòng)在Jenkins或者Hudson中運(yùn)行單元/集成測(cè)試,測(cè)試通過后,馬上就能自動(dòng)將新版本鏡像更新到線上,完成服務(wù)升級(jí)。整個(gè)過程全自動(dòng)化,一氣呵成,最大程度地簡(jiǎn)化了運(yùn)維,而且保證線上、線下環(huán)境完全一致,而且線上服務(wù)版本與Git/SVN發(fā)布分支也實(shí)現(xiàn)統(tǒng)一。 解決微服務(wù)架構(gòu)的實(shí)施難題 基于Spring Cloud這樣的微服務(wù)框架,能夠?qū)崿F(xiàn)微服務(wù)的管理,但微服務(wù)本身還是需要運(yùn)行在操作系統(tǒng)上。一個(gè)采用微服務(wù)架構(gòu)開發(fā)的應(yīng)用中,微服務(wù)的個(gè)數(shù)往往很多,這就導(dǎo)致了一臺(tái)服務(wù)器上往往需要啟動(dòng)多個(gè)微服務(wù)來提高資源的利用率,而微服務(wù)本身可能就只能兼容部分操作系統(tǒng),這就導(dǎo)致了就算有大量的服務(wù)器資源(操作系統(tǒng)可能不一樣),但由于微服務(wù)本身與操作系統(tǒng)可能相關(guān),就不能做到讓微服務(wù)在任意服務(wù)器上運(yùn)行,這就帶來了資源的浪費(fèi)和運(yùn)維的困難。利用Docker容器的環(huán)境隔離能力,讓微服務(wù)運(yùn)行在容器內(nèi),就能夠解決以上所說的問題。 執(zhí)行臨時(shí)任務(wù) 有時(shí)候用戶只是想執(zhí)行一次性的任務(wù),但如果用傳統(tǒng)虛擬機(jī)的方式就要搭建環(huán)境,執(zhí)行完任務(wù)后還要釋放資源,比較麻煩。使用Docker容器就可以構(gòu)建臨時(shí)的運(yùn)行環(huán)境,執(zhí)行完任務(wù)后關(guān)閉容器即可,方便快捷。 多租戶環(huán)境 利用Docker的環(huán)境隔離能力,可以為不同的租戶提供獨(dú)占的容器,實(shí)現(xiàn)簡(jiǎn)單而且成本較低。
11. 總結(jié)
Docker的技術(shù)并不神秘,只是整合了前人積累的各種成果實(shí)現(xiàn)的應(yīng)用級(jí)的容器化技術(shù),它利用各種Linux發(fā)行版中使用了版本兼容的內(nèi)核容器化技術(shù),來實(shí)現(xiàn)鏡像一次構(gòu)建到處運(yùn)行的效果,并且利用了容器內(nèi)的基礎(chǔ)操作系統(tǒng)鏡像層,屏蔽了實(shí)際運(yùn)行環(huán)境的操作系統(tǒng)差異,使用戶在開發(fā)應(yīng)用程序時(shí),只需確保在選定的操作系統(tǒng)和內(nèi)核版本上能正確運(yùn)行即可,幾乎不需要關(guān)心實(shí)際的運(yùn)行環(huán)境的系統(tǒng)差異,大大提高效率和兼容性。
但隨著容器運(yùn)行得越來越多,容器管理將會(huì)稱為另一個(gè)運(yùn)維的難題,這時(shí)候就需要引入Kubernetes、Mesos或Swarm這些容器管理系統(tǒng),后面有機(jī)會(huì)再介紹這些技術(shù)。
最后,關(guān)注公眾號(hào)Java技術(shù)棧,在后臺(tái)回復(fù):面試,可以獲取我整理的 Java 系列面試題和答案,非常齊全。






關(guān)注Java技術(shù)??锤喔韶?/strong>


