最近有個專案功能需求是需要我們獲取一條mysql的記錄,對其處理,處理完後再寫回es和mysql,然後更新該msyql記錄,因為我們是多使用者併發操作,在此處理過程中需要嚴格保證只容許乙個執行緒乙個時候占用。即需要在mysql和elasticsearch資料庫間保證分布式資料的原子性。那該如何做呢?
具體需求是mysql資料庫中存在一張表學生遊戲積分表,積分表的每條記錄對應該學生玩的遊戲和該遊戲產生的積分總和,如下:
es中我們還有一張表是該學生玩該遊戲具體每天產生的積分和描述,如下:
現在學生玩家希望每次登入就能看到自己的歷史積分成績(玩家等級),然後將玩家的遊戲等級和其盆友的等級按等級排序出來顯示出來。如下圖所示:
從後台程式設計師的業務角度來看,學生具體的遊戲積分在es中乙個叫detail的表中,如果每次玩家登陸開始統計積分就需要從es表中對ta/盆友分組,然後分組內的成績求和。因為所有記錄都儲存在同乙個表中,因此需要對detail表進行掃瞄然後分組求和,使用者少的情況下,可以支撐業務需求,但學生玩家通常統一在網上8點左右登入,也就是在這個時候點達到峰值,如果一秒幾百個使用者同時進入,es將同時掃瞄全表做聚合排序,會很慢,因此解決的辦法時在資料入庫時將統計結果統計後更新統計結果至scores欄位中,這樣就對效能有很多改善,現在的問題來了,如何保證es將累計積分更新至mysql過程中,別的程式不會同時更新了該行表字段?
這個時候自然想到的是分布式鎖,分布式鎖能保證乙個時候多個使用者只能對同乙個資源訪問,常見的實現分布式鎖有資料庫、redis、zookeeper。考慮到我們每次都是對乙個學生玩家更新,因此採用資料庫的行鎖將滿足我們的要求, 相對於myisam來說,innodb 是 mysql 上第乙個提供外來鍵約束的資料儲存引擎,除了提供事務處理外,innodb 還支援行鎖,行鎖的使用方式為:
select * from student_game where id=3 for update;
不過需要注意的是行鎖不是指對記錄,而是對索引枷鎖,如上面的id設為索引,同時,設為索引的字段值不應該重複多的,否則加了行鎖就可能跟加表鎖效果一樣了,這裡我們選擇innodb作為student_game表的檢索引擎,學號id作為預設索引。
這樣我們的業務**大概是這樣的:
conn.setautocommit(false);//關閉自動提交
select scores from test.student where id= 4 for update;//上鎖
//獲取學生id,scores資料,查詢es記錄,等到總分。
//更新學生得分表scores記錄
conn.commit;//釋放鎖
測試簡單起見,假設玩家沒玩一天得一分,則意味著向es每寫一條記錄,將在game_score表的特定使用者id的成績加1,下面是測試流程:
@test
public void testquery4() catch (sqlexception e)
}}.start();}}
上述測試,我們將同時啟動20個執行緒同時向es中寫入記錄,寫完後跟新game_score資料表的玩家得分。
public static void execute(connection conn, int id)
rs.close();
statement.execute("update test.student_score set scores = " + (sum + 1) + " where id=" + id);//更新玩家得分
statement.close();
} catch (sqlexception e)
}
當我們沒有設定行鎖的情況下,每個使用者向es插入記錄後同時根據mysql表得到如下結果:
玩家3目前總得分:0
玩家3目前總得分:0
玩家3目前總得分:0
玩家3目前總得分:0
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:1
玩家3目前總得分:2
玩家3目前總得分:2
設定行鎖:
public static void executetest3(connection conn, int id)
rs.close();
statement.execute("update test.game_score set scores = " + (sum + 1) + " where id="+id);
statement.close();
} else try catch (interruptedexception e)
}conn.commit();
} catch (sqlexception e)
}
得到結果為:
玩家3目前總得分:0
玩家3目前總得分:1
玩家3目前總得分:2
玩家3目前總得分:3
玩家3目前總得分:4
玩家3目前總得分:5
玩家3目前總得分:6
玩家3目前總得分:7
玩家3目前總得分:8
玩家3目前總得分:9
玩家3目前總得分:10
玩家3目前總得分:11
玩家3目前總得分:12
玩家3目前總得分:13
玩家3目前總得分:14
玩家3目前總得分:15
玩家3目前總得分:16
玩家3目前總得分:17
玩家3目前總得分:18
Redis 分布式事務和分布式事務鎖
講分布式事務鎖前的幾個概念 絕大部分請求是純粹的記憶體操作 非常快速 避免了與硬碟的接觸 豐富的特性 可用於快取,訊息,按key設定過期時間,過期後將會自動刪除 使用大量的hash思想的k v鍵值對,獲取效率為o 1 依靠非阻塞的io多路復用原則,使redis形成單執行緒去執行命令的伺服器,避免了不...
分布式鎖 資料庫實現
select 檢索出的資料,for update 加上了一把鎖,其他的人是不能修改這個資料的,也不能在給這個資料加鎖。其他執行緒可以檢索出來,但是我在用 for update 再給這些資料加鎖是加不上的,因為這個鎖呢,已經被前乙個執行緒給鎖住了。其他人是不能給它加鎖的,在加鎖的期間,其他人也不能修改...
基於資料庫實現分布式鎖
多個程序 多個執行緒訪問共同元件資料庫.通過selec.for update訪問同一條資料 for update鎖定資料,其他執行緒只能等待 此時只有乙個操作可以對資料進行修改,而其他人不能夠對該資料進行修改操作,但可以檢視 select from distribute lock where bus...