docker进阶(redis主从集群,一致性hash算法)

docker进阶(redis主从集群,一致性hash算法)

redis主从集群,一致性哈希算法

哈希取余分区

在这里插入图片描述

举例,我们要存储2亿条数据,也就是2亿个k.v。这时候我们单机不行,必须要进行分布式多级,假设我们有3台机器构成一个集群,用户每次读写操作都是根据公式hash(key)%N(N为机器的太熟),计算出哈希值,用来决定数据映射到哪一个节点上。

优点:

简单粗暴,直接有效,只需要预估好数据,规划好节点;例如3台,8台,10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落在同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求信息),起到负载均衡+分而治之的作用。

缺点:

原来规划好的节点,进行扩容或者缩容就比较麻烦了。不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或者故障停机的情况下,原来的取模公式就会发生变化,Hash(key)/3会变成Hash(key)/?。此时地址经过某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

一致性哈希算法分区

算法背景:一致性哈希算法在1997年由麻省理工学院中提出,设计目标是为了解决分布式缓存数据变动和映射问题,单个机器宕机了,分母数量改变了,自然取余数就不行了。提出一致性Hash解决方案,目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系

实现一致性哈希算法的三个步骤:

  1. 算法构建一致性哈希环

    一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,232-1],这是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它收尾相连(0=232),这样让它逻辑上形成了一个环形空间。

    它也是按照使用取模的方法,前面的哈希取余算法分区使用的是对节点数进行取模。而一致性Hash算法是对232取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,假设某个哈希函数H的值空间为0-232-1(即hash值是一个32位无符号整形),整个哈希环按照顺时针方向组织,圆环的正上方的点代表0,0的右侧的第一个点代表1,以此类推2,3,4,…,2…32-1,也就是说在0点上左侧的第一个点代表332-1,0和232-1在零点中方向重合,我们把这个由232个点组成的圆环成为Hash环。

  2. 服务IP节点映射

    将集群中各个IP节点映射到环上的某一个位置。将各个服务器使用Hash进行哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。

  3. key落到服务器的落键规则

    当我们需要存储一个key/value时,首先计算key的hash值,hash(key),将每个key使用第二步服务IP节点映射相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针行走,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点。

在这里插入图片描述

优点:

加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。

一致性哈希算法的容错性

假设 cs3 服务器出现故障导致服务下线,这时原本存储于 cs3 服务器的对象 o4,需要被重新分配至 cs2 服务器,其它对象仍存储在原有的机器上。

在这里插入图片描述

一致性哈希算法的扩展性

假设由于业务需要,我们需要增加一台服务器 cs4,经过同样的 hash 运算,该服务器最终落于 t1 和 t2 服务器之间 。

在这里插入图片描述

缺点:

数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上,所以数据在进行存储时达不到均匀分布的效果。

一致性哈希算法的数据倾斜问题

一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)

哈希槽分区

哈希槽分区算法的出现就是为了解决一致性哈希算法的数据倾斜问题。哈希槽实质就是一个数组,数组[0,2^14-1]形成hash slot空间。

解决均匀分配的问题,在数据和节点之间又加入了一层,把这一层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。

在这里插入图片描述

槽解决的是颗粒度问题,相当于把粒度变大,这样便于数据移动。哈希解决的是映射问题,使用key的哈希值来计算,所在的槽,便于数据分配。

一个集群只能又16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪些主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几,key就落入对应的槽里。slot=CRC16(key)%16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

哈希槽计算

Redis集群中内置了16384个哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点上。当需要在Redis集群中放置一个key-value时,redis先对key使用,crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16383之间的哈希槽,也就是映射到某个节点上。

在这里插入图片描述

三主三从Redis集群配置

关闭防火墙:

# 环境为CentOS7.8
# 查看防火墙服务状态命令
systemctl status firewalld
# 查看防火墙的状态
firewall-cmd --state
# 开启防火墙服务
service firewalld start
# 重启防火墙服务
service firewalld restart
# 关闭防火墙服务
service firewalld stop

启动docker服务:

# 启动docker
systemctl start docker
# 查看docker状态
systemctl status docker

在这里插入图片描述

启动六台Redis实例:

## 参数说明
## docker run:创建并运行docker容器实例
## --name redis-node-6:容器名字
## --net host:使用宿主机的IP和端口,默认
## --privileged=true:获取宿主机root用户权限
## -v /data/redis/share/redis-node-6:/data:容器卷,宿主机地址:docker内部地址
## redis:6.2.6:redis镜像和版本号
## --cluster-enabled yes:开启Redis集群
## --appendonly yes:开启持久化
## --prot 6386:redis端口号

docker run -d --name redis-node-1 --net host --privileged=true -v /data/redis/share/redis-node-1:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6381

docker run -d --name redis-node-2 --net host --privileged=true -v /data/redis/share/redis-node-2:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6382


docker run -d --name redis-node-3 --net host --privileged=true -v /data/redis/share/redis-node-3:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6383

docker run -d --name redis-node-4 --net host --privileged=true -v /data/redis/share/redis-node-4:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6384


docker run -d --name redis-node-5 --net host --privileged=true -v /data/redis/share/redis-node-5:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6385

docker run -d --name redis-node-6 --net host --privileged=true -v /data/redis/share/redis-node-6:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6386

在这里插入图片描述

进入容器redis-node-1并为6台机器构建集群关系:

# 进入容器
docker exec -it redis-node-1 /bin/bash
# 以下命令是进入容器内执行的(ip为你自己实际的IP)
# 命令说明:
# redis-cli:进入redis实例内部
# --cluster create:创建redis集群
# --cluster-replicas 1:为每一个master创建一个slave节点
redis-cli --cluster create 你自己的IP:6381 你自己的IP:6382 你自己的IP:6383 你自己的IP:6384 你自己的IP:6385 你自己的IP:6386  --cluster-replicas 1

在这里插入图片描述

以6381作为切入点,查看集群的状态:

# 注意以下命令是进入容器内执行的(docker exec -it redis-node-1 /bin/bash)
# 进入6381端口这台redis内
redis-cli -p 6381
# 查看集群信息
cluster info
# 查看节点信息
cluster nodes

在这里插入图片描述

根据节点信息可以找出以下的主从关系:

6381主机对应6385的从机

6382主机对应6386的从机

6383主机对应6384的从机

主从容错切换迁徙

数据存储:

由于现在是cluster环境了,直接连接某一台主机的话,存储数据可能会出现数据存不进去的问题(也就是key-value的key的槽位可能刚好不是你连接的那个集群,就会出现存不进去的问题)

# 演示存不进去数据的情况
docker exec -it redis-node-1 /bin/bash
redis-cli -p 6381

在这里插入图片描述

下面演示正确的连接redis集群的方式:

# 正确的连接方式 -c就表示以集群的方式连接,优化路由
docker exec -it redis-node-1 /bin/bash
redis-cli -p 6381 -c

在这里插入图片描述

在这里插入图片描述

注意:

我们存储完k1之后,能看到数据是在第三台redis上。当存储完成后可以看到,我们是从6381上重定向(Redirected)到6383上的。最后我们控制台也是停留在6383上。如果存储的数据机器没有变,就不用切换了。

在这里插入图片描述

查看集群的存储情况:

redis-cli --cluster check 你自己的IP:6381

在这里插入图片描述

容错切换迁移

将其中一台主机人为宕机掉,看下对应的从机是否会上位????

主体步骤:

  1. 登录redis-node-1节点查看集群状态,各个节点均存活
  2. 登录redis-node-2节点查看集群状态,各个节点均存活
  3. 将redis-node-1节点容器停止
  4. 在redis-node-2节点查看集群状态,发现之前redis-node-1的slave6385上位成功

在这里插入图片描述

在这里插入图片描述

这时如果将redis-node-1重启,6385是退位还是redis-node-1从新上位为master????

  • 重启redis-node-1
docker start redis-node-1

在这里插入图片描述

  • 查看集群状态
cluster nodes

在这里插入图片描述

结论:

6381并没有上位,而是6385继续当master。

<think>好的,我需要向用户介绍如何用Go实现一个分块存储服务。首先,得理解分块存储的基本概念,也就是将大文件分成小块存储,这样可以提高效率和可靠性。然后,考虑Go语言的特性,比如并发处理,这对上传下载分块很有帮助。 接下来,要设计系统的架构。可能需要一个主服务器管理元数据,比如文件名、分块信息,以及存储节点的位置。存储节点负责实际存储分块数据。需要决定分块的大小,比如每块10MB,这样处理起来比较高效。 然后是API设计,用户需要上传、下载、合并文件。上传时,客户端将文件分块,逐个上传,服务器返回每个块的成功与否。下载的时候,客户端获取分块信息,并行下载再合并。删除操作需要清理元数据和存储节点的数据。 在技术实现上,元数据存储可以用数据库,比如MySQL或Redis。存储节点可以用对象存储服务,或者自己管理文件系统。分块的唯一标识可以用哈希值,比如SHA-256,确保唯一性。并发上传下载用Go的goroutine和channel来处理,提高效率。 代码示例部分,可能需要写上传分块的HTTP处理函数。用multipart/form-data接收文件块和文件信息。然后保存到临时目录,计算哈希,最后移动到存储路径。还要更新元数据,比如数据库记录分块信息。 最后,要考虑优化和扩展性。比如支持分布式存储节点,动态扩展。数据一致性方面,使用校验和或副本机制。安全性需要HTTPS和鉴权。高可用可以通过主从架构或集群实现。 用户可能需要更详细的代码示例,或者具体某个部分的实现,比如如何合并分块。我需要确保解释清晰,步骤明确,让用户能够根据示例进行开发。同时,提醒可能遇到的问题,比如分块顺序、错误处理,以及性能优化的方法。</think>以下是一个使用 Go 语言实现分块存储服务的详细介绍: -- ### 一、核心概念 **Chunk存储服务**的核心思想是将大文件切分为固定大小的块(如 10MB/块),通过并行处理提升上传/下载效率,同时便于存储管理、断点续传和数据去重。 --- ### 二、架构设计(示例) ```bash ├── API Server # 对外提供REST接口 ├── Metadata DB # 存储文件元数据(如文件-分块映射) ├── Chunk Storage # 实际存储分块的对象/文件存储 └── Client SDK # 客户端分块上传/下载工具 ``` --- ### 三、关键实现步骤 #### 1. 文件分块规则 ```go // 建议分块大小 5MB~50MB const ChunkSize = 10 * 1024 * 1024 // 计算文件哈希作为唯一标识 func fileHash(file *os.File) string { hasher := sha256.New() io.Copy(hasher, file) return hex.EncodeToString(hasher.Sum(nil)) } ``` #### 2. 元数据管理(MySQL示例) ```sql CREATE TABLE files ( file_id VARCHAR(64) PRIMARY KEY, filename VARCHAR(255), total_chunks INT ); CREATE TABLE chunks ( chunk_id VARCHAR(64) PRIMARY KEY, file_id VARCHAR(64), chunk_number INT, size INT, storage_path VARCHAR(255) ); ``` #### 3. 分块上传接口示例 ```go // Gin框架示例 r.POST("/upload_chunk", func(c *gin.Context) { // 接收分块数据 file, header, _ := c.Request.FormFile("chunk") chunkNumber := c.PostForm("chunkNumber") fileHash := c.PostForm("fileHash") // 保存临时分块 tempPath := fmt.Sprintf("/tmp/%s_part%d", fileHash, chunkNumber) os.WriteFile(tempPath, io.ReadAll(file), 0644) // 验证分块完整性(可选) if verifyChunk(tempPath) { c.JSON(200, gin.H{"status": "success"}) } }) ``` #### 4. 分块合并逻辑 ```go func mergeChunks(fileHash string, totalChunks int) { outputFile, _ := os.Create(fmt.Sprintf("/storage/%s", fileHash)) defer outputFile.Close() for i := 0; i < totalChunks; i++ { chunkPath := fmt.Sprintf("/tmp/%s_part%d", fileHash, i) chunkData, _ := os.ReadFile(chunkPath) outputFile.Write(chunkData) os.Remove(chunkPath) // 清理临时分块 } } ``` --- ### 四、性能优化技巧 1. **并发控制**:使用 `sync.WaitGroup` 实现并行上传/下载 ```go var wg sync.WaitGroup for _, chunk := range chunks { wg.Add(1) go func(c Chunk) { defer wg.Done() uploadChunk(c) }(chunk) } wg.Wait() ``` 2. **去重机制**:存储前校验分块哈希,避免重复存储 3. **断点续传**:记录已上传分块序号,客户端可查询续传 --- ### 五、扩展功能建议 - **分布式存储**:使用一致性哈希分配分块到不同节点 - **数据冗余**:为每个分块创建 3 个副本 - **冷热分离**:将低频访问数据迁移至廉价存储 - **数据压缩**:在存储前对分块进行压缩(如Snappy算法) --- ### 六、部署示例 ```bash # 使用MinIO作为存储后端 docker run -p 9000:9000 minio/minio server /data # 启动Go服务 go run main.go --port 8080 --minio-endpoint localhost:9000 ``` 需要完整实现代码或对某个模块深入解释,可以进一步说明具体需求。这种设计适合网盘、镜像仓库等需要处理大文件的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值