第三方数据延迟不可以出款 【项目日常问题-读写分离导致数据不一致问题】
项目背景技术架构相关业务模块
模块A:是专门数据同步模块,由业务系统的数据传给第三方,所以会涉及到存消息表,和要根据消息表更新业务表两个步骤。
模块B:业务模块,会有单据,每个单据会生成业务单据编号,生成业务单据编号采用数据库配置业务规则,由单个工具类实时生成。
所遇到问题
模块A ,更新业务表时有时会查询不到存的消息表的数据。
模块B , 生成业务单据编号重复(前提已增加了分布式锁控制)。
排查问题
首先模块A的问题,因为整个模版方法里包含了发送http请求到第三方,所以此方法并未添加事务,之后新增消息表数据,立刻查询就没有查询到结果,此时综合考虑定位在数据库层面。接下来通过代码层面去验证这个问题,思路就是加一个大的事务,之后在另一个事务里查询。因为大事务可以产生大的日志,会出现主从延迟。代码如下:
新建表
CREATE TABLE `test11` (
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL
) ENGINE = InnoDB AUTO_INCREMENT = 10001 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
.java
@RestController
@Slf4j
public class BizController {
@Autowired
private CommonHandleMapper commonHandleMapper;
@Autowired
private IEfsThirdInvService efsThirdInvService;
@GetMapping("/test")
public String test(){
// 测试主从延迟
efsThirdInvService.allSave();
List<Map<String, Object>> listDataSql = commonHandleMapper.getListDataSql("select * from test11");
System.out.println(listDataSql.size());
return "ok";
}
}
.
public interface IEfsThirdInvService {
void allSave();
}
pl.java
@Service
@Slf4j
public class EfsThirdInvServiceImpl implements IEfsThirdInvService {
@Autowired
private CommonHandleMapper commonHandleMapper;
@Override
@Transactional
public void allSave() {
// 批量插入
String sql = "insert into test11(name) values ";
List<String> sqls = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
sqls.add("('xx')");
}
commonHandleMapper.handleSql(sql + sqls.stream().collect(Collectors.joining(",")));
}
}
.java
public interface CommonHandleMapper {
int handleSql(@Param("sql") String sql);
List<Map<String, Object>> getListDataSql(@Param("sql") String sql);
}
.xml
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xxx.mapper.CommonHandleMapper">
<update id="handleSql" >
${sql}
</update>
<select id="getListDataSql" resultType="java.util.Map">
${sql}
</select>
</mapper>
修复方案:
可以把存消息表,和要根据消息表更新业务表两个步骤放到同一个事务里。
可以把mysql的数据复制策略,改为半同步策略,需要安装相关插件。
直接全走主库。
mysql主从相关知识
问答方式:
在同一个事务里 如果先写入一条数据 再读取一条数据,那写入和读取 都在主库么?
在同一个事务中,如果先写入一条数据再读取一条数据,这两个操作通常会在同一个数据库节点上执行。如果是在主库上开始的事务,那么:
这是由事务的隔离级别保证的。在 Read隔离级别下(的默认隔离级别),一旦事务开始,它将会看到该事务开始时已经存在或者在该事务中进行的任何数据修改。这意味着在事务中进行的写入操作,同一事务中的后续读取操作都能够看到。
在主从复制的环境中,如果事务在主库上开始,并且包含了写入操作,那么:
可以等同步到从库 再返回成功么
在MySQL的主从复制架构中,确保数据已经同步到从库再返回成功,是一个重要的需求,特别是在需要强一致性的场景下。这种做法可以确保客户端在写入操作完成后,能够从从库读取到最新的数据。以下是一些实现这一需求的方法:
如何实现半同步策略的
插件加载:
在主库和从库上分别加载半同步复制插件.so和.so。这些插件提供了半同步复制的功能。
事务提交过程:
当客户端向主库提交一个事务时,主库会将事务写入其二进制日志()。
主库的插件会等待至少一个从库的确认,即从库已经接收并写入中继日志(relay log)并刷新到磁盘。
从库确认:
从库的插件在接收到主库发送的事务事件后,会将其写入中继日志并刷新到磁盘。
完成这些操作后,从库会发送一个确认(ACK)消息回主库。
超时和降级:
如果主库在指定的超时时间内(由设置)没有收到至少一个从库的确认,主库会降级为异步复制,并将事务提交给客户端。
当从库再次赶上并能够确认接收事务时,主库会重新启用半同步复制。
数据一致性保证:
半同步复制确保在主库故障时,所有已提交的事务都至少在一个从库上有所**,从而减少了数据丢失的风险。
性能考量:
半同步复制可能会增加事务提交的延迟,因为主库需要等待从库的确认。这种延迟至少是网络往返时间(RTT)。
故障转移:
在故障转移时,如果使用了半同步复制,故障的主库不应该被重新作为主库使用,因为它可能包含未被从库确认的事务。
配置灵活性:
可以通过设置t来指定需要等待的从库数量,默认为1。
可以通过设置来指定在事务提交过程中等待从库确认的具体点位,可以是(默认)或。
总结:如果需要同步复制,把t设置为总的从库的数量就可以,还需要增大超时时间。