使用Websockets和Vert.x进行实时竞价

news2025/1/31 19:45:39

翻译: 白石(https://github.com/wjw465150/Vert.x-Core-Manual)

原文地址: https://vertx.io/blog/real-time-bidding-with-websockets-and-vert-x/

在过去的几年中,用户对网络应用程序的期望发生了变化。在拍卖竞价过程中,用户不再需要按下刷新按钮来检查价格是否变化或拍卖是否结束。这使得竞标变得困难且不那么有趣。相反,他们希望在应用程序中实时看到更新。

在本文中,我想展示如何创建一个提供实时出价的简单应用程序。 我们将使用 WebSockets、SockJS 和 Vert.x。

我们将创建一个用于快速出价的前端,它与用 Java 编写并基于 Vert.x 的微服务进行通信。

Websocket 是什么?

WebSocket 是异步、双向、全双工协议,它通过单个 TCP 连接提供通信通道。 通过 WebSocket API,它提供了网站和远程服务器之间的双向通信。

WebSockets 解决了许多阻止 HTTP 协议适用于现代实时应用程序的问题。 不再需要像轮询这样的解决方法,这简化了应用程序架构。 WebSockets 不需要打开多个 HTTP 连接,它们减少了不必要的网络流量并减少了延迟。

Websocket API 与 SockJS

遗憾的是,并非所有 Web 浏览器都支持 WebSocket。 但是,当 WebSockets 不可用时,有些库会提供回退。一个这样的库是 SockJS。 SockJS 从尝试使用 WebSocket 协议开始。但是,如果这不可能,它会使用各种特定于浏览器的传输协议。 SockJS 是一个库,旨在在所有现代浏览器和不支持 WebSocket 协议的环境中工作,例如在限制性公司代理后面。 SockJS 提供了一个类似于标准 WebSocket API 的 API。

快速出价的前端

拍卖网页包含投标表格和一些简单的 JavaScript,它从服务中加载当前价格,打开到 SockJS 服务器的事件总线连接并提供投标。 我们出价的示例网页的 HTML 源代码可能如下所示:

<h3>Auction 1</h3>
<div id="error_message"></div>
<form>
    Current price:
    <span id="current_price"></span>
    <div>
        <label for="my_bid_value">Your offer:</label>
        <input id="my_bid_value" type="text">
        <input type="button" onclick="bid();" value="Bid">
    </div>
    <div>
        Feed:
        <textarea id="feed" rows="4" cols="50" readonly></textarea>
    </div>
</form>

我们使用 vertx-eventbus.js 库来创建到事件总线的连接。 vertx-eventbus.js 库是 Vert.x 发行版的一部分。 vertx-eventbus.js 在内部使用 SockJS 库将数据发送到 SockJS 服务器。在下面的代码片段中,我们创建了一个事件总线实例。构造函数的参数是连接到事件总线的 URI。然后我们注册监听地址 auction.<auction_id> 的处理程序。每个客户端都可以在多个地址注册,例如 在拍卖 1234 中出价时,他们会在地址auction.1234等上注册。当数据到达处理程序时,我们会更改当前价格和拍卖网页上的出价提要。

function registerHandlerForUpdateCurrentPriceAndFeed() {
    var eventBus = new EventBus('http://localhost:8080/eventbus');
    eventBus.onopen = function () {
        eventBus.registerHandler('auction.' + auction_id, function (error, message) {
            document.getElementById('current_price').innerHTML = JSON.parse(message.body).price;
            document.getElementById('feed').value += 'New offer: ' + JSON.parse(message.body).price + '\n';
        });
    }
};

任何尝试出价的用户都会向服务生成一个 PATCH Ajax 请求,其中包含有关拍卖中新出价的信息(请参阅下面的bid()函数)。在服务器端,我们将事件总线上的此信息发布给注册到某个地址的所有客户端。如果您收到200 (OK)以外的 HTTP 响应状态代码,则会在网页上显示一条错误消息。

function bid() {
    var newPrice = document.getElementById('my_bid_value').value;

    var xmlhttp = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
    xmlhttp.onreadystatechange = function () {
        if (xmlhttp.readyState == 4) {
            if (xmlhttp.status != 200) {
                document.getElementById('error_message').innerHTML = 'Sorry, something went wrong.';
            }
        }
    };
    xmlhttp.open("PATCH", "http://localhost:8080/api/auctions/" + auction_id);
    xmlhttp.setRequestHeader("Content-Type", "application/json");
    xmlhttp.send(JSON.stringify({price: newPrice}));
};

拍卖 服务

SockJS 客户端需要服务器端部分。现在我们要创建一个轻量级的 RESTful 拍卖服务。我们将以 JSON 格式发送和检索数据。让我们从创建一个 Verticle 开始。首先我们需要继承自 AbstractVerticle 类并覆盖 start 方法。每个 Verticle 实例都有一个名为vertx的成员变量。这提供了对 Vert.x 核心 API 的访问。例如,要创建一个 HTTP 服务器,您可以在 vertx 实例上调用 createHttpServer 方法。要告诉服务器在端口 8080 上侦听传入请求,您可以使用 listen 方法。

我们需要一个带有路由的router。 router 接受 HTTP 请求并找到第一个匹配的路由。 路由可以有一个与之关联的处理程序,它接收请求(例如,匹配路径 /eventbus/* 的路由与 eventBusHandler 相关联)。

我们可以对请求做一些事情,然后结束它或将它传递给下一个匹配的处理程序。

如果您有很多处理程序,则将它们拆分为多个路由器是有意义的。

您可以通过在另一个路由器的挂载点挂载一个路由器来完成此操作(参见下面代码片段中与 /api 挂载点相对应的 auctionApiRouter)。

Here’s an ex­am­ple ver­ti­cle:

public class AuctionServiceVerticle extends AbstractVerticle {

    @Override
    public void start() {
        Router router = Router.router(vertx);

        router.route("/eventbus/*").handler(eventBusHandler());
        router.mountSubRouter("/api", auctionApiRouter());
        router.route().failureHandler(errorHandler());
        router.route().handler(staticHandler());

        vertx.createHttpServer().requestHandler(router::accept).listen(8080);
    }

    //…
}

现在我们将更详细地看一下。 我们将讨论 Verticle 中使用的 Vert.x 功能:错误处理程序、SockJS 处理程序、Body处理程序、共享数据、静态处理程序和基于方法、路径等的路由。

错误处理器

除了设置处理程序来处理请求外,您还可以为路由中的失败设置处理程序。 如果处理程序抛出异常,或者如果处理程序调用 fail方法。 为了呈现错误页面,我们使用 Vert.x 提供的错误处理程序:

private ErrorHandler errorHandler() {
    return ErrorHandler.create();
}

SockJS 处理程序

Vert.x 为 SockJS 处理程序提供了事件总线桥,它将服务器端 Vert.x 事件总线扩展到客户端 JavaScript。

配置网桥以告诉它哪些消息应该通过很容易。您可以使用 BridgeOptions 指定允许哪些匹配项用于入站和出站流量 .如果消息是出站的,在将其从服务器发送到客户端 JavaScript 之前,Vert.x 将查看所有出站允许的匹配项。在下面的代码片段中,我们允许来自以“拍卖”开头的地址的任何消息。 并以数字结尾(例如 auction.1auction.100 等)。

如果你想在桥上发生事件时得到通知,你可以在调用桥时提供一个处理程序。例如,创建新的 SockJS 套接字时将发生 SOCKET_CREATED 事件。该事件是 Future 的实例。完成事件处理后,您可以使用“true”完成未来以启用进一步处理。

要启动桥,只需在 SockJS 处理程序上调用 bridge 方法:

private SockJSHandler eventBusHandler() {
    BridgeOptions options = new BridgeOptions()
            .addOutboundPermitted(new PermittedOptions().setAddressRegex("auction\\.[0-9]+"));
    return SockJSHandler.create(vertx).bridge(options, event -> {
         if (event.type() == BridgeEventType.SOCKET_CREATED) {
            logger.info("A socket was created");
        }
        event.complete(true);
    });
}

Body 处理器

BodyHandler 允许您检索请求正文、限制正文大小并处理文件上传。对于需要此功能的任何请求,Body处理程序应该在匹配的路由上。我们在投标过程中需要 BodyHandler(PATCH 方法请求 /auctions/<auction_id> 包含请求正文,其中包含有关拍卖中新报价的信息)。创建一个新的Body处理程序很简单:

BodyHandler.create();

如果请求体是JSON格式,可以通过getBodyAsJson方法获取。

共享数据

共享数据包含允许您在同一 Vert.x 实例中或跨 Vert.x 实例集群的不同应用程序之间安全地共享数据的功能。 共享数据包括本地共享Map、分布式集群范围Map、异步集群范围锁和异步集群范围计数器。

为了简化应用程序,我们使用本地共享Map来保存有关拍卖的信息。 本地共享Map允许您在同一 Vert.x 实例中的不同 Verticle 之间共享数据。 以下是在拍卖服务中使用共享本地Map的示例:

public class AuctionRepository {

    //…

    public Optional<Auction> getById(String auctionId) {
        LocalMap<String, String> auctionSharedData = this.sharedData.getLocalMap(auctionId);

        return Optional.of(auctionSharedData)
            .filter(m -> !m.isEmpty())
            .map(this::convertToAuction);
    }

    public void save(Auction auction) {
        LocalMap<String, String> auctionSharedData = this.sharedData.getLocalMap(auction.getId());

        auctionSharedData.put("id", auction.getId());
        auctionSharedData.put("price", auction.getPrice());
    }

    //…
}

如果您想将拍卖数据存储在数据库中,Vert.x 提供了一些不同的异步客户端来访问各种数据存储(MongoDB、Redis 或 JDBC 客户端)。

拍卖 API

Vert.x 允许您根据请求路径上的模式匹配将 HTTP 请求路由到不同的处理程序。 它还使您能够从路径中提取值并将它们用作请求中的参数。 每个 HTTP 方法都存在相应的方法。 第一个匹配的将收到请求。 此功能在开发 REST 样式的 Web 应用程序时特别有用。

要从路径中提取参数,您可以使用冒号字符来表示参数的名称。 正则表达式也可用于提取更复杂的匹配项。 通过模式匹配提取的任何参数都将添加到请求参数映射中。

Consumes 描述了处理程序可以使用哪些 MIME 类型。通过使用 produces 您可以定义路由生成的 MIME 类型。在下面的代码中,路由将匹配任何带有匹配 application/jsoncontent-type 标头和 accept 标头的请求。

让我们看一个挂载在主路由器上的子路由器的例子,它是在 Verticle 的 start 方法中创建的:

private Router auctionApiRouter() {
    AuctionRepository repository = new AuctionRepository(vertx.sharedData());
    AuctionValidator validator = new AuctionValidator(repository);
    AuctionHandler handler = new AuctionHandler(repository, validator);

    Router router = Router.router(vertx);
    router.route().handler(BodyHandler.create());

    router.route().consumes("application/json");
    router.route().produces("application/json");

    router.get("/auctions/:id").handler(handler::handleGetAuction);
    router.patch("/auctions/:id").handler(handler::handleChangeAuctionPrice);

    return router;
}

GET 请求返回拍卖数据,而 PATCH 方法请求允许您在拍卖中出价。让我们关注更有趣的方法,即 handleChangeAuctionPrice。用最简单的术语来说,该方法可能看起来像这样:

public void handleChangeAuctionPrice(RoutingContext context) {
    String auctionId = context.request().getParam("id");
    Auction auction = new Auction(
        auctionId,
        new BigDecimal(context.getBodyAsJson().getString("price"))
    );

    this.repository.save(auction);
    context.vertx().eventBus().publish("auction." + auctionId, context.getBodyAsString());

    context.response()
        .setStatusCode(200)
        .end();
}

/auctions/1PATCH 请求将导致变量 auctionId 获得值 1。我们在拍卖中保存一个新的报价,然后在事件总线上将这个信息发布给所有在客户端JavaScript地址上注册的客户端。完成 HTTP 响应后,您必须对其调用 end 函数。

静态 处理器

Vert.x 提供了处理静态网络资源的处理程序。提供静态文件的默认目录是 webroot,但可以对其进行配置。默认情况下,静态处理程序将设置缓存标头以使浏览器能够缓存文件。可以使用 setCachingEnabled 方法禁用设置缓存标头。要从拍卖服务提供拍卖 HTML 页面、JS 文件(和其他静态文件),您可以创建一个静态处理程序,如下所示:

private StaticHandler staticHandler() {
    return StaticHandler.create()
        .setCachingEnabled(false);
}

我们来Run一下!

github 上提供了完整的应用程序代码。

克隆存储库并运行 ./gradlew run

打开一个或多个浏览器并将它们指向“http://localhost:8080”。现在您可以在拍卖中出价:
在这里插入图片描述

总结

本文概述了一个允许实时出价的简单应用程序。 我们创建了一个用 Java 编写并基于 Vert.x 的轻量级、高性能和可扩展的微服务。 我们讨论了 Vert.x 提供的内容,其中包括分布式事件总线和优雅的 API,可让您立即创建应用程序。


<<<<<< [完] >>>>>>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193479.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】STL — map和set的介绍 + 使用

文章目录&#x1f4d6; 前言1. 键值对的引入⚡2. 树形结构的关联式容器&#x1f31f;3. set的介绍 使用⭐4. map的介绍 使用⭐&#x1f3c1;4.4.1 利用map统计次数&#xff1a;&#x1f3c1;4.4.2 std::map::operator[]&#x1f4d6; 前言 本章将继续学习STL中的两个很重要的…

23.2.2打卡 2023牛客寒假算法基础集训营5 ABCDHKL 最详细的一集

A 这题据说可以贪心写 我为了省事直接upper二分第一个大于x的商品然后向前遍历完事 /* ⣿⣿⣿⣿⣿⣿⡷⣯⢿⣿⣷⣻⢯⣿⡽⣻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠸⣿⣿⣆⠹⣿⣿⢾⣟⣯⣿⣿⣿⣿⣿⣿⣽⣻⣿⣿⣿⣿⣿⣿⣿ ⣿⣿⣿⣿⣿⣿⣻⣽⡿⣿⣎⠙⣿⣞⣷⡌⢻⣟⣿⣿⣿⣿⣿⣿⣿…

使用动态创建pinia时报injection “Symbol(pinia)“ not found

前阵发现项目内用pinia报了injection "Symbol(pinia)" not found这个错误&#xff0c;因为前一阵用并没有这个问题。问了相关同事后发现是他新建了节点后调用的&#xff0c;导致的问题。 出现的警告如下图&#xff1a; 这问题排除了比较久&#xff0c;为什么呢&…

python自动化办公--pyautogui控制鼠标和键盘操作

在公司某些工作场景下&#xff0c;需要大量重复的工作&#xff0c;重复的工作完全可以通过python软件的自动化实现&#xff0c;省时省力。本文分享python自动化办公的利器之一--pyautogui&#xff0c;通过pyautogui可以轻松控制鼠标和键盘操作。 PyAutoGUI是一个纯Python的GUI自…

纯滞后系统的大林控制算法

大林控制算法原理早在1968年&#xff0c;美国IBM公司的大林&#xff08;Dahlin&#xff09;就提出了一种不同于常规PID控制规律的新型算法&#xff0c;即大林算法。该算法的最大特点是将期望的闭环响应设计成一阶惯性加纯延迟&#xff0c;然后反过来得到能满足这种闭环响应的控…

Linux服务器之间设置共享目录

前言有时候我们需要在两台linux服务器之间共享资源&#xff0c;例如在服务器A上面部署了一个大文件上传程序&#xff0c;但是需要将文件上传到服务器B的某个目录下面&#xff0c;因为上传大文件&#xff0c;需要先将文件所有分块单独上传到服务器B&#xff0c;然后在服务器B上面…

数字文档管理解决财务部门哪些常见问题?

如今&#xff0c;会计部门实施文档管理和自动化工作流程系统至关重要。这些组织要么缺乏数字系统&#xff0c;要么没有充分利用其文档管理解决方案的潜力。 数字文档管理解决财务部门哪些常见问题&#xff1f; 1.错过提前付款折扣&#xff1a;供应商经常为提前付款提供折扣&am…

虹科教您 | 浅谈现代GNSS模拟中的软件定义架构

随着技术的迭代更新&#xff0c;GPS/GNSS模拟技术也在不断发展进步。在过去&#xff0c;想要进行GNSS仿真基本上只有一种选择&#xff1a;使用固定式或分配式的硬件进行模拟。而如今&#xff0c;带来颠覆性创新的新型软件定义架构正在迅速取代传统的定制架构&#xff0c;这种独…

EvilnoVNC:一款针对组织安全与安全意识培训的网络钓鱼平台

关于EvilnoVNC EvilnoVNC是一款针对组织安全与安全意识培训的网络钓鱼平台&#xff0c;该工具开箱即用&#xff0c;可以帮助各大企业组织对内部员工进行安全意识培训&#xff0c;而且也可以帮助广大研究人员测试和研究网络钓鱼防御技术。 和其他网络钓鱼技术不同的不同之处在…

高并发秒杀的 常见的7种方案

高并发场景在现场的日常工作中很常见&#xff0c;特别是在互联网公司中&#xff0c;这篇文章就来通过秒杀商品来模拟高并发的场景。 本文环境&#xff1a; SpringBoot 2.5.7 MySQL 8.0 X MybatisPlus Swagger2.9.2 模拟工具&#xff1a; Jmeter 模拟场景&#xff1a; 减库…

2.5G网卡调试记录

2.5G网卡调试记录 下载驱动 去https://www.realtek.com/zh-tw/downloads这个网站下载2.5G USB网卡对应的驱动 编译驱动 Makefile中需要进入到内核目录/lib/modules/4.19.232/build中进行内核编译&#xff0c;但是我们的build文件链接已经失效了&#xff0c;并且源文件也删除…

力扣sql简单篇练习(八)

力扣sql简单篇练习(八) 1 修复表中的名字 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # 考察的是字符串函数的用法 SELECT user_id,concat(upper(left(name,1)),lower(substr(name,2))) name FROM Users ORDER BY user_id asc1.3 运行截图 2 订单…

Qt TCP (小型聊天窗口)

实现的具体功能为&#xff1a; 服务器端需要主动监听&#xff0c;可以主动断开连接&#xff0c;可以发送信息给客户端客户端需要输入主机&#xff0c;端口号&#xff0c;需要主动连接&#xff0c;可以主动断开连接&#xff0c;可以发送信息给服务器端服务器端和客户端都能看到聊…

【C++算法图解专栏】一篇文章带你掌握差分算法

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为 0 基础刚入门数据结构与算法的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们一起交流~ &#x1f4da;专栏地址&#xff1a;https://blog.csdn.net/Newin…

hadoop.fs.FileSystem.get导致OOM的原因和解决方案

问题描述 在调用HDFS获取文件系统的get接口时&#xff0c;指定用户可能会导致OOM问题&#xff0c;示例代码如下&#xff1a; FileSystem fileSystem FileSystem.get(uri, conf, "hadoopuser");问题溯源 该方法源码&#xff1a; 在有缓存的情况下将从Cache中取&a…

Android 11 SystemUI(状态/导航栏)-状态栏下拉时图标的隐藏与通知面板的半透黑色背景

概述 本文续自:Android 11 的状态栏的隐藏 PS 本文虽然同属于SystemUI, 但目前并 没有 打算整理成专橍或撰写一个系列的想法. 仅仅为了记录一些过程, 留下那些容易被遗忘的点滴. 开始下拉时状态栏图标被隐藏 状态橍的图标在用户开始触摸(ACTION_DOWN)后, 会开始展开, 显示扩展…

答题小程序题目批量导入模板以及题库文本格式规范

近期又接到了一个知识竞赛的需求&#xff0c;在开发答题小程序的过程中&#xff0c;遇到了不少难题&#xff0c;但是都一一克服了。凭借多年的答题小程序开发经验&#xff0c;我总结了一下题目批量导入题库文本格式规范。一、答题小程序题目批量导入模板小程序【答题小博士】二…

《数字经济全景白皮书》后疫情时代数字化驱动增长洞察之赛道篇

易观分析&#xff1a;《数字经济全景白皮书》浓缩了易观分析对于数字经济各行业经验和数据的积累&#xff0c;并结合数字时代企业的实际业务和未来面临的挑战&#xff0c;以及数字技术的创新突破等因素&#xff0c;最终从数字经济发展大势以及各领域案例入手&#xff0c;帮助企…

面试之 Python 框架 Flask、Django、DRF

Django、flask、tornado 框架的比较 ★★★★★ Django&#xff1a;大而全的框架。它的内部组件比较多&#xff0c;如 ORM、Admin、中间件、Form、ModelForm、Session、缓存、信号、CSRF等&#xff0c;功能也都很完善。 flask&#xff1a;微型框架&#xff0c;内部组件就比较少…

JavaScript中的事件对象、事件对象的属性

一、什么是事件对象​ 1&#xff09;、 事件对象 就是保存着事件相关信息的对象。当事件发生时&#xff0c;会自动产生事件对象&#xff08;不需要new&#xff09;&#xff0c;事件对象中包含着&#xff1a;事件源&#xff08;发生事件的dom元素&#xff09;&#xff0c;点击是…