Zookeeper簡介(三)

一、zookeeper的安裝

1、單機模式

zookeeper目錄下的conf子目錄, 創建zoo.cfg

tickTime=2000    
dataDir=/Users/apple/zookeeper/data    
dataLogDir=/Users/apple/zookeeper/logs    
clientPort=4180   

bin/zkServer.sh start //啓動服務
bin/zkCli.sh -server localhost:4180

2、僞集羣模式

僞集羣, 是指在單臺機器中啓動多個zookeeper進程, 並組成一個集羣. 以啓動3個zookeeper進程爲例.
將zookeeper的目錄拷貝2份:

|--zookeeper0  
|--zookeeper1  
|--zookeeper2 

更改zookeeper0/conf/zoo.cfg文件爲:

tickTime=2000    
initLimit=5    
syncLimit=2    
dataDir=/Users/apple/zookeeper0/data    
dataLogDir=/Users/apple/zookeeper0/logs    
clientPort=4180  
server.0=127.0.0.1:8880:7770    
server.1=127.0.0.1:8881:7771    
server.2=127.0.0.1:8882:7772 

參照zookeeper0/conf/zoo.cfg, 配置zookeeper1/conf/zoo.cfg, 和zookeeper2/conf/zoo.cfg文件. 只需更改dataDir, dataLogDir, clientPort參數即可.
在之前設置的dataDir中新建myid文件, 寫入一個數字, 該數字表示這是第幾號server. 該數字必須和zoo.cfg文件中的server.X中的X一一對應.
/Users/apple/zookeeper0/data/myid文件中寫入0, /Users/apple/zookeeper1/data/myid文件中寫入1, /Users/apple/zookeeper2/data/myid文件中寫入2.
分別進入/Users/apple/zookeeper0/bin, /Users/apple/zookeeper1/bin, /Users/apple/zookeeper2/bin三個目錄, 啓動server.
任意選擇一個server目錄, 啓動客戶端:

3、集羣模式

集羣模式的配置和僞集羣基本一致.

tickTime=2000    
initLimit=5    
syncLimit=2    
dataDir=/home/zookeeper/data    
dataLogDir=/home/zookeeper/logs    
clientPort=4180  
server.43=10.1.39.43:2888:3888  
server.47=10.1.39.47:2888:3888    
server.48=10.1.39.48:2888:3888  

注意:各server的dataDir目錄下的myid文件中的數字必須不同.
10.1.39.43 server的myid爲43, 10.1.39.47 server的myid爲47, 10.1.39.48 server的myid爲48.

二、zk的配置指令

globalOutstandingLimit
這個配置指定了等待處理的最大請求數量的限制(zookeeper.globalOutstandingLimit)。
client發送請求的速度可能會比server端處理的速度快,會導致請求在server端排隊,最終(在若干秒內)會使server的內存耗盡。爲了避免這一點,如果等待的請求數量達到了globalOutstandingLimit,server端會拒絕client的請求。但是這個限制不是hard限制。每一個client至少能有一個outstanding請求,否則連接會開始出現超時。所以,當達到globalOutstandingLimit之後,只有在沒有任何的pending請求時,server纔會從client連接讀取數據。
爲了決定某一臺確定的server的限制,可以簡單的用這個配置項的值除以server的數量。現在沒有一種聰明的方式來決定這個值來進行限制,總的說來,這個配置項的值就是outstanding請求的上限。實際上,負載無法在server間進行均衡,總有一些server的負載會高一些,即使沒有達到上限。
默認的限制爲1000個請求。通常不需要改變這個配置,如果有很多client會發送非常大的請求,你需要調低這個值,但是在實踐中通常不需要改變這個值。
//最大請求堆積數。默認是1000。ZK運行的時候, 儘管server已經沒有空閒來處理更多的客戶端請求了,但是還是允許客戶端將請求提交到服務器上來,以提高吞吐性能。當然,爲了防止Server內存溢出,這個請求堆積數還是需要限制下的。
(Java system property:zookeeper.globalOutstandingLimit. )

maxClientCnxns
決定了每個IP地址可以發起的socket連接個最大個數。
ZooKeeper使用了flow control和limit來避免連接過載。建立連接消耗的資源遠遠超過普通的操作消耗的資源。一瞬間過多的請求會造成拒絕服務的問題,所以加上了這個限制,當某個IP的連接超過了這個限制,server會拒絕連接。默認值爲60。

clientPortAddress
默認server會監聽所有的網絡接口提供client來連接。有一些服務器會有多個網絡接口,通常一些爲內網接口一些爲外網接口。如果不想開放外網接口,可以將此配置項設爲內網接口。

minSessionTimeout
這是session過期的最小超時時間,單位爲毫秒。當client發起連接時,它會請求一個特定的超時時間,但是實際的超時時間並能小於這個配置項。
開發者喜歡立即並準確的檢測到client端的失敗。但不幸的是,系統不能實時的檢測到,實際上是使用心跳和超時來做的。超時的使用依賴於client端和server端的網絡延遲和可靠性。超時時間必須至少等於網絡的round trip time,但是偶爾會有丟包的情況,在這種情況下接收響應的時間會增加,因爲會發送發送丟失的包。
默認minSessionTimeout是tickTime的2倍。把這個值設置得過低的話會導致錯誤的檢測client的失敗。設置得太高的話會導致檢測client的失敗的延遲。

maxSessionTimeout
這是最大的session超時時間,單位爲毫秒。當client發起連接時,它會請求一個特定的超時時間,但是實際的超時時間並能大於這個配置項。
儘管這個配置不會影響系統性能,但會限制client消耗系統資源的時間。默認是tickTime的20倍。

clientPort
客戶端連接server的端口,即對外服務端口,一般設置爲2181吧。

dataDir
存儲快照文件snapshot的目錄。默認情況下,事務日誌也會存儲在這裏。建議同時配置參數dataLogDir, 事務日誌的寫性能直接影響zk性能。

tickTime
ZK中的一個時間單元。ZK中所有時間都是以這個時間單元爲基礎,進行整數倍配置的。例如,session的最小超時時間是2*tickTime。

dataLogDir
事務日誌輸出目錄。儘量給事務日誌的輸出配置單獨的磁盤或是掛載點,這將極大的提升ZK性能。 (No Java system property)

preAllocSize
預先開闢磁盤空間,用於後續寫入事務日誌。默認是64M,每個事務日誌大小就是64M。如果ZK的快照頻率較大的話,建議適當減小這個參數。(Java system property:zookeeper.preAllocSize )

snapCount
每進行snapCount次事務日誌輸出後,觸發一次快照(snapshot), 此時,ZK會生成一個snapshot.文件,同時創建一個新的事務日誌文件log.。默認是100000.(真正的代碼實現中,會進行一定的隨機數處理,以避免所有服務器在同一時間進行快照而影響性能)(Java system property:zookeeper.snapCount )

traceFile
用於記錄所有請求的log,一般調試過程中可以使用,但是生產環境不建議使用,會嚴重影響性能。(Java system property:? requestTraceFile )

maxClientCnxns
單個客戶端與單臺服務器之間的連接數的限制,是ip級別的,默認是60,如果設置爲0,那麼表明不作任何限制。請注意這個限制的使用範圍,僅僅是單臺客戶端機器與單臺ZK服務器之間的連接數限制,不是針對指定客戶端IP,也不是ZK集羣的連接數限制,也不是單臺ZK對所有客戶端的連接數限制。指定客戶端IP的限制策略,這裏有一個patch,可以嘗試一下:http://rdc.taobao.com/team/jm/archives/1334(No Java system property)

clientPortAddress
對於多網卡的機器,可以爲每個IP指定不同的監聽端口。默認情況是所有IP都監聽 clientPort 指定的端口。 New in 3.3.0

minSessionTimeoutmaxSessionTimeout
Session超時時間限制,如果客戶端設置的超時時間不在這個範圍,那麼會被強制設置爲最大或最小時間。默認的Session超時時間是在2 tickTime ~ 20 tickTime 這個範圍 New in 3.3.0

fsync.warningthresholdms
事務日誌輸出時,如果調用fsync方法超過指定的超時時間,那麼會在日誌中輸出警告信息。默認是1000ms。(Java system property: fsync.warningthresholdms )New in 3.3.4

autopurge.purgeInterval
3.4.0及之後版本,ZK提供了自動清理事務日誌和快照文件的功能,這個參數指定了清理頻率,單位是小時,需要配置一個1或更大的整數,默認是0,表示不開啓自動清理功能。(No Java system property) New in 3.4.0

autopurge.snapRetainCount
這個參數和上面的參數搭配使用,這個參數指定了需要保留的文件數目。默認是保留3個。(No Java system property) New in 3.4.0

electionAlg
在3.4之前的版本中, 這個參數配置是允許我們選擇leader選舉算法,但是由於在以後的版本中,只會留下一種“TCP-based version of fast leader election”算法,所以這個參數目前看來沒有用了,這裏也不詳細展開說了。(No Java system property)

initLimit
Follower在啓動過程中,會從Leader同步所有最新數據,然後確定自己能夠對外服務的起始狀態。Leader允許F在 initLimit 時間內完成這個工作。通常情況下,我們不用太在意這個參數的設置。如果ZK集羣的數據量確實很大了,F在啓動的時候,從Leader上同步數據的時間也會相應變長,因此在這種情況下,有必要適當調大這個參數了。(No Java system property)

syncLimit
在運行過程中,Leader負責與ZK集羣中所有機器進行通信,例如通過一些心跳檢測機制,來檢測機器的存活狀態。如果L發出心跳包在syncLimit之後,還沒有從F那裏收到響應,那麼就認爲這個F已經不在線了。注意:不要把這個參數設置得過大,否則可能會掩蓋一些問題。(No Java system property)

leaderServes
默認情況下,Leader是會接受客戶端連接,並提供正常的讀寫服務。但是,如果你想讓Leader專注於集羣中機器的協調,那麼可以將這個參數設置爲no,這樣一來,會大大提高寫操作的性能。(Java system property: zookeeper. leaderServes )。

server.x=[hostname]:nnnnn[:nnnnn]
這裏的x是一個數字,與myid文件中的id是一致的。右邊可以配置兩個端口,第一個端口用於F和L之間的數據同步和其它通信,第二個端口用於Leader選舉過程中投票通信。
(No Java system property)
server.X=A:B:C 其中X是一個數字, 表示這是第幾號server. A是該server所在的IP地址. B配置該server和集羣中的leader交換消息所使用的端口. C配置選舉leader時所使用的端口. 由於配置的是僞集羣模式, 所以各個server的B, C參數必須不同.

group.x=nnnnn[:nnnnn]weight.x=nnnnn
對機器分組和權重設置,可以 參見這裏(No Java system property)

cnxTimeout
Leader選舉過程中,打開一次連接的超時時間,默認是5s。(Java system property: zookeeper. cnxTimeout )

zookeeper.DigestAuthenticationProvider.superDigest
ZK權限設置相關,具體參見 《 使用super 身份對有權限的節點進行操作 》 和 《 ZooKeeper 權限控制 》

skipACL
對所有客戶端請求都不作ACL檢查。如果之前節點上設置有權限限制,一旦服務器上打開這個開頭,那麼也將失效。(Java system property: zookeeper.skipACL )

forceSync
這個參數確定了是否需要在事務日誌提交的時候調用 FileChannel .force來保證數據完全同步到磁盤。(Java system property: zookeeper.forceSync )

jute.maxbuffer
每個節點最大數據量,是默認是1M。這個限制必須在server和client端都進行設置纔會生效。(Java system property: jute.maxbuffer )

三、zk命令行的使用

[zookeeper, node_2]
[zk: localhost:3218(CONNECTED) 43] h

ZooKeeper -server host:port cmd args
    stat path [watch]
    set path data [version]
    ls path [watch]
    delquota [-n|-b] path
    ls2 path [watch]
    setAcl path acl
    setquota -n|-b val path
    history 
    redo cmdno
    printwatches on|off
    delete path [version]
    sync path
    listquota path
    rmr path
    get path [watch]
    create [-s] [-e] path data acl
    addauth scheme auth
    quit 
    getAcl path
    close 
    connect host:port

1、create

創建新的Znode節點 (-s : 順序節點 -e :臨時數據節點(重啓會消失)) path:路徑 data:數據 acl:權限,不指定默認爲world:anyone:cdwra,可參考Zookeeper的ACL access control list
[zk: 192.168.0.23:2181(CONNECTED) 10] create /node_1 "test"
Created /node_1

權限主要有:

CREATE : 創建子節點
READ : 獲取節點數據和子節點列表
WRITE : 更新節點數據
DELETE : 刪除子節點
ADMIN : 設置節點ACL的權限

與授權相關的幾個概念:
shchema:權限模式,有IP和digest兩種
ID:授權對象
schema爲IP時,該值爲具體的IP地址
scheme爲digest時,該值爲 userName:base64(sha1(userName:password))
zookeeper的javaAPI提供了一個工具類org.apache.zookeeper.server.auth.DigestAuthenticationProvider 可以快速生成加密的密文
permission:權限,指的就是上面所說的五種權限
最終的組合爲: schema + ID + permission

[zk: localhost:2181(CONNECTED) 35] create /mpcapp/mpc8 88888 ip:192.168.174.139:crwda

2、get

查看節點數據path:路徑

[zk: 192.168.0.23:2181(CONNECTED) 11] get /node_1
test
cZxid = 0x13
ctime = Thu Feb 16 13:19:40 CST 2017
mZxid = 0x13
mtime = Thu Feb 16 13:19:40 CST 2017
pZxid = 0x13
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
test 爲節點存儲的數據內容
CZxid:表示該節點在那個事務中創建的事務id。
ctime:表示該節點的創建時間
mZxid:表示該節點更新時的事務id
mtime:表示該節點的修改時間
pZxid:表示該節點的子節點列表最後一次被修改的事務id
cversion:子節點版本號
dataversion:數據版本號
aclversion:權限版本號
ephemeralOwner:專門用於臨時節點,表示創建該臨時節點的事務id(如果當前節點是持久節點,該值固定爲0)
dataLength:當前節點存放數據的長度
numChildren:當前節點的子節點數目

3、stat

查看數據節點的狀態信息 path:路徑
[zk: 192.168.0.23:2181(CONNECTED) 12] stat /node_1

cZxid = 0x13
ctime = Thu Feb 16 13:19:40 CST 2017
mZxid = 0x13
mtime = Thu Feb 16 13:19:40 CST 2017
pZxid = 0x13
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0

4、set

設置數據節點的值,path:路徑,data:數據,可以帶版本號,可以不帶版本號

[zk: 192.168.0.23:2181(CONNECTED) 19] set /node_1 "11114"  
cZxid = 0x13
ctime = Thu Feb 16 13:19:40 CST 2017
mZxid = 0x18
mtime = Thu Feb 16 13:32:59 CST 2017
pZxid = 0x17
cversion = 4
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 3
[zk: 192.168.0.23:2181(CONNECTED) 20] get /node_1 
11114
cZxid = 0x13
ctime = Thu Feb 16 13:19:40 CST 2017
mZxid = 0x18
mtime = Thu Feb 16 13:32:59 CST 2017
pZxid = 0x17
cversion = 4
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 5
numChildren = 3

在node_1下建立了三個節點,node_1_1,node_1_2以及node_1_3

set:添加上版本號(樂觀鎖)

[zk: 192.168.0.23:2181(CONNECTED) 27] get /node_1/node_1_3
333
cZxid = 0x30
ctime = Fri Feb 17 10:11:45 CST 2017
mZxid = 0x33
mtime = Fri Feb 17 10:29:37 CST 2017
pZxid = 0x30
cversion = 0
dataVersion = 2
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
[zk: 192.168.0.23:2181(CONNECTED) 28] set /node_1/node_1_3 332 2
cZxid = 0x30
ctime = Fri Feb 17 10:11:45 CST 2017
mZxid = 0x34
mtime = Fri Feb 17 10:31:51 CST 2017
pZxid = 0x30
cversion = 0
dataVersion = 3
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 3
numChildren = 0
[zk: 192.168.0.23:2181(CONNECTED) 29] set /node_1/node_1_3 332 2
version No is not valid : /node_1/node_1_3
通過get,我們知道了node_1_3,的數據值和版本號,然後修改node_1_3的數據值和版本號,版本號一致會修改成功,版本號不一致,拋出版本號無效的異常。

5、delete

刪除node_1_3節點,此時它沒有子節點,否則會報錯,也可以攜帶版本
rmr 循環刪除有子節點的父節點,例如刪除node_1
[zk: 192.168.0.23:2181(CONNECTED) 6] rmr /node_1
[zk: 192.168.0.23:2181(CONNECTED) 7] ls /
[zookeeper]

6、quota

(配額):增加一些節點的限制,必須在一定的範圍內
setquota :設置子節點的個數(-n:子節點個數的限制,-b:數據數據節點數據長度的限制)
listquota,查看數據節點配額的情況
delquota,刪除數據節點配額的情況

[zk: 192.168.0.23:2181(CONNECTED) 33] create /node_1 1
Created /node_1
[zk: 192.168.0.23:2181(CONNECTED) 40] setquota -n 2 /node_1
Comment: the parts are option -n val 2 path /node_1
[zk: 192.168.0.23:2181(CONNECTED) 47] create /node_1/node_1_1 11
Created /node_1/node_1_1
[zk: 192.168.0.23:2181(CONNECTED) 48] create /node_1/node_1_2 12
Created /node_1/node_1_2
[zk: 192.168.0.23:2181(CONNECTED) 49] create /node_1/node_1_3 13
Created /node_1/node_1_3
創建node_1節點,並給它設置節點數爲2的限制,但是我們成功了創建子節點,node_1_3,並沒有給我們拋出異常,它僅僅是在跟目錄下的zookeeper.out的輸出了一個警告。
[localhost zookeeper-3.4.9]$ tail zookeeper.out 
....省略
2017-02-17 10:45:32,472 [myid:] - WARN  [SyncThread:0:DataTree@301] - Quota exceeded: /node_1 count=3 limit=2
[zk: 192.168.0.23:2181(CONNECTED) 50] listquota /node_1
absolute path is /zookeeper/quota/node_1/zookeeper_limits
Output quota for /node_1 count=2,bytes=-1
Output stat for /node_1 count=4,bytes=7

刪除節點的配置之後,我們驗證一下,看到此時node_1節點的子節點個數沒有限制,數據長度沒有限制了。

7、其他命令

ls2 是ls的超級指令,不僅可以列出當前節點的子節點,還可以輸出節點的狀態信息,不再演示。
history 打印出最近執行的十個命令
redo cmdno 根據命令編號(可用history查詢編號)重新執行以前執行過的命令
close關閉當前連接,可用connect 再次連接,不會退出客戶端
quit 關閉連接並退出連接客戶端
connect連接服務器

8、常用四字命令

conf 輸出相關服務配置的詳細信息
cons 列出所有連接到服務器的客戶端的完全的連接/會話的詳細信息.包括“接受/發送”的包數量,會話,id,操作延遲,最後的操作執行等等信息
dump 列出未經處理的會話和臨時節點.
envi 輸出關於服務環境的詳細信息
reqs 列出未經處理的請求
ruok 測試服務是否處於正確狀態.如果確實如此,那麼服務返回”imok”,否則不做任何響應
stat 輸出關於性能和連接的客戶端的列表
wchs 列出服務器watch的詳細信息
wchc 通過session列出服務器watch的詳細信息,它的輸出是一個與watch相關的會話的列表
wchp 通過路徑列出服務器watch的詳細信息.它輸出一個與session相關的路徑

conf)
[localhost zookeeper-3.4.9]$ echo conf | nc 192.168.0.23 2181
clientPort=2181
dataDir=/home/yinpeng/yuliang/data/version-2
dataLogDir=/home/yinpeng/yuliang/data/version-2
tickTime=2000
maxClientCnxns=60
minSessionTimeout=4000
maxSessionTimeout=40000
serverId=0

cons)
[localhost zookeeper-3.4.9]$ echo cons | nc 192.168.0.23 2181
/192.168.0.23:389680

ruok
測試服務是否處於正確狀態.如果確實如此,那麼服務返回” imok”,否則不做任何相應.
[localhost zookeeper-3.4.9]$ echo ruok | nc 192.168.0.23 2181
imok
回覆imok表示已經啓動

stat)
[localhost zookeeper-3.4.9]$ echo stat | nc 192.168.0.23 2181
Zookeeper version: 3.4.9-1757313, built on 08/23/2016 06:50 GMT
Clients:
/192.168.0.23:389720

Latency min/avg/max: 0/0/32
Received: 5425
Sent: 5424
Connections: 1
Outstanding: 0
Zxid: 0x4a
Mode: standalone
Node count: 11

四、基於ZK的ACL進行znode控制

1、zk提供的認證方式

Zookeeper對權限的控制是znode級別的,不繼承即對父節點設置權限,其子節點不繼承父節點的權限。
world:有個單一的ID,anyone,表示任何人。
auth:不使用任何ID,表示任何通過驗證的用戶(驗證是指創建該znode的權限)。
digest:使用 用戶名:密碼 字符串生成MD5哈希值作爲ACL標識符ID。權限的驗證通過直接發送用戶名密碼字符串的方式完成,
ip:使用客戶端主機ip地址作爲一個ACL標識符,ACL表達式是以 addr/bits 這種格式表示的。ZK服務器會將addr的前bits位與客戶端地址的前bits位來進行匹配驗證權限。

2、簡單認證

[zk: 127.0.0.1:3218(CONNECTED) 55] create /auth auth auth:127.0.0.1:rw //127.0.0.1需要登錄,
[zk: 127.0.0.1:3218(CONNECTED) 6] ls /auth //另外一個終端
Authentication is not valid : /auth
[zk: 192.168.2.113:3218(CONNECTED) 2] addauth digest tom:tom //添加完認證後就可以ls查看了,127.0.0.1也要登錄才能查看
[zk: 192.168.2.113:3218(CONNECTED) 3] ls /auth
[]
[zk: 127.0.0.1:3218(CONNECTED) 1] getAcl /auth
'digest,'tom:GcSMsIa2MmdW+zdSJKAv8gcnrpI=
: rw
[zk: 127.0.0.1:3218(CONNECTED) 6] create /tom "test" digest:tom:GcSMsIa2MmdW+zdSJKAv8gcnrpI=:rwdca
[zk: 127.0.0.1:3218(CONNECTED) 7] getAcl /tom
'digest,'tom:GcSMsIa2MmdW+zdSJKAv8gcnrpI=
: cdrwa

3、zk提供的權限信息

write 能夠設置znode的值 w
read 能夠讀取znode的值和列出它的children znode r
create 能夠創建children znode c
delete 能夠刪除children znode d
admin 能夠執行setAcl即設置訪問控制列表 a
all 所有權限 wrcda

4、注意問題:

1.通過zkCli.sh設置acl的格式是scheme:id:perm,perm的寫法是簡寫字母連接,如讀寫權限rw和Linux的文件系統的權限相似。有些版本可能是:READ|WRITE, 所以需要注意命令行提示信息。
2.通過zkCli.sh設置acl時,scheme是digest的時候,id需要密文
生成方式:
java -cp $ZK_CLASSPATH \
org.apache.zookeeper.server.auth.DigestAuthenticationProvider amy:secret
....
amy:secret->amy:Iq0onHjzb4KyxPAp8YWOIC8zzwY=
3.通過Zookeeper的客戶端編碼方式添加認證,digest對應的auth數據是明文
4.Zookeeper認證的擴展
org.apache.zookeeper.server.auth.AuthenticationProvider // 實現AuthenticationProvider接口提供自定義的認證方式。
比如自定義實現AuthenticationProvider類是secondriver.MyProvier,可以通過兩種方式註冊Zookeeper認證體系中去。

第一種:啓動Zookeeper服務是通過-Dzookeeper.authPorivder.X=secondriver.MyProvider
第二種:添加到配置文件(zoo.conf)中如:
zookeeper.authProvider.1=secondriver.MyProvider
注:上面X是對authProvider實現提供編號用來區別不同的authProvider。

參考博客:
http://zookeeper.apache.org/doc/r3.4.3/zookeeperHierarchicalQuorums.html
https://blog.csdn.net/jiuqiyuliang/article/details/55213517
http://coolxing.iteye.com/blog/1871009
http://blog.51cto.com/aiilive/1686132

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章