美文网首页
Redis挖掘机(一)基础必备

Redis挖掘机(一)基础必备

作者: 进击的阿黑 | 来源:发表于2019-11-12 09:14 被阅读0次

Redis的特点

  • 基于内存的数据库,读取快
  • 单线程模型,减少了线程开销(上下文切换、竞争)的性能损耗
  • 使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。

数据结构

  • 字符串String

  • 字典Hash

    内部结构:数组+链表,相当于HashMap,是无序字典。但是redis只能存储字符串,redis为了高性能,它的扩容方式为渐进式rehash策略,不同于JAVA的HashMap为一次性全部rehash。

    渐进式rehash会在rehash的同时,保留新旧两个hash结构。然后在后续的定时任务中以及 hash 操作指令中,循序渐进地将旧 hash 的内容一点点迁移到新的 hash 结构中。当搬迁完成了,就会使用新的hash结构取而代之。

    用途:存储用户信息。hash可以对一个结构体中单个或多个字段进行存储,这样当需要获取用户信息时就可以部分获取。而用字符串存储用户信息的话,需要一次性获取,浪费流量。

    注意:当 hash 移除了最后一个元素之后,该数据结构自动被删除,内存被回收。

    缺点:hash结构的存储消耗要大于字符串

    主要方法:hset、hget、hgetall、hlen、hmset、hincrBy、hkeys、hvals、hdel、hexists

        @Test
      public void test() {
          // 将哈希表 key 中的字段 field 的值设为 value 。
          hashRedisTemplate.hset("Jerry", "sex", "男");
          // 获取在哈希表中指定 key 的所有字段和值
          System.out.println(hashRedisTemplate.hgetAll("Jerry"));
          // 获取存储在哈希表中指定字段的值。
          System.out.println(hashRedisTemplate.hget("Jerry", "sex"));
          //output: {sex=男}
          //output: 男
    
          Map<String, String> map = Maps.newHashMap();
          map.put("age", "24");
          map.put("like", "basketball");
          map.put("sex", "男");
          // 同时将多个键值对设置到哈希表 key 中。
          hashRedisTemplate.hmset("Jim", map);
          // 获取所有给定字段的值
          List<String> list = hashRedisTemplate.hmget("Jim", "age", "like", "sex");
          System.out.println(list);
          //output: [24, basketball, 男]
    
          // 
          Map<String, String> map1 = hashRedisTemplate.hgetAll("Jim");
          System.out.println(map1);
          //output: {like=basketball, age=24, sex=男}
    
          // 为哈希表 key 中的指定字段age的整数值加上增量1
          Long newAge = hashRedisTemplate.hincrBy("Jim", "age", 1)
          System.out.println(newAge);
          //output: 25
      }
    

    由于哈希表是一个二维结构,第一维是数组,第二维是链表,它可能会遇到

    1. 哈希表中的箱子比较多的情况下扩容会重新rehash,移动数据,性能影响极大
    2. 哈希函数设计不合理,在极端情况下会变成线性表,性能极低。

    Java和Redis对于以上哈希表问题的解决方案?

    • Java主要是在哈希函数不合理而导致链表过长时,会使用红黑树来保证插入和查询的效率,但是当遇到哈希表比较大需要扩容时,扩容会导致瞬时效率降低,因为是一次性扩容。而由于它有一个初识容量的设定,所以可以通过设置合适的初识容量,来预防rehash的操作。
    • Redis主要是有一个默认的哈希函数可以有效避免链表过长;当遇到哈希表扩容时,会采用渐进式扩容策略的方式,来提高效率。
  • 列表List

    内部结构是链表,相当于LinkedList,插入和删除的时间复杂度为O(1),查询的时间复杂度为O(n)

    用途:当做异步队列使用。将需要延后处理的数据序列化成字符串塞进列表,另一个线程轮询该列表获取数据进行处理

    注意:当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

    主要方法:rpush、lpush、rpop、lpop、lindex、lrange

    获取索引范围内的数据

    @Test
    public void test() {
        // 批量添加多个元素到列表尾部
        listRedisTemplate.rpush("name1", "Jerry", "Tom", "Tony");
        // 获取索引范围内的数据
        System.out.println(listRedisTemplate.lrange("name1", 1, 2));
    //  output: [Tom, Tony]     
    }
    

    根据索引获取列表数据

    @Test
    public void test() {
        // 批量添加多个元素到列表尾部
        listRedisTemplate.rpush("name1", "Jerry", "Tom", "Tony");
        // 根据索引获取列表数据
        System.out.println(listRedisTemplate.lindex("name1", 2));
    //  output: Tony    
    }
    

    队列:左进右出

    @Test
    public void test() {
        // 批量添加多个元素到列表头部
        listRedisTemplate.lpush("name", "Jerry", "Tom", "Tony");
        while (true) {
            // 移除并返回列表的最后一个元素
            String result = listRedisTemplate.rpop("name");
            System.out.println(result);
            if(Strings.isNullOrEmpty(result)) break;
    //      output: Jerry
    //      output: Tom
    //      output: Tony
    //      output: null
        }
        
    }
    

    栈:右进右出

    @Test
    public void test() {
        // 批量添加多个元素到列表尾部
        listRedisTemplate.rpush("name", "Jerry", "Tom", "Tony");
        while (true) {
            // 移除并返回列表的最后一个元素
            String result = listRedisTemplate.rpop("name");
            System.out.println(result);
            if(Strings.isNullOrEmpty(result)) break;
    //      output: Tony
    //      output: Tom
    //      output: Jerry
    //      output: null
        }
    }
    
  • 集合Set

    内部结构:相当于一个特殊字典Hash,value都是null,内部的键值对都是无序且唯一。相当于JAVA的HashSet

    用途:可以存储抽奖活动的中奖用户ID

    注意:当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。

    主要方法:sadd、spop、smembers(获取集合内所有元素)、sismember(是否存在)、scard(获取集合元素个数)

    @Test
    public void test() {
        // 批量添加元素
        setRedisTemplate.sadd("nickName", "Jerry", "Tom", "Jim");
        // 获取集合内元素个数
        System.out.println(setRedisTemplate.scard("nickName"));
        // 集合内是否存在某个元素
        System.out.println(setRedisTemplate.sismember("nickName", "Tony"));
        // 获取集合内所有元素
        System.out.println(setRedisTemplate.smembers("nickName"));
        // 弹出一个元素
        System.out.println(setRedisTemplate.spop("nickName"));
        // 弹出指定3个元素
        System.out.println(setRedisTemplate.spop("nickName", 3));
        //output: 3
        //output: false
        //output: [Jim, Tom, Jerry]
        //output: Jim
        //output: [Tom, Jerry] (由于已经被推出1个元素,所以仅剩2个元素)
    }
    
  • 有序集合SortedSet

    内部结构:相当于JAVA的SortedSetheHashMap的结合。既能保存元素的唯一性,也能给元素添加一个score,代表这个元素的排序权重。内部实现是基于跳跃列表

    用途:存储按时间排序的粉丝列表,即粉丝:粉丝名称+粉丝关注时间。

    注意:最后一个 value 被移除后,数据结构自动删除,内存被回收。

    主要方法:zcard、zadd、zscore、zrank、zrem、zincrby、zrange、zrevrange、zrangeByScore等

    @Test
    public void test() {
        // 向有序集合添加一个元素
        sortedSetRedisTemplate.zadd("scores", 100.0, "Jerry");
        sortedSetRedisTemplate.zadd("scores", 70.0, "Tom");
        sortedSetRedisTemplate.zadd("scores", 50.0, "Jim");
        // 获取有序集合元素个数
        System.out.println(sortedSetRedisTemplate.zcard("scores"));
        // 根据索引区间按score顺序输出
        Set<String> members = sortedSetRedisTemplate.zrange("scores", 0, -1);
        System.out.println(members);
        // 根据索引区间按score逆序输出
        members = sortedSetRedisTemplate.zrevrange("scores", 0, -1);
        System.out.println(members);
        // 根据分数区间按score顺序输出
        members = sortedSetRedisTemplate.zrangeByScore("scores", 40.0, 80.0);
        System.out.println(members);
        // 根据元素获取索引值,即排名
        System.out.println(sortedSetRedisTemplate.zrank("scores", "Jerry"));
        // 根据元素获取score
        System.out.println(sortedSetRedisTemplate.zscore("scores", "Jerry"));
        // 删除元素
        sortedSetRedisTemplate.zrem("scores", "Jerry", "Jim");
        // 重新获取有序集合元素个数
        System.out.println(sortedSetRedisTemplate.zcard("scores"));
        //output: 3
        //output: [Jim, Tom, Jerry]
        //output: [Jerry, Tom, Jim]
        //output: [Jim, Tom]
        //output: 2
        //output: 100.0
        //output: 1
    }
    
  • HyperLogLog

    内部结构:-

    特点:

    • 提供不精确去重计数方案,标准误差是0.81%

    • 每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。相对于用Set集合元素越多,耗费内存也越多,优势明显。

    • 计数较小时,存储空间采用稀疏矩阵存储,空间占用很小。当超过一定阈值时,会一次性转变为稠密矩阵,才会占用12k的内存

    用途:做基数统计。比如统计一个网站的UV。

    如何实现使用较小的内存去实现对大数据的去重计数?

    1. 使用HashSet或redis的set集合,数据量太大会导致内存占用大,分配不足会导致内存泄漏

    2. 使用位图法。位图法所需的空间很少,像JAVA的BitSet就是使用了位图法,底层是基于long[]实现,long是8字节存储的,所以BitSet最小是64位。它并不存储每一个数据,而是对每一个数据做了一个标志(1表示存在,0表示不存在),来实现去重。

    也可以自己实现一个BitMap,参考

    public static int getUniqueNum(int[] array) {
        if(null == array || array.length == 0) array = new int [] {1, 0, 5, 1, 8, 10, 5, 20};
        BitSet bitSet  = new BitSet(1);
        System.out.println(bitSet.size());   //64
    
        for(int i=0;i<array.length;i++)
        {
            // 将指定索引处的位设置为 true
            bitSet.set(array[i], true);
        }
        int uv = bitSet.cardinality();
        System.out.println("去重结果" + uv);
        return uv;
    }
    
    1. 使用HyperLogLog,提供了不精确的去重计数方案,误差在0.81%

容器型数据结构有哪些?

list/set/hash/sortedSet 这四种。容器型数据结构遵循以下规则:

  1. create if not exists
  2. drop if no elements

过期时间设置

​ 以上所有的数据结构均可设置过期时间,只要在set添加后,设置expire时间即可

​ 注意:当设置了过期时间后,继续调用set方法修改了它,会使之前设置的过期时间失效

@Test
public void test() {
    stringRedisTemplate.set("name", "Jerry");
    System.out.println(stringRedisTemplate.get("name"));
    stringRedisTemplate.expire("name", 5);
    try {
        Thread.sleep(5 * 1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(stringRedisTemplate.get("name"));
    //output: Jerry
    //output: null
}

以上是非原子性操作的过期时间设置

来一份面试题:

天下无难试之Redis面试题刁难大全

相关文章

网友评论

      本文标题:Redis挖掘机(一)基础必备

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