【商城秒殺項目】-- 分佈式Session的實現

通常秒殺項目可能不止部署在一個服務器上,而是使用分佈式部署在多臺服務器,這時候假如用戶登錄是在第一個服務器,第一個請求到了第一臺服務器,這是沒問題的;但是第二個請求到了第二個服務器,那麼用戶的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參數,如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章