对于秒杀系统,用户点击秒杀按钮后,确定其秒杀状态的过程通常包含以下几个关键步骤:
请求处理:
身份验证和参数校验:
读取库存状态:
判断库存:
库存扣减:
生成订单:
【本节考察的重点】返回状态:
后续处理:
异常处理:
在秒杀系统中,尤其是在高并发的场景下,使用异步消息系统来处理和返回用户的秒杀状态可以极大地提高系统的性能和响应速度。以下是详细的解释:
降低系统压力:
提高用户体验:
提升系统伸缩性:
请求接收:
消息队列:
后台处理:
返回最终结果:
使用异步消息系统,在用户发起秒杀请求后,可以迅速响应,然后在后台异步处理请求,并最终将处理结果返回给用户。这种方式不仅提高了系统的处理能力和伸缩性,还能提供更好的用户体验。在设计秒杀系统时,异步处理机制是提高系统性能的关键之一。
当秒杀系统后台完成处理后,将最终结果(例如“秒杀成功”或“秒杀失败”)异步地返回给用户,可以通过几种不同的技术来实现,主要包括 WebSocket、服务端事件(Server-Sent Events, SSE)和轮询。下面是这些技术的详细讲解:
在选择合适的技术时,需要根据应用的具体需求和现有架构来决定。例如,WebSocket 和 SSE 需要服务器和客户端支持长连接,而轮询则对服务器的并发处理能力有一定要求。
WebSocket 和 Server-Sent Events (SSE) 在性能和资源消耗方面有一些关键区别,这些区别通常取决于应用场景和通信的性质:
在选择时,还需要考虑服务器的负载能力、客户端的需求以及应用的特定场景。
对于秒杀系统来说,选择 WebSocket 还是 SSE 主要取决于系统的具体需求和架构。考虑到秒杀场景的特点,下面是一些决策因素:
双向通信需求:如果您的系统除了发送秒杀结果之外,还需要实现客户端到服务器的实时通信(例如,实时更新用户的出价、聊天等),那么 WebSocket 是更好的选择。
高频更新:如果秒杀系统涉及到频繁的状态更新,如实时库存显示,WebSocket 由于其低延迟特性可能更适合。
连接维持:WebSocket 需要在客户端和服务器之间维持一个持久的连接,这可能在资源上更加昂贵,特别是当有大量用户同时在线时。
单向数据流:如果秒杀系统只需要服务器向客户端推送消息(例如,仅通知秒杀结果),SSE 是一个简单而高效的选择。
资源效率:相比于 WebSocket,SSE 在服务器端通常更加轻量,因为它只涉及单向通信,并且连接的维护开销较低。
易于实现和维护:SSE 在某些情况下比 WebSocket 更容易实现和维护,特别是当应用的主要需求是单向数据流时。
在做出选择时,还需要考虑系统的整体架构、预期的用户规模、以及服务器资源等因素。在某些情况下,也可以考虑将这两种技术结合使用,以满足不同的业务需求。
在 Spring Boot 中实现 WebSocket 通信涉及几个关键步骤。以下是一个简化的示例,展示如何在 Spring Boot 应用中设置 WebSocket 以用于秒杀系统通知。
首先,在 pom.xml
文件中添加 Spring WebSocket 的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-websocketartifactId>
dependency>
dependencies>
创建一个配置类来启用和配置 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
前缀下广播消息。
创建一个控制器来处理 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
。
在前端,使用 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");
});
在这个简单的例子中,我们设置了一个 Spring Boot 应用,其中包含 WebSocket 支持以实现秒杀系统中的实时通讯。当用户发起秒杀请求时,前端通过 WebSocket 向服务器发送信息,并订阅特定主题以接收秒杀结果。
请注意,这只是一个基础的示例,实际的秒杀系统可能涉及更复杂的逻辑,如处理高并发请求、确保数据一致性和安全性等。
在 Spring Boot 中实现 Server-Sent Events (SSE) 用于秒杀系统的通知相对简单。SSE 允许服务器端向客户端推送实时数据,而无需客户端轮询或建立复杂的连接。下面是一个基本的实现流程:
通常情况下,使用 Spring Boot Web Starter 依赖就足够了,因为 SSE 依赖于 Spring MVC 的功能:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
在 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
方法可以被用来发送秒杀结果给所有连接的客户端。
在前端,你可以使用 EventSource API 来监听来自服务器的事件:
var eventSource = new EventSource('/sse');
eventSource.onmessage = function(event) {
console.log('Received: ', event.data);
// 处理服务器发送的数据
};
eventSource.onerror = function(event) {
// 处理错误
};
在服务器端的某个逻辑中(例如秒杀处理逻辑),你可以调用 sendToAllClients
方法来向所有监听的客户端发送数据。
使用 SSE,秒杀系统的服务器可以轻松地向客户端推送实时更新(如秒杀成功或失败的通知)。这种方法的优点是实现简单,无需建立复杂的通信协议,而且对于单向数据流来说是非常高效的。
答案就是参考28.7的sse技术,只允许单向推送,服务端可以用更少的资源维护和大量的秒杀客户端建立的连接
实际上,Server-Sent Events (SSE) 技术确实需要与客户端建立一个连接,但这种连接的性质和 WebSocket 不同,它是一种单向的、长期的 HTTP 连接。让我们来详细了解一下:
单向通信:SSE 是一种服务器到客户端的单向通信。服务器可以不断地向客户端发送消息,但客户端无法通过这个连接发送消息给服务器(客户端需要使用其他方式,如 AJAX 请求,来发送数据到服务器)。
基于 HTTP:SSE 通过标准的 HTTP 协议建立连接。当客户端通过 EventSource
接口发起请求时,服务器响应这个请求并保持连接打开,然后通过这个连接推送消息。
长期连接:一旦建立,SSE 连接会保持开放,允许服务器持续不断地发送更新,直到连接被关闭。这种长期连接是通过 HTTP 的响应机制实现的,其中服务器不会立即结束响应,而是保持响应打开状态。
无需手动管理:对于开发者而言,SSE 无需手动管理连接建立和维护的过程。客户端通过创建 EventSource
对象自动处理连接的创建和维护,服务器端只需按照 SSE 的标准格式发送消息即可。
当我们说 SSE “无需建立连接”时,通常是指与 WebSocket 相比,SSE 不需要实现复杂的握手协议和连接管理。在 SSE 中,连接的建立和维护对于开发者来说是透明的,他们只需关注如何发送消息。从技术角度看,SSE 确实建立了一个持久的 HTTP 连接,但这个过程对开发者而言是简化和隐藏的。
SSE 技术确实涉及到与客户端的连接建立,但这个过程对开发者来说是简化的,主要体现在单向通信和基于标准 HTTP 协议的长期连接上。这使得 SSE 在单向数据流场景下(如服务器推送更新)变得非常高效和易于实现。
WebSocket 的建立流程包括一个名为“握手”的过程,该过程在客户端和服务器之间建立一个全双工的通信通道。下面是详细的步骤:
发起请求:客户端创建一个到服务器的标准 HTTP 请求。
升级头部:在这个请求中,客户端包括一个 “Upgrade” 头部,指明希望“升级”到 WebSocket 协议。
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
校验请求:服务器会校验请求是否有效,包括检查 Upgrade 头部和 WebSocket 版本等。
接受升级:如果校验通过,服务器返回一个 HTTP 响应,确认升级到 WebSocket。
响应头部:响应中包括一个 “Sec-WebSocket-Accept” 头部,其值是对客户端 Sec-WebSocket-Key 的回应,由服务器生成。
示例响应头可能如下所示:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
WebSocket 握手过程实际上是一个将标准的 HTTP 连接“升级”到 WebSocket 连接的过程。这个过程保证了服务器和客户端都理解和同意建立一个全双工的通信通道。这种机制使 WebSocket 非常适合需要实时双向通信的应用,如聊天应用、游戏、实时数据监控等。
在使用 Server-Sent Events (SSE) 时,确实主要涉及到发送缓冲区(send buffer),而不像 WebSocket 或其他双向通信协议那样需要处理接收缓冲区(read buffer)。
SSE 的工作原理:SSE 是单向的,服务器向客户端发送消息,但客户端不能通过 SSE 向服务器发送消息(如果需要,通常使用 AJAX 或其他 HTTP 请求)。因此,在服务器端,SSE 主要管理和维护发送消息的缓冲区。
资源消耗:相比于 WebSocket,SSE 可能在服务器端消耗更少的资源,主要是因为它的通信是单向的,并且不需要维护一个完整的双向通信状态。这使得 SSE 在某些场景下(尤其是仅需要服务器到客户端的单向数据流的场景)更加高效。
客户端响应不是必需的:在 WebSocket 协议中,发送数据后并不强制要求客户端响应。WebSocket 提供了一个全双工的通信通道,客户端和服务器可以独立地发送消息,无需每次交互都进行请求和响应。
根据业务逻辑决定:是否需要客户端对服务器的消息进行响应,完全取决于特定的业务逻辑。例如,如果服务器发送了一个要求确认的指令或数据,客户端可能需要发送一个确认消息或相应的响应。
实时性和独立性:WebSocket 的一个关键优势是其实时性和通信的独立性,允许服务器和客户端按照各自的需求和逻辑独立发送数据。
在实际应用中,WebSocket 通常用于那些需要快速、实时通信的场景,其中客户端和服务器可能会独立地发送和接收消息,而不一定遵循传统的请求-响应模式。
因为订单系统的复杂性,针对频繁和客户端双向交互的接口,我们采用websoket协议,比如查询订单状态,支付状态;对于秒杀结果返回这个接口,采用sse技术,只需要维持单向通信。