阶段十-分布式锁

5.1 节 为什么要使用分布式锁

锁是多线程代码中的概念,只有当多任务访问同一个互斥共享资源时才需要。如下图:

在我们进行单机应用开发,涉及并发同步的时候,我们往往采用synchronized或者lock的方式来解决多线程间的代码同步问题,这时多线程的运行都是在同一个JVm之下。但当我们的应用是分布式集群工作的情况下,属于JVM下的工作环境,JVM之间已经无法通过多线程的锁来解决同步问题。需要更加高级的锁机制,来处理跨机器的进程的数据之间的同步问题——这就是分布式锁

5.2 节 分布式锁的几种方式

分布式锁的核心思路是记住外力解决多JVM进程操作共享数据时需要使用互斥锁问题。常见的方式有:

  1. mysql数据库

  2. zookeeper

  3. redis

5.3 节 搭建测试分布式锁的环境

【1】创建工程distributed-lock-study ,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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.6</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>top.psjj</groupId><artifactId>distributed-lock-study</artifactId><version>0.0.1-SNAPSHOT</version><packaging>pom</packaging><name>distributed-lock-study</name><description>distributed-lock-study</description><modules><module>moduleB</module><module>moduleA</module></modules><properties><java.version>8</java.version><mysql-connection.version>8.0.26</mysql-connection.version><druid.version>1.2.1</druid.version><mybatis-plus.version>3.5.2</mybatis-plus.version></properties><dependencyManagement><dependencies><!-- Dubbo 依赖 --><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>${dubbo.starter}</version></dependency><!-- zookeeper 注册中心 依赖 --><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-registry-zookeeper</artifactId><version>${dubbo.registry.zookeeper}</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql-connection.version}</version><scope>runtime</scope></dependency><!--druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><!--mybatis-plus依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus.version}</version></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--mybatis-plus依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!--druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!--redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--common-pool--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

【2】创建moduleA和moduleB两个模块

【3】在两个模块中编写application.yml

spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/lock_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: rootredis:host: 192.168.9.132port: 6379lettuce:pool:max-active: 8max-idle: 8min-idle: 0max-wait: 100ms
server:port: 1111
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/lock_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456redis:host: 192.168.184.200port: 6379lettuce:pool:max-active: 8max-idle: 8min-idle: 0max-wait: 100ms
server:port: 2222

【4】编写启动类

package com.sh;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @Auther: 世豪讲java* @Date: 2024/1/14 - 01 - 14 - 20:55* @Decsription: com.sh* @version: 1.0*/
@SpringBootApplication
@MapperScan("com.sh.ma.mapper")
public class ModuleAApplication {public static void main(String[] args) {SpringApplication.run(ModuleAApplication.class,args);}
}
package com.sh;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @Auther: 世豪讲java* @Date: 2024/1/14 - 01 - 14 - 20:55* @Decsription: com.sh* @version: 1.0*/
@SpringBootApplication
@MapperScan("com.sh.ma.mapper")
public class ModuleBApplication {public static void main(String[] args) {SpringApplication.run(ModuleBApplication.class,args);}
}

【5】准备数据库local_db,并出入下张表

/*Navicat Premium Data TransferSource Server         : connSource Server Type    : MySQLSource Server Version : 80026Source Host           : 127.0.0.1:3306Source Schema         : lock_dbTarget Server Type    : MySQLTarget Server Version : 80026File Encoding         : 65001Date: 12/01/2024 15:43:48
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for t_goods
-- ----------------------------
DROP TABLE IF EXISTS `t_goods`;
CREATE TABLE `t_goods`  (`id` int(0) NOT NULL AUTO_INCREMENT COMMENT '主键',`goods` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,`count` int(0) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of t_goods
-- ----------------------------
INSERT INTO `t_goods` VALUES (1, '手机', -1);
INSERT INTO `t_goods` VALUES (2, '笔记本', 100);SET FOREIGN_KEY_CHECKS = 1;

【6】编写po 、mapper 、service 、controller,两个模块代码完全一样

package com.sh.ma.po;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;/*** @Auther: 世豪讲java* @Date: 2024/1/14 - 01 - 14 - 21:01* @Decsription: com.sh.ma.po* @version: 1.0*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_goods")
public class Goods implements Serializable {@TableId(type = IdType.AUTO)private Integer id;private String goods;private Integer count;
}
package com.sh.ma.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sh.ma.po.Goods;
import org.apache.ibatis.annotations.Update;/*** @Auther: 世豪讲java* @Date: 2024/1/14 - 01 - 14 - 21:01* @Decsription: com.sh.ma.service* @version: 1.0*/
public interface GoodsMapper extends BaseMapper<Goods> {@Update("update t_goods set count = count-1 where id=#{id}")void subCount(Integer id);
}
package com.sh.ma.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.sh.ma.po.Goods;/*** @Auther: 世豪讲java* @Date: 2024/1/14 - 01 - 14 - 21:02* @Decsription: com.sh.ma.service* @version: 1.0*/
public interface GoodsService extends IService<Goods> {void updateGoodsCount(Integer id);
}
package com.sh.ma.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sh.ma.mapper.GoodsMapper;
import com.sh.ma.po.Goods;
import com.sh.ma.service.GoodsService;
import org.springframework.stereotype.Service;/*** @Auther: 世豪讲java* @Date: 2024/1/14 - 01 - 14 - 21:02* @Decsription: com.sh.ma.service.impl* @version: 1.0*/
@Service
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods> implements GoodsService {@Overridepublic void updateGoodsCount(Integer id) {Goods goods = this.baseMapper.selectById(id);Integer count = goods.getCount();if(count>0){try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}this.baseMapper.subCount(id);}}
}
package com.sh.ma.controller;import com.sh.ma.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Auther: 世豪讲java* @Date: 2024/1/14 - 01 - 14 - 21:03* @Decsription: com.sh.ma.controller* @version: 1.0*/
@RestController
@RequestMapping("/goods")
public class GoodsController {@Autowiredprivate GoodsService goodsService;@RequestMapping("/update")public String updateGoodsCount(Integer id){goodsService.updateGoodsCount(id);return "ok";}
}

【最后】测试

 

出现了超卖问题

5.4 节 基于Mysql实现的分布式锁

5.4.1 mysql实现分布式锁原理

基于分布式锁的实现,首先肯定是想单独分离出一台mysql数据库,所有服务要想操作文件(共享资源),那么必须先在mysql数据库中插入一个标志,插入标志的服务就持有了锁,并对文件进行操作,操作完成后,主动删除标志进行锁释放,其与服务会一直查询数据库,看是否标志有被占用,直到没有标志占用时自己才能写入标志获取锁。

  但是这样有这么一个问题,如果服务(jvm1)宕机或者卡顿了,会一直持有锁未释放,这样就造成了死锁,因此就需要有一个监视锁进程时刻监视锁的状态,如果超过一定时间未释放就要进行主动清理锁标记,然后供其与服务继续获取锁。

  如果监视锁字段进程和jvm1同时挂掉,依旧不能解决死锁问题,于是又增加一个监视锁字段进程,这样一个进程挂掉,还有另一个监视锁字段进程可以对锁进行管理。这样又诞生一个新的问题,两个监视进程必须进行同步,否则对于过期的情况管理存在不一致问题。

  因此存在以下问题,并且方案变得很复杂:

  1. 监视锁字段进程对于锁的监视时间周期过短,仍旧会造成多售(jvm1还没处理完其持有的锁就被主动销毁,造成多个服务同时持有锁进行操作)。

  2. 监视锁字段进程对于锁的监视时间周期过长,会造成整个服务卡顿过长,吞吐低下。

  3. 监视锁字段进程间的同步问题。

  4. 当一个jvm持有锁的时候,其余服务会一直访问数据库查看锁,会造成其余jvm的资源浪费。

5.4.2 基于update实现分布锁(特殊情况)

举一个电商系统,商品数量超卖问题:

如果库存递减使用下面的伪代码会产生超卖现象,超卖现象的本质都是多线程数据同步问题,因为时分布式系统,不能单纯的加锁处理,如果处理下面的逻辑需要使用分布式锁,关于分布式锁,因为我们的代码直接执行语句,有数据库行级锁,不会产生超卖问题

//mysql行锁解决分布锁问题
try {Thread.sleep(5000);
} catch (InterruptedException e) {throw new RuntimeException(e);
}
this.baseMapper.subCount2(goods);
@Update("update t_goods set count=count-1 where id=#{id} and count>0")
void subCount2(Goods goods);

              

5.4 节 基于Redis实现分布式锁

5.4.1 Redis实现分布式锁优点

(1)Redis有很高的性能; (2)Redis命令对此支持较好,实现起来比较方便

 

5.4.2 命令介绍

(1)SETNX

SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
​

(2)expire

expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
​

(3)delete

delete key:删除key

在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

5.4.3 Redis实现分布式锁原理

(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。

(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。

(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

5.4.4 Redisson分布式锁使用

1)引入依赖

<!-- redis 使用-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.14.0</version>
</dependency>

2)配置文件

spring:redis:host: 192.168.9.132port: 6379

关键代码

//创建锁
RLock lock = redissonClient.getLock("goods-" + id);
//加锁
try {lock.lock();Goods goods = this.baseMapper.selectById(id);Integer count = goods.getCount();if(count>0){try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}this.baseMapper.subCount(id);}
} catch (RuntimeException e) {throw new RuntimeException("超卖了");
} finally {//释放锁lock.unlock();
}

5.4.5 总结

可以使用缓存来代替数据库来实现分布式锁,这个可以提供更好的性能,同时,很多缓存服务都是集群部署的,可以避免单点问题。并且很多缓存服务都提供了可以用来实现分布式锁的方法,比如Tair的put方法,redis的setnx方法等。并且,这些缓存服务也都提供了对数据的过期自动删除的支持,可以直接设置超时时间来控制锁的释放。

使用缓存实现分布式锁的优点

  • 性能好,实现起来较为方便。

使用缓存实现分布式锁的缺点

  • 通过超时时间来控制锁的失效时间并不是十分的靠谱。

后面项目中采用的Redisson,就是采用redis实现分布式锁

Redisson是Redis官方推荐的Java版的Redis客户端。它提供的功能非常多,也非常强大,此处我们只用它的分布式锁功能。

5.5 节 基于Zookeeper实现的分布式锁

基于以上两种实现方式,有了基于zookeeper实现分布式锁的方案。由于zookeeper有以下特点:

  1. 维护了一个有层次的数据节点,类似文件系统。

  2. 有以下数据节点:临时节点、持久节点、临时有序节点(分布式锁实现基于的数据节点)、持久有序节点。

  3. zookeeper可以和client客户端通过心跳的机制保持长连接,如果客户端链接zookeeper创建了一个临时节点,那么这个客户端与zookeeper断开连接后会自动删除。

  4. zookeeper的节点上可以注册上用户事件(自定义),如果节点数据删除等事件都可以触发自定义事件。

  5. zookeeper保持了统一视图,各服务对于状态信息获取满足一致性。

Zookeeper的每一个节点,都是一个天然的顺序发号器。

  在每一个节点下面创建子节点时,只要选择的创建类型是有序(EPHEMERAL_SEQUENTIAL 临时有序或者PERSISTENT_SEQUENTIAL 永久有序)类型,那么,新的子节点后面,会加上一个次序编号。这个次序编号,是上一个生成的次序编号加一

  比如,创建一个用于发号的节点“/test/lock”,然后以他为父亲节点,可以在这个父节点下面创建相同前缀的子节点,假定相同的前缀为“/test/lock/seq-”,在创建子节点时,同时指明是有序类型。如果是第一个创建的子节点,那么生成的子节点为/test/lock/seq-0000000000,下一个节点则为/test/lock/seq-0000000001,依次类推,等等。

5.5.1 Zookeeper实现分布式锁原理

 

  1. 创建一个目录mylock;

  2. 线程A想获取锁就在mylock目录下创建临时顺序节点;

  3. 获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;

  4. 线程B获取所有节点,判断自己不是最小节点,设置监听比自己小的节点;

  5. 线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

来看下Zookeeper能不能解决前面提到的问题。

  • 锁无法释放?使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。

  • 非阻塞锁?使用Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。

  • 不可重入?使用Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。

  • 单点问题?使用Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。 

 

5.5.2 基于Curator 客户端实现分布式锁

Curator Framework提供了简化使用zookeeper更高级的API接口。它包涵很多优秀的特性,主要包括以下三点

  1. 自动连接管理:自动处理zookeeper的连接和重试存在一些潜在的问题;可以watch NodeDataChanged event和获取updateServerList;Watches可以自动被Cruator recipes删除;

  2. 更干净的API:简化raw zookeeper方法,事件等;提供现代流式API接口

  3. Recipe实现:leader选举,分布式锁,path缓存,和watcher,分布式队列等。

单独使用依赖

<dependency><groupId>org.apache.curator</groupId><artifactId>curator-framework</artifactId><version>4.0.1</version>
</dependency>
<dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>4.0.1</version>
</dependency>

代码:

package com.sh.ma.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sh.ma.mapper.GoodsMapper;
import com.sh.ma.po.Goods;
import com.sh.ma.service.GoodsService;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;/*** @Auther: 世豪讲java* @Date: 2024/1/14 - 01 - 14 - 21:02* @Decsription: com.sh.ma.service.impl* @version: 1.0*/
@Service
public class GoodsServiceImpl extends ServiceImpl<GoodsMapper, Goods> implements GoodsService {@Autowiredprivate RedissonClient redissonClient;@Overridepublic void updateGoodsCount(Integer id) {
//        Goods goods = this.baseMapper.selectById(id);
//        Integer count = goods.getCount();
//        if(count>0){
//            try {
//                Thread.sleep(5000);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }//this.baseMapper.subCount(id);//this.baseMapper.subCount2(goods);//创建锁
//        RLock lock = redissonClient.getLock("goods-" + id);
//        try {
//            //加锁
//            lock.lock();
//            Goods goods = this.baseMapper.selectById(id);
//            Integer count = goods.getCount();
//            if (count > 0) {
//                try {
//                    Thread.sleep(5000);
//                } catch (InterruptedException e) {
//                    throw new RuntimeException(e);
//                }
//                this.baseMapper.subCount(id);
//            }
//        } catch (RuntimeException e) {
//            throw new RuntimeException(e);
//        } finally {
//            //释放锁
//            lock.unlock();
//        }//zookeeper 分布式锁解决超卖问题//1.创建zookeeper连接RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);CuratorFramework client= CuratorFrameworkFactory.newClient("192.168.9.132:2181", retryPolicy);client.start();//创建分布式锁InterProcessMutex interProcessMutex = new InterProcessMutex(client,"/ordersettinglock");try {interProcessMutex.acquire();Goods goods = this.baseMapper.selectById(id);Integer count = goods.getCount();if(count>0){try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}this.baseMapper.subCount(id);}} catch (Exception e) {throw new RuntimeException("超卖了");}finally {//释放锁try {interProcessMutex.release();} catch (Exception e) {throw new RuntimeException(e);}}}
}

5.5.3 总结

使用Zookeeper实现分布式锁的优点

  • 有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题。实现起来较为简单。

使用Zookeeper实现分布式锁的缺点

  • 性能上不如使用缓存实现分布式锁。 需要对ZK的原理有所了解,比较复杂

5.6 节 分布式锁总结

上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。

从理解的难易程度角度(从低到高)

  • 数据库 > 缓存 > Zookeeper

从实现的复杂性角度(从低到高)

  • Zookeeper >= 缓存 > 数据库

从性能角度(从高到低)

  • 缓存 > Zookeeper >= 数据库

从可靠性角度(从高到低)

  • Zookeeper > 缓存 > 数据库    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/622720.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

远程登陆利器 ssh

文章目录 远程登陆利器 ssh登陆远程服务器指定用户名多数情况的登陆方式查看服务器的时间指定端口更多信息 远程登陆利器 ssh ssh命令是openssh套件中的客户端连接工具&#xff0c;使用加密协议实现安全的远程登录服务器&#xff0c;实现对服务器的远程管理。 官方定义为&…

ZZULIOJ 1110: 最近共同祖先(函数专题)

题目描述 如上图所示&#xff0c;由正整数1, 2, 3, ...组成了一棵无限大的二叉树。从某一个结点到根结 点&#xff08;编号是1 的结点&#xff09;都有一条唯一的路径&#xff0c;比如从10 到根结点的路径是(10, 5, 2, 1)&#xff0c; 从4 到根结点的路径是(4, 2, 1)&#xff0…

网络地图服务(WMS)详解

文章目录 1.概述2.GetCapabilities3.GetMap4.GetFeatureInfo 阅读本文之前可参考前文&#xff1a;《地图服务器GeoServer的安装与配置》与《GeoServer发布地图服务&#xff08;WMS、WFS&#xff09;》。 1.概述 经过前文的介绍&#xff0c;相信我们对WMS/WFS服务已经有了一个非…

Modbus协议学习第二篇之Modbus poll slave仿真软件初体验

软件准备 学习Modbus离不开硬件&#xff0c;好在我们可以通过仿真软件来模拟硬件&#xff0c;本篇博客就来简单介绍一下Modbus仿真软件的最基础使用方法&#xff0c;需要用到的3款仿真软件如下&#xff1a; Modbus Poll 64位 / Modbus Poll 32位&#xff08;根据自己机器位数选…

C++——map和set的基本使用

目录 一&#xff0c;关联式容器 二&#xff0c;键值对 三&#xff0c;set的使用 3.1 set介绍 3.2 set的插入和删除 3.3 set的pair 3.4 multiset 四&#xff0c;map的使用 4.1 map介绍 4.2 map实现简易字典 4.3 map实现统计次数 4.4 map的[] 五&#xff0c;使用map或…

LV.13 D11 Linux驱动移植及内核深化 学习笔记

一、设备树 1.1 设备树 设备树是一种描述硬件信息的数据结构&#xff0c;Linux内核运行时可以通过设备树将硬件信息直接传递给Linux内核&#xff0c;而不再需要在Linux内核中包含大量的冗余编码 举例&#xff1a;让LED2闪烁的代码中&#xff0c;有逻辑代码和设备代码。Li…

案例121:基于微信小程序的作品集展示系统设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

【模型评估 04】A/B测试的陷阱

互联网公司中&#xff0c;A/B测试是验证新模块、新功能、新产品是否有效&#xff1b;新算法、新模型的效果是否有提升&#xff1b;新设计是否受到用户欢迎&#xff1b;新更改是否影响用户体验的主要测试方法。在机器学习领域中&#xff0c;A/B测试是验证模型最终效果的主要手段…

C语言 - 最简单,最易懂的指针、引用讲解

一、变量、地址、变量值 二、直接上代码&#xff0c;一边看上图&#xff0c;一边讲解 #include <stdio.h>struct Hello {int a;int b; };int main() {struct Hello h;h.a 10;h.b 20;struct Hello *hp;hp &h;printf("1: h的地址是%d&#xff0c;hp地址是%d \…

stm32学习笔记:USART串口通信

1、串口通信协议&#xff08;简介软硬件规则&#xff09; 全双工&#xff1a;打电话。半双工&#xff1a;对讲机。单工&#xff1a;广播 时钟&#xff1a;I2C和SPI有单独的时钟线&#xff0c;所以它们是同步的&#xff0c;接收方可以在时钟信号的指引下进行采样。串口、CAN和…

docker完成redis 三主三从

文章目录 关闭防火墙启动docker后台服务新建6个docker容器redis实例创建并运行docker容器实例 进入容器redis-node-1并为6台机器构建集群关系链接进入6381作为切入点&#xff0c;查看集群状态主从容错切换迁移案例容错切换迁移 主从扩容案例为主节点6387分配从节点6388主从缩容…

一、MOJO环境部署和安装

以Ubuntu系统为例。 安装mojo-CLI curl https://get.modular.com | MODULAR_AUTHmut_fe303dc5ca504bc4867a1db20d897fd8 sh - 安装mojo SDK modular auth mojo modular auth install mojo 查看mojo版本号 mojo --version 输入mojo指令&#xff0c;进入交互编程窗口

On the Robustness of Backdoor-based Watermarkingin Deep Neural Networks

关于深度神经网络中基于后门的数字水印的鲁棒性 ABSTRACT 在过去的几年中&#xff0c;数字水印算法已被引入&#xff0c;用于保护深度学习模型免受未经授权的重新分发。我们调查了最新深度神经网络水印方案的鲁棒性和可靠性。我们专注于基于后门的水印技术&#xff0c;并提出了…

6、C语言:输入与输出

输入输出 标准输入输出getchar&putchar函数printf函数sprintf函数格式化输入——scanf函数 文件访问文件读写 错误处理&#xff1a;stderr和exit行输入和行输出常用函数字符串操作函数字符类别测试和转换函数存储管理函数数学函数随机数发生器函数其他 标准输入输出 getch…

2024年【氧化工艺】免费试题及氧化工艺作业模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 氧化工艺免费试题根据新氧化工艺考试大纲要求&#xff0c;安全生产模拟考试一点通将氧化工艺模拟考试试题进行汇编&#xff0c;组成一套氧化工艺全真模拟考试试题&#xff0c;学员可通过氧化工艺作业模拟考试全真模拟…

洛谷 P1439 【模板】最长公共子序列【线性dp+dp模型转换】

原题链接&#xff1a;https://www.luogu.com.cn/problem/P1439 题目描述 给出 1,2,…,n 的两个排列 P1​ 和 P2​ &#xff0c;求它们的最长公共子序列。 输入格式 第一行是一个数 n。 接下来两行&#xff0c;每行为 n 个数&#xff0c;为自然数 1,2,…,n 的一个排列。 输…

2023 年全国职业院校技能大赛(高职组) “云计算应用”赛项赛卷 B部分解析

2022 年全国职业院校技能大赛高职组云计算赛项试卷部分解析 【赛程名称】第一场&#xff1a;模块一 私有云、模块二 容器云【任务 1】私有云服务搭建[5 分]【题目 1】1.1.1 基础环境配置[0.2 分]【题目 2】1.1.2 Yum 源配置[0.2 分]【题目 3】1.1.3 配置无秘钥 ssh[0.2 分]【题…

Keil5如何生成反汇编文件

Keil5如何生成反汇编文件 在Keil5界面下点击选项&#xff0c;选择“User”&#xff0c;勾选“After Build/Rebuild”中“RUN #1”&#xff0c;复制fromelf --text -a -c --outputxxx.dis xxx.axf 在Linker栏中找到“Linker Control string”里最后-o后的.axf文件&#xff0c;将…

Linux:nginx设置网站https

http和https的区别 http: 80 https: 443 这种协议比http协议要安全&#xff0c;因为传输数据是经过加密的 HTTPS简介 HTTPS其实是有两部分组成&#xff1a;HTTP SSL / TLS&#xff0c;也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过…

Nginx的安装配置和使用

最近有好几个地方用到了nginx&#xff0c;但是一直还没时间记录下nginx的安装、配置和使用&#xff0c;这篇文章可以将这块内容整理出来&#xff0c;方便大家一起学习~ 安装 安装是相对简单一些的&#xff0c;直接使用yum即可。 yum install -y nginx 默认安装位置在/usr/sb…