首页电脑使用生成偶数个随机数 生成偶数序列号

生成偶数个随机数 生成偶数序列号

圆圆2025-08-08 14:01:16次浏览条评论

生成多应用实例无间隙序列号指南论文详细介绍了在多实例环境下,如何利用数据库悲观锁和事务机制,实现序列号的无间隙生成。通过引入一个专用的分区表,并结合JPA的PESSIMISTIC_WRITE锁模式,确保在并发下,每个序列号场景唯一且地连续递增,有效避免了事务回滚等或并发问题导致的序列号跳跃或重复,适用于必须严格顺序和中断的业务应用场景。 1. 问题背景与挑战

在多个系统新生儿应用实例环境中,生成具备特定系列(系列)且连续递增(数量)的设备号是一系列常见的需求。例如,设备号可能表现为aaa|1、aa|2、aa|3、bb|1等格式,其中每个系列都有其最大允许数量。核心挑战在于:无间隙生成:序列号必须连续,即使在事务回滚或系统崩溃的情况下,也不能出现跳号(如从1直接跳到3)。传统数据库自增序列或findMax()然后递增的方式,在连续和回滚场景下,往往难以保证无间隙。同时安全:多个应用实例或线程同时请求生成设备号时,必须保证序列号的唯一性和顺序性,避免竞态条件。当一个系列的序列号达到上限时,需要能够自动切换到下一个系列并从1开始重新计数。

传统的SELECT MAX(NUMBER)方法在并发环境下出现严重问题。当一个事务查询到顶部并准备插入新记录时,另一个事务也可能同时查询到相同,因为更新,导致两个都尝试插入下一个相同的序列号,从而引发唯一性冲突或复杂的重试机制。即使通过行锁记录锁定存在到的峰值避免,也可能无法完全问题,而不是锁定的只是现有记录,是“下一个”序列号的查询的生成权。2. 解决方案:专用粒子表与悲观锁

为了解决上述挑战,一个健壮且可靠的方案是引入一个专用的粒子表,并结合数据库的悲观锁(PESSIMISTIC_WRITE)机制。2.1核心思路

独立粒子表:创建一个独立的数据库表,例如series_counter,用于存储每个SERIES的当前下一个可用序列号。 current_counter------------------------AA | 1BB | 1CC | 1...登录后复制

current_counter字段表示对应series_id下一次将要分配的序列号。

悲观锁锁定:当需要为某个SERIES生成序列号时,首先通过悲观写锁(PESSIMISTIC_WRITE)锁定series_counter表中对应series_id的那一行记录。这保证了在当前事务完成,任何其他尝试读取或修改之前该行记录的事务都会被阻塞,直到锁被释放。

事务原子性:在同一个数据库事务中,完成以下操作:读取被锁定的current_counter值。使用该值生成新的设备号记录。将series_counter表中对应series_id的current_counter值递增1。保存新的设备号记录。提交事务。2.2实现示例(基于Spring Data JPA和PostgreSQL)

想象我们有以下场景:SeriesCounter:用于存储每个系列的序号。

Device:实际的设备记录,包含系列和编号。

2.2.1 实体定义 import jakarta.persistence.*;@Entity@Table(name = quot;series_counterquot;)public class SeriesCounter { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = quot;series_idquot;, unique = true, nullable = false) private String seriesId; // 例如 quot;AAquot;, quot;BBquot; @Column(name = quot;current_counterquot;, nullable = false) private Long currentCounter; // 当前下一个可用的序列号 // 构造函数 public SeriesCounter() {} public SeriesCounter(String seriesId, Long currentCounter) { this.seriesId = seriesId; this.currentCounter = currentCounter; } // Getters 和 Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getSeriesId() { return seriesId; } public void setSeriesId(String seriesId) { this.seriesId = seriesId; } public Long getCurrentCounter() { return currentCounter; } public void setCurrentCounter(Long currentCounter) { this.currentCounter = currentCounter; } // 递增计数器的方法 public void incrementValue() { this.currentCounter ; }}@Entity@Table(name = quot;devicequot;)public class Device { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = quot;seriesquot;, nullable = false) private String series; @Column(name = quot;numberquot;, nullable = false) private

Long number; // 构造函数 public Device() {} public Device(String series, Long number) { this.series = series; this.number = number; } // Getters 和 Setters public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getSeries() { return series; } public void setSeries(String series) { this.series = series; } public Long getNumber() { return number; } public void setNumber(Long number) { this.number = number; }}登录后复制

2.2.2 Repository 定义 import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.Lock;import org.springframework.data.jpa.repository.Query;import org.springframework.data.repository.query.Param;import jakarta.persistence.LockModeType;import java.util.Optional;public interface SeriesCounterRepository extends JpaRepositorylt;SeriesCounter, Longgt; { /** * 根据seriesId获取并锁定对应的SeriesCounter记录。 * 使用PESSIMISTIC_WRITE悲观锁,确保在当前事务中该行的独占访问。

* * @param seriesId 要锁定的系列ID * @return 包含SeriesCounter的可选对象 */ @Lock(LockModeType.PESSIMISTIC_WRITE) @Query(quot;SELECT sc FROM SeriesCounter sc WHERE sc.seriesId = :seriesIdquot;)Optionallt;SeriesCountergt; findBySeriesIdWithLock(@Param(quot;seriesIdquot;) String seriesId);}public interface DeviceRepository extends JpaRepositorylt;Device, Longgt; { // 基础的CRUD操作}登录后复制

2.2.3 服务层实现import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Servicepublic class DeviceNumberGeneratorService { private final SeriesCounterRepository seriesCounterRepository; private final DeviceRepository deviceRepository; public DeviceNumberGeneratorService(SeriesCounterRepository seriesCounterRepository, DeviceRepository deviceRepository) { this.seriesCounterRepository = seriesCounterRepository; this.deviceRepository = deviceRepository; } /** * 生成一个无间隙的设备序列号。 * 整个操作在一个事务中完成,表项进行悲观锁定。 * * @param seriesId 要生成序列号的系列ID * @param maxNumForSeries该系列允许的最大序列号(业务逻辑限制) * @return 生成的设备对象 * @throws IllegalStateException 如果当前系列已达到最大数量 */ @Transactional //确定整个方法在一个事务中执行 public DevicegenerateDeviceNumber(String seriesId, int maxNumForSeries) { // 1. 获取并锁定对应系列的条目 // 如果series_counter表中没有该seriesId的记录,则需要初始化。

// 生产环境中,通常会在系统启动或首次使用时预先初始化所有系列的计数器。 // 这里简化处理,如果不存在则流量异常,或根据实际需求添加初始化信号。 SeriesCounter seriesCounter = seriesCounterRepository.findBySeriesIdWithLock(seriesId) .orElseThrow(() -gt; new IllegalArgumentException(quot;SeriesCounter for seriesId quot;seriesId quot;未找到。请初始化it.quot;)); Long currentNumber = seriesCounter.getCurrentCounter(); // 2.检查是否达到当前系列的最大允许数量 if (currentNumber gt; maxNumForSeries) { //如果当前系列已满,业务根据需求可以发送异常, //或者实现切换到下一个系列的逻辑(例如,通过查找下一个可用的seriesId并调用)。 throw new IllegalStateException(quot;Series quot;seriesId quot;已达到最大数量”; maxNumForSeries ”;无法生成更多数字。 for this series.quot;); } // 3. 使用当前统计生成设备号 Device newDevice = new Device(); newDevice.setSeries(seriesId); newDevice.setNumber(currentNumber); // ... 设置其他设备属性,例如设备名称、型号等 // 4. 保存新的设备记录 deviceRepository.save(newDevice); // 5. 递增概率,为下一个请求准备 seriesCounter.incrementValue(); // currentCounter seriesCounterRepository.save(seriesCounter); // 更新计数,将递增后的值持久化返回加上newDevice; }}登录后复制2.3详解悲观写锁(@Lock(LockModeType.PESSIMISTIC_WRITE)):当findBySeriesIdWithLock方法被调用时,它会在数据库层面为series_counter表中seriesId对应的行一个他锁。

这意味着:其他事务如果尝试读取(SELECT ... FOR UPDATE 或 SELECT ... FOR SHARE)或修改(UPDATE、DELETE)同一个行,将会被阻塞,直到持有锁的事务提交或回滚。这种锁在事务开始时获取,在事务结束(提交或回滚)时释放。事务 (@Transactional): generateDeviceNumber方法被标记为@Transactional,确保整个操作(获取节点、生成设备、保存设备、更新节点)是一个原子单元。如果其中任何一步失败(例如,deviceRepository.save(newDevice)失败),整个事务都会回滚。回滚时,series_counter表中current_counter的值将恢复到事务开始前的状态,从而不会保证出现间隙。即使事务失败,序列号也不会被“”掉。并发处理:相同系列: 多个请求尝试为同一个seriesId生成设备号时,只有一个请求能成功获取到series_counter表的行锁。其他请求会被阻塞,队列等待。一旦前一个事务完成并释放锁,下一个等待的事务才能获取锁并继续执行。保证这了同一系列序列的严格顺序和无间隙。不同系列:如果ARM请求为不同的seriesId生成设备号,它们会锁定series_counter表中不同的行,因此它们可以任务执行,互不影响,提高了系统的并发能力。 3. 优点与事项注意3.1优点严格无间隙:即使在并发高、事务回滚的场景下,也能保证序列号的严格无间隙生成。并发安全:通过数据库层面的悲观锁,有效解决了多实例并发生成序列号的竞态条件问题。数据一致性:间隙事务的原子性确保了设备号生成与密集更新的同步,避免了数据不一致。3.2注意事项与潜在问题性能瓶颈:悲观锁会阻止其他事务对相同资源的访问。如果某个seriesId的设备号生成频率过高,可能会导致该seriesId成为性能瓶颈。对于这种极端情况,可能需要考虑更复杂的多元化ID生成方案(如雪花算法),但这些方案通常无法保证严格的无均衡性,或者需要额外的补偿机制。死锁风险: 虽然本方案中只锁定了一个资源(series_counter的单行),死锁的风险较低。但在较为复杂的业务场景中,如果一个事务需要锁定多个资源,而这些资源的锁定顺序不一致,则可能发生死锁。良好的事务设计和统一的锁定顺序可以规避此风险。数据库兼容性:悲观锁的具体实现和行为可能因数据库类型(如PostgreSQL、MySQL、Oracle)而异。例如,PostgreSQL的FOR UPDATE通常会锁定行,而MySQL的InnoDB引擎在某些隔离级别下可能锁定索引范围。但在JPA的PESSIMISTIC_WRITE抽象下,通常能获得预期的行级锁定行为。初始化:确定series_counter表中所有预期的seriesId都有对应的初始初始记录。在生产环境中,这通常通过数据初始化脚本或管理界面来完成。系列切换逻辑: 当一个系列的current_counter达到maxNumForSeries时,如何自动切换到下一个系列是一个业务决策。这部分逻辑需要根据实际需求在generateDeviceNumber方法中实现,例如通过查找下一个可用的seriesId并请求调用,或者引发异常让调用方处理。

4.总结

通过引入专用的series_counter表并结合Spring Data JPA的@Lock(LockModeType.PESSIMISTIC_WRITE)和@Transactional注解,我们能够构建一个在多应用实例环境下可靠、无间隙的序列号生成系统。该方案利用了数据库事务的原子性和悲观锁的排他性,确保了数据的一致性和对称安全性。尽管悲观锁可能引入一定的性能开销,但对于那些对序列号的连续性和缺陷有严格要求的业务场景,提供了一个简洁而强大的解决方案。在实际应用中,应根据具体的负载量和性能需求,权衡其优缺点。

以上就是生成多应用实例无间隙序列号指南的详细内容,更多请关注乐哥常识网其他相关文章!

生成多应用实例无间隙
golang 编程规范 golang编程框架教程
相关内容
发表评论

游客 回复需填写必要信息