权限----用多线程优化登录

来源:互联网 发布:江南大学网络教育报名 编辑:程序博客网 时间:2024/05/23 23:54

前言:

  • 在最近这个权限框架中,小编发现登录很慢,因为是框架启动后第一次登录,需要创建很多对象还有打通额外的链接,所以很慢,但是第二次登录就明显的快了。于是打开项目的源码,开始优化起来,在了解完大体的业务逻辑后,感觉能用上多线程,于是就测试了一下,发现挺管用,小编将经验分享给大家吧!

使用的技术点

  • 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是单例的,所以小编声明了一个带缓冲的线程池,也就是说,当有大量用户登录时,这个缓冲池就能提现效果了。
原创粉丝点击