面试题篇-14-业务场景相关面试题

1. 扫码登录到底是怎么实现的?

扫码登录的本质是,通过已经登录过的 App 应用,扫描未登录的 Web 端程序中的二维码, 通过某种机制触发登录凭证的写入从而实现 Web 端自动登录的过程。
在这里插入图片描述

  1. 首先,在网页端打开登录页面,展示一个二维码,这个二维码有一个唯一编号是服务端生成的。然后浏览器定时轮询这个二维码的状态;
  2. 接着,APP 扫描这个二维码,把 APP 的token 信息、二维码 ID 发送给Server 端, Server 收到请求后修改二维码的扫码状态,并生成一个临时token;
  3. 此时,网页端展示的二维码状态会提示已扫码,待确认。 而APP 端扫码之后,会提示确认授权的操作。
  4. 于是,用户确认登录后,携带临时 token 给到server,server 端修改二维码状态并为网页端生成授权 token
  5. 最后,网页端轮询到状态变化并获取到token,从而完成扫码授权。

2. 消息推送中的已读消息和未读消息设计

“站内信”有两个基本功能:

  • 点到点的消息传送。用户给用户发送站内信,管理员给用户发送站内信。
  • 点到面的消息传送。管理员给用户(指定满足某一条件的用户群)群发消息

这两个功能实现起来也很简单:
只需要设计一个消息内容表和一个用户通知表,当创建一条系统通知后,数据插入到消息内容表。消息内容包含了发送渠道,根据发送渠道决定后续动作。
如果是站内渠道,在插入消息内容后异步的插入记录到用户通知表。
在这里插入图片描述
这个方案看起来没什么问题,但实际上,我们把所有用户通知的消息全部放在一个表里面,如果有 10W 个用户,那么同样的消息需要存储 10W 条。
很明显,会带来两个问题:

  • 随着用户量的增加,发送一次消息需要插入到数据库中的数据量会越来越大,导致耗时会越来越长;
  • 用户通知表的数据量会非常大,对未读消息的查询效率会严重下降;

所以上面这种方案很明显行不通,要解决这两个问题,我有两个参考解决思路。

  • 第一个方式(如图),先取消用户通知表, 避免在发送平台消息的时候插入大量重复数据问题。
    其次增加一个“message_offset”站内消息进度表,每个用户维护一个消息消费的进度Offset。
    每个用户去获取未读消息的时候,只需要查询大于当前维护的 msg_id_offset 的数据即可。
    在这种设计方式中,即便我们发送给 10W 人,也只需要在消息内容表里面插入一条记录即可。在性能上和数据量上都有较大的提升。
    在这里插入图片描述
  • 第二种方式,和第一种方式类似,使用 Redis 中的Set 集合来保存已经读取过的消息id。
    使用 userid_read_message 作为key,这样就可以为每个用户保存已经读取过的所有 消息的id
    当用户读取了未读消息后, 就直接在redis 的已读消息id 的set 中新增一条记录。这样,在已经得知到已读消息的数量和具体消息 id 的情况下,我们可以直接使用消息 id 来查询没有消费过的数据。

3. 布隆过滤器到底是什么东西?它有什么用?

问大家一个问题,如果我想判断一个元素是否存在某个集合里面怎么做?
一般的解决方案是先把所有元素保存起来,然后通过循环比较来确定。
但是如果我们有几千万甚至上亿的数据的时候{如图},虽然可以通过不同的数据结构来优化数据检索的时间复杂度,但是整体的效率依然很慢,而且会占用非常多的内存空间,这个问题该怎么解决呢?
在这里插入图片描述
这个时候,位图就派上了用场{如图}
BitMap 的基本原理就是用一个bit 位来存储当前数据是否存在的状态值,也就是把一个数据通过hash 运算取模后落在 bit 位组成的数组中,通过 1 对该位置进行标记。 这种方式适用于大规模数据,但数据状态又不是很多的情况,通常是用来判断某个数据存不存在的。

在这里插入图片描述
布隆过滤器就是在位图的基础上做的一个优化设计{如图}。
它的原理是,当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组中的K 个点,把它们置为 1。
检索的时候,使用同样的方式去映射,只要看到每个映射的位置的值是不是 1,就可以大概知道该元素是否存在集合中了。
如果这些点有任何一个 0,则被检查的元素一定不在;如果都是 1,则被检查的元素很可能存在。
在这里插入图片描述

4. 会员批量过期的方案怎么实现?

有这样一个场景问题。
“有一张 200W 数据量的会员表,每个会员会有长短不一的到期时间,现在想在快到期之前发送邮件通知提醒续费” 该怎么实现。

很显然,这个问题里面有几个关键词:

  • 200W 数据意味着数据量比较大
  • 每个会员都有过期时间,需要能够筛选出快过期的会员

很显然,如果直接去通过select 语句做筛选,就掉入坑里了,因为这里会存在性能问题,那接下来看一下一些相对比较合理的回答。

关于这个问题,可以有三种解决方案:

  • 第一种,系统不主动轮询,而是等用户登录到系统以后,触发一次检查。如果发现会员的过期时间小于设定的阈值,就触发一次弹窗和邮件提醒。这种方式规避了轮询问题,不会对数据库和后端应用程序造成任何压力。
    • 缺点是,如果用户一直不登陆,就一直无法实现会员过期,并且也无法提前去根据运营策略发送续期的提醒消息。
  • 第二种,我们可以使用搜索引擎,比如Solr、或者 Elasticsearch。把会员表里面的会员 id 和会员到期时间存储一份到搜索引擎中。
    搜索引擎的优势在于大数据量的快速检索,并且具有高可扩展性和高可靠性,非常适合大规模数据的处理。
  • 第三种,可以使用Redis 来实现。
    用户开通会员以后,在 Redis 里面存储这个会员id,以及设置这个 id 的过期时间。然后可以使用redis 的过期提醒功能,
    把配置项 notify-keyspace-events 改为notify-keyspace-events “Ex” ,当Redis 里面的key 过期以后,会触发一个 key 过期事件,我们可以在应用程序中监听这个事件来处理。
  • 第四种,可以直接使用 MQ 提供的延迟队列,当用户开通会员以后,直接计算这个会员的过期时间,然后发送一个延迟消息到 MQ 上,一旦消息达到过期时间,消费者就可以消费这个消息来触发会员过期的提醒。

5. 如果让你设计一个秒杀系统,怎么设计?

我认为秒杀系统的核心有两个:

  • 过滤掉 90%以上的无效流量
  • 解决库存超卖的问题

在这里插入图片描述

如何解决这些问题?

  • 页面静态化
    秒杀活动的页面,大多数内容都是固定不变的,如商品名称,商品图片等等,可以对活动页面做静态化处理,减少访问服务端的请求。秒杀用户会分布在全国各地,有的在上海,有的在深圳,地域相差很远,网速也各不相同。为了让用户最快访问到活动页面,可以使用CDN(Content Delivery Network,内容分发网络)。CDN可以让用户就近获取所需内容。
  • 按钮至灰控制
    秒杀活动开始前,按钮一般需要置灰的。只有时间到了,才能变得可以点击。这是防止,秒杀用户在时间快到的前几秒,疯狂请求服务器,然后秒杀时间点还没到,服务器就自己挂了。
  • 服务单一职责
    我们都知道微服务设计思想,也就是把各个功能模块拆分,功能那个类似的放一起,再用分布式的部署方式。
    如用户登录相关的,就设计个用户服务,订单相关的就搞个订单服务,再到礼物相关的就搞个礼物服务等等。那么,秒杀相关的业务逻辑也可以放到一起,搞个秒杀服务,单独给它搞个秒杀数据库。” 服务单一职责有个好处:如果秒杀没抗住高并发的压力,秒杀库崩了,服务挂了,也不会影响到系统的其他服务。
  • 秒杀链接加盐
    链接如果明文暴露的话,会有人获取到请求Url,提前秒杀了。因此,需要给秒杀链接加盐。可以把URL动态化,如通过MD5加密算法加密随机的字符串去做url。
  • 限流
    一般有两种方式限流:nginx限流和redis限流。
    • 为了防止某个用户请求过于频繁,我们可以对同一用户限流;
    • 为了防止黄牛模拟几个用户请求,我们可以对某个IP进行限流;
    • 为了防止有人使用代理,每次请求都更换IP请求,我们可以对接口进行限流。
    • 为了防止瞬时过大的流量压垮系统,还可以使用阿里的Sentinel、Hystrix组件进行限流。
  • 分布式锁
    • 可以使用redis分布式锁解决超卖问题。
      使用Redis的SET EX PX NX + 校验唯一随机值,再删除释放锁。
      在这里插入图片描述
  • MQ异步处理
    • 如果瞬间流量特别大,可以使用消息队列削峰,异步处理。用户请求过来的时候,先放到消息队列,再拿出来消费。
  • 限流&降级&熔断
    • 限流,就是限制请求,防止过大的请求压垮服务器;
    • 降级,就是秒杀服务有问题了,就降级处理,不要影响别的服务;
    • 熔断,服务有问题就熔断,一般熔断降级是一起出现。

6. 在 2G 大小的文件中,找出高频 top100 的单词?

这是一个典型的 top k 问题,在面试的时候,会产生很多变体。
但是不管怎么变,top k 问题的本质是一样的。

  • 1.把 2G 的文件进行分割成大小为 512KB 小文件,总共得到 2048 个小文件,避免一次性读入整个文件造成内存不足。
  • 2.定义一个长度为 2048 的hash 表数组,用来统计每个小文件中单词出现的频率。
  • 3.使用多线程并行遍历 2048 个小文件,针对每个单词进行 hash 取模运算分别存储到长度为 2048 的hash 表数组中
    inthash=Math.abs(word.hashCode() %hashTableSize);
    hashTables[hash].merge(word, 1, Integer::sum);
  • 4.接着再遍历这 2048 个hash 表,把频率前 100 的单词存入小顶堆中
  • 5.最后,小顶堆中最终得到的 100 个单词,就是 top 100 了。

这种解决方案的核心思想是将大文件分割为多个小文件,然后采用分治和堆的算法,来解决这个问题。

7. 一个客户端长连接多个服务端问题

客户端和多个目标服务节点保持长连接,然后通过负载均衡机制对连接进行分发,形成这样的一个结构。
在这里插入图片描述
假设现在需要进行停机发布,按照发布流程,会先切掉一部分机器的流量进行重启,这就会导致原本的长连接断开(如图)。
客户端出发重连操作,使得这些长连接全部连接到其他没有重启的节点上。
在这里插入图片描述
问题来了,因为是客户端建立的是长连接,所以节点重新启动以后(如图),客户端不会触发重连,使得新的节点没有任何连接。
而老的节点连接数量暴增,出现 “旱的旱死涝的涝死” 的现象,问,怎么解决这个问题。
在这里插入图片描述
这个问题第一反应可能觉得没有思路,但是我们仔细来分析一下这个问题的背景,这里存在几个已知的情况(如图)。
在这里插入图片描述

  • 1.客户端和多个目标服务节点建立连接,并且实现动态负载均衡,意味着需要用到注册中心实现服务上下线感知
  • 2.客户端层面存在一个长连接管理机制,用来维护每个连接的状态。

有了这样一个背景的分析,那这个问题就很好解决了,如果服务下线以后(如图),通过注册中心的感知机制,客户端会收到通知。
于是,客户端把所有失败的连接删除,并重新向健康状态的节点发起连接即可。
在这里插入图片描述
同样的道理,当发布的服务节点重新启动以后,注册中心也同样会收到通知,于是,把本地长连接池里面的长连接销毁,并重新建立新的连接出发新的 reblance 平衡即可。
在这里插入图片描述

8. 如何从0-1设计一个架构

架构的目的是为了解决业务痛点或者提高人效等,以只开发一次,就不需要开发介入为目的;

当面试官问你这个问题的时候,你要从几个角度考虑:

  • 设计架构肯定是基于业务当前遇到的痛点或者待解决的业务需求
  • 要先知道将会遇到多大的QPS,需要多大的时效性(P99)
    • 如果需要很高的性能要求,可以从以下几个点出发
      • 预热
        将一些需要查询db获取元信息或者需要调用其他服务获取的元信息,进行预热缓存处理;
      • 主核心链路设计优化
        • 对于高性能要求场景可以设计策略缩短调用链路(比如拿到一个结果立马返回不再执行后面策略)
        • 需要多个地方抓取数据来源的,可以并发抓取数据来源,通过设定一个总的耗时时长,每个数据来源起一个线程池,分别去抓取数据(ExecutorCompletionService),超时了可以丢弃该数据,后续通过回溯去弥补
      • 本地缓存(guava或者Spring的caffeine等)
      • 相同入参响应结果抗重放
    • 对于核心链路
      • 设计模式(模板/策略/责任链)(主要考虑服务的拓展性和可维护性)
    • 服务治理
      • 监控告警(企微群推送告警/电话告警)
      • 服务错误码
      • 全局的兜底异常/rpc超时设定,连接池配置
      • 服务日志规范管理
      • 服务架构包命名等
      • git分支命名等
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 作为一名程序员,我在2023年对自己的计划包括: 1. 不断学习和提升技能:我会继续学习新的编程语言和框架,以及不断提高编码能力。 2. 参加行业相关的培训和会议:我会参加各种行业相关的培训课程和会议,以便获得最新的行业知识和技能。 3. 参加项目并积累经验:我会参加各种项目,以便积累丰富的经验,并提升个人能力。 4. 在业内建立良好的人脉关系:我会努力与业内的同行建立良好的人脉关系,并与他们保持联系。 5. 寻找更好的发展机会:我会不断寻找更好的发展机会,以便在职业生涯取得更大的成功。 总之,我在2023年会努力工作,不断学习和提升,并寻找更好的发展机会,以在职业生涯取得更大的成功。 ### 回答2: 2023年的计划对于我作为一名程序员来说十分重要,我希望能够提升自己的技能水平并取得更好的职业发展。以下是我的计划: 首先,我计划继续学习并掌握新的编程语言和技术。随着科技的不断发展,编程语言和技术也在不断更新换代,作为一名程序员,保持学习的状态是必不可少的。我将花费更多的时间学习目前主流的编程语言,如Python、Java等,并尝试掌握最新的前端和后端技术。通过不断学习,我希望能够拥有更广泛的技术视野和更强大的技术能力。 其次,我计划参与更多的项目和实践。在理论学习之外,实践是提升技能的关键。我计划积极寻找项目机会,无论是个人项目还是团队项目,都可以提供宝贵的实践机会。通过参与各种项目,我可以锻炼解决问题的能力,提高编码和协作能力。同时,我也希望通过实践的挑战和失败,不断完善自己,进一步提高自己的技术水平和经验。 第三,我计划参加相关的培训和技术交流活动。参加培训和技术交流活动可以与其他程序员交流和学习,了解行业最新动态和趋势。我计划参加各种技术研讨会、讲座和培训班,通过与行业专家和其他程序员的交流,深入了解各种编程技术和最佳实践。同时,我也希望能够积极参与技术社区,与其他程序员分享自己的经验和见解,不断提高自己的影响力和口碑。 最后,我计划在个人项目和开源社区上做出更多的贡献。通过自己的努力,我希望能够在个人项目实现一些有意义的功能或解决一些实际问题,并将其开源。通过开源社区的贡献,我可以帮助他人解决问题,同时也能够借助其他人的反馈和指导,不断改进自己的代码和设计能力。 总之,2023年对于我作为一名程序员来说是充满挑战和机遇的一年。我将不懈努力,持续学习和实践,不断提升自己的技能水平和职业发展。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alan0517

感谢您的鼓励与支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值