單機與集群下如何保證數據一致性


背景:

當我們需要對數據進行先讀取,滿足某條件再做新增,往往會面臨着線程不安全的問題,導致數據被重復插入。

下面分別舉例子來說明單實例與多實例(集群)下的保證數據安全。

 

 需要用到的工具:

1、並發測試工具JMeter,模擬多用戶並發請求,也就是多個用戶在同一時刻同時情求該接口。

2、ngnix (1.12.2版本) 本地組建集群服務,通過配置完成,來自用戶的請求,從多個服務節點選擇一個來完成該請求響應。nginx會自動做分發,從而達到負載均衡。

需求:

插入80個項目實例到mysql 的project 表中。

 

 一、單節點的情況

 

主要代碼如下:

service層 class ProjectServiceImpl中:

 

public void addProject2(Project project) {

int maxCount =100;
int count = 80;

public void addProject2(Project project) {
for (int i = 0; i < maxCount; i++) {
Date date = new Date();
List<Project> projectList = selectProjects(project);
if (projectList.size() >= count) {
return;
}
project.setId(UUID.randomUUID().toString().replaceAll("-", ""));
project.setProjectName(project.getId());
project.setCreateTime(date);
project.setModifyTime(date);
project.setModifier("1");
project.setCreator("1");
insertProject(project);
}
}

控制器層的代碼:
/**
* 新增項目
*/
@PostMapping("addProject")
public RestMessage addProject(@RequestBody Project project) {
service.addProject2(project);
return RestBuilders.successBuilder().build();
}
 

打開postman開多個tab快速的依次切換tab並點擊send,均成功返回:(此處也可以用JMeter測試)

 

 

去navicat 查看數據發現產生了81條數據,而不是80條。其原因就是線程不安全所致,查詢與插入的動作沒有在一個同步代碼塊中

 

 

單節點解決上述問題方案:方法加 synchronized 修飾,也可以加在代碼塊,還可以用同步鎖實現。

 

 修改上述addProject2 方法如下:

synchronized public void addProject2(Project project) {
//xxx 省略相同部分
}
此處我們改用更專業的JMeter軟件來測試:設置200個線程(相當於上述步驟的postman開200個窗口同時點擊的效果)

 

 

htpp 請求設置如下:

 

 去數據庫工具navicat 查看有多少條該數據:相同project_code共有80條整:

 

 

結論:

對於單機服務(單節點),加synchronized 修飾方法,或采用同步代碼塊,或同步鎖可以解決此類問題:代碼中線程不安全導致數據重復插入的問題。

 

一、集群多節點的情況

還是上述代碼不改,繼續保留synchronized 修飾方法,現在在idea中起兩個服務 LinkappApiApplication 服務端口是5042 LInkappApiApplication-B的服務端口是5043

 

nginx 集群配置如下,現在是兩個節點 127.0.0.1:5042 和127.0.0.1:5043

 

啟動nginx

打開JMeter請求,依然是設置200個線程(模擬200個用戶並發)

請求插入項目接口 http://localhost:5042/project/addProject 的請求參數如下:

{
"projectCode": "test_data_3"
}

 

 

 

 

 

 待請求結束后去mysql 查看插入了多少條:結果卻顯示插入了81條:仍然出現了重復插入的問題:可以看到即使加了synchronized修飾方法仍然有重復插入

 

 

問題:如何解決多節點的情況下,數據重復插入的問題?

解答:這里需要用到分布式鎖,數字在做讀和寫的時候需要用分布式鎖將讀寫代碼鎖住。分布式鎖的實現方式有很多種,這里采用基於redis的redisson鎖

需要在pom中引入redisson依賴:

 

 依賴注入對象:

 

 對上面單節點的新增項目的代碼的改造:將addProject2 放在addProject方法中調用:

 

 

修改完成后測試:

依然采用JMeter 設置200個線程測試:

 

 

去數據庫查看插入的數據:不多不少剛好80條:有效果。

 

 多次測試將JMeter進程數設置為500 ,仍然數據正確,剛好80條插入成功。

 

原創博文, 如需轉載請言明出處,謝謝!

 
        

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM