Redis 学习笔记(一)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 的数据类型
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 对应的值加1decr key
: 将 key 对应的值减1incrby key amount
: 将key 对应的值加上整数decrby key amount
:将key 对应的值减去整数
应用场景
- 缓存:可以将常见的字符串、图片等信息缓存在 redis,mysql 作为持久化层。降低 mysql 的读写压力
计数器: 实现快速计数、查询缓存,同时数据可以异步落地到其他数据源。
共享Session:分布式服务器将用户的 Session 进行集中的管理,每次用户更新或者查询登录信息都直接从 Redis 中集中获取。
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 来说更加节省空间。比如缓存用户信息
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 阻塞式抢列表尾部的元素。保证消费的负载均衡和高可用性
set
set 类型是用来保存多个字符串元素,但是 set 中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。它的底层是通过哈希表来实现的,因此添加、删除、查找的复杂度都是 O(1)
常见命令
sadd key value
: 向集合中添加一个或者多个成员scard key
: 获取集合中的成员数smember key member
: 返回集合中的所有成员sismember key member
: 判断 member 元素是否是集合 key 的成员
应用场景
- 标签:给用户添加标签,所有这样有同一标签或者类似的可以推荐关注的事情或者关注的人
zset
有序集合 zset 相对于 set 而言,其内部的元素可以进行排序,它是通过给每个元素设置一个分数来作为排序的依据。
常见命令
zadd zset-key int member1
: 将一个带有给定分值的成员添加到有序的集合中zrange zset-key 0-1
: 根据元素在有序集合中所处的位置,从有序集合中获取对应的元素zrem zset-key member1
: 如果给定元素存在于有序集合中,就移除该元素
应用场景
- 排行榜:榜单可以按照用户关注数,更新时间等打分,并做排行
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 或者用户数
Bitmaps
它本身不是一种数据结构,实际上就是字符串,但是它可以对字符串的位进行操作
Bitmaps 相当于一个以位为单位的数组,数组的每个单元只能存储0 和 1 , 数组的下标在 Bitmaps 中叫做偏移量。
常用命令
setbit key offset value
: 设置键和偏移量的值getbit key offset
: 获取键的第 offset 位的值bitcount key
: 统计该键的次数值
应用场景
- 活跃用户分析: 存储和统计一天中活跃的用户
Geo
Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位 置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能
常用命令
- geoadd: 添加地理位置信息
geopos: 获取地理位置信息
geodist: 获取两个地理位置的距离
georadius: 获取范围内的信息位置集合-->附近的人
geohash: 将二维经纬度转换为一维的字符串
zrem: 删除地理位置信息(实际上是利用 zset 中的命令实现对位置信息的删除)
应用场景
- 附近的人
- 推算两地之间的距离
Redis 的数据结构
为什么 Redis 会设计 RedisObject 对象,因为操作数据类型的命令除了要对键的类型进行检查以外,还需要根据数据类型的不同编码进行多态处理,所以 Redis 构建了自己的类型系统,主要有:
- redisObject 的对象机制
- redisObject 对象的类型检查和多态
- 对 redisObject 进行分配、共享和销毁的机制
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 里面的引用计数法标记)
redis 命令的类型检查和多态
redis 当执行一个处理数据类型命令时,比如 LPOP key
命令redis 执行的步骤:
- 根据给定的 key,在数据库字典中查找对应的 redisObject 对象,没找到就返回null
- 检查找到的 redisObject 的 type 属性和执行命令所需要的类型是否相同,如果不相同就返回类型错误
- 根据 redisObject 的 encoding 属性所指定的编码,选择合适的操作函数来处理底层的数据结构
- 最后返回命令的操作结构
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
Redis 学习笔记(一)redis 数据类型和对象机制的更多相关文章
- Redis学习笔记一:数据结构与对象
1. String(SDS) Redis使用自定义的一种字符串结构SDS来作为字符串的表示. 127.0.0.1:6379> set name liushijie OK 在如上操作中,name( ...
- StackExchange.Redis学习笔记(一) Redis的使用初探
Redis Redis将其数据库完全保存在内存中,仅使用磁盘进行持久化. 与其它键值数据存储相比,Redis有一组相对丰富的数据类型. Redis可以将数据复制到任意数量的从机中 Redis的安装 官 ...
- Redis学习笔记(4) Redis事务、生存时间及排序
1. Redis事务 Redis中的事务(transaction)是一组命令的集合,一个事务中的命令要么都执行,要么都不执行.事务的原理是先将属于一个事务的命令发送给Redis,然后再让Redis依次 ...
- Redis学习笔记(1) Redis介绍及基础
1. Redis的特性 (1) 存储结构 Redis(Remote Dictionary Server,远程字典服务器)是以字典结构存储数据,并允许其他应用通过TCP协议读写字典中的内容.Redis支 ...
- redis学习笔记之redis简介
redis简介 Redis是一个开源的,高性能的,基于键值对的缓存与存储系统,通过设置各种键值数据类型来适应不同场景下的缓存与存储需求.同事redis的诸多高层级功能使其可以胜任消息队列,任务队列等不 ...
- Redis学习笔记之Redis单机,伪集群,Sentinel主从复制的安装和配置
0x00 Redis简介 Redis是一款开源的.高性能的键-值存储(key-value store).它常被称作是一款数据结构服务器(data structure server). Redis的键值 ...
- Redis学习笔记--五种数据类型的使用场景
String 1.String 常用命令: 除了get.set.incr.decr mget等操作外,Redis还提供了下面一些操作: 获取字符串长度 往字符串append内容 设置和获取字符串的某一 ...
- StackExchange.Redis学习笔记(二) Redis查询 五种数据类型的应用
ConnectionMultiplexer ConnectionMultiplexer 是StackExchange.Redis的核心对象,用这个类的实例来进行Redis的一系列操作,对于一个整个应用 ...
- Redis 学习笔记-5种数据类型的基本操作
1.string类型 基本操作列表: GET 获取指定键对应的值 SET 设定键值 DEL 删除指定键对应的值(对所有数据类型都有效) > set hello world OK > get ...
- Redis学习笔记之Redis的对象
类型与编码: typedef struct redisObject { unsigned type:4://类型 unsigned encod ...
随机推荐
- 【LeetCode】380. Insert Delete GetRandom O(1) 解题报告(Python)
[LeetCode]380. Insert Delete GetRandom O(1) 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxu ...
- SMOOTHING (LOWPASS) SPATIAL FILTERS
目录 FILTERS Box Filter Kernels Lowpass Gaussian Filter Kernels Order-Statistic (Nonlinear) Filters Go ...
- <学习opencv>opencv数据类型
目录 Opencv数据类型: 基础类型概述 固定向量类class cv::Vec<> 固定矩阵类cv::Matx<> 点类 Point class cv::Scalar 深入了 ...
- Java初学者作业——编写JAVA程序,计算跳水运动员本次动作的最终得分。
返回本章节 返回作业目录 需求说明: 编写JAVA程序,计算跳水运动员本次动作的最终得分. 规则如下: 在跳水比赛中,共有六位裁判对运动员所完成的动作进行评分,每位裁判的评分在0-10之间,运动员最终 ...
- 编写Java程序,利用List维护用户信息
返回本章节 返回作业目录 需求说明: 将新增的用户信息添加到List集合. 用户信息包括用户编号.姓名和性别. 按照姓名和性别查找用户信息. 实现思路: 创建类UserInfo,在该类中定义3个Str ...
- Linux操作系统RedHat6.5安装
1.说明 安装Linux操作系统Red Hat 6.5, 安装镜像为rhel-server-6.5-x86_64-dvd.iso. 2.开始安装 在BIOS里设置成从光驱启动, 服务器上电后会加载光驱 ...
- cpu负载
查看cpu负载,我们经常会使用top,或者是uptime命令 但是这只能看到cpu的总体的负载情况.如果我们想看cpu每个核心的负载情况是看不到的. 所以我们可以用mpstat命令 服务器一共32核心 ...
- MySQL存储过程入门基础
创建存储过程无参语法: delimiter // create procedure 函数名() begin 业务逻辑 end // call 函数名() 通过函数名调用存储过程 创建存储过程有参与法: ...
- Window10系统修改hosts文件的方法
背景: 调试smtp程序时遇到问题,度娘说需要修改hosts文件 使用老方法修改了很久,始终无法保存 又百度了一下,在此重温,以加深记忆 方法: Step1.同时按住Windows+X Step2.选 ...
- [ SQLAlchemy ] 自我引用型的多对多关系(Self-Referential Many-to-Many Relationship)理解
参考: https://www.jianshu.com/p/2c6c76f94b88 https://madmalls.com/blog/post/followers-and-followeds/ 实 ...