系统设计类题目汇总
1 设计一个系统统计当前时刻北京用户在线人数
【Redis】位图以及位图的使用场景(统计在线人数和用户在线状态)
1.1 方案一:
- 在用户登录时,使用 Redis SET 将用户 ID 添加到一个特定的键(例如 “online:beijing”)。
- 用户退出时,从 SET 中移除用户 ID。
- 为了统计在线人数,直接获取 SET 的元素数量。
1.2 方案二:为什么不能用一个 count 字段
如果使用单一的 count 字段,当多个进程或线程同时更新该字段时,可能会出现竞争条件,导致不准确的计数。此外,你无法知道哪些用户是在线的。
1.3 方案三:方案一在计数方面不方便,所以我是不是可以把这两个方案结合起来,要查总在线人数的时候使用count字段一次性查出,想要看哪些活跃的人数的时候就按照方案一查(其实redis的set集合中应该也提供了计算元素数量的api)
确实,你可以将两个方案结合起来以便根据不同的需求灵活地查询。
结合方案:
-
处理登录:
- 当用户登录时,执行以下操作:
- 使用 Redis SET 将用户 ID 添加到一个特定的键(例如 “online:beijing”)。
- 使用 Redis 的
INCR
命令增加一个 “online:count:beijing” 字段的值。
- 当用户登录时,执行以下操作:
-
处理退出:
- 当用户退出时,执行以下操作:
- 从 SET “online:beijing” 中移除用户 ID。
- 使用 Redis 的
DECR
命令减少 “online:count:beijing” 字段的值。
- 当用户退出时,执行以下操作:
-
查询在线人数:
- 直接获取 “online:count:beijing” 字段的值。
-
查询在线的具体用户:
- 直接获取 SET “online:beijing” 的所有成员。
这种方法结合了两种方案的优点:INCR
和 DECR
命令都是原子的,这意味着即使有多个进程或线程同时操作,“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新值
6 给一个场景:有很多图片,然后我们需要对图片进行存储,以及查找,有什么数据结构比较适合?如果我要加速查询的速率,你要怎么设计?
6.1 方案一
处理大量图片的存储和检索通常涉及多个层次的设计。以下是针对这一场景的一些建议:
-
存储:
- 分布式文件系统:例如 Hadoop Distributed FileSystem (HDFS) 或 Facebook 的 Haystack,它们专为存储大量文件而设计。
- 对象存储:例如 Amazon S3,它可以存储和检索任意数量的数据。
-
为图片建立索引:
当你只知道图片的元数据(例如上传者、时间、标签等)并希望基于这些数据检索图片时:
- 使用关系数据库或NoSQL数据库来存储图片的元数据和其在分布式文件系统或对象存储中的位置。
当你希望基于图片内容本身进行检索(例如查找与给定图片相似的图片)时:
- 使用特征提取技术从每张图片中提取特征,并使用这些特征为图片建立索引。
- 一种常见的方法是使用哈希函数将图片特征转化为“图像哈希”,并将这些哈希值存储在数据库中。
-
加速查询:
- 缓存:对于经常被查询的图片,可以使用像 Redis 这样的内存数据库进行缓存,以减少对主存储的访问。
- 数据库索引:确保数据库表中用于查询的字段都已经建立了索引。
- 减少数据量:通过数据分片或选择性地只查询某些数据,可以加速查询速度。
- 内容检索优化:对于基于内容的检索,可以使用近似最近邻搜索(ANNS)库,如 FAISS,以加速相似度搜索。
-
其他加速技术:
- CDN:使用内容分发网络(CDN)可以将图片缓存到全球各地,从而加速对图片的访问速度。
- 预加载技术:根据用户的使用模式和行为,预先加载他们可能会访问的图片。
- 图片压缩:通过减少图片的大小,可以加速加载速度和减少存储需求。
-
搜索扩展性:
如果搜索请求量非常大,可以考虑使用分布式搜索引擎,如 Elasticsearch 或 Solr,它们提供了分布式搜索能力,易于扩展,并支持复杂的查询。
总之,选择哪种方法取决于具体的使用场景,例如查询的频率、数据量、预算等。
方案二
是的,使用云存储来存放图片是现代应用中的常见做法,尤其是当应用需要可扩展的存储和全球分布时。以下是这种方法的详细步骤:
-
上传到云存储:
- 用户或应用将图片上传到云存储服务,如 Amazon S3、Google Cloud Storage 或 Azure Blob Storage。
- 这些服务通常会为每个上传的文件提供一个唯一的URL。
-
存储URL:
- 上传成功后,将从云服务获得的URL存储在本地数据库中。这个数据库可以是关系型数据库、NoSQL数据库等。
- 可以存储与图片相关的其他元数据,如上传日期、标签和描述等。
-
查询与检索:
- 当用户或应用需要查询图片时,你可以查询本地数据库并返回相关的URL。
- 用户或应用可以直接使用这些URL从云存储服务下载图片。
-
加速查询:
- 数据库缓存:像 Redis 这样的内存数据库可以用来缓存热门的图片URL。
- CDN:考虑使用内容分发网络服务,这样热门的图片可以缓存在全球各地的边缘位置,从而减少加载时间。
-
安全与权限:
- 云存储服务通常提供详细的权限和安全设置。确保只有授权的用户可以上传或访问图片。
- 如果需要,可以为URL设置过期时间,这样它们在某段时间后就不能再访问了。
这种设计方案不仅可以扩展存储,还可以通过利用云服务的全球网络来提高访问速度,同时,通过将存储与处理解耦,还可以简化应用的架构和维护。
7 讲解了我怎么设计负载均衡算法的,以及每种策略的适用场景
负载均衡的目的是将网络流量分散到多个服务器,以确保每个服务器都不会因超载而宕机,并且可以最大化吞吐量、最小化响应时间并避免任何单一点的故障。以下是一些常用的负载均衡策略,以及各自的适用场景:
-
轮询 (Round Robin)
- 策略:这是最简单的负载均衡算法,请求按顺序分配到服务器。如果服务器列表到达末尾,则重新开始。
- 适用场景:当所有服务器都具有相似的规格并且预期的请求处理时间相似时,轮询是一个好选择。
-
加权轮询 (Weighted Round Robin)
- 策略:与轮询相似,但给每个服务器一个权重,权重较高的服务器会接收到更多的请求。
- 适用场景:当你有不同能力的服务器并希望每台服务器都接收到与其能力相称的流量时。
-
最少连接 (Least Connections)
- 策略:将请求路由到连接数最少的服务器。
- 适用场景:适用于服务器处理速度大致相同,但处理请求的时间可以变化的场景。例如,如果有一个长轮询或Websockets服务。
-
加权最少连接 (Weighted Least Connections)
- 策略:与最少连接类似,但考虑到每个服务器的权重。
- 适用场景:当服务器规格和处理速度不同时,且处理请求的时间可变。
-
IP哈希 (IP Hash)
- 策略:基于请求者的IP地址确定应该路由到哪个服务器。通常是通过取IP的哈希值然后对服务器数取模得到的。
- 适用场景:当你希望来自特定IP的客户端始终连接到同一个服务器,这在需要保持会话或某些级联数据缓存时非常有用。
-
URL哈希 (URL Hash)
- 策略:基于请求URL的哈希值来确定路由到哪个服务器。
- 适用场景:特别适用于HTTP缓存服务器,因为请求的相同URL可以确保路由到包含其缓存的同一服务器。
-
最短延迟 (Least Latency)
- 策略:负载均衡器持续检测每台服务器的延迟或响应时间,并将请求路由到响应最快的服务器。
- 适用场景:对于需要实时或快速响应的应用,如在线游戏或语音通信。
-
健康检查
- 策略:定期检查服务器的健康状况,如果服务器未响应或返回错误,它将从活动服务器池中移除,直至再次被确定为健康。
- 适用场景:适用于任何需要高可用性的应用。
根据你的应用类型、服务器规格和预期的流量模式选择合适的策略是关键。很多现代的负载均衡器都支持这些策略,并允许你基于实时流量模式动态地切换策略。
8 注册中心能否处理容灾情况,这里的灾是指哪些
注册中心是微服务架构中的一个核心组件,它负责为服务提供发现和配置功能。如果注册中心发生故障,可能会对整个系统的正常运行产生巨大影响。因此,确保其高可用性和对各种“灾难”情况的容错能力是至关重要的。
以下是一些可能影响注册中心的“灾难”情况:
- 硬件故障:如服务器、存储或网络设备的物理故障。
- 软件故障:软件缺陷、资源耗尽(例如内存溢出)、不正确的配置等。
- 网络问题:网络分区、延迟、抖动或连接中断等。
- 数据中心故障:如火灾、洪水、电力中断或其他自然灾害。
- 安全事件:如DDoS攻击、恶意软件感染、未经授权的访问或数据泄露。
- 人为错误:如误删除数据、误配置或发布有缺陷的代码。
为了处理这些容灾情况,可以采取以下策略:
- 多实例部署:在不同的物理服务器上运行多个注册中心的实例,确保一个实例故障时,其他实例可以继续提供服务。
- 跨区域部署:在地理位置分散的多个数据中心部署注册中心的实例,确保某一地区的灾难不会影响整体系统。
- 数据持久化:定期将注册中心的数据(例如服务列表、配置数据等)备份到持久存储,以便在故障发生时进行恢复。
- 网络冗余:确保有多条网络路径可供使用,以避免单点故障。
- 安全策略:实施防火墙、入侵检测系统、流量限制和其他安全措施,以防止恶意攻击。
- 监控和报警:持续监控注册中心的健康状况,并在检测到故障时立即发出报警。
- 故障转移和恢复:当检测到故障时,自动将流量切换到健康的注册中心实例,并启动恢复过程。
具体的容灾策略会根据所使用的注册中心软件(如Eureka、Consul、Zookeeper等)和组织的需求有所不同。总之,设计一个高可用和容错的注册中心是确保微服务系统稳定运行的关键。