zoukankan      html  css  js  c++  java
  • Redis学习笔记1-Redis数据类型

    Redis数据类型

    最为常用的数据类型主要有五种:String, Hash, List, Set和SortedSet. Redis内部使用一个redisObject对象来表示所有的key和value。
    redisObject 最主要的信息如下:
    typedef struct redisObject {
        unsigned [type] 4;
        unsigned [encoding] 4;
        unsigned [lru] REDIS_LRU_BITS;
        int refcount;
        void *ptr;
    } robj;
    简单介绍一下这几个字段:
    • type:数据类型,就是我们熟悉的string、hash、list等。
    • encoding:内部编码,其实就是本文要介绍的数据结构。指的是当前这个value底层是用的什么数据结构。因为同一个数据类型底层也有多种数据结构的实现,所以这里需要指定数据结构。
    • REDIS_LRU_BITS:当前对象可以保留的时长。这个我们在后面讲键的过期策略的时候讲。
    • refcount:对象引用计数,用于GC。
    • ptr:指针,指向以encoding的方式实现这个对象的实际地址。 
    下面介绍五种数据类型 每种数据类型内部都采用至少2种数据结构。
     
    举个例子,在列表对象包含的元素比较少时,Redis使用压缩列表作为列表对象的底层实现: ·因为压缩列表比双端链表更节约内存,并且在元素数量较少时,在内存中以连续块方式保存的压缩列表比起双端链表可以更快被载入到缓存中; ·随着列表对象包含的元素越来越多,使用压缩列表来保存元素的优势逐渐消失时,对象就会将底层实现从压缩列表转向功能更强、也更适合保存大量元素的双端链表上面; 其他类型的对象也会通过使用多种不同的编码来进行类似的优化。 在接下来的内容中,我们将分别介绍Redis中的五种不同类型的对象,说明这些对象底层所使用的编码方式,列出对象从一种编码转换成另一种编码所需的条件,以及同一个命令在多种不同编码上的实现方法。

    Redis支持5种数据类型,它们描述如下:

     

     

    Strings - 字符串

        Redis没有采用原生C语言的字符串类型而是自己实现了字符串结构,内部简单动态字符串(simple dynamic string,SDS)。结构如图
        
    • 在Redis内部,字符串对象的编码可以是int、(动态字符串)raw或者embstr。
    如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw。
     
    embstr编码是专门用于保存短字符串的一种优化编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中依次包含redisObject和sdshdr两个结构:
    • Redis会根据存储的数据及用户的操作指令自动选择合适的结构:
    • int:存放整数类型;
    • SDS:存放浮点、字符串、字节类型;
    有个问题Redis字符串与C字符串区别 或者说2.2 SDS与C字符串的区别
    C:因为C字符串并不记录自身的长度信息,所以为了获取一个C字符串的长度,程序必须遍历整个字符串,对遇到的每个字符进行计数,直到遇到代表字符串结尾的空字符为止,这个操作的复杂度为O(N)。
    SDS和C字符串不同,因为SDS在len属性中记录了SDS本身的长度,所以获取一个SDS长度的复杂度仅为O(1)。
     
    SDS: 简单动态字符串 simple dynamic string
    SDS
    可见,其底层是一个char数组。buf最大容量为512M,里面可以放字符串、浮点数和字节。所以你甚至可以放一张序列化后的图片。它为什么没有直接使用数组,而是包装成了这样的数据结构呢?
    因为buf会有动态扩容和缩容的需求。如果直接使用数组,那每次对字符串的修改都会导致重新分配内存,效率很低。
    buf的扩容过程如下:
    • 如果修改后len长度将小于1M,这时分配给free的大小和len一样,例如修改过后为10字节, 那么给free也是10字节,buf实际长度变成了10 + 10 + 1 = 21byte
    • 如果修改后len长度将大于等于1M,这时分配给free的长度为1M,例如修改过后为30M,那么给free是1M.buf实际长度变成了30M + 1M + 1byte

    惰性空间释放指的是当字符串缩短时,并没有真正的缩容,而是移动free的指针。这样将来字符串长度增加时,就不用重新分配内存了。但这样会造成内存浪费,Redis提供了API来真正释放内存。
    补充简单动态字符串:
    redis自定义了简单动态字符串数据结构(sds),并将其作为默认字符串表示。
    struct SDS<T> {
    T capacity; // 数组容量
    T len; // 数组长度
    byte flags; // 特殊标识位,不理睬它
    byte[] content; // 数组内容
    }
    因为使用len表示当前字符串长度,capacity表示内存分配空间,当往sds字符串中添加过多字符(len达到capacity大小),则会触发扩容,在字符串长度大小小于1M时,扩容策略为成倍扩容;大于1M时,每次新增1M空间,避免空间浪费。
    比如执行如下命令 redis> set name Redis,Redis将在数据库中创建一个新的键值对,其中键是一个字符串,一个保存着"name"的sds;值是一个字符串,一个保存着"Redis"的sds。使用sds作为字符串存储结构,有以下优势:
    •O(1)复杂度获取字符长度•避免缓冲区溢出•减少修改字符操作时引起的内存分配次数•二进制安全的•兼容部分C字符串函数(因为字符串后面以''结尾)
     
    问题:为什么不使用c语言的字符串呢?这么实现有什么好处?
    • 快速获取字符串的长度
    • C语言获取字符串的长度是通过遍历字符数组然后计数实现的,时间复杂度为O(n),对于SDS,由于len的存在,我们只需要读取len的值即可,时间复杂度为O(1)。
    • 解决了c语言字符串拼接缓冲区溢出的问题
    • 在c语言中。俩个字符串的拼接通过使用stract函数来完成,在拼接之前需要定义一个足够大的数组用来存在拼接好的字符串,如果没有分配足够长度的内存空间,那么拼接好的字符串放不进去。就会造成缓存区溢出,对于SDS,在进行拼接的时候,会首先查看free和len的情况,在拼接的时候通过len获取到字符串的长度,free获取到字符数组的剩余空间,可以做到精确的拼接操作
    • 修改字符串减少内存分配次数
    • 在c语言中。因为字符串的长度没有记录,所以每次在修改字符串的时候都需要重新开辟合适的内存,为什么要开辟呢??
    • 因为如果不开辟新的内存大小,字符串增大会造成内存溢出,缩短会造成内存泄漏,
     
    对于SDS,由于len和free的存在,对于修改字符串SDS实现了空间预分配和惰性空间释放两种策略
    • 空间预分配:对字符串进行空间扩展的时候,扩展的内存比实际需要的多,这样可以减少连续执行字符串增长操作所需的内存重分配次数。
    • 惰性空间释放:对字符串进行缩短操作时,程序不立即使用内存重新分配来回收缩短后多余的字节,而是使用 free 属性将这些字节的数量记录下来,等待后续使用。(当然SDS也提供了相应的API,当我们有需要时,也可以手动释放这些未使用的空间。)
    • 二进制安全。
    • c语言是二进制不安全的,因为c语言是以空字符来作为字符串结束的标记,像图片,音乐这些文件,内容中间可能包含空字符串,所以c语言只能存取一些文本文件,但是对于SDS而言,它是通过len的长度来记录字符串的长度的,所以SDS可以存取图片或者音乐等二进制文件。
    1 redis 127.0.0.1:6379> SET name "hello"
    2 OK
    3 redis 127.0.0.1:6379> GET name
    4 "hello"

     

    列表 (Lists)

    Redis 列表仅仅是按照插入顺序排序的字符串列表。可以添加一个元素到 Redis 列表的头部 (左边) 或者尾部 (右边)。

    LPUSH 命令用于插入一个元素到列表的头部,RPUSH 命令用于插入一个元素到列表的尾部。当这两个命令操作在一个不存在的键时,将会创建一个新的列表。同样,如果一个操作会清空列表,那么该键将会从键空间 (key space) 移除。这些是非常方便的语义,因为列表命令如果使用不存在的键作为参数,就会表现得像命令运行在一个空列表上一样。

     1 redis 127.0.0.1:6379> lpush listtest test1
     2 (integer) 1
     3 redis 127.0.0.1:6379> lpush listtest test2
     4 (integer) 2
     5 redis 127.0.0.1:6379> lpush listtest test3
     6 (integer) 3
     7 redis 127.0.0.1:6379> lrange listtest 0 -1
     8 
     9 1 "test1"
    10 2 "test2"
    11 3 "test3"

     

    集合 (Sets)

    Redis 集合是没有顺序的字符串集合 (collection)。可以在 O(1) 的时间复杂度添加、删除和测试元素存在与否 (不管集合中有多少元素都是常量时间)。

    Redis 集合具有你需要的不允许重复成员的性质。多次加入同一个元素到集合也只会有一个拷贝在其中。实际上,这意味着加入一个元素到集合中并不需要检查元素是否已存在。

    Redis 集合非常有意思的是,支持很多服务器端的命令,可以在很短的时间内和已经存在的集合一起计算并集,交集和差集。

    redis 127.0.0.1:6379> sadd setdemo set1
    (integer) 1
    redis 127.0.0.1:6379> sadd setdemo set2
    (integer) 1
    redis 127.0.0.1:6379> smembers setdemo
    
    1) "set1"
    2) "set2"

     

    Hashes - 哈希值

    Redis的哈希键值对的集合。 Redis的哈希值是字符串字段和字符串值之间的映射,所以它们被用来表示对象。

    redis 127.0.0.1:6379> HMSET user:1 username testname password 123456 
    OK
    redis 127.0.0.1:6379> HGETALL user:1
    
    1) "testname"
    2) "123456"

    有序集合 (Sorted sets)

    Redis 有序集合和 Redis 集合类似,是非重复字符串集合 (collection)。不同的是,每一个有序集合的成员都有一个关联的分数 (score),用于按照分数高低排序。尽管成员是唯一的,但是分数是可以重复的。

    对有序集合我们可以通过很快速的方式添加,删除和更新元素 (在和元素数量的对数成正比的时间内)。由于元素是有序的而无需事后排序,你可以通过分数或者排名 (位置) 很快地来获取一个范围内的元素。访问有序集合的中间元素也是很快的,所以你可以使用有序集合作为一个无重复元素,快速访问你想要的一切的聪明列表:有序的元素,快速的存在性测试,快速的访问中间元素!

    总之,有序集合可以在很好的性能下,做很多别的数据库无法模拟的事情。

    redis 127.0.0.1:6379> zadd list 0 name1
    (integer) 1
    redis 127.0.0.1:6379> zadd list 0 name2
    (integer) 1
    redis 127.0.0.1:6379> ZRANGEBYSCORE list 0 1000
    
    1) "name1"
    2) "name2"
  • 相关阅读:
    linux字符设备文件的打开操作
    Linux用ps命令查找进程PID再用kill命令终止进程的方法
    Linux内核锁与中断处理
    写给大数据开发初学者的话
    zabbix监控系统客户端安装
    详解zabbix安装部署(Server端篇)
    Keepalived+Nginx架构整理版
    Nginx + Tomcat 动静分离实现负载均衡
    五个常用的Linux监控脚本代码
    16个Linux服务器监控命令
  • 原文地址:https://www.cnblogs.com/houziwty/p/5105955.html
Copyright © 2011-2022 走看看