降低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等也可以用类似的方式实现。

联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2024

备案号:京ICP备15003423号-3