redis(1、数据类型)

Redis是一个基于内存的高性能数据存储系统,支持多种数据结构如string、hash、list、set和zset。数据以key-value形式存储,提供持久化选项和高可用性方案。文章详细介绍了不同数据类型的特性和应用场景,如使用Bitmap统计用户登录天数,List的压缩列表和双向链表实现,以及Zset的跳跃表结构。
摘要由CSDN通过智能技术生成

redis

remote dictionary server

  • 数据存储形式 key-value
  • redis是基于内存存储,性能快
  • redis提供了多种持久化机制
  • redis提供了主从,哨兵及集群的搭建方式,高可用

数据存储形式key-value

key

  • key的话一般是string类型;空字符串也能作为key值
    在这里插入图片描述
  • key不宜过长,一是从存储角度来讲比较耗费内存,而且在查找key时对key进行比较将会增加开销。
  • 推荐使用中划线或冒号将描述字段分隔,整体作为key,如:“student:id”、“student:age”。
  • key的最大容量为512M。

value的类型

  • String
    平时存储对象都是转成json串以string的形式存储
  • hash
    hash的话就相当于map,redis的数据结构本身就类似一个map;常用于一对多的关系存储,如;购物车-商品的关系
  • list
    list是有序的集合
  • set
    无序的但是是唯一的,使用场景如:判断一段时间内,该操作是否是第一次进入,第一次进入有特殊处理的场景
  • zset
    有序且唯一的,使用场景:类似于朋友圈点赞,需要顺序展示,且一人只能显示一个;

String

  • 最常见的就是用来存储对象序列化后的字符串
    例:用于分布式存储session
  • 还可以存储数值,进行数值操作(incr\decr key、incrby\decyby key 值)
    例:可以用来记录当前系统在线人数
  • bitmap,不是一个实际的数据类型,只是对string进行bit运算
    从【Redis基本数据类型String】这篇文章中了解到,redis中还可以对字符串进行位运算
    setbit/getbit key [offset value]
    bitcount key [start end]用来统计value中有多少位为“1”;
    bitpos key bit [start end]用来查找指定字节范围中第一个bit位的索引;
    bitop operation destkey key [key …] —(bitop and andkey k1 k2 、bitop or orkey k1 k2)
    假设有以下需求:电信运营商给用户提供了掌上营业厅服务,现在局方想要统计每个用户在一年内的掌上营业厅登录情况,在哪些天登录了,一共有多少天登录?
    方案一:使用数据库,记录每一个用户的登录情况。用户每登录一次,则增加一条记录,记录包含登录当天的日期。然后一年中一共有多少天登录通过SQL语句来查询。
    问题:一个用户在一年中有多少天登录,就会产生多少条记录,这里还要注意到电信运营商的用户数基本是千万级的,这样这张表就会变的庞大无比,本方案不可行。
    方案二:使用Redis的Bitmap来存储用户登录信息
    一位就代表一天,1表示登录了,0表示没有登录,使用count就能获取到一年总的登录天数

    后续的工作中可以以这个为例子,做一些功能的优化

list

列表(list)用于存储多个有序的字符串。可以充当栈和队列的角色

数据结构

一般有序会采用数组或者是双向链表,其中双向链表由于有前后指针实际上会很浪费内存。

3.2版本之前:
Redis 列表list使用两种数据结构作为底层实现

  • 压缩列表ziplist(插入元素过多或字符串太大,就需要调用 realloc 扩展内存 )
  • 双向链表linkedlist(需附加指针prev 和 next,较浪费空间,加重内存的碎片化 )
    通过redis.conf配置完成

list-max-ziplist-value 64
#当元素个数 > 512个, 转变为 linkedlist
list-max-ziplist-entries 512
#当某个元素值 > 64字节,转变为 linkedlist

3.2版本之后:
Redis 列表使用quciklist (快速链表) 数据结构作为底层实现

双向链表linkedlist

优缺点:添加、删除元素快,但内存开销比较大!
每个节点上除了保存数据,还额外保存两个指针prev、next(16字节)
双向链表的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片
在这里插入图片描述

压缩列表 ziplist

在这里插入图片描述
由于ziplist是紧凑存储,如果插入的元素过大或过多,就会调用realloc扩展内存,realloc可能会重新分配内存空间,这时候就会导致大量数据的拷贝;
优缺点:存储在一段连续的内存上,查询快,存储效率高。不利于修改,插入和删除操作需要频繁的申请和释放内存。特别是当ziplist长度很长的时候,一次realloc可能会导致大批量的数据拷贝。

每一个存储节点(entry)都是一个zlentry (zip list entry)。

// 压缩列表节点
typedef struct zlentry {    
// prevrawlen是前一个节点的长度,prevrawlensize是指prevrawlen的大小,有1字节和5字节两种
    unsigned int prevrawlensize, prevrawlen;    
    unsigned int lensize, len;  // len为当前节点长度 lensize为编码len所需的字节大小
    unsigned int headersize;    // 当前节点的header大小
    unsigned char encoding; // 节点的编码方式
    unsigned char *p;   // 指向节点的指针
} zlentry;

可见,每一个存储节点 zlentry,都包含

  • prevrawlen 前一个 entry的长度
  • lensize 当前entry的长度

▶ 如何通过一个节点向前跳转到另一个节点?
指向当前节点的指针e - 前一个entry的长度 = 指向前一个节点的地址 p

▶ 完整的zlentry由以下3各部分组成:

  • prevrawlen:记录前一个 entry的长度,通过该值可计算出前一个节点的地址
  • len/encoding:记录当前节点content占有的内存字节数及其存储类型,用来解析content用
  • content:保存了当前节点的值。

▶ prevrawlen是变长编码,有两种表示方法:

  • 前一节点的长度 < 254字节,则使用1字节来存储 prevrawlen;
  • 前一节点的长度 >= 254字节,第 1 个字节的值设为 254 ,剩下4字节保存实际长度(5字节)

这时候就会考虑到ziplist会存在连锁更新问题:
每个entry都存储着前一个节点所占的字节数,这个数值又是变长编码的,由此可知:

假设:一个压缩列表e1、e2、e3、e4……,其中e1节点= 253字节,则e2.prevrawlen = 1字节 如果:此时在e2与e1之间插入一个新节点a,a长度= 254字节,则e2.prevrawlen需扩充到5字节;

以此类推,如果e2的长度变化又引起了e3.prevrawlen的长度变化,则e3也需扩充,直到表尾节点或某一节点的prevrawlen本身长度可容纳前一个节点的变化。每次扩充都需进行空间再分配操作。删除节点也是,一旦引起了节点的prevrawlen的变化,都可能产生连锁更新反应!

快速列表quicklist

在这里插入图片描述
参考-【Redis五大数据类型——List列表】

hash

  • Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)
  • hash的话就相当于map,redis的数据结构本身就类似一个map;常用于一对多的关系存储,如;购物车-商品的关系;
  • 我们经常把对象序列化成json串,然后以string的形式存储在redis中,如果在一个对象其他值都不发生变化,只有某两个属性需要经常性变动的时候,也可以使用hash的形式,将属性做key,以hash的形式存储
  • 其实hash也有string中的数值运算等操作,hincr

数据结构:
底层使用两种数据结构存储:

  • ziplist 压缩列表
  • hashtable 普通的哈希表(key为set的值,value为null)

当使用ziplist编码必须满足下面两个条件,否则使用hashtable

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
  • 哈希对象保存的键值对数量小于512个
    类似于list数据类型

set

Set 集合用于存储无序且不重复的元素。
Set 重要的特性:即在服务器端完成多个Sets之间的聚合计算操作,如unions、intersections和differences。由于这些操作均在服务端完成,因此效率极高,而且也节省了大量的网络IO开销。

数据结构

底层使用两种数据结构存储:

  • inset 可理解为数组
  • hashtable 普通的哈希表(key为set的值,value为null)

使用intset存储必须满足下面两个条件,否则使用hashtable,条件如下:

  • 对象保存的所有元素都是整数值(int)
  • 对象保存的元素数量不超过512个

set-max-inset-entries 512

常用指令

在这里插入图片描述

应用范围
  • 文章浏览统计;利用set保存唯一性数据;同一个IP地址访问当前页面不断刷新只记录一次有效浏览数,仅需在每次访问该文章时将访问者的IP存入Redis中
  • 好友推荐;可以通过Set类型的数据,做交集、并集、差集等方式,得到好友之间的共同关注

除了值的唯一性之外,他可以有获取并、交、差集的指令

zset

Zset 是Set的一个升级版本,他在set的基础上增加了一个 顺序属性,每个member成员都带有一个score分数( redis通过分数进行集合内成员的排序)。

数据结构

底层使用两种数据结构存储:

  • ziplist 压缩列表
  • skiplist 跳跃表(简称跳表)+ dict 字典

当使用ziplist编码必须满足下面两个条件,否则使用跳表

  • 有序集合保存的元素数量 < 128个
  • 有序集合保存的所有元素的长度 < 64字节

对应redis中的配置如下

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

跳表

所谓跳表,就是在有序链表的基础上优化查询
在这里插入图片描述
优化方式有点类似于二分查找,比如上图要查10应该放在哪里,会先判断19和10的大小,10比19小;这时判断10和7的大小,10比7大;这时判断10和11的大小,这时候,直接把10放到7和11之间;
可见上图是3个层级,那么10放在7和11之间一定就是放在最下层吗?
其实不是的,跳表中的实现,会随机一个层级,比如随机成2,那么就会和7处于一样的层级;
计算随机层数是一个很关键的过程,对skiplist的统计特性有着很重要的影响。这并不是一个普通的服从均匀分布的随机数,而是服从一定规则的:

  • 首先,每个节点肯定都有第1层指针(每个节点都在第1层链表里)。
  • 如果一个节点有第i层(i>=1)指针(即节点已经在第1层到第i层链表中),那么它有第(i+1)层指针的概率为p。
  • 节点最大的层数不允许超过一个最大值,记为MaxLevel(Redis里是32)。
#define ZSKIPLIST_MAXLEVEL 32 
#define ZSKIPLIST_P 0.25 
 
int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

红黑树、B树、B+树、跳表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值