redis核心与实战(一)数据结构篇

1.《redis数据结构概览》
1.数据结构概览
数据模型:一共5种,String(字符串)、List(列表)、Hash(哈希)、Set(集合)和 Sorted Set(有序集合)

数据结构:一共有 6 种,分别是简单动态字符串、双向链表、压缩列表、哈希表、跳表和整数数组。

对应关系图:

2.全局哈希表
Redis 使用了一个全局哈希表来保存所有键值对

哈希桶中的元素保存的并不是值本身,而是指向具体值的指针。这也就是说,不管值是 String,还是集合类型,哈希桶中的元素都是指向它们的指针。

3.列表(list)
对应两种实现方法,一种是压缩列表(ziplist),另一种是双向循环链表。

当列表中存储的数据量比较小的时候,列表就可以采用压缩列表的方式实现。具体需要同时满足下面两个条件:

列表中保存的单个数据(有可能是字符串类型的)小于 64 字节;(cpu缓存行一行最多64字节)
列表中数据个数少于 512 个。
压缩列表:

特点:

可以存储不同字节大小的数据
基于数组实现,但不支持随机访问
获取首尾元素时间复杂度O(1),其他情况O(n)
压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度(所占空间大小)、列表尾的偏移量(列尾到起始位置的偏移量)和列表中的 entry 个数;

其中每个entry,分为四个部分previous_entry_length,entry_length,encoding,content

分别表示 上一个元素的长度,本元素长度 ,编码方式,文本内容

4.字典(hash)
有两种实现方式,一种是压缩列表,另一种是散列表

有当存储的数据量比较小的情况下,Redis 才使用压缩列表来实现字典类型。具体需要满足两个条件:

字典中保存的键和值的大小都要小于 64 字节;
字典中键值对的个数要小于 512 个。
5.集合(set)
有两种实现方法,一种是基于有序数组,另一种是基于散列表。

当要存储的数据,同时满足下面这样两个条件的时候,Redis 就采用有序数组,来实现集合这种数据类型。

存储的数据都是整数;
存储的数据元素个数不超过 512 个。
6.有序集合(sortedset)
当数据量比较小的时候,Redis 会用压缩列表来实现有序集合。具体点说就是,使用压缩列表来实现有序集合的前提,有这样两个:

所有数据的大小都要小于 64 字节;
元素个数要小于 128 个。
7.为什么 sortedset,hash,list都会用压缩列表呢 ?
当数据量小的时候通过数组下标访问最快、占用内存最小,而压缩列表只是数组的升级版;
因为数组需要占用连续的内存空间,所以当数据量大的时候,就需要使用链表了,同时为了保证速度又需要和数组结合,也就有了散列表。

1、内存利用率,数组和压缩列表都是非常紧凑的数据结构,它比链表占用的内存要更少。Redis是内存数据库,大量数据存到内存中,此时需要做尽可能的优化,提高内存的利用率。

2、数组对CPU高速缓存支持更友好,所以Redis在设计时,集合数据元素较少情况下,默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度。当数据元素超过设定阈值后,避免查询时间复杂度太高,转为哈希和跳表数据结构存储,保证查询效率。

2.《redis 动态字符串详解》
1.案例:实现一个图片存储系统,并快速定位文件位置
案例描述:开发一个图片存储系统,要求这个系统能快速地记录图片 ID 和图片在存储系统中保存时的 ID(可以直接叫作图片存储对象 ID)。同时,还要能够根据图片 ID 快速查找到图片存储对象 ID。因为图片数量巨大,所以我们就用 10 位数来表示图片 ID 和图片存储对象 ID,

例如,图片 ID 为 1101000051,它在存储系统中对应的 ID 号是 3301000051。

photo_id: 1101000051
photo_obj_id: 3301000051
1
2
图片 ID 和图片存储对象 ID 正好一一对应,是典型的“键 - 单值”模式,这和 String 类型提供的“一个键对应一个值的数据”的保存形式刚好契合。

当我们储存一亿张图片时,用6.4G内存,但是,随着图片数据量的不断增加,我们的 Redis 内存使用量也在增加,结果就遇到了大内存 Redis 实例因为生成 RDB 而响应变慢的问题。

我们保存了 1 亿张图片的信息,用了约 6.4GB 的内存,一个图片 ID 和图片存储对象 ID 的记录平均用了 64 字节。但问题是,一组图片 ID 及其存储对象 ID 的记录,实际只需要 16 字节就可以了。

question:那么redis 的动态字符串类型为什么会使用这么多内存呢?另外48Byte 的内存用到哪里了呢?

2.动态字符串结构体 (Simple Dynamic String,SDS)
当我们用String类型键值对存储数据时,

当你保存 64 位有符号整数时,String 类型会把它保存为一个 8 字节的 Long 类型整数,这种保存方式通常也叫作 int 编码方式。

当你保存的数据中包含字符时,String 类型就会用简单动态字符串(Simple Dynamic String,SDS)结构体来保存,如下图所示:

len: 4字节 ,表示buf 使用长度

alloc:4字节,表示 内存分配器分配空间 大小

buf:字节数组,保存实际数据,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销。

3.RedisObject
对于 String 类型来说,除了 SDS 的额外开销,还有一个来自于 RedisObject 结构体的开销。

因为 Redis 的数据类型有很多,而且,不同数据类型都有些相同的元数据要记录(比如最后一次访问的时间、被引用的次数等),所以,Redis 会用一个 RedisObject 结构体来统一记录这些元数据,同时指向实际数据。

Long类型时: ptr引用指针直接填充赋值为整数数据这样就不用额外的指针再指向整数了,节省了指针的空间开销。这种布局方式也被称为 。
小于等于44字节的字符串:RedisObject 中的元数据、指针和 SDS 是一块连续的内存区域,这样就可以避免内存碎片。这种布局方式也被称为
大于44字节的字符串时,SDS 的数据量就开始变多了,Redis 就不再把 SDS 和 RedisObject 布局在一起了,而是会给 SDS 分配独立的空间,并用指针指向 SDS 结构。这种布局方式被称为
下面是 三种编码方式的结构图:

所以,例子中,保存“1101000051:3301000051”只使用 了 32个字节,另外32字节消耗在哪里了?

这就要讲一下 redis 中的 dicEntry结构了

4.全局哈希表的 dicEntry结构
Redis 会使用一个全局哈希表保存所有键值对,哈希表的每一项是一个 dictEntry 的结构体,用来指向一个键值对。

dictEntry 结构中有三个 8 字节的指针,分别指向 key、value 以及下一个 dictEntry,三个指针共 24 字节,如下图所示:

但是,加上 这三个指针也才 24个字节 + 32字节 ,不够64字节啊,为什么?

应为redis 内存分配器 jemalloc,分配内存 按页分配,且分配 2的幂次方大小的内存页

所以,申请 了 56个字节,但是 内存分配器分配了 64字节

至此,保存“1101000051:3301000051”为什么分配了64字节就 真相大白了

那么,用什么来存储海量 的String类型,单向键值对呢?

3.压缩列表
1. ziplist结构
压缩列表(ziplist):表头有 zlbyts(4字节,列表所占字节大小),zltail(4字节,列尾到 列表其实位置的偏移量offset),zllen(2字节,列表entry元素个数); 最后在列表尾部有一个 zlend(1字节)表示列表结束

prev_len: 上一个元素 长度,prev_len 有两种取值情况:1 字节或 5 字节。

取值 1 字节时,表示上一个 entry 的长度小于 254 字节。虽然 1 字节的值能表示的数值范围是 0 到 255,但是压缩列表中 zlend 的取值默认是 255,因此,就默认用 255 表示整个压缩列表的结束,其他表示长度的地方就不能再用 255 这个值了。所以,当上一个 entry 长度小于 254 字节时,prev_len 取值为 1 字节;

否则,就取值为 5 字节。

len:当前元素长度,4字节

encoding: 编码方式,1字节

content:保存实际数据

如上图,展示了一个总长为80字节,包含3个节点的压缩列表。如果我们有一个指向压缩列表起始地址的指针p,那么表尾节点的地址就是P+60。

回到 存储海量图片并要求快速定位图片的例子:

每个 entry 保存一个图片存储对象 ID(8 字节),此时,每个 entry 的 prev_len 只需要 1 个字节就行,因为每个 entry 的前一个 entry 长度都只有 8 字节,小于 254 字节。这样一来,一个图片的存储对象 ID 所占用的内存大小是 14 字节(1+4+1+8=14),实际分配 16 字节。

2.使用ziplist好处以及 ziplist应用
Redis 基于压缩列表实现了 List、Hash 和 Sorted Set 这样的集合类型,这样做的最大好处就是节省了 dictEntry 的开销。当你用 String 类型时,一个键值对就有一个 dictEntry,要用 32 字节空间。但采用集合类型时,一个 key 就对应一个集合的数据,能保存的数据多了很多,但也只用了一个 dictEntry,这样就节省了内存。

3.如何用集合类型保存单值的键值对?
在保存单值的键值对时,可以采用基于 Hash 类型的二级编码方法。这里说的二级编码,就是把一个单值的数据拆分成两部分,前一部分作为 Hash 集合的 key,后一部分作为 Hash 集合的 value,这样一来,我们就可以把单值数据保存到 Hash 集合中了。

以图片 ID 1101000060 和图片存储对象 ID 3302000080 为例,我们可以把图片 ID 的前 7 位(1101000)作为 Hash 类型的键,把图片 ID 的最后 3 位(060)和图片存储对象 ID 分别作为 Hash 类型值中的 key 和 value。

增加一条记录后,内存占用只增加了 16 字节:

127.0.0.1:6379> hset 1101000 060 3302000080
(integer) 1
1
2
4.什么时候使用ziplist?
Redis Hash 类型的两种底层实现结构,分别是压缩列表和哈希表。

Hash 类型底层结构什么时候使用压缩列表,什么时候使用哈希表呢?其实,Hash 类型设置了用压缩列表保存数据时的两个阈值,一旦超过了阈值,Hash 类型就会用哈希表来保存数据了。

hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。
hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。
为了能充分使用压缩列表的精简内存布局,我们一般要控制保存在 Hash 集合中的元素个数。所以,在刚才的二级编码中,我们只用图片 ID 最后 3 位作为 Hash 集合的 key,也就保证了 Hash 集合的元素个数不超过 1000,同时,我们把 hash-max-ziplist-entries 设置为 1000,这样一来,Hash 集合就可以一直使用压缩列表来节省内存空间了。

4.集合类型:一亿个key要统计怎么办?
总结


几种类型使用:

HyperLogLog:基数统计,集合元素量达到亿级别而且不需要精确统计

Bitmap:记录的数据只有 0 和 1 两个值的状态

Set 和 Sorted Set:都支持多种聚合统计(交并差集),不过,对于差集计算来说,只有 Set 支持

List和Sorted Set:

排序统计时,List 中的元素虽然有序,但是一旦有新元素插入,原来的元素在 List 中的位置就会移动,那么,按位置读取的排序结果可能就不准确了。而 Sorted Set 本身是按照集合元素的权重排序,可以准确地按序获取结果,所以建议你优先使用它。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/535359.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis核心技术与实战(四)高可用高扩展篇

1.《redis架构组成》 1.redis学习维度 2.一个基本的键值型数据库包括什么? 1.访问框架 redis通过网络框架进行访问,使得 Redis 可以作为一个基础性的网络服务进行访问,扩大了redis应用范围; 过程:如果客户端发送“pu…

tomcat监控脚本

#!/bin/sh# func:自动监控tomcat脚本并且执行重启操作# 获取tomcat进程ID(其中[grep -w .....]中的.....需要替换为实际部署的tomcat文件夹名,如下) TomcatID$(ps -ef |grep tomcat |grep -w /usr/local/tomcat/apache-tomcat-8.5.31|grep -v…

weblogic命令行操作

启动和停止子节点: [rootoud bin]# cd /sotware/oracle_ldap/Middleware/user_projects/domains/base_domain/bin/ [rootoud bin]# ./startManagedWebLogic.sh Server-0 http://192.168.63.129:7001 -Dweblogic.management.usernameweblogic -Dweblogic.management…

Ansible系列--Copy模块

copy模块 copy模块在ansible里的角色就是把ansible执行机器上的文件拷贝到远程节点上。 与fetch模块相反的操作 常用参数 参数名是否必须默认值选项说明srcno 用于定位ansible执行的机器上的文件,需要绝对路径。如果拷贝的是文件夹,那么文件夹会整体…

ANSIBLE--handlers的概念

handlers可以理解成另一种tasks,handlers是另一种’任务列表’,handlers中的任务会被tasks中的任务进行”调用”,但是,被”调用”并不意味着一定会执行,只有当tasks中的任务”真正执行”以后(真正的进行实际…

ansible--- tags

tags可以帮助我们对任务进行’打标签’的操作,当任务存在标签以后,我们就可以在执行playbook时,借助标签,指定执行哪些任务,或者指定不执行哪些任务。在实际的使用中,我们应该让tags的值能够见名知义。 当…

ANSIBLE---变量

注册变量 ansible的模块在运行之后,其实都会返回一些”返回值”,只是默认情况下,这些”返回值”并不会显示而已,我们可以把这些返回值写入到某个变量中,这样我们就能够通过引用对应的变量从而获取到这些返回值了&…

inux中限制用户进程CPU和内存占用率

#!/bin/sh PIDStop -bn 1 | grep "^ *[1-9]" | awk { if($9 > 50 || $10 > 25 && id -u $2 > 500) print $1} echo $PIDS for PID in $PIDS dorenice 10 $PIDecho "renice 10 $PID" done

按月拆分数据库表--oracle

生产有一张日志表,数据量很大,需要按月进行存储,存储过程如下: CREATE OR REPLACE PROCEDURE NEWLOG4_SUB_TABLE IStable_name1 VARCHAR2(50);create_table_sql VARCHAR2(4000);insert_data_sql VARC…

plsql定时器

Oralce中的任务有2种:Job和Dbms_job,两者的区别有: 1. jobs是oracle数据库的对象, dbms_jobs只是jobs对象的一个实例, 就像对于tables, emp和dept都是表的实例。 2. 创建方式也有…

PL/SQL批处理语句:BULK COLLECT 和 FORALL

PL/SQL程序中运行SQL语句是存在开销的,因为SQL语句是要提交给SQL引擎处理,这种在PL/SQL引擎和SQL引擎之间的控制转移叫做上下文却换,每次却换时,都有额外的开销 请看下图: 但是,FORALL和BULK COLLEC…

oracle 中DATETIME与TIMESTAMP区别

1.DATETIME的日期范围是1001——9999年,TIMESTAMP的时间范围是1970——2038年。 2.DATETIME存储时间与时区无关,TIMESTAMP存储时间与时区有关,显示的值也依赖于时区。在mysql服务器,操作系统以及客户端连接都有时区的设置。 3.DAT…

PARALLEL(并行)

在Oracle中,PARALLEL(并行)方式最大化调用计算机资源来成倍提高数据分析效率。 1. 用途 强行启用并行度来执行当前SQL。这个在Oracle 9i之后的版本可以使用,之前的版本现在没有环境进行测试。也就是说,加…

Oracle数据库查询优化

1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t w…

redis-full-check

https://github.com/alibaba/RedisFullCheck/releases redis-full-check是阿里云Redis&MongoDB团队开源的用于校验2个redis数据是否一致的工具。   redis-full-check通过全量对比源端和目的端的redis中的数据的方式来进行数据校验,其比较方式通过多轮次比较&a…

2021-06-22

服务器信息 [rootiZs7z01dz0z12dyttz9zn5Z cluster]# /app/redis/redis-3.2.1/src/redis-cli -c -h 10.252.120.9 -p 8003 10.252.120.9:8003> cluster nodes b1f543d646c5c97a70b0635439a44a72f8a143b1 10.252.120.10:8004 master - 0 1624349601417 7 connected 0-5460 1…

Docker目录挂载

Docker容器启动的时候,如果要挂载宿主机的一个目录,可以用-v参数指定。 譬如我要启动一个centos容器,宿主机的/test目录挂载到容器的/soft目录,可通过以下方式指定: # docker run -it -v /test:/soft centos /bin/ba…

Redis主从复制原理学习

Redis主从复制原理学习总结 - 运维笔记 和Mysql主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构&#xff…

redis数据恢复

公司线上一个项目数据存储采用MySQL,共分为10个库,分布在4台机器上,每个库数据量约为10G,各机器均采用RAID5加速磁盘访问; 当同时在线人数达高峰期(10w),DB磁盘IO压力巨大&#xff0…

Redis哨兵模式(sentinel)学习总结及部署记录(主从复制、读写分离、主从切换)

Redis的集群方案大致有三种:1)redis cluster集群方案;2)master/slave主从方案;3)哨兵模式来进行主从替换以及故障恢复。 一、sentinel哨兵模式介绍 Sentinel(哨兵)是用于监控redis集群中Master状态的工具&…