欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報邀請碼778899分享:分布式鎖詳解

柚子快報邀請碼778899分享:分布式鎖詳解

http://yzkb.51969.com/

網(wǎng)上有很多分布式鎖相關(guān)的文章,寫了一個相對簡潔易懂的版本,針對面試和工作應(yīng)該夠用了。

# 分布式鎖介紹

對于單機多線程來說,在 Java 中,我們通常使用 ReetrantLock 類、synchronized 關(guān)鍵字這類 JDK 自帶的 本地鎖 來控制一個 JVM 進程內(nèi)的多個線程對本地共享資源的訪問。

下面是我對本地鎖畫的一張示意圖。

從圖中可以看出,這些線程訪問共享資源是互斥的,同一時刻只有一個線程可以獲取到本地鎖訪問共享資源。

分布式系統(tǒng)下,不同的服務(wù)/客戶端通常運行在獨立的 JVM 進程上。如果多個 JVM 進程共享同一份資源的話,使用本地鎖就沒辦法實現(xiàn)資源的互斥訪問了。于是,分布式鎖 就誕生了。

舉個例子:系統(tǒng)的訂單服務(wù)一共部署了 3 份,都對外提供服務(wù)。用戶下訂單之前需要檢查庫存,為了防止超賣,這里需要加鎖以實現(xiàn)對檢查庫存操作的同步訪問。由于訂單服務(wù)位于不同的 JVM 進程中,本地鎖在這種情況下就沒辦法正常工作了。我們需要用到分布式鎖,這樣的話,即使多個線程不在同一個 JVM 進程中也能獲取到同一把鎖,進而實現(xiàn)共享資源的互斥訪問。

下面是我對分布式鎖畫的一張示意圖。

從圖中可以看出,這些獨立的進程中的線程訪問共享資源是互斥的,同一時刻只有一個線程可以獲取到分布式鎖訪問共享資源。

一個最基本的分布式鎖需要滿足:

互斥 :任意一個時刻,鎖只能被一個線程持有;高可用 :鎖服務(wù)是高可用的。并且,即使客戶端的釋放鎖的代碼邏輯出現(xiàn)問題,鎖最終一定還是會被釋放,不會影響其他線程對共享資源的訪問??芍厝耄阂粋€節(jié)點獲取了鎖之后,還可以再次獲取鎖。

通常情況下,我們一般會選擇基于 Redis 或者 ZooKeeper 實現(xiàn)分布式鎖,Redis 用的要更多一點,我這里也以 Redis 為例介紹分布式鎖的實現(xiàn)。

# 基于 Redis 實現(xiàn)分布式鎖

# 如何基于 Redis 實現(xiàn)一個最簡易的分布式鎖?

不論是本地鎖還是分布式鎖,核心都在于“互斥”。

在 Redis 中, SETNX 命令是可以幫助我們實現(xiàn)互斥。SETNX 即 SET if Not eXists (對應(yīng) Java 中的 setIfAbsent 方法),如果 key 不存在的話,才會設(shè)置 key 的值。如果 key 已經(jīng)存在, SETNX 啥也不做。

> SETNX lockKey uniqueValue

(integer) 1

> SETNX lockKey uniqueValue

(integer) 0

釋放鎖的話,直接通過 DEL 命令刪除對應(yīng)的 key 即可。

> DEL lockKey

(integer) 1

為了防止誤刪到其他的鎖,這里我們建議使用 Lua 腳本通過 key 對應(yīng)的 value(唯一值)來判斷。

選用 Lua 腳本是為了保證解鎖操作的原子性。因為 Redis 在執(zhí)行 Lua 腳本時,可以以原子性的方式執(zhí)行,從而保證了鎖釋放操作的原子性。

// 釋放鎖時,先比較鎖對應(yīng)的 value 值是否相等,避免鎖的誤釋放

if redis.call("get",KEYS[1]) == ARGV[1] then

return redis.call("del",KEYS[1])

else

return 0

end

這是一種最簡易的 Redis 分布式鎖實現(xiàn),實現(xiàn)方式比較簡單,性能也很高效。不過,這種方式實現(xiàn)分布式鎖存在一些問題。就比如應(yīng)用程序遇到一些問題比如釋放鎖的邏輯突然掛掉,可能會導(dǎo)致鎖無法被釋放,進而造成共享資源無法再被其他線程/進程訪問。

# 為什么要給鎖設(shè)置一個過期時間?

為了避免鎖無法被釋放,我們可以想到的一個解決辦法就是: 給這個 key(也就是鎖) 設(shè)置一個過期時間 。

127.0.0.1:6379> SET lockKey uniqueValue EX 3 NX

OK

lockKey :加鎖的鎖名;uniqueValue :能夠唯一標(biāo)示鎖的隨機字符串;NX :只有當(dāng) lockKey 對應(yīng)的 key 值不存在的時候才能 SET 成功;EX :過期時間設(shè)置(秒為單位)EX 3 標(biāo)示這個鎖有一個 3 秒的自動過期時間。與 EX 對應(yīng)的是 PX(毫秒為單位),這兩個都是過期時間設(shè)置。

一定要保證設(shè)置指定 key 的值和過期時間是一個原子操作?。?! 不然的話,依然可能會出現(xiàn)鎖無法被釋放的問題。

這樣確實可以解決問題,不過,這種解決辦法同樣存在漏洞:如果操作共享資源的時間大于過期時間,就會出現(xiàn)鎖提前過期的問題,進而導(dǎo)致分布式鎖直接失效。如果鎖的超時時間設(shè)置過長,又會影響到性能。

你或許在想: 如果操作共享資源的操作還未完成,鎖過期時間能夠自己續(xù)期就好了!

# 如何實現(xiàn)鎖的優(yōu)雅續(xù)期?

對于 Java 開發(fā)的小伙伴來說,已經(jīng)有了現(xiàn)成的解決方案:Redissonopen in new window 。其他語言的解決方案,可以在 Redis 官方文檔中找到,地址:https://redis.io/topics/distlock 。

Redisson 是一個開源的 Java 語言 Redis 客戶端,提供了很多開箱即用的功能,不僅僅包括多種分布式鎖的實現(xiàn)。并且,Redisson 還支持 Redis 單機、Redis Sentinel 、Redis Cluster 等多種部署架構(gòu)。

Redisson 中的分布式鎖自帶自動續(xù)期機制,使用起來非常簡單,原理也比較簡單,其提供了一個專門用來監(jiān)控和續(xù)期鎖的 Watch Dog( 看門狗),如果操作共享資源的線程還未執(zhí)行完成的話,Watch Dog 會不斷地延長鎖的過期時間,進而保證鎖不會因為超時而被釋放。

看門狗名字的由來于 getLockWatchdogTimeout() 方法,這個方法返回的是看門狗給鎖續(xù)期的過期時間,默認為 30 秒(redisson-3.17.6open in new window)。

//默認 30秒,支持修改

private long lockWatchdogTimeout = 30 * 1000;

public Config setLockWatchdogTimeout(long lockWatchdogTimeout) {

this.lockWatchdogTimeout = lockWatchdogTimeout;

return this;

}

public long getLockWatchdogTimeout() {

return lockWatchdogTimeout;

}

renewExpiration() 方法包含了看門狗的主要邏輯:

private void renewExpiration() {

//......

Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {

@Override

public void run(Timeout timeout) throws Exception {

//......

// 異步續(xù)期,基于 Lua 腳本

CompletionStage future = renewExpirationAsync(threadId);

future.whenComplete((res, e) -> {

if (e != null) {

// 無法續(xù)期

log.error("Can't update lock " + getRawName() + " expiration", e);

EXPIRATION_RENEWAL_MAP.remove(getEntryName());

return;

}

if (res) {

// 遞歸調(diào)用實現(xiàn)續(xù)期

renewExpiration();

} else {

// 取消續(xù)期

cancelExpirationRenewal(null);

}

});

}

// 延遲 internalLockLeaseTime/3(默認 10s,也就是 30/3) 再調(diào)用

}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

ee.setTimeout(task);

}

默認情況下,每過 10 秒,看門狗就會執(zhí)行續(xù)期操作,將鎖的超時時間設(shè)置為 30 秒??撮T狗續(xù)期前也會先判斷是否需要執(zhí)行續(xù)期操作,需要才會執(zhí)行續(xù)期,否則取消續(xù)期操作。

Watch Dog 通過調(diào)用 renewExpirationAsync() 方法實現(xiàn)鎖的異步續(xù)期:

protected CompletionStage renewExpirationAsync(long threadId) {

return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,

// 判斷是否為持鎖線程,如果是就執(zhí)行續(xù)期操作,就鎖的過期時間設(shè)置為 30s(默認)

"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +

"redis.call('pexpire', KEYS[1], ARGV[1]); " +

"return 1; " +

"end; " +

"return 0;",

Collections.singletonList(getRawName()),

internalLockLeaseTime, getLockName(threadId));

}

可以看出, renewExpirationAsync 方法其實是調(diào)用 Lua 腳本實現(xiàn)的續(xù)期,這樣做主要是為了保證續(xù)期操作的原子性。

我這里以 Redisson 的分布式可重入鎖 RLock 為例來說明如何使用 Redisson 實現(xiàn)分布式鎖:

// 1.獲取指定的分布式鎖對象

RLock lock = redisson.getLock("lock");

// 2.拿鎖且不設(shè)置鎖超時時間,具備 Watch Dog 自動續(xù)期機制

lock.lock();

// 3.執(zhí)行業(yè)務(wù)

...

// 4.釋放鎖

lock.unlock();

只有未指定鎖超時時間,才會使用到 Watch Dog 自動續(xù)期機制。

// 手動給鎖設(shè)置過期時間,不具備 Watch Dog 自動續(xù)期機制

lock.lock(10, TimeUnit.SECONDS);

如果使用 Redis 來實現(xiàn)分布式鎖的話,還是比較推薦直接基于 Redisson 來做的。

# 如何實現(xiàn)可重入鎖?

所謂可重入鎖指的是在一個線程中可以多次獲取同一把鎖,比如一個線程在執(zhí)行一個帶鎖的方法,該方法中又調(diào)用了另一個需要相同鎖的方法,則該線程可以直接執(zhí)行調(diào)用的方法即可重入 ,而無需重新獲得鎖。像 Java 中的 synchronized 和 ReentrantLock 都屬于可重入鎖。

不可重入的分布式鎖基本可以滿足絕大部分業(yè)務(wù)場景了,一些特殊的場景可能會需要使用可重入的分布式鎖。

可重入分布式鎖的實現(xiàn)核心思路是線程在獲取鎖的時候判斷是否為自己的鎖,如果是的話,就不用再重新獲取了。為此,我們可以為每個鎖關(guān)聯(lián)一個可重入計數(shù)器和一個占有它的線程。當(dāng)可重入計數(shù)器大于 0 時,則鎖被占有,需要判斷占有該鎖的線程和請求獲取鎖的線程是否為同一個。

實際項目中,我們不需要自己手動實現(xiàn),推薦使用我們上面提到的 Redisson ,其內(nèi)置了多種類型的鎖比如可重入鎖(Reentrant Lock)、自旋鎖(Spin Lock)、公平鎖(Fair Lock)、多重鎖(MultiLock)、 紅鎖(RedLock)、 讀寫鎖(ReadWriteLock)。

# Redis 如何解決集群情況下分布式鎖的可靠性?

為了避免單點故障,生產(chǎn)環(huán)境下的 Redis 服務(wù)通常是集群化部署的。

Redis 集群下,上面介紹到的分布式鎖的實現(xiàn)會存在一些問題。由于 Redis 集群數(shù)據(jù)同步到各個節(jié)點時是異步的,如果在 Redis 主節(jié)點獲取到鎖后,在沒有同步到其他節(jié)點時,Redis 主節(jié)點宕機了,此時新的 Redis 主節(jié)點依然可以獲取鎖,所以多個應(yīng)用服務(wù)就可以同時獲取到鎖。

針對這個問題,Redis 之父 antirez 設(shè)計了 Redlock 算法open in new window 來解決。

Redlock 算法的思想是讓客戶端向 Redis 集群中的多個獨立的 Redis 實例依次請求申請加鎖,如果客戶端能夠和半數(shù)以上的實例成功地完成加鎖操作,那么我們就認為,客戶端成功地獲得分布式鎖,否則加鎖失敗。

即使部分 Redis 節(jié)點出現(xiàn)問題,只要保證 Redis 集群中有半數(shù)以上的 Redis 節(jié)點可用,分布式鎖服務(wù)就是正常的。

Redlock 是直接操作 Redis 節(jié)點的,并不是通過 Redis 集群操作的,這樣才可以避免 Redis 集群主從切換導(dǎo)致的鎖丟失問題。

Redlock 實現(xiàn)比較復(fù)雜,性能比較差,發(fā)生時鐘變遷的情況下還存在安全性隱患?!稊?shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計》一書的作者 Martin Kleppmann 曾經(jīng)專門發(fā)文(How to do distributed locking - Martin Kleppmann - 2016open in new window)懟過 Redlock,他認為這是一個很差的分布式鎖實現(xiàn)。感興趣的朋友可以看看Redis 鎖從面試連環(huán)炮聊到神仙打架open in new window這篇文章,有詳細介紹到 antirez 和 Martin Kleppmann 關(guān)于 Redlock 的激烈辯論。

實際項目中不建議使用 Redlock 算法,成本和收益不成正比。

如果不是非要實現(xiàn)絕對可靠的分布式鎖的話,其實單機版 Redis 就完全夠了,實現(xiàn)簡單,性能也非常高。如果你必須要實現(xiàn)一個絕對可靠的分布式鎖的話,可以基于 ZooKeeper 來做,只是性能會差一些。

# 基于 ZooKeeper 實現(xiàn)分布式鎖

Redis 實現(xiàn)分布式鎖性能較高,ZooKeeper 實現(xiàn)分布式鎖可靠性更高。實際項目中,我們應(yīng)該根據(jù)業(yè)務(wù)的具體需求來選擇。

# 如何基于 ZooKeeper 實現(xiàn)分布式鎖?

ZooKeeper 分布式鎖是基于 臨時順序節(jié)點 和 Watcher(事件監(jiān)聽器) 實現(xiàn)的。

獲取鎖:

首先我們要有一個持久節(jié)點/locks,客戶端獲取鎖就是在locks下創(chuàng)建臨時順序節(jié)點。假設(shè)客戶端 1 創(chuàng)建了/locks/lock1節(jié)點,創(chuàng)建成功之后,會判斷 lock1是否是 /locks 下最小的子節(jié)點。如果 lock1是最小的子節(jié)點,則獲取鎖成功。否則,獲取鎖失敗。如果獲取鎖失敗,則說明有其他的客戶端已經(jīng)成功獲取鎖??蛻舳?1 并不會不停地循環(huán)去嘗試加鎖,而是在前一個節(jié)點比如/locks/lock0上注冊一個事件監(jiān)聽器。這個監(jiān)聽器的作用是當(dāng)前一個節(jié)點釋放鎖之后通知客戶端 1(避免無效自旋),這樣客戶端 1 就加鎖成功了。

釋放鎖:

成功獲取鎖的客戶端在執(zhí)行完業(yè)務(wù)流程之后,會將對應(yīng)的子節(jié)點刪除。成功獲取鎖的客戶端在出現(xiàn)故障之后,對應(yīng)的子節(jié)點由于是臨時順序節(jié)點,也會被自動刪除,避免了鎖無法被釋放。我們前面說的事件監(jiān)聽器其實監(jiān)聽的就是這個子節(jié)點刪除事件,子節(jié)點刪除就意味著鎖被釋放。

實際項目中,推薦使用 Curator 來實現(xiàn) ZooKeeper 分布式鎖。Curator 是 Netflix 公司開源的一套 ZooKeeper Java 客戶端框架,相比于 ZooKeeper 自帶的客戶端 zookeeper 來說,Curator 的封裝更加完善,各種 API 都可以比較方便地使用。

Curator主要實現(xiàn)了下面四種鎖:

InterProcessMutex:分布式可重入排它鎖InterProcessSemaphoreMutex:分布式不可重入排它鎖InterProcessReadWriteLock:分布式讀寫鎖InterProcessMultiLock:將多個鎖作為單個實體管理的容器,獲取鎖的時候獲取所有鎖,釋放鎖也會釋放所有鎖資源(忽略釋放失敗的鎖)。

CuratorFramework client = ZKUtils.getClient();

client.start();

// 分布式可重入排它鎖

InterProcessLock lock1 = new InterProcessMutex(client, lockPath1);

// 分布式不可重入排它鎖

InterProcessLock lock2 = new InterProcessSemaphoreMutex(client, lockPath2);

// 將多個鎖作為一個整體

InterProcessMultiLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2));

if (!lock.acquire(10, TimeUnit.SECONDS)) {

throw new IllegalStateException("不能獲取多鎖");

}

System.out.println("已獲取多鎖");

System.out.println("是否有第一個鎖: " + lock1.isAcquiredInThisProcess());

System.out.println("是否有第二個鎖: " + lock2.isAcquiredInThisProcess());

try {

// 資源操作

resource.use();

} finally {

System.out.println("釋放多個鎖");

lock.release();

}

System.out.println("是否有第一個鎖: " + lock1.isAcquiredInThisProcess());

System.out.println("是否有第二個鎖: " + lock2.isAcquiredInThisProcess());

client.close();

# 為什么要用臨時順序節(jié)點?

每個數(shù)據(jù)節(jié)點在 ZooKeeper 中被稱為 znode,它是 ZooKeeper 中數(shù)據(jù)的最小單元。

我們通常是將 znode 分為 4 大類:

持久(PERSISTENT)節(jié)點 :一旦創(chuàng)建就一直存在即使 ZooKeeper 集群宕機,直到將其刪除。臨時(EPHEMERAL)節(jié)點 :臨時節(jié)點的生命周期是與 客戶端會話(session) 綁定的,會話消失則節(jié)點消失 。并且,臨時節(jié)點只能做葉子節(jié)點 ,不能創(chuàng)建子節(jié)點。持久順序(PERSISTENT_SEQUENTIAL)節(jié)點 :除了具有持久(PERSISTENT)節(jié)點的特性之外, 子節(jié)點的名稱還具有順序性。比如 /node1/app0000000001 、/node1/app0000000002 。臨時順序(EPHEMERAL_SEQUENTIAL)節(jié)點 :除了具備臨時(EPHEMERAL)節(jié)點的特性之外,子節(jié)點的名稱還具有順序性。

可以看出,臨時節(jié)點相比持久節(jié)點,最主要的是對會話失效的情況處理不一樣,臨時節(jié)點會話消失則對應(yīng)的節(jié)點消失。這樣的話,如果客戶端發(fā)生異常導(dǎo)致沒來得及釋放鎖也沒關(guān)系,會話失效節(jié)點自動被刪除,不會發(fā)生死鎖的問題。

使用 Redis 實現(xiàn)分布式鎖的時候,我們是通過過期時間來避免鎖無法被釋放導(dǎo)致死鎖問題的,而 ZooKeeper 直接利用臨時節(jié)點的特性即可。

假設(shè)不適用順序節(jié)點的話,所有嘗試獲取鎖的客戶端都會對持有鎖的子節(jié)點加監(jiān)聽器。當(dāng)該鎖被釋放之后,勢必會造成所有嘗試獲取鎖的客戶端來爭奪鎖,這樣對性能不友好。使用順序節(jié)點之后,只需要監(jiān)聽前一個節(jié)點就好了,對性能更友好。

# 為什么要設(shè)置對前一個節(jié)點的監(jiān)聽?

Watcher(事件監(jiān)聽器),是 ZooKeeper 中的一個很重要的特性。ZooKeeper 允許用戶在指定節(jié)點上注冊一些 Watcher,并且在一些特定事件觸發(fā)的時候,ZooKeeper 服務(wù)端會將事件通知到感興趣的客戶端上去,該機制是 ZooKeeper 實現(xiàn)分布式協(xié)調(diào)服務(wù)的重要特性。

同一時間段內(nèi),可能會有很多客戶端同時獲取鎖,但只有一個可以獲取成功。如果獲取鎖失敗,則說明有其他的客戶端已經(jīng)成功獲取鎖。獲取鎖失敗的客戶端并不會不停地循環(huán)去嘗試加鎖,而是在前一個節(jié)點注冊一個事件監(jiān)聽器。

這個事件監(jiān)聽器的作用是: 當(dāng)前一個節(jié)點對應(yīng)的客戶端釋放鎖之后(也就是前一個節(jié)點被刪除之后,監(jiān)聽的是刪除事件),通知獲取鎖失敗的客戶端(喚醒等待的線程,Java 中的 wait/notifyAll ),讓它嘗試去獲取鎖,然后就成功獲取鎖了。

# 如何實現(xiàn)可重入鎖?

這里以 Curator 的 InterProcessMutex 對可重入鎖的實現(xiàn)來介紹(源碼地址:InterProcessMutex.javaopen in new window)。

當(dāng)我們調(diào)用 InterProcessMutex#acquire方法獲取鎖的時候,會調(diào)用InterProcessMutex#internalLock方法。

// 獲取可重入互斥鎖,直到獲取成功為止

@Override

public void acquire() throws Exception {

if (!internalLock(-1, null)) {

throw new IOException("Lost connection while trying to acquire lock: " + basePath);

}

}

internalLock 方法會先獲取當(dāng)前請求鎖的線程,然后從 threadData( ConcurrentMap 類型)中獲取當(dāng)前線程對應(yīng)的 lockData 。 lockData 包含鎖的信息和加鎖的次數(shù),是實現(xiàn)可重入鎖的關(guān)鍵。

第一次獲取鎖的時候,lockData為 null。獲取鎖成功之后,會將當(dāng)前線程和對應(yīng)的 lockData 放到 threadData 中

private boolean internalLock(long time, TimeUnit unit) throws Exception {

// 獲取當(dāng)前請求鎖的線程

Thread currentThread = Thread.currentThread();

// 拿對應(yīng)的 lockData

LockData lockData = threadData.get(currentThread);

// 第一次獲取鎖的話,lockData 為 null

if (lockData != null) {

// 當(dāng)前線程獲取過一次鎖之后

// 因為當(dāng)前線程的鎖存在, lockCount 自增后返回,實現(xiàn)鎖重入.

lockData.lockCount.incrementAndGet();

return true;

}

// 嘗試獲取鎖

String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());

if (lockPath != null) {

LockData newLockData = new LockData(currentThread, lockPath);

// 獲取鎖成功之后,將當(dāng)前線程和對應(yīng)的 lockData 放到 threadData 中

threadData.put(currentThread, newLockData);

return true;

}

return false;

}

LockData是 InterProcessMutex中的一個靜態(tài)內(nèi)部類。

private final ConcurrentMap threadData = Maps.newConcurrentMap();

private static class LockData

{

// 當(dāng)前持有鎖的線程

final Thread owningThread;

// 鎖對應(yīng)的子節(jié)點

final String lockPath;

// 加鎖的次數(shù)

final AtomicInteger lockCount = new AtomicInteger(1);

private LockData(Thread owningThread, String lockPath)

{

this.owningThread = owningThread;

this.lockPath = lockPath;

}

}

如果已經(jīng)獲取過一次鎖,后面再來獲取鎖的話,直接就會在 if (lockData != null) 這里被攔下了,然后就會執(zhí)行l(wèi)ockData.lockCount.incrementAndGet(); 將加鎖次數(shù)加 1。

整個可重入鎖的實現(xiàn)邏輯非常簡單,直接在客戶端判斷當(dāng)前線程有沒有獲取鎖,有的話直接將加鎖次數(shù)加 1 就可以了。

# 總結(jié)

這篇文章我們介紹了分布式鎖的基本概念以及實現(xiàn)分布式鎖的兩種常見方式。至于具體選擇 Redis 還是 ZooKeeper 來實現(xiàn)分布式鎖,還是要看業(yè)務(wù)的具體需求。如果對性能要求比較高的話,建議使用 Redis 實現(xiàn)分布式鎖。如果對可靠性要求比較高的話,建議使用 ZooKeeper 實現(xiàn)分布式鎖。

柚子快報邀請碼778899分享:分布式鎖詳解

http://yzkb.51969.com/

精彩內(nèi)容

評論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。

轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://m.gantiao.com.cn/post/19148669.html

發(fā)布評論

您暫未設(shè)置收款碼

請在主題配置——文章設(shè)置里上傳

掃描二維碼手機訪問

文章目錄