건축설계7강: 일일&온라인 테이블 구조 데이터 검사 시스템의 자동 비교
본 글은 정형환경과 테스트 환경 간의 데이터베이스/테이블 및 컬럼 구조의 불일치로 인해 발생하는 문제를 방지하기 위한 아키텍처 설계, 데이터 검사 시스템의 일일 및 온라인 테이블 구조 자동 비교에 관한 일곱 번째 강의입니다.
기사 디렉토리
1. 배경
일일 및 온라인 테이블 구조, 인덱스 불일치 시나리오 정리
검사 : 검사 업무 차이 테이블, 테이블 이름이 일치하지 않아 수정되었습니다.
사례:
alter table finance_sub_order_info modify total_receive decimal(19,3) default 0.000 not null comment '待删除';
alter table finance_sub_order_info modify total_pay decimal(19,3) default 0.000 not null comment '待删除';
alter table finance_sub_order_info modify total_cost decimal(19,3) default 0.000 not null comment '待删除';
alter table finance_fare_info modify img text null comment '附件图片';
alter table finance_bill drop column bill_amount;
alter table finance_sub_order_settlement drop column price;
alter table finance_sub_order_settlement drop column business_types;
alter table finance_sub_order_settlement drop column invalid_state;
alter table finance_sub_order_settlement drop column record_user;
alter table finance_sub_order_settlement drop column can_settlement;
alter table finance_sub_order_settlement drop column create_user;
팀 재정:
Finance_sub_order_info
- 질문 1: 세 개의 필드를 삭제해야 합니다.
total_receive
십진수(19,3) NOT NULL DEFAULT '0.000' COMMENT '미수금 총액',total_pay
십진수(19,3) NOT NULL DEFAULT '0.000' COMMENT '지불총액',total_cost
십진수(19,3) NOT NULL DEFAULT '0.000' COMMENT '총 비용',
- 먼저 이 필드를 삭제하도록 설정하세요.
Finance_fare_info
-
질문 1:
-
질문 2: 필요에 따라 AttachmentId를 설정하세요. 기본값은 0입니다.
- 그런 다음 코드에서 호환되어야 하는 위치를 확인하세요.
-
질문 3: Finance_fare_info 테이블
-
confirm_time datetime null comment '确认时间', confirm_user bigint null comment '确认人',
-
이 두 필드는 온라인에서 삭제되었지만 일상적인 환경은 여전히 존재하며 확인이 필요합니다.
-
새로운 기능
-
-
질문 4: 송장 번호
-
invoice_code varchar(128) null comment '发票号码数组,以逗号分割',
-
이 필드는 온라인에서는 삭제되었으나, 일상 환경에는 여전히 존재하므로 확인이 필요합니다.
-
새로운 기능
-
-
질문 5: 협업 상태
-
team_state int default 0 not null comment '协作状态 0非协作费用 1协作费用',
-
이 필드는 온라인에서는 삭제되었으나, 일상 환경에는 여전히 존재하므로 확인이 필요합니다.
-
새로운 기능
-
금융_청구서
-
질문 1: 총 청구서 필드는 온라인에는 존재하지만 일상 환경에는 존재하지 않습니다.
bill_amount decimal(19,3) NOT NULL DEFAULT '0.000' COMMENT '账单金额',
- 삭제해야한다
-
질문 2: 이 필드는 매일에 존재합니다.
-
settlement_owned_type tinyint default 0 not null comment '1 自营 2-外协', settlement_entity_id bigint default 0 not null comment '结算实体id', settlement_entity_classify tinyint default 0 not null comment '结算实体类型,1-司机 2-企业id 3-车队id ',
-
Finance_sub_order_settlement
-
질문 1: 총 비용 필드는 온라인에는 존재하지만 일상 환경에는 존재하지 않습니다.
price decimal(19,3) NOT NULL DEFAULT '0.000' COMMENT '费用合计'
-
질문 2: 다음 5개 필드는 온라인에는 존재하지만 일상 환경에는 존재하지 않습니다.
- business_types varchar(60) null comment '업종, 중량물 배송, Door to Door, 중량물 배송, 빈 배송, 빈 배송, 물품 운반, 화물',
- valid_state int(10) default 0 not null comment '무효 또는 삭제 여부, 0: 정상 주문, 1: 주문 무효, 2: 주문 삭제',
- Record_user bigint null 설명 '레코더',
- can_settlementtinyint null 코멘트 '정산 가능 여부',
- create_user bigint는 null 주석 '작성자'가 아닙니다.
- 이 데이터 배치를 삭제해야 합니다. 기능이 이미 온라인 상태입니다.
-
문제 3: 일관성 없는 인덱스
-
-- daily create index idx_tenantid_settlementtype_invalidstate on falcon_convoy.finance_sub_order_settlement (tenant_id, settlement_type); -- 线上 create index idx_tenantid_settlementtype_invalidstate on falcon_convoy.finance_sub_order_settlement (tenant_id, settlement_type, invalid_state); -- todo 索引名称需要修改
-
2. 문제가 있는 시나리오
시나리오 1: 인덱스 충돌
두 인덱스의 테넌트 ID 필드가 중복되며, 다음 인덱스가 삭제됩니다.
시나리오 2: 인덱스 불일치
-
-- daily create index idx_tenantid_settlementtype_invalidstate on falcon_convoy.finance_sub_order_settlement (tenant_id, settlement_type); -- 线上 create index idx_tenantid_settlementtype_invalidstate on falcon_convoy.finance_sub_order_settlement (tenant_id, settlement_type, invalid_state);
시나리오 3: 일부 필드가 온라인에 존재하지만 일상 환경에는 존재하지 않습니다.
시나리오 4: 일부 필드가 온라인에서 삭제되었지만 일상적인 환경은 여전히 존재합니다.
3. 기술 솔루션
3.1 페이지는 다음과 같습니다
3.2 전체 흐름도
목표 : 정형환경과 테스트 환경의 데이터베이스/테이블 및 컬럼 구조의 불일치로 인해 발생하는 문제를 방지합니다.
- 일상 환경과 온라인 환경 테이블 구조의 일관성 여부를 감지하고, 불일치하는 데이터를 기록하며, DingTalk 알림을 푸시합니다.
1단계: 데이터 수집
-
업스트림: 온라인 환경 라이브러리 + 테이블
-
하류: 일일 환경 라이브러리 + 테이블
-
빈도: 일주일에 두 번이면 충분합니다.
2단계: 데이터 비교
- 1. 온라인에는 존재하지만 데일리에는 존재하지 않는 경우, 데일리 환경이 비호환적으로 업그레이드되어 메시지가 푸시될 수 있는 시나리오가 있을 수 있습니다.
- 2. 온라인에는 존재하지 않지만 Daily에는 존재합니다. Daily가 새 테이블을 추가하고 Redis에 테이블 이름을 저장할 수 있는 시나리오가 있을 수 있습니다. 7일 후에 테이블은 아직 온라인에 존재하지 않으며 메시지가 푸시됩니다.
- 3. 모두 존재하지만 일관성이 없습니다.인덱스가 누락되고, 주석이 만료되고, 필드 이름이 변경되고, 필드 유형이 변경되고, 메시지가 즉시 푸시되는 시나리오입니다.
3단계: 오류 처리
- 일치하지 않는 데이터가 기록되고 DingTalk 알림이 푸시됩니다. ( DingTalk 로봇과 도킹 )
3.3 데이터 수집
걸림돌 1: 일상 환경과 온라인 환경 사이에 네트워크 연결이 없습니다.
- 해결 방법: EC를 제어 영역에 배포
막힌 지점 2: application.yml 파일에 구성된 다중 데이터 소스 구성
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
first:
url: ${
huxun.datasource.url}
username: ${
huxun.datasource.username}
password: ${
huxun.datasource.password}
second:
url: ${
huxun.datasource.daily.url}
username: ${
huxun.datasource.daily.username}
password: ${
huxun.datasource.daily.password}
여러 데이터 소스의 구체적인 구현:
1. 동적 데이터 소스 정의: AbstractRoutingDataSource 추상 클래스를 상속하고determinCurrentLookupKey() 메서드를 재정의합니다.
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
return dataBaseType;
}
}
2. 데이터 소스 유형을 전환하는 클래스 만들기
public class DataSourceType {
public enum DataBaseType {
//默认数据库
FIRST,
SECOND;
}
// 使用ThreadLocal保证线程安全
private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();
// 往当前线程里设置数据源类型
public static void setDataBaseType(DataBaseType dataBaseType) {
if (dataBaseType == null) {
throw new NullPointerException();
}
System.out.println("[将当前数据源改为]:" + dataBaseType);
TYPE.set(dataBaseType);
}
// 获取数据源类型
public static DataBaseType getDataBaseType() {
DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.FIRST : TYPE.get();
System.out.println("[获取当前数据源的类型为]:" + dataBaseType);
return dataBaseType;
}
// 清空数据类型(清理时机不好掌控,且目前ThreadLocal只存在一个值,不清理也没影响)
public static void clearDataBaseType() {
TYPE.remove();
}
}
3. 다중 데이터 소스 정의: 정의된 다중 데이터 소스를 동적 데이터 소스에 배치합니다.
@Configuration
@MapperScan(basePackages = {
"com.huxun.inspection.mapper"}, sqlSessionFactoryRef = "SqlSessionFactory")
public class DruidConfig {
@Bean(name = "firstDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.first")
public DataSource firstDataSource(){
return DruidDataSourceBuilder
.create()
.build();
}
@Bean(name = "secondDataSource")
@ConfigurationProperties(prefix = "spring.datasource.second")
public DataSource secondDataSource(){
return DruidDataSourceBuilder
.create()
.build();
}
@Bean(name = "dynamicDataSource")
public DynamicDataSource DataSource(@Qualifier("firstDataSource") DataSource test1DataSource,
@Qualifier("secondDataSource") DataSource test2DataSource) {
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put(DataSourceType.DataBaseType.FIRST, test1DataSource);
targetDataSource.put(DataSourceType.DataBaseType.SECOND, test2DataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSource);
dataSource.setDefaultTargetDataSource(test1DataSource);
return dataSource;
}
@Bean(name = "SqlSessionFactory")
public SqlSessionFactory test1SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
return bean.getObject();
}
}
4. AOP를 정의하십시오: 다른 비즈니스 데이터베이스를 전환하는 데 사용되는 입구입니다.
@Aspect
@Component
public class DataSourceAspect {
@Before("execution(* com.huxun.inspection.mapper..Daily*.*(..))")
public void setDataSource2test01() {
System.err.println("读取第二个数据源");
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.SECOND);
}
@Before("execution(* com.huxun.inspection.mapper..*.*(..)) && !execution(* com.huxun.inspection.mapper..Daily*.*(..))")
public void setDataSource2test02() {
System.err.println("读取第一个数据源");
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.FIRST);
}
}
전체 카탈로그는 다음과 같습니다.
TABLES
information_schema.data를 읽을 수 있는 권한이 필요합니다.
예정된 작업 실행 시간 : 매주 수요일, 금요일 (해제 후 첫 번째 날)
3.4 데이터 비교
논리는 다음과 같습니다.
-
1. 온라인에는 존재하지만 데일리에는 존재하지 않는 경우, 데일리 환경이 비호환적으로 업그레이드되어 메시지가 푸시될 수 있는 시나리오가 있을 수 있습니다.
-
2. 온라인에는 존재하지 않지만 Daily에는 존재합니다. Daily가 새 테이블을 추가하고 Redis에 테이블 이름을 저장할 수 있는 시나리오가 있을 수 있습니다. 7일 후에 테이블은 아직 온라인에 존재하지 않으며 메시지가 푸시됩니다.
-
3. 모두 존재하지만 일관성이 없습니다.인덱스가 누락되고, 주석이 만료되고, 필드 이름이 변경되고, 필드 유형이 변경되고, 메시지가 즉시 푸시되는 시나리오입니다.
3.5.데이터 테이블 검사 정보 푸시
- 비즈니스 유형: %s 데이터가 일관성이 없습니다. 제때 처리해 주세요.
- 테이블 이름: %s
- 담당자: %s
- %s 업스트림 및 다운스트림 데이터가 일치하지 않습니다. 제때에 처리하십시오.
- 차이 범주(0-생성, 1-업데이트, 2-삭제): %s
- 배치 ID: %s
3.6, DB변경
표 1: 테이블 차이 검사 테이블
CREATE TABLE IF NOT EXISTS `table_diff_inspection`(
`id` bigint unsigned auto_increment comment '主键id' primary key,
`biz_id` bigint not null comment '业务id',
`batch_id` bigint not null comment '批次id',
`status` tinyint(1) default 0 not null comment '状态,0-待确认,1-确认',
`key_field_json` longtext not null comment '业务关键字段数据',
`diff_type` tinyint null comment '差异类别 (0-create、1-update、2-delete)',
`db_name` varchar(50) not null COMMENT '库名',
`group_name` varchar(50) not null COMMENT '处理人',
`create_user` bigint not null comment '创建人',
`update_user` bigint null comment '更新人',
`create_time` datetime default CURRENT_TIMESTAMP not null comment '创建时间',
`update_time` datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间'
) DEFAULT CHARACTER SET = utf8mb4 COMMENT = 'table差异巡检表';
create index idx_batchId_bizIds on falcon_inspection.falcon_table_diff (batch_id, biz_id);
4. 문제 기록
문제 1: dbName에 값이 없습니다.
질문 2: 테이블 구조 수정
/**
* 创建人
*/
private Long createUser;
/**
* 更新人
*/
private Long updateUser;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
질문 3: DingTalk 로봇의 흐름 제어
- 너무 빠르게 전송, 분당 20회 초과: 분당 최대 20개 메시지
- 흐름은 10분 동안 제한됩니다.
- 푸시 메시지 본문이 너무 큽니다. 단일 메시지의 최대 길이는 2000바이트입니다.
질문 4: SQL 구문 분석 실패 매개 변수
: 무시 를 에 삽입 합니다 falcon_convoy
. tp_4740783_ogt_finance_fare_**info**
( id
, tenant_id
, sub_order_id
, sub_order_carrier_id
, sub_order_settlement_id
, fare_item_id
, bill_no
, settlement_type
, settlement_id
, creator_type
, price
, tax_rate
, , , , , , , , , , , , , , , , , ) 선택 , , , , , , , , , , , , , _currency
img
attachment_id
remark
confirm_state
confirm_no
confirm_user
confirm_remark
confirm_time
collate_state
invoice_state
invoice_user
invoice_code
invoice_time
verify_state
verify_user
verify_time
team_fare_state
team_state
deleted
create_user
update_user
create_time
update_time
id
tenant_id
sub_order_id
sub_order_carrier_id
sub_order_settlement_id
fare_item_id
bill_no
settlement_type
settlement_id
creator_type
price
tax_rate
currency
img
attachment_id
, remark
, confirm_state
, confirm_no
, confirm_user
, confirm_remark
, confirm_time
, collate_state
, invoice_state
, invoice_user
, invoice_code
, invoice_time
, , verify_state
, verify_user
, , verify_time
, team_fare_state
, team_state
, deleted
, create_user
, update_user
, create_time
, update_time
에서 falcon_convoy
. finance_fare_**info**
강제 인덱스( primary
), 여기서 id
> $0 및 ( id
< $1 또는 id
= $2) 공유 모드 잠금
조치 1: 일상 환경의 온라인 환경 데이터 테이블 비교를 달성하려면 이 두 가지 문제를 해결해야 합니다.
1. 일상환경과 온라인 환경이 연결되어 있지 않습니다 . : 온라인 환경 DB와 일상 환경 DB를 하나의 환경에서 모두 접속 해야 합니다 .
- 제어 영역에 EC를 배포하려고 합니다.
2. 이제 각 온라인 라이브러리는 자체 계정과 비밀번호를 사용합니다 . 온라인 db 인스턴스의 모든 라이브러리에 액세스할 수 있는 읽기 전용 권한이 있는 계정을 제공할 수 있습니까?
- 이러한 여러 데이터 소스의 경우 일일 인스턴스와 온라인 인스턴스라는 두 개의 연속적인 예만 필요합니다.
조치 2: SpringBoot는 여러 데이터 소스를 사용하므로 MyBatis 페이징 플러그인이 유효하지 않게 됩니다.
배경
해당 현상은 게이트웨이에서 오류를 보고하는 현상인데 FluxOnAssembly$OnAssemblyException
, 조사 결과 페이징 쿼리 중 1,000개 이상의 데이터가 반환되어 데이터량이 게이트웨이 한도를 초과해 오류가 발생한 것으로 확인됐다. 중단점은 MyBatis 페이징 플러그인이 유효하지 않으며 MyBatis 페이징 인터셉터 중단점을 입력할 수 없음을 발견했습니다.
장면
1. 스프링부트 사용
2. sqlSession 사용자 정의(다중 데이터 소스)
해결책
1. 페이징 플러그인 클래스에 @Component 주석이 추가되었는지 확인하세요 ✅
2. SqlSessionFactoryConfig 클래스에 인터셉터 삽입✅
삼,sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageInterceptor});
참고: 플러그인을 설정할 때 sqlSessionFactoryBean.getObject() 이전이어야 합니다. SqlSessionFactory는 플러그인이 생성될 때 플러그인을 얻어서 구성에서 설정합니다. 나중에 설정하면 삽입되지 않습니다.