腾讯音乐一面

news2025/5/25 3:48:28

在这里插入图片描述
1、自我介绍+项目(省略)
2、为什么存储要从TiDB迁移到Mysql?
TiDB 迁移至 MySQL 核心原因总结:

  1. 成本优化

    • TiDB 需多节点集群(PD/TiKV/TiDB Server),硬件、运维及学习成本高。
    • 中小业务(数据量 <10TB,QPS < 万级)用 MySQL 可降本 30%+,避免资源浪费。
  2. 性能适配

    • TiDB 跨节点 RPC 通信增加延迟,MySQL 单机执行简单查询更快。
    • MySQL 本地事务(InnoDB)强于 TiDB 的分布式事务(2PC),适合高频 OLTP 场景。
  3. 生态兼容性

    • 老旧系统依赖 MySQL 特有功能(存储过程/视图)或工具(Canal/PHPMyAdmin),TiDB 部分兼容性不足,迁移可减少适配成本。
  4. 运维简化

    • MySQL 单机监控/备份/恢复流程成熟,运维门槛低;TiDB 需专业团队处理分布式扩缩容、数据均衡等问题。

总结:业务未达分布式规模时,MySQL 在性价比、性能、生态及运维复杂度上更具优势。
3、迁移过程中有遇到过什么问题或者有难度的挑战吗?
TiDB 迁移至 MySQL 的典型问题与解决方案总结:


a. 数据一致性保障(核心挑战)

问题
迁移期间业务持续写入,需保证零停机、零数据丢失。
解决方案

  • 双写同步
    • 使用中间件(如 Canal/Debezium)实时同步 TiDB 增量数据到 MySQL。
    • 业务层双写:优先写 TiDB,异步写 MySQL(通过事务或消息队列保证最终一致)。
  • 数据校验
    • 全量迁移后,用工具(如 pt-table-checksum)对比主键、唯一索引及敏感字段的一致性。
    • 多次校验(如业务低峰期),修复差异后逐步切换流量。

示例
电商订单表迁移时,双写阶段发现因网络抖动导致 MySQL 部分订单号丢失,通过消息队列重试机制补全数据。


b. 查询性能差异
问题
TiDB 的分布式优化器与 MySQL 单机优化器差异导致复杂 SQL(如多表 Join、深度分页)性能下降。
解决方案

  • 索引优化
    • 分析 MySQL 执行计划,添加覆盖索引或前缀索引(如 INDEX(col(20)))。
  • 分页改造
    • 将 TiDB 的 LIMIT 10000,10 改写为基于主键范围的分页(如 WHERE id > 10000 LIMIT 10)。
  • 分库聚合
    • 分布式 Join 场景(如跨分片查询用户订单),改为应用层分库查询后合并结果。

示例
用户行为分析报表的 JOIN 查询在 MySQL 中耗时增加 5 倍,通过拆分子查询并添加联合索引优化至原性能水平。


c. 事务逻辑调整
问题
TiDB 的分布式事务(如跨分片扣库存)迁移后需适配 MySQL 单机事务。
解决方案

  • 业务重构
    • 将跨分片事务拆分为单库操作(如按用户 ID 分片,保证同一用户操作在单库内完成)。
    • 使用消息队列(如 RocketMQ)实现最终一致性(如订单创建后异步扣减库存)。
  • 强一致方案
    • 引入 Seata AT 模式,通过全局事务 ID 和 undo log 协调多库事务。

示例
原 TiDB 跨库转账业务迁移后,通过 Seata 的 AT 模式实现跨分片账户余额同步更新,事务耗时从 200ms 降至 50ms。


d. 分库分表策略转换
问题
TiDB 自动分片迁移至 MySQL 需手动设计分库分表(如订单表按时间分片)。
解决方案

  • 分片键设计
    • 选择高频查询字段(如 user_id)作为分片键,按 Hash 或 Range 分片(如 user_id % 64)。
  • 非分片键查询
    • 对全表扫描场景,使用 全局表(如字典表全量同步至所有分库)。
    • 通过 ES 或 ClickHouse 构建异构索引,支持复杂查询。
      工具选型
    • 使用 ShardingSphereMyCat 实现分片路由,屏蔽业务层复杂性。

示例
物流系统中运单表按 region_id 分片后,查询未带分片键的运单号时,通过 ES 二级索引快速定位分库。


e. 监控体系重建

问题
从 TiDB 的 Prometheus 监控切换至 MySQL 传统监控体系。
解决方案

  • 核心监控指标
    • 性能类:QPS、TPS、慢查询率(阈值建议 ≤1%)。
    • 资源类:连接数、InnoDB 缓冲池命中率(>95%)、锁等待时间。
  • 告警策略
    • 设置慢查询阈值(如 >50ms)、死锁频率(如 1 次/分钟)触发告警。
    • 集成企业微信/钉钉,实时通知运维人员。
      工具推荐
    • 使用 Percona Monitoring and Management (PMM)Prometheus + mysqld_exporter 构建监控。

总结

迁移需从数据流、事务逻辑、查询性能、分片策略、监控五方面系统化处理,核心原则:
渐进式切换:灰度验证 → 双写 → 校验 → 流量切换。
业务适配:根据规模选择分库分表方案,避免过度设计。
自动化工具:利用校验、同步、监控工具降低人工干预风险。

4、rocketMQ是怎么保证不重复的?

RocketMQ 保证消息不重复消费的机制与实现方案
RocketMQ 通过 生产者幂等性 + 消费者去重逻辑 + 业务层容错设计 三者结合实现消息的精准一次消费,具体策略如下:


1. 生产者侧:确保消息唯一性

(1)消息唯一标识设计

  • 业务唯一键(Keys)
    发送消息时强制设置 setKeys("业务唯一键")(如订单号 order_id),用于后续消费者去重。
    Message msg = new Message("Topic", "Tag", "order_12345", "支付成功".getBytes());
    
  • 全局唯一 msgId
    RocketMQ 自动为每条消息生成 msgId(Broker 端生成),但 msgId 在生产者重试时可能重复(如网络超时导致重发),需结合业务唯一键使用。

(2)生产者幂等模式

  • 启用幂等生产者:设置 enableIdempotent=true,RocketMQ 通过 生产者组 + 消息 sequence 号 保证 同一生产者实例 的消息不重复。
  • 限制:仅对同一实例有效,多实例或宕机重启时仍需依赖业务唯一键去重。

示例
订单支付成功后,生产者发送消息时设置 keys=order_12345,即使因网络抖动重试,Broker 会过滤重复消息(相同 keys 的消息在短时间内仅存储一条)。


2. 消费者侧:消息去重机制

(1)基于唯一标识去重

  • 提取标识:消费者从消息中获取 keys(如 order_12345)作为去重依据。
  • 分布式缓存去重
    • 使用 Redis 记录已处理标识,设置过期时间(如订单处理周期 + 10分钟):
    String key = "msg_dedup:" + message.getKeys();
    if (redis.setnx(key, "1")) {  // 若 key 不存在则处理
        processMessage(message);
        redis.expire(key, 3600);  // 过期时间根据业务调整
    }
    
    • 优化:高并发场景下可改用 Redis Lua 脚本 保证原子性操作。

(2)消息重试策略控制

  • 限制重试次数:设置 maxReconsumeTimes=3,超过后消息进入死信队列(无法被正常消费的消息),避免无限重试导致积压。
  • 死信处理:监控死信队列,人工介入或自动化修复(如补偿事务)。

3. 业务层:幂等性兜底设计

(1)核心原则

  • 所有写操作必须幂等:例如订单状态更新使用 UPDATE order SET status=paid WHERE id=12345 AND status=unpaid
  • 唯一约束兜底:数据库层对关键字段(如 order_id)添加唯一索引,拦截重复提交。

(2)典型场景示例

  • 支付回调
    • 支付成功后,根据 out_trade_no(业务唯一键)更新订单状态,即使消息重复,数据库唯一索引或 WHERE 条件会保证幂等性。
  • 库存扣减
    • 使用 CAS 操作:UPDATE stock SET count=count-1 WHERE product_id=100 AND count>=1,避免超卖。

4. 特殊场景优化

(1)全局唯一 ID 生成

  • 若业务无天然唯一键,可引入 Snowflake 算法Redis 自增 ID 生成全局唯一标识,作为消息 keys

(2)事务消息去重

  • 事务消息的 Transaction ID 由生产者生成,需保证唯一性(如结合业务 ID + 时间戳),避免 Commit 阶段重复提交。

(3)顺序消息处理

  • 顺序消息(如订单状态变更)需保证同一 Sharding Key 的消息由同一消费者线程处理,避免并发导致状态混乱。

总结

环节方案关键点
生产者业务唯一键 + 幂等生产者keys 必须唯一,enableIdempotent 减少重试风险
消费者Redis 去重 + 重试次数限制分布式锁防并发,死信队列兜底
业务层幂等 SQL + 唯一约束所有写操作需支持幂等,数据库唯一索引兜底
监控死信队列告警 + 重复消息日志实时监控异常,人工介入修复

最终建议:生产环境中需结合 RocketMQ 机制 + 业务幂等设计 + 监控告警 三位一体,才能实现可靠的消息精准一次消费。
5、rocketMQ的结构是什么样子的?
在这里插入图片描述(不是很全)

  1. 名称服务(NameServer):
    角色:轻量级路由注册中心,无状态(无数据存储,节点间不通信)。
    功能:
    接收 Broker 注册信息(如 Broker 地址、Topic 路由表)。
    为生产者 / 消费者提供动态路由查询(如获取 Topic 对应的 Queue 分布在哪些 Broker 上)。
  2. 消息代理(Broker):
    角色:实际存储消息的节点,支持主从(Master/Slave)架构。
    功能:
    存储消息:按 Topic + Queue 组织消息,通过 commit log 顺序写入磁盘,提升 IO 效率。
    处理读写请求:生产者发送消息至 Broker 的 Master 节点,消费者默认从 Master 拉取(可配置从 Slave 读)。
    主从同步:通过异步复制(默认)或同步复制保证数据可靠性,Slave 节点可用于读负载均衡。
  3. 生产者(Producer):
    角色:发送消息至 Broker,支持集群模式或广播模式。
    关键特性:
    负载均衡:根据 NameServer 返回的路由信息,将消息均匀发送至 Topic 的多个 Queue。
    重试机制:消息发送失败时自动重试(默认 3 次),可配置规避特定 Broker(如故障节点)。
  4. 消费者(Consumer):
    角色:从 Broker 拉取或监听消息,支持推模式(Pull + 长轮询模拟 Push)和拉模式。
    两种模式:
    集群消费:多个消费者实例组成消费组,同一消息仅被组内一个实例消费(通过队列负载均衡实现)。
    广播消费:消息会被消费组内所有实例消费。
  5. 主题与队列(Topic & Queue):
    Topic:消息的逻辑分类(如 “order_topic”),一个 Topic 包含多个 Queue(默认 4 个)。
    Queue:物理存储单元,一个 Queue 对应 Broker 上的一个文件队列,保证消息有序(单个 Queue 内消息按发送顺序存储)。
  6. 其他组件
    控制台(RocketMQ-Console):可视化管理界面,用于查看 Topic、消费组、Broker 状态等。
    工具模块:如 mqadmin 命令行工具,用于创建 Topic、查询消费进度等。

6、了解分布式id生成器吗?

方案核心原理优点缺点适用场景
UUID基于 MAC 地址 + 时间戳 + 随机数生成 36 位字符串简单、无中心节点长度长、无序、不适合数据库索引无需排序的唯一标识
雪花算法时间戳 + 工作节点 ID + 序列号(64bit 长整型)高性能、有序、可反查时间依赖时钟同步、节点数受限高并发有序 ID 生成
数据库自增单库自增主键或分布式主键表(如 MySQL 利用 auto_increment + 分段策略)简单直观存在性能瓶颈(需锁表)低并发场景
Redis 生成利用 Redis 的 INCR 命令原子性生成 ID(可带时间前缀)高性能、可自定义规则依赖 Redis 集群稳定性高并发、需灵活规则场景
美团 Leaf结合雪花算法(Leaf-Snowflake)和号段模式(Leaf-Segment),支持多模式切换灵活、可扩展、兼容旧系统实现复杂大型分布式系统
百度 UidGenerator基于雪花算法,通过 RingBuffer 优化序列号生成,支持高并发低延迟、高吞吐、可动态调整节点需集成框架百度系内部系统

7、雪花算法介绍一下?
核心设计(64bit 长整型):
┌─────────────┬──────────┬──────────────┐
│ 41bit 时间戳 │ 10bit 工作节点ID │ 12bit 序列号 │
└─────────────┴──────────┴──────────────┘
各部分含义:
41bit 时间戳:
单位为毫秒,可表示 (2^41 - 1) / (1000606024365) ≈ 69年(从某个起始时间点开始计算,如 2023-01-01)。
作用:保证 ID 按时间有序,且不同时间戳段的 ID 天然不重复。
10bit 工作节点 ID:
可支持 2^10 = 1024 个节点,通常划分为 5bit 数据中心ID + 5bit 机器ID,便于集群管理。
示例:数据中心 ID 范围 0-31,机器 ID 范围 0-31,共支持 32×32=1024 个节点。
12bit 序列号:
同一节点同一毫秒内可生成 2^12 = 4096 个 ID,通过原子递增保证唯一性。
生成流程:
获取当前时间戳,若与上一次生成时间相同,序列号递增;若不同,序列号重置为 0。
检查工作节点 ID 是否合法(如是否在 0-1023 范围内)。
组合三部分生成 64bit ID。
优点:
** 高性能:** 纯内存计算,无网络 IO 或数据库依赖,单机 QPS 可达数万。
** 有序性:** ID 按时间戳排序,适合数据库索引(如主键自增)。
** 可反查:** 通过时间戳可解析出 ID 生成的大致时间。
缺点:
时钟回退问题:若节点时钟回退到之前某一时刻,可能生成重复 ID。
解决方案:
检测到时钟回退时,等待至回退的时间结束再生成 ID。
记录回退的时间戳,通过序列号补偿(如增加额外的偏移量)。
节点数限制:10bit 最多支持 1024 个节点,若集群规模超过需扩展为 11bit(牺牲序列号位数)。
8、雪花算法如何保证workid不重复?提示用 zookeeper

基于 ZooKeeper 实现雪花算法 WorkId 唯一性的方案
以下是通过 ZooKeeper 分布式协调服务为雪花算法(Snowflake)分配唯一 workId 的完整设计,结合临时节点、顺序节点与异常处理机制,确保分布式环境下节点 ID 不冲突。


1. 核心实现流程

步骤 1:创建 ZooKeeper 父节点

在 ZooKeeper 中预先创建持久化父节点 /snowflake/workers(若不存在则自动创建):

[zk: localhost:2181(CONNECTED) 0] create /snowflake/workers "snowflake_work_ids" 
步骤 2:实例启动时注册临时顺序节点

每个雪花算法实例启动时,在父节点下注册一个 临时顺序节点

// 连接 ZooKeeper(需处理连接超时、重试等异常)
ZooKeeper zk = new ZooKeeper("zk-host:2181", 5000, watchedEvent -> {
    // 监听连接状态变化
});

// 创建临时顺序节点,节点名称示例:/snowflake/workers/worker-0000000001
String nodePath = zk.create(
    "/snowflake/workers/worker-",  // 节点前缀
    new byte[0],                   // 数据内容(无需存储)
    ZooDefs.Ids.OPEN_ACL_UNSAFE,   // 开放权限
    CreateMode.EPHEMERAL_SEQUENTIAL // 临时顺序节点
);
步骤 3:提取 WorkId

从节点路径中提取顺序号,转换为整数并限制范围(如 10bit 对应 0~1023):

// 示例路径:/snowflake/workers/worker-0000000001 → 后缀为 0000000001
String sequenceStr = nodePath.substring(nodePath.lastIndexOf("-") + 1);
long sequence = Long.parseLong(sequenceStr);
int workId = (int) (sequence % 1024);  // 控制 WorkId 在 10bit 范围内
步骤 4:异常处理与容错
  • 节点已存在:若节点创建冲突(极小概率),重试创建。
  • ZooKeeper 连接中断
    • 启动时 ZooKeeper 不可用:启用本地缓存的上次有效 workId(需持久化到磁盘)。
    • 运行中断开:维持当前 workId,但需监听会话状态,尝试重连后重新注册。

2. 关键设计原理

(1)临时顺序节点的优势
特性作用
临时性实例宕机或断开时,节点自动删除,避免僵尸节点占用 workId
顺序性ZooKeeper 自动生成全局唯一递增序号,保证节点名称不重复。
自动清理无需手动维护节点列表,依赖 ZooKeeper 的会话机制自动释放资源。
(2)WorkId 分配逻辑
  • 范围控制:通过 sequence % 1024 强制限制 workId 在 10bit 范围内(0~1023)。
  • 冲突风险:当节点数超过 1024 时,取模会导致 workId 重复,需人工干预(如扩容位数或拆分业务)。

3. 完整代码示例(含容错)

public class SnowflakeWorkIdGenerator {
    private static final String ZK_PATH = "/snowflake/workers";
    private static final int MAX_WORKER_ID = 1023; // 10bit 上限

    public int generateWorkId(ZooKeeper zk) throws Exception {
        // 1. 创建临时顺序节点
        String nodePath = zk.create(
            ZK_PATH + "/worker-", 
            new byte[0], 
            ZooDefs.Ids.OPEN_ACL_UNSAFE, 
            CreateMode.EPHEMERAL_SEQUENTIAL
        );

        // 2. 提取序号并计算 workId
        String sequenceStr = nodePath.substring(nodePath.lastIndexOf("-") + 1);
        long sequence = Long.parseLong(sequenceStr);
        int workId = (int) (sequence % (MAX_WORKER_ID + 1));

        // 3. 检查是否超出上限
        if (workId > MAX_WORKER_ID) {
            throw new IllegalStateException("Workers exceed maximum limit: " + MAX_WORKER_ID);
        }

        return workId;
    }

    // 容错:尝试从本地缓存读取 workId
    public int getWorkIdWithFallback(ZooKeeper zk, String localCachePath) {
        try {
            int workId = generateWorkId(zk);
            // 写入本地缓存
            Files.write(Paths.get(localCachePath), String.valueOf(workId).getBytes());
            return workId;
        } catch (Exception e) {
            // ZooKeeper 不可用时使用本地缓存
            try {
                String cachedId = Files.readAllLines(Paths.get(localCachePath)).get(0);
                return Integer.parseInt(cachedId);
            } catch (IOException ex) {
                throw new RuntimeException("Failed to get workId from cache", ex);
            }
        }
    }
}

4. 注意事项与优化建议

  1. ZooKeeper 集群部署:至少部署 3 节点集群,避免单点故障。
  2. WorkId 回收机制
    • 依赖临时节点自动删除特性,无需手动回收。
    • 若实例需主动释放 workId(如优雅停机),可手动删除节点:
      zk.delete(nodePath, -1); // -1 表示忽略版本检查
      
  3. 监控与告警
    • 监控 /snowflake/workers 下节点数量,接近 1024 时触发告警。
    • 监控 ZooKeeper 会话状态,确保连接稳定性。
  4. 历史节点清理
    • 定期检查父节点下的残留持久节点(异常情况遗留),手动清理。

5. 方案对比

方案优点缺点
ZooKeeper 临时节点自动回收 ID,强一致性依赖 ZooKeeper 可用性
数据库分配无第三方依赖需手动清理,存在僵尸 ID 风险
配置文件硬编码简单快速不适用于动态扩缩容场景

总结:通过 ZooKeeper 的 临时顺序节点 特性,结合业务逻辑的 workId 取模限制,可高效、可靠地实现雪花算法的节点 ID 唯一性分配。需额外处理 ZooKeeper 连接异常和节点数超限问题,确保分布式环境下的稳定性。
9、为什么说hashmap是线程不安全的?桶数组的概念?

1. HashMap 线程不安全的核心原因

(1)多线程扩容导致循环链表(JDK 7 典型问题)

场景

  • 当 HashMap 触发扩容(resize())时,会创建新桶数组(容量翻倍),并迁移旧数据。
  • JDK 7 使用头插法迁移链表:旧链表的节点顺序会被反转插入新链表。

多线程风险

  • 线程 A 和线程 B 同时扩容,可能导致两个线程交替修改链表节点的 next 指针。
  • 结果:链表形成环状结构(如节点 A → B → A),后续 get() 操作遍历链表时陷入死循环。

示例(JDK 7 头插法代码片段):

void transfer(Entry[] newTable) {
    for (Entry<K,V> e : table) {  // 遍历旧数组
        while (e != null) {
            Entry<K,V> next = e.next;  // 线程切换点
            int newIndex = e.hash & (newTable.length - 1);
            e.next = newTable[newIndex];  // 头插法反转链表
            newTable[newIndex] = e;
            e = next;
        }
    }
}

示意图(线程 A 和 B 并发执行导致循环链表):

初始链表:1 → 2 → 3  
线程 A 迁移后:3 → 2 → 1  
线程 B 中断后恢复,继续迁移:1 → 3 → 2 → 1(形成环)  

(2)JDK 8 的优化与残留风险
  • 尾插法替代头插法:JDK 8 在扩容时改为尾插法,避免链表反转,解决了循环链表问题。
  • 残留风险
    • 数据覆盖:多线程同时 put() 时,若哈希到同一桶位,可能覆盖彼此的键值对。
    • 红黑树结构破坏:并发修改红黑树(如插入、删除)可能导致树结构不一致。
    • size 计算错误size 字段未同步,多线程更新后实际元素数与统计值不符。

示例(JDK 8 数据覆盖问题):

// 线程 A 和 B 同时执行 put("a", 1) 和 put("a", 2)
if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);  // 若线程 A 和 B 同时判断 p == null,会重复插入

(3)其他线程安全问题
问题类型描述
脏读线程 A 正在扩容,线程 B 读取到未完全迁移的旧数组,导致数据不完整。
丢失更新两个线程同时调用 put() 插入不同键但哈希冲突的条目,导致其中一个被覆盖。
size 不一致size 字段未原子更新,多线程并发插入后实际元素数量与 size 值不符。

2. 桶数组(Table)的核心概念

(1)定义与结构
  • 底层实现:HashMap 使用 Node<K,V>[] table 数组存储键值对(JDK 7 为 Entry[])。
  • 桶(Bucket):数组的每个元素称为一个桶,存储哈希冲突的键值对集合。
(2)哈希定位机制
  1. 计算哈希值
    int hash = key.hashCode() ^ (key.hashCode() >>> 16);  // 扰动函数减少哈希冲突
    
  2. 确定桶索引
    int index = (table.length - 1) & hash;  // 等价于 hash % table.length(当 length 为 2^n 时)
    
(3)解决哈希冲突
数据结构触发条件时间复杂度JDK 版本
链表桶中元素数量 < 8O(n)JDK 7/8
红黑树桶中元素数量 ≥ 8 且数组容量 ≥ 64O(log n)JDK 8+

示例结构(JDK 8 的桶数组与红黑树):

Table: [
    null,
    Node<K,V>(hash=1, key="a", value=1) → Node<K,V>(hash=9, key="k", value=11),
    TreeNode<K,V>(hash=5, key="c", value=3) → ...  // 红黑树结构
]

3. 线程安全解决方案

(1)替代方案
方案原理适用场景
ConcurrentHashMap分段锁(JDK 7)或 CAS + synchronized(JDK 8),保证并发安全且高性能。高并发读写
Collections.synchronizedMap通过全局锁封装 HashMap,所有操作串行化。低并发或兼容旧代码
Hashtable所有方法使用 synchronized 修饰,性能低下。不推荐,仅遗留系统使用
(2)代码示例
// 使用 ConcurrentHashMap 保证线程安全
Map<String, Integer> safeMap = new ConcurrentHashMap<>();

// 使用 synchronizedMap 包装
Map<String, Integer> synchronizedMap = Collections.synchronizedMap(new HashMap<>());

总结

问题JDK 7 表现JDK 8 优化线程安全方案
循环链表头插法导致死循环尾插法消除循环链表使用 ConcurrentHashMap
数据覆盖存在仍存在同步锁或 CAS 操作
红黑树结构破坏无红黑树并发修改可能破坏结构分段锁保护树操作

结论:HashMap 的线程不安全本质源于 无锁设计 + 共享状态多线程修改,需根据场景选择线程安全的 Map 实现。
10、mysql的隔离级别?
MySQL 的隔离级别详解


1. 四种隔离级别及特性

MySQL 支持 SQL 标准定义的四种隔离级别,隔离性由低到高如下:

隔离级别脏读(Dirty Read)不可重复读(Non-Repeatable Read)幻读(Phantom Read)MySQL 默认
读未提交(Read Uncommitted)允许允许允许
读已提交(Read Committed)禁止允许允许否(Oracle 默认)
可重复读(Repeatable Read)禁止禁止部分禁止是(InnoDB 引擎默认)
串行化(Serializable)禁止禁止禁止

2. 关键问题定义

  • 脏读:事务 A 读取到事务 B 未提交的修改,若 B 回滚,A 读取的数据无效。
    示例:事务 B 更新 balance=200 但未提交,事务 A 读到 balance=200,B 回滚后,A 的数据与实际不一致。

  • 不可重复读:事务 A 多次读取同一行,因事务 B 提交了修改,导致前后结果不一致。
    示例:事务 A 第一次读到 balance=100,事务 B 提交 balance=150,事务 A 第二次读到 balance=150

  • 幻读:事务 A 多次查询同一条件,因事务 B 插入或删除符合条件的数据并提交,导致前后结果集不同。
    示例:事务 A 查询 age>20 得 5 条记录,事务 B 插入一条 age=25 并提交,事务 A 再次查询得 6 条记录。


3. InnoDB 的实现机制

(1)读已提交(Read Committed)
  • 实现原理:通过 MVCC(多版本并发控制),每次查询生成最新的 Read View,只读取已提交的数据版本。
  • 特点
    • 禁止脏读,但允许不可重复读和幻读。
    • 适合对数据实时性要求较高的场景。
(2)可重复读(Repeatable Read)
  • 实现原理
    • 快照读:事务启动时生成一致性快照(Read View),整个事务期间读取同一快照,避免不可重复读。
    • 当前读:通过 Next-Key Locks(行锁 + 间隙锁) 防止其他事务插入新数据,抑制幻读。
  • 特点
    • 禁止脏读和不可重复读。
    • 幻读的“部分禁止”
      • 快照读(普通 SELECT)不会出现幻读(基于一致性视图)。
      • 当前读(如 SELECT ... FOR UPDATE)可能触发幻读检测,通过间隙锁阻塞其他事务的插入操作。

示例

-- 事务 A
BEGIN;
SELECT * FROM users WHERE age > 20;  -- 快照读,返回 5 条记录(其他事务插入不影响)

-- 事务 B
INSERT INTO users (age) VALUES (25); -- 提交后,事务 A 的普通 SELECT 仍返回 5 条

-- 事务 A
SELECT * FROM users WHERE age > 20 FOR UPDATE; -- 当前读,触发间隙锁,阻塞事务 B 的插入
(3)串行化(Serializable)
  • 实现原理:所有操作加锁(读加共享锁,写加排他锁),事务串行执行。
  • 特点
    • 完全禁止脏读、不可重复读和幻读。
    • 性能极差,仅用于强一致性场景(如金融交易)。

4. 隔离级别选择建议

场景推荐隔离级别原因
高并发读写,允许短暂不一致读已提交平衡性能与一致性,避免脏读。
事务需多次读取一致性数据可重复读默认级别,通过 MVCC 和间隙锁保证可重复读,抑制幻读。
强一致性,低并发串行化牺牲性能,确保绝对一致性(如账户余额更新)。

5. 总结

  • InnoDB 默认隔离级别为可重复读,通过 MVCC + Next-Key Locks 实现高效并发控制。
  • 幻读的“部分禁止”
    • 普通查询(快照读)不会出现幻读。
    • 当前读(加锁操作)通过间隙锁阻止其他事务插入,避免幻读。
  • 隔离级别越高,并发性能越低,需根据业务场景权衡选择。
    11、undo log,redolog,bin log分别介绍一下?
  1. undo log(回滚日志)

    • 作用

      • 事务回滚 :当事务进行修改操作(如 INSERT、UPDATE、DELETE)时,undo log 会记录修改前的数据状态。如果事务需要回滚(ROLLBACK),就可以利用 undo log 中的信息将数据恢复到事务修改之前的状态。例如,一个事务执行了 UPDATE 操作将某个字段的值从 10 改为 20,在 undo log 中会记录该字段原来的值 10。如果事务回滚,就可以通过这个记录把值改回 10。
      • MVCC(多版本并发控制) :在数据库的可重复读隔离级别下,InnoDB 存储引擎利用 undo log 来实现 MVCC。它可以为不同事务提供数据的快照,使得事务能够读取到数据的一致性版本。比如,事务 A 在读取数据时,事务 B 对同一数据进行了修改。通过 undo log,事务 A 可以读取到数据在事务 B 修改之前的版本,从而保证事务 A 的一致性读。
    • 格式与存储

      • undo log 是逻辑日志,它记录的是操作的反向步骤。对于 INSERT 操作,其反向操作是 DELETE;对于 UPDATE 操作,是将数据还原为旧值。它存储在 undo 表空间中,可以是 ibdata1 文件(当使用默认的表空间配置时)或者独立的表空间。数据库系统通过 purge 线程定期清理过期的 undo log,判断 undo log 是否过期的标准是当没有事务需要访问旧版本数据时。
    • 与事务的关系

      • 每个事务启动时,会分配 undo log segment。事务提交后,undo log 不会立即被删除,而是被标记为可回收。这是因为 MVCC 机制可能还需要这些 undo log 来提供数据的旧版本给其他事务读取。只有当没有事务需要这些旧版本数据时,purge 线程才会真正清理这些 undo log。
  2. redo log(重做日志)

    • 作用

      • 实现事务的持久性(Durability) :根据 Write - Ahead Logging(WAL)机制,先写 redo log 再更新数据页。这样即使在数据库系统崩溃后,通过 redo log 也可以恢复数据,确保事务的修改最终能够持久地保存到磁盘上。例如,当事务对数据进行修改并提交后,即使在数据尚未完全写入磁盘时系统崩溃,重启后可以通过 redo log 将修改后的数据恢复到磁盘上。
      • 加速数据修改 :数据修改时,先在内存(Buffer Pool)中进行操作,然后将修改记录到 redo log,最后异步地将内存中的数据刷盘。这种方式可以减少磁盘 I/O 操作,提高数据修改的效率。因为写 redo log 的操作相对简单且快速,而数据在内存中的操作速度也很快,同步数据到磁盘的操作可以延迟进行。
    • 格式与存储

      • redo log 是物理日志,它记录的是数据页的物理修改情况,比如某个数据页的某个偏移量写入了新的值。它存储在 ib_logfile0 和 ib_logfile1 文件中(默认情况下,每个文件大小为 48MB,可以通过 innodb_log_file_size 参数进行配置),并且是循环写入的,类似环形缓冲区的模式。
    • 关键流程

      • 当事务修改数据时,首先在内存中的数据页(称为脏页)上进行修改,同时将修改记录写入 redo log buffer。事务提交时,根据 innodb_flush_log_at_trx_commit 参数的配置来决定 redo log 的刷盘时机。如果设置为 1,表示每次事务提交时都同步刷盘;设置为 0,每秒刷盘一次;设置为 2,提交时将 redo log 刷到操作系统缓存。在 MySQL 重启时,系统会通过 redo log 恢复未刷盘的脏页数据,以保证数据的一致性。
  3. bin log(二进制日志)

    • 作用

      • 主从复制 :在主从复制架构中,主库将 bin log 传输给从库,从库解析 bin log 中的语句或数据变更,然后在本地执行这些操作,从而实现主从数据的同步。例如,主库上执行了一个 INSERT 语句插入了一条数据,主库会将这个操作记录在 bin log 中。从库通过读取这个 bin log,也在自己的数据库中执行这个 INSERT 操作,使得主从库的数据保持一致。
      • 数据恢复 :可用于基于时间点(Point - In - Time Recovery)的恢复。如果因为误操作(如误删表)等情况,可以通过 bin log 回滚到之前的状态。比如,管理员在某个时间点错误地删除了一张表,可以通过 bin log 找到删除操作之前的状态,将数据恢复。
    • 格式与存储

      • bin log 是逻辑日志,记录的是对数据的修改操作,如 INSERT、UPDATE、DELETE 等语句。它支持三种格式:

        • ROW 格式 :记录每行数据的具体变更,包括旧值和新值。这种格式安全性高,可以避免因为使用 SELECT * 等可能导致主从不一致的问题,但日志体积相对较大。
        • STATEMENT 格式 :记录 SQL 语句本身,如 INSERT INTO t VALUES (1, ‘a’)。日志体积小,但如果语句中包含不确定性的函数(如 NOW())或存储过程等,可能会导致主从不一致。
        • MIXED 格式 :会根据情况自动在 ROW 格式和 STATEMENT 格式之间切换,是默认的格式。
      • bin log 存储在文件系统中,文件名通常为 mysql - bin.000001 等。可以通过 expire_logs_days 参数配置自动清理过期的 bin log,以避免磁盘空间被占满。

    • 与 InnoDB 的交互

      • 事务提交时,遵循两阶段提交的规则。首先是 redo log 的 prepare 阶段,然后写 bin log,最后 commit redo log。这种机制可以保证 redo log 和 bin log 的一致性,避免在主从复制过程中出现主从数据不一致的情况。例如,如果在 redo log 写入完成但 bin log 写入失败的情况下,通过两阶段提交,可以保证事务要么在主库和从库都成功,要么都失败,从而保证主从复制的正确性。
        12、灰度引擎的过程中如何保证数据一致性?
  4. 核心策略:双写与校验

    • 双写阶段(新旧系统并存)

      • 请求路由

        • 灰度规则是灰度发布的关键部分。通过设定灰度规则,如用户 ID 尾号、白名单等方式,可以将一部分流量引导到新系统,其余流量仍然由旧系统处理。例如,采用用户 ID % 10 == 0 的规则,这样大约有 1/10 的用户请求会进入新系统,其余 9/10 的请求走旧系统。这种分流方式可以让新系统在小规模的流量下进行验证,观察其是否能够正常工作,同时保证大部分流量由稳定的旧系统处理。
      • 数据双写

        • 对于业务写操作,如创建订单等,需要同时写入新旧系统。以订单创建为例,在伪代码中,首先尝试在新系统中创建订单,然后在旧系统中也创建订单。这里需要注意旧系统写入的数据格式可能与新系统不同,需要进行兼容处理。
        • 如果同步双写对性能影响较大,可以通过消息队列实现异步双写。例如,先写新系统,然后发送消息到消息队列,由旧系统消费消息进行写入。但为了保证最终一致性,需要使用事务消息机制,如 RocketMQ 的事务消息。如果新系统写入成功后发送事务消息,旧系统在消费消息时进行写入,若写入失败,可以通过回查等方式进行补偿,确保数据最终在两个系统中都正确写入。
    • 数据校验机制

      • 实时校验

        • 利用中间件(如 Canal)可以实时同步新旧数据库的数据。然后使用数据校验工具(如 DataSphere Studio)对关键业务表(如订单表、用户表)的关键字段(如主键、金额等)进行对比。例如,对于同步过来的订单数据,检查新旧库中的 order_amount 字段是否一致。如果不一致,及时触发告警,以便开发和运维人员能够快速发现并处理数据不一致的问题。
      • 定时全量校验

        • 在每天凌晨等业务低谷时段,对核心表进行全量数据对比。可以采用哈希值校验的方法,例如,对表的数据生成 MD5 哈希值,然后对比新旧库中对应表的哈希值。如果发现数据存在差异,可以通过人工核查来确定问题所在,或者采用自动补偿的机制,如从正确的那一方将数据同步到另一方,修复数据不一致的情况。
    • 回滚机制

      • 如果在灰度发布过程中发现严重的数据不一致问题,比如金额计算出现错误,这可能会导致数据的错误积累和业务的异常。此时,应立即关闭新系统的流量,将所有流量切换回旧系统。同时,要通过 bin log 或备份等方式,将新系统的数据恢复到灰度发布之前的状态,以保证数据的正确性和业务的正常运行。
        13、短暂的数据不一致要怎么办呢?
        针对短暂的数据不一致问题,可以采取以下措施来处理:

一、重试机制

  1. 重试策略

    • 对于因网络波动、资源竞争等原因导致的短暂不一致,可以设置自动重试机制。例如,当调用下游接口失败时,可以重试3次,每次间隔100毫秒。这样可以在短时间内多次尝试完成操作,有可能在短暂问题消失后成功写入数据,从而恢复数据一致性。
    • 重试机制可以应用在多种场景,如数据库连接失败时重试写入操作、微服务之间的接口调用失败时重试请求等。
  2. 幂等性保障

    • 在重试过程中,必须保证操作是幂等的。幂等性是指对同一个操作请求多次执行和执行一次的效果相同。例如,在支付场景中,根据订单号重复提交支付请求时,要确保只扣款一次。可以通过在数据库中添加唯一约束(如订单号在支付记录表中唯一)、使用分布式锁等方式来实现幂等性。这样可以避免因重试导致数据错误,如重复扣款等问题。

二、补偿机制

  1. 异步补偿

    • 利用消息队列来发送补偿消息。例如,当订单状态未成功更新时,可以发送一个order_status_update消息。消费者收到消息后,触发状态修正操作。消息队列可以确保消息的可靠传递,即使消费者暂时不可用,消息也可以在之后被消费并处理。
    • 例如,使用 RabbitMQ 等消息队列系统,生产者在发现数据不一致时将补偿消息发送到指定队列,消费者订阅该队列并进行相应的补偿处理。
  2. 定时任务修复

    • 设计数据修复脚本,定时扫描不一致的数据。例如,每天凌晨可以查询状态为“处理中”且超过24小时的订单,然后调用下游接口确认订单的实际状态。如果发现订单实际已经完成,就将状态更新为“已完成”;如果订单失败,则更新为“失败”。
    • 示例SQL语句:
      UPDATE orders SET status = 'failed' WHERE status = 'processing' AND create_time < NOW() - INTERVAL 1 DAY;
      
      这条语句将超过24小时仍处于“处理中”状态的订单更新为“失败”状态,从而修复数据不一致的问题。

三、最终一致性方案

  1. 采用 BASE 理论
    • 消息队列:通过可靠的消息传递机制来保证跨系统操作的最终一致。例如,使用 RocketMQ 的事务消息。在生产者发送消息时,先发送一个 prepare 消息,当生产者的本地事务完成后,再发送 commit 或 rollback 消费者消息。消费者在收到消息后,进行相应的业务操作,这样可以确保在分布式环境下多个系统操作的一致性。
    • 分布式事务中间件:如 Seata 的 AT 模式。它通过在数据库中记录 undo log(数据前镜像),在事务回滚时可以利用 undo log 恢复数据。这种方式可以在分布式事务场景下,保证事务的原子性和一致性。
    • 对账系统:对于像支付系统这样的核心业务,定期与银行流水等外部数据进行对账。通过对比支付系统记录的交易数据和银行流水数据,找出差异数据。对于差异数据,可以通过人工审核或者自动规则(如根据银行流水状态更新支付系统状态)进行处理,从而实现最终的数据一致。

四、监控与告警

  1. 监控设置

    • 对关键业务指标进行监控,如订单创建成功率、库存一致性比率等。可以使用 Prometheus 等监控工具来收集这些指标数据。
    • 例如,监控订单创建成功率指标,如果成功率低于正常水平,可能意味着存在数据不一致或其他业务问题。
  2. 告警机制

    • 当数据不一致率超过设定的阈值(如0.1%)时,触发告警。通过 Grafana 等工具配置告警面板,当监控指标达到告警条件时,实时通知运维和开发团队。
    • 例如,设置告警规则,当gray_data_inconsistency_count(灰度数据不一致数量)指标在1分钟内增长超过100个时,向相关人员发送短信、邮件或在即时通讯工具中发送告警通知。这样可以及时发现问题并进行处理,避免短暂不一致问题积累成严重的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2385070.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pluto实验报告——基于2ASK的简易的通信系统

一、实验目的 1. 熟悉并掌握PLUTO SDR 主动学习模块的使用&#xff1b; 2.通过matlab 编码与adalm pluto 相配合达成一个简易的通信系统&#xff0c;并能 够传输一些较为简单的信息。 二、实验原理 2ASK 调制原理&#xff1a; 振幅键控是指利用载波的振幅变化来传递数字基带信…

深入了解redis的哈希槽的知识

目录 1、哈希算法分类 1.1、简单哈希算法 1.2、一致性哈希算法 1、原理&#xff1a; 2、解决问题 3、数据倾斜问题 4、虚拟节点 2. 哈希槽 2.1、介绍 2. 2、作用 1、数据分片&#xff08;Sharding&#xff09; 2、高可用性&#xff08;HA&#xff09; 3…

农业机械化、电气化和自动化知网英文普刊:1天录用,2周见刊发表!

CSP科学出版社&#xff0c;旨在通过为研究人员提供最佳环境来发表、参考、阅读和引用他们的作品&#xff0c;从而为科学界服务。现已与科检易学术达成出版战略合作&#xff0c;现在联合共同出版高质量学术水平的期刊&#xff0c;为方便广大科研学者投稿方便&#xff0c;现已经建…

Docker-Compose使用自定义网桥后在OpenWrt系统中容器无法访问网络解决方案

Docker-Compose使用自定义bridge网桥后在OpenWrt系统中容器无法访问网络解决方案 示例compose描述文件如下&#xff0c;注意最后网络配置&#xff1a; # docker-compose --env-file .env.yoko.prod.local up -d services:...postgres:image: kuluseky/postgres-zhparser-post…

界面组件DevExpress WPF中文教程:Grid - 行和卡片

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

Qt enabled + geometry 属性(2)

文章目录 enabled属性可用与禁用的概念API接口代码演示 阐述说明1. 先简单描述下要如何演示出上面两个接口的效果&#xff08;思路&#xff09;2. 事先规范按钮对象的命名3. 定义两个按钮对象的槽函数 动图演示效果4. widget.cpp geometry属性预备知识API接口上下左右移动 ta…

Llamaindex自学笔记(完)

Llamaindex框架主要做RAG&#xff0c;工作流用LangGraph做 换源&#xff1a; -i https://pypi.mirrors.ustc.edu.cn/simple/环境搭建&#xff1a; conda create -n llamaindex python3.12 conda activate llamaindexpip install llama-index pip install llama-cloud-servic…

飞书知识问答深度测评:企业AI应用落地的“范本级”产品

前言 当 AI 逐渐从技术前沿走向日常办公&#xff0c;我们最常听到的一个词是“效率提升”。但真正能做到降本增效、让企业员工切实受益的 AI 产品&#xff0c;仍属少数。尤其是在组织内部知识管理这一块&#xff0c;大多数企业仍停留在“搜索靠关键词、记录靠记忆、协作靠问人…

draw.io的基础与进阶使用指南

前言 一、Draw.io 简介 Draw.io 是一款功能强大的绘图工具&#xff0c;支持在线使用和本地安装。它提供了丰富的模板和形状元素&#xff0c;能够绘制流程图、UML 图、甘特图、网络图等多种图形。Draw.io 的文件格式支持可编辑的矢量图和位图&#xff0c;方便后续修改 draw.io的…

Sanitizers

一、简介 sanitizers 是谷歌提供的一套开源工具&#xff0c;能够发现堆栈读写溢出、内存泄漏、线程数据竞争和死锁等问题。包括&#xff1a; AddressSanitizers (Asan):检测地址相关问题&#xff0c;如use-after-free,heap-buffer-overflow, stack_buffer_overflow,use_after_…

pip代理出现问题 ProxyError

WARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNone)) after connection broken by ‘ProxyError(‘Cannot connect to proxy.’, NewConnectionError(’<pip._vendor.urllib3.connection.HTTPSConnection object at 0x7f8347ad5ae0>: F…

vue+threeJS 创建镂空球体(SphereGeometry)

嗨&#xff0c;我是小路。今天主要和大家分享的主题是“vuethreeJS 创建镂空球体&#xff08;SphereGeometry&#xff09;”。 上次看到一个做镂空球体的项目&#xff0c;自己也准备尝试着做一做。今天终于做完了&#xff0c;并对这个项目进行梳理。 镂空球体示例效果…

[ Qt ] | 常见控件(一)

目录 Widget enable geometry 标题中的&#xff1a;有一不一定有二&#xff0c;但是有一说明还没结束。 Widget 控件(Widget)&#xff0c;是界面上各种元素&#xff0c;各种部分的统称。 Qt中的控件都是继承自QWidget这个类&#xff0c;是Qt控件体系中&#xff0c;通用的…

【八股战神篇】Java虚拟机(JVM)高频面试题

目录 专栏简介 一 请解释Java虚拟机(JVM)及其主要功能 延伸 1. JVM的基本概念 2. JVM的主要功能 二 对象创建的过程了解吗 延伸 1.Java 创建对象的四种常见方式 三 什么是双亲委派模型 延伸 1.双亲委派机制的作用: 2.双亲委派模型的核心思想: 3.双亲委派模型的…

NDVI谐波拟合(基于GEE实现)

在遥感影像中&#xff0c;我们常用 NDVI&#xff08;归一化植被指数&#xff09;来衡量地表植被的绿度。它简单直观&#xff0c;是生态监测、农情分析的基础工具。但你是否注意到&#xff1a; NDVI 虽然“绿”&#xff0c;却常常“乱”。 因为云层、观测频率、天气干扰&#xf…

南京邮电大学《智能控制技术》期末抢救(上)

一、智能控制的提出 传统控制方法包括经典控制和现代控制——基于被控对象精确模型的控制方式&#xff0c;缺乏灵活性和应变能力&#xff0c;适于解决线性、时不变性等相对简单的控制问题。传统控制方法在实际应用中遇到很多难解决的问题&#xff0c;主要表现以下几点&#xff…

TPDS-2014《Efficient $k$-means++ Approximation with MapReduce》

推荐深蓝学院的《深度神经网络加速&#xff1a;cuDNN 与 TensorRT》&#xff0c;课程面向就业&#xff0c;细致讲解CUDA运算的理论支撑与实践&#xff0c;学完可以系统化掌握CUDA基础编程知识以及TensorRT实战&#xff0c;并且能够利用GPU开发高性能、高并发的软件系统&#xf…

地理特征类可视化图像

目录 一、蜂窝热力地图 1. 特点 (1)优点 (2)缺点 2. 应用场景 3.python代码实现 (1)代码 (2)实现结果 二、变形地图 1. 特点 (1)优点 (2)缺点 2. 应用场景 3.python代码实现 (1)代码 (2)实现结果 三、关联地图 1. 特点 (1)优点 (2)缺点 2. 应用场景 3.pyth…

【Java高阶面经:微服务篇】8.高可用全链路治理:第三方接口不稳定的全场景解决方案

一、第三方接口治理的核心挑战与架构设计 1.1 不稳定接口的典型特征 维度表现影响范围响应时间P99超过2秒,波动幅度大(如100ms~5s)导致前端超时,用户体验恶化错误率随机返回5xx/429,日均故障3次以上核心业务流程中断,交易失败率上升协议不一致多版本API共存,字段定义不…

DataGridView中拖放带有图片的Excel,实现数据批量导入

1、带有DataGridView的窗体&#xff0c;界面如下 2、编写DataGridView支持拖放的代码 Private Sub DataGridView1_DragEnter(ByVal sender As Object, ByVal e As DragEventArgs) Handles DataGridView1.DragEnterIf e.Data.GetDataPresent(DataFormats.FileDrop) ThenDim file…