SpringCloud微服務架構分散式元件如何共享session物件
一.簡單做一個背景說明
1.為說明問題,本文簡單微服務架構示例如下
2.元件說明
分散式架構,每個元件都是叢集或者主備。具體說明如下:
zuul service:閘道器,API呼叫都走zuul service。
micro service1 & micro service2:業務功能實現,資料庫增刪改查。
eureka:元件註冊,zuul service,micro service等元件都註冊到eureka,管理元件呼叫地址。
db-master & db-slave:資料庫叢集,一主兩從。
redis master & redis slave:redis叢集,快取。這裡主要儲存session物件。
3.元件之間API呼叫
①:閘道器zuul接收到的API請求,路由至業務實現元件。
②:閘道器zuul以及業務元件將session物件儲存到redis、或從redis獲取session物件。
③:業務元件實現資料增刪改查。
④:業務元件之間通過springCloud feign元件進行呼叫。
⑤:閘道器zuul以及micro service元件註冊到eureka元件,或從eureka獲取元件呼叫地址。
二.存在問題
基於如上微服務的分散式架構如果按照傳統方式,將session物件儲存在記憶體中。在zuul閘道器將路由請求至不同的micro service1或者micro service2時,記憶體中的session物件將不能被共享,無法判斷使用者的登陸狀態,也無法獲取session物件儲存的全域性資料。
三.解決方案
1.Spring管理session物件
通過EnableRedisHttpSession註解支援基於Redis儲存session,全域性共享session物件。
2.微服務架構下共享session物件實現說明
1)客戶端API請求到zuul,zuul基於spring管理session將session物件儲存到redis,並將生成的sessionId返回給客戶端。
2)zuul將請求路由到micro service,將sessionId通過cookie頭帶給micro service。
3)micro service通過sessionId從redis獲取到已經生成的session物件。
4)micro servcie1呼叫micro service2時,將sessionId也通過cookie頭帶給micro service2,micro service2通過sessionId從redis中獲取session物件。
5)客戶端再次呼叫時將a)步返回的sessionId增加到cookie頭,在redis中儲存的session失效之前zuul和micro service一直共享這個session。
5.具體實現
1)通過springframework的EnableRedisHttpSession註解管理session,zuul和micro service元件實現這個類以儲存、獲取redis中儲存的session物件。
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = GlobalConstants.SESSION_TIMOUT, redisFlushMode = RedisFlushMode.IMMEDIATE)
public class SessionConfig {
}
EnableRedisHttpSession註解引數說明:
maxInactiveIntervalInSeconds:session過期時間配置。
redisFlushMode:redis session重新整理模式。配置為RedisFlushMode.IMMEDIATE,可以確保zuul儲存到redis的session物件在請求到micro service中能立即被獲取。在實際開發過程中出現由於沒有這個配置值,有時候zuul將session物件儲存到了redis,但是micro service無法立即獲取。
2)在zuul過濾器方法中呼叫addZuulRequestHeader增加請求頭,將sessionId通過cookie頭路由到micro service。
public class AccessFilter extends ZuulFilter {
@Autowired
HttpServletRequest httpServletRequest;
@Autowired
HttpServletResponse httpServletResponse;
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String sessionId = httpServletRequest.getSession().getId();
ctx.addZuulRequestHeader("Cookie", "SESSION=" + sessionId);
ctx.setSendZuulResponse(true);// 對該請求進行路由
ctx.setResponseStatusCode(200); // 返回200正確響應
3)micro service1通過feign呼叫micro service2時,實現RequestInterceptor介面。通過增加cookie頭,將sessionId帶到micro service2。
@Configuration
public class MyRequestInterceptor implements RequestInterceptor {
@Autowired
HttpServletRequest request;
@Override
public void apply(RequestTemplate requestTemplate) {
logger.info("MyRequestInterceptor apply begin.");
try {
String sessionId = RequestContextHolder.currentRequestAttributes().getSessionId();
if (null != sessionId) {
requestTemplate.header("Cookie", "SESSION=" + sessionId);
}
} catch (Exception e) {
logger.error("MyRequestInterceptor exception: ", e);
}
}
}
6.驗證
1)通過postman請求zuul服務地址,呼叫登陸介面。
2)檢視各元件sessionId
zuul sessionId:
micro service1 sessionId:
micro service1呼叫micro service2 sessionId:
結論:可以看到zuul和micro service中sessionId都是相同的,都是586b*c9a4,通過這種方式實現了API呼叫過程中的session物件共享。