日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

1|0業務場景

在單機系統中,用戶登陸之后,服務端會保存用戶的會話信息,只要用戶不退出重新登陸,在一段時間內用戶可以一直訪問該網站,無需重復登陸。用戶的信息存在服務端的 session 中,session中可以存放服務端需要的一些用戶信息,例如用戶ID,所屬公司companyId,所屬部門deptId等等。

分布式系統中session一致性問題

 

但是隨著業務的發展,技術架構需要調整,原來的單機系統逐漸被更換,架構由單機擴展到分布式,甚至當下流行的微服務。雖然在用戶端看來系統仍然是一個整體,但在技術端來說業務則被拆分成多個模塊,各個模塊之間相互獨立,甚至不在同一臺物理機器上,模塊之間通過 RPC 進行通信。

分布式系統中session一致性問題

 

那么原來單機只需一份的 session, 如何滿足在多系統的運行下保證會話一致性呢?單獨保存在任何一個系統中都不合適,而且每個單獨模塊系統也可能是分布式形式的,是由集群組成。那么session的分配就更復雜了。

2|0redis 實現

針對以上問題,我們可能會從以下幾個方面想到解決的方法,每個服務端存儲一份,通過同步的方式保證一致性,但是這種方式有個很明顯的缺點:session的同步需要數據傳輸,占內網帶寬,有時延,網絡不穩定的時候會造成部分系統同步延遲,那么就不能保證 session 一致性。而且所有服務端都包含所有session數據,數據量受內存限制,無法水平擴展。

那么我們是否可以單獨將 session 信息存儲在某一個獨立的介質中,介質可以是DB也可以是緩存。

考慮到如下業務:登陸的時候我們經常會給用戶一個過期時間(一般移動端常設置為7天或者一個月甚至更久),到期后用戶需要輸入登陸信息重新登陸,即會話過期。這種到期的設置我們自然想到了Redis的 key expire功能,所以最終我們可以將Redis引入進來實現我們的這種需求。系統如下圖所示:

分布式系統中session一致性問題

 

我們只需在用戶首次登陸的時候將用戶信息放到 Token并緩存到 Redis 中,同時設置一個過期時間,偽代碼如下:

@Override
 public Map login(UserDto dto) {
 Map<String, Object> restMap = new HashMap<>();
 
 // 校驗登陸信息
 User user = checkLoginInfo(dto);
 //刪除舊的token
 String token = (String) redisUtils.get(CacheConstants.USER_TOKEN_KEY_COPY + user.getUserName());
 
 if (!ObjectUtils.isEmpty(token)) {
 redisUtils.delete(CacheConstants.USER_TOKEN_KEY_WEB + token);
 }
 // 唯一簽名信息
 String signStr = user.getCompanyId() + user.getUserName() + dto.getPassword() + DateUtils.now().getTime();
 token = MD5Utils.md5(signStr);
 // 設置用戶 token
 redisUtils.setExpiredAt(CacheConstants.USER_TOKEN_KEY_WEB + token, user.getId(), LOGIN_EXPIRED_TIME);
 //緩存新的token
 redisUtils.setExpiredAt(CacheConstants.USER_TOKEN_KEY_COPY + user.getUserName(), token, LOGIN_EXPIRED_TIME);
 dto.setCompanyId(user.getCompanyId());
 dto.setId(user.getId());
 restMap.put("token", token);
 restMap.put("userName", user.getUserName());
 return restMap;
 }

那么在系統中如何使用呢,我們可以定義一個攔截器 SessionInterceptor,當訪問 web 接口的時候檢驗用戶的 token 信息,判斷用戶是否登陸,未登錄的情況下一些業務接口是無法訪問的,以及在登陸的情況下拿到我們需要的用戶信息,如 userId。

public class SessionInterceptor {
 @Autowired
 private RedisUtils redisUtils;
 
 @Autowired
 private UserService userService;
 @Pointcut("execution(* com.jajian.demo.web.*.controller.*.*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMApping)")
 public void controllerMethodPointcut() {
 }
 @Around("controllerMethodPointcut()")
 public Object Interceptor(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
 
 Signature signature = proceedingJoinPoint.getSignature();
 MethodSignature methodSignature = (MethodSignature) signature;
 Method targetMethod = methodSignature.getMethod();
 if (targetMethod.getDeclaringClass().isAnnotationPresent(NoLogin.class) || targetMethod.isAnnotationPresent(NoLogin.class)) {
 return proceedingJoinPoint.proceed();
 }
 // 從獲取RequestAttributes中獲取HttpServletRequest的信息
 RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
 HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
 String token = request.getHeader("token");
 if(StringUtils.isEmpty(token)){
 Log.debug("驗證token", "token驗證失敗,{}", "token不存在");
 throw new FieldException(Constants.LOGIN_ERROR_CODE, "login.session.timeout");
 }
 Integer userId= (Integer)redisUtils.get(CacheConstants.USER_TOKEN_KEY_WEB + token);
 
 if (null == userId) {
 Log.debug("驗證token", "token驗證失敗,{}", "token超時");
 throw new FieldException(Constants.LOGIN_ERROR_CODE, "login.session.timeout");
 }
 User user = userService.getById(userId.longValue());
 if (ObjectUtils.isEmpty(user)){
 Log.debug("驗證token", "token驗證失敗,{}", "用戶信息不存在");
 throw new FieldException(Constants.LOGIN_ERROR_CODE, "login.session.timeout");
 }
 if (user.getStatus() == UserStatusEnum.NO.getCode() || user.getDeleteFlag() == DeleteFlagEnum.YES.getCode()){
 Log.debug("驗證token", "token驗證失敗,用戶信息異常 userName : {}, status : {},deleteFlag : {}", user.getUserName(),user.getStatus(), user.getDeleteFlag());
 throw new FieldException(Constants.LOGIN_ERROR_CODE, "login.session.timeout");
 }
 return proceedingJoinPoint.proceed();
 }
 
}

以上實現方式簡單易用,而且Redis 在分布式系統中的使用率也很高,所以無需額外的技術引入。可以支持水平擴展,數據庫或緩存水平切分即可,服務端重啟或者擴容都不會有session丟失的情況發生。

分享到:
標簽:分布式 系統
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定