重新分析 - 第1部分:线程模型
#教程 #redis

介绍

在其核心上,redis是一个单线程系统。要水平扩展,您需要使用称为 redis cluster 的部署拓扑,该拓扑自动将数据自动划分跨多个redis节点。 Redis不是一致的哈希(这是其他系统中常用技术),而是采用了另一种碎片形式,其中每个键在概念上都是 hash slot的一部分。。

从单个redis实例转移到redis群集后,您就可以进入分布式系统的世界。它有其优点,但也有很多限制:

  • 碎片/节点管理 - 这包括添加/删除节点,重新平衡数据等。
  • 处理最终的一致性和可能的​​数据丢失-REDIS复制是异步的,这可能会导致复制品的过时读取。更糟糕的是,由于主要节点故障和分裂脑场景可能会导致数据丢失。
  • 应用程序级别的复杂性 - REDIS群集不支持属于不同插槽的密钥上的多键操作。这样做会导致(in)著名的CROSSSLOT错误。

就像"The Best Code is No Code At All"一样,理想的分布式系统也许是不需要分发的系统。在Redis的情况下,您可以使用蜻蜓等单节点解决方案来实现此目标。

多亏了它的API compatibility,蜻蜓可以用作Redis的 drop-in替换。蜻蜓解决了Redis群集的大多数缺点,从而大大降低了操作的复杂性和提高可靠性。

  • 蜻蜓依赖于垂直缩放单个节点。这使其比Redis群集和其他多节点方法更快,更便宜,更可预测。
  • 与Redis不同,Dragonfly有一个multi-threaded, shared-nothing architecture,可以垂直扩展到每个实例的记忆。

在此博客文章中,您将学习如何将数据从redis群集迁移到单节点的蜻蜓实例。我们将使用Dragonfly的emulated cluster mode,该emulated cluster mode更容易将您现有的应用程序从Redis群集迁移到Dragonfly。我们将使用sample application来演示迁移过程并逐步介绍一切。

先决条件

开始之前,请确保已安装以下内容:

克隆存储库并导航到正确的目录:

git clone git@github.com:dragonflydb/redis-cluster-application-example.git
cd redis-cluster-application-example

启动蜻蜓和雷迪斯集群

要保持简单,您将使用Docker组成:

docker compose -p migrate-to-dragonflydb up

验证Docker容器正在运行:

docker compose -p migrate-to-dragonflydb ps

预期输出:

NAME                                    COMMAND                  SERVICE             STATUS              PORTS
migrate-to-dragonflydb-dragonfly-1      "entrypoint.sh drago…"   dragonfly           running (healthy)   0.0.0.0:6380->6379/tcp
migrate-to-dragonflydb-rediscluster-1   "docker-entrypoint.s…"   rediscluster        running             6379/tcp, 0.0.0.0:7000-7002->7000-7002/tcp

从redis容器中,启动一个redis群集:

docker exec -it migrate-to-dragonflydb-rediscluster-1 /bin/bash
./start-cluster.sh

# expected output
Redis Cluster ready

此时,您应该有:

  1. 在port 6380上访问的蜻蜓实例2
  2. 700070017002上访问的三节点REDIS群集可访问

开始迁移过程之前,让我们快速查看示例应用程序。

应用概述

您可以在/app下访问源代码here

该应用程序是用GO编写的Web服务,可通过Go Redis客户端提供REDIS的数据。该应用程序在启动期间将示例数据加载到redis群集中 - 这是存储为HASH的虚拟用户数据。

// main.go
func loadData() {
   fmt.Println("loading sample data into redis.....")


   user := map[string]string{}


   for i := 0; i < 100; i++ {
       key := "user:" + strconv.Itoa(i)
       name := "user-" + strconv.Itoa(i)
       email := name + "@foo.com"
       user["name"] = name
       user["email"] = email


       err := client.HMSet(context.Background(), key, user).Err()
       if err != nil {
           log.Fatal("failed to load data", err)
       }
   }


   fmt.Println("data load complete")
}

它还提供了一个REST API来获取用户数据的ID。

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/{id}", func(w http.ResponseWriter, r *http.Request) {
        id := mux.Vars(r)["id"]
        hashName := "user:" + id
        fmt.Println("getting data for", hashName)

        var user User
        err := client.HMGet(context.Background(), hashName, "name", "email").Scan(&user)

        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }

        err = json.NewEncoder(w).Encode(user)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
    })

    log.Fatal(http.ListenAndServe(":8080", r))
}

应用程序运行后,您可以使用任何HTTP client(例如curl)使用应用程序公开的API查询REDIS的数据 - 稍后会详细介绍。

此博客文章使用RIOT-Redis,这是Redis的数据迁移工具。它不使用REPLICAOF命令,而是使用DUMPRESTORE或基于类型的replication(例如GETSET)在snapshotlive模式中实现客户端复制。

为迁移做准备

1.启动客户端应用程序

在新终端中:

cd app

export REDIS_HOSTS="localhost:7000,localhost:7001,localhost:7002"
export LOAD_DATA=true

go run main.go

该应用程序将将种子数据(100 HASHes)加载到源redis群集中。您应该看到以下输出:

loading sample data into redis.....
data load complete

2.验证源redis群集中的数据

数据负载完成后,请连接到redis群集中的每个节点,然后检查键的数量:

redis-cli -p 7000 -c DBSIZE

# output
(integer) 28

redis-cli -p 7001 -c DBSIZE

# output
(integer) 36

redis-cli -p 7002 -c DBSIZE

# output
(integer) 36

如果添加它们,它们应该是100-与我们在启动过程中加载的应用程序相同。

您可以使用curl
测试应用程序

curl -X GET http://localhost:8080/1

# expected output
{"name":"user-1","email":"user-1@foo.com"}

curl -X GET http://localhost:8080/2

# expected output
{"name":"user-2","email":"user-2@foo.com"}

您已经验证了该应用程序是否按预期工作。

将数据从redis群集迁移到单个节点蜻蜓实例(在群集模式下)

1.验证蜻蜓中的数据

redis-cli -p 6380 -c DBSIZE

# expected output
(integer) 0

正如预期的那样, 在蜻蜓实例中。

2.使用RIOT-REDIS工具启动迁移

使用以下riot-redis命令:

riot-redis --info -h localhost -p 7000 --cluster replicate-ds -h localhost -p 6380 --batch 10

我们使用redis群集作为源(请注意端口号和--cluster标志)。这将迁移数据从redis cluster节点localhost:7000到蜻蜓实例localhost:6380--batch标志指定要迁移的密钥数。

您应该看到迁移作业的类似输出(时间和其他统计信息可能有所不同):

Executing step: [snapshot-replication]
Job: [SimpleJob: [name=scan-reader]] launched with the following parameters: [{}]
Executing step: [scan-reader]
Scanning   0% │                                                                      │  0/10 (0:00:00 / ?) ?/s
Step: [scan-reader] executed in 79ms
Closing with items still in queue
Job: [SimpleJob: [name=scan-reader]] completed with the following parameters: [{}] and the following status: [COMPLETEScanning 100% │██████████████████████████████████████████████████████████████│ 100/100 (0:00:00 / 0:00:00) ?/s
Step: [snapshot-replication] executed in 856ms
Executing step: [verification]
Job: [SimpleJob: [name=RedisItemReader]] launched with the following parameters: [{}]
Executing step: [RedisItemReader]
Verifying   0% │                                                                     │  0/10 (0:00:00 / ?) ?/s
Step: [RedisItemReader] executed in 147ms
Closing with items still in queue
Job: [SimpleJob: [name=RedisItemReader]] completed with the following parameters: [{}] and the following status: [COMPVerifying 100% │██████████████████████████████│ 100/100 (0:00:00 / 0:00:00) ?/s >0 T0 ≠0 ⧗0
Verification completed - all OK
Step: [verification] executed in 368ms
Job: [SimpleJob: [name=snapshot-replication]] completed with the following parameters: [{}] and the following status: [COMPLETED] in 1s443ms

3.验证蜻蜓中的数据

redis-cli -p 6380 -c DBSIZE

# expected output
(integer) 100

redis-cli -p 6380 -c HMGET user:42 name email

# expected output
1) "user-42"
2) "user-42@foo.com"

如果迁移成功,您应该在蜻蜓实例中看到100项目。

4.将应用程序切换到使用蜻蜓

在启动应用程序之前,请关闭上一个实例以避免端口8080 Collision

export REDIS_HOSTS="localhost:6380"
export LOAD_DATA=false

go run main.go

# expected output
connected to redis localhost:6380

请注意,我们更改了REDIS_HOSTS值以指向Dragonfly,并且我们不再使用数据加载选项。这只会启动Web服务器并提供来自Dragonfly的数据。

您可以这样测试:

curl -X GET http://localhost:8080/4
curl -X GET http://localhost:8080/5
curl -X GET http://localhost:8080/42

该应用程序将继续按预期工作并提供来自蜻蜓的数据。

我们所做的只是指向蜻蜓而不是Redis群集,而无需更改代码。

完成本教程中的步骤后,请使用此命令停止redis群集和蜻蜓实例:

docker compose -p migrate-to-dragonflydb down -v

结论和下一步

在本教程中,我们使用RIOT-REDIS工具将数据从Redis群集迁移到蜻蜓。无需更改任何应用程序代码!多亏了Draginfly Cluster Mode,单个蜻蜓实例可以实现与多节点redis群集相同的容量。您使用的迁移技术取决于源群集。这可能与REPLICAOF命令(Dragonfly supports a primary/secondary replication model)MIGRATE命令,甚至是自定义应用程序。

由于蜻蜓的硬件效率,您可以在一个小的8GB实例上运行一个节点实例,并垂直缩放到带有64内核的大型768GB机器。这降低了基础架构成本,并降低了分布式数据系统固有的复杂性。您可以使用Dragonfly,而不是花费时间和精力来维持REDIS群集,而是可以使用与单节点Redis相同的语义,以及在需要时模仿Redis群集的能力。

在撰写本文时,Dragonfly has implemented more than 200 Redis commands代表了绝大多数用例的良好覆盖范围。

如果您想了解更多信息,请查看我们在how to fire up a Dragonfly instance上的文档,带上client of your choice并开始使用熟悉的Redis commands建造!