🚀 核心优化内容: - 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]>
9.4 KiB
9.4 KiB
消息集成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版本成功上线的关键前提。