美文网首页
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