redis常见问题(一)

  • 主流应用架构

    1557059387535

  • redis和memcache的区别

    • memcache

      1. 支持简单数据类型
      2. 不支持数据持久化存储
      3. 不支持主从
      4. 不支持分片
    • Redis

      1. 数据类型丰富
      2. 支持数据磁盘持久化存储
      3. 支持主从
      4. 支持分片
  • redis为什么这么快?——10万+QPS(query per second,每秒查询次数)

    • 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高
    • 数据结构简单,对数据操作也简单
    • 采用单线程,单线程也能处理高并发请求,想多核也可以启动多实例
    • 使用多路I/O复用模型,非阻塞IO
  • redis采用的I/O多路复用函数:epoll/kqueue/evport/select?

    • 因地制宜
    • 优先选择时间复杂度为O(1)的I/O多路复用函数作为底层实现
    • 以时间复杂度为O(n)的select作为保底
    • 基于react线程模型监听I/O事件
  • redis的数据类型

    • String:最基本的数据类型,二进制安全
    • Hash:String元素组成的字典,适合用于存储对象
    • List: 列表,按照String元素插入顺序排序(实现最新消息排行榜)
    • Set:String元素组成的无序集合,通过哈希表实现,不允许重复
    • Sorted Set(ZSet):通过分数来为集合中的成员进行从小到大的排序
    • 用于计数的HyperLogLog,用于支持存储地理位置信息的Geo

1557059415359

  • 从海量Key里查询出某一固定前缀的Key

    1. KEYS pattern:查找所有符合给定模式pattern的key

      • KEYS指令一次性返回所有匹配的key
      • 健的数量过大会使服务器卡顿
    2. SCAN cursor [MATCH pattern] [COUNT count]

      • 基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程
      • 以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历
      • 不保证每次执行都返回某个给定数量的元素,支持模糊查询
      • 一次返回的数量不可控,只能是大概率符合count参数
    • 模拟海量数据查询,往redis中灌入2千万的数据

      通过编写Shell脚本来注入数据:

      批量生成redis测试数据

      1. Linux Bash下面执行 for((i=1;i<=20000000;i++)); do echo "set k$i v$i" >> /tmp/redisTest.txt ;done; 生成2千万条redis批量设置kv的语句(key=kn,value=vn)写入到/tmp目录下的redisTest.txt文件中
      2. 用vim去掉行尾的^M符号,使用方式如下:: vim /tmp/redisTest.txt :set fileformat=dos #设置文件的格式,通过这句话去掉每行结尾的^M符号 ::wq #保存退出
      3. 通过redis提供的管道--pipe形式,去跑redis,传入文件的指令批量灌数据,需要花10分钟左右 cat /tmp/redisTest.txt | 路径/redis-5.0.0/src/redis-cli -h 主机ip -p 端口号 --pipe

1557063640950

scan指令每次游标迭代出的结果可能会重复,所以需要代码进行筛选去重(利用set)

  • redis实现分布式锁

分布式锁需要解决的问题:

  1. 互斥性:任意时刻只能有一个客户节点获得锁

  2. 安全性:只能由获得锁的节点释放锁

  3. 死锁:获得锁的节点由于某些原因突然宕机,其他节点永远都拿不到锁,从而导致死锁

  4. 容错:部分节点宕机,客户节点仍能获得锁和释放锁

    • 不好的方案:SETNX key value:如果key不存在,则创建并赋值

      • 如何解决SETNX长期有效的问题:EXPIRE key seconds

        • 设置key的生存时间,当key过期时,会被自动删除
        • 原子性得不到满足
      Jedis jedisPool = new JedisPool(config,"localhost",6379,2000);
      long status = jedisPool.setnx(key,"1");
      if(status == 1){
          jedisPool.expire(key,expire);
       //执行独占资源逻辑
          doSomething();
      }
      

      上述伪代码是有问题的,因为如果执行到setnx操作后该节点宕机,还没有设置expire时间,将会导致其他节点再也拿不到锁,导致死锁。(不具有原子性)

    • 好的解决方案:SET key value [EX seconds] [px milliseconds] [NX|XX]

    1557066641308

    Jedis jedisPool = new JedisPool(config,"localhost",6379,2000);
    String result = jedisPool.set(lockKey,requestId,SET_IF_NOT_EXIST,
                                  SET_WITH_EXPIRE_TIME,expireTime);
    if("OK".equals(result)){
         //执行独占资源逻辑
         doSomething();
    }
    
  • 大量Key同时过期,由于清除大量的Key很耗时,会出现短暂的卡顿现象,如何解决

    • 解决方案:在设置key的过期时间时,给每个key加上一个随机值