初识Aerospike

258 阅读16分钟

官方文档: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

image.png (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操作

image.png

远程连接: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

![]( "商业化平台技术部 > Aerospike快速入门版 > image2024-11-13_15-52-54.png")

image.png image.png

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可变形参 image.png

(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的值

image.png

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等基本数据类型

image.png

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

image.png

// 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