架构设计第七讲:数据巡检系统之daily&线上表结构自动化比对

简介: 架构设计第七讲:数据巡检系统之daily&线上表结构自动化比对

1、背景

daily与线上表结构,索引不一致场景梳理

巡检:巡检业务差异表,表名不一致,已经修改

案例:

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 decimal(19,3) NOT NULL DEFAULT ‘0.000’ COMMENT ‘总应收’,
  • total_pay decimal(19,3) NOT NULL DEFAULT ‘0.000’ COMMENT ‘总应付’,
  • total_cost decimal(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 '确认人',
  • 这两字段在线上已经被删除了,但是daily环境还存在,需要排查
  • 新功能
  • 问题4:发票号码
invoice_code   varchar(128)      null comment '发票号码数组,以逗号分割',
  • 这字段在线上已经被删除了,但是daily环境还存在,需要排查
  • 新功能
  • 问题5:协作状态
team_state              int         default 0                    not null comment '协作状态 0非协作费用 1协作费用',
  • 这字段在线上已经被删除了,但是daily环境还存在,需要排查
  • 新功能

finance_bill

  • 问题1:账单总额字段,在线上存在,但是在daily环境不存在
  • bill_amount decimal(19,3) NOT NULL DEFAULT '0.000' COMMENT '账单金额',
  • 应该被删除
  • 问题2:这几个字段在daily存在
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:费用合计字段,在线上存在,但是在daily环境不存在
  • price decimal(19,3) NOT NULL DEFAULT '0.000' COMMENT '费用合计'
  • 问题2:以下5字段,在线上存在,但是在daily环境不存在
  • business_types varchar(60) null comment ‘业务类型,送重、门到门、提重、送空、提空、带货、运费’,
  • invalid_state int(10) default 0 not null comment ‘是否作废或删除,0:正常订单,1:作废订单,2:删除订单’,
  • record_user bigint null comment ‘录单员’,
  • can_settlement tinyint null comment ‘是否可结算’,
  • create_user bigint not null comment ‘创建人’,
  • 需要删除这批数据 已经上线的功能
  • 问题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:索引冲突

两索引tenantId字段重合了,下面这个索引做删除处理

场景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:某些字段在线上存在,但是在daily环境不存在

场景4:某些字段在线上已经被删除了,但是daily环境还存在

3、技术方案

3.1、页面如下

3.2、整体流程图

目标:避免正式环境与测试环境数据库/表、列结构不一致带来问题。

  • 检测daily环境和线上环境表结构是否一致,不一致的数据记录起来,并推送钉钉告警

步骤1:数据获取

  • 上游:线上环境库+表
  • 下游:daily环境库+表
  • 频率:一周两次即可

步骤2:数据比对

  • 1、线上存在,daily不存在,场景可能是daily环境发生了不兼容的升级改造,消息推送即可;
  • 2、线上不存在,daily存在,场景可能是daily在新增了表,可以将表名存放到redis中,7天后,线上还不存在该表,消息推送;
  • 3、都存在,但是不一致,场景是索引遗漏、comment该了、字段名改了、字段类型改了,立即消息推送。

步骤3:差错处理

  • 不一致的数据记录起来,并推送钉钉告警(对接钉钉机器人)

3.3、数据获取

卡点1:daily环境与线上环境网络不通

  • 解法:将ecs部署到control区

卡点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 抽象类,并重写determineCurrentLookupKey()方法

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);
    }
}

整体目录如图:

需要权限,能读取information_schema.TABLES 数据

定时任务执行时机:每周三和周五(发版后的第一天)

3.4、数据比对

逻辑如下:

  • 1、线上存在,daily不存在,场景可能是daily环境发生了不兼容的升级改造,消息推送即可;
  • 2、线上不存在,daily存在,场景可能是daily在新增了表,可以将表名存放到redis中,7天后,线上还不存在该表,消息推送;
  • 3、都存在,但是不一致,场景是索引遗漏、comment该了、字段名改了、字段类型改了,立即消息推送。

3.5、数据表巡检信息推送

  • 业务类型:%s 数据不一致,请及时处理
  • 表名:%s
  • 负责人:%s
  • %s 上下游数据不一致,请及时处理
  • 差异类别(0-create、1-update、2-delete):%s
  • 批次id:%s

3.6、dbChange

表1:table差异巡检表

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:钉钉机器人的流控

  1. send too fast, exceed 20 times per minute:每分钟最多 20 条
  1. 会限流10分钟
  1. 推送消息体过大 单条消息最长 2000 字节

问题4:sql解析失败

param:insert ignore into 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) select 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 from falcon_convoy.finance_fare_**info** force index (primary) where id > $0 and (id < $1 or id = $2) lock in share mode

Action1:为了实现daily环境线上环境数据表比对,需要解决这两问题

1、daily环境与线上环境网络不通:需要在一个环境中,既访问线上环境db,又访问daily环境db

  • 即 将ecs部署到control区

2、现在线上各个库使用各自的账号密码:能不能提供一个只读权限的账号,能访问线上db实例 全部的库

  • 这样多数据源只用连两就行:daily实例、线上实例

Action2:SpringBoot使用多数据源导致MyBatis分页插件无效

背景

现象是gateway 网关 报错 FluxOnAssembly$OnAssemblyException,经过排查,发现是分页查询时返回了1000多条数据,导致数据量超出了网关限制,从而抛错。打断点发现MyBatis分页插件无效,MyBatis分页拦截器断点无法进入。

情景

1、使用Springboot

2、自定义sqlSession(多数据源)

解决方法

1、检查分页插件类上是否加注解 @Component ✅

2、在SqlSessionFactoryConfig类注入拦截器 ✅

3、sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageInterceptor});

注意:设置plugins时必须在sqlSessionFactoryBean.getObject()之前。SqlSessionFactory在生成的时候就会获取plugins,并设置到Configuration中,如果在之后设置则不会注入。

相关文章
|
13天前
|
运维 Kubernetes 监控
构建高效自动化运维系统:基于容器技术的策略与实践
【4月更文挑战第19天】随着云计算和微服务架构的兴起,传统的运维模式正逐渐向自动化、智能化转型。本文将探讨如何利用容器技术构建一个高效、可靠的自动化运维系统,涵盖系统设计原则、关键技术选型以及实践经验分享。通过引入容器技术,我们可以实现应用的快速部署、弹性伸缩和故障自愈,从而提高运维效率,降低系统维护成本。
|
20天前
|
数据采集 存储 API
网络爬虫与数据采集:使用Python自动化获取网页数据
【4月更文挑战第12天】本文介绍了Python网络爬虫的基础知识,包括网络爬虫概念(请求网页、解析、存储数据和处理异常)和Python常用的爬虫库requests(发送HTTP请求)与BeautifulSoup(解析HTML)。通过基本流程示例展示了如何导入库、发送请求、解析网页、提取数据、存储数据及处理异常。还提到了Python爬虫的实际应用,如获取新闻数据和商品信息。
|
2月前
|
数据采集 机器学习/深度学习 算法框架/工具
利用Python实现基于图像识别的自动化数据采集系统
本文介绍了如何利用Python编程语言结合图像识别技术,构建一个自动化的数据采集系统。通过分析图像内容,实现对特定信息的提取和识别,并将其转化为结构化数据,从而实现高效、准确地采集需要的信息。本文将详细讨论系统的设计思路、技术实现以及应用场景。
|
2月前
|
存储 SQL 关系型数据库
ClickHouse(02)ClickHouse架构设计介绍概述与ClickHouse数据分片设计
ClickHouse的核心架构包括执行过程和数据存储两部分。执行过程涉及Parser与Interpreter解析SQL,通过Column、DataType、Block、Functions和Storage模块处理数据。Column是内存中列的表示,Field处理单个值,DataType负责序列化和反序列化,Block是内存中表的子集,Block Streams处理数据流。Storage代表表,使用不同的引擎如StorageMergeTree。数据存储基于分片和副本,1个分片由多个副本组成,每个节点只能拥有1个分片。
89 0
ClickHouse(02)ClickHouse架构设计介绍概述与ClickHouse数据分片设计
|
2月前
|
运维 Prometheus 监控
构建高效自动化运维系统的关键策略
【2月更文挑战第30天】随着云计算和微服务架构的兴起,现代IT运维环境变得愈加复杂多变。为保持业务连续性、提高响应速度并降低成本,企业亟需构建一个高效的自动化运维系统。本文将深入探讨自动化运维系统构建过程中的关键策略,包括工具和技术选型、流程优化、监控与告警体系搭建以及持续集成/持续部署(CI/CD)实践,旨在为读者提供一个清晰的构建蓝图和实用的实施建议。
|
3天前
|
运维 监控 安全
构建高效自动化运维系统:策略与实践
【4月更文挑战第29天】 在信息技术日新月异的今天,高效的运维管理已成为企业保持竞争力的关键因素。本文将探讨如何构建一个能够适应快速变化需求的自动化运维系统。通过深入分析自动化工具的选择、配置管理的最佳实践以及持续集成和部署的策略,我们旨在为读者提供一个清晰的框架来优化他们的运维流程。文章的核心在于提出一种结合了最新技术和思维模式的综合解决方案,以实现运维工作的最优化。
|
2月前
|
人工智能 运维 监控
构建高性能微服务架构:现代后端开发的挑战与策略构建高效自动化运维系统的关键策略
【2月更文挑战第30天】 随着企业应用的复杂性增加,传统的单体应用架构已经难以满足快速迭代和高可用性的需求。微服务架构作为解决方案,以其服务的细粒度、独立性和弹性而受到青睐。本文将深入探讨如何构建一个高性能的微服务系统,包括关键的设计原则、常用的技术栈选择以及性能优化的最佳实践。我们将分析微服务在处理分布式事务、数据一致性以及服务发现等方面的挑战,并提出相应的解决策略。通过实例分析和案例研究,我们的目标是为后端开发人员提供一套实用的指南,帮助他们构建出既能快速响应市场变化,又能保持高效率和稳定性的微服务系统。 【2月更文挑战第30天】随着信息技术的飞速发展,企业对于信息系统的稳定性和效率要求
|
2天前
|
运维 Kubernetes 持续交付
构建高效自动化运维系统:基于容器技术的持续集成与持续部署实践
【4月更文挑战第30天】 在快速发展的云计算时代,传统的运维模式已无法满足敏捷开发和快速迭代的需求。本文将介绍如何利用容器技术搭建一套高效自动化运维系统,实现软件的持续集成(CI)与持续部署(CD)。文章首先探讨了现代运维面临的挑战,接着详细阐述了容器技术的核心组件和工作原理,最后通过实际案例展示了如何整合这些组件来构建一个可靠、可扩展的自动化运维平台。
|
2天前
|
运维 监控 安全
构建高效自动化运维系统:策略与实践
【4月更文挑战第30天】 在现代IT基础设施管理中,自动化运维不再是可选项而是必需品。随着复杂性的增加和变更的频繁性,自动化可以提高效率、减少错误并释放人员专注于更有价值的任务。本文将探讨构建一个高效的自动化运维系统的关键环节,包括工具选择、流程设计以及监控和优化策略。通过案例分析和最佳实践分享,读者可以获得实施自动化运维的实用指导和启发。
|
3天前
|
机器学习/深度学习 人工智能 运维
构建高效自动化运维系统的策略与实践
【4月更文挑战第29天】 在数字化转型的浪潮中,企业IT基础设施变得日益复杂多变。传统的手动运维方式已无法满足快速响应和高效率的需求。本文将探讨如何通过一系列策略和技术手段构建一个高效的自动化运维系统。首先,分析当前自动化运维的必要性及其带来的益处;接着,详细阐述自动化运维的核心组件、工具选择以及实施步骤;最后,通过案例分析展示自动化运维在实际环境中的应用效果,并讨论面临的挑战及未来发展趋势。

热门文章

最新文章