赞
踩
分布式锁的作用:在解决单个服务的线程同步安全中,我们使用的synchronized等
java的方式加锁。但是在面临多个服务去访问一个公共资源时,是要保证服务层面的
同步安全性,synchronized等java的加锁方式就不解决不了问题了。
这个就要解决服务的同步性问题
使用Redis分布式锁的原理:首先,redis是单线程的,这是前提条件。redis中有值
超时的设置以及重复值不可插入并返回false的功能。就能保证在一个进程执行了一个
访问公共资源的方法时,往redis中设置了一个标识,这个过程叫做加锁。等另一个进
程同样操作这个方法时,再去加锁,就发现已经有了,就等待,并不停的尝试加锁。
示例图:
使用springboot项目,使用nginx做代理,开启redis服务。
项目结构:
项目的配置文件:
#连接数据库
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
实体类:
@Data
public class Toll {
@TableId(type = IdType.AUTO)
private Integer pid;//id
private Integer count;//总量
}
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);
}
}
dao:
public interface TollDao extends BaseMapper<Toll> {
}
(1)、导入连接redis的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(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 "服务器正忙请稍后再试.........."; } } }
(3)、结果
JMETER压测:
(4)、问题
普通的redis可能出现的问题:当redis中的A进程标识到期后,但是当前A进程并没
有执行结束,A进程继续执行。然后B进程发现redis中的A进程标识已经消失,B进程
就进行加锁,然后B进程也可以进入到了业务执行中,这样又出现了同步安全问题,
并且进程A再释放锁,释放就是进程B的锁。
不过,redis给出了解决方法。Redis官方给出了一个高级的协调的Redis客服端--》Redisson。
简介:Redisson
Redisson是架设在redis基础上的一个java驻内存数据网络。Redisson在基于NIO的
Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包
中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原
本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能
力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服
务,更进一步简化了分布式环境中程序相互之间的协作。
原理图:引用自“石杉的架构笔记”
(1)、引入依赖
可以从这个:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.15.3</version>
</dependency>
也可以用这个:这个必须在项目中配置RedissonClient类,如启动类中所写
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.3</version>
</dependency>
(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; } }
(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(); } } } } }
(4)、Redisson是如何解决问题的
Redisson中有一个“看门狗”模式,就是当线程执行时,会去查看进程有没有执行
完,如果还没有执行完,会给进程延长锁的存在时间。看门狗模式中,有一个默认时
间30秒,这个时间设置了锁的默认超时时间(lockWatchdogTimeout),然后看门狗会
在lockWatchdogTimeout/3,也就是每10秒查看一次当前进程有没有执行完,没有执行
完,把时间再延长至30s,等进程执行完毕后,自动释放锁。
getLock()创建锁实例时,就会加载默认的时间30s。可以手动配置。
lock()方法获取锁,可以在里边设置锁的存活时间,设置之后,就不会再自动释放锁,不会延长锁的存活时间。
unlock():释放锁
源码赏析:
1、getlock():
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。