使用RabbitMQ優(yōu)化用戶登錄功能
1. 前言
Hello,大家好。通過上述幾個(gè)小節(jié)的介紹,我們已經(jīng)對(duì) RabbitMQ 中的消息發(fā)送模式有了代碼實(shí)現(xiàn)層面的了解,這些實(shí)操代碼是應(yīng)用 RabbitMQ 基礎(chǔ)中的基礎(chǔ),同學(xué)們必須要掌握并記憶。
那么,從本節(jié)開始,會(huì)為大家?guī)?,在我們的?shí)際的一個(gè)日常開發(fā)工作中,針對(duì)常用的幾個(gè)功能,我們應(yīng)該如何使用 RabbitMQ 來進(jìn)行實(shí)現(xiàn),同時(shí),通過對(duì)常用功能的 RabbitMQ 集成,可以提升傳統(tǒng)功能實(shí)現(xiàn)中的一些性能等指標(biāo),這對(duì)后續(xù)傳統(tǒng)功能的優(yōu)化是很有必要的。
本小節(jié)會(huì)為同學(xué)們介紹,如何使用 RabbitMQ 消息中間件,去優(yōu)化我們使用傳統(tǒng)的方式,來實(shí)現(xiàn)的用戶登錄功能點(diǎn),包括對(duì)傳統(tǒng)用戶登錄功能的概述,以及使用 RabbitMQ 進(jìn)行優(yōu)化的關(guān)鍵步驟,希望同學(xué)們可以對(duì)本節(jié)內(nèi)容有所了解。
本節(jié)主要內(nèi)容:
-
傳統(tǒng)用戶登錄功能概述;
-
使用 RabbitMQ 優(yōu)化傳統(tǒng)用戶登錄功能。
2.傳統(tǒng)用戶登錄功能概述
2.1 傳統(tǒng)實(shí)現(xiàn)方案介紹
眾所周知,我們現(xiàn)在的信息化系統(tǒng)項(xiàng)目,項(xiàng)目的首要功能模塊,基本上都會(huì)要求我們來設(shè)計(jì)并實(shí)現(xiàn)一個(gè)完備的用戶模塊,而這個(gè)用戶模塊中,必定會(huì)包含用戶注冊與用戶登錄功能點(diǎn)。
只有在項(xiàng)目中實(shí)現(xiàn)了用戶注冊與用戶登錄這兩個(gè)最基本的功能點(diǎn),我們的項(xiàng)目功能才能繼續(xù)向后進(jìn)行開發(fā),因?yàn)轫?xiàng)目后續(xù)的功能,或多或少都會(huì)與這兩個(gè)功能點(diǎn)相聯(lián)系。
所以,這就要求我們在實(shí)現(xiàn)用戶注冊與用戶登錄功能時(shí),要從多方面進(jìn)行考慮, 如果有一點(diǎn)我們沒有考慮到,很可能在進(jìn)行項(xiàng)目后續(xù)功能開發(fā)時(shí),還要回過頭去,修改這兩個(gè)基本功能,從而與項(xiàng)目后續(xù)的功能相結(jié)合。
那么,傳統(tǒng)的用戶登錄功能的實(shí)現(xiàn)方案是什么樣的呢?
傳統(tǒng)用戶登錄功能流程介紹
我們首先接觸用戶登錄功能或者用戶注冊功能,有絕大部分同學(xué)是在自己的學(xué)校內(nèi),也就是學(xué)校老師教授我們的內(nèi)容,往往這種實(shí)現(xiàn)方式都會(huì)存在諸多問題,下面讓我們來看一下這種方式是如何實(shí)現(xiàn)的。
針對(duì)用戶登錄功能,對(duì)于后端開發(fā)的同學(xué),我們首先需要接收前端傳遞過來的數(shù)據(jù),也就是用戶的登錄數(shù)據(jù),然后我們會(huì)根據(jù)這一用戶登錄數(shù)據(jù)來進(jìn)行相應(yīng)的邏輯檢驗(yàn)。
這些邏輯校驗(yàn)包括對(duì)用戶的名稱進(jìn)行檢測,以查驗(yàn)當(dāng)前要登錄的用戶是否在我們的系統(tǒng)中存在,如果不存在,則提示用戶不存在與本系統(tǒng)中,如果存在,則檢測用戶所輸入的登錄密碼是否與用戶注冊時(shí)所填寫的一致,如果不一致,則提示用戶,用戶登錄密碼錯(cuò)誤。
如果當(dāng)前要登錄的用戶在本系統(tǒng)中存在,且用戶所輸入的登錄密碼與用戶注冊時(shí)所填寫的登錄密碼一致,則表明用戶登錄成功,此時(shí),需要提示用戶登錄成功,并跳轉(zhuǎn)到我們的系統(tǒng)首頁。下面,我們來通過一個(gè)功能流程圖來體現(xiàn)上述的業(yè)務(wù)流轉(zhuǎn)過程:

通過上述功能流程圖,我們可以很清楚地理解上述傳統(tǒng)用戶登錄功能的實(shí)現(xiàn)過程,如果沒有理解的同學(xué),建議多看幾次,理解這一流程之后我們再繼續(xù)學(xué)習(xí)下面的內(nèi)容。
傳統(tǒng)用戶登錄功能代碼實(shí)現(xiàn)
以 SpringBoot 框架為例,我們來實(shí)現(xiàn)一下上述的用戶登錄功能,實(shí)現(xiàn)代碼如下所示:
實(shí)現(xiàn)代碼:
public Response<User> userLogin(User user, HttpSession session){
int userExistsCount = userMapper.selectUserExixtsByUsername(user.getUsername());
if (userExistsCount <= 0){
return Response.createByError("用戶不存在");
}
User userLogin = userMapper.selectLoginUserByUsernamePassword(user.getUsername(), user.getPassword());
if (userLogin != null){
userLogin.setPassword("");
session.setAttribute("loginUser", userLogin);
return Response.createBySuccess("登錄成功", userLogin);
}
return Response.createByError("用戶登錄密碼輸入錯(cuò)誤,請重新輸入");
}
代碼解釋:
第 1 行,我們定義了一個(gè)名為 userLogin 的方法來處理用戶登錄的業(yè)務(wù)邏輯,該方法的返回值是 Response 類型,并且泛型被指定為 User 類型,這表明,我們的方法最終會(huì)返回 User 用戶數(shù)據(jù)。
在該方法中,我們定義了兩個(gè)參數(shù),分別是 User 類型的 user 參數(shù),以及 HttpSession 類型的 session 參數(shù)。其中,user 參數(shù)就表示用戶登錄時(shí)所填入的登錄數(shù)據(jù),session 參數(shù)則表示存儲(chǔ)用戶登錄狀態(tài)和登錄數(shù)據(jù)的參數(shù)。
第 2-5 行,我們通過獲取到用戶的用戶名,并且調(diào)用數(shù)據(jù)庫 ORM 層的方法,來對(duì)系統(tǒng)數(shù)據(jù)庫進(jìn)行查詢,查詢當(dāng)前用戶名是否存在我們的數(shù)據(jù)庫中,如果存在,則表明用戶注冊過我們的系統(tǒng),即這個(gè)用戶是在我們的系統(tǒng)中的。如果不存在,則會(huì)提示用戶當(dāng)前用戶名不存在。
第 6-14 行,我們通過獲取到的用戶的用戶名和用戶登錄密碼,調(diào)用數(shù)據(jù)庫 ORM 層的犯法,來對(duì)系統(tǒng)數(shù)據(jù)庫進(jìn)行查詢,查詢當(dāng)前用戶名和密碼是否在系統(tǒng)中匹配。
即,只有用戶名匹配的話是不行的,還要將這個(gè)用戶名和其所對(duì)應(yīng)的密碼進(jìn)行匹配,當(dāng)使用這兩個(gè)條件進(jìn)行匹配查詢,且可以正確查詢到結(jié)果時(shí),表明用戶已經(jīng)登錄成功了,此時(shí),我們可以將用戶登錄的數(shù)據(jù)放到 session 中,以備后續(xù)功能的使用,與此同時(shí),我們還應(yīng)該提示用戶登錄成功了。
如果根據(jù)這兩個(gè)查詢條件,沒有查詢到任何數(shù)據(jù),即 userLogin 變量為 null 時(shí),則表明當(dāng)前用戶名是存在的,但是當(dāng)前登錄用戶輸入的登錄密碼和注冊時(shí)所輸入的是不一樣的,此時(shí),系統(tǒng)會(huì)提示用戶,用戶登錄密碼錯(cuò)誤,可以進(jìn)行重試。
Tips:
1. 在我們的 userLogin 方法中存在一個(gè)參數(shù) session ,我們只是對(duì) session 進(jìn)行了簡單介紹,因?yàn)檫@不屬于我們的重點(diǎn)內(nèi)容,所以,有不了解的同學(xué)可以自行查閱資料來了解;
2. 可以看到,在 userLogin 方法中,存在一行 userLogin.setPassword("") ,這句代碼的作用是將用戶登錄成功后的密碼設(shè)置為空,并不會(huì)同步更新數(shù)據(jù)庫,并將 userLogin 用戶數(shù)據(jù)返回給前端,出于安全考慮,這里將密碼設(shè)置為空了,前端是獲取不到用戶的登錄密碼的;
3. userLogin 方法中所使用的 userMapper 為 MyBatis 數(shù)據(jù)庫 ORM 框架所定義的內(nèi)容,有不了解的同學(xué)可以自行查閱資料進(jìn)行了解,本小節(jié)不會(huì)對(duì)其進(jìn)行介紹。
通過上述代碼的編寫,我們基本上實(shí)現(xiàn)了傳統(tǒng)的用戶登錄功能,但是這種登錄功能是最簡單的實(shí)現(xiàn),完全沒有考慮其他因素,如果我們在實(shí)際項(xiàng)目中這樣來實(shí)現(xiàn),那么在開發(fā)后續(xù)功能時(shí),必定會(huì)出現(xiàn)一些意料之中的問題。
那么,我們采用這種實(shí)現(xiàn)方式所實(shí)現(xiàn)的用戶登錄功能到底有哪些弊端呢?下面就讓我們一探究竟。
2.2 傳統(tǒng)實(shí)現(xiàn)方案中存在的弊端
整體來說,我們傳統(tǒng)的實(shí)現(xiàn)用戶登錄功能的業(yè)務(wù)邏輯還是正確的, 不管我們再怎么對(duì)用戶登錄功能進(jìn)行優(yōu)化,這個(gè)傳統(tǒng)的用戶登錄功能業(yè)務(wù)流程始終不會(huì)發(fā)生改變,發(fā)生改變的只是我們的代碼實(shí)現(xiàn)層。
那么,傳統(tǒng)實(shí)現(xiàn)方案中存在的弊端,也就存在于我們的功能代碼實(shí)現(xiàn)層上。
弊端一 用戶登錄狀態(tài)的處理
對(duì)于上述代碼來說,我們處理用戶登錄狀態(tài)所采用的手段,是通過將用戶登錄成功后的數(shù)據(jù)存放在服務(wù)端中的 session 中,后續(xù)如果需要使用到用戶登錄狀態(tài),我們可以直接對(duì) session 進(jìn)行判斷,并返回相應(yīng)的判斷結(jié)果。
但是,這種模式如果遇到了前后端分離的項(xiàng)目,我們就無法再使用 session 了,因?yàn)榍岸丝蚣懿荒苤苯硬倏匚覀兒蠖说?session ,所以,我們就要把 session 替換掉,替換成前后端均可操控的手段來實(shí)現(xiàn)。
弊端二 高并發(fā)環(huán)境下容易出現(xiàn)的問題
如果我們的項(xiàng)目在使用過程中出現(xiàn)了用戶激增的情況,即會(huì)有越來越多的人來訪問我們的用戶登錄功能,如果我們只是采用上述的實(shí)現(xiàn)方式來處理,不會(huì)這種情況進(jìn)行考慮,那么我們的用戶登錄功能就會(huì)在非常短的一段時(shí)間過后癱瘓,我們的用戶登錄功能不會(huì)再返回任何響應(yīng)數(shù)據(jù)。
這就是高并發(fā)環(huán)境下傳統(tǒng)的用戶登錄功能最容易出現(xiàn)問題的地方,而這一地方則體現(xiàn)在用戶登錄前和用登錄后的數(shù)據(jù)不一致問題上。
即,在高并發(fā)環(huán)境下,我們的用戶 A 進(jìn)行了登錄,與此同時(shí),我們的用戶 B 也進(jìn)行了登錄,這兩個(gè)用戶進(jìn)行登錄的時(shí)刻恰好重疊了,由于我們并沒有對(duì)這種場景進(jìn)行處理,所以,在這兩個(gè)用戶登錄之后,就有可能出現(xiàn)返回給用戶 A 的用戶數(shù)據(jù),其實(shí)是用戶 B 的。
除了數(shù)據(jù)不一致問題,還有一種問題也比較容易出現(xiàn),那就是當(dāng)我們的用戶數(shù)激增時(shí),同一時(shí)刻進(jìn)行登錄的用戶也隨之激增,我們的用戶登錄功能接口無法在一瞬間響應(yīng)這么多的用戶登錄請求,導(dǎo)致了后續(xù)登錄的用戶只能等待,只能等待當(dāng)先用戶登錄完成之后,才能進(jìn)行登錄。
這個(gè)等待過程是不確定的,可能很長,也可能很短,這就對(duì)用戶的體驗(yàn)造成了非常嚴(yán)重的不良影響。
上述就是兩個(gè)在傳統(tǒng)用戶登錄功能中,出現(xiàn)的顯而易見的問題,我們會(huì)對(duì)這兩個(gè)功能進(jìn)行優(yōu)化,并且會(huì)使用 RabbitMQ 對(duì)第二個(gè)弊端進(jìn)行優(yōu)化。
3 使用 RabbitMQ 優(yōu)化傳統(tǒng)用戶登錄功能
3.1 優(yōu)化用戶登錄狀態(tài)的處理問題
前面我們已經(jīng)介紹了,在傳統(tǒng)用戶登錄功能中,當(dāng)我們的項(xiàng)目架構(gòu)采用前后端分離的架構(gòu)方式來管理時(shí),出現(xiàn)的用戶登錄狀態(tài)的問題,那么我們可以怎樣來優(yōu)化這個(gè)問題呢?
優(yōu)化出現(xiàn)的用戶登錄狀態(tài)問題,不需要我們使用 RabbitMQ 來處理,我們可以將 session 參數(shù)使用一種 token 機(jī)制來替代掉。
即在處理用戶狀態(tài)時(shí),我們不使用 session 來存儲(chǔ),而是使用 token 來進(jìn)行存儲(chǔ),這個(gè) token 我們一般會(huì)使用 JWT 框架來生成,并為生成的 token 設(shè)置一個(gè)有效期。
這樣一來,當(dāng)前端發(fā)來用戶登錄請求時(shí),就會(huì)將用戶登錄成功的 token 進(jìn)行傳遞,然后我們后臺(tái)需要接收這個(gè) token ,并將這個(gè)加密的 token 進(jìn)行解析。這樣一來,前端在用戶登錄成功之后,會(huì)將這個(gè) token 保存到前端的狀態(tài)機(jī)中,而后端默認(rèn)是會(huì)存儲(chǔ)這個(gè) token 的。
這就實(shí)現(xiàn)了一種前后端都可以操作這個(gè) token 的功能,通過這樣的優(yōu)化,無論是前后端分離項(xiàng)目,還是其他類型的一些項(xiàng)目,我們都可以很好地處理用戶的登錄狀態(tài)和用戶數(shù)據(jù)。
3.2 優(yōu)化高并發(fā)環(huán)境下的用戶登錄功能
在高并發(fā)環(huán)境下,我們的用戶登錄功能需要在同一時(shí)刻,處理大量的用戶請求,如果我們的用戶登錄功能沒有考慮到這樣的業(yè)務(wù)場景,也就不會(huì)對(duì)這種業(yè)務(wù)場景進(jìn)行優(yōu)化,所以,一旦請求量上來之后,我們的用戶登錄功能肯定就會(huì)癱瘓,不能正常使用了。
我們可以從兩個(gè)角度去優(yōu)化高并發(fā)環(huán)境下的用戶登錄功能。第一個(gè)角度就是優(yōu)化我們的數(shù)據(jù)庫訪問頻率。
角度一:優(yōu)化數(shù)據(jù)庫訪問頻率
我們都知道,在后臺(tái)程序代碼中,比較耗時(shí)的操作無疑就是訪問數(shù)據(jù)庫了,因?yàn)樵L問數(shù)據(jù)庫之前,我們需要先通過代碼的方式,來將我們的項(xiàng)目與數(shù)據(jù)庫相連接,最后,通過 ORM 框架來達(dá)到對(duì)數(shù)據(jù)庫中的數(shù)據(jù)增刪改查的一個(gè)目的。
我們在對(duì)數(shù)據(jù)庫中的數(shù)據(jù)進(jìn)行增刪改查時(shí),需要我們將處理好的數(shù)據(jù)傳入到數(shù)據(jù)庫中,然后數(shù)據(jù)庫通過執(zhí)行 SQL 腳本來根據(jù)我們的數(shù)據(jù)去處理數(shù)據(jù)庫中的數(shù)據(jù),并最后返回給我們處理數(shù)據(jù)的結(jié)果。
在用戶登錄功能中,訪問數(shù)據(jù)庫的次數(shù)一般都是兩次,第一次是校驗(yàn)當(dāng)前要登錄的用戶名在系統(tǒng)中是否存在;第二次是校驗(yàn)當(dāng)前要登錄的用戶名和密碼是否在系統(tǒng)中匹配。 只有這兩個(gè)校驗(yàn)通過之后,用戶才能成功登錄,而數(shù)據(jù)庫的耗時(shí)操作也就出現(xiàn)在這兩個(gè)過程中。
那么我們應(yīng)該怎么來優(yōu)化呢?
我們可以使用 Redis 來緩存我們的用戶請求數(shù)據(jù),即當(dāng)有用戶請求數(shù)據(jù)請求我們的用戶登錄功能接口時(shí),我們會(huì)首先進(jìn)行第一次的數(shù)據(jù)庫操作,此時(shí),在數(shù)據(jù)庫將操作結(jié)果返回給我們時(shí),我們可以將這一結(jié)果緩存在 redis 中,后續(xù)如果再有相同的請求時(shí),我們可以直接訪問 redis ,而不是再次重復(fù)地去訪問我們的數(shù)據(jù)庫。
這樣可以提高我們的服務(wù)響應(yīng)速度,因?yàn)樵L問 redis 的速度要比單純訪問數(shù)據(jù)庫的速度快很多。所以,第二次的數(shù)據(jù)庫操作我們也可以這樣來進(jìn)行處理。
Tips: 關(guān)于什么是 Redis ,以及 redis 的一些基本操作,我們會(huì)在后續(xù)的小節(jié)中進(jìn)行介紹,這里只介紹功能優(yōu)化的思路,感興趣的同學(xué)可以自行查閱資料實(shí)現(xiàn)。
這是第一種優(yōu)化思路,接下來讓我們看第二種優(yōu)化思路,即優(yōu)化我們的用戶請求序列。
角度二:優(yōu)化用戶請求序列
此種優(yōu)化措施,就需要使用我們的 RabbitMQ 了。
我們都知道,高并發(fā)環(huán)境下的請求數(shù)量是非常多的,那么,對(duì)于一個(gè)用戶登錄功能接口而言,在高并發(fā)環(huán)境下接收的用戶請求也是非常多的,而且這些用戶請求都是無序的。
即,例如我們在一瞬間有 5000 個(gè)用戶登錄請求,根據(jù)計(jì)算機(jī) CPU 以及操作系統(tǒng)的處理時(shí)序可以得出,在這 5000 個(gè)用戶登錄請求中,只要誰先獲取到了 CPU 資源,誰就會(huì)先執(zhí)行,那么其他沒有獲取到 CPU 資源的請求就只能等待,這就是高并發(fā)環(huán)境下的資源搶占問題。
對(duì)于 CPU 來說,我們的一個(gè)用戶請求就代表著是一個(gè)計(jì)算機(jī)線程,比如我們現(xiàn)在有兩個(gè)用戶請求,即兩個(gè)線程來訪問我們的用戶登錄接口,我們分別使用線程 A 和線程 B 來命名。
假設(shè)我們的線程 A 先訪問到了我們的用戶登錄接口,而線程 B 比線程 A 晚 1 秒才訪問到,那么,當(dāng)我們的線程 A 在執(zhí)行業(yè)務(wù)邏輯時(shí),線程 B 也會(huì)跟著執(zhí)行。
如果我們的線程 A 的用戶數(shù)據(jù)比較長,需要執(zhí)行數(shù)據(jù)庫查詢所需的時(shí)間較長,而線程 B 的用戶數(shù)據(jù)則比較端,需要執(zhí)行數(shù)據(jù)庫查詢所需的時(shí)間較短,那么,在統(tǒng)一時(shí)刻,可能晚于線程 A 才訪問到的用戶登錄接口線程 B 就會(huì)先于線程 A 執(zhí)行結(jié)束。
這就會(huì)造成:我們實(shí)際上是處理的線程 A 的業(yè)務(wù)邏輯,但是返回的時(shí)候卻將線程 B 的用戶數(shù)據(jù)進(jìn)行了返回,這也是高并發(fā)環(huán)境下非常容易出現(xiàn)的一個(gè)問題。
那么我們應(yīng)該怎么來優(yōu)化呢?
我們可以使用 RabbitMQ 來作為一個(gè)消息隊(duì)列,當(dāng)用戶請求來請求我們的服務(wù)時(shí),我們可以將所有的用戶請求放入到我們的 RabbitMQ 消息隊(duì)列中,由于 RabbitMQ 消息隊(duì)列可以設(shè)置單條消息進(jìn)行消費(fèi),所以,我們的用戶請求在處理時(shí)就會(huì)逐條進(jìn)行處理,不會(huì)出現(xiàn)上述所描述的問題。
我們來簡單看下這種問題的優(yōu)化代碼,優(yōu)化代碼如下所示:
實(shí)現(xiàn)代碼:
public Response<User> userLogin(User user, HttpSession session){
rabbitTemplate.convertAndSend("userLoginQueue", "user.login", user);
// 獲取用戶數(shù)據(jù)并處理用戶登錄業(yè)務(wù)邏輯
}
代碼解釋:
第 2 行,我們使用了 rabbitTemplate 的 convertAndSend 方法,來將我們的用戶登錄數(shù)據(jù)發(fā)送到 RabbitMQ Server 中。
由于消費(fèi)者的實(shí)現(xiàn)比較復(fù)雜,考慮到篇幅原因,所以在這里代碼就沒有給出。
通過上述代碼,當(dāng)有用戶請求我們的用戶登錄接口時(shí),會(huì)首先將該請求的用戶登錄數(shù)據(jù)存儲(chǔ)到我們的 RabbitMQ Server 中,存儲(chǔ)完成后,接著會(huì)從 RabbitMQ Server 中將該消息進(jìn)行取出并消費(fèi),只有一條消息被消費(fèi)之后,RabbitMQ 才會(huì)繼續(xù)消費(fèi)下一條消息,這就保證了用戶請求的有序性,也就不會(huì)出現(xiàn)上述所提到的問題。
Tips: 我們在優(yōu)化用戶登錄功能時(shí),一定要先理解我們所優(yōu)化問題的產(chǎn)生原因,這樣我們才能從根本上優(yōu)化這一問題。
4. 小結(jié)

本小節(jié)為同學(xué)們詳細(xì)介紹了傳統(tǒng)的用戶登錄功能的基本概念和基本實(shí)現(xiàn)方法,以及在傳統(tǒng)的用戶登錄功能中所出現(xiàn)的可優(yōu)化的問題,針對(duì)出現(xiàn)頻率較高的問題,我們分別介紹了不同問題的不同優(yōu)化措施,并針對(duì)可以使用 RabbitMQ 進(jìn)行優(yōu)化的場景,我們使用了 Spring 生態(tài)中的 RabbitMQ 中的方法來進(jìn)行了優(yōu)化。