水平遷庫:保證數(shù)據(jù)一致性、多數(shù)據(jù)源事務(wù)處理

背景

為了降低訂單系統(tǒng)的復(fù)雜度和壓力豆茫,計(jì)劃將順風(fēng)車相關(guān)的業(yè)務(wù)侨歉、內(nèi)容抽取獨(dú)立成新的子系統(tǒng)——順風(fēng)車系統(tǒng)。摒棄之前的convenient澜薄,以hitch命名为肮。

尿點(diǎn)、痛點(diǎn)
  • 新系統(tǒng)都必須采用mysql數(shù)據(jù)庫替換sqlServer數(shù)據(jù)庫肤京,由于業(yè)務(wù)需要颊艳,不能先進(jìn)行數(shù)據(jù)遷移再上線,必須上線同時(shí)實(shí)現(xiàn)平滑水平遷庫忘分,做到用戶無感知棋枕。
  • 如何保證遷庫前后的數(shù)據(jù)一致性?
  • 如何處理多數(shù)據(jù)源的數(shù)據(jù)一致性問題妒峦?
  • redis新集群與訂單系統(tǒng)的redis集群也要做到平滑遷庫重斑。
  • 原本依賴順風(fēng)車相關(guān)的業(yè)務(wù)操作抽象成遠(yuǎn)程服務(wù)

SqlServert ---> Mysql 數(shù)據(jù)遷移方案 -- 雙寫

經(jīng)過團(tuán)隊(duì)的調(diào)研、討論最終定下數(shù)據(jù)遷移方案:\color{red}{雙寫法}
大致思路是新系統(tǒng)上線時(shí)以及上線后的一段時(shí)間繼續(xù)采取之前的做法:從sqlServer數(shù)據(jù)庫存取數(shù)據(jù)肯骇,上線后 立即打開\color{red}{雙寫開關(guān)}開始數(shù)據(jù)同步并采取\color{red}{雙寫}操作窥浪,數(shù)據(jù)同步過程中在對sqlServer進(jìn)行讀寫操作時(shí)同時(shí)對mySql數(shù)據(jù)庫進(jìn)行同樣的一次讀寫操作祖很。 在數(shù)據(jù)同步完成之后確保兩個(gè)數(shù)據(jù)庫的數(shù)據(jù)完全一致后,關(guān)閉雙寫開關(guān)漾脂,此后順風(fēng)車的一切讀寫操作全都由mysql數(shù)據(jù)庫完成假颇,至此完成\color{red}{用戶無感知不停機(jī)遷庫}。流程圖如下圖所示:

image.png

雙寫開關(guān):代碼中采取硬編碼的形式骨稿, 在zk配置中心配置雙寫開關(guān)笨鸡,以便靈活的對開/閉 開關(guān)不用修改代碼。配置信息和關(guān)鍵代碼如下:


AEHA%FX}H8AKCM(NX1(28R6.png

摒棄之前用一次去配置中心取一次的操作坦冠,改為配置類(單例)初始化時(shí)讀取到本項(xiàng)目的所有配置中心數(shù)據(jù)形耗, 通過與字段對應(yīng)關(guān)系 \color{red}{反射}給字段賦值。大大節(jié)省了 從配置中心取數(shù)據(jù)的高頻操作所耗費(fèi)的時(shí)間辙浑。
當(dāng)然激涤,如果配置中心的數(shù)據(jù)被修改,采用配置中心的\color{red}{回調(diào)}機(jī)制第一時(shí)間給字段重新賦值例衍,注意為了保證實(shí)例變量在線程之間的可見性昔期,字段用\color{red}{volatile}修飾。

@Data
public final class ConfCenterProperties {
    /** 遷移開關(guān)   true: myql  false: sqlServer*/
    @NotNull
    private volatile Boolean transferSwitch;
    

    /** 配置中心數(shù)據(jù)源名稱 與 當(dāng)前類字段名 對應(yīng)關(guān)系映射 */
    private static final Map<String, String> FIELD_MAP = new HashMap<>();

    // 新增字段需要配置該對應(yīng)關(guān)系
    static {
        FIELD_MAP.put("xxx.xxx.switch", "transferSwitch");
    }

    private static final ConfCenterProperties instance = new ConfCenterProperties();

    private static final Logger LOGGER = LoggerFactory.getLogger(ConfCenterProperties.class);
    
    //實(shí)例化時(shí)反射賦值
    private ConfCenterProperties() {
        //獲取所有的配置數(shù)據(jù)佛玄,遍歷匹配實(shí)例變量并反射賦值
        Map<String, ClientDataSource> allDataSourceMap = ConfCenterUtil.getAllDataSource();
        for (String key : allDataSourceMap.keySet()) {
            String fieldName = FIELD_MAP.get(key);
            if (fieldName == null) {
                continue;
            }
            try {
                BeanUtils.setProperty(this, fieldName, allDataSourceMap.get(key).getSourceValue());
                Field field = ReflectionUtils.findField(getClass(), fieldName);
                if (field == null) {
                    throw new Exception(fieldName + "不存在,請檢查FIELD_MAP配置");
                }
                NotNull notNull = field.getAnnotation(NotNull.class);
                if (notNull != null && field.get(this) == null) {
                    throw new Exception(fieldName + "不可為空累澡,請檢查配置中心配置梦抢,數(shù)據(jù)源為" + key);
                }
            } catch (Exception e) {
                throw new IllegalStateException("初始化配置中心數(shù)據(jù)異常:" + e.getMessage());
            }
        }
    }

//回調(diào)方法
public static void onEvent(DataSourceTransport dataSourceTransport) {
     //配置中心數(shù)據(jù)發(fā)生變化  修改實(shí)例變量屬性值  代碼省略……
}


//配置中心監(jiān)視器  回調(diào)
public class ConfCenterListener implements DataChangeListener {

    @Override
    public void call(DataSourceTransport dataSourceTransport) {
        ConfCenterProperties.onEvent(dataSourceTransport);
    }
}

數(shù)據(jù)同步: 聯(lián)系架構(gòu)、DBA進(jìn)行數(shù)據(jù)庫數(shù)據(jù)遷移操作愧哟,他們有成熟的一套體系奥吩,業(yè)務(wù)人員只需要關(guān)注自身業(yè)務(wù)即可。 通過與DBA同事交流得知 數(shù)據(jù)遷移是采用xxxx的方式進(jìn)行蕊梧,想進(jìn)一步深入了解的小伙伴可以霞赫。點(diǎn)擊此鏈接

如何保證遷庫后 的數(shù)據(jù)一致性: 重點(diǎn)來了~~~由于業(yè)務(wù)繁忙,如何確保兩個(gè)數(shù)據(jù)庫數(shù)據(jù)的一致性是本次需求的重中之重肥矢。稍有不慎產(chǎn)生臟數(shù)據(jù)或數(shù)據(jù)丟失就會(huì)造成嚴(yán)重的線上事故端衰。一大波用戶投訴又會(huì)接踵而至,事件單又會(huì)處理的讓人頭皮搔更短 甘改,渾欲不勝簪旅东。話不多說先來雙寫遷移圖壓壓鯨@#¥%……&*

image.png

熱心讀者趙二狗提出如下疑問@!#$%^&*(

  • \color{red}{Q:} 數(shù)據(jù)遷移完成之后,就能夠切到新庫提供服務(wù)了么十艾?
    \color{red}{A:}答案是肯定的抵代,因?yàn)榍爸貌襟E進(jìn)行了雙寫,所以理論上數(shù)據(jù)遷移完之后忘嫉,新庫與舊庫的數(shù)據(jù)應(yīng)該完全一致荤牍。
  • \color{red}{Q:} 怎么證明數(shù)據(jù)遷移完成之后數(shù)據(jù)就完全一致了呢案腺?
    \color{red}{A:}這就得分為以下三種情況分別討論
    1. 雙insert操作:舊庫新庫都插入了數(shù)據(jù),數(shù)據(jù)一致性沒有被破壞
    2.雙delete操作:delete的數(shù)據(jù)屬于[min,now]范圍內(nèi)和范圍外分別討論
    3.雙update操作:可以認(rèn)為update操作是一個(gè)delete加一個(gè)insert操作的復(fù)合操作康吵,所以數(shù)據(jù)仍然是一致的
  • \color{red}{Q:} 上線時(shí)開啟雙寫開關(guān)救湖,那什么時(shí)候關(guān)閉雙寫開關(guān)呢?
    \color{red}{A:}當(dāng)我們通過DBA提供的工具發(fā)現(xiàn)兩個(gè)數(shù)據(jù)庫的數(shù)據(jù)并無并無差異涎才,完全一致的時(shí)候鞋既,就可以關(guān)閉雙寫開關(guān)。之后的讀寫操作全部交由mysql處理耍铜。當(dāng)然啦邑闺,如果過程中發(fā)現(xiàn)問題,我們可以清空新庫的數(shù)據(jù)重新開始同步數(shù)據(jù)棕兼,直至兩個(gè)數(shù)據(jù)庫的數(shù)據(jù)完全一致為止
  • \color{red}{Q:} 舊庫進(jìn)行了insert操作插入一條數(shù)據(jù)陡舅,新庫同樣也要進(jìn)行一次insert操作,舊庫Insert操作是主鍵遞增伴挚,那么這個(gè)新庫Insert操作的id從何而來呢靶衍?
    \color{red}{A:}這個(gè)不難解決,舊庫執(zhí)行完insert操作之后我們可以拿到insert后的主鍵id給新庫insert操作使用就好了
  • \color{red}{Q:} 如果一個(gè)service方法中茎芋,既有sqlServer的寫操作又有mysql的寫操作颅眶,假設(shè)其中一個(gè)操作失敗怎么保證另外的操作會(huì)跟著回滾呢? 事務(wù)的一致性如何保證田弥?
    \color{red}{A:}小老弟涛酗,你可算問到點(diǎn)子上了,這個(gè)問題一句兩句說不清楚偷厦,先接著往后看吧商叹。你會(huì)得到你想要的答案~~~~


多數(shù)據(jù)源事務(wù)處理

由于項(xiàng)目中需要對sqlServer數(shù)據(jù)庫和mySql數(shù)據(jù)庫進(jìn)行雙寫操作,所以不可避免的要配置兩個(gè)數(shù)據(jù)源只泼。那么一系列操蛋的問題接踵而來剖笙。

事務(wù)四大特性
  • \color{green}{原子性}:一個(gè)事務(wù)中的操作要么全部成功要么全部失敗
  • \color{green}{一致性}:在一個(gè)事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫都必須處于一致性狀態(tài)。
  • \color{green}{隔離性}:并發(fā)的事務(wù)是相互隔離的请唱。
  • \color{green}{持久性}:意味著當(dāng)系統(tǒng)或介質(zhì)發(fā)生故障時(shí)弥咪,確保已提交事務(wù)的更新不能丟失。即一旦一個(gè)事務(wù)提交籍滴,保證它對數(shù)據(jù)庫中數(shù)據(jù)的改變應(yīng)該是永久性的酪夷,耐得住任何系統(tǒng)故障。持久性通過數(shù)據(jù)庫備份和恢復(fù)來保證孽惰。
spring事務(wù)抽象

在spring當(dāng)中為我們的數(shù)據(jù)訪問層提供了很多抽象晚岭,在這些抽象的幫助下面我們可以非常方便的在不同的框架當(dāng)中使用一樣的方式來進(jìn)行數(shù)據(jù)操作, 其中最重要的一個(gè)抽象就是\color{green}{事務(wù)抽象}勋功。

spring的事務(wù)抽象: 一致的事務(wù)模型
spring提供了一致的事務(wù)模型坦报,不管是用JDBC還是Mybatis或者是Hibernate來操作數(shù)據(jù)庫库说,也不管我們是使用的datasource的事務(wù)還是使用JTA的事務(wù),在這個(gè)事務(wù)抽象里面都能給他們很好的統(tǒng)一在一起片择。

  • JDBC/Hibernate/myBatis
  • DataSource/JTA
事務(wù)抽象的核心接口
//事務(wù)抽象核心接口
public interface PlatformTransactionManager {
    //獲取事務(wù)信息    
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //提交事務(wù)
    void commit(TransactionStatus var1) throws TransactionException;
    //回滾事務(wù)
    void rollback(TransactionStatus var1) throws TransactionException;

PlatformTransactionManager

  • DataSourceTransactionManager
  • JtaTransactionManager
  • HibernateTransactionManger

TransactionDefinition
通過TransactionDefinition 可以獲取TransactionStatus 潜的,包含了事務(wù)的只讀狀態(tài)、回滾狀態(tài)等等信息字管。

  • Propagation 傳播特性
  • Isolation 隔離性
  • Timeout 超時(shí)設(shè)置
  • Read-only status 是否只讀
public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0;
    int PROPAGATION_SUPPORTS = 1;
    int ISOLATION_DEFAULT = -1;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;
    ……
  
   int getPropagationBehavior();
    int getIsolationLevel();
    int getTimeout();
    boolean isReadOnly();
    @Nullable
    String getName();
事務(wù)傳播特性
事務(wù)傳播特性

默認(rèn) required
new和 nested的區(qū)別:
new 始終啟動(dòng)一個(gè)新事務(wù)啰挪,跟外層的事務(wù)沒有關(guān)聯(lián)
nested 兩個(gè)事務(wù)有關(guān)聯(lián),外部事務(wù)回滾嘲叔,內(nèi)嵌事務(wù)也會(huì)回滾 保持一致

事務(wù)隔離特性
事務(wù)隔離特性

默認(rèn)-1 完全取決于數(shù)據(jù)庫 可以根據(jù)實(shí)際需求去做設(shè)定或者默認(rèn) 使用數(shù)據(jù)庫的默認(rèn)的隔離特性

編程式事務(wù)

TransactionTemplate
最基本的最簡單的方式就是使用TransactionTemplate亡呵,當(dāng)然你也可以說我就是這么傲嬌,我就想直接使用JDBC原生的方法硫戈,用Connection在里面去做開始事務(wù)锰什,提交事務(wù),回滾事務(wù)丁逝,當(dāng)然也可以汁胆。

  • TrancastionCallBack 有返回值
  • TranscationCallBackWithoutResult 沒有返回值
    PlatformTransactionManger
  • 可以傳入TransactionDefinition進(jìn)行定義
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void insert() {
        log.info("before count:{}",getCount());
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                jdbcTemplate.execute("insert into dog(id,nickname) values(1,'二狗')");
                log.info("ing count:{}",getCount());
                //讓事務(wù)回滾
                transactionStatus.setRollbackOnly();
            }
        });
        log.info("after count:{}",getCount());
    }

事務(wù)開啟前0條數(shù)據(jù),事務(wù)過程當(dāng)中 1條數(shù)據(jù)霜幼,事務(wù)回滾后0條數(shù)據(jù)

聲明式事務(wù)

利用spring的動(dòng)態(tài)代理在我們的目標(biāo)方法上加了一層封裝(環(huán)繞通知)嫩码,幫助我們進(jìn)行模版式的事務(wù)操作。

  • \color{red}{配置文件} <tx:annotation-driven/> 開始事務(wù)功
 <!-- 配置事務(wù)注解驅(qū)動(dòng) -->
   <tx:annotation-driven transaction-manager="transactionManager"/>
  • \color{red}{注解} @EnableTranscationManagement開始事務(wù)功能 大勢所趨本文介紹注解方法

@Transaction

  • transactionMager 一般是datasoureTransactionManger辛掠,如果就一個(gè)谢谦,就會(huì)默認(rèn)選中這個(gè)
  • propagation
  • isolaton
  • timeout
  • readOnly
  • 如何判斷回滾 碰到特定的異常類回滾
@Transactional(rollbackFor = Exception.class)
    public void insert1() throws Exception {
        jdbcTemplate.execute("insert into dog(id,nickname) values(1,'二狗')");
        throw new Exception("回滾術(shù)");
    }

\color{red}{注意類內(nèi)部調(diào)用 不會(huì)調(diào)用代理類的方法,因此事務(wù)不會(huì)生效萝衩。}


大多數(shù)人存在的誤區(qū)

有了聲明式事務(wù)之后,許多人都會(huì)認(rèn)為只要在調(diào)用dao的service方法上加上@Transactional就萬事大吉了没咙。而事實(shí)情況并非如此猩谊。先來看一段代碼~~

hellow
多數(shù)據(jù)源事務(wù)如何處理?
透過'事務(wù)'看本質(zhì)
拓展大法好祭刚!
redis雙寫遷移

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牌捷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子涡驮,更是在濱河造成了極大的恐慌暗甥,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捉捅,死亡現(xiàn)場離奇詭異撤防,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)棒口,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門寄月,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辜膝,“玉大人,你說我怎么就攤上這事漾肮〕Ф叮” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵克懊,是天一觀的道長忱辅。 經(jīng)常有香客問我,道長谭溉,這世上最難降的妖魔是什么墙懂? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮夜只,結(jié)果婚禮上垒在,老公的妹妹穿的比我還像新娘。我一直安慰自己扔亥,他們只是感情好场躯,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著旅挤,像睡著了一般踢关。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上粘茄,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天签舞,我揣著相機(jī)與錄音,去河邊找鬼柒瓣。 笑死儒搭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芙贫。 我是一名探鬼主播搂鲫,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼磺平!你這毒婦竟也來了魂仍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拣挪,失蹤者是張志新(化名)和其女友劉穎擦酌,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體菠劝,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡赊舶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锯岖。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡介袜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出出吹,到底是詐尸還是另有隱情遇伞,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布捶牢,位于F島的核電站鸠珠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏秋麸。R本人自食惡果不足惜渐排,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灸蟆。 院中可真熱鬧驯耻,春花似錦、人聲如沸炒考。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斋枢。三九已至帘靡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓤帚,已是汗流浹背描姚。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戈次,地道東北人轩勘。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像怯邪,于是被迫代替她去往敵國和親赃阀。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

推薦閱讀更多精彩內(nèi)容