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

Redis 的使⽤和原理

第一章:初识 Redis

1.1盛赞 Redis
Redis 是⼀种基于键值对(key-value)的 NoSQL 数据库,与很多键值对数据库不同的是,Redis
中的值可以是由 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、 Bitmaps(位图)、HyperLogLog、GEO(地理信息定位)等多种数据结构和算法组成,因此 Redis可以满⾜很多的应⽤场景,⽽且因为 Redis 会将所有数据都存放再内存中,所以它的读写性能⾮常惊⼈。不仅如此,Redis 还可以将内存的数据利⽤快照和⽇志的形式保存到硬盘上,这样在发⽣类似断电或者机器故障的时候,内存中的数据不会“丢失”。除了上述功能以外,Redis 还提供了键过期、发布订阅、事务、流⽔线、Lua 脚本等附加功能。总之,如果在合适的场景使⽤号 Redis,它就会像⼀把瑞⼠军⼑⼀样所向披靡。
2008 年,Redis 的作者 Salvatore Sanfilippo 在开发⼀个叫 LLOOGG 的⽹站时,需要实现⼀个⾼
性能的队列功能,最开始是使⽤ MySQL 来实现的,但后来发现⽆论怎么优化 SQL 语句等都不能使⽹站的性能提⾼上去,再加上⾃⼰囊中羞涩,于是他决定⾃⼰做⼀个专属于 LLOOGG 的数据库,这个就是 Redis 的前⾝。后来,Salvatore Sanfilippo 将 Redis 1.0 的源码发布到 Github 上,可能连他⾃⼰都没想到,Redis 后来如此受欢迎。
假如现在有⼈问 Redis 的作者都有谁在使⽤ Redis,我想他可以开句玩笑的回答:还有谁不使⽤
Redis,当然这只是开玩笑,但是从 Redis 的官⽅公司统计来看,有很多重量级的公司都在使⽤
Redis,如国外的 Twitter、Instagram、Stack Overflow、Github 等,国内就更多了,如果单单从体量来统计,新浪微博可以说是全球最⼤的 Redis 使⽤者,除了新浪微博,还有像阿⾥巴巴、腾讯、搜狐、优酷⼟⾖、美团、⼩⽶、唯品会等公司都是 Redis 的使⽤者。除此之外,许多开源技术像 ELK 等
已经把 Redis 作为它们组件中的重要⼀环,⽽且 Redis 还提供了模块系统让第三⽅⼈员实现功能扩展,让 Redis 发挥出更⼤的威⼒。所以,可以这么说,熟练使⽤和运维 Redis 已经成为开发运维⼈员的⼀个必备技能。
1.2 Redis 特性
Redis 之所以受到如此多公司的⻘睐,必然有之过⼈之处,下⾯是关于 Redis 的 8 个重要特性。
1. 速度快
正常情况下,Redis 执⾏命令的速度⾮常快,官⽅给出的数字是读写性能可以达到 10 万 / 秒,当
然这也取决于机器的性能,但这⾥先不讨论机器性能上的差异,只分析⼀下是什么造就了 Redis 如此之快,可以⼤概归纳为以下四点:
Redis 的所有数据都是存放在内存中的,下表是⾕歌公司 2009 年给出的各层级硬件执⾏速度,所以把数据放在内存中是 Redis 速度快的最主要原因。
2. 基于键值对的数据结构服务器
⼏乎所有的编程语⾔都提供了类似字典的功能,例如 C++ ⾥的 map、Java ⾥的 map、Python ⾥
的 dict 等,类似于这种组织数据的⽅式叫做基于键值对的⽅式,与很多键值对数据库不同的是,
Redis 中的值不仅可以是字符串,⽽且还可以是具体的数据结构,这样不仅能便于在许多应⽤场景的开发,同时也能提⾼开发效率。Redis 的全程是 REmote Dictionary Server,它主要提供了 5 种数据结构:字符串(string)、哈希(hash)、列表(list)、集合(set)、有序集合(ordered set / zset),同时在字符串的基础之上演变出了位图(Bitmaps)和 HyperLogLog 两种神奇的 ”数据结构“,并且随着 LBS(Location Based Service,基于位置服务)的不断发展,Redis 3.2. 版本种加⼊有关 GEO(地理信息定位)的功能,总之在这些数据结构的帮助下,开发者可以开发出各种 “有意思” 的应⽤。
3. 丰富的功能
除了 5 种数据结构,Redis 还提供了许多额外的功能:
提供了键过期功能,可以⽤来实现缓存。
提供了发布订阅功能,可以⽤来实现消息系统。
⽀持 Lua 脚本功能,可以利⽤ Lua 创造出新的 Redis 命令。
提供了简单的事务功能,能在⼀定程度上保证事务特性。
提供了流⽔线(Pipeline)功能,这样客⼾端能将⼀批命令⼀次性传到 Redis,减少了⽹络的开
销。
4. 简单稳定
Redis 的简单主要表现在三个⽅⾯。⾸先,Redis 的源码很少,早期版本的代码只有 2 万⾏左右,
3.0 版本以后由于添加了集群特性,代码增⾄ 5 万⾏左右,相对于很多 NoSQL 数据库来说代码量相对要少很多,也就意味着普通的开发和运维⼈员完全可以 “吃透” 它。其次, Redis 使⽤单线程模型 , 这样不仅使得 Redis 服务端处理模型变得简单,⽽且也使得客⼾端开发变得简单。最后,Redis 不需要依赖于操作系统中的类库(例如 Memcache 需要依赖 libevent 这样的系统类库),Redis ⾃⼰实现了事件处理的相关功能。
但与简单相对的是 Redis 具备相当的稳定性,在⼤量使⽤过程中,很少出现因为 Redis ⾃⾝ BUG
⽽导致宕掉的情况。
5. 客⼾端语⾔多
Redis 提供了简单的 TCP 通信协议,很多编程语⾔可以很⽅便地接⼊到 Redis,并且由于 Redis 受到社区和各⼤公司的⼴泛认可,所以⽀持 Redis 的客⼾端语⾔也⾮常多,⼏乎涵盖了主流的编程语⾔,例如 C、C++、Java、PHP、Python、NodeJS 等,后续我们会对 Redis 的客⼾端使⽤做详细说明。
6. 持久化(Persistence)
通常看,将数据放在内存中是不安全的,⼀旦发⽣断电或者机器故障,重要的数据可能就会丢
失,因此 Redis 提供了两种持久化⽅式:RDB 和 AOF,即可以⽤两种策略将内存的数据保存到硬盘中
(如图 1-1 所⽰),这样就保证了数据的可持久性,后续我们将对 Redis 的持久化进⾏详细说明。
7. 主从复制(Replication)
Redis 提供了复制功能,实现了多个相同数据的 Redis 副本(Replica)(如图 1- 2 所⽰),复制
功能是分布式 Redis 的基础。后续我们会对 Redis 的复制功能进⾏详细演⽰。
8. ⾼可⽤(High Availability)和分布式(Distributed)
Redis 提供了⾼可⽤实现的 Redis 哨兵(Redis Sentinel),能够保证 Redis 结点的故障发现和故
障⾃动转移。也提供了 Redis 集群(Redis Cluster),是真正的分布式实现,提供了⾼可⽤、读写和容量的扩展性
1.3 Redis 使⽤场景
1.3.1 Redis 可以做什么
1. 缓存(Cache)
缓存机制⼏乎在所有⼤型⽹站都有使⽤,合理地使⽤缓存不仅可以加速数据的访问速度,⽽且能
够有效地降低后端数据源的压⼒。Redis 提供了键值过期时间设置,并且也提供了灵活控制最⼤内存和内存溢出后的淘汰策略。可以这么说,⼀个合理的缓存设计能够为⼀个⽹站的稳定保驾护航。
2. 排⾏榜系统
排⾏榜系统⼏乎存在于所有的⽹站,例如按照热度排名的排⾏榜,按照发布时间的排⾏榜,按照
各种复杂维度计算出的排⾏榜,Redis 提供了列表和有序集合的结构,合理地使⽤这些数据结构可以很⽅便地构建各种排⾏榜系统。
3. 计数器应⽤
计数器在⽹站中的作⽤⾄关重要,例如视频⽹站有播放数、电商⽹站有浏览数,为了保证数据的
实时性,每⼀次播放和浏览都要做加 1 的操作,如果并发量很⼤对于传统关系型数据的性能是⼀种挑战。Redis 天然⽀持计数功能⽽且计数的性能也⾮常好,可以说是计数器系统的重要选择。
4. 社交⽹络
赞 / 踩、粉丝、共同好友 / 喜好、推送、下拉刷新等是社交⽹站的必备功能,由于社交⽹站访问量
通常⽐较⼤,⽽且传统的关系型数据不太合适保存这种类型的数据,Redis 提供的数据结构可以相对⽐较容易地实现这些功能。
5. 消息队列系统
消息队列系统可以说是⼀个⼤型⽹站的必备基础组件,因为其具有业务解耦、⾮实时业务削峰等
特性。Redis 提供了发布订阅功能和阻塞队列的功能,虽然和专业的消息队列⽐还不够⾜够强⼤,但是对于⼀般的消息队列功能基本可以满⾜
1.3.2 Redis 不可以做什么
实际上和任何⼀⻔技术⼀样,每个技术都有⾃⼰的应⽤场景和边界,也就是说 Redis 并不是万⾦
油,有很多合适它解决的问题,但是也有很多不合适它解决的问题。 我们可以站在数据规模和数据冷热的⻆度来进⾏分析。
站在数据规模的⻆度看,数据可以分为⼤规模数据和⼩规模数据,我们知道 Redis 的数据是存放
在内存中的,虽然现在内存已经⾜够便宜,但是如果数据量⾮常⼤,例如每天有⼏亿的⽤⼾⾏为数
据,使⽤ Redis 来存储的话,基本上是个⽆底洞,经济成本相当⾼。
站在数据冷热的⻆度,数据分为热数据和冷数据,热数据通常是指需要频繁操作的数据,反之为
冷数据 ,例如对于视频⽹站来说,视频基本信息基本上在各个业务线都是经常要操作的数据,⽽⽤⼾的观看记录不⼀定是经常需要访问的数据,这⾥暂且不讨论两者数据规模的差异,单纯站在数据冷热的⻆度上看,视频信息属于热数据,⽤⼾观看记录属于冷数据。如果将这些冷数据放在 Redis 上,基本上是对于内存的⼀种浪费,但是对于⼀些热数据可以放在 Redis 中加速读写,也可以减轻后端存储的负载,可以说是事半功倍。
所以,Redis 并不是万⾦油,相信随着我们对 Redis 的逐步学习,能够清楚 Redis 真正的使⽤场
景。
第⼆章 Redis 常⻅数据类型

2.1.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
返回值:匹配 pattern 的所有 key。
EXISTS
判断某个 key 是否存在。
DEL
删除指定的 key。
返回值:删除掉的 key 的个数
EXPIRE
为指定的 key 添加秒级的过期时间(Time To Live TTL)
返回值:1 表⽰设置成功。0 表⽰设置失败
TTL
获取指定 key 的过期时间,秒级
剩余过期时间。-1 表⽰没有关联过期时间,-2 表⽰ key 不存在。
关于键过期机制,可以参考图 2-1 所⽰。
TYPE
返回 key 对应的数据类型。
返回值: none , string , list , set , zset , hash and stream
2.1.2 数据结构和内部编码
type 命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、list(列
表)、hash(哈希)、set(集合)、zset(有序集合),但这些只是 Redis 对外的数据结构。
Redis 的 5 种数据类型
实际上 Redis 针对每种数据结构都有⾃⼰的底层内部编码实现,⽽且是多种实现,这样 Redis 会
在合适的场景选择合适的内部编码,如表 2-1 所⽰。
可以看到每种数据结构都有⾄少两种以上的内部编码实现,例如 list 数据结构包含了 linkedlist 和
ziplist 两种内部编码。同时有些内部编码,例如 ziplist,可以作为多种数据结构的内部实现,可以通过 object encoding 命令查询内部编码
可以看到 hello 对应值的内部编码是 embstr,键 mylist 对应值的内部编码是 ziplist。
Redis 这样设计有两个好处:
1)可以改进内部编码,⽽对外的数据结构和命令没有任何影响,这样⼀旦开发出更优秀的内部编码,⽆需改动外部数据结构和命令,例如 Redis 3.2 提供了 quicklist,结合了 ziplist 和 linkedlist 两者的优势,为列表类型提供了⼀种更为优秀的内部编码实现,⽽对⽤⼾来说基本⽆感知。
2)多种内部编码实现可以在不同场景下发挥各⾃的优势,例如 ziplist ⽐较节省内存,但是在列表元素⽐较多的情况下,性能会下降,这时候 Redis 会根据配置选项将列表类型的内部实现转换为
linkedlist,整个过程⽤⼾同样⽆感知。
2.1.3 单线程架构
Redis 使⽤了单线程架构来实现⾼性能的内存数据库服务,本节⾸先通过多个客⼾端命令调⽤的例
⼦说明 Redis 单线程命令处理机制,接着分析 Redis 单线程模型为什么性能如此之⾼,最终给出为什么理解单线程模型是使⽤和运维 Redis 的关键。
1. 引出单线程模型

redis单线程,只有一个线程去接收处理所有的请求,并不是服务器进程内部只有一个线程,其实也有很多线程,这些线程都在处理网络IO

上面这个情况,可能会有线程安全问题

其实redis服务器并不会有这种情况,因为redis服务器是单线程服务器,把接收到的请求串行执行,多个请求到达redis队列中,也是需要进行排队等候的.

redis能够使用单线程模型,很好的工作,是因为redis处理的业务逻辑是短平快,不消耗CPU资源,如果一个操作执行的时间太长,就会影响其他的请求的执行

2.为什么redis是单线程,速度还能这么快,效率这么高?

他的速度快和效率高是与关系性数据库(MySQL,Orcale)进行对比

1.redis直接访问的是内存,而关系性数据库访问的硬盘

2.redis的核心功能,比关系性数据库的核心功能要简单,数据库对于数据的插入查询,都有更复杂的功能支持,

3.单线程模型,减少了不必要的线程竞争开销,redis每个操作都是短平快,不涉及特别消耗cpu的操作

4.处理网络IO的时候,使用了epoll这样的IO多路复用机制,IO多路复用机制,一个线程就可以处理多个socket,,epoll事件通知/回调机制,一个线程可以完成好多任务,前提是这些任务的交互不频繁,大部分时间都在等,可以采取epoll多路复用机制

1.String 字符串

在redis中,所以的key都是string类型,value的数据类型才有差异

字符串类型是 Redis 最基础的数据类型,直接按照二进制数据的方式进行存储,不会做任何的编码转化,存的是啥,取出来就是啥,关于字符串需要特别注意:1)⾸先 Redis 中所有的键的 类型都是字符串类型,⽽且其他⼏种数据结构也都是在字符串类似基础上构建的,例如列表和集合的元素类型是字符串类型,所以字符串类型能为其他 4 种数据结构的学习奠定基础。2)其次,如图 2-7所⽰,字符串类型的值实际可以是字符串,包含⼀般格式的字符串或者类似 JSON、XML 格式的字符串;数字,可以是整型或者浮点型;甚⾄是⼆进制流数据,例如图⽚、⾳频、视频等。不过⼀个字符串的最⼤值不能超过 512 MB。

常见命令
SET
将 string 类型的 value 设置到 key 中。如果 key 之前存在,则覆盖,⽆论原来的数据类型是什么。之前关于此 key 的 TTL 也全部失效。
语法:
1 SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
命令有效版本:1.0.0 之后
时间复杂度:O(1)

SET 命令⽀持多种选项来影响它的⾏为:
EX seconds⸺使⽤秒作为单位设置 key 的过期时间。
PX milliseconds ⸺使⽤毫秒作为单位设置 key 的过期时间。
NX ⸺只在 key 不存在时才进⾏设置,即如果 key 之前已经存在,设置不执⾏。
XX ⸺只在 key 存在时才进⾏设置,即如果 key 之前不存在,设置不执⾏。
返回值:
如果设置成功,返回 OK。
如果由于 SET 指定了 NX 或者 XX 但条件不满⾜,SET 不会执⾏,并返回 (nil)。
GET
获取 key 对应的 value。如果 key 不存在,返回 nil。如 果 value 的数据类型不是 string,会报错
语法:
1 GET key
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:key 对应的 value,或者 nil 当 key 不存在。
MGET
⼀次性获取多个 key 的值。如果对应的 key 不存在或者对应的数据类型不是 string,返回 nil。
语法:
1 MGET key [key ...]
命令有效版本:1.0.0 之后
时间复杂度:O(N) N 是 key 数量
返回值:对应 value 的列表
MSET
⼀次性设置多个 key 的值。
语法:
1 MSET key value [key value ...]
命令有效版本:1.0.1 之后
时间复杂度:O(N) N 是 key 数量
返回值:永远是 OK
学会使⽤批量操作,可以有效提⾼业务处理效率,但是要注意,每次批量操作所发送的键的数量也不是⽆节制的,否则可能造成单⼀命令执⾏时间过⻓,导致 Redis 阻塞。
SETNX
设置 key-value 但只允许在 key 之前不存在的情况下。
语法:
1 SETNX key value
命令有效版本:1.0.0 之后
时间复杂度:O(1)
返回值:1 表⽰设置成功。0 表⽰没有设置。
SET、SET NX、SET XX 执⾏流程
计数命令
INCR
将 key 对应的 string 表⽰的数字加⼀。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。
INCRBY
将 key 对应的 string 表⽰的数字加上对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。
返回值:integer 类型的加完后的数值。
DECR
将 key 对应的 string 表⽰的数字减⼀。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错
DECYBY
将 key 对应的 string 表⽰的数字减去对应的值。如果 key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的 string 不是⼀个整型或者范围超过了 64 位有符号整型,则报错。
INCRBYFLOAT
将 key 对应的 string 表⽰的浮点数加上对应的值。如果对应的值是负数,则视为减去对应的值。如果key 不存在,则视为 key 对应的 value 是 0。如果 key 对应的不是 string,或者不是⼀个浮点数,则报错。允许采⽤科学计数法表⽰浮点数。
很多存储系统和编程语⾔内部使⽤ CAS 机制实现计数功能,会有⼀定的 CPU 开销,但在 Redis 中完全不存在这个问题,因为 Redis 是单线程架构,任何命令到了 Redis 服务端都要顺序执⾏。
APPEND
如果 key 已经存在并且是⼀个 string,命令会将 value 追加到原有 string 的后边。如果 key 不存在,则效果等同于 SET 命令。
语法:
1 APPEND KEY VALUE
时间复杂度:O(1). 追加的字符串⼀般⻓度较短, 可以视为 O(1).
返回值:追加完成之后 string 的⻓度。
GETRANGE
返回 key 对应的 string 的⼦串,由 start 和 end 确定(左闭右闭)。可以使⽤负数表⽰倒数。-1 代表倒数第⼀个字符,-2 代表倒数第⼆个,其他的与此类似。超过范围的偏移量会根据 string 的⻓度调整成正确的值。
语法:
1 GETRANGE key start end
时间复杂度:O(N). N 为 [start, end] 区间的⻓度. 由于 string 通常⽐较短, 可以视为是 O(1)
返回值:string 类型的⼦串
SETRANGE
覆盖字符串的⼀部分,从指定的偏移开始。
语法:
1 SETRANGE key offset value
时间复杂度:O(N), N 为 value 的⻓度. 由于⼀般给的 value ⽐较短, 通常视为 O(1).
返回值:替换后的 string 的⻓度。
STRLEN
获取 key 对应的 string 的⻓度。当 key 存放的类似不是 string 时,报错。
语法:
1 STRLEN key
时间复杂度:O(1)
返回值:string 的⻓度。或者当 key 不存在时,返回 0
命令⼩结
字符串类型命令的效果、时间复杂度,可以参考此表,结合业务需求和数据⼤⼩选择合适的命令。
内部编码

字符串类型的内部编码有 3 种:使用object encoding来查询当前编码
int:8 个字节的⻓整型。
embstr:⼩于等于 39 个字节的字符串。
raw:⼤于 39 个字节的字符串

典型使⽤场景

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

举一个例子来理解一下缓存

这是一段伪代码

1)假设业务是根据⽤⼾ uid 获取⽤⼾信息
UserInfo getUserInfo(long uid) {
...
}
2)⾸先从 Redis 获取⽤⼾信息,我们假设⽤⼾信息保存在 "user:info:<uid>" 对应的键中:
// 根据 uid 得到 Redis 的键
String key = "user:info:" + uid;
// 尝试从 Redis 中获取对应的值
String value = Redis 执⾏命令: get key;
// 如果缓存命中( hit
if (value != null) {
// 假设我们的⽤⼾信息按照 JSON 格式存储
UserInfo userInfo = JSON 反序列化 (value);
return userInfo;
}
如果没有从 Redis 中得到⽤⼾信息,及缓存 miss,则进⼀步从 MySQL 中获取对应的信息,随后写⼊缓存并返回:
// 如果缓存未命中( miss
if (value == null) {
// 从数据库中,根据 uid 获取⽤⼾信息
UserInfo userInfo = MySQL 执⾏ SQL select * from user_info where uid = <uid>
// 如果表中没有 uid 对应的⽤⼾信息
if (userInfo == null) {
响应 404
return null;
}
// 将⽤⼾信息序列化成 JSON 格式
String value = JSON 序列化 (userInfo);
// 写⼊缓存,为了防⽌数据腐烂( rot ),设置过期时间为 1 ⼩时( 3600 秒)
Redis 执⾏命令: set key value ex 3600
// 返回⽤⼾信息
return userInfo;
}

redis这样的缓存,经常存储"热点"数据(被高频使用的数据),最近一段时间都会反复用到的数据,

计数(Counter)功能

许多应⽤都会使⽤ Redis 作为计数的基础⼯具,它可以实现快速计数、查询缓存的功能,同时数
据可以异步处理或者落地到其他数据源。如图 2-11 所⽰,例如视频⽹站的视频播放次数可以使⽤
Redis 来完成:⽤⼾每播放⼀次视频,相应的视频播放数就会⾃增 1。
// Redis 中统计某视频的播放次数
long incrVideoCounter(long vid) {
key = "video:" + vid;
long count = Redis 执⾏命令: incr key
return count;
}
共享会话(Session)
如图 2-12 所⽰,⼀个分布式 Web 服务将⽤⼾的 Session 信息(例如⽤⼾登录信息)保存在各⾃
的服务器中,但这样会造成⼀个问题:出于负载均衡的考虑,分布式服务会将⽤⼾的访问请求均衡到不同的服务器上,并且通常⽆法保证⽤⼾每次请求都会被均衡到同⼀台服务器上,这样当⽤⼾刷新⼀次访问是可能会发现需要重新登录,这个问题是⽤⼾⽆法容忍的。
为了解决这个问题,可以使⽤ Redis 将⽤⼾的 Session 信息进⾏集中管理,如图 2-13 所⽰,在这种模式下,只要保证 Redis 是⾼可⽤和可扩展性的,⽆论⽤⼾被均衡到哪台 Web 服务器上,都集中从Redis 中查询、更新 Session 信息。
⼿机验证码
很多应⽤出于安全考虑,会在每次进⾏登录时,让⽤⼾输⼊⼿机号并且配合给⼿机发送验证码,
然后让⽤⼾再次输⼊收到的验证码并进⾏验证,从⽽确定是否是⽤⼾本⼈。为了短信接⼝不会频繁访问,会限制⽤⼾每分钟获取验证码的频率,例如⼀分钟不能超过 5 次
此功能可以⽤以下伪代码说明基本实现思路:
2.Hash 哈希
⼏乎所有的主流编程语⾔都提供了哈希(hash)类型,它们的叫法可能是哈希、字典、关联数
组、映射。在 Redis 中,哈希类型是指值本⾝⼜是⼀个键值对结构,形如 key = "key",value = { {
field1, value1 }, ..., {fieldN, valueN } },Redis 键值对和哈希类型⼆者的关系可以⽤图 2-15 来表⽰。
常见命令
1.HSET
设置 hash 中指定的字段(field)的值(value)。
语法:
HSET key field value [field value ...]
时间复杂度:插⼊⼀组 field 为 O(1), 插⼊ N 组 field 为 O(N)
返回值:添加的字段的个数。
2.HGET
获取 hash 中指定字段的值。
语法:
1 HGET key field
时间复杂度:O(1)
返回值:字段对应的值或者 nil。
3.HEXISTS
判断 hash 中是否有指定的字段。
语法:
HEXISTS key field
时间复杂度:O(1)
返回值:1 表⽰存在,0 表⽰不存在。
4.HDEL
删除 hash 中指定的字段。
语法:
1 HDEL key field [field ...]
时间复杂度:删除⼀个元素为 O(1). 删除 N 个元素为 O(N).
返回值:本次操作删除的字段个数。
5.HKEYS
获取 hash 中的所有字段。
语法:
HKEYS key
时间复杂度:O(N), N 为 field 的个数.
返回值:字段列表。
6.HVALS
获取 hash 中的所有的值。
语法:
HVALS key
时间复杂度:O(N), N 为 field 的个数.
返回值:所有的值。
6.HGETALL
获取 hash 中的所有字段以及对应的值。
语法:
HGETALL key
时间复杂度:O(N), N 为 field 的个数.
返回值:字段和对应的值。
7.HMGET
⼀次获取 hash 中多个字段的值。
语法:
HMGET key field [field ...]
时间复杂度:只查询⼀个元素为 O(1), 查询多个元素为 O(N), N 为查询元素个数.
返回值:字段对应的值或者 nil。
在使⽤ HGETALL 时,如果哈希元素个数⽐较多,会存在阻塞 Redis 的可能。如果开发⼈员只
需要获取部分 field,可以使⽤ HMGET,如果⼀定要获取全部 field,可以尝试使⽤ HSCAN
命令,该命令采⽤渐进式遍历哈希类型,
8.HLEN
获取 hash 中的所有字段的个数。
语法:
HLEN key
时间复杂度:O(1)
返回值:字段个数。
9.HSETNX
在字段不存在的情况下,设置 hash 中的字段和值。
语法:
HSETNX key field value
时间复杂度:O(1)
返回值:1 表⽰设置成功,0 表⽰失败。
10.HINCRBY
将 hash 中字段对应的数值添加指定的值。
语法:
HINCRBY key field increment
时间复杂度:O(1)
返回值:该字段变化之后的值
HINCRBYFLOAT
HINCRBY 的浮点数版本。
语法:
HINCRBYFLOAT key field increment
时间复杂度:O(1)
返回值:该字段变化之后的值。
命令⼩结
表 2-4 是哈希类型命令的效果、时间复杂度,开发⼈员可以参考此表,结合⾃⾝业务需求和数据
⼤⼩选择合适的命令。
内部编码
哈希的内部编码有两种:
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:

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

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

使⽤场景
图 2-16 为关系型数据表记录的两条⽤⼾信息,⽤⼾的属性表现为表的列,每条⽤⼾信息表现为
⾏。
。如果映射关系表⽰这两个⽤⼾信息,则如图 2-17 所⽰。
相⽐于使⽤ JSON 格式的字符串缓存⽤⼾信息,哈希类型变得更加直观,并且在更新操作上变得
更灵活。可以将每个⽤⼾的 id 定义为键后缀,多对 field-value 对应⽤⼾的各个属性
注意
哈希类型和关系型数据库有两点不同之处:
哈希类型是稀疏的,⽽关系型数据库是完全结构化的,例如哈希类型每个键可以有不同的 field,⽽
关系型数据库⼀旦添加新的列,所有⾏都要为其设置值,即使为 null,如图 2-18 所⽰。
关系数据库可以做复杂的关系查询,⽽ Redis 去模拟关系型复杂查询,例如联表查询、聚合查询等
基本不可能,维护成本⾼。
缓存⽅式对⽐
截⾄⽬前为⽌,我们已经能够⽤三种⽅法缓存⽤⼾信息,下⾯给出三种⽅案的实现⽅法和优缺点
分析。
1. 原⽣字符串类型
使⽤字符串类型,每个属性⼀个键。
set user:1:name James
set user:1:age 23
set user:1:city Beijing
优点:实现简单,针对个别属性变更也很灵活。
缺点:占⽤过多的键,内存占⽤量较⼤,同时⽤⼾信息在 Redis 中⽐较分散,缺少内聚性,所以这种⽅案基本没有实⽤性。
2. 序列化字符串类型,例如 JSON 格式

set user:1 经过序列化后的⽤⼾对象字符串
优点:针对总是以整体作为操作的信息⽐较合适,编程也简单。同时,如果序列化⽅案选择合适,内存的使⽤效率很⾼。
缺点:本⾝序列化和反序列需要⼀定开销,同时如果总是操作个别属性则⾮常不灵活。
3. 哈希类型         
hmset user:1 name James age 23 city Beijing
优点:简单、直观、灵活。尤其是针对信息的局部变更或者获取操作。
缺点:需要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,可能会造成内存的较⼤消耗。
关于内聚和耦合

高内聚:有关联的代码紧密联系在一起

低耦合:代码的各个模块之间影响不大

3.List列表
列表类型是⽤来存储多个有序的字符串,如图 2-19 所⽰,a、b、c、d、e 五个元素从左到右组成
了⼀个有序的列表,列表中的每个字符串称为元素(element),⼀个列表最多可以存储 个元
素。在 Redis 中,可以对列表两端插⼊(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等(如图 2-19 和图 2-20 所⽰)。列表是⼀种⽐较灵活的数据结构,它可以充当栈和队列的⻆⾊,在实际开发上有很多应⽤场景
列表类型的特点:
第⼀:
列表中的元素是有序的,这意味着可以通过索引下标获取某个元素或者某个范围的元素列表,
例如要获取图 2-20 的第 5 个元素,可以执⾏ lindex user:1:messages 4 或者倒数第 1 个元素,lindex user:1:messages -1 就可以得到元素 e。
第⼆
区分获取和删除的区别,例如图 2-20 中的 lrem 1 b 是从列表中把从左数遇到的前 1 个 b 元素删
除,这个操作会导致列表的⻓度从 5 变成 4;但是执⾏ lindex 4 只会获取元素,但列表⻓度是不会变化的。
第三
列表中的元素是允许重复的,例如图 2-21 中的列表中是包含了两个 a 元素的。
常见命令
LPUSH(头插)
将⼀个或者多个元素从左侧放⼊(头插)到 list 中。
语法:
LPUSH key element [element ...]
时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.
返回值:插⼊后 list 的⻓度
LPUSHX
在 key 存在时,将⼀个或者多个元素从左侧放⼊(头插)到 list 中。不存在,直接返回0
语法:
1 LPUSHX key element [element ...]
时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.
返回值:插⼊后 list 的⻓度。
RPUSH(尾插)
将⼀个或者多个元素从右侧放⼊(尾插)到 list 中。
语法:
RPUSH key element [element ...]
时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.
返回值:插⼊后 list 的⻓度。
RPUSHX
在 key 存在时,将⼀个或者多个元素从右侧放⼊(尾插)到 list 中。
语法:
RPUSHX key element [element ...]
时间复杂度:只插⼊⼀个元素为 O(1), 插⼊多个元素为 O(N), N 为插⼊元素个数.
返回值:插⼊后 list 的⻓度。
LRANGE
获取从 start 到 end 区间的所有元素,左闭右闭。
语法:
LRANGE key start stop
时间复杂度:O(N)
返回值:指定区间的元素。
LPOP(头删)
从 list 左侧取出元素(即头删)。
语法:
LPOP key
时间复杂度:O(1)
返回值:取出的元素或者 nil。
RPOP(尾删)
从 list 右侧取出元素(即尾删)。
语法:
RPOP key
时间复杂度:O(1)
返回值:取出的元素或者 nil。
LINDEX
获取从左数第 index 位置的元素。
语法:
LINDEX key index
时间复杂度:O(N)
返回值:取出的元素或者 nil
LINSERT
在特定位置插⼊元素。
语法:
LINSERT key <BEFORE | AFTER> pivot element
时间复杂度:O(N)
返回值:插⼊后的 list ⻓度。
LLEN
获取 list ⻓度。
语法:
LLEN key
时间复杂度:O(1)
返回值:list 的⻓度。
LREM

指定元素精准删除

1.当count>0时,从头开始删除指定元素的次数

2.当count<0时,从尾开始删除指定元素的次数

3.当count=0时,删除全部指定的元素

LTRIM

只保留范围内的元素

LSET

根据下标修改元素

阻塞版本命令
blpop 和 brpop 是 lpop 和 rpop 的阻塞版本,和对应⾮阻塞版本的作⽤基本⼀致,除了:
在列表中有元素的情况下,阻塞和⾮阻塞表现是⼀致的。但如果列表中没有元素,⾮阻塞版本会理
解返回 nil,但阻塞版本会根据 timeout,阻塞⼀段时间,期间 Redis 可以执⾏其他命令,但要求执
⾏该命令的客⼾端会表现为阻塞状态(如图 2-22 所⽰)。
命令中如果设置了多个键,那么会从左向右进⾏遍历键,⼀旦有⼀个键对应的列表中可以弹出元
素,命令⽴即返回。
如果多个客⼾端同时多⼀个键执⾏ pop,则最先执⾏命令的客⼾端会得到弹出的元素。
BLPOP
LPOP 的阻塞版本。
语法:
BLPOP key [key ...] timeout
时间复杂度:O(1)
返回值:取出的元素或者 nil。
BRPOP
RPOP 的阻塞版本。
语法:
BRPOP key [key ...] timeout
时间复杂度:O(1)
返回值:取出的元素或者 nil。
 
内部编码
列表类型的内部编码有两种:
ziplist(压缩列表):
当列表的元素个数⼩于 list-max-ziplist-entries 配置(默认 512 个),同时
列表中每个元素的⻓度都⼩于 list-max-ziplist-value 配置(默认 64 字节)时,Redis 会选⽤
ziplist 来作为列表的内部编码实现来减少内存消耗。
linkedlist(链表):
当列表类型⽆法满⾜ ziplist 的条件时,Redis 会使⽤ linkedlist 作为列表的内部实现。
quicklist

每个节点都是一个压缩列表,以链表的形式连接起来

使⽤场景
作为数组

消息队列
如图 2-22 所⽰,Redis 可以使⽤ lpush + brpop 命令组合实现经典的阻塞式⽣产者-消费者模型队列,⽣产者客⼾端使⽤ lpush 从列表左侧插⼊元素,多个消费者客⼾端使⽤ brpop 命令阻塞式地从队列中"争抢" 队⾸元素。通过多个客⼾端来保证消费的负载均衡和⾼可⽤性。
分频道的消息队列
如图 2-23 所⽰,Redis 同样使⽤ lpush + brpop 命令,但通过不同的键模拟频道的概念,不同的消费者可以通过 brpop 不同的键值,实现订阅不同频道的理念。
微博 Timeline
每个⽤⼾都有属于⾃⼰的 Timeline(微博列表),现需要分⻚展⽰⽂章列表。此时可以考虑使⽤
列表,因为列表不但是有序的,同时⽀持按照索引范围获取元素。
1)每篇微博使⽤哈希结构存储,例如微博中 3 个属性:title、timestamp、content:
hmset mblog:1 title xx timestamp 1476536196 content xxxxx
...
hmset mblog:n title xx timestamp 1476536196 content xxxxx
2)向⽤⼾ Timeline 添加微博,user:<uid>:mblogs 作为微博的键:
lpush user:1:mblogs mblog:1 mblog:3
...
lpush user:k:mblogs mblog:9
3)分⻚获取⽤⼾的 Timeline,例如获取⽤⼾ 1 的前 10 篇微博:
keylist = lrange user:1:mblogs 0 9
for key in keylist {
hgetall key
}
此⽅案在实际中可能存在两个问题:
1. 1 + n 问题。即如果每次分⻚获取的微博个数较多,需要执⾏多次 hgetall 操作,此时可以考虑使⽤pipeline(流⽔线)模式批量提交命令,或者微博不采⽤哈希类型,⽽是使⽤序列化的字符串类型,使⽤ mget 获取。
2. 分裂获取⽂章时,lrange 在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。
4.set集合
集合类型也是保存多个字符串类型的元素的,但和列表类型不同的是,集合中 1)元素之间是⽆序
的 2)元素不允许重复,如图 2-24 所⽰。⼀个集合中最多可以存储 个元素。Redis 除了⽀持
集合内的增删查改操作,同时还⽀持多个集合取交集、并集、差集,合理地使⽤好集合类型,能在实际开发中解决很多问题。
set这个术语有不同的含义,如果是集合的话,就是把有关联的数据联系在一起,集合中的元素是无序的,集合中的元素是唯一的,集合中的每个元素都是string类型的
基本命令
SADD
将⼀个或者多个元素添加到 set 中。注意,重复的元素⽆法添加到 set 中。
语法:
SADD key member [member ...]
时间复杂度:O(1)
返回值:本次添加成功的元素个数
SMEMBERS
获取⼀个 set 中的所有元素,注意,元素间的顺序是⽆序的。
语法:
SMEMBERS key
时间复杂度:O(N)
返回值:所有元素的列表。
SISMEMBER
判断⼀个元素在不在 set 中。
语法:
SISMEMBER key member
时间复杂度:O(1)
返回值:1 表⽰元素在 set 中。0 表⽰元素不在 set 中或者 key 不存在。
SCARD
获取⼀个 set 的基数(cardinality),即 set 中的元素个数。
语法:
SCARD key
时间复杂度:O(1)
返回值:set 内的元素个数。
SPOP
从 set 中删除并返回⼀个或者多个元素。注意,由于 set 内的元素是⽆序的,所以取出哪个元素实际是未定义⾏为,即可以看作随机的。
语法:
SPOP key [count]
时间复杂度:O(N), n 是 count
返回值:取出的元素。
srandmember
从set中随机获取一个元素,但是不会移除该元素
SMOVE
将⼀个元素从源 set 取出并放⼊⽬标 set 中。
语法:
SMOVE source destination member
时间复杂度:O(1)
返回值:1 表⽰移动成功,0 表⽰失败
SREM
将指定的元素从 set 中删除。
语法:
SREM key member [member ...]
时间复杂度:O(N), N 是要删除的元素个数.
返回值:本次操作删除的元素个数。
集合间操作
交集(inter)、并集(union)、差集(diff)的概念如图 2-25 所⽰。
SINTER
获取给定 set 的交集中的元素。
语法:
SINTER key [key ...]
时间复杂度:O(N * M), N 是最⼩的集合元素个数. M 是最⼤的集合元素个数.
返回值:交集的元素。
SINTERSTORE
获取给定 set 的交集中的元素并保存到⽬标 set 中。
语法:
SINTERSTORE destination key [key ...]
时间复杂度:O(N * M), N 是最⼩的集合元素个数. M 是最⼤的集合元素个数.
返回值:交集的元素个数。
SUNION
获取给定 set 的并集中的元素。
语法:
SUNION key [key ...]
时间复杂度:O(N), N 给定的所有集合的总的元素个数.
返回值:并集的元素。
SUNIONSTORE
获取给定 set 的并集中的元素并保存到⽬标 set 中。
语法:
1 SUNIONSTORE destination key [key ...]
时间复杂度:O(N), N 给定的所有集合的总的元素个数.
返回值:并集的元素个数。
SDIFF
获取给定 set 的差集中的元素。
语法:
SDIFF key [key ...]
时间复杂度:O(N), N 给定的所有集合的总的元素个数.
返回值:差集的元素。
SDIFFSTORE
获取给定 set 的差集中的元素并保存到⽬标 set 中。
语法:
SDIFFSTORE destination key [key ...]
时间复杂度:O(N), N 给定的所有集合的总的元素个数.
返回值:差集的元素个数
命令小结

内部编码
集合类型的内部编码有两种:
intset(整数集合):
当集合中的元素都是整数并且元素的个数⼩于 set-max-intset-entries 配置
(默认 512 个)时,Redis 会选⽤ intset 来作为集合的内部实现,从⽽减少内存的使⽤。
hashtable(哈希表):
当集合类型⽆法满⾜ intset 的条件时,Redis 会使⽤ hashtable 作为集合
的内部实现。
使⽤场景
集合类型⽐较典型的使⽤场景是标签(tag)。例如 A ⽤⼾对娱乐、体育板块⽐较感兴趣,B ⽤⼾
对历史、新闻⽐较感兴趣,这些兴趣点可以被抽象为标签。有了这些数据就可以得到喜欢同⼀个标签的⼈,以及⽤⼾的共同喜好的标签,这些数据对于增强⽤⼾体验和⽤⼾黏度都⾮常有帮助。 例如⼀个电⼦商务⽹站会对不同标签的⽤⼾做不同的产品推荐。
1.使用set来保存用户的标签
2.使用set来计算共同好友,基于集合求交集
3.使用set来计算uv
Zset 有序集合
有序集合相对于字符串、列表、哈希、集合来说会有⼀些陌⽣。它保留了集合不能有重复成员的
特点,但与集合不同的是,有序集合中的每个元素都有⼀个唯⼀的浮点类型的分数(score)与之关联,着使得有序集合中的元素是可以维护有序性的,但这个有序不是⽤下标作为排序依据⽽是⽤这个分数。如图 2-26 所⽰,该有序集合显⽰了三国中的武将的武⼒
有序集合提供了获取指定分数和元素范围查找、计算成员排名等功能,合理地利⽤有序集合,可
以帮助我们在实际开发中解决很多问题。
常见命令
zadd
添加或者更新指定的元素以及关联的分数到 zset 中,分数应该符合 double 类型,+inf/-inf 作为正负
极限也是合法的。
ZADD 的相关选项:
XX:仅仅⽤于更新已经存在的元素,不会添加新元素。
NX:仅⽤于添加新元素,不会更新已经存在的元素。
CH:默认情况下,ZADD 返回的是本次添加的元素个数,但指定这个选项之后,就会还包含本次更新的元素的个数。
INCR:此时命令类似 ZINCRBY 的效果,将元素的分数加上指定的分数。此时只能指定⼀个元素和分数。
ZCARD
获取⼀个 zset 的基数(cardinality),即 zset 中的元素个数。
ZCOUNT
返回分数在 min 和 max 之间的元素个数,默认情况下,min 和 max 都是包含的,可以通过 (,表示开区间
ZRANGE
返回指定区间⾥的元素,分数按照升序。带上 WITHSCORES 可以把分数也返回。
ZREVRANGE
返回指定区间⾥的元素,分数按照降序。带上 WITHSCORES 可以把分数也返回。
备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。
ZRANGEBYSCORE
返回分数在 min 和 max 之间的元素,默认情况下,min 和 max 都是包含的,可以通过 ( 排除。
备注:这个命令可能在 6.2.0 之后废弃,并且功能合并到 ZRANGE 中。
ZPOPMAX
删除并返回分数最⾼的 count 个元素。
BZPOPMAX
ZPOPMAX 的阻塞版本。
ZPOPMIN
删除并返回分数最低的 count 个元素。
BZPOPMIN
ZPOPMIN 的阻塞版本。
ZRANK
返回指定元素的排名,升序。
ZREVRANK
返回指定元素的排名,降序。
ZSCORE
返回指定元素的分数。
ZREM
删除指定的元素。
ZREMRANGEBYRANK
按照排序,升序删除指定范围的元素,左闭右闭。
ZREMRANGEBYSCORE
按照分数删除指定范围的元素,左闭右闭。
ZINCRBY
为指定的元素的关联分数添加指定的分数值
集合间操作

ZINTERSTORE(交集)

求出给定有序集合中元素的交集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数。
时间复杂度:O(N*K)+O(M*log(M)) N 是输⼊的有序集合中, 最⼩的有序集合的元素个数; K 是输⼊了⼏个有序集合; M 是最终结果的有序集合的元素个数.
ZUNIONSTORE并集

求出给定有序集合中元素的并集并保存进⽬标有序集合中,在合并过程中以元素为单位进⾏合并,元素对应的分数按照不同的聚合⽅式和权重得到新的分数。
语法:
ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight
[weight ...]] [AGGREGATE <SUM | MIN | MAX>]
时间复杂度:O(N)+O(M*log(M)) N 是输⼊的有序集合总的元素个数; M 是最终结果的有序集合的元素个数.
返回值:⽬标集合中的元素个数

命令⼩结

内部编码
有序集合类型的内部编码有两种:
ziplist(压缩列表):
当有序集合的元素个数⼩于 zset-max-ziplist-entries 配置(默认 128 个), 同时每个元素的值都⼩于 zset-max-ziplist-value 配置(默认 64 字节)时,Redis 会⽤ ziplist 来作
为有序集合的内部实现,ziplist 可以有效减少内存的使⽤。
skiplist(跳表):
当 ziplist 条件不满⾜时,有序集合会使⽤ skiplist 作为内部实现,因为此时ziplist 的操作效率会下降
1)当元素个数较少且每个元素较⼩时,内部编码为 ziplist:
2)当元素个数超过 128 个,内部编码 skiplist:
3)当某个元素⼤于 64 字节时,内部编码 skiplist:
使⽤场景

有序集合⽐较典型的使⽤场景就是排⾏榜系统。例如常⻅的⽹站上的热榜信息,榜单的维度可能
是多⽅⾯的:按照时间、按照阅读量、按照点赞量。本例中我们使⽤点赞数这个维度,维护每天的热榜:

相关文章:

Redis 的使⽤和原理

第一章:初识 Redis 1.1盛赞 Redis Redis 是⼀种基于键值对&#xff08;key-value&#xff09;的 NoSQL 数据库&#xff0c;与很多键值对数据库不同的是&#xff0c;Redis 中的值可以是由 string&#xff08;字符串&#xff09;、hash&#xff08;哈希&#xff09;、list&…...

前端学Java

一&#xff1a;语法 1、注解 注解&#xff08;Annotation&#xff09;是Java中的一种特殊类型的语法&#xff0c;它可以被用来为代码提供元数据。元数据是关于数据的数据&#xff0c;注解可以用于类、方法、变量等的描述与标记。 理解注解可以从以下几个方面入手&#xff1a…...

VR游戏:多人社交将是VR的下一个风口

第一部分&#xff1a;创业笔记 1. 市场趋势 从单机游戏转向多人互动体验&#xff1a;随着技术的进步&#xff0c;VR游戏正从单机模式向多人互动体验转变。代表作品如Rec Room、Phasmophobia、Among Us和Breachers等&#xff0c;这些游戏的成功证明了多人互动模式的巨大潜力。…...

Docker与虚拟机(VM)的不同

Docker与虚拟机&#xff08;VM&#xff09;在实现的原理上存在显著的不同&#xff0c;主要体现在以下几个方面&#xff1a; 一、基础原理 Docker 利用Linux内核的特性&#xff0c;如容器&#xff08;containers&#xff09;、命名空间&#xff08;namespaces&#xff09;和控制…...

Pr 视频效果:透视

效果面板/视频效果/透视 Video Effects/Perspective Adobe Premiere Pro 的视频效果中&#xff0c;透视 Perspective效果组主要用于在二维平面的视频剪辑中模拟三维空间的透视效果。 通过调整这些效果&#xff0c;可以改变图像的视角、添加阴影、创造立体感&#xff0c;增强画面…...

C 语言标准库 - <limit.h>

简介 <limits.h> 是 C 标准库中的一个头文件&#xff0c;定义了各种数据类型的限制。这些宏提供了有关整数类型&#xff08;char、short、int、long 和 long long 等&#xff09;和其他数据类型的最大值和最小值的信息。 这些限制指定了变量不能存储任何超出这些限制的…...

Python | Leetcode Python题解之第519题随机翻转矩阵

题目&#xff1a; 题解&#xff1a; class Solution:def __init__(self, m: int, n: int):self.m mself.n nself.total m * nself.map {}def flip(self) -> List[int]:x random.randint(0, self.total - 1)self.total - 1# 查找位置 x 对应的映射idx self.map.get(x,…...

大数据新视界 -- 大数据大厂之提升 Impala 查询效率:索引优化的秘籍大揭秘(上)(3/30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

【AI工作流】FastGPT - 深入解析FastGPT工作流编排:从基础到高级应用的全面指南

文章目录 一、工作流编排概述二、FastGPT的节点类型1. 基础功能插件(1) 文本输出(2) 功能调用(3) 工具(4) 外部调用(5) 其他 2. 系统插件3. 团队插件 三、工作流中的流向结语 在当今快速发展的人工智能领域&#xff0c;工作流编排的能力已成为提升用户体验和应用效率的关键因素…...

VS+Qt解决提升控件后,包含头文件格式不对问题处理

一、前言 VSQt 提升控件后&#xff0c;在uic目录下会生成ui相关的初始化文件&#xff0c;对于提升的控件头文件包含的格式为#include<> 而非 #include “ ” 导致无法找到头文件。如果手动修改为 #include “ ”相当麻烦&#xff0c;甚至每次编译都要修改一遍&#xff0c…...

opencv - py_imgproc - py_filtering filtering 过滤-卷积平滑

文章目录 平滑图像目标2D 卷积&#xff08;图像过滤&#xff09;图像模糊&#xff08;图像平滑&#xff09;1. 平均2. 高斯模糊3. 中值模糊4. 双边滤波 其他资源 平滑图像 目标 学习&#xff1a; 使用各种低通滤波器模糊图像将定制滤波器应用于图像&#xff08;2D 卷积&…...

精华帖分享|缠论系列 -笔

本文来源于量化小论坛策略分享会板块精华帖&#xff0c;作者为吴奕萱&#xff0c;发布于2023年6月4日。 以下为精华帖正文&#xff1a; 01 笔 昨天讲了3根K线组合关系的完全分类&#xff0c;按照逻辑&#xff0c;其实我们会考虑是不是应该讲4根、5根K线的组合关系了。 精华帖…...

Java项目实战II基于Spring Boot的文理医院预约挂号系统的设计与实现(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 在医疗资源日益紧张的背景下&#xff0…...

NumPy Ndarray学习

1.NumPy Ndarray 对象简介 NumPy 最重要的特点是其 N 维数组对象 ndarray&#xff0c;它是一系列同类型数据的集合&#xff0c;以 0 下标为开始进行集合中元素的索引。ndarray 对象是用于存放同类型元素的多维数组。ndarray 中的每个元素在内存中都有相同存储大小的区域。 2.N…...

Browserslist 配置

Browserslist 是一个工具和规范&#xff0c;用于定义和共享支持的浏览器列表&#xff0c;以便在前端开发中管理不同工具的兼容性。这些工具可以包括 Babel、Autoprefixer、ESLint 等&#xff0c;它们都可以使用 Browserslist 提供的配置来确定应支持哪些浏览器及其版本。 主要…...

vue2中的v-bind相当于原生js的什么

在 Vue 2 中&#xff0c;v-bind 是一个指令&#xff0c;用于动态地将一个或多个属性绑定到 DOM 元素上。它相当于在原生 JavaScript 中直接操作 DOM 元素属性的方法。 v-bind 的基本用法 在 Vue 中&#xff0c;v-bind 可以这样使用&#xff1a; <!-- 绑定一个属性 -->…...

c语言-scanf函数的用法

文章目录 一、scanf是什么&#xff1f;二、通过scanf进行赋值scanf 输入一段带空格的句子&#xff0c; %[^\n] 格式字符串。 三、赋值忽略符 一、scanf是什么&#xff1f; 函数原型&#xff1a;int scanf ( const char * format, … ); scanf是一个格式输出库函数&#xff0c;…...

AI带货主播插件开发之商品推荐模块!

AI带货主播&#xff0c;作为新兴的人工智能技术应用领域&#xff0c;正逐渐改变着电商直播的格局&#xff0c;在这一领域&#xff0c;商品推荐模块是提升用户体验、增加销售额的关键一环。 本文将探讨AI带货主播插件的商品推荐模块开发&#xff0c;并分享五段关键的源代码&…...

使用Nginx作为反向代理和负载均衡器

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Nginx作为反向代理和负载均衡器 引言 Nginx 简介 安装 Nginx Ubuntu CentOS 配置 Nginx 作为反向代理 配置 Nginx 作为负载…...

【数据结构二叉树】C非递归算法实现二叉树的先序、中序、后序遍历

引言: 遍历二叉树&#xff1a;指按某条搜索路径巡访二叉树中每个结点&#xff0c;使得每个结点均被访问一次&#xff0c;而且仅被访问一次。 除了层次遍历外&#xff0c;二叉树有三个重要的遍历方法&#xff1a;先序遍历、中序遍历、后序遍历。 1、递归算法实现先序、中序、后…...

终极指南:Seal中Kotlin协程上下文组合的实用技巧

终极指南&#xff1a;Seal中Kotlin协程上下文组合的实用技巧 【免费下载链接】Seal &#x1f9ad; Video/Audio Downloader for Android, based on yt-dlp 项目地址: https://gitcode.com/gh_mirrors/se/Seal Seal是一款基于yt-dlp的Android音视频下载器&#xff0c;在其…...

Jetson AGX Orin到手后,第一件事不是装CUDA,而是先搞定这个源(附nvidia-l4t-apt-source.list配置)

Jetson AGX Orin开发板开箱必做&#xff1a;正确配置软件源的深度指南 当你第一次拿到Jetson AGX Orin这款强大的边缘计算设备时&#xff0c;兴奋之余可能会迫不及待地想要安装CUDA、cuDNN等AI开发环境。但很多开发者都会在这里踩到一个"坑"——直接运行sudo apt ins…...

PCIe 6.0 Flit Mode 实战解析:从TLP到Flit,你的数据包到底经历了什么?

PCIe 6.0 Flit Mode 深度解析&#xff1a;数据包的奇幻漂流之旅 当一颗来自CPU的事务请求被封装成TLP&#xff08;Transaction Layer Packet&#xff09;时&#xff0c;它即将开始一段穿越PCIe 6.0协议栈的奇妙旅程。这段旅程不再是传统PCIe版本中的"自由行"&#xf…...

终极iOS弹窗解决方案SDCAlertView:10个强大功能超越系统UIAlertController

终极iOS弹窗解决方案SDCAlertView&#xff1a;10个强大功能超越系统UIAlertController 【免费下载链接】SDCAlertView The little alert that could 项目地址: https://gitcode.com/gh_mirrors/sd/SDCAlertView SDCAlertView是一款强大的iOS弹窗解决方案&#xff0c;它为…...

3分钟为Windows 11 LTSC恢复微软商店的完整指南:解决精简版系统应用生态缺失问题

3分钟为Windows 11 LTSC恢复微软商店的完整指南&#xff1a;解决精简版系统应用生态缺失问题 【免费下载链接】LTSC-Add-MicrosoftStore Add Windows Store to Windows 11 24H2 LTSC 项目地址: https://gitcode.com/gh_mirrors/ltscad/LTSC-Add-MicrosoftStore Windows …...

告别论文焦虑:Paperxie 为本科毕业论文搭建的「全流程写作脚手架」

paperxie-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/AI PPThttps://www.paperxie.cn/ai/dissertationhttps://www.paperxie.cn/ai/dissertation 毕业季的凌晨三点&#xff0c;宿舍台灯下亮着的电脑屏幕&#xff0c;是无数本科生共同的记忆。当 10000 字的毕业…...

ubuntu linux虚拟机安装部署hermes详细教程(安装、问题处理)

文章目录 前言 一、Hermes 介绍 1. 什么是 Hermes Agent? 2. 核心特性 3. 为什么选择 Hermes Agent? 4. 适用场景 二、安装Hermes 1.安装 2.配置 3.开始对话 4.接入多平台(可选) 5.保持更新 三、Hermes接入微信 四、常见错误解决 1.Failed to connect to github.com port 4…...

开源简历解析工具Open-Resume:从数据模型到自动化生成全解析

1. 项目概述&#xff1a;一个开源的简历解析与构建工具最近在帮团队筛选简历和整理自己的履历时&#xff0c;我再次被简历格式不统一、信息提取困难的问题所困扰。无论是HR手动从PDF里复制粘贴&#xff0c;还是求职者为了适配不同岗位反复调整简历模板&#xff0c;这个过程都充…...

Java程序员什么时候要深入学习JVM底层原理?

当你工作多年之后&#xff0c;你遇到的项目会越来越复杂&#xff0c;遇到的问题也会越来越复杂&#xff1a;各种古怪的内存溢出&#xff0c;死锁&#xff0c;应用崩溃……这些都会迫使你不得不去深入学习JVM底层原理那么应该如何学JVM只靠周大神的JVM圣经吗&#xff1f;当然不够…...

避坑指南:用MOT17训练YOLOv7检测器时,为什么你的mAP上不去?可能是数据划分的锅

MOT17数据集划分陷阱&#xff1a;为什么你的YOLOv7检测器性能不达标&#xff1f; 当你在MOT17数据集上训练YOLOv7检测器时&#xff0c;是否遇到过这样的困境&#xff1a;损失曲线看起来完美&#xff0c;训练集准确率节节攀升&#xff0c;但验证集mAP却始终徘徊在低水平&#xf…...