亚洲国产成人精品久久_欧美日韩国产综合一区二区_亚洲精品理论电影_色综合久久中文字幕

不這樣做,同名互踢容易踩坑!

放大字體  縮小字體 發(fā)布日期:2019-12-30  來源:來自互聯(lián)網(wǎng)  作者:來自互聯(lián)網(wǎng)  瀏覽次數(shù):546
導(dǎo)讀

set

作者 | 張超

責(zé)編 | 伍杏玲

出品 | CSDN(ID:CSDNnews)

最近我在做連接服務(wù)的結(jié)構(gòu)調(diào)整,梳理到其中的一個(gè)功能點(diǎn):同賬號(hào)連續(xù)登錄,舊的連接會(huì)被新的連接踢掉的功能。

第一個(gè)感覺這是個(gè)簡(jiǎn)單的需求點(diǎn),不就是新的建立好了,然后把舊的連接踢掉?

可仔細(xì)想想就會(huì)發(fā)現(xiàn)一個(gè)問題:是在新的連接建立好了再踢掉舊的連接還是先踢掉舊的連接,然后再建立新的連接呢?如果存在多個(gè)同時(shí)進(jìn)行的新連接呢,是不是就有數(shù)據(jù)競(jìng)爭(zhēng)了?如果集群要是有多個(gè)機(jī)房,多個(gè)集群該如何高效的實(shí)現(xiàn)這個(gè)功能點(diǎn)?

如何既要考慮功能的實(shí)現(xiàn),又要考慮工程實(shí)現(xiàn)的復(fù)雜程度以及后續(xù)的維護(hù)成本,是否滿足對(duì)服務(wù)的整體服務(wù)質(zhì)量的要求?在本文中,我和大家一起探討下。

功能需求的描述

1、新連接能夠正常的進(jìn)行操作。

一般的操作是新連接會(huì)覆蓋舊連接,在這種情況下,不會(huì)影響新連接的所有工作。在通常的實(shí)現(xiàn)下,確實(shí)是這樣的,覆蓋之后肯定舊連接沒法正常找到,所以發(fā)送給連接的數(shù)據(jù)能夠正常到達(dá)新的連接上。

但有些系統(tǒng)為了保證連接的活躍性,采用的連接是定時(shí)進(jìn)行刷新自己的連接信息。在這種情況下,就會(huì)出現(xiàn)新連接被覆蓋的情況,具體關(guān)于連接的維護(hù)的后續(xù)的文章中在詳細(xì)的介紹。

2、舊連接不能再發(fā)送或者接收任何的數(shù)據(jù)。

實(shí)現(xiàn)方案

下面介紹幾種解決方案,并進(jìn)行優(yōu)缺點(diǎn)比較。該文章中的數(shù)據(jù)均是基于Erlang版本,其他的編程語言除去第一種之外都是一樣的。

一、利用可比較的程序標(biāo)識(shí)實(shí)現(xiàn):

%% 新創(chuàng)建連接的邏輯

open_session(SID, User, Server, Resource, Priority, Info) ->

set_session(SID, User, Server, Resource, Priority, Info),

check_for_sessions_to_replace(User, Server, Resource),

JID = jid:make(User, Server, Resource),

ejabberd_hooks:run(sm_register_connection_hook,

JID#jid.lserver, [SID, JID, Info]).

%% 設(shè)置新的連接信息,新的連接信息是可以存儲(chǔ)在不同的數(shù)據(jù)庫(kù)中,目前在ejabberd的實(shí)現(xiàn)中就

%% 已經(jīng)支持了mnesia,redis,mysql,postgresql等數(shù)據(jù)庫(kù)的實(shí)現(xiàn)

set_session(SID, User, Server, Resource, Priority, Info) ->

LUser = jid:nodeprep(User),

LServer = jid:nameprep( Server),

LResource = jid:resourceprep(Resource),

US = {LUser, LServer},

USR = {LUser, LServer, LResource},

set_session(#session{sid = SID, usr = USR, us = US,

priority = Priority, info = Info}).

-spec set_session(#session{}) -> ok | { error, any}.

set_session(#session{us = {LUser, LServer}} = Session) ->

Mod = get_sm_backend(LServer),

caseMod:set_session(Session) of

ok ->

caseuse_cache(Mod, LServer) of

true->

ets_cache:delete(?SM_CACHE, {LUser, LServer},

cache_nodes(Mod, LServer));

false->

ok

end;

{ error, _} = Err->

Err

end.

%% 檢查已經(jīng)存在的連接,如果檢測(cè)到連接已經(jīng)是離線的狀態(tài),就直接進(jìn)行刪除相應(yīng)的連接信息

%% 連接是在線的狀態(tài)的話,則保留最大的連接,其他的連接則踢掉(這里通過下面的replaced消息處理)

check_for_sessions_to_replace(User, Server, Resource) ->

LUser = jid:nodeprep(User),

LServer = jid:nameprep( Server),

LResource = jid:resourceprep(Resource),

check_existing_resources(LUser, LServer, LResource),

check_max_sessions(LUser, LServer).

-spec check_existing_resources(binary, binary, binary) -> ok.

check_existing_resources(LUser, LServer, LResource) ->

Mod = get_sm_backend(LServer),

Ss = get_sessions(Mod, LUser, LServer, LResource),

ifSs == [] -> ok;

true->

SIDs = [SID || #session{sid = SID} <- Ss],

MaxSID = lists:max(SIDs),

lists:foreach(fun ({_, Pid} = S) when S /= MaxSID ->

ejabberd_c2s:route(Pid, replaced);

(_) -> ok

end,

SIDs)

end.

%% 生成連接的唯一ID,這里加入了時(shí)間戳,而且是單調(diào)遞增的數(shù)據(jù)

%% self加入到sid中,能夠保證sid的唯一性,pid在erlang中是能夠保證唯一性的,具體的

%% 實(shí)現(xiàn)原理有興趣可以自行查找

make_sid ->

{misc:unique_timestamp, self}.

%% misc.erl

unique_timestamp ->

{MS, S, _} = erlang:timestamp,

{MS, S, erlgang:unique_integer([positive, monotonic]) rem1000000}.

原理說明:

1. 設(shè)置新的連接信息是這段代碼中的set_session方法;

2. 查找已經(jīng)存在的連接是代碼中的get_sessions,并沒有列出完整的實(shí)現(xiàn);

3. 踢掉舊的連接,通過上一步查到的所有連接,根據(jù)連接的大小關(guān)系,除了最大的連接保留之外,其他的所有連接都執(zhí)行踢掉的邏輯。

關(guān)鍵點(diǎn)的說明:

2. 在連接信息中添加了一個(gè)可進(jìn)行比較的字段sid,有了這規(guī)則之后,無論怎樣的踢掉操作都能夠保證競(jìng)爭(zhēng)的雙方保留的結(jié)果是一致的。即使在競(jìng)爭(zhēng)的情況下,也不會(huì)出現(xiàn)刪除錯(cuò)誤的情況。

3. 下面列舉了一些可能的時(shí)序場(chǎng)景:

(1)正常的登錄場(chǎng)景

(2)登錄過程在setSession之后之后被其他的登錄過程打斷:

(3)新的登錄過程插入到另一個(gè)登錄過程中:

(4)雖然業(yè)務(wù)是A是先執(zhí)行setSession但是,但是寫入的sid卻比后寫入的sid要大,比如A在寫入時(shí)網(wǎng)絡(luò)延時(shí)很大等情況都有可能出現(xiàn)如下的情況:

二、利用獨(dú)立的鎖結(jié)構(gòu)實(shí)現(xiàn)

%% 1.獲取新鍵連接的鎖,獲取到相應(yīng)的鎖之后

%% 2.踢掉之前已經(jīng)存在的舊連接

%% 3.初始化新的連接

open_session( true, ClientInfo = #{clientid := ClientId}, ConnInfo) ->

CleanStart = fun(_)->

ok = discard_session(ClientId),

%% 具體的初始化新連接的具體邏輯

Session = emqx_session:init(ClientInfo, ConnInfo),

{ok, #{session => Session, present => false}}

end,

%% 這里沒有列舉獲取鎖的相關(guān)過程,處理完所有的邏輯之后,釋放獲取的鎖

emqx_cm_locker:trans(ClientId, CleanStart);

%% 根據(jù)連接的ClientID找到具體的ChanPid,然后循環(huán)執(zhí)行discard_session

discard_session(ClientId) whenis_binary(ClientId) ->

case lookup_channels(ClientId) of

[] -> ok;

ChanPids ->

lists:foreach(

fun(ChanPid)->

try

discard_session(ClientId, ChanPid)

catch

_:Error:_Stk ->

?LOG(error, "Failed to discard ~p: ~p", [ChanPid, Error])

end

end, ChanPids)

end.

%% 具體的操作踢掉的邏輯

discard_session(ClientId, ChanPid) whennode(ChanPid) == node ->

case get_chan_attrs(ClientId, ChanPid) of

#{conninfo := #{conn_mod := ConnMod}} ->

ConnMod:call(ChanPid, discard);

undefined -> ok

end;

原理說明如下:

1. 根據(jù)連接的標(biāo)識(shí)(ClientID)來獲取對(duì)應(yīng)的鎖,該鎖是排他的,emqx的代碼中的鎖是程序?qū)崿F(xiàn)通過集群中廣播的方式。

在集群中維護(hù)著對(duì)應(yīng)鎖,對(duì)應(yīng)的鎖可以借助其他的數(shù)據(jù)庫(kù),比如Redis等方式實(shí)現(xiàn)同樣的鎖的功能。不過要注意鎖的釋放,否則將會(huì)影響正常的用戶登錄。

2. 查找已經(jīng)存在的連接,踢掉相應(yīng)的連接。

3. 初始化新創(chuàng)建的連接。

討論方案的對(duì)比

以上兩種實(shí)現(xiàn)是現(xiàn)在使用相對(duì)較多的長(zhǎng)連接服務(wù)的登錄,并且踢掉舊連接的邏輯。

1. 功能實(shí)現(xiàn):

兩種方案都能完整地實(shí)現(xiàn)新連接踢掉舊連接的過程。

2. 實(shí)現(xiàn)的復(fù)雜度:

Ejabberd的實(shí)現(xiàn)通過添加一個(gè)可比較的sid的方式,保留最大sid的連接。在存在競(jìng)爭(zhēng)的情況下,依然能保證邏輯的正確性;EMQX采用的是比較常見的加鎖的方式來避免競(jìng)爭(zhēng)帶來的數(shù)據(jù)不一致性問題。

總的來說,實(shí)現(xiàn)的復(fù)雜度Ejabberd相對(duì)復(fù)雜一些,這里不包括EMQX實(shí)現(xiàn)的鎖功能的復(fù)雜度。

3. 理解的難易程度:

Ejabberd的理解難度相對(duì)較大,因?yàn)橐紤]多種情況下的服務(wù)表現(xiàn)是否符合預(yù)期;EMQX通過直接加鎖的方式來避免了這些竟態(tài)的出現(xiàn)。Ejabberd的理解難度更大。

4. 數(shù)據(jù)存儲(chǔ)方面:

Ejabberd的設(shè)計(jì)是同一個(gè)用戶的連接信息在數(shù)據(jù)庫(kù)是有一組的(不同的sid),而EMQX的存儲(chǔ)只會(huì)有一個(gè)(其中 ClientID 為唯一性的key)。

從這方面來看,EMQX在存儲(chǔ)上會(huì)有一定的優(yōu)勢(shì),在沒有考慮對(duì)鎖的存儲(chǔ)需求下。

5. 集群擴(kuò)展性:

Ejabberd由于做到了無狀態(tài)的新創(chuàng)建連接,能進(jìn)行很好地?cái)U(kuò)展。EMQX由于實(shí)現(xiàn)是在集群中進(jìn)行全局鎖的情況,機(jī)器的擴(kuò)展會(huì)加大獲取鎖的成本,雖然采用并發(fā)調(diào)用的邏輯,但是在網(wǎng)絡(luò)等不穩(wěn)定的條件下,可能會(huì)出現(xiàn)獲取鎖比較慢,或者失敗的情況,所以整體的可擴(kuò)展性會(huì)較差一些。

多機(jī)房、多集群的部署方案

在多機(jī)房、多集群的部署形態(tài)下,由于連接的信息都需要進(jìn)行跨機(jī)房的訪問,會(huì)存在很大的延時(shí)和挑戰(zhàn)。

然而多機(jī)房的部署主要是提供多機(jī)房的活躍互相備份,以保證在其中任何一個(gè)機(jī)房出現(xiàn)問題也不影響整體的服務(wù)。

所以如果是在多機(jī)房的模式下,建議通過負(fù)載的方式來將同一個(gè)賬號(hào)連接到一個(gè)機(jī)房/集群,這樣問題就重新降到解決一個(gè)集群中的踢掉問題。

結(jié)語

登錄連接這個(gè)看似簡(jiǎn)單、但又不簡(jiǎn)單的需求,其中涉及到對(duì)服務(wù)的預(yù)期和要求。我們可采用結(jié)構(gòu)簡(jiǎn)單的加鎖方式來實(shí)現(xiàn),也可以通過在業(yè)務(wù)層通過自己的選擇策略來進(jìn)行踢出連接的操作。

選擇策略時(shí)要保證分布式情況下保持一致(切忌將除了自己之外的其他連接都踢掉,這樣就會(huì)出現(xiàn)多個(gè)連接的選擇策略不同)。實(shí)現(xiàn)功能的方式可以是多種選擇的,關(guān)鍵看具體的需求是什么樣,選擇一種適合團(tuán)隊(duì)的方式才是最重要的。

傳送門:

EMQX源碼:https://github/emqx/emqx

Ejabberd源碼:https://github/processone/ejabberd

作者簡(jiǎn)介:張超,360 IoT 云連接服務(wù)技術(shù)負(fù)責(zé)人,畢業(yè)于南京大學(xué)。曾從事過游戲開發(fā)、IM 服務(wù)端開發(fā),目前從事物聯(lián)網(wǎng)接入層相關(guān)工作。

 
 
免責(zé)聲明
本文為會(huì)員免費(fèi)發(fā)布,僅代表發(fā)布者個(gè)人觀點(diǎn),本站未對(duì)其內(nèi)容進(jìn)行核實(shí),請(qǐng)讀者僅做參考,如若文中涉及有違公德、觸犯法律的內(nèi)容,一經(jīng)發(fā)現(xiàn),立即刪除,作者需自行承擔(dān)相應(yīng)責(zé)任。涉及到版權(quán)或其他問題,請(qǐng)及時(shí)聯(lián)系我們刪除處理。
 
 
主站蜘蛛池模板: 亚洲一区二区三区免费观看| 国产99视频在线观看| 午夜精品在线视频| 国产日韩欧美91| 欧美激情久久久久久| 日韩免费黄色av| 日韩视频 中文字幕| 国产精品大全| 99精品免费在线观看| 九九热精品视频| 国产综合香蕉五月婷在线| 国语精品免费视频| 日本国产高清不卡| 久久精品ww人人做人人爽| 日本一区二区三区免费看| 中文字幕制服丝袜在线| 日韩在线一区二区三区免费视频| 国产精品美女久久久久av福利| 九九热精品在线| 黄色国产精品一区二区三区| 久久婷婷国产精品| 欧美日韩国产va另类| 精品国产依人香蕉在线精品| 日本一区二区三区精品视频| 99精品国产一区二区| 丰满少妇久久久| 国产精品日韩欧美综合| 国产精品永久免费在线| 久久天天狠狠| 国产精品 欧美在线| 国产精品一久久香蕉国产线看观看| 精品中文字幕在线| 国严精品久久久久久亚洲影视| 国产精品一香蕉国产线看观看| 国产精品久久久久久久久免费看| 国产日韩欧美视频| 国产精品中文字幕在线| 成人国产精品日本在线| 国产激情久久久| 国产精品91久久| 成人av中文|