大厂Data+Agent 秘籍:腾讯/阿里/字节解析如何提升数据分析智能。 了解详情
写点什么

使用 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:3013510

评论

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

Java AOT之GraalVM native image介绍以及简单长连接服务实践

BUG侦探

GraalVM java aot native image

TDesign React Starter 发布

TDesign

浅谈信息熵在数字体验监控领域的应用

博睿数据

什么是数据恢复?数据丢失的最常见原因有哪些?

Ethereal

提升客户服务体验的技巧

小炮

客户服务 SaaS平台

企业如何挖掘知识“金矿”?这本白皮书讲得够透彻!

百度大脑

Kubernetes中API的不同版本, Alpha, Beta, Stable 都是什么?

工程师薛昭君

Kubernetes API

从建好到用好,阿里云原生微服务生态的演进

阿里巴巴云原生

国家产业政策不断加码,氢能步入加速发展期

易观分析

氢能源 氢能源产业

Nginx限速模块初探

喀拉峻

nginx

无监控不运维—浅述各种监控方案使用场景

穿过生命散发芬芳

3月月更

从0到1落地电商小程序之微服务设计

晨亮

「架构实战营」

ABAP 获取本地路径

Jasen Ye

abap 文件路径

【愚公系列】2022年03月 Docker容器 Kafka集群的搭建

愚公搬代码

3月月更

Windows、Linux、Apple三大操作系统的主流文件系统包含哪些?

Ethereal

Apache ShardingSphere 5.1.0 执行引擎性能优化揭秘

SphereEx

数据库 ShardingSphere SphereEx apache 社区

自动化知识图谱表示:从三元组到子图

第四范式开发者社区

人工智能 自动化 知识图谱

详细的网站定制步骤有哪些?

源字节1号

网站开发 软件定制

弱监督语义分割:从图像级标注快进到像素级预测

网易云信

安全

客户画像赋能百度推广生态实践

百度Geek说

前端 后端

延期通知 RocketMQ Summit 议题全揭秘

阿里巴巴云原生

产品升级|1-2月合刊:多款重磅产品来袭

百度大脑

地狱开局的2022,穿好你的安全铠甲

脑极体

Go 中的空白标识符(下划线)

宇宙之一粟

Go 语言 3月月更

Python 的排序方法 sort 和 sorted 的区别

AlwaysBeta

Python

手把手教你从Apk中取出算法

奋飞安全

android 安全 java

治理有精度,AI赋智加强城市精细化管理

百度大脑

恒源云(GpuShare)_租卡怎么选?看这一篇就够了!

恒源云

人工智能 GPU服务器

AI+遥感智能解译,赋能智慧城市规划革新

百度大脑

网络安全入门5天速成教程: WEB安全渗透攻防技术

网络安全学海

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

错误码设计思考

木小风

Java 架构 错误码

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