Redis
REmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库NoSQL。使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API
概念
NoSQL
- 分类
- 键值对(Key-Value)存储数据库
- Redis
- 列存储数据库
- 关系型数据库是典型的行存储数据库,在物理层面占用连续存储空间,不适合海量数据存储
- 而按例存储可实现分布式存储,适合海量数据。如 HBase用于大数据
- 文档型数据库
- 如MongoDB,把记录变成json格式的文档,最像关系型数据库的NoSQL;
- 图形(Graph)数据库
- 用于存放节点关系的数据库,如描述不同人之间的关系。典型 Neo4J
- 键值对(Key-Value)存储数据库
其他概念
- 常见多路复用器的多路选择算法
- select 模型
- poll 模型:采用轮询算法,对就绪处理可能存在延迟,等待下次轮询到达
- epoll 模型:采用回调方式,到达即就绪,就绪队列。根据就绪事件发生后的处理方式不同,又可分为LT模型、ET模型
介绍Redis
- 用途
- 将应用程序、数据缓存层、数据持久层分隔开,Redis做数据缓存
- 客户端从DBMS中查询的数据放入Redis中,后续无论客户端N都直接读取Redis即可,减小RT,降低DBMS压力
- 同步性划分
- 实时同步缓存:DBMS中数据更新后,redis缓存中相关数据被立即清除,保证下次从DBMS更新最新数据并存入Redis
- 阶段性同步缓存:允许段时间内数据不完全一致,通过设置缓存数据过期时间实现
- warmup:部署预热时,提前把数据获取到Redis中
- 将应用程序、数据缓存层、数据持久层分隔开,Redis做数据缓存
- 特性
- 性能极高:Redis 读速度可达11万次/秒,写速度可达8万次/秒
- 使用C语言开发、且所有操作在内存中执行
- 核心源码很少,精细稳定(性能与优雅并存)
- 持久化支持:ROB 或 AOF 方式
- 高可用集群:提供高可用主从集群功能,确保系统安全性
- 丰富的数据类型
- 功能丰富:发布/订阅、简单事务、数据过期、Lua脚本扩展等
- 客户端语言广泛:提供TCP通信协议,放便各种语言接入
- 支持ACL权限控制:Redis6+支持,可为不同用户定制不同的用户权限
- ACL Access Control List 访问控制列表,一种细粒度的权限控制策略,可针对任意用户与组进行权限控制
- UGO权限控制策略,User用户-Group组-Other其他,默认linux文件的权限,粗粒度权限管理策略
- 支持多线程IO模型
- 早期为单线程模型,Redis6.0+支持多线程模型
- 性能极高:Redis 读速度可达11万次/秒,写速度可达8万次/秒
- IO模型:Redis处理客户端请求所采用的处理架构,不同版本IO模型不同
- 单线程模型:Redis3.0及以前版本,采用纯粹的单线程模型,所有客户端请求全由一个线程处理
- 采用多路复用技术,每个客户端都需与Redis建立socket连接,并向事务分发器注册一个事件,事件发生表示连接就绪,后获取客户端发送的请求,交由事件分发器绑定的线程处理,线程忙碌中,则写入任务队列等待线程处理。
- 事件分发器会根据不同的就绪事件,交由不同的事件处理器处理
- 混合线程模型:Redis4.0版本开始
- 处理客户端请求时仍是单线程模型,对于部分耗时单不影响客户端响应的操作,由后台其他线程处理。如持久化、AOD的rewrite、失效连接的清理等
- 多线程模型:Redis6.0版本,对客户端请求解析采用多线程模型
- 核心命令的执行依然是单线程。所有命令的解析与执行逻辑都在主线程中串行完成,从而避免了多线程并发修改数据结构带来的竞态条件,保证了命令执行的原子性和数据一致性
- “多线程” 仅用于接受、解析客户端的请求,然后讲解析出的请求写入到任务队列。具体任务(命令)处理仍由主线程处理,从而无需考虑线程安全、事务控制、LPUSH/LPOP等命令的执行顺序问题
- 单线程模型:Redis3.0及以前版本,采用纯粹的单线程模型,所有客户端请求全由一个线程处理


基础应用
安装与配置
- 安装gcc 或 gcc-c++
- C/C++编译器,对Redis进行编译安装,下载安装包可能未编译的源码
- GCC:GNU Compiler Collection,GUN编译器集合
- 下载redis
- 客户端
方式一:使用包管理器安装(最快捷) Ubuntu/Debian 系统
# 1. 更新软件源并安装 Redis
sudo apt update
sudo apt install redis-server
# 2. 启动 Redis 并设置开机自启
sudo systemctl start redis-server
sudo systemctl enable redis-server
方式二:【自行百度/AI】源码编译安装(推荐生产环境) Ubuntu/Debian 系统
# 1. 准备编译环境 Redis 是基于 C 语言开发的,需要先安装 gcc 等编译依赖:
# 2. 下载并解压源码(以 7.2.4 为例)
# 3.解压并进入目录
# 4. 编译与安装
# 安装到指定目录(例如 /usr/local/redis)
# 创建配置和数据目录
# 修改配置文件,使其支持后台运行
# 使用配置文件启动 Redis
# 另开终端,使用客户端连接并测试# 默认端口 6379
# 启动与终止
redis-server # 方式一:前台直接窗口启动redis
ps aux | grep redis # 列出系统中所有包含 "redis" 关键字的进程信息(linux命令)
nohup redis-server & # 方式二:命令式后台启动 redis,返回id,会在目录中产生 nohup.out
# 方式三:配置式后台启动【推荐】
## 修改启动文件 redis.conf
daemonize yes # no-不开启守护进程方式启动 yes-守护进程方式启动
redis-server /配置文件路径 ## 以指定配置文件的形式启动
redis-cli shutdown # 停止redisredis.conf
核心配置文件redis.conf,在安装根目录下
daemonize yes # no-不开启守护进程方式启动 yes-守护进程方式启动
# 绑定客户端IP,若需要所有客户端都能访问,需要注释掉当前行
bing 127.0.0.1 -::1 # 绑定客户端IP,ipv4的127.0.0.1 ipv6的-::1
# 保护模式,默认模式开启
protected-mode yes # no-关闭保护模式 yes-保护模式开启,限制为仅自己可访问
# 端口号配置,默认6379
port 6379
# 密码,默认没密码直接使用
requirepass 123456 # 设置密码后,进入交互后,需先执行 `auth 密码` 后正常执行命令
# 重命名对应命令 `rename-command 命令 "新名称"`
## 重命名为 "" 表示删除,对应不再能被使用 `rename-command 命令 ""`
rename-command flushall "" # flushall 删除所有现有数据库中的所有键(Keys)
rename-command flushdb "myflushdb" # flushdb 删除当前选中数据库中的所有键(Keys)
timeout 0 # 连接空闲的超时时间,单位s 0-永不超时 通用推荐值300秒 高并发推荐60~120秒
tcp-keepalive 300 # 按设定间隔发送探测包,主动检测客户端是否断开的时间,会检测两次确认后关闭连接
# tcp-backlog TCP连接队列,解决高并发下客户端慢连接问题
## backlog消息队列长度在linux中由内核参数somaxconn决定,Redis中配置后,取两者最小值
tcp-backlog 511 # 设置连接队列的长度,高并发场景下值大些,可优化性能!!!
# 指定Redis运行时pid写入的文件
## 未配置时,守护进程启动pid为`/var/run/redis.pid ` 前台启动时不会生成pid
pidfile /var/run/redis.pid
loglevel notice # 日志文件类型 dubug、verbose、notice、warning
logfile "" # 日志文件路径,""在前台进程时输出到显示器,守护进程时不记录
databases 16 # 设置数据库数量,默认从0开始,到'databases-1'
# 设置redis的最大并发量,默认为10000,达到最大连接-32后会拒绝新的连接,并返回异常信息
## 超过linux支持可打开的文件描述符最大阈值时无效,性能影响!!!
## 32个用于内部的连接通讯
## 使用集群时,每个节点会占用2个client
maxclients 10000
# 限制内存使用最大值,达到限制时,将根据逐出策略 maxmemory-policy 删除符合的key
## 无符合条件可删除key时,对写操作返回error
## 建议留足够的内存给使用的副本使用
maxmemory <bytes>
# 逐出策略,前提是设置maxmemory
## volatile-lru 从已设置过期时间的键中,使用 LRU-最近最少使用 算法淘汰数据,适用缓存场景优先保留未设过期时间的“重要”键。
## allkeys-lru 从所有键中使用 LRU算法淘汰数据。适用于纯缓存场景,不区分是否设置过期时间
## volatile-lfu 从已设置过期时间的键中,使用 LFU-最不经常使用 算法淘汰数据,适用访问频率差异大的场景
## allkeys-lfu 从所有键中使用 LFU 算法淘汰数据。比 LRU 更能反映长期访问热度
## volatile-random 从已设置过期时间的键中随机淘汰,简单粗暴一致性要求不高场景可用
## allkeys-random 从所有键中随机淘汰。性能开销最小,但可能误删热点数据
## volatile-ttl 从已设置过期时间的键中,优先淘汰 TTL-剩余生存时间 最短的键
## noeviction(默认值):不淘汰任何键,当内存不足时直接返回写错误
maxmemory-policy 策略名
# 随机抽取 maxmemory-samples 个键作为候选集,样本数越大,越接近真实 LRU,然后在样本内应用maxmemory-policy的淘汰策略
maxmemory-samples 5 # 指定样本数量,随机抽取 maxmemory-samples 个键作为候选集
maxmemory-eviction-tenacity 10 # 默认值10,移除容忍度,数值越小容忍度越低,更快的被删除,延迟低
############## THREADEN I/O #############
# 多线程默认关闭,建议CUP 4核以上时开启,并至少保留1个核
io-threads 4 # 指定启用多线程IO模型时,要使用的线程数量,数值超过8就没有多大意义了【建议仅存在性能问题时再使用】
io-threads-do-reads no # 默认 no-多线程只处理读命令,yes-支持处理写命令,可能无太大帮助
include /文件路径 # 在项目配置中引用其他来源的配置文件,写在配置文件 最后以保证生效Redis命令
Redis存储的整体是一个Map,key为String类型
基本命令
- 字符串String、链表List、集合Set、有序集合Zset、哈希类型Hash、key不存在none等
- BitMap:一般用于大数据量的二值性统计
- HyperLogLog:实现对数据量超级庞大的日志做去重统计
- Geospatial:用于地理位置相关的计算
redis-cli # 进入redis交互窗口
redis-cli -a 密码 # 在配置存在密码时,直接携带密码登录
redis-cli -h 192.168.192.102 -p 6379 -a 密码 # 连接远程redis,配置ip+端口号[ -a 密码]
cat /proc/version # 查看linux内核版本
cat /proc/sys/net/core/somaxconn # 查看somaxconn的值,与tcp-backlog配置有关联
# 终端交互
ping # 会看到pong响应,表明客户端与redis连接正常,心跳命令
set name TOM # 写命令,设置name
get name # 读命令,获取name
select <dbid> # 选择使用第几个数据库,默认为第0个
desize # 查看当前数据库中有几条数据
flushdb # 删除当前库的所有数据
flushall # 删除所有库的所有数据
shutdown # 停止redis
quit 或 exit # 退出交互窗口
# 查看所有符合正则条件的key,为避免数据量大阻塞服务,常用scan命令代替
keys * # 查看所有key
keys *o* # 查看包含o的key
keys h?ll* # 查看h?ll的数据,?为一个随机字符
exists key的值 # 检查对应key是否存在 1-存在 0-不存在
del key [key...]# 删除给定的1个或多个key,不存在时忽略
remane 原始key 新的key # 把key重命名
move key 数据库id # 把当前数据库中的key移动到指定数据库
type key # 返回对应key存储值的五种Redis数据类型
object encoding key # 返回key对应value在内存中实际的类型
# 设置key的生存时间,1-设置成功范围,0-key不存在
expire key seconds # 设置key的生存时间,达到后自动删除,单位秒
pexpire key seconds # 设置key的生存时间,达到后自动删除,单位毫秒
# 获取给定key的剩余生存时间 -2:key不存在/已被删除 -1:key存在但未设置生存时间 其他-剩余的生存时间
ttl key # 返回剩余生存时间,单位秒
pttl key # 返回剩余生存时间,单位毫秒
persist key # 去除key的剩余生存时间设置,从“易失的”改为“持久的” 1-设置成功 0-key不存在/未设置生存时间
randomkey # 从当前数据库中随机返回一个key,数据库为空时返回nil(用户数据库是否为空时检查)
# 基于游标的渐进式迭代器,用于遍历数据库中的键。解决 KEYS命令在大数据量下会长时间阻塞 Redis 主线程而设计
## cursor (游标):迭代起始位置。首次调用时传入 0,后续调用则使用上一次返回的新游标;返回的游标 0 时,表示迭代结束
## MATCH pattern:可选,用于模式匹配。支持 glob 风格的通配符,例如 user* 可以过滤出所有以 user 开头的键。
## COUNT count:可选,每次迭代返回的键数量,默认为 10。只是提示(hint),实际返回的数量可能多于或少于该值。
## TYPE type:可选(Redis 6.0+ 引入),用于指定键的值类型,默认所有类型,例如 string、hash、list 等。
scan cursor [MATCH pattern] [COUNT count] [TYPE type] ## 如 scan 0 count 3
hscan # 属于Hash型Value操作命令集合,用于遍历当前db中指定Hash表的所有 fieId-value 对
sscan # 属于ZSet型Value操作命令集合,用于遍历当前db中指定有序集合的所有元素(数值和元素值)String类型
- Value可以存放数据:数值型、二进制图片、音视频、序列化对象等
- 最大值是512M,字符中间存在空格时,使用双引号包裹,否则会认为多个参数报错
set hi "hello word"
使用场景
- 数据缓存,作为应用服务器和数据库-数据存储层之间的数据缓存
- 计数器,value为数值型的数据,用于统计播放量、访问量等;直接修改Redis的计数器,再以异步方式持久化存储到数据库
- 共享Session,分布式系统中用于保存用户登录信息,简洁高效的用户认证
- 限速器,防止Dos(Denial of Service拒绝服务)攻击,Redis结合key的过期时间与incr命令完成限速功能,充当限速器
Boolean isExists = redis.set(ip,1,"EX 60","NX") // 用于判定限速的伪命令 isExists!=null || redis.incr(ip)<=5- 可以防Dos拒绝服务攻击,但无法防止DDos分布式拒绝服务攻击
# 按类型划分操作命令
## String类型
### ex 设置过期时间 单位:秒
### px 设置过期时间 单位:毫秒
### nx 指定的key不存在时,才会设置成功;等价于SETNX
### xx 指定的key已经存在时,才会设置成功;仅用于修改,而不新增
set (key值) (Value值) ex (过期时间/s) # 如 set name 张三 ex 200
# psetex/setex属于原子性操作,两个操作会在同一时间完成,
setex key seconds value # 设置过期时间,单位s
psetex key seconds value # 设置过期时间,单位ms
setnx key value # 成功返回1,失败返回0;等同于`set key value nx seconds`
getset key value # 先获取再设置,返回旧的值;原key不存在时,返回nil
mset key value [key value...] # 同时设置多个key-value
msetnx key value [key value...] # 同时设置多个key-value,且要求所有的key都不存在,任意key已存在时,就返回0-全部失败
mget key1 key2 [key...] # 返回所有给定的key值,key不存在时对应位置为nil
append key value # 对已存在的String数据追加Value内容,追加在Value之后;key不存在时,key设置为value并插入
## 增/减方法;key不存在时,会先被初始化为0,再执行增减;key不能表示为数字时返回报错;执行成功返回执行结果
# `incr key ` incr自增/decr自减 方法
incr key # incr 自增方法,如key对应value为22,incr key 后为23
incr key number
decr key # decr 自减方法,如key对应value为22,decr key 后为21
# 增加number,number必须为整数,可以为负数则减少
incrby key [number] # incrby key 5 当key对应value为22,执行后value为27
# 减少number,number必须为整数,可以为负数则增加
decrby key [number] # decrby key 5 当key对应value为22,执行后value为17
# 增加浮点型数字-带小数点,number必须为整数,可以为负数则减少;不存在decrbyfloat
incrbyfloat key [floatNumber]
strlen key # 返回key中value的字符个数
getrange key start end # 返回字符串中start到end的字符串,支持使用负值-反向检索;end必须大雨start
setrange key start value2 # 将value2从key的value第start的位置开始替换;不存在的空位自动填充为零字节\0x00;不存在的key当作空字符串正常处理Hash类型
- Hash类型也称Hash表、字典、Map映射表等;由键值对组成,为区分这里的键称为
fieId,值为value - Hash表中的 fieId-value对均为String类型
- 应用场景
- 适合对象形式的数据存储,便于读写使用
- 也可以直接使用JSON序列化后String类型代替,但会显得这个人很笨
hset key fieId value # 设置key值为hash类型,数据为fieId-value对,1-创建成功,0-修改旧值
hmset key fieId value [fieId value...] # 【等效hset】key值为hash类型,同时设置多组fieId-value对
hget key fieId # 获取key中fieId对应的值
hmget key fieId [fieId...] # 【等效hget】获取key中多个fieId对应的值
hgetall key # 获取key中的所有数据fieId-value按顺序展示,每一个都是String类型
hkeys key # 获取key中所有的fieId,按顺序排列
hvals key # 获取key中所有的value,按顺序排列
hlen key # 获取key中共有几组fieId-value对
hsrtlen key fieId # 获取key中fieId对应Value的字符长度
hsetnx key fieId value [fieId value...] # 设置key中fieId的值为对应value,hsetnx仅在不存在是设置成功,fieId已存在则返回0-设置失败
hdel key fieId [fieId...] # 删除key中指定的fieId数据对
hexists key fieId # 判断key中是否存在对应fieId的数据,0-没有 1-已存在
hincrby key fieId number # 给key中对应fieId的数值增加number,number为负值时则减少
hincrbyfloat key fieId number # (浮点型-小数)给key中对应fieId的数值增加number,number为负值时则减少List类型
- String列表类型数据,数据按插入顺序进行排列,底层实际是无头节点的双向链表,所以对表头/表尾的操纵性能较高,中间插入或删除的操作性能相对较差
- 应用场景:基于数据结构实现
- 栈-先进后出 lpush + lpop
- 队列-先进先出 lpush + rpop
- 阻塞式消息队列 lpush +brpop
- 生产者使用 lpush插入,消费者使用brpop阻塞式抢占尾部数据消费,timeout设置为0表示没数据就永久阻塞
- 动态有限集合 lpush + ltrim 或 rpush + ltrim
lpush/rpush # 执行成功后返回列表的长度
lpush key value1 value2 [value3...] # 往表头依次插入 value1 value2 value3;存入后顺序为value3 value2 value1
rpush key value1 value2 [value3...] # 往表尾依次插入 value1 value2 value3;存入后顺序为value1 value2 value3
lpushx key newValue [newValue...] # 往表头依次插入newValue,lpushx要求key必须存在,0-失败key不存在 列表长度-成功
rpushx key newValue [newValue...] # 往表尾依次插入newValue,rpushx要求key必须存在,0-失败key不存在 列表长度-成功
llen key # 返回key列表的长度
lrange key start end # 返回key列表中start到end的元素,支持负数,从后往前算,如`lrange key 0 -1`返回所有数据
lindex key index # 返回key列表中index索引位置对应的值,index为number数字
lset key index newValue # 替换key列表中索引为index的值为newValue
linsert key before value newValue # 把newValue插入到key列表中指定的value之前,value从左向右遍历只匹配第一个
linsert key after value newValue # 把newValue插入到key列表中指定的value之后,value从左向右遍历只匹配第一个
lpop key [number] # 从key列表开头弹出number个数据,即移除并返回前number个数据,默认为1个,key不存在时返回nil
rpop key [number] # 从key列表末尾弹出number个数据,即移除并返回后number个数据,默认为1个,key不存在时返回nil
blpop/brpop # 阻塞式弹出,timeout为0表示无数据时永久阻塞
blpop key [key...] timeout # 按顺序从多个key中进行检测,弹出1个元素;都为空时且等待timeout秒后还为空就返回nil和等待时长
rpoplpush sourceKey destinationKey # 将sourceKey列表的尾元素,移动到destinationKey列表的开头,并返回该元素,key相同时视为旋转操作
brpoplpush sourceKey destinationKey timeout # rpoplpush的阻塞版本,效果一致,当sourceKey为空时最多等待timeout秒
lrem key number value # key列表中删除number个值为value的数据,number为正从前往后匹配移除,number为负从后往前匹配移除
ltrim key start stop # 从列表key中截取索引为start到stop的元素,其他内容直接被删除Set类型
- Set类型数据:无序性、不可重复性
- List类型-有序且可重复
- Redis的Set类型类似与Java的Set类型,底层都是value为null的hash表
- 应用场景
- 动态黑名单,客户端访问时从Redis中判断是否存在该ip,进行动态判定拦截/放行
- 有限随机数,利用spop 或srandmember
- 用户画像
- 将用户标签和习惯特征存放在Set类型中,无序且不重复的特点
- 同时可使用sinter和sinterstore根据多个用户/产品间交集进行好友推荐、商品推荐、客户推荐
sadd key value [value...] # 在集合key中添加多个value数据,已存在的重复数据会被忽略
scard key # 返回集合key的长度,key不存在时返回0
sismember key value # 判断value是否为集合key的成员,1-是 0-不是或key不存在
smove sourceKey destinationKey value # 将value元素从sourceKey移动到destinationKey;sourceKey中不存在value时返回0且不执行;destinationKey已存在时仅删除sourceKey中的value;集合destinationKey不存在时会自动创建
srem key value [value...] # 在集合key中移除多个指定value数据
smembers key # 查看集合key中的数据
srandmember key [count] # 从集合key中随机返回count个数据,count大于总长度时全部返回,count为负数时,返回包含count绝对值个元素的数组,可能出现重复数据
spop key [count] # 从集合key中随机移除并返回count个数据
# 差集/并集/交集
sdiff key1 key2 # 返回key1中除去key2的差集,仅返回-不保存
sdiffstore newkey key1 key2 # 返回key1-key2的差集,并保存在新的newkey中
sinter key1 key2 # 返回key1与key2的交集(共同部分),仅返回-不保存
sinterstore newkey key1 key2 # 返回key1与key2的交集,并保存在新的newkey中
sunion key1 key2 # 返回key1与key2的并集(整合并去重),仅返回-不保存
sunion newkey key1 key2 # 返回key1与key2的并集,并保存在新的newkey中有序Set类型
- 与Set类型不同,有序Set中每个元素都有对应分值
score,Redis根据score值进行由小到大排序。 - Set中元素不能重复,score可以重复
- 有序Set类型的所有命令都以z开头,也称为 ZSet
- 排序规则
- score从小到大 排序,score一致时根据字母先后排序
- 应用场景
- 排行榜,音乐/视频/销量/评价 等进行排序
zadd key score1 value1 [score value...] # 添加Zset类型数据,score+value
# 按索引值范围检索
zrange key start end # 返回字符串中start到end的字符串,支持使用负值-反向检索;end必须大于start `zrange key 0 -1`
zrange key start end withscores # 返回start到end的字符串,并显示score数值
# 按score数值范围检索
## min和max可以是具体数值,也可用 -inf负无穷 和 +inf正无穷 表示
zrangebyscore key min max withscores # 返回Zset类型key中score数值为[min,max]之间的数据,withscores显示score数值
zrangebyscore key (min (max withscores # 返回Zset类型key中score数值为(min,max)之间的数据,不包含min和max
zrangebyscore key -inf +inf limit offset count # 指定Zset类型的key中,从索引offset开始,返回count个数据
zrevrangebyscore key max min # 从大到小,倒序返回数据
zcard key # 返回集合的长度
zcount key min max # 返回有序集key中,score值在[min,max]范围的成员数量
zscore key value # 返回有序集key中,value对应的score值
zrank key value # 返回有序集key中,value对应的索引值,即排名-1值(score值可能相同时按字母先后排序)
zincrby key number value # 有序集key中,value对应score值增加number,number可为负
zrem key value [value...] # 移除有序集key中的多个value
zremrangebyrank key start end # 根据排名,移除有序集key中索引[start,end]的数据
zremrangebyscore key min max # 根据数值,移除有序集key中score值为[min,max]范围的数据,返回被移除的成员
# 根据字母删除时,d比dog小,按字母综合排序,类似叠加的数字效果
zrangebylex key [min (max # 当score值都一致时,检索并返回字母[min,max)的数据,+表示正无穷,-表示负无穷
zlexcount key [min [max # 当score值都一致时,检索并返回值本身为[min,max]之间的数据BitMap类型
并不是 Redis 的一种独立数据类型,它的底层本质上是 String(字符串)类型。
- 本质
- 底层实现:Bitmap 依托于 Redis 的 SDS 实现。将字符串内部的二进制比特位(0 或 1)看作是一个巨大的位数组。
- 寻址方式:每个比特位通过一个整数偏移量(offset)来寻址,偏移量从 0 开始。
- 自动扩容:当设置一个非常大的 offset 时,SDS 会按需扩容,中间空缺的比特位会被自动填充为 0。
- 容量上限:由于底层是 String 类型,Redis 单个 Bitmap 最多可存储 512MB 数据,相当于约 42.9 亿个二进制位(232232 )
- 应用场景:大数据量的二值性统计,数据量较小的二值性统计用Set更合适
- 用户签到系统:
- 设计:为每个用户创建一个 Bitmap(如
user:sign:1001),偏移量代表一年中的第几天(0-365)。 - 操作:用户签到时执行
SETBIT置为 1;使用GETBIT检查某天是否签到;使用BITCOUNT统计一年的总签到次数。
- 设计:为每个用户创建一个 Bitmap(如
- 活跃用户统计(DAU/MAU):
- 设计:为每一天创建一个 Bitmap,偏移量代表用户 ID。用户活跃则将该 ID 对应的位设为 1。
- 操作:使用
BITCOUNT快速统计当天的活跃用户数;使用BITOP OR合并多天的 Bitmap,快速计算一段时间内的去重活跃用户总数。
- 状态标记与开关管理:用于标记用户是否拥有某些特性(如 VIP、已实名、已绑定手机等)。每个特性对应一个 Bitmap,偏移量对应用户 ID。
- 布隆过滤器(Bloom Filter)的基础:Bitmap 是 Redis 实现布隆过滤器的底层存储结构。布隆过滤器通过多个哈希函数将元素映射到位图上,用于高效判断海量数据中某个元素是否绝对不存在或可能存在。
- 用户签到系统:
# 指定偏移量/索引位(offset)设置为 0 或 1。超出了当前字符串长度时,Redis 会自动扩容并用 0 填充空位
## 较大的offset执行setbit时,且发生自动内存扩容分配可能造成Redis服务器阻塞,建议提前分配较大内存
SETBIT key offset value # 设置key中指定位offset的值value
GETBIT key offset # 获取key中指定偏移量上的比特位值(0 或 1),超出了当前字符串的长度时默认返回 0
# 用于统计位图中值为 1 的比特位总数。可选参数 start 和 end 用于指定统计的字节范围(注意是字节而非比特)
BITCOUNT key [start end] # 统计索引start到索引end之间值为 1 的位数,不写start end时统计整个key
# 支持对一个或多个 Bitmap 执行 AND(与)、OR(或)、XOR(异或)、NOT(非)操作,并将计算结果存储到新的键(destkey)中
BITOP operation destkey key [key ...] # 位运算操作
BITPOS key bit [start] [end] # 查找指定范围内第一个被设置为特定值bit(0 或 1)的位置
# 提供了更灵活的操作能力,可以对位图的指定范围进行增量加操作(INCRBY)、获取(GET)和设置(SET),支持无符号/有符号整型。
BITFIELD key [GET/SET/INCRBY ...] # 批量与复杂操作HyperLogLog
- HyperLogLog 是一种用于基数统计(Cardinality Estimation)的概率型数据结构。它的核心使命是在允许极小误差的前提下,以极低的内存开销高效地统计海量数据集中“不重复元素的数量”
- 优势:只计数,不存值
- 极致的内存效率:无论你要统计的元素是几万个还是 2^64 个,HyperLogLog 占用的内存始终是固定的,仅需约 12 KB。
- 极高的查询性能:获取基数估算值的时间复杂度为 O(1),速度极快。
- 可接受的误差率:作为一种概率算法,它的标准误差率约为 0.81%。对于绝大多数大数据统计场景,这个误差完全可以接受。
- 应用场景
- 海量网页 UV 统计:统计独立访客数量,无需存储所有用户的 ID,节省大量内存。
- 社交媒体互动统计:估算某篇帖子、评论的独立点赞或互动人数。
- 广告点击与 API 监控:估算不同广告活动的独立用户访问量,或监控 API 接口的独立访问者数量。
- IoT 设备连接数估算:统计连接的独立设备数量,以优化网络资源分配。
- 局限性
- 无法获取具体元素:它只能告诉你“有多少个不重复元素”,不能返回具体的元素列表,也无法判断某个特定元素是否存在。
- 大基数下的“数值不变化”现象:当基数非常大(例如超过 2.44 亿)时,由于概率分布的特性,新增少量元素可能不会导致
PFCOUNT的返回值发生变化。这是正常的统计现象,此时更应关注数据的变化趋势而非单点绝对值。 - 不适用于精确计数:涉及财务结算、计费、库存扣减等对数据精确度要求极高的业务,绝对不能使用 HyperLogLog。
- 小数据量建议用 Set:当基数较小(如小于几千)时,Set 的内存开销同样很小且能提供 100% 精确的值,此时使用 Set 更为合适。
pfadd key element [element ...] # 将一个或多个元素添加到 HyperLogLog 中。如果元素已存在,会自动过滤,不会重复计数。
pfcount key [key ...] # 返回指定 HyperLogLog 的基数估算值。如果传入多个 key,Redis 会实时计算这些集合的并集基数
pfmerge destkey sourcekey [sourcekey ...] # 将多个 HyperLogLog 合并为一个新的 HyperLogLog为destkey。Geospatial地理空间
Geospatial(地理空间)数据类型是构建 LBS(基于位置的服务)应用的核心利器。它允许开发者高效地存储地理位置(经纬度),并支持范围查询、距离计算等操作
- Redis GEO 并不是一个独立的数据结构,它的底层本质上是 Sorted Set(有序集合 / ZSet)。
- 底层实现:利用 GeoHash 算法,将二维的经纬度坐标编码成一个可比较的整数(52-bit 整数),并将这个整数作为 ZSet 的
score,而业务标识(如 userId、shopId)作为member - 查询原理:GEO 的范围查询本质上是在 ZSet 里做“范围查找 + 过滤 + 排序/截断”。这使得它的写入和查询都非常快,时间复杂度为 O(log(N) + M)
- 可设置、查询某地理位置的经纬度,查询范围内的空间元素,计算两空间元素间距离等
- 底层实现:利用 GeoHash 算法,将二维的经纬度坐标编码成一个可比较的整数(52-bit 整数),并将这个整数作为 ZSet 的
- 应用场景
- 附近的人/门店(LBS 社交/O2O):以用户当前位置为中心,快速查询指定半径(如 5km)内的其他用户、商家或可接单的骑手,并按距离从近到远排序。
- 物流配送:计算配送点与用户地址的距离,辅助规划配送路线。
- 地理围栏(简化版):判断用户是否进入了某个特定区域(如商圈、园区),或进行“距离异常”风控检测。
- 打车出行:根据乘客位置快速匹配附近的司机,提升服务效率。
# 集合三元素:
## 经度 longitude 范围[-180,180] 正-东经 负-西经
## 纬度 latitude 范围[-80.05112878,85.05112878] 正-北纬 负-南纬
## 位置名称 member 经纬度位置命名,也称该Geospatial集合的空间元素名称
# 将一个或多个位置(经度、纬度、成员名)添加到指定的键中。注意参数顺序是先经度,后纬度。
GEOADD key longitude latitude member [longitude latitude member ...] # key中添加地理位置,命名为member
GEOPOS key member # 获取key中指定成员的经纬度坐标
# unit 支持单位:M(米,默认)、KM(千米)、MI(英里)、FT(英尺) 默认单位M
# 若位置不存在,返回空值;计算假设地球为完美球型,极限误差最大为0.5%
GEODIST key member1 member2 [unit] # 计算两个成员之间的直线距离。
GEOHASH key member [member ...] # 获取一个或多个成员的 GeoHash 编码(通常用于调试,将二维经纬度编码为一个字符串)
# 替代旧版 georadius 命令。支持以指定坐标(FROMLONLAT)或已有成员(FROMMEMBER)为中心,进行圆形(BYRADIUS)或矩形(BYBOX)搜索,并可指定按距离排序(ASC/DESC)、限制返回数量(COUNT)以及附带返回距离和坐标。
GEOSEARCH key FROMLONLAT/GROMMEMBER ... # 范围搜索(强烈推荐使用)发布/订阅命令
Redis 的发布/订阅(Pub/Sub)是一种消息通信模式,其核心思想是解耦。在这种模式下,消息的发送者(发布者)和接收者(订阅者)互不感知,它们通过“频道(Channel)”作为中介进行通信;和常见MQ产品完全不是一个量级,没有可比性
- 实现:Redis 的发布/订阅完全在内存中完成,没有中间存储节点。底层通过
redisServer的两个核心数据结构来实现- 精确频道订阅字典 (
pubsub_channels):- 这是一个字典结构,Key 是频道名称,Value 是一个链表,保存了所有订阅该频道的客户端。
- 当客户端执行
SUBSCRIBE时,Redis 会将该客户端追加到对应频道的链表末尾。
- 模式订阅链表 (
pubsub_patterns):- 这是一个链表结构,每个节点保存了订阅的模式(Pattern)以及对应的客户端。
- 当客户端执行
PSUBSCRIBE时,Redis 会将该客户端和模式添加到这个链表中。
- 精确频道订阅字典 (
- 消息发布 (
PUBLISH) 流程- 在
pubsub_channels字典中查找该频道,遍历其链表,将消息推送给所有直接订阅该频道的客户端。 - 遍历
pubsub_patterns链表,将频道名与所有模式进行匹配,如果匹配成功,则将消息推送给相应的客户端。 消息推送完成后,Redis 会立即丢弃该消息,不做任何保存
- 在
- 优点
- 极度轻量:无需额外部署重型消息中间件,上手极快。
- 天然解耦:发布者和订阅者无需知道彼此的存在,支持一对多的广播通信。
- 高吞吐、低延迟:消息基于内存直接推送,无需落盘,实时性极高
- 缺点
- 消息不持久化:消息仅存在于内存中,发布后即消失,不支持消息重放或历史追溯。
- 无离线补发:如果订阅者断开连接或重启,在此期间发布的消息会永久丢失。
- 无确认机制(ACK):采用“Fire-and-forget”(发后即忘)模式,发布者无法确认订阅者是否成功收到消息。
- 无消息堆积与背压机制:如果消费者处理速度远慢于生产者,Redis 不会排队等待,而是继续强行推送,这极易导致订阅端内存溢出或消息丢失
- 应用场景:**“允许偶尔丢失、对实时性要求高”**的通知类业务
- 实时消息推送:如直播间弹幕、游戏房间内状态广播、WebSocket 多节点后端广播。
- 系统解耦的事件通知:如订单创建后,通知积分服务、通知服务并行处理(非核心资金链路)。
- 配置动态刷新:修改限流阈值、开关状态或第三方密钥时,通过广播通知所有微服务实例刷新本地缓存,无需重启。
- 简单的监控告警:系统内部异常时的实时控制台告警推送。
- 使用
- 作为后端服务(或后端多个服务器节点)之间的“内部通信总线”,再交由后端服务的websocket或http通知用户感知
# 基础命令(精确频道)
subscribe channel [channel ...] # 订阅一个或多个频道channel,channel名随便自定义起。
publish channel message # 向指定频道发送消息,返回接收到消息的订阅者数量。
unsubscribe channel [channel ...] # 退订指定的频道。
# 模式匹配命令(通配符)
psubscribe pattern [pattern ...] # 订阅符合模式的所有频道(如 order.* 会匹配 order.created、order.paid 等,不包含order本身)
punsubscribe pattern [pattern ...] # 退订指定的模式
# 状态查看命令
pubsub channels [argument] # 查看当前活跃的频道列表(即至少有一个订阅者的频道)
pubsub numsub [channel ...] # 返回给定频道的订阅者数量,不给定任何频道则返回空列表
pubsub numpat # 查询当前redis所有客户端订阅的所有频道模式的数量总和Redis事务
本质是一组命令的批处理,将多条命令打包,在提交时一次性、按顺序串行执行。执行期间Redis 独占处理线程,保证这些命令不会被其他客户端的请求插队打断
- 特性
- 仅保证数据一致性,不具备数据库DBMS一样的ACID特性
- 不具备原子性,部分命令的失败,不影响其他命令执行,不会引发回滚
- 无复杂隔离级别,通过乐观锁机制实现简单隔离
- 执行结果写入内存,是否持久化取决于Redis持久化策略,与事务无关
- 注意
- 适合使用:简单的批量命令打包、防止并发插队、配合
WATCH实现乐观锁(如简单的计数器更新、库存扣减)。 - 不适合使用:强一致性要求、需要出错回滚的复杂业务(如转账、扣款)。
- 进阶替代方案:如果业务需要完整的原子性和复杂的逻辑判断,强烈推荐使用 Lua 脚本。Lua 脚本在 Redis 中执行全程不可中断,出错不会导致半写入数据,比原生事务更靠谱。
- 适合使用:简单的批量命令打包、防止并发插队、配合
muti # 开启事务,后输入多个命令
exec # 执行事务,真正执行前面写好的多个命令
discard # 放弃事务
watch #(乐观锁):在 MULTI 之前执行,用于监控一个或多个 Key。如果在 EXEC 提交前,被监控的 Key 被其他客户端修改,本次事务将直接作废,返回 nil。这常用于秒杀、库存扣减等防超卖场景Redis持久化
Redis 提供了两种主要的持久化方式:RDB(快照) 和 AOF(追加日志),以及结合两者优点的混合持久化
- 默认为 RDB 快照模式,同时开启时 AOF 追加日志优先级更高,启时加载优先级同理AOF优先
- 手动/定时/条件触发三种方式将内存数据库的状态描述信息写入指定持久化文件中;当系统重启时,自动加载持久化文件并恢复到内存中
RDB持久化
- 三种方式:手动save、手动bgsave、自动条件触发
benchmark基准测试
通过一系列标准化的操作,评估 Redis 在不同负载下的性能表现的过程。它主要用于了解 Redis 的吞吐量(QPS)、响应时间(延迟)等关键指标,从而帮助开发者和系统管理员发现性能瓶颈、评估系统稳定性以及比较不同硬件或配置方案的性能。
常用官方自带命令行工具
redis-benchmark,可模拟多个客户端同时向 Redis 发送查询命令的场景,测试其读写性能
- 影响性能的核心因素
- 网络延迟:本地回环地址(127.0.0.1)测试没有网络损耗,QPS 极高;跨机器测试时,网络 RTT 会严重拉低 QPS。
- Value 大小:数据体积越大,内存拷贝和 IO 开销越高,QPS 会明显下降。
- 持久化机制:关闭 AOF 性能最高;开启
appendfsync always会让性能大幅下降。 - 命令复杂度:
GET/SET等 O(1) 命令很快,但KEYS *、HGETALL等 O(n) 命令会阻塞单线程,压测时应避免使用。 - CPU 瓶颈:Redis 核心是单线程的,当一个 CPU 核心被打满时,即使增加并发数,QPS 也不会再涨,延迟反而会飙升。
- 除了官方的
redis-benchmark,还有一些第三方工具可用于更复杂的压测,例如 Redis Labs 的memtier_benchmark、Twitter 的rpc-perf以及 Yahoo 的YCSB等
ll /use/local/bins # 默认安装位置
# 基础测试
## -h 和 -p:指定 Redis 服务器的地址和端口
## -c:指定并发连接数(如 100 个客户端)
## -n:指定测试的总请求数(如 10000 次)
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000
# 指定测试
## -t:指定只测试某些命令(如 SET 和 GET),多个命令用逗号分隔
redis-benchmark -t set,get -c 50 -n 100000
# 模拟真实数据大小
## -d:指定单条数据(value)的大小(单位 Byte)。默认值极小(3字节),实际业务中调大此值能更真实地反映性能
redis-benchmark -d 1024 -c 100 -n 100000
# 开启 Pipeline(流水线)
## -P:模拟批量发送命令,减少网络往返次数,可以大幅提升 QPS
redis-benchmark -t set,get -P 16
# 集群模式
## 如果是测试 Redis 集群实例,需要加上 --cluster 参数
# 测试完成后,工具会输出详细的性能指标
SET: 112359.55 requests per second # 表示在测试条件下,Redis 每秒能处理约 11.2 万次 SET 操作原理层
简单动态字符串SDS
Redis中,无论Key还是Value,其基础数据类型都是字符串。使用标准C语言开发,但未直接使用C字符串,而是自定义字符串结构。结构简单且功能强大,称为"简单动态字符串SDS"
- C字符串:使用双引号包裹,以空字符
/0结尾的字符序列- C字符串出现场景:在字符串“字面常量”中,且该字符串不会发生变更(或理解为:存入是SDS,取出时字符串)
- SDS简单动态字符串是一个结构体,定义在Redis安装目录下的
src/sds.h中
- SDS优势
- O(1) 获取长度:防止“字符串长度获取”性能瓶颈,C字符串获取长度时需遍历整个字符串,而SDS内置
len字段 - 兼容C函数:Redis提供SDS的API,为兼容C函数便于二次开发,SDS底层buf[]仍以空字符串
/0结尾 - 保障二进制安全,C字符串仅能包含符合的编码格式,如ASCII、UTF-8等,且不能包含空字符(结束符)
/0作为分隔符- C字符串不能直接保存图片、音/视频、压缩文件、office文件等二进制数据
- SDS通过len属性判断是否结束,而不是
/0,无需额外处理,可随意存储数据
- 杜绝缓冲区溢出与内存再分配优化
- C字符串每次变化,都需分配新的空间并转移
- 空间预分配:当字符串需要扩展时,Redis 不仅分配所需空间,还会额外分配空闲空间(
len < 1MB时分配与len相等的空闲空间,len >= 1MB时分配 1MB 空闲空间) - 惰性空间释放:当字符串缩短时,程序不立即释放多余内存,而是将其记录在
free或alloc属性中,等待后续扩展时复用,从而大幅减少内存重分配次数。- 需释放SDS未使用空间时,可通过
sdsRemoveFreeSpace()函数释放
- 需释放SDS未使用空间时,可通过
- O(1) 获取长度:防止“字符串长度获取”性能瓶颈,C字符串获取长度时需遍历整个字符串,而SDS内置
- 演进
- Redis 7:主要使用
sdshdr8、sdshdr16、sdshdr32等结构体。其核心字段包括len(已使用长度)、alloc(分配的总容量)、flags(类型标记)以及柔性数组buf[](存储实际字符串)。 - Redis 8:在 Redis 7 的基础上进行了深度的内存瘦身。Redis 8 针对短字符串键(Short String Keys)引入了更紧凑的内存布局,最高可将短字符串键的内存占用减少 37%。
- Redis 7:主要使用
// SDS数据结构
struct sdshdr{
char buf[]; // 字节数组,用于保存字符串
int len; // buf[]中已使用字节数量,称为SDS长度
int free; // buf[]中尚未使用的字节数量
}集合底层实现
- Redis 7 中的 Set 底层实现,支持三种结构
- IntSet(整数集合):当集合中所有元素都是整数,且元素数量小于等于 512 个(由
set-max-intset-entries配置)时,Redis 会使用 IntSet。它将所有整数按从小到大排序,紧凑地存储在一块连续内存中。 - ListPack(紧凑列表):在 Redis 7.2 中,为了替代早期的 ZipList,引入了 ListPack。当集合元素是字符串类型(SDS),且同时满足元素个数小于 128 个(
set-max-listpack-entries)和元素大小小于 64 字节(set-max-listpack-value)时,使用 ListPack。它解决了 ZipList 的“级联更新”问题,内存利用率极高。 - HashTable(哈希表):当集合元素数量或大小超过上述阈值时,Redis 会自动将其转换为 HashTable。HashTable 采用链地址法解决哈希碰撞,提供 O(1) 的查找、添加和删除性能,但空间开销和碰撞概率相对较高。
- Redis 8 中的 Set 底层实现
- 结构延续与优化:Redis 8 延续了 Redis 7 中
IntSet + ListPack + HashTable的三态转换机制。 - 内存极致压缩:得益于 Redis 8 整体底层结构的优化,Set 类型在存储大规模数据(即使用 HashTable 编码)时,内存占用相比 Redis 7 降低了约 16.7%。
- 无缝转换机制:在 Redis 8 中,这种底层结构的转换对用户是完全透明的。当向 Set 中添加数据时,Redis 会根据数据特征和阈值自动升级编码(例如从 IntSet 升级到 ListPack,再升级到 HashTable)。需要注意的是,这种升级是单向的,一旦从小内存结构转换为大内存结构,就不会再自动降级。
- 结构延续与优化:Redis 8 延续了 Redis 7 中
理论key与value
- 一个 Redis 实例理论上最多可以存储 232−1232−1 个 Key(约 42.9 亿个)。这是由 Redis 内部使用的哈希表索引基于 32 位无符号整数决定的
- 实际生产中,Key 的数量完全受限于服务器的可用物理内存
- 任何一个 List、Set、Hash 或 Sorted Set,最多都可以包含 232−1232−1 个元素(约 42.9 亿个)
- 实际瓶颈:同样,元素的实际容纳数量也受限于当前 Redis 实例的可用内存大小。
