51mee - AI智能招聘平台Logo
模拟面试题目大全招聘中心会员专区

设计一个用户表的高并发读写方案,用于360安全产品的用户数据管理。请说明分库分表策略(垂直/水平分表)、读写分离、分片路由(一致性哈希)、缓存(Redis)及Golang分片路由实现。

360服务端开发工程师-Golang难度:困难

答案

1) 【一句话结论】:为支撑360安全产品(用户量达亿级,用户ID连续增长)的高并发读写,采用水平分库分表(按用户ID哈希+分段+随机偏移分片)、读写分离(主从复制)、Redis缓存(热点数据预热+LRU淘汰),结合一致性哈希实现分片路由,并通过Golang封装路由逻辑,以分散数据压力、提升系统吞吐。

2) 【原理/概念讲解】:

  • 分库分表:
    • 垂直分表:按业务字段拆分表(如用户基本信息表、用户行为表),减少单表字段数,适合字段关联紧密的业务(类比:把大蛋糕按功能切小块,每个小块只放相关食材)。
    • 水平分表:按数据量或ID哈希分表(如用户表按ID取模100分片),适合海量数据,分散单表压力(类比:把大蛋糕切成多个小蛋糕,每个小蛋糕重量均衡)。
  • 读写分离:主库负责写(事务一致性),从库负责读(复制主库数据),通过负载均衡(如Nginx)分发读请求,提升读吞吐(类比:主库是“总账”,从库是“分账”,读请求优先从分账处理,减少总账压力)。
  • 一致性哈希:节点列表为环状,数据哈希值取模节点列表,节点增删时仅影响部分数据迁移(迁移量约1/n),解决分片节点动态调整的迁移问题(类比:环形轨道上,新增站点仅影响部分列车停靠,不影响整体)。
  • Redis缓存:缓存热点数据(如用户登录状态、常用配置),减少数据库压力,设置过期时间(避免缓存穿透),淘汰策略用LRU(最近最少使用),缓存击穿用布隆过滤器过滤无效请求(类比:缓存是“快速缓存”,减少对数据库的频繁访问,布隆过滤器是“防穿透的网”)。

3) 【对比与适用场景】:

方案/概念定义特性使用场景注意点
垂直分表按业务字段拆分表(如用户表拆为用户基础表、用户行为表)单表字段少,查询关联少,但需跨表关联业务字段关联紧密,数据量不大(如用户表字段多,拆为多个表)需维护表间关联逻辑,扩展性一般(新增字段需新增表)
水平分表按数据量或ID哈希分表(如用户表按ID取模100分片)单表数据量分散,适合海量数据用户数据量巨大(如360安全产品用户量亿级),读/写高并发需处理跨表关联(如关联其他表需合并分片),分片路由复杂(如热点分片)
读写分离主库写,从库读(主从复制)提升读吞吐,降低主库压力读请求远多于写请求(如360安全产品用户查询登录状态、数据统计,读/写比例约10:1)需保证从库数据一致性(延迟),写操作需同步到从库(如延迟超过500ms时回退主库)
一致性哈希节点列表环状,数据哈希取模节点节点增删时数据迁移量小(约1/n)分片节点可能动态增删(如扩容、故障转移)需处理哈希冲突(如取模余数相同),节点故障时数据迁移(如故障节点数据迁移到新节点)

4) 【示例】:

  • 分片路由(水平分表+ID分段+随机偏移,缓解热点分片):
    func getShardUserID(userID int64) string {
        // 分段:取模100,再取模10(子分片),加随机偏移
        baseShard := userID % 100
        subShard := (userID % 10) + rand.Intn(10) // 随机偏移0-9
        return fmt.Sprintf("user_%d_%d", baseShard+1, subShard+1)
    }
    func GetUserByID(userID int64) (User, error) {
        shard := getShardUserID(userID)
        db, err := sql.Open("mysql", fmt.Sprintf("user:pass@tcp:db%d:3306/%s", shard, "user_db"))
        if err != nil { return User{}, err }
        var user User
        err = db.QueryRow("SELECT * FROM users WHERE id = ?", userID).Scan(&user.ID, &user.Name, ...)
        return user, err
    }
    
  • 一致性哈希示例(节点列表为["node1", "node2", "node3"]):
    func getShardConsistentHash(key string) string {
        hash := fnv1a(key)
        nodes := []string{"node1", "node2", "node3"}
        idx := (hash % uint32(len(nodes)))
        return nodes[idx]
    }
    
  • 跨表关联示例(水平分表后,关联其他表):
    查询用户表所有分片,再关联行为表:
    SELECT u.*, b.* FROM (
        SELECT id FROM user_db.user_1
        UNION ALL
        SELECT id FROM user_db.user_2
        -- ... 所有分片
    ) u
    JOIN user_db.behavior b ON u.id = b.user_id;
    

5) 【面试口播版答案】:
“针对360安全产品用户数据的高并发读写,核心方案是分库分表(水平分表为主,按用户ID哈希+分段+随机偏移分片)、读写分离(主从复制)、Redis缓存。具体来说,水平分表通过用户ID取模100后,再取模10并加随机偏移,分散海量数据压力,避免热点分片;读写分离用主库写、从库读,提升读吞吐,从库延迟超过500ms时回退主库;Redis缓存缓存热点数据(如登录状态、常用配置),设置过期时间(避免缓存穿透),淘汰策略用LRU,缓存击穿时用布隆过滤器过滤无效请求。分片路由采用一致性哈希,节点增删时数据迁移量小,Golang中通过封装分片函数(如getShardUserID)实现路由,确保请求正确路由到对应分片。这样能支撑亿级用户的高并发读写场景。”

6) 【追问清单】:

  • 问:分片路由中如何处理哈希冲突?
    回答要点:通过取模余数相同,设置随机偏移或轮询解决(如取模后+随机数0-9,或取模后取余数后轮询)。
  • 问:缓存穿透/雪崩如何处理?
    回答要点:缓存穿透用布隆过滤器或空值缓存(缓存null值);缓存雪崩设置随机过期时间(如原过期时间+随机数0-300秒),避免集中过期。
  • 问:分片扩容时数据迁移如何处理?
    回答要点:一致性哈希下,新增节点后重新计算哈希值,迁移部分数据(迁移量约1/n),减少影响(如新增节点后,原节点数据迁移到新节点,迁移时间控制在分钟级)。
  • 问:读写分离的延迟如何控制?
    回答要点:从库延迟通过监控(如Prometheus监控延迟),延迟超过阈值(如500ms)时,写操作回退主库,读操作优先从从库,并设置读从库的熔断机制。
  • 问:跨表关联如何处理?
    回答要点:水平分表后,关联表需合并分片(如先查询用户表所有分片,再关联其他表),或使用分布式事务(如两阶段提交,但成本高,适用于关键业务)。

7) 【常见坑/雷区】:

  • 热点分片:按ID取模导致用户ID连续增长,部分分片数据量激增,影响性能,需用分段+随机偏移缓解。
  • 跨表关联:水平分表后,关联其他表需合并分片,否则查询效率低,需先聚合用户ID再关联。
  • 缓存击穿:热点数据(如登录状态)缓存失效时,大量请求直接访问数据库,需用布隆过滤器过滤无效请求,或设置互斥锁。
  • 分片路由错误:路由逻辑错误导致数据路由到错误分片,需严格测试,如用mock数据验证。
  • 从库延迟:读写分离时,从库延迟可能导致数据不一致,需监控延迟并回退主库,避免脏读。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1