redis 缓存 和 分布式锁

文章介绍了如何在JavaSpringBoot应用中使用Redis实现缓存,并确保增删改操作时缓存与数据库的一致性。在删除和更新操作中,通过删除对应缓存条目来维持一致性。接着,文章讨论了线程安全问题,提出了使用`synchronized`和`Lock`解决并发问题,但在线程集群环境下仍可能出现问题。为解决这个问题,文章转向使用Redis作为分布式锁,并展示了如何通过设置超时和重试机制避免锁资源误删。最后,文章推荐使用Redisson库来更优雅地处理分布式锁,包括自动的超时监控和锁续租功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. redis缓存

当执行增删改操纵时必须保证缓存和数据库数据一致性。---删除缓存

1.1 增删改

service: 

/**
 * @program: redis-springboot01
 * @author: ♥丁新华
 * @create: 2023-04-25 11:12
 **/

public interface StudentService {

     /**
      * 根据ID查询
      * @param id
      * @return
      */
     Student findById (Integer id);

     /**
      * 增加数据
      * @return
      */

     Student insert(Student s);

     /**
      * 删除数据
      */

     Integer delete(Integer id);

     /**
      * 修改数据
      */

     Student update(Student s);
}

serviceimpl :

/**
 * @program: redis-springboot01
 * @author: ♥丁新华
 * @create: 2023-04-25 11:16
 **/
@Service
public class StudentServiceimpl implements StudentService {

    @Resource
    private StudentMapper studentMapper;

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 查询 ID
     * @param id
     * @return
     */
    @Override
    public Student findById(Integer id) {
        ValueOperations<String, Object> sv = redisTemplate.opsForValue ();

        //先获取ID
        Object o = sv.get (id);

        //判断
        if (o != null){
            //证明没有 存入
            return (Student) o;
        }

        Student student = studentMapper.selectById (id);
        if (student != null){

            sv.set ("student::" + id,student);
        }
            return student;
    }

    /**
     * 增加数据  缓存没有影响
     * @return
     */
    @Override
    public Student insert(Student s) {

        int insert = studentMapper.insert (s);

        return s;

    }


    @Override
    public Integer delete(Integer id) {
        //缓存要与数据库保持一致
        redisTemplate.delete ("delete::" + id);

        int i = studentMapper.deleteById (id);
        return i;
    }



    @Override
    public Student update(Student s) {
        //修改也要删除数据库缓存
        redisTemplate.delete ("student::" + s);

        int i = studentMapper.updateById (s);
        return s;
    }








}

controller:

/**
 * @program: redis-springboot01
 * @author: ♥丁新华
 * @create: 2023-04-25 11:20
 **/
@RestController
public class StudentController {
    @Resource
    private StudentService studentService;

    /**
     * 查询
     * @param id
     * @return
     */
    //restful风格
    @GetMapping("b/{id}")
    public Student findById(@PathVariable Integer id){
        return studentService.findById (id);

    }

    /**
     * 增加
     */
    @PostMapping("insert")
    public Student insert(Student s){
        return studentService.insert (s);
    }

    /**
     * 删除数据
     * @return
     */

    @DeleteMapping("delete")
    public Integer delete(Integer id){
        return studentService.delete (id);
    }

    /**
     * 修改
     */

    @PostMapping("update")
    public Student update(Student s){
        return studentService.update (s);
    }


}

2. redis使用分布式锁

(1)通过使用jmeter压测工具测试

@Data
@TableName(value = "tbl_stock")
public class Stock {
    @TableId(type = IdType.AUTO,value = "productId")
    private int productId;
    private Integer num;
}

 

问题 :同一个库存数被多个线程卖,线程安全问题。---思考:之间出现线程安全问题时如何解决。

解决 :可以使用锁解决:----synchronized和Lock锁

@Service
public class StockService_lock_syn {
    @Autowired
    private StockDao stockDao;
    public static Object o=new Object();
    Lock lock=new ReentrantLock();
    public  String jianStock(Integer pid){
        try {
            lock.lock();//加锁
            //1. 查询指定的商品库存
            Stock stock = stockDao.selectById(pid);
            if (stock.getNum() > 0) {
                //2.库存减1
                stock.setNum(stock.getNum() - 1);
                stockDao.updateById(stock);
                System.out.println("库存剩余数量:" + stock.getNum());
                return "减库存成功";
            } else {
                System.out.println("库存不足");
                return "库存减失败";
            }
        }
        finally {
            lock.unlock(); //释放锁
        }

    }
}

需要在idea中跑项目的集群

配置nginx

 启动nginx

jmeter压测

 

问题: 两台集群出现重卖问题。

 3. 使用redis来解决分布式锁

  图示:

**
 * @program: redis-lock-qy145
 * @description:
 * @author: 闫克起2
 * @create: 2022-03-01 11:13
 **/
@Service
public class StockService_lock_syn2 {
    //注入dao
    @Resource
    private StockDao stockDao;

    @Autowired
    private StringRedisTemplate template;

    public String jianStock(Integer pid) {

        //占锁
        ValueOperations<String, String> forValue = template.opsForValue ();

        Boolean aBoolean = forValue.setIfAbsent ("product::" + pid, "", 30, TimeUnit.SECONDS);

        while (!aBoolean){

                try {
                    //休眠一会
                    Thread.sleep (20);
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }

        }
      try {
        //获取
        Stock stock = stockDao.selectById (pid);

        //判断
        if (stock.getNum () > 0){
            //先减一
            stock.setNum (stock.getNum () - 1);
            //更新缓存
            stockDao.updateById (stock);

            //输出
            System.out.println ("剩余票数" + stock.getNum ());
            return "减去成功";
        }else {
            System.out.println ("库存不足");
            return "减去失败";
        }
    }finally {
        //释放锁资源
        template.delete ("product::" + pid);
    }
  }
}

如果你的业务代码的执行时间超过30s,当前线程删除的是其他线程的锁资源。 --watchDog机制

每个10s检测当前线程是否还持有所资源,如果持有则为当前线程延迟。---可以自己设置watchDog机制,---第三方Redission完美的解决分布式锁。

4. redisson完美解决redis超时问题

 

(1) 添加依赖

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>

(2)main函数

@SpringBootApplication
public class RedisLockQy145Application {

    public static void main(String[] args) {
        SpringApplication.run(RedisLockQy145Application.class, args);
    }

    @Bean //创建redisson交于spring容器来管理
    public RedissonClient redisson() {
        Config config = new Config();
        config.setLockWatchdogTimeout(300);
        config.useSingleServer().setAddress("redis://192.168.28.222:6379");
        RedissonClient redisson = Redisson.create(config);
        return redisson;
    }
}

(3)使用

/**
 * @program: redis-lock-qy145
 * @description:
 * @author: 闫克起2
 * @create: 2022-03-01 11:13
 **/
@Service
public class StockService_lock_syn3 {
    //注入dao
    @Resource
    private StockDao stockDao;

    //注入redisson
    @Autowired
    private RedissonClient redisson;

    public String jianStock(Integer pid) {
        RLock lock = redisson.getLock ("product::" + pid);
        

        try {
            //加锁
            lock.lock (30,TimeUnit.SECONDS);
        //获取
        Stock stock = stockDao.selectById (pid);

        //判断
        if (stock.getNum () > 0){
            //先减一
            stock.setNum (stock.getNum () - 1);
            //更新缓存
            stockDao.updateById (stock);

            //输出
            System.out.println ("剩余票数" + stock.getNum ());
            return "减去成功";
        }else {
            System.out.println ("库存不足");
            return "减去失败";
        }
    }finally {
        //释放锁资源
        lock.unlock ();
    }
  }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值