springBoot对jedis的整合and顶替登录续租和缓存查询的后台代码功能实现案例
1.springBoot对jedis的整合:
springBoot对jedis做了很好的整合帮我们把槽道这一类算法已经给封装了起来,我们需要的仅仅是学会使用他的api即可
第一步:在pom文件中使用依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency>
第二步:springBoot自动配置需要一些参数,根据redis的结构不同,可以去application中配置不同的参数:
-
单节点redis:
spring.redis.host=ip地址
spring.redis.port=端口 -
哨兵集群:
spring.redis.sentinel.master=主从代号
spring.redis.sentinel.nodes=哨兵节点信息 ip1:port1,ip2,port2 -
集群结构:
spring.redis.cluster.nodes=若干个集群节点信息 ip1:port1,ip2:port2 -
不同结构都共享连接池属性配置:
spring.redis.pool.maxActive=最大连接
spring.redis.pool.maxIdle=最大空闲
spring.redis.pool.minIdle=最小空闲
这里的测试案例我们使用的是cluster结构,所以配置如下:
spring.redis.cluster.nodes=10.9.100.26:8000,10.9.100.26:8001,10.9.100.26:8002
第三步:就可以开始写java代码来使用redis了!
声明一个
@Autowired private StringRedisTemplate template;
因为redis中又五种数据格式,所以template对应着5种操作redis数据的方式:
//查询redis中的key template.hasKey(KeyName); //String ValueOperations<String, String> StringOps = template.opsForValue(); StringOps.set(key, UUID.randomUUID().toString()); //hash HashOperations<String, Object, Object> hashOps = template.opsForHash(); //list ListOperations<String, String> listOps = template.opsForList(); //set opsForSet //zset opsForZset
2.顶替登录
redis可以解决微服务中的session共享问题,原因看我上一篇微博,这里我们就使用stringredistemplate和一个redis的cluster结构来实现这个功能:
首先我们需要一个思路来解决这个问题:账号的登录状态从什么地方获取?存储在什么地方?怎么判断用户是否登录?
答: 当用户第一次登录,他的登录状态会以key-value的形式保存在redis中,并且以cookie的形式返回一份给当前浏览器,当这个key在redis中没有被删除,我们就认为用户是登录状态。当用户使用另外一个浏览器进行登录,这个时候我们就会获取到上一个浏览器的登录key,通过这个key把它在redis中删除,旧浏览器再去拿登录信息的时候,就会发现拿到的是一个null,这样就退出了登录状态。
public String doLogin(User user) { user.setUserPassword(MD5Util.md5(user.getUserPassword())); //数据库中查找此人账号密码是否正确 User existUser=userMapper.selectUserByNameAndPw(user); if(existUser==null){ return ""; }else { //存在user 先解决登录顶替的问题 //生成一个和userName有关的key String userLoginKey="user_login_"+user.getUserName(); //判断userLoginKey是否存在,存在则说明有相同用户曾经登录过 if(template.hasKey(userLoginKey)){ //说明有人登陆过,相同用户,把上次ticket获取删除 //获取value值就是上次的ticket String oldTicket=template.opsForValue().get(userLoginKey); template.delete(oldTicket); } String ticket="EM_TICKET_"+user.getUserName()+System.currentTimeMillis(); try{ existUser.setUserPassword(""); String userJson = mapper.writeValueAsString(existUser); //第一次登录时在redis中存入ticket和userjson template.opsForValue().set(ticket,userJson,2, TimeUnit.HOURS); //为后续登录顶替我做准备 template.opsForValue().set(userLoginKey,ticket,2,TimeUnit.HOURS); return ticket; }catch(Exception e){ e.printStackTrace(); return ""; } } }
这里需要注意的是,每次登录的时候,都会set两个key-value到redis中,一个是用来记录这次登录的key-value,另外一个是用来记录这次登录的key,这样顶替登录的时候,就可以找到上一次登录的key,用这个key去redis中做删除操作。
关于登录的续租问题: 我们在redis中的key-value时间总是固定的,超时就会被销毁,销毁用户就会退出登录,为了提高用户的需求,我们就需要给用户的key-value做一个超时续租。具体原理就是,每次浏览器刷新的时候,都会拿着自己cookie中的key,去后台找redis,看看自己还能活多长时间,如果活不了多久了,就赶紧叫redis给自己增加寿命。
前端传递过来带参数的请求链接,这是一个rest风格,可以从链接中获取参数,拿着这个参数去调用service层
@RequestMapping("/query/{ticket}") public SysResult queryTicket(@PathVariable String ticket){ String userJson=userService.queryTicket(ticket); if(userJson==null||"".equals(userJson)){ return SysResult.build(201,"超时",null); }else { return SysResult.build(200,"ok",userJson); } }
service层:
service层拿着这个key值(ticket),就去redis中找,找得到就进行一系列操作,找不到就retrun null
@Autowired private StringRedisTemplate template;//续租时间 public String queryTicket(String ticket) { Long leftTime=template.getExpire(ticket,TimeUnit.SECONDS); if(leftTime>0&&leftTime<60*60){ //给第一个set到redis的key-value延长寿命 template.expire(ticket,60*60*2,TimeUnit.SECONDS); String userName=ticket.substring(9,(ticket.length()-13)); String userLoginKey="user_login_"+userName; //给第二个set到redis的key-value延长寿命 template.expire(userLoginKey,60*60*2,TimeUnit.SECONDS); } return template.opsForValue().get(ticket); }
注意这里如果要续租,要给两个redis中的key-value续租,不然顶替登录就会出现找不到上一个登录浏览器的key值情况,从而导致顶替登录失败。
3.缓存查询:
思路: 当查询商品的时候,先到缓存中进行查询,如果有则直接返回,没有的话就去数据库中查,查到了就去缓存中保存一份,然后返回查询结果,没有查到就返回null。
@Autowired private ProductMapper productMapper; //json和对象互相转换的序列化 ObjectMapper mapper=new ObjectMapper(); //调用redis的对象 @Autowired private StringRedisTemplate template; public Product queryOneProduct(String productId) { //被动缓存 //判断缓存中有没有当前商品的数据key-value 有则直接使用无则到数据库查询 //存放缓存一份 String productKey="product_"+productId; //缓存中查到了数据 if(template.hasKey(productKey)){ //获取缓存json String pJson = template.opsForValue().get(productKey); try{ //反序列化成product return mapper.readValue(pJson,Product.class); }catch (Exception e){ e.printStackTrace(); return null; } }else{ //缓存没有数据,查询数据库 Product product = productMapper.selectProductById(productId); //存放到缓存一份 key=productKey value=pJson try{ //把对象转化为json,存入缓存中 String pJson = mapper.writeValueAsString(product); template.opsForValue().set(productKey,pJson,2, TimeUnit.DAYS); }catch (Exception e ){ e.printStackTrace(); return null; } return product; } }
缓存查询的数据一致性问题: 当给查询加入了缓存逻辑后,就会出现一个缓存一致性问题,即缓存数据要和DB中的数据一致。例如:当你查询一条数据,发现缓存中有,于是就去了缓存中查询,但是在DB中已经被修改了,你所查出来的数据就不一致。
解决办法:当使用更新数据功能的时候加上一个缓存锁,用人话来说就是,当你使用更新数据功能的时候,把你修改的商品id存入redis中,当你修改过程中有人去缓存中查这个商品的时候,我们就先判断一下,这个商品上有没有锁,如果有就去DB中查询(需要注意,更新商品数据完成后,要把在缓存中旧数据删除)
更新代码:
public void editProduct(Product product) { //保证1 2 同步执行完整 //添加一个内存锁,被动缓存中,只要发现这个内存锁,说明有人在更细年数据 //不会使用缓存 String lock="product_"+product.getProductId()+".lock"; Boolean ok = template.opsForValue().setIfAbsent(lock, "");//set NX if(ok){//设置锁成功 //删除旧缓存 template.delete("product_"+product.getProductId()); //执行数据库更新 productMapper.updateProductById(product); }else{ throw new RuntimeException("更新缓存发现有冲突,有人正在更新"); } //释放锁 template.delete(lock); }
查询代码:
public Product fqueryOneProduct(String productId) { //判断锁是否存在 String lock="product_"+productId+".lock"; if(template.hasKey(lock)){ return productMapper.selectProductById(productId); } //被动缓存 //判断缓存中有没有当前商品的数据key-value 有则直接使用无则到数据库查询 //存放缓存一份 String productKey="product_"+productId; if(template.hasKey(productKey)){ //获取缓存json 反序列化成product String pJson = template.opsForValue().get(productKey); try{ return mapper.readValue(pJson,Product.class); }catch (Exception e){ e.printStackTrace(); return null; } }else{ //缓存没有数据,查询数据库 Product product = productMapper.selectProductById(productId); //存放到缓存一份 key=productKey value=pJson try{ String pJson = mapper.writeValueAsString(product); template.opsForValue().set(productKey,pJson,2, TimeUnit.DAYS); }catch (Exception e ){ e.printStackTrace(); return null; } return product; } }
- springboot整合shiro登录失败次数限制功能的实现代码
- maven项目 spring-boot 整合 mybatis 实现查询功能demo
- Spring Boot/Angular整合Keycloak实现单点登录功能
- [分页查询]SpringBoot整合PageHelper分页插件实现简单的分页查询(含前端及后端代码)
- SpringBoot 整合Shiro 实现登录验证拦截功能
- spring boot整合Shiro实现单点登录的示例代码
- 使用IDEA创建Spring Boot项目, 整合Mybatis ,连接MySql数据库,实现简单的登录注册功能
- 【个人学习】使用idea搭建SpringBoot,整合Mybatis、Thymeleaf,连接数据库,实现具有前端界面项目:主要功能登录,注册,个人信息查看、更改,不定时更新中...
- Spring Boot整合Ehcache实现缓存功能
- spring boot 整合shiro和redis实现权限管理和登录功能
- spring整合ehcache注解实现查询缓存,并实现实时缓存更新或删除
- [置顶] 【二】Springboot+Mybatis+Redis实现用户信息查询缓存
- 搭建spring-boot+vue前后端分离框架并实现登录功能
- Spring Boot整合WebSocket 实现消息群发(群聊)功能
- Spring Boot 整合 Redis 实现缓存操作
- S2SH整合JQuery+Ajax实现登录验证功能实现代码
- Spring整合Mybaits实现ehcache 注解查询缓存
- spring-boot之spring-boot整合ehcache实现缓存机制
- SpringBoot2.0整合SpringSecurity实现自定义表单登录
- JWT整合springboot实现token认证(代码实践)