当前位置: 首页 > news >正文

【Redis】Redis中的常见数据类型(一)

文章目录

  • 前言
  • 一、Redis前置知识
    • 1. 全局命令
    • 2、数据结构和内部编码
    • 3. 单线程架构
  • 二、String 字符串
    • 1. 常见命令
    • 2. 计数命令
    • 3.其他命令
    • 4. 内部编码
    • 5. 典型使用场景
  • 三、Hash哈希
    • 1. 命令
    • 2.内部编码
    • 3. 使用场景
    • 4. 缓存方式对比
  • 结语


在这里插入图片描述

前言

Redis 提供了 5 种数据结构,理解每种数据结构的特点对于 Redis 开发运维非常重要,同时掌握每 种数据结构的常见命令,会在使用Redis 的时候做到游刃有余。本篇文章将带大家认识Redis中的部分数据结构的特点及其命令还有其应用场景。为介绍的会在下篇文章为大家详解!

在正式介绍数据结构之前,了解⼀下 Redis 的⼀些全局命令、数据结构和内部编码、单线程命令处理机制是十分必要的,接下来我们先对这些内容进行介绍。

主要体现在两个方面:
1)Redis 的命令有上百个,如果纯靠死记硬背比较困难,但是如果理解 Redis 的⼀些机制,会发现这 些命令有很强的通用性。
2)Redis 不是万⾦油,有些数据结构和命令必须在特定场景下使用,⼀旦使用不当可能对 Redis 本身或者应用本身造成致命伤害。

一、Redis前置知识

1. 全局命令

Redis 有 5 种数据结构,但它们都是键值对种的值,对于键来说有⼀些通用的命令

KEYS
返回所有满⾜样式(pattern)的 key。⽀持如下统配样式。 • h?llo 匹配 hello , hallo 和 hxllo
• h*llo 匹配 hllo 和 heeeello
• h[ae]llo 匹配 hello 和 hallo 但不匹配 hillo
• h[^e]llo 匹配 hallo , hbllo , … 但不匹配 hello
• h[a-b]llo 匹配 hallo 和 hbllo
语法:

KEYS pattern

时间复杂度:O(N)
返回值:匹配 pattern 的所有 key。

示例:

redis> MSET firstname Jack lastname Stuntman age 35
"OK"
redis> KEYS *name*
1) "firstname"
2) "lastname"
redis> KEYS a??
1) "age"
redis> KEYS *
1) "age"
2) "firstname"
3) "lastname"

EXISTS
判断某个 key 是否存在。 语法:

 EXISTS key [key ...]

时间复杂度:O(1)
返回值:key 存在的个数

redis> SET key1 "Hello"
"OK"
redis> EXISTS key1
(integer) 1
redis> EXISTS nosuchkey
(integer) 0
redis> SET key2 "World"
"OK"
redis> EXISTS key1 key2 nosuchkey
(integer) 2

DEL
删除指定的 key。
语法:

DEL key [key ...]

时间复杂度:O(1)
返回值:删除掉的 key 的个数。

⽰例:

redis> SET key1 "Hello"
"OK"
redis> SET key2 "World"
"OK"
redis> DEL key1 key2 key3
(integer) 2
EXPIRE

EXPIRE
为指定的 key 添加秒级的过期时间(Time To Live TTL)
语法:

EXPIRE key seconds

时间复杂度:O(1)
返回值:1 表示设置成功。0 表示设置失败。
示例:

redis> SET mykey "Hello"
"OK"
redis> EXPIRE mykey 10
(integer) 1
redis> TTL mykey
(integer) 10

TTL
获取指定 key 的过期时间,秒级。
语法:

 TTL key

时间复杂度:O(1)
返回值:剩余过期时间。-1 表⽰没有关联过期时间,-2 表示key 不存在。

redis> SET mykey "Hello"
"OK"
redis> EXPIRE mykey 10
(integer) 1
redis> TTL mykey
(integer) 10

注意:

EXPIRE 和 TTL 命令都有对应的⽀持毫秒为单位的版本:PEXPIRE 和 PTTL,使用方法两者相同

关于键过期机制,可以参考下图所示:

在这里插入图片描述

TYPE
返回 key 对应的数据类型。
语法:

TYPE key

时间复杂度:O(1)
返回值: none , string , list , set , zset , hash and stream .

示例:


redis> SET key1 "value"
"OK"
redis> LPUSH key2 "value"
(integer) 1
redis> SADD key3 "value"
(integer) 1
redis> TYPE key1
"string"
redis> TYPE key2
"list"
redis> TYPE key3
"set"

2、数据结构和内部编码

type 命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、list(列 表)、hash(哈希)、set(集合)、zset(有序集合),但这些只是 Redis 对外的数据结构,如下图所示:

在这里插入图片描述
实际上 Redis 针对每种数据结构都有⾃⼰的底层内部编码实现,⽽且是多种实现,这样 Redis 会 在合适的场景选择合适的内部编码

数据结构内部编码
stringraw ,int , embstr
hashhashtable , ziplist
listlinkedlist , ziplist
sethashtable , inset
zsetskiplist ,ziplist

可以看到每种数据结构都有⾄少两种以上的内部编码实现,例如 list 数据结构包含了 linkedlist 和ziplist 两种内部编码。同时有些内部编码,例如 ziplist,可以作为多种数据结构的内部实现,可以通 过 object encoding 命令查询内部编码:

127.0.0.1:6379> set hello world
OK
127.0.0.1:6379> lpush mylist a b c
(integer) 3
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> object encoding mylist
"quicklist

可以看到 hello 对应值的内部编码是 embstr,键 mylist 对应值的内部编码是 ziplist。

Redis 这样设计有两个好处: 1)可以改进内部编码,而对外的数据结构和命令没有任何影响,这样一旦开发出更优秀的内部编码, 无需改动外部数据结构和命令,例如 Redis 3.2 提供了 quicklist,结合了 ziplist 和 linkedlist 两者的优势,为列表类型提供了⼀种更为优秀的内部编码实现,而对用户来说基本⽆感知。
2)多种内部编码实现可以在不同场景下发挥各自的优势,例如 ziplist 比较节省内存,但是在列表元素比较多的情况下,性能会下降,这时候 Redis 会根据配置选项将列表类型的内部实现转换为linkedlist,整个过程用户同样无感知。

3. 单线程架构

Redis 使用了单线程架构来实现⾼性能的内存数据库服务,本节⾸先通过多个客户端命令调用的例子说明 Redis 单线程命令处理机制,接着分析 Redis 单线程模型为什么性能如此之高,最终给出为什么理解单线程模型是使用和运维 Redis 的关键。

1 . 引出单线程模型
现在开启了三个 redis-cli 客户端同时执行命令。
客⼾端 1 设置⼀个字符串键值对:

127.0.0.1:6379> set hello world

客⼾端 2 对 counter 做自增操作:

127.0.0.1:6379> incr counter

客⼾端 3 对 counter 做自增操作:

127.0.0.1:6379> incr counter

我们已经知道从客户端发送的命令经历了:发送命令、执行命令、返回结果三个阶段,其中我们 重点关注第 2 步。我们所谓的 Redis 是采⽤单线程模型执⾏命令的是指:虽然三个客户端看起来是同 时要求 Redis 去执行命令的,但微观角度,这些命令还是采用线性⽅式去执⾏的,只是原则上命令的 执行顺序是不确定的,但⼀定不会有两条命令被同步执行,如下图所示,可以想象 Redis内部只有⼀个服务窗⼝,多个客⼾端按照它们达到的先后顺序被排队在窗口前,依次接受 Redis 的服务,所以两条 incr 命令无论执行顺序,结果⼀定是 2,不会发生并发问题,这个就是 Redis 的单线程执行模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2. 为什么单线程还能这么快
通常来讲,单线程处理能力要比多线程差,例如有 10 000 公⽄货物,每辆⻋的运载能⼒是每次200 公斤,那么要 50 次才能完成;但是如果有 50 辆⻋,只要安排合理,只需要依次就可以完成任 务。那么为什么 Redis 使⽤单线程模型会达到每秒万级别的处理能⼒呢?可以将其归结为三点:

a. 纯内存访问。Redis 将所有数据放在内存中,内存的响应时长大约为 100 纳秒,这是 Redis 达到每秒万级别访问的重要基础。
b. 非阻塞 IO。Redis 使⽤ epoll 作为 I/O 多路复用技术的实现,再加上 Redis 自身的事件处理模型 将 epoll 中的连接、读写、关闭都转换为事件,不在网络 I/O 上浪费过多的时间。
c. 单线程避免了线程切换和竞态产生的消耗。单线程可以简化数据结构和算法的实现,让程序模 型更简单;其次多线程避免了在线程竞争同⼀份共享数据时带来的切换和等待消耗。

Redis 使用 I/O 多路复用模型:
在这里插入图片描述
虽然单线程给 Redis 带来很多好处,但还是有⼀个致命的问题:对于单个命令的执行时间都是有 要求的。如果某个命令执行过长,会导致其他命令全部处于等待队列中,迟迟等不到响应,造成客户端的阻塞,对于 Redis 这种高性能的服务来说是非常严重的,所以 Redis 是面向快速执行场景的数据库。

二、String 字符串

字符串类型是 Redis 最基础的数据类型,关于字符串需要特别注意:
1)首先 Redis 中所有的键的 类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础上构建的,例如列表和集合的 元素类型是字符串类型,所以字符串类型能为其他 4 种数据结构的学习奠定基础。
2)其次,如图 2-7所⽰,字符串类型的值实际可以是字符串,包含⼀般格式的字符串或者类似 JSON、XML 格式的字符串;数字,可以是整型或者浮点型;甚至是二进制流数据,例如图片、⾳频、视频等。不过⼀个字符串的最大值不能超过 512 MB。

字符串数据类型
在这里插入图片描述

1. 常见命令

SET
将 string 类型的 value 设置到 key 中。如果 key 之前存在,则覆盖,无论原来的数据类型是什么。之前关于此 key 的 TTL 也全部失效。

语法:

SET key value [expiration EX seconds|PX milliseconds] [NX|XX]

时间复杂度:O(1)
选项:
SET 命令⽀持多种选项来影响它的⾏为:
• EX seconds⸺使用秒作为单位设置 key 的过期时间。
• PX milliseconds⸺使⽤毫秒作为单位设置 key 的过期时间。
• NX ⸺只在 key 不存在时才进⾏设置,即如果 key 之前已经存在,设置不执行。
• XX ⸺只在 key 存在时才进⾏设置,即如果 key 之前不存在,设置不执行。
注意:由于带选项的 SET 命令可以被 SETNX 、 SETEX 、 PSETEX 等命令代替,所以之后的版本 中,Redis 可能进行合并。
返回值:
• 如果设置成功,返回 OK。
• 如果由于 SET 指定了 NX 或者 XX 但条件不满足,SET 不会执行,并返回 (nil)。
示例:

redis> EXISTS mykey
(integer) 0
redis> SET mykey "Hello"
OK
redis> GET mykey
"Hello"
redis> SET mykey "World" NX
(nil)
redis> DEL mykey
(integer) 1
redis> EXISTS mykey
(integer) 0
redis> SET mykey "World" XX
(nil)
redis> GET mykey
(nil)
redis> SET mykey "World" NX
OK
redis> GET mykey
"World"
redis> SET mykey "Will expire in 10s" EX 10
OK
redis> GET mykey
"Will expire in 10s"
redis> GET mykey # 10秒之后
(nil)

GET
获取 key 对应的 value。如果 key 不存在,返回 nil。如果 value 的数据类型不是 string,会报错。
语法:

GET key

时间复杂度:O(1)
返回值:key 对应的 value,或者 nil 当 key 不存在。
示例:

redis> GET nonexisting
(nil)
redis> SET mykey "Hello"
"OK"
redis> GET mykey
"Hello"
redis> DEL mykey
(integer) 1
redis> EXISTS mykey
(integer) 0
redis> HSET mykey name Bob
(integer) 1
redis> GET mykey
(error) WRONGTYPE Operation against a key holding the wrong kind of value

MGET
⼀次性获取多个 key 的值。如果对应的 key 不存在或者对应的数据类型不是 string,返回 nil
语法:

MGET key [key ...]

时间复杂度:O(N) N 是 key 数量
返回值:对应 value 的列表

示例:

redis> SET key1 "Hello"
"OK"
redis> SET key2 "World"
"OK"
redis> MGET key1 key2 nonexisting
1) "Hello"
2) "World"
3) (nil)

MSET
⼀次性设置多个 key 的值。

语法:

 MSET key value [key value ...]

时间复杂度:O(N) N 是 key 数量
返回值:永远是 OK

示例:

redis> MSET key1 "Hello" key2 "World"
"OK"
redis> GET key1
"Hello"
redis> GET key2
"World

多次 get vs 单次 mget见下图:

在这里插入图片描述
使用 mget / mset 由于可以有效地减少了⽹络时间,所以性能相较更高。假设网络耗 时 1 毫秒,命令执行时间耗时 0.1 毫秒,则执行时间如下表所示。

操作时间
1000次get1000 x 1 + 1000 x 0.1 = 1100 毫秒
1 次 mget 1000 个键1 x 1 + 1000 x 0.1 = 101 毫秒

学会使用批量操作,可以有效提高业务处理效率,但是要注意,每次批量操作所发送的键的数量也不是无节制的,否则可能造成单⼀命令执行时间过长,导致 Redis 阻塞。

SETNX
设置 key-value 但只允许在 key 之前不存在的情况下。
语法:

SETNX key value

时间复杂度:O(1)
返回值:1 表示设置成功。0 表示没有设置。

示例:

redis> SETNX mykey "Hello"
(integer) 1
redis> SETNX mykey "World"
(integer) 0
redis> GET mykey
"Hello"

SET、SETNX 和 SETXX 的执行流程如下图。

在这里插入图片描述

2. 计数命令

INCR
将 key 对应的 string 表⽰的数字加⼀。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对 应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。

语法:

INCR key

时间复杂度:O(1)
返回值:integer 类型的加完后的数值。

redis> EXISTS mykey
(integer) 0
redis> INCR mykey
(integer) 1
redis> SET mykey "10"
"OK"
redis> INCR mykey
(integer) 11
redis> SET mykey "234293482390480948029348230948"
"OK"
redis> INCR mykey
(error) value is not an integer or out of range
redis> SET mykey 'not a number'
"OK"
redis> INCR mykey
(error) value is not an integer or out of range

INCRBY
将 key 对应的 string 表⽰的数字加上对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如 果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。
语法:

INCRBY key decrement

时间复杂度:O(1)
返回值:integer 类型的加完后的数值。
示例:

redis> EXISTS mykey
(integer) 0
redis> INCRBY mykey 3
(integer) 3
redis> SET mykey "10"
"OK"
redis> INCRBY mykey 3
(integer) 13
redis> INCRBY mykey "not a number"
(error) ERR value is not an integer or out of range
redis> SET mykey "234293482390480948029348230948"
"OK"
redis> INCRBY mykey 3
(error) value is not an integer or out of range
redis> SET mykey 'not a number'
"OK"
redis> INCRBY mykey 3
(error) value is not an integer or out of range

DECR
将 key 对应的 string 表⽰的数字减⼀。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对 应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。
语法:

DECR key

时间复杂度:O(1)
返回值:integer 类型的减完后的数值。
示例:

redis> EXISTS mykey
(integer) 0
redis> DECR mykey
(integer) -1
redis> SET mykey "10"
"OK"
redis> DECR mykey
(integer) 9
redis> SET mykey "234293482390480948029348230948"
"OK"
redis> DECR mykey
(error) value is not an integer or out of range
redis> SET mykey 'not a number'
"OK"
redis> DECR mykey
(error) value is not an integer or out of range

DECYBY
将 key 对应的 string 表示的数字减去对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如 果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。

语法:

DECRBY key decrement

时间复杂度:O(1)
返回值:integer 类型的减完后的数值。
示例:

redis> EXISTS mykey
(integer) 0
redis> DECRBY mykey 3
(integer) -3
redis> SET mykey "10"
"OK"
redis> DECRBY mykey 3
(integer) 7
redis> DECRBY mykey "not a number"
(error) ERR value is not an integer or out of range
redis> SET mykey "234293482390480948029348230948"
"OK"
redis> DECRBY mykey 3
(error) value is not an integer or out of range
redis> SET mykey 'not a number'
"OK"
redis> DECRBY mykey 3
(error) value is not an integer or out of range

INCRBYFLOAT

将 key 对应的 string 表⽰的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的不是 string,或者不是⼀个浮点数,则报 错。允许采⽤科学计数法表示浮点数。
语法:

INCRBYFLOAT key increment

时间复杂度:O(1)
返回值:加/减完后的数值。
示例:

redis> SET mykey 10.50
"OK"
redis> INCRBYFLOAT mykey 0.1
"10.6"
redis> INCRBYFLOAT mykey -5
"5.6"
redis> SET mykey 5.0e3
"OK"
redis> INCRBYFLOAT mykey 2.0e2
"5200"

很多存储系统和编程语⾔内部使⽤ CAS 机制实现计数功能,会有一定的 CPU 开销,但在 Redis 中完全 不存在这个问题,因为 Redis 是单线程架构,任何命令到了 Redis 服务端都要顺序执行。

3.其他命令

APPEND
如果 key 已经存在并且是⼀个 string,命令会将 value 追加到原有 string 的后边。如果 key 不存在, 则效果等同于SET 命令。

语法:

APPEND KEY VALUE

时间复杂度:O(1). 追加的字符串⼀般长度较短, 可以视为 O(1).
返回值:追加完成之后 string 的长度。
示例:

redis> EXISTS mykey
(integer) 0
redis> APPEND mykey "Hello"
(integer) 5
redis> GET mykey
"Hello"
redis> APPEND mykey " World"
(integer) 11
redis> GET mykey
"Hello World"

GETRANGE

返回 key 对应的 string 的⼦串,由 start 和 end 确定(左闭右闭)。可以使⽤负数表示倒数。-1 代表 倒数第⼀个字符,-2 代表倒数第⼆个,其他的与此类似。超过范围的偏移量会根据 string 的⻓度调整 成正确的值。

语法:

GETRANGE key start end

时间复杂度:O(N). N 为 [start, end] 区间的⻓度. 由于 string 通常比较短, 可以视为是 O(1)
返回值:string 类型的子串

示例:

redis> SET mykey "This is a string"
"OK"
redis> GETRANGE mykey 0 3
"This"
redis> GETRANGE mykey -3 -1
"ing"
redis> GETRANGE mykey 0 -1
"This is a string"
redis> GETRANGE mykey 10 100
"string"

SETRANGE
覆盖字符串的⼀部分,从指定的偏移开始。

语法:

SETRANGE key offset value

时间复杂度:O(N), N 为 value 的长度. 由于一般给的 value 比较短, 通常视为 O(1).
返回值:替换后的 string 的长度。

示例:

redis> SET key1 "Hello World"
"OK"
redis> SETRANGE key1 6 "Redis"
(integer) 11
redis> GET key1
"Hello Redis"

STRLEN
获取 key 对应的 string 的⻓度。当 key 存放的类似不是 string 时,报错。
语法:

STRLEN key

时间复杂度:O(1)
返回值:string 的⻓度。或者当 key 不存在时,返回 0。
示例:

redis> SET mykey "Hello world"
"OK"
redis> STRLEN mykey
(integer) 11
redis> STRLEN nonexisting
(integer) 0

4. 内部编码

字符串类型的内部编码有 3 种:
• int:8 个字节的长整型。
• embstr:小于等于 39 个字节的字符串。
• raw:大于 39 个字节的字符串。

Redis 会根据当前值的类型和⻓度动态决定使⽤哪种内部编码实现。

整型类型示例如下:

127.0.0.1:6379> set key 6379
OK
127.0.0.1:6379> object encoding key
"int"

短字符串示例如下:

// ⼩于等于 39 个字节的字符串
127.0.0.1:6379> set key "hello"
OK
127.0.0.1:6379> object encoding key
"embstr"

长字符串示例如下:

// ⼤于 39 个字节的字符串
127.0.0.1:6379> set key "one string greater than 39 bytes ........"
OK
127.0.0.1:6379> object encoding key
"raw"

5. 典型使用场景

缓存(Cache)功能
下图 是比较典型的缓存使⽤场景,其中 Redis 作为缓冲层,MySQL 作为存储层,绝大部分请求的数据都是从 Redis 中获取。由于 Redis 具有⽀撑⾼并发的特性,所以缓存通常能起到加速读写和 降低后端压力的作用

Redis + MySQL 组成的缓存存储架构
在这里插入图片描述
下面我们通过伪代码大概模拟业务数据访问教程:

1)假设业务是根据用户 uid 获取用户信息

UserInfo getUserInfo(long uid) {...
}

2)首先从 Redis 获取⽤⼾信息,我们假设⽤⼾信息保存在 “user:info:” 对应的键中:

// 根据 uid 得到 Redis 的键
String key = "user:info:" + uid;
// 尝试从 Redis 中获取对应的值
String value = Redis 执⾏命令:get key;
// 如果缓存命中(hit)
if (value != null) {// 假设我们的⽤⼾信息按照 JSON 格式存储UserInfo userInfo = JSON 反序列化(value);return userInfo;
}

3)如果没有从 Redis 中得到⽤⼾信息,及缓存 miss,则进⼀步从 MySQL 中获取对应的信息,随后写 ⼊缓存并返回

// 如果缓存未命中(miss)
if (value == null) {// 从数据库中,根据 uid 获取⽤⼾信息UserInfo userInfo = MySQL 执⾏ SQL:select * from user_info where uid = 
<uid>// 如果表中没有 uid 对应的⽤⼾信息if (userInfo == null) {响应 404return null;}// 将⽤⼾信息序列化成 JSON 格式String value = JSON 序列化(userInfo);// 写⼊缓存,为了防⽌数据腐烂(rot),设置过期时间为 1 ⼩时(3600 秒)Redis 执⾏命令:set key value ex 3600// 返回⽤⼾信息return userInfo;
}

通过增加缓存功能,在理想情况下,每个⽤⼾信息,⼀个⼩时期间只会有⼀次 MySQL 查询,极⼤地提 升了查询效率,也降低了 MySQL 的访问数。

与 MySQL 等关系型数据库不同的是,Redis 没有表、字段这种命名空间,⽽且也没有对键名 有强制要求(除了不能使⽤⼀些特殊字符)。但设计合理的键名,有利于防⽌键冲突和项⽬ 的可维护性,⽐较推荐的⽅式是使⽤ “业务名:对象名:唯⼀标识:属性” 作为键名。例如MySQL 的数据库名为 vs,⽤⼾表名为 user_info,那么对应的键可以使⽤"vs:user_info:6379"、“vs:user_info:6379:name” 来表⽰,如果当前 Redis 只会被⼀个业务使⽤,可以省略业务名 “vs:”。如果键名过程,则可以使⽤团队内部都认同的缩写替代,例如"user:6379:friends:messages:5217" 可以被 “u:6379🇫🇷m:5217” 代替。毕竟键名过⻓,还 是会导致 Redis 的性能明显下降的。

计数(Counter)功能
许多应⽤都会使⽤ Redis 作为计数的基础⼯具,它可以实现快速计数、查询缓存的功能,同时数 据可以异步处理或者落地到其他数据源。如图 2-11 所⽰,例如视频⽹站的视频播放次数可以使⽤Redis 来完成:用户每播放⼀次视频,相应的视频播放数就会⾃增 1。

图 2-11 记录视频播放次数

在这里插入图片描述

// 在 Redis 中统计某视频的播放次数
long incrVideoCounter(long vid) {key = "video:" + vid;long count = Redis 执⾏命令:incr keyreturn counter;
}

实际中要开发⼀个成熟、稳定的真实计数系统,要⾯临的挑战远不⽌如此简单:防作弊、按 照不同维度计数、避免单点问题、数据持久化到底层数据源等。

共享会话(Session) 如图 2-12 所示,⼀个分布式 Web 服务将用户的 Session 信息(例如用户登录信息)保存在各自的服务器中,但这样会造成⼀个问题:出于负载均衡的考虑,分布式服务会将⽤⼾的访问请求均衡到 不同的服务器上,并且通常⽆法保证用户每次请求都会被均衡到同⼀台服务器上,这样当用户刷新⼀ 次访问是可能会发现需要重新登录,这个问题是用户无法容忍的。

图 2-12 Session 分散存储

在这里插入图片描述
为了解决这个问题,可以使用 Redis 将用户的 Session 信息进行集中管理,如下图所示,在这种模式下,只要保证 Redis 是高可用和可扩展性的,无论用户被均衡到哪台 Web 服务器上,都集中从Redis 中查询、更新 Session 信息。

Redis 集中管理 Session

在这里插入图片描述
⼿机验证码 很多应⽤出于安全考虑,会在每次进行登录时,让用户输⼊⼿机号并且配合给⼿机发送验证码, 然后让用户再次输⼊收到的验证码并进⾏验证,从而确定是否是用户本⼈。为了短信接口不会频繁访问,会限制用户每分钟获取验证码的频率,例如⼀分钟不能超过 5 次,如下图所示。

短信验证码

在这里插入图片描述
此功能可以⽤以下伪代码说明基本实现思路:

 String 发送验证码(phoneNumber) {key = "shortMsg:limit:" + phoneNumber;// 设置过期时间为 1 分钟(60 秒)// 使⽤ NX,只在不存在 key 时才能设置成功bool r = Redis 执⾏命令:set key 1 ex 60 nxif (r == false) {// 说明之前设置过该⼿机的验证码了long c = Redis 执⾏命令:incr keyif (c > 5) {// 说明超过了⼀分钟 5 次的限制了// 限制发送return null;}}// 说明要么之前没有设置过⼿机的验证码;要么次数没有超过 5 次String validationCode = ⽣成随机的 6 位数的验证码();validationKey = "validation:" + phoneNumber;// 验证码 5 分钟(300 秒)内有效Redis 执⾏命令:set validationKey validationCode ex 300;// 返回验证码,随后通过⼿机短信发送给⽤⼾return validationCode;}// 验证⽤⼾输⼊的验证码是否正确bool 验证验证码(phoneNumber, validationCode) {validationKey = "validation:" + phoneNumber;String value = Redis 执⾏命令:get validationKey;if (value == null) {// 说明没有这个⼿机的验证码记录,验证失败return false;}if (value == validationCode) {return true;} else {return false;}}

以上介绍了使用 Redis 的字符串数据类型可以使用的几个场景,但其适用场景远不止于此,开发人员可以结合字符串类型的特点以及提供的命令,充分发挥自己的想象力,在自己的业务中去找到合适的场景去使用 Redis 的字符串类型

三、Hash哈希

几乎所有的主流编程语言都提供了哈希(hash)类型,它们的叫法可能是哈希、字典、关联数组、映射。在 Redis 中,哈希类型是指值本身又是⼀个键值对结构,形如 key = “key”,value = { {field1, value1 }, …, {fieldN, valueN } },Redis 键值对和哈希类型二者的关系可以用下图来表示。

字符串和哈希类型对比

在这里插入图片描述
哈希类型中的映射关系通常称为 field-value,⽤于区分 Redis 整体的键值对(key-value),注意这里的 value 是指 field 对应的值,不是键(key)对应的值,请注意 value 在不同上下文的作用。

1. 命令

HSET
设置 hash 中指定的字段(field)的值(value)。
语法:

 HSET key field value [field value ...]

时间复杂度:插⼊⼀组 field 为 O(1), 插⼊ N 组 field 为 O(N)
返回值:添加的字段的个数。
示例:

redis> HSET myhash field1 "Hello"
(integer) 1
redis> HGET myhash field1
"Hello

HGET
获取 hash 中指定字段的值。
语法:

HGET key field

时间复杂度:O(1)
返回值:字段对应的值或者 nil。
示例:

redis> HSET myhash field1 "foo"
(integer) 1
redis> HGET myhash field1
"foo"
redis> HGET myhash field2
(nil)

HEXISTS
判断 hash 中是否有指定的字段。

语法:

 HEXISTS key field

时间复杂度:O(1)
返回值:1 表⽰存在,0 表⽰不存在。
示例:

redis> HSET myhash field1 "foo"
(integer) 1
redis> HEXISTS myhash field1
(integer) 1
redis> HEXISTS myhash field2
(integer) 0

HDEL
删除 hash 中指定的字段。

语法:

HDEL key field [field ...]

时间复杂度:删除⼀个元素为 O(1). 删除 N 个元素为 O(N).
返回值:本次操作删除的字段个数。
示例:

redis> HSET myhash field1 "foo"
(integer) 1
redis> HDEL myhash field1
(integer) 1
redis> HDEL myhash field2
(integer) 0

HKEYS
获取 hash 中的所有字段

语法:

HKEYS key

时间复杂度:O(N), N 为 field 的个数.
返回值:字段列表。
示例:

redis> HSET myhash field1 "Hello"
(integer) 1
redis> HSET myhash field2 "World"
(integer) 1
redis> HKEYS myhash
1) "field1"
2) "field2"

HVALS
获取 hash 中的所有的值。

语法:

HVALS key

时间复杂度:O(N), N 为 field 的个数.
返回值:所有的值。
示例:

redis> HSET myhash field1 "Hello"
(integer) 1
redis> HSET myhash field2 "World"
(integer) 1
redis> HVALS myhash
1) "Hello"
2) "World"

HGETALL
获取 hash 中的所有字段以及对应的值。
语法:

 HGETALL key

时间复杂度:O(N), N 为 field 的个数.
返回值:字段和对应的值。
示例:

redis> HSET myhash field1 "Hello"
(integer) 1
redis> HSET myhash field2 "World"
(integer) 1
redis> HGETALL myhash
1) "field1"
2) "Hello"
3) "field2"
4) "World"

HMGET
⼀次获取 hash 中多个字段的值。
语法:

HMGET key field [field ...]

时间复杂度:只查询⼀个元素为 O(1), 查询多个元素为 O(N), N 为查询元素个数.
返回值:字段对应的值或者 nil。
示例:

redis> HSET myhash field1 "Hello"
(integer) 1
redis> HSET myhash field2 "World"
(integer) 1
redis> HMGET myhash field1 field2 nofield
1) "Hello"
2) "World"
3) (nil)

在使用 HGETALL 时,如果哈希元素个数⽐较多,会存在阻塞 Redis 的可能。如果开发人员只需要获取部分 field,可以使用 HMGET,如果⼀定要获取全部 field,可以尝试使用HSCAN命令,该命令采⽤渐进式遍历哈希类型

HLEN
获取 hash 中的所有字段的个数。
语法:

HLEN key

时间复杂度:O(1)
返回值:字段个数。

示例:

redis> HSET myhash field1 "Hello"
(integer) 1
redis> HSET myhash field2 "World"
(integer) 1
redis> HLEN myhash
(integer) 2

HSETNX

在字段不存在的情况下,设置 hash 中的字段和值。
语法:

HSETNX key field value

时间复杂度:O(1)
返回值:1 表⽰设置成功,0 表示失败。
示例:

redis> HSETNX myhash field "Hello"
(integer) 1
redis> HSETNX myhash field "World"
(integer) 0
redis> HGET myhash field
"Hello“

HINCRBY

将 hash 中字段对应的数值添加指定的值
语法:

HINCRBY key field increment

时间复杂度:O(1)
返回值:该字段变化之后的值。
示例:

redis> HSET myhash field 5
(integer) 1
redis> HINCRBY myhash field 1
(integer) 6
redis> HINCRBY myhash field -1
(integer) 5
redis> HINCRBY myhash field -10
(integer) -5

HINCRBYFLOAT

HINCRBY 的浮点数版本。
语法:

HINCRBYFLOAT key field increment

时间复杂度:O(1)
返回值:该字段变化之后的值。
示例:

redis> HSET mykey field 10.50
(integer) 1
redis> HINCRBYFLOAT mykey field 0.1
"10.6"
redis> HINCRBYFLOAT mykey field -5
"5.6"
redis> HSET mykey field 5.0e3
(integer) 0
redis> HINCRBYFLOAT mykey field 2.0e2
"5200

2.内部编码

哈希的内部编码有两种:
• ziplist(压缩列表):当哈希类型元素个数小于 hash-max-ziplist-entries 配置(默认 512 个)、 同时所有值都小于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用ziplist 作为哈 希的内部实现,ziplist 使⽤更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable 更加优秀。
• hashtable(哈希表):当哈希类型无法满足ziplist 的条件时,Redis 会使用 hashtable 作为哈希 的内部实现,因为此时 ziplist 的读写效率会下降,而hashtable 的读写时间复杂度为 O(1)。 下面的示例演示了哈希类型的内部编码,以及响应的变化。
1)当 field 个数⽐较少且没有⼤的 value 时,内部编码为 ziplist

127.0.0.1:6379> hmset hashkey f1 v1 f2 v2
OK
127.0.0.1:6379> object encoding hashkey
"ziplist"

2)当有 value ⼤于 64 字节时,内部编码会转换为 hashtable:

127.0.0.1:6379> hset hashkey f3 "one string is bigger than 64 bytes ... 省略
..."
OK
127.0.0.1:6379> object encoding hashkey
"hashtable"

3)当 field 个数超过 512 时,内部编码也会转换为 hashtable:

127.0.0.1:6379> hmset hashkey f1 v1 h2 v2 f3 v3 ... 省略 ... f513 v513
OK
127.0.0.1:6379> object encoding hashkey
"hashtable“

3. 使用场景

下图为关系型数据表记录的两条用户信息,用户的属性表现为表的列,每条用户信息表现为行。如果映射关系表示这两个用户信息,则如下图所示。

在这里插入图片描述
在这里插入图片描述
相比于使用 JSON 格式的字符串缓存用户信息,哈希类型变得更加直观,并且在更新操作上变得更灵活。可以将每个用户的 id 定义为键后缀,多对 field-value 对应⽤⼾的各个属性,类似如下伪代码:

UserInfo getUserInfo(long uid) {// 根据 uid 得到 Redis 的键String key = "user:" + uid;// 尝试从 Redis 中获取对应的值userInfoMap = Redis 执⾏命令:hgetall key;// 如果缓存命中(hit)if (value != null) {// 将映射关系还原为对象形式UserInfo userInfo = 利⽤映射关系构建对象(userInfoMap);return userInfo;}// 如果缓存未命中(miss)// 从数据库中,根据 uid 获取⽤⼾信息UserInfo userInfo = MySQL 执⾏SQL:select * from user_info where uid = 
<uid >// 如果表中没有 uid 对应的⽤⼾信息if (userInfo == null) {响应 404return null;}// 将缓存以哈希类型保存Redis 执⾏命令:hmset key name userInfo.name age userInfo.age cityuserInfo.city// 写⼊缓存,为了防⽌数据腐烂(rot),设置过期时间为 1 ⼩时(3600 秒)Redis 执⾏命令:expire key 3600// 返回⽤⼾信息return userInfo;}

但是需要注意的是哈希类型和关系型数据库有两点不同之处:
• 哈希类型是稀疏的,⽽关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,⽽ 关系型数据库⼀旦添加新的列,所有⾏都要为其设置值,即使为 null
• 关系数据库可以做复杂的关系查询,而 Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等 基本不可能,维护成本高。

关系型数据库稀疏性

在这里插入图片描述

4. 缓存方式对比

截至目前为止,我们已经能够用三种方法缓存用户信息,下⾯给出三种方案的实现方法和优缺点分析

  1. 原⽣字符串类型⸺使用字符串类型,每个属性⼀个键:
set user:1:name James
set user:1:age 23
set user:1:city Beijing

优点:实现简单,针对个别属性变更也很灵活。
缺点:占用过多的键,内存占用量较⼤,同时⽤⼾信息在 Redis 中比较分散,缺少内聚性,所以这种方案基本没有实用性

  1. 序列化字符串类型,例如 JSON 格式
set user:1 

优点:针对总是以整体作为操作的信息比较合适,编程也简单。同时,如果序列化⽅案选择合适,内存的使用效率很高。
缺点:本身序列化和反序列需要⼀定开销,同时如果总是操作个别属性则非常不灵活。

  1. 哈希类型
hmset user:1 name James age 23 city Beijing

优点:简单、直观、灵活。尤其是针对信息的局部变更或者获取操作。
缺点:需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,可能会造成内存的较大消耗。

结语

本文主要介绍了Redis中的全局命令、内部编码、单线程架构,以及String数据结构类型的具体使用和hash的具体使用。

以上就是本文全部内容,感谢各位能够看到最后,如有问题,欢迎各位大佬在评论区指正,希望大家可以有所收获!创作不易,希望大家多多支持!

最后,大家再见!祝好!我们下期见!

相关文章:

【Redis】Redis中的常见数据类型(一)

文章目录 前言一、Redis前置知识1. 全局命令2、数据结构和内部编码3. 单线程架构 二、String 字符串1. 常见命令2. 计数命令3.其他命令4. 内部编码5. 典型使用场景 三、Hash哈希1. 命令2.内部编码3. 使用场景4. 缓存方式对比 结语 前言 Redis 提供了 5 种数据结构&#xff0c;…...

Vue3 + TypeScript,使用祖先传后代模式重构父传子模式

父传子模式 父组件 SampleInput.vue <script setup lang"ts" name"SampleInput"> import { ref } from "vue"; import type { ApplyBasicInfo, Apply, ApplySample } from "/interface"; import CommonApplySampleTable from …...

MySQL:9.表的内连和外连

9.表的内连和外连 表的连接分为内连和外连 9.1 内连接 内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选&#xff0c;之前查询都是内连 接&#xff0c;也是在开发过程中使用的最多的连接查询。 语法&#xff1a; select 字段 from 表1 inner join 表2 on 连接…...

(mac)Grafana监控系统之监控Linux的Redis

Grafana安装-CSDN博客 普罗米修斯Prometheus监控安装&#xff08;mac&#xff09;-CSDN博客 1.Redis_exporter安装 直接下载 wget https://github.com/oliver006/redis_exporter/releases/download/v1.0.3/redis_exporter-v1.0.3.linux-amd64.tar.gz 解压 tar -xvf redis_…...

Multisim使用教程详尽版--(2025最新版)

一、Multisim14前言 1.1、主流电路仿真软件 1. Multisim&#xff1a;NI开发的SPICE标准仿真工具&#xff0c;支持模拟/数字电路混合仿真&#xff0c;内置丰富的元件库和虚拟仪器&#xff08;示波器、频谱仪等&#xff09;&#xff0c;适合教学和竞赛设计。官网&#xff1a;艾…...

python pdf转图片再OCR

先pdf转图片 import os from pdf2image import convert_from_path# PDF文件路径 pdf_path /Users/xxx/2022.pdf # 输出图片的文件夹 output_folder ./output_images2022 # 输出图片的命名格式 output_name page# 如果输出文件夹不存在&#xff0c;创建它 if not os.path.ex…...

npm link 使用指南

npm link 使用指南 npm link 是一个非常有用的命令&#xff0c;主要用于在开发过程中将本地 npm 包链接到全局 npm 目录&#xff0c;从而可以在其他项目中使用这个本地包&#xff0c;而不需要发布到 npm 仓库。 基本用法 1. 创建全局链接 进入你要链接的本地包的根目录&…...

免费图片软件,可矫正倾斜、调整去底效果

软件介绍 有个超棒的软件要给大家介绍一下哦&#xff0c;它就是——ImgTool&#xff0c;能实现图片漂白去底的功能&#xff0c;而且重点是&#xff0c;它是完全免费使用的呢&#xff0c;功能超强大&#xff01; 软件特点及使用便捷性 这软件是绿色版本的哟&#xff0c;就像一…...

5G网络切片:精准分配资源,提升网络效率的关键技术

5G网络切片&#xff1a;精准分配资源&#xff0c;提升网络效率的关键技术 随着5G技术的广泛应用&#xff0c;网络切片&#xff08;Network Slicing&#xff09;作为其核心创新之一&#xff0c;正在改变传统网络架构。它通过将物理网络划分为多个逻辑网络&#xff08;切片&…...

seate TCC模式案例

场景描述 用户下单时&#xff0c;需要创建订单并从用户账户中扣除相应的余额。如果订单创建成功但余额划扣失败&#xff0c;则需要回滚订单创建操作。使用 Seata 的 TCC 模式来保证分布式事务的一致性。 1. 项目结构 假设我们有两个微服务&#xff1a; Order Service&#x…...

Transfomer的本质

Transformer是一个基于自注意力机制的深度学习模型&#xff0c;用于处理序列数据&#xff0c;主要结构包括编码器和解码器&#xff0c;每个部分由多头自注意力和前馈网络组成&#xff0c;加上残差连接和层归一化&#xff0c;以及位置编码。它解决了RNN的并行处理问题&#xff0…...

final修饰变量的注意

在Java中&#xff0c;使用final修饰变量时&#xff0c;需注意以下关键事项&#xff1a; 1. 初始化规则 实例变量&#xff1a; 必须在声明时、构造器或实例初始化块中初始化。所有构造器分支必须保证初始化。 class Example {final int x; // 实例变量final int y;public Exampl…...

前端与传统接口的桥梁:JSONP解决方案

1.JSONP原理 1.1.动态脚本注入 说明&#xff1a;通过创建 <script> 标签绕过浏览器同源策略 1.2.回调约定 说明&#xff1a;服务端返回 函数名(JSON数据) 格式的JS代码 1.3.自动执行 说明&#xff1a;浏览器加载脚本后立即触发前端预定义的回调函数&#xff08;现代开…...

SQL注入 01

0x01 用户、脚本、数据库之间的关系 首先客户端发出了ID36的请求&#xff0c;脚本引擎收到后将ID36的请求先代入脚本的sql查询语句Select * from A where id 36 &#xff0c; 然后将此代入到数据库中进行查询&#xff0c;查到后将返回查询到的所有记录给脚本引擎&#xff0c;接…...

Java之封装(学习笔记)

封装定义&#xff08;个人理解&#xff1a;&#xff09; 封装就像电视遥控器的按钮&#xff0c;比如音量键&#xff0c;对于我们使用者来说就是可以直接按下去调控音量&#xff0c;对于代码写作者来说就是封装了调控音量的方法&#xff0c;使得我们只能去调控&#xff0c;不能改…...

每天学一个 Linux 命令(27):head

​​可访问网站查看,视觉品味拉满: http://www.616vip.cn/27/index.html head 是 Linux 中用于查看文件开头部分内容的命令,默认显示文件前 10 行,适合快速预览文件结构或日志头部信息。 命令格式 head [选项] [文件]常用选项 选项说明-n <行数>指定显示前 N 行(如…...

山东大学软件学院创新项目实训开发日志(20)之中医知识问答自动生成对话标题bug修改

在原代码中存在一个bug&#xff1a;当前对话的标题不是现有对话的用户的第一段的前几个字&#xff0c;而是历史对话的第一段的前几个字。 这是生成标题的逻辑出了错误&#xff1a; 当改成size()-1即可...

论文阅读:2024 ICML Is DPO Superior to PPO for LLM Alignment? A Comprehensive Study

Is DPO Superior to PPO for LLM Alignment? A Comprehensive Study https://www.doubao.com/chat/3506902534329346 https://arxiv.org/pdf/2404.10719 速览 这篇论文主要探讨了大语言模型对齐中两种主流方法——**DPO&#xff08;直接偏好优化&#xff09;和PPO&#xf…...

2025年人工智能指数报告:技术突破与社会变革的全景透视

《2025年人工智能指数报告》作为斯坦福大学人工智能实验室与多方合作的年度重磅研究&#xff0c;以超过千页的篇幅全景式展现了人工智能技术在全球范围内的发展轨迹与深远影响。这份报告不仅延续了对AI技术性能、科研进展与产业应用的追踪&#xff0c;更首次深入探讨了AI硬件的…...

【Python笔记 01】变量、标识符

一、 变量 1、变量的作用 计算机存储空间&#xff0c;用于保存数据。 2、定义变量的格式 格式&#xff1a;变量名 值 示例&#xff1a; num1 3 # num1就是一个变量&#xff0c;保存蛋糕的价格 num2 10 #num2也是一个变量&#xff0c;保存雪碧的价格 total num1 num2 …...

WebSocket启用备忘

一&#xff1a;引入依赖&#xff1a; <!--WebSocket专用--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org…...

文件管理详解(曼波脑图版)

(✪ω✪)曼波来啦&#xff01;文件管理的知识曼波这就为你详细讲解哟~ 记得要准备好小本本做笔记哦&#xff01;(๑˃̵ᴗ˂̵)و &#x1f31f; 文件读写操作 &#x1f31f; // 最可爱的文件读取写法 (✧∇✧) try (BufferedReader reader new BufferedReader(new FileRead…...

学习笔记十九——Rust多态

&#x1f9e9; Rust 多态终极通俗指南 &#x1f4da; 目录导航 多态一句话概念静态分派 vs 动态分派——根本差异参数化多态&#xff08;泛型&#xff09; 3.1 函数里的泛型 3.2 结构体里的泛型 3.3 方法里的泛型 3.4 枚举里的泛型Ad hoc 多态&#xff08;特例多态&#xff0…...

在 Linux 上部署 .NET Core 应用并配置为开机自动启动

在本文中&#xff0c;我们将详细介绍如何在 Linux 系统上部署 .NET Core 应用程序&#xff0c;并配置为开机自动启动。以下是一步一步的详细部署过程&#xff0c;适用于将 .NET Core Web 应用部署到生产环境中。 1. 安装 .NET 运行时和 SDK 首先&#xff0c;确保 Linux 系统上…...

dubbo SPI插件扩展点使用

参考&#xff1a;SPI插件扩展点 Dubbo SPI概述 使用IoC容器帮助管理组件的生命周期、依赖关系注入等是很多开发框架的常用设计&#xff0c;Dubbo中内置了一个轻量版本的IoC容器&#xff0c;用来管理框架内部的插件&#xff0c;实现包括插件实例化、生命周期、依赖关系自动注入…...

P8512 [Ynoi Easy Round 2021] TEST_152 Solution

Description 有一序列 c ( c 1 , c 2 , ⋯ , c m ) c(c_1,c_2,\cdots,c_m) c(c1​,c2​,⋯,cm​) 和 n n n 个三元组 ( l i , r i , v i ) (l_i,r_i,v_i) (li​,ri​,vi​). 回答 q q q 次形如 ( L , R ) (L,R) (L,R) 的询问&#xff0c;具体如下&#xff1a; 将 c c …...

开源项目FastAPI-MCP:一键API转换MCP服务

在当今AI开发的世界中,应用程序与AI模型之间的无缝集成至关重要。 模型上下文协议(Model Context Protocol, MCP)通过允许AI模型访问外部工具和数据源,弥合了这一差距。 FastAPI MCP是一个强大的工具,它可以通过最少的配置将您现有的FastAPI端点转换为MCP兼容的工具。 本…...

8、constexpr if、inline、类模版参数推导、lambda的this捕获---c++17

一、constexpr if&#xff1a;编译时条件分支 作用&#xff1a;在模板编程中&#xff0c;根据条件在编译时选择不同的代码路径&#xff0c;无需特化版本或复杂SFINAE技巧[替代SFINAE]。[SFINAE将在模版元编程再讲。下个月了。]基本语法 if constexpr (condition) {// 如果 co…...

github新建一个远程仓库并添加了README.md,本地git仓库无法push

1.本地git仓库与远程仓库绑定 2.push时报错&#xff0c;本地的 main 分支落后于远程仓库的 main 分支&#xff08;即远程有更新&#xff0c;但你本地没有&#xff09;&#xff0c;需要拉取远程的仓库--->在merge合并&#xff08;解决冲突&#xff09;--->push 3.但是git …...

贝叶斯分类器:原理、算法与应用详解

内容摘要 本文聚焦贝叶斯分类器&#xff0c;介绍其在各类分类器中分类错误概率最小的特性。详细阐述贝叶斯分类器的基本原理、朴素贝叶斯和半朴素贝叶斯分类器的算法&#xff0c;结合西瓜数据集实例说明朴素贝叶斯的应用。此外&#xff0c;还深入探讨极大似然估计和贝叶斯估计…...

算法篇之单调栈

单调栈算法入门 单调栈是一种特殊的数据结构应用&#xff0c;它的核心在于维护一个栈&#xff0c;使得栈内元素保持单调递增或者单调递减的顺序。这种数据结构在解决很多算法问题时非常有效&#xff0c;例如求数组中每个元素的下一个更大元素、每日温度问题等。 一、单调栈的…...

用python + PIL 实现图片格式转换工具

用python PIL 实现图片格式转换工具 要运行该程序&#xff0c;需要使用第三方库PIL&#xff08;pillow&#xff09;&#xff0c;详情可见https://blog.csdn.net/cnds123/article/details/126141838 格式支持&#xff1a; 支持常见图片格式转换&#xff08;JPEG, PNG, BMP, GIF…...

【数据库】事务

目录 1. 什么是事务&#xff1f; 2. 事务的ACID特性 3. 为什么使用事务&#xff1f; 4. 如何使用事务 4.1 查看支持事务的存储引擎 4.2 语法 4.3 保存点 4.4 自动/手动提交事务 5. 事物的隔离性和隔离级别 5.1 什么是隔离性 5.2 隔离级别 5.3 查看和设置隔离级别 1…...

C++:详解命名空间

目录 前言 一、命名空间是什么&#xff1f; 1.1命名空间域的定义 二、为什么有命名空间&#xff1f; 三、命名空间的用法 总结 前言 主要讲解命名空间是什么&#xff1f;为什么有命名空间&#xff1f;以及它的用法 一、命名空间是什么&#xff1f; 命名空间域&#xff08;nam…...

ClickHouse核心架构设计

列式存储原理与数据压缩 列式存储原理 列式存储 vs 行式存储 特性行式存储&#xff08;如MySQL&#xff09;列式存储&#xff08;ClickHouse&#xff09;数据排列按行连续存储&#xff08;所有字段相邻&#xff09;按列连续存储&#xff08;单列数据紧密排列&#xff09;适用场…...

K8s-Pod详解

Pod介绍 Pod是Kubernetes中能够创建和部署的最小单元&#xff0c;是Kubernetes集群中的一个应用实例&#xff0c;总是部署在同一个节点Node上。&#xff08;程序运行部署在容器中&#xff0c;容器必须存在pod中。pod可以认为是容器的封装&#xff0c;一个pod中可以存在一个或者…...

SSM(SpringMVC+spring+mybatis)整合的步骤以及相关依赖

目录 &#xff08;一&#xff09;导入SSM框架相关的依赖 ①创建一个新的web工程&#xff08;idea2023版&#xff09; ②思考三者之间的联系&#xff0c;回忆依赖 ③在pom.xml文件中引入依赖坐标 &#xff08;二&#xff09;使用注解开发&#xff0c;编写Spring的配置类&am…...

【LeetCode】算法详解#5 ---轮转数组

1.题目介绍 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 1 < nums.length < 105-231 < nums[i] < 231 - 10 < k < 105 2.解决思路 这道题的解决方法有很多&#xff0c;我这里给大家介绍我使用的方法。…...

LeetCode 打家劫舍+删除并获得点数

题目描述 打家劫舍题目传送门1 删除并获得点数传送门2 思路 这两道题看似毫无关系&#xff0c;但竟然可以用桶数组联系起来&#xff01;&#xff01; 先说打家劫舍这道题 限制条件是不能走相邻的屋&#xff0c;再联想到跳台阶&#xff08;走一格或两格&#xff09;&#x…...

【计量地理学】实验四 主成分分析与莫兰指数

一、实验内容 &#xff08;一&#xff09; 某地区35个城市2004年的7项经济统计指标数据见&#xff08;数据中的“题目1”sheet&#xff09;。 &#xff08;1&#xff09;试用最短距离聚类法对35个城市综合实力进行系统聚类分析&#xff0c;并画出聚类谱系图: 在此次实验内容…...

TDengine 性能监控与调优实战指南(二)

四、TDengine 性能调优实战 4.1 硬件层面优化 硬件是 TDengine 运行的基础&#xff0c;其性能直接影响着 TDengine 的整体表现。在硬件层面进行优化&#xff0c;就如同为高楼大厦打下坚实的地基&#xff0c;能够为 TDengine 的高效运行提供有力支持。 CPU&#xff1a;CPU 作…...

Linux `init 5` 相关命令的完整使用指南

Linux init 5 相关命令的完整使用指南—目录 一、init 系统简介二、init 5 的含义与作用三、不同 Init 系统下的 init 5 行为1. SysVinit&#xff08;如 CentOS 6、Debian 7&#xff09;2. systemd&#xff08;如 CentOS 7、Ubuntu 16.04&#xff09;3. Upstart&#xff08;如 …...

uni-app中map的使用

uni-app中map的使用 一、基本使用步骤 1. 引入 map 组件 在 .vue 文件的 template 中直接使用 <map> 标签&#xff1a; <template><view><map :latitude"latitude" :longitude"longitude" :markers"markers" style&quo…...

备战2025年全国信息素养大赛图形化大赛——绘制雪花

以上题目点击下方地址&#xff0c;可查看答案或者在线编程&#xff5e; 绘制雪花_scratch_少儿编程题库学习中心-嗨信奥https://www.hixinao.com/tiku/scratch/show-5775.html?_shareid3 程序演示可点击下方地址&#xff0c;支持源码和素材获取&#xff0c;方便高效&#xff…...

1Panel - 基于Web的Linux服务器管理工具

本文翻译整理自&#xff1a;https://github.com/1Panel-dev/1Panel 文章目录 一、关于 1Panel相关链接资源关键功能特性 二、安装系统要求安装脚本 三、基本使用&#xff08;快速开始&#xff09;1、快速安装2、访问面板 四、界面展示五、专业版六、安全信息感谢 一、关于 1Pan…...

基于SpringAI Alibaba实现RAG架构的深度解析与实践指南

一、RAG技术概述 1.1 什么是RAG技术 RAG&#xff08;Retrieval-Augmented Generation&#xff09;检索增强生成是一种将信息检索技术与生成式AI相结合的创新架构。它通过以下方式实现智能化内容生成&#xff1a; 知识检索阶段&#xff1a;从结构化/非结构化数据源中检索相关…...

SpringBoot Actuator指标收集:Micrometer与Prometheus集成

文章目录 引言一、Spring Boot Actuator基础二、Micrometer简介与集成三、基本指标收集与配置四、自定义业务指标实现五、与Prometheus集成六、实战案例&#xff1a;API性能监控总结 引言 在现代微服务架构中&#xff0c;监控应用程序的健康状况和性能指标变得至关重要。Sprin…...

Spring Boot 集成 Kafka 及实战技巧总结

Spring Boot 集成 Kafka 及实战技巧总结 一、Spring Boot 集成 Kafka 添加依赖 <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId> </dependency>配置 Kafka 在 application.yml 中配置生产…...

LeetCode hot 100—分割等和子集

题目 给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集&#xff0c;使得两个子集的元素和相等。 示例 示例 1&#xff1a; 输入&#xff1a;nums [1,5,11,5] 输出&#xff1a;true 解释&#xff1a;数组可以分割成 [1, 5, 5] 和 [11] 。…...

JUC复习及面试题学习

资源来自沉默王二、小林coding、竹子爱熊猫、代码随想录 一、JUC 1、进程与线程 进程是对运行程序的封装&#xff0c;是系统进行资源调度和分配的最小单位。 线程是进程的子任务&#xff0c;是CPU调度分配的基本单位 不同的进程之间很难数据共享&#xff0c;同进程下的不同线…...