jdbc map获取keys_跟我学shardingjdbc之分布式主键及其自定义
博客地址:朝·聞·道?www.wuwenliang.net本文是 “跟我學Sharding-JDBC” 系列的第三篇,我將帶領讀者一起了解下Sharding-JDBC的分布式主鍵,并實現業務性更強的自定義主鍵。
首先了解下,什么是分布式主鍵。
傳統的關系型數據庫,如MySQL中,數據庫本身自帶自增主鍵生成機制,但在分布式環境下,由于分庫分表導致數據水平拆分后無法使用單表自增主鍵,因此我們需要一種全局唯一id生成策略作為分布式主鍵。
當前業界已經有不少成熟的方案能夠解決分布式主鍵的生成問題,如:UUID、SnoWflake算法(Twitter)、Leaf算法(美團點評)等。
UUIDUUID是Universally Unique Identifier的縮寫,它是在一定的范圍內(從特定的名字空間到全球)唯一的機器生成的標識符。
UUID具有如下特點: 1. 經由一定的算法機器生成,算法定義了網卡MAC地址、時間戳、名字空間(Namespace)、隨機或偽隨機數、時序等元素,以及從這些元素生成UUID的算法。UUID的復雜特性在保證了其唯一性的同時,意味著只能由計算機生成。 2. 非人工指定,非人工識別。UUID的復雜性決定了“一般人“不能直接從一個UUID知道哪個對象和它關聯。 3. 在特定的范圍內重復的可能性極小。
UUID能夠保證最少在3000+年內不會重復。因此它的唯一性是很可靠的。但也有不足之處,就是可讀性差,不能直接用來做分片鍵并進行取模分庫表的操作,需要進行額外的開發,如:轉換UUID為unicode/ASCII碼,對數字進行疊加后取模。
SnoWflake
雪花算法(SnoWflake)是Twitter公布的分布式主鍵生成算法,也是ShardingSphere默認提供的配置分布式主鍵生成策略方式。在ShardingSphere的類路徑為:io.shardingsphere.core.keygen.DefaultKeyGenerator
SnoWflake能夠保證不同進程主鍵的不重復性,以及相同進程內主鍵的有序性。
在同一個進程中,SnoWflake首先是通過時間位保證不重復,如果時間相同則是通過序列位保證。 同時由于時間位是單調遞增的,且各個服務器如果大體做了時間同步,那么生成的主鍵在分布式環境可以認為是總體有序的,這就保證了對索引字段的插入的高效性。例如MySQL的Innodb存儲引擎的主鍵。
雪花算法生成的主鍵的二進制表示形式包含4部分,從高位到低位分別為:1bit符號位、41bit時間戳位、10bit工作進程位以及12bit序列號位。
雪花算法能夠保證全局唯一,同時也存在一些問題,如時鐘回撥可能導致產生重復序列。為了解決這個問題,ShardingSphere默認分布式主鍵生成器提供了一個最大容忍的時鐘回撥毫秒數。
如果時鐘回撥的時間超過最大容忍的毫秒數閾值,則程序報錯;如果在可容忍的范圍內,默認分布式主鍵生成器會等待時鐘同步到最后一次主鍵生成的時間后再繼續工作。 最大容忍的時鐘回撥毫秒數的默認值為0,可通過調用靜態方法DefaultKeyGenerator.setMaxTolerateTimeDifferenceMilliseconds()設置。
其他方案這里再簡單介紹下其他的分布式主鍵生成的方案。
Leaf算法
Redis計數器
我們還可以通過第三方的組件的特性二次開發自己的分布式id生成器。如:使用Redis的 INCR key自增計數器,它是 Redis 的原子性自增操作最直觀的模式,其原理相當簡單:每當某個操作發生時,向 Redis 發送一個 INCR 命令。
比如在一個 web 應用中,想知道用戶在一年中每天的點擊量,那么只要將用戶ID及相關的日期信息作為鍵,并在每次用戶點擊頁面時,執行一次自增操作即可。
它有著多種擴展模式,如: 1. 通過組合使用 INCR 和 EXPIRE達到只在規定的生存時間內進行計數(counting)的目的 2. 客戶端通過使用 GETSET 命令原子性地獲取計數器當前值并將計數器清零,更多信息請參考 GETSET 命令。 3. 通過用其他自增/自減操作,比如 DECR 和 INCRBY ,用戶可以在完成業務操作之后增加或減少計數器的值,如在游戲中的記分器就是一個典型的場景。
它的優點在于: 1. 不依賴數據庫且性能優于數據庫。 2. ID天然有序,對分頁或者需要排序的場景很友好。
但是它還存在如下的缺點: 1. 如果系統中沒有Redis需要引入Redis增加了系統復雜度。 2. 需要額外的編碼和配置工作。
但總體來講,這是個不錯的方案,分布式環境下,我們通過集群Redis能夠保證生成器高可用運行,集群之間通過復制能夠保證序列生成不會有單點故障。
Zookeeper
通過利用zookeeper的持久順序節點特性,多個客戶端同時創建同一節點,zk可以保證有序的創建,創建成功并返回的path類似于/root/generateid0000000001這樣的節點,能夠看到是順序有規律的。利用這個特性,我們能夠實現基于zk的分布式id生成器。
不過一般我們很少會使用zookeeper來生成唯一ID。主要是由于需要依賴zookeeper,并且是多步調用API,如果在競爭較大的情況下,需要考慮使用分布式鎖。因此,在高并發的分布式環境下,性能不甚理想。
MySQL自增id
這種方式很好理解,就是建立一張序列表,執行插入操作,并獲取記錄的id值。
它的優點如下: 1. 容易理解,開發量不多,且性能可以接受。 2. 通過自增主鍵生成的ID天然排序,對分頁或者需要排序的結果很有幫助。
同時它存在如下的缺點: 1. 不同數據庫語法的和實現不同,如果需要切換數據庫或多數據庫版本支持的時候需要在每個庫中單獨處理。 2. 在單數據庫或讀寫分離或一主多從的情況下,只有一個主庫可以生成。有單點故障風險。 3. id的生成與數據庫的性能強關聯。 4. 如果存在數據的遷移,則id序列表也需要同步遷移。 5. 分表分庫場景下會有麻煩。
當然這些問題都有針對的解決方案: 1. 對于不同的數據庫,只需要將id的生成作為單獨的服務開發,不同的業務通過接口調用id生成,屏蔽后方的實現細節 2. 針對主庫單點,可以改造為多Master架構 3. 如果條件允許,使用高性能磁盤及主機部署數據庫 4. 通過雙寫操作的方式進行數據遷移 5. 分庫分表場景下,只需要在每個數據分片上設置對應表的序列生成表即可,序列表與業務表使用相同的分片規則,這樣就能保證序列與業務是一一對應的,在每個片上,都是唯一且自增的。
我的選擇
通過了解各種分布式主鍵生成策略,我最終選擇了Redis的計數器作為自定義分布式主鍵的核心技術方案。
原因如下: 1. 業務id如果直接使用UUID、snowflake等可讀性較差,需要有業務屬性,最好能直觀的看到分片屬性 2. 業務中本身就引入了Redis集群,不需要額外的依賴 3. Redis方案開發簡單且可靠性強
基于Redis的分布式主鍵的自定義開發到此,我們對主流的分布式主鍵的生成策略進行了分析后選定了使用Redis的計數器進行開發,接下來就講解下如何實現業務友好的自定義分布式主鍵。
id格式解析
首先解析一下最終生成的ID的格式,舉個例子,如:生成訂單號如下:
OD00000101201903251029141503200002
從左往右依次為:
業務編碼(2位) + 庫下標(2位)+ 表下標(4位)
+ 序列版本號(默認為01,2位)+ 時間戳(yyMMddHHmmssSSS,精確到毫秒,15位)
+ 機器id(2位)
+ 序列號(5位)
共32位。
這個格式的id對于業務而言,可讀性更好,能夠直觀的看到是哪個業務的id,分布在哪個片上,是哪個時間生成的,比純數字的更加直觀。
開發過程-01-定義分布式主鍵格式
首先,我們定義分布式主鍵的格式,這里通過枚舉實現。
新建名為 DbAndTableEnum 的庫表規則枚舉類,根據上述id的格式,分別定義屬性如下
public enum DbAndTableEnum {
/**
* 用戶信息表 UD+db+table+01+yyMMddHHmmssSSS+機器id+序列號id
* 例如:UD000000011902261230103345300002 共 2+6+2+15+2+5=32位
*/
T_USER("t_user", "user_id", "01", "01", "UD", 2, 2, 4, 4, 16, "用戶數據表枚舉"),
T_NEW_ORDER("t_new_order", "order_id", "01", "01", "OD", 2,2, 4, 4, 8, "訂單數據表枚舉");
/**分片表名*/
private String tableName;
/**分片鍵*/
private String shardingKey;
/**系統標識*/
private String bizType;
/**主鍵規則版本*/
private String idVersion;
/**表名字母前綴*/
private String charsPrefix;
/**分片鍵值中純數字起始下標索引,第一位是0,第二位是1,依次類推*/
private int numberStartIndex;
/**數據庫索引位開始下標索引*/
private int dbIndexBegin;
/**表索引位開始下標索引*/
private int tbIndexBegin;
/**分布所在庫數量*/
private int dbCount;
/**分布所在表數量-所有庫中表數量總計*/
private int tbCount;
/**描述*/
private String desc;
...省略getter setter 構造方法...
這里我根據屬性,定義了我的demo中需要使用的兩個枚舉,分別為用戶表、訂單表的主鍵枚舉。以用戶表舉例:
T_USER("t_user", // 用戶邏輯表名
"user_id", // 用戶表分片鍵
"01", // 系統標識默認為01
"01", // 主鍵規則默認為01
"UD", // 用戶表前綴
2, // 分片鍵值中純數字起始下標,默認為2
2, // 數據庫索引位開始下標索引,同上,默認第二位
4, // 分片數量,eg:分4庫
4, // 每個分片中分表數量,每個片上4表
16, // 所有分片的分表總數
"用戶數據表枚舉"), // 描述
在不同的業務中,可以根據對應的業務定義對應的id枚舉,原則是:開發階段一定能夠知道當前id是為哪個業務準備的,也能夠事先預估好數據的容量。
開發過程-02-定義序列生成器接口并實現定義一個抽象序列接口,方便擴展
public interface SequenceGenerator {
/**
* @param targetEnum
* @param dbIndex
* @param tbIndex
* @return
*/
String getNextVal(DbAndTableEnum targetEnum, int dbIndex, int tbIndex);
}
由于我們使用了Redis作為序列生成器,因此只需要編寫SequenceGenerator的實現類,利用Redis的計數器實現序列生成操作getNextVal()即可。
@Component(value = "redisSequenceGenerator")
public class RedisSequenceGenerator implements SequenceGenerator {
/**序列生成器key前綴*/
public static String LOGIC_TABLE_NAME = "sequence:redis:";
/**序列長度=5,不足5位的用0填充*/
public static int SEQUENCE_LENGTH = 5;
/**序列最大值=90000*/
public static int sequence_max = 90000;
@Autowired
StringRedisTemplate stringRedisTemplate;
/**
* redis序列獲取實現方法
* @param targetEnum
* @param dbIndex
* @param tbIndex
* @return
*/
@Override
public String getNextVal(DbAndTableEnum targetEnum, int dbIndex, int tbIndex) {
//拼接key前綴
String redisKeySuffix = new StringBuilder(targetEnum.getTableName())
.append("_")
.append("dbIndex")
.append(StringUtil.fillZero(String.valueOf(dbIndex), ShardingConstant.DB_SUFFIX_LENGTH))
.append("_tbIndex")
.append(StringUtil.fillZero(String.valueOf(tbIndex), ShardingConstant.TABLE_SUFFIX_LENGTH))
.append("_")
.append(targetEnum.getShardingKey()).toString();
String increKey = new StringBuilder(LOGIC_TABLE_NAME).append(redisKeySuffix).toString();
long sequenceId = stringRedisTemplate.opsForValue().increment(increKey);
//達到指定值重置序列號,預留后10000個id以便并發時緩沖
if (sequenceId == sequence_max) {
stringRedisTemplate.delete(increKey);
}
// 返回序列值,位數不夠前補零
return StringUtil.fillZero(String.valueOf(sequenceId), SEQUENCE_LENGTH);
}
}
由于用到了StringRedisTemplate作為Redis操作工具,因此需要引入Redis并配置對應的參數,具體方法此處不贅述,請移步我的另一篇文章 《springboot整合redis小結》。
分析一下代碼邏輯,首先拼接了序列在redis中的key,將當前記錄所在的庫、表下標以及當前的表名和分片鍵名稱拼接在一起,在最前面拼接好當前key的功能,最終生成的key如下:
sequence:redis:t_new_order_dbIndex00_tbIndex0001_order_id
這個key表示:redis生成的sequence序列,序列所屬表為t_new_order,分片鍵為order_id,序列所屬庫下標為00庫,所屬表下標為0001表。
開發過程-03-實現自定義的KeyGen自定義主鍵生成器
上面的操作中,我們實現了核心的自增序列生成器,下面的內容中我們著手開發對業務暴露的生成器KeyGenerator的核心邏輯。
新建一個類,KeyGenerator.java標記為spring的一個Component。由于我們的業務基本上使用了Spring Boot框架,因此我開發的時候均通過Spring Bean的方式進行類定義。如果你要在非Spring框架中使用,需要自行完成Redis的連接等操作。
由于此處的邏輯較多,我只放核心的業務,完整的代碼煩請移步github的項目頁,本節的代碼已經上傳,sql腳本也同步更新了。項目地址:snowalker-shardingjdbc-demo
/**
* 根據路由id生成內部系統主鍵id,
* 路由id可以是內部其他系統主鍵id,也可以是外部第三方用戶id
* @param targetEnum 待生成主鍵的目標表規則配置
* @param relatedRouteId 路由id或外部第三方用戶id
* @return
*/
public String generateKey(DbAndTableEnum targetEnum, String relatedRouteId) {
if (StringUtils.isBlank(relatedRouteId)) {
throw new IllegalArgumentException("路由id參數為空");
}
StringBuilder key = new StringBuilder();
/** 1.id業務前綴*/
String idPrefix = targetEnum.getCharsPrefix();
/** 2.id數據庫索引位*/
String dbIndex = getDbIndexAndTbIndexMap(targetEnum, relatedRouteId).get("dbIndex");
/** 3.id表索引位*/
String tbIndex = getDbIndexAndTbIndexMap(targetEnum, relatedRouteId).get("tbIndex");
/** 4.id規則版本位*/
String idVersion = targetEnum.getIdVersion();
/** 5.id時間戳位*/
String timeString = DateUtil.formatDate(new Date());
/** 6.id分布式機器位 2位*/
String distributedIndex = getDistributedId(2);
/** 7.隨機數位*/
String sequenceId = sequenceGenerator.getNextVal(targetEnum, Integer.parseInt(dbIndex), Integer.parseInt(tbIndex));
/** 庫表索引靠前*/
return key.append(idPrefix)
.append(dbIndex)
.append(tbIndex)
.append(idVersion)
.append(timeString)
.append(distributedIndex)
.append(sequenceId).toString();
}
該方法為外部業務調用的生成主鍵的核心API,方法聲明為:
generateKey(DbAndTableEnum targetEnum, String relatedRouteId)
第一個參數為需要生成id的目標表的數據源/數據表枚舉,第二個參數為相對路由id。這里解釋一下相對路由id的含義。
在實際開發中,我們需要將外部的id轉換為內部的id使用,這樣既可以保證數據的分布均勻,又有利于數據安全。如:根據支付寶uid生成系統內部的用戶id。對外交互使用支付寶uid,內部統一使用內部的用戶id。
繼續我們的邏輯,當我們有了內部的用戶id之后,通過內部用戶id生成業務表id,如:賬戶id、訂單id等。由于賬戶id、用戶id使用同一個相對路由id(內部用戶id),賬戶信息與訂單信息使用了相同的路由規則,因此它們會位于同一個數據分片上,這樣就能在業務上保證同一個用戶的業務信息都在同一個數據分片上,單庫事務得以繼續使用,同庫內的join操作也能夠支持。由于所有的數據都在一個數據分片上,因此少了跨片join及跨片的歸并操作,查詢效率大幅度提升。
代碼邏輯很清晰,就是按位填充對應的參數,其中時間戳使用SimpleDateFormat的format方法獲取,這里使用ThreadLocal包裝SimpleDateFormat保證線程安全。
我們著重看下如何獲取庫表索引及分布式機器位,
獲取庫表索引
通過方法 getDbIndexAndTbIndexMap 獲取數據庫的庫表下標,代碼如下:
/**
* 根據已知路由id取出庫表索引,外部id和內部id均 進行ASCII轉換后再對庫表數量取模
* @param targetEnum 待生成主鍵的目標表規則配置
* @param relatedRouteId 路由id
* @return
*/
private Map getDbIndexAndTbIndexMap(DbAndTableEnum targetEnum,String relatedRouteId) {
Map map = new HashMap<>();
/** 獲取庫索引*/
String preDbIndex = String.valueOf(
getDbIndexByMod(
relatedRouteId,
targetEnum.getDbCount(),
targetEnum.getTbCount()));
String dbIndex = StringUtil.fillZero(preDbIndex, ShardingConstant.DB_SUFFIX_LENGTH);
/** 獲取表索引*/
String preTbIndex = String
.valueOf(StringUtil.getTbIndexByMod(relatedRouteId,targetEnum.getDbCount(),targetEnum.getTbCount()));
String tbIndex = StringUtil
.fillZero(preTbIndex,ShardingConstant.TABLE_SUFFIX_LENGTH);
map.put("dbIndex", dbIndex);
map.put("tbIndex", tbIndex);
return map;
}
public static long getDbIndexByMod(Object obj,int dbCount,int tbCount) {
long tbRange = getModValue(obj, tbCount);
BigDecimal bc = new BigDecimal(tbRange);
BigDecimal[] results = bc.divideAndRemainder(new BigDecimal(dbCount));
return (long)results[0].intValue();
}
/**
* 先對指定對象取ASCII碼后取模運算
* @param obj
* @param num
* @return
*/
public static long getModValue(Object obj,long num) {
String str = getAscII(obj == null?"":obj.toString());
BigDecimal bc = new BigDecimal(str);
BigDecimal[] results = bc.divideAndRemainder(new BigDecimal(num));
return (long)results[1].intValue();
}
首先轉換外部id為ASCII碼,通過該ASCII碼對庫取商,對表取余,得到庫表下標,并拼接到主鍵中,如圖:
此方案是針對ShardingJDBC的分片模式的,在ShardingJDBC中,每個分片中的數據庫表的結構是相同的,如:
db_00--
|--t_order_0000
|--t_order_0001
db_01--
|--t_order_0000
|--t_order_0001
db_02--
|--t_order_0000
|--t_order_0001
db_03--
|--t_order_0000
|--t_order_0001
獲取分布式機器id
接著看下如何獲取分布式機器id。
/**
* 生成id分布式機器位
* @return 分布式機器id
* length與hostCount位數相同
*/
private String getDistributedId(int length, int hostCount) {
return StringUtil
.fillZero(String.valueOf(getIdFromHostName() % hostCount), length);
}
/**
* 適配分布式環境,根據主機名生成id
* 分布式環境下,如:Kubernates云環境下,集群內docker容器名是唯一的
* 通過 @See org.apache.commons.lang3.SystemUtils.getHostName()獲取主機名
* @return
*/
private Long getIdFromHostName(){
//unicode code point
int[] ints = StringUtils.toCodePoints(SystemUtils.getHostName());
int sums = 0;
for (int i: ints) {
sums += i;
}
return (long)(sums);
}
這里我們通過StringUtils.toCodePoints(SystemUtils.getHostName());獲取到當前主機名的unicode值,并將每個字符的unicode值相加,這里只要保證我們服務器的名稱是唯一的,則codePoint值就是唯一的。例如:使用K8S進行部署的環境下,生成的docker容器的名稱是集群內唯一的,保證了getIdFromHostName()返回值的唯一性。
我們用主機名生成的codePoint值對全局主機數量進行取模操作,即可獲取當前id位于哪臺機器上。
又由于在整個序列中添加了精確到毫秒的時間戳以及使用了Redis的計數器,能夠大幅度的支撐高并發環境下的主鍵生成策略。只要不存在時鐘回撥,系統穩定的情況下,不存在主鍵碰撞的情況。
加餐:關于codePoint
我們之所以將主機名稱轉為CodePoint并疊加各個字符的CodePoint值,原因在于Unicode中每個字符的codePoint值是不同的,因此我們可以確定不同的主機名的CodePoint值也是不同的,因此可以根據該CodePoint的值去做機器節點的取模計算。
首先了解下什么是CodePoint,CodePoint(中文叫代碼點). wiki上關于CodePoint的解釋
CodePoint不同于pointCode, 前者是字符編碼的術語。后者更類似IP地址,用于標志網絡結點地址,wiki上關于PointCode的解釋。
ASCII字符集由于使用7bit表示字符,因此有128個CodePoint.
Extended ASCII字符集(擴展ASCII字符集)使用了8bit表示字符,因此有256個CodePoint.
而最新版Unicode6.2則擁有0x0~0x10FFFF個CodePoint. 總數可以達到1,114,112個,而目前全球只使用了110,182個來表示全世界所有語言的字符。這里可以看到Unicode的強大之處了,它真正做到了統一編碼。
我們可以認為CodePoint就是不同字符集用來表示字符的所有整數的范圍,且起點都是0.
舉例
這里以一個實例進行講解,準備這樣一個字符串:snowalker朝聞道夕死可矣
解析這個字符串每個字符的codePoint并疊加,代碼如下:
String snowalker = "snowalker朝聞道夕死可矣";
int [] snowalkerCodePoints = StringUtils.toCodePoints(snowalker);
long sum = 0;
for (int i = 0; i < snowalkerCodePoints.length; i++) {
sum += snowalkerCodePoints[i];
System.out.println("i=" + i + "--snowalkerCodePoints[" + i + "]=" + snowalkerCodePoints[i]);
}
System.out.println("sum=" + sum);
long sum2 = 0;
for (int i = 0; i < snowalkerCodePoints.length; i++) {
sum2 += snowalkerCodePoints[i];
System.out.println("原生方式--i=" + i + "--snowalkerCodePoints[" + i + "]=" + snowalker.codePointAt(i));
}
System.out.println("sum2=" + sum2);
兩種方式,分別為org.apache.commons.lang3.StringUtils.toCodePoints(String string) 以及 java.lang.String.codePointAt(int index)。
org.apache.commons.lang3.StringUtils.toCodePoints(String string)解析字符串后返回一個codePoint數組,遍歷數組并疊加。
java.lang.String.codePointAt(int index)從字符串的起始下標開始到結束下標為止,遍歷字符串的每個元素的codePoint并疊加。
運行程序,控制臺打印如下:
i=0--snowalkerCodePoints[0]=115
i=1--snowalkerCodePoints[1]=110
i=2--snowalkerCodePoints[2]=111
i=3--snowalkerCodePoints[3]=119
i=4--snowalkerCodePoints[4]=97
i=5--snowalkerCodePoints[5]=108
i=6--snowalkerCodePoints[6]=107
i=7--snowalkerCodePoints[7]=101
i=8--snowalkerCodePoints[8]=114
i=9--snowalkerCodePoints[9]=26397
i=10--snowalkerCodePoints[10]=38395
i=11--snowalkerCodePoints[11]=36947
i=12--snowalkerCodePoints[12]=22805
i=13--snowalkerCodePoints[13]=27515
i=14--snowalkerCodePoints[14]=21487
i=15--snowalkerCodePoints[15]=30691
sum=205219
原生方式--i=0--snowalkerCodePoints[0]=115
原生方式--i=1--snowalkerCodePoints[1]=110
原生方式--i=2--snowalkerCodePoints[2]=111
原生方式--i=3--snowalkerCodePoints[3]=119
原生方式--i=4--snowalkerCodePoints[4]=97
原生方式--i=5--snowalkerCodePoints[5]=108
原生方式--i=6--snowalkerCodePoints[6]=107
原生方式--i=7--snowalkerCodePoints[7]=101
原生方式--i=8--snowalkerCodePoints[8]=114
原生方式--i=9--snowalkerCodePoints[9]=26397
原生方式--i=10--snowalkerCodePoints[10]=38395
原生方式--i=11--snowalkerCodePoints[11]=36947
原生方式--i=12--snowalkerCodePoints[12]=22805
原生方式--i=13--snowalkerCodePoints[13]=27515
原生方式--i=14--snowalkerCodePoints[14]=21487
原生方式--i=15--snowalkerCodePoints[15]=30691
sum2=205219
可以看到,兩種方式獲取到的unicode的codePoint是相同的,通過這些方式我們就可以完成很多需求,如:本文中我們就是通過這種方式去解析主機名并轉換為集群節點id。也可以通過這個方法,進行分片算法的開發,思路為:遍歷主鍵的所有元素,疊加元素的codePoint并對庫表取模,進行數據的分片。
總結到這里,我們就完成了自定義分布式主鍵的自定義操作,詳細的代碼請訪問:
項目地址:snowalker-shardingjdbc-demo
在本文中,我們分析了多種分布式主鍵的生成策略及其優缺點,最終選擇了Redis作為序列的生成器。并基于Redis序列生成器開發了可讀性更好的主鍵生成工具,在接下來的文章中,我將使用該主鍵生成器,配合Sharding-JDBC的自定義分庫分表策略,將Sharding-JDBC的使用更加推向實戰化。希望本文的思路能夠對讀者開發自己的主鍵生成組件有所啟發。
總結
以上是生活随笔為你收集整理的jdbc map获取keys_跟我学shardingjdbc之分布式主键及其自定义的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: aspose 换行写_aspose.wo
- 下一篇: 字节跳动测试开发4轮面试_字节跳动201