Redis集羣-官方推薦方案RedisCluster

Redis集羣-官方推薦方案RedisCluster

前情提要

  • 理解RedisCluster的原理和容錯機制
  • 能夠配置RedisCluster並使用

redis使用中遇到的瓶頸

我們日常工作中使用Redis,經常會遇到一些問題:

1、高可用問題,如何保證redis的持續高可用性。  
2、容量問題,單實例redis內存無法無限擴充,達到32G後就進入了64位世界,性能下降。  
3、併發性能問題,redis號稱單實例10萬併發,但也是有盡頭的。

RedisCluster的原理和容錯機制

redis的集羣策略

redis3.0以後推出的redis cluster 集羣方案,redis cluster集羣保證了高可用、高性能、高可擴展性。

主要有下面三種集羣策略,分別爲:

  1. 推特:twemproxy : 代理式
  2. 豌豆莢:codis :代理式
  3. 官方:redis cluster : 非代理

我們主要來學習官方推出的 redis cluster,這也是生產項目中用的最多的。

redis-cluster的優勢

1、官方推薦,毋庸置疑。  
2、去中心化,集羣最大可增加1000個節點,性能隨節點增加而線性擴展。  
3、管理方便,後續可自行增加或摘除節點,移動分槽等等。  
4、簡單,易上手。

redis-cluster名詞介紹

1、master  主節點、  
2、slave   從節點  
3、slot    槽,一共有16384數據分槽,分佈在集羣的所有主節點中。

redis cluster 架構圖

架構細節:

  1. 圖中描述的是六個redis實例構成的集羣,6379端口爲客戶端通訊端口,16379端口爲集羣總線端口

  2. 集羣內部劃分爲16384個數據分槽,分佈在三個主redis中。

  3. 從redis中沒有分槽,不會參與集羣投票,也不會幫忙加快讀取數據,僅僅作爲主機的備份。

  4. 三個主節點中平均分佈着16384數據分槽的三分之一,每個節點中不會存有有重複數據,僅僅有自己的從機幫忙冗餘。

  5. 所有的redis主節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬。

  6. 客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集羣所有節點,連接集羣中任何一個可用節點即可。

  7. 節點的fail是通過集羣中超過半數的節點檢測失效時才生效。

操作原理演示:

Redis 集羣中內置了 16384個哈希槽,當需要在 Redis 集羣中放置一個 key-value 時,redis 先對 key使用 crc16 算法算出一個結果,然後把結果對 16384 求餘數,這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,redis 會根據節點數量大致均等的將哈希槽映射到不同的節點。

redis cluster 投票:容錯

  1. 節點失效判斷: 所有master參與投票,如果半數以上master節點與其中一個master節點通信超過(cluster-node-timeout),認爲該master節點掛掉.

  2. 掛掉主節點的從節點自動升級爲主節點,redis集羣操作.

集羣失效判斷: 什麼時候整個集羣不可用(cluster_state:fail)?

  1. 如果集羣任意master掛掉,且當前master沒有slave,則集羣進入fail狀態。也可以理解成集羣的[0-16383]slot映射不完全時進入fail狀態。
  2. 如果集羣超過半數以上master掛掉,無論是否有slave,集羣進入fail狀態。 (投票無效)

集羣部署

redis 集羣最少需要三臺服務器,這裏我們採用3主3從來配置 redis cluster。端口可以自定定義,我設爲 6310、6320、6330、6340、6350、6360

  • 第一步,複製安裝redis bin目錄的 redis.conf 爲 6份。eg:redis1.conf …
  • 第二步,修改這6份配置文件,如下所示,記着6份配置文件都需要修改哈;
#redis.conf默認配置
daemonize yes
pidfile /var/run/redis/redis.pid  #多實例情況下需修改,例如redis_6380.pid
port 6379        #多實例情況下需要修改,例如6380
tcp-backlog 511
bind 0.0.0.0      
timeout 0
tcp-keepalive 0
loglevel notice
logfile “”   #多實例情況下需要修改,例如6380.log
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb  #多實例情況下需要修改,例如dump.6380.rdb
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
appendonly yes
appendfilename "appendonly.aof"  #多實例情況下需要修改,例如 appendonly_6380.aof
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10

#################自定義配置
#系統配置
#vim /etc/sysctl.conf
#vm.overcommit_memory = 1

aof-rewrite-incremental-fsync yes
maxmemory 4096mb
maxmemory-policy allkeys-lru
dir /opt/redis/data      #多實例情況下需要修改,例如/data/6380

#集羣配置
cluster-enabled yes #打開集羣配置
cluster-config-file /opt/redis/6380/nodes.conf   #多實例情況下需要修改,例如/6380/
cluster-node-timeout 5000


#從ping主間隔默認10秒
#複製超時時間
#repl-timeout 60

#遠距離主從
#config set client-output-buffer-limit "slave 536870912 536870912 0"
#config set repl-backlog-size 209715200
  • 第三步,啓動6個實例,./redis-server redis.conf 注意,redis.conf應爲6個不同的修改過的多實例配置文件;
  • 第四步,創建redis集羣;
[root@localhost redis-cluster]# ./redis-cli --cluster create 192.168.137.6:6310 192.168.137.6:6320 192.168.137.6:6330 192.168.137.6:6340 192.168.137.6:6350 192.168.137.6:6360 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.137.6:6350 to 192.168.137.6:6310
Adding replica 192.168.137.6:6360 to 192.168.137.6:6320
Adding replica 192.168.137.6:6340 to 192.168.137.6:6330
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 6de62fad996e29fec56265d6259e0221e07d83f2 192.168.137.6:6310
   slots:[0-5460] (5461 slots) master
M: 6b3a0a5826bf5350953a6f1a75954f5d9276b821 192.168.137.6:6320
   slots:[5461-10922] (5462 slots) master
M: 305613cdddafbb2fbbe02bda877a9337b88bc8b7 192.168.137.6:6330
   slots:[10923-16383] (5461 slots) master
S: 35d42e9e33baaef1f295cf36f88fd49766fa549b 192.168.137.6:6340
   replicates 6de62fad996e29fec56265d6259e0221e07d83f2
S: 66864b86e16a139640b52aeb1f8a95297a236edd 192.168.137.6:6350
   replicates 6b3a0a5826bf5350953a6f1a75954f5d9276b821
S: a85a97502509a7a5d53266bb9720cb176ec5f957 192.168.137.6:6360
   replicates 305613cdddafbb2fbbe02bda877a9337b88bc8b7
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....
>>> Performing Cluster Check (using node 192.168.137.6:6310)
M: 6de62fad996e29fec56265d6259e0221e07d83f2 192.168.137.6:6310
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 305613cdddafbb2fbbe02bda877a9337b88bc8b7 192.168.137.6:6330
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
M: 6b3a0a5826bf5350953a6f1a75954f5d9276b821 192.168.137.6:6320
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: a85a97502509a7a5d53266bb9720cb176ec5f957 192.168.137.6:6360
   slots: (0 slots) slave
   replicates 305613cdddafbb2fbbe02bda877a9337b88bc8b7
S: 35d42e9e33baaef1f295cf36f88fd49766fa549b 192.168.137.6:6340
   slots: (0 slots) slave
   replicates 6de62fad996e29fec56265d6259e0221e07d83f2
S: 66864b86e16a139640b52aeb1f8a95297a236edd 192.168.137.6:6350
   slots: (0 slots) slave
   replicates 6b3a0a5826bf5350953a6f1a75954f5d9276b821
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

可以看到,16384個槽已經平均分配給了3臺master實例。

命令行客戶端連接集羣

./redis-cli -p 6310 -c

注意:-c 表示是以redis集羣方式進行連接

127.0.0.1:6310> set key1 11
-> Redirected to slot [9189] located at 192.168.137.10:6320
OK
192.168.137.10:6320> 
  • 查看集羣狀態
127.0.0.1:6310> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:777
cluster_stats_messages_pong_sent:772
cluster_stats_messages_sent:1549
cluster_stats_messages_ping_received:767
cluster_stats_messages_pong_received:777
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:1549
  • 查看集羣中的節點
127.0.0.1:6310> cluster nodes
7bd6149e1350b1494e73d3e0d3623c8a5d0f6578 192.168.137.10:6340@16340 slave 8ab958bf84a6d9d30fe79ff379c9afade2587416 0 1582208322552 4 connected
550dd0cd4939a1a14cbabc7c7cf4c2cb6ce48d47 192.168.137.10:6350@16350 slave ce1707cae8882f129bf00f8b4282be9b8b875adc 0 1582208323555 5 connected
85709be5115d23f9c449a382de119cf189851fd1 192.168.137.10:6330@16330 master - 0 1582208322000 3 connected 10923-16383
ce1707cae8882f129bf00f8b4282be9b8b875adc 192.168.137.10:6320@16320 master - 0 1582208323555 2 connected 5461-10922
8ab958bf84a6d9d30fe79ff379c9afade2587416 192.168.137.10:6310@16310 myself,master - 0 1582208322000 1 connected 0-5460
e31d92d97d96bcbe43c0a1f8c33ab66540db974a 192.168.137.10:6360@16360 slave 85709be5115d23f9c449a382de119cf189851fd1 0 1582208322953 6 connected

維護節點

添加主節點

  • 先創建6370節點
  • 添加新創建的節點到集羣
[root@localhost redis-cluster]# ./redis-cli --cluster add-node 192.168.137.10:6370 192.168.137.10:6320
>>> Adding node 192.168.137.10:6370 to cluster 192.168.137.10:6320
>>> Performing Cluster Check (using node 192.168.137.10:6320)
M: ce1707cae8882f129bf00f8b4282be9b8b875adc 192.168.137.10:6320
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: e31d92d97d96bcbe43c0a1f8c33ab66540db974a 192.168.137.10:6360
   slots: (0 slots) slave
   replicates 85709be5115d23f9c449a382de119cf189851fd1
M: 85709be5115d23f9c449a382de119cf189851fd1 192.168.137.10:6330
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
M: 8ab958bf84a6d9d30fe79ff379c9afade2587416 192.168.137.10:6310
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: 550dd0cd4939a1a14cbabc7c7cf4c2cb6ce48d47 192.168.137.10:6350
   slots: (0 slots) slave
   replicates ce1707cae8882f129bf00f8b4282be9b8b875adc
S: 7bd6149e1350b1494e73d3e0d3623c8a5d0f6578 192.168.137.10:6340
   slots: (0 slots) slave
   replicates 8ab958bf84a6d9d30fe79ff379c9afade2587416
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 192.168.137.10:6370 to make it join the cluster.
[OK] New node added correctly.

新節點必須是空的,不能包含任何數據。請把之前aof和dump文件刪掉,並且若有nodes.conf也需要刪除。add-node 將一個節點添加到集羣裏面, 第一個是新節點ip:port, 第二個是任意一個已存在節點ip:port,node:新節點沒有包含任何數據, 因爲它沒有包含任何slot。

  • 查看集羣節點發現新增的6370節點
127.0.0.1:6310> cluster nodes
e305402550696f152b76d6d77fca3b8ede162033 192.168.137.10:6370@16370 master - 0 1582209128696 0 connected
7bd6149e1350b1494e73d3e0d3623c8a5d0f6578 192.168.137.10:6340@16340 slave 8ab958bf84a6d9d30fe79ff379c9afade2587416 0 1582209129198 4 connected
550dd0cd4939a1a14cbabc7c7cf4c2cb6ce48d47 192.168.137.10:6350@16350 slave ce1707cae8882f129bf00f8b4282be9b8b875adc 0 1582209130601 5 connected
85709be5115d23f9c449a382de119cf189851fd1 192.168.137.10:6330@16330 master - 0 1582209129598 3 connected 10923-16383
ce1707cae8882f129bf00f8b4282be9b8b875adc 192.168.137.10:6320@16320 master - 0 1582209130201 2 connected 5461-10922
8ab958bf84a6d9d30fe79ff379c9afade2587416 192.168.137.10:6310@16310 myself,master - 0 1582209129000 1 connected 0-5460
e31d92d97d96bcbe43c0a1f8c33ab66540db974a 192.168.137.10:6360@16360 slave 85709be5115d23f9c449a382de119cf189851fd1 0 1582209129699 6 connected

Hash槽重新分配

添加完主節點需要對主節點進行hash槽分配,這樣該主節纔可以存儲數據。

通過 cluster nodes 查看集羣中槽的佔用情況:

給剛添加的6370結點分配槽

  • 第一步:連接上集羣(連接集羣中任意一個可用結點都行),執行下面的命令

./redis-cli -p 6320 --cluster reshard 192.168.137.10:6310

  • 第二步:輸入要分配的槽數量

輸入:3000,表示要給目標節點分配3000個槽

  • 第三步:輸入接收槽的結點id

輸入:e305402550696f152b76d6d77fca3b8ede162033

PS:這裏準備給6370分配槽,通過cluster nodes查看7007結點id爲:e305402550696f152b76d6d77fca3b8ede162033

  • 第四步:輸入源結點id


輸入all。

ps: 這裏輸入源節點id,即從此節點取出槽,分配後此節點中將不再有這些槽。輸入all 從所有源節點中拿,輸入done取消分配。

  • 第五步:輸入yes開始移動槽到目標結點id

輸入:yes

添加從節點

添加 6380 從結點,將 6380 作爲 6370 的從結點

命令:

./redis-cli --cluster add-node 新節點的ip : 端口 舊節點ip : 端口 --cluster-slave --
cluster-master-id 主節點id

例如:

./redis-cli --cluster add-node 192.168.137.10:6380 192.168.137.10:6370 --cluster-slave --cluster-master-id e305402550696f152b76d6d77fca3b8ede162033

e305402550696f152b76d6d77fca3b8ede162033 是6370節點的id,可以通過 cluster nodes 命令查看.

上圖所示,即已經配置從節點成功了。

注意:如果原來該結點在集羣中的配置信息已經生成到 cluster-config-file 指定的配置文件中(如果cluster-config-file 沒有指定則默認爲nodes.conf),這時可能會報錯:

[ERR] Node XXXXXX is not empty. Either the node already knows other nodes (check
with CLUSTER NODES) or contains some key in database 0

解決方法是刪除生成的配置文件nodes.conf,刪除後再執行 ./redis-cli add-node 指令.

查看集羣中的結點,剛添加的 6380 爲 6370 的從節點:

刪除節點

命令:
./redis-cli --cluster del-node 192.168.137.10:6380 89a4504a36c37bc2f898a977aee1182d40101d49

執行完上面的刪除節點命令後,我們可以再次查看集羣節點狀況:

可以發現6380這臺從節點實例已經被刪除掉了。

看到這裏,認真思考的同學可能會想上面我們是直接刪除了從節點實例,如果要刪除主節點呢,那麼動手去嘗試下吧!

如你所願,確實出錯啦,因爲 6370 是一臺主節點,而且已經被分配了 slot (槽),即會有數據分配到這臺機器上,所以是不允許刪除有分配槽的節點的。 如果你實在想要下點集羣中的實例,那麼需要將該節點佔用的 hash 槽分配出去,參考 上面我們學習到的 Hash槽重新分配的 命令。

Jedis連接集羣

如果你跟着我從上面已經完成了redis集羣的搭建,那麼接下來是不是就應該想下我們應用中應該如何使用啦?
由於我是搞Java的,這裏就寫下如果使用Jedis連接RedisCluster的代碼,當然主流語言都是有連接Redis的工具包,就不多做贅述了。

  1. 創建一個Maven項目,這個步驟就不列了,如果有不知道的小夥伴,可以文末關注我,加我好友聯繫交流哈。
  2. 添加Jedis客戶端jar包,測試如下代碼;
# pom文件添加依賴
<dependency>  
    <groupId>redis.clients</groupId>  
    <artifactId>jedis</artifactId>  
    <version>3.1.0</version>
</dependency>
# 測試代碼
@Test
public void testJedisCluster() throws Exception {    
//創建一連接,JedisCluster對象,在系統中是單例存在     
    Set<HostAndPort> nodes = new HashSet<>();   
    nodes.add(new HostAndPort("192.168.137.10", 6310));    
    nodes.add(new HostAndPort("192.168.137.10", 6320));    
    nodes.add(new HostAndPort("192.168.137.10", 6330));    
    nodes.add(new HostAndPort("192.168.137.10", 6340));   
    nodes.add(new HostAndPort("192.168.137.10", 6450));    
    nodes.add(new HostAndPort("192.168.137.10", 6360));    
    JedisCluster cluster = new JedisCluster(nodes);     
    //執行JedisCluster對象中的方法,方法和redis一一對應。     
    cluster.set("cluster-test", "my jedis cluster test");    
    String result = cluster.get("cluster-test");     
    System.out.println(result);     
    //程序結束時需要關閉JedisCluster對象     
    cluster.close();
}

注意:執行上面代碼有可能會報網絡不可達,只要關閉redis服務器的防火牆就好了;

systemctl stop firewalld.service # 我的系統是centos7

使用Spring

  • 添加xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 連接池配置 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最大連接數 -->
        <property name="maxTotal" value="30"/> 
        <!-- 最大空閒連接數 -->
        <property name="maxIdle" value="10"/> 
        <!-- 每次釋放連接的最大數目 -->
        <property name="numTestsPerEvictionRun" value="1024"/> 
        <!-- 釋放連接的掃描間隔(毫秒) -->
        <property name="timeBetweenEvictionRunsMillis" value="30000"/> 
        <!-- 連接最小空閒時間 -->
        <property name="minEvictableIdleTimeMillis" value="1800000"/> 
        <!-- 連接空閒多久後釋放, 當空閒時間>該值 且 空閒連接>最大空閒連接數 時直接釋放 -->
        <property name="softMinEvictableIdleTimeMillis" value="10000"/>
        <!-- 獲取連接時的最大等待毫秒數,小於零:阻塞不確定的時間,默認-1 -->
        <property name="maxWaitMillis" value="1500"/> 
        <!-- 在獲取連接的時候檢查有效性, 默認false -->
        <property name="testOnBorrow" value="true"/> 
        <!-- 在空閒時檢查有效性, 默認false -->
        <property name="testWhileIdle" value="true"/> 
        <!-- 連接耗盡時是否阻塞, false報異常,ture阻塞直到超時, 默認true -->
        <property name="blockWhenExhausted" value="false"/>
    </bean> 
    <!-- redis集羣 -->
    <bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">
        <constructor-arg index="0">
            <set>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg index="0" value="192.168.137.10"></constructor-arg>
                    <constructor-arg index="1" value="6310"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg index="0" value="192.168.137.10"></constructor-arg>
                    <constructor-arg index="1" value="6320"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg index="0" value="192.168.137.10"></constructor-arg>
                    <constructor-arg index="1" value="6330"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg index="0" value="192.168.137.10"></constructor-arg>
                    <constructor-arg index="1" value="6340"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg index="0" value="192.168.137.10"></constructor-arg>
                    <constructor-arg index="1" value="6350"></constructor-arg>
                </bean>
                <bean class="redis.clients.jedis.HostAndPort">
                    <constructor-arg index="0" value="192.168.137.10"></constructor-arg>
                    <constructor-arg index="1" value="6360"></constructor-arg>
                </bean>
            </set>
        </constructor-arg>
        <constructor-arg index="1" ref="jedisPoolConfig"></constructor-arg>
    </bean>
</beans>
  • 添加 spring 的jar包
  <dependency>
    <groupId>org.springframework</groupId>
  <artifactId>spring-beans</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.2.RELEASE</version>
</dependency>
  • 測試代碼
public class RedisClusterSpring {
    private ApplicationContext applicationContext;
    @Before
    public void init() {
        applicationContext = new ClassPathXmlApplicationContext("classpath:application.xml");
    }
    @Test
    public void testJedisCluster() {
        JedisCluster jedisCluster = (JedisCluster) applicationContext.getBean("jedisCluster");
        jedisCluster.set("name","羅小黑");
        String value = jedisCluster.get("name");
        System.out.println(value);
    }
}

好了,今天主要學了Redis官方推出的集羣搭建方式 Redis-Cluster,你學會了? 如果對Redis主從模式、哨兵模式不太瞭解的同學建議閱讀筆者的這兩篇文章,學習Redis這幾個知識點是必會的,面試中也經常問Redis主從、哨兵、集羣模式的區別和共同點之類的。

Redis進階你不得不瞭解的知識點-主從複製原理
Redis哨兵機制-你不得不瞭解的知識點

如果在操作過程中有遇到問題歡迎掃碼關注加我好友,一起溝通學習,有問必答!

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