系统设计类题目汇总

1 设计一个系统统计当前时刻北京用户在线人数

【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态)

1.1 方案一:

  • 在用户登录时,使用 Redis SET 将用户 ID 添加到一个特定的键(例如 “online:beijing”)。
  • 用户退出时,从 SET 中移除用户 ID。
  • 为了统计在线人数,直接获取 SET 的元素数量。

1.2 方案二:为什么不能用一个 count 字段

如果使用单一的 count 字段,当多个进程或线程同时更新该字段时,可能会出现竞争条件,导致不准确的计数。此外,你无法知道哪些用户是在线的。

1.3 方案三:方案一在计数方面不方便,所以我是不是可以把这两个方案结合起来,要查总在线人数的时候使用count字段一次性查出,想要看哪些活跃的人数的时候就按照方案一查(其实redis的set集合中应该也提供了计算元素数量的api)

确实,你可以将两个方案结合起来以便根据不同的需求灵活地查询。

结合方案

  1. 处理登录

    • 当用户登录时,执行以下操作:
      1. 使用 Redis SET 将用户 ID 添加到一个特定的键(例如 “online:beijing”)。
      2. 使用 Redis 的 INCR 命令增加一个 “online:count:beijing” 字段的值。
  2. 处理退出

    • 当用户退出时,执行以下操作:
      1. 从 SET “online:beijing” 中移除用户 ID。
      2. 使用 Redis 的 DECR 命令减少 “online:count:beijing” 字段的值。
  3. 查询在线人数

    • 直接获取 “online:count:beijing” 字段的值。
  4. 查询在线的具体用户

    • 直接获取 SET “online:beijing” 的所有成员。

这种方法结合了两种方案的优点:INCRDECR 命令都是原子的,这意味着即使有多个进程或线程同时操作,“online:count:beijing” 的值也会被准确地更新。而 SET “online:beijing” 则提供了查询特定在线用户的能力。

需要注意的是,这种方法会稍微增加存储和操作的开销,因为每次登录或退出都需要操作两个 Redis 键。但从实用性和灵活性的角度来看,这是一个很好的折中方案。

1.4 方案四:使用bitMap

前几个方案的缺点:如果使用set,会存储每一个用户的id,在1亿用户量的情况下,每一个用户id占用4B,总的内存使用量就是10^9*4B=4GB,内存会撑爆

答:所以这个时候会使用位图,将每一个在线用户放入到一个编码函数生成一串数字,根据对应的数字将其在bitMap中对应位置的值置为1,用户下线时就将对应位置的值置换为0,此时内存使用量为100000000/8b/1024B/1024MB 约等于 12MB;

本方案不足:当需要查找在线人数的时候,就是用bitcount()获取,但是这个方法会遍历bitMap,复杂度是O(n)的

1.5 方案五:使用bitMap+count字段

新设置一个count字段,用于统计在线人数,然后每次上线一个用户,就使用原子化操作将bitMap和count自增操作打包在一起更新。这样在查询总人数的时间复杂度也是O(1)

2 让你设计一个mysql优化器,怎么设计

2.1 收集统计信息:

扫描数据表和索引来估计行数、数据分布和存储大小。
**定期更新这些统计信息,**以保持查询优化器的信息是最新的。

2.2 SQL 重写:

解析输入的 SQL 查询并形成一个初始的执行计划。
对计划进行转化,例如合并相邻的表扫描,简化 WHERE 子句等。

2.3 索引建议:

分析查询以确定哪些列经常被用作过滤条件。
基于这些信息提供索引创建的建议,以加速查询。

2.4 缓存:

为经常运行的查询结果提供缓存,避免重复的计算。
考虑缓存的失效策略,如 LRU。

2.5 分析查询:

对查询的执行计划进行深入的分析,找出可能的性能瓶颈。
提供关于查询如何修改或重写以改善性能的建议。

3 让你设计一个延时任务系统怎么做?

3.1 Redis ZSET

(1)使用 Redis ZSET,score作为时间戳,任务id作为哈希表的key:
(2)分片: 为了抗高并发,可以将数据分散到多个 Redis 实例中,使用一致性哈希或其他分片算法。
(3)持久化: 利用 Redis 的 RDB 或 AOF 功能,确保数据不丢失。
(4)哨兵模式: 用于故障转移,当主节点出现问题时,哨兵可以自动将从节点提升为主节点。

3.2 时间片轮转算法

时间轮是一个非常高效的延时任务调度方法,其基本概念是将时间分成多个小的时间片段,并使用一个循环队列(轮子)来表示。每个槽代表一个时间片段。时间轮持续地旋转,当时间推进到某个槽时,会执行该槽中的所有任务。

  • 初始化: 创建一个固定大小的时间轮,每个槽都有一个任务队列。
  • 添加任务: 根据任务的延时时间,计算应该放入哪个槽。将任务放入相应槽的任务队列中。
  • 时间推进: 定期(例如每秒)检查当前槽,执行所有任务,然后移动到下一个槽。
  • 槽溢出处理: 对于超过时间轮大小的延时,可以使用多层时间轮来处理。

4 Redis 的 ZSET 做排行榜时,如果要实现分数相同时按时间顺序排序怎么实现?

4.1 方案一:拆分 score:

即将 score 拆分为高 32 位和低 32 位,高32位存储时间戳,低32位存储score

4.2 方案二:使用 ZSET

使用 ZSET 存储分数,再使用一个 HASH 表存储每个用户的时间戳。在获取排行榜时,首先按分数排序,分数相同的则根据 HASH 表中的时间戳排序。

5 redis实现好友关系、粉丝数

5.1 好友关系(使用一个set存储我关注的人)

  • 对于每个用户,使用一个 SET 来存储他的所有好友的 ID。
  • 添加好友:在两个用户的 SET 中互相添加对方的 ID。
  • 删除好友:在两个用户的 SET 中互相移除对方的 ID。
  • 检查是否为好友:查询其中一个用户的 SET 是否包含另一个用户的 ID。
  • 获取好友列表:直接获取用户的 SET 中的所有元素。
  • 共同好友:将两个用户的set都查出来,取得交集

5.2 粉丝数

再设置一个set,存储关注我的人,别人关注我就需要同时在两个set上put新值

你可能感兴趣的:(系统设计类面经)