Redis應用實戰
1、分佈式鎖
一般情况下直接使用redisson就ok了,本节我们用redis自己手写
一般对数据加锁时,首先需要通过获取锁来得到对数据进行排他性访问能力,然后在对数据进行一系列操作,最后需要将锁释放,共所有程序抢占。
目的
为了保证一个方法或属性在高并发情况下的同一时间只能被同一个线程执行,在传统单体应用单机部署的情况下,可以使用并发处理相关的功能进行互斥控制。但是,随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的应用并不能提供分布式锁的能力。为了解决这个问题就需要一种跨机器的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
分布式锁应该具备的条件
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用、高性能的获取锁与释放锁;
3、锁具备可重入性;
4、要防止死锁;
5、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败。
常见分布式锁的实现方式
1、基于数据库实现
核心思想:单独创建一个表,核心列(如存放内容为方法名)增加索引,想要执行某个方法,需要向表中插入数据,成功插入的则获取锁,执行完成删除此行数据进行锁释放
问题:
- 不具备可重入性
- 性能比较受限
- 锁失效机制不太容易实现
2、基于zookeeper实现
Zookeeper内部是一个分层的文件系统目录树结构,规定同一目录下只能有一个唯一的文件名,基于此实现分布式锁
3、基于redis实现
选redis实现的原因
1)性能高
2)redis命令对此很有好(setnx)
使用的redis命令
1)setnx:setnx key val,当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0.
2)expire:expire key timeout,为可以设置一个超时时间,单位为second,超过这个时间锁自动释放,避免死锁。
3)delete:delete key,删除key
实现思想:
1)获取锁的时候,使用setnx枷锁,并用expire命令为添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的uuid,通过此在释放的时候进行判断。(uuid用来做释放的判断依据)
2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
3)释放锁的时候,通过uuid判断是不是该锁,若是该锁则执行delete进行锁释放。
public void synlock(){
String clientId = UUID.randomUUID().toString();
String lockKey="lockKey";
try {
// 先加锁
Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockKey,clientId,10, TimeUnit.SECONDS);
System.out.println("flag="+flag);
// 加锁成功 执行扣除库存
if(flag){
// 读取库存
Integer stock = (Integer) redisTemplate.opsForValue().get("stock");
if (stock != null && stock > 0) {
redisTemplate.opsForValue().set("stock", stock - 1);
System.out.println("抢购成功--扣除库存成功" + "-----原库存:" + stock + "剩余库存:" + (stock - 1));
}
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
// 释放锁
String verfiy = (String) redisTemplate.opsForValue().get(lockKey);
// 判断是否为当前线程加的锁
if (Objects.equals(verfiy, clientId)) {
redisTemplate.delete(lockKey);
System.out.println("delete lockKey success!");
}
}
最后总结:本书的前半部分还可以,后半部分由于时间关系实用性不是太强,所以略读了。