通常秒殺項目可能不止部署在一個服務器上,而是使用分佈式部署在多臺服務器,這時候假如用戶登錄是在第一個服務器,第一個請求到了第一臺服務器,這是沒問題的;但是第二個請求到了第二個服務器,那麼用戶的Session信息就丟失了
解決:使用session同步,無論訪問那一臺服務器,session都可以取得到
本項目:利用一臺緩存服務器集中管理session,即利用緩存統一管理session
分佈式Session的幾種實現方式
- 使用Session Replication方式管理 (即session複製)
簡介:將一臺機器上的Session數據廣播複製到集羣中其餘機器上
使用場景:機器較少,網絡流量較小
優點:實現簡單、配置較少、當網絡中有機器Down掉時不影響用戶訪問
缺點:廣播式複製到其餘機器有一定廷時,帶來一定網絡開銷
- 使用Session Sticky方式管理
簡介:即粘性Session、當用戶訪問集羣中某臺機器後,強制指定後續所有請求均落到此機器上
使用場景:機器數適中、對穩定性要求不是非常苛刻
優點:實現簡單、配置方便、沒有額外網絡開銷
缺點:網絡中有機器Down掉時、用戶Session會丟失、容易造成單點故障
- 使用緩存集中式管理
簡介:將Session存入分佈式緩存集羣中的某臺機器上,當用戶訪問不同節點時先從緩存中拿Session信息
使用場景:集羣中機器數多、網絡環境複雜
優點:可靠性好
缺點:實現複雜、穩定性依賴於緩存的穩定性、Session信息放入緩存時要有合理的策略寫入
本項目分佈式Session的實現
實現思路:用戶登錄成功之後,給這個用戶生成一個sessionId(用token來標識這個用戶),並寫到cookie中傳遞給客戶端;然後客戶端在隨後的訪問中,都在cookie中傳遞這個token,服務端拿到這個token之後,就根據這個token來取得對應的session信息(token利用uuid生成)
登錄成功後給用戶生成sessionId:
生成sessionId的具體代碼:
addCookie方法解讀:將MiaoshaUserKey前綴+sessionId(sessionId即token)組成了一個完整的Key,例如:“MiaoshaUserKey:tk4470ee9b98eb4e63bbc52a4e9b65052e”,其中MiaoshaUserKey前綴=“MiaoshaUserKey:tk”,token=“4470ee9b98eb4e63bbc52a4e9b65052e”,作爲Key和對應的用戶信息(user對象信息會轉換爲字符串類型)一起存入Redis 緩存中;此token對應的是一個用戶,將用戶信息存放到一個第三方的緩存中,當訪問其他頁面的時候,就可以從cookie中獲取到token,再訪問redis拿到用戶信息來判斷登錄情況,存入redis中的內容如下:
客戶端在隨後的訪問中,都會在cookie中傳遞這個token,服務端拿到這個token之後,就根據這個token去緩存中取得對應的(用戶信息)session信息,如下:
後端驗證session代碼如下:
這裏就是登錄成功之後,後端把token以響應頭的形式返回給前端,然後在後面請求的時候,會帶上這個token,那麼後端就可以根據該token去緩存裏面取得相對應的用戶信息,從而實現分佈式session
像上面那樣使用@RequestParam和@CookieValue來獲取token比較麻煩,可想辦法直接在controller的請求方法上面直接注入MiaoshaUser(用戶的信息),然後直接通過方法的參數就可以獲取用戶的信息,從而簡化代碼;就像SpringMVC中的controller 方法中可以有很多參數可以直接使用(例如request和response對象),有些參數不需要傳值,就可以直接獲取到一樣
例如優化後的代碼:
優化所需具體代碼
創建一個UserArgumentResolver類並且實現HandlerMethodArgumentResolver接口,然後重寫裏面的resolveArgument和supportsParameter方法,既然要讓MiaoshaUser這個實例對象可以像SpringMVC中的controller那樣直接使用HttpServletRequest的實例對象request,那麼解析前端傳來的token或者請求參數裏面的token的業務邏輯就在這裏完成:
package com.javaxl.miaosha_05.config;
import com.javaxl.miaosha_05.domain.MiaoshaUser;
import com.javaxl.miaosha_05.service.MiaoshaUserService;
import com.javaxl.miaosha_05.util.UserContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//將UserArgumentResolver註冊到config裏面去
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
//既然能注入service,那麼可以用來容器來管理,將其放在容器中
@Autowired
MiaoshaUserService miaoshaUserService;
public Object resolveArgument(MethodParameter arg0, ModelAndViewContainer arg1, NativeWebRequest webRequest,
WebDataBinderFactory arg3) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
String paramToken = request.getParameter(MiaoshaUserService.COOKIE_NAME_TOKEN);
//獲取cookie
String cookieToken = UserContext.getCookieValue(request, MiaoshaUserService.COOKIE_NAME_TOKEN);
if (StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
return null;
}
String token = StringUtils.isEmpty(paramToken) ? cookieToken : paramToken;
MiaoshaUser user = miaoshaUserService.getByToken(response, token);
return user;
}
public boolean supportsParameter(MethodParameter parameter) {
//返回參數的類型
Class<?> clazz = parameter.getParameterType();
return clazz == MiaoshaUser.class;
}
}
新建一個WebConfig類繼承WebMvcConfigurerAdapter,並且重寫addArgumentResolvers方法,並且注入之前寫好的UserArgumentResolver類,因爲UserArgumentResolver類使用了@Service進行標註,已經放到容器裏面了,所以這裏可以直接注入:
package com.javaxl.miaosha_05.config;
import com.javaxl.miaosha_05.interceptor.AccessInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import java.util.List;
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
UserArgumentResolver userArgumentResolver;
@Autowired
AccessInterceptor accessInterceptor;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
//將UserArgumentResolver註冊到config裏面去
argumentResolvers.add(userArgumentResolver);
}
/**
* 註冊攔截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//註冊
registry.addInterceptor(accessInterceptor);
super.addInterceptors(registry);
}
}
然後就可以直接在controller裏面的方法裏獲取我們想要的MiaoshaUser參數,如下: