单机与集群下如何保证数据一致性


背景:

当我们需要对数据进行先读取,满足某条件再做新增,往往会面临着线程不安全的问题,导致数据被重复插入。

下面分别举例子来说明单实例与多实例(集群)下的保证数据安全。

 

 需要用到的工具:

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