.php的网站是怎么做的/广州公关公司
锁的种类
单机版同一个JVM虚拟机内synchronized或者Lock接口。
分布式多个不同JVM虚拟机,单机的线程锁机制不再起作用,资源类在不同的服务器之间共享了。
一个靠谱分布式锁需要具备的条件和刚需
独占性 :OnlyOne,任何时刻只能有且仅有一个线程持有。
高可用:若redis集群环境下,不能因为某一个节点挂了而出现获取锁和释放锁失败的情况,高并发请求下,依旧性能OK好使。(redis集群为cp模式,zookeeper集群为ap模式)
防死锁:杜绝死锁,必须有超时控制机制或者撤销操作,有个兜底终止跳出方案。
不乱抢:防止张冠李戴,不能私下unlock别人的锁,只能自己加锁自己释放,自己约的锁含着泪也要自己解。
重入性:同一个节点的同一个线程如果获得锁之后,它也可以再次获取这个锁。
分布式锁
setnx key value
差评,setnx+expire不安全,两条命令非原子性的。
set key value [EX seconds] [PX milliseconds] [NX|XX]。
重点
JUC中AQS锁的规范落地参考+可重入锁考虑+Lua脚本+Redis命令一步步实现分布式锁。
Base案例(boot+redis)
使用场景:多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击)。
建Module
redis_distributed_lock2、redis_distributed_lock3
改POM
<?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.test.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>
写YML
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
主启动
package com.test.redislock;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @auther admin*/
@SpringBootApplication
public class RedisDistributedLockApp7777 {public static void main(String[] args) {SpringApplication.run(RedisDistributedLockApp7777.class,args);}}
业务类
Swagger2Config
package com.test.redislock.config;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;/*** @auther admin*/
@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()//你自己的package.apis(RequestHandlerSelectors.basePackage("com.test.redislock")) .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();}}
RedisConfig
package com.test.redislock.config;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;/*** @auther admin*/
@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;}}
InventoryService
package com.test.redislock.service;/*** @auther admin*/
public class InventoryService {/*** 模拟商品库存扣减*/String sale();}
InventoryServiceImpl
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();/*** 模拟商品库存扣减*/@Overridepublic 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;}}
InventoryController
package com.test.redislock.controller;import cn.hutool.core.util.IdUtil;
import com.test.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;/*** @auther admin*/
@RestController
@Api(tags = "redis分布式锁测试")
public class InventoryController {@Autowiredprivate InventoryService inventoryService;@ApiOperation("扣减库存,一次卖一个")@GetMapping(value = "/inventory/sale")public String sale() {return inventoryService.sale();}}
使用swagger进行测试
http://localhost:7777/swagger-ui.html#/
手写分布式锁思路分析2023
大家来找茬
上面测试通过求吐槽
1.初始化版本简单添加
InventoryServiceImpl
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();/*** 模拟商品库存扣减*/@Overridepublic 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;}
}
请将端口7777的业务逻辑代码原样拷贝到端口8888
加了synchronized或者Lock。
2.nginx分布式微服务架构
问题
V2.0版本代码分布式部署后,单机锁还是出现超卖现象,需要分布式锁。
Nginx配置负载均衡
命令地址+配置地址
命令地址
/usr/local/nginx/sbin
配置地址
/usr/local/nginx/conf
启动
cd /usr/local/nginx/sbin
./nginx
启动Nginx并测试通过,浏览器看到nginx欢迎welcome页面
/usr/local/nginx/conf目录下修改配置文件,nginx.conf新增反向代理和负载均衡配置
关闭
cd /usr/local/nginx/sbin
./nginx -s stop
指定配置启动
在/usr/local/nginx/sbin路径下执行下面的命令
./nginx -c /usr/local/nginx/conf/nginx.conf
重启
cd /usr/local/nginx/sbin
./nginx -s reload
V2.0版本代码修改+启动两个微服务
端口7777服务
InventoryServiceImpl
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();/*** 模拟商品库存扣减*/@Overridepublic 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服务
InventoryServiceImpl
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;private Lock lock = new ReentrantLock();/*** 模拟商品库存扣减*/@Overridepublic 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;}}
通过Nginx访问,Linux服务器地址IP,反向代理+负载均衡
可以点击看到效果,一边一个,默认轮询
http://192.168.111.185/inventory/sale
上面纯手点验证OK,下面高并发模拟
线程组redis
100个商品足够了
http请求
jmeter压测
76号商品被卖出2次,出现超卖故障现象
bug-why
为什么加了synchronized或者Lock还是没有控制住?
解释
在单机环境下,可以使用synchronized或Lock来实现。
但是在分布式系统中,因为竞争的线程可能不在同一个节点上(同一个jvm中),
所以需要一个让所有进程都能访问到的锁来实现(比如redis或者zookeeper来构建)
不同进程jvm层面的锁就不管用了,那么可以利用第三方的一个组件,来获取锁,未获取到锁,则阻塞当前想要运行的线程。
分布式锁出现
能做什么
跨进程+跨服务
解决超卖
防止缓存击穿
解决方案
上Redis分布式锁
官网命令
3.redis分布式锁
修改代码为3.1版
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;/*** 模拟商品库存扣减*/@Overridepublic 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;}}
通过递归重试的方式。
存在的问题
测试手工OK,测试Jmeter压测5000OK。
递归是一种思想没错,但是容易导致StackOverflowError,不太推荐,需进一步完善。
修改代码为3.2版
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;/*** 模拟商品库存扣减*/@Overridepublic 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;}}
多线程判断想想JUC里面说过的虚假唤醒,用while替代if。
用自旋替代递归重试。
4.宕机与过期+防止死锁
当前代码为3.2版接上一步
存在问题
部署了微服务的Java程序机器挂了,代码层面根本没有走到finally这块,没办法保证解锁(无过期时间该key一直存在),这个key没有被删除,需要加入一个过期时间限定key。
解决方案
修改代码为4.1版
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;/*** 模拟商品库存扣减*/@Overridepublic 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();}}//设置过期时间,请思考可以这么操作吗?stringRedisTemplate.expire(key, 30L, TimeUnit.SECONDS);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;}}
stringRedisTemplate.expire(key,30L,TimeUnit.SECONDS);
请思考可以这么操作吗?
4.1版本结论
设置key+过期时间分开了,必须要合并成一行具备原子性
修改代码为4.2版
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;/*** 模拟商品库存扣减*/@Overridepublic String sale() {String retMessage = "";String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();//设置过期时间,形成原子操作while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {//暂停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;}}
Jmeter压测OK
4.2版本结论
加锁和过期时间设置必须同一行,保证原子性
5.防止误删key的问题
当前代码为4.2版接上一步
存在问题
实际业务处理时间如果超过了默认设置key的过期时间??尴尬 ̄□ ̄||
会出现张冠李戴的情况,删除了别人的锁。
解决方案
只能自己删除自己的,不许动别人的
修改代码为5.0版
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;/*** 模拟商品库存扣减*/@Overridepublic String sale() {String retMessage = "";String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();//设置过期时间,形成原子操作while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {//暂停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 {// v5.0判断加锁与解锁是不是同一个客户端,同一个才行,自己只能删除自己的锁,不误删他人的if (stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)) {stringRedisTemplate.delete(key);}}return retMessage + "\t" + "服务端口号:" + port;}}
6.Lua保证原子性
当前代码为5.0版接上一步
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;/*** 模拟商品库存扣减*/@Overridepublic String sale() {String retMessage = "";String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();//设置过期时间,形成原子操作while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {//暂停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 {// v5.0判断加锁与解锁是不是同一个客户端,同一个才行,自己只能删除自己的锁,不误删他人的if (stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)) {stringRedisTemplate.delete(key);}}return retMessage + "\t" + "服务端口号:" + port;}}
存在问题
finally块的判断+del删除操作不是原子性的
启用lua脚本编写redis分布式锁判断+删除判断代码
lua脚本
官网
官方脚本
Lua脚本浅谈
Lua脚本初识
Redis调用Lua脚本通过eval命令保证代码执行的原子性,直接用return返回脚本执行后的结果值
语法:eval luascript numkeys [key [key ...]] [arg [arg ...]]
helloworld入门
1-hello lua
2-set k1 v1 get k1
3-mset
Lua脚本进一步
Redis分布式锁lua脚本官网练习
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
条件判断语法
条件判断案例
解决方案
修改代码为6.0版code
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;/*** 模拟商品库存扣减*/@Overridepublic String sale() {String retMessage = "";String key = "zzyyRedisLock";String uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();//设置过期时间,形成原子操作while (!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue, 30L, TimeUnit.SECONDS)) {//暂停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 {//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说明
stringRedisTemplate.execute( new DefaultRedisScript<>(script), Arrays. asList(key),value); |
stringRedisTemplate.execute( new DefaultRedisScript<>(script, Long.class), Arrays. asList(key),value); //使用该构造方法,不然报错 |
7.可重入锁+设计模式
当前代码为6.0版接上一步
while判断并自旋重试获取锁+setnx含自然过期时间+Lua脚本官网删除锁命令。
存在问题
如何兼顾锁的可重入性问题?
复习写好一个锁的条件和规约
可重入锁(又名递归锁)
说明
可重入锁又名递归锁
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
“可重入锁”这四个字分开来解释:
可:可以。
重:再次。
入:进入。
锁:同步锁。
进入什么
进入同步域(即同步代码块/方法或显式锁锁定的代码)
一句话
一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。自己可以获取自己的内部锁。
JUC知识复习,可重入锁出bug会如何影响程序?
可重入锁种类
隐式锁(即synchronized关键字使用的锁)默认是可重入锁。
指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
简单的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的
与可重入锁相反,不可重入锁不可递归调用,递归调用就发生死锁。
同步块
package com.test.juc.senior.prepare;/*** @auther admin*/
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();}
}
同步方法
package com.test.juc.senior.prepare;/*** @auther admin* 在一个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();}}
Synchronized的重入的实现机理
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
显式锁(即Lock)也有ReentrantLock这样的可重入锁。
package com.test.juc.senior.prepare;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @auther admin* 在一个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配合可重入锁进行AQS源码分析讲解
切记,一般而言,你lock了几次就要unlock几次
思考,上述可重入锁计数问题,redis中那个数据类型可以代替
K,K,V
hset zzyyRedisLock 29f0ee01ac77414fb8b0861271902a94:1
Map<String, Map<Object, Object>>
案例命令
hset key field value |
hset redis锁名字(zzyyRedisLock) 某个请求线程的UUID+ThreadID 加锁的次数 |
小总结
setnx,只能解决有无的问题,够用但是不完美。
hset,不但解决有无,还解决可重入问题。
思考+设计重点(一横一纵)
目前有2条支线,目的是保证同一个时候只能有一个线程持有锁进去redis做扣减库存动作。
2个分支
1.保证加锁/解锁,lock/unlock
2.扣减库存redis命令的原子性
lua脚本
redis命令过程分析
加锁lua脚本lock
先判断redis分布式锁这个key是否存在
EXISTS key
返回零说明不存在,hset新建当前线程属于自己的锁BY UUID:ThreadID
HSET zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1 1
命令 key value = UUID:ThreadID 次数
返回壹说明已经有锁,需进一步判断是不是当前线程自己的
HEXISTS key uuid:ThreadID
HEXISTS zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1
返回零说明不是自己的返回壹说明是自己的锁,自增1次表示重入
HINCRBY key field increment
HINCRBY zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1 1
将上述设计修改为Lua脚本
V1
if redis.call('exists', 'key') == 0 thenredis.call('hset', 'key', 'uuid:threadid', 1)redis.call('expire', 'key', 30)return 1elseif redis.call('hexists', 'key', 'uuid:threadid') == 1 thenredis.call('hincrby', 'key', 'uuid:threadid', 1)redis.call('expire', 'key', 30)return 1elsereturn 0end
相同部分是否可以替换处理???
相同部分是否可以替换处理???
V2
if redis.call('exists', 'key') == 0 or redis.call('hexists', 'key', 'uuid:threadid') == 1 thenredis.call('hincrby', 'key', 'uuid:threadid', 1)redis.call('expire', 'key', 30)return 1elsereturn 0end
V3
key | KEYS[1] | zzyyRedisLock |
value | ARGV[1] | 2f586ae740a94736894ab9d51880ed9d:1 |
过期时间值 | ARGV[2] | 30 秒 |
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 elsereturn 0end
测试
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
设计思路:有锁且还是自己的锁
HEXISTS key uuid:ThreadID
HEXISTS zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1
返回零,说明根本没有锁,程序块返回nil,不是零,说明有锁且是自己的锁,直接调用HINCRBY 负一 表示每次减个一,解锁一次。直到它变为零表示可以删除该锁Key,del 锁key。
全套流程
上述设计修改为Lua脚本
V1
if redis.call('HEXISTS', lock, uuid:threadID) == 0 thenreturn nilelseif redis.call('HINCRBY', lock, uuid:threadID, -1) == 0 thenreturn redis.call('del', lock)else return 0end
V2
if redis.call('HEXISTS', KEYS[1], ARGV[1]) == 0 thenreturn nilelseif redis.call('HINCRBY', KEYS[1], ARGV[1], -1) == 0 thenreturn redis.call('del', KEYS[1])elsereturn 0end
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程序
复原程序为初始无锁版
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;/*** 模拟商品库存扣减*/@Overridepublic 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类并实现JUC里面的Lock接口
满足JUC里面AQS对Lock锁的接口规范定义来进行实现落地代码
结合设计模式开发属于自己的Redis分布式锁工具类
lock方法的全盘通用讲解
lua脚本
加锁lock
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 elsereturn 0end
解锁unlock
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 0end
工厂设计模式引入
通过实现JUC里面的Lock接口,实现Redis分布式锁RedisDistributedLock。
package com.test.redislock.mylock;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;/*** @auther admin*/
//@Component 引入DistributedLockFactory工厂模式,从工厂获得而不再从spring拿到
public class RedisDistributedLock implements Lock {private StringRedisTemplate stringRedisTemplate;//KEYS[1]private String lockName;//ARGV[1]private String uuidValue;//ARGV[2]private long expireTime;public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;//UUID:ThreadIDthis.uuidValue = IdUtil.simpleUUID() + ":" + Thread.currentThread().getId();this.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;}}
InventoryServiceImpl直接使用上面的代码设计,有什么问题?
考虑扩展,本次是redis实现分布式锁,以后zookeeper、mysql实现那??
引入工厂模式改造7.1版code
DistributedLockFactory
package com.test.redislock.mylock;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;/*** @auther admin*/
@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;}}
RedisDistributedLock
package com.test.redislock.mylock;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;/*** @auther admin*/
//@Component 引入DistributedLockFactory工厂模式,从工厂获得而不再从spring拿到
public class RedisDistributedLock implements Lock {private StringRedisTemplate stringRedisTemplate;//KEYS[1]private String lockName;//ARGV[1]private String uuidValue;//ARGV[2]private long expireTime;public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate = stringRedisTemplate;this.lockName = lockName;//UUID:ThreadIDthis.uuidValue = IdUtil.simpleUUID() + ":"+Thread.currentThread().getId();this.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;}}
InventoryServiceImpl使用工厂模式版
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;import ch.qos.logback.core.joran.conditional.ThenAction;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.test.redislock.mylock.DistributedLockFactory;
import com.test.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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;@Autowiredprivate DistributedLockFactory distributedLockFactory;/*** 模拟商品库存扣减*/@Overridepublic String sale() {String retMessage = "";Lock redisLock = distributedLockFactory.getDistributedLock("redis");redisLock.lock();try {//1 查询库存信息String result = stringRedisTemplate.opsForValue().get("inventory001");//2 判断库存是否足够Integer inventoryNumber = result == null ? 0 : 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;}}
单机+并发测试通过
http://localhost:7777/inventory/sale
可重入性测试重点
可重入测试???
InventoryServiceImpl类新增可重入测试方法
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;import com.test.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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;@Autowiredprivate DistributedLockFactory distributedLockFactory;/*** 模拟商品库存扣减*/@Overridepublic String sale() {String retMessage = "";Lock redisLock = distributedLockFactory.getDistributedLock("redis");redisLock.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 + "\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();}}}
http://localhost:7777/inventory/sale
结果
ThreadID一致了但是UUID不OK,o(╥﹏╥)o
引入工厂模式改造7.2版code
DistributedLockFactory
package com.test.redislock.mylock;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;/*** @auther admin*/
@Component
public class DistributedLockFactory {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private String lockName;private String uuidValue;public DistributedLockFactory() {//UUIDthis.uuidValue = IdUtil.simpleUUID();}public Lock getDistributedLock(String lockType) {if (lockType == null) {return 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;}
}
RedisDistributedLock
package com.test.redislock.mylock;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;/*** @auther admin*/
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;}@Overridepublic void lock() {this.tryLock();}@Overridepublic boolean tryLock() {try {return this.tryLock(-1L, TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}return false;}@Overridepublic 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;}@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";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查询无");}}//=========================================================@Overridepublic void lockInterruptibly() throws InterruptedException {}@Overridepublic Condition newCondition() {return null;}}
InventoryServiceImpl类新增可重入测试方法
package com.test.redislock.service.impl;import com.test.redislock.service.InventoryService;import cn.hutool.core.util.IdUtil;
import com.test.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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;@Autowiredprivate 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 ? 0 : 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();}}}
单机+并发+可重入性,测试通过
8.自动续期
确保redisLock过期时间大于业务执行时间的问题。
分布式锁如何续期?
CAP
Redis集群是AP(AP)
redis异步复制造成的锁丢失,比如:主节点没来的及把刚刚set进来这条数据给从节点,master就挂了,从机上位但从机上无该数据。
Zookeeper集群是CP(CP)
故障
顺便复习Eureka集群是AP(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 |
//==============自动续期 if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end |
8.0版新增自动续期功能
修改为V8.0版程序
del掉之前的lockName zzyyRedisLock
RedisDistributedLock
package com.test.redislock.mylock;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;/*** @auther admin*/
public class RedisDistributedLock implements Lock {private StringRedisTemplate stringRedisTemplate;//KEYS[1]private String lockName;//ARGV[1]private String uuidValue;//ARGV[2]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;}@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);}this.renewExpire();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");}}/*** 锁自动续期*/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() {@Overridepublic 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分布式锁暂时用不到=======================================@Overridepublic void lockInterruptibly() throws InterruptedException {}@Overridepublic Condition newCondition() {return null;}}
InventoryServiceImpl
package com.atguigu.redislock.service.impl;import com.test.redislock.service.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;/*** @auther admin*/
@Slf4j
@Service
public class InventoryServiceImpl implements InventoryService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;@Autowiredprivate 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 ? 0 : 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();}}}
记得去掉可重入测试testReEnter()
InventoryService业务逻辑里面故意sleep一段时间测试自动续期
总结
synchronized单机版OK,上分布式出现异常。
nginx分布式微服务单机锁不行/(ㄒoㄒ)/~~。
取消单机锁,上redis分布式锁setnx。
只加了锁,没有释放锁,出异常的话,可能无法释放锁,必须要在代码层面finally释放锁。
宕机了,部署了微服务代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,需要有lockKey的过期时间设定。
为redis的分布式锁key,增加过期时间此外,还必须要setnx+过期时间必须同一行。
必须规定只能自己删除自己的锁,你不能把别人的锁删除了,防止张冠李戴,1删2,2删3。
unlock变为Lua脚本保证。
锁重入,hset替代setnx+lock变为Lua脚本保证。
自动续期。
面试题总结
Redis除了拿来做缓存,你还见过基于Redis的什么用法?
- 数据共享,分布式Session
- 分布式锁
- 全局ID
- 计算器、点赞
- 位统计
- 购物车
- 轻量级消息队列(list、stream)
- 抽奖
- 点赞、签到、打卡
- 差集交集并集,用户关注、可能认识的人,推荐模型
- 热点新闻、热搜排行榜
Redis 做分布式锁的时候有需要注意的问题?
你们公司自己实现的分布式锁是否用的setnx命令实现?这个是最合适的吗?你如何考虑分布式锁的可重入问题?
如果是 Redis 是单点部署的,会带来什么问题?那你准备怎么解决单点问题呢?
Redis集群模式下,比如主从模式,CAP方面有没有什么问题呢?
那你简单的介绍一下 Redlock 吧?你简历上写redisson,你谈谈。
Redis分布式锁如何续期?看门狗知道吗?
。。。。。。
相关文章:

第十九章_手写Redis分布式锁
锁的种类 单机版同一个JVM虚拟机内synchronized或者Lock接口。 分布式多个不同JVM虚拟机,单机的线程锁机制不再起作用,资源类在不同的服务器之间共享了。 一个靠谱分布式锁需要具备的条件和刚需 独占性 :OnlyOne,任何时刻只能有且…...

电路设计【8】原理图中VCC、VDD、VEE、VSS、VBAT各表示什么意思
文章目录 一、名词解析二、应用讲解三、举例分析:为什么stm32vet6中要分出5对VDD VSS?它们分别负责哪些模块的供电? 一、名词解析 (1)VCC:Ccircuit 表示电路的意思, 即接入电路的电压 (2&…...

Volatile、Synchronized、ReentrantLock锁机制使用说明
一、Volatile底层原理 volatile是轻量级的同步机制,volatile保证变量对所有线程的可见性,不保证原子性。 当对volatile变量进行写操作的时候,JVM会向处理器发送一条LOCK前缀的指令,将该变量所在缓存行的数据写回系统内存。由于缓…...

港联证券|AI概念股继续活跃 科创50指数逆势走高
周三,A股市场出现极致分化态势。得益于存储芯片为代表的硬科技股的强势,科创50指数逆势走高。但创业板指、深证成指等主要股指仍然跌跌不休,沪指险守3200点关口。AI概念股继续逆势活跃,国资云、数据方向领涨,算力概念股…...

分布式事务一 事物以及分布式事物介绍
一 事务简介 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。 原子性(at…...

【十四】设计模式~~~行为型模式~~~中介者模式(Java)
【学习难度:★★★☆☆,使用频率:★★★★★】 3.1. 模式动机 建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标&#…...

css3--nth-child的用法
目录 使用CSS nth-child选择器基本用法使用公式从零开始关键点结论 使用CSS nth-child选择器 CSS的 :nth-child 选择器是一个强大的工具,允许我们根据它们在父元素中的位置选择元素。这为我们提供了更大的灵活性来控制页面上的元素。 基本用法 基本形式为 :nth-c…...

【假捻发加工生产工单下达】
假捻工单是需要下到工作中心的,比如A01机台或者A02机台。 所以下工单之前要先查询A01机台上的最新工单量。 查询结果如下: 她会按照创建时间进行排序,后下的工单排在最前面 (如果下了个新工单,那么前一个工单的执行状态会自动改为关闭。) 因此查询结果,最上面的工单执…...

Go for-range VS for
Go 语言中,for-range 可以用来遍历string、数组(array)、切片(slice)、map和channel,实际使用过程踩了一些坑,所以,还是总结记录下for-range的原理。 首先,go是值传递语言。变量是指针类型,复制指针传递&a…...

大数据教程【01.01】--大数据介绍及开发环境
更多信息请关注WX搜索GZH:XiaoBaiGPT 大数据简介 大数据(Big Data)是指规模庞大、结构复杂、增长速度快且难以使用传统技术处理的数据集合。大数据分析可以帮助企业和组织从海量的数据中提取有价值的信息,用于业务决策、市场分析、…...

文件阅览功能的实现(适用于word、pdf、Excel、ppt、png...)
需求描述: 需要一个组件,同时能预览多种类型文件,一种类型文件可有多个的文件。 看过各种博主的方案,其中最简单的是利用第三方地址进行预览解析(无需任何插件); 这里推荐三个地址:…...

面试-RabbitMQ常见面试问题
1.什么是RabbitMQ? RabbitMQ是一款基于AMQP协议的消息中间件,消费方并不需要确保提供方的存在,实现服务之间的高度解耦。 基本组成有: Queue:消息队列,存储消息,消息送达队列后转发给指定的消费方Exchange:消息队列交…...

使用VBA在单元格中快速插入Unicode符号
Unicode 符号 Unicode 符号在实际工作中有着广泛的应用,比如用于制作邮件签名、文章排版、演示文稿制作等等。在 Excel 表格中,插入符号可以让表格的排版更加美观,同时也能够帮助用户更清晰地表达意思。 Dingbats Dingbats是一个包含装饰符…...

PyTorch 深度学习 || 专题六:PyTorch 数据的准备
PyTorch 数据的准备 1. 生成数据的准备工作 import torch import torch.utils.data as Data#准备建模数据 x torch.unsqueeze(torch.linspace(-1, 1, 500), dim1) # 生成列向量 y x.pow(3) # yx^3#设置超参数 batch_size 15 # 分块大小 torch.manual_seed(10) # 设置种子点…...

迅为RK3568开发板2800页手册+220集视频
iTOP-3568开发板采用瑞芯微RK3568处理器,内部集成了四核64位Cortex-A55处理器。主频高达2.0Ghz,RK809动态调频。集成了双核心架构GPU,ARM G52 2EE、支持OpenGLES1.1/2.0/3.2OpenCL2.0、Vulkan 1.1、内高性能2D加速硬件。 内置NPU 内置独立NP…...

模拟电子 | 稳压管及其应用
模拟电子 | 稳压管及其应用 稳压二极管工作在反向击穿状态时,其两端的电压是基本不变的。利用这一性质,在电路里常用于构成稳压电路。 稳压二极管构成的稳压电路,虽然稳定度不很高,输出电流也较小,但却具有简单、经济实…...

使用大型语言模(LLM)构建系统(二):内容审核、预防Prompt注入
今天我学习了DeepLearning.AI的 Building Systems with LLM 的在线课程,我想和大家一起分享一下该门课程的一些主要内容。 下面是我们访问大型语言模(LLM)的主要代码: import openai#您的openai的api key openai.api_key YOUR-OPENAI-API-KEY def get_…...

springboot---mybatis操作事务配置的处理
目录 前言: 事务的相关问题 1、什么是事务? 2、事务的特点(ACID) 3、什么时候想到使用事务? 4、通常使用JDBC访问数据库,还是mybatis访问数据库,怎么处理事务? 5、问题中事务处…...

游戏盾是什么防御DDOS攻击的
游戏盾是一种专门用于防御分布式拒绝服务(DDoS)攻击的安全工具。DDoS攻击是指攻击者利用大量的计算机或设备同时向目标服务器发送海量的请求,以使目标服务器超负荷运行,无法正常提供服务。游戏盾通过一系列智能的防护措施…...

java快速结束嵌套循环
java快速结束嵌套循环 快速结束for循环 out:for (int i 0; i < 5; i) {in:for (int j 0; j < 5; j) {if (j 2) {break out;}System.out.println("i " i " j " j);}}解释 将外层for循环起别名 o u t \color{red}{out} out,将内层for循环起别名…...

chatgpt赋能python:Python屏蔽一段代码
Python屏蔽一段代码 在Python编程中,有时我们需要屏蔽一段代码以便于调试或者测试。在很多情况下,我们可能不想删除这段代码,因为需要在将来的某个时间再次使用它。为了解决这个问题,我们可以使用Python中的注释语句或者条件语句…...

项目跑不起来
Sa-Token/sa-token-core/src/main/java/cn/dev33/satoken/temp/SaTempUtil.java:10:8 java: 写入cn.dev33.satoken.temp.SaTempUtil时出错: Output directory is not specified 写入cn.dev33.satoken.temp.SaTempUtil时出错: Output directory is not specified 答案…...

黑马Redis视频教程高级篇(多级缓存案例导入说明)
目录 一、安装MYSQL 1.1、准备目录 1.2、运行命令 1.3、修改配置 1.4、重启 二、导入SQL 三、导入Demo工程 3.1、分页查询商品 3.2、新增商品 3.3、修改商品 3.4、修改库存 3.5、删除商品 3.6、根据id查询商品 3.7、根据id查询库存 3.8、启动 四、导入商品查询…...

2023系统分析师下午案例分析真题
真题1 阅读以下关于软件系统分析与建模的叙述,在纸上回答问题1至3. 说明: 某软件公司拟开发一套汽车租赁系统,科学安全和方便的管理租赁公司的各项业务,提高公司效率,提升利率。注册用户在使用系统镜像车辆预约时需执行以下操作…...

【Python练习】Matplotlib数据可视化
文章目录 一、实验目标二、实验内容1. 用画布的各种设置,绘制类似如图1所示的:y1=sin(x)和y2=cos(x)的曲线图2. 某校高一3班12名同学语数外三科成绩分布情况如表5-2所示,数据值也可以自拟,适当调整。绘制折线图、纵向条形图分析这些同学单科成绩情况,绘制纵向堆叠条形图查…...

【2611. 老鼠和奶酪】
来源:力扣(LeetCode) 描述: 有两只老鼠和 n 块不同类型的奶酪,每块奶酪都只能被其中一只老鼠吃掉。 下标为 i 处的奶酪被吃掉的得分为: 如果第一只老鼠吃掉,则得分为 reward1[i] 。如果第二…...

Reid strong baseline 代码详解
本项目是对Reid strong baseline代码的详解。项目暂未加入目标检测部分,后期会不定时更新,请持续关注。 本相比Reid所用数据集为Markt1501,支持Resnet系列作为训练的baseline网络。训练采用表征学习度量学习的方式。 目录 训练参数 训练代…...

宝塔面板搭建网站教程:Linux下使用宝塔一键搭建网站,内网穿透发布公网上线
文章目录 前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4. 固定http地址5. 配置二级子域名6. 创建一个测试页面 转载自cpolar内网穿透的文章:使用宝塔面板快速搭建网站,并内网穿透实现公网远程访问 前言 宝塔面板作为简单好用的服务器运维管理面板&…...

常微分方程(ODE)求解方法总结
常微分(ODE)方程求解方法总结 1 常微分方程(ODE)介绍1.1 微分方程介绍和分类1.2 常微分方程的非计算机求解方法1.3 线性微分方程求解的推导过程 2 一阶常微分方程(ODE)求解方法2.1 欧拉方法2.1.1 欧拉方法2…...

【华为OD机试】区间交集【2023 B卷|200分】
【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述 给定一组闭区间,其中部分区间存在交集。 任意两个给定区间的交集,称为公共区间 (如:[1,2],[2,3]的公共区间为[2,2],[3,5],[3,6]的公共区间为[3,5])。 公共区间之间若存在交集,则需…...