伴隨著業(yè)務(wù)的不斷發(fā)展,逐漸由單庫(kù)單表向分庫(kù)分表進(jìn)行發(fā)展。在這個(gè)過(guò)程中不可避免的一個(gè)問(wèn)題是確保主鍵要的唯一性,以便于后續(xù)的數(shù)據(jù)聚合、分析等等場(chǎng)景的使用。在進(jìn)行分庫(kù)分表的解決方案中有多種技術(shù)選型,大概分為兩大類客戶端分庫(kù)分表、服務(wù)端分庫(kù)分表。例如 Sharding-JDBC、ShardingSphere、 MyCat、 ShardingSphere-Proxy等等。
在這個(gè)燥熱的夏天,又突然收到告警,分庫(kù)分表的主鍵沖突了,這還能忍?不,堅(jiān)決不能忍,必須解決掉!后面咱們慢慢道來(lái)是如何破局的,如何走了一條坎坷路……
翻山第一步
咱們的系統(tǒng)使用的是ShardingSphere進(jìn)行分庫(kù)分表的,大概的配置信息如下: (出于信息的安全考慮,隱藏了部分信息,只保留的部分內(nèi)容,不要在意這些細(xì)節(jié)能說(shuō)明問(wèn)題即可)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sharding="http://shardingsphere.apache.org/schema/shardingsphere/sharding"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd http://shardingsphere.apache.org/schema/shardingsphere/sharding http://shardingsphere.apache.org/schema/shardingsphere/sharding/sharding.xsd">
<!--數(shù)據(jù)源-->
<bean id="database1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
</bean>
<bean id="database2" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
</bean>
<bean id="database3" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
</bean>
<sharding:inline-strategy id="databaseStrategy" sharding-column="cloum1" algorithm-expression="table1_$->{(Math.abs(cloum1.hashCode()) % 512).intdiv(32) }" />
<sharding:inline-strategy id="orderNoDatabaseStrategy" sharding-column="cloum2" algorithm-expression="table2_$->{(Math.abs(cloum2.hashCode()) % 512).intdiv(32) }" />
<sharding:inline-strategy id="businessNoDatabaseStrategy" sharding-column="cloum3" algorithm-expression="table3_$->{(Math.abs(cloum3.hashCode()) % 512).intdiv(32) }" />
<!-- 主鍵生成策略 -雪花算法-->
<sharding:key-generator id="idKeyGenerator" type="SNOWFLAKE" column="id" props-ref="snowFlakeProperties"/>
<sharding:data-source id="dataSource">
<sharding:sharding-rule data-source-names="database1,database2,database3">
<sharding:table-rules>
<sharding:table-rule logic-table="table1"
actual-data-nodes="database1_$->{0..15}.table1_$->{0..31}"
database-strategy-ref="orderNoDatabaseStrategy"
table-strategy-ref="order_waybill_tableStrategy"
key-generator-ref="idKeyGenerator"/>
<sharding:table-rule logic-table="table2"
actual-data-nodes="database2_$->{0..15}.table2_$->{0..31}"
database-strategy-ref="databaseStrategy"
table-strategy-ref="waybill_contacts_tableStrategy"
key-generator-ref="idKeyGenerator"/>
<sharding:table-rule logic-table="table3"
actual-data-nodes="database3_$->{0..15}.table3->{0..31}"
database-strategy-ref="databaseStrategy"
table-strategy-ref="waybill_tableStrategy"
key-generator-ref="idKeyGenerator"/>
</sharding:table-rules>
</sharding:sharding-rule>
</sharding:data-source>
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:spring/mybatis-env-setting.xml"/>
<property name="mapperLocations" value="classpath*:/mapper/*.xml"/>
</bean>
</beans>
從上面的配置可以看出配置的是"SNOWFLAKE" 主鍵使用的是雪花算法,雪花算法產(chǎn)生的ID的組成總計(jì)64位,第一位為符號(hào)位不用,后41位為時(shí)間戳用于區(qū)別不同的時(shí)間點(diǎn),在后面10位為workId用于區(qū)別不同的機(jī)器,最后12位為sequence用于同一時(shí)刻同一機(jī)器的并發(fā)數(shù)量。
那接下來(lái)就看看咱們自己的系統(tǒng)是怎么配置的吧,其中的屬性snowFlakeProperties配置如下,其中的max.vibration.offset配置表示sequence的范圍為1024。按照正常的理解任何單個(gè)機(jī)器的配置都很難達(dá)到這個(gè)并發(fā)量,難道是這個(gè)值沒(méi)有生效?
<sharding:key-generator id="idKeyGenerator" type="SNOWFLAKE" column="id" props-ref="snowFlakeProperties"/>
shardingsphere中實(shí)現(xiàn)獲取主鍵的實(shí)現(xiàn)源碼如下簡(jiǎn)述,具體的實(shí)現(xiàn)類org.apache.shardingsphere.core.strategy.keygen.SnowflakeShardingKeyGenerator,從源碼看源碼竟然一個(gè)日志都沒(méi)有,那讓咱們?cè)趺慈ヅ挪槟??怎么證明咱們的猜想是否正確的呢?囧……
真是敗也蕭何成也蕭何,shardingsphere是通過(guò)java的SPI的方式進(jìn)行的主鍵生成策略的擴(kuò)展。自定義實(shí)現(xiàn)方式如下:實(shí)現(xiàn)org.apache.shardingsphere.spi.keygen.ShardingKeyGenerator接口,如果自己想要實(shí)現(xiàn)使用SPI方式進(jìn)行加載即可,那就讓咱們自己加日志吧,走你……
既然都自己寫(xiě)實(shí)現(xiàn)了,那日志就該加的都加吧,咱這絕不吝嗇這幾行日志
修改主鍵選擇生成策略為自己實(shí)現(xiàn)的類 type=“MYSNOWFLAKE”
<sharding:key-generator id="idKeyGenerator" type="MYSNOWFLAKE" column="id" props-ref="snowFlakeProperties"/>
啟動(dòng)看日志,屬性中有max.vibration.offset=1024這個(gè)屬性,竟然依舊拿到的還是默認(rèn)的值1,驚訝中,細(xì)細(xì)一瞅,終究發(fā)現(xiàn)了問(wèn)題,在getProperty(key)時(shí)如果返回的不是String類型那么為null,進(jìn)而取值默認(rèn)值1。從咱們的系統(tǒng)配置中可以看到系統(tǒng)配置的int類型的的1024,所以取值默認(rèn)值1就說(shuō)通了。
INFO 2023-08-17 14:07:51.062 2174320.63604.16922524693996408 176557 com.jd.las.waybill.center.config.MySnowflakeShardingKeyGenerator.getMaxVibrationOffset(MySnowflakeShardingKeyGenerator.java:107) -- 選擇自定義的雪花算法獲取到的properties={"max.vibration.offset":1024,"worker.id":"217","max.tolerate.time.difference.milliseconds":"3000"}
INFO 2023-08-17 14:07:51.063 2174320.63604.16922524693996408 176558 com.jd.las.waybill.center.config.MySnowflakeShardingKeyGenerator.getMaxVibrationOffset(MySnowflakeShardingKeyGenerator.java:110) -- 選擇自定義的雪花算法獲取到的getMaxVibrationOffset=1
截止到目前主鍵重復(fù)的問(wèn)題終于可以解釋的通了,因?yàn)椴l(fā)支持的是0~1總共2個(gè)并發(fā),所以在生產(chǎn)系統(tǒng)中尤其出現(xiàn)生產(chǎn)波次的時(shí)候出現(xiàn)重復(fù)值的可能性是存在的,然后把1024變成字符串修改上線,相信系統(tǒng)后面應(yīng)該不會(huì)產(chǎn)生此類問(wèn)題了。
越嶺第二步
如果生活總是喜歡跟你開(kāi)玩笑,逗你玩,那你就配合它笑一笑吧。
當(dāng)上完線后,過(guò)了一段時(shí)間發(fā)現(xiàn)重復(fù)主鍵的問(wèn)題竟然依舊存在只是頻率少了些,不科學(xué)呀……
重新梳理思路,進(jìn)行更詳細(xì)的日志輸出,下單進(jìn)行驗(yàn)證,將接單落庫(kù)這坨代碼一并都加上日志以及觸發(fā)雪花算法的生成的ID也加上日志
通過(guò)日志分析,又又又發(fā)現(xiàn)"靈異事件",10條插入SQL,只有兩條觸發(fā)了shardingsphere的雪花算法,詫異的很呀~
查看其他8張表落庫(kù)的ID數(shù)據(jù)如下圖,ID(1692562397556875266) 都為1692開(kāi)頭且長(zhǎng)度20位,而shardingsphere產(chǎn)生的ID(899413419526356993)都為899開(kāi)頭且長(zhǎng)度19位,很明顯這8張表的主鍵不是shardingsphere生成的,那是這20位的數(shù)據(jù)哪來(lái)的呢???從ID上看明顯也不是自增產(chǎn)生的主鍵,又不科學(xué)了……
又是一個(gè)深夜……
梳理思路重新在鋝,主鍵相關(guān)的除了數(shù)據(jù)自增長(zhǎng)、shardingsphere配置的雪花還有唯一的一個(gè)相關(guān)的組件那就是mybatis,由于項(xiàng)目是很早之前的應(yīng)用了,使用的是baomidou的mybaits插件,如圖是插件的入口,MybatisSqlSessionFactoryBean實(shí)現(xiàn)FactoryBean, InitializingBean, ApplicationListener幾個(gè)Spring的接口
baomidou涉及該塊問(wèn)題的源碼如下:
如果GlobalConfig沒(méi)有配置workId和DataCenterId會(huì)使用無(wú)參構(gòu)造,默認(rèn)的workId
baomidou的雪花算法和shardingphere思路一致有一點(diǎn)點(diǎn)區(qū)別在于第12位和22位有datacenter<<17|workId<<12獲取,且datacenter和workId需要在0~31之間
不同機(jī)器的Name:
所以又解釋了為什么不同機(jī)器會(huì)出現(xiàn)相同的主鍵問(wèn)題,但是如果有細(xì)心的同學(xué)就會(huì)問(wèn)為啥10張表中有8張表走的是baomidou的雪花算法呢,那是因?yàn)閎aomidou會(huì)判斷保存的入?yún)?shí)體bean上是否有id字段,是否能匹配上該字段,如果存在則在baomidou這層就給賦值了baomidou雪花算法生產(chǎn)的ID,后續(xù)就不會(huì)再次觸發(fā)shardingsphere中ID生成,進(jìn)而導(dǎo)致該問(wèn)題。
截止到目前終于又解釋通了為什么跨機(jī)器會(huì)產(chǎn)生相同的主鍵問(wèn)題。
問(wèn)題的解決方式:
baomidou配置的過(guò)程中指定workId和centerDataId,但是需要確保centerDataId<<17|workId<<12確保唯一。類比shardingphere,借用shardingphere中的12~22位唯一數(shù),前5高位給(centerDataId<<17),后5低位給workId<<12;
夜已沉默……
生產(chǎn)環(huán)境已上線驗(yàn)證通過(guò)
作者:京東物流 王義杰文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-682973.html
來(lái)源:京東云開(kāi)發(fā)者社區(qū) 自猿其說(shuō)Tech 轉(zhuǎn)載請(qǐng)注明來(lái)源文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-682973.html
到了這里,關(guān)于破局主鍵重復(fù)問(wèn)題的坎坷路 | 京東物流技術(shù)團(tuán)隊(duì)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!