当前位置:   article > 正文

Redis分布式锁_redid分布式锁

redid分布式锁

一、为什么使用分布式

	分布式锁的作用:在解决单个服务的线程同步安全中,我们使用的synchronized等
java的方式加锁。但是在面临多个服务去访问一个公共资源时,是要保证服务层面的
同步安全性,synchronized等java的加锁方式就不解决不了问题了。
  • 1
  • 2
  • 3

二、多服务访问示例图

在这里插入图片描述这个就要解决服务的同步性问题

三、Redis分布式锁

	使用Redis分布式锁的原理:首先,redis是单线程的,这是前提条件。redis中有值
超时的设置以及重复值不可插入并返回false的功能。就能保证在一个进程执行了一个
访问公共资源的方法时,往redis中设置了一个标识,这个过程叫做加锁。等另一个进
程同样操作这个方法时,再去加锁,就发现已经有了,就等待,并不停的尝试加锁。
  • 1
  • 2
  • 3
  • 4

示例图:
在这里插入图片描述

四、Redis分布式锁示例

使用springboot项目,使用nginx做代理,开启redis服务。
  • 1

项目结构:
在这里插入图片描述

项目的配置文件:

#连接数据库
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.url=jdbc:mysql:///sb_shiro?serverTimeZone=Asia\Shanghai
spring.datasource.druid.username=root
spring.datasource.druid.password=root
#连接redis
spring.redis.host=192.168.253.122
spring.redis.port=6379
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

实体类:

@Data
public class Toll {
    @TableId(type = IdType.AUTO)
    private Integer pid;//id
    private Integer count;//总量
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

Controller:

@RestController
@RequestMapping("stock")
public class TollController {
    @Autowired
    private TollServerWatchDog tollServer;

//   实现的功能是每次访问自动减1,业务的方法名起的有问题
    @GetMapping("descr/{productId}")
    public String descstock(@PathVariable("productId")Integer productId){
        return tollServer.selectById(productId);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

dao:

public interface TollDao extends BaseMapper<Toll> {
}
  • 1
  • 2

1、普通的redis分布锁

(1)、导入连接redis的依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  • 1
  • 2
  • 3
  • 4

(2)servies:业务实现

@Service
public class TollServerLock {
    @Resource
    private TollDao tollDao;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
/*
redis分布式锁:这种是阻塞时锁,在分布式项目中,多个进程访问同一个资源,再使用synchronized就不管用了,我们可以使用redis分布式锁,redis是单线程的。
    当两个进程对同一个资源操作时,就可以让两个进程设置相同的一个属性值,在一段时间,两个进程只能有一个进程能在redis中设置值
    当有两个进程A和B,进程A访问资源时,先去redis中去查看一个锁,这个锁就是一个key-value,如果存在,说明进程B已经运行了,进程A就会等待,并
        不断去尝试加锁,当B的锁到期,A就会到redis中加锁,然后A进程运行
*/
    public String selectById(Integer pid){
// 这就是加锁,就是进程在redis放入一个标识:    setIfAbsent():如果里边有相同的值,就不存入,返回0(false).相当于redis的setnx和expire两个方法
//      key\value字段就是存储的字段,timeout表示这个值多久过期
        Boolean status = stringRedisTemplate.opsForValue().setIfAbsent("product::" + pid, "fy", 3, TimeUnit.SECONDS);

        if(status){
            try{
            Toll toll = tollDao.selectById(pid);
            if(toll.getCount()>0){
                System.out.println("更改前的剩余"+toll.getCount());
                Thread.sleep(5000);
                toll.setCount(toll.getCount()-1);
                tollDao.updateById(toll);
                System.out.println("库存剩余:"+toll.getCount());
                return "库存减少成功";
            }else {
                return "库存不足";
            }
        }catch (Exception e){
                throw  new RuntimeException(e.getMessage());
        }finally {
       		 //释放锁资源 一定再finally
              stringRedisTemplate.delete("product::"+pid);
        }
        }else {
//            System.out.println("服务器正忙请稍后再试..........");
            return "服务器正忙请稍后再试..........";
        }

    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

(3)、结果
JMETER压测:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

(4)、问题

	普通的redis可能出现的问题:当redis中的A进程标识到期后,但是当前A进程并没
有执行结束,A进程继续执行。然后B进程发现redis中的A进程标识已经消失,B进程
就进行加锁,然后B进程也可以进入到了业务执行中,这样又出现了同步安全问题,
并且进程A再释放锁,释放就是进程B的锁。
	不过,redis给出了解决方法。Redis官方给出了一个高级的协调的Redis客服端--》Redisson。
  • 1
  • 2
  • 3
  • 4
  • 5

2、使用Redisson实现分布锁

简介:Redisson
	Redisson是架设在redis基础上的一个java驻内存数据网络。Redisson在基于NIO的
Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包
中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原
本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能
力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服
务,更进一步简化了分布式环境中程序相互之间的协作。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

原理图:引用自“石杉的架构笔记”
在这里插入图片描述
(1)、引入依赖
可以从这个:

	    <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.15.3</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

也可以用这个:这个必须在项目中配置RedissonClient类,如启动类中所写

		<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.15.3</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

(2)、启动类

@SpringBootApplication
@MapperScan("com.ykq.distributedlock.dao")
public class DistributedLockApplication {

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


    @Bean //Configuration
    public RedissonClient getRedisson(){
        Config config=new Config();
        config.useSingleServer().setAddress("redis://192.168.213.188:6379");
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

(3)、service业务实现

@Service
public class TollServerWatchDog {
    @Resource
    private TollDao tollDao;

    @Resource
    private RedissonClient redissonClient;//解决了使用redis分布锁的过程中,产生的业务还没执行完,但是锁到期的问题

    public String selectById(Integer pid){
//        根据参数创建锁实例
        RLock lock = redissonClient.getLock("selectById::" + pid);
        try{
            lock.lock();
            Toll toll = tollDao.selectById(pid);
            if (toll.getCount()>0) {
                System.out.println("开始前"+toll.getCount());
               toll.setCount(toll.getCount()-1);
               tollDao.updateById(toll);
                System.out.println("剩余"+toll.getCount());
                return "库存减少成功";
            } else {
                return "库存不足";
            }
        }catch (Exception e){
            throw new RuntimeException(e.getMessage());
        }finally {
            if(lock.isLocked()){//判断锁是否处于锁定
                if(lock.isHeldByCurrentThread()){//判断是否时该进程自己的锁
                    lock.unlock();
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

(4)、Redisson是如何解决问题的

	Redisson中有一个“看门狗”模式,就是当线程执行时,会去查看进程有没有执行
完,如果还没有执行完,会给进程延长锁的存在时间。看门狗模式中,有一个默认时
间30秒,这个时间设置了锁的默认超时时间(lockWatchdogTimeout),然后看门狗会
在lockWatchdogTimeout/3,也就是每10秒查看一次当前进程有没有执行完,没有执行
完,把时间再延长至30s,等进程执行完毕后,自动释放锁。
	getLock()创建锁实例时,就会加载默认的时间30s。可以手动配置。
	lock()方法获取锁,可以在里边设置锁的存活时间,设置之后,就不会再自动释放锁,不会延长锁的存活时间。
	unlock():释放锁
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

源码赏析:
1、getlock():
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/696689
推荐阅读
相关标签
  

闽ICP备14008679号