Redis 基础入门教程

Redis

一 安装gcc:需要安装gcc 才能使用make编译
yum -y install gcc
yum -y install gcc-c++

二 编译:在redis解压所在目录下使用make编译
cd redis-4.0.8
make

三 安装:编译好了需要安装 安装目录为/usr/local/redis-5.0.8
make PREFIX=/usr/local/redis-5.0.8 install

四:如果没有配置文件,去官网下载
http://download.redis.io/redis-stable/redis.conf

五:配置文件修改
bind 127.0.0.1改为 bind 0.0.0.0或者 # bind 127.0.0.1(注释掉即可)
protected-mode no  //关闭保护模式
appendonly yes     //持久化
requirepass 123456   //密码 
daemonize yes //后台启动   
maxmemory-policy allkeys-lru   //设置过期淘汰(无需修改)
...根据需要修改

六:进入/usr/local/redis/bin目录 启动    --raw value才能显示中文
./redis-server redis.conf
./redis-cli -a 123456 --raw  

# 以挂载启动
需要注释daemonize 或者 改成 no
#daemonize no //后台启动
如果有密码需要添加 --requirepass "123456"

docker run -p 6379:6379 --name redis  -v /usr/local/docker/redis/conf/redis.conf:/etc/redis/redis.conf -v /usr/local/docker/redis/data:/data -d redis redis-server /etc/redis/redis.conf  --requirepass "123456" --appendonly yes 


127.0.0.1:6379> set a 123
(error) NOAUTH Authentication required. //请输入密码
127.0.0.1:6379> auth 密码

Redis 基本数据类型介绍

字符串(string),列表(list),集合(set),有序集合(zset)以及哈希(hash)

redisObject

在redis中每个value都是以一个redisObject结构来表示,如下:

type就是指这个对象的数据类型,即我们平常所认知的redis的五种数据类型,可以通过TYPE命令查看一个对象的数据类型

typedef struct redisObject{
    
    
//类型
unsigned type:4; //
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
//引用计数器
int refCount;
//最后一次的访问时间
unsigned lru:
}
127.0.0.1:6379> set a 123
OK
127.0.0.1:6379> type a
string
127.0.0.1:6379> hmset b name jack age 12
OK
127.0.0.1:6379> type b
hash
127.0.0.1:6379> lpush c 1 2 3
(integer) 3
127.0.0.1:6379> type c
list
127.0.0.1:6379> sadd d 1 2 3
(integer) 3
127.0.0.1:6379> type d
set
127.0.0.1:6379> zadd e 2 a 3 b
(integer) 2
127.0.0.1:6379> type e
zset
127.0.0.1:6379> 

lru:
最后一次访问该对象的时间,可以通过Object idletime查看当前时间距离该键的lru的时间,即空转时间如下:

127.0.0.1:6379> set cbd 123
OK
127.0.0.1:6379> object idletime cbd
(integer) 90
127.0.0.1:6379> object idletime cbd
(integer) 95
127.0.0.1:6379> object idletime cbd
(integer) 98
127.0.0.1:6379> get cbd //访问了该键,因此lru重置
"123"
127.0.0.1:6379> object idletime cbd
(integer) 3
127.0.0.1:6379> 

refCount:
引用计数器,当创建一个对象的时间便将它的值初始化为1,当它被其它程序引用之时则加1,不再被引用则减1,当它的引用计数值变为0时,对象所占用的内存就会被释放。因此它主要有两个用途,内存回收的标志以及用于对象共享。
对象共享:当新建的两个或多个键都是整数值并且相同时,则它们的键会共享这一个值对象,这样可以减少内存的分配和回收,可以用OBJECT REFCOUNT查看引用情况,如下:

127.0.0.1:6379> set first 123
OK
127.0.0.1:6379> OBJECT refcount first
(integer) 1
127.0.0.1:6379> set second 123
OK
127.0.0.1:6379> OBJECT refcount second
(integer) 2
127.0.0.1:6379> OBJECT refcount first
(integer) 2
127.0.0.1:6379> 

字符串(string)

string 是 redis 最基本的类型,最大上限是1G 字节。你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。

string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。

字符串对象的值底层都是由简单动态字符串实现的,在redis中,它并未使用C语言中的字符串,而是自己实现了一种叫做SDS的数据结构它的结构表示如下:

struct sdshdr{
    
    
//记录buf数组中已使用字节的长度
int len;
//记录buf数组中剩余空间的长度
int free;
//字节数组,用于存储字符串
char buf[];
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENhYY1Sw-1600850002023)(http://www.codesuger.com/upload/2020/08/image-862d2a47cb6a4d98862e70f1f3244db0.png)]

SDS拼接:

当C字符串进行拼接之时,如果未事先对当前字符串的空间进行调整,则可能会导致当前字符串的数据溢出,导致拼接过来的字符串内容被意外的修改,而SDS在拼接之前会进行自动的调整和扩展;减少内存分配次数,在SDS拼接发生以后,如果此时的len小于1MB则它会多分配和len大小相同的未使用空间,用free表示,如果大于1MB,则会分配1MB的为使用空间;惰性空间释放,当字符串进行缩短的时候,程序并不会立即回收空间(也可以调用API立即释放),而是记录到free之中,以便于后序拼接的使用。

编码:
字符串对象的编码可以是int、raw或者embstr。其中int表示整型的值,embstr表示小于等于39字节的字符串值,剩余的均用raw表示,并且int和embstr都是只读的,一旦发生了append拼接操作,即会转换为raw。如下:

127.0.0.1:6379> set age 12
OK
127.0.0.1:6379> Object encoding age
"int"
127.0.0.1:6379> APPEND age 3
(integer) 3
127.0.0.1:6379> Object encoding age
"raw"
127.0.0.1:6379> set name jack
OK
127.0.0.1:6379> Object encoding name
"embstr"
127.0.0.1:6379> APPEND name ja
(integer) 6
127.0.0.1:6379> get name 
"jackja"
127.0.0.1:6379> Object encoding name
"raw"

字符串的基本操作------m:批量 nx:根据是否存在 ex:有效期 rang:范围

set get--------基本操作

设置key 对应的值为string 类型的value。

127.0.0.1:6379> set name zhu
OK
127.0.0.1:6379> get name
"zhu"

setnx--------根据是否存在key而创建

设置key 对应的值为string 类型的value。如果key 已经存在,返回0,nx 是not exist 的意思。成功返回1

127.0.0.1:6379> setnx name zhuln
(integer) 0
127.0.0.1:6379> get name 
"zhu"

setex--------设置有效期

设置key 对应的值为string 类型的value,并指定此键值对应的有效期。例如我们添加一个whk= good 的键值对,并指定它的有效期是10 秒,如果不设置有效期会报错

127.0.0.1:6379> setex whk 10 good
OK
127.0.0.1:6379> get whk
"good"
//10s--------------
127.0.0.1:6379> get whk
(nil)

setrange--------替换字符串的某部分

设置指定key 的value 值的子字符串。例如我们希望替换name某部分字符:

127.0.0.1:6379> set name [email protected]
OK
127.0.0.1:6379> get name
"[email protected]"
127.0.0.1:6379> setrange name 4 126.com
(integer) 11
127.0.0.1:6379> get name
"[email protected]"
其中4是指从下标为4(包含4)的字符开始替换,下标从0开始。

mset--------批量设置多个字符串的值

mget-------批量获取多个字符串的值 一次获取多个key 的值,如果对应key 不存在,则对应返回nil。

一次设置多个key 的值,成功返回ok 表示所有的值都设置了,失败返回0 表示没有任何值被设置。

127.0.0.1:6379> mset num1 12 num2 34 num3 56
OK
127.0.0.1:6379> mget num1  num2  num3 num4 num5
"12"
"34"
"56"
(nil)
(nil)

msetnx--------批量设置多个字符串的值,只有不存在的字符串可以设置

一次设置多个key 的值,成功返回ok 表示所有的值都设置了,失败返回0 表示没有任何值被设置,但是不会覆盖已经存在的key。

127.0.0.1:6379> msetnx num3 55 num4 78
(integer) 0
127.0.0.1:6379> get num4
(nil)
127.0.0.1:6379> get num3
"56"

getset--------设置key完了立刻查看历史的key值

设置key 的值,并返回key 的旧值。

127.0.0.1:6379> get name
"[email protected]"
127.0.0.1:6379> getset name [email protected]
"[email protected]"
127.0.0.1:6379> get name
"[email protected]"

getrange--------获取指定范围的字符串,可以从左边也可以从右边开始

获取指定key 的value 值的子字符串,左下标从0开始,右下标从-1开始,当下标超出字符串长度时,将默认为是同方向的最大下标。

127.0.0.1:6379> get name
"[email protected]"
127.0.0.1:6379> getrange name 0 4
"whk@q"
127.0.0.1:6379> getrange name -1 -5
""
127.0.0.1:6379> getrange name  -5 -1
"q.com"

incr--------将 key 中储存的数字值增

decr--------对key 的值做的是减一操作,decr 一个不存在key,则设置key 为-1

将 key 中储存的数字值增一。

127.0.0.1:6379> get age
"10"
127.0.0.1:6379> incr age
(integer) 11
127.0.0.1:6379> get age
"11"

incrby--------将 key 中储存的数字值增指定的值,不存在会创建并默认为0

decrby-------同decr,减指定值,可为负值。

加指定值 ,key 不存在时候会设置key,并认为原来的value 是 0

127.0.0.1:6379> get age
"11"
127.0.0.1:6379> incrby age 5
(integer) 16
127.0.0.1:6379> get age
"16"
127.0.0.1:6379> incrby age1 5
(integer) 5
127.0.0.1:6379> get age1
"5" 

append--------追加并返回新字符串值的长度

给指定key 的字符串值追加value,返回新字符串值的长度。     

127.0.0.1:6379> get name
"[email protected]"
127.0.0.1:6379> append name .china
(integer) 16
127.0.0.1:6379> get name
"[email protected]"

strlen--------取指定key 的value 值的长度。

取指定key 的value 值的长度。

127.0.0.1:6379> get name
"[email protected]"
127.0.0.1:6379> strlen name
(integer) 16
127.0.0.1:6379> strlen age1
(integer) 2
127.0.0.1:6379> get age
"16"

列表(list)

list 是一个链表结构,简单的字符串列表,按照插入顺序排序,链表的最大长度是(2的32 次方)。主要功能是push、pop、获取一个范围的所有值等等,操作中key 可以理解为链表的名字。

链表提供了节点重排以及节点顺序访问的能力,redis中的列表对象主要是由压缩列表和双端链表实现。

双端链表(linkedlist)结构如下:

type struct list{
    
    
//表头节点
listNode *head;
//表尾节点
listNode *tail;
//包含的节点总数
unsigned long len;
//一些操作函数 dup free match...
};

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7PozF54p-1600850002028)(http://www.codesuger.com/upload/2020/09/image-8b8fa68ba256465b9e16769806ced26f.png)]

其中每个节点都有一个prev指针和一个next指针,而节点中的value则是列表对象具体的值。

压缩列表(ziplist)结构如下:

type struct ziplist{
    
    
//整个压缩列表的字节数
uint32_t zlbytes;
//记录压缩列表尾节点到头结点的字节数,直接可以求节点的地址
uint32_t zltail_offset;
//记录了节点数,有多种类型,默认如下
uint16_t zllength;
//节点
列表节点 entryX;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J4BXXdja-1600850002033)(http://www.codesuger.com/upload/2020/09/image-31a736b11d744a1a8fdb3ab7f2cfe0f0.png)]

而每个列表节点中主要包括以下几项:previous_entry_length,记录了压缩列表中前一节点的字节长度,当小于254字节时,它的长度为1字节,当大于254字节时,长度为5字节且后4字节保存真正的长度,用于表尾向表头遍历;content,节点所存储的内容,可以是一个字节数组或者整数;encoding,记录content属性中所保存的数据类型以及长度。

编码:
当列表对象所存储的字符串元素长度小于64字节并且元素数量小于512个时,使用ziplist编码,否则使用linkedlist编码,如下:

127.0.0.1:6379> rpush zip "hello" "world"
(integer) 2
127.0.0.1:6379> OBJECT encoding zip
"ziplist"
127.0.0.1:6379> rpush zip "fffffffffffffffffffffzzzzzzzzzzzzzzzzzzzzzzzzzzzzzkkkkkkkkkkkkkkkkkkkkkkkkkcccccccccc"
(integer) 3
127.0.0.1:6379> OBJECT encoding zip
"linkedlist"
127.0.0.1:6379> 

列表的基本操作------- l:li/左侧 r:右侧

lpush--------在左侧添加

在key 对应list 的头部添加字符串元素(left)

127.0.0.1:6379> lrange testlist 0 0
1) "word"
127.0.0.1:6379> lpush testlist "hello"
(integer) 2
127.0.0.1:6379> lrange testlist 0 1
1) "hello"
2) "world"

rpush--------在右侧添加

在key 对应list 的尾部添加字符串元素(right)

127.0.0.1:6379> lrange testlist 0 1
1) "hello"
2) "word"
127.0.0.1:6379> rpush testlist "!"
(integer) 3
127.0.0.1:6379> lrange testlist 0 2
1) "hello"
2) "word"
3) "!"

linsert--------特定位置之前或之后添加字符串元素

在key 对应list 的特定位置之前或之后添加字符串元素
LINSERT key BEFORE|AFTER pivot value

127.0.0.1:6379> linsert testlist before "word" "beautiful"
(integer) 4
127.0.0.1:6379> lrange testlist 0 3
1) "hello"
2) "beautiful"
3) "word"
4) "!"

lset--------修改指定下标的元素值(下标从0 开始)

设置list 中指定下标的元素值(下标从0 开始)

127.0.0.1:6379> lrange testlist 0 3
1) "hello"
2) "beautiful"
3) "word"
4) "!"
127.0.0.1:6379> lset testlist 0 "Say"
OK
127.0.0.1:6379> lrange testlist 0 4
1) "Say"
2) "beautiful"
3) "word"
4) "!"

lrem--------删除指定元素值

从key 对应list 中删除count 个和value 相同的元素。count>0 时,按从头到尾的顺序删除;count<0 时,按从尾到头的顺序删除;count<0 时,按从尾到头的顺序删除。

127.0.0.1:6379> lrange testlist 0 6
1) "2010"
2) "Say"
3) "beautiful"
4) "word"
5) "2010"
6) "2010"
127.0.0.1:6379> lrem testlist 2 "2010"
(integer) 2
127.0.0.1:6379> lrange testlist 0 6
1) "Say"
2) "beautiful"
3) "word"
4) "2010"

ltrim--------保留指定下标 的值范围内的数据

保留指定key 的值范围内的数据

127.0.0.1:6379> lrange testlist 0 3
1) "Say"
2) "beautiful"
3) "word"
4) "2010"
127.0.0.1:6379> ltrim testlist 0 2
OK
127.0.0.1:6379> lrange testlist 0 3
1) "Say"
2) "beautiful"
3) "word"

lpop--------从list 的头部删除元素,并返回删除元素

从list 的头部删除元素,并返回删除元素

127.0.0.1:6379> lrange testlist 0 3
1) "Say"
2) "beautiful"
3) "word"
127.0.0.1:6379> lpop testlist 
"Say"
127.0.0.1:6379> lrange testlist 0 3
1) "beautiful"
2) "word"

rpop--------从list 的尾部删除元素,并返回删除元素

从list 的尾部删除元素,并返回删除元素

127.0.0.1:6379> lrange testlist 0 3
1) "beautiful"
2) "word"
127.0.0.1:6379> rpop testlist
"word"
127.0.0.1:6379> lrange testlist 0 3
1) "beautiful"

rpoplpush--------从第一个list 的尾部移除元素并添加到第二个list 的头部 (原子性)

从第一个list 的尾部移除元素并添加到第二个list 的头部,最后返回被移除的元素值,整个操作是原子的.如果第一个list 是空或者不存在返回nil

127.0.0.1:6379> lrange testlist 0 3
1) "beautiful"
127.0.0.1:6379> lrange testlist2 0 1
(empty list or set)
127.0.0.1:6379> rpoplpush testlist testlist2
"beautiful"
127.0.0.1:6379> lrange testlist 0 1
(empty list or set)
127.0.0.1:6379> lrange testlist2 0 1
1) "beautiful"

lindex--------返回列表指定位置元素

返回名称为key 的list 中index 位置的元素

127.0.0.1:6379> lrange testlist2 0 1
1) "beautiful"
127.0.0.1:6379> index testlist2 1
(error) ERR unknown command 'index'
127.0.0.1:6379> lindex testlist2 1
(nil)
127.0.0.1:6379> lindex testlist2 0
"beautiful"

llen--------返回列表长度

返回key 对应list 的长度

127.0.0.1:6379> lrange testlist2 0 1
1) "beautiful"
127.0.0.1:6379> llen testlist2
(integer) 1

集合(set)

set 是集合,和我们数学中的集合概念相似,对集合的操作有添加删除元素,有对多个集合求交并差等操作,操作中key 理解为集合的名字。集合成员是唯一的,这就意味着集合中不能出现重复的数据。set 元素最大可以包含(2 的32 次方)个元素。

整数集合与字典:
集合对象的编码可以是整数集合(intset)或者字典(hashtable)。

整数集合结构如下:

typedef struct intset{
    
    
//编码方式
uint32_t encoding;
//元素数量
uint32_t length;
//存储元素的数组
int8_t contents[];
}

整数集合的每个元素都是contents数组的一个数组项,各个项在数组中按值得大小从小到大有序排列,并且不包含重复的项contents数组中元素的类型由encoding决定,当新加入元素之时,如果元素的编码大于contents是数组的编码,则会将所有元素的编码升级为新加入元素的编码,然后再插入。编码不会发生降级。

rehash 当哈希表的大小不能满足需求,就可能会有两个或者以上数量的键被分配到了哈希表数组上的同一个索引上,于是就发生冲突(collision),在Redis中解决冲突的办法是链接法[拉链法|链地址法](separate chaining)。常见的解决hash冲突的方法还有开放定址法,再散列,公共溢出区。

字典的结构如下:

typedef struct dict{
    
    
//类型特定函数
dictType *type;
//哈希表 两个,一个用于实时存储,一个用于rehash
dictht ht[2];
//rehash索引 数据迁移时使用
unsigned rehashidx;
}

而哈希表的结构如下:

typedef struct dictht{
    
    
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表掩码,总是等于size-1,存储时计算索引值
unsigned long sizemask;
//已有元素数量
unsigned long used;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CI5CFFWj-1600850002035)(http://www.codesuger.com/upload/2020/08/image-9f68c5da4e58423e81fd3e2e38dff087.png)]

其中键值对都保存在节点dictEntry之中,并且通过拉链法解决哈希冲突,存储时通过MurmurHash算法来计算键的哈希值,能够更好的提供随机分布性且速度也快。扩容时时采用渐进式的rehash,采用分而治之的方法,通过改变rehashidx的值,来一个个将元素移动到ht[1]中,完成以后将ht[1]变为ht[0],原先的ht[0]变为ht[1],同时将redhashidx置为-1。

unsigned int murMurHash(const void *key, int len)
    {
    	const unsigned int m = 0x5bd1e995;
    	const int r = 24;
        const int seed = 97;
    	unsigned int h = seed ^ len;
    	// Mix 4 bytes at a time into the hash
    	const unsigned char *data = (const unsigned char *)key;
    	while(len >= 4)
    	{
    		unsigned int k = *(unsigned int *)data;
    		k *= m; 
    		k ^= k >> r; 
    		k *= m; 
    		h *= m; 
    		h ^= k;
    		data += 4;
    		len -= 4;
    	}
    	// Handle the last few bytes of the input array
    	switch(len)
    	{
    	    case 3: h ^= data[2] << 16;
    	    case 2: h ^= data[1] << 8;
    	    case 1: h ^= data[0];
            h *= m;
    	};
    	// Do a few final mixes of the hash to ensure the last few
    	// bytes are well-incorporated.
    	h ^= h >> 13;
    	h *= m;
    	h ^= h >> 15;
    	return h;
    }

编码:
当集合对象所保存的元素都是整数值且元素数量不超过512个时,使用intset编码,否则使用hashtable编码,如下:

127.0.0.1:6379> sadd cloud 1 2 3 4 5
(integer) 5
127.0.0.1:6379> object encoding cloud
"intset"
127.0.0.1:6379> sadd cloud a b c
(integer) 3
127.0.0.1:6379> object encoding cloud
"hashtable"

集合的基本操作------- add:添加 rem:删除 pop:随机删除 diff:差集 inter:交集 union:并集 store:保存 move:移动

sadd-------向set 中添加元素

向set 中添加元素

127.0.0.1:6379> sadd testset 'Hello'
(integer) 1
127.0.0.1:6379> sadd testset 'everybody'
(integer) 1
127.0.0.1:6379> smembers testset
1) "Hello"
2) "everybody"

srem------- 删除set 中的元素

删除set 中的元素member

127.0.0.1:6379> smembers testset
1) "Hello"
2) "everybody"
127.0.0.1:6379> srem testset 'Hello'
(integer) 1
127.0.0.1:6379> smembers testset
1) "everybody"

spop------- 随机删除set 中一个元素

随机删除set 中一个元素

127.0.0.1:6379> smembers testset
1) "is"
2) "OK"
3) "everybody"
127.0.0.1:6379> spop testset
"is"
127.0.0.1:6379> smembers testset
1) "OK"
2) "everybody"

sdiff------- 返回与第一个key 的差集

返回与第一个key 的差集

127.0.0.1:6379> smembers testset
1) "OK"
2) "everybody"
127.0.0.1:6379> sadd testset1 'OK'
(integer) 1
127.0.0.1:6379> sadd testset1 '!!'
(integer) 1
127.0.0.1:6379> smembers testset1
1) "!!"
2) "OK"
127.0.0.1:6379> sdiff testset testset1
1) "everybody"
127.0.0.1:6379> sdiff testset1 testset
1) "!!"

sdiffstore------- 返回与第一个key 的差集,并将结果存为另一个destination

返回与第一个key 的差集,并将结果存为另一个key
sdffstore destination key key2

127.0.0.1:6379> sdiff testset1 testset
1) "!!"
127.0.0.1:6379> sdiffstore testset2 testset1 testset
(integer) 1
127.0.0.1:6379> smembers testset2
1) "!!"

sinter------- 返回所有给定key 的交集

返回所有给定key 的交集

127.0.0.1:6379> smembers testset
1) "OK"
2) "everybody"
127.0.0.1:6379> smembers testset1
1) "!!"
2) "OK"
127.0.0.1:6379> sinter testset testset1 
1) "OK"
127.0.0.1:6379> sdiff testset testset1
1) "everybody"

sinterstore------- 返回所有交集,并将结果存为另一个key

返回所有交集,并将结果存为另一个key
sinterstore destination key key2

127.0.0.1:6379> sinter testset testset1 
1) "OK"
127.0.0.1:6379> sdiff testset testset1
1) "everybody"
127.0.0.1:6379> sinterstore testset3 testset testset1
(integer) 1
127.0.0.1:6379> smembers testset3
1) "OK"

sunion------- 返回所有的并集

返回所有的并集

127.0.0.1:6379> smembers testset3
1) "OK"
127.0.0.1:6379> smembers testset2
1) "!!"
127.0.0.1:6379> sunion testset3 testset2
1) "!!"
2) "OK"

sunionstore------- 返回所有并集,并将结果存为另一个key

返回所有并集,并将结果存为另一个key
sunionstore destination key key2

127.0.0.1:6379> sunion testset3 testset2
1) "!!"
2) "OK"
127.0.0.1:6379> sunionstore testset4 testset3 testset2
(integer) 2
127.0.0.1:6379> smembers testset4
1) "!!"
2) "OK"

smove------- 从第一个对应的set 中移除member 并添加到第二个对应set 中

从第一个对应的set 中移除member 并添加到第二个对应set 中

127.0.0.1:6379> smembers testset1
1) "!!"
2) "OK"
127.0.0.1:6379> smembers testset2
1) "!!"
127.0.0.1:6379> smove testset1 testset2 "!!"
(integer) 1
127.0.0.1:6379> smembers testset1
1) "OK"
127.0.0.1:6379> smembers testset2
1) "!!"

scard------- 返回set 的元素个数

返回set 的元素个数

127.0.0.1:6379> smembers testset2
1) "!!"
127.0.0.1:6379> scard testset2
(integer) 1

sismember------- 测试member 是否是set 的元素

测试member 是否是set 的元素

127.0.0.1:6379> smembers testset1
1) "OK"
127.0.0.1:6379> sismember testset1 OK
(integer) 1
127.0.0.1:6379> sismember testset1 ok
(integer) 0
127.0.0.1:6379> sismember testset1 "OK"
(integer) 1
127.0.0.1:6379> sismember testset1 'OK'
(integer) 1

srandmember------- 随机返回set 的一个元素,但是不删除元素

随机返回set 的一个元素,但是不删除元素

127.0.0.1:6379> smembers testset1
1) "OK"
2) "!"
3) "Everybody"
127.0.0.1:6379> srandmember testset1
"!"
127.0.0.1:6379> smembers testset1
1) "OK"
2) "!"
3) "Everybody"

有序集合(zset)

有序集合是集合的升级版,不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。

有序集合的编码可以是压缩列表(ziplist)或者跳跃表(skiplist)。
跳跃表的结构如下:

typedef struct zskiplist{
    
    
//跳跃表的头结点
zskiplistNode header;
//尾节点
zskiplistNode tail;
//跳跃表中层数最大的节点的层数(不包括头结点)
unsigned long level;
//跳跃表长度(不包括头节点)
unsigned int length;

其中的跳跃表节点结构如下:

typedef struct zskiplistNode{
    
    
//后退指针
struct zskiplistNode *backward;
//分值
double score;
//成员对象
robj *obj;
//层
struct zskiplistLevel{
    
    
//前进指针
struct zskiplistNode *forward;
//跨度
unsigned int span;
}level[];
};

每个level数组可以包含多个元素,里面存储有指向其他节点(不是下一个)的指针,可以加快访问速度;跨度用于表示两个节点之间的距离,排位时使用;后退指针依次的从后向前访问;分值用于排序;成员是一个指向字符串对象的指针。

编码:
当元素数量小于128个并且所有元素成员的长度都小于64字节之时使用ziplist编码,否则使用skiplist编码,如下:

127.0.0.1:6379> zadd test 2 a 3 b
(integer) 2
127.0.0.1:6379> OBJECT encoding test
"ziplist"
127.0.0.1:6379> zadd test 4 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffqwqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
(integer) 1
127.0.0.1:6379> OBJECT encoding test
"skiplist"

有序集合的基本操作

zadd-------向zset 中添加元素member,score 用于排序。如果该元素已经存在,则根据score 更新该元素的顺序。

向zset 中添加元素member,score 用于排序。如果该元素已经存在,则根据score 更新该元素的顺序。

127.0.0.1:6379> zadd testzset  1 'one'
(integer) 1
127.0.0.1:6379> zadd testzset 2 'two'
(integer) 1
127.0.0.1:6379> zadd testzset 3 'two'
(integer) 0
127.0.0.1:6379> zrange testzset 0 2
1) "one"
2) "two"
127.0.0.1:6379> zrange testzset 0 2 withscores
1) "one"
2) "1"
3) "two"
4) "3"

zrem-------删除zset 中的元素member

删除zset 中的元素member

127.0.0.1:6379> zrange testzset 0 2 withscores
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
127.0.0.1:6379> zrem testzset one
(integer) 1
127.0.0.1:6379> zrange testzset 0 2 withscores
1) "two"
2) "2"
3) "three"
4) "3"

zincrby-------

如果zset 中已经存在元素member,则增加该元素的score,new_score=old_score+zincrby_score;否则向集合中添加该元素。

127.0.0.1:6379> zrange testzset 0 2 withscores
1) "two"
2) "2"
3) "three"
4) "3"
127.0.0.1:6379> zincrby testzset 5 "two"
"7"
127.0.0.1:6379> zrange testzset 0 5 withscores
1) "three"
2) "3"
3) "two"
4) "7"
127.0.0.1:6379> zincrby testzset 5 "four"
"5"
127.0.0.1:6379> zrange testzset 0 5 withscores
1) "three"
2) "3"
3) "four"
4) "5"
5) "two"
6) "7"

zrank-------返回某个元素的排名**(按score 从小到大排)**,即下标

返回某个元素的排名(按score 从小到大排),即下标

127.0.0.1:6379> zrange testzset 0 5 withscores
1) "three"
2) "3"
3) "four"
4) "5"
5) "two"
6) "7"
127.0.0.1:6379> zrank testzset "four"
(integer) 1
127.0.0.1:6379> zrank testzset "two"
(integer) 2
127.0.0.1:6379> zrank testzset "three"
(integer) 0

zrevrank-------返回zset中元素的排名 (按score 从大到小排序),即下标

返回zset中元素的排名(按score 从大到小排序),即下标

127.0.0.1:6379> zrange testzset 0 5 withscores
1) "three"
2) "3"
3) "four"
4) "5"
5) "two"
6) "7"
127.0.0.1:6379> zrevrank testzset "four"
(integer) 1
127.0.0.1:6379> zrevrank testzset "two"
(integer) 0
127.0.0.1:6379> zrevrank testzset "three"
(integer) 2

zrevrange-------返回**(按score 从大到小排序)** 从start 到end 的所有元素

返回(按score 从大到小排序) 从start 到end 的所有元素

127.0.0.1:6379> zrevrange testzset 0 1 withscores
1) "two"
2) "7"
3) "four"
4) "5"

zrangebyscore-------返回集合中score 在给定区间的元素

返回集合中score 在给定区间的元素

127.0.0.1:6379> zrange testzset 0 2 withscores
1) "three"
2) "3"
3) "four"
4) "5"
5) "two"
6) "7"
127.0.0.1:6379> zrangebyscore testzset 0 5 withscores
1) "three"
2) "3"
3) "four"
4) "5"

zcount-------返回集合中score 在给定区间的数量

返回集合中score 在给定区间的数量

127.0.0.1:6379> zcount testzset 3 5
(integer) 2

zcard-------返回集合中元素个数zcount

返回集合中元素个数

127.0.0.1:6379> zcard testzset
(integer) 3
127.0.0.1:6379> zrange testzset 0 5 withscores
1) "three"
2) "3"
3) "four"
4) "5"
5) "two"
6) "7"

zscore-------返回给定元素对应的score

返回给定元素对应的score

127.0.0.1:6379> zrange testzset 0 5 withscores
1) "three"
2) "3"
3) "four"
4) "5"
5) "two"
6) "7"
127.0.0.1:6379> zscore testzset "three"
"3"

zremrangebyrank-------删除集合中排名在给定区间(下标)的元素

删除集合中排名在给定区间(下标)的元素

127.0.0.1:6379> zrange testzset 0 5 withscores
1) "three"
2) "3"
3) "four"
4) "5"
5) "two"
6) "7"
127.0.0.1:6379> zremrangebyrank testzset 2 2
(integer) 1
127.0.0.1:6379> zrange testzset 0 5 withscores
1) "three"
2) "3"
3) "four"
4) "5"

zremrangebyscore-------删除集合中score 在给定区间的元素

删除集合中score 在给定区间的元素

127.0.0.1:6379> zrange testzset 0 5 withscores
1) "three"
2) "3"
3) "four"
4) "5"
127.0.0.1:6379> zremrangebyscore testzset 3 5
(integer) 2
127.0.0.1:6379> zrange testzset 0 5 withscores
(empty list or set)

哈希(hash)

Redis hash 是一个键值(key=>value)对集合。是一个 string 类型的 field 和 value 的映射表,特别适合用于存储对象。

相较于将对象的每个字段存成单个string 类型。将一个对象存储在hash 类型中会占用更少的内存,省内存的原因是新建一个hash 对象时开始是zipmap(又称为small hash)来存储的。当field 或者value的大小超出一定限制后,Redis 会在内部自动将zipmap 替换成正常的hash 实现. 这个限制可以在配置文件中设定:
hash-max-zipmap-entries 64 #配置字段最多64 个
hash-max-zipmap-value 512 #配置value 最大为512 字节

哈希对象的编码可以是压缩列表(ziplist)或者字典(hashtable),当哈希对象保存的所有键值对的键和值得长度都小于64字节并且元素数量小于512个时使用ziplist,否则使用hashtable。使用ziplist时,是依次将键和值压入链表之中,两者相邻。使用hashtable是是将键值对存于dictEntry之中。

压缩列表(ziplist)结构如下:

type struct ziplist{
    
    
//整个压缩列表的字节数
uint32_t zlbytes;
//记录压缩列表尾节点到头结点的字节数,直接可以求节点的地址
uint32_t zltail_offset;
//记录了节点数,有多种类型,默认如下
uint16_t zllength;
//节点
列表节点 entryX;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vgUMQ0uU-1600850002036)(http://www.codesuger.com/upload/2020/08/image-ebc6a90ad5f6494e98c6a79d34e32c9c.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3y6vAp9V-1600850002037)(image-20200506104530919.png)]

而每个列表节点中主要包括以下几项:previous_entry_length,记录了压缩列表中前一节点的字节长度,当小于254字节时,它的长度为1字节,当大于254字节时,长度为5字节且后4字节保存真正的长度,用于表尾向表头遍历;content,节点所存储的内容,可以是一个字节数组或者整数;encoding,记录content属性中所保存的数据类型以及长度。

字典(hashtable)的结构如下:

typedef struct dict{
    
    
//类型特定函数
dictType *type;
//哈希表 两个,一个用于实时存储,一个用于rehash
dictht ht[2];
//rehash索引 数据迁移时使用
unsigned rehashidx;
}

而哈希表的结构如下:

typedef struct dictht{
    
    
//哈希表数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表掩码,总是等于size-1,存储时计算索引值
unsigned long sizemask;
//已有元素数量
unsigned long used;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3yMJxOJP-1600850002038)(http://www.codesuger.com/upload/2020/08/image-95964dca30f84278918c89f46fc716ed.png)]

其中键值对都保存在节点dictEntry之中,并且通过拉链法解决哈希冲突,存储时通过MurmurHash算法来计算键的哈希值,能够更好的提供随机分布性且速度也快。扩容时时采用渐进式的rehash,采用分而治之的方法,通过改变rehashidx的值,来一个个将元素移动到ht[1]中,完成以后将ht[1]变为ht[0],原先的ht[0]变为ht[1],同时将redhashidx置为-1。

哈希的基本操作

hset

设置hash field 为指定值,如果key 不存在,则先进行创建

127.0.0.1:6379> hset mytest field1 Hello
(integer) 1
127.0.0.1:6379> hget mytest field1
"Hello"

hsetnx

 设置hash field 为指定值,如果key 不存在,则先创建。如果field 已经存在,返回0。

hmset

同时设置hash 的多个field。

hget

获取指定的hash field。

hmget-------批量获取 需要指定属性

获取全部指定的hash filed。

127.0.0.1:6379> hmget mytest field1 field2 field3
1) "Hello"
2) "world"
3) "!"

hincrby--------追加 只能加在int上

指定的hash filed 加上给定值(无hincr操作)。
new_filed=old_filed+hincrby_filed; 

127.0.0.1:6379> hset mytest field4 10
(integer) 1
127.0.0.1:6379> hget mytest field4
"10"
127.0.0.1:6379> hincr mytest field4
(error) ERR unknown command 'hincr'
127.0.0.1:6379> hincrby mytest field4 8
(integer) 18

hexists-------

测试指定field 是否存在,存在返回1,不存在返回0。

hlen-------

返回指定hash 的field 数量。

127.0.0.1:6379> hlen mytest
(integer) 5

hdel

删除指定hash 的field 。

127.0.0.1:6379> hlen mytest
(integer) 2
127.0.0.1:6379> hdel mytest field3
(integer) 1
127.0.0.1:6379> hlen mytest
(integer) 1

hkeys

返回hash 的所有field。

127.0.0.1:6379> hkeys mytest
1) "txt1"
127.0.0.1:6379> hset mytest field2 2
(integer) 1
127.0.0.1:6379> hkeys mytest
1) "txt1"
2) "field2"

hvals

返回hash 的所有value值。

127.0.0.1:6379> hvals mytest
1) "Hello"
2) "2"

hgetall

获取某个hash 中全部的filed 及value。

127.0.0.1:6379> hgetall mytest
1) "txt1"
2) "Hello"
3) "field2"
4) "2"

各种数据类型对比以及使用场景

字符串(string)

使用场景:常规key-value缓存应用。常规计数: 微博数, 粉丝数。

实现方式:String在redis内部存储默认就是一个字符串,被redisObject所引用,当遇到incr,decr等操作时会转成数值型进行计算,此时redisObject的encoding字段为int。

列表(list)

实现方式:Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。

Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。 获取越接近两端的元素速度越快,但通过索引访问时会比较慢。

使用场景:

消息队列系统:使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。比如:将Redis用作日志收集器,实际上还是一个队列,多个端点将日志信息写入Redis,然后一个worker统一将所有日志写到磁盘。

取最新N个数据的操作:记录前N个最新登陆的用户Id列表,超出的范围可以从数据库中获得。

//把当前登录人添加到链表里
ret = r.lpush("login:last_login_times", uid)
//保持链表只有N位
ret = redis.ltrim("login:last_login_times", 0, N-1)
//获得前N个最新登陆的用户Id列表
last_login_list = r.lrange("login:last_login_times", 0, N-1)

比如微博:

在Redis中我们的最新微博ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID函数会一直询问Redis。只有在start/count参数超出了这个范围的时候,才需要去访问数据库。我们的系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,而主页或第一个评论页是不会麻烦到硬盘上的数据库了。

redis 127.0.0.1:6379> lpush runoob redis
(integer) 1
redis 127.0.0.1:6379> lpush runoob mongodb
(integer) 2
redis 127.0.0.1:6379> lpush runoob rabitmq
(integer) 3
redis 127.0.0.1:6379> lrange runoob 0 10
1) "rabitmq"
2) "mongodb"
3) "redis"
redis 127.0.0.1:6379>

列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。

集合(set)

Redis set是string类型的无序集合。集合是通过hashtable实现的,概念和数学中个的集合基本类似,可以交集,并集,差集等等,set中的元素是没有顺序的。所以添加,删除,查找的复杂度都是O(1)。

*sadd 命令:*添加一个 string 元素到 key 对应的 set 集合中,成功返回1,如果元素已经在集合中返回 0,如果 key 对应的 set 不存在则返回错误。

常用命令:sadd,spop,smembers,sunion 等。

应用场景:Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。

Set 就是一个集合,集合的概念就是一堆不重复值的组合。利用Redis提供的Set数据结构,可以存储一些集合性的数据。

案例:在微博中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

实现方式: set 的内部实现是一个 value永远为null的HashMap,实际就是通过计算hash的方式来快速排重的,这也是set能提供判断一个成员是否在集合内的原因。

使用场景:

①交集,并集,差集:(Set)

//book表存储book名称
set book:1:name    ”The Ruby Programming Language”
set book:2:name     ”Ruby on rail”
set book:3:name     ”Programming Erlang”
//tag表使用集合来存储数据,因为集合擅长求交集、并集
sadd tag:ruby 1
sadd tag:ruby 2
sadd tag:web 2
sadd tag:erlang 3
//即属于ruby又属于web的书?
inter_list = redis.sinter("tag.web", "tag:ruby") 
//即属于ruby,但不属于web的书?
inter_list = redis.sdiff("tag.ruby", "tag:web") 
//属于ruby和属于web的书的合集?
inter_list = redis.sunion("tag.ruby", "tag:web")

②获取某段时间所有数据去重值

这个使用Redis的set数据结构最合适了,只需要不断地将数据往set中扔就行了,set意为集合,所以会自动排重。

sadd key member
redis 127.0.0.1:6379> sadd runoob redis
(integer) 1
redis 127.0.0.1:6379> sadd runoob mongodb
(integer) 1
redis 127.0.0.1:6379> sadd runoob rabitmq
(integer) 1
redis 127.0.0.1:6379> sadd runoob rabitmq
(integer) 0
redis 127.0.0.1:6379> smembers runoob
1) "redis"
2) "rabitmq"
3) "mongodb"

**注意:**以上实例中 rabitmq 添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略。集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。

有序集合(zset)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

*zadd 命令:*添加元素到集合,元素在集合中存在则更新对应score。

常用命令:zadd,zrange,zrem,zcard等

使用场景:Redis sorted set的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。和Set相比,Sorted Set关联了一个double类型权重参数score,使得集合中的元素能够按score进行有序排列,redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。比如一个存储全班同学成绩的Sorted Set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。另外还可以用Sorted Set来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

实现方式:Redis sorted set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

zadd key score member
redis 127.0.0.1:6379> zadd runoob 0 redis
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 mongodb
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 rabitmq
(integer) 1
redis 127.0.0.1:6379> zadd runoob 0 rabitmq
(integer) 0
redis 127.0.0.1:6379> > ZRANGEBYSCORE runoob 0 1000
1) "mongodb"
2) "rabitmq"
3) "redis"

哈希(hash)

应用场景:我们简单举个实例来描述下Hash的应用场景,比如我们要存储一个用户信息对象数据,包含以下信息:

用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4CRyp6LC-1600850002039)(http://www.codesuger.com/upload/2020/09/image-e44ec3576d2a45bc95df72d4258bd5ad.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vFWUwPCV-1600850002039)(1368782-20180821202451984-479691318.png)]

第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B36OuK8Y-1600850002040)(http://www.codesuger.com/upload/2020/09/image-83e9474a4e454bb3968702493d75fd0c.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FPHfYS7i-1600850002040)(http://www.codesuger.com/upload/2020/08/image-f30cf956f7724ef2bcf1a084bbec326f.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oo1tT7Wo-1600850002041)(1368782-20180821202546802-636845502.png)]

第二种方法是这个用户信息对象有多少成员就存成多少个key-value对儿,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。

那么Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存取这个Map成员的接口,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CxtKKI6o-1600850002041)(http://www.codesuger.com/upload/2020/09/image-9ec14efb1cfe44be8bc99c61036e9cd7.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8gAIDZAG-1600850002042)(1368782-20180821202632663-939669694.png)]

也就是说,Key仍然是用户ID, value是一个Map,这个Map的key是成员的属性名,value是属性值,这样对数据的修改和存取都可以直接通过其内部Map的Key(Redis里称内部Map的key为field), 也就是通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题,很好的解决了问题。

这里同时需要注意,Redis提供了接口(hgetall)可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意。

使用场景:存储部分变更数据,如用户信息等。

实现方式:上面已经说到Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。

对照表

类型 简介 特性 场景
String(字符串) 二进制安全 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M
Hash(字典) 键值对集合,即编程语言中的Map类型 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) 存储、读取、修改用户属性
List(列表) 链表(双向链表) 增删快,提供了操作某一段元素的API 1、最新消息排行等功能(比如朋友圈的时间线) 2、消息队列
Set(集合) 哈希表实现,元素不重复 1、添加、删除、查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐
Sorted Set(有序集合) 将Set中的元素增加一个权重参数score,元素按score有序排列 数据插入集合时,已经进行天然排序 1、排行榜 2、带权重的消息队列

Redis实际应用场景

Redis在很多方面与其他数据库解决方案不同:它使用内存提供主存储支持,而仅使用硬盘做持久性的存储;它的数据模型非常独特,用的是单线程。另一个大区别在于,你可以在开发环境中使用Redis的功能,但却不需要转到Redis。

转向Redis当然也是可取的,许多开发者从一开始就把Redis作为首选数据库;但设想如果你的开发环境已经搭建好,应用已经在上面运行了,那么更换数据库框架显然不那么容易。另外在一些需要大容量数据集的应用,Redis也并不适合,因为它的数据集不会超过系统可用的内存。所以如果你有大数据应用,而且主要是读取访问模式,那么Redis并不是正确的选择。

然而我喜欢Redis的一点就是你可以把它融入到你的系统中来,这就能够解决很多问题,比如那些你现有的数据库处理起来感到缓慢的任务。这些你就可以通过Redis来进行优化,或者为应用创建些新的功能。在本文中,我就想探讨一些怎样将Redis加入到现有的环境中,并利用它的原语命令等功能来解决 传统环境中碰到的一些常见问题。在这些例子中,Redis都不是作为首选数据库。

1、显示最新的项目列表

下面这个语句常用来显示最新项目,随着数据多了,查询毫无疑问会越来越慢。

SELECT * FROM foo WHERE ... ORDER BY time DESC LIMIT 10 

在Web应用中,“列出最新的回复”之类的查询非常普遍,这通常会带来可扩展性问题。这令人沮丧,因为项目本来就是按这个顺序被创建的,但要输出这个顺序却不得不进行排序操作。

类似的问题就可以用Redis来解决。比如说,我们的一个Web应用想要列出用户贴出的最新20条评论。在最新的评论边上我们有一个“显示全部”的链接,点击后就可以获得更多的评论。

​ 我们假设数据库中的每条评论都有一个唯一的递增的ID字段。我们可以使用分页来制作主页和评论页,使用Redis的模板,每次新评论发表时,我们会将它的ID添加到一个Redis列表:

LPUSH latest.comments <ID> 

我们将列表裁剪为指定长度,因此Redis只需要保存最新的5000条评论:

LTRIM latest.comments 0 5000 

每次我们需要获取最新评论的项目范围时,我们调用一个函数来完成(使用伪代码):

FUNCTION get_latest_comments(start, num_items):  
    id_list = redis.lrange("latest.comments",start,start+num_items - 1)  
    IF id_list.length < num_items  
        id_list = SQL_DB("SELECT ... ORDER BY time LIMIT ...")  
    END  
    RETURN id_list  
END 

这里我们做的很简单。在Redis中我们的最新ID使用了常驻缓存,这是一直更新的。但是我们做了限制不能超过5000个ID,因此我们的获取ID函数会一直询问Redis。只有在start/count参数超出了这个范围的时候,才需要去访问数据库。我们的系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,而主页或第一个评论页是不会麻烦到硬盘上的数据库了。

2、删除与过滤

我们可以使用LREM来删除评论。如果删除操作非常少,另一个选择是直接跳过评论条目的入口,报告说该评论已经不存在。

redis 127.0.0.1:6379> LREM KEY_NAME COUNT VALUE

有些时候你想要给不同的列表附加上不同的过滤器。如果过滤器的数量受到限制,你可以简单的为每个不同的过滤器使用不同的Redis列表。毕竟每个列表只有5000条项目,但Redis却能够使用非常少的内存来处理几百万条项目。

3、排行榜相关

另一个很普遍的需求是各种数据库的数据并非存储在内存中,因此在按得分排序以及实时更新这些几乎每秒钟都需要更新的功能上数据库的性能不够理想。

典型的比如那些在线游戏的排行榜,比如一个Facebook的游戏,根据得分你通常想要:

  • 列出前100名高分选手

    列出某用户当前的全球排名

这些操作对于Redis来说小菜一碟,即使你有几百万个用户,每分钟都会有几百万个新的得分。

模式是这样的,每次获得新得分时,我们用这样的代码:

 ZADD leaderboard  <score>  <username>

你可能用userID来取代username,这取决于你是怎么设计的。

得到前100名高分用户很简单:ZREVRANGE leaderboard 0 99。

用户的全球排名也相似,只需要:ZRANK leaderboard 。

4、按照用户投票和时间排序

排行榜的一种常见变体模式就像Reddit或Hacker News用的那样,新闻按照类似下面的公式根据得分来排序:

score = points / time^alpha

因此用户的投票会相应的把新闻挖出来,但时间会按照一定的指数将新闻埋下去。下面是我们的模式,当然算法由你决定。

模式是这样的,开始时先观察那些可能是最新的项目,例如首页上的1000条新闻都是候选者,因此我们先忽视掉其他的,这实现起来很简单。

每次新的新闻贴上来后,我们将ID添加到列表中,使用LPUSH + LTRIM,确保只取出最新的1000条项目。

有一项后台任务获取这个列表,并且持续的计算这1000条新闻中每条新闻的最终得分。计算结果由ZADD命令按照新的顺序填充生成列表,老新闻则被清除。这里的关键思路是排序工作是由后台任务来完成的。

5、处理过期项目

另一种常用的项目排序是按照时间排序。我们使用unix时间作为得分即可。

模式如下:

  • 每次有新项目添加到我们的非Redis数据库时,我们把它加入到排序集合中。这时我们用的是时间属性,current_time和time_to_live。

    • 另一项后台任务使用ZRANGE…SCORES查询排序集合,取出最新的10个项目。如果发现unix时间已经过期,则在数据库中删除条目。

6、计数

Redis是一个很好的计数器,这要感谢INCRBY和其他相似命令。

我相信你曾许多次想要给数据库加上新的计数器,用来获取统计或显示新信息,但是最后却由于写入敏感而不得不放弃它们。

好了,现在使用Redis就不需要再担心了。有了原子递增(atomic increment),你可以放心的加上各种计数,用GETSET重置,或者是让它们过期。

例如这样操作:

INCR user:<id> EXPIRE 

user:<id> 60 

你可以计算出最近用户在页面间停顿不超过60秒的页面浏览量,当计数达到比如20时,就可以显示出某些条幅提示,或是其它你想显示的东西。

7、特定时间内的特定项目

另一项对于其他数据库很难,但Redis做起来却轻而易举的事就是统计在某段特点时间里有多少特定用户访问了某个特定资源。比如我想要知道某些特定的注册用户或IP地址,他们到底有多少访问了某篇文章。

每次我获得一次新的页面浏览时我只需要这样做:

SADD page:day1:<page_id> <user_id>

当然你可能想用unix时间替换day1,比如time()-(time()%3600*24)等等。

想知道特定用户的数量吗?只需要使用

SCARD page:day1:<page_id>

需要测试某个特定用户是否访问了这个页面?

SISMEMBER page:day1:<page_id>

8、实时分析正在发生的情况,用于数据统计与防止垃圾邮件等

我们只做了几个例子,但如果你研究Redis的命令集,并且组合一下,就能获得大量的实时分析方法,有效而且非常省力。使用Redis原语命令,更容易实施垃圾邮件过滤系统或其他实时跟踪系统。

9、Pub/Sub

Redis的Pub/Sub非常非常简单,运行稳定并且快速。支持模式匹配,能够实时订阅与取消频道。

10、队列

你应该已经注意到像list push和list pop这样的Redis命令能够很方便的执行队列操作了,但能做的可不止这些:比如Redis还有list pop的变体命令,能够在列表为空时阻塞队列。

现代的互联网应用大量地使用了消息队列(Messaging)。消息队列不仅被用于系统内部组件之间的通信,同时也被用于系统跟其它服务之间的交互。消息队列的使用可以增加系统的可扩展性、灵活性和用户体验。非基于消息队列的系统,其运行速度取决于系统中最慢的组件的速度(注:短板效应)。而基于消息队列可以将系统中各组件解除耦合,这样系统就不再受最慢组件的束缚,各组件可以异步运行从而得以更快的速度完成各自的工作。

此外,当服务器处在高并发操作的时候,比如频繁地写入日志文件。可以利用消息队列实现异步处理。从而实现高性能的并发操作。

11、缓存

Redis的缓存,这里只是简单的说一下。Redis能够替代memcached,让你的缓存从只能存储数据变得能够更新数据,因此你不再需要每次都重新生成数据了。

如果有什么问题请在下面留言

-------书,打开能净化人的心灵,合上能净化别人的脑袋。

猜你喜欢

转载自blog.csdn.net/qq_37806753/article/details/108756003