1. 程式人生 > >許可權----用多執行緒優化登入

許可權----用多執行緒優化登入

前言:

  • 在最近這個許可權框架中,小編髮現登入很慢,因為是框架啟動後第一次登入,需要建立很多物件還有打通額外的連結,所以很慢,但是第二次登入就明顯的快了。於是開啟專案的原始碼,開始優化起來,在瞭解完大體的業務邏輯後,感覺能用上多執行緒,於是就測試了一下,發現挺管用,小編將經驗分享給大家吧!

使用的技術點

  • SSM
  • Shiro
  • Json Web Token
  • Redis

登入的邏輯

  1. 使用者輸入使用者名稱和密碼傳送給後端程式,Shiro去驗證
  2. 驗證成功後,生成token資訊,緊接著將token資訊存入Redis
  3. 呼叫Dubbo服務,將使用者的基礎資訊、存入Redis
  4. 呼叫Dubbo服務,將使用者的許可權標識存入Redis
  5. 給使用者返回登入資訊,主要是token資訊

    這裡寫圖片描述

登入慢的原因

  • Shiro在第一次認證的時候很耗時
  • 第一次呼叫Dubbo服務的時候很耗時

  • 在shiro第一次認證時,需要走shiro的內部很多程式,這耗點時間,第一呼叫dubbo服務,需要建立連線,也浪費點時間。

  • 估計有讀者在問,為什麼還需要呼叫dubbo服務存入使用者資訊那?shiro認證完了使用者資訊不就在庫裡都搜出來了嗎?原因是醬紫滴:因為使用者的一些基本資訊是其他服務提供的,不是許可權框架提供的,這個許可權框架是單獨抽出來做認證、授權、單點登入用的,在自己的微服務中並不能將使用者的所有資訊都在自己的庫中搜出來。

優化思路

  • 當shiro認證通過之後,我們需要生成使用者的token資訊,將token資訊存入實體後返回給前臺,而在這個過程,呼叫dubb服務和往redis存入任何資訊都是不耽誤給前臺返回資訊的,也就是說登入邏輯中的第一步過後,第2、3、4不是互不干涉的,於是小編就將2、3、4步抽到子執行緒中去執行,結果效果非常明顯。
  • 讓主執行緒去認證和返回使用者資訊,讓子執行緒呼叫dubbo介面存入使用者資訊。

主要登入方法程式碼

@RestController
@RequestMapping("/access")
public class AccessController extends BaseController {

    @Autowired
    private UserService userService;

    //列印日誌相關
    private static final Logger logger = LoggerFactory.getLogger(AccessController.class);

    //建立一個執行緒池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); @RequestMapping(value = "/login", method = RequestMethod.POST) public Object login(@RequestBody AllUsers allUsers) throws InterruptedException{ final CountDownLatch connectedSemaphore = new CountDownLatch(1); String userCode=allUsers.getUserCode(); String pwd=allUsers.getPassword(); final VOUserLogin userLogin = new VOUserLogin(); if(StringUtils.isEmpty(userCode)){ throw new UserCodeRequiredException(); } if(StringUtils.isEmpty(pwd)){ throw new PasswordRequiredException(); } //shiro進行登入驗證 SecurityUtils.getSubject().login(new UsernamePasswordToken(userCode,pwd)); //驗證成功後將使用者資訊搜出來 final AllUsers user= userService.findByUserCode(userCode); //生成token,返回userLogin物件 cachedThreadPool.execute(new Runnable() { public void run() { try { //上次訪問的時間標誌,將它和token一塊存入redis中,解決token過期問題 String strNowMillis=String.valueOf(System.currentTimeMillis()); String lastLoninTime=PasswordUtil.base64Encoede(strNowMillis); //生成token String token=TokenUtil.generate(UUidUtil.generate(),user.getId(),"http://tfjybj.com",60*60*1000,user.getSchoolNo()); userLogin.setToken(token+"@"+lastLoninTime); BeanUtils.copyProperties(userLogin, user); connectedSemaphore.countDown();//釋放訊號,返回userLogin物件 String loginKey="aum"+":"+"tokenMessage"+":"+user.getSchoolNo()+":"+user.getId(); //向redis中存入登入資訊(token) addMessageToRedis(loginKey,userLogin.getToken(),60*30); } catch (Exception e) { logger.error("AccessController.thread1 生成token並存入Redis失敗:{}",e); } } }); cachedThreadPool.execute(new Runnable() { public void run() { String userInfoKey = "aum" + ":" + "userInfo" + ":" + user.getSchoolNo() + ":" + user.getId(); //向redis中 存入使用者資訊 UserModel userModel=new UserModel(); try { BeanUtils.copyProperties(userModel, user); addUserInfoToRedis(userInfoKey,userModel); //向redis中 存入使用者許可權標識資訊 addPermissionsToRedis(userInfoKey,user.getSchoolNo()); } catch (Exception e) { logger.error("AccessController.thread2 向Redis存入使用者資訊和許可權標識失敗",e); } } }); //返回登入實體 connectedSemaphore.await(); return userLogin; } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

呼叫dubbo服務,向redis中存入使用者資訊

 public void addUserInfoToRedis(String key,UserModel userModel){
        userService.getUserInfo(key,userModel);
    }
  • 1
  • 2
  • 3

呼叫dubbo服務,向redis中存入使用者的許可權標識

public void addPermissionsToRedis(String key,String schoolNo){
       String userInfo= jedisCacheUtil.get(key);
       if (StringUtils.isNotBlank(userInfo)){
           try {
               UserModel userModel=JacksonJsonUntil.jsonToPojo(userInfo, UserModel.class);
               List<String> roleIds=userModel.getRoleId();
               if (roleIds.size()>0){
                   userService.getPermissionsFromRedis(roleIds,schoolNo);
               }
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

小結

  • 通過上面的優化,整個登入時間提升了大3/4,瞬間感覺到了多執行緒的魅力了,還有值得一提的是,小編由於用的是SpringMvc,登入所用的Handler是單例的,所以小編聲明瞭一個帶緩衝的執行緒池,也就是說,當有大量使用者登入時,這個緩衝池就能提現效果了。