Redis(一)基础

一、基本特性

  1. 速度快
  2. 支持多种数据类型
  3. 支持多种编程语言
  4. 持久化,内存淘汰
  5. 支持事务,发布订阅,pipeline,lua
  6. 集群,分布式

二、基本命令

  • 增加/修改:set key value
  • 查询:get key
  • 查询全部的key:keys *
  • 查看key的数量:dbsize
  • 查询key是否存在:exists key
  • 删除:del key key……可一次删除多个
  • 更改key的名字:rename oldKey newKey
  • 查看key存储的value的数据类型:type key
  • 清空当前数据库内容:flushdb
  • 清空全部内容:flushall

接下来,将介绍redis支持存储的value类型

三、string类型

1、存储类型

  1. Int整形
  2. Float 单精度浮点型
  3. String 字符串

2、常见命令

  • 查看固定长度的值:getrange key int int,示例:getrange redis 0 1 即查询key为redis的value的0至1位的数值
  • 查看value长度:strlen key
  • 追加字符串:append key value
  • 如果key不存在,增加成功:setnx key value
  • 同时增加多个key和value:mset key value key value
  • 同时查看多个key的value:mget key key……
  • 对key的value增加1:incr key
  • 对key的value增加任意数值:incrby key int
  • 对key的value减少1:decr key
  • 对key的value减少任意数值:decr key int
  • 对key的value增加任意浮点数:incrbyfloat key float

3、存储原理

在这里插入图片描述
key的存储都采用了redis特有的SDS类型,所有的value都是redisObject,然后由redisObject中的指针指向一个SDS存储的数值。

一)SDS

在redis中,key的存储都采用了redis特有的SDS类型

  1. sdshdr5:2^5=32byte(不用)
  2. sdshdr8:2^8=256byte
  3. sdshdr516:2^16=64KB
  4. sdshdr532:2^32=4GB

组成内容如下

  1. len:当前字符数组的长度
  2. alloc:当前字符数组总共分配的内存大小
  3. flags:当前字符数组的属性,用来表示SDS的类型
  4. buf []:字符串真正的值

对比优势

1)C字符数组本身存在的问题
  1. 使用字符数组必须先给目标变量分配足够的空间,否则可能会溢出。
  2. 如果要获取字符长度,必须遍历字符数组,时间复杂度是O(n)。
  3. C字符串长度的变更会对字符数组做内存重分配。
  4. 通过从字符串开始到结尾碰到的第一个’\0’来标记字符串的结束,因此不能保存图片、音频、视频、压缩文件等二进制(bytes)保存的内容,二进制不安全。
2)SDS的优势
  1. 不用担心内存溢出问题,如果需要会对SDS进行扩容
  2. 获取字符串长度时间复杂度为O(1),因为定义了len属性。
  3. 修改字符串长度N次最多需要执行N次内存重分配
  4. 判断是否结束的标志是len属性,可以包含’\O’(它同样以’\0’结尾是因为这样就可以使用C语言中函数库操作字符串的函数了)。
3)对比表格
C字符数组SDS
获取字符串长度的复杂度为O(N)获取字符串长度的复杂度为O(1)
API是不安全的,可能会造成缓冲区溢出API是安全的,不会造成缓冲区溢出
修改字符串长度N次必然需要执行N次内存重分配修改字符串长度N次最多需要执行N次内存重分配
只能保存文本数据可以保存文本或者二进制数据
可以使用所有<string.h>库中的函数可以使用一部分<string.h>库中的函数

二)redisObject

所有的value都是redisObject,然后由redisObject中的指针指向不同的数据地址,redisObject由五部分组成

  1. type:对象类型
  2. encoding:具体的数据结构(编码)
  3. lru:LRU_BITS:24位,对象最后一次被命令程序访问的时间,与内存回收有关
  4. refcount:引用计数,当refcount为0的时候,表示该对象已经不被任何对象引用,则可以进行垃圾回收了
  5. *ptr:指向对象实际的数据结构

4、编码类型

string共有三种编码类型

  1. int:存储8个字节的长整型(long,2^63-1)
  2. embstr:embstr格式的SDS,存储小于44个字节的字符串
  3. raw:raw格式的SDS,存储大于44个字节的字符串

embstr和raw的区别在于embstr只分配一次内存空间,因为RedisObject和SDS是连续的,好处是这样创建时会少分配一次内存空间,删除时少释放一次内存空间,所有数据连在一起,方便查找,坏处是如果字符串长度改变,需要整个重分配内存空间,因此embstr只读。raw需要分配两次内存空间。

5、类型转换

类型在某些情况下会发生转换,编码转换在Redis写入数据时完成,且转换过程不可逆,只能从小内存编码向大内存编码转换,但不包括重新set的情况

  1. int数据不再是整数——raw
  2. int大小超过了long的范围(2^64-1)–embstr
  3. embstr的长度超过了44个字节——raw
  4. 修改了embstr——raw,因为embstr是只读的,因此在对embstr进行修改时,都会转化为raw再进行修改

6、应用场景

  1. 缓存:缓存热点数据提高访问速度
  2. 分布式session:因为是分布式的独立服务,数据可以在多个应用间共享,例如可以缓存token
  3. 分布式锁:setnx方法,只有不存在时才能添加成功,返回true,存在竞争性
  4. 分布式全局ID:int类型,利用incr或incrby增加数值操作的原子性来实现一次增加一段计数,例如分库分表的场景,一次拿一段数据
  5. 计数器:int类型,incr方法,暂时将计数结果加入到redis中
  6. 限流:int类型,incr方法,访问者每做某种操作就增加计数,超过一定数值就禁止操作。

四、hash类型

如果想要在redis中存储一张表的数据,要怎么做呢?我们可以采用序列化的方式,将一张表的数据压缩成一个字符串,或者,在redis有一种方法是key分层,可以将多个key用冒号:分开,例如
mset key0:key1:key2 value0 key3:key4:key5 value1 key6:key7:key8 value2
取值时一次取出,用list接收
mget key0:key1:key2 key3:key4:key5 key6:key7:key8
但这样做显然也有缺点,就是key太长,很不方便,除此之外,还有一个方法,就是hash类型

1、用法与优缺点

hash类型用来存储多个无序的键值对,最大存储量为2^32-1(40亿),hash的value只能是string类型,不能是其他嵌套类型如hash或list
hash存储的优点

  1. 节省内存空间
  2. 减少key冲突
  3. 取值减少性能消耗

hash存储的缺点

  1. field不能单独设置过期时间
  2. 需要考虑数据量分布的问题,内部数据无法被拆分

在这里插入图片描述

2、常见命令

  1. 以hash形式保存数据:hset key field value
  2. 以hash形式保存多对数据:hmset key field0 value0 field1 value1 field2 value2
  3. 以hash形式获取数据:hget key field
  4. 以hash形式获取多对数据:hmget key field0 field1 field2
  5. 获取某个hash值的全部field:hkeys key
  6. 获取某个hash值的全部value:hvals key
  7. 获取某个hash值的全部内容:hgetall key
  8. 删除某个hash值的某个field:hdel key field
  9. 获取某个hash值的field数量:hlen key
  10. 删除某个hash值:del key

3、存储结构

一)ziplist(压缩列表)

ziplist(压缩列表)OJB_ENCODING_ZIPLIST,是一个经过特殊编码的,由连续内存块组成的双向链表。它不存储指向上一个链表节点和指向下一个链表节点的指针,而是储存上一个节点长度和当前节点长度。

组成内容

  1. prevrawlensize:int 存储上一个链表节点的长度数值所需要的字节数
  2. prevrawlen:int 上一个链表节点占用的长度
  3. lensize:int 储存当前链表节点的长度数值所需要的字节数
  4. len:int 当前链表节点占用的长度
  5. headersize:int 当前链表节点的头部大小(prevrawlensize+lensize)即非数据域的大小
  6. encoding:char 编码方式
  7. *p:char 压缩链表以字符串的形式保存,该指针指向当前节点起始位置

应用情况

  1. 一个hash对象保存的field数量<512个
  2. 一个hash对象中所有的field和value的字符串长度都<64byte

如果范围超过上面任意一个限制,就会被转化为hashtable

二)hashtable(哈希表)

1)dictEntry

redis是key-value的数据库,它采用hashtable来存储最外层的key-value结构数据,每个键值对都是一个dictEntry,通过指针指向key的存储结构和value的存储结构,而且next存储了指向下一个键值对的指针,具体内容如下

  • *key:关键字定义
  • *val:值定义
  • *next:dictEntry 指向下一个键值对节点
2)dictht(hashtable)

dictEntry放到了dictht里面,内容如下:

  • **table: dictEntry 哈希表数组
  • size:long 哈希表大小
  • sizemask:long 掩码大小,用于计算索引值。总是等于size-1
  • used:long 已有节点数
3)dict

dictht放到dict里,内容如下:

  • *type:dictType 字典数据
  • *privdata:私有数据
  • ht[2]:dictht 一个字典有两个hash表
  • rehashidx:long rehash索引
  • iterators:long 当前正在使用的迭代器数量
4)redisDb

最外层的结构实际上是redisDb,redisDb里放置的是dict,内容如下

  • *dict:所有的键值对
  • *expires:设置了过期时间的键值对
5) 整体结构

在dict的ht[1]指向的dictht的table一直为空,为扩容做准备,ht[0]指向一个dictEntry数组,每一个数组内容又指向一个链表,也就是说,hashtable本质上就是一个数组加链表构成的。dictEntry的位置将根据field的hash计算得到位数,如果为空则说明当前没有任何field进行hash计算后到这个地址,多于一个则证明出现hash冲突。如果dictEntry总数除以数组长度得到的负载因子,也就是平均每一个ht保存了5个entry时,证明这个table已经存满了,需要扩容。
在这里插入图片描述

6) 扩容rehash

rehash的步骤

  1. 为字符ht[1]哈希表分配空间。ht[1]的大小为第一个大于等于ht[O].used*2的2的N次方幂。比如已经使用了10000,那就是16384。
  2. 将所有的ht[0]上的节点rehash到 ht[1]上,重新计算hash 值和索引,然后放入指定的位置。
  3. 当ht[0]全部迁移到了ht[1]之后,释放 ht[0]的空间,将ht[1]设置为ht[0]表,并创建新的 ht[1],为下次rehash做准备。

4、应用场景

购物车使用场景

  • key:用户ID(一个用户一个购物车)
  • field:商品ID(购物车有很多商品)
  • value:商品数量(每种商品数量不同)
  • 商品数量增加:hincr
  • 商品数量减少:hincrby key field -1
  • 删除商品:hdel
  • 全选商品:hgetall
  • 购物车商品种类数:hlen

五、list类型

1、用法与优缺点

存储有序的字符串(从左到右,因此左边是header,右边是tail),元素可以重复,最大存储数量2^32-1(40亿左右)

2、常见命令

  • 向某个队列的左边放入一个元素:lpush key value
  • 向某个队列的左边放入数个元素:lpush key value0 value1
  • 向某个队列的右边放入数个元素:rpush key value0 value1
  • 某个队列的最左边元素弹出,这个队列将失去这个元素:lpop key
  • 某个队列的最右边元素弹出,这个队列将失去这个元素:rpop key
  • 取出队列的第几位元素:lindex key 0
  • 取出队列的第几位到第几位元素:lrange key 0 -1(0代表开始,-1代表末尾)

3、存储结构

在较早的版本中,list采用ziplist和linkedlist两种结构,后来被替换为quicklist

一)整体结构

在这里插入图片描述

二)结构内容

1)quicklist

quicklist的内容如下:

  • *head:quicklistNode 指向双向列表的表头
  • *tail:quicklistNode 指向双向列表的表尾
  • count:long 所有的ziplist中一共存了多少个元素
  • len:long 双向链表的长度,node的数量
  • fill:int ziplist最大大小,对应list-max-ziplist-size
  • compress:压缩深度,对应list-compress-depth
  • bookmark_count:4位,bookmarks数组的大小
  • bookmarks[]:quicklistBookmark 是一个可选字段,quicklist重新分配内存空间时使用,不使用时不占用空间。
2)list-max-ziplist-size(fill)
  • 正数表示单个ziplist最多所包含的entry个数
  • 负数表示单个ziplist的大小,默认8K
  • 数字代表:-1:4KB;-2:8KB;-3:16KB;-4:32KB;-5:64KB;
3)list-compress-depth(compress)

压缩深度,默认是0

  1. 首尾的ziplist不压缩
  2. 首尾第一第二个ziplist不压缩,以此类推。
4)quicklistNode
  • *prev:quicklistNode 指向前一个节点
  • *next:quicklistNode 指向后一个节点
  • *zl:char 指向实际的ziplist
  • sz:int 当前ziplist占用多少字节
  • count:int 当前ziplist中存储了多少个元素,占16bit(下同),最大65536个
  • encoding:int 是否采用了LZF压缩算法压缩节点(RAW1,LZF0)
  • container:int 存储结构为ziplist,未来可能支持其他结构存储(NONE1,ZIPLIST2)
  • recompress:int 当前ziplist是不是已经被解压出来作临时使用
  • attempted_compress:int 测试用
  • extra:int 预留给未来使用

4、应用场景

一)列表

用户的消息列表,网站的公告列表,活动列表,博客的文章列表,评论列表。
存储所有字段,lrange取出一页

二)队列/栈

当做分布式环境下的队列/栈使用
list提供了两个阻塞的弹出操作:BLPOP/BRPOP,可以设置超时时间(单位:秒)

  • BLPOP:BLPOP key timeout 移出并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
  • BRPOP:BRPOP key timeout 移出并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
  • 队列:先进先出,因此使用rpush和blpop,左头右尾,右边进入队列,左边出队列。
  • 栈:先进后出,因此使用rpush和brpop。

六、set类型

1、用法与优缺点

set存储string类型的无序集合,最大存储数量为2^32-1(40亿左右)

2、常见命令

  • 添加一个或者多个元素:sadd key value0 value1 value2
  • 获取所有元素:smembers key
  • 统计元素个数:scard key
  • 随机获取一个元素:srandmember key
  • 随机弹出一个元素:spop key
  • 移除一个或者多个元素:srem key value0 value1 value2
  • 查看元素是否存在:sismember key value

3、存储结构

因为可以存储两种类型,int和string类型,所以采用两种存储方式,intset或hashtable

一)inset

如果元素都是整数类型,就用intset存储
内容如下:

  • encoding:编码类型
  • length:长度,最大长度
  • contents[]:用来存储成员的动态数组

二)hashtable

如果元素不全为整数类型,或者都是整数类型但是超过512个的时候,就会使用hashtable。
512是一个设置值,可以改变
set类型没有value,但是可以将value置为空来解决hashtable存储set数据的问题。

4、应用场景

一)抽奖

利用随机获取元素的方法,spop setkey

二)点赞,签到,打卡

利用set集合不可重复的特性,可以对内容进行维护和筛选

  • 打卡:sadd
  • 取消打卡:srem
  • 是否打卡:sismember
  • 所有打卡者:smembers
  • 打卡人数:scard

三)商品标签

用来维护商品的不同标签

四)商品筛选

不同的set之间可以取得交集,并集和差集

  • 获取差集:sdiff set1 set2
  • 获取交集:sinter set1 set2
  • 获取并集:sunion set1 set2

五)用户关注,互相关注

利用set存储用户关注的账号,利用并集获取互相都关注的账号

六、zset类型

1、用法与优缺点

zset存储有序的元素,每一个元素都有一个score,按照score从小到大排序,score相同的时候,按照key的ASCII吗排序

2、常见命令

  • 添加元素:zadd zsetkey score0 value0 score1 value1 score2 value2
  • 获取所有元素和分值,从小到大排列:zrange zsetkey 0 -l withscores
  • 获取所有元素和分值,从大到小排列:zrevrange zsetkey 0 -1 withscores
  • 根据分值区间获取元素:zrangebyscore zsetkey score0 score1
  • 移除元素,也可以根据score或rank删除:zrem zsetkey value0 value1
  • 统计元素个数:zcard zsetkey
  • 分值递增:zincrby zsetkey score value
  • 根据分值统计个数:zcount zsetkey score0 score1
  • 获取元素rank:zrank zsetkey value
  • 获取元素score:zscore zsetkey value
  • 也有倒序的rev操作(reverse)

3、存储结构

采用两种存储方式,ziplist和skiplist+dict

一)ziplist

如果元素数量小于128,并且所有元素长度小于64bytes,使用ziplist,如果这两个条件任意一个不满足,则转换为skiplist+dict结构。
ziplist会根据score进行排序,插入的时候会移动后面的数据
内容如下:

  • encoding:编码类型
  • length:长度,最大长度
  • contents[]:用来存储成员的动态数组

二)skiplist+dict

skiplist,跳表,和普通链表不同的是,它还有跳跃指针。

1)有序链表

在一个有序链表中,想要找到一个数据,就必须从头开始进行逐个比较,直到找到包含数据的那个节点,或是比给定数据大的节点为止,时间复杂度为O(N),当需要插入数据时,同理。二分查找只适合有序数组,有序链表无法二分查找。
在这里插入图片描述

2)第二层链表

假如每相邻两个节点增加一个指针,让该指针指向下下个节点,这样所有新增加的指针就组成了一个新的链表,但它包含的节点个数只有原来的一半,现在再查找数据时,就可以先沿着新的链表进行查找,找到比它大的节点时就可以进入下一层链表继续查找。在这里插入图片描述

3)跳表

实际在skiplist中,节点的链表层级(level)被算法控制决定,在某种层度上来说是随机的,因此链表不止一层。
在这里插入图片描述
算法如下

int zslRandomLevel(void) {
	int level = 1;
	while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
		level += 1;
	return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}
4)具体实现

zskiplist的内容组成

  • *header:zskiplistNode 指向跳跃表的头结点
  • *tail:zskiplistNode 指向跳跃表的尾结点
  • length:long 跳跃表的节点数
  • level:int 最大层数

zskiplistNode的内容组成

  • ele:sds zset的元素
  • score:double 分值score
  • *backwark:zskiplistNode 后退指针
  • zskiplistLevel
    • *forward:zskiplistNode 前进指针,对应level的下一个节点
    • span:long 从当前节点到下一节点的跨度(跨越的节点数)
  • level[]:层

4、应用场景

排行榜,通过点击来进行计数排名

  • zincrby:对某条新闻进行点击数加一
  • zrevrange:倒序排列新闻热度,即从大到小排列

七、其他类型

1、bitmaps

bitmaps是在字符串类型上定义的位操作,一个字节由八位二进制组成,可以进行多种位运算

2、hyperloglogs

hyperloglogs提供了一种不太精确的基数统计方法,用来统计一个集合中不重复的元素个数。

3、geo

geo主要用于存储地理位置信息,并对存储的信息进行操作,如增加,获取地址位置信息,计算两个位置间的距离,指定范围内的迪狸合集

4、streams

streams是 Redis 5.0 版本新增加的数据结构,主要用于支持多播可持久化消息队列,用于实现发布订阅功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值