赞
踩
用户在提交手机号后,会校验手机号是否合法
为啥什么要存到Session中?因为登录的时候还要校验这个验证码。后面会用Redis代替Session。
用户将验证码和手机号进行输入,后台从Session中拿到当前验证码。然后和用户输入的验证码进行校验,如果不一致,则无法通过校验,如果一致,则后台根据手机号查询用户,如果用户不存在,则为用户创建账户信息,保存到数据库,最后都将用户保存到session中,方便后续获得当前登录信息。
用户在请求时候,会从Cookie
中携带着JsessionId
到后台,后台通过Session
中拿到用户信息,如果没有Session
信息,则进行拦截,如果有则将用户信息保存到ThreadLocal
中,并且放行。
登录校验功能不止需要在登录地过程中需要实现,在其他功能上也需要实现登录校验功能,让很多业务逻辑执行之前需要实现登录校验,可以使用拦截器来实现。
有一个问题:实现拦截之后,在后续的使用过程中是需要获取用户信息的,让拦截器拦截到的用户信息传递到Controller
里,并且还要实现线程安全问题。
可以使用ThreadLocal
来做到线程隔离,让每个线程操作自己的一份数据。
ThreadLocal是如何实现线程隔离的?
看过threadLocal的源码,你会发现在threadLocal中,无论是他的put方法还是get方法, 都是先获得当前用户的线程,然后从线程中取出线程的成员变量map,只要线程不一样,map就不一样,所以可以通过这种方式来做到线程隔离。
如何隐藏用户敏感信息?
通过浏览器观察到此时用户的全部信息都在,这样极为不靠谱,所以我们应当在返回用户信息之前,将用户的敏感信息进行隐藏,采用的核心思路就是书写一个UserDto对象,这个UserDto对象就没有敏感信息了,在返回前,将有用户敏感信息的User对象转化成没有敏感信息的UserDto对象,那么就能够避免这个尴尬的问题了
Session 共享问题
每个服务器中都有一份属于自己的session,假设用户第一次访问第一台服务器,并且把信息存放到第一台服务器的session中,但是第二次这个用户访问到了第二台服务器,那么在第二台服务器上,肯定没有第一台服务器存放的session,所以此时 整个登录拦截功能就会出现问题,如何解决这个问题呢?
早期的方案是session拷贝,就是说虽然每个服务器上都有不同的session,但是每当任意一台服务器的session修改时,都会同步给其他的服务器,这样的话,就可以实现session的共享
但是这种方案具有两个大问题
1、每台服务器中都有完整的一份session数据,服务器压力过大。
2、session拷贝数据时,可能会出现延迟
后来采用的方案都是基于redis来完成,把session换成redis,redis数据本身就是共享的,就可以避免session共享的问题了
发送验证码信息:将手机号作为key,生成的验证码作为value存到redis中,等登录的时候,在去校验验证码是否一致。
验证码登录、注册:将用户发过来的手机号和验证码进行校验,查询redis对应的验证码,检验是否和发送过来的验证码是否一致,如果一致查询是否存在用户信息,如果不存在则去创建用户信息,最后将该用户信息存入redis中,以token作为key,用户信息作为value。为了后续的校验登录状态。
校验登录状态:根据请求携带的token去redis查询对应的用户信息,如果没有则拦截,如果有则保存到Threadlocal中,并且放行。
首次请求验证码
非首次请求验证码
ip地址或手机号每发送一条验证码,其计数就会加1,同时为其设置过期时间(每次计数加一就会重新设置过期时间),因此,如果一段时间内发送的验证码达到上限,就会停止发送,直到该计数过期。
定时删除策略:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。(创建定时器删除)
惰性删除策略:放任键过期不管,每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。(使用的时候删除)
定期删除策略:每隔一段时间,程序就对数据库进行一次检查,删除里面过期的键。至于要删除多少过期键,以及要检查多少个数据库,则有算法决定。(定期扫描删除)
Redis 实际使用的过期键删除策略是定期删除策略和惰性删除策略:
Redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,在客户端访问这个 key 的时候,Redis 对 key 的过期时间进行检查,如果过期了就立即删除。定期删除是集中处理,惰性删除是零散处理。通过配合使用这两种删除策略,服务器可以很好地合理使用CPU时间和避免浪费内存空间之间取得平衡。
Redis是部署在物理机上的,内存不可能无限扩充的,当内存达到我们设定的界限后,便自动触发Redis内存淘汰策略,而具体的策略方式要根据实际业务情况进行选取。
LRU的全称为Least Recently Used,翻译出来就是最近最少使用的意思。它是一种内存淘汰算法,当内存不够时,将内存中最久没使用的数据清理掉。LRU算法常用于缓存的淘汰策略。
为什么要引入LFU算法呢?因为LRU算法淘汰数据的立足点是根据使用时间间隔淘汰数据,将当前内存中最长时间没有使用过的数据清理掉。这样就存在一个问题:如果一个不会经常使用的数据偶然被使用了一次,那这个并不被经常使用的数据就会一直待在内存中,浪费内存空间。如果我们想淘汰内存中不经常使用的数据,保留经常使用的热点数据,就要使用到LFU算法了。
LFU的全称为Least Frequently Used,意思就是最不频繁使用,所以,LFU算法会淘汰掉使用频率最低的数据。如果存在相同使用频率的数据,则再根据使用时间间隔,将最久未使用的数据淘汰。
LRU算法淘汰数据的注重点是时间间隔,只淘汰最久未使用的数据;LFU算法淘汰数据的注重点是使用频率,只淘汰最不经常使用的数据。
LRU算法实现简单,只需要一个hash表+一个双向链表即可实现;LFU算法实现就复杂很多,需要两个hash表+多个双向链表才能实现。
具体使用时,选择哪种算法作为内存淘汰策略要看具体场景,如果对于热点数据的查询要求比较高,则最好采用LFU算法作为内存淘汰策略。如果没有那么高的热点数据要求,则可以选择实现更为简单的LRU算法。
需要注意的是,Redis中的LRU算法并没有严格按照常规的LRU算法的方式实现,而是基于LRU算法的思想做了自己的优化。我们知道,实现LRU算法时,需要将所有的数据按照访问时间距离当前时间的长短排序放到一个双向链表中,基于这个链表实现数据的淘汰。但Redis中存储的数据量是非常庞大的,如果要基于常规的LRU算法,就需要把所有的key全部放到这个双向链表中,这样就会导致这个链表非常非常大,不止需要提供更多的内存来存放这个链表结构,而且操作这么庞大的链表的性能也是比较差的。
所以,Redis中的LRU算法是这样实现的:首先定义一个淘汰池,这个淘汰池是一个数组(大小为16),然后触发淘汰时会根据配置的淘汰策略,先从符合条件的key中随机采样选出5(可在配置文件中配置)个key,然后将这5个key按照空闲时间排序后放到淘汰池中,每次采样之后更新这个淘汰池,让这个淘汰池里保留的总是那些随机采样出的key中空闲时间最长的那部分key。需要删除key时,只需将淘汰池中空闲时间最长的key删掉即可。
过期策略是在正常情况下清除过期键的策略;内存淘汰策略是在非正常情况下为保证 Redis顺利运行的保护策略。
配置Redis最大内存:在配置文件redis.conf 中,可以通过参数 maxmemory 来设定最大内存;当数据内存达到 maxmemory 时,便会触发redis的内存淘汰策略。该参数通常设定为其物理内存的四分之三。
配置Redis淘汰策略:在配置文件redis.conf 中,通过设置 maxmemory-policy 来指定使用哪种内存淘汰策略
配置最大采样数量:在讲Redis的LRU算法的时候,提到了Redis每次会随机选择5个key放入淘汰池中,这个5的数量就是在redis.conf配置文件中通过 maxmemory-samples 选项配置的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。