简介:全世界内存最快的数据库,Dragonfly是一种针对现代应用程序负荷需求而构建的内存数据库,完全兼容Redis和Memcached的 API,迁移时无需修改任何代码。相比于这些传统的内存数据库,Dragonfly提供了其25倍的吞吐量,高缓存命中率和低尾延迟,并且对于相同大小的工作负载运行资源最多可减少80%。
目录
0.开发背景
Dragonfly始于一项实验,旨在探索如果在2022年重新设计内存数据库,它会是什么样子。基于我们作为内存存储的用户以及作为云服务公司的工程师的经验教训,我们得知需要保留Dragonfly的两个关键属性:a) 为其所有操作提供原子性保证,b) 保证在非常高的吞吐量下实现低于毫秒的延迟。
我们面临的首要挑战是如何充分利用当今云服务器的CPU、内存和I/O资源。为了解决这个问题,我们使用了 无共享式架构(shared-nothing architecture),它允许我们在不同的线程之间分割内存存储的空间,使得每个线程可以管理自己的字典数据切片。我们称这些切片为“分片(shards)”。为无共享式架构提供线程和I/O管理功能的库在这里开源。
为了提供对多键并发操作的原子性保证,我们使用了最近学术研究的进展。我们选择了论文 "VLL: a lock manager redesign for main memory database systems” 来开发Dragonfly的事务框架。无共享式架构和VLL的选择使我们能够在不使用互斥锁或自旋锁的情况下组合原子的多键操作。这是我们 PoC 的一个重要里程碑,它的性能在商业和开源解决方案中脱颖而出。
我们面临的第二个挑战是为新存储设计更高效的数据结构。为了实现这个目标,我们基于论文"Dash: Scalable Hashing on Persistent Memory"构建了核心哈希表结构。这篇论文本身是以持久性内存为中心的,与主存没有直接相关性。
然而,它非常适用于我们的问题。它提出了一种哈希表设计,允许我们维护Redis字典中存在的两个特殊属性:a) 数据存储增长时的渐进式哈希能力;b)使用无状态扫描操作时,遍历变化的字典的能力。除了这两个属性之外,Dash在CPU和内存方面都更加高效。通过利用Dash的设计,我们能够进一步创新,实现以下功能:
针对TTL的高效记录过期功能。
一种新颖的缓存驱逐算法,具有比其他缓存策略(如LRU和LFU)更高的命中率,同时零内存开销。
一种新颖的无fork快照算法。
在我们为Dragonfly打下基础并满意其性能后,我们开始实现Redis和Memcached功能。 目前,我们已经实现了约185个Redis命令(大致相当于Redis 5.0 API)和13个Memcached命令。
1.快速开始
1.1 安装dragonflydb
docker安装
docker run --network=host --ulimit memlock=-1 docker.dragonflydb.io/dragonflydb/dragonfly
[root@iZf8z7m44hwv1t7vxgtbtiZ ~]# docker run -p 6379:6379 --ulimit memlock=-1 docker.dragonflydb.io/dragonflydb/dragonfly
I20240410 01:15:53.395677 1 init.cc:70] dragonfly running in opt mode.
I20240410 01:15:53.395985 1 dfly_main.cc:641] Starting dragonfly df-v1.16.1-48541c75eaac69044d020569ce076dc256795f7c
* Logs will be written to the first available of the following paths:
/tmp/dragonfly.*
./dragonfly.*
* For the available flags type dragonfly [--help | --helpfull]
* Documentation can be found at: https://www.dragonflydb.io/docs
I20240410 01:15:53.396301 1 dfly_main.cc:685] maxmemory has not been specified. Deciding myself....
I20240410 01:15:53.396371 1 dfly_main.cc:694] Found 1.29GiB available memory. Setting maxmemory to 1.03GiB
W20240410 01:15:53.396488 1 dfly_main.cc:343] Kernel is older than 5.10, switching to epoll engine.
I20240410 01:15:53.474810 1 proactor_pool.cc:146] Running 2 io threads
I20240410 01:15:53.606942 1 server_family.cc:713] Host OS: Linux 3.10.0-1160.76.1.el7.x86_64 x86_64 with 2 threads
I20240410 01:15:53.609256 1 snapshot_storage.cc:108] Load snapshot: Searching for snapshot in directory: "/data"
W20240410 01:15:53.609514 1 server_family.cc:806] Load snapshot: No snapshot found
I20240410 01:15:53.624845 9 listener_interface.cc:101] sock[7] AcceptServer - listening on port 6379
验证docker是否启动成功
[root@iZf8z7m44hwv1t7vxgtbtiZ ~]#
[root@iZf8z7m44hwv1t7vxgtbtiZ ~]# docker ps -a |grep 'dragonflydb'
9005b5a59d46 docker.dragonflydb.io/dragonflydb/dragonfly "entrypoint.sh drago…" 8 hours ago Up 8 hours (healthy) 0.0.0.0:6379->6379/tcp, :::6379->6379/tcp interesting_pascal
1.2 连接dragonflydb
1.2.1 使用连接工具quickRedis
1.2.2 命令测试
官网命令支持:Command Reference | Dragonfly
1.2.2.1 string类型
# string 类型
# 设置 key 为 hello,值为 db
> set hello db
OK
>
> get hello
db
> keys *
hello
>
# 设置 key 为 test_incr,值为 0
> set test_incr 0
OK
# 测试自增
> incr test_incr
1
> incr test_incr
2
> incr test_incr
3
>
1.2.2.2 hash 类型
# hash类型
# 设置哈希表 test_hash 中的字段 field1 的值为 hello1
> hset test_hash filed1 "hello1"
1
# 获取哈希表 test_hash 中的字段 field1 的值
> hget test_hash filed1
hello1
# 获取哈希表 test_hash 中的所有字段和值
> hgetall test_hash
filed1
hello1
> hset test_hash filed2 'hello2'
1
>
# 获取哈希表 test_hash 中的所有字段和值
> hgetall test_hash
filed1
hello1
filed2
hello2
1.2.2.3 list 类型
# list类型
# 在列表test_list的头部插入元素"one"
> lpush test_list 'one'
1
# 在列表test_list的头部插入元素"two"
> lpush test_list 'two'
2
# 获取列表test_list从索引 0 到 1 的所有元素
> lrange test_list 0 1
two
one
>
# 移除列表test_list的第一个元素
> lpop test_list
two
> lrange test_list 0 1
one
1.2.2.4 set 类型
# 添加元素到集合 test_set
> sadd test_set 'member1'
1
>
> smembers test_set
member1
>
> srem test_set 'member1'
1
> smembers test_set
1.2.2.5 sorted set(有序集合) 类型
# 添加元素到有序集合 test_zset,并带有一个分数 1
> zadd test_zset 1 'one'
1
# 添加多个元素到有序集合 test_zset,并带有一个分数
> zadd test_zset 2 'two' 3 'three'
2
# 获取集合中所有元素
> zrange test_zset 0 -1
one
two
three
# 按照分数倒序获取集合中所有元素
> zrevrange test_zset 0 -1
three
two
one
>
1.2.3 集成Spring测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: 127.0.0.1
port: 6379
server:
port: 8898
public class TestDragonflyDBController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@GetMapping("/getValue")
public String getValue(String key) {
return redisTemplate.opsForValue().get(key);
}
@GetMapping("/setValue")
public void setValue(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
}
http://192.168.10.135:8898/test/setValue?key=hello
> get hello
db