分布式项目使用Redis实现数据库对象自增主键ID

server/2024/12/2 19:27:13/

hello。大家好,我是灰小猿,一个超会写bug的程序猿!

分布式项目中,数据表的主键ID一般可能存在于UUID或自增ID这两种形式,UUID好理解而且实现起来也最容易,但是缺点就是数据表中的主键ID是32位的字符串,在大数据查询等情况下性能会相对比较差,所以在需求允许的情况下,我们通常会优先考虑使用自增ID来代替UUID使用。

分布式项目中如果你的数据表的主键ID是自增ID,那么常见的生成对象主键ID的方式有:

  • 雪花算法

    • 优点:实现简单

    • 缺点:生成ID较长、生成ID不连续,会造成ID浪费

  • 采用框架自带的ID生成器,如MybatisPlus的@AutoID

    • 优点:依赖框架,实现简单

    • 缺点:无法在插入对象前获取到对象的主键ID

  • 采用Redis缓存生成主键ID等

    • 优点:生成的ID连续,数据在表中的可读性好

    • 缺点:借助Redis缓存,实现相比前两种较复杂

通过雪花算法生成对象主键ID的方式我们在之前的文章中已经介绍过了,这篇文章我们主要介绍如何通过Redis来实现生成对象自增ID的方法。

1、缓存实现原理

通过Redis实现对象自增ID的方式,主要是通过Redis的INCRINCRBY 命令来对键值进行递增操作,从而实现计数器的功能,主要原因是Redis 是单线程模型,所有命令都是原子操作,不会发生竞态条件,

在使用时要留意以下特点与注意事项

  1. 原子性INCRINCRBYINCRBYFLOAT 命令都是原子性的,这意味着如果多个客户端同时执行这些命令,Redis 会保证每个命令的执行不会发生竞态条件。

  2. 数据类型要求:这些命令要求操作的值是整数(INCRINCRBY)或浮点数(INCRBYFLOAT)。如果键对应的值不是数值类型,Redis 会返回错误。

  3. 性能:Redis 是单线程的,所有命令都是原子操作,因此在高并发环境下执行这些命令时,性能表现非常好。

  4. 键不存在的情况:如果执行 INCRINCRBY 时,键不存在,Redis 会自动创建这个键并初始化其值为 0,然后进行递增。

2、Redis工具类实现ID自动生成

了解到通过Redis实现的原理之后,就是如何设计缓存以保证每张表的数据之间不会相互影响,

以user表为例,对应的model为UserModel,那么我们就可以将model名称作为key,当前已有的最大的ID作为value来进行存储,因为每一张表对应的model名称都是不一样的,所以这里一定不会出现key重复的情况,

下面是一些在Redis中生成和获取对象自增ID的工具方法,方便新建对象时直接调用

java">@Component("redisUtils")
@RequiredArgsConstructor
@Slf4j
public class RedisUtils implements InitializingBean {private final StringRedisTemplate stringRedisTemplate;private static RedisUtils redisUtils;@Overridepublic void afterPropertiesSet() {redisUtils = this;}/*** 获取自增ID*/public static <T> Integer getIncr(Class<T> tClass) {try {String key = tClass.getName();Long increment = redisUtils.stringRedisTemplate.opsForValue().increment(key, 1);return Math.toIntExact(increment);} catch (Exception e) {e.printStackTrace();throw new RedisSystemException(e.getMessage(), e);}}/*** 获取自增ID,指定递增长度,用于批量创建对象时,只请求一次Redis* 如批量创建五个对象,delta等于5*/public static <T> Integer getIncr(Class<T> tClass, int delta) {try {String key = tClass.getName();Long increment = redisUtils.stringRedisTemplate.opsForValue().increment(key, delta);return Math.toIntExact(increment);} catch (Exception e) {e.printStackTrace();throw new RedisSystemException(e.getMessage(), e);}}/*** 缓存预热,用于项目启动时初始化Redis中各个表的最大ID*/public static <T> long incrPreheat(Class<T> tClass, Long maxValue) {if (maxValue == null) {maxValue = 0L;}if (maxValue < 0) {//异常抛出,预热的初始值不能小于0}Integer incr = getIncr(tClass);if (maxValue <= incr) {return incr;}return getIncr(tClass, (int) (maxValue - incr));}
}

解释:InitializingBean是Spring提供的拓展性接口,InitializingBean接口为bean提供了属性初始化后的处理方法,它只有一个afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。

3、缓存预热

缓存预热是指在项目启动时,将每一张表当前最大的主键ID预先加载到Redis中,这样在后面使用的时候,就可以直接从Redis中获取下一次ID即可,不需要再去访问数据库查询最大ID,缓存预热一般会建立在Redis专门的初始化类中,以便在启动项目时可以运行该类,具体如下:

java">/*** 缓存预热** @author hxy*/
@Component
@DependsOn("redisUtils")
@RequiredArgsConstructor
public class RedisInit {private final UserMapper userMapper;/*** 获取每一张表中当前的最大ID,然后预热到redis中*/@PostConstructpublic void init() {RedisUtils.incrPreheat(UserModel.class, userMapper.maxId());}
}

解释:@DependsOn注解可以定义在类和方法上,意思是我这个组件要依赖于另一个组件,也就是说被依赖的组件会比该组件先注册到IOC容器中。

解释:@PostConstruct注解

在Java中,@PostConstruct注解,通常用于标记一个方法,它表示该方法在类实例化之后(通过构造函数创建对象之后)立即执行。

加上@PostConstruct注解的方法会在对象的所有依赖项都已经注入完成之后执行。通过使用@PostConstruct注解,我们可以确保在对象完全创建和初始化之后才执行这些操作。这个注解通常用在依赖注入(Dependency Injection)的框架中,例如Spring。

@PostConstruct 注解可以用在任何类的方法上,但它最常用于标记在 Spring Framework 中的 Bean 类中的初始化方法。

4、model层封装调用

在实例化一个model对象的时候,要将model的主键ID进行赋值,这个时候就要从Redis中获取到当前对象应该对应的主键ID,我们可以在model类中构建一个newInstance方法或有参构造,来专门的生成需要赋值主键ID的对象,在方法中调用redis工具类的getIncr方法,获取到最新最小未使用的主键ID并赋值给新建的model即可。

java">@Getter
@Setter
public class UserModel {private Integer id;private String name;private Integer age;private String tel;public UserModel() {}/*** 提供一个能够生成自增ID的有参构造,需要生成自增ID时调用*/public UserModel(boolean autoId) {if (autoId) {this.id = RedisUtils.getIncr(UserModel.class);}}
}

如果你是需要批量创建对象并且给对象赋值主键ID的情况,可以按照如下方式使用,这样可以只调用一次Redis,避免重复调用Redis影响性能。

java">@Service
@RequiredArgsConstructor
public class UserServiceImpl {private final UserMapper userMapper;public void batchAddUser(){int addNumber = 10;//提前生成自定跨度的ID,比如之前最大ID是6,获取后得到的incr是16Integer incr = RedisUtils.getIncr(UserModel.class, addNumber);List<UserModel> userModels = new ArrayList<>();for (int i = 0; i < addNumber; i++) {UserModel userModel = new UserModel();//每一个主键ID依次递减使用userModel.setId(incr--);userModels.add(userModel);}userMapper.insertBatch(userModels);}
}

至此,通过Redis来获取对象自增ID的方法已经完成,如果在使用过程中有其他场景需求,可以对redisUtils中的方法进行拓展即可。

我是灰小猿,我们下期见!


http://www.ppmy.cn/server/146813.html

相关文章

鸿蒙征文|鸿蒙技术分享:使用到的开发框架和技术概览

目录 每日一句正能量前言正文1. 开发环境搭建关键技术&#xff1a;2. 用户界面开发关键技术&#xff1a;3. 应用逻辑开发关键技术&#xff1a;4. 应用测试关键技术&#xff1a;5. 应用签名和打包关键技术&#xff1a;6. 上架流程关键技术&#xff1a;7. 后续维护和更新关键技术…

去哪儿Android面试题及参考答案

TCP 的三次握手与四次挥手过程是什么? TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议 ,三次握手和四次挥手是其建立连接和断开连接的重要过程。 三次握手过程 第一次握手:客户端向服务器发送一个 SYN(同步序列号)包,其中包…

计算机网络课程设计:理论与实践全解析

标题&#xff1a;计算机网络课程设计&#xff1a;理论与实践全解析 计算机网络课程设计是计算机专业培养学生动手能力和理论联系实际的重要环节。通过设计并实现实际项目&#xff0c;学生能够更深刻地理解网络协议、网络通信及应用开发的核心技术。本文将从课程设计的选题、实施…

Qt 面试题学习13_2024-12-1

Qt 面试题 1、 QString与基本数据类型如何转换?2、常用数据结构3、进程之间的道信方式有哪些? 1、 QString与基本数据类型如何转换? 1、将QString转换为基本数据类型通过QString的各种转换函数&#xff0c;可以将QString转换为int、float、double等基本数据类型。 QStri…

AI开发-机器学习库-sklearn

1 需求 官网&#xff1a;scikit-learn: machine learning in Python — scikit-learn 1.5.2 documentation scikit - learn&#xff08;简称sklearn&#xff09;是一个用于机器学习的开源 Python 库。它建立在NumPy、SciPy和matplotlib之上&#xff0c;提供了丰富的工具用于数…

k8s集成skywalking

如果能科学上网的话&#xff0c;安装应该不难&#xff0c;如果有问题可以给我留言 本篇文章我将给大家介绍“分布式链路追踪”的内容&#xff0c;对于目前大部分采用微服务架构的公司来说&#xff0c;分布式链路追踪都是必备的&#xff0c;无论它是传统微服务体系亦或是新一代…

数据结构-简单排序

一.前提 二.冒泡排序 三.插入排序 #include<iostream> using namespace std; typedef int ElemengType; void Bubble_Sort(ElemengType A[], int N) {for (int p N - 1; p > 0; p--) {int flag 0;for (int i 0; i < p; i) {if (A[i] > A[i 1]) {swap(A[i], …

cocos creator 触摸事件官方案例

cocos creator 触摸事件官方案例 1、官方示例&#xff1b; 2、对官方示例的总结&#xff1a; &#xff08;1&#xff09;介绍了鼠标事件和触摸事件的类型&#xff0c;这里主要关注的是在移动端和PC端都可以触发的触摸事件。 &#xff08;2&#xff09;节点事件的派发&#…