美文网首页java高级开发
Redis短结构与分片方式

Redis短结构与分片方式

作者: 老鼠AI大米_Java全栈 | 来源:发表于2018-11-27 10:10 被阅读15次

本文将介绍两种降低Redis内存占用的方法——使用短结构存储数据和对数据进行分片。
降低Redis内存占用有助于减少创建快照和加载快照所需的时间、提升载入AOF文件和重写AOF文件时的效率、缩短从服务器同步所需的时间,并能让Redis存储更多的数据。

Redis短结构

Redis为列表、集合、散列和有序集合提供了一组配置选项(配置文件中),这些选项可以让Redis以更加节约空间的方式存储长度较短的结构(即短结构)。

在列表、散列和有序集合的长度较短或者体积较小的时候,Redis可以选择使用一种名为压缩列表(ziplist)的紧凑存储方式来存储这些结构。压缩列表会以序列化的方式存储数据,这些序列化数据每次被读取的时候都要就行解码,每次被写入的时候都要进行局部的重新编码,并且可能需要对内存里的数据进行移动。因此读写一个长度较大的压缩列表可能会给性能带来负面的影响。

来看一下不同结构使用压缩列表表示的配置选项:

list-max-ziplist-entries 512
list-max-ziplist-value 64   当列表的元素长度都小于64字节并且列表元素数量小于512时,使用压缩列表,反之使用linkedlist。

hash-max-ziplist-entries 512
hash-max-ziplist-value 64  当散列的元素的键和值都小于64字节并且键值对的数量小于512时,使用压缩列表,反之使用hashtable

zset-max-ziplist-entries 128
zset-max-ziplist-value 64  当有序集合的元素都小于64字节并且元素数量小于128个的时候,使用压缩列表,反之使用skiplist

-max-ziplist-entries:表示列表、散列和有序集合在被编码为压缩列表的情况下,允许包含的最大元素数量。

-max-ziplist-value:表示压缩列表的每个节点的最大体积是多少 。

列表、散列和有序集合的基本配置选项很相似,它们都由-max-ziplist-entries和-max-ziplist-value组成。当这些选项设置的限制条件中的任意一个被打破的时候,Redis就会将相应的列表、散列或者有序集合从压缩列表编码转换为其他结构,而内存的占用也会因此增加。下面给出一个例子:

redis0.png
上面的图中,首先推入4个元素到test列表,然后通过debug object命令查看“test”的相关信息,可以发现“test”的存储结构为ziplist(压缩列表),当推入一个超出编码允许大小的元素时,“test”的存储结构将从ziplist转换为linkedlist,而且即使将超出编码允许大小的元素弹出后,列表test的存储结构也不会重新转换为压缩列表。

跟列表、散列和有序集合一样,体积较小的集合也有自己的短结构:如果集合包含的所有成员都可以被解释为十进制整数,而这些整数又处于平台的有符号整数范围之内,并且集合成员的数量满足配置文件中的限制条件,那么Redis就会以有序整数数组的方式存储集合。这种存储方式又被称为整数集合(intset)。

我们先来看一下集合使用整数集合表示的限制条件:

set-max-intset-entries 512  --只要集合存储的整数数量没有超过512,Redis就会使用整数集合表示以减少数据的体积。

再来看一下操作实例:


redis1.png

为了方便测试,我将set-max-intset-entries设置为10,首先向集合testset中加入10个整数元素,testset的存储结构为intset(整数集合),当再向testset中增加一个整数元素时,此时集合元素数量超过了set-max-intset-entries的设置,此时testset的存储结构将从整数集合(intset)转换为散列表(hashtable)。

前面提到过,读写一个长度较大的压缩列表可能会给性能带来负面的影响,同样,操作一个元素数量较多的整数集合时也可能会给性能带来负面影响,所以Redis才会在压缩列表和整数集合突破限制条件时,将其转换为更底层的结构类型。

Redis分片结构

分片(sharding)是一种广为人知的技术,很多数据库都使用这种技术来扩展存储空间并提高自己所能处理的负载量。分片本质上就是基于某些简单的规则,将数据划分为更小的部分,然后根据数据所属的部分来决定将数据分配到哪或者从哪获取数据。而Redis分片就是将数据拆分到多个Redis服务器的过程,这样每个Redis服务器将只存储原数据的子集。

Redis分片的作用:

  • 允许使用很多电脑的内存总和来支持更大的数据库。没有分片,你就被局限于单机能支持的内存容量。
  • 允许伸缩计算能力到多核或多服务器,伸缩网络带宽到多服务器或多网络适配器。
    Redis分片的方式:

假设我们有4个Redis服务器R0,R1,R2,R3,还有很多表示用户的键,像user:1,user:2....,我们能找到不同的方式来选择一个指定的键存储在哪个服务器中。换句话说,有许多不同的办法来映射一个键到一个指定的Redis服务器。

最简单的执行分片的方式之一是范围分片(range partitioning),通过映射对象的范围到指定的Redis实例来完成分片。例如,我可以假设用户从ID 0到ID 10000进入实例R0,用户从ID 10001到ID 20000进入实例R1。

这套办法行得通,然而,这样做有一个缺点,就是需要一个映射范围到实例的表格。这张表需要管理,不同类型的对象都需要一个表,所以范围分片在Redis中并不经常使用,因为这要比其它分片可选方案低效得多。

还有一种分片方式是哈希分片(hash partitioning)。这种模式适用于任何键,它的工作原理是 :

  • 使用一个哈希函数(例如,CRC32哈希函数)将键名转换为一个数字。例如,如果键是foobar,CRC32(foobar)将会输出类似于93024922的东西。
  • 对这个数据进行取模运算,以将其转换为一个0到3之间的数字,这样这个数字就可以映射到4台Redis服务器总的一个上。93024922模4等于2,所以键foobar应当存储到R2实例。

Redis分片的缺点:

  • 涉及多个键的操作通常不支持。例如,你不能对映射在两个不同Redis服务器上的键执行交集。
  • 涉及多个键的事务不能使用。
  • 数据处理变得更复杂,例如,你需要处理多个RDB/AOF文件,备份数据时你需要聚合多个实例和主机的持久化文件。

用Java实现Redis分片:
在对数据结构进行分片的时候,我们既可以实现结构的所有功能,也可以只实现结构的部分功能。这里的例子中,实现了散列结构的分片函数以及分片散列的HSET和HGET功能。

例子中对散列进行分片的时候,使用的是哈希分片的方式,即使用CRC32函数将散列里元素的键转换为数字,再根据元素的总数量和每个分片需要存储的元素数量计算出所需要的分片数量,并使用这个分片数量和之前的数字键来决定应该把元素存储到哪个分片里面。

根据基础键和散列键计算出分片键的函数:

//在调用shardKey函数时,用户需要给定基础散列的名字、将要被存储到分片散列里面的元素的键、预计的元素总数量以及每个分片存储的元素数量
    public String shardKey(String base, String key, long totalElements, int shardSize) {
        long shardId = 0;
        if (isDigit(key)) {     //如果key是一个整数,那么它将被直接用于计算分片ID
            shardId = Integer.parseInt(key, 10) / shardSize;
        }else{
            CRC32 crc = new CRC32();
            crc.update(key.getBytes());
            long shards = 2 * totalElements / shardSize;    //计算分片的总数量
            shardId = Math.abs(((int)crc.getValue()) % shards);     //计算分片ID
        }
        return base + ':' + shardId;    //把基础键和分片ID组合在一起,得到分片键
    }

    //判断字符串是否为一个整数字符串
    private boolean isDigit(String string) {
        for(char c : string.toCharArray()) {
            if (!Character.isDigit(c)){
                return false;
            }
        }
        return true;
    }

分片散列的HSET和HGET函数:

public Long shardHset(
        Jedis conn, String base, String key, String value, long totalElements, int shardSize)
    {
        String shard = shardKey(base, key, totalElements, shardSize);   //计算出应该由哪个分片来存储
        return conn.hset(shard, key, value);    //将元素存储到分片里面
    }

    public String shardHget(
        Jedis conn, String base, String key, int totalElements, int shardSize)
    {
        String shard = shardKey(base, key, totalElements, shardSize);   //计算出元素应该被存储到了哪个分片
        return conn.hget(shard, key);   //获取存储在分片里面的元素
    }

分片散列的设置和获取操作并不复杂,其它的操作如HDEL、HINCRBY等也可以用类似的方式实现。
注意,此方式适合3.0之前(client cluster),之后版本的集群实现了smart cluster。

Redis 集群的数据分片(Redis Cluster data sharding)

Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。
Redis 集群没有使用一致性哈希,而是另外一种不同的分片形式,每个键概念上是被我们称为哈希槽 (hash slot)的东西的一部分。 Redis 集群有 16384 个哈希槽,我们只是使用键的 CRC16 编码对 16384 取模来计算一个指定键所属的 哈希槽。 每一个 Redis 集群中的节点都承担一个哈希槽的子集,例如,你可能有一个 3 个节点的集群,其中:

  • 节点 A 包含从 0 到 5500 的哈希槽。
  • 节点 B 包含从 5501 到 11000 的哈希槽。
  • 节点 C 包含从 11001 到 16384 的哈希槽。
    这可以让在集群中添加和移除节点非常容易。例如,如果我想添加一个新节点 D,我需要从节点 A,B, C 移动一些哈希槽到节点 D。同样地,如果我想从集群中移除节点 A,我只需要移动 A 的哈希槽到 B 和 C。 当节点 A 变成空的以后,我就可以从集群中彻底删除它。 因为从一个节点向另一个节点移动哈希槽并不需要停止操作,所以添加和移除节点,或者改变节点持有 的哈希槽百分比,都不需要任何停机时间(downtime)。

节点与槽

Redis集群通常由多个节点(node)组成,开始每个节点都是图例的,它们都处于一个只包含自己的集群中,当要组建一个真正可工作的集群,我们必须将节点连接起来,构成一个包含多个节点的集群。

Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为 16384 (=1024*16)个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽。当数据库中的16384个槽有节点在处理时,集群处于一个上线状态(ok);相反地,如果数据库中任何一个槽没有得到处理,那么集群处于下线状态(fail)。
关系:cluster>node>slot>key

redis2.png
槽指派信息记录在 clusterNode.slots[16384/8] 属性中, numslots 记录了节点负责处理的槽的数量。Redis以0为起始索引,16383为终止索引,对slots数组中的16384个二进制位进行编号,并根据索引i上的二进制位来判断节点是否负责处理槽i:
  • 如果slots数组在索引i上的二进制位值为1,那么表示节点负责处理槽i。
  • 如果slots数组在索引i上的二进制位值为0, 那么表示节点不负责处理槽i。
    节点会把自己处理的槽信息发送给其他集群中的其他节点,因此集群中的每个节点都会知道数据库中16384个槽分别被指派给了集群中哪些节点。
    clusterState 结构中的 slots[16384] 数组则更上面的正好反过来,它记录了每个槽是由哪个节点在管理的。之所以会有这两种结构是为了在查找节点管理了哪些槽和槽由哪个节点管理的复杂度都降低了。

关于分片
在3.0版本之前的分布式方案都是自己实现的,然后利用Sentinel进行监控。后来Redis自己实现了集群方案,可以用其默认的集群方案来代替之前的自己实现方案。他们之间是相辅相成的,根据自己的需要进行选择。

相关文章

  • Redis短结构与分片方式

    本文将介绍两种降低Redis内存占用的方法——使用短结构存储数据和对数据进行分片。降低Redis内存占用有助于减少...

  • Redis降低内存占用

    1.Redis降低内存占用方案 短结构 分片结构 2.短结构 2.1 压缩列表 以列表为例,列表是由双向链表实现,...

  • k3s operator 方式 安装 redis 无中心集群

    redis有单机,复制,分片这几种方式 复制就是把一个redis的数据,复制到另外一个redis, 分片就是把ke...

  • Redis Cluster 原理

    Redis Cluster 提供了一种支持数据在多个 Redis 节点上自动进行分片的部署方式。Redis Clu...

  • Redis之前的思考笔记

    预分片(数据迁移的方式基于当前使用爹redis2.0+的版本 3.0+服务端可以自动分片和迁移) 我们已经知道分片...

  • Redis 使用方法

    1.redis基本数据结构与短结构压缩了解redis的数据结构有助于了解每种数据结构的优劣势,方便设计合理的cac...

  • Redis-Cluster笔记1

    问:Redis-Cluster通过什么方式存储数据? Redis集群通过分片的方式来保存数据库中的键值对(K/V)...

  • Redis - 集群Hash槽分配

    1. Redis的hash槽介绍 常见的Redis集群架构是三主三从的结构,为了保证数据分片,redis采用了Ha...

  • 降低Redis内存的占用

    1.原理 短结构 Redis为列表、集合、散列和有序集合提供了一组配置选项,让Redis以跟节约内存的方式存储这些...

  • Redis集群重新分片

    内容部分摘自于《Redis的设计与实现》(17章节) Redis 集群的重新分片操作是由 Redis 的集群管理软...

网友评论

    本文标题:Redis短结构与分片方式

    本文链接:https://www.haomeiwen.com/subject/zjvwqqtx.html