微服务系统面经之四: 以秒杀系统为例 - 用户秒杀状态的通知【京东支付后端一面】

28 秒杀状态相关

28.1 对于一个秒杀系统,用户点击秒杀按钮后,怎么才能知道其秒杀状态?【京东支付后端一面】

对于秒杀系统,用户点击秒杀按钮后,确定其秒杀状态的过程通常包含以下几个关键步骤:

  1. 请求处理

    • 用户点击秒杀按钮后,前端会向服务器发送一个秒杀请求。
    • 为了应对高并发,通常使用负载均衡等技术将请求分发到多个服务器。
  2. 身份验证和参数校验

    • 服务器首先验证用户身份和请求的有效性,例如,检查用户是否登录、请求参数是否合法。
  3. 读取库存状态

    • 服务器查询当前商品的库存量。
    • 为了提高性能,库存数据可能存储在快速的缓存系统中,如 Redis。
  4. 判断库存

    • 如果库存足够,进入下一步;如果库存不足,直接返回“秒杀失败”状态。
  5. 库存扣减

    • 对库存进行扣减处理。
    • 这个操作需要保证原子性,防止超卖现象。
  6. 生成订单

    • 库存扣减成功后,生成秒杀订单。
    • 订单信息通常会被写入数据库。
  7. 【本节考察的重点】返回状态

    • 服务器向用户返回秒杀成功的消息。
    • 在高并发情况下,这一过程可能通过异步消息系统进行。
  8. 后续处理

    • 包括支付处理、订单超时监测等。
  9. 异常处理

    • 系统需要妥善处理各类异常情况,例如库存锁定失败、数据库写入失败等。

28.2 返回状态:服务器向用户返回秒杀成功的消息。在高并发情况下,这一过程可能通过异步消息系统进行。详细说说?

在秒杀系统中,尤其是在高并发的场景下,使用异步消息系统来处理和返回用户的秒杀状态可以极大地提高系统的性能和响应速度。以下是详细的解释:

为何使用异步消息系统?

  1. 降低系统压力

    • 在高并发情况下,同步处理所有的秒杀请求和立即返回状态会给服务器带来巨大压力,可能导致响应延迟甚至系统崩溃。
    • 异步处理允许系统先快速响应用户请求,然后在后台处理复杂的业务逻辑。
  2. 提高用户体验

    • 用户在提交秒杀请求后不需要等待整个处理过程完成,即可得到一个初步的响应(如“请求已提交”)。
    • 用户可以继续其他操作或者在客户端轮询/等待最终结果。
  3. 提升系统伸缩性

    • 异步消息系统允许分布式处理,易于横向扩展,有助于处理更多的用户请求。

异步消息系统的工作流程

  1. 请求接收

    • 用户提交秒杀请求后,服务器立即返回一个预处理响应,如“请求已接收,正在处理中”。
  2. 消息队列

    • 请求被放入消息队列中。消息队列起到缓冲的作用,平衡请求处理的负载。
    • 可以使用如 RabbitMQ、Kafka 等消息中间件。
  3. 后台处理

    • 后台服务不断从消息队列中拉取请求并处理,如库存扣减、订单生成等。
    • 这个处理过程可以分布在多个服务器或服务实例上。
  4. 返回最终结果

    • 处理完成后,最终结果(如“秒杀成功”或“秒杀失败”)通过异步方式返回给用户。
    • 这可以通过 WebSocket、服务端事件(SSE)、轮询等方式实现。

总结

使用异步消息系统,在用户发起秒杀请求后,可以迅速响应,然后在后台异步处理请求,并最终将处理结果返回给用户。这种方式不仅提高了系统的处理能力和伸缩性,还能提供更好的用户体验。在设计秒杀系统时,异步处理机制是提高系统性能的关键之一。

28.3 处理完成后,最终结果(如“秒杀成功”或“秒杀失败”)通过异步方式返回给用户。这可以通过 WebSocket、服务端事件(SSE)、轮询等方式实现。再讲解一下?

当秒杀系统后台完成处理后,将最终结果(例如“秒杀成功”或“秒杀失败”)异步地返回给用户,可以通过几种不同的技术来实现,主要包括 WebSocket、服务端事件(Server-Sent Events, SSE)和轮询。下面是这些技术的详细讲解:

WebSocket

  1. 原理:WebSocket 提供了全双工通信机制,允许服务器和客户端之间建立持久的连接,并随时发送消息。
  2. 应用:在秒杀场景中,一旦用户提交了秒杀请求,客户端(如网页或移动应用)通过 WebSocket 与服务器建立连接。服务器在处理完秒杀请求后,可以立即通过这个连接将结果推送给客户端。
  3. 优点:提供实时、双向的通信,适用于需要快速响应的场景。

服务端事件(Server-Sent Events, SSE)

  1. 原理:SSE 允许服务器主动向客户端发送新的数据流。与 WebSocket 不同,SSE 是单向通信,仅服务器向客户端发送数据。
  2. 应用:在秒杀系统中,服务器可以通过 SSE 将秒杀结果实时发送给客户端,而客户端无需发送请求即可接收数据。
  3. 优点:简单易用,适合于只需要服务器单向推送数据的场景。

轮询

  1. 原理:轮询是一种最传统的技术,客户端定期向服务器发送请求以询问最新的状态。
  2. 应用:用户提交秒杀请求后,客户端开始周期性地向服务器发送查询请求,直到收到秒杀结果。
  3. 优点与缺点:轮询实现简单,兼容性好,但效率相对较低,可能存在延迟,并且对服务器造成一定的负担。

总结

  • WebSocketSSE 更适合需要实时通信的场景,如秒杀系统,它们可以减少延迟并提高用户体验。
  • 轮询 更适合简单应用或对实时性要求不高的场景。尽管它的效率相对较低,但在一些场景中仍然是一个可行的解决方案。

在选择合适的技术时,需要根据应用的具体需求和现有架构来决定。例如,WebSocket 和 SSE 需要服务器和客户端支持长连接,而轮询则对服务器的并发处理能力有一定要求。

28.4 WebSocket和SSE技术这两者在性能上有什么区别?哪一个更消耗资源呢?

WebSocket 和 Server-Sent Events (SSE) 在性能和资源消耗方面有一些关键区别,这些区别通常取决于应用场景和通信的性质:

WebSocket

  • 全双工通信:WebSocket 提供全双工通信,即客户端和服务器可以同时发送和接收消息。这种通信方式非常适合实时互动和高频数据交换的场景。
  • 资源消耗:由于维持了一个持久的连接,WebSocket 可能在某些场景下会消耗更多资源。服务器需要为每个客户端保持一个打开的连接,这在大量用户同时在线的情况下可能会占用更多的内存和网络资源。
  • 适用场景:需要频繁、双向通信的场景,如在线游戏、实时聊天等。

Server-Sent Events (SSE)

  • 单向通信:SSE 是一种单向通信,只允许服务器推送数据到客户端。客户端不能通过 SSE 连接发送数据给服务器(需要使用其他方式,如 AJAX 请求)。
  • 资源消耗:SSE 通常在服务器端的资源消耗上比 WebSocket 要少,因为它仅支持单向通信。对于不需要客户端向服务器发送消息的应用来说,SSE 可能是一个更轻量的选择。
  • 适用场景:只需要服务器单向推送的场景,如股票价格更新、新闻订阅等。

性能对比

  • 连接维持:WebSocket 需要维持一个持久的双向连接,而 SSE 只需维持单向连接。因此,在大规模并发连接的环境下,WebSocket 可能对服务器造成较高压力。
  • 网络延迟和吞吐量:WebSocket 由于是双向的,对于需要快速响应的双向数据传输更为有效。SSE 在单向数据流中表现更好,特别是当消息主要从服务器到客户端时。

结论

  • 如果应用需要双向通信且频繁交互,WebSocket 可能是更好的选择,尽管可能会消耗更多资源。
  • 如果应用仅需要服务器向客户端推送数据,并且对服务器资源使用有较严格的要求,SSE 可能是一个更高效的选项。

在选择时,还需要考虑服务器的负载能力、客户端的需求以及应用的特定场景。

28.5 对于我提到的秒杀系统的秒杀成功的通知,选用websoket还是sse?

对于秒杀系统来说,选择 WebSocket 还是 SSE 主要取决于系统的具体需求和架构。考虑到秒杀场景的特点,下面是一些决策因素:

使用 WebSocket 的考虑因素:

  1. 双向通信需求:如果您的系统除了发送秒杀结果之外,还需要实现客户端到服务器的实时通信(例如,实时更新用户的出价、聊天等),那么 WebSocket 是更好的选择。

  2. 高频更新:如果秒杀系统涉及到频繁的状态更新,如实时库存显示,WebSocket 由于其低延迟特性可能更适合。

  3. 连接维持:WebSocket 需要在客户端和服务器之间维持一个持久的连接,这可能在资源上更加昂贵,特别是当有大量用户同时在线时。

使用 SSE 的考虑因素:

  1. 单向数据流:如果秒杀系统只需要服务器向客户端推送消息(例如,仅通知秒杀结果),SSE 是一个简单而高效的选择。

  2. 资源效率:相比于 WebSocket,SSE 在服务器端通常更加轻量,因为它只涉及单向通信,并且连接的维护开销较低。

  3. 易于实现和维护:SSE 在某些情况下比 WebSocket 更容易实现和维护,特别是当应用的主要需求是单向数据流时。

结论

  • 如果秒杀系统需要的是一种简单的单向通知机制,且希望尽可能节省服务器资源,那么 SSE 可能是更合适的选择。
  • 如果秒杀系统的需求超出了单纯的通知,例如需要实现双向实时交互,则 WebSocket 更加适合。

在做出选择时,还需要考虑系统的整体架构、预期的用户规模、以及服务器资源等因素。在某些情况下,也可以考虑将这两种技术结合使用,以满足不同的业务需求。

28.6 websokcet技术在springboot中的代码是如何实现的呢

在 Spring Boot 中实现 WebSocket 通信涉及几个关键步骤。以下是一个简化的示例,展示如何在 Spring Boot 应用中设置 WebSocket 以用于秒杀系统通知。

1. 添加依赖

首先,在 pom.xml 文件中添加 Spring WebSocket 的依赖:

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-websocketartifactId>
    dependency>
    
dependencies>

2. 配置 WebSocket

创建一个配置类来启用和配置 WebSocket:

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/websocket").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
        registry.setApplicationDestinationPrefixes("/app");
    }
}

在这里,我们配置了一个 STOMP(Simple Text Oriented Messaging Protocol)端点 /websocket 并启用了 SockJS 作为备选选项。我们还设置了一个简单的消息代理,它将在 /topic 前缀下广播消息。

3. 创建 WebSocket 控制器

创建一个控制器来处理 WebSocket 消息:

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class WebSocketController {

    @MessageMapping("/seckill")
    @SendTo("/topic/seckillResult")
    public String handleSeckill(String seckillInfo) {
        // 处理秒杀逻辑...
        // 返回秒杀结果
        return "Seckill result for " + seckillInfo;
    }
}

在这个控制器中,当客户端发送消息到 /app/seckill 时,将触发 handleSeckill 方法,并将返回的消息广播到 /topic/seckillResult

4. 前端集成

在前端,使用 JavaScript 来建立与 WebSocket 的连接并发送/接收消息:

var socket = new SockJS('/websocket');
var stompClient = Stomp.over(socket);

stompClient.connect({}, function(frame) {
    // 订阅秒杀结果主题
    stompClient.subscribe('/topic/seckillResult', function(response) {
        alert("Received: " + response.body);
    });
    
    // 发送秒杀请求
    stompClient.send("/app/seckill", {}, "user1234");
});

5. 总结

在这个简单的例子中,我们设置了一个 Spring Boot 应用,其中包含 WebSocket 支持以实现秒杀系统中的实时通讯。当用户发起秒杀请求时,前端通过 WebSocket 向服务器发送信息,并订阅特定主题以接收秒杀结果。

请注意,这只是一个基础的示例,实际的秒杀系统可能涉及更复杂的逻辑,如处理高并发请求、确保数据一致性和安全性等。

28.7 sse技术呢?

在 Spring Boot 中实现 Server-Sent Events (SSE) 用于秒杀系统的通知相对简单。SSE 允许服务器端向客户端推送实时数据,而无需客户端轮询或建立复杂的连接。下面是一个基本的实现流程:

1. 添加依赖

通常情况下,使用 Spring Boot Web Starter 依赖就足够了,因为 SSE 依赖于 Spring MVC 的功能:

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
dependencies>

2. 创建 Controller

在 Spring Boot 应用中创建一个 Controller,使用 SseEmitter 类来发送事件给客户端:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;

@RestController
public class SseController {

    private final CopyOnWriteArrayList<SseEmitter> emitters = new CopyOnWriteArrayList<>();

    @GetMapping("/sse")
    public SseEmitter handleSse() {
        SseEmitter emitter = new SseEmitter();
        emitters.add(emitter);

        emitter.onCompletion(() -> emitters.remove(emitter));
        emitter.onTimeout(() -> emitters.remove(emitter));

        return emitter;
    }

    public void sendToAllClients(String data) {
        for (SseEmitter emitter : emitters) {
            try {
                emitter.send(data);
            } catch (IOException e) {
                emitters.remove(emitter);
            }
        }
    }

    // 其他逻辑...
}

在这个 Controller 中,当客户端请求 /sse 时,将创建一个新的 SseEmitter 并添加到列表中。sendToAllClients 方法可以被用来发送秒杀结果给所有连接的客户端。

3. 前端集成

在前端,你可以使用 EventSource API 来监听来自服务器的事件:

var eventSource = new EventSource('/sse');

eventSource.onmessage = function(event) {
    console.log('Received: ', event.data);
    // 处理服务器发送的数据
};

eventSource.onerror = function(event) {
    // 处理错误
};

4. 触发事件

在服务器端的某个逻辑中(例如秒杀处理逻辑),你可以调用 sendToAllClients 方法来向所有监听的客户端发送数据。

5. 注意事项

  • SSE 支持在单个 HTTP 连接上持续发送多个消息。
  • SSE 适用于单向数据流,即只有服务器向客户端发送数据。
  • SSE 连接是长期存在的,因此需要考虑客户端断开连接时的处理。

总结

使用 SSE,秒杀系统的服务器可以轻松地向客户端推送实时更新(如秒杀成功或失败的通知)。这种方法的优点是实现简单,无需建立复杂的通信协议,而且对于单向数据流来说是非常高效的。

28.8 【重点】如果你采用了发布订阅机制来通知用户,服务端仍然需要维持大量的订阅端点,保持大量的,会消耗资源,有没有什么可以改进的【京东支付后端一面】

答案就是参考28.7的sse技术,只允许单向推送,服务端可以用更少的资源维护和大量的秒杀客户端建立的连接

28.8.1 为什么sse技术无需和客户端建立连接?

实际上,Server-Sent Events (SSE) 技术确实需要与客户端建立一个连接,但这种连接的性质和 WebSocket 不同,它是一种单向的、长期的 HTTP 连接。让我们来详细了解一下:

1 SSE 连接的特性
  1. 单向通信:SSE 是一种服务器到客户端的单向通信。服务器可以不断地向客户端发送消息,但客户端无法通过这个连接发送消息给服务器(客户端需要使用其他方式,如 AJAX 请求,来发送数据到服务器)。

  2. 基于 HTTP:SSE 通过标准的 HTTP 协议建立连接。当客户端通过 EventSource 接口发起请求时,服务器响应这个请求并保持连接打开,然后通过这个连接推送消息。

  3. 长期连接:一旦建立,SSE 连接会保持开放,允许服务器持续不断地发送更新,直到连接被关闭。这种长期连接是通过 HTTP 的响应机制实现的,其中服务器不会立即结束响应,而是保持响应打开状态。

  4. 无需手动管理:对于开发者而言,SSE 无需手动管理连接建立和维护的过程。客户端通过创建 EventSource 对象自动处理连接的创建和维护,服务器端只需按照 SSE 的标准格式发送消息即可。

2 为什么说“无需建立连接”?

当我们说 SSE “无需建立连接”时,通常是指与 WebSocket 相比,SSE 不需要实现复杂的握手协议和连接管理。在 SSE 中,连接的建立和维护对于开发者来说是透明的,他们只需关注如何发送消息。从技术角度看,SSE 确实建立了一个持久的 HTTP 连接,但这个过程对开发者而言是简化和隐藏的。

3 总结

SSE 技术确实涉及到与客户端的连接建立,但这个过程对开发者来说是简化的,主要体现在单向通信和基于标准 HTTP 协议的长期连接上。这使得 SSE 在单向数据流场景下(如服务器推送更新)变得非常高效和易于实现。

28.8.2 websoket的建立流程

WebSocket 的建立流程包括一个名为“握手”的过程,该过程在客户端和服务器之间建立一个全双工的通信通道。下面是详细的步骤:

1. 客户端请求(握手请求)
  1. 发起请求:客户端创建一个到服务器的标准 HTTP 请求。

  2. 升级头部:在这个请求中,客户端包括一个 “Upgrade” 头部,指明希望“升级”到 WebSocket 协议。

  3. Key:客户端还会发送一个 Base64 编码的随机值(Sec-WebSocket-Key),这是一个握手的一部分,用于之后的验证过程。

    示例请求头可能如下所示:

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Version: 13
    
2. 服务器响应(握手响应)
  1. 校验请求:服务器会校验请求是否有效,包括检查 Upgrade 头部和 WebSocket 版本等。

  2. 接受升级:如果校验通过,服务器返回一个 HTTP 响应,确认升级到 WebSocket。

  3. 响应头部:响应中包括一个 “Sec-WebSocket-Accept” 头部,其值是对客户端 Sec-WebSocket-Key 的回应,由服务器生成。

    示例响应头可能如下所示:

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    
3. 连接建立
  1. 完成握手:一旦客户端收到服务器的确认响应,WebSocket 握手过程就完成了,此时,一个全双工的 WebSocket 连接就建立起来了。
  2. 数据传输:随后,客户端和服务器就可以通过这个连接开始发送和接收数据。
4. 数据帧
  • 数据通过 WebSocket 数据帧传输,这些帧可以包含文本或二进制数据。
  • 客户端和服务器都可以随时发送数据帧。
5. 连接维护
  • WebSocket 连接保持活跃,直到被客户端或服务器主动关闭。
6总结

WebSocket 握手过程实际上是一个将标准的 HTTP 连接“升级”到 WebSocket 连接的过程。这个过程保证了服务器和客户端都理解和同意建立一个全双工的通信通道。这种机制使 WebSocket 非常适合需要实时双向通信的应用,如聊天应用、游戏、实时数据监控等。

28.8.3 sse技术在服务端是不是没有read buffer,只有send buffer,所以相对而言消耗的资源更少呢?

在使用 Server-Sent Events (SSE) 时,确实主要涉及到发送缓冲区(send buffer),而不像 WebSocket 或其他双向通信协议那样需要处理接收缓冲区(read buffer)。

SSE 的工作原理:SSE 是单向的,服务器向客户端发送消息,但客户端不能通过 SSE 向服务器发送消息(如果需要,通常使用 AJAX 或其他 HTTP 请求)。因此,在服务器端,SSE 主要管理和维护发送消息的缓冲区。
资源消耗:相比于 WebSocket,SSE 可能在服务器端消耗更少的资源,主要是因为它的通信是单向的,并且不需要维护一个完整的双向通信状态。这使得 SSE 在某些场景下(尤其是仅需要服务器到客户端的单向数据流的场景)更加高效。

28.8.4 WebSocket技术每一次发送完毕后都需要客户端响应吗?

客户端响应不是必需的:在 WebSocket 协议中,发送数据后并不强制要求客户端响应。WebSocket 提供了一个全双工的通信通道,客户端和服务器可以独立地发送消息,无需每次交互都进行请求和响应。

根据业务逻辑决定:是否需要客户端对服务器的消息进行响应,完全取决于特定的业务逻辑。例如,如果服务器发送了一个要求确认的指令或数据,客户端可能需要发送一个确认消息或相应的响应。

实时性和独立性:WebSocket 的一个关键优势是其实时性和通信的独立性,允许服务器和客户端按照各自的需求和逻辑独立发送数据。

在实际应用中,WebSocket 通常用于那些需要快速、实时通信的场景,其中客户端和服务器可能会独立地发送和接收消息,而不一定遵循传统的请求-响应模式。

28.9【我的总结】

因为订单系统的复杂性,针对频繁和客户端双向交互的接口,我们采用websoket协议,比如查询订单状态,支付状态;对于秒杀结果返回这个接口,采用sse技术,只需要维持单向通信。

你可能感兴趣的:(java,面经,微服务,架构,秒杀系统)