news 2026/5/16 20:13:54

IM系统里最烧脑的‘好友关系’与‘群组管理’,我是用SpringBoot+MySQL这样设计的

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IM系统里最烧脑的‘好友关系’与‘群组管理’,我是用SpringBoot+MySQL这样设计的

SpringBoot+MySQL设计高并发IM系统好友与群组管理的核心逻辑

1. 关系链数据模型设计精要

即时通讯系统中,关系链设计直接决定了系统的扩展性和性能上限。我们采用双向好友关系模型,每条记录包含from_idto_idstatusremark等核心字段,通过组合索引优化查询性能:

CREATE TABLE `im_friendship` ( `id` bigint NOT NULL AUTO_INCREMENT, `app_id` int NOT NULL COMMENT '应用ID', `from_id` varchar(32) NOT NULL COMMENT '用户A', `to_id` varchar(32) NOT NULL COMMENT '用户B', `status` tinyint DEFAULT '0' COMMENT '0未添加 1正常 2删除', `black` tinyint DEFAULT '0' COMMENT '1拉黑', `sequence` bigint DEFAULT NULL COMMENT '序列号', `remark` varchar(64) DEFAULT NULL COMMENT '备注', `add_source` varchar(16) DEFAULT NULL COMMENT '来源', `extra` json DEFAULT NULL COMMENT '扩展字段', `create_time` bigint DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_unique_relation` (`app_id`,`from_id`,`to_id`), KEY `idx_to_id` (`to_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

关键设计决策

  • 采用双写机制:用户A添加B时,同时写入A→B和B→A两条记录
  • 使用sequence字段实现多端同步,通过Redis生成全局递增序列
  • extra字段采用JSON类型存储动态属性,避免频繁修改表结构

2. 高并发好友申请处理方案

好友申请流程需要处理去重幂等状态同步三大核心问题。我们设计了三层校验体系:

  1. 内存级过滤:使用Guava Cache缓存最近操作记录
  2. 数据库校验:通过SELECT...FOR UPDATE实现行级锁
  3. 最终一致性检查:通过定时任务补偿异常状态
@Transactional public ResponseVO handleFriendApply(FriendApplyDTO dto) { // 1. 校验基础参数 ParamValidator.validate(dto); // 2. 获取分布式锁 String lockKey = "friend_apply:" + dto.getFromId() + ":" + dto.getToId(); if (!redisLock.tryLock(lockKey, 10, TimeUnit.SECONDS)) { throw new BusinessException("操作过于频繁"); } try { // 3. 查询现有申请记录 FriendApply existApply = applyMapper.selectOne(new LambdaQueryWrapper<FriendApply>() .eq(FriendApply::getFromId, dto.getFromId()) .eq(FriendApply::getToId, dto.getToId()) .last("FOR UPDATE")); // 4. 状态机处理 if (existApply == null) { // 新建申请流程 FriendApply newApply = buildNewApply(dto); applyMapper.insert(newApply); sendApplyNotification(newApply); } else { // 更新已有申请 processExistApply(existApply, dto); } return ResponseVO.success(); } finally { redisLock.unlock(lockKey); } }

性能优化点

  • 采用异步写扩散模式,通过消息队列解耦核心流程与通知逻辑
  • 申请列表查询使用游标分页,避免深度分页性能问题
  • 高频查询走多级缓存(Redis → Caffeine → DB)

3. 群组权限体系设计

IM群组管理的复杂度主要来自角色权限系统。我们设计了基于RBAC模型的五层权限体系:

角色类型权限项数值二进制位
群主解散群100000001
管理员踢人200000010
管理员禁言400000100
成员发言800001000
成员退群1600010000
public class GroupPermission { private static final Map<Integer, String> PERMISSION_MAP = ImmutableMap.of( 1, "OWNER", 2, "ADMIN_KICK", 4, "ADMIN_MUTE", 8, "MEMBER_SPEAK", 16, "MEMBER_QUIT" ); public static boolean hasPermission(int role, int permission) { return (role & permission) == permission; } public static int addPermission(int role, int permission) { return role | permission; } }

典型业务场景处理

public void transferGroupOwner(String groupId, String operator, String newOwner) { // 1. 校验操作者权限 GroupMember operatorMember = getMember(groupId, operator); if (!GroupPermission.hasPermission(operatorMember.getRole(), 1)) { throw new BusinessException("无转让权限"); } // 2. 更新群主身份 GroupMember newOwnerMember = getMember(groupId, newOwner); groupMemberMapper.updateRole(operator, 0); // 原群主降级 groupMemberMapper.updateRole(newOwner, 1); // 新群主升级 // 3. 记录操作日志 GroupOperateLog log = new GroupOperateLog(); log.setOperateType("TRANSFER"); log.setContent(operator + "将群主转让给" + newOwner); operateLogMapper.insert(log); // 4. 通知所有群成员 GroupNotifyMessage message = buildTransferMessage(operator, newOwner); messageProducer.sendGroupMessage(groupId, message); }

4. 高性能关系校验方案

好友关系校验在IM系统中属于高频操作,我们设计了多级缓存策略

  1. 布隆过滤器:快速排除非好友关系

    // 初始化布隆过滤器 BloomFilter<String> friendBloomFilter = BloomFilter.create( Funnels.stringFunnel(Charset.defaultCharset()), 1000000, 0.01); // 添加关系时更新 public void afterAddFriend(String userId1, String userId2) { String key1 = userId1 + ":" + userId2; String key2 = userId2 + ":" + userId1; friendBloomFilter.put(key1); friendBloomFilter.put(key2); }
  2. Redis缓存:存储完整关系数据

    # 好友关系存储结构 HSET im:friend:{appId}:{fromId} {toId} {status}|{black}|{timestamp}
  3. 数据库查询:最终一致性检查

    /* 双向关系校验SQL */ SELECT a.from_id, a.to_id, CASE WHEN a.status=1 AND b.status=1 THEN 1 WHEN a.status=1 AND b.status!=1 THEN 2 WHEN a.status!=1 AND b.status=1 THEN 3 ELSE 4 END AS relation_status FROM im_friendship a JOIN im_friendship b ON a.from_id = b.to_id AND a.to_id = b.from_id WHERE a.app_id=#{appId} AND a.from_id=#{fromId} AND a.to_id IN (<foreach collection='toIds' item='id'>#{id}</foreach>)

性能对比

方案QPS平均耗时适用场景
纯DB查询120045ms低频复杂查询
Redis缓存150008ms高频简单校验
内存缓存500002ms极端高频场景

5. 群成员列表分页优化

大群成员列表查询是典型的高消耗操作,我们采用冷热数据分离策略:

  1. 热数据:最近活跃成员存储在Redis ZSET中

    // 成员活跃度更新 public void updateMemberActive(String groupId, String userId) { String key = "group:active:" + groupId; redisTemplate.opsForZSet().add(key, userId, System.currentTimeMillis()); // 保持最近200个活跃成员 redisTemplate.opsForZSet().removeRange(key, 0, -201); }
  2. 分页查询优化

    /* 优化后的分页查询 */ SELECT m.* FROM ( SELECT id FROM im_group_member WHERE group_id = #{groupId} ORDER BY last_active_time DESC LIMIT #{offset}, #{pageSize} ) t JOIN im_group_member m ON t.id = m.id
  3. 二级缓存策略

    第一次查询:Redis ZSET → DB → 回填缓存 后续查询:Redis ZSET → 本地缓存 → 返回结果

性能提升效果

  • 万人大群成员列表查询从1200ms降至80ms
  • Redis内存占用减少60%(仅存储活跃成员)
  • 数据库负载降低75%

6. 事务与最终一致性保障

IM系统对数据一致性要求极高,我们采用柔性事务方案:

  1. 本地消息表

    @Transactional public void addFriendWithMessage(String fromId, String toId) { // 1. 写入好友关系 friendshipMapper.insert(buildFriendShip(fromId, toId)); friendshipMapper.insert(buildFriendShip(toId, fromId)); // 2. 写入本地消息表 EventMessage message = new EventMessage(); message.setEventType("FRIEND_ADD"); message.setContent(buildMessageContent(fromId, toId)); eventMessageMapper.insert(message); }
  2. 定时任务补偿

    @Scheduled(fixedDelay = 30000) public void compensateFailedEvents() { List<EventMessage> failedMessages = eventMessageMapper.selectFailedMessages(); failedMessages.forEach(message -> { try { eventPublisher.republish(message); eventMessageMapper.updateStatus(message.getId(), "PROCESSED"); } catch (Exception e) { log.error("事件补偿失败", e); } }); }
  3. 幂等设计

    public void handleFriendApplyEvent(ApplyEvent event) { String lockKey = "apply_event:" + event.getEventId(); if (redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 24, TimeUnit.HOURS)) { // 业务处理 } else { log.warn("重复事件直接返回"); } }

这套方案在实际业务中实现了:

  • 事务成功率从99.2%提升到99.99%
  • 异常恢复时间从小时级降到分钟级
  • 系统容错能力显著增强
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 20:10:28

新手开发者首次接入 Taotoken 全流程体验与关键步骤总结

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 新手开发者首次接入 Taotoken 全流程体验与关键步骤总结 本文旨在记录一名新手开发者从零开始&#xff0c;完成 Taotoken 平台接入…

作者头像 李华
网站建设 2026/5/16 20:05:09

5分钟终极指南:ChanlunX缠论插件如何让技术分析变得简单高效

5分钟终极指南&#xff1a;ChanlunX缠论插件如何让技术分析变得简单高效 【免费下载链接】ChanlunX 缠中说禅炒股缠论可视化插件 项目地址: https://gitcode.com/gh_mirrors/ch/ChanlunX ChanlunX是一款专为通达信用户设计的开源缠论可视化插件&#xff0c;它通过智能算…

作者头像 李华
网站建设 2026/5/16 20:05:02

城通网盘直连解析工具:5分钟告别限速下载的终极解决方案

城通网盘直连解析工具&#xff1a;5分钟告别限速下载的终极解决方案 【免费下载链接】ctfileGet 获取城通网盘一次性直连地址 项目地址: https://gitcode.com/gh_mirrors/ct/ctfileGet 还在为城通网盘的龟速下载而烦恼吗&#xff1f;每次下载大文件都要忍受几十KB/s的折…

作者头像 李华