Docker安装Redis

简介: 本文介绍Docker安装Redis 6.0.8的单机版与实际应用版配置,涵盖容器卷映射、配置文件设置及集群存储算法。重点解析哈希取余、一致性哈希与哈希槽分区算法,说明Redis集群为何采用16384个槽,及其在数据分布、节点扩容与网络开销间的权衡设计。

10-Docker安装Redis
Docker安装Redis
以 Redis 6.0.8 为例:
单机版安装
简单版 Redis
简单的启动Redis容器:
简单版没有配置容器卷映射,当容器被删除时数据无法恢复。
实际应用版Redis
配置文件、数据文件都和容器卷进行映射。
步骤:
宿主机创建目录/app/redis
在/app/redis下创建文件redis.conf,主要修改以下几项配置

即最后的配置文件为:
启动docker容器:(因为要使用自定义的配置文件,所以需要指定容器运行的命令为redis-server 容器内配置文件路径)

集群存储算法
分布式存储算法
分布式存储的常见算法:
哈希取余算法分区
一致性哈希算法分区
哈希槽算法分区
哈希取余算法
算法描述:hash(key) % N(其中,key是要存入Redis的键名,N是Redis集群的机器台数)。用户每次读写操作,都是根据传入的键名经过哈希运算,对机器台数取余决定该键存储在哪台服务器上。
优点:简单直接有效,只需要预估好数据规划好节点,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
缺点:原来规划好的节点,如果进行了扩容或者缩容,导致节点有变动,映射关系需要重新进行计算。在服务器个数固定不变时没问题,如果需要弹性扩容或者故障停机的情况下,原来取模公式中的 N就会发生变化,此时经过取模运算的结果就会发生很大变化,导致根据公式获取的服务器变得不可控。
一致性哈希算法
算法背景:一致性哈希算法是为了解决哈希取余算法中的分布式缓存数据变动和映射问题。当服务器个数发生变化时,尽量减少影响到客户端与服务器的映射关系。
算法描述:
一致性哈希算法必然有个hash函数并按照算法产生Hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个Hash区间[0, 2^32 - 1],这是一个线性空间。但是在这个算法中,我们通过适当的逻辑控制将它首尾相连(0 = 2^32),这样让它逻辑上形成了一个环形空间。
它也是按照使用取模的方式。前面的哈希取余算法是对节点个数进行取模,而一致性哈希算法是对 2^32取模。
简单来说,一致性Hash算法将整个哈希值空间组成一个虚拟的圆环。如假设某个哈希函数H的值空间为 0到2^32 - 1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4.......直到2^32 - 1,也就是说0点左侧的第一个点代表 2^32 - 1。0 和 2^32 - 1在零点钟方向重合,我们把这个由 2^32个点组成的圆环称为Hash环。
有了哈希环之后,还需要进行节点映射,将集群中各个IP节点映射到环上的某一个位置。
将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希。这样每台机器就能确定其在哈希环上的位置。
假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希值后在环空间的位置如下:
key落到服务器的落键规则。当我们需要存储一个key键值对时,首先计算key的hash值(hash(key)),将这个key使用相同的函数hash,计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储字该节点上。
假如我们有ObjectA、B、C、D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性hash算法,数据A会被定位到NodeA上,B被定位到NodeB上,C被定位到NodeC上,D被定位到NodeD上。
假设NodeC宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重新定位到NodeD。
一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间的数据,其他不会受到影响。
即:假设NodeC宕机,只会影响到Hash定位到NodeB到NodeC之间的数据,并且这些数据会被转移到NodeD进行存储。
假如需要扩容增加一台节点NodeX,NodeX的hash(ip)位于NodeB和NodeC之间,那受到影响的就是NodeB 到 NodeX 之间的数据。重新将B到X的数据录入到X节点上即可,不会导致Hash取余全部数据重新洗牌的后果。
但是Hash环会存在数据倾斜问题。
一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象都集中到某一台或某几台服务器)。
为了解决数据倾斜问题,一致性哈希算法引入了虚拟节点机制。
对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以先确定每个物理节点关联的虚拟节点数量,然后在IP或主机名后面加上编号。
例如,可以对NodeA节点虚拟出 NodeA#1、NodeA#2、NodeA#3,对NodeB虚拟出NodeB#1、NodeB#2、NodeB#3的节点,形成六个虚拟节点。
优点:加入和删除节点时,只会影响哈希环中顺时针方向相邻节点,对其他节点无影响。
缺点:数据的分布和节点的位置有关,因为这些节点不是均匀分布在哈希环上的,所以在数据进行存储时达不到均匀部分的效果。
哈希槽分区
哈希槽分区是为了解决一致性哈希算法的数据倾斜问题。
哈希槽实质上就是一个数组,数组 [0, 2^14 - 1]形成的 hash slot空间。
目的是为了解决均匀分配的问题。在数据和节点之间又加入了一层,把这层称之为槽(slot),用于管理数据和节点之间的关系。就相当于节点上放的是槽,槽里面放的是数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。
哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。
一个集群只能有 16394个槽,编号 0 - 16383(2^14 - 1)。这些槽会分配给集群中所有的主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点,集群会记录节点和槽的对应关系。
解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,根据余数决定key落到哪个槽里。
以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
Redis集群存储策略
Redis集群使用的就是哈希槽。Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置在哪个槽,集群的每个节点负责一部分hash槽。
哈希槽数量16384(2^14)的决定原因:
CRC16算法产生的hash值有 16bit,该算法可以产生 2^16 = 65536个值。但是为了心跳方便和数据传输最大化,槽的数量只能有 2^14个。
如果槽位数量为65535个,那么发送心跳信息的消息头将达到 8k,发送的心跳包过于庞大。在消息头中最占空间的是 myslots[CLUSTER_SLOTS/8]。当槽位为65536时,这块的大小是 :

每秒中redis节点需要发送一定数量的ping消息作为心跳,如果槽位为65536,那么这个ping消息头就会太大浪费带宽。
redis集群的主节点数量基本不可能超过1000个。集群节点越多,心跳包的消息体内携带的数据越多。如果节点超过1000个,也会导致网络拥堵。因此redis作者不建议redis cluster节点超过1000个。对于节点数在1000以内的redis cluster集群,16384个槽位足够了,没有必要扩展到65536个。
槽位越小,节点少的情况下压缩比越高,容易传输。Redis主节点的配置信息中它锁负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率 slots / N(N为节点数)很高的话,bitmap的压缩率就很低。如果节点数很少,而哈希槽数很多的话,bitmap的压缩率就很低。
原文:
正常的心跳数据包带有节点的完整配置,使得可以用幂等方式用旧的节点替换旧节点,以便更新旧的配置。这意味着它们包含原始节点的插槽配置,该节点使用 2k 的空间和 16k 的插槽,而不是使用 8k 的空间(使用65k的插槽)。
同时,因为其他设计折衷,Redis集群的主节点不太可能扩展到1000个以上
Redis集群中内置了16384个哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在Redis集群中放置一个Key-Value时,redis先对key使用 CRC16 算法算出一个结果,然后把结果对 16384 取余,这样每个key都会对应一个编号在0-16383之间的哈希槽,也就是映射到某个节点上。
Java
运行代码
复制代码
1
2
3
4
5
6
7
8
@Test
public void test() {
// import io.lettuce.core.cluster.SlotHash;
System.out.println(SlotHash.getSlot('A')); // 计算结果6373,存入上图的Node2
System.out.println(SlotHash.getSlot('B')); // 计算结果10374,存入上图的Node2
System.out.println(SlotHash.getSlot('C')); // 计算结果14503,存入上图的Node3
System.out.println(SlotHash.getSlot('Hello')); // 计算结果866,存入上图的Node1
}

相关文章
|
1天前
|
存储 Java 编译器
Java泛型类型擦除以及类型擦除带来的问题
Java泛型在编译时会进行类型擦除,所有泛型信息被移除,仅保留原始类型(如Object或限定类型)。例如,List<String>和List<Integer>在运行时均为List,导致无法通过instanceof判断泛型类型。类型检查在编译期完成,基于引用而非对象本身。擦除后的方法重写可能引发桥方法机制以解决多态冲突,且泛型不支持基本数据类型、静态成员不能使用类级泛型参数。反射可绕过泛型限制,自动类型转换由编译器插入强转实现。
|
1天前
|
存储 缓存 算法
零拷贝
实现文件传输时,传统方式需频繁系统调用与内存拷贝,导致大量上下文切换和性能损耗。为提升效率,可采用零拷贝技术,如`sendfile`,在内核态直接将磁盘数据通过PageCache传至Socket,减少上下文切换与内存拷贝。对于大文件或高并发场景,结合异步IO与直接IO,绕过PageCache,避免缓存污染,进一步优化性能。
|
1天前
|
存储 Java 索引
单/双链表代码实现
本文详解单/双链表的代码实现,涵盖增删查改操作。重点解析三大技巧:1)同时持有头尾节点引用以优化插入删除效率;2)使用虚拟头尾节点简化边界处理;3)避免内存泄漏的良好编程习惯。适合掌握链表基础后深入学习。
|
1天前
|
存储 算法 搜索推荐
线性结构检索:从数组和链表的原理初窥检索本质
本节深入解析数组与链表的存储特性及其对检索效率的影响。数组支持随机访问,适合二分查找,检索效率为O(log n);链表虽检索较慢,但插入删除高效,适用于频繁动态调整场景。通过改造链表结构,如结合数组提升检索性能,揭示了数据组织方式对检索的核心作用,帮助理解“快速缩小查询范围”这一检索本质。
|
1天前
|
存储 算法 Java
链表(链式存储)基本原理
链表是一种通过指针串联节点的线性结构,无需连续内存,支持高效增删。单链表仅有next指针,双链表增加prev指针以支持双向遍历。相比数组,链表插入删除灵活,无扩容负担,但不支持随机访问,查找需从头遍历。实际开发中常用双链表,配合虚拟头结点简化操作。
|
1天前
|
存储 Java API
数组(顺序存储)基本原理
本章讲解数组的底层原理,区分静态数组与动态数组。静态数组是连续内存空间,支持O(1)随机访问,但增删效率低,需搬移数据;通过手动实现动态数组,理解其扩容、插入、删除等操作的实现逻辑与时间复杂度,为后续数据结构打下基础。
|
1天前
|
存储 数据采集 搜索推荐
状态检索:如何快速判断一个用户是否存在?
本文探讨如何高效判断用户是否存在,对比有序数组、二分查找树和哈希表后,引出更优方案:位图与布隆过滤器。位图以bit为单位存储,大幅节省空间;布隆过滤器通过多哈希函数降低冲突概率,虽有一定误判率,但查询效率达O(1),适用于注册去重、爬虫去重等场景,是提升系统性能的关键技术。
|
1天前
|
SQL 算法 关系型数据库
熔断限流:业务如何实现自我保护?
本讲介绍RPC框架中业务的自我保护机制。面对高并发,服务端通过限流(如令牌桶、滑动窗口)防止过载,支持应用级、IP级配置,并可结合注册中心动态调整阈值;调用端则通过熔断机制避免因下游故障引发雪崩,熔断器在动态代理层拦截请求,实现快速失败与恢复,保障系统稳定性。
|
1天前
|
负载均衡 算法 网络协议
负载均衡:节点负载差距这么大,为什么收到的流量还一样?
本文探讨RPC框架中的自适应负载均衡机制。针对传统权重调节滞后问题,提出通过实时采集节点CPU、内存、请求耗时等指标,结合权重算法动态打分,自动调整节点最终权重,实现流量智能分配,提升系统稳定性与响应效率。
|
1天前
|
存储 缓存 搜索推荐
特别加餐丨倒排检索加速(二):如何对联合查询进行加速?
本文深入探讨联合查询的加速方法,针对倒排索引中复杂查询场景,系统介绍四种工业级优化技术:调整次序法通过优化求交/并集顺序降低计算代价;快速多路归并法利用跳表提升多列表合并效率;预先组合法提前计算高频查询结果;缓存法则借助LRU机制动态存储热点组合,显著提升检索性能。