使用Redis實現(xiàn)數(shù)據(jù)庫對象自增ID的方法
在分布式項目中,數(shù)據(jù)表的主鍵ID一般可能存在于UUID或自增ID這兩種形式,UUID好理解而且實現(xiàn)起來也最容易,但是缺點就是數(shù)據(jù)表中的主鍵ID是32位的字符串,在大數(shù)據(jù)查詢等情況下性能會相對比較差,所以在需求允許的情況下,我們通常會優(yōu)先考慮使用自增ID來代替UUID使用。
在分布式項目中如果你的數(shù)據(jù)表的主鍵ID是自增ID,那么常見的生成對象主鍵ID的方式有:
雪花算法
優(yōu)點:實現(xiàn)簡單
缺點:生成ID較長、生成ID不連續(xù),會造成ID浪費
采用框架自帶的ID生成器,如MybatisPlus的@AutoID
優(yōu)點:依賴框架,實現(xiàn)簡單
缺點:無法在插入對象前獲取到對象的主鍵ID
采用Redis緩存生成主鍵ID等
優(yōu)點:生成的ID連續(xù),數(shù)據(jù)在表中的可讀性好
缺點:借助Redis緩存,實現(xiàn)相比前兩種較復(fù)雜
這篇文章我們主要介紹如何通過Redis來實現(xiàn)生成對象自增ID的方法。
1、緩存實現(xiàn)原理
通過Redis實現(xiàn)對象自增ID的方式,主要是通過Redis的INCR
和 INCRBY
命令來對鍵值進行遞增操作,從而實現(xiàn)計數(shù)器的功能,主要原因是Redis 是單線程模型,所有命令都是原子操作,不會發(fā)生競態(tài)條件,
在使用時要留意以下特點與注意事項
原子性:
INCR
、INCRBY
和INCRBYFLOAT
命令都是原子性的,這意味著如果多個客戶端同時執(zhí)行這些命令,Redis 會保證每個命令的執(zhí)行不會發(fā)生競態(tài)條件。數(shù)據(jù)類型要求:這些命令要求操作的值是整數(shù)(
INCR
和INCRBY
)或浮點數(shù)(INCRBYFLOAT
)。如果鍵對應(yīng)的值不是數(shù)值類型,Redis 會返回錯誤。性能:Redis 是單線程的,所有命令都是原子操作,因此在高并發(fā)環(huán)境下執(zhí)行這些命令時,性能表現(xiàn)非常好。
鍵不存在的情況:如果執(zhí)行
INCR
或INCRBY
時,鍵不存在,Redis 會自動創(chuàng)建這個鍵并初始化其值為 0,然后進行遞增。
2、Redis工具類實現(xiàn)ID自動生成
了解到通過Redis實現(xiàn)的原理之后,就是如何設(shè)計緩存以保證每張表的數(shù)據(jù)之間不會相互影響,
以user表為例,對應(yīng)的model為UserModel,那么我們就可以將model名稱作為key,當(dāng)前已有的最大的ID作為value來進行存儲,因為每一張表對應(yīng)的model名稱都是不一樣的,所以這里一定不會出現(xiàn)key重復(fù)的情況,
下面是一些在Redis中生成和獲取對象自增ID的工具方法,方便新建對象時直接調(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,指定遞增長度,用于批量創(chuàng)建對象時,只請求一次Redis * 如批量創(chuàng)建五個對象,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ù)熱,用于項目啟動時初始化Redis中各個表的最大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)); } }
解釋:InitializingBean是Spring提供的拓展性接口,InitializingBean接口為bean提供了屬性初始化后的處理方法,它只有一個afterPropertiesSet方法,凡是繼承該接口的類,在bean的屬性初始化后都會執(zhí)行該方法。
3、緩存預(yù)熱
緩存預(yù)熱是指在項目啟動時,將每一張表當(dāng)前最大的主鍵ID預(yù)先加載到Redis中,這樣在后面使用的時候,就可以直接從Redis中獲取下一次ID即可,不需要再去訪問數(shù)據(jù)庫查詢最大ID,緩存預(yù)熱一般會建立在Redis專門的初始化類中,以便在啟動項目時可以運行該類,具體如下:
/** * 緩存預(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()); } }
解釋:@DependsOn注解可以定義在類和方法上,意思是我這個組件要依賴于另一個組件,也就是說被依賴的組件會比該組件先注冊到IOC容器中。
解釋:@PostConstruct注解
在Java中,@PostConstruct注解,通常用于標記一個方法,它表示該方法在類實例化之后(通過構(gòu)造函數(shù)創(chuàng)建對象之后)立即執(zhí)行。
加上@PostConstruct注解的方法會在對象的所有依賴項都已經(jīng)注入完成之后執(zhí)行。通過使用@PostConstruct注解,我們可以確保在對象完全創(chuàng)建和初始化之后才執(zhí)行這些操作。這個注解通常用在依賴注入(Dependency Injection)的框架中,例如Spring。
@PostConstruct 注解可以用在任何類的方法上,但它最常用于標記在 Spring Framework 中的 Bean 類中的初始化方法。
4、model層封裝調(diào)用
在實例化一個model對象的時候,要將model的主鍵ID進行賦值,這個時候就要從Redis中獲取到當(dāng)前對象應(yīng)該對應(yīng)的主鍵ID,我們可以在model類中構(gòu)建一個newInstance方法或有參構(gòu)造,來專門的生成需要賦值主鍵ID的對象,在方法中調(diào)用redis工具類的getIncr方法,獲取到最新最小未使用的主鍵ID并賦值給新建的model即可。
@Getter @Setter public class UserModel { private Integer id; private String name; private Integer age; private String tel; public UserModel() { } /** * 提供一個能夠生成自增ID的有參構(gòu)造,需要生成自增ID時調(diào)用 */ public UserModel(boolean autoId) { if (autoId) { this.id = RedisUtils.getIncr(UserModel.class); } } }
如果你是需要批量創(chuàng)建對象并且給對象賦值主鍵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(); //每一個主鍵ID依次遞減使用 userModel.setId(incr--); userModels.add(userModel); } userMapper.insertBatch(userModels); } }
至此,通過Redis來獲取對象自增ID的方法已經(jīng)完成,如果在使用過程中有其他場景需求,可以對redisUtils中的方法進行拓展即可。
到此這篇關(guān)于使用Redis實現(xiàn)生成對象自增ID的方法的文章就介紹到這了,更多相關(guān)Redis實現(xiàn)對象自增ID內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Redis集群水平擴展、集群中添加以及刪除節(jié)點的操作
這篇文章主要介紹了Redis集群水平擴展、集群中添加以及刪除節(jié)點的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03redis中使用redis-dump導(dǎo)出、導(dǎo)入、還原數(shù)據(jù)實例
這篇文章主要介紹了redis中使用redis-dump導(dǎo)出、導(dǎo)入、還原數(shù)據(jù)實例,本文直接給出操作命令,并給出注釋加以說明,需要的朋友可以參考下2014-11-11Redis 8種基本數(shù)據(jù)類型及常用命令和數(shù)據(jù)類型的應(yīng)用場景小結(jié)
Redis是一種基于內(nèi)存操作的數(shù)據(jù)庫,其中多虧于高效的數(shù)據(jù)結(jié)構(gòu),本文主要介紹了Redis 8種基本數(shù)據(jù)類型及常用命令和數(shù)據(jù)類型的應(yīng)用場景小結(jié),具有一定的參考價值,感興趣的可以了解一下2024-03-03