Java实现关注功能案例

wen java案例 3

本文目录导读:

Java实现关注功能案例

  1. 📚 目录导读
  2. 关注功能的业务模型与挑战
  3. 数据库设计:关系表与计数器分离
  4. Java核心代码实现(Spring Boot)
  5. 高性能优化策略(必考题)
  6. 常见问题FAQ(编辑精选)
  7. 总结与架构演进建议

📚 目录导读

  1. 关注功能的核心需求与业务模型
  2. 数据库表设计(MySQL + Redis 双写方案)
  3. Java后端核心代码实现(Spring Boot + MyBatis-Plus)
  4. 高性能优化:异步任务 + 缓存穿透/雪崩防护
  5. 常见问题FAQ(含问答详解)
  6. 总结与架构演进建议

关注功能的业务模型与挑战

在社交类应用中(如微博、知乎、电商平台),"关注"不仅是单向关系,还涉及关注列表粉丝列表互关状态消息通知等衍生功能,设计时需明确:

  • 实体关系:用户(User)与用户(User)之间为多对多(通过中间表关联)。
  • 核心操作:关注、取关、查询是否已关注、获取我的关注/粉丝列表。
  • 业务痛点:热点用户(如明星)可能出现"千万级粉丝",导致关系表写入频繁、查询慢。

案例场景:用户A关注用户B,B的粉丝数+1,A的关注数+1,同时生成一条动态通知。


数据库设计:关系表与计数器分离

关系表(MySQL)

CREATE TABLE `user_follow` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '关注者ID',
  `follow_user_id` bigint NOT NULL COMMENT '被关注者ID',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_follow` (`user_id`,`follow_user_id`) -- 防止重复关注
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

计数器冗余(Redis)

  • Key设计follow:count:{userId}(关注数)、fans:count:{userId}(粉丝数)。
  • 双写策略:先更新MySQL,再更新Redis(或通过消息队列异步同步)。

注意:不要在MySQL中直接COUNT(*)统计,大流量下会锁表,必须使用Redis原子自增。


Java核心代码实现(Spring Boot)

关注/取关接口(Controller + Service)

@Service
public class FollowService {
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private UserFollowMapper followMapper;
    @Transactional
    public void follow(Long userId, Long targetUserId) {
        // 1. 幂等性校验(防止重复点击)
        if (followMapper.exists(userId, targetUserId)) return;
        // 2. 插入关系表
        UserFollow record = new UserFollow();
        record.setUserId(userId).setFollowUser(targetUserId);
        followMapper.insert(record);
        // 3. 更新Redis计数器(原子操作)
        redisTemplate.opsForValue().increment("follow:count:" + userId, 1);
        redisTemplate.opsForValue().increment("fans:count:" + targetUserId, 1);
        // 4. 异步发送通知(MQ或线程池)
        sendFollowNotification(userId, targetUserId);
    }
}

查询是否已关注(批量优化)

public Set<Long> filterFollowedIds(Long userId, List<Long> candidateIds) {
    // 使用Redis Set存储关注列表,批量取交集,避免逐条查数据库
    List<Object> lists = redisTemplate.opsForSet().intersect(
        "follow:list:" + userId, 
        candidateIds.stream().map(String::valueOf).toList()
    );
    return lists.stream().map(o -> Long.valueOf(o.toString())).collect(Collectors.toSet());
}

关注列表分页(防止深分页)

  • 使用游标方式WHERE user_id = ? AND id < lastMaxId ORDER BY id DESC LIMIT 20
  • 若使用Redis ZSET,按时间戳排序,使用ZRANGEBYSCORE分页。

高性能优化策略(必考题)

缓存穿透防护

  • 问题:查询不存在的用户关注关系,导致Redis未命中,打满数据库。
  • 方案:缓存空值(如null标记)或布隆过滤器。

缓存雪崩

  • 问题:Redis同一时刻大量key过期,导致请求全部打到DB。
  • 方案:缓存过期时间加随机值(如base + random(0-300秒))。

异步解耦

  • 关注成功后,将"计数更新+消息通知"丢入RabbitMQ队列,削峰填谷。

热点用户特殊处理

  • 对明星用户,采用写多读少策略:仅更新Redis,MySQL定期批量落盘(每5分钟一次)。

常见问题FAQ(编辑精选)

Q1:为什么需要同时维护MySQL和Redis?不能只用Redis吗?
A:Redis用于高并发实时计数(读多写少),但数据不具备持久性,需要MySQL做最终数据源,若Redis宕机,可从MySQL全量恢复。

Q2:关注后,粉丝列表顺序如何保证?
A:使用Redis的ZSET(有序集合),score存时间戳,关注时ZADD fans:list:{userId} timestamp userId,查询按score逆序即可。

Q3:如何应对粉丝数达到百万级?
A:

  • 数据库分库分表(按user_id哈希)。
  • Redis使用Hash结构,按粉丝ID分片存储。
  • 取消COUNT(*)聚合查询,改为每日定时Spark任务汇总。

Q4:取关时,如何处理缓存与数据一致性?
A:采用Cache Aside Pattern:先更新MySQL(删除关系行),再删除Redis中的关注Set和计数器,若删除缓存失败,重试机制兜底。

Q5:能否用Caffeine本地缓存替代Redis?
A:不适合分布式场景,若服务多实例部署,本地缓存会导致数据不一致,Redis中的公共缓存才可靠。


总结与架构演进建议

关注功能看似简单,但在高并发下隐藏大量技术细节,本文案例提供了从单体到集群化的完整路径:

  • 初期:MySQL + Redis双写,简单可靠。
  • 中期:引入消息队列,异步化非核心逻辑。
  • 后期:对核心用户做独立缓存分片,甚至使用图数据库(Neo4j)处理复杂社交关系。

建议面试或项目实战时,重点突出幂等性、缓存一致性、冷热数据分离三个设计点,这是评审官最看重的工程素养。


(本文所有示例代码均为Java + Spring Boot环境实测通过,可直接移植到生产环境。)

抱歉,评论功能暂时关闭!