系统设计类题目汇总一

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 时间片轮转算法

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

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

3.3 rocketMQ实现延时队列的机制(保证延迟消息不丢失,在秒杀系统中,用户过期未支付执行订单回退操作时比较常用)

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 方案一

处理大量图片的存储和检索通常涉及多个层次的设计。以下是针对这一场景的一些建议:

数据结构:

  • 哈希表 (HashMap):如果我们需要按照图片的ID或名字快速查找图片,哈希表是非常理想的。其时间复杂度为O(1)。键可以是图片ID或名称,值可以是图片的存储路径或实际的图片数据。

  • 平衡树 (如TreeMap)或者跳表:如果我们需要按照某种顺序(例如,拍摄日期)或者范围来查找图片,平衡树是更好的选择。

  • 前缀树 (Trie):如果我们需要按照图片的名称(或某个特定的字符串标识)来进行前缀搜索,那么前缀树是一个很好的选择。

  1. 存储

    • 分布式文件系统:例如 Hadoop Distributed FileSystem (HDFS) 或 Facebook 的 Haystack,它们专为存储大量文件而设计。
    • 对象存储:例如 Amazon S3,它可以存储和检索任意数量的数据。
  2. 为图片建立索引

    当你只知道图片的元数据(例如上传者、时间、标签等)并希望基于这些数据检索图片时:

    • 使用关系数据库或NoSQL数据库来存储图片的元数据和其在分布式文件系统或对象存储中的位置。

    当你希望基于图片内容本身进行检索(例如查找与给定图片相似的图片)时:

    • 使用特征提取技术从每张图片中提取特征,并使用这些特征为图片建立索引。
    • 一种常见的方法是使用哈希函数将图片特征转化为“图像哈希”,并将这些哈希值存储在数据库中。
  3. 加速查询

    • 缓存:对于经常被查询的图片,可以使用像 Redis 这样的内存数据库进行缓存,以减少对主存储的访问。
    • 数据库索引:确保数据库表中用于查询的字段都已经建立了索引。
    • 减少数据量:通过数据分片或选择性地只查询某些数据,可以加速查询速度。
    • 内容检索优化:对于基于内容的检索,可以使用近似最近邻搜索(ANNS)库,如 FAISS,以加速相似度搜索。
  4. 其他加速技术

    • CDN:使用内容分发网络(CDN)可以将图片缓存到全球各地,从而加速对图片的访问速度。
    • 预加载技术:根据用户的使用模式和行为,预先加载他们可能会访问的图片。
    • 图片压缩:通过减少图片的大小,可以加速加载速度和减少存储需求。
  5. 搜索扩展性

    如果搜索请求量非常大,可以考虑使用分布式搜索引擎,如 Elasticsearch 或 Solr,它们提供了分布式搜索能力,易于扩展,并支持复杂的查询。

总之,选择哪种方法取决于具体的使用场景,例如查询的频率、数据量、预算等。

方案二

是的,使用云存储来存放图片是现代应用中的常见做法,尤其是当应用需要可扩展的存储和全球分布时。以下是这种方法的详细步骤:

  1. 上传到云存储

    • 用户或应用将图片上传到云存储服务,如 Amazon S3、Google Cloud Storage 或 Azure Blob Storage。
    • 这些服务通常会为每个上传的文件提供一个唯一的URL。
  2. 存储URL

    • 上传成功后,将从云服务获得的URL存储在本地数据库中。这个数据库可以是关系型数据库、NoSQL数据库等。
    • 可以存储与图片相关的其他元数据,如上传日期、标签和描述等。
  3. 查询与检索

    • 当用户或应用需要查询图片时,你可以查询本地数据库并返回相关的URL。
    • 用户或应用可以直接使用这些URL从云存储服务下载图片。
  4. 加速查询

    • 数据库缓存:像 Redis 这样的内存数据库可以用来缓存热门的图片URL。
    • CDN:考虑使用内容分发网络服务,这样热门的图片可以缓存在全球各地的边缘位置,从而减少加载时间。
  5. 安全与权限

    • 云存储服务通常提供详细的权限和安全设置。确保只有授权的用户可以上传或访问图片。
    • 如果需要,可以为URL设置过期时间,这样它们在某段时间后就不能再访问了。

这种设计方案不仅可以扩展存储,还可以通过利用云服务的全球网络来提高访问速度,同时,通过将存储与处理解耦,还可以简化应用的架构和维护。

7 讲解怎么设计负载均衡算法的,以及每种策略的适用场景

负载均衡的目的是将网络流量分散到多个服务器,以确保每个服务器都不会因超载而宕机,并且可以最大化吞吐量、最小化响应时间并避免任何单一点的故障。以下是一些常用的负载均衡策略,以及各自的适用场景:

  1. 轮询 (Round Robin)

    • 策略:这是最简单的负载均衡算法,请求按顺序分配到服务器。如果服务器列表到达末尾,则重新开始。
    • 适用场景:当所有服务器都具有相似的规格并且预期的请求处理时间相似时,轮询是一个好选择。
  2. 加权轮询 (Weighted Round Robin)

    • 策略:与轮询相似,但给每个服务器一个权重,权重较高的服务器会接收到更多的请求。
    • 适用场景:当你有不同能力的服务器并希望每台服务器都接收到与其能力相称的流量时。
  3. 最少连接 (Least Connections)

    • 策略:将请求路由到连接数最少的服务器。
    • 适用场景:适用于服务器处理速度大致相同,但处理请求的时间可以变化的场景。例如,如果有一个长轮询或Websockets服务。
  4. 加权最少连接 (Weighted Least Connections)

    • 策略:与最少连接类似,但考虑到每个服务器的权重。
    • 适用场景:当服务器规格和处理速度不同时,且处理请求的时间可变。
  5. IP哈希 (IP Hash)

    • 策略:基于请求者的IP地址确定应该路由到哪个服务器。通常是通过取IP的哈希值然后对服务器数取模得到的。
    • 适用场景:当你希望来自特定IP的客户端始终连接到同一个服务器,这在需要保持会话或某些级联数据缓存时非常有用。
  6. URL哈希 (URL Hash)

    • 策略:基于请求URL的哈希值来确定路由到哪个服务器。
    • 适用场景特别适用于HTTP缓存服务器,因为请求的相同URL可以确保路由到包含其缓存的同一服务器。
  7. 最短延迟 (Least Latency)

    • 策略:负载均衡器持续检测每台服务器的延迟或响应时间,并将请求路由到响应最快的服务器。
    • 适用场景:对于需要实时或快速响应的应用,如在线游戏或语音通信。
  8. 健康检查

    • 策略:定期检查服务器的健康状况,如果服务器未响应或返回错误,它将从活动服务器池中移除,直至再次被确定为健康。
    • 适用场景:适用于任何需要高可用性的应用。

根据你的应用类型、服务器规格和预期的流量模式选择合适的策略是关键。很多现代的负载均衡器都支持这些策略,并允许你基于实时流量模式动态地切换策略。

8 注册中心能否处理容灾情况,这里的灾是指哪些

注册中心是微服务架构中的一个核心组件,它负责为服务提供发现和配置功能。如果注册中心发生故障,可能会对整个系统的正常运行产生巨大影响。因此,确保其高可用性和对各种“灾难”情况的容错能力是至关重要的。

以下是一些可能影响注册中心的“灾难”情况:

  1. 硬件故障:如服务器、存储或网络设备的物理故障。
  2. 软件故障:软件缺陷、资源耗尽(例如内存溢出)、不正确的配置等。
  3. 网络问题:网络分区、延迟、抖动或连接中断等。
  4. 数据中心故障:如火灾、洪水、电力中断或其他自然灾害。
  5. 安全事件:如DDoS攻击、恶意软件感染、未经授权的访问或数据泄露。
  6. 人为错误:如误删除数据、误配置或发布有缺陷的代码。

为了处理这些容灾情况,可以采取以下策略:

  1. 多实例部署:在不同的物理服务器上运行多个注册中心的实例,确保一个实例故障时,其他实例可以继续提供服务。
  2. 跨区域部署:在地理位置分散的多个数据中心部署注册中心的实例,确保某一地区的灾难不会影响整体系统。
  3. 数据持久化:定期将注册中心的数据(例如服务列表、配置数据等)备份到持久存储,以便在故障发生时进行恢复。
  4. 网络冗余:确保有多条网络路径可供使用,以避免单点故障。
  5. 安全策略:实施防火墙、入侵检测系统、流量限制和其他安全措施,以防止恶意攻击。
  6. 监控和报警:持续监控注册中心的健康状况,并在检测到故障时立即发出报警。
  7. 故障转移和恢复:当检测到故障时,自动将流量切换到健康的注册中心实例,并启动恢复过程。

具体的容灾策略会根据所使用的注册中心软件(如Eureka、Consul、Zookeeper等)和组织的需求有所不同。总之,设计一个高可用和容错的注册中心是确保微服务系统稳定运行的关键。

9 如果一台服务器,然后要对单机进行拓展,你要怎么设计后续的拓展工作?

单机拓展(即垂直扩展)和多机拓展(即水平扩展)有其各自的优缺点。垂直扩展是提高单一机器的性能,而水平扩展是通过添加更多的机器来增加整体的系统性能。

如果你希望继续进行单机拓展,以下是一些建议:

单机拓展(垂直扩展):

  1. 硬件升级:

    • 增加 RAM:对于内存密集型任务或需要大量缓存的应用非常有效。
    • 更快的 CPU 或更多的核心:对于计算密集型应用有帮助。
    • 升级存储:考虑使用 SSDs 或更高速的硬盘阵列。
    • 增加磁盘空间:如果存储需求在增加。
    • 网络升级:考虑更高速的网络接口卡、增加带宽或更快速的交换机/路由器。
  2. 软件优化:

    • 操作系统调优:根据应用需求进行系统参数调优。
    • 应用代码优化:优化代码,删除不必要的处理,提高执行效率。
    • 数据库优化:查询优化、正确的索引、数据库参数调优等。
    • 使用更有效的算法和数据结构
  3. 服务分解:将不同的服务(如数据库、缓存、应用服务器)部署在不同的机器上。

多机拓展(水平扩展):

当单机扩展达到瓶颈时,你可能需要考虑水平扩展。

  1. 负载均衡:部署负载均衡器(如 Nginx、HAProxy)将流量分发到多个服务器。

  2. 数据库扩展

    • 主-从复制:例如,MySQL的主从复制可以将读取负载分散到多个从服务器上。
    • 分片(Sharding):将数据分布到多个数据库实例上。
  3. 缓存:部署分布式缓存解决方案如 Redis Cluster 或 Memcached。

  4. 服务分解:将应用拆分成多个独立的微服务,并将它们部署到不同的服务器或集群上。

  5. 使用分布式存储系统:如 Hadoop HDFS、Cassandra 或 Amazon S3。

  6. 消息队列:引入消息队列系统如 Kafka 或 RabbitMQ,以解耦不同的系统组件并提高系统的整体响应性。

总之,拓展工作的设计取决于你的应用需求、预期的用户量、负载类型(读密集、写密集或计算密集)以及预算等因素。在扩展前,最好首先进行性能分析和瓶颈检测,以确定哪种拓展策略最适合你的场景。

9 redis

首先弄明白限流的粒度:
(1)对某一个接口的流量进行限流,限流key是接口id
(2)对某一个用户访问后端系统的粗粒度限流,这个时候的限流key是uid
(3)对某一个用户访问某一个接口的频次进行限流,key是"接口的全限定名或者id+uid"
(4)对某一组用户访问某一个接口的频次进行限流,key是"接口的全限定名或者id+这个用户所属的组id"
如果使用redis的zset实现上述四种限流方式:

(1)第一种的实现方案是dict的key和跳表的score都是时间戳,member是请求id,但是插入一个请求到跳表中的时间复杂度是O(logn),因为这种是按照接口限流的,接口id即为key,在秒杀这样的大流量场景中,可能会产生大key问题,阻塞了其他网络请求的处理,因此不推荐,一种解决办法是降低粒度,比如后面提到的三种,
(2)第二种和第三种不会产生大key,但是会占用大量的redis空间;
(4)第四种是兼顾第一种和第三种

9.1 redis的zset实现滑动窗口限流

滑动窗口是限流常见的一种策略。如果我们把一个用户的 ID 作为 key 来标识一个 zset ,也就是说每一个用户都有自己的限流滑动窗口,member 或者 score 都为访问时的时间戳。我们只需统计该用户在指定时间戳区间内的个数,就能得到这个用户滑动窗口内访问频次,与最大通过次数比较,来决定是否允许通过。

9.2 这样的话,如果是对接口进行限流,那么如果有几十万用户同时访问一个接口,那么查找某一个用户量的代价是不是很大(大key问题,字节朝夕光年面试)?

你描述的是使用 Redis 的 ZSET 数据结构实现滑动窗口对接口进行限流的方法。使用 ZSET 可以高效地为每个用户存储时间窗口内的访问时间戳,并对其进行查询和限流。但你关心的是当大量用户同时访问一个接口时,是否存在查询某个用户数据的性能问题。

首先,让我们解析这个问题:

  1. 数据存储:对于每个用户,我们使用其ID作为键,并在 ZSET 中存储时间戳。因此,每个用户都有自己的 ZSET

  2. 查询效率:当你根据用户 ID 查询一个 ZSET 时,由于每个用户都有自己的键,所以查询操作的时间复杂度是 O(1),这是非常高效的。然后,要计算给定时间窗口内的请求数量,你可能需要进行范围查询(例如使用 ZCOUNT),但这也是非常快速的,因为每一个用户在一段时间内的点击次数都不会太多,比如几十次。

  3. 高并发情况下的问题:在数十万用户同时访问的情况下,主要的瓶颈可能不在查询某个用户的 ZSET,而在 Redis 服务器本身的处理能力和网络延迟上。每次请求都需要与 Redis 进行至少一次交互,这可能会导致高延迟和大量的网络I/O。

所以,回答你的问题,查找特定用户的 ZSET 的代价并不大,但当面对大量并发用户时,整体系统的瓶颈可能出现在网络延迟和 Redis 本身的处理能力上。

如果担心性能问题,可以考虑以下策略:

  1. 使用 Redis 集群:通过使用 Redis 集群来分摊负载和提高可用性。
  2. 本地缓存:为了减少与 Redis 的交互次数,可以在应用服务器上缓存某些用户的请求频率。
  3. 其他限流算法:除了滑动窗口外,还可以考虑使用其他算法,如漏桶或令牌桶,它们可能更适合某些使用场景。

总的来说,使用 Redis 的 ZSET 实现滑动窗口限流是一个高效且实用的方法,但在高并发情境下,需要考虑整个系统的性能和瓶颈。

9.3 如何实现为每一个接口的所有用户进行限流

以接口名+用户id的方式作为key,为每一个这样的组合都建立一个zset,这样的话,最终,如果有10个用户,10个接口,会使用redis存储100个这样的限流zset。会导致在Redis中生成大量的键。
所以一般的情况下

9.4 对用户分组

确实,如果使用接口名+用户ID作为键来进行限流,会导致在Redis中生成大量的键。这在用户和接口数量增长时可能变得不太可扩展。不过,有几种方法和策略可以帮助管理和减少这种情况的复杂性。

  1. 对用户分组:而不是为每个用户单独限流,**可以将用户分成几个组,然后为每个组进行限流。**例如,你可以按用户的角色、级别或其他属性将他们分组。

  2. 全局限流:你可以有一种全局的限流策略,它不是基于用户,而是基于整体的请求率。这适用于那些不需要为每个用户设置独特限制的情况。

  3. 分层限流:首先进行全局限流,然后进行更具体的用户或用户组限流。这种方法结合了上述两种策略,可以提供更灵活的控制。

  4. 使用哈希或其他数据结构:而不是为每个接口和用户组合创建一个键,你可以考虑使用更复杂的数据结构,例如哈希,其中每个接口名是哈希的键,而值则是与该接口关联的所有用户的zset。

  5. 限流的粒度:是否真的需要为每个接口和每个用户都进行限流?也许某些接口不需要这么严格的限流,或者某些用户的行为是值得信任的,所以不需要对他们进行限流。

  6. 清除旧的数据:为了避免Redis存储太多过时的数据,你可以定期清除那些不再需要的限流键。

  7. 使用更大的时间窗口:而不是使用非常短的滑动窗口,可以考虑使用较大的时间窗口(如10分钟或1小时),这样可以减少数据的频繁更新。

  8. 扩展Redis:如果真的需要存储大量的数据,你可以考虑使用Redis集群来分摊负载和提供更高的可用性。

不论采取哪种策略,关键是要确保系统可以灵活地扩展,并且可以满足业务的实际需求。

9.5 对用户分组:而不是为每个用户单独限流,可以将用户分成几个组,然后为每个组进行限流。例如,你可以按用户的角色、级别或其他属性将他们分组。这里,所有的属于这个分组的用户都属于这一个限流器吗?

是的,当你按照“对用户分组”的策略进行限流时,所有属于同一分组的用户共享同一个限流器。这意味着分组内的所有用户共同受到该限流器的限制。

例如,假设你按用户角色进行分组,并为“普通用户”和“VIP用户”建立了两个分组。如果你为“普通用户”分组设置了每秒10次请求的限制,那么该分组内的所有用户共同只能进行10次请求/秒。无论该分组内有多少用户,他们的请求速率总和都不能超过这个限制。

使用这种策略的好处是:

  1. 简化管理:不需要为每个用户单独管理限流,这在有大量用户的场景下尤其有用。
  2. 预防滥用:即使一个分组内的某个用户突然增加了请求频率,也不会影响到其他分组。
  3. 相比于按照单个用户进行限流,分组限流可以解约内存空间

但也有一些潜在的问题或挑战:

  1. 公平性问题:在一个分组内,如果某些用户请求得非常频繁,那么其他用户可能会受到影响,因为他们共享同一个限流器。
  2. 需要精细的分组策略:你需要决定如何对用户进行分组以确保公平性和有效性。例如,不同的用户角色可能有不同的请求模式和需求。

总之,使用用户分组进行限流是一种权衡,它可能更适合某些场景而不适合其他场景。你需要根据实际的业务需求和用户行为来决定是否使用这种策略。

9.6 固定窗口值的限流 和 带指定误差的滑动窗口限流

9.6.1 不用中间件,设计一个单服务的限流,一分钟6000流量;

回答了设置一个变量,控制6000;

缺点:这是一种固定窗口的限流,会造成一分钟的误差,无法应对激增的流量,因为他无法保证限流的速率,比如在第58s的时候,count还是0,但是在59s的时候,流量会激增到6000,然后在第60s结束,count清0,此时1s的流量是6000了,这与我们一分钟6000的流量是相违背的。

如:设需要控制的最大请求数为1w, 在第一个单位时间(0-10s)的最后一秒(即第9s)里达到的请求数为1w,接下来第二个单位时间(10-20s)的第一秒(即第10s)里达到请求数也是1w,由于超时重置发生在两个单位时间之间,所以这2w个请求都将通过控制,也就是说在2s里处理2w个请求,与我们设置的10s里1w个请求的设想是相违背。

9.6.2 题目进阶,任意1分钟,误差为1秒;(字节一面的进阶题目,滑动窗口计数器)

方案一:
  1. 回答设置长度 60的数组,每个框放每秒的流量;
  2. 另设一个总流量变量;
方案二(进阶):

其实思路是这总共就6000的流量,均摊到每一秒就100,所以可以每一秒设置一个计数器,计数器的上限是100,key是服务id,当应用服务器设置的时候发现这个键值对过期了,就重新设置一个为value为0的键值对。这样做的好处是

9.6.3 十个微服务接口共用一个限流器,设计一分钟流量上限5w的限流窗口

方案一:

首先需要将10个微服务接口映射到同一个限流器id上,假设这10个微服务是一个服务组,限流器id也是组id,则可以然后将这个限流器id作为redis的一个key,对应的value是一个zset有序集合,其中的hash表的key和skiplist的score是时间戳,每次set一个请求到zset时,需要设置一个60s后过期的时间戳,随后在使用zset.size()求得最近60s内的有效流量(会剔除过期的流量);

方案二:

如果允许误差为1s的话,可以使用9.6.2中的方案,将10个微服务接口映射到同一个限流器id上,假设这10个微服务是一个服务组,限流器id
也是组id,然后将一分钟放置60个框,每一个框放置该

9.7 TCP中的滑动窗口与应用层实现的滑动窗口限流器的区别

  • 功能区别:TCP的滑动窗口主要用于流量控制和拥塞控制,保证数据传输的稳定性和可靠性;而应用层的滑动窗口限流器主要用于限制应用层请求的速率,防止系统过载。

  • 实现层次:TCP的滑动窗口是在传输层实现的,作用于TCP连接的数据传输;应用层的滑动窗口限流器是在应用层实现的,作用于应用层的请求和响应。

  • 控制对象:TCP的滑动窗口控制的是数据包的传输速率,涉及的是发送方和接收方之间的数据交换;应用层的滑动窗口限流器控制的是应用层请求的速率,涉及的是客户端和服务器之间的请求和响应。

  • 动态调整:TCP的滑动窗口大小会根据网络状况动态调整;应用层的滑动窗口限流器通常是根据业务需求和系统承载能力设置的固定窗口大小。

总之,TCP的滑动窗口和应用层的滑动窗口限流器都是滑动窗口机制的应用,但它们的功能、实现层次、控制对象和动态调整机制都有所不同。

9.8 tcp中的滑动窗口和应用层的实现滑动窗口限流器在滑动时机上的区别?

TCP滑动窗口和应用层滑动窗口限流器在滑动时机上的区别主要体现在以下几个方面:

  1. 滑动的基准

    • TCP滑动窗口的滑动基于数据包的确认机制。当发送方收到接收方的确认ACK,滑动窗口会根据确认的序列号向前移动,表示已经成功发送并确认的数据量。滑动的单位是已确认的数据包数量。

    • 应用层滑动窗口限流器通常基于时间进行滑动。窗口分为多个时间段(或称为“桶”),每个时间段内记录一定数量的请求。当时间流逝,窗口会周期性地向前滑动,丢弃过时的请求记录。滑动的单位是时间。

  2. 滑动的触发条件

    • TCP滑动窗口的滑动是由数据包的确认触发的。当发送方收到接收方的确认ACK,窗口就会滑动。

    • 应用层滑动窗口限流器的滑动通常是周期性的,例如每秒滑动一次。窗口的滑动与请求的处理无关,而是与时间相关。

  3. 滑动的目的

    • TCP滑动窗口的目的是进行流量控制和拥塞控制,保证数据传输的稳定性和可靠性。

    • 应用层滑动窗口限流器的目的是控制请求的速率,防止系统过载。

总结起来,TCP滑动窗口和应用层滑动窗口限流器在滑动时机上的主要区别是,TCP滑动窗口是基于数据包确认进行滑动的,而应用层滑动窗口限流器是基于时间进行滑动的。两者的滑动目的和滑动机制也有所不同。

10 游戏服务器怎么设计

6.1 从你的后台开发经历来说,你觉得做游戏后台最重要的是做哪些事情呢?(腾讯北极光工作室二面问题)

答:最重要的应该是保证低延迟,响应快。然后讲了一些怎么设计一个游戏服务器,比如用户首先打到后端会过一遍缓存,将不符合要求的请求都过滤掉,然后将请求放入到消息队列中进行削峰填谷操作,对于队列的消费,我只需要保证消费者端按照一定的速率去消费,这个速率能发挥数据库的最大性能但是又不会崩掉的那种。

其次还可以使用合适的负载均衡算法,比如使用最短延迟算法,负载均衡器独立部署,定期探测各个节点的延迟情况,然后选择延迟时间最少的节点的路由给客户端。

6.2 还有什么优化呢?

答:可以使用就近缓存策略呢。将一些动态的图片资源提前分发到各个离游戏玩家距离最近的cdn服务器上,这样玩家可以最快的速度获取实时信息,另外可以将游戏地图缓存在客户端,用户只需要请求一次,因为地图是固定的,可以一次性全部保存到本地,这样的话就可以避免每次都请求服务器加载了。

上面说的都是性能上的优化,其实还可以针对一致性、可用性进行说明(可能是时间问题,下面的没说)

6.3 如何保证游戏后台的稳定性和高可用?

答:首先,为了保证后台的稳定性,我们需要有充分的测试,包括压力测试、性能测试和安全测试,确保在高并发场景下,系统能够稳定运行,并及时响应玩家的请求。其次,我们应该采用分布式架构,保证系统的横向扩展能力。当玩家数量增加时,可以通过增加服务器来分摊负载。此外,为了防止单点故障,我们还需要部署冗余的服务,确保任何一个节点出问题,其他节点可以立即接替,保证服务的连续性。

对于高可用,我们需要设置监控和报警机制,一旦系统出现异常,能够及时通知到运维团队,并自动切换到备用服务器或服务。此外,定期的数据备份和灾难恢复策略也是必不可少的,确保在任何情况下数据都不会丢失,并能够在最短时间内恢复服务。

6.4 游戏数据的一致性如何保证?

答:在游戏后台,数据的一致性至关重要,特别是涉及到虚拟物品交易或玩家之间的互动。为了保证数据一致性,我们可以使用事务来确保一系列操作要么全部成功,要么全部失败。此外,使用分布式事务解决方案,如两阶段提交或Saga模式,可以帮助在多个服务或数据库间保持数据一致性。

针对可能出现的网络分区或延迟问题,我们可以采用CAP原理来设计系统。根据业务需求,选择更偏向于一致性还是可用性。例如,对于游戏积分或物品交易,我们可能更偏向于保证数据的一致性,即使牺牲了部分可用性。而对于非关键数据,如玩家的一些统计数据,我们可能更偏向于可用性。

6.5 如何处理作弊玩家?

答:首先,我们需要有一套检测作弊玩家的机制,可以基于玩家的行为数据、游戏日志或者其他玩家的举报来发现异常行为。一旦发现作弊行为,可以根据情况给予警告、限制账号功能或直接封禁账号。此外,通过不断的游戏更新和算法优化,加强反作弊机制,使得作弊变得更加困难。同时,积极与玩家社区互动,鼓励玩家举报作弊行为,并为此提供一些激励措施。

11 如何设计一个高性能/高并发/高可用/高可靠/可扩展的系统?(一般二三面可能会问到)

推荐看这篇文章:如何设计一个高性能/高并发/高可用/高可靠/可扩展的系统?

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值