Redis命令查询

 

目录

String

List

Set

Hash

Sorted Set


String

String是Redis最基本的类型,可以理解成与Memcached一模一样的类型,一个key对应一个value。

String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。(二进制安全指只关心二进制化的字符串,不关心具体格式,不会对某种字符组合给予特殊含义,比如C语言的  \0  可作为字符串结尾标识,那么C语言就不是二进制安全的)为什么是二进制安全的?虽然SDS也是把'\0'作为字符串结尾,但是SDS通过len属性而不是'\0'来判断字符串的结尾。SDS依然用'\0'作为结尾只是为了兼容部分C字符串函数,通过遵循C字符串以空字符结尾的惯例,SDS可以在有需要时重用<string.h>函数库,从而避免了不必要的代码重复。

Redis存储图片的两种方式:

  1. 直接用String存,这样可以先把图片用base64编码,将编码结果作为value存储起来,get的时候对得到的value进行相应的base64解码即可。
  2. 将图片保存在本地(上云也可以),value直接存对应的路径即可。
  3. 将图片转为对象,通过对象的序列化与反序列化来存。

String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M

这里说一下Redis的Incr操作,这个操作可以对值+1,并且是原子操作。什么是原子操作?单线程中,单条指令可认为是原子操作,因为中断只发生于指令间;多线程中,不能被其它线程打断的操作就是原子操作。Redis因为是单线程的,所以这种单条指令都是原子操作。

那么Java中的i++是原子操作吗?不是。

i=0,两个线程同时执行i++100次,i的范围是多少?2~200

i++底层分为了三步:获取i值、+1操作、新值赋给i。(也就是说一共100轮,每轮分为3步操作)

现在有A、B两个线程,出现2的极端情况就是:

B线程先获取到了i的值是0,而A随后也获取到了i的值并执行了99次+1操作让i等于了99,此时B执行了1次+1操作让i等于1,此时A对i执行第100次+1操作,首先也是取i的值,结果取值为1,然后在A执行+1前B去对i执行完了100次+1,此时B最后也给i赋值为100,但是A又来最后一次对i+1,由于之前取的i是1,那么+1就是2,这次A对i进行赋值i=2把B的赋值给覆盖了,i最后就等于2.

200就是正常情况,一个线程处理完了另一个再处理。

String底层的数据结构是用的SDS(Simple Dynamic String),这是Redis自己封装的一个结构体,也就是大小可变的字符串,可以把它看做Java中的ArrayList,采用的预分配额外空间,扩容是当现在空间不够了就会新增len长度的空间,但是当大小len已大于1M时,扩容就只是增加1M的容量。

struct sdshdr {

    // buf 已占用长度
    int len;

    // buf 剩余可用长度
    int free;

    // 实际保存字符串数据的地方
    char buf[];
};

 通过 len 属性, sdshdr 可以实现复杂度为 O(1)的计算长度操作(c语言不能直接得到元素个数)。

注意只有执行append之后才会创建额外1倍的空间,最开始的set后free为0

List

redis 偶数稳定版 redis 0_缓存

一个key对应的value可以有多个值,底层由双向链表实现,两端操作效率更高。

底层数据结构在值较少的情况下是zipList,也就是一块连续的内存空间,当值比较多了就会变为quickList,其实就是通过链表的方式将多个zipList链接起来。

使用zipList是为了避免为每个值都分配next、pre指针从而浪费空间,使用quickList则可以提高插入和删除的性能。(综合了链表和数组的优点)

Set

相比于List,Set可以实现自动去重并且还能提供判断一个元素是否在集合内的接口。

Set的底层在满足1. 集合元素全部是整型值;2. 集合元素个数不超过512 的情况下是intset

typedef struct intset {
    
    // 编码方式
    uint32_t encoding;

    // 集合包含的元素数量
    uint32_t length;

    // 保存元素的数组
    int8_t contents[];

} intset;

intset是有序数组(查找用的二分查找)

另外就是用的dict字典,也就是哈希表。在Java中HashSet的底层也是HashMap,不过所有value都指向一个空对象(一个空对象占16字节->8字节的mark word+8字节的class pointer),而Redis中的Set中的哈希表的value都指向null。

Hash

value就是键值对集合

相当于Java中的Map<String,Object>

应用场景比如存储用户数据,key是用户id,value可以是<"name","zhangsan">、<"age","14">...

好处是什么呢?我们想想如果不用这种方式要怎么实现,首先key还是用户id,但是value用String类型来存姓名、年龄这些数据,就要先序列化才能存,当我要修改的时候就需要先反序列化、改值、序列化,最后存进去,这样很麻烦并且不是原子操作还会引入并发修改问题。

底层数据结构是当<field,value>长度小,个数少时用zipList(键值对数量小于512,并且所有键值长度小于64字节),否则用HashTable。

Sorted Set

有序集合(Zset又称Sorted Set),和Set很相似都是字符串的去重集合,所不同的是,Zset的每个value都有一个对应的score值,用于按照从低到高的评分来对value排序,value不能重复但score可以重复。

底层数据结构用了两个,一个是hash,建立每对value和score的映射关系。

另一个是跳跃表。

redis 偶数稳定版 redis 0_数据库_02

redis 偶数稳定版 redis 0_Redis_03

 有序集合的底层实现可以有很多,比如用数组,只是插入删除不方便,用红黑树虽然效率高但是太复杂,用链表在查询时需要遍历效率低,而跳跃表的效率和红黑树差不多但实现起来很简单。

如上图,第0层是原始链表,而第1、2层是索引链表(如果每个链表的数据域是很大的对象,那么索引链表只需要存关键值和几个指针,不需要存整个对象),空间复杂度没变的还是O(n),虽然空间复杂度没变但还是会多很多空间,为了节省空间我们可以多几个节点抽取为一个节点,比如隔3个或5个。这样查询的时间复杂度为O(logn)

注意,不同层的相同score节点用zipList存,比如上面3层的3个1号节点存在一个zipList中,查询时从上往下找,然后跳表每个节点是通过存指向score和value的指针找到对应的值,而非直接存值(只存指针的好处是链表节点不会太大)。