IT技术栈:Redis的9种数据类型原来是这样!
mhr18 2024-11-13 11:00 13 浏览 0 评论
整理不易,不要收藏吃灰,还要点赞支持!
字符串
Redis的字符串类型底层使用的是简单动态字符串(Simple Dynamic String,SDS)作为其数据结构。SDS是Redis自己实现的一种字符串表示,它具有一些传统C语言字符串不具备的特性,例如预分配、自动扩展和内存重用等。
在SDS中,字符串被存储在一个字节数组中,数组的头部包含了一个指向字符串内容的指针,以及一些其他元数据,如字符串长度、空闲空间等。当字符串增长时,SDS会自动分配更多的内存,并将指针指向新的内存位置。此外,SDS还支持释放未使用的空闲空间,以避免内存浪费。
这种数据结构的设计使得Redis字符串在处理大量数据时能够保持高效和快速。例如,对于包含大量元素的列表和集合,Redis可以使用SDS来存储和操作这些数据,避免了传统字符串处理中可能出现的内存分配和复制问题。
Golang模拟演示
package main
import (
"fmt"
"unsafe"
)
type RedisSDS struct {
len int
free int
buf []byte
}
func NewRedisSDS(s string) *RedisSDS {
sds := &RedisSDS{}
sds.init(len(s))
copy(sds.buf, s)
return sds
}
func (sds *RedisSDS) init(size int) {
sds.len = size
sds.free = 0
sds.buf = make([]byte, size+1)
}
func (sds *RedisSDS) Free() int {
return sds.free
}
func (sds *RedisSDS) Len() int {
return sds.len
}
func (sds *RedisSDS) Append(s string) {
sds.len += len(s)
sds.free -= len(s)
copy(sds.buf[sds.len:], s)
}
func main() {
sds := NewRedisSDS("Hello")
fmt.Printf("sds: %v\n", sds)
fmt.Printf("sds.Len(): %v\n", sds.Len())
fmt.Printf("sds.Free(): %v\n", sds.Free())
sds.Append(" World")
fmt.Printf("sds: %v\n", sds)
fmt.Printf("sds.Len(): %v\n", sds.Len())
fmt.Printf("sds.Free(): %v\n", sds.Free())
}
特点
- 内存分配:Redis SDS使用预分配和自动扩展的策略,避免了频繁的内存分配和复制操作。在创建字符串时,会预先分配一定大小的内存空间,并将字符串内容存储在其中。当字符串长度增加时,会自动扩展内存空间,避免了频繁的内存分配和释放操作,提高了性能。
- 内存重用:Redis SDS还支持内存重用,当字符串长度减少时,会释放多余的内存空间,以便后续重新使用。这种内存重用的策略可以有效减少内存浪费,提高内存利用率。
- 高效操作:由于Redis SDS结构的设计,使得对字符串的操作更加高效。通过直接操作字节数组,可以避免传统字符串处理中的复制和拼接操作,提高了字符串操作的性能。
- 方便扩展:Redis SDS结构的设计使得其可以方便地扩展其他功能。例如,可以通过在结构中添加额外的字段来支持字符串的编码和解码操作,或者支持字符串的子串查找等功能。这种可扩展性使得Redis的字符串类型具有更强的适应性和灵活性。
列表
Redis的列表类型(List)底层使用的是双端队列(Double-Ended Queue,也称为Deque)作为其数据结构。在Redis中,双端队列被实现为两个双向链表,一个用于存储数据,另一个用于指向队列的头部和尾部。
Golang模拟演示
package main
import "fmt"
type RedisList struct {
data []interface{}
}
func (l *RedisList) LeftPush(value interface{}) {
l.data = append(l.data, value)
}
func (l *RedisList) RightPush(value interface{}) {
l.data = append(l.data, value)
}
func (l *RedisList) LeftPop() interface{} {
if len(l.data) == 0 {
return nil
}
value := l.data[0]
l.data = l.data[1:]
return value
}
func (l *RedisList) RightPop() interface{} {
if len(l.data) == 0 {
return nil
}
value := l.data[len(l.data)-1]
l.data = l.data[:len(l.data)-1]
return value
}
func main() {
list := RedisList{}
list.LeftPush("foo")
list.RightPush("bar")
fmt.Println(list) // Output: [bar foo]
fmt.Println(list.LeftPop()) // Output: foo
fmt.Println(list.RightPop()) // Output: bar
}
特点
- 高效插入和删除:双端队列支持在队列的头部和尾部高效地插入和删除元素。这使得Redis列表可以快速地在两端添加或删除元素。
- 灵活的操作:使用双端队列,Redis可以实现从列表的两端进行添加和删除操作。这使得Redis列表适用于需要从两端处理数据的场景,如聊天室的消息队列、缓存等。
- 内存使用效率高:双端队列通过使用链表实现了空间的复用。当删除元素时,只需要调整指针指向,而不需要移动其他元素。这使得Redis列表在处理大量数据时能够更高效地利用内存资源。
使用场景
- 发布/订阅模型:Redis的发布/订阅功能可以用于实现消息的广播和监听。发布者可以向一个或多个频道发布消息,而订阅者则可以订阅一个或多个频道来接收消息。这种模型可以用于实现实时通信、消息推送等应用。
- 任务队列:Redis可以作为一个简单的任务队列使用。任务队列通常用于将任务添加到一个队列中,然后由后台工作线程或进程执行。这种模型可以用于实现异步任务处理、后台任务调度等应用。
- 缓存队列:Redis可以作为一个缓存队列使用,将数据暂时存储在队列中,然后由其他系统进行消费和处理。这种模型可以用于减轻数据库压力、加速数据访问等应用。
- 日志处理:Redis可以作为一个高速的日志处理平台,将日志数据添加到队列中,然后由其他系统进行实时分析和处理。这种模型可以用于实现实时监控、异常检测等应用。
- 事件驱动架构:Redis可以作为一个事件驱动架构的组成部分,实现事件发布和订阅、事件处理等环节。这种模型可以用于实现响应速度快、可扩展性强的事件驱动应用。
集合
Redis的集合类型(Set)底层使用的是哈希表(Hash Table)作为其数据结构。
Golang模拟实现
package main
import "fmt"
type RedisSet struct {
hashTable map[string]bool
}
func NewRedisSet() *RedisSet {
return &RedisSet{
hashTable: make(map[string]bool),
}
}
func (set *RedisSet) Add(member string) {
set.hashTable[member] = true
}
func (set *RedisSet) Remove(member string) {
delete(set.hashTable, member)
}
func (set *RedisSet) Contains(member string) bool {
return set.hashTable[member]
}
func (set *RedisSet) Size() int {
count := 0
for key := range set.hashTable {
count++
}
return count
}
func main() {
set := NewRedisSet()
set.Add("member1")
set.Add("member2")
set.Add("member3")
fmt.Println("Set size:", set.Size())
fmt.Println("Contains member1:", set.Contains("member1"))
set.Remove("member1")
fmt.Println("Set size after removing member1:", set.Size())
}
特点
- 快速查找:哈希表在平均情况下具有接近O(1)的查找复杂度,这意味着无论集合中有多少成员,查找成员的操作都可以在几乎恒定的时间内完成。这使得Redis的集合类型非常适合于需要快速查找操作的数据。
- 动态扩容:哈希表可以动态地分配内存空间,根据需要自动扩展或收缩。当集合中成员数量增加时,哈希表可以自动扩展以容纳更多成员;当集合中成员数量减少时,哈希表可以自动收缩以释放内存。这种动态扩容的能力使得Redis的集合类型可以高效地处理数据的变化。
- 空间利用率高:哈希表的空间利用率通常较高,因为它们可以很好地利用内存空间,使得每个槽位(bucket)都尽可能地被使用。这有助于减少内存浪费,提高集合类型的空间效率。
使用场景
- 存储一些集合性的数据,例如在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。
- 利用Redis提供的集合数据结构进行求交集、并集、差集等操作,可以非常方便地实现如共同关注、共同喜好、二度好友等功能。
有序集合
Redis的有序集合(Sorted Set)底层使用的是一种称为跳跃列表(Skip List)的数据结构来实现。跳跃列表是一个随机化的数据结构,它允许在O(log N)的时间复杂度内实现元素的插入、删除和查找操作。
跳跃列表是一个随机化的数据结构,它通过多级索引实现高效的插入、删除和查找操作。每个节点包含一个值、一个分数(Score)和一个指向下一层节点的指针。通过这种分层结构,跳跃列表可以在log(N)的时间复杂度内完成元素的插入、删除和查找操作。
在Redis的有序集合中,每个节点还包含一个分数(Score)字段,用于对元素进行排序。跳跃列表的每个节点都有一个指向下一层相同值节点的指针,形成一个双向链表结构,这样可以方便地进行遍历和排序操作。
使用跳跃列表作为底层数据结构,Redis的有序集合可以高效地处理大量有序的元素,并提供类似ZADD、ZRANGE等操作来对集合进行排序和查找。跳跃列表还支持反向查找功能,可以通过给定的值和指针反向查找上一层中具有相同值的节点。
此外,Redis有序集合还支持一些常用的操作,如ZREM、ZINCRBY等,用于删除元素、增加元素的分数等。这些操作的时间复杂度通常是O(log N),其中N是有序集合中元素的数量。
Golang模拟演示
package main
import (
"fmt"
"math/rand"
"time"
)
// SkipListNode 表示跳跃列表中的节点
type SkipListNode struct {
Value string // 节点值
Score float64 // 节点分数
Right *SkipListNode // 右指针
Left *SkipListNode // 左指针
Down *SkipListNode // 下指针
Level int // 节点所在的层数
}
// SkipList 表示跳跃列表
type SkipList struct {
Head *SkipListNode // 头节点
}
// NewSkipList 创建一个新的跳跃列表
func NewSkipList() *SkipList {
return &SkipList{Head: &SkipListNode{}}
}
// Insert 插入一个元素到跳跃列表中
func (sl *SkipList) Insert(value string, score float64) {
level := sl.Head.Level + 1 // 计算层数
for level > 0 {
// 随机决定向左或向右插入节点
if rand.Float64() < 0.5 {
right := sl.Head.Right
if right == nil || right.Level < level-1 {
newNode := &SkipListNode{Value: value, Score: score, Right: nil, Left: nil, Down: sl.Head, Level: level}
right = newNode
} else {
for right != nil && right.Level == level-1 {
right = right.Right
}
right = &SkipListNode{Value: value, Score: score, Right: nil, Left: nil, Down: sl.Head, Level: level}
}
right.Right = newNode
newNode.Left = right
newNode.Down = newNode.Right.Down
newNode.Right.Down = newNode.Left.Down
sl.Head.Right = newNode.Right.Right
} else {
left := sl.Head.Left
if left == nil || left.Level < level-1 {
newNode := &SkipListNode{Value: value, Score: score, Right: nil, Left: nil, Down: sl.Head, Level: level}
left = newNode
} else {
for left != nil && left.Level == level-1 {
left = left.Left
}
left = &SkipListNode{Value: value, Score: score, Right: nil, Left: nil, Down: sl.Head, Level: level}
}
left.Left = newNode
newNode.Right = left
newNode.Down = newNode.Left.Down
newNode.Left.Down = newNode.Right.Down
sl.Head.Left = newNode.Left.Left
}
level-- // 层数递减,继续向下插入节点直到到达顶层或无法插入为止
}
}
特点
- 有序:底层数据结构是有序的,每个节点都包含一个分数(Score)字段,用于对元素进行排序。
- 多层:跳跃列表是一个分层结构,每个节点都有一个指向下一层相同值节点的指针,形成一个双向链表结构。
- 去重:底层数据结构中的每个元素都是唯一的,不会出现重复的情况。
- 构成有序集合的底层结构(ZSET):有序集合是跳跃列表的一种应用,它底层的数据结构就是跳跃列表。
- 查询效率高:由于跳跃列表的分层结构和每个节点包含指向下一层相同值节点的指针,使得它可以在log(N)的时间复杂度内完成元素的插入、删除和查找操作。
- 支持反向查找:跳跃列表还支持反向查找功能,可以通过给定的值和指针反向查找上一层中具有相同值的节点。
- 压缩列表:Redis为了节省内存,自行设计了一种数据结构——压缩列表,它是对于数组的每一个元素,都带有前一元素占用内存的大小,从而减少内存开销。
使用场景
- 带有权重的元素:例如,一个游戏的用户得分排行榜。
- 比较复杂的数据结构:一般用到的场景不算太多,和Sets相比,Sorted Sets是将Set中的元素增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。
- 带权重的消息队列:Sorted Set还可以用来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务,让重要的任务优先执行。
- 共同好友、共同喜好、二度好友等场景:可以利用Sorted Set统计网站访问IP(利用唯一性,统计访问网站的所有独立IP)以及进行好友推荐(好友推荐时,根据tag求交集,大于某个阈值就可以推荐)。
Hash
Redis的Hash类型底层使用的是哈希表作为其数据结构。在Redis中,哈希表用于存储键值对,其中键用于唯一标识每个值,而值可以是任何类型的数据。
哈希类型常用的方法包括:HSET、HGET、HDEL等。这些方法用于设置、获取和删除哈希表中的键值对。使用场景包括但不限于存储用户信息、缓存对象等。时间复杂度通常是O(1),即对单个键值对的操作是常数时间复杂度。
Golang模拟演示
package main
import "fmt"
// Hash 表示Redis的哈希表
type Hash map[string]interface{}
// NewHash 创建一个新的哈希表
func NewHash() Hash {
return make(map[string]interface{})
}
// Get 获取哈希表中指定键的值
func (h Hash) Get(key string) interface{} {
value, ok := h[key]
if ok {
return value
}
return nil
}
// Set 设置哈希表中键值对的值
func (h Hash) Set(key string, value interface{}) {
h[key] = value
}
// Delete 删除哈希表中指定的键值对
func (h Hash) Delete(key string) {
delete(h, key)
}
// Keys 返回哈希表中所有键的列表
func (h Hash) Keys() []string {
keys := make([]string, 0, len(h))
for key := range h {
keys = append(keys, key)
}
return keys
}
// Values 返回哈希表中所有值的列表
func (h Hash) Values() []interface{} {
values := make([]interface{}, 0, len(h))
for _, value := range h {
values = append(values, value)
}
return values
}
特点
- 快速访问:哈希表提供快速的键值查找操作,使得在Redis中访问哈希类型的元素非常高效。
- 动态增长:哈希表可以动态地增长以适应存储的数据量,无需预先分配固定数量的内存空间。
- 内存高效:哈希表在内存中以紧凑的形式存储键值对,使得Redis能够高效地利用内存资源。
使用场景
- 数据库:Redis的哈希类型可以用于实现数据库的底层结构,例如实现表和列的映射关系,或者作为数据库中的数据项的存储结构。
- 缓存:哈希类型可以用于缓存数据库查询结果或其他计算结果,以提供快速访问。
- 会话管理:哈希类型可以用于存储和管理用户会话信息,包括用户ID、登录状态等信息。
- 配置管理:Redis的哈希类型可以用于存储和管理应用程序的配置信息,例如配置参数、环境变量等。
- 对象存储:哈希类型可以用于存储对象,例如文件、图片或其他二进制数据。
- 计数器:哈希类型可以用于实现计数器功能,例如统计网站访问量、用户行为等。
其他
Redis支持的不仅仅是五种数据类型。除了五种基本数据类型(string、hash、list、set、zset)之外,Redis还支持其他四种特殊的数据类型。因此,Redis总共支持九种数据类型。
- Stream:用于实现高级消息队列,可以用于处理实时数据流。
- HyperLogLog:用于估计数据集的基数,可以在不存储完整数据集的情况下进行基数统计。
- Geospatial:用于存储地理空间数据,可以用于地理位置的搜索和距离计算。
- Pub/Sub:用于实现发布/订阅模型,可以用于实时通信和消息传递。
特殊的这4类,目前没有用过,欢迎大家补充!
【申明:部分图片来源于网络,侵权,联系,删除】
相关推荐
- Docker集群管理之Docker Compose
-
前言:在上一篇《Docker集群管理之DockerMachine》中,我们通过源码分析了解了DockerMachine的工作原理,使用者可以通过DockerMachine的一条命令在任意支持的平...
- 使用Dockerfile build镜像
-
Docker映像可以看作是Docker容器的压缩包,包含了应用程序以及运行应用程序所需的依赖,容器是映像的运行时实例。一般构建镜像都是使用dockerfile进行构建而不是dockercommit,...
- 自建私有云相册:Docker一键部署Immich,照片视频备份利器
-
自建私有云相册:Docker一键部署Immich,照片视频备份利器前言随着人们手机、PC、平板等电子产品多样,我们拍摄和保存的照片和视频数量也在不断增加。如何高效地管理和备份这些珍贵的记忆成为了一个重...
- docker容器的使用以及部署mysql
-
首先什么是docker官方:翻译:Docker是一个用于开发、发布和运行应用程序的开放平台。Docker使您能够将应用程序与基础架构分离,以便您可以快速交付软件。使用Docker,您可以像管理应...
-
- 自建Docker镜像加速服务,免费且简单,服务器VPS、NAS皆可用
-
写在前面:流程十分简单,有手就行,还请耐心看完。本文的实例仅做演示用,不久后将会删除,有需要的各位请自行搭建。免费实例如果15分钟内未收到入站流量,Render会关闭实例的网络服务。Render会在下次收到处理请求时重新启动该服务。Ren...
-
2025-05-24 15:40 mhr18
- 用了8年的方式-用 Docker 瞬间搭建本地开发环境
-
有些时候我们需要在本地搭开发环境,比如平时学习新技术的时候。或者有时候公司的项目需要在本地建一套类似的,方便调试修改。开发环境可能包括MySQL、Redis、Nginx、MQ、Elasticsea...
- 使用dockerfile构建docker镜像
-
准备工作购买vps使用ssh工具连接上1、更新系统aptupdate-y2、一键安装Dockercurl-fsSLhttps://get.docker.com-oget-docker.sh...
- 快速搭建 SpringCloud 微服务开发环境的脚手架
-
本文适合有SpringBoot和SpringCloud基础知识的人群,跟着本文可使用和快速搭建SpringCloud项目。本文作者:HelloGitHub-秦人HelloGitHub推出...
- Docker Hub最全详解(图文全面总结)
-
DockerHubDockerHub是一个由Docker公司负责维护的公共注册中心,它包含了超过15000多个可用来下载和构建容器的Docker镜像。DockerHub作用Docker好比一个代...
- Docker 命令详解
-
dockerimages—查看本地镜像命令dockerimages说明列出本地已下载的所有镜像及其标签、ID、大小等信息。适用场景查看本地镜像资源、准备删除或管理镜像时。常见用法docker...
- Kylin安装Dify
-
cd/mntgitclonehttps://github.com/langgenius/dify.gitcp/mnt/dify/docker/.env.example/mnt/dif...
- kali下对Docker的详细安装
-
Docker是渗透测试中必学不可的一个容器工具,在其中,我们能够快速创建、运行、测试以及部署应用程序。如,我们对一些漏洞进行本地复现时,可以使用Docker快速搭建漏洞环境,完成复现学习。注:本教程仅...
- 银河麒麟V10使用Docker方式部署应用
-
现在越来越多的企业级应用需要运行在国产化环境中,而银河麒麟V10是目前我碰到的最常用的服务器,在银河麒麟上部署应用有两种方式:使用二进制文件编译部署和使用Docker。关于使用二进制文件的方式...
- Docker入门到精通超详细教程,Docker全家桶实战攻略
-
大家好,我是各位双生的武魂、随身老爷爷。从看到这篇内容开始,你就是被选定的天命骚年,将承担起学完docker教程的使命,本使命为单向契约,你可选择YES或者选择YES。正式学习之前,我先给大家做一下d...
- 【Docker 新手入门指南】第一章:前言
-
一、基本介绍Docker介绍Docker是基于Go语言开发的开源容器化平台,旨在实现“一次镜像,处处运行”。它通过将应用程序及其依赖环境(代码、运行时、系统工具、系统库等)打包成一个轻量级、可移...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)