Redis分段设计

作者: 8033b4d1f3ec | 来源:发表于2018-06-22 21:03 被阅读55次

    Hash存储结构是redis中非常常用的类型,hash和HashMap有点类似,提供快速查询和更节约内存的存储,在redis中,当Map内部小于64个成员时会采用线性紧凑格式存储,超过该值自动转成真正的HashMap,这样处理也是为了减少不必要的浪费。

    hash提供了更快的查询速度(o(1)),节约了内存,那么是不是无论多少元素都可以往hash里面填充呢?答案显然是否定的,主要有这两方面的原因,一是因为:在往hash中不断添加元素的时候,hash需要扩容,扩容机制其实可以理解为hashmap的扩容机制,在大量添加元素的时候,扩容往往是非常耗费性能的,在做初始化的时候,会导致cpu过高;二是因为,服务器物理内存是有限的,如果把大量元素全部塞入内存,会导致内存不够用和内存浪费。

    之前我做过这样一个设计,app中需要接入腾讯云的im,在发送系统消息或者c2c聊天的时候,服务端必须把本地的用户账号注册到腾讯服务器,每次发送消息都要去请求腾讯服务器判断是否注册了,导致发送消息的时候,就需要耗费大量的网络请求,实际上这个请求只需要在第一次注册的时候请求就可以了。这个业务其实属于和第三方的业务,不在自己的业务里面,不需要做持久化,这些信息存储到redis中即可,每次从redis中读取和判断即可。

    方案确定了,接下来就是存储格式的设计了,毫无疑问这个场景的业务hash是非常适合存储的。那么如果我们把所有用户都放在一个hash中存储会是怎样的呢?首先在redis中开辟出一个key,然后把用户是否已经在腾讯服务器上注册过的标志,写入hash存储结构,最终的结构就是这样的:

    WechatIMG99.jpeg

    这样的存储,存在一个问题,就是内存浪费比较严重,而且用户数据其实是非常大的,hash中会存储非常非常多的field,导致hash表过长。那么有什么方法能够节省内存呢,网上找了下资料,可以利用key分段的方法,来做hash存储,方法如下:

    WechatIMG100.jpeg

    上面的设计是这样的:key=userId/100, field=userId%100,经过这样进行分段设计,可以大大节省服务器的使用内存,当然这也也有一个缺点,就是阅读不够直观,最终的代码是这样的:

       private final String imKey = "imUser:%s";
    
        public void setUserRegister(int userId) {
            String segmentKey = getSegmentHashKey(userId);
            String segmentField = getSegmentHashField(userId);
            String key = String.format(imKey, segmentKey);
            try {
                jedis.HASH.hset(key, segmentField, "1");
            } catch (Exception e) {
                logger.error("setUserRegister userId:" + userId + ",error:", e);
            }
        }
    
        public boolean isRegister(int userId) {
            String segmentKey = getSegmentHashKey(userId);
            String segmentField = getSegmentHashField(userId);
            String key = String.format(imKey, segmentKey);
            try {
                String value = jedis.HASH.hget(key, segmentField);
                return value != null;
            } catch (Exception e) {
                logger.error("isRegister,userId:" + userId + ",error:", e);
                return false;
            }
        }
    
        private final int segment = 500;
    
        private String getSegmentHashKey(int id) {
            return String.valueOf(id / segment);
        }
    
        private String getSegmentHashField(int id) {
            return String.valueOf(id % segment);
        }
    

    这样确实节约了内存使用量,但是平时的业务我们是不是应该都考虑用这种方式实现呢?从上述的代码中我们至少可以看到两个缺陷,首先是阅读不够直观,当线上出问题的时候,你可能要到redis中查询数据,结果一看傻了,看不懂,然后非常无奈。

    第二点则是过期时间,由于是分段存储的,过期时间则是整个key过期,但是有时候往往只需要某个field过期。

    所以分段设计虽然节约了大量内存,但是只有在海量的业务中才会去采用,如果是一般的公司业务,其实是不建议采用分段设计的。只有深入了解了其中的设计思想和应用场景,才能在平时做到游刃有余。

    相关文章

      网友评论

        本文标题:Redis分段设计

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