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

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

本小節為同學們詳細介紹了傳統的用戶注冊功能的基本概念和基本實現方法,以及在傳統的用注冊功能中所出現的可優化的問題,針對核心問題,我們分別介紹了不同問題的不同優化措施,并針對可以使用 RabbitMQ 進行優化的場景,我們使用了 Spring 生態中的 RabbitMQ 中的方法來進行了優化。