Redis 数据类型和对象机制
一、Redis 简介
Redis 是(key-value)的 NoSQL 数据库,所有的 key 都是 String ,它的 value 可以是 String、hash、list、set、zset(有序集合)、Bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等数据类型,这些类型都支持 push/pop、add/remove 及取交集和差集。而且这些操作都是原子性的。
Redis 的数据是缓存在内存中,但是 Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件中。在此基础上实现了 master-slave (主从)同步
主从复制
Redis 提供复制功能,能实现多个相同数据的 Redis 副本,复制功能是分布式 Redis 的基础
删除数据命令
# 删除指定的 key 数据
del key
# 根据 value 选择非阻塞删除,也就是现在是将 keys 从 keyspace 元数据中删除,真正的删除会在后续异步操作
unlink key
二、Redis 的数据类型
2.1 String
redis 中最基本的数据结构,所有的 key 都是 String 。String 类型的 Value 可以是 String、数字、jpg图片或者序列化的对象(值不能超过 512MB)
常见命令
-
set key value [ex seconds][px milliseconds][nx|xx]
: 设置给定键和值 -
get key
: 获取值 -
del key
:删除存储在给定键中的值 -
incr key
: 将 key 对应的值加1 -
decr key
: 将 key 对应的值减1 -
incrby key amount
: 将key 对应的值加上整数 -
decrby key amount
:将key 对应的值减去整数
应用场景
- 缓存:可以将常见的字符串、图片等信息缓存在 redis,mysql 作为持久化层。降低 mysql 的读写压力
-
计数器: 实现快速计数、查询缓存,同时数据可以异步落地到其他数据源。
-
共享Session:分布式服务器将用户的 Session 进行集中的管理,每次用户更新或者查询登录信息都直接从 Redis 中集中获取。
2.2 Hash
哈希类型指的是 value 本身又是一个键值对结构,比如 value = {{field1, value1}, … {fieldN, valueN}}。
常见命令
hset hash-key sub-key1 value1
:添加键值对hget hash-key key1
: 获取制定散列键的值hgetall hash-key
:获取哈希中包含的所有键值对hdel hash-key sub-key1
: 在哈希中移除这个键
应用场景
- 缓存:能够更加直观,相比 String 来说更加节省空间。比如缓存用户信息
2.3 List
Redis 中的 List 采用双端链表来实现,可以用来存储多个有序的字符创,列表最多可以存储 2^32 - 1 个元素(element)。可以对列表两端插入(push)和弹出(pop),还可以获取制定范围的元素列表,获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色。
常见命令
rpush, lpush 分别是右边和左边插入,linsert 命令会从列表中找到等于某个值的元素,在其前或者后插入新的元素。可以转换成其他的数据结构:
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpush+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
应用场景
- 消息队列:
lpush + brpop
组合可以实现阻塞队列,生产者使用 lpush 从左侧插入元素,多个消费者使用 brpop 阻塞式抢列表尾部的元素。保证消费的负载均衡和高可用性
2.4 set
set 类型是用来保存多个字符串元素,但是 set 中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。它的底层是通过哈希表来实现的,因此添加、删除、查找的复杂度都是 O(1)
常见命令
sadd key value
: 向集合中添加一个或者多个成员scard key
: 获取集合中的成员数smember key member
: 返回集合中的所有成员sismember key member
: 判断 member 元素是否是集合 key 的成员
应用场景
- 标签:给用户添加标签,所有这样有同一标签或者类似的可以推荐关注的事情或者关注的人
2.5 zset
有序集合 zset 相对于 set 而言,其内部的元素可以进行排序,它是通过给每个元素设置一个分数来作为排序的依据。
常见命令
zadd zset-key int member1
: 将一个带有给定分值的成员添加到有序的集合中zrange zset-key 0-1
: 根据元素在有序集合中所处的位置,从有序集合中获取对应的元素zrem zset-key member1
: 如果给定元素存在于有序集合中,就移除该元素
应用场景
- 排行榜:榜单可以按照用户关注数,更新时间等打分,并做排行
2.6 HyperLogLogs
HyperLogLog并不是一种新的数据结构(实际类型为字符串类 型),而是一种基数算法,通过HyperLogLog可以利用极小的内存空间 完成独立总数的统计,比如注册IP u数,每日访问IP 数等等。它是一个基于基数估算的算法,只能比较准确的估算出基数,可以使用少量固定的内存去存储并识别集合中的唯一元素。而且这个估算的技术并不一定准确,它是一个带有0.81%标准错误的近似值(对于一些可以接受容错的业务场景可以忽略不计)
例如2016-03-06的访问用户是 uuid-1、uuid-2、uuid-3、uuid-4,2016-03-05的访问用户是uuid-4、uuid-5、uuid-6、uuid-7,如图所示。
常用命令
-
pfadd : 用于在基数统计中添加元素,添加成功会返回1
-
pfcount:用于计算一个或者多个 HyperLogLogs 的独立总数
-
pfmerge:求出多个HyperLogLogs 的并集并赋值给 destkey
应用场景
- IP 数 : 用于统计某个时段的 IP 或者用户数
2.7 Bitmaps
它本身不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作
Bitmaps 相当于一个以位为单位的数组,数组的每个单元只能存储0 和 1 , 数组的下标在 Bitmaps 中叫做偏移量。
常用命令
setbit key offset value
: 设置键和偏移量的值getbit key offset
: 获取键的第 offset 位的值bitcount key
: 统计该键的次数值
应用场景
- 活跃用户分析: 存储和统计一天中活跃的用户
2.8 Geo
Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位 置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能
常用命令
- geoadd: 添加地理位置信息
-
geopos: 获取地理位置信息
-
geodist: 获取两个地理位置的距离
?
-
georadius: 获取范围内的信息位置集合–>附近的人
-
geohash: 将二维经纬度转换为一维的字符串
-
zrem: 删除地理位置信息(实际上是利用 zset 中的命令实现对位置信息的删除)
应用场景
- 附近的人
- 推算两地之间的距离
三、Redis 的数据结构
为什么 Redis 会设计 RedisObject 对象,因为操作数据类型的命令除了要对键的类型进行检查以外,还需要根据数据类型的不同编码进行多态处理,所以 Redis 构建了自己的类型系统,主要有:
- redisObject 的对象机制
- redisObject 对象的类型检查和多态
- 对 redisObject 进行分配、共享和销毁的机制
3.1 redisObject 的对象机制
/*
* Redis对象
*/
typedef struct redisObject {
//类型
unsigned type:4;
//编码方式
unsigned encoding:4;
//LRU 记录最后一次访问时间
unsigned lru:LRU_BITS;
//引用计数
int refcount;
//指向底层数据结构实例
void *ptr
} robj;
type属性
记录了对象所保存的值类型,也就是常用的五个数据类型
/*
* 对象类型
*/
#define OBJ_STRING 0 // 字符串
#define OBJ_LIST 1 // 列表
#define OBJ_SET 2 // 集合
#define OBJ_ZSET 3 // 有序集
#define OBJ_HASH 4 // 哈希表
encoding 属性
记录了对象所保存的值的编码,表示数据类型对应的编码类型
/*
* 对象编码
*/
#define OBJ_ENCODING_RAW 0 /* Raw representation */
#define OBJ_ENCODING_INT 1 /* Encoded as integer */
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3 /* 注意:版本2.6后不再使用. */
#define OBJ_ENCODING_LINKEDLIST 4 /* 注意:不再使用了,旧版本2.x中String的底层之一. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
* ptr 指针
它指向实际保存值的数据结构,而数据结构类型是由前面的 encoding 和 type 两个属性来决定。如下图,数据类型和编码类型决定指向实际的数据结构。
lru 属性
记录的是对象最后一次被命令程序访问的时间,那么如何实现对对象的回收,这里引入一个概念:空转时长
空转时长,也就是当前系统时间减去 键的值对象的 LRU 时间。如果服务器用于回收内存的算法是 Volatile-lru 或者 allkeys-lru。那么当服务器占用的内存树超过了 maxmemory 选项所设置的上限值时,空转时长较高的那部分键会优先被服务器所释放。
refcount 属性
用于计数,对指向这个对象的引用计数。
比如创建了一个值为 100 的 key A ,使用 OBJECT REFCOUNT 命令查看 key A 的值对象的引用计数 refcount ,发现引用计数为 2,说明这个值对象被两个程序所引用,两个程序共享了这个值对象的 key
那么当对象的 refcount 值为 0 时,这个对象将会被内存回收释放,这也是对象的销毁机制。(对应 JVM 里面的引用计数法标记)
3.2 redis 命令的类型检查和多态
redis 当执行一个处理数据类型命令时,比如 LPOP key
命令redis 执行的步骤:
- 根据给定的 key,在数据库字典中查找对应的 redisObject 对象,没找到就返回null
- 检查找到的 redisObject 的 type 属性和执行命令所需要的类型是否相同,如果不相同就返回类型错误
- 根据 redisObject 的 encoding 属性所指定的编码,选择合适的操作函数来处理底层的数据结构
- 最后返回命令的操作结构
3.3 redisObject 对象共享和销毁
共享对象的出现是为了避免重复分配的麻烦。通过 refcount 来表示对象所引用的次数。比如创键一个 值为 100 的 key A,然后再创建一个值为 100 的 key B ,这个时候共享对象的引用计数值变为了 3
redis> SET A 100
OK
redis> SET B 100
OK
redis> OBJECT REFCOUNT A
(integer) 3
此外共享对象不单单只有字符串键可以使用, 那些在数据结构中嵌套了字符串对象的对象(linkedlist
编码的列表对象、 hashtable
编码的哈希对象、 hashtable
编码的集合对象、以及 zset
编码的有序集合对象)都可以使用这些共享对象。
为什么redis 不共享包含value 为字符串的对象?
当服务器考虑将一个共享对象设置为键的值对象时, 程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同, 只有在共享对象和目标对象完全相同的情况下, 程序才会将共享对象用作键的值对象, 而一个共享对象保存的值越复杂, 验证共享对象和目标对象是否相同所需的复杂度就会越高, 消耗的 CPU 时间也会越多:
- 如果共享对象是保存整数值的字符串对象, 那么验证操作的复杂度为 O(1) ;
- 如果共享对象是保存字符串值的字符串对象, 那么验证操作的复杂度为 O(N) ;
- 如果共享对象是包含了多个值(或者对象的)对象, 比如列表对象或者哈希对象, 那么验证操作的复杂度将会是 O(N^2) 。
因此, 尽管共享更复杂的对象可以节约更多的内存, 但受到 CPU 时间的限制, Redis 只对包含整数值的字符串对象进行共享。
引用计数及对象的销毁
前面谈到过,redisObject 中带有一个 refcount 属性,表示这个对象被引用了多少次。
- 当对象被新程序共享时,其 refcount 值加1;
- 当使用完一个对象后或者消除一个对象的引用后,程序将对象的 refcount 值减1
- 当对象的 refcount 降为0 时,这个 redisObject 结构以及它所引用的数据结构的内存都会被释放
四、参考资料
- 《redis 开发与运维》 付磊
- 《redis 设计与实现》 黄健宏
- https://pdai.tech/md/db/nosql-redis/db-redis-x-redis-object.html
欢迎使用Markdown编辑器
你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。
新的改变
我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:
- 全新的界面设计 ,将会带来全新的写作体验;
- 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
- 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
- 全新的 KaTeX数学公式 语法;
- 增加了支持甘特图的mermaid语法1 功能;
- 增加了 多屏幕编辑 Markdown文章功能;
- 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
- 增加了 检查列表 功能。
功能快捷键
撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G
查找:Ctrl/Command + F
替换:Ctrl/Command + G
合理的创建标题,有助于目录的生成
直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC
语法后生成一个完美的目录。
如何改变文本的样式
强调文本 强调文本
加粗文本 加粗文本
标记文本
删除文本
引用文本
H2O is是液体。
210 运算结果是 1024.
插入链接与图片
链接: link.
图片:
带尺寸的图片:
居中的图片:
居中并且带尺寸的图片:
当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。
如何插入一段漂亮的代码片
去博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片
.
// An highlighted block
var foo = 'bar';
生成一个适合你的列表
- 项目
- 项目
- 项目
- 项目
- 项目1
- 项目2
- 项目3
- 计划任务
- 完成任务
创建一个表格
一个简单的表格是这么创建的:
项目 | Value |
---|---|
电脑 | $1600 |
手机 | $12 |
导管 | $1 |
设定内容居中、居左、居右
使用:---------:
居中
使用:----------
居左
使用----------:
居右
第一列 | 第二列 | 第三列 |
---|---|---|
第一列文本居中 | 第二列文本居右 | 第三列文本居左 |
SmartyPants
SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:
TYPE | ASCII | HTML |
---|---|---|
Single backticks | 'Isn't this fun?' | ‘Isn’t this fun?’ |
Quotes | "Isn't this fun?" | “Isn’t this fun?” |
Dashes | -- is en-dash, --- is em-dash | – is en-dash, — is em-dash |
创建一个自定义列表
-
Markdown
- Text-to- HTML conversion tool Authors
- John
- Luke
如何创建一个注脚
一个具有注脚的文本。2
注释也是必不可少的
Markdown将文本转换为 HTML。
KaTeX数学公式
您可以使用渲染LaTeX数学表达式 KaTeX:
Gamma公式展示 Γ ( n ) = ( n ? 1 ) ! ? n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n?1)!?n∈N 是通过欧拉积分
Γ ( z ) = ∫ 0 ∞ t z ? 1 e ? t d t ? . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=∫0∞?tz?1e?tdt.
你可以找到更多关于的信息 LaTeX 数学表达式here.
新的甘特图功能,丰富你的文章
- 关于 甘特图 语法,参考 这儿,
UML 图表
可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图:
这将产生一个流程图。:
- 关于 Mermaid 语法,参考 这儿,
FLowchart流程图
我们依旧会支持flowchart的流程图:
- 关于 Flowchart流程图 语法,参考 这儿.
导出与导入
导出
如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。
导入
如果你想加载一篇你写过的.md文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。
注脚的解释 ??
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!