使用RabbitMQ優(yōu)化用戶注冊(cè)功能
1. 前言
Hello,大家好。在上個(gè)小節(jié)中,我們對(duì)傳統(tǒng)的用戶登錄功能做了基本的流程介紹,以及使用 RabbitMQ 偽代碼對(duì)傳統(tǒng)的用戶登錄功能進(jìn)行了優(yōu)化。
考慮到用戶登錄功能往往會(huì)和用戶注冊(cè)功能同步出現(xiàn),所以,本小節(jié)會(huì)為同學(xué)們介紹,如何使用 RabbitMQ 消息中間件,去優(yōu)化我們使用傳統(tǒng)的方式,來(lái)實(shí)現(xiàn)的用戶注冊(cè)的功能點(diǎn),包括對(duì)傳統(tǒng)用戶注冊(cè)功能的概述,以及使用 RabbitMQ 進(jìn)行優(yōu)化的關(guān)鍵步驟,希望同學(xué)們可以對(duì)本節(jié)內(nèi)容有所了解。
本節(jié)主要內(nèi)容:
-
傳統(tǒng)用戶注冊(cè)功能概述;
-
使用 RabbitMQ 優(yōu)化傳統(tǒng)用戶注冊(cè)功能。
2.傳統(tǒng)用戶注冊(cè)功能概述
2.1 傳統(tǒng)實(shí)現(xiàn)方案介紹
和用戶登錄功能那樣,在我們現(xiàn)在的信息化系統(tǒng)項(xiàng)目中,項(xiàng)目的首要功能模塊,基本上都會(huì)要求我們來(lái)設(shè)計(jì)并實(shí)現(xiàn)一個(gè)完備的用戶模塊,而這個(gè)用戶模塊中,必定會(huì)包含用戶注冊(cè)與用戶登錄功能點(diǎn)。
只有在項(xiàng)目中實(shí)現(xiàn)了用戶注冊(cè)與用戶登錄這兩個(gè)最基本的功能點(diǎn),我們的項(xiàng)目功能才能繼續(xù)向后進(jìn)行開發(fā),因?yàn)轫?xiàng)目后續(xù)的功能,或多或少都會(huì)與這兩個(gè)功能點(diǎn)相聯(lián)系。
所以,這就要求我們?cè)趯?shí)現(xiàn)用戶注冊(cè)與用戶登錄功能時(shí),要從多方面進(jìn)行考慮, 如果有一點(diǎn)我們沒(méi)有考慮到,很可能在進(jìn)行項(xiàng)目后續(xù)功能開發(fā)時(shí),還要回過(guò)頭去,修改這兩個(gè)基本功能,從而與項(xiàng)目后續(xù)的功能相結(jié)合。
那么,傳統(tǒng)的用戶注冊(cè)功能的實(shí)現(xiàn)方案是什么樣的呢?
傳統(tǒng)用戶注冊(cè)功能流程介紹
傳統(tǒng)的用戶注冊(cè)功能,其實(shí)和傳統(tǒng)的用戶登錄功能差別并不大,我們首先接觸用戶注冊(cè)功能,大部分的同學(xué)都是在自己學(xué)校的實(shí)驗(yàn)課上,都是通過(guò)老師講授的方式來(lái)對(duì)用戶注冊(cè)功能有所了解,但是,這種實(shí)現(xiàn)方式都會(huì)存在諸多問(wèn)題,下面讓我們來(lái)看一下這種方式是如何實(shí)現(xiàn)的。
對(duì)于用戶注冊(cè)功能來(lái)說(shuō),我們從事后端開發(fā)的同學(xué),我們首先需要接收前端傳遞過(guò)來(lái)的數(shù)據(jù),也就是用戶的注冊(cè)數(shù)據(jù),然后我們會(huì)根據(jù)這一用戶注冊(cè)數(shù)據(jù)來(lái)進(jìn)行相應(yīng)的邏輯檢驗(yàn)。
這些邏輯校驗(yàn)包括對(duì)用戶所填寫的注冊(cè)用戶名、郵箱、手機(jī)等用戶唯一關(guān)鍵信息屬性的校驗(yàn)。如果用戶所填寫的用戶名在系統(tǒng)中已經(jīng)存在了,那么用戶就不能使用該用戶名進(jìn)行注冊(cè)了,郵箱和手機(jī)則同理,這里不再贅述。
在對(duì)注冊(cè)用戶名、注冊(cè)郵箱,以及注冊(cè)手機(jī)進(jìn)行唯一性校驗(yàn)之后,我們還需要對(duì)用戶所填寫注冊(cè)郵箱和注冊(cè)手機(jī)號(hào)進(jìn)行邏輯合法校驗(yàn),即用戶所填寫的郵箱到底是不是一個(gè)可用的郵箱,以及用戶所填寫的手機(jī)到底是不是一個(gè)可用的手機(jī)號(hào)。下面,我們來(lái)通過(guò)一個(gè)功能流程圖來(lái)體現(xiàn)上述的業(yè)務(wù)流轉(zhuǎn)過(guò)程:

通過(guò)上述功能流程圖,我們可以很清楚地理解上述傳統(tǒng)用戶注冊(cè)功能的實(shí)現(xiàn)過(guò)程,如果沒(méi)有理解的同學(xué),建議多看幾次,理解這一流程之后我們?cè)倮^續(xù)學(xué)習(xí)下面的內(nèi)容。
傳統(tǒng)用戶注冊(cè)功能代碼實(shí)現(xiàn)
以 SpringBoot 框架為例,我們來(lái)實(shí)現(xiàn)一下上述的用戶注冊(cè)功能,實(shí)現(xiàn)代碼如下所示:
實(shí)現(xiàn)代碼:
public Response<String> userRegister(User registUser){
int userExistsCount = userMapper.selectUserExixtsByUsername(registUser.getUsername());
if (userExistsCount > 0){
return Response.createByError("用戶已存在");
}
int userEmailCount = userMapper.selectUserEmailByUsernameEmail(registUser.getUsername(), registUser.getEmail());
if (userEmailCount > 0){
return Response.createByError("郵箱已存在");
}
int userPhoneCount = userMapper.selectUserPhoneByUsernamePhone(registUser.getUsername(), registUser.getPhone());
if (userPhoneCount > 0){
return Response.createByError("手機(jī)已存在");
}
int userRegist = userMapper.insert(registUser);
if (userRegist > 0){
return Response.createBySuccess("用戶注冊(cè)成功");
}
return Response.createByError("系統(tǒng)錯(cuò)誤,用戶注冊(cè)失敗");
}
代碼解釋:
第 1 行,我們定義了一個(gè)名為 userRegister 的方法來(lái)處理用戶注冊(cè)的業(yè)務(wù)邏輯,該方法的返回值是 Response 類型,并且泛型被指定為 String 類型,這表明,我們的方法最終會(huì)返回注冊(cè)業(yè)務(wù)邏輯的字符串?dāng)?shù)據(jù)。
與用戶登錄方法不同,在該方法中,我們只需要定義一個(gè)參數(shù),那就是 User 類型的 registUser 參數(shù)。該參數(shù)負(fù)責(zé)接收前端傳遞到后臺(tái)的用戶注冊(cè)數(shù)據(jù),并且程序會(huì)根據(jù)這個(gè)數(shù)據(jù)做一些邏輯判斷。
第 2-24 行,我們分別使用了不同的數(shù)據(jù)庫(kù)查詢方法,來(lái)分別對(duì)用戶的注冊(cè)用戶名、注冊(cè)郵箱,以及注冊(cè)手機(jī)進(jìn)行了邏輯唯一性校驗(yàn),即檢測(cè)這些關(guān)鍵字段屬性是不是已經(jīng)存在于系統(tǒng)中了,如果系統(tǒng)中已經(jīng)有了,那表明,可能當(dāng)前需要注冊(cè)的用戶已經(jīng)注冊(cè)過(guò)了,不能重復(fù)注冊(cè)。
第 25-30 行,如果前面的邏輯校驗(yàn)都通過(guò),那么我們就要使用 MyBatis 自帶的 insert 方法,將這一合法的用戶注冊(cè)數(shù)據(jù),插入到我們的數(shù)據(jù)庫(kù)中所對(duì)應(yīng)的用戶數(shù)據(jù)表中,并且在數(shù)據(jù)插入數(shù)據(jù)庫(kù)成功之后,提示用戶,當(dāng)前用戶已經(jīng)注冊(cè)成功了。
如果在將用戶注冊(cè)數(shù)據(jù)插入到數(shù)據(jù)庫(kù)的過(guò)程中,出現(xiàn)了任何問(wèn)題,導(dǎo)致用戶注冊(cè)數(shù)據(jù)無(wú)法插入到數(shù)據(jù)庫(kù)中,則提示當(dāng)前注冊(cè)的用戶:系統(tǒng)錯(cuò)誤,用戶注冊(cè)失敗。此時(shí),需要等待系統(tǒng)管理員解決。
Tips: 1. 在用戶所填寫的用戶注冊(cè)數(shù)據(jù)通過(guò)邏輯檢驗(yàn)時(shí),我們應(yīng)該將用戶注冊(cè)數(shù)據(jù)插入到我們的數(shù)據(jù)庫(kù)中,而不是直接放入到緩存中去,否則,我們剛剛注冊(cè)的用戶是無(wú)法進(jìn)行登錄的;
2. 傳統(tǒng)的用戶注冊(cè)功能,在用戶注冊(cè)成功之后,我們不需要將用戶注冊(cè)成功的數(shù)據(jù)進(jìn)行返回,或者進(jìn)行一個(gè)狀態(tài)的處理,這點(diǎn)需要同學(xué)們注意;
3. 在將用戶注冊(cè)數(shù)據(jù)插入到數(shù)據(jù)庫(kù)的過(guò)程中,我們使用了 MyBatis 自帶的 insert 方法,同學(xué)們可以了解一下這個(gè) insert 方法的作用。
通過(guò)上述代碼的編寫,我們基本上實(shí)現(xiàn)了傳統(tǒng)的用戶注冊(cè)功能,但是這種注冊(cè)功能是最簡(jiǎn)單的實(shí)現(xiàn),完全沒(méi)有考慮其他因素,如果我們?cè)趯?shí)際項(xiàng)目中這樣來(lái)實(shí)現(xiàn),那么在開發(fā)后續(xù)功能時(shí),必定會(huì)出現(xiàn)一些意料之中的問(wèn)題。
那么,我們采用這種實(shí)現(xiàn)方式所實(shí)現(xiàn)的用戶注冊(cè)功能到底有哪些弊端呢?下面就讓我們一探究竟。
2.2 傳統(tǒng)實(shí)現(xiàn)方案中存在的弊端
和用戶登錄功能相似,我們傳統(tǒng)的實(shí)現(xiàn)用戶注冊(cè)功能的業(yè)務(wù)邏輯還是正確的, 不管我們?cè)僭趺磳?duì)用戶注冊(cè)功能進(jìn)行優(yōu)化,這個(gè)傳統(tǒng)的用戶注冊(cè)功能業(yè)務(wù)流程始終不會(huì)發(fā)生改變,發(fā)生改變的只是我們的代碼實(shí)現(xiàn)層。
那么,傳統(tǒng)實(shí)現(xiàn)方案中存在的弊端,也就存在于我們的功能代碼實(shí)現(xiàn)層上。
傳統(tǒng)用戶注冊(cè)功能中存在的弊端,整體來(lái)說(shuō),最核心的也就只有一種弊端了。
那就是,無(wú)論我們的用戶注冊(cè)功能所在的場(chǎng)景是不是在高并發(fā)環(huán)境中,都會(huì)直接對(duì)我們的數(shù)據(jù)庫(kù)的訪問(wèn)壓力造成較高的沖擊, 如果在高并發(fā)環(huán)境中,我們的數(shù)據(jù)庫(kù)可能會(huì)隨著用戶注冊(cè)請(qǐng)求數(shù)量的激增,而直接崩潰,這是非常致命的一點(diǎn)。
我們先來(lái)說(shuō)非高并發(fā)環(huán)境下,用戶注冊(cè)數(shù)據(jù)在邏輯檢測(cè)通過(guò)之后,會(huì)直接訪問(wèn)我們的數(shù)據(jù)庫(kù),并將用戶注冊(cè)數(shù)據(jù)插入到我們的數(shù)據(jù)庫(kù)中,正常環(huán)境下我們的數(shù)據(jù)庫(kù)還是能扛得住的,但是,即使能扛得住,數(shù)據(jù)庫(kù)的壓力在此時(shí)也是較高的。
那么,在高并發(fā)環(huán)境下,由于我們沒(méi)有在用戶數(shù)據(jù)和數(shù)據(jù)庫(kù)之間做處理,這就導(dǎo)致,當(dāng)大量請(qǐng)求都在同一時(shí)刻到來(lái)時(shí),我們的數(shù)據(jù)庫(kù)的壓力會(huì)直線上升,并最終導(dǎo)致數(shù)據(jù)庫(kù)崩潰,這會(huì)直接造成我們的用戶注冊(cè)功能直接卡死,無(wú)法完成用戶注冊(cè)功能。
針對(duì)這種情況,我們又該如何優(yōu)化呢?下面就讓我們來(lái)看一下,如何使用 RabbitMQ 來(lái)優(yōu)化這一弊端。
3 使用 RabbitMQ 優(yōu)化傳統(tǒng)用戶注冊(cè)功能
我們注意到,對(duì)于用戶注冊(cè)功能而言,無(wú)論是在普通環(huán)境,還是在高并發(fā)環(huán)境,我們的傳統(tǒng)邏輯,都是在用戶注冊(cè)數(shù)據(jù)校驗(yàn)正確后,直接地將用戶數(shù)據(jù)插入到數(shù)據(jù)庫(kù)中,中間并沒(méi)有一層過(guò)渡的措施。
正是由于我們?nèi)鄙龠@一措施,我們的數(shù)據(jù)庫(kù)壓力才會(huì)持續(xù)升高,而這一持續(xù)升高的結(jié)果,和普通環(huán)境和高并發(fā)環(huán)境并沒(méi)有太大的本質(zhì)因素,所以,我們的優(yōu)化重點(diǎn)就放在了這一中間措施上面。
針對(duì)這一問(wèn)題,我們同樣的有兩種角度可以考慮:
角度一:使用緩存承載中間壓力
在眾多緩存中間件中,使用頻率和普適度最高的,要數(shù) redis 緩存中間件了。
對(duì)于用戶注冊(cè)而言,當(dāng)用戶提交的注冊(cè)數(shù)據(jù)通過(guò)了我們的邏輯校驗(yàn)之后,我們可以使用 redis 來(lái)將該用戶注冊(cè)數(shù)據(jù)進(jìn)行存儲(chǔ),并在一個(gè)固定的時(shí)間內(nèi),將位于 redis 緩存中的用戶注冊(cè)數(shù)據(jù),同步插入到我們的數(shù)據(jù)庫(kù)中, 這樣既可實(shí)現(xiàn) redis 緩存數(shù)據(jù)與數(shù)據(jù)庫(kù)數(shù)據(jù)之間的同步。
與此同時(shí),當(dāng)我們添加了 redis 緩存中間件之后,我們所對(duì)應(yīng)的用戶登錄功能也要進(jìn)行相應(yīng)的調(diào)整,即從之前用戶登錄時(shí),對(duì)數(shù)據(jù)庫(kù)所做的校驗(yàn),換做優(yōu)先對(duì) redis 緩存中的用戶注冊(cè)數(shù)據(jù)進(jìn)行校驗(yàn)。
Tips: 在將用戶注冊(cè)數(shù)據(jù)存入 redis 緩存中間件前,我們應(yīng)該設(shè)置好我們的 redis 緩存 key 值的生成策略,目的就是將不同業(yè)務(wù)場(chǎng)景所對(duì)應(yīng)的 key 值進(jìn)行區(qū)分。
當(dāng)我們這樣設(shè)置之后,合法的用戶注冊(cè)數(shù)據(jù)會(huì)被優(yōu)先存儲(chǔ)到 redis 緩存中間件中,然后,在某一固定時(shí)間周期內(nèi),系統(tǒng)會(huì)自動(dòng)將 redis 緩存中間中的數(shù)據(jù)同步到我們的數(shù)據(jù)庫(kù)中,這樣一來(lái),我們的 redis 緩存中間件就承擔(dān)了大部分的數(shù)據(jù)庫(kù)壓力。
這是第一種優(yōu)化思路,接下來(lái)讓我們看第二種優(yōu)化思路,即通過(guò)消息隊(duì)列來(lái)分發(fā)數(shù)據(jù),從而減少數(shù)據(jù)庫(kù)的壓力。
角度二:采用消息隊(duì)列分發(fā)數(shù)據(jù)壓力
此種優(yōu)化措施,就需要使用我們的 RabbitMQ 了。
在角度一中所提到的優(yōu)化措施,在很多業(yè)務(wù)場(chǎng)景中是沒(méi)有大問(wèn)題的,但是在高并發(fā)環(huán)境下,會(huì)出現(xiàn)一個(gè)問(wèn)題,那就是由于數(shù)據(jù)同步的延遲,導(dǎo)致的用戶無(wú)法登錄的問(wèn)題。 所以,當(dāng)用戶注冊(cè)功能處于高并發(fā)環(huán)境下時(shí),我們必須要使用 RabbitMQ 消息隊(duì)列來(lái)進(jìn)行優(yōu)化了。
我們都知道,高并發(fā)環(huán)境下的請(qǐng)求數(shù)量是非常多的,那么,對(duì)于一個(gè)用戶注冊(cè)功能接口而言,在高并發(fā)環(huán)境下接收的用戶請(qǐng)求也是非常多的。
我們可以使用 RabbitMQ 來(lái)作為一個(gè)消息隊(duì)列,當(dāng)合法的用戶注冊(cè)數(shù)據(jù)需要插入數(shù)據(jù)庫(kù)時(shí),我們可以將數(shù)據(jù)發(fā)送到我們的 RabbitMQ 的消息隊(duì)列中去,然后我們定義的消費(fèi)者會(huì)從消息隊(duì)列中獲取并消費(fèi)該數(shù)據(jù)。
當(dāng)消費(fèi)者將消息隊(duì)列中的用戶注冊(cè)數(shù)據(jù)進(jìn)行消費(fèi)之后,我們可以將這一數(shù)據(jù)直接插入到數(shù)據(jù)庫(kù)中,無(wú)須經(jīng)過(guò)角度一中提到的 redis 緩存中間件層,因?yàn)槲覀兊?RabbitMQ 消息隊(duì)列已經(jīng)對(duì)數(shù)據(jù)壓力進(jìn)行了分發(fā)。
即,當(dāng)有一個(gè)用戶注冊(cè)請(qǐng)求需要處理時(shí),RabbitMQ 就會(huì)在消息隊(duì)列中存儲(chǔ)這個(gè)請(qǐng)求所對(duì)應(yīng)的合法的用戶注冊(cè)數(shù)據(jù),并在消息進(jìn)行消費(fèi)之后,再將數(shù)據(jù)進(jìn)行持久化存儲(chǔ)。
這樣一來(lái),通過(guò)集成 RabbitMQ 消息隊(duì)列,將用戶注冊(cè)時(shí)的數(shù)據(jù)壓力通過(guò)消息隊(duì)列進(jìn)行分發(fā),從而達(dá)到減小數(shù)據(jù)庫(kù)壓力的目的。
我們來(lái)簡(jiǎn)單看下這種問(wèn)題的優(yōu)化代碼,優(yōu)化代碼如下所示:
實(shí)現(xiàn)代碼:
public Response<String> userRegister(User registUser){
int userExistsCount = userMapper.selectUserExixtsByUsername(registUser.getUsername());
if (userExistsCount > 0){
return Response.createByError("用戶已存在");
}
int userEmailCount = userMapper.selectUserEmailByUsernameEmail(registUser.getUsername(), registUser.getEmail());
if (userEmailCount > 0){
return Response.createByError("郵箱已存在");
}
int userPhoneCount = userMapper.selectUserPhoneByUsernamePhone(registUser.getUsername(), registUser.getPhone());
if (userPhoneCount > 0){
return Response.createByError("手機(jī)已存在");
}
boolean isAck = false;
rabbitTemplate.convertAndSend("userRegistQueue", "user.register", user);
// 省略消費(fèi)者消費(fèi)數(shù)據(jù)過(guò)程 isAck = true;
if(isAck) {
int userRegist = userMapper.insert(registUser);
if(userRegist > 0) {
return Response.createBySuccess("用戶注冊(cè)成功");
}
return Response.createByError("系統(tǒng)錯(cuò)誤,用戶注冊(cè)失敗");
}
return Response.createByError("系統(tǒng)錯(cuò)誤,用戶注冊(cè)失敗");
}
代碼解釋:
第 1-19 行,像傳統(tǒng)用戶注冊(cè)那樣,我們對(duì)用戶所提交的注冊(cè)數(shù)據(jù)進(jìn)行了校驗(yàn),直到用戶的注冊(cè)數(shù)據(jù)通過(guò)了我們的邏輯檢測(cè)為止。
第 20 行,我們聲明了一個(gè) boolean 類型的變量 isAck ,并且將他的默認(rèn)值設(shè)為了 false ,該變量表示我們的消息是否已經(jīng)被消費(fèi)了。
第 21 行,我們使用 rabbitTemplate 的 convertAndSend 方法,將合法的用戶注冊(cè)數(shù)據(jù)發(fā)送到 RabbitMQ 的消息隊(duì)列中去,等待消費(fèi)者消費(fèi)。
第 22 行,我們對(duì) isAck 進(jìn)行了檢測(cè),當(dāng)消費(fèi)者成功從消息隊(duì)列中獲取并消費(fèi)了用戶注冊(cè)數(shù)據(jù)的消息之后,isAck 標(biāo)志位會(huì)被置位 true 。
第 23-28 行,如果 isAck 標(biāo)志位為 true ,則將用戶注冊(cè)數(shù)據(jù)插入到我們的數(shù)據(jù)庫(kù)中,并提示用戶注冊(cè)成功,如果用戶數(shù)據(jù)在插入數(shù)據(jù)庫(kù)過(guò)程中遇到問(wèn)題,導(dǎo)致數(shù)據(jù)無(wú)法插入,則提示用戶:系統(tǒng)錯(cuò)誤,用戶注冊(cè)失敗。
由于消費(fèi)者的實(shí)現(xiàn)比較復(fù)雜,考慮到篇幅原因,所以在這里代碼就沒(méi)有給出。
Tips: 使用 RabbitMQ 消息隊(duì)列去優(yōu)化用戶注冊(cè)功能時(shí),一定要根據(jù)上述代碼片段的先后順序來(lái)進(jìn)行優(yōu)化,特別是使用 RabbitMQ 代碼部分,同學(xué)們注意。
4. 小結(jié)

本小節(jié)為同學(xué)們?cè)敿?xì)介紹了傳統(tǒng)的用戶注冊(cè)功能的基本概念和基本實(shí)現(xiàn)方法,以及在傳統(tǒng)的用注冊(cè)功能中所出現(xiàn)的可優(yōu)化的問(wèn)題,針對(duì)核心問(wèn)題,我們分別介紹了不同問(wèn)題的不同優(yōu)化措施,并針對(duì)可以使用 RabbitMQ 進(jìn)行優(yōu)化的場(chǎng)景,我們使用了 Spring 生態(tài)中的 RabbitMQ 中的方法來(lái)進(jìn)行了優(yōu)化。