官方文档:Aerospike Technical Documentation | Aerospike Documentation 一.了解
Aerospike是一个分布式,高可用的K-V类型的Nosql数据库。AS可基于行随机存取,索引放在内存中,数据存取可以在内存中或SSD。
Redis就是使用K-V类型,但是redis数据完全存储在内存,成本太高。AS可以存储在SSD上,并且与redis能达到相同的查询性能。AS通过访问SSD屏蔽文件系统层级,直接访问地址,保证了数据的读取速度。
T级别大数据高并发的结构化数据存储
读写操作达微妙级,99%的响应可在1毫秒内实现
采用混合架构,索引存储在内存中,而数据可存储在机械硬盘(HDD)或固态硬盘(SSD) 上(也可存储在
内存)
AS内部在访问SSD屏蔽了文件系统层级,直接访问地址,保证了数据的读取速度。
AS同时支持二级索引与Client聚合,支持简单的sql操作(aql),相比于其他nosql数据库,有一定优势。
适合对容量要求比较大,QPS相对低一些的场景,目前主要集中于互联网广告行业使用(国外)
1.架构
分为三层,分别是Client、Distribution、Data三层
(1) Client层
对Aerospike Server中的数据进行访问。
包括CRUD、批量操作和基于二级索引的查询
追踪节点感知数据存储位置,当节点启动或停止时立即感知集群配置变化。
具有高效性、稳定性和内部的连接池
(2) Distribution层
负责管理集群内部数据的平衡分布、备份、容错和不同集群之间的数据同步。主要包含三个模块:
<1> Cluster Management Module
用于追踪集群节点。关键算法是确定哪些节点是集群的一部分的Paxos-like一致投票过程。
Aerospike实现专门的心跳检测(主动与被动),用于监控节点间的连通性。
<2> Data Migration Module
当有节点添加或移除时,该模块保证数据的重新分布,按照系统配置的复制因子确保每个数据块跨节点和跨数据中心复制。
<3> Transaction Processing Module
确保读写的一致性与隔离性,写操作先写副本在写主库。该模块包括:
Sync/Async Replication(同步/异步复制):为保证写一致性,在提交数据之前向所有副本传播更新并将结果返回客户端。
Proxy (代理):集群重配置期间客户端可能出现短暂过期,透明代理请求到其他节点。
Duplicate Resolution(副本解析):当集群从活动分区恢复时,解决不同数据副本之间的冲突。
(3) Data层
负责数据的存储,Aerospike 属于弱语法的key-value数据库。
2.基本概念
Aerospike有几个核心概念分别是namespace、sets、record、bin
(1)namespace 类似于database
namespace命名空间,数据存在命名空间中,相当于RDBMS中的database,最多可设置32个。
一个namespace可关联多块SSD,一块SSD只关联一个namespace,每个namespace下包含4096个分片
(2)sets 类似于table
集合,类似于数据库表,一个namespace最多1023个set
(3)record 类似于row 包括key(主键也叫PK)、Bins(value)、Metadata(元数据)。key全局唯一,作为K-V数据库一般都是通过key去进行查询,Bins相当于列,存储具体的数据。元数据存储一些基本信息,比如TTL。
注意-1.Metadata
每一条记录都包含以下几条元数据
1.generation(代):表示记录被修改的次数。
2.time-to-live(TTL):AS会自动根据记录的TTL使其过期。
3.last-update-time(LUT):上次更新时间,这是一个数据库内部的元数据,不会返回给客户端。
(4)bin 就是每一个字段(每一列)
注意-2.Bins
在一条记录里,数据被存储在一个或多个bins里,bins由名称和值组成。bins不需要指定数据类型,数据类型有bins中的值决定。
类似于数据库字段,支持Java基本数据类型以及Decimal、List、Map、Blob, 一个namespace下最多32767个bin
索引:Aerospike Index包含一级索引(Primary Index)和二级索引(Second Index),索引存储在内存
中,并不保存在硬盘里
一级索引(Primary-Index)一级索引位于主键(key),采用hash表与红黑树的混合
二级索引(Secondary-Index)二级索引位于非主键上,这允许一对多关系。采用哈希表和B-tree的混合
磁盘:Aerospike可以直接访问硬盘中的raw blocks(原始块)直接寻址,并特别优化了Aerospike的最
小化读、大块写和并行SSD来增加响应速度和吞吐量。
| 数据类型 | 描述 |
|---|---|
| 整数 (Integer) | 64 位数值,可以是有符号或无符号,用于正负整数(例如,42,-12)。 |
| 布尔值 (Boolean) | 表示 true 或 false 的值。 |
| 双精度 (Double) | 用于存储小数或包含小数点的数值,适用于 GPS 坐标、价格、温度等(例如,37.4212,-122.0988)。 |
| 字符串 (String) | 以 UTF-8 编码的字符,存储在不透明的字节数组中。可以表示任意字符组合(例如,“Aerospike is fast”、“Tim”或“Pineapple42 themed backpack”)。 |
| 映射 (Map) | 类似于 HashMap 或字典的数据结构,存储键值对关系。映射中的键是唯一的,不允许重复,因此所有与该键关联的值都集中在一起。映射的键必须是标量数据类型(仅限字符串、整数或 blob),值可以是标量或集合类型,可以嵌套。例如: myMapBin: { "Aerospike": "Fast", "Product1234": "OReilly canned beans", 23: True, 255: [1,2,2,3] } |
| 列表 (List) | 类似数组的数据结构,与映射相似,它是一个可以操作的集合。不同的是列表没有键值关系,且可以有重复的数据。列表中的条目可以是标量数据类型或集合数据类型,也可以嵌套。例如:pageViews: [1691950585, 1691953456, 1691958589]。列表索引从 0 开始,写入时可配置排序与否。列表元素无需类型相同,因此类似 [1691950585, "Tim", 0.42, true] 的列表是有效的。 |
| Blob | 二进制数据,原始字节。这种数据类型适合存储语言特定结构的原始字节,例如压缩的序列化字典、布隆过滤器或内部数据类型。 |
| GeoJSON | 几何对象,用于地理空间匹配(例如,找到在另一点 42 英里范围内的点)。 |
| HyperLogLog | 概率数据类型,用于统计集合或集合并集中的成员数量(例如,对“Computers”和“Furniture”感兴趣的用户数量)。 |
补充:索引,有Primary Index(默认存在)、Secondary-Index
目前,每个索引项需要64个字节。除了20字节摘要外,以下元数据也存储在索引中。
1、写入生成:跟踪密钥的所有更新;用于解决冲突更新。
2、过期时间:跟踪密钥过期的时间。逐出子系统使用此元数据。
3、上次更新时间:跟踪对密钥的最后一次写入(Citrusleaf epoch)。用于冷重启期间的冲突解决、迁移期间的冲突解决(基于配置)、谓词筛选、增量备份扫描、截断和截断命名空间命令。
4、存储地址:数据的存储位置(包括内存和持久性)。
3.AQL 语法和SQL类似
(1) DDL操作
远程连接:aql -p 3000 -h xx.xxx.xx.xx
显示所有命名空间:show namespaces
显示所有字段(相当于mysql table中的column):show bins
显示所有集合(相当于mysql中的show tables):show sets
显示查询信息:select * from bar.messagecache where PK=xx
查看AS库是否正常:systemctl status aerospike
创建索引
前提:没有建立索引时是不能根据bin查询的。
CREATE INDEX <index> ON <ns>[.<set>] (<bin>) <type>
<index>索引名称,在一个命名空间上索引名称必须唯一;
<ns>命名空间;
<set>设置是可选的,如果没有设置,则索引的记录不属于一个set;
<bin>根据bin建立索引
<type>索引存储的类型,必须是: NUMERIC or STRING
比如
create index idx_demo_name on testMemory.demo(name) string
select * from testMemory.demo where name='abc'
删除索引
drop index <ns> <index>
比如
drop index test number
查看AS库,可以执行命令查看对应的字段,在查询对应的内容
set output raw
select * from database.TML_REALTIME
(2) DML操作
INSERT INTO <ns>[.<set>] (PK, <bins>) VALUES (<key>, <values>) --PK是主键必须要写PK
DELETE FROM <ns>[.<set>] WHERE PK = <key>
CAST(<Value> AS <TypeName>)
案例如下
INSERT INTO test.demo (PK, foo, bar, baz) VALUES ('key1', 123, 'abc', true)
INSERT INTO test.demo (PK, foo, bar, baz) VALUES ('key1', CAST('123' AS INT), JSON('{"a": 1.2, "b": [1, 2, 3]}'), BOOL(1))
INSERT INTO test.demo (PK, foo, bar) VALUES ('key1', LIST('[1, 2, 3]'), MAP('{"a": 1, "b": 2}'), CAST(0 as BOOL))
INSERT INTO test.demo (PK, gj) VALUES ('key1', GEOJSON('{"type": "Point", "coordinates": [123.4, -56.7]}'))
DELETE FROM test.demo WHERE PK = 'key1'
(3) Query操作
1.普通查询SELECT
SELECT <bins> FROM <ns>[.<set>]
SELECT <bins> FROM <ns>[.<set>] [limit <max-records>]
SELECT <bins> FROM <ns>[.<set>] WHERE <bin> = <value> [and <bin2> = <value>] [limit <max-records>]
SELECT <bins> FROM <ns>[.<set>] WHERE <bin> BETWEEN <lower> AND <upper> [limit <max-records>]
SELECT <bins> FROM <ns>[.<set>] WHERE PK = <key> --PK就是其实就是索引
SELECT <bins> FROM <ns>[.<set>] IN <index-type> WHERE <bin> = <value>
SELECT <bins> FROM <ns>[.<set>] IN <index-type> WHERE <bin> BETWEEN <lower> AND <upper>
SELECT <bins> FROM <ns>[.<set>] IN <index-type> WHERE <bin> CONTAINS <GeoJSONPoint>
SELECT <bins> FROM <ns>[.<set>] IN <index-type> WHERE <bin> WITHIN <GeoJSONPolygon>
案例如下
SELECT * FROM test.demo
SELECT * FROM test.demo WHERE PK = 'key1'
SELECT foo, bar FROM test.demo WHERE PK = 'key1'
SELECT foo, bar FROM test.demo WHERE foo = 123 limit 10
SELECT foo, bar FROM test.demo WHERE foo = 123 and bar = "abc" limit 10
SELECT foo, bar FROM test.demo WHERE foo BETWEEN 0 AND 999 limit 20
SELECT * FROM test.demo WHERE gj CONTAINS CAST('{"type": "Point", "coordinates": [0.0, 0.0]}' AS GEOJSON)
2.聚合查询AGGREGATION 这里的module是udf模块,function是udf函数
AGGREGATE <module>.<function>(<args>) ON <ns>[.<set>]
AGGREGATE <module>.<function>(<args>) ON <ns>[.<set>] WHERE <bin> = <value>
AGGREGATE <module>.<function>(<args>) ON <ns>[.<set>] WHERE <bin> BETWEEN <lower> AND <upper>
案例如下
AGGREGATE myudfs.udf2(2) ON test.demo WHERE foo = 123
AGGREGATE myudfs.udf2(2) ON test.demo WHERE foo BETWEEN 0 AND 999
4.利用JavaAPI操作AS数据库
(1) 导入依赖
<dependencies>
<dependency>
<groupId>com.aerospike</groupId>
<artifactId>aerospike-client</artifactId>
<version>3.3.4</version>
</dependency>
</dependencies>
(2) 具体操作
<1> 获取as的客户端对象
IAerospikeClient是一个接口,具体的实现类是AerospikeClient

IAerospikeClient client = new AerospikeClient(address, port);//需要传ip和端口号
//AerospikeClient是操作的客户端具体实现类,有三种构造方法
1.AerospikeClient client = new AerospikeClient(address, port);//传host和port
2.AerospikeClient client = new AerospikeClient(clientPolicy, host,port);//传客户端策略、host、port
3.AerospikeClient client = new AerospikeClient(cilentPolicy,host...);//传客户端策略和host可变形参
-------------------------------------------------------------------------------------------------------------------------------ClientPolicy补充
ClientPolicy cp= new ClientPolicy();
cp.user = "admin";
cp.password = "adminPassword";
cp.timeout=1000;//默认1000ms,设置首次和server节点的连接以及轮询每一个节点的集群状态时的超时时间
cp.loginTimeout=5000;//默认是5000ms,设置登录超时时间
cp.maxConnsPerNode=100;//默认是100,设置每个服务器节点允许的最大同步连接数
cp.writePolicyDefault.expiration = 60 * 60 * 48;//默认是0s,设置AS中的数据TTL为48小时 集群中默认100D
-------------------------------------------------------------------------------------------------------------------------------
as支持多host连接,客户端会发现集群中的所有节点信息
比如
Host[] hosts = new Host[] {
new Host("172.17.0.2", 3000),
new Host("172.17.0.3", 3000),
new Host("172.17.0.4", 3000)
};
//下面是配置企业服务器用户身份验证
ClientPolicy policy = new ClientPolicy();
policy.user = "myuser";
policy.password = "mypass";
AerospikeClient client = new AerospikeClient(policy, hosts);
<2> put操作
put操作as数据库需要先获取as的客户端对象,然后put写入需要三个参数分别是写策略WritePolicy、Key、Bins可变形参
(1)WritePolicy写策略
WritePolicy wp= new WritePolicy();//参数可以不传,也可以传其他的策略Policy对象
wp.totalTimeout = 5000;//7.0.0版本设置事务的超时时间,默认是1000ms 在3.3.4版本是timeout
wp.maxRetries=4;//设置最大重试次数,默认是2次,
//注意:非幂等的操作不可重试如add(),因此对于非幂等的操作要设置重试次数为0,并且底层write操作默认重试次数是0,read是2(初始1次+重试2次=3次),scan/query是5(初始1次+重试5次=6次)
wp.generationPolicy=GenerationPolicy.NONE;//确定如何根据记录生成来确定数据的写入(类似于乐观锁)
有三种策略
GenerationPolicy.NONE:不用使用记录生成来限制写入(默认)
EXPECT_GEN_EQUAL:如果预期生成量等于服务器生成量,则更新/删除记录。否则,失败。
EXPECT_GEN_GT:如果预期生成量大于服务器生成量,则更新/删除记录。否则,失败。这对备份后的恢复很有用。
wp.recordExistsAction = RecordExistsAction.UPDATE;//设置如何处理已存在的数据写入的模式
有以下几种策略
CREATE_ONLY:如果记录不存在,则创建,否则失败。
UPDATE:如果记录不存在,则创建,否则更新。(默认)
UPDATE_ONLY:仅在记录存在的情况下更新,否则失败。
REPLACE:如果记录不存在,则创建,否则替换。
REPLACE_ONLY:仅在记录存在时替换,否则失败。
wp.durableDelete = true;//是否永久删除记录,默认是false
如果事务导致记录删除,则为记录留下墓碑。
* 这可防止已删除的记录在节点故障后重新出现。
* 仅对 Aerospike Server 企业版 3.10+ 有效。
wp.commitLevel = CommitLevel.COMMIT_ALL;//设置事务提交级别
COMMIT_ALL:服务器需要等待master和其他副本全部提交才能响应(默认)
COMMIT_MASTER:服务器只需要等待master节点提交即可响应
wp.expiration = 60 * 60 * 48;//默认是0s,设置AS中的数据TTL为48小时 集群中默认100D
如果希望设置正数的 WritePolicy 到期值,需要在 Aerospike 服务器中将 nsup-period 设置为非零值。如果不确定起点,建议将 nsup-period 设置为 120 秒。
WritePolicy 到期值 效果
−2:写入时不更改 TTL。
−1:永不过期。
0:使用服务器配置的命名空间 default-ttl。如果服务器的default-ttl 设置为0表示“永不过期”
大于 0 的值 TTL 秒数:记录在 Aerospike 中的生存时间。
(2)Key对象
一个由命名空间、集名称和用户定义的键组成的元组,需要的参数是namespace和setName以及主键K的值
Key key = new Key(namespace, set, "bar");//单个声明
//批量声明如下
Key[] keys = new Key[];
for (int i = 0; i < keys.length; i++) {
keys[i]=new Key(namespace,set,"key"+i);
}
(3)Bin对象—KV键值对
需要的参数是字段名和对应的字段值
Bin中的V的类型支持List、Map、String、int等基本数据类型
Bin bin = new Bin("myBin", "Hello World!");
入门案例如下
try {
client.put(writePolicy, key, bin);
System.out.println("Successfully wrote record");
}
catch (AerospikeException e) {
e.printStackTrace();
}
案例:把json格式的数据插入到as中
public void createUser(String input, IAerospikeClient client) throws AerospikeException {
// Parse the input
JSONObject user = new JSONObject(input);
String userName = user.getString("name");//获取name的值
String userEmail = user.getString("email");//获取email的值
// This value is randomly generated upon sign up
String userBgColor = user.getString("bgColor");//获取bgColor的值
boolean userConsent = user.optBoolean("consent");//获取consent的值
// Create a write policy to only create new users
WritePolicy writePolicy = new WritePolicy();
writePolicy.recordExistsAction = RecordExistsAction.CREATE_ONLY;//选择写策略是如果记录不存在则创建
// Create a key using the email
Key key = new Key(namespace, set, userEmail);
// Create the user bins
Bin name = new Bin("name", userName);
Bin email = new Bin("email", userEmail);
Bin bgColor = new Bin("bgColor", userBgColor);
Bin consent = new Bin("consent", userConsent);
try {
// Write the new user record to the database
client.put(writePolicy, key, name, email, bgColor, consent);
}
catch (AerospikeException e) {
if (e.getResultCode() == ResultCode.KEY_EXISTS_ERROR) {
throw new IllegalArgumentException("Email " + userEmail + " already exists");
}
else {
throw e;
}
}
}
<2> get操作
读取需要policy策略和key对象(还有bin字段名)
Policy策略
Policy readPolicy = new Policy();
readPolicy.totalTimeout = 5000;//设置事务的超时时间,默认是1000ms
入门案例如下
try {
Record record = client.get(readPolicy, key);
// 从记录中获取映射 bin,存储为映射
Map retrievedMap = record.getMap("product");
System.out.format("Record: %s", record.bins);//展示当前key对应record的每一个bin的信息
}
catch (AerospikeException e) {
e.printStackTrace();
}
finally {
client.close();
}
批量读取Record案例
// Establish connection to the server
AerospikeClient client = new AerospikeClient("127.0.0.1", 3000);
// Create the keys
Key[] keys = new Key[10];
for (int i = 0; i < 10; i++) {
keys[i] = new Key("sandbox", "ufodata", (i + 1));
}
// Read the records
try {
Record[] records = client.get(null, keys);
for (int i = 0; i < records.length; i++) {
if (records[i] != null) {
System.out.format("Key: %s\nRecord: %s\n", keys[i].userKey, gson.toJson(records[i].bins));
}
else {
System.out.format("Key not found\nKey: %s", keys[i].userKey);
}
}
}
catch (AerospikeException ae) {
System.out.format("Error: %s", ae.getMessage());
}
finally {
// Close the client
client.close();
}
<3> 更新操作
该操作可处理创建(插入)和更新。写策略中指定的操作语义定义了记录已存在时的操作语义,利用put操作结合recordExistsAction模式
CREATE_ONLY: 如果记录不存在,则创建。
UPDATE:如果记录不存在,则创建,否则更新。(默认)
UPDATE_ONLY:仅在记录存在的情况下更新: 仅在记录存在时更新。
REPLACE: 如果记录不存在,则创建,否则替换。
REPLACE_ONLY: 仅在记录存在时替换。
使用创建记录时的写入策略,仅在记录存在时更新该记录的 bins 值。尝试更改策略以获得不同的结果
// 创建客户端连接对象
AerospikeClient client = new AerospikeClient("127.0.0.1", 3000);
// 从创建记录开始,在写入策略上设置更新策略
writePolicy.recordExistsAction = RecordExistsAction.UPDATE_ONLY;
// 更新Record
try {
posted = new Bin("posted", 20220602);
client.put(writePolicy, key, posted);
Record record = client.get(null, key, "posted");
System.out.format("Update succeeded\nBin: %s\n", gson.toJson(record.bins));
}
catch (AerospikeException ae) {
System.out.format("Update Failed\nError: %s", ae.getMessage());
}
finally {
// Close the client
client.close();
}
<4> 删除操作
as具备高效删除操作,只删除目标数据的primary index
// Establish connection to the server
AerospikeClient client = new AerospikeClient("127.0.0.1", 3000);
// 删除Record,也可以配置writePolicy.durableDelete = true;参数永久删除
try {
client.delete(writePolicy, key);
System.out.format("Record deleted\n");
// Check if record exists
boolean exists = client.exists(null, key);
System.out.format("Exists: %b\n", exists);
}
catch (AerospikeException ae) {
System.out.format("Delete Failed\nError: %s", ae.getMessage());
}
finally {
// Close the client
client.close();
}
补充–删除bin
用Value.getAsNull()的值去覆盖要写入的bin,即可删除bin。要操作的记录不存在会抛出异常。
当该记录所有bin都被删除后,该记录也会被删除。但和delete与truncate一样,重启后会出现已经被删除的记录,出现的记录为删除操作前的状态。
Key key = new Key("ns1","test","123");
Bin bin1 = new Bin("DATA1","123");
Bin bin2 = new Bin("DATA2","321");
client.put(null,key,bin1,bin2);
Record record1 = client.get(null,key);
System.out.println(record1.toString());
//(gen:1),(exp:355634387),(bins:(DATA2:321),(DATA1:123))
//删除名为"DATA2"的bin
Bin bin3 = new Bin("DATA2", Value.getAsNull());
client.put(null,key,bin3);//利用null值把他删除掉,因为Aerospike会把null的哪一个bin直接删掉
Record record2 = client.get(null,key);
System.out.println(record2.toString());
//(gen:2),(exp:355634387),(bins:(DATA1:123))
<5> 轻量级操作
touch操作是一种轻量级方式,用于更新记录的元数据,如 TTL 或 generation。主要用于读取时刷新记录,以确保记录保持有效。touch 操作仅向服务器发送元数据和 WritePolicy,对客户端来说是非常轻量的操作。
touch自身不需要重写整个记录,只需要刷新记录的元数据
在以下示例中,TTL 被延长了一天(86,400 秒):
WritePolicy writePolicy = new WritePolicy();
writePolicy.expiration = 86400;//设置ttl
client.touch(writePolicy, myKey);
exists 函数类似,它仅在网络上传输元数据到客户端。用于在客户端应用中获取布尔值 True 或 False,以确定记录是否存在:
client.exists(null, myKey);
注意
需要注意的是,
exists返回的是调用执行时服务器中是否包含该记录。在客户端接收到响应时,由于可能存在多个客户端实例,值可能已不准确。例如,一个线程执行exists调用以检查记录是否存在,只有在记录不存在时才创建该记录。API 调用执行时服务器上该记录不存在,因此返回 False。然而,几毫秒后,另一个客户端创建了该记录。原始客户端认为可以安全创建记录,实际上却不能。
因此,exists 很少使用;通常情况下,RecordExistsAction 可以执行相同功能,但带有原子保证。在这种情况下,正确的处理方式是使用 RecordExistsAction.CREATE_ONLY。