写点什么

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

评论

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

Serverless Devs 社区联合信通院邀请您参加 2022 中国 Serverless 用户调查

阿里巴巴云原生

阿里云 Serverless 云原生

MatrixOne从入门到实践05——数据类型介绍

MatrixOrigin

数据库 分布式 MatrixOrigin MatrixOne

ONE 2.0应用场景解读 | 如何通过时序拓扑直观还原故障传导链路?

博睿数据

可观测性 应用场景 智能运维 博睿数据 ONE平台

三分建设,七分运营|用现代化安全运营应对数据安全风险

爱科技的水月

阿里云云原生加速器成员企业袋鼠云创始人陈吉平:深耕国产自研数字化技术与服务,持续为客户创造价值

阿里巴巴云原生

阿里云 云原生

Zebec流支付生态,开启多链布局的“两手准备”

鳄鱼视界

《数据》杂志 | 浅析《网络安全法》修改对数据合规与隐私计算的影响

洞见科技

MatrixOne从入门到实战04——MatrixOne的连接和建表

MatrixOrigin

数据库 分布式 MatrixOrigin MatrixOne

5步法助力自动化转型

FunTester

低代码开发是未来软件开发的主流模式

元年技术洞察

低代码 方舟PaaS

帮助中心:培养客户自助服务意识的实用工具

Baklib

游戏品类加速回暖,文娱内容持续火热——2022年IAA行业品类发展洞察系列报告·第三期

易观分析

游戏 报告 文娱

Amazon Braket 与量子计算

亚马逊云科技 (Amazon Web Services)

量子计算 Hero 专栏 Amazon Braket

关于平台工程的开发者工具链,你还想加点啥?

阿里巴巴云原生

阿里云 微服务 云原生 EDAS

开源让这位00后逆袭成为各类大奖收割者

OpenI启智社区

开源 OpenI启智社区 免费算力

如何用JavaScripte和HTML 实现一整套的考试答题卡和成绩表

葡萄城技术团队

Stack Memory vs Heap Memory in Java

Mahipal_Nehra

Java heap memory Stack memory Java development

Hire Remote Developers

Mahipal_Nehra

Java angular blockchain React app development

阿里 CTO 程立:Severless 化正加速重塑阿里应用架构和研发模式

阿里巴巴云原生

阿里云 Serverless 云原生

折叠屏“世界杯”开哨,荣耀Magic Vs踢出关键一球

脑极体

DevData Talks | 知乎艾辉:从工具建设到运营,千人团队研发提效最佳实践

思码逸研发效能

研发管理 研发效能

容器服务 ACK 结合 MSE Ingress,让集群入口流量管理更丰富、更容易

阿里巴巴云原生

阿里云 云原生 容器服务

第三章 TCP/IP ip地址概念与应用

我叫于豆豆吖.

11月月更

从手动测试到自动测试,企业该如何选择?

飞算JavaAI开发助手

年终最重磅!云原生实时数仓 SelectDB 首次产品发布等你来约!

SelectDB

数据库 云计算 大数据 实时计算

10分钟为你全面解答HDFS的SecondaryNamenode的作用

好程序员IT教育

大数据 hdfs

分布式存储之 etcd 的集群管理

焱融科技

云计算 分布式系统 etcd 高性能 分布式存储

Chrome 103支持使用本地字体,纯前端导出PDF优化

葡萄城技术团队

chrome 前端 HTTP PDF

如何通过Java 合并和取消合并 Excel 单元格

在下毛毛雨

Java Excel 合并单元格

数据监控预警系统,实现不同端信息推送

葡萄城技术团队

前端 数据可视化

专利解析|多维建模结合AI识别商品特征的方法

元年技术洞察

AI 数字化转型

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