Java EE 7批处理和魔兽世界–第1部分

这是我在上一个JavaOne上的会议之一。 这篇文章将扩展主题并使用Batch JSR-352 API进入一个实际的应用程序。 此应用程序与MMORPG 魔兽世界集成。

由于JSR-352是Java EE世界中的新规范,所以我认为许多人不知道如何正确使用它。 确定本规范适用的用例也可能是一个挑战。 希望该示例可以帮助您更好地理解用例。

抽象

《魔兽世界》是一款全球超过800万玩家玩的游戏。 该服务按地区提供:美国(US) ,欧洲(EU) ,中国和韩国。 每个区域都有一组称为Realm的服务器,您可以使用这些服务器进行连接以玩游戏。 对于此示例,我们仅研究美国欧盟地区。

orgrimmar拍卖行

该游戏最有趣的功能之一是允许您使用拍卖行买卖称为“ 物品”的游戏内商品。 每个领域都有两个拍卖行 。 平均每个领域交易约70.000 。 让我们计算一些数字:

  • 512 境界美国欧盟
  • 每个领域 7万个 物品
  • 整个商品超过3500万

数据

《魔兽世界》的另一个有趣之处在于,开发人员提供了REST API来访问大多数游戏内信息,包括拍卖行的数据。 在此处检查完整的API。

拍卖行的数据分两步获得。 首先,我们需要查询对应的Auction House Realm REST端点,以获取对JSON文件的引用。 接下来,我们需要访问该URL并下载包含所有拍卖行 物品信息的文件。 这是一个例子:

http://eu.battle.net/api/wow/auction/data/aggra-portugues

应用程序

我们的目标是建立一个下载拍卖行的应用程序,对其进行处理并提取指标。 这些指标将建立商品价格随时间变化的历史记录。 谁知道? 也许借助这些信息,我们可以预测价格波动并在最佳时间购买或出售商品

设置

对于设置,我们将在Java EE 7中使用一些其他功能:

  • Java EE 7
  • 角JS
  • 角度ng-grid
  • UI引导程序
  • 谷歌图表
  • 野蝇

职位

批处理JSR-352作业将执行主要工作。 作业是封装整个批处理过程的实体。 作业将通过作业规范语言连接在一起。 使用JSR-352 ,作业只是这些步骤的容器。 它组合了逻辑上属于流程的多个步骤。

我们将把业务登录分为三个工作:

  • 准备 –创建所需的所有支持数据。 列出领域 ,创建文件夹以复制文件。
  • 文件 –查询领域以检查是否有新文件要处理。
  • 处理 –下载文件,处理数据,提取指标。

编码

后端–具有Java 8的Java EE 7

大多数代码将在后端。 我们需要Batch JSR-352 ,但我们还将使用Java EE的许多其他技术:例如JPA , JAX-RS , CDI和JSON-P 。

由于“ 准备工作”仅用于初始化应用程序资源以进行处理,因此我将跳过它,而深入到最有趣的部分。

文件作业

文件作业是AbstractBatchlet的实现。 批处理是批处理规范中可用的最简单的处理样式。 这是一个面向任务的步骤,其中任务被调用一次,执行并返回退出状态。 对于执行各种非面向项目的任务,例如执行命令或执行文件传输,此类型最有用。 在这种情况下,我们的Batchlet将在每个Realm上对每个发出REST请求,以进行迭代,并使用包含要处理的数据的文件检索URL。 这是代码:

LoadAuctionFilesBatchlet

@Named
public class LoadAuctionFilesBatchlet extends AbstractBatchlet {@Injectprivate WoWBusiness woWBusiness;@Inject@BatchProperty(name = "region")private String region;@Inject@BatchProperty(name = "target")private String target;@Overridepublic String process() throws Exception {List<Realm> realmsByRegion = woWBusiness.findRealmsByRegion(Realm.Region.valueOf(region));realmsByRegion.parallelStream().forEach(this::getRealmAuctionFileInformation);return "COMPLETED";}void getRealmAuctionFileInformation(Realm realm) {try {Client client = ClientBuilder.newClient();Files files = client.target(target + realm.getSlug()).request(MediaType.TEXT_PLAIN).async().get(Files.class).get(2, TimeUnit.SECONDS);files.getFiles().forEach(auctionFile -> createAuctionFile(realm, auctionFile));} catch (Exception e) {getLogger(this.getClass().getName()).log(Level.INFO, "Could not get files for " + realm.getRealmDetail());}}void createAuctionFile(Realm realm, AuctionFile auctionFile) {auctionFile.setRealm(realm);auctionFile.setFileName("auctions." + auctionFile.getLastModified() + ".json");auctionFile.setFileStatus(FileStatus.LOADED);if (!woWBusiness.checkIfAuctionFileExists(auctionFile)) {woWBusiness.createAuctionFile(auctionFile);}}
}

关于此的一个很酷的事情是Java 8的使用parallelStream()一次调用多个REST请求很容易! 您真的可以注意到其中的区别。 如果您想尝试一下,只需运行示例,然后用stream()替换parallelStream() stream()并检出即可。 在我的机器上,使用parallelStream()可使任务执行速度提高约5或6倍。

更新资料
通常,我不会使用这种方法。 我这样做了,因为部分逻辑涉及调用慢速的REST请求,而parallelStreams确实在这里闪耀。 可以使用批处理分区执行此操作,但是很难实现。 我们还需要每次都在服务器池中收集新数据,因此,如果跳过一个或两个文件,这并不可怕。 请记住,如果您不想错过任何一条记录,块处理样式将更适合。 感谢Simon Simonelli引起我的注意。

由于美国欧盟领域要求调用不同的REST端点,因此它们非常适合分区。 分区意味着该任务将运行到多个线程中。 每个分区一个线程。 在这种情况下,我们有两个分区。

要完成作业定义,我们需要提供一个JoB XML文件。 这需要放置在META-INF/batch-jobs目录中。 这是此作业的files-job.xml

files-job.xml

<job id="loadRealmAuctionFileJob" xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="1.0"><step id="loadRealmAuctionFileStep"><batchlet ref="loadAuctionFilesBatchlet"><properties><property name="region" value="#{partitionPlan['region']}"/><property name="target" value="#{partitionPlan['target']}"/></properties></batchlet><partition><plan partitions="2"><properties partition="0"><property name="region" value="US"/><property name="target" value="http://us.battle.net/api/wow/auction/data/"/></properties><properties partition="1"><property name="region" value="EU"/><property name="target" value="http://eu.battle.net/api/wow/auction/data/"/></properties></plan></partition></step>
</job>

files-job.xml我们需要定义我们Batchletbatchlet元素。 对于分区,只需定义partition元素并为每个plan分配不同的properties 。 然后,可以使用这些properties使用表达式#{partitionPlan['region']}#{partitionPlan['target']}将值后期绑定到LoadAuctionFilesBatchlet 。 这是一种非常简单的表达式绑定机制,仅适用于简单的属性和字符串。

处理作业

现在,我们要处理领域拍卖数据文件。 使用上一份工作中的信息,我们现在可以下载文件并对数据进行某些处理。 JSON文件具有以下结构:

item-auctions-sample.json

{"realm": {"name": "Grim Batol","slug": "grim-batol"},"alliance": {"auctions": [{"auc": 279573567,            // Auction Id"item": 22792,               // Item for sale Id"owner": "Miljanko",         // Seller Name"ownerRealm": "GrimBatol",   // Realm"bid": 3800000,              // Bid Value"buyout": 4000000,           // Buyout Value"quantity": 20,              // Numbers of items in the Auction"timeLeft": "LONG",          // Time left for the Auction"rand": 0,"seed": 1069994368},{"auc": 278907544,"item": 40195,"owner": "Mongobank","ownerRealm": "GrimBatol","bid": 38000,"buyout": 40000,"quantity": 1,"timeLeft": "VERY_LONG","rand": 0,"seed": 1978036736}]},"horde": {"auctions": [{"auc": 278268046,"item": 4306,"owner": "Thuglifer","ownerRealm": "GrimBatol","bid": 570000,"buyout": 600000,"quantity": 20,"timeLeft": "VERY_LONG","rand": 0,"seed": 1757531904},{"auc": 278698948,"item": 4340,"owner": "Celticpala","ownerRealm": "Aggra(Português)","bid": 1000000,"buyout": 1000000,"quantity": 10,"timeLeft": "LONG","rand": 0,"seed": 0}]}
}

该文件包含从其下载的领域拍卖列表。 在每个记录中,我们可以检查待售物品,价格,卖方和拍卖结束前的剩余时间。 拍卖的算法按拍卖行类型进行汇总: AllianceHorde

对于process-job我们要读取JSON文件,转换数据并将其保存到数据库。 这可以通过块处理来实现。 块是一种ETL(提取–转换–加载)样式的处理,适合处理大量数据。 块一次读取一个数据,并在事务内创建要写出的块。 从ItemReader读入一项,交给ItemProcessor并进行聚合。 一旦读取的项目数等于提交间隔,就通过ItemWriter写入整个块,然后提交事务。

ItemReader

实际文件太大,以致无法将它们完全加载到内存中,否则可能会耗尽它。 相反,我们使用JSON-P API以流方式解析数据。

AuctionDataItemReader

@Named
public class AuctionDataItemReader extends AbstractAuctionFileProcess implements ItemReader {private JsonParser parser;private AuctionHouse auctionHouse;@Injectprivate JobContext jobContext;@Injectprivate WoWBusiness woWBusiness;@Overridepublic void open(Serializable checkpoint) throws Exception {setParser(Json.createParser(openInputStream(getContext().getFileToProcess(FolderType.FI_TMP))));AuctionFile fileToProcess = getContext().getFileToProcess();fileToProcess.setFileStatus(FileStatus.PROCESSING);woWBusiness.updateAuctionFile(fileToProcess);}@Overridepublic void close() throws Exception {AuctionFile fileToProcess = getContext().getFileToProcess();fileToProcess.setFileStatus(FileStatus.PROCESSED);woWBusiness.updateAuctionFile(fileToProcess);}@Overridepublic Object readItem() throws Exception {while (parser.hasNext()) {JsonParser.Event event = parser.next();Auction auction = new Auction();switch (event) {case KEY_NAME:updateAuctionHouseIfNeeded(auction);if (readAuctionItem(auction)) {return auction;}break;}}return null;}@Overridepublic Serializable checkpointInfo() throws Exception {return null;}protected void updateAuctionHouseIfNeeded(Auction auction) {if (parser.getString().equalsIgnoreCase(AuctionHouse.ALLIANCE.toString())) {auctionHouse = AuctionHouse.ALLIANCE;} else if (parser.getString().equalsIgnoreCase(AuctionHouse.HORDE.toString())) {auctionHouse = AuctionHouse.HORDE;} else if (parser.getString().equalsIgnoreCase(AuctionHouse.NEUTRAL.toString())) {auctionHouse = AuctionHouse.NEUTRAL;}auction.setAuctionHouse(auctionHouse);}protected boolean readAuctionItem(Auction auction) {if (parser.getString().equalsIgnoreCase("auc")) {parser.next();auction.setAuctionId(parser.getLong());parser.next();parser.next();auction.setItemId(parser.getInt());parser.next();parser.next();parser.next();parser.next();auction.setOwnerRealm(parser.getString());parser.next();parser.next();auction.setBid(parser.getInt());parser.next();parser.next();auction.setBuyout(parser.getInt());parser.next();parser.next();auction.setQuantity(parser.getInt());return true;}return false;}public void setParser(JsonParser parser) {this.parser = parser;}
}

要打开JSON Parse流,我们需要Json.createParser并传递输入流的引用。 要读取元素,我们只需要调用hasNext()next()方法。 这将返回一个JsonParser.Event ,它使我们能够检查解析器在流中的位置。 从Batch API ItemReaderreadItem()方法中读取并返回元素。 当没有更多元素可读取时,返回null以完成处理。 注意,我们还实现了从ItemReader openclose的方法。 这些用于初始化和清理资源。 它们只执行一次。

ItemProcessor

ItemProcessor是可选的。 它用于转换读取的数据。 在这种情况下,我们需要向竞价添加其他信息。

AuctionDataItemProcessor

@Named
public class AuctionDataItemProcessor extends AbstractAuctionFileProcess implements ItemProcessor {@Overridepublic Object processItem(Object item) throws Exception {Auction auction = (Auction) item;auction.setRealm(getContext().getRealm());auction.setAuctionFile(getContext().getFileToProcess());return auction;}
}

ItemWriter

最后,我们只需要将数据写到数据库中即可:

AuctionDataItemWriter

@Named
public class AuctionDataItemWriter extends AbstractItemWriter {@PersistenceContextprotected EntityManager em;@Overridepublic void writeItems(List<Object> items) throws Exception {items.forEach(em::persist);}
}

在我的计算机上,具有70 k记录文件的整个过程大约需要20秒。 我确实注意到了一些非常有趣的事情。 在编写此代码之前,我使用的是注入的EJB,它通过persist操作来调用方法。 这总共花费了30秒,因此注入EntityManager并执行持久操作可以直接为我节省三分之一的处理时间。 我只能推测该延迟是由于堆栈调用的增加而造成的,其中EJB拦截器位于中间。 这是在Wildfly中发生的。 我将对此进行进一步调查。

要定义块,我们需要将其添加到process-job.xml文件中:

process-job.xml

<step id="processFile" next="moveFileToProcessed"><chunk item-count="100"><reader ref="auctionDataItemReader"/><processor ref="auctionDataItemProcessor"/><writer ref="auctionDataItemWriter"/></chunk>
</step>

item-count属性中,我们定义每个处理块中可以容纳多少个元素。 这意味着每100个事务就会提交一次。 这对于保持较小的事务大小和检查数据很有用。 如果我们需要停止然后重新开始操作,我们可以这样做而不必再次处理每个项目。 我们必须自己编写逻辑代码。 该示例中不包括此功能,但以后会做。

跑步

要运行作业,我们需要获得JobOperator的引用。 JobOperator提供了一个界面来管理作业处理的各个方面,包括操作命令(例如开始,重新启动和停止),以及与作业存储库相关的命令,例如检索作业和步骤执行。

要运行先前的files-job.xml Job,我们执行:

执行工作

JobOperator jobOperator = BatchRuntime.getJobOperator();
jobOperator.start("files-job", new Properties());

请注意,我们使用Job xml文件的名称,而没有扩展名到JobOperator

下一步

我们仍然需要汇总数据以提取指标并将其显示在网页中。 这篇文章已经很长了,因此我将在以后的文章中介绍以下步骤。 无论如何,该部分的代码已经在Github存储库中。 检查资源部分。

资源资源

您可以从我的github存储库中克隆完整的工作副本,然后将其部署到Wildfly。 您可以在此处找到说明进行部署。

翻译自: https://www.javacodegeeks.com/2014/10/java-ee-7-batch-processing-and-world-of-warcraft-part-1.html

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

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

相关文章

学习笔记-AngularJs(十)

前面一直在说自定义指令&#xff0c;但是却一直没有一次系统地去了解&#xff0c;现在需要我们一起来学习如何去使用自定义指令&#xff0c;去丰富html标签、属性&#xff0c;实现多元化、多功能的标签&#xff08;或是属性&#xff09;。辣么&#xff0c;啥是指令&#xff1f;…

WildFly 9 –别希望您的控制台像这样!

每个人都可能听到这个消息。 周一发布了第一个WildFly 9.0.0.Alpha1版本。 您可以从wildfly.org网站上下载它&#xff0c;最大的变化是它是由一个新的功能配置工具构建的&#xff0c;该工具位于现在单独的核心发行版中&#xff0c;并且还包含一个新的Servlet发行版 &#xff08…

磁盘性能 -- IOPS 和 吞吐量 说明

一. Wikepedia上有关IOPS 的说明链接如下&#xff1a;http://en.wikipedia.org/wiki/IOPSIOPS (Input/Output OperationsPer Second, pronounced i-ops) is a common performance measurement used to benchmark computer storage devices like harddisk drives (HDD), solid s…

3使用Jsoup解析Java中HTML文件的示例

HTML是Web的核心&#xff0c;无论您是通过JavaScript&#xff0c;JSP&#xff0c;PHP&#xff0c;ASP还是任何其他Web技术动态生成的&#xff0c;您在Internet上看到的所有页面都是基于HTML的。 您的浏览器实际上是解析HTML并为您呈现。 但是&#xff0c;如果需要解析HTML文档并…

径向菜单的制作

最终效果&#xff1a; 在径向菜单的制作前&#xff0c;首先需要知道几点知识点&#xff1a; Math.sin(x) x 的正玄值。返回值在 -1.0 到 1.0 之间&#xff1b; Math.cos(x) x 的余弦值。返回的是 -1.0 到 1.0 之间的数&#xff1b; 这两个函数中的X 都是指的“弧度”…

OptaPlanner –具有真实道路距离的车辆路线

在现实世界中&#xff0c;车辆路径问题&#xff08;VRP&#xff09;中的车辆必须走这条路&#xff1a;它们不能在客户之间直线行驶。 大多数VRP研究论文和演示都乐于忽略此实现细节。 和我一样&#xff0c;过去。 尽管使用道路距离&#xff08;而不是空中距离&#xff09;不会对…

关于如何在PSA众多请求号中查找数据是属于哪一条。

其中有两个TCODE: RSTSODS与RSTSODS&#xff0c;我们可以查找数据源的PSA表&#xff0c;然后在SE16中可以看到。 另外我们对PSA点击管理&#xff0c;一般会出现在窗口上面出现PSA的表名。 当然有些不在的话&#xff0c;那就去查找那两个TCODE。转载于:https://www.cnblogs.com/…

揭示垃圾收集暂停的时间长度

有几种方法可以改善您的产品。 一种方法是仔细跟踪用户的体验并在此基础上进行改进。 我们确实自己应用了此技术&#xff0c;并再次花了一些时间查看不同的数据 除了我们追求的许多其他方面之外&#xff0c;我们还提出了一个问题“延迟GC触发应用程序的最坏情况是什么”。 为了…

[转]android ListView详解

本文转自&#xff1a;http://www.cnblogs.com/allin/archive/2010/05/11/1732200.html 由于google doc 很多人都打不开&#xff0c;故更新了源码下载地址 【源码下载】----2011-01-18 在android开发中ListView是比较常用的组件&#xff0c;它以列表的形式展示具体内容&#xff…

JBoss BPM Suite 6.0.3版本的5个实用技巧

上周&#xff0c;红帽发布了标记为6.0.3的JBoss BPM Suite的下一版本&#xff0c;已订阅的用户可以在其客户门户中使用。 如果您对该版本的新增功能感到好奇&#xff0c;请在客户门户网站上在线查看版本说明和其余文档 。 我们正在寻找一些简单的方法来开始使用此新版本&…

Django学习---原生ajax

Ajax 原生ajax Ajax主要就是使用 【XmlHttpRequest】对象来完成请求的操作&#xff0c;该对象在主流浏览器中均存在(除早起的IE)&#xff0c;Ajax首次出现IE5.5中存在&#xff08;ActiveX控件&#xff09;。 XmlHttpRequest对象的主要方法&#xff1a; a. void open(String …

霸主–统治和管理API的地方

今天我们生活在一个越来越分散的世界中。 如今的计算机系统不再是在随机桌子下面的某些硬件上运行单个部门项目&#xff0c;而是大规模&#xff0c;集中甚至分散地运行。 监视和管理的需求从未改变&#xff0c;但是随着时间的推移变得越来越复杂。 如果将所有这些跨功能功能都放…

Java开发人员应该知道的5种错误跟踪工具

随着Java生态系统的不断发展&#xff0c;可满足不断增长的请求和用户对高性能需求的Web应用程序成为了新型的现代开发工具。 具有快速新部署的快速节奏环境需要跟踪错误并获得应用程序行为的洞察力&#xff0c;而传统方法无法维持这种水平。 在这篇文章中&#xff0c;我们决定收…

Emacs中的Color Theme以及字体设置

先上一张效果图&#xff1a; Color Theme用的是gnome2, 字体用的是Visual Studio自带的Consolas。我使用的环境是WindowsCygwinEmacs23.2。 1,安装Color Theme插件 首先&#xff0c;从http://download.savannah.gnu.org/releases/color-theme/下载color theme 6.6.0版本。 接着…

vue兼容ie10问题并且node——module中出现es6语法如何解决

一、首先进行安装babel-polyfill&#xff0c;如果你用yarn安装babel-polyfill的话需要yarn add babel-polyfill进行安装 二、在babel.config.js中加入 三、在ie浏览器中找到报错的文件&#xff0c;然后将文件加入其中 转载于:https://www.cnblogs.com/changhuanran/p/11193149.…

2个在Java中将Byte []数组转换为String的示例

将字节数组转换为String似乎很容易&#xff0c;但是很难做到正确。 每当字节转换为String或char时&#xff0c;许多程序员都会犯忽略字符编码的错误&#xff0c;反之亦然。 作为程序员&#xff0c;我们都知道计算机只能理解二进制数据&#xff0c;即0和1。我们看到和使用的所有…

Linux文件IO-例会笔记总结

上周日实验室例会主要涉及linux文件操作的内核实现。主要讨论了linux下对文件进行操作时&#xff0c;系统内部调用了那些函数以及它们是怎么相互配合的。 linux系统是怎样对不同介质和不同的文件系统提供统一的文件操作接口呢&#xff1f;答案是&#xff1a;VFS。系统中所有文件…

用js来实现那些数据结构12(散列表)

上一篇写了如何实现简单的Map结构&#xff0c;因为东西太少了不让上首页。好吧。。。 这一篇文章说一下散列表hashMap的实现。那么为什么要使用hashMap&#xff1f;hashMap又有什么优势呢&#xff1f;hashMap是如何检索数据的&#xff1f;我们一点一点的来解答。 在我们学习一门…

探索SwitchYard 2.0.0.Alpha2快速入门

在我的最后一篇文章中&#xff0c;我解释了如何在WildFly 8.1上使用SwitchYard。 同时&#xff0c;该项目很忙&#xff0c;并发布了另一个Alpha2。 这是一个很好的机会&#xff0c;在这里浏览快速入门并刷新您的记忆。 除了版本更改之外&#xff0c;您仍然可以使用较早的博客来…

走进webpack(1)--环境拆分及模块化

初级的文章和demo已经基本完成了&#xff0c;代码也已经上传到了我的github上&#xff0c;如果你对webpack的使用并不是十分了解&#xff0c;那么建议你回头看下走近系列&#xff0c;里面包括了当前项目中使用频繁的插件&#xff0c;loader的讲解。以及基本的webpack配置&#…