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

分布式主键生成服务

目录

一、使用线程安全的类——AtomicInteger或者AtomicLong

二、主键生成最简单写法(不推荐) 

三、主键生成方法一:Long型id生成——雪花算法

四、主键生成方法二:流水号

(一)流水号概述

(二)添加配置

1.pom.xml

2.application.properties

3.创建实体类、Mapper,修改启动类

(三)流水号生成基础写法

1.基础代码

2.基础代码存在的问题 

3.基础代码优化 

4.使用httpd进行并发测试 

(四)流水号生成批量拿取id

五、主键生成方法三:级次编码


一、使用线程安全的类——AtomicInteger或者AtomicLong

import java.util.concurrent.atomic.AtomicInteger;public class TestService {public Integer count = 0;// 多个线程同时对同一个数字进行操作的时候,使用AtomicInteger或者AtomicLongpublic AtomicInteger atomicInteger = new AtomicInteger(0);public static void main(String[] args) {// 创建两个线程,每个线程对count进行10000次自增操作TestService testService = new TestService();Thread t1 = new Thread(() -> {for (int i = 0; i < 10000; i++) {
//                primaryService.count++;
//                primaryService.atomicInteger.incrementAndGet();testService.atomicInteger.getAndIncrement();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 10000; i++) {
//                primaryService.count++;
//                primaryService.atomicInteger.incrementAndGet();testService.atomicInteger.getAndIncrement();}});t1.start();t2.start();// 使用 t1.join() 和 t2.join() 来确保主线程// (即 main 方法所在的线程)会等待 t1 和 t2 执行完毕后再继续执行try {t1.join();t2.join();} catch (InterruptedException e) {e.printStackTrace();}
//        System.out.println("count=" + primaryService.count); // count=10671// 如果你只是关心最终的 count 值,而不是每次操作的具体返回值,那么 incrementAndGet() 和 getAndIncrement() 的结果看起来是一样的,因为它们都会正确地对 count 进行自增操作。但是,// 如果你需要依赖返回值来进行某些逻辑判断或处理,那么选择哪个方法就很重要了。System.out.println("atomicInteger=" + testService.atomicInteger); // count=10671}
}

二、主键生成最简单写法(不推荐) 

创建一个项目,添加依赖:

<parent><artifactId>spring-boot-starter-parent</artifactId><groupId>org.springframework.boot</groupId><version>2.6.11</version>
</parent>
<dependencies>    <!--注意:所有的依赖不可以随意添加,不需要的依赖不要添加,宁可少不能多--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
oject>

Service: 

@Service
public class PrimaryService {// 生成主键最简单方式private AtomicLong num = new AtomicLong(0);public Long getId() {return num.incrementAndGet();}
}

Controller: 

@RestController
public class PrimaryController {@Autowiredprivate PrimaryService primaryService;@GetMapping("/getId")public Long getId() {return primaryService.getId();}
}

存在的问题:该方法虽然能够保证多线程下的安全性,但是每次重启服务都会从1开始获取。

因此,new AtomicLong(0)的初始值不应该设置为0,可以设置为当前毫秒值。正确写法:

@Service
public class PrimaryService implements InitializingBean {// 生成主键最简单方式private AtomicLong num;// 初始化Bean对象中的属性@Overridepublic void afterPropertiesSet() throws Exception {num = new AtomicLong(System.currentTimeMillis());}public Long getId() {return num.incrementAndGet();}
}

无论重启多少次,拿到的id永远是越来越大。

        但是使用时间戳作为主键也有问题:

        当主键生成服务部署成集群时,启动主键生成服务集群,生成的时间戳是一样的,这样当user服务第一次拿取和第二次拿取的主键值很有可能一样。针对这一问题的解决方案就是:雪花算法。

三、主键生成方法一:Long型id生成——雪花算法

雪花算法的含义:用不同的,表示不同的含义。

1个字节是8位,以int类型为例,该数据类型是4个字节,所以是4*8=32位。

long类型是8个字节,就是8*8=64位

例如:

           0                    1000000 00000000 00000000 00000000 

第一位表示符号位     第2位如果是0表示男,如果是1表示女

这里,我们将时间戳转为二进制:

public static void main(String[] args) {System.out.println(Long.toBinaryString(1740485559705L));
}

结果:

1 10010101 00111101 00000110 00000101 10011001

不够64位,左边补0:

00000000 00000000 00000001 10010101 00111101 00000110 00000101 10011001

使用雪花算法分成三段:

第1位是符号位,0表示正数

第2位到第7位表示机器号,如果都占满,则为1111111,转为十进制为:127台机器

一个普通的微服务架构,最多127台机器已经足够了

剩下的56位,表示当前时间,一个 56 位时间戳(以毫秒为单位,从 Unix 纪元开始计时)最多能表示到大约 公元 3020 年 左右。如果今天是 2025 年 2 月 25 日,则从今天开始,56 位时间戳可以覆盖未来约 千年的时间范围,也足够了。

其他的雪花算法形式: 

位运算Tips:

按位或(|) :当两个相应的二进制位中至少有一个是1时,结果为1;否则为0。

例如:0101 | 0111 = 0111

按位异或(^):当两个相应的二进制位不同时,结果为1;相同则为0。

例如:0101 ^ 0111 = 0010

以下图的时间戳为例:如果机器号为1,那么先将机器号左移56位 

左移后的数据,与当前时间戳进行位运算,最终,机器号和时间戳就可以成功拼接。 

@Service
public class PrimaryService implements InitializingBean {// 生成主键最简单方式private AtomicLong baseId;// 将机器号作为配置@Value("${node.id:1}") // 表示配置文件中node.id的值,如果没有配置,则默认为1private long nodeId;// afterPropertiesSet方法初始化Bean对象中的属性@Overridepublic void afterPropertiesSet() throws Exception {long now = System.currentTimeMillis();// 机器号先左移56位,再和时间戳做或运算,就是初始id值baseId = new AtomicLong(nodeId << 56 | now);}public Long getId() {return baseId.incrementAndGet();}
}

http://localhost:8080/getId结果:72059334530093401

返回的Id不宜过大,因为前端的js的number无法容纳这么长的值,因此需要转为字符串返回给前端,否则会精度丢失。

四、主键生成方法二:流水号

(一)流水号概述

流水号示例:000001、000002、000003或者202502250001、202502250002

流水号的生成必须按顺序来,第一次生成的是000001,那么第二次生成的必须是000002,这种生成方式就不能借助时间戳了,需要借助数据库来生成。

创建一张表:

create table if not exists pk.`pk|_seed`
(type    varchar(30) not null comment '业务类型:user,contract,order'primary key,value   int         null comment '最新值',version int         null comment '版本号,做乐观锁'
);

type表示不同的业务类型

(二)添加配置

1.pom.xml

<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.29</version>
</dependency>

注意:当下载依赖的时候,依赖树中存在依赖,但是左侧的依赖没有,重启IDEA即可,这是IDEA的bug。 

2.application.properties

server.port=8080
#数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.157.102:3306/pk?useSSL=false
spring.datasource.username=root
spring.datasource.password=123456

3.创建实体类、Mapper,修改启动类

@TableName("pk_seed")
public class PkSeed {@TableId // 标识该字段是主键private String type;private Integer value;private Integer version;public String getType() {return type;}public void setType(String type) {this.type = type;}public Integer getValue() {return value;}public void setValue(Integer value) {this.value = value;}public Integer getVersion() {return version;}public void setVersion(Integer version) {this.version = version;}
}
public interface PkSeedMapper extends BaseMapper<PkSeed> {
}
@SpringBootApplication
@MapperScan("com.javatest.mapper")
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class,args);}
}

我们不使用mybatisplus自带的雪花算法,而是自定义,可以控制某一个位是什么含义。

(三)流水号生成基础写法

1.基础代码

PrimaryService类中添加:

// 流水号生成基础写法
@Autowired
private PkSeedMapper pkSeedMapper;
public String getSerialNum(String type) {if (!StringUtils.hasText(type)) {throw new RuntimeException("type is null");}int result = 0;PkSeed pkSeed = pkSeedMapper.selectById(type);if (pkSeed == null) {pkSeed = new PkSeed();pkSeed.setType(type);pkSeed.setValue(1);pkSeed.setVersion(1);pkSeedMapper.insert(pkSeed);result = pkSeed.getValue(); // 返回最新的值} else {// 如果根据主键查询到,则更新result = pkSeed.getValue() + 1;pkSeedMapper.updateValue(result, type, pkSeed.getVersion());}return String.format("%06d", result);
}
public interface PkSeedMapper extends BaseMapper<PkSeed> {@Update("update pk_seed set value = #{value} , version = version + 1 where type = #{type} and version = #{version}")void updateValue(int value, String type, int version);
}

重启服务,浏览器访问: 

user服务:

order也是一样:

2.基础代码存在的问题 

上述代码存在的问题:多线程问题。

        当有两个线程同时执行更新语句updateValue方法时,A线程和B线程同时查到version=5,A线程先执行,version改为6,接下来B线程后执行,因为B线程要根据version=5的条件修改value,但是version此时已经是6了,所以会执行失败。这就是数据库乐观锁

        失败会直接中断程序,我们可以将失败的情况进行循环。

3.基础代码优化 

@Autowired
private PkSeedMapper pkSeedMapper;
public String getSerialNum(String type) {if (!StringUtils.hasText(type)) {throw new RuntimeException("type is null");}// 流水号的最新值int result = 0;int updateCount = 0;int times = 0;do {if (times > 10) {throw new RuntimeException("服务器繁忙,请稍候再试");}PkSeed pkSeed = pkSeedMapper.selectById(type);if (pkSeed == null) {pkSeed = new PkSeed();pkSeed.setType(type);pkSeed.setValue(1);pkSeed.setVersion(1);pkSeedMapper.insert(pkSeed);result = pkSeed.getValue(); // 返回最新的值updateCount = 1;} else {// 如果根据主键查询到,则更新updateCount = pkSeedMapper.updateValue(pkSeed.getValue() + 1, type, pkSeed.getVersion());if (updateCount == 1) {result = pkSeed.getValue() + 1;}}// 每次更新都增加一次次数times++;} while (updateCount == 0); // 更新失败,重新执行循环return String.format("%06d", result);
}

注意:这里流水号的长度,也就是String.format("%06d", result);中的6,也应该由参数传入,这里简写了。

public interface PkSeedMapper extends BaseMapper<PkSeed> {@Update("update pk_seed set value = #{value} , version = version +1 where type = #{type} and version = #{version}")int updateValue(int value, String type, int version);
}

4.使用httpd进行并发测试 

安装一个并发测试工具:httpd

 yum -y install httpd

使用ab测试: ab -c 10 -n 10 http://localhost/

解释:

  • -c 10:一次发送10个请求
  • -n 10:请求的总数量是10
  • -c 10 -n 100:表示发送测试10次,每次并发10个请求

测试前:

[root@localhost soft]# ab -c 10 -n 10 http://192.168.0.16:8080/getSerialNum?type=user

测试后: 

一次性使用50个并发请求:

[root@localhost soft]# ab -c 50 -n 50 http://192.168.0.16:8080/getSerialNum?type=user

因此,优化后的也有线程问题。我们继续进行优化。

(四)流水号生成批量拿取id

        根据上面的代码,在同时有更多请求时,虽然使用了数据库乐观锁保证了数据的安全,但是不能保证所有的sql都执行成功。每次从数据库获取id,都要进行IO操作,且不一定每次都成功,如果添加100个id,就要修改100次数据库,效率低下

        优化思路:每次从数据库拿到200个id,把这200个数字放到内存中,之后的请求都可以从内存中拿到id,不需要修改数据库。每次拿1个,数据库中的id就+1,以此类推,每次从数据库拿200个,数据库中的id就+200。

        200就是最新值,也就是最大值。根据最大值可以计算出最小值,min=max-num+1,num就是要获取的id个数,当前最大值和要获取的id个数都是200,那么min=200-200+1=1。拿到了最大值和最小值,中间的200个数字就能计算出来了。

        以此类推,当id初始值为1684时,要一次性从数据库中拿412个id,那么最大值就是1684+412=2096,最小值=2096-412+1=1685。拿到了最大值和最小值,中间的412个数字就能计算出来了。

        这412个数字不需要都放在内存中,只需要将最大值和最小值放在map中即可,直接查询内存即可。每次拿取最小值都+1,直到最小值>最大值,说明内存中累计存放的412个数字用完了,再去数据库中一次性取412个id,循环往复。

每次从数据库拿到200个id,放到内存中,具体操作是:
  a. 每次让数据库中的value+200,得到一个最大的id = max
  b. 根据 max 计算出 最小的id, 计算公式 min = max - count + 1
  c. 把最大 id 和最小 id 放到 map 中,
  d. 如果有人请求获取id,先判断内存中是否有,如果有就从内存获取,
      每次获取都让 min++,直到 min 的值 > max时,下一次请求,再次从数据库中查出200个id,放到内存中

private static final int count = 200; // 一次性从数据库获取的流水号数量
// 最大id的map key是type,value是当前type的最大id
private static ConcurrentHashMap<String, AtomicLong> maxIdMap = new ConcurrentHashMap<>();
// 最小id的map
private static ConcurrentHashMap<String, AtomicLong> minIdMap = new ConcurrentHashMap<>();
public String callGetSerialNum(String type) {AtomicLong maxValue = maxIdMap.get(type);AtomicLong minValue = minIdMap.get(type);synchronized (type.intern()) {if (minValue == null || maxValue == null || minValue.get() > maxValue.get()) {// 从数据库中获取新的值=最大值long max = getSerialNum(type, count);// 计算最小值long min = max - count + 1;minIdMap.put(type, new AtomicLong(min));maxIdMap.put(type, new AtomicLong(max));}return String.format("%06d", minIdMap.get(type).getAndIncrement()); // 返回给前端最小值}
}
// 流水号生成基础写法
@Autowired
private PkSeedMapper pkSeedMapper;
public long getSerialNum(String type, int count) {if (!StringUtils.hasText(type)) {throw new RuntimeException("type is null");}// 流水号的最新值int result = 0;int updateCount = 0;int times = 0;do {if (times > 10) {throw new RuntimeException("服务器繁忙,请稍候再试");}PkSeed pkSeed = pkSeedMapper.selectById(type);if (pkSeed == null) {pkSeed = new PkSeed();pkSeed.setType(type);pkSeed.setValue(count);pkSeed.setVersion(1);pkSeedMapper.insert(pkSeed);result = pkSeed.getValue(); // 返回最新的值updateCount = 1;} else {// 如果根据主键查询到,则更新updateCount = pkSeedMapper.updateValue(pkSeed.getValue() + count, type, pkSeed.getVersion());if (updateCount == 1) {result = pkSeed.getValue() + count;}}// 每次更新都增加一次次数times++;} while (updateCount == 0); // 更新失败,重新执行循环return result;
}
@GetMapping("/getSerialNum")
public String callGetSerialNum(String type) {return primaryService.callGetSerialNum(type);
}

删除user那一行:重启项目,浏览器访问

高并发请求也没有错误,并且时间更短了 

ab -c 100 -n 100 http://192.168.0.16:8080/getSerialNum?type=user

  

五、主键生成方法三:级次编码

PrimaryController:

@GetMapping("/getCode")
public String getCode(String type, String parentCode, String rule) {return GradeCodeGenerator.getNextCode(type, parentCode, rule);
}
import com.javatest.domain.GradeCodeRule;import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;/*** 级次编码生成工具*/
public class GradeCodeGenerator {//级次编码规则缓存private static ConcurrentMap<String, GradeCodeRule> codeRuleMap = new ConcurrentHashMap<>();static PrimaryService primaryService;/*** 例如:2|2|2|2* 根据父编码获取下一个子编码* parentCode=01            第一次调用返回:0101,第二次调用返回:0102* parentCode=null || ""    第一次调用返回:01,第二次调用返回:02** @param codeRule   编码规则* @param parentCode 父编码 可以为空* @param type       编码类型:业务名称* @return*/public static String getNextCode(String type, String parentCode, String codeRule) {if (!codeRuleMap.containsKey(codeRule)) {codeRuleMap.putIfAbsent(codeRule, new GradeCodeRule(codeRule));}return createCode(codeRuleMap.get(codeRule), parentCode, type);}private static String createCode(GradeCodeRule gradeCodeRule, String parentCode, String type) {parentCode = parentCode == null || parentCode.length() == 0 ? "" : parentCode;int parentGrade = gradeCodeRule.getCodeGrade(parentCode);//父编码级次if (parentGrade < 0) {throw new IllegalArgumentException("parentCode(" + parentCode + ")跟codeRule(" + gradeCodeRule.getRule() + ")不匹配");}if (parentGrade >= gradeCodeRule.getMaxGradeCount()) {throw new IllegalArgumentException(parentCode + "已经是最末级编码,无法获取子编码!");}
//        int parentGradeLength = gradeCodeRule.getGradeLength(parentGrade);int subCodeSectionLength = gradeCodeRule.getSectionLength(parentGrade + 1);//子编码末级片段长度long nextSubCode = primaryService.getLongKey(type + "-" + gradeCodeRule.getRule() + "-" + parentCode);String nextSubCodeStr = String.format("%0" + subCodeSectionLength + "d", nextSubCode);if (nextSubCodeStr.length() > subCodeSectionLength) {throw new IllegalArgumentException(parentCode + "的下一级编码已经用完!");}StringBuilder codeBuilder = new StringBuilder(parentCode);codeBuilder.append(nextSubCodeStr);return codeBuilder.toString();}static void setPrimaryKeyService(PrimaryService ps) {primaryService = ps;}}
package com.javatest.domain;import java.util.Arrays;
import java.util.StringTokenizer;/*** 级次编码规则实体*/
public class GradeCodeRule {private static final String regex = "([1-9]\\d{0,}\\|{0,1})+";/*** 编码规则,例:2|2|2* 表示:一共有3级,每级的长度为2*/private String rule;/*** 各级分段长度,例:2|2|2* 则:[2,2,2]*/private int[] codeSection = null;/*** 各级编码长度,例:2|2|2* 则:[2,4,6]*/private int[] codeLength = null;/**完整编码长度*/private int fullGradeLength;public GradeCodeRule(String rule){checkRule(rule);this.rule = rule;parseRule(rule);}private void parseRule(String rule) {StringTokenizer st = new StringTokenizer(rule,"|");int count = st.countTokens();codeSection = new int[count];codeLength = new int[count];int index = 0;fullGradeLength = 0;try {while (st.hasMoreTokens()) {codeSection[index] = Integer.parseInt(st.nextToken());fullGradeLength += codeSection[index];codeLength[index++] = fullGradeLength;}} catch (Exception e) {throw new IllegalArgumentException("请提供正确的级次编码规则,例如:2|2|2|2,表示:一共有4级,每级的长度为2");}}/*** 得到编码第grade级片段的长度。<br/>* 如编码规则为"2/3/2/3",则:<br/>* 2级编码片段长度为3,<br/>* 3级编码片段长度为2。<br/>* @param grade int 1:代表第一级,依次类推* @return int*/public int getSectionLength(int grade) {if (grade <= 0) {return 0;} else {return codeSection[--grade];}}/*** 获取编码级次* 如编码规则为"2|2|2",则:<br>* 编码"1010"的级次为2,<br>* 编码"10101010"的级次为-1,表示code跟编码规则不匹配,<br>* @param code* @return 编码级次*/public int getCodeGrade(String code) {if(code==null || code.length()==0) return 0;int index = Arrays.binarySearch(codeLength, code.length());return index >=0 ? index+1 : -1;}/*** 验证编码规则是否符合规范* @param rule*/public void checkRule(String rule) {if(!rule.matches(regex)){throw new IllegalArgumentException(rule+":不正确,请提供正确的级次编码规则,例如:2|2|2|2,表示:一共有4级,每级的长度为2");}}/*** 得到最大编码级次* @return 最大编码级次*/public int getMaxGradeCount(){return codeSection.length;}public String getRule() {return rule;}public void setRule(String rule) {this.rule = rule;}public static void main(String[] args) {new GradeCodeRule("2,3,4");}
}
@Service
public class PrimaryService implements InitializingBean {// 初始id值private AtomicLong baseId;// 将机器号作为配置@Value("${node.id:1}") // 表示配置文件中node.id的值,如果没有配置,则默认为1private long nodeId;// afterPropertiesSet方法初始化Bean对象中的属性@Overridepublic void afterPropertiesSet() throws Exception {long now = System.currentTimeMillis();// 机器号先左移56位,再和时间戳做或运算,就是初始id值baseId = new AtomicLong(nodeId << 56 | now);GradeCodeGenerator.setPrimaryKeyService(this); // todo}public Long getId() {return baseId.incrementAndGet();}private static final int count = 200; // 一次性从数据库获取的流水号数量// 最大id的map key是type,value是当前type的最大idprivate static ConcurrentHashMap<String, AtomicLong> maxIdMap = new ConcurrentHashMap<>();// 最小id的mapprivate static ConcurrentHashMap<String, AtomicLong> minIdMap = new ConcurrentHashMap<>();public String callGetSerialNum(String type) {AtomicLong maxValue = maxIdMap.get(type);AtomicLong minValue = minIdMap.get(type);synchronized (type.intern()) {if (minValue == null || maxValue == null || minValue.get() > maxValue.get()) {// 从数据库中获取新的值=最大值long max = getSerialNum(type, count);// 计算最小值long min = max - count + 1;minIdMap.put(type, new AtomicLong(min));maxIdMap.put(type, new AtomicLong(max));}return String.format("%06d", minIdMap.get(type).getAndIncrement()); // 返回给前端最小值}}// 流水号生成基础写法@Autowiredprivate PkSeedMapper pkSeedMapper;public long getSerialNum(String type, int count) {if (!StringUtils.hasText(type)) {throw new RuntimeException("type is null");}// 流水号的最新值int result = 0;int updateCount = 0;int times = 0;do {if (times > 10) {throw new RuntimeException("服务器繁忙,请稍候再试");}PkSeed pkSeed = pkSeedMapper.selectById(type);if (pkSeed == null) {pkSeed = new PkSeed();pkSeed.setType(type);pkSeed.setValue(count);pkSeed.setVersion(1);pkSeedMapper.insert(pkSeed);result = pkSeed.getValue(); // 返回最新的值updateCount = 1;} else {// 如果根据主键查询到,则更新updateCount = pkSeedMapper.updateValue(pkSeed.getValue() + count, type, pkSeed.getVersion());if (updateCount == 1) {result = pkSeed.getValue() + count;}}// 每次更新都增加一次次数times++;} while (updateCount == 0); // 更新失败,重新执行循环return result;}// 生成级次编码需要借助流水号的方法public long getLongKey(String type) {return getSerialNum(type, 1); // 1表示每次从数据库中 获取1个流水号}
}

数据库结果: 

相关文章:

分布式主键生成服务

目录 一、使用线程安全的类——AtomicInteger或者AtomicLong 二、主键生成最简单写法(不推荐) 三、主键生成方法一&#xff1a;Long型id生成——雪花算法 四、主键生成方法二&#xff1a;流水号 (一)流水号概述 (二)添加配置 1.pom.xml 2.application.properties 3.创…...

如何通过网管提升运维效率?

网络系统在企业信息化系统扮演着越来越重要的作用&#xff0c;网络规模不断扩大&#xff0c;网络结构越来越复杂&#xff0c;传统的运维方式已经难以满足高效、稳定运行的要求。网管系统作为IT运维的重要工具&#xff0c;能够帮助企业实现网络的智能化管理&#xff0c;显著提升…...

(python)Arrow库使时间处理变得更简单

前言 Arrow库并不是简单的二次开发,而是在datetime的基础上进行了扩展和增强。它通过提供更简洁的API、强大的时区支持、丰富的格式化和解析功能以及人性化的显示,填补了datetime在某些功能上的空白。如果你需要更高效、更人性化的日期时间处理方式,Arrow库是一个不错的选择…...

机器学习数学基础:33.分半信度

分半信度&#xff08;Split-Half Reliability&#xff09;深度教程 专为零基础小白打造&#xff0c;全面掌握分半信度知识 一、深入理解分半信度 分半信度是一种用于评估测验内部一致性的重要方法&#xff0c;其核心思路在于将一个完整的测验拆分成两个部分&#xff0c;然后通…...

PyTorch 源码学习:GPU 内存管理之深入分析 CUDACachingAllocator

因引入 expandable_segments 机制&#xff0c;PyTorch 2.1.0 版本发生了较大变化。本文关注的是 PyTorch 原生的 GPU 内存管理机制&#xff0c;故研究的 PyTorch 版本为 2.0.0。代码地址&#xff1a; c10/cuda/CUDACachingAllocator.hc10/cuda/CUDACachingAllocator.cpp 更多内…...

0—QT ui界面一览

2025.2.26&#xff0c;感谢gpt4 1.控件盒子 1. Layouts&#xff08;布局&#xff09; 布局控件用于组织界面上的控件&#xff0c;确保它们的位置和排列方式合理。 Vertical Layout&#xff08;垂直布局&#xff09; &#xff1a;将控件按垂直方向排列。 建议&#xff1a;适…...

Jenkinsfile流水线构建教程

前言 Jenkins 是目前使用非常广泛的自动化流程的执行工具, 我们目前的一些自动化编译, 自动化测试都允许在 Jenkins 上面. 在 Jenkins 的术语里面, 一些自动化工作联合起来称之为流水线, 比如拉取代码, 编译, 运行自动化测试等. 本文的主要目的是引导你快速熟悉 Jenkinsfile …...

flex布局自定义一行几栏,靠左对齐===grid布局

模板 <div class"content"><div class"item">1222</div><div class"item">1222</div><div class"item">1222</div><div class"item">1222</div><div class"…...

开发HarmonyOS NEXT版五子棋游戏实战

大家好&#xff0c;我是 V 哥。首先要公布一个好消息&#xff0c;V 哥原创的《鸿蒙HarmonyOS NEXT 开发之路 卷1&#xff1a;ArkTS 语言篇》图书终于出版了&#xff0c;有正在学习鸿蒙的兄弟可以关注一下&#xff0c;写书真是磨人&#xff0c;耗时半年之久&#xff0c;感概一下…...

AI革命下的多元生态:DeepSeek、ChatGPT、XAI、文心一言与通义千问的行业渗透与场景重构

前言 人工智能技术的爆发式发展催生了多样化的AI模型生态&#xff0c;从通用对话到垂直领域应用&#xff0c;从数据挖掘到创意生成&#xff0c;各模型凭借其独特的技术优势与场景适配性&#xff0c;正在重塑全球产业格局。本文将以DeepSeek、ChatGPT、XAI&#xff08;可解释人…...

大语言模型(LLM)微调技术笔记

图1&#xff1a;大模型进化树2 大模型微调 在预训练后&#xff0c;大模型可以获得解决各种任务的通用能力。然而&#xff0c;越来越多的研究表明&#xff0c;大语言模型的能力可以根据特定目标进一步调整。 这就是微调技术&#xff0c;目前主要有两种微调大模型的方法1&…...

六十天前端强化训练之第一天HTML5语义化标签深度解析与博客搭建实战

欢迎来到编程星辰海的博客讲解 目录 一、语义化标签的核心价值 1.1 什么是语义化&#xff1f; 1.2 核心优势 二、语义标签详解与使用场景 2.1 布局容器标签 2.2 内容组织标签 三、博客结构搭建实战 3.1 完整HTML结构 3.2 核心结构解析 3.3 实现效果说明 四、学习要点…...

NLP的预处理数据

处理文本数据的主要工具是Tokenizer。Tokenizer根据一组规则将文本拆分为tokens。然后将这些tokens转换为数字&#xff0c;然后转换为张量&#xff0c;成为模型的输入。模型所需的任何附加输入都由Tokenizer添加。 如果您计划使用预训练模型&#xff0c;重要的是使用与之关联的…...

【DeepSeek问答】QProcess::start是异步的吗?会使UI卡顿吗?

好的&#xff0c;我现在需要回答用户关于QProcess::start是否是异步的&#xff0c;以及它是否会导致UI卡顿的问题。用户之前已经询问过如何开发一个调用7-Zip的Qt应用&#xff0c;并且我给出了示例代码。现在用户可能在使用过程中遇到了问题&#xff0c;或者想更深入了解QProce…...

【Java项目】基于Spring Boot的体质测试数据分析及可视化设计

【Java项目】基于Spring Boot的体质测试数据分析及可视化设计 技术简介&#xff1a;采用Java技术、Spring Boot框架、MySQL数据库等实现。 系统简介&#xff1a;体质测试数据分析及可视化设计是一个基于Web的在线平台&#xff0c;主要分为前台和后台两大功能模块。前台功能模…...

JAVA-如何理解Mysql的索引

一、索引的概念 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用(指针/地址)。可以对表中的一列或多列创建索引&#xff0c; 并指定索引的类型&#xff0c;各类索引有各自的数据结构实现。 二、索引是什么&#xff0c;用来干嘛 数据库中的表、数据、索引之间的…...

VUE向外暴露文件,并通过本地接口调用获取,前端自己生成接口获取public目录里面的文件

VUE中&#xff0c;如果我们想对外暴露一个文件&#xff0c;可以在打包之后也能事实对其进行替换&#xff0c;我们只需要把相关文件放置在public目录下即可&#xff0c;可以放置JSON&#xff0c;Excel等文件 比如我在这里放置一个other文件 我们可以直接在VUE中使用axios去获取…...

京准电钟:NTP精密时钟服务器在自动化系统中的作用

京准电钟&#xff1a;NTP精密时钟服务器在自动化系统中的作用 京准电钟&#xff1a;NTP精密时钟服务器在自动化系统中的作用 NTP精密时钟服务器在自动化系统中的作用非常重要&#xff0c;特别是在需要高精度时间同步的场景中。NTP能够提供毫秒级的时间同步精度&#xff0c;这…...

CSDN年度评选揭晓,永洪科技AI技术与智能应用双星闪耀

近日&#xff0c;永洪科技在CSDN&#xff08;中国专业开发者社区&#xff09;的年度评选中&#xff0c;凭借在人工智能技术创新与vividime在行业应用中的卓越表现&#xff0c;一举斩获“人工智能企业”及“智能应用”双料大奖。这一荣誉不仅彰显了永洪科技在AI领域的领先地位&a…...

vscode settings(二):文件资源管理器编辑功能主题快捷键

参考资料 Visual Studio Code权威指南 by 韩骏 一. 文件资源管理器 1.1 文件资源管理器隐藏文件夹 默认情况下&#xff0c;Visual Studio Code会在文件资源管理器中隐藏一些文件夹&#xff08;如.git文件夹&#xff09;​。可以通过files.exclude来配置要被隐藏的文件和文件…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

服务器硬防的应用场景都有哪些?

服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式&#xff0c;避免服务器受到各种恶意攻击和网络威胁&#xff0c;那么&#xff0c;服务器硬防通常都会应用在哪些场景当中呢&#xff1f; 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

使用 SymPy 进行向量和矩阵的高级操作

在科学计算和工程领域&#xff0c;向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能&#xff0c;能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作&#xff0c;并通过具体…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

深度学习水论文:mamba+图像增强

&#x1f9c0;当前视觉领域对高效长序列建模需求激增&#xff0c;对Mamba图像增强这方向的研究自然也逐渐火热。原因在于其高效长程建模&#xff0c;以及动态计算优势&#xff0c;在图像质量提升和细节恢复方面有难以替代的作用。 &#x1f9c0;因此短时间内&#xff0c;就有不…...

Redis:现代应用开发的高效内存数据存储利器

一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发&#xff0c;其初衷是为了满足他自己的一个项目需求&#xff0c;即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源&#xff0c;Redis凭借其简单易用、…...

【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看

文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文通过代码驱动的方式&#xff0c;系统讲解PyTorch核心概念和实战技巧&#xff0c;涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...

MyBatis中关于缓存的理解

MyBatis缓存 MyBatis系统当中默认定义两级缓存&#xff1a;一级缓存、二级缓存 默认情况下&#xff0c;只有一级缓存开启&#xff08;sqlSession级别的缓存&#xff09;二级缓存需要手动开启配置&#xff0c;需要局域namespace级别的缓存 一级缓存&#xff08;本地缓存&#…...