10.4 数据库架构演进

面试重要程度:⭐⭐⭐⭐⭐

常见提问方式:如何进行分库分表?读写分离如何实现?数据库架构如何演进?

预计阅读时间:45分钟

🎯 数据库架构演进路径

单体数据库到分布式数据库

/**
 * 数据库架构演进阶段
 */
@Component
public class DatabaseArchitectureEvolution {
    
    /**
     * 架构演进阶段定义
     */
    public enum EvolutionStage {
        SINGLE_DB("单体数据库", "单机MySQL", "< 1万QPS"),
        MASTER_SLAVE("主从复制", "一主多从", "< 10万QPS"),
        READ_WRITE_SPLIT("读写分离", "读写分离中间件", "< 50万QPS"),
        HORIZONTAL_SHARDING("水平分片", "分库分表", "< 100万QPS"),
        DISTRIBUTED_DB("分布式数据库", "分布式事务", "> 100万QPS");
        
        private final String name;
        private final String description;
        private final String capacity;
    }
    
    /**
     * 架构演进决策器
     */
    @Component
    public static class ArchitectureDecisionMaker {
        
        /**
         * 根据业务指标决定架构演进方向
         */
        public EvolutionStage decideNextStage(DatabaseMetrics metrics) {
            long qps = metrics.getQps();
            long dataSize = metrics.getDataSizeGB();
            double cpuUsage = metrics.getCpuUsage();
            double memoryUsage = metrics.getMemoryUsage();
            
            if (qps < 10000 && dataSize < 100 && cpuUsage < 70) {
                return EvolutionStage.SINGLE_DB;
            } else if (qps < 100000 && cpuUsage > 80) {
                return EvolutionStage.MASTER_SLAVE;
            } else if (qps < 500000 && hasReadWriteImbalance(metrics)) {
                return EvolutionStage.READ_WRITE_SPLIT;
            } else if (dataSize > 1000 || qps > 500000) {
                return EvolutionStage.HORIZONTAL_SHARDING;
            } else {
                return EvolutionStage.DISTRIBUTED_DB;
            }
        }
        
        private boolean hasReadWriteImbalance(DatabaseMetrics metrics) {
            return metrics.getReadQps() / (double) metrics.getWriteQps() > 3;
        }
        
        /**
         * 生成演进建议
         */
        public EvolutionRecommendation generateRecommendation(DatabaseMetrics metrics) {
            EvolutionStage nextStage = decideNextStage(metrics);
            
            return EvolutionRecommendation.builder()
                .currentStage(metrics.getCurrentStage())
                .recommendedStage(nextStage)
                .reasons(generateReasons(metrics, nextStage))
                .migrationSteps(generateMigrationSteps(nextStage))
                .estimatedEffort(estimateEffort(nextStage))
                .risks(identifyRisks(nextStage))
                .build();
        }
    }
}

📖 读写分离架构

主从复制与读写分离实现

/**
 * 读写分离架构实现
 */
@Configuration
public class ReadWriteSplitConfiguration {
    
    /**
     * 主从数据源配置
     */
    @Bean
    @Primary
    public DataSource masterDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://master.db.com:3306/app_db");
        config.setUsername("app_user");
        config.setPassword("app_password");
        config.setMaximumPoolSize(50);
        config.setMinimumIdle(10);
        return new HikariDataSource(config);
    }
    
    @Bean
    public DataSource slave1DataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://slave1.db.com:3306/app_db");
        config.setUsername("readonly_user");
        config.setPassword("readonly_password");
        config.setMaximumPoolSize(30);
        config.setMinimumIdle(5);
        config.setReadOnly(true);
        return new HikariDataSource(config);
    }
    
    @Bean
    public DataSource slave2DataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://slave2.db.com:3306/app_db");
        config.setUsername("readonly_user");
        config.setPassword("readonly_password");
        config.setMaximumPoolSize(30);
        config.setMinimumIdle(5);
        config.setReadOnly(true);
        return new HikariDataSource(config);
    }
    
    /**
     * 动态数据源路由
     */
    @Bean
    public DataSource routingDataSource() {
        DynamicRoutingDataSource routingDataSource = new DynamicRoutingDataSource();
        
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("master", masterDataSource());
        dataSourceMap.put("slave1", slave1DataSource());
        dataSourceMap.put("slave2", slave2DataSource());
        
        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(masterDataSource());
        
        return routingDataSource;
    }
    
    /**
     * 动态数据源实现
     */
    public static class DynamicRoutingDataSource extends AbstractRoutingDataSource {
        
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceContextHolder.getDataSourceType();
        }
    }
    
    /**
     * 数据源上下文持有者
     */
    public static class DataSourceContextHolder {
        
        private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
        
        public static void setDataSourceType(String dataSourceType) {
            CONTEXT_HOLDER.set(dataSourceType);
        }
        
        public static String getDataSourceType() {
            return CONTEXT_HOLDER.get();
        }
        
        public static void clearDataSourceType() {
            CONTEXT_HOLDER.remove();
        }
    }
}

读写分离中间件

/**
 * 读写分离中间件实现
 */
@Component
public class ReadWriteSplitMiddleware {
    
    /**
     * 读写分离注解
     */
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataSource {
        String value() default "master";
    }
    
    /**
     * 读写分离切面
     */
    @Aspect
    @Component
    public static class DataSourceAspect {
        
        private final List<String> slaveDataSources = Arrays.asList("slave1", "slave2");
        private final AtomicInteger counter = new AtomicInteger(0);
        
        @Around("@annotation(dataSource)")
        public Object around(ProceedingJoinPoint joinPoint, DataSource dataSource) throws Throwable {
            try {
                String targetDataSource = determineDataSource(dataSource, joinPoint);
                DataSourceContextHolder.setDataSourceType(targetDataSource);
                
                return joinPoint.proceed();
            } finally {
                DataSourceContextHolder.clearDataSourceType();
            }
        }
        
        /**
         * 确定目标数据源
         */
        private String determineDataSource(DataSource dataSource, ProceedingJoinPoint joinPoint) {
            String specifiedDataSource = dataSource.value();
            
            // 如果明确指定了数据源,直接使用
            if (!"auto".equals(specifiedDataSource)) {
                return specifiedDataSource;
            }
            
            // 自动判断读写操作
            String methodName = joinPoint.getSignature().getName().toLowerCase();
            
            if (isReadOperation(methodName)) {
                return selectSlaveDataSource();
            } else {
                return "master";
            }
        }
        
        /**
         * 判断是否为读操作
         */
        private boolean isReadOperation(String methodName) {
            return methodName.startsWith("select") ||
                   methodName.startsWith("get") ||
                   methodName.startsWith("find") ||
                   methodName.startsWith("query") ||
                   methodName.startsWith("count") ||
                   methodName.startsWith("exists");
        }
        
        /**
         * 选择从库数据源(负载均衡)
         */
        private String selectSlaveDataSource() {
            int index = counter.getAndIncrement() % slaveDataSources.size();
            return slaveDataSources.get(index);
        }
    }
    
    /**
     * 主从延迟监控
     */
    @Component
    public static class ReplicationLagMonitor {
        
        @Autowired
        private JdbcTemplate masterJdbcTemplate;
        
        @Autowired
        private JdbcTemplate slaveJdbcTemplate;
        
        /**
         * 检查主从延迟
         */
        @Scheduled(fixedRate = 30000) // 30秒检查一次
        public void checkReplicationLag() {
            try {
                // 获取主库位置
                String masterStatus = masterJdbcTemplate.queryForObject(
                    "SHOW MASTER STATUS", String.class);
                
                // 获取从库状态
                Map<String, Object> slaveStatus = slaveJdbcTemplate.queryForMap(
                    "SHOW SLAVE STATUS");
                
                long lagSeconds = calculateLag(masterStatus, slaveStatus);
                
                if (lagSeconds > 10) { // 延迟超过10秒告警
                    log.warn("Replication lag detected: {} seconds", lagSeconds);
                    handleReplicationLag(lagSeconds);
                }
                
            } catch (Exception e) {
                log.error("Failed to check replication lag", e);
            }
        }
        
        private long calculateLag(String masterStatus, Map<String, Object> slaveStatus) {
            // 计算主从延迟的具体逻辑
            return 0;
        

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试圣经 文章被收录于专栏

Java面试圣经,带你练透java圣经

全部评论
欢迎讨论
点赞 回复 分享
发布于 09-06 11:26 江西

相关推荐

投递联想等公司10个岗位
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务