ZooKeeper可以以standalone
,偽分布式和分布式三種方式部署.standalone
模式下只有一臺(tái)機(jī)器作為服務(wù)器,喪失了ZooKeeper高可用的特點(diǎn).偽分布式是在一臺(tái)電腦上使用不同端口啟動(dòng)多個(gè)ZooKeeper服務(wù)器.分布式是使用多個(gè)機(jī)器,每臺(tái)機(jī)器上部署一個(gè)ZooKeeper服務(wù)器,即使有服務(wù)器宕機(jī),只要少于半數(shù),ZooKeeper集群依然可以正常對(duì)外提供服務(wù).
ZooKeeper以standalone
模式啟動(dòng)只需啟動(dòng)對(duì)客戶端提供服務(wù)的組件,無需啟動(dòng)集群內(nèi)部通信組件,較為簡單,因此先從standalone
模式開始介紹.
ServerCnxnFactory
,SessionTracker
,RequestProcessor
,FileTxnSnapLog
等眾多組件,這些組件都會(huì)在日后一一介紹.standalone
模式啟動(dòng)主要包括如下幾個(gè)步驟:
配置文件解析
創(chuàng)建并啟動(dòng)歷史文件清理器
初始化數(shù)據(jù)管理器
注冊(cè)shutdownHandler
啟動(dòng)Admin server
創(chuàng)建并啟動(dòng)網(wǎng)絡(luò)IO管理器
啟動(dòng)ZooKeeperServer
創(chuàng)建并啟動(dòng)secureCnxnFactory
創(chuàng)建并啟動(dòng)ContainerManager
源碼如下:
protected void initializeAndRun(String[] args)throws ConfigException, IOException, AdminServerException {//1.解析配置文件QuorumPeerConfig config = new QuorumPeerConfig();if (args.length == 1) { config.parse(args[0]); }// Start and schedule the the purge task//2.創(chuàng)建并啟動(dòng)歷史文件清理器(對(duì)事務(wù)日志和快照數(shù)據(jù)文件進(jìn)行定時(shí)清理)DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config .getDataDir(), config.getDataLogDir(), config .getSnapRetainCount(), config.getPurgeInterval()); purgeMgr.start();if (args.length == 1 && config.isDistributed()) {//集群啟動(dòng)runFromConfig(config); } else {//單機(jī)啟動(dòng)LOG.warn('Either no config or no quorum defined in config, running ' ' in standalone mode');// there is only server in the quorum -- run as standaloneZooKeeperServerMain.main(args); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/** * Run from a ServerConfig. * * @param config ServerConfig to use. * @throws IOException * @throws AdminServerException */public void runFromConfig(ServerConfig config)throws IOException, AdminServerException { LOG.info('Starting server'); FileTxnSnapLog txnLog = null;try {//3.創(chuàng)建ZooKeeper數(shù)據(jù)管理器txnLog = new FileTxnSnapLog(config.dataLogDir, config.dataDir);final ZooKeeperServer zkServer = new ZooKeeperServer(txnLog, config.tickTime, config.minSessionTimeout, config.maxSessionTimeout, null); txnLog.setServerStats(zkServer.serverStats());//4.注冊(cè)shutdownHandler,在ZooKeeperServer的狀態(tài)變化時(shí)調(diào)用shutdownHandler的handle()final CountDownLatch shutdownLatch = new CountDownLatch(1); zkServer.registerServerShutdownHandler(new ZooKeeperServerShutdownHandler(shutdownLatch));//5.啟動(dòng)Admin serveradminServer = AdminServerFactory.createAdminServer(); adminServer.setZooKeeperServer(zkServer); adminServer.start();//6.創(chuàng)建并啟動(dòng)網(wǎng)絡(luò)IO管理器boolean needStartZKServer = true;if (config.getClientPortAddress() != null) { cnxnFactory = ServerCnxnFactory.createFactory(); cnxnFactory.configure(config.getClientPortAddress(), config.getMaxClientCnxns(), false);//7.此方法除了啟動(dòng)ServerCnxnFactory,還會(huì)啟動(dòng)ZooKeepercnxnFactory.startup(zkServer);// zkServer has been started. So we don't need to start it again in secureCnxnFactory.needStartZKServer = false; }//8.創(chuàng)建并啟動(dòng)secureCnxnFactory if (config.getSecureClientPortAddress() != null) { secureCnxnFactory = ServerCnxnFactory.createFactory(); secureCnxnFactory.configure(config.getSecureClientPortAddress(), config.getMaxClientCnxns(), true); secureCnxnFactory.startup(zkServer, needStartZKServer); }//9.創(chuàng)建并啟動(dòng)ContainerManagercontainerManager = new ContainerManager(zkServer.getZKDatabase(), zkServer.firstProcessor, Integer.getInteger('znode.container.checkIntervalMs', (int) TimeUnit.MINUTES.toMillis(1)), Integer.getInteger('znode.container.maxPerMinute', 10000) ); containerManager.start();// Watch status of ZooKeeper server. It will do a graceful shutdown// if the server is not running or hits an internal error.//服務(wù)器正常啟動(dòng)時(shí),運(yùn)行到此處阻塞,只有server的state變?yōu)镋RROR或SHUTDOWN時(shí)繼續(xù)運(yùn)行后面的代碼shutdownLatch.await(); shutdown();if (cnxnFactory != null) { cnxnFactory.join(); }if (secureCnxnFactory != null) { secureCnxnFactory.join(); }if (zkServer.canShutdown()) { zkServer.shutdown(true); } } catch (InterruptedException e) {// warn, but generally this is okLOG.warn('Server interrupted', e); } finally {if (txnLog != null) { txnLog.close(); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
ZooKeeperstandalone
啟動(dòng)共有9個(gè)步驟,其中有些步驟還有子步驟,有些步驟還會(huì)啟動(dòng)線程.因此下文中只會(huì)對(duì)一些簡單的步驟進(jìn)行介紹,復(fù)雜的步驟留作日后補(bǔ)充,接下來我們就分別看下這9個(gè)步驟.
ZooKeeper啟動(dòng)時(shí)會(huì)讀取配置文件,默認(rèn)讀取$ZK_HOME/conf/zoo.cfg
,其將文件解析為java.util.Properties
,根據(jù)Properties
中鍵值對(duì)的key
設(shè)置相應(yīng)value
.
ZooKeeper雖然是一個(gè)內(nèi)存數(shù)據(jù)庫,但是其通過快照和事務(wù)日志提供了持久化的功能.在ZooKeeper啟動(dòng)時(shí),會(huì)根據(jù)快照和事務(wù)日志恢復(fù)數(shù)據(jù),重建內(nèi)存數(shù)據(jù)庫;每次寫操作提交時(shí),會(huì)在事務(wù)日志中增加一條記錄,表明此次寫操作更改了哪些數(shù)據(jù);在進(jìn)行snapCount
次事務(wù)之后,將內(nèi)存數(shù)據(jù)庫所有節(jié)點(diǎn)的數(shù)據(jù)和當(dāng)前會(huì)話信息生成一個(gè)快照.
ZooKeeper的事務(wù)日志類似于MySQL的redolog,若每次寫操作后直接將數(shù)據(jù)寫到磁盤上,則會(huì)存在大量的磁盤隨機(jī)讀寫,若是寫事務(wù)日志,則將磁盤隨機(jī)讀寫轉(zhuǎn)換為順序讀寫.保證了數(shù)據(jù)的持久性的同時(shí)也兼顧了性能.
隨著時(shí)間的推移,會(huì)生成越來越多的快照和事務(wù)日志文件,為了定時(shí)清理無效日志,DatadirCleanupManager
啟動(dòng)定時(shí)任務(wù)完成日志文件的清理.
屬性名 | 對(duì)應(yīng)配置 | 配置方式 | 默認(rèn)值 | 含義 |
---|---|---|---|---|
snapRetainCount | autopurge.snapRetainCount | 配置文件 | 3 | 清理后保留的快照文件個(gè)數(shù),最小值為3,若設(shè)置為<3的數(shù),則修改為3 |
purgeInterval | autopurge.purgeInterval | 配置文件 | 0 | 清理任務(wù)TimeTask的執(zhí)行周期,即幾小時(shí)執(zhí)行一次,單位:小時(shí),若設(shè)置為<=0的值,則不會(huì)設(shè)置定時(shí)任務(wù),默認(rèn)不設(shè)置. |
配置項(xiàng)中只有snapRetainCount
用于設(shè)置清理后保留的快照文件個(gè)數(shù),那清理快照文件時(shí)會(huì)同時(shí)清理事務(wù)日志文件嗎?若會(huì)清理,清理之后會(huì)保留幾個(gè)事務(wù)日志文件呢?
答案:清理快照文件時(shí)會(huì)同時(shí)清理事務(wù)日志文件,假如保留了3個(gè)快照文件,其后綴名分別為100,200,300,則若事務(wù)日志文件中包含事務(wù)id>100的事務(wù),則該事務(wù)日志文件被保留.則事務(wù)日志文件后綴>100的都會(huì)被保留,此外,后綴名<=100的事務(wù)日志文件中最新的事務(wù)日志也被保留.因?yàn)榧词乖撌聞?wù)日志文件后綴<=100,但是可能其包含的事務(wù)中一部分id<=100,一部分>100,此時(shí)也需保留該文件
事務(wù)日志和快照文件后綴名的含義見Zookeeper-持久化
即初始化FileTxnSnapLog
,FileTxnSnapLog
組合了TxnLog
和SnapShot
,根據(jù)類名也可以看出,TxnLog
負(fù)責(zé)處理事務(wù)日志,SnapShot
負(fù)責(zé)處理快照.FileTxnSnapLog
是Zookeeper上層服務(wù)器和底層數(shù)據(jù)存儲(chǔ)之間的對(duì)接層,提供一系列操作數(shù)據(jù)文件的方法,如:
restore(DataTree, Map, PlayBackListener)
啟動(dòng)ZookeeperServer
時(shí)調(diào)用此方法從磁盤上的快照和事務(wù)日志中恢復(fù)數(shù)據(jù)
getLastLoggedZxid()
獲取日志中記載的最新的zxid
save(DataTree,ConcurrentHashMap, boolean syncSnap)
將內(nèi)存中的數(shù)據(jù)持久化到磁盤中
除此之外還有大量方法便于操作快照和事務(wù)日志.
在服務(wù)器單機(jī)啟動(dòng)結(jié)束后有一句shutdownLatch.await()
,服務(wù)器運(yùn)行到此已經(jīng)啟動(dòng)完畢,主線程阻塞在此處.但服務(wù)器退出時(shí)還需要做一些清理工作,因此注冊(cè)shutdownhandler
,在ZooKeeperServer#setState(State)
中調(diào)用此方法.
/** * 當(dāng)服務(wù)器狀態(tài)變?yōu)閌ERROR`或`SHUTDOWN`時(shí)喚醒shutdownLatch,執(zhí)行后續(xù)的清理代碼. * @param state new server state */void handle(State state) {if (state == State.ERROR || state == State.SHUTDOWN) { shutdownLatch.countDown(); } }
1
2
3
4
5
6
7
8
9
AdminServer是3.5.0之后支持的特性,啟動(dòng)了一個(gè)jettyserver,默認(rèn)端口是8080,訪問此端口可以獲取Zookeeper運(yùn)行時(shí)的相關(guān)信息:
其配置如下
參數(shù)名 | 默認(rèn) | 描述 |
---|---|---|
admin.enableServer | true | 設(shè)置為“false”禁用AdminServer。默認(rèn)情況下,AdminServer是啟用的。對(duì)應(yīng)java系統(tǒng)屬性是:zookeeper.admin.enableServer |
admin.serverPort | 8080 | Jetty服務(wù)的監(jiān)聽端口,默認(rèn)是8080。對(duì)應(yīng)java系統(tǒng)屬性是:zookeeper.admin.serverPort |
admin.commandURL | “/commands” | 訪問路徑 |
如果在啟動(dòng)Zookeeper時(shí)提示Unable to start AdminServer, exiting abnormally
,可能就是tomcat或其他軟件占用了8080端口,需要修改AdminServer
的默認(rèn)端口.
ServerCnxnFactory
是Zookeeper中的重要組件,負(fù)責(zé)處理客戶端與服務(wù)器的連接.主要有兩個(gè)實(shí)現(xiàn),一個(gè)是NIOServerCnxnFactory
,使用Java原生NIO處理網(wǎng)絡(luò)IO事件;另一個(gè)是NettyServerCnxnFactory
,使用Netty處理網(wǎng)絡(luò)IO事件.作為處理客戶端連接的組件,其會(huì)啟動(dòng)若干線程監(jiān)聽客戶端連接端口(即默認(rèn)的9876端口).由于此組件非常復(fù)雜,日后單寫一篇博客講解
啟動(dòng)Zookeeper會(huì)完成兩件事情,一是從磁盤上快照和事務(wù)日志文件將數(shù)據(jù)恢復(fù)到內(nèi)存中,二是啟動(dòng)會(huì)話管理器
/** * 初始化ZkDatabase */public void startdata()throws IOException, InterruptedException {//check to see if zkDb is not nullif (zkDb == null) { zkDb = new ZKDatabase(this.txnLogFactory); }if (!zkDb.isInitialized()) {//從快照和事務(wù)日志中恢復(fù)數(shù)據(jù)loadData(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** * Restore sessions and data */public void loadData() throws IOException, InterruptedException {if (zkDb.isInitialized()) { setZxid(zkDb.getDataTreeLastProcessedZxid()); } else {//1.由于zkDatabase尚未初始化,進(jìn)入此分支(通過快照和事務(wù)日志恢復(fù)數(shù)據(jù))setZxid(zkDb.loadDataBase()); }// 2.清理過期session,刪除其對(duì)應(yīng)的nodeList<Long> deadSessions = new LinkedList<Long>();for (Long session : zkDb.getSessions()) {if (zkDb.getSessionWithTimeOuts().get(session) == null) { deadSessions.add(session); } }for (long session : deadSessions) {// XXX: Is lastProcessedZxid really the best thing to use?killSession(session, zkDb.getDataTreeLastProcessedZxid()); }// 3.做一次快照takeSnapshot(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
在介紹Zookeeper的回話之前,我們先回憶下Http中的會(huì)話.
由于HTTP協(xié)議是無狀態(tài)的協(xié)議,所以服務(wù)端需要記錄用戶的狀態(tài)時(shí),就需要用某種機(jī)制來識(shí)具體的用戶,這個(gè)機(jī)制就是Session.典型的場景比如購物車,當(dāng)你點(diǎn)擊下單按鈕時(shí),由于HTTP協(xié)議無狀態(tài),所以并不知道是哪個(gè)用戶操作的,所以服務(wù)端要為特定的用戶創(chuàng)建了特定的Session,用于標(biāo)識(shí)這個(gè)用戶,并且跟蹤用戶,這樣才知道購物車?yán)锩嬗袔妆緯?。這個(gè)Session是保存在服務(wù)端的,有一個(gè)唯一標(biāo)識(shí)。在服務(wù)端保存Session的方法很多,內(nèi)存、數(shù)據(jù)庫、文件都有。集群的時(shí)候也要考慮Session的轉(zhuǎn)移,在大型的網(wǎng)站,一般會(huì)有專門的Session服務(wù)器集群,用來保存用戶會(huì)話,這個(gè)時(shí)候 Session 信息都是放在內(nèi)存的,使用一些緩存服務(wù)比如Memcached之類的來放 Session。
session是一個(gè)抽象概念,開發(fā)者為了實(shí)現(xiàn)中斷和繼續(xù)等操作,將 user agent 和 server 之間一對(duì)一的交互,抽象為”會(huì)話”,進(jìn)而衍生出”會(huì)話狀態(tài)”,也就是 session 的概念.
而session的實(shí)現(xiàn)一般是在服務(wù)端保存的一個(gè)數(shù)據(jù)結(jié)構(gòu),用來跟蹤用戶的狀態(tài),這個(gè)數(shù)據(jù)可以保存在集群,數(shù)據(jù)庫,文件中,每一個(gè)session都有一個(gè)sessionId用來唯一標(biāo)識(shí)session.客戶端在發(fā)送請(qǐng)求時(shí),將sessionId作為請(qǐng)求參數(shù)發(fā)送給服務(wù)器,服務(wù)器就可根據(jù)sessionId找到保存在服務(wù)器中的session.
由于Zookeeper提供了臨時(shí)節(jié)點(diǎn),Watcher通知等功能.自然需要保存客戶端的狀態(tài),會(huì)話管理器就是Zookeeper中用于管理會(huì)話的組件.由于此組件過于復(fù)雜,單獨(dú)介紹.
類比于tomcat,tomcat處理請(qǐng)求時(shí)會(huì)構(gòu)造pipeline
和value
,filter
和filterChain
兩個(gè)攔截過濾器處理請(qǐng)求,便于職責(zé)的解耦.Zookeeper會(huì)構(gòu)造一個(gè)請(qǐng)求處理鏈用于處理客戶端發(fā)送的請(qǐng)求.此組件過于復(fù)雜,單獨(dú)介紹.
JMX的全稱為Java Management Extensions. 顧名思義,是管理Java的一種擴(kuò)展。這種機(jī)制可以方便的管理、監(jiān)控正在運(yùn)行中的Java程序。常用于管理線程,內(nèi)存,日志Level,服務(wù)重啟,系統(tǒng)環(huán)境等
Zookeeper內(nèi)部封裝了注冊(cè)JMX的邏輯,JMX注冊(cè)成功后,可以通過visualVM查看和修改運(yùn)行時(shí)屬性.JMX相關(guān)知識(shí)請(qǐng)查閱資料.
個(gè)人推測此組件應(yīng)該和ServerCnxnFactory
提供的功能類似,可能增加了認(rèn)證的邏輯,目前沒有在網(wǎng)上關(guān)于此組件源碼的資料,有時(shí)間查看源碼后補(bǔ)充.
Zookeeper中的節(jié)點(diǎn)類型有持久節(jié)點(diǎn),持久順序節(jié)點(diǎn),臨時(shí)節(jié)點(diǎn),臨時(shí)順序節(jié)點(diǎn),通過創(chuàng)建臨時(shí)順序節(jié)點(diǎn),我們可以實(shí)現(xiàn)leader選舉,分布式鎖等功能,比如實(shí)現(xiàn)一個(gè)分布式鎖,我們的思路一般如下:
創(chuàng)建一個(gè)持久節(jié)點(diǎn),如”/lock”
每一個(gè)想獲取鎖的進(jìn)程在該節(jié)點(diǎn)下創(chuàng)建子節(jié)點(diǎn),子節(jié)點(diǎn)類型為臨時(shí)順序節(jié)點(diǎn)
創(chuàng)建了若干臨時(shí)順序節(jié)點(diǎn)中順序號(hào)最小的節(jié)點(diǎn)的線程獲得鎖;若進(jìn)程未獲得鎖,則在順序號(hào)最小的節(jié)點(diǎn)上注冊(cè)監(jiān)聽事件,監(jiān)聽事件中包括競爭鎖的相關(guān)邏輯.當(dāng)獲取鎖的進(jìn)程釋放鎖(即刪除順序號(hào)最小的節(jié)點(diǎn))時(shí)將回調(diào)監(jiān)聽事件競爭鎖.(簡單介紹分布式鎖的實(shí)現(xiàn)思路,未解決羊群效應(yīng))
問題出現(xiàn)在第一步,為了實(shí)現(xiàn)分布式鎖的邏輯,我們必須建立一個(gè)父節(jié)點(diǎn),且其類型為持久節(jié)點(diǎn),但是當(dāng)不需要分布式鎖時(shí)誰來刪除/lock
節(jié)點(diǎn)呢?
為了解決這個(gè)問題,Zookeeper在3.6.0
版本新增一種節(jié)點(diǎn)類型,即容器節(jié)點(diǎn).其特點(diǎn)為:當(dāng)容器節(jié)點(diǎn)的最后一個(gè)孩子節(jié)點(diǎn)被刪除之后,容器節(jié)點(diǎn)將被標(biāo)注并在一段時(shí)間后刪除.
那么在實(shí)現(xiàn)分布式鎖時(shí),可以將/lock
類型設(shè)置為容器節(jié)點(diǎn),當(dāng)沒有線程競爭分布式鎖時(shí),/lock
節(jié)點(diǎn)會(huì)被Zookeeper自動(dòng)刪除.
ContainerManager
中有兩個(gè)重要參數(shù)控制其行為:
屬性名 | 對(duì)應(yīng)配置 | 配置方式 | 默認(rèn)值 | 含義 |
---|---|---|---|---|
checkIntervalMs | znode.container.checkIntervalMs | 系統(tǒng)屬性 | 60_000 | 執(zhí)行兩次檢查任務(wù)之間的時(shí)間間隔,單位:ms,默認(rèn)1min |
maxPerMinute | znode.container.maxPerMinute | 系統(tǒng)屬性 | 10_000 | 一分鐘內(nèi)最多刪除多少個(gè)容器節(jié)點(diǎn),即刪除兩個(gè)容器節(jié)點(diǎn)之間的最少時(shí)間間隔為60000/10000=6ms |
注:上述屬性通過設(shè)置系統(tǒng)屬性配置,即在啟動(dòng)QuorumPeerMain
時(shí)添加-Dznode.container.checkIntervalMs=XXX
為了能夠及時(shí)清理容器節(jié)點(diǎn),通過Timer
來執(zhí)行定時(shí)任務(wù),實(shí)現(xiàn)代碼如下:
/** * Manually check the containers. Not normally used directly */public void checkContainers()throws InterruptedException {//刪除兩個(gè)容器節(jié)點(diǎn)之間的最小間隔,默認(rèn):6mslong minIntervalMs = getMinIntervalMs();//遍歷待刪除的容器節(jié)點(diǎn)(同時(shí)會(huì)刪除過期的TTL節(jié)點(diǎn))for (String containerPath : getCandidates()) {long startMs = Time.currentElapsedTime(); ByteBuffer path = ByteBuffer.wrap(containerPath.getBytes()); Request request = new Request(null, 0, 0, ZooDefs.OpCode.deleteContainer, path, null);try { LOG.info('Attempting to delete candidate container: {}', containerPath);//只是將刪除節(jié)點(diǎn)的請(qǐng)求發(fā)送給PrepRequestProcessor,并未真正刪除該節(jié)點(diǎn)requestProcessor.processRequest(request); } catch (Exception e) { LOG.error('Could not delete container: {}', containerPath, e); }//刪除一個(gè)容器節(jié)點(diǎn)所需時(shí)間long elapsedMs = Time.currentElapsedTime() - startMs;long waitMs = minIntervalMs - elapsedMs;//若刪除一個(gè)容器節(jié)點(diǎn)所需時(shí)間小于minIntervalMs,線程sleep.// 由于Timer內(nèi)部只有一個(gè)線程,因此可以保證刪除兩個(gè)容器節(jié)點(diǎn)之間的時(shí)間間隔至少是minIntervalMsif (waitMs > 0) { Thread.sleep(waitMs); } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
作為一個(gè)服務(wù)器,除了在主線程中進(jìn)行初始化工作,還會(huì)開啟若干線程,為客戶端提供服務(wù),在這里,我們總結(jié)下Zookeeper單機(jī)啟動(dòng)時(shí)啟動(dòng)了多少線程:
歷史文件清理線程:通過Timer
定時(shí)執(zhí)行,執(zhí)行周期為小時(shí)級(jí)別
Admin server:通過內(nèi)置的jetty監(jiān)聽8080端口,但由于對(duì)jetty不了解,不知一共啟動(dòng)了多少個(gè)線程
ServerCnxnFactory:此組件負(fù)責(zé)管理客戶端的TCP連接,其有兩種實(shí)現(xiàn),分別是原生NIO和基于Netty的實(shí)現(xiàn)
會(huì)話管理器:啟動(dòng)若干線程
請(qǐng)求處理鏈:啟動(dòng)若干線程
SecureServerCnxnFactory:與ServerCnxnFactory類似
ContainerManager:通過Timer
定時(shí)執(zhí)行,清理過期的容器節(jié)點(diǎn)和TTL節(jié)點(diǎn),執(zhí)行周期為分鐘級(jí)別
可以看出,有很多啟動(dòng)線程的組件在此都未做介紹,正式因?yàn)閱?dòng)線程的組件是服務(wù)器的重點(diǎn),內(nèi)容繁多,另開博客介紹
聯(lián)系客服