报名参加CloudWeGo黑客松,奖金直推双丰收! 了解详情
写点什么

使用 Spring Boot 构建 RESTful Web 服务以访问存储于 Aerospike 集群中的数据

  • 2014-02-20
  • 本文字数:8257 字

    阅读完需:约 27 分钟

Spring Boot 是对 Spring 快速入门的强大工具。Spring Boot 能够帮助你很容易地构建基于 Spring 的应用。

Aerospike 是分布式和可复制的内存数据库,不管使用 DRAM 还是原生的 flash/SSD,Aerospike 都进行了优化。

Aerospike 具有高可靠性并且遵循 ACID。开发人员能够在不停止数据库服务的情况下,很快地将数据库集群从两个节点扩展到二十个节点。

你所要构建的是什么

本文将会引领你使用 Spring Boot 创建一个简单的 RESTful Web 服务。

要构建的服务接受一个 HTTP GET 请求。它的响应是如下的 JSON:

复制代码
{"expiration":121023390,"bins":{"DISTANCE":2446,"DEST_CITY_NAME":"New
York","DEST":"JFK","YEAR":2012,"ORI_AIRPORT_ID":"14679","DEP_TIME":
"802","DAY_OF_MONTH":12,"DEST_STATE_ABR":"NY","ORIGIN":"SAN","FL_NUM"
:160,"CARRIER":"AA","ORI_STATE_ABR":"CA","FL_DATE":"2012/01/12",
"AIR_TIME":291,"ORI_CITY_NAME":"San Diego","ELAPSED_TIME":321,
"ARR_TIME":"1623","AIRLINE_ID":19805},"generation":1}

这里所使用的数据是商业上的飞行航班详情(包含在样例代码中,这是一个名为 flights_from.csv 的数据文件,它包含了大约一百万条航班信息)。

在产品化(或其他)环境中,还会有很多内置的特性添加到应用中以管理服务。这个功能来源于 Spring,参见 Spring 指导: Building a RESTful web service

你所需要的是什么

搭建工程

在构建应用的时候,你可以使用任何喜欢的构建系统,不过在这里提供了 Maven 的代码。如果你不熟悉 Maven 的话,请参考 Spring 指导: Building Java Projects with Maven

你还需要构建并安装 Aerospike 的 Java 客户端到本地 Maven 仓库之中。下载源码发布版本,将其进行进行 unzip/untar 并运行如下的 Maven 命令:

  • mvn install:install-file -Dfile=client/depends/gnu-crypto.jar -DgroupId=org.gnu -DartifactId=gnu-crypto -Dversion=2.0.1 -Dpackaging=jar
  • mvn clean
  • mvn package

创建目录结构

在你选择的工程之中,创建如下所示的子目录结构:

->src
->main
->java
->com
->aerospike
->client
->rest

创建 Maven 的 pom 文件

在工程的根目录下创建一个 maven 的 pom.xml,其代码如下:

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.aerospike</groupId>
<artifactId>aerospike-restful-example</artifactId>
<version>1.0.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>0.5.0.M4</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Aerospike client. -->
<dependency>
<groupId>com.aerospike</groupId>
<artifactId>aerospike-client</artifactId>
<version>3.0.9</version>
</dependency>
<!-- Apache command line parser. -->
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<properties>
<start-class>com.aerospike.client.rest.AerospikeRESTfulService
</start-class>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>http://repo.spring.io/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>http://repo.spring.io/libs-snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

乍看上去有些恐怖,但实际上并非如此。

创建一个 JSON 转换类

Aerospike API 会返回一个 Record 对象,它会包含记录的 generation、expiry 以及 bin 值。但是你想让这些值以 JSON 格式返回。要达到这一点,最简单的方式就是使用一个转换类(translator class)。

所创建的转换类代码如下所示。这是一个工具类,能够将 Aerospike Record 转换为 JSONObject。

复制代码
<span color="#408080">src/main/java/com/aerospike/client/rest/JSONRecord.java</span>
package com.aerospike.client.rest;
import java.util.Map;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import com.aerospike.client.Record;
/**
* JSONRecord is used to convert an Aerospike Record
* returned from the cluster to JSON format
*
*/
@SuppressWarnings("serial")
public class JSONRecord extends JSONObject {
@SuppressWarnings("unchecked")
public JSONRecord(Record record){
put("generation", record.generation);
put("expiration", record.expiration);
put("bins", new JSONObject(record.bins));
if (record.duplicates != null){
JSONArray duplicates = new JSONArray();
for (Map<String, Object> duplicate : record.duplicates){
duplicates.add(new JSONObject(duplicate));
}
put("duplicates", duplicates);
}
}
}

这个类并不复杂也很通用。你可能会希望为特定的记录指定使用你的 JSON 转换器。

创建资源控制器

在 Spring 中,REST 端点(endpoint)是 Spring MVC 控制器。如下的代码能够处理对 /as/{namespace}/{set}/getAll/1234 的 GET 请求,并会返回 key 为 1234 的航班记录,在这里{namespace}是针对 Aerospike 命名空间的路径变量,{set}是针对 Aerospike 集合的路径变量。

复制代码
<span color="#408080">src/main/java/com/aerospike/client/rest/RESTController.java</span>
package com.aerospike.client.rest;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
import com.aerospike.client.policy.Policy;
import com.aerospike.client.policy.WritePolicy;
@Controller
public class RESTController {
@Autowired
AerospikeClient client;
@RequestMapping(value="/as/{namespace}/{set}/getAll/{key}",
method=RequestMethod.GET)
public @ResponseBody JSONRecord getAll(@PathVariable
("namespace") String namespace,
@PathVariable("set") String set,
@PathVariable("key") String keyvalue) throws Exception {
Policy policy = new Policy();
Key key = new Key(namespace, set, keyvalue);
Record result = client.get(policy, key);
return new JSONRecord(result);
}
}

针对人类用户的控制器和针对 REST 端点控制器之间的区别在于响应体中要包含数据,在这个场景中也就是一个 JSON 对象,它代表了从 Aerospike 读取到的记录。

@ResponseBody 注解会告知 Spring MVC 将返回的对象写入到响应体之中。

创建可执行的主类

现在要实现主方法来创建 Spring MVC 控制器,最简单的方式就是使用 SpringApplication 帮助类。

复制代码
<span color="#408080">src/main/java/com/aerospike/client/rest/AerospikeRESTfulService.java</span>
package com.aerospike.client.rest;
import java.util.Properties;
import javax.servlet.MultipartConfigElement;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.AerospikeException;
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class AerospikeRESTfulService {
@Bean
public AerospikeClient asClient() throws AerospikeException {
Properties as = System.getProperties();
return new AerospikeClient(as.getProperty("seedHost"),
Integer.parseInt(as.getProperty("port")));
}
@Bean
public MultipartConfigElement multipartConfigElement() {
return new MultipartConfigElement("");
}
public static void main(String[] args) throws ParseException {
Options options = new Options();
options.addOption("h", "host", true,
"Server hostname (default: localhost)");
options.addOption("p", "port", true, "Server port (default: 3000)");
// parse the command line args
CommandLineParser parser = new PosixParser();
CommandLine cl = parser.parse(options, args, false);
// set properties
Properties as = System.getProperties();
String host = cl.getOptionValue("h", "localhost");
as.put("seedHost", host);
String portString = cl.getOptionValue("p", "3000");
as.put("port", portString);
// start app
SpringApplication.run(AerospikeRESTfulService.class, args);
}
}

这里添加了 @EnableAutoConfiguration 注解:它会对一些内容进行默认的加载(如嵌入式的 servlet 容器),这取决于类路径的内容以及其他的一些事情。

它还使用了 @ComponentScan 注解,这个注解会告诉 Spring 扫描 rest 包来查找控制器(以及其他有注解的组件类)。

最后,这个类还使用了 @Configuration 注解。它允许你将 AerospikeClient 实例配置为一个 Spring 的 bean。

这里还定义了一个 MultipartConfigElement bean。它能够让你使用这个服务处理 POST 操作。

主方法中大部分的主体内容都是读取命令行参数以及系统属性,以便指定 Aerospike 集群的 seed 主机和端口。

非常简单!

上传数据

你可能希望往这个服务中上传数据。要做到这一点的话,我们需要为 RESTController 类添加一个额外的方法来处理上传的文件。在这个例子中,这会是包含航行记录的 CSV 文件。

复制代码
<span color="#408080">src/main/java/com/aerospike/client/rest/RESTController.java</span>
@Controller
public class RESTController {
. . . (code omitted) . . .
/*
* CSV flights file upload
*/
@RequestMapping(value="/uploadFlights", method=RequestMethod.GET)
public @ResponseBody String provideUploadInfo() {
return "You can upload a file by posting to this same URL.";
}
@RequestMapping(value="/uploadFlights", method=RequestMethod.POST)
public @ResponseBody String handleFileUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file){
if (!file.isEmpty()) {
try {
WritePolicy wp = new WritePolicy();
String line = "";
BufferedReader br = new BufferedReader(new
InputStreamReader(file.getInputStream()));
while ((line = br.readLine()) != null) {
// use comma as separator
String[] flight = line.split(",");
/*
* write the record to Aerospike
* NOTE: Bin names must not exceed 14 characters
*/
client.put(wp,
new Key("test", "flights",flight[0].trim() ),
new Bin("YEAR", Integer.parseInt(flight[1].trim())),
new Bin("DAY_OF_MONTH", Integer.parseInt(flight[2].trim())),
new Bin("FL_DATE", flight[3].trim()),
new Bin("AIRLINE_ID", Integer.parseInt(flight[4].trim())),
new Bin("CARRIER", flight[5].trim()),
new Bin("FL_NUM", Integer.parseInt(flight[6].trim())),
new Bin("ORI_AIRPORT_ID", Integer.parseInt(flight[7].trim())),
new Bin("ORIGIN", flight[8].trim()),
new Bin("ORI_CITY_NAME", flight[9].trim()),
new Bin("ORI_STATE_ABR", flight[10].trim()),
new Bin("DEST", flight[11].trim()),
new Bin("DEST_CITY_NAME", flight[12].trim()),
new Bin("DEST_STATE_ABR", flight[13].trim()),
new Bin("DEP_TIME", Integer.parseInt(flight[14].trim())),
new Bin("ARR_TIME", Integer.parseInt(flight[15].trim())),
new Bin("ELAPSED_TIME", Integer.parseInt(flight[16].trim())),
new Bin("AIR_TIME", Integer.parseInt(flight[17].trim())),
new Bin("DISTANCE", Integer.parseInt(flight[18].trim()))
);
System.out.println("Flight [ID= " + flight[0]
+ " , year=" + flight[1]
+ " , DAY_OF_MONTH=" + flight[2]
+ " , FL_DATE=" + flight[3]
+ " , AIRLINE_ID=" + flight[4]
+ " , CARRIER=" + flight[5]
+ " , FL_NUM=" + flight[6]
+ " , ORIGIN_AIRPORT_ID=" + flight[7]
+ "]");
}
br.close();
return "You successfully uploaded " + name;
} catch (Exception e) {
return "You failed to upload " + name + " => " + e.getMessage();
}
} else {
return "You failed to upload " + name +
" because the file was empty.";
}
}
}

新方法 handleFileUpload() 响应 POST 请求并且会读取上传的流,每次读取一行。每一行解析后,会构建一个 Key 对象和多个 Bin 对象,据此来形成 Aerospike 记录。最后,调用 Aerospike 的 put() 方法,将记录存储到 Aerospike 集群之中。

另外一个新方法 provideUploadInfo() 响应 GET 请求,并返回一条信息来表明允许进行上传。

上传的客户端应用

上传可以通过任何你希望的方式来实现。不过,你可以使用下面这个单独的 Java 类将数据上传到服务上。

复制代码
<span color="#408080">src/test/java/com.aerospike.client.rest/FlightsUploader.java</span>
package com.aerospike.client.rest;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class FilghtsUploader {
private static final String TEST_FILE = "flights_from.csv";
@Before
public void setUp() throws Exception {
}
@Test
public void upload() {
RestTemplate template = new RestTemplate();
MultiValueMap<String, Object> parts = new LinkedMultiValueMap
<String, Object>();
parts.add("name", TEST_FILE);
parts.add("file", new FileSystemResource(TEST_FILE));
String response = template.postForObject
("<a href="http://localhost:8080/uploadFlights">http://localhost:8080/uploadFlights</a>",parts, String.class);
System.out.println(response);
}
}

航班数据

这是来自 2012 年的真实数据,包括了大约一百万条的记录,所以请注意它需要几分钟的时间才能完成上传。

构建并运行服务

Maven 的 pom.xml 会将服务打包为一个单独的 jar 文件。使用如下的命令:

mvn clean package

这样会生成独立的 web 服务应用,它会打包为一个可运行的 jar 文件,位于 target 子目录之中。这个 jar 文件中包含了一个 Tomcat 的实例,所以你可以直接运行这个 jar 文件,而没有必要将其安装到应用服务器之中。

java -jar aerospike-restful-example-1.0.0.jar

总结

恭喜你!你现在已经使用 Spring 开发了一个简单的 RESTful 服务,并且连接到了 Aerospike 集群之中。

完整的样例代码

样例代码

设计中的考量

目前,访问控制是通过应用来处理的,并不是通过数据库。因为认证过程会拖慢数据库的速度,实际上,所有的NoSQL 数据库均不支持这种功能。我们的大多数客户更关注于提升的速度,而不是集成的认证特性。

另外一个要求的通用特性就是两个不同数据集之间的连接(join)。对于所有的分布式数据库来讲,这都是一个挑战,因为要连接的数据是分布式的。在本例中,开发人员必须在应用中实现连接。

关于作者

Peter Milne是一位很有经验的 IT 专业人士,对于软件开发和产品的整个生命周期都有着丰富的经验。对于小型和大型的开发团队,他都具有技术技能和管理经验。Peter 最近以来在 Aerospike 担任高级解决方案架构师。在此之前,他在 MLC 担任高级分析师和编码人员,并且在 iTerative Consulting 担任过 CTO,在此期间,他构建了一个 Forte/UDS 到 Java 的转换工具,达到了 99.999% 准确率。Peter 在悉尼科技大学获得了分布式计算的理科硕士学位,并且具有多个直升机安全许可和证书。

原文英文链接: Building a RESTful Web Service with Spring Boot to Access Data in an Aerospike Cluster

2014-02-20 23:3013354

评论

发布
暂无评论
发现更多内容

阿里云发布云原生加速器,携手生态企业拥抱数字时代

阿里巴巴云原生

云计算 阿里云 云原生 生态企业 创投

到底什么才是真正的区块链游戏

CECBC

猿辅导 x StarRocks:构建统一OLAP平台,全面升级数据分析能力

StarRocks

数据库 数据分析 StarRocks

我为什么坚持在公司开分享会?

boshi

团队管理 知识管理

Can't Connect to MySQL Server on IP Address (10061) 错误的解决方案

蒋川

MySQL 数据库 MySQL 运维 MySQL 数据库

技术分享 | 让实时协同的框架绘图变得丝滑起来

ShowMeBug

架构 绘图 实时消息

起飞!Alibaba全新出品JDK源码学习指南(终极版)限时开源

热爱java的分享家

Java 架构 面试 程序人生 编程语言

中国邮政邮科院 X StarRocks:统一OLAP平台,大幅降低运维成本

StarRocks

数据库 大数据 数据分析 StarRocks

跨越速运 x StarRocks:统一查询引擎,强悍性能带来极速体验

StarRocks

数据库 大数据 数据分析 StarRocks

MatrixDB 4.3 新特性:从“持续聚集”说起

YMatrix 超融合数据库

时序数据库 分布式时序数据库 MatrixDB

自定义View:如何手动实现ViewGroup的拖拽

Changing Lin

11月日更

区块链技术如何在涉诉信访中显身手

CECBC

NLP进阶,使用TextRNN和TextRNN_ATT实现文本分类

AI浩

运维体系建设思考-稳定性篇

ning

运维 云原生 监控 稳定性

Lenovo x StarRocks:简化数据处理链路,极大提升BI分析效率

StarRocks

数据库 大数据 数据分析 StarRocks

蚁剑的分析和利用

网络安全学海

网络安全 信息安全 渗透测试 WEB安全 安全漏洞

【Three.js】随着元宇宙开启WEB3D之路

devpoint

JavaScript WebGL 3D three.js 11月日更

Python代码阅读(第60篇):列表是否包括指定的元素

Felix

Python 编程 列表 阅读代码 Python初学者

前端遇到的那些技术难点

CRMEB

Go 语言学习查缺补漏ing Day1

Regan Yue

Go 语言 11月日更

惊为天人!Alibaba新产SpringBoot深度历险(嵩山版)开源

热爱java的分享家

Java 架构 面试 程序人生 编程语言

linux shell 命令行移动光标快捷键之跳到行首尾

WindFlying

阿里云发布云原生加速器,携手生态企业拥抱数字时代

阿里巴巴中间件

云计算 阿里云 云原生 中间件 数字化

【得物技术】基于自注意机制的图像识别算法

得物技术

算法 图像识别 识别 图像 算法实践

如何巧妙使用Camtasia制作PPT讲解视频?

淋雨

Camtasia

金链银链不如区块链 如何利用区块链技术探索“太空世界”?

CECBC

太顶了!华为高工用一份423页的网络协议笔记把计算机网络讲清了

热爱java的分享家

Java 架构 面试 编程语言 网络协议

🎅Less快速学习(下)🎅

空城机

CSS 大前端 less 11月日更

StarRocks在格洛博电商系统的应用实践

StarRocks

数据库 大数据 数据分析 StarRocks

clickhouse sql之Array函数

WindFlying

文本检测实战:使用OpenCV实现文本检测(EAST 文本检测器)

AI浩

使用Spring Boot构建RESTful Web服务以访问存储于Aerospike集群中的数据_语言 & 开发_Peter Milne_InfoQ精选文章