攻克Redis

基本使用
一些问题
  1. Redis中Hash结构的扩容机制是什么?

  2. Redis的部署有哪些方式?

  3. Redis是如何主从复制的?

  4. Redis中跳表的原理

    跳表的原理类似二分查找,但是数据结构用的链表,跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。

    创建索引会占据一定性能,在插入或者删除数据时,如果没插入或删除一个元素就重新创建索引又有点浪费,所以需要平衡一下,比如记录最底层的索引间的元素个数间隔,超过多少个再重新创建索引。

    二分查找算法之所以能达到 O(logn) 这样高效的一个重要原因在于它所依赖的数据结构是数组,数组支持随机访问一个元素,通过下标很容易定位到中间元素。而链表是不支持随机访问的,只能从头到尾依次访问。但是数组有数组的局限性,比如需要连续的内存空间,插入删除操作会引起数组的扩容和元素移动,链表有链表的优势,链表不需要先申请连续的空间,插入删除操作的效率非常高。所以有了跳表。

    Example:

    假如链表中有 n 个元素,我们每两个节点建立一个索引,

    那么第 1 级索引的结点个数就是 n/2 ,第二级就是 n/4,第三级就是 n/8, 依次类推,那么第k级索引的节点个数为 n 除以 2 的 k 次方,即 n/(2^k)。

    假设索引有 h 级,最高级的索引有 2 个结点。通过上面的公式,我们可以得到 n/(2^h) = 2,得到 h=log2n - 1,包含原始链表这一层的话,跳表的高度就是 log2n,假设每层需要访问 m 个结点,那么总的时间复杂度就是O(m*log2n)。而每层需要访问的 m 个结点,m 的最大值不超过 3,所以查找的时间复杂度为O(logn)。

    查询效率的提升,是拿空间换时间得到的。

    假设原始链表大小为 n,这几级索引的结点总和就是 n/2+n/4+n/8…+8+4+2=n-2。所以,跳表的空间复杂度是 O(n)。也就是说,如果将包含 n 个结点的单链表构造成跳表,我们需要额外再用接近 n 个结点的存储空间。在实际的软件开发中,原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值和几个指针,并不需要存储对象,所以当对象比索引结点大很多时,那索引占用的额外空间就可以忽略了。

    当我们往跳表中插入数据的时候,通过一个随机函数,来决定将这个结点插入到哪几级索引中,比如随机函数生成了值 K,那我们就将这个结点添加到第一级到第 K 级这 K 级索引中。

    **跳表是一种动态数据结构,支持快速的插入、删除、查找操作,时间复杂度都是 O(logn)。跳表的空间复杂度是 O(n)。不过,跳表的实现非常灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗。

  5. Redis底层数据结构

  6. Redis的新版本的一些功能以及对于redis配置文件,一般需要通过配置文件来解决问题

  7. Redis大key问题

    1. 内存占用多、流量大,比如一次取走100K的数据,当QPS为1000时,就会产生100M/s的流量

    2. 如果为list,hash等数据结构,大量的elements需要多次遍历,多次系统调用拷贝数据消耗时间

    3. 主动删除、被动过期删除、数据迁移等,由于处理这一个KEY时间长,导致服务端发生阻塞

    4. 解决方案:对于需要整取value的key,可以尝试将对象分拆成几个key-value, 使用multiGet获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多个实例中,降低对单个实例的IO影响;每次需要取部分value的key,同样可以拆成几个key-value,也可以将这些存储在一个hash中,每个field代表具体属性,使用hget,hmget来获取部分value,使用hset,hmset来更新部分属性;

   #命令 每隔 100 条 scan 指令就会休眠 0.1s,ops 就不会剧烈抬升,但是扫描的时间会变长。
   
   redis-cli  --bigkeys -i 0.1
  1. redis中调用lua脚本:http://doc.redisfans.com/script/index.html

    set name xkx
    // 通过lua脚本来获取值
    EVAL "return redis.call('get','name')" 0
    

    EVAL直接对输入的脚本代码体(body)进行求值

    EVALSHA要求输入某个脚本的 SHA1 校验和, 这个校验和所对应的脚本必须至少被 EVAL 执行过一次

    EVALSHA 命令的目的就是减少带宽,因为执行过的脚本在服务端会有缓存,可以直接通过传递摘要值进行调用脚本,以此来减少带宽,也可以通过命令来清除缓存SCRIPT FLUSH或者判断脚对应的脚本是否在服务端存在SCRIPT EXISTS,可以通过SCRIPT LOAD 将脚本加载到服务端的缓存中,但是并不会执行。

    可以通过SCRIPT KILL杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效;假如当前正在运行的脚本已经执行过写操作,那么即使执行 SCRIPT KILL,也无法将它杀死,因为这是违反 Lua 脚本的原子性执行原则的。在这种情况下,唯一可行的办法是使用 SHUTDOWN NOSAVE 命令,通过停止整个 Redis 进程来停止脚本的运行,并防止不完整(half-written)的信息被写入数据库中。

  2. showlog

    与其他任意存储系统例如mysql,mongodb可以查看慢日志一样,redis也可以,即通过命令slowlog。

    #SLOWLOG subcommand [argument]
    
    #subcommand主要有:
    
    #get,用法:slowlog get [argument],获取argument参数指定数量的慢日志。
    #len,用法:slowlog len,总慢日志数量。
    #reset,用法:slowlog reset,清空慢日志。
    
    redis-cli slowlog get 5
    
  3. rename-command

# 为了防止把问题带到生产环境,我们可以通过配置文件重命名一些危险命令,
# 例如keys等一些高危命令。操作非常简单,
#只需要在conf配置文件增加如下所示配置即可:

rename-command flushdb flushddbb

rename-command flushall flushallall

rename-command keys keysys
一些常用的命令
  • scan

每次执行都只会返回少量元素, 所以这些命令可以用于生产环境, 而不会出现像 KEYS SMEMBERS命令带来的问题 —— 当 KEYS 命令被用于处理一个大的数据库时, 又或者 SMEMBERS 命令被用于处理一个大的集合键时, 它们可能会阻塞服务器达数秒之久。

0 作为游标开始一次新的迭代, 一直调用 SCAN 命令, 直到命令返回游标 0 , 我们称这个过程为一次完整遍历(full iteration)。

# 命令的游标参数被设置为 0 时, 服务器将开始一次新的迭代, 而当服务器向用户返回值为 0 的游标时, 表示迭代已结束。
scan 0

scan 288 MATCH *11*

scan 176 MATCH *11* COUNT 1000

下面三个命令的第一个参数总是一个数据库键。

SSCAN命令用于迭代集合键中的元素。

HSCAN命令用于迭代哈希键中的键值对。

ZSCAN命令用于迭代有序集合中的元素

  • type

  • object 命令允许从内部察看给定 key 的 Redis 对象

好玩的
不可结缘--[夏目友人帐]
相关介绍
参考

> 可在下面留言(需要有 GitHub 账号)