当前位置: 首页 > news >正文

网页设计与制作工作云南seo简单整站优化

网页设计与制作工作,云南seo简单整站优化,温泉网站建设,万宁市住房和城乡建设局网站面试题: Redis除了拿来做缓存,你还见过基于Redis的什么用法? 1.数据共享,分布式Session 2.分布式锁 3.全局ID 4.计算器、点赞 5.位统计 6.购物车 7.轻量级消息队列:list、stream 8.抽奖 9.点赞、签到、打卡 10.差集交集…

面试题:

  •  Redis除了拿来做缓存,你还见过基于Redis的什么用法?

1.数据共享,分布式Session
2.分布式锁
3.全局ID
4.计算器、点赞
5.位统计
6.购物车
7.轻量级消息队列:list、stream
8.抽奖
9.点赞、签到、打卡
10.差集交集并集,用户关注、可能认识的人,推荐模型
11.热点新间、热搜排行榜

  • Redis做分布式锁的时候有需要注意的问题?
  • 你们公司自己实现的分布式锁是否用的setnx命令实现?这个是最合适的吗?你如何考虑分布式锁的可重入问题?
  • 如果是Redis是单点部署的,会带来什么问题?那你准备怎么解决单点问题呢?
  • Redis集群模式下,比如主从模式,CAP方面有没有什么问题呢?
  • 那你简单的介绍一下Redlock吧?你简历上写redisson,你谈谈
  • Redis分布式锁如何续期?看门狗知道吗?

分布式锁需要具备的条件和刚需

独占性

OnlyOne,任何时刻只能有且仅有一个线程持有

高可用

若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况;高并发请求下,依旧性能OK好使

防死锁

杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底终止跳出方案

不乱抢

防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放,自己约的锁含着泪也要自己解

重入性

同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁

分布式锁

官网:set 命令

使用set进行占锁,删掉key代表锁释放。

setnx key value

注:setnx+expire不安全,两条命令非原子性的。

set key value [EX seconds] [PX milliseconds] [NX|XX]

重点:JUC中AQS锁的规范落地参考+可重入锁考虑+Lua脚本+Redist命令一步步实现分布式锁

案例

V1.0 版本(基础案例)

使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击)。

1、pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.atguigu.redislock</groupId><artifactId>redis_distributed_lock2</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.12</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><lombok.version>1.16.18</lombok.version></properties><dependencies><!--SpringBoot通用依赖模块--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--SpringBoot与Redis整合依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><!--swagger2--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency><!--通用基础配置boottest/lombok/hutool--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><optional>true</optional></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.8</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

2、ymal文件

server.port=7777spring.application.name=redis_distributed_lock
# ========================swagger2=====================
# http://localhost:7777/swagger-ui.html
swagger2.enabled=true
spring.mvc.pathmatch.matching-strategy=ant_path_matcher# ========================redis单机=====================
spring.redis.database=0
spring.redis.host=192.168.111.185
spring.redis.port=6379
spring.redis.password=111111
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

3、主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class RedisDistributedLockApp {public static void main(String[] args) {SpringApplication.run(RedisDistributedLockApp.class,args);}
}

4、配置类

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import springfox.documentation.swagger2.annotations.EnableSwagger2;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;@Configuration
@EnableSwagger2
public class Swagger2Config {@Value("${swagger2.enabled}")private Boolean enabled;@Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(enabled).select().apis(RequestHandlerSelectors.basePackage("com.atguigu.redislock")) //你自己的package.paths(PathSelectors.any()).build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().title("springboot利用swagger2构建api接口文档 "+"\t"+ DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now())).description("springboot+redis整合").version("1.0").termsOfServiceUrl("https://www.baidu.com/").build();}}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(lettuceConnectionFactory);//设置key序列化方式stringredisTemplate.setKeySerializer(new StringRedisSerializer());//设置value的序列化方式jsonredisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.afterPropertiesSet();return redisTemplate;}
}

5、业务类

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@Service
@Slf4j
public class InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;public String sale() {String retMessage = "";try {//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber > 0) {stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber;System.out.println(retMessage);}else{retMessage = "商品卖完了,o(╥﹏╥)o";}} finally {// 释放操作}return retMessage+"\t"+"服务端口号:"+port;}
}
import cn.hutool.core.util.IdUtil;
import com.atguigu.redislock.service.InventoryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.atomic.AtomicInteger;@RestController
@Api(tags = "redis分布式锁测试")
public class InventoryController {@Autowiredprivate InventoryService inventoryService;@ApiOperation("扣减库存,一次卖一个")@GetMapping(value = "/inventory/sale")public String sale() {return inventoryService.sale();}
}

丝袜哥测试地址:http://localhost:7777/swagger-ui.html#/

V2.0 版本

使用 synchronized 或者 lock 进行加锁操作

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@Service
@Slf4j
public class InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();public String sale() {String retMessage = "";lock.lock();try {//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber > 0) {stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber;System.out.println(retMessage);}else{retMessage = "商品卖完了,o(╥﹏╥)o";}}finally {lock.unlock();}return retMessage+"\t"+"服务端口号:"+port;}
}

若在添加一个相同服务,另一个服务为8888端口,使用 nginx 轮训访问 7777 和 8888 服务,此时就会出现超卖现象。lock 只针对当前服务有用,锁不了其他服务

Nginx配置负载均衡

1、常用命令

检查nginx.conf文件合法性:
    nginx -t -c ./conf/nginx.conf
检查nginx服务是否启动:
    tasklist /fi "imagename eq nginx.exe"
启动nginx:
    start nginx 或 ./nginx
重新加载:
    nginx -s reload
停止服务:
    nginx -s stop

2、配置文件

服务测试

启动 7777 和 8888 服务,通过Nginx访问,你的Linux服务器地址lP,反向代理+负裁均衡,可以点击看到效果,一边一个,默认轮询:http://192.168.0.185/inventory/sale

使用 jmeter 进行压测

http请求:

测试结果:

异常:76号商品被卖出2次,出现超卖故章现象。

结论

单机环境下,可以使用synchronized或Lock来实现。

但是在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个jvm中),所以需要一个让所有进程都能访问到的锁来实现(比如redis或者zookeeper来构建)。

不同进程jvm层面的锁就不管用了,那么可以利用第三方的一个组件,来获取锁,未获取到锁,则阻塞当前想要运行的线程。

V3.1 版本

使用递归进行重试

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@Service
@Slf4j
public class InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();public String sale() {String retMessage = "";String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);if(!flag){//暂停20毫秒后递归调用try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }sale();}else{try{//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber > 0) {stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber;System.out.println(retMessage);}else{retMessage = "商品卖完了,o(╥﹏╥)o";}}finally {stringRedisTemplate.delete(key);}}return retMessage+"\t"+"服务端口号:"+port;}
}

结果:

1、手工测试OK,使用Jmeter压测5000也OK

2、递归是一种思想没错,但是容号导致StackOverflowError,不太推荐,需进一步完善

V3.2 版本

使用多线程判断想想JUC里面的虚假唤醒,用while替代if,用自旋替代送归重试。

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@Service
@Slf4j
public class InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();public String sale() {String retMessage = "";String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)){//暂停20毫秒,类似CAS自旋try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }}try {//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber > 0) {stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber;System.out.println(retMessage);}else{retMessage = "商品卖完了,o(╥﹏╥)o";}}finally {stringRedisTemplate.delete(key);}return retMessage+"\t"+"服务端口号:"+port;}
}

问题:部署了微服务的Java程序机器挂了,代码层面根本没有走到finallyi这块,没办法保证解锁(无过期时间该key一直存在),这个kev没有被删除,需要加入一个过期时间限定key。

V4.1 版本

......
while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)) {//暂停20毫秒,进行递归重试.....try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
}
......
stringRedisTemplate.expire(key,30L,TimeUnit.SECONDS);
......

结论:虽然加了过期时间,但是设置key+过期时间分开了,不具备原子性。

V4.2 版本

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@Service
@Slf4j
public class InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();public String sale() {String retMessage = "";String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)){//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }}try {//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber > 0) {stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber;System.out.println(retMessage);}else{retMessage = "商品卖完了,o(╥﹏╥)o";}}finally {stringRedisTemplate.delete(key);}return retMessage+"\t"+"服务端口号:"+port;}
}

使用Jmeter压测OK

结论加锁和过期时间设置必须同一行,保证原子性。

V5.0 版本

4.2版本出现的问题:实际业务处理时间如果超过了默认设晋key的过期时间怎么办??张冠李戴,别除了别人的锁(只能自己除自己的,不许动别人的)

改进:

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@Service
@Slf4j
public class InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();public String sale() {String retMessage = "";String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)) {//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }}try {//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber > 0) {stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber+"\t"+uuidValue;System.out.println(retMessage);}else{retMessage = "商品卖完了,o(╥﹏╥)o";}}finally {// v5.0判断加锁与解锁是不是同一个客户端,同一个才行,自己只能删除自己的锁,不误删他人的if(stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)){stringRedisTemplate.delete(key);}}return retMessage+"\t"+"服务端口号:"+port;}
}

V6.0 版本(小厂够用)

上面5.0版本出现的问题:finally块的判断+del删除除操作不是原子性的。

解决方案:启用lua脚本编写redis分布式锁判断+删除判断代码。

原理:Redis调用Lua本通过eval命令保证代码执行的原子性,直接用return返回脚本执行后的结果值。

Lua 脚本

官网:https://redis.io/docs/manual/patterns/distributed-locks/

脚本格式:

eval luascript numkeys [key [key ]][arg [arg ...]]

案例一:输出文字

案例二:组合命令

案例三:执行命令

案例四:条件判断

使用格式如下

eval "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end" 1 zzyyRedisLock 1111-2222-3333

解决方式

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@Service
@Slf4j
public class InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();public String sale() {String retMessage = "";String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)) {//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }}try {//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber > 0) {stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber+"\t"+uuidValue;System.out.println(retMessage);}else{retMessage = "商品卖完了,o(╥﹏╥)o";}}finally {//V6.0 将判断+删除自己的合并为lua脚本保证原子性String luaScript ="if (redis.call('get',KEYS[1]) == ARGV[1]) then " +"return redis.call('del',KEYS[1]) " +"else " +"return 0 " +"end";stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Boolean.class), Arrays.asList(key), uuidValue);}return retMessage+"\t"+"服务端口号:"+port;}
}

BUG 说明

V7.0 版本(可重入锁)

6.0版本:while判断并自旋重试获取锁+setx含自然过期时间+ua脚本官网除锐命令。那么问题来了:如何兼顾锁的可重入性问题?

一个靠谱分布式锁需要具备的条件和刚需:独占性、高可用、防死锁、不乱抢、重入性

可重入锁

说明:可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

“可重入锁”这四个字分开来解释:

可:可以。
重:再次。
入:进入。
锁:同步锁。

进入什么:进入同步域(即同步代码快/方法或显式锁锁定的代码)

一句话:一个线程中的多个流程可以获取同一把锁,持有这把同步锁句以再次进入。自己可以获取自己的内部锁。

可重入锁种类:

第一种:隐式锁(即synchronized关键字使用的锁)默认是可重入锁。

指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。

简单的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的。

与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁。

同步块:

public class ReEntryLockDemo {public static void main(String[] args) {final Object objectLockA = new Object();new Thread(() -> {synchronized (objectLockA) {System.out.println("-----外层调用");synchronized (objectLockA) {System.out.println("-----中层调用");synchronized (objectLockA) {System.out.println("-----内层调用");}}}},"a").start();}
}

同步方法:

/*** 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的*/
public class ReEntryLockDemo {public synchronized void m1() {System.out.println("-----m1");m2();}public synchronized void m2() {System.out.println("-----m2");m3();}public synchronized void m3() {System.out.println("-----m3");}public static void main(String[] args) {ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();reEntryLockDemo.m1();}
}

实现原理:

        每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

第二种:显式锁(即Lock)也有ReentrantLock这样的可重入锁。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的*/
public class ReEntryLockDemo {static Lock lock = new ReentrantLock();public static void main(String[] args) {new Thread(() -> {lock.lock();try {System.out.println("----外层调用lock");lock.lock();try {System.out.println("----内层调用lock");}finally {// 这里故意注释,实现加锁次数和释放次数不一样// 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。lock.unlock(); // 正常情况,加锁几次就要解锁几次}}finally {lock.unlock();}},"a").start();new Thread(() -> {lock.lock();try {System.out.println("b thread----外层调用lock");}finally {lock.unlock();}},"b").start();}
}

备注:一般而言,你lock了几次就要unlock几次。

Redis 可重入锁数据类型

使用<key,<key,value>>结构:Map<String,Map<Object,Object>>

hset  zzyyRedisLock 29f0ee01ac77414fb8b0861271902a94:1

案例:

hset key field value

hset redis锁名字(zzyyRedisLock)  某个请求线程的UUID+ThreadID  加锁的次数

总结:setnx,只能解决有无的问题,够用但是不完美;hset,不但解决有无,还解决可重入问题。

Lua脚本

上面版本扣减库存的核心代码:(保证原子性)

实现要求:(lua脚本实现过程分析)

一、加锁lua脚本(其实就是 lock)

步骤:

1、先断redis分布式锁这个key是否存在 :EXISTS key 

2、返回零说明不存在,hset新建当前线程属于自己的锁BY UUID:ThreadID

3、返回壹说明已经有锁,需进一步判断是不是当前线程自己的:HEXISTS key uuid:ThreadID

命令:HEXISTS zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1

3.1、返回零说明不是自己的
3.2、返回壹说明是自己的锁,自增1次表示重入

命令:HINCRBY key field increment

案例:HINCRBY zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1 1

根据以上步骤,得出lua脚本:

if redis.call('exists','key') == 0 then
  redis.call('hset','key','uuid:threadid',1)
  redis.call('expire','key',30)
  return 1

elseif redis.call('hexists','key','uuid:threadid') == 1 then
  redis.call('hincrby','key','uuid:threadid',1)
  redis.call('expire','key',30)
  return 1

else
  return 0
end

以上相同部分是否可以替换处理???

答案:hincrby命令可否替代hset命令。(自增命令,没有key会创建key)

优化脚本:

if redis.call('exists','key') == 0 or redis.call('hexists','key','uuid:threadid') == 1 then
  redis.call('hincrby','key','uuid:threadid',1)
  redis.call('expire','key',30)
  return 1
else
  return 0
end

替换参数:

if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then
    redis.call('hincrby',KEYS[1],ARGV[1],1)
    redis.call('expire',KEYS[1],ARGV[2])
    return 1
else 
    return 0
end

参数说明:

测试加锁lua脚本:

EVAL "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end" 1 zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1 30

HGET zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1

二、解锁Lua脚本(其实就是 unlock)

步骤:

1、先判断redis分布式锁这个key是否存在:EXISTS key

2、返回零,说明根本没有锁,程序块返回nil
3、不是零,说明有锁且是自己的锁,直接调用 HINCRBY -1 表示每次减个一,解领一次,直到它变为零表示可以除该锁key,del锁key

根据以上步骤得出解锁lua脚本:

if redis.call('HEXISTS',lock,uuid:threadID) == 0 then
    return nil
elseif redis.call('HINCRBY',lock,uuid:threadID,-1) == 0 then
    return redis.call('del',lock)
else 
    return 0
end

替换参数:

if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then
    return nil
elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then
    return redis.call('del',KEYS[1])
else
    return 0
end

调试命令: 

eval "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then return nil elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then return redis.call('del',KEYS[1]) else return 0 end" 1 zzyyRedisLock 2f586ae740a94736894ab9d51880ed9d:1

测试解锁lua脚本:

整合Java程序

原程序:(初始无锁版)

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;@Service
@Slf4j
public class InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;public String sale() {String retMessage = "";//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber > 0) {stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber+"\t";System.out.println(retMessage);}else{retMessage = "商品卖完了,o(╥﹏╥)o";}return retMessage+"\t"+"服务端口号:"+port;}
}

使用工厂设计模式,新建RedisDistributedLock类并实现UUC里面的Lock接口,满足JUC里面AQS对Lock锁的接口规范定义来进行实现落地代码。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.locks.Lock;@Component
public class DistributedLockFactory {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private String lockName;public Lock getDistributedLock(String lockType) {if(lockType == null) return null;if(lockType.equalsIgnoreCase("REDIS")){lockName = "zzyyRedisLock";return new RedisDistributedLock(stringRedisTemplate,lockName);} else if(lockType.equalsIgnoreCase("ZOOKEEPER")){//TODO zookeeper版本的分布式锁实现return new ZookeeperDistributedLock();} else if(lockType.equalsIgnoreCase("MYSQL")){//TODO mysql版本的分布式锁实现return null;}return null;}
}
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.support.collections.DefaultRedisList;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;//@Component 引入DistributedLockFactory工厂模式,从工厂获得而不再从spring拿到
public class RedisDistributedLock implements Lock {private StringRedisTemplate stringRedisTemplate;private String lockName;//KEYS[1]private String uuidValue;//ARGV[1]private long   expireTime;//ARGV[2]public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;this.uuidValue = IdUtil.simpleUUID()+":"+Thread.currentThread().getId();//UUID:ThreadIDthis.expireTime = 30L;}@Overridepublic void lock(){tryLock();}@Overridepublic boolean tryLock(){try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}return false;}/*** 干活的,实现加锁功能,实现这一个干活的就OK,全盘通用* @param time* @param unit* @return* @throws InterruptedException*/@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException{if(time != -1L){this.expireTime = unit.toSeconds(time);}String script ="if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +"redis.call('hincrby',KEYS[1],ARGV[1],1) " +"redis.call('expire',KEYS[1],ARGV[2]) " +"return 1 " +"else " +"return 0 " +"end";System.out.println("script: "+script);System.out.println("lockName: "+lockName);System.out.println("uuidValue: "+uuidValue);System.out.println("expireTime: "+expireTime);while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) {TimeUnit.MILLISECONDS.sleep(50);}return true;}/***干活的,实现解锁功能*/@Overridepublic void unlock() {String script ="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then " +"   return nil " +"elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then " +"   return redis.call('del',KEYS[1]) " +"else " +"   return 0 " +"end";// nil = false 1 = true 0 = falseSystem.out.println("lockName: "+lockName);System.out.println("uuidValue: "+uuidValue);System.out.println("expireTime: "+expireTime);Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime));if(flag == null) {throw new RuntimeException("This lock doesn't EXIST");}}//===下面的redis分布式锁暂时用不到=======================================//===下面的redis分布式锁暂时用不到=======================================//===下面的redis分布式锁暂时用不到=======================================@Overridepublic void lockInterruptibly() throws InterruptedException {}@Overridepublic Condition newCondition() {return null;}
}

InventoryService使用工厂模式版:  

import ch.qos.logback.core.joran.conditional.ThenAction;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.atguigu.redislock.mylock.DistributedLockFactory;
import com.atguigu.redislock.mylock.RedisDistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.omg.IOP.TAG_RMI_CUSTOM_MAX_STREAM_FORMAT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j

public class InventoryService {
    @Autowired
    
private StringRedisTemplate stringRedisTemplate;
    @Value("${server.port}")
    private String port;
    @Autowired
    private DistributedLockFactory distributedLockFactory;

    
    public String sale()  {

        String retMessage = "";

        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();

        try {
            //1 查询库存信息
            
String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            
Integer inventoryNumber = result == null : Integer.parseInt(result);
            //3 扣减库存
            
if(inventoryNumber > 0) {
                inventoryNumber = inventoryNumber - 1;
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber+"\t服务端口:" +port;
                System.out.println(retMessage);
                return retMessage;
            }
            retMessage = "商品卖完了,o(╥╥)o"+"\t服务端口:" +port;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            redisLock.unlock();
        }

        return retMessage;
    }
}

可重入性加锁

测试代码改造:InventoryService 类

import com.atguigu.redislock.mylock.DistributedLockFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.locks.Lock;

@Service
@Slf4j

public class InventoryService {
    @Autowired
    
private StringRedisTemplate stringRedisTemplate;
    @Value("${server.port}")
    private String port;
    @Autowired
    
private DistributedLockFactory distributedLockFactory;

    public String sale() {
        String retMessage = "";
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            //1 查询库存信息
            
String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            
Integer inventoryNumber = result == null : Integer.parseInt(result);
            //3 扣减库存
            
if(inventoryNumber > 0) {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber+"\t";
                System.out.println(retMessage);
                testReEnter();
            }else{
                retMessage = "商品卖完了,o(╥╥)o";
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            redisLock.unlock();
        }
        return retMessage+"\t"+"服务端口号:"+port;
    }

    private void testReEnter() {
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            System.out.println("################
测试可重入锁#######");
        }finally {
            redisLock.unlock();
        }
    }

}
 

测试结果:ThreadID一致了但是UUID不OK。

改造代码:

import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.locks.Lock;

@Component
public class DistributedLockFactory {
    @Autowired
    
private StringRedisTemplate stringRedisTemplate;
    private String lockName;
    private String uuidValue;

    public DistributedLockFactory() {
        this.uuidValue = IdUtil.simpleUUID();//UUID
    
}


    public Lock getDistributedLock(String lockType) {
        if(lockType == nullreturn null;

        if(lockType.equalsIgnoreCase("REDIS")){
            lockName "zzyyRedisLock";
            return new RedisDistributedLock(stringRedisTemplate,lockName,uuidValue);

        } else if(lockType.equalsIgnoreCase("ZOOKEEPER")){
            //TODO zookeeper版本的分布式锁实现
            
return new ZookeeperDistributedLock();
        } else if(lockType.equalsIgnoreCase("MYSQL")){
            //TODO mysql版本的分布式锁实现
            
return null;
        }
        return null;
    }
}
 

import cn.hutool.core.util.IdUtil;
import lombok.SneakyThrows;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class RedisDistributedLock implements Lock {
    private StringRedisTemplate stringRedisTemplate;
    private String lockName;
    private String uuidValue;
    private long   expireTime;

    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName,String uuidValue) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuidValue+":"+Thread.currentThread().getId();
        this.expireTime = 30L;
    }


    @Override
    
public void lock() {
        this.tryLock();
    }
    @Override
    
public boolean tryLock() {
        try {
            return this.tryLock(-1L,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if(time != -1L) {
            expireTime = unit.toSeconds(time);
        }

        String script =
                "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +
                    "redis.call('hincrby',KEYS[1],ARGV[1],1) " +
                    "redis.call('expire',KEYS[1],ARGV[2]) " +
                    "return 1 " +
                "else " +
                    "return 0 " +
                "end";
        System.out.println("lockName: "+lockName+"\t"+"uuidValue: "+uuidValue);

        while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) {
            try { TimeUnit.MILLISECONDS.sleep(60); } catch (InterruptedException e) { e.printStackTrace(); }
        }

        return true;
    }

    @Override
    
public void unlock() {
        String script =
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then " +
                    "return nil " +
                "elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then " +
                    "return redis.call('del',KEYS[1]) " +
                "else " +
                        "return 0 " +
                "end";
        System.out.println("lockName: "+lockName+"\t"+"uuidValue: "+uuidValue);
        Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime));
        if(flag == null) {
            throw new RuntimeException("没有这个锁,HEXISTS查询无");
        }
    }

    //=========================================================
    
@Override
    
public void lockInterruptibly() throws InterruptedException {

    }
    @Override
    
public Condition newCondition() {
        return null;
    }
}

InventoryService类新增可重入测试方法:

import cn.hutool.core.util.IdUtil;
import com.atguigu.redislock.mylock.DistributedLockFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j

public class InventoryService {
    @Autowired
    
private StringRedisTemplate stringRedisTemplate;
    @Value("${server.port}")
    private String port;
    @Autowired
    
private DistributedLockFactory distributedLockFactory;

    public String sale() {
        String retMessage = "";
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            //1 查询库存信息
            
String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            
Integer inventoryNumber = result == null : Integer.parseInt(result);
            //3 扣减库存
            
if(inventoryNumber > 0) {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber;
                System.out.println(retMessage);
                this.testReEnter();
            }else{
                retMessage = "商品卖完了,o(╥╥)o";
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            redisLock.unlock();
        }
        return retMessage+"\t"+"服务端口号:"+port;
    }


    private void testReEnter() {
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            System.out.println("################
测试可重入锁####################################");
        }finally {
            redisLock.unlock();
        }
    }

}

结果:单机+并发+可重入性,测试通过

V8.0版本(自动续期)

思考:确保redisLock过期时间大于业务执行时间的问题,Redis分布式如何续续期?

什么是CAP?

  • C(一致性):所有的节点上的数据时刻保持同步
  • A(可用性):每个请求都能接受到一个响应,无论响应成功或失败
  • P(分区容错):系统应该能持续提供服务,即使系统内部有消息丢失(分区)
  • CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但其实分区不是你想不想的问题,而是始终会存在,因此CA的系统更多的是允许分区后各子系统依然保持CA。
  • CP without A:如果不要求A(可用),相当于每个请求都需要在Server之间强一致,而P(分区)会导致同步时间无限延长,如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。
  • AP wihtout C:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。
     

Redis集群是AP

        redis异步复制造成的锁丢失,比如:主节点没来的及把刚刚set进来这条数据给从节点,master就挂了,从机上位但从机上无该数据。
Zookeeper集群是CP

故障问题:

Eureka集群是AP

Nacos集群是AP

lua代码(加钟)

脚本测试:

hset zzyyRedisLock 111122223333:11 3

EXPIRE zzyyRedisLock 30

ttl zzyyRedisLock

。。。。。

eval "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end" 1 zzyyRedisLock 111122223333:11 30

ttl zzyyRedisLock

lua脚本:

//==============自动续期
if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then
  return redis.call('expire',KEYS[1],ARGV[2])
else
  return 0
end

自动续期

import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.support.collections.DefaultRedisList;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class RedisDistributedLock implements Lock {
    private StringRedisTemplate stringRedisTemplate;

    private String lockName;//KEYS[1]
    
private String uuidValue;//ARGV[1]
    
private long   expireTime;//ARGV[2]

    
public RedisDistributedLock(StringRedisTemplate stringRedisTemplate,String lockName,String uuidValue) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuidValue = uuidValue+":"+Thread.currentThread().getId();
        this.expireTime 30L;
    }
    @Override
    
public void lock() {
        tryLock();
    }

    @Override
    
public boolean tryLock() {
        try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}
        return false;
    }

    /**
     * 
干活的,实现加锁功能,实现这一个干活的就OK,全盘通用
     
@param time
     
@param unit
     
@return
     
@throws InterruptedException
     */
    
@Override
    
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        if(time != -1L) {
            this.expireTime = unit.toSeconds(time);
        }

        String script =
                "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then " +
                        "redis.call('hincrby',KEYS[1],ARGV[1],1) " +
                        "redis.call('expire',KEYS[1],ARGV[2]) " +
                        "return 1 " +
                        "else " +
                        "return 0 " +
                        "end";

        System.out.println("script: "+script);
        System.out.println("lockName: "+lockName);
        System.out.println("uuidValue: "+uuidValue);
        System.out.println("expireTime: "+expireTime);

        while (!stringRedisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) {
            TimeUnit.MILLISECONDS.sleep(50);
        }
        this.renewExpire();
        return true;
    }

    /**
     *
干活的,实现解锁功能
     
*/
    
@Override
    
public void unlock() {
        String script =
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 then " +
                        "   return nil " +
                        "elseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 then " +
                        "   return redis.call('del',KEYS[1]) " +
                        "else " +
                        "   return 0 " +
                        "end";
        // nil = false 1 = true 0 = false
        
System.out.println("lockName: "+lockName);
        System.out.println("uuidValue: "+uuidValue);
        System.out.println("expireTime: "+expireTime);
        Long flag = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime));
        if(flag == null) {
            throw new RuntimeException("This lock doesn't EXIST");
        }
    }

    private void renewExpire() {
        String script =
                "if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then " +
                        "return redis.call('expire',KEYS[1],ARGV[2]) " +
                        "else " +
                        "return 0 " +
                        "end";

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                if (stringRedisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) {
                    renewExpire();
                }
            }
        },(this.expireTime * 1000)/3);
    }


    //===下面的redis分布式锁暂时用不到=======================================
    //===
下面的redis分布式锁暂时用不到=======================================
    //===
下面的redis分布式锁暂时用不到=======================================
    
@Override
    
public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    
public Condition newCondition() {
        return null;
    }
}

import cn.hutool.core.util.IdUtil;
import com.atguigu.redislock.mylock.DistributedLockFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j

public class InventoryService {
    @Autowired
    
private StringRedisTemplate stringRedisTemplate;
    @Value("${server.port}")
    private String port;
    @Autowired
    
private DistributedLockFactory distributedLockFactory;

    public String sale() {
        String retMessage = "";
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            //1 查询库存信息
            
String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            
Integer inventoryNumber = result == null : Integer.parseInt(result);
            //3 扣减库存
            
if(inventoryNumber > 0) {
                stringRedisTemplate.opsForValue().set("inventory001",String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: "+inventoryNumber;
                System.out.println(retMessage);
                //暂停几秒钟线程,为了测试自动续期
                
try { TimeUnit.SECONDS.sleep(120); } catch (InterruptedException e) { e.printStackTrace(); }
            }else{
                retMessage = "商品卖完了,o(╥╥)o";
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            redisLock.unlock();
        }
        return retMessage+"\t"+"服务端口号:"+port;
    }


    private void testReEnter() {
        Lock redisLock = distributedLockFactory.getDistributedLock("redis");
        redisLock.lock();
        try {
            System.out.println("################测试可重入锁####################################");
        }finally {
            redisLock.unlock();
        }
    }
}

总结流程

1、使用 synchronized 关键字或使用jdk中Lock,单机版OK,上分布式死翅翘

2、nginx分布式微服务,单机锁不行

3、取消单机锁,上redis分布式锁setnx

3.1、只加了锁,没有释放锁,出异常的话,可能无法释放锁,必须要在代码层面finally释放锁

3.2、宕机了,部署了微服务代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,需要有lockKey的过期时间设定

4、为redis的分布式锁key,增加过期时间,此外,还必须要setnx+过期时间同一行

4.1、必须规定只能自己删除自己的锁,你不能把别人的锁删除了,防止张冠李戴,1别2,2删3的情况

5、unlock变为lua脚本保证原子性

6、锁重入,hset替代setnx+lock变为Lua脚本保证

7、锁的自动续期

http://www.qdjiajiao.com/news/11479.html

相关文章:

  • 伦敦做网站2024年重启核酸
  • 上海史特网站建设种子搜索
  • 工程建设云谷歌seo顾问
  • 做网站域名起什么作用app安装下载
  • 兴义网站建设网站建设seo值怎么提高
  • 自己做网站怎么跳过备案成都最新数据消息
  • wordpress 个人公众号网站优化排名推荐
  • 杭州政府网站建设关键词排名优化工具
  • html网站开头怎么做的软件开发外包
  • 淘宝客网站整站源码朋友圈推广一天30元
  • 凤阳做网站百度软件商店
  • 做盗版网站吗校园推广
  • 做游戏网站思想步骤东莞seo软件
  • 网站建设佰首选金手指十八全国广告投放平台
  • 台州网站建设企业什么是网络整合营销
  • 个人网站主页设计教程网站优化和网站推广
  • 有合作社做网站得不站长工具排名查询
  • 如何规划企业网站什么是搜索引擎竞价推广
  • 小户型室内装修设计公司网站公司网络营销实施计划
  • 微网站开发平台免费关键词搜索引擎工具
  • 龙之向导外贸向导新野seo公司
  • 辽宁建设工程信息网怎么入库seo优化教程自学
  • 西安网站建设设计的好公司排名页优化软件
  • 东莞网站建设定制南昌百度搜索排名优化
  • 大理网站建设做好网络推广的技巧
  • 网站建设技术网站推广渠道
  • 北海教网站建设百度推广的定义
  • 公司申请网站建设的工作方案西安关键词seo
  • 网站建设方案书 个人备案百度网页版浏览器
  • 秦皇岛网站制作哪家好b2b平台运营模式