亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

全部開發者教程

RabbitMQ 入門教程

RabbitMQ 簡介
RabbitMQ 簡介
首頁 慕課教程 RabbitMQ 入門教程 RabbitMQ 入門教程 使用RabbitMQ優化用戶登錄功能

使用RabbitMQ優化用戶登錄功能

1. 前言

Hello,大家好。通過上述幾個小節的介紹,我們已經對 RabbitMQ 中的消息發送模式有了代碼實現層面的了解,這些實操代碼是應用 RabbitMQ 基礎中的基礎,同學們必須要掌握并記憶。

那么,從本節開始,會為大家帶來,在我們的實際的一個日常開發工作中,針對常用的幾個功能,我們應該如何使用 RabbitMQ 來進行實現,同時,通過對常用功能的 RabbitMQ 集成,可以提升傳統功能實現中的一些性能等指標,這對后續傳統功能的優化是很有必要的。

本小節會為同學們介紹,如何使用 RabbitMQ 消息中間件,去優化我們使用傳統的方式,來實現的用戶登錄功能點,包括對傳統用戶登錄功能的概述,以及使用 RabbitMQ 進行優化的關鍵步驟,希望同學們可以對本節內容有所了解。

本節主要內容:

  • 傳統用戶登錄功能概述;

  • 使用 RabbitMQ 優化傳統用戶登錄功能。

2.傳統用戶登錄功能概述

2.1 傳統實現方案介紹

眾所周知,我們現在的信息化系統項目,項目的首要功能模塊,基本上都會要求我們來設計并實現一個完備的用戶模塊,而這個用戶模塊中,必定會包含用戶注冊與用戶登錄功能點。

只有在項目中實現了用戶注冊與用戶登錄這兩個最基本的功能點,我們的項目功能才能繼續向后進行開發,因為項目后續的功能,或多或少都會與這兩個功能點相聯系。

所以,這就要求我們在實現用戶注冊與用戶登錄功能時,要從多方面進行考慮, 如果有一點我們沒有考慮到,很可能在進行項目后續功能開發時,還要回過頭去,修改這兩個基本功能,從而與項目后續的功能相結合。

那么,傳統的用戶登錄功能的實現方案是什么樣的呢?

傳統用戶登錄功能流程介紹

我們首先接觸用戶登錄功能或者用戶注冊功能,有絕大部分同學是在自己的學校內,也就是學校老師教授我們的內容,往往這種實現方式都會存在諸多問題,下面讓我們來看一下這種方式是如何實現的。

針對用戶登錄功能,對于后端開發的同學,我們首先需要接收前端傳遞過來的數據,也就是用戶的登錄數據,然后我們會根據這一用戶登錄數據來進行相應的邏輯檢驗。

這些邏輯校驗包括對用戶的名稱進行檢測,以查驗當前要登錄的用戶是否在我們的系統中存在,如果不存在,則提示用戶不存在與本系統中,如果存在,則檢測用戶所輸入的登錄密碼是否與用戶注冊時所填寫的一致,如果不一致,則提示用戶,用戶登錄密碼錯誤。

如果當前要登錄的用戶在本系統中存在,且用戶所輸入的登錄密碼與用戶注冊時所填寫的登錄密碼一致,則表明用戶登錄成功,此時,需要提示用戶登錄成功,并跳轉到我們的系統首頁。下面,我們來通過一個功能流程圖來體現上述的業務流轉過程:

通過上述功能流程圖,我們可以很清楚地理解上述傳統用戶登錄功能的實現過程,如果沒有理解的同學,建議多看幾次,理解這一流程之后我們再繼續學習下面的內容。

傳統用戶登錄功能代碼實現

以 SpringBoot 框架為例,我們來實現一下上述的用戶登錄功能,實現代碼如下所示:

實現代碼:

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("用戶登錄密碼輸入錯誤,請重新輸入");
}

代碼解釋:

第 1 行,我們定義了一個名為 userLogin 的方法來處理用戶登錄的業務邏輯,該方法的返回值是 Response 類型,并且泛型被指定為 User 類型,這表明,我們的方法最終會返回 User 用戶數據。

在該方法中,我們定義了兩個參數,分別是 User 類型的 user 參數,以及 HttpSession 類型的 session 參數。其中,user 參數就表示用戶登錄時所填入的登錄數據,session 參數則表示存儲用戶登錄狀態和登錄數據的參數。

第 2-5 行,我們通過獲取到用戶的用戶名,并且調用數據庫 ORM 層的方法,來對系統數據庫進行查詢,查詢當前用戶名是否存在我們的數據庫中,如果存在,則表明用戶注冊過我們的系統,即這個用戶是在我們的系統中的。如果不存在,則會提示用戶當前用戶名不存在。

第 6-14 行,我們通過獲取到的用戶的用戶名和用戶登錄密碼,調用數據庫 ORM 層的犯法,來對系統數據庫進行查詢,查詢當前用戶名和密碼是否在系統中匹配。

即,只有用戶名匹配的話是不行的,還要將這個用戶名和其所對應的密碼進行匹配,當使用這兩個條件進行匹配查詢,且可以正確查詢到結果時,表明用戶已經登錄成功了,此時,我們可以將用戶登錄的數據放到 session 中,以備后續功能的使用,與此同時,我們還應該提示用戶登錄成功了。

如果根據這兩個查詢條件,沒有查詢到任何數據,即 userLogin 變量為 null 時,則表明當前用戶名是存在的,但是當前登錄用戶輸入的登錄密碼和注冊時所輸入的是不一樣的,此時,系統會提示用戶,用戶登錄密碼錯誤,可以進行重試。

Tips:
1. 在我們的 userLogin 方法中存在一個參數 session ,我們只是對 session 進行了簡單介紹,因為這不屬于我們的重點內容,所以,有不了解的同學可以自行查閱資料來了解;
2. 可以看到,在 userLogin 方法中,存在一行 userLogin.setPassword("") ,這句代碼的作用是將用戶登錄成功后的密碼設置為空,并不會同步更新數據庫,并將 userLogin 用戶數據返回給前端,出于安全考慮,這里將密碼設置為空了,前端是獲取不到用戶的登錄密碼的;
3. userLogin 方法中所使用的 userMapper 為 MyBatis 數據庫 ORM 框架所定義的內容,有不了解的同學可以自行查閱資料進行了解,本小節不會對其進行介紹。

通過上述代碼的編寫,我們基本上實現了傳統的用戶登錄功能,但是這種登錄功能是最簡單的實現,完全沒有考慮其他因素,如果我們在實際項目中這樣來實現,那么在開發后續功能時,必定會出現一些意料之中的問題。

那么,我們采用這種實現方式所實現的用戶登錄功能到底有哪些弊端呢?下面就讓我們一探究竟。

2.2 傳統實現方案中存在的弊端

整體來說,我們傳統的實現用戶登錄功能的業務邏輯還是正確的, 不管我們再怎么對用戶登錄功能進行優化,這個傳統的用戶登錄功能業務流程始終不會發生改變,發生改變的只是我們的代碼實現層。

那么,傳統實現方案中存在的弊端,也就存在于我們的功能代碼實現層上。

弊端一 用戶登錄狀態的處理

對于上述代碼來說,我們處理用戶登錄狀態所采用的手段,是通過將用戶登錄成功后的數據存放在服務端中的 session 中,后續如果需要使用到用戶登錄狀態,我們可以直接對 session 進行判斷,并返回相應的判斷結果。

但是,這種模式如果遇到了前后端分離的項目,我們就無法再使用 session 了,因為前端框架不能直接操控我們后端的 session ,所以,我們就要把 session 替換掉,替換成前后端均可操控的手段來實現。

弊端二 高并發環境下容易出現的問題

如果我們的項目在使用過程中出現了用戶激增的情況,即會有越來越多的人來訪問我們的用戶登錄功能,如果我們只是采用上述的實現方式來處理,不會這種情況進行考慮,那么我們的用戶登錄功能就會在非常短的一段時間過后癱瘓,我們的用戶登錄功能不會再返回任何響應數據。

這就是高并發環境下傳統的用戶登錄功能最容易出現問題的地方,而這一地方則體現在用戶登錄前和用登錄后的數據不一致問題上。

即,在高并發環境下,我們的用戶 A 進行了登錄,與此同時,我們的用戶 B 也進行了登錄,這兩個用戶進行登錄的時刻恰好重疊了,由于我們并沒有對這種場景進行處理,所以,在這兩個用戶登錄之后,就有可能出現返回給用戶 A 的用戶數據,其實是用戶 B 的。

除了數據不一致問題,還有一種問題也比較容易出現,那就是當我們的用戶數激增時,同一時刻進行登錄的用戶也隨之激增,我們的用戶登錄功能接口無法在一瞬間響應這么多的用戶登錄請求,導致了后續登錄的用戶只能等待,只能等待當先用戶登錄完成之后,才能進行登錄。

這個等待過程是不確定的,可能很長,也可能很短,這就對用戶的體驗造成了非常嚴重的不良影響。

上述就是兩個在傳統用戶登錄功能中,出現的顯而易見的問題,我們會對這兩個功能進行優化,并且會使用 RabbitMQ 對第二個弊端進行優化。

3 使用 RabbitMQ 優化傳統用戶登錄功能

3.1 優化用戶登錄狀態的處理問題

前面我們已經介紹了,在傳統用戶登錄功能中,當我們的項目架構采用前后端分離的架構方式來管理時,出現的用戶登錄狀態的問題,那么我們可以怎樣來優化這個問題呢?

優化出現的用戶登錄狀態問題,不需要我們使用 RabbitMQ 來處理,我們可以將 session 參數使用一種 token 機制來替代掉。

即在處理用戶狀態時,我們不使用 session 來存儲,而是使用 token 來進行存儲,這個 token 我們一般會使用 JWT 框架來生成,并為生成的 token 設置一個有效期。

這樣一來,當前端發來用戶登錄請求時,就會將用戶登錄成功的 token 進行傳遞,然后我們后臺需要接收這個 token ,并將這個加密的 token 進行解析。這樣一來,前端在用戶登錄成功之后,會將這個 token 保存到前端的狀態機中,而后端默認是會存儲這個 token 的。

這就實現了一種前后端都可以操作這個 token 的功能,通過這樣的優化,無論是前后端分離項目,還是其他類型的一些項目,我們都可以很好地處理用戶的登錄狀態和用戶數據。

3.2 優化高并發環境下的用戶登錄功能

在高并發環境下,我們的用戶登錄功能需要在同一時刻,處理大量的用戶請求,如果我們的用戶登錄功能沒有考慮到這樣的業務場景,也就不會對這種業務場景進行優化,所以,一旦請求量上來之后,我們的用戶登錄功能肯定就會癱瘓,不能正常使用了。

我們可以從兩個角度去優化高并發環境下的用戶登錄功能。第一個角度就是優化我們的數據庫訪問頻率。

角度一:優化數據庫訪問頻率

我們都知道,在后臺程序代碼中,比較耗時的操作無疑就是訪問數據庫了,因為訪問數據庫之前,我們需要先通過代碼的方式,來將我們的項目與數據庫相連接,最后,通過 ORM 框架來達到對數據庫中的數據增刪改查的一個目的。

我們在對數據庫中的數據進行增刪改查時,需要我們將處理好的數據傳入到數據庫中,然后數據庫通過執行 SQL 腳本來根據我們的數據去處理數據庫中的數據,并最后返回給我們處理數據的結果。

在用戶登錄功能中,訪問數據庫的次數一般都是兩次,第一次是校驗當前要登錄的用戶名在系統中是否存在;第二次是校驗當前要登錄的用戶名和密碼是否在系統中匹配。 只有這兩個校驗通過之后,用戶才能成功登錄,而數據庫的耗時操作也就出現在這兩個過程中。

那么我們應該怎么來優化呢?

我們可以使用 Redis 來緩存我們的用戶請求數據,即當有用戶請求數據請求我們的用戶登錄功能接口時,我們會首先進行第一次的數據庫操作,此時,在數據庫將操作結果返回給我們時,我們可以將這一結果緩存在 redis 中,后續如果再有相同的請求時,我們可以直接訪問 redis ,而不是再次重復地去訪問我們的數據庫。

這樣可以提高我們的服務響應速度,因為訪問 redis 的速度要比單純訪問數據庫的速度快很多。所以,第二次的數據庫操作我們也可以這樣來進行處理。

Tips: 關于什么是 Redis ,以及 redis 的一些基本操作,我們會在后續的小節中進行介紹,這里只介紹功能優化的思路,感興趣的同學可以自行查閱資料實現。

這是第一種優化思路,接下來讓我們看第二種優化思路,即優化我們的用戶請求序列。

角度二:優化用戶請求序列

此種優化措施,就需要使用我們的 RabbitMQ 了。

我們都知道,高并發環境下的請求數量是非常多的,那么,對于一個用戶登錄功能接口而言,在高并發環境下接收的用戶請求也是非常多的,而且這些用戶請求都是無序的。

即,例如我們在一瞬間有 5000 個用戶登錄請求,根據計算機 CPU 以及操作系統的處理時序可以得出,在這 5000 個用戶登錄請求中,只要誰先獲取到了 CPU 資源,誰就會先執行,那么其他沒有獲取到 CPU 資源的請求就只能等待,這就是高并發環境下的資源搶占問題。

對于 CPU 來說,我們的一個用戶請求就代表著是一個計算機線程,比如我們現在有兩個用戶請求,即兩個線程來訪問我們的用戶登錄接口,我們分別使用線程 A 和線程 B 來命名。

假設我們的線程 A 先訪問到了我們的用戶登錄接口,而線程 B 比線程 A 晚 1 秒才訪問到,那么,當我們的線程 A 在執行業務邏輯時,線程 B 也會跟著執行。

如果我們的線程 A 的用戶數據比較長,需要執行數據庫查詢所需的時間較長,而線程 B 的用戶數據則比較端,需要執行數據庫查詢所需的時間較短,那么,在統一時刻,可能晚于線程 A 才訪問到的用戶登錄接口線程 B 就會先于線程 A 執行結束。

這就會造成:我們實際上是處理的線程 A 的業務邏輯,但是返回的時候卻將線程 B 的用戶數據進行了返回,這也是高并發環境下非常容易出現的一個問題。

那么我們應該怎么來優化呢?

我們可以使用 RabbitMQ 來作為一個消息隊列,當用戶請求來請求我們的服務時,我們可以將所有的用戶請求放入到我們的 RabbitMQ 消息隊列中,由于 RabbitMQ 消息隊列可以設置單條消息進行消費,所以,我們的用戶請求在處理時就會逐條進行處理,不會出現上述所描述的問題。

我們來簡單看下這種問題的優化代碼,優化代碼如下所示:

實現代碼:

public Response<User> userLogin(User user, HttpSession session){
    rabbitTemplate.convertAndSend("userLoginQueue", "user.login", user);
    // 獲取用戶數據并處理用戶登錄業務邏輯
}

代碼解釋:

第 2 行,我們使用了 rabbitTemplate 的 convertAndSend 方法,來將我們的用戶登錄數據發送到 RabbitMQ Server 中。

由于消費者的實現比較復雜,考慮到篇幅原因,所以在這里代碼就沒有給出。

通過上述代碼,當有用戶請求我們的用戶登錄接口時,會首先將該請求的用戶登錄數據存儲到我們的 RabbitMQ Server 中,存儲完成后,接著會從 RabbitMQ Server 中將該消息進行取出并消費,只有一條消息被消費之后,RabbitMQ 才會繼續消費下一條消息,這就保證了用戶請求的有序性,也就不會出現上述所提到的問題。

Tips: 我們在優化用戶登錄功能時,一定要先理解我們所優化問題的產生原因,這樣我們才能從根本上優化這一問題。

4. 小結

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