阿里真题:千万用户社交系统设计

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

真题来源:阿里巴巴2024春招技术面试

考察重点:大规模系统架构设计、高并发处理、数据存储方案

预计阅读时间:50分钟

真题背景

面试官: "设计一个支持千万用户的社交系统,类似微博。需要支持用户关注、发布动态、点赞评论、消息推送等功能。系统要求支持每秒10万QPS,99.9%可用性,用户动态能够实时推送给粉丝。请详细设计系统架构,包括数据库设计、缓存策略、消息推送方案等。"

考察意图:

  • 大规模分布式系统架构设计能力
  • 高并发场景下的技术方案选择
  • 数据存储和缓存的合理设计
  • 实时推送和消息系统设计

🎯 需求分析与架构设计

系统需求分析

/**
 * 千万用户社交系统需求定义
 */
public class SocialSystemRequirements {
    
    // 用户规模
    public static final long TOTAL_USERS = 10_000_000;        // 千万用户
    public static final long DAILY_ACTIVE_USERS = 2_000_000;  // 日活200万
    public static final long PEAK_CONCURRENT_USERS = 500_000; // 峰值并发50万
    
    // 性能需求
    public static final long MAX_QPS = 100_000;               // 10万QPS
    public static final int MAX_LATENCY_MS = 200;             // 响应时间<200ms
    public static final double AVAILABILITY = 99.9;           // 99.9%可用性
    
    // 业务数据量估算
    public static final long DAILY_POSTS = 1_000_000;         // 日发布100万条动态
    public static final long DAILY_INTERACTIONS = 50_000_000; // 日互动5000万次
    public static final int AVG_FOLLOWERS = 100;              // 平均粉丝数100
    public static final int MAX_FOLLOWERS = 10_000_000;       // 最大粉丝数1000万
}

整体架构设计

/**
 * 社交系统整体架构
 */
@Component
public class SocialSystemArchitecture {
    
    /**
     * 核心微服务设计
     */
    @Service
    public class UserService {
        
        @Autowired
        private UserRepository userRepository;
        
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        
        /**
         * 用户注册
         */
        public RegisterResult register(RegisterRequest request) {
            try {
                // 1. 参数验证
                validateRegisterRequest(request);
                
                // 2. 检查用户名/手机号是否已存在
                if (userRepository.existsByUsername(request.getUsername())) {
                    return RegisterResult.error("用户名已存在");
                }
                
                // 3. 创建用户
                User user = User.builder()
                    .username(request.getUsername())
                    .phone(request.getPhone())
                    .password(passwordEncoder.encode(request.getPassword()))
                    .nickname(request.getNickname())
                    .status(UserStatus.ACTIVE)
                    .createTime(new Date())
                    .build();
                
                user = userRepository.save(user);
                
                // 4. 初始化用户相关数据
                initUserData(user.getId());
                
                // 5. 缓存用户信息
                String cacheKey = "user:profile:" + user.getId();
                redisTemplate.opsForValue().set(cacheKey, user, Duration.ofHours(2));
                
                return RegisterResult.success(user.getId());
                
            } catch (Exception e) {
                log.error("User registration failed", e);
                return RegisterResult.error("注册失败");
            }
        }
        
        /**
         * 获取用户资料
         */
        @Cacheable(value = "user:profile", key = "#userId")
        public UserProfile getUserProfile(Long userId) {
            User user = userRepository.findById(userId);
            if (user == null) {
                return null;
            }
            
            UserStats stats = getUserStats(userId);
            
            return UserProfile.builder()
                .userId(user.getId())
                .username(user.getUsername())
                .nickname(user.getNickname())
                .avatar(user.getAvatar())
                .followingCount(stats.getFollowingCount())
                .followersCount(stats.getFollowersCount())
                .postsCount(stats.getPostsCount())
                .build();
        }
    }
    
    /**
     * 关注服务
     */
    @Service
    public class FollowService {
        
        @Autowired
        private FollowRepository followRepository;
        
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        
        /**
         * 关注用户
         */
        @Transactional
        public FollowResult followUser(Long followerId, Long followeeId) {
            try {
                // 1. 参数验证
                if (followerId.equals(followeeId)) {
                    return FollowResult.error("不能关注自己");
                }
                
                // 2. 检查是否已关注
                if (isFollowing(followerId, followeeId)) {
                    return FollowResult.error("已经关注该用户");
                }
                
                // 3. 创建关注关系
                Follow follow = Follow.builder()
                    .followerId(followerId)
                    .followeeId(followeeId)
                    .createTime(new Date())
                    .build();
                
                followRepository.save(follow);
                
                // 4. 更新统计数据
                updateFollowStats(followerId, followeeId, 1);
                
                // 5. 更新缓存
                updateFollowCache(followerId, followeeId, true);
                
                // 6. 发送关注通知
                notificationService.sendFollowNotification(followerId, followeeId);
                
                // 7. 触发时间线更新
                timelineService.onUserFollowed(followerId, followeeId);
                
                return FollowResult.success();
                
            } catch (Exception e) {
                log.error("Follow user failed", e);
                return FollowResult.error("关注失败");
            }
        }
        
        /**
         * 检查关注关系
         */
        public boolean isFollowing(Long followerId, Long followeeId) {
            String cacheKey = "follow:relation:" + followerId + ":" + followeeId;
            
            Boolean cached = (Boolean) redisTemplate.opsForValue().get(cacheKey);
            if (cached != null) {
                return cached;
            }
            
            boolean isFollowing = followRepository.existsByFollowerIdAndFolloweeId(followerId, followeeId);
            redisTemplate.opsForValue().set(cacheKey, isFollowing, Duration.ofHours(1));
            
            return isFollowing;
        }
    }
}

📱 动态发布与时间线设计

动态发布服务

/**
 * 动态发布服务
 */
@Service
public class PostService {
    
    @Autowired
    private PostRepository postRepository;
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 发布动态
     */
    @Transactional
    public PostResult publishPost(PublishPostRequest request) {
        try {
            // 1. 内容审核
            ContentAuditResult auditResult = contentAuditService.auditContent(request.getContent());
            if (!auditResult.isPass()) {
                return PostResult.error("内容审核未通过");
            }
            
            // 2. 创建动态
            Post post = Post.builder()
                .userId(request.getUserId())
                .content(request.getContent())
                .images(request.getImages())
                .type(request.getType())
                .status(PostStatus.PUBLISHED)
                .createTime(new Date())
                .build();
            
            post = postRepository.save(post);
            
            // 3. 更新用户统计
            userStatsService.incrementPostsCount(request.getUserId());
            
            // 4. 缓存动态
            String cacheKey = "post:detail:" + post.getId();
            redisTemplate.opsForValue().set(cacheKey, post, Duration.ofHours(6));
            
            // 5. 异步推送到时间线
            PostPublishedEvent event = PostPublishedEvent.builder()
                .postId(post.getId())
                .userId(request.getUserId())
                .publishTime(post.getCreateTime())
                .build();
            
            rabbitTemplate.convertAndSend("post.exchange", "post.published", event);
            
            return PostResult.success(post.getId());
            
        } catch (Exception e) {
            log.error("Publish post failed", e);
            return PostResult.error("发布失败");
        }
    }
    
    /**
     * 获取动态详情
     */
    public PostDetail getPostDetail(Long postId, Long viewerId) {
        String cacheKey = "post:detail:" + postId;
        
        PostDetail cached = (PostDetail) redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            setUserRelatedInfo(cached, viewerId);
            return cached;
        }
        
        Post post = postRepository.findById(postId);
        if (post == null) {
            return null;
        }
        
        PostDetail detail = buildPostDetail(post, viewerId);
        redisTemplate.opsForValue().set(cacheKey, detail, Duration.ofHours(6));
        
        return detail;
    }
}

时间线服务设计

/**
 * 时间线服务 - 推拉结合策略
 */
@Service
public class TimelineService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 大V用户阈值(粉丝数超过10万)
    private static final int BIG_V_THRESHOLD = 100000;
    
    /**
     * 处理动态发布事件
     */
    @RabbitListener(queues = "post.published.queue")
    public void handlePostPublished(PostPublishedEvent event) {
        Long userId = event.getUserId();
        Long postId = event.getPostId();
        
        // 获取用户粉丝数
        int followersCount = userStatsService.getFollowersCount(userId);
        
        if (followersCount <= BIG_V_THRESHOLD) {
            // 普通用户:推模式,直接推送到粉丝时间线
            pushToFollowersTimeline(userId, postId);
        } else {
            // 大V用户:拉模式,只更新自己的发件箱
            updateUserOutbox(userId, postId);
        }
    }
    
    /**
     * 推送到粉丝时间线(推模式)
     */
    private void pushToFollowersTimeline(Long userId, Long postId) {
        int batchSize = 1000;
        int offset = 0;
        
        while (true) {
            List<Long> followerIds = followService.getFollowerIds(userId, offset, batchSize);
            if (followerIds.isEmpty()) {
                break;
            }
            
            // 批量推送到粉丝时间线
            for (Long followerId : followerIds) {
                String timelineKey = "timeline:home:" + followerId;
                redisTemplate.opsForZSet().add(timelineKey, postId, System.currentTimeMillis());
                
                // 保持时间线长度,只保留最新1000条
                redisTemplate.opsForZSet().removeRange(timelineKey, 0, -1001);
            }
            
            offset += batchSize;
        }
    }
    
    /**
     * 获取用户首页时间线
     */
    public List<PostDetail> getHomeTimeline(Long userId, int page, int size) {
        String timelineKey = "timeline:home:" + userId;
        
        // 获取时间线中的动态ID
        Set<Object> postIds = redisTemplate.opsForZSet().reverseRange(
            timelineKey, page * size, (page + 1) * size - 1);
        
        if (postIds.isEmpty()) {
            // 时间线为空,采用拉模式补充
            return pullTimelineFromFollowees(userId, page, size);
        }
        
        // 批量获取动态详情
        List<Long> postIdList = postIds.stream()
            .map(id -> (Long) id)
            .collect(Collectors.toList());
        
        return postService.getPostDetails(postIdList, userId);
    }
    
    /**
     * 从关注用户拉取时间线(拉模式)
     */
    private List<PostDetail> pullTimelineFromFollowees(Long userId, int page, int size) {
        List<Long> followeeIds = followService.getFolloweeIds(userId, 0, 100);
        List<PostDetail> timeline = new ArrayList<>();
        
        for (Long followeeId : followeeIds) {
            String outboxKey = "timeline:outbox:" + followeeId;
            Set<Object> postIds = redisTemplate.opsForZSet().reverseRange(outboxKey, 0, 10);
            
            for (Object postId : postIds) {
                PostDetail post = postService.getPostDetail((Long) postId, userId);
                if (post != null) {
                    timeline.add(post);
                }
            }
        }
        
        // 按时间排序并分页
        timeline.sort((a, b) -> b.getCreateTime().compareTo(a.getCreateTime()));
        
        int start = page * size;
        int end = Math.min(start + size, timeline.size());
        
        return timeline.subList(start, end);
    }
}

🔔 实时消息推送系统

WebSocket推送服务

/**
 * WebSocket实时推送服务
 */
@Component
public class WebSocketPushService {
    
    /**
     * 通知WebSocket处理器
     */
    @Component
    public static class NotificationWebSocketHandler extends TextWebSocketHandler {
        
        // 用户连接管理
        private final ConcurrentHashMap<Long, Set<WebSocketSession>> userSessions = new ConcurrentHashMap<>();
        
        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
            Long userId = getUserIdFromSession(session);
            if (userId != null) {
                userSessions.computeIfAbsent(userId, k -> ConcurrentHashMap.newKeySet()).add(session);
                log.info("User {} connected, session: {}", userId, session.getId());
            }
        }
        
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
            Long userId = getUserIdFromSession(session);
            if (userId != null) {
                Set<WebSocketSession> sessions = userSessions.get(userId);
                if (sessions != null) {
                    sessions.remove(session);
                    if (sessions.isEmpty()) {
                        userSessions.remove(userId);
                    }
                }
            }
        }
        
        /**
         * 推送消息给指定用户
         */
        public void pushToUser(Long userId, NotificationMessage message) {
            Set<WebSocketSession> sessions = userSessions.get(userId);
            if (sessions != null && !sessions.isEmpty()) {
                String messageJson = JSON.toJSONString(message);
                
                sessions.removeIf(session -> {
                    try {
                        if (session.isOpen()) {
                            session.sendMessage(new TextMessage(messageJson));
                            return false;
                        } else {
                            return true; // 移除已关闭的连接
                        }
                    } catch (Exception e) {
                        log.error("Failed to send message to session: {}", session.getId(), e);
                        return true; // 移除异常连接
                    }
                });
            }
        }
        
        /**
         * 广播消息给所有在线用户
         */
        public void broadcast(NotificationMessage message) {
            String messageJson = JSON.toJSONString(message);
            
            userSessions.values().forEach(sessions -> {
                sessions.removeIf(session -> {
                    try {
                        if (session.isOpen()) {
                            session.sendMessage(new TextMessage(messageJson));
                            return false;
                        } else {
                            return true;
                        }
                    } catch (Exception e) {
                        return true;
                    }
                });
            });
        }
        
        private Long getUserIdFromSession(WebSocketSession session) {
            // 从session中解析用户ID
            String token = session.getHandshakeHeaders().getFirst("Authorization");
            if (token != null) {
                return jwtTokenProvider.getUserIdFromToken(token);
            }
            return null;
        }
    }
    
    /**
     * 消息推送服务
     */
    @Service
    public static class NotificationPushService {
        
        @Autowired
        private NotificationWebSocketHandler webSocketHandler;
        
        @Autowired
        private RabbitTemplate rabbitTemplate;
        
        /**
         * 推送点赞通知
         */
        public void pushLikeNotification(Long postUserId, Long likerUserId, Long postId) {
            NotificationMessage message = NotificationMessage.builder()
                .type(NotificationType.LIKE)
                .fromUserId(likerUserId)
                .toUserId(postUserId)
                .content("点赞了你的动态")
                .relatedId(postId)
                .timestamp(System.currentTimeMillis())
                .build();
            
            // WebSocket实时推送
            webSocketHandler.pushToUser(postUserId, message);
            
            // 保存到数据库(离线推送用)
            saveNotification(message);
        }
        
        /**
         * 推送关注通知
         */
        public void pushFollowNotification(Long followerId, Long followeeId) {
            NotificationMessage message = NotificationMessage.builder()
                .type(NotificationType.FOLLOW)
                .fromUserId(followerId)
                .toUserId(followeeId)
                .content("关注了你")
                .timestamp(System.currentTimeMillis())
                .build();
            
            webSocketHandler.pushToUser(followeeId, message);
            saveNotification(message);
        }
        
        /**
         * 推送评论通知
         */
        public void pushCommentNotification(Long postUserId, Long commentUserId, Long postId, String commentContent) {
            NotificationMessage message = NotificationMessage.builder()
                .type(NotificationType.COMMENT)
                .fromUserId(commentUserId)
                .toUserId(postUserId)
                .content("评论了你的动态:" + commentContent)
                .relatedId(postId)
                .timestamp(System.currentTimeMillis())
                .build();
            
            webSocketHandler.pushToUser(postUserId, message);
            saveNotification(message);
        }
        
        private void saveNotification(NotificationMessage message) {
            // 异步保存通知到数据库
            rabbitTemplate.convertAndSend("notification.exchange", "notification.save", message);
        }
    }
}

🗄️ 数据存储方案

数据库分库分表设计

/**
 * 数据库分库分表设计
 */
@Component
public class DatabaseShardingDesign {
    
    /**
     * 分库分表策略
     */
    public static class ShardingStrategy {
        
        // 数据库分片数量
        private static final int DB_SHARD_COUNT = 16;
        
        // 表分片数量
        private static final int TABLE_SHARD_COUNT = 64;
        
        /**
         * 用户表分片策略
         */
        public static String getUserTableName(Long userId) {
            int tableIndex = (int) (userId % TABLE_SHARD_COUNT);
            return "user_" + tableIndex;
        }
        
        public static String getUserDatabaseName(Long userId) {
            int dbIndex = (int) (userId % DB_SHARD_COUNT);
            return "social_db_" + dbIndex;
        }
        
        /**
         * 动态表分片策略
         */
        public static String getPostTableName(Long postId) {
            int tableIndex = (int) (postId % TABLE_SHARD_COUNT);
            return "post_" + tableIndex;
        }
        
        /**
         * 关注关系表分片策略(按关注者ID分片)
         */
        public static String getFollowTableName(Long followerId) {
            int tableIndex = (int) (followerId % TABLE_SHARD_COUNT);
            return "follow_" + tableIndex;
        }
        
        /**
         * 互动表分片策略(按动态ID分片)
         */
        public static String getInteractionTableName(Long postId) {
            int tableIndex = (int) (postId % TABLE_SHARD_COUNT);
            return "interaction_" + tableIndex;
        }
    }
    
    /**
     * 数据库表结构设计
     */
    public static class TableSchema {
        
        /**
         * 用户表
         */
        public static final String USER_TABLE_DDL = """
            CREATE TABLE user_%d (
                id BIGINT PRIMARY KEY,
                username VARCHAR(50) UNIQUE NOT NULL,
                phone VARCHAR(20) UNIQUE,
                email VARCHAR(100),
                password VARCHAR(255) NOT NULL,
                nickname VARCHAR(50),
                avatar VARCHAR(255),
                bio TEXT,
                gender TINYINT DEFAULT 0,
                birthday DATE,
                location VARCHAR(100),
                status TINYINT DEFAULT 1,
                create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_username (username),
                INDEX idx_phone (phone),
                INDEX idx_create_time (create_time)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
            """;
        
        /**
         * 动态表
         */
        public static final String POST_TABLE_DDL = """
            CREATE TABLE post_%d (
                id BIGINT PRIMARY KEY,
                user_id BIGINT NOT NULL,
                content TEXT,
                images JSON,
                video VARCHAR(255),
                location VARCHAR(100),
                type TINYINT DEFAULT 1,
                status TINYINT DEFAULT 1,
                like_count INT DEFAULT 0,
                comment_count INT DEFAULT 0,
                share_count INT DEFAULT 0,
                create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_user_id (user_id),
                INDEX idx_create_time (create_time),
                INDEX idx_status (status)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
            """;
        
        /**
         * 关注关系表
         */
        public static final String FOLLOW_TABLE_DDL = """
            CREATE TABLE follow_%d (
                id BIGINT PRIMARY KEY AUTO_INCREMENT,
                follower_id BIGINT NOT NULL,
                followee_id BIGINT NOT NULL,
                create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                UNIQUE KEY uk_follow (follower_id, followee_id),
                INDEX idx_follower (follower_id),
                INDEX idx_followee (followee_id),
                INDEX idx_create_time (create_time)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
            """;
        
        /**
         * 互动表(点赞、评论等)
         */
        public static final String INTERACTION_TABLE_DDL = """
            CREATE TABLE interaction_%d (
                id BIGINT PRIMARY KEY AUTO_INCREMENT,
                user_id BIGINT NOT NULL,
                post_id BIGINT NOT NULL,
                type TINYINT NOT NULL COMMENT '1:点赞 2:评论 3:转发',
                content TEXT COMMENT '评论内容',
                status TINYINT DEFAULT 1,
                create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                UNIQUE KEY uk_user_post_type (user_id, post_id, type),
                INDEX idx_post_id (post_id),
                INDEX idx_user_id (user_id),
                INDEX idx_create_time (create_time)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
            """;
    }
}

💡 面试回答要点

标准回答模板

第一部分:架构设计思路

"千万用户社交系统架构设计:
1. 微服务架构:用户服务、关注服务、动态服务、推送服务等
2. 数据存储:MySQL分库分表 + Redis缓存 + ES搜索
3. 消息队列:RabbitMQ处理异步任务和解耦
4. 实时推送:WebSocket + 消息推送服务

核心是通过分层解耦、水平扩展来支撑高并发。"

第二部分:时间线设计

"时间线采用推拉结合策略:
1. 普通用户(粉丝<10万):推模式,发布时推送到粉丝时间线
2. 大V用户(粉丝>10万):拉模式,用户访问时实时拉取
3. Redis ZSet存储时间线,按时间戳排序
4. 异步处理关注/取关事件,更新时间线

平衡了实时性和存储成本。"

第三部分:数据存储方案

"分库分表策略:
1. 用户表:按用户ID哈希分片,16库64表
2. 动态表:按动态ID分片,支持高并发写入
3. 关注表:按关注者ID分片,优化关注列表查询
4. 互动表:按动态ID分片,便于统计和查询

配合Redis缓存热点数据,ES支持全文搜索。"

第四部分:性能优化

"性能优化措施:
1. 多级缓存:本地缓存 + Redis + CDN
2. 异步处理:消息队列解耦,提升响应速度
3. 读写分离:主从复制,读多写少场景优化
4. 限流熔断:保护系统稳定性

通过水平扩展和缓存优化支撑10万QPS。"

核心要点总结:

  • ✅ 掌握大规模社交系统的架构设计思路
  • ✅ 理解时间线的推拉结合策略和实现方案
  • ✅ 具备分库分表和数据存储的设计能力
  • ✅ 了解实时推送和高并发优化技术
Java面试圣经 文章被收录于专栏

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

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

相关推荐

查看30道真题和解析
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

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