Files
java-template/docs/message_integration_v2_critical_fixes.md
T
BrunoGaoandClaude dfee47d3a2 feat: 完成管理员用户查询优化 - 实施admin_user_query_optimization_proposal
🚀 核心优化内容:
- ljwx-boot: 添加user_type和admin_level冗余字段到sys_user表
- ljwx-boot: 重构管理员判断方法,从复杂多表JOIN优化为单字段查询
- ljwx-boot: 实现数据同步维护机制,确保数据一致性
- ljwx-bigscreen: 移除复杂admin_helper,直接在SQL中使用user_type过滤
- 性能提升: 查询复杂度从O(N*M)优化为O(1)

📊 技术改进:
- 数据库: 新增UserType和AdminLevel枚举定义
- 迁移: 创建数据迁移脚本初始化现有用户类型
- 同步: 角色/组织变更时自动更新用户类型字段
- 查询: 消除多表JOIN,直接使用索引查询

🎯 预期效果:
- 减少数据库负载,提升查询性能
- 简化代码逻辑,降低维护成本
- 实时准确,无需复杂缓存机制

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2025-09-12 14:53:34 +08:00

9.4 KiB
Raw Blame History

消息集成V2关键问题修复方案

🔴 紧急修复项目

1. 分区函数致命缺陷修复

问题: TO_DAYS(create_time) 函数导致分区剪枝完全失效

-- ❌ 错误的分区设计
PARTITION BY RANGE (TO_DAYS(create_time)) (
    PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01'))
);

-- ✅ 正确的分区设计
PARTITION BY RANGE (YEAR(create_time) * 100 + MONTH(create_time)) (
    PARTITION p202501 VALUES LESS THAN (202502),
    PARTITION p202502 VALUES LESS THAN (202503),
    PARTITION p202503 VALUES LESS THAN (202504),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

修复脚本:

-- 重建分区表
ALTER TABLE t_device_message_v2 REMOVE PARTITIONING;
ALTER TABLE t_device_message_v2 
PARTITION BY RANGE (YEAR(create_time) * 100 + MONTH(create_time)) (
    PARTITION p202501 VALUES LESS THAN (202502),
    PARTITION p202502 VALUES LESS THAN (202503),
    PARTITION p202503 VALUES LESS THAN (202504),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

2. 索引优化补充

-- 添加缺失的关键索引
ALTER TABLE t_device_message_v2 
ADD INDEX idx_cleanup_expired (expired_time, is_deleted, message_status),
ADD INDEX idx_stats_query (customer_id, create_time, message_type, message_status);

-- 为JSON字段添加虚拟列索引
ALTER TABLE t_device_message_v2 
ADD COLUMN channels_count INT AS (JSON_LENGTH(channels)) STORED,
ADD INDEX idx_channels_count (channels_count);

3. 分布式事务方案

// 使用Spring事务性消息
@Service
@RequiredArgsConstructor
public class MessageServiceV2Fixed {
    
    private final RocketMQTemplate rocketMQTemplate;
    
    @Transactional
    public Long createMessage(MessageCreateRequestV2 request) {
        // 1. 数据库操作
        TDeviceMessageV2 message = buildMessageEntity(request);
        messageMapper.insert(message);
        
        List<TDeviceMessageDetailV2> details = buildDistributionDetails(message, request.getTargets());
        if (!details.isEmpty()) {
            detailMapper.insertBatch(details);
            message.setTargetCount(details.size());
            messageMapper.updateById(message);
        }
        
        // 2. 发送事务性消息(确保数据一致性)
        rocketMQTemplate.sendMessageInTransaction(
            "message:created", 
            message.getId().toString(),
            new MessageCreatedEvent(message, details),
            null // 本地事务执行器会在数据库提交后才发送消息
        );
        
        return message.getId();
    }
}

4. 缓存一致性修复

// 添加分布式锁防止缓存穿透
@Component
public class MessageQueryServiceFixed {
    
    private final RedissonClient redissonClient;
    
    public IPage<MessageResponseV2> getMessagePageWithLock(MessageQueryV2 query) {
        String cacheKey = buildCacheKey("message_page_v2", query);
        String lockKey = "lock:" + cacheKey;
        
        RLock lock = redissonClient.getLock(lockKey);
        try {
            // 尝试获取锁,最多等待10秒,锁定30秒
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                
                IPage<MessageResponseV2> cached = cacheService.get(cacheKey);
                if (cached != null) return cached;
                
                // 只有获得锁的线程才查询数据库
                IPage<MessageResponseV2> result = messageMapper.selectOptimizedMessagePage(page, query);
                cacheService.set(cacheKey, result, Duration.ofMinutes(5));
                return result;
                
            } else {
                // 获取锁失败,返回降级结果
                return getFallbackResult(query);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return getFallbackResult(query);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

5. N+1查询性能优化

<!-- 修复JOIN查询性能问题 -->
<select id="selectOptimizedMessagePageFixed" resultType="MessageResponseV2">
    -- 第一步:查询主数据(利用索引)
    WITH message_page AS (
        SELECT id, customer_id, department_id, user_id, device_sn,
               title, message, message_type, sender_type, receiver_type,
               priority_level, urgency, message_status, sent_time,
               received_time, expired_time, responded_count, target_count,
               require_ack, channels, metadata, create_time, create_user_id, version
        FROM t_device_message_v2
        <where>
            customer_id = #{query.customerId}
            AND is_deleted = 0
            <if test="query.departmentId != null">
                AND department_id = #{query.departmentId}
            </if>
            -- 其他过滤条件...
        </where>
        ORDER BY priority_level DESC, create_time DESC
        LIMIT #{offset}, #{pageSize}
    )
    -- 第二步:批量关联查询(避免N+1)
    SELECT m.*, 
           COALESCE(dept.org_name, '未知部门') as departmentName,
           COALESCE(user.user_name, '系统') as userName
    FROM message_page m
    LEFT JOIN sys_org_units dept ON m.department_id = dept.id AND dept.is_deleted = 0
    LEFT JOIN sys_user user ON m.user_id = user.id AND user.is_deleted = 0
</select>

6. 资源管理修复

// 正确的资源管理
@Service
public class MessageDistributionServiceFixed {
    
    private final ThreadPoolTaskExecutor distributionExecutor;
    
    public MessageDistributionServiceFixed() {
        this.distributionExecutor = new ThreadPoolTaskExecutor();
        this.distributionExecutor.setCorePoolSize(5);
        this.distributionExecutor.setMaxPoolSize(20);
        this.distributionExecutor.setQueueCapacity(100);
        this.distributionExecutor.setThreadNamePrefix("MessageDistribution-");
        this.distributionExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        this.distributionExecutor.initialize();
    }
    
    @PreDestroy
    public void cleanup() {
        distributionExecutor.shutdown();
        try {
            if (!distributionExecutor.getThreadPoolExecutor().awaitTermination(60, TimeUnit.SECONDS)) {
                distributionExecutor.getThreadPoolExecutor().shutdownNow();
            }
        } catch (InterruptedException e) {
            distributionExecutor.getThreadPoolExecutor().shutdownNow();
        }
    }
}

7. 批量操作内存优化

// 防止OOM的分页批量处理
public void batchProcessMessagesFixed(List<MessageV2> messages) {
    int batchSize = 100; // 限制批次大小
    
    for (int i = 0; i < messages.size(); i += batchSize) {
        int end = Math.min(i + batchSize, messages.size());
        List<MessageV2> batch = messages.subList(i, end);
        
        // 处理批次
        processBatch(batch);
        
        // 强制垃圾回收(在批量处理中)
        if (i % 1000 == 0) {
            System.gc();
        }
    }
}

🔧 监控和告警增强

数据库性能监控

-- 慢查询检查
SELECT * FROM information_schema.PROCESSLIST 
WHERE TIME > 1 AND COMMAND != 'Sleep';

-- 分区剪枝效果验证
EXPLAIN PARTITIONS 
SELECT * FROM t_device_message_v2 
WHERE create_time >= '2025-01-01' AND create_time < '2025-02-01';

应用性能监控

// Micrometer指标收集
@Component
public class MessageMetricsCollectorFixed {
    
    private final Counter messageCreateCounter = Metrics.counter("message.create.total");
    private final Timer messageQueryTimer = Metrics.timer("message.query.duration");
    private final Gauge cacheHitRate = Gauge.builder("message.cache.hit.rate")
        .register(Metrics.globalRegistry, this, MessageMetricsCollectorFixed::getCacheHitRate);
    
    @EventListener
    public void onMessageCreated(MessageCreatedEvent event) {
        messageCreateCounter.increment(
            Tags.of("type", event.getMessageType(), 
                   "priority", String.valueOf(event.getPriority()))
        );
    }
    
    private double getCacheHitRate() {
        // 实现缓存命中率计算
        return cacheService.getHitRate();
    }
}

📊 性能验证脚本

#!/bin/bash
# 性能验证脚本

echo "=== V2消息系统性能验证 ==="

# 1. 数据库连接测试
mysql -h localhost -u root -p -e "SELECT COUNT(*) FROM t_device_message_v2;"

# 2. 分区剪枝验证
mysql -h localhost -u root -p -e "
EXPLAIN PARTITIONS 
SELECT COUNT(*) FROM t_device_message_v2 
WHERE create_time >= '2025-01-01';"

# 3. 索引使用验证
mysql -h localhost -u root -p -e "
EXPLAIN 
SELECT * FROM t_device_message_v2 
WHERE customer_id = 1 AND message_status = 'pending' 
ORDER BY create_time DESC LIMIT 20;"

# 4. API响应时间测试
curl -w "@curl-format.txt" -o /dev/null -s "http://localhost:8080/api/v2/message/page?customerId=1"

echo "性能验证完成"

🚀 部署检查清单

  • 分区函数修复完成
  • 缺失索引添加完成
  • 分布式事务配置完成
  • 缓存一致性机制就绪
  • 资源管理优化完成
  • 监控指标配置完成
  • 性能验证通过
  • 告警阈值设置完成

预期修复效果

修复后预期达到的性能指标:

  • 查询响应时间:< 50ms
  • 分区剪枝效果:> 90%
  • 缓存命中率:> 85%
  • 并发处理能力:> 1000 TPS
  • 系统稳定性:99.9% 可用性

这些修复是确保V2版本成功上线的关键前提。