美文网首页
Redis对象(三) - 其它特性

Redis对象(三) - 其它特性

作者: 牛牛_735d | 来源:发表于2020-04-29 16:25 被阅读0次

    类型检查和多态命令的实现

    redis中用于键操作的命令基本上可以分为两类:

    1. 可以对任何类型的键执行, eg. del, expire, rename, type, object

    2. 只能对特定命令执行的键,

      eg. setgetappendstrlen 等命令只能对字符串键执行

      hdelhsethgethlen 等命令只能对hash键执行

      rpushlpoplinsertllen等只能对列表键执行

      saddspopsinterscard等命令只能对集合键执行

      zaddzcardzrankzcore 等命令只能对有序集合键执行

    类型检查的实现

    类型特定命令所进行的类型检查是通过redisObject结构的type属性 来实现的.

    1. 在执行一个类型特定命令之前, 服务器先检查输入数据库键的值对象是否为执行命令所需要的类型, 是、就执行
    2. 否则, server拒绝执行、并向client返回一个类型错误

    eg. 对于llen命令:

    在执行llen命令前、server会先检查输入数据库键的值对象是否为列表类型, 即: 检查redisObjecttype属性是否为redis_list, 是的话、执行 llen命令, 否则返回类型错误

    多态命令的实现

    Redis除了会根据值对象的类型来判断是否能执行特定命令外、还会根据值对象的编码方式、选择正确的命令实现代码来执行命令

    eg. 对一个键执行 llen命令, 则服务器除了要确保执行命令的是列表键之外, 还要根据键的值对象所使用的编码来选择正确的llen命令实现

    1. 若列表对象的编码为 ziplist, 那么说明列表对象的实现为压缩列表, 程序将使用 ziplistLen 函数来返回列表的长度
    2. 若列表对象的编码为 linkedlist, 说明列表对象的实现为双端链表, 程序将使用 listLength 函数来返回列表的长度

    借用面向对象的术语来说、可以认为llen命令的实现是多态的, 只要执行 llen 命令的是列表键、无论值对象是 ziplist 还是 linkedlist 编码、命令都可以正常执行

    delexpire等命令和llen命令的区别在于、前者是基于类型的多态, 一个命令可以同时处理多种不同类型的键、而后者是基于编码的多态: 一个命令可以同时用于处理多种不同的编码

    内存回收

    因为C语言并不具备内存回收功能, redis 在自己的对象系统中构建了一个引用计数(reference counting) 技术来实现内存回收机制, 通过引用计数机制、程序可以通过跟踪对象的引用计数信息、在适当的时候自动释放对象并进行内存回收

    每个对象的引用计数信息由 RedisObject 结构的 refcount属性记录:

    typedef struct redisObject {
      // ...
      int refcount; // 引用计数
      // ...
    } robj;
    

    对象的引用技术信息会随着对象的使用状态不断变化

    1. 创建一个新的对象时、引用计数初始化为1
    2. 对象被一个新的程序引用时、引用计数值 +1
    3. 对象不再被一个程序引用时、引用计数值 -1
    4. 对象的引用计数值变为0时、对象所占用的内存会被释放

    下边是修改对象引用计数的API

    函数 作用
    incrRefCount 将对象的引用计数值+1
    decrRefCount 将对象的引用计数值-1, 当对象的引用计数值=0时、释放对象
    resetRefCount 将对象的引用计数值设为0, 但不释放对象、需要重设对象引用值是使用

    其它不同类型的对象也会经历类似的过程

    共享对象

    除了实现引用计数内存回收机制外、对象的引用计数属性还带有对象共享的作用.

    eg. A键创建了一个包含整数值100的字符串对象作为值对象, 此时若B键也想要创建一个同样保存了整数值100的字符串对象作为值对象、那么Server有两种做法:

    1. 为键B创建一个包含整数值100的字符串对象
    2. 让键A和键B共享同一个字符串对象

    明显, 第二种方式更节约内存, 在Redis中、多个键共享同一个值需要执行以下步骤:

    • 将数据库键的值指向一个现有的值对象
    • 将被共享的值对象的引用计数+1

    **注意: **

    创建共享字符串对象的数量可以通过修改 redis.h/redis_shared_integers 常量来修改

    eg, 创建一个值为100的键a, 使用object refcount 命令查看a的引用计数, 会发现值为2

    redis> set a 100
    OK
    redis> object refcount a
    (integer) 2
    

    引用这个值对象的两个程序分表是持有这个值对象的服务器程序, 及共享这个值对象的键A

    另外: 这些共享对象不单单只有字符串键可以使用, 那些在数据结构中嵌套了字符串对象的对象(linkedlist编码的列表对象、hashtable编码的hash对象、hashtable编码的集合对象及zset编码的有序集合对象)等都可以使用这些共享对象

    思考

    为什么redis不共享包含字符串的对象?

    当服务器考虑将一个共享对象设置为键的值对象时、程序需要检查给定的共享对象和键想创建的目标对象是否完全相同, 只有在共享对象和目标对象完全相同的情况下、程辉才会将共享对象的用作键的值对象、而一个共享对象保存的值越复杂、验证两者相同的复杂度就会越高, 消耗的CPU时间也会越多

    • 若共享对象保存整数值的字符串对象、那么验证操作的复杂度为 O(1)
    • 若共享对象是保存字符串值的字符串对象、那么验证操作的复杂度为 O(N)
    • 若共享对象是包含了多个值(或者对象)的对象, 比如列表对象或者hash对象、验证的复杂度将是O(N²)

    因此、尽管共享更复杂的对象可以节约更多内存、但受到CPU时间的限制、redis只对包含整数值的字符串对象进行共享

    对象的空转时长

    除了前边介绍过的typeencodingptrrefcount 4个属性外, redisObject结构包含的最后一个属性为 lru属性, 它记录了对象最后一次被命令访问的时间

    typedef struct redisObject {
      // ...
      unsigned lru:22;
      // ...
    } robj;
    

    object idletime 命令可以打印出给定键的空转时长, 就是通过当前时间 - 键的lru时间得到的

    注意:

    Object idletime的实现是特殊的, 它在访问键的时候、不会修改值对象的lru属性

    除了使用 命令打印键的空转时长, lru属性还用于回收内存, 当设置了 maxmemory 选项, 且服务器用于回收内存的算法为 volatile-lru 或者 allkeys-lru 时、当服务器占用内存超过了 maxmemory设置的上限值时, 空转时长较高的键会优先被服务器释放.

    重点回顾

    1. redis数据库的中每个键值对的键和值都是一个对象
    2. redis共有字符串、列表、hash、结合、有序集合五种类型的对象, 每种类型的对象至少有2种或以上的编码方式, 不同的编码可以在不同的场景上优化对象的使用概率
    3. 服务器在执行某些命令之前、会先检查给定键的类型能否执行
    4. redis的对象系统带有引用计数实现的内存回收机制, 当一个对象不再被使用时、该对象占用的内存会被自动释放
    5. redis会共享值为 0 到 9999 的字符串对象
    6. 对象会记录自己最后一次被访问的时间, 这个时间还可以用于计算对象的空转时长

    相关文章

      网友评论

          本文标题:Redis对象(三) - 其它特性

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