分布式使用Redis實(shí)現(xiàn)數(shù)據(jù)庫(kù)對(duì)象自增主鍵ID
在分布式項(xiàng)目中,數(shù)據(jù)表的主鍵ID一般可能存在于UUID或自增ID這兩種形式,UUID好理解而且實(shí)現(xiàn)起來(lái)也最容易,但是缺點(diǎn)就是數(shù)據(jù)表中的主鍵ID是32位的字符串,在大數(shù)據(jù)查詢(xún)等情況下性能會(huì)相對(duì)比較差,所以在需求允許的情況下,我們通常會(huì)優(yōu)先考慮使用自增ID來(lái)代替UUID使用。
在分布式項(xiàng)目中如果你的數(shù)據(jù)表的主鍵ID是自增ID,那么常見(jiàn)的生成對(duì)象主鍵ID的方式有:
雪花算法
優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單
缺點(diǎn):生成ID較長(zhǎng)、生成ID不連續(xù),會(huì)造成ID浪費(fèi)
采用框架自帶的ID生成器,如MybatisPlus的@AutoID
優(yōu)點(diǎn):依賴(lài)框架,實(shí)現(xiàn)簡(jiǎn)單
缺點(diǎn):無(wú)法在插入對(duì)象前獲取到對(duì)象的主鍵ID
采用Redis緩存生成主鍵ID等
優(yōu)點(diǎn):生成的ID連續(xù),數(shù)據(jù)在表中的可讀性好
缺點(diǎn):借助Redis緩存,實(shí)現(xiàn)相比前兩種較復(fù)雜
通過(guò)雪花算法生成對(duì)象主鍵ID的方式我們?cè)谥暗奈恼轮幸呀?jīng)介紹過(guò)了,這篇文章我們主要介紹如何通過(guò)Redis來(lái)實(shí)現(xiàn)生成對(duì)象自增ID的方法。
1、緩存實(shí)現(xiàn)原理
通過(guò)Redis實(shí)現(xiàn)對(duì)象自增ID的方式,主要是通過(guò)Redis的INCR 和 INCRBY 命令來(lái)對(duì)鍵值進(jìn)行遞增操作,從而實(shí)現(xiàn)計(jì)數(shù)器的功能,主要原因是Redis 是單線程模型,所有命令都是原子操作,不會(huì)發(fā)生競(jìng)態(tài)條件,
在使用時(shí)要留意以下特點(diǎn)與注意事項(xiàng)
原子性:
INCR、INCRBY和INCRBYFLOAT命令都是原子性的,這意味著如果多個(gè)客戶(hù)端同時(shí)執(zhí)行這些命令,Redis 會(huì)保證每個(gè)命令的執(zhí)行不會(huì)發(fā)生競(jìng)態(tài)條件。數(shù)據(jù)類(lèi)型要求:這些命令要求操作的值是整數(shù)(
INCR和INCRBY)或浮點(diǎn)數(shù)(INCRBYFLOAT)。如果鍵對(duì)應(yīng)的值不是數(shù)值類(lèi)型,Redis 會(huì)返回錯(cuò)誤。性能:Redis 是單線程的,所有命令都是原子操作,因此在高并發(fā)環(huán)境下執(zhí)行這些命令時(shí),性能表現(xiàn)非常好。
鍵不存在的情況:如果執(zhí)行
INCR或INCRBY時(shí),鍵不存在,Redis 會(huì)自動(dòng)創(chuàng)建這個(gè)鍵并初始化其值為 0,然后進(jìn)行遞增。
2、Redis工具類(lèi)實(shí)現(xiàn)ID自動(dòng)生成
了解到通過(guò)Redis實(shí)現(xiàn)的原理之后,就是如何設(shè)計(jì)緩存以保證每張表的數(shù)據(jù)之間不會(huì)相互影響,
以u(píng)ser表為例,對(duì)應(yīng)的model為UserModel,那么我們就可以將model名稱(chēng)作為key,當(dāng)前已有的最大的ID作為value來(lái)進(jìn)行存儲(chǔ),因?yàn)槊恳粡埍韺?duì)應(yīng)的model名稱(chēng)都是不一樣的,所以這里一定不會(huì)出現(xiàn)key重復(fù)的情況,
下面是一些在Redis中生成和獲取對(duì)象自增ID的工具方法,方便新建對(duì)象時(shí)直接調(diào)用
@Component("redisUtils")
@RequiredArgsConstructor
@Slf4j
public class RedisUtils implements InitializingBean {
private final StringRedisTemplate stringRedisTemplate;
private static RedisUtils redisUtils;
@Override
public 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,指定遞增長(zhǎng)度,用于批量創(chuàng)建對(duì)象時(shí),只請(qǐng)求一次Redis
* 如批量創(chuàng)建五個(gè)對(duì)象,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);
}
}
/**
* 緩存預(yù)熱,用于項(xiàng)目啟動(dòng)時(shí)初始化Redis中各個(gè)表的最大ID
*/
public static <T> long incrPreheat(Class<T> tClass, Long maxValue) {
if (maxValue == null) {
maxValue = 0L;
}
if (maxValue < 0) {
//異常拋出,預(yù)熱的初始值不能小于0
}
Integer incr = getIncr(tClass);
if (maxValue <= incr) {
return incr;
}
return getIncr(tClass, (int) (maxValue - incr));
}
}解釋?zhuān)篒nitializingBean是Spring提供的拓展性接口,InitializingBean接口為bean提供了屬性初始化后的處理方法,它只有一個(gè)afterPropertiesSet方法,凡是繼承該接口的類(lèi),在bean的屬性初始化后都會(huì)執(zhí)行該方法。
3、緩存預(yù)熱
緩存預(yù)熱是指在項(xiàng)目啟動(dòng)時(shí),將每一張表當(dāng)前最大的主鍵ID預(yù)先加載到Redis中,這樣在后面使用的時(shí)候,就可以直接從Redis中獲取下一次ID即可,不需要再去訪問(wèn)數(shù)據(jù)庫(kù)查詢(xún)最大ID,緩存預(yù)熱一般會(huì)建立在Redis專(zhuān)門(mén)的初始化類(lèi)中,以便在啟動(dòng)項(xiàng)目時(shí)可以運(yùn)行該類(lèi),具體如下:
/**
* 緩存預(yù)熱
*
* @author hxy
*/
@Component
@DependsOn("redisUtils")
@RequiredArgsConstructor
public class RedisInit {
private final UserMapper userMapper;
/**
* 獲取每一張表中當(dāng)前的最大ID,然后預(yù)熱到redis中
*/
@PostConstruct
public void init() {
RedisUtils.incrPreheat(UserModel.class, userMapper.maxId());
}
}解釋?zhuān)篅DependsOn注解可以定義在類(lèi)和方法上,意思是我這個(gè)組件要依賴(lài)于另一個(gè)組件,也就是說(shuō)被依賴(lài)的組件會(huì)比該組件先注冊(cè)到IOC容器中。
解釋?zhuān)篅PostConstruct注解
在Java中,@PostConstruct注解,通常用于標(biāo)記一個(gè)方法,它表示該方法在類(lèi)實(shí)例化之后(通過(guò)構(gòu)造函數(shù)創(chuàng)建對(duì)象之后)立即執(zhí)行。
加上@PostConstruct注解的方法會(huì)在對(duì)象的所有依賴(lài)項(xiàng)都已經(jīng)注入完成之后執(zhí)行。通過(guò)使用@PostConstruct注解,我們可以確保在對(duì)象完全創(chuàng)建和初始化之后才執(zhí)行這些操作。這個(gè)注解通常用在依賴(lài)注入(Dependency Injection)的框架中,例如Spring。
@PostConstruct 注解可以用在任何類(lèi)的方法上,但它最常用于標(biāo)記在 Spring Framework 中的 Bean 類(lèi)中的初始化方法。
4、model層封裝調(diào)用
在實(shí)例化一個(gè)model對(duì)象的時(shí)候,要將model的主鍵ID進(jìn)行賦值,這個(gè)時(shí)候就要從Redis中獲取到當(dāng)前對(duì)象應(yīng)該對(duì)應(yīng)的主鍵ID,我們可以在model類(lèi)中構(gòu)建一個(gè)newInstance方法或有參構(gòu)造,來(lái)專(zhuān)門(mén)的生成需要賦值主鍵ID的對(duì)象,在方法中調(diào)用redis工具類(lèi)的getIncr方法,獲取到最新最小未使用的主鍵ID并賦值給新建的model即可。
@Getter
@Setter
public class UserModel {
private Integer id;
private String name;
private Integer age;
private String tel;
public UserModel() {
}
/**
* 提供一個(gè)能夠生成自增ID的有參構(gòu)造,需要生成自增ID時(shí)調(diào)用
*/
public UserModel(boolean autoId) {
if (autoId) {
this.id = RedisUtils.getIncr(UserModel.class);
}
}
}如果你是需要批量創(chuàng)建對(duì)象并且給對(duì)象賦值主鍵ID的情況,可以按照如下方式使用,這樣可以只調(diào)用一次Redis,避免重復(fù)調(diào)用Redis影響性能。
@Service
@RequiredArgsConstructor
public class UserServiceImpl {
private final UserMapper userMapper;
public void batchAddUser(){
int addNumber = 10;
//提前生成自定跨度的ID,比如之前最大ID是6,獲取后得到的incr是16
Integer incr = RedisUtils.getIncr(UserModel.class, addNumber);
List<UserModel> userModels = new ArrayList<>();
for (int i = 0; i < addNumber; i++) {
UserModel userModel = new UserModel();
//每一個(gè)主鍵ID依次遞減使用
userModel.setId(incr--);
userModels.add(userModel);
}
userMapper.insertBatch(userModels);
}
}至此,通過(guò)Redis來(lái)獲取對(duì)象自增ID的方法已經(jīng)完成,如果在使用過(guò)程中有其他場(chǎng)景需求,可以對(duì)redisUtils中的方法進(jìn)行拓展即可。
到此這篇關(guān)于分布式使用Redis實(shí)現(xiàn)數(shù)據(jù)庫(kù)對(duì)象自增主鍵ID的文章就介紹到這了,更多相關(guān)Redis對(duì)象自增主鍵ID內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis SETEX命令實(shí)現(xiàn)鍵值對(duì)管理
本文主要介紹了Redis SETEX命令實(shí)現(xiàn)鍵值對(duì)管理,SETEX命令用于設(shè)置具有過(guò)期時(shí)間的鍵值對(duì),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
Redis Desktop Manager(Redis可視化工具)安裝及使用圖文教程
這篇文章主要介紹了Redis Desktop Manager(Redis可視化工具)安裝及使用圖文教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
Redis生成分布式系統(tǒng)全局唯一ID的實(shí)現(xiàn)
在互聯(lián)網(wǎng)系統(tǒng)中,并發(fā)越大的系統(tǒng),數(shù)據(jù)就越大,數(shù)據(jù)越大就越需要分布式,本文主要介紹了Redis生成分布式系統(tǒng)全局唯一ID的實(shí)現(xiàn),感興趣的可以了解一下2021-10-10
Redis設(shè)置Hash數(shù)據(jù)類(lèi)型的過(guò)期時(shí)間
在Redis中,我們可以使用Hash數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)一組鍵值對(duì),而有時(shí)候,我們可能需要設(shè)置這些鍵值對(duì)的過(guò)期時(shí)間,本文主要介紹了Redis設(shè)置Hash數(shù)據(jù)類(lèi)型的過(guò)期時(shí)間,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01
SpringBoot3+Redis實(shí)現(xiàn)分布式鎖的配置方法
這篇文章主要介紹了SpringBoot3+Redis實(shí)現(xiàn)分布式鎖,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-07-07
redis數(shù)據(jù)結(jié)構(gòu)之intset的實(shí)例詳解
這篇文章主要介紹了redis數(shù)據(jù)結(jié)構(gòu)之intset的實(shí)例詳解的相關(guān)資料, intset也即整數(shù)集合,當(dāng)集合保存的值數(shù)量不多時(shí),redis使用intset作為其底層數(shù)據(jù)保存結(jié)構(gòu),希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09
Redis數(shù)據(jù)遷移RedisShake的實(shí)現(xiàn)方法
本文主要介紹了Redis數(shù)據(jù)遷移RedisShake的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04

