第十九章_手写Redis分布式锁
锁的种类
单机版同一个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循环起别名…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
【JavaSE】多线程基础学习笔记
多线程基础 -线程相关概念 程序(Program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存…...

STM32HAL库USART源代码解析及应用
STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...