一、Redis 简述
redis是一个开源的使用C语言编写的一个kv存储系统,是一个速度非常快的非关系远程内存数据库。它支持包括String、List、Set、Zset、hash五种数据结构。
除此之外,通过复制、持久化和客户端分片等特性,用户可以很方便地将redis扩展成一个能够包含数百GB数据和每秒处理上百万次的请求的系统。目前支持多种语言的api,方便用户使用。
redis同时也内置了事务、LUA脚本、复制等功能,提供两种持久化选项,一种是每隔一段时间将数据导入到磁盘(RDB快照模式),另一种是追加命令到日志中(AOF模式)。如果只是作为高效的内存数据库使用也可以关闭持久化功能。
通过哨兵(sentinel)和自动分区(Cuuster)的方式可以提高redis服务器的高可用性。
与关系型数据库相比,redis的命令请求不需要经过查询分析器或查询优化器进行处理,也避免了更新数据时引起的随机读\写,这些慢操作。它直接读写内存中的数据,并且数据是按照一定的数据结构存储的,所以它的速度非常快。
- redis命令手册:http://www.redis.cn/commands.html
- redis 命令说明:https://www.redis.net.cn/order/
二、Redis 基本数据类型
声明:这里的数据类型是value的数据类型,key的数据类型(
区分大小写
)都是字符串;
2.1 Redis 键(key)
2.2 Redis字符串 String
1) 定义与简述
与其他语言或者键值存储提供相似,键(key)------值(value) (字符串格式),字符串拥有一些操作命令,如原子自增 incr ,自减 decr(自增自减范围signed long区间,超出会报错
) 、get、 set、 del等等。
Redis是使用C语言开发,但是C中并没有字符串类型,只能使用指针或者字符数组的形式表示一个字符串,所以Redis设计了一种简单的动态字符串(SDS[Simple Dynamic String]) 作为底层实现:【SDS具体结构后续探索】
定义SDS对象,此对象中包含三个属性:
- len buf中已经占有的长度(表示此字符串的实际长度)
- free buf中未使用的缓冲区长度
- buf[] 实际保存字符串数据的地方
所以取字符串的长度时间复杂度为O(1) , 另外 buf[] 依然采用C语言的以 \0 结尾可以直接使用C语言部分标准C字符串库函数。
空间分配原则:
- 当len小于1MB(1024*1024)时 增加字符串分配空间大小为加倍现有空间
- 当len大于1MB时每次只会多扩容1MB的空间,【字符串最大长度是512MB】
由此总结以下特性:
- Redis为字符分配空间的次数是小于等于字符串的长度N,而原C语言中分配原则必须为N,降低了分配次数,提高了追加速度,代价就是多占用一些内存空间,且这些空间不会自动释放。
- 二进制安全性高
- 高效的计算字符串长度(时间复杂度O(1))
- 高效的追加字符串操作
Redis的字符串类型是其他几种的基础,值可以是字符串(简单,复杂的json,xml),数字(整型,浮点),二进制(图片,音频,视频),最大值不能超过512MB。
2)常用命令
3)应用场景
1、分布式锁
2、抖音点赞
3、喜欢的文章
阅读数:只要点击了rest地址,直接可以使用incr key 命令增加一个数字1,完成记录数字。
2.3 Redis 列表(List)
1)定义及简述
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边),它底层是一个双端链表的结构,容量是2的32次方减1个元素,大概40多亿,主要功能有push/pop等,一般用在栈、队列、消息队列等场景。
left、right都可以插入添加;
- 如果键不存在,创建新的链表;
- 如果键已存在,新增内容;
- 如果值全移除,对应的键也就消失了。
因为它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。
在3.2版本以后,重新引入了一个quicklist的数据结构,列表的底层都是使用quicklist实现的,它结合ziplist和linkedlist的优点。按照原文解释这种数据结构时【A doubly linked list of ziplists】的意思就是一个由ziplist组成的双向链表。那么这两种数据结构是怎么结合的呢?
宏观上就是一个链表结构,只不过每个节点都是以压缩列表的ziplist的结构保存着数据,而每个ziplist又可以包含多个entry,也可以理解为每一个quicklist 节点保存的是一片数据,而不是一个数据。quicklist将多个ziplist用双向指针串联起来。
整体上quicklist就是一个双向链表结构,和普通链表一样插入删除效率很高,但是查询效率却是O(n),不过这样链表访问两端的元素时间复杂度就是O(1),所以对list的操作多是 poll 或 push;
每个quicklist 节点就是一个ziplist ,具备压缩列表的特性
2)常见命令
3)应用场景
1、微信公众号订阅消息
- 大V作者李永乐老师和CSDN发布了文章分别是 11 和 22
- 你关注了他们两个,只要他们发布了新文章,就会安装进我的List
lpush likearticle:你id 11 22
- 查看你自己的号订阅的全部文章,类似分页,下面0~10就是一次显示10条
lrange likearticle:你id 0 9
2.4 Redis 哈希表(hash)
1) 定义及简述
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)。
散列可以看做是文档或者关系数据库里的一行hash底层数据结构实现有两种:
一种是ziplist,当存储的数据超过配置的阈值时,就转用hashtable的结构,这种转换比较消耗性能,所以应该尽量避免这种转换操作,同时满足以下两个条件时,才会使用这种结构,
- 当键的个数小于hash-max-ziplist-entries(默认512)
- 当所有值都小于hash-max-ziplist-value(默认64)
另一种就是hashtable。这种结构的时间复杂度为O(1),但是会消耗比较多的内存空间。
缺点:hash结构的存储消耗高于单个字符串。具体使用需要考虑场景是使用hash还是字符串
2)常用命令
3)应用场景
1、早起购物车设计
新增商品 → hset shopcar:uid1024 334488 1
新增商品 → hset shopcar:uid1024 334477 1
增加商品数量 → hincrby shopcar:uid1024 334477 1
商品总数 → hlen shopcar:uid1024
全部选择 → hgetall shopcar:uid1024
2.5 Redis 集合(Set)
1) 定义及简述
Redis的集合和列表都可以存储多个字符串,他们之间的不同在于,列表可以存储多个相同的字符串,而集合通过使用散列表(hashset)来保证自己存储的每个字符串是各不相同(这些散列只有key,而没有与之对应的值),redis中的集合是无序的。
还可能存在另一种集合,那就是intset,它是用于存储整数的有序集合,里面存放同一类型的整数。共有三种整数:int16_t、int32_t、int64_t。查找的时间复杂度为O(logN),但是插入的时候,有可能会涉及到升级(比如:原来是int16_t的集合,当插入int32_t的整数的时候就会为每个元素升级为int32_t)这时候会对内存重新分配,所以此时的时间复杂度就是O(N)级别的了。
注意:intset只支持升级不支持降级操作。
intset在redis.conf中也有一个配置参数set-max-intset-entries默认值为512。表示如果entry的个数小于此值,则可以编码成REDIS_ENCODING_INTSET类型存储,节约内存。否则采用dict的形式存储。
Set集合类型底层使用intset和hashset两种数据结构;intset可以理解为数组,hashset就理解为普通的hash表key为set的值,value为null)
Set的底层存储intset和hashtable是存在编码转换的,使用intset存储必须满足下面两个条件,否则使用hashtable,条件如下:
- 集合对象保存的所有元素都是整数值
- 集合对象保存的元素数量不超过512个
2) 常用命令
2.6 Redis 有序集合(Sorted Set)
1) 定义及简述
zset类似于Java中的SortedSet和HashMap的结合体,一方面使用Set保证了内部Value的唯一性,给每一个value赋予score的排序权重。Zset的底层存储结构包括ziplist或skiplist,在同时满足以下两个条件的时候使用ziplist,其他使用skiplist,条件如下:
- 有序集合保存的元素数量小于128个
- 有序集合保存的所有元素长度小于64个
当ziplist作为底层存储结构时,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。
当skiplist作为zset的底层存储结构的时候,使用skiplist按序保存元素及分值,使用dict来保存元素和分值的映射关系
2)常用命令
2.7 Redis 位图(bitmap)
1) 定义及简述
bitmap是由0和1状态表现的二进制位的bit数组。
用String类型作为底层数据结构实现的一种统计二值状态的数据类型,位图本质是数组,它是基于String数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们称之为一个索引)。
Bitmap支持的最大位数是232位,它可以极大的节约存储空间,使用512M内存就可以存储多达42.9亿的字节信息(232 = 4294967296)。
2) 常见命令
2.8 Redis 基数统计(HyperLogLog)
1)定义及简述
HyperLogLog 是用来做基数统计
的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定且是很小的。(基数:去重后的元素真实个数
)
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
举例说明:
基数统计:用于统计一个集合中不重复元素的个数,就是对集合去重后的计算。
2)常用命令
实际案例
3)应用场景
1、网站首页亿级UV的统计方案
2.9 Redis 地理空间(GEO)
1)定义及简述
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,包括
- 添加地理位置的坐标。
- 获取地理位置的坐标。
- 计算两个位置之间的距离。
- 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合
2) 常用命令
2.10 Redis 流(Stream)
1)定义及简述
Redis Stream 是 Redis 5.0 版本新增加的数据结构。
Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。
简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。
而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失
结构和原理
一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的 ID 和对应的内容
2) 常用命令
建议:
- stream 还是不能100%等价于Kafka、RocketMQ,生产案例少用、慎用。
2.11 Redis 位域(bitfield)
1)定义及简述
通过bitfield命令可以一次性操作多个比特位域(指的是连续的多个比特位),它会执行一系列操作并返回一个响应数组,这个数组中的元素对应参数列表中的相应操作的执行结果。说白了就是通过bitfield命令我们可以一次性对多个比特位域进行操作。
主要作用是:位域修改、溢出控制
2) 常用命令
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
三、总结
本章介绍了redis的十大数据结构和它们使用的底层存储原理,为了达到节省内存和快速访问的目的每种数据结构可能有两种存储和访问结构,在必要的时候会由一种结构转换成另一种结构,但这个转换的过程会消耗系统性能和内存空间的,所以在使用的过程中需要注意这些配置参数,开发中尽量避免达到这些峰值,使得redis能够持续的提供高效的服务。
3.1 五种基本类型的常见场景
1)String类型
redis对于KV操作的效率很高,可以直接用于计数器,例如,统计在线人数等等,另外string类型是二进制存储安全的,所以也可以使用它来存储图片,甚至是视频等。
2)Hash类型
存放键值对,一般可以用来存某个对象的基本属性信息,例如,用户信息,商品信息等,
另外,由于hash的大小在小于配置的大小的时候使用的是ziplist结构,比较节约内存,所以针对大量的数据存储可以考虑使用hash来分段存储来达到压缩数据量,节约内存的目的,例如,对于大批量的商品对应的图片地址名称。比如:商品编码固定是10位,可以选取前7位做为hash的key,后三位作为field,图片地址作为value。这样每个hash表都不超过999个,只要把redis.conf中的hash-max-ziplist-entries改为1024,即可。
3)List类型
列表类型,可以用于实现消息队列,也可以使用它提供的range命令,做分页查询功能。
4)Set类型
集合,整数的有序列表可以直接使用set。可以用作某些去重功能,例如用户名不能重复等,另外,还可以对集合进行交集,并集操作,来查找某些元素的共同点,比如:去重作用实现中奖用户,共同好友等
5)zset类型
有序集合,可以使用范围查找,排行榜功能或者topN功能。