位图应用,原理和性能分析

1.应用

在平时的开发过程中,会有一些bool型数据需要存取,比如用户一年的签到记录,签了是1,没签是0,要记录365天。如果使用普通的key/value,每个用户要记录365个,当用户数上亿时,需要相当大的存储空间。

Redis提供了位图数据结构,每天的签到记录只占据一个位,365天就是365个位,46个字节(一个稍长的字符串)就可以完全容纳下,大大节约了存储空间。

位图的最小单位是bit,每个bit的取值只能是01.也就是说一个bit能存储的最多信息是2。

2.原理

8bit = 1b

位图不是特殊的数据结构,它的内容就是普通的字符串,即byte数组。我们可以使用普通的get/set直接获取和设置整个位图的内容,也可以使用位图操作getbit/setbit等将byte数组看成“位数组”来处理。

3.性能分析

1.时间和内存分析

对位图的操作和对redis基本类型String的get/set操作是一样的。

使用Java编写一段测试程序如下:

public class BitmapTest {

    private static final Jedis jedis = new Jedis("127.0.0.1", 6379);

    private static final String TEST_BM_KEY = "test.bm.key";
    /**
     * 为了降低位图读取时的时间复杂度,将业务id分片存储
     */
    private static final Integer PIECE_NUMBER = 10000;

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.println("输入测试条数:");
        int times = scan.nextInt();
        System.out.println("输入测试指令:");
        String order = scan.next();
        System.out.println("----start test----");

        long start = System.currentTimeMillis();
        if ("bmset".equals(order)) {
            bmsetTest(times);
        } else if ("set".equals(order)) {
            setTest(times);
        } else if ("bmget".equals(order)) {
            bmgetTest(times);
        } else if ("get".equals(order)) {
            getTest(times);
        }

        System.out.println(order + "共耗时" + (System.currentTimeMillis() - start) + "ms");
        jedis.close();
    }

    private static void getTest(int times) {
        for (int i=0; i<times; i++) {
            jedis.get(String.valueOf(i));
        }
    }

    private static void bmgetTest(int times) {
        for (int i=0; i<times; i++) {
            bmget(i);
        }
    }

    private static void setTest(int times) {
        for (int i=0; i<times; i++) {
            jedis.set(String.valueOf(i), String.valueOf(true));
        }
    }

    private static void bmsetTest(int times) {
        for (int i=0; i<times; i++) {
            bmset(i, true);
        }
    }

    public static boolean bmset(long businessId, boolean status) {
        String redisKey = getRedisKey(businessId);
        long offset = getOffset(businessId);
        return jedis.setbit(redisKey, offset, status);
    }

    public static boolean bmget(long businessId) {
        String redisKey = getRedisKey(businessId);
        long offset = getOffset(businessId);
        return jedis.getbit(redisKey, offset);
    }

    private static String getRedisKey(long businessId) {
        /**
         * 对id分片获取redis key
         */
        String redisKey = TEST_BM_KEY + ":" + (businessId / PIECE_NUMBER);
        return redisKey;
    }

    private static long getOffset(long businessId) {
        /**
         * 分片后的偏移
         */
        long offset = businessId % PIECE_NUMBER;
        return offset;
    }
}

这里需要说明的是分片这个概念,由于bitmap读取时的时间复杂度是O(n),为了提高读取速度,对业务id进行分片存储,属于不同片的业务id存储到不同的redis key当中。

测试流程:

首先,使用redis的redis-cli info memory命令看下当前redis的内存情况。

编译运行java程序,插入100w条记录进行测试,业务id为0~999999。

1.bmset指令

消耗时间15s,看下内存使用情况

约用了181K的空间。

2.set指令

耗时比setbit略小,再看内存使用情况:

可以看到使用了约61.5M的空间,这个数据远大于bitmap使用的空间。

3.bmget指令

查询100w条数据耗时约15s。

4.get指令

查询100w条数据耗时约15s。

综上可以看出,使用redis位图数据结构可以显著的减少内存使用空间,但是在消耗时间上面基本上是一致的。

 

参考文献:

《Redis深度历险:核心原理与应用实践》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值