您的位置:首页 > 编程语言 > Java开发

springBoot对jedis的整合and顶替登录续租和缓存查询的后台代码功能实现案例

2020-06-07 04:40 411 查看

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;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: