分布式锁-Redisson

2023-11-12

1.分布式并发问题

提交订单:商品超卖问题

2.如何解决分布式并发问题呢 ?

使⽤ redis 实现分布式锁

3.使⽤Redis实现分布式锁-代码实现

@Transactional
public Map<String,String> addOrder(String cids,Orders order) throws
SQLException {
 logger.info("add order begin...");
 Map<String, String> map = null;
 //1.校验库存:根据cids查询当前订单中关联的购物⻋记录详情(包括库存)
 String[] arr = cids.split(",");
 List<Integer> cidsList = new ArrayList<>();
 for (int i = 0; i < arr.length; i++) {
 cidsList.add(Integer.parseInt(arr[i]));
 }
 
 //根据⽤户在购物⻋列表中选择的购物⻋记录的id 查询到对应的购物⻋记录
 List<ShoppingCartVO> list =
shoppingCartMapper.selectShopcartByCids(cidsList);
 //从购物⻋信息中获取到要购买的 skuId(商品ID) 以skuId为key写到redis中: 1
 2 3
 boolean isLock = true;
 String[] skuIds = new String[list.size()]; //记录已经锁定的商品的ID
 for (int i = 0; i <list.size() ; i++) {
 String skuId = list.get(i).getSkuId(); //订单中可能包含多个商品,
每个skuId表示⼀个商品
 Boolean ifAbsent =
stringRedisTemplate.boundValueOps(skuId).setIfAbsent("fmmall");
 if(ifAbsent){
 skuIds[i] = skuId;
 }
 isLock = isLock && ifAbsent;
 }
 //如果isLock为true,表示“加锁”成功
 if(isLock){
 try{
 //1.⽐较库存: 当第⼀次查询购物⻋记录之后,在加锁成功之前,可能被其他
的并发线程修改库存
 List<ShoppingCartVO> list =
shoppingCartMapper.selectShopcartByCids(cidsList);
 boolean f = true;
 String untitled = "";
 for (ShoppingCartVO sc : list) {
 if (Integer.parseInt(sc.getCartNum()) >
sc.getSkuStock()) {
 f = false;
 }
 untitled = untitled + sc.getProductName() + ",";
 }
 if (f) {
 //2.添加订单
 //3.保存快照
 //4.修改库存
 //5.删除购物⻋
 map = new HashMap<>();
 logger.info("add order finished...");
 map.put("orderId", orderId);
 map.put("productNames", untitled);
 }
 }catch(Exception e){
 e.printStackTrance();
 }finally{
 //释放锁
 for (int m = 0; m < skuIds.length ; m++) {
 String skuId = skuIds[m];
 if(skuId!=null && !"".equals(skuId)){
 stringRedisTemplate.delete(skuId);
 }
 }
 }
 return map;
 }else{
 //表示加锁失败,订单添加失败
 // 当加锁失败时,有可能对部分商品已经锁定,要释放锁定的部分商品
 for (int i = 0; i < skuIds.length ; i++) {
 String skuId = skuIds[i];
 if(skuId!=null && !"".equals(skuId)){
 stringRedisTemplate.delete(skuId);
 }
 }
 return null;
 }
}
问题:
1. 如果订单中部分商品加锁成功,但是某⼀个加锁失败,导致最终加锁状态失败 —— 需要对
已经锁定的部分商品释放锁
2. 在成功加锁之前,我们根据购物⻋记录的 id 查询了购物⻋记录(包含商品库存),能够直接
使⽤这个库存进⾏库存校验?
—— 不能,因为在查询之后加锁之前可能被并发的线程修改了库存;因此在进⾏库存⽐较之
前需要重新查询库存。
3. 当当前线程加锁成功之后,执⾏添加订单的过程中,如果当前线程出现异常导致⽆法释放
锁,这个问题⼜该如何解决呢?

4.解决因线程异常导致⽆法释放锁的问题

解决⽅案:在对商品进⾏加锁时,设置过期时间,这样⼀来及时线程出现故障⽆法释放
锁,在过期时间结束时也会⾃动 释放锁
问题:当给锁设置了过期时间之后,如果当前线程 t1 因为特殊原因,在锁过期前没有完成业
务执⾏,将会释放锁,同时其他线程( t2 )就可以成功加锁了,当 t2 加锁成功之后, t1 执⾏结
束释放锁就会释放 t2 的锁 , 就会导致 t2 在⽆锁状态下执⾏业务。

5.解决因t1过期释放t2锁的问题

  • 在加锁的时候,为每个商品设置唯⼀的value

  • 在释放锁的时候,先获取当前商品在redis中对应的value,如果获取的值与当前value同,则释放锁

问题:当释放锁的时候,在查询并判断 这个锁是当前线程加的锁 成功之后,正要进⾏删除时
锁过期了,并且被其他线程成功加锁,⼀样会导致当前线程删除其他线程的锁。
  • Redis的操作都是原⼦性的
  • 要解决如上问题,必须保证查询操作和删除操作的原⼦性——使⽤lua脚本
使⽤ lua 脚本
  • resources⽬录下创建unlock.lua,编辑脚本:
if redis.call("get",KEYS[1]) == ARGV[1] then
 return redis.call("del",KEYS[1])
else
 return 0
end
  • 配置Bean加载lua脚本
@Bean
public DefaultRedisScript<List> defaultRedisScript(){
 DefaultRedisScript<List> defaultRedisScript = new
DefaultRedisScript<>();
 defaultRedisScript.setResultType(List.class);
 defaultRedisScript.setScriptSource(new ResourceScriptSource(new
ClassPathResource("unlock.lua")));
 return defaultRedisScript; }
  • 通过执⾏lua脚本解锁
@AutoWired
private DefaultRedisScript defaultRedisScript;
//执⾏lua脚本
List<String> keys = new ArrayList<>();
keys.add(skuId);
List rs = stringRedisTemplate.execute(defaultRedisScript,keys ,
values.get(skuId));
System.out.println(rs.get(0));

6.看⻔狗机制

看⻔⼝线程:⽤于给当前 key 延⻓过期时间,保证业务线程正常执⾏的过程中,锁不会过期。

7.分布式锁框架-Redisson

基于 Redis+ 看⻔狗机制的分布式锁框架

7.1 Redisson介绍

Redisson 在基于 NIO Netty 框架上,充分的利⽤了 Redis 键值数据库提供的⼀系列优势,在
Java 实⽤⼯具包中常⽤接⼝的基础上,为使⽤者提供了⼀系列具有分布式特性的常⽤⼯具
类。使得原本作为协调单机多线程并发程序的⼯具包获得了协调分布式多机多线程并发系统
的能⼒,⼤⼤降低了设计和研发⼤规模分布式系统的难度。同时结合各富特⾊的分布式服
务,更进⼀步简化了分布式环境中程序相互之间的协作

7.2 SpringBoot应⽤中使⽤Redisson

  • 添加依赖

<dependency>
<groupId> org.redisson </groupId>
<artifactId> redisson </artifactId>
<version> 3.12.0 </version>
</dependency>
  • 配置yml

redisson :
addr :
singleAddr :
host : redis : //47.96.11.185 : 6370
password : 12345678
database : 0
  • 配置RedissonClient
@Configuration
public class RedissonConfig {
 @Value("${redisson.addr.singleAddr.host}")
 private String host;
 @Value("${redisson.addr.singleAddr.password}")
 private String password;
 @Value("${redisson.addr.singleAddr.database}")
 private int database;
 @Bean
 public RedissonClient redissonClient(){
 Config config = new Config();
 
config.useSingleServer().setAddress(host).setPassword(password).se
tDatabase(database);
 return Redisson.create(config);
 }
}
  • 在秒杀业务实现中注⼊RedissonClient对象

7.3 Redisson⼯作原理

看⻔狗
Redisson ⼯作原理图

7.4 Redisson使⽤扩展

7.4.1 Redisson单机连接

  • application.yml

redisson :
addr :
singleAddr :
host : redis : //47.96.11.185 : 6370
password : 12345678
database : 0
  • RedissonConfig
@Configuration
public class RedissonConfig {
 @Value("${redisson.addr.singleAddr.host}")
 private String host;
 @Value("${redisson.addr.singleAddr.password}")
 private String password;
 @Value("${redisson.addr.singleAddr.database}")
 private int database;
 @Bean
 public RedissonClient redissonClient(){
 Config config = new Config(); 
config.useSingleServer().setAddress(host).setPassword(password).se
tDatabase(database);
 return Redisson.create(config);
 }
}

7.4.2 Redisson集群连接

  • application.yml

redisson :
addr :
cluster :
hosts : redis : //47.96.11.185 : 6370,...,redis : //47.96.11.185 : 6373
password : 12345678
  • RedissonConfig——RedissonClient对象
@Configuration
public class RedissonConfig {
 @Value("${redisson.addr.cluster.hosts}")
 private String hosts;
 @Value("${redisson.addr.cluster.password}")
 private String password;
 /**
 * 集群模式
 * @return
 */
 @Bean
 public RedissonClient redissonClient(){
 Config config = new Config();
 config.useClusterServers().addNodeAddress(hosts.split("
[,]"))
 .setPassword(password)
 .setScanInterval(2000)
 .setMasterConnectionPoolSize(10000)
 .setSlaveConnectionPoolSize(10000);
 return Redisson.create(config);
 }
}

7.4.3 Redisson主从连接

  • application.yml

redisson :
addr :
masterAndSlave :
masterhost : redis : //47.96.11.185 : 6370
slavehosts :
redis : //47.96.11.185 : 6371,redis : //47.96.11.185 : 6372
password : 12345678
database : 0
  • RedissonConfig --- RedissonClient
@Configuration
public class RedissonConfig3 {
 @Value("${redisson.addr.masterAndSlave.masterhost}")
 private String masterhost;
 @Value("${redisson.addr.masterAndSlave.slavehosts}")
 private String slavehosts;
 @Value("${redisson.addr.masterAndSlave.password}")
 private String password;
 @Value("${redisson.addr.masterAndSlave.database}")
 private int database;
 /**
 * 主从模式
 * @return
 */
 @Bean
 public RedissonClient redissonClient(){
 Config config = new Config();
 config.useMasterSlaveServers()
 .setMasterAddress(masterhost)
 .addSlaveAddress(slavehosts.split("[,]"))
 .setPassword(password)
 .setDatabase(database)
 .setMasterConnectionPoolSize(10000)
 .setSlaveConnectionPoolSize(10000);
 return Redisson.create(config);
 }
}

7.5 分布式锁总结

7.5.1 分布式锁特点

1 、互斥性
和我们本地锁⼀样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
2 、可重⼊性
同⼀个节点上的同⼀个线程如果获取了锁之后那么也可以再次获取这个锁。
3 、锁超时
和本地锁⼀样⽀持锁超时,加锁成功之后设置超时时间,以防⽌线程故障导致不释放锁,防
⽌死锁。
4 、⾼效,⾼可⽤
加锁和解锁需要⾼效,同时也需要保证⾼可⽤防⽌分布式锁失效,可以增加降级。
redission 是基于 redis 的, redis 的故障就会导致 redission 锁的故障,因此 redission ⽀持单节
redis reids 主从、 reids 集群
5 、⽀持阻塞和⾮阻塞
ReentrantLock ⼀样⽀持 lock trylock 以及 tryLock(long timeOut)

7.5.2 锁的分类

1 、乐观锁与悲观锁
乐观锁
悲观锁
2 、可重⼊锁和⾮可重⼊锁
可重⼊锁:当在⼀个线程中第⼀次成功获取锁之后,在此线程中就可以再次获取
⾮可重⼊锁
3 、公平锁和⾮公平锁
公平锁:按照线程的先后顺序获取锁
⾮公平锁:多个线程随机获取锁
4 、阻塞锁和⾮阻塞锁
阻塞锁:不断尝试获取锁,直到获取到锁为⽌
⾮阻塞锁:如果获取不到锁就放弃,但可以⽀持在⼀定时间段内的重试
—— 在⼀段时间内如果没有获取到锁就放弃

7.5.3 Redission的使⽤

1 、获取锁 —— 公平锁和⾮公平锁
// 获取公平锁
RLock lock = redissonClient . getFairLock ( skuId );
// 获取⾮公平锁
RLock lock = redissonClient . getLock ( skuId );
2 、加锁 —— 阻塞锁和⾮阻塞锁
// 阻塞锁(如果加锁成功之后,超时时间为 30s ;加锁成功开启看⻔狗,剩 5s 延⻓过期时间)
lock . lock ();
// 阻塞锁(如果加锁成功之后,设置⾃定义 20s 的超时时间)
lock . lock ( 20 , TimeUnit . SECONDS );
// ⾮阻塞锁(设置等待时间为 3s ;如果加锁成功默认超时间为 30s
boolean b = lock . tryLock ( 3 , TimeUnit . SECONDS );
// ⾮阻塞锁(设置等待时间为 3s ;如果加锁成功设置⾃定义超时间为 20s
boolean b = lock . tryLock ( 3 , 20 , TimeUnit . SECONDS );
3 、释放锁
lock . unlock ();
4 、应⽤示例
// 公平⾮阻塞锁
RLock lock = redissonClient . getFairLock ( skuId );
boolean b = lock . tryLock ( 3 , 20 , TimeUnit . SECONDS );
8. 分布式锁释放锁代码优化
  • 伪代码

HashMap map = null ;
加锁
try {
if ( isLock ){
校验库存
if ( 库存充⾜ ){
保存订单
保存快照
修改库存
删除购物⻋
map = new HashMap ();
...
}
}
} catch ( Exception e ){
e . printStackTrace ();
} finally {
释放锁
}
return map ;
  • Java代码实现
/**
* 保存订单业务
*/
@Transactional
public Map<String, String> addOrder(String cids, Orders order)
throws SQLException {
 logger.info("add order begin...");
 Map<String, String> map = null;
 //1.校验库存:根据cids查询当前订单中关联的购物⻋记录详情(包括库存)
 String[] arr = cids.split(",");
 List<Integer> cidsList = new ArrayList<>();
 for (int i = 0; i < arr.length; i++) {
 cidsList.add(Integer.parseInt(arr[i]));
 }
 //根据⽤户在购物⻋列表中选择的购物⻋记录的id 查询到对应的购物⻋记录
 List<ShoppingCartVO> list =
shoppingCartMapper.selectShopcartByCids(cidsList);
 //加锁
 boolean isLock = true;
 String[] skuIds = new String[list.size()]; 
 Map<String, RLock> locks = new HashMap<>(); //⽤于存放当前订单的锁
 for (int i = 0; i < list.size(); i++) {
 String skuId = list.get(i).getSkuId();
 boolean b = false;
 try {
 RLock lock = redissonClient.getLock(skuId);
 b = lock.tryLock(10, 3, TimeUnit.SECONDS);
 if (b) {
 skuIds[i] = skuId;
 locks.put(skuId, lock);
 }
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 isLock = isLock & b;
 }
 //如果isLock为true,表示“加锁”成功
 try {
 if (isLock){
 //1.检验库存
 boolean f = true;
 String untitled = "";
 list =
shoppingCartMapper.selectShopcartByCids(cidsList);
 for (ShoppingCartVO sc : list) {
 if (Integer.parseInt(sc.getCartNum()) >
sc.getSkuStock()) {
 f = false;
 }
 untitled = untitled + sc.getProductName() + ",";
 }
 if (f) {
 //如果库存充⾜,则进⾏下订单操作
 logger.info("product stock is OK...");
 //2.保存订单
 order.setUntitled(untitled);
 order.setCreateTime(new Date());
 order.setStatus("1");
 //⽣成订单编号
 String orderId =
UUID.randomUUID().toString().replace("-", "");
 order.setOrderId(orderId);
 int i = ordersMapper.insert(order);
 //3.⽣成商品快照
 for (ShoppingCartVO sc : list) {
 int cnum = Integer.parseInt(sc.getCartNum());
 String itemId = System.currentTimeMillis() +
"" + (new Random().nextInt(89999) + 10000);
 OrderItem orderItem = new OrderItem(itemId,
orderId, sc.getProductId(), sc.getProductName(),
sc.getProductImg(), sc.getSkuId(), sc.getSkuName(), new
BigDecimal(sc.getSellPrice()), cnum, new
BigDecimal(sc.getSellPrice() * cnum), new Date(), new Date(), 0);
 orderItemMapper.insert(orderItem);
 //增加商品销量
 }
 //4.扣减库存:根据套餐ID修改套餐库存量
 for (ShoppingCartVO sc : list) {
 String skuId = sc.getSkuId();
 int newStock = sc.getSkuStock() -
Integer.parseInt(sc.getCartNum());
 ProductSku productSku = new ProductSku();
 productSku.setSkuId(skuId);
 productSku.setStock(newStock);
 
productSkuMapper.updateByPrimaryKeySelective(productSku);
 //5.删除购物⻋:当购物⻋中的记录购买成功之后,购物⻋中对应
做删除操作
 for (int cid : cidsList) {
 shoppingCartMapper.deleteByPrimaryKey(cid);
 }
 map = new HashMap<>();
 logger.info("add order finished...");
 map.put("orderId", orderId);
 map.put("productNames", untitled);
 }
 }
 }catch (Exception e){
 e.printStackTrace();
 }finally {
 //释放锁
 for (int i = 0; i < skuIds.length; i++) {
 String skuId = skuIds[i];
 if (skuId != null && !"".equals(skuId)) {
 locks.get(skuId).unlock();
 System.out.println("-----------------------
unlock");
 }
 }
 }
 return map; }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

分布式锁-Redisson 的相关文章

  • 使用 objectGUID 进行查询 - Spring LDAP 模板

    我正在尝试获取 存储并依次使用 objectGUID 来查询 Active Directory 为了获取用户属性我正在使用以下 public static class MyDnKeyValueAttMapper implements Att
  • 抽象超类的默认接口方法

    可以说我有以下结构 abstract class A abstract boolean foo interface B default boolean foo return doBlah class C extends A implemen
  • 按下按钮时清除编辑文本焦点并隐藏键盘

    我正在制作一个带有编辑文本和按钮的应用程序 当我在 edittext 中输入内容然后单击按钮时 我希望键盘和焦点在 edittext 上消失 但我似乎无法做到这一点 我在 XML 中插入了这两行代码 android focusable tr
  • 如何将列表转换为地图?

    最近我和一位同事讨论了转换的最佳方式是什么List to Map在 Java 中 这样做是否有任何具体的好处 我想知道最佳的转换方法 如果有人可以指导我 我将非常感激 这是个好方法吗 List
  • 使用 xuggle 将 mp3 转换为 wav 出现异常

    我正在尝试将 mp3 转换为 wav 代码在这里 String mp3 F work pic2talk38512 mp3 String wav F work pic2talk38512 wav TranscodeAudioAndVideo
  • 根据哈希值确认文件内容

    我需要 检查完整性 content文件数量 文件将写入 CD DVD 可能会被复制多次 这个想法是识别正确复制的副本 在从 Nero 等中删除它们之后 我对此很陌生 但快速搜索表明Arrays hashCode byte http down
  • 总结二维数组

    鉴于我当前的程序 我希望它在用户输入所有值后计算每列和每行的总和 我当前的代码似乎只是将数组的值加倍 这不是我想要做的 例如 如果用户输入具有以下值 1 2 3 2 3 4 3 4 5 的 3x3 矩阵 则看起来就像我在下面的程序中对其进行
  • 确定代码是否在 App Engine 运行时 (Java) 上运行

    如何确定某些代码 Serv let 或简单的类 是否正在 Google App Engine 运行时 Java 上运行 以便决定是否使用 App Engine 的特定库 是否有一些可靠的运行时环境 ID 您可以检查com google ap
  • 将 EditText 聚焦在设备上运行的 PopupWindow 中时出现异常

    我正在为 Android 开发一个弹出窗口 它正在工作 我在上面添加了一个 EditText 和一个按钮 当在 ADV 上运行时 它可以正常工作 而在设备上运行时 当我专注于 EditText 时 这会抛出一个奇怪的异常 android v
  • 使用 Java 通过 HTTP 下载未知长度的文件

    我想用java下载一个HTTP查询 但是我下载的文件在下载时有一个未确定的长度 我认为这将是相当标准的 所以我搜索并找到了它的代码片段 http snipplr com view 33805 http snipplr com view 33
  • 使用 include 进行 JAXB 剧集编译不起作用

    我有 2 个模式 A B 我在 B 中重用了一些 A 元素 我不使用命名空间 我在用着
  • java Runtime.getRunTime().exec 和通配符?

    我正在尝试使用删除垃圾文件 Process p Runtime getRuntime exec 只要我不使用通配符 它 就可以正常工作 即 Process p Runtime getRuntime exec bin rm f specifi
  • 处理照片上传的最佳方式是什么?

    我正在为一个家庭成员的婚礼制作一个网站 他们要求的一个功能是一个照片部分 所有客人都可以在婚礼结束后前往并上传他们的照片 我说这是一个很棒的想法 然后我就去实现它 那么只有一个问题 物流 上传速度很慢 现代相机拍摄的照片很大 2 5 兆 我
  • Java .split("|") 不工作

    我刚刚遇到了一个问题分割法 http docs oracle com javase 6 docs api java lang String html split 28java lang String 29for 字符串不适用于字符 作为一个
  • 将变量从 jenkins 传递到 testng.xml

    我想根据从詹金斯传递的变量运行测试用例 例如 选择您要运行的测试用例 测试用例一 测试用例二 在 pom xml maven 中
  • 如何告诉 IntelliJ 使用 Java 1.6 JDK 启动 gradle?

    一个简单的问题 即使经过几个小时的尝试和搜索 我也无法弄清楚 我安装了 Java 6 和 7 如何告诉 IntelliJ 使用 JDK 版本 1 6 启动 Gradle 构建 无论我做什么 IntelliJ 都会以以下方式开始我的 grad
  • 如何配置嵌入式 MongoDB 以在 Spring Boot 应用程序中进行集成测试?

    我有一个相当简单的 Spring Boot 应用程序 它公开一个小型 REST API 并从 MongoDB 实例检索数据 对 MongoDB 实例的查询通过基于 Spring Data 的存储库 下面的一些关键代码 Main applic
  • 如何隐藏或删除 Android HoneyComb 中的状态栏?

    如何隐藏或删除 Android HoneyComb 中的状态栏 每次运行应用程序时 我都会发现某些内容必须被状态栏覆盖 我尝试改变AndroidManifest xml 但没有任何改变 你不知道 它被认为是永久的屏幕装饰 就像电容式主页 菜
  • Java,如何管理线程读取socket(websocket)?

    我有一个 WebSocket 服务器 我的服务器创建一个新线程来处理新连接 该线程一直处于活动状态 直到 websocket 中断 我的问题 对于 1 000 000 个连接 我需要 1 000 000 个线程 我如何通过一个线程处理多个
  • 使用 Hibernate 防止无限循环数据检索

    我想知道 想象一个场景 例如 POJO public class User private String userName private String name private String surname private List

随机推荐

  • Python点云处理(二)点云数据可视化

    目录 0 简述 1 matplotlib 1 1 安装 1 2 点云可视化 2 Mayavi 3 Open3D 4 Vispy 5 VTK 6 结语 0 简述 点云可视化是数据分析 数据展示及程序集成的基础性功能 Python提供了许多强大
  • Error:Abnormal build process termination:

    Error Abnormal build process termination C Program Files Java jdk1 8 0 121 bin java Xmx700m Djava awt headless true Djav
  • 分布式计算框架Spark集群实战

    一 Spark整体架构 1 Spark集群架构 从集群部署的角度看 Spark集群由集群管理器 Cluster Manager 工作节点 Worker 执行器 Executor 驱动器 Driver 应用程序 Application 等部分
  • 1.8,strerror和perror

    这个例子主要是报错 其实 我不大注重报错的差异 只要知道大概在哪里出错就行了 先抄代码 运行 ok
  • clone()

    深复制 浅复制
  • css选择器

    css选择器 1 选择器的作用 找到特定的HTML标签元素 选择所需要的标签 2 基础选择器 2 1标签选择器 作用 可以把一类标签全部选择出来 比如div span标签 快速的为页面中同类型的标签统一化 但是不能设计差异化的样式 div
  • WIN10应用程序无法正常启动(0xc0000142)。请单击‘确认’关闭应用程序

    在网上找了好久解决方法 有说用命令行for 1 in windir system32 dll do regsvr32 exe s 1 来重新注册系统组件 有说删除 appdata microsoft templates 这个目录下的文件 还
  • 软件产品license的简单实现java

    目录 软件License简介 License控制内容 实现方案 代码示例讲解 注意事项 源码 软件License简介 我们在使用一些需要购买版权的软件产品时 或者我们做的商业软件需要进行售卖 为了收取费用 一般需要一个软件使用许可证 然后输
  • 基于Arduino开发板和20×4 I2C LCD显示屏制作一款实时时钟

    当我们在制作一个有趣的硬件项目时 可能会需要使用到一款显示屏 但选择显示屏的尺寸和控制它所需的引脚都令人困惑 在上一篇文章中 我们介绍了0 96寸I2C OLED显示屏 在本篇文章中 我们将介绍使用I2C 20 4字符显示屏 所需的零件 本
  • You are running the esm-bundler build of vue-i18n. It is recommended to configure your bundler to ex

    vue3项目启动之后 会提示如下警告 You are running the esm bundler build of vue i18n It is recommended to configure your bundler to expl
  • 网络编程之channel

    介绍 Channel 是一个对象 可以通过它读取和写入数据 拿 NIO 与原来的 I O 做个比较 通道就像是流 所有数据都通过 Buffer 对象来处理 您永远不会将字节直接写入通道中 相反 您是将数据写入包含一个或者多个字节的缓冲区 同
  • 官网下载Eclipse历史版本

    官网下载Eclipse历史版本 Eclipse官网 downloads路径 https www eclipse org downloads 点击 Download Packages 点击 Download Packages 选择你想要的版本
  • Idea 断点调试PHP

    老实说 我尝试过xdebug 但是说实话 没一次成功过 看来我还是 经验不足 简单的方法 前期工作需要装上xdebug 在php ini 末尾加上 XDebug 这是xdebug的dll 需要到官网上下载 需要注意区分自己的PHP是线程安全
  • Jordan Lecture Note-12: Kernel典型相关分析(Kernel Canonical Correlation Analysis, KCCA).

    Jordan Lecture Note 12 Kernel典型相关分析 Kernel Canonical Correlation Analysis KCCA Kernel典型相关分析 一 KCCA 同样 我们可以引入Kernel函数 通过非
  • 华为培训 05 PON EPON GPON

    学习目标 PON架构 EPON主要技术 GPON主要技术 EPON 基于以太网方式的无源光网络 GPON 千兆无源光网络 1 PON网络加架构
  • 2022电大国家开放大学网上形考任务-客户关系管理非免费(非答案)

    客户关系管理形考作业一答案 试题 1 1 不是常用的市场营销组合理论 A 4C 理论 B 4P 理论 C 4A 理论 D 4S 理论 试题 2 2 企业实施客户关系管理的作用主要体现在提升企业竞争优势 提高客户满意度 以及提升企业销售业绩
  • 【Verilog 常见设计】(0)二进制码和格雷码互转 Verilog 实现

    目录 格雷码介绍 转化原理 Verilog 实现 testbench 测试代码 仿真波形 格雷码介绍 在一组数的编码中 若任意两个相邻的代码只有一位二进制数不同 则称这种编码为格雷码 Gray Code 另外由于最大数与最小数之间也仅一位数
  • uniapp原生插件-YL视频播放器

    YL视频播放器uniapp插件市场地址 https ext dcloud net cn plugin id 9569 简介 YL视频播放器是一款适用于安卓端的高性能原生插件 ios暂不支持 支持3核心切换 exo ijk 安卓原生 支持点播
  • 【ChatGPT】500个ChatGPT/GPT-4 Prompt技巧

    文章目录 一 前言 二 什么是Prompt Engineering 三 ChatGPT GPT 4 Prompt Engineering使用技巧 一 前言 随着 GPT 4 和 DALL E等大型 强大的生成式 AI 模型变得更好 更可用
  • 分布式锁-Redisson

    目录 1 分布式并发问题 2 如何解决分布式并发问题呢 3 使 Redis实现分布式锁 代码实现 4 解决因线程异常导致 法释放锁的问题 5 解决因t1过期释放t2锁的问题 6 看 狗机制 7 分布式锁框架 Redisson 7 1 Red