栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java项目中使用redis

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java项目中使用redis

通过引入SpringRedisTemplate使用redis 1.单机模式下使用

如下代码段(在单机环境下多线程访问会有库存扣减不一致问题,比如线程一扣完库存,同时线程二争抢到资源,此时线程二获取到的库存仍是没有扣减的库存)

@RestController
public class IndexController{
    @Autowired
    private StringRedisTemplate stringRedisTempldate;

    public String deductStock(){
        int stock = Integer.parseInt(stringRedisTempldate.opsForValue().get("stock"));
        if(stock > 0){
            int realStock = stock - 1;
            StringRedisTemplate.opsForValue().set("stock",realStock+"");
            System.out.println("减扣成功,剩余库存:"+realStock );
        }else{
            System.out.println("减扣失败,余额不足");
        }
        return "end";
    }
}

单机环境下解决办法:加入同步块或者锁,修改如下:

@RestController
public class IndexController{
    @Autowired
    private StringRedisTemplate stringRedisTempldate;

    public String deductStock(){
        synchronized(this){
            int stock = Integer.parseInt(stringRedisTempldate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                StringRedisTemplate.opsForValue().set("stock",realStock+"");
                System.out.println("减扣成功,剩余库存:"+realStock );
            }else{
                System.out.println("减扣失败,余额不足");
            }
        }
        return "end";
    }
}
2.分布式环境下

分布式环境下上面的代码就会失效,仍然会出现库存扣减不一致的问题。假如我们通过nginx配置一个负载均衡,反响代理到自己模式的两台服务器上面,通过jmeter进行高并发请求压测,就会出现库存扣减不一致的问题,出现超卖的概率越大。synchronized可以控制解决单机的并发问题,不能跨jvm。

解决步骤思路如下:

1.使用用redis的setnx的命令(

        sentnx命令格式:setnx key value

           将key的值设为value,当且仅当key存在。

           若给定的key已经存在,则setnx不做任何操作。

         setnx是『set if not exists』(如果不存在,则set)的简写。

),可以简单的构建分布式锁。

@RestController
public class IndexController{
    @Autowired
    private StringRedisTemplate stringRedisTempldate;

    public String deductStock(){
        String lockKey = "kockKey";
        Boolean result = stringRedisTempldate.opsForValue().setIfAbsent(lockKey,"abagg");
        //true代表加锁成功,false代表加锁失败,直接返回错误
        if(!result){
            return "error_code";
        }
                int stock = Integer.parseInt(stringRedisTempldate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                StringRedisTemplate.opsForValue().set("stock",realStock+"");
                System.out.println("减扣成功,剩余库存:"+realStock );
            }else{
                System.out.println("减扣失败,余额不足");
            }
        stringRedisTempldate.delete(lockKey);
        return "end";
    }
}

原理大致如下:当多个请求同时进来访问nginx,反响代理到不同服务器上,当线程执行到redis上时,就会进行排队,通过setnx这条指令看看是否加锁成功,成功之后程序执行完,下个线程才能进来。

异常情况一:假如加锁成功,业务代码突然抛异常了,就意味着redis那边的key永远删除不了了,下面的请求再过来永远就获取不到锁,造成死锁。解决方法,加个try-finally,这样抛异常锁就正常删除,代码如下:

@RestController
public class IndexController{
    @Autowired
    private StringRedisTemplate stringRedisTempldate;

    public String deductStock(){
        String lockKey = "kockKey";
        try{
         Boolean result = stringRedisTempldate.opsForValue().setIfAbsent(lockKey,"abagg");
            //true代表加锁成功,false代表加锁失败,直接返回错误
            if(!result){
                return "error_code";
            }
                int stock = Integer.parseInt(stringRedisTempldate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                StringRedisTemplate.opsForValue().set("stock",realStock+"");
                System.out.println("减扣成功,剩余库存:"+realStock );
            }else{
                System.out.println("减扣失败,余额不足");
            }
        }finally{
            stringRedisTempldate.delete(lockKey);
        }
         return "end";
    }
}

异常情况二:宕机。造成死锁。

解决方案:加个过期时间

@RestController
public class IndexController{
    @Autowired
    private StringRedisTemplate stringRedisTempldate;

    public String deductStock(){
        String lockKey = "kockKey";
        try{
         Boolean result = stringRedisTempldate.opsForValue().setIfAbsent(lockKey,"abagg");
StringRedisTemplate.expire(lockkey,10,TimeUnit.SECONDS);
            //true代表加锁成功,false代表加锁失败,直接返回错误
            if(!result){
                return "error_code";
            }
                int stock = Integer.parseInt(stringRedisTempldate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                StringRedisTemplate.opsForValue().set("stock",realStock+"");
                System.out.println("减扣成功,剩余库存:"+realStock );
            }else{
                System.out.println("减扣失败,余额不足");
            }
        }finally{
            stringRedisTempldate.delete(lockKey);
        }
         return "end";
    }
}

情况三:但是这样仍然有原子性能问题,假如程序运行在设置过期时间之前,有的运维比较粗暴,直接kill -9这个进程,过期时间又会设置不成功,造成死锁。

解决方案:调用具有原子性的解决方法,将加锁与设置过期时间一步设置好。

@RestController
public class IndexController{
    @Autowired
    private StringRedisTemplate stringRedisTempldate;

    public String deductStock(){
        String lockKey = "kockKey";
        try{
         Boolean result = stringRedisTempldate.opsForValue().setIfAbsent(lockKey,"abagg",10,TimeUnit.SECONDS);
            //true代表加锁成功,false代表加锁失败,直接返回错误
            if(!result){
                return "error_code";
            }
                int stock = Integer.parseInt(stringRedisTempldate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                StringRedisTemplate.opsForValue().set("stock",realStock+"");
                System.out.println("减扣成功,剩余库存:"+realStock );
            }else{
                System.out.println("减扣失败,余额不足");
            }
        }finally{
            stringRedisTempldate.delete(lockKey);
        }
         return "end";
    }
}

情况四:情况三的代码放到超高并发场景下还会出现问题,假如线程一执行整个程序需要15s,执行业务代码需要10s,此时redis锁失效,线程二此时就能获取到锁,假如线程二执行整个程序需要8s,执行业务代码需要5s,此时线程一在最终删除锁的时候就会删除的是线程二设置的锁,如此往下推,后台其他线程就会造成混乱,就会造成加的锁永久失效。

解决方案:在最终删除锁的时候做个判断,每一个线程进来设置一个唯一的id(生成方式根据公司具体方案定),放入redis中,在最终删除锁的时候判断一下redis中的id与当前的id是否同一个,是同一个再删除。

@RestController
public class IndexController{
    @Autowired
    private StringRedisTemplate stringRedisTempldate;

    public String deductStock(){
        String lockKey = "kockKey";
        String clinetId = UUID.randomUUID().toString();
        try{
         Boolean result = stringRedisTempldate.opsForValue().setIfAbsent(lockKey,clientId,10,TimeUnit.SECONDS);
            //true代表加锁成功,false代表加锁失败,直接返回错误
            if(!result){
                return "error_code";
            }
                int stock = Integer.parseInt(stringRedisTempldate.opsForValue().get("stock"));
            if(stock > 0){
                int realStock = stock - 1;
                StringRedisTemplate.opsForValue().set("stock",realStock+"");
                System.out.println("减扣成功,剩余库存:"+realStock );
            }else{
                System.out.println("减扣失败,余额不足");
            }
        }finally{
            if(clientId.equals(stringRedisTempldate.opsForValue.get(lockKey ))){
                 stringRedisTempldate.delete(lockKey);
            }
         }
         return "end";
    }
}

情况五:情况四执行的代码还是有点小问题,假如线程一执行的代码超过redis的锁超时时间,线程二又可以进来获取到锁,又会存在同时执行业务代码的问题,又会出现超卖。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/424172.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号