短剧

  • 面试官:什么是S3
  • 我:

    AWS 的 S3 是 AWS 提供的一种 对象存储服务,主要用于在云端存储和检索任何数量的数据。

  • 面试官:用过MinIO吗?

  • 我:

    我没有用过MinIO🧠
    MinIO 是一个高性能、开源的对象存储系统,兼容 Amazon S3 接口,常用于私有云、本地部署或边缘计算场景下的对象存储。
    MinIO 是一个轻量、兼容 S3 的对象存储服务,适合本地部署和私有云场景,特别适合企业内部或开发者使用。

  • 面试官:对象存储,文件存储,块存储的区别是什么?

  • 我:

    文件存储给人用、对象存储给程序用、块存储给操作系统用。

    特性文件存储对象存储块存储
    📦 存储单位文件(File)对象(Object)块(Block)
    📁 组织方式文件夹 + 文件路径结构扁平结构,用唯一ID访问无结构,由操作系统管理
    🔌 访问方式路径访问(如 /home/a.txtAPI/URL 访问(如 s3://...挂载磁盘后由操作系统访问
    🧠 元数据支持有限(创建时间、大小等)丰富(可自定义键值对)几乎无(仅存数据)
    🚀 性能/IO中等适中(适合大文件)高性能、低延迟
    🌐 可扩展性一定限制极强(PB 级别、全球访问)通常限于单个实例或卷
    🛠 典型用途文件共享、代码仓库、日志文件图片/音视频、备份、云应用资源数据库、操作系统磁盘、VM磁盘
    ☁️ AWS 示例服务Amazon EFSAmazon S3Amazon EBS
  • 面试官:你使用了谷歌登录,那你一定了解0auth2.0吧

  • 我:

    是的,OAuth 2.0 是一种 授权协议,用于在第三方应用和资源服务器(如用户的数据)之间安全地进行授权访问,而无需暴露用户密码。
    OAuth 2.0 解决了 用户想让一个应用访问 TA 的资源(比如 GitHub、微博、Google 相册)但又不想给密码 的问题,可以在没有密码的情况下,在用户允许的范围内访问你的信息

  • 面试官:Oauth2.0的流程是怎样的

  • 我:

    拿谷歌登录为例:

    1. 登录谷歌控制台获取 KEY 和密钥,设置回调地址
    2. web端用户点击谷歌登录后,我后端会生成一个谷歌登录页面的链接
    3. 用户同意之后,谷歌会进行回调并携带一个code
    4. 后端使用code,KEY 密钥 向谷歌发起请求获取到token
    5. 后端使用token调用谷歌API获取到用户信息
  • 面试官:你是怎么实现文件传到S3上的

  • 我:
    1. 使用 Multipart Upload(分片上传)
      AWS 提供多部分上传 API:将大文件拆成 5MB~500MB 的小块上传,最后合并,具备:
    • ✅ 断点续传能力
    • ✅ 可并发分片上传,加快速度
    • ✅ 上传大文件更安全、稳定
  • 面试官:你都使用redis实现了那些功能,具体怎么实现的
  • 我:

    我使用redis实现了防止账号重复登陆的问题,具体实现:我在拦截器中会先解析传过来的token,解析出账号和id,并根据账号和id去查询redis中存放的token,判断两个token是否一致,不一致就将旧的token替换掉,实现防止账号重复登录问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    @Component//加入到sprng容器里
    public class LoginInterceptor implements HandlerInterceptor {
    @Autowired
    RedisTemplate redisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String token = request.getHeader("Authorization");
    //令牌验证token
    try {
    //拦截器中判断redis中的是否存在token

    Map<String, Object> claims = JwtUtil.parseToken(token);
    String account = (String) claims.get("account");
    Integer id = (Integer) claims.get("id");
    String redisToken = (String) redisTemplate.opsForValue().get("Backstage"+account+id);
    if (redisToken==null||!redisToken.equals(token)){
    // 设置响应头
    response.setContentType("application/json;charset=UTF-8");
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401

    // 构造响应体
    Map<String, Object> result = new HashMap<>();
    result.put("code", 401);
    result.put("message", "登录已失效,请重新登录");
    result.put("data", null);

    // 返回 JSON
    response.getWriter().write(new ObjectMapper().writeValueAsString(result));
    throw new Exception("token已过期");
    }
    //将claims的值加载到线程ThreadLocal中
    ThreadLocalUtil.set(claims);
    //放行
    return true;
    }catch (Exception e){
    //http响应401
    response.setStatus(401);
    return false;
    }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    //请求结束后清空线程
    ThreadLocalUtil.remove();
    }
    }

  • 面试官:为什么要防止账号重复登录,一个账号在APP端和web端不能同事登录吗

  • 我:

    APP端和web端当然可以同时登录,防止账号重复登录时为了解决同一个账号同时在两台电脑或者手机上同时登录,我是通过前端向后端发送请求的时候,会有请求头,请求头中会携带一个参数来判断是APP还是WEB,例如 参数是1 则是APP登录,参数是0 则是WEB端登录

ChatMate — 类微信即时通讯系统

  • 面试官:什么是RTpoic 发布-订阅(Pub/Sub)
  • 我:

    RTopic 是 Redisson(一个基于 Redis 的 Java 客户端)提供的一个基于 Redis 发布/订阅机制的接口。

    • 发布-订阅(Publish/Subscribe, Pub/Sub) 是一种消息传递模式,核心思想是:
      • 发布者(Publisher):负责向一个或多个主题(Topic)发布消息。
      • 订阅者(Subscriber):订阅一个或多个主题,接收发布到这些主题的消息。
      • 主题(Topic):消息传递的逻辑通道,发布者发消息到主题,订阅者订阅主题消息。
    • 特点:
      • 发布者和订阅者相互独立,互不知晓对方存在。
      • 订阅者接收所有发布到所订阅主题的消息,实现广播效果。
  • 面试官:使用RTpoic 发布-订阅 做了什么?

  • 我:

    实现了消息跨实例也能传输,如果部署到多台服务器上,不使用消息队列,那么只能实现一台服务器上的用户通信,使用消息队列后可以实现不同服务器上的用户进行通信。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    @Component("messageHandler")
    public class MessageHandler<T> {

    private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class);

    private static final String MESSAGE_TOPIC = "message.topic";

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private ChannelContextUtils channelContextUtils;


    @PostConstruct
    public void lisMessage() {
    // 订阅消息频道
    RTopic rTopic = redissonClient.getTopic(MESSAGE_TOPIC);
    // 监听消息,收到消息进行发送消息的方法
    rTopic.addListener(MessageSendDto.class, (MessageSendDto, sendDto) -> {
    logger.info("收到广播消息:{}", JSON.toJSONString(sendDto));
    channelContextUtils.sendMessage(sendDto);
    });
    }
    // 发送消息,将消息发送到频道里
    public void sendMessage(MessageSendDto sendDto) {
    RTopic rTopic = redissonClient.getTopic(MESSAGE_TOPIC);
    rTopic.publish(sendDto);
    }
    }

  • 面试官:发送消息流程是怎样的

  • 我:
    1. 登录后会连接netty

    2. 通过http接口获取到发送的消息的内容

      • 这一步会将消息存数据库一份
      • 接着会将消息 发给消息队列
        1
        2
        3
        4
        5
        6
        7
        8
        9
            @RequestMapping("/sendMessage")
        @GlobalInterceptor
        public ResponseVO sendMessage(HttpServletRequest request,
        @NotEmpty String contactId,
        @NotEmpty @Max(500) String messageContent,
        @NotNull Integer messageType,
        Long fileSize,
        String fileName,
        Integer fileType) {......}
    3. redis的rTopic 接收消息
      • rTopic 收到消息后 会触发 channelContextUtils.sendMessage(sendDto)发送消息;
      • Redis 的 Pub/Sub(发布订阅)机制,它是“即时广播”机制,不会持久保存消息,也不会堆积在 Redis 里。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
           @PostConstruct
        public void lisMessage() {
        RTopic rTopic = redissonClient.getTopic(MESSAGE_TOPIC);
        rTopic.addListener(MessageSendDto.class, (MessageSendDto, sendDto) -> {
        logger.info("收到广播消息:{}", JSON.toJSONString(sendDto));
        channelContextUtils.sendMessage(sendDto);
        });
        }

        public void sendMessage(MessageSendDto sendDto) {
        RTopic rTopic = redissonClient.getTopic(MESSAGE_TOPIC);
        rTopic.publish(sendDto);
        }
    4. 发送消息
      1
      2
      3
      4
      5
       Channel sendChannel = USER_CONTEXT_MAP.get(reciveId);
      if (sendChannel == null) {
      return;
      }
      sendChannel.writeAndFlush(new TextWebSocketFrame(JsonUtils.convertObj2Json(messageSendDto)));
    5. 前端渲染消息
      • 前端 WebSocket 实例监听到第4步的消息,会展示到聊天界面上

场景题

  • 面试官: 如果有一个后端的接口,接口内需要访问其他四个服务,这四个服务都是100ms,如果顺序访问要400ms, 如果只用一个线程,不用多线程,怎么降到100ms多
  • 我:

    可以使用 异步非阻塞 IO(如异步 HTTP 客户端) 来实现“多个请求同时发出”,从而让接口总耗时≈最长的一个(即约 100ms),即使你用的是单线程

    例如: webclient
    特点:

    • 单线程+ 非阻塞
    • 多个请求可同时发出