Redis基础

Redis简介

Redis是一个开源的高性能键值对存储系统,其使用了单线程加多路IO复用的技术来提供快速且高效的数据处理能力

Redis应用场景

应用场景描述
缓存Redis 可以作为高性能缓存系统使用,将常用的数据缓存在内存中,提供快速的读写操作。
会话存储用于存储用户会话数据,例如登录状态、购物车信息等,在分布式环境下可以提供共享会话功能。
计数器Redis支持原子操作,可以用来实现计数器功能,如网站访问量统计、文章点赞数、粉丝数等
排行榜Redis 的有序集合(Sorted Set)可以实现排行榜功能,有序集合可以存储多个元素,并且每个元素都关联一个分数,通过分数来进行排序,如热门文章排行、用户积分排行等。
分布式锁Redis 可以用于实现分布式锁,确保在分布式环境下对共享资源的互斥访问,避免并发冲突。
分布式缓存Redis 可以作为分布式缓存系统使用,通过多个节点组成集群,提供高可用性和扩展性。
消息队列Redis 提供 List 和 Pub/Sub 功能,可以实现简单的消息队列系统,用于异步任务处理、事件驱动等场景。
实时消息推送利用 Redis 的发布订阅功能,可以将实时的消息推送给订阅者,适用于聊天室、即时通讯等场景。
地理位置信息存储Redis 的地理位置特性(Geo)可以存储和查询地理位置信息,如附近的人、地点搜索等功能。

Redis安装与配置

Linux操作系统安装

安装包下载

在linux系统有两种下载方式,一种是通过官网下载,一种是在命令行使用wget下载

下载方式简介
官网下载打开官网下载:http://redis.io
命令行下载执行命令下载:wget http://download.redis.io/releases/redisvi /etc/profilevi /etc/profile

Linux准备工作

配置网络、关闭防火墙、下载安装gcc编译器

1
2
3
4
5
6
7
8
9
10
# 设置开机禁用防火墙(永久关闭防火墙)
systemctl disable firewalld.service
# 安装gcc编译器
yum -y install gcc
# 测试gcc编译器是否安装
gcc --version
# 进入安装目录,并上传压缩包
cd /opt/software/
# 解压到指定目录
tar -zxvf /opt/software/redis-6.2.1.tar.gz -C /opt/module/

安装与配置

(1)编译Redis

1
2
3
4
5
6
# 创建安装目录/usr/local/redis
sudo mkdir /usr/local/redis
# 进入redis解压目录
cd /opt/module/redis-6.2.1/
# 执行make命令进行编译
make

如果没有准备好C语言编译环境gcc,执行make命令进行编译时会报错—Jemalloc/jemalloc.h:没有那个文件

需要先安装C语言编译环境gcc,然后执行make distclean清理后,再次执行make编译

(2)安装Redis可以选择指定目录或不指定

1
2
3
4
# 指定目录安装redis
make PREFIX=/usr/local/redis install
# 不指定目录安装redis,默认为/usr/local/bin
make install

(3)配置环境变量

1
2
3
# REDIS HOME
export REDIS_HOME=/usr/local/redis
export PATH=$REDIS_HOME/bin:$PATH

(4)执行生效

1
source /etc/profile

Windows操作系统安装

安装包下载

github上可以下载Windows Redis5.0终版,有安装包形式和zip两种,下载zip包

https://github.com/tporadowski/redis/releases

安装与配置

(1)新建一个文件夹,将zip包解压到新建文件夹中

文件简介
redis.windows.conf
redis.windows-service.conf
redis配置文件
redis-cliredis命令行工具,可以启动redis命令行,执行redis命令
redis-service.exeredis 服务,用于启动redis服务

(2)配置环境变量:【此电脑】-【属性】,找到【高级系统设置】,点击【环境变量】

(3)打开cmd窗口进行测试

1
2
3
4
# 查看是否安装成功
redis-cli -v
# 启动redis服务,启动成功后不能关闭改cmd窗口,如果关了,则redis服务就关掉了
redis-server

(4)启动一个新的cmd窗口,连接Redis,测试存入数据

1
2
3
4
5
6
7
8
9
10
# 启动客户端
redis-cli
# 存入数据
set k v
# 查看指定key的数据
get k
# 查看所有key
keys *
# 数据清除,通杀全部库
flushall

免安装在线Redis

https://try.redis.io/

Redis安装目录简介

目录简介
redis-serverRedis服务端启动脚本
redis-cliRedis提供的命令行客户端
redis-benchmark性能测试工具,可以在自己本子运行,看看自己本子性能如何
redis-check-aof修复有问题的AOF文件
redis-check-dump修复有问题的dump.rdb文件
redis-sentinelRedis哨兵启动脚本

Redis连接与关闭

Redis服务启动

前台启动(不推荐)

在前台启动的Redis命令行窗口不能关闭,否则服务器停止,命令如下

1
/usr/local/redis/bin/redis-server

后台启动(推荐)

(1)拷贝一份redis.conf到其他目录(备份redis.conf)

1
cp /opt/redis-6.2.1/redis.conf /opt

(2)修改redis.conf(128行)文件,将里面的后台启动设置daemonize,将no改成yes,让服务在后台启动

1
2
3
4
5
6
# 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes后即可后台运行
daemonize yes
# 密码,设置后访问Redis必须输入密码
requirepass 123321

(3)指定配置文件启动Redis

1
2
3
4
# 指定配置文件启动Redis
redis-server /opt/redis.conf
# 查看Redis进程
ps -ef | grep redis

设置开机自启(推荐)

新建一个系统服务文件

1
vi /etc/systemd/system/redis.service

内容如下

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target

重载系统服务

1
systemctl daemon-reload

使用命令操作Redis

1
2
3
4
5
6
7
8
9
10
# 启动
systemctl start redis
# 停止
systemctl stop redis
# 重启
systemctl restart redis
# 查看状态
systemctl status redis
# 设置开机自启
systemctl enable redis

Redis客户端连接

使用自带redis-cli

方式一:连接时输入密码

1
2
# 使用redis-cli连接Redis,启动Redis客户端(-h 主机地址 -a 密码 -p 端口号 -c 采用集群策略连接)
/usr/local/redis/bin/redis-cli -h 主机地址 -a 密码

方式二:先连接再输入密码

1
2
3
4
5
6
# 使用redis-cli连接上redis
redis-cli
# 输入密码
auth 密码
# 查看密码
config get requirepass

使用使用图形化工具

下载AnotherRedisDeskTopManager图形化工具并安装使用

下载Redis Desktop Manager图形化工具并安装使用

Redis设置密码

配置文件(推荐)

在配置文件设置的密码为永久设置,只需要修改requirepass 参数配置密码即可

1
requirepass 密码

客户端命令(不推荐)

使用客户端命令设置密码只是临时设置,重启redis服务器,密码就会还原,命令如下

1
2
3
4
# 使用redis-cli连接上redis
redis-cli
# 设置临时密码
config set requirepass 密码

Redis关闭

单实例关闭

1
/usr/local/redis/bin/redis-cli shutdown

终端后关闭

1
2
3
4
# 使用redis-cli连接上redis
redis-cli
# 关闭
shutdown

指定端口关闭

1
/usr/local/redis/bin/redis-cli -p 6379 shutdown

使用kill命令

1
2
3
4
# 查看进程
ps -ef | grep redis
# 杀掉对应进程
kill -9 进程号

Redis配置文件详解

Units(单位配置)

配置数据单位换算关系,大小单位,只支持bytes,不支持bit、大小写不敏感

include(包含配置)

多实例的情况,可以把公用的配置文件提取出来配置在子配置文件,然后在主文件引用子配置文件

1
include /path/xxx.conf

Network(网络相关配置)

配置简介
bind 127.0.0.1 -::1这项配置绑定的IP并不是远程访问的客户端的IP地址,而是本机的IP地址 默认情况bind=127.0.0.1只能接受本机的访问请求,因为服务器需要远程访问,所以生产环境要写应用服务器的地址; 若将其注释掉不写,将无限制接受任何ip地址的访问 如果想要让Redis可以远程连接的话,就需要让Redis监听eht0这块网卡, 加上配置bind 127.0.0.1 10.0.4.5,这样既可以本地访问,也能够远程访问。 bind只能有一行配置,如果有多个网卡要监听,就配置多个ip,用空格隔开,否者只有配置的最后一个bind生效
protected-mode yes是否开启保护模式,设置no 若开启保护模式,没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应,不支持远程访问 若关闭保护模式,支持远程访问
port 6379端口号,默认 6379
tcp-backlog 511设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列+ 已经完成三次握手队列。 在高并发环境下,需要一个高backlog值来避免慢客户端连接问题。 注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128), 所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果
timeout 0一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。
tcp-keepalive 300对访问客户端的一种心跳检测,每个n秒检测一次。 单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60

GENERAL(通用配置)

配置简介
daemonize no是否为后台进程,设置为yes,守护进程,后台启动
pidfile /var/run/redis_6379.pid存放pid文件的位置,每个实例会产生一个不同的pid文件
loglevel notice指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice 四个级别根据使用阶段来选择,生产环境选择notice 或者warning
logfile “”日志文件名称
databases 16设定数据库的数量,默认16,默认数据库为0,可以使用SELECT命令在连接上指定数据库id

SNAPSHOTTING (快照相关)

配置简介
dbfilename dump.rdb设置保存的RDB的文件名称,默认dump.rdb
dir ./设置RDB保存路径,默认在启动目录/usr/local/bin生成快照文件
stop-writes-on-bgsave-error yes当Redis无法写入磁盘的话,直接关掉Redis的写操作。推荐yes.
rdbcompression yes存储到磁盘中的快照,是否进行压缩存储,推荐yes 如果是的话,redis会采用LZF算法进行压缩。 如果不想消耗CPU来进行压缩的话,可以设置为关闭此功能。
rdbchecksum yes在存储快照后,还可以让redis使用CRC64算法来进行数据校验, 但是这样做会增加大约10%的性能消耗, 如果希望获取到最大的性能提升,可以关闭此功能,推荐yes.

SECURITY(安全配置)

由于redis很快,所以被破解密码时,性能也很好,如果密码太简单,那么可能很快就被破解了,因此尽量使用长且不容易被猜到的密码作为redis的访问密码,在配置文件设置密码是永久设置

1
requirepass 密码

LIMITS(限制配置)

配置简介
maxclients设置redis同时可以与多少个客户端进行连接,默认情况下为10000个客户端,如果达到了此限制,redis会拒绝新的请求,并回应“max number of clients reached”
maxmemory设置redis可以使用的内存量,建议必须设置,否则,将内存占满,造成服务器宕机 一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。 如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”, 那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。 但是对于无内存申请的指令,仍然会正常响应,比如GET等。 如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时, 需要在系统中留出一些内存空间给同步队列缓存, 只有在你设置的是“不移除”的情况下,才不用考虑这个因素。
maxmemory-policyvolatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)
allkeys-lru:在所有集合key中,使用LRU算法移除key
volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
allkeys-random:在所有集合key中,移除随机的key
volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
noeviction:不进行移除。针对写操作,只是返回错误信息
maxmemory-samples设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值, 所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。 一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。

Redis线程模型

Redis 是单线程吗?

(1)Redis 程序并不是单线程的

(2)Redis主要使用单个线程(即主线程)执行「接收客户端请求——>解析请求——>进行数据读写等操作——>发送数据给客户端」的流程,因此我们常说Redis是单线程的

(3)实际上,Redis在启动时会创建后台线程(BIO),来处理一些耗时的任务,后台线程主要负责以下三个任务

  1. 关闭文件任务(BIO_CLOSE_FILE):当需要关闭文件时,这个任务会被添加到关闭文件任务队列。后台线程会不断轮询该队列,并调用close(fd)方法来关闭相应的文件。
  2. AOF刷盘任务(BIO_AOF_FSYNC):如果AOF持久化配置为每秒同步(everysec),主线程将把AOF写入操作封装为一个任务,并将其放入AOF刷盘任务队列。后台线程会不断轮询该队列,并调用fsync(fd)方法将AOF日志刷盘,确保数据持久化。
  3. 内存释放任务(BIO_LAZY_FREE):这个任务队列用于释放内存。当有需要释放对象、删除数据库中所有对象或释放跳表对象的操作时,这些操作会被封装成相应的任务,并添加到内存释放任务队列。后台线程会不断轮询该队列,并执行相关的内存释放操作。

(4)可以将后台线程视为消费者,它从任务队列中获取任务并执行相应的操作,而生产者则是主线程,负责将耗时任务放入相应的任务队列中。通过这种方式,Redis能够在处理客户端请求的同时,利用后台线程处理一些耗时任务,避免主线程阻塞,从而提高了系统的性能和响应速度

Redis 单线程为什么快?

  1. 内存操作:Redis大部分操作都是基于内存的,而内存读写的速度远快于磁盘读写。因此,Redis能够在短时间内快速地执行读写操作,这是它高速的一个重要原因。
  2. 单线程模型:Redis采用单线程模型,通过避免线程切换和竞争条件,提高了性能。单线程模型可以减少上下文切换的开销,并且在大多数情况下,单线程已足够处理大部分的请求。
  3. 高效的数据结构:Redis支持丰富的数据结构,如字符串、列表、哈希表、集合、有序集合等。这些数据结构底层都经过精心设计和实现,使得Redis能够高效地执行各种操作,如查询、插入、更新和删除等。
  4. 异步非阻塞IO:Redis利用了异步非阻塞IO模型来处理网络请求。它使用事件驱动的方式接收和响应客户端请求,通过监听和触发事件来实现高效的IO操作。这种模型允许Redis在处理一个客户端请求时,同时监听其他客户端请求,提高了处理并发请求的能力。
  5. 非阻塞式的持久化机制:Redis的持久化机制采用了非阻塞式的方式。当进行AOF日志刷盘或RDB快照时,Redis会使用后台线程来执行相关的操作,而不会阻塞主线程的正常工作。这样可以确保Redis在进行持久化操作时不会对客户端请求产生明显的影响。

Redis数据结构

Redis是一个内存数据库,它的数据结构是键值对(key:value),其中键(key)是一个字符串,而值(value)可以采用多种数据类型。

数据结构简介示例
字符串(key:String)一个key对应一个字符串key:”Hello World”
哈希(key:Hash)一个key对应一个键值对key:{name: “Alice”, age: 25}
列表(key:List)一个key对应一个按照插入顺序存储的字符串列表key:[“apple”, “banana”, “orange”]
无序集合(key:Set)一个key对应一个无序、唯一的字符串集合key:{“apple”, “banana”, “orange”}
有序集合(key:Zset)一个key对应一个有序、唯一的字符串集合key:{“apple”, “banana”, “orange”}
地理位置(key:Geospatial)一个key对应一个地理位置信息的数据结构key:{longitude: 39.9087, latitude: 116.3975}
位图(key:Bitmaps)一个key对应一个位图,支持高效地处理二进制位操作key:0101010011
基数估算算法(key:HyperLogLog)一个key对应一个基数估算算法,用于统计元素数量的近似值key:{“apple”, “banana”, “orange”}

键(key)

在Redis中,键(key)是一个字符串,用于唯一标识存储在数据库中的值,可以通过 key获取 Redis 中保存的数据。

key 基本操作

命令简介
SET key value设置键值对,将指定的值与键关联
GET key获取键对应的值
DEL key删除指定的键值对

key 时效性控制操作

命令简介
expire key seconds为给定的key设置过期时间(秒),设置还能活多久
pexpire key milliseconds为给定的key设置过期时间(毫秒),设置还能活多久
expireat key timestamp为给定的key设置过期时间为timestamp所指定的毫秒数时间戳(秒)
pexpireat key milliseconds-timestamp为给定的key设置过期时间为timestamp所指定的毫秒数时间戳(毫秒)
persist key移除键的过期时间,使其永久有效
ttl key获取 key 的剩余过期时间(秒)
pttl key获取 key 的剩余过期时间(毫秒)

key 查询操作

命令简介
key pattern按照指定的正则表达式查询Redis中的key
查询所有:keys *
查询以 it 开头:keys it*
查询以 it 结尾:keys *it
查询以 user: 开头,最后一个字符任意:keys user:?
exists key查询 key 是否存在(1 - key存在,0 - key不存在)
type key获取 key 的类型

key 其他操作

命令简介
rename key newkey为 key 改名
move key db移动key到另外一个库中
renamenx key newkey当且仅当 newkey 不存在时,将 key 改名为 newkey
sort对所有 key 排序(list、hash、sorted_sort)

字符串(key:String)

添加 / 修改数据

同等情况下,多数据操作要比单数据操作执行效率高,如果多数据操作一次发送的数据太多,要进行切分(一次发送 1 亿个数据,太大,切成每次发送 100 万个)

命令简介
set key value添加、修改键值对
mset key1 value1 key2 value2 …..添加、修改多个数据
msetnx key1 value1 key2 value2 …..同时添加一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
append key value在key对应的value末尾,追加内容(不存在该key就创建,存在就追加)
setnx key value设置值,key存在,什么事都不做,只有在 key 不存在时,设置 key 的值

获取数据

命令简介
get key获取对应键值,根据 key 查询对应的 value ,如果不存在,返回空(nil)
mget key1 key2 …..获取多个数据
strlen key获得value字符串的长度
type key获取key的类型

删除数据

命令简介
del key删除数据,删除指定的key数据

其他命令

命令简介
unlink key根据value选择非阻塞删除,仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作
getrange key 开始位置 结束位置获得值的范围,类似java中的substring,前包,后包
setrange key 开始位置 value用 value 覆写key所储存的字符串值,从开始位置开始(索引从0开始)。
getset key value以新换旧,设置了新值同时获得旧值。
exists key判断某个key是否存在

哈希(key:Hash)

添加 / 修改数据

命令简介
hset key field value添加 / 修改数据,给key集合中的 field 键赋值value gg:{id:val,id:val}
hmset key field1 value1 field2 value2…批量添加 / 修改多个数据,批量设置hash的值
hsetnx key field value设置值,将哈希表 key 中的域 field 的值设置为 value
field不存在,正常添加,存在,什么事都不做

获取数据

命令简介
hget key field获取数据,从key集合field取出 value
hexists key field获取field是否存在,哈希表key中是否存在指定的字段
hmget key field1 field2 …获取多个数据
hlen key获取当前hash结构中field的数量
hgetall key获取所有数据,获取当前hash结构中的全部field和value
hkeys key获取当前hash结构中的全部field
hvals key获取当前hash结构的所有value

删除数据

命令简介
hdel key field1 [field2]删除数据,删除key对应的field,可以删除多个

扩展操作

命令简介
hincrby key field increment自增,设置指定字段的数值,为哈希表 key 中的域 field 的值加上增量 1 -1
hincrbyfloat key field increment增加指定范围的值

列表(key:List)

添加 / 修改数据

命令简介
lpush key value1 value2 value3 ….从左边插入一个或多个值
rpush key value1 value2 value3 ….从右边插入一个或多个值
rpoplpush list1 list2将list1列表右边一个值(末尾),插到list2列表左边(头部)
linsert key before value newvalue在value的后面插入newvalue插入值
lset key index value修改数据,将列表key下标为index的值替换成value
index超出整个列表的长度,也会失败

获取数据

命令简介
llen key获取列表长度
lindex key index获取指定索引位置的数据(从左到右)
lrange key start stop获取指定索引范围的数据(从右到左) start从0开始,stop输入-1,代表最后一个,-2代表倒数第二个
例:lrange mylist 0 -1 0 左边第一个,-1右边第一个,(0-1表示获取所有)
llen key获取整个列表的长度(arr.length)

删除数据

命令简介
lpop key从左边吐出一个值。值在键在,值光键亡
rpop key从右边吐出一个值。值在键在,值光键亡
lrem key n value从左边删除n个value(n>0从左到右,n<0从右到左)

扩展操作

命令简介
blpop key1 [key2] … timeout规定时间内从左边获取并移除 例:等待 30 s ,如果有数据直接取出 blpop list1 30
brpop key1 [key2] … timeout规定时间内从右边获取并移除
ltrim key start stop保留列表中的数据(保留指定索引范围内的数据,超过整个索引范围被移除掉)

无序集合(key:Set)

常用命令

命令简介
sadd key value1 value2…添加数据
smembers key获取数据(获取全部数据)
srem key value1 value2 …删除一个或多个数据,不存在的成员元素会被忽略
sismember key value查看当前的set集合中是否包含这个值

扩展命令

命令简介
srandmember key count随机获取指定数量的数据(获取的同时,移除数据,count默认为1,代表弹出数据的数量)
spop key count随机取出指定数量的数据并移除
sinter key1 key2返回交集(取多个set集合交集)
sunion key1 key2返回并集(获取全部集合中的数据)
sdiff key1 key2返回差集(获取多个集合中不一样的数据),数据放在前面的为数据基点
sinterstore newkey key1 key2将交集保存到newKey
sunionstore newkey key1 key2将并集保存到newKey
sdiffstore newkey key1 key2将key1 - key2并集保存到newKey
smove oldkey newkey value将原集合的数据移动到其他集合

有序集合(key:Zset)

添加数据

命令简介
zadd key score1 member1 score2 member2…添加一个或多个数据(score必须是数值,可重复。member不允许重复)
zincrby key increment member修改value的分数(member存在于key中,正常增加分数,不存在,命令相当于zadd)

获取数据

命令简介
zcard key获取zset中数据的数量 获取数组长度
zcount key min max统计该集合,分数区间内的元素个数,包含边界值(sql 语句里面between)
zrank key value返回该值在集合中的排名,从0开始
zrange key start stop [withscores]返回有序集 key 中,下标在start stop之间的元素(默认根据score从小到大排序) 带withscores,可以让分数和值一起返回到结果集。
zrangebyscore key min max [withscores] [limit offset count]返回有序集 key 中,所有score 值介于min 和max 之间(包括等于min 或max )的成员。 有序集成员按score 值递增(从小到大)次序排列。
zrevrangebyscore key min max [withscores] [limit offset count]返回有序集 key 中,所有score 值介于min 和max 之间(包括等于min 或max )的成员。 有序集成员按score 值递增(从大到小)次序排列。

删除数据

命令简介
zrem key member删除该集合下,指定值的元素

其他操作

命令简介
zscore key member查看指定的member的分数
zrange key start stop [withscores]根据分数从小到大排序,获取指定范围内的数据 withscores如果添加这个参数,那么会返回member对应的分数
zrevrange key start stop [withscores]根据分数从大到小排序,获取指定范围内的数据(降序排序) withscores如果添加这个参数,那么会返回member对应的分数
zrangebyscore key min max [withscores] [limit offset count]根据分数的返回去获取member withscores代表同时返回score,添加limit,就和MySQL中一样, 如果不希望等于min或者max的值被查询出来可以采用 (分数 相当于 < 但是不等于的方式,最大值和最小值使用+inf和-inf来标识)

地理位置(key:Geospatial)

常用命令

命令简介
geoadd key longitude latitude member [longitude latitude member…]添加地理位置(经度,纬度,名称)
geopos key member [member…]获得指定地区的坐标值
geodist key member1 member2 [m|km|ft|mi ]获取两个位置之间的直线距离

位图(key:Bitmaps)

常用命令

命令简介
setbit key offset value设置Bitmaps中某个偏移量的值(0或1)offset:偏移量从0开始
getbit key offset获取Bitmaps中某个偏移量的值 获取键的第offset位的值(从0开始算)
bitcount key [start end]统计字符串从start字节到end字节比特值为1的数量,start和end代表起始和结束字节数
bitop and(or/not/xor) destkey [key…]bitop是一个复合操作,它可以做多个Bitmaps的and(交集)、 or(并集)、 not(非)、 xor(异或)操作 并将结果保存在destkey中。

基数估算算法(key:HyperLogLog)

常用命令

命令简介
pfadd key element [element …]添加指定元素到 HyperLogLog 中 如果执行命令后HLL估计的近似基数发生变化,则返回1,否则返回0
pfcount key [key …]计算HLL的近似基数,可以计算多个HLL, 比如用HLL存储每天的UV,计算一周的UV可以使用7天的UV合并计算即可
pfmerge destkey sourcekey [sourcekey …]将一个或多个HLL合并后的结果存储在另一个HLL中, 比如每月活跃用户可以使用每天的活跃用户来合并计算可得

Java操作Redis

Redis客户端

(1)Redis客户端是与Redis服务器进行通信的工具或库。它允许开发人员使用各种编程语言(如Python、Java、C#等)来连接和与Redis数据库进行交互。通过Redis客户端,开发人员可以发送各种命令来读取、写入和操作存储在Redis中的数据

(2)不同的编程语言有不同的Redis客户端库可以使用,开发者可以根据自己使用的编程语言选择相应的Redis客户端,并根据需要使用客户端提供的功能与Redis数据库进行交互,一些常见的Redis客户端如下,详见官网:https://redis.io/docs/clients/

  • Redis-cli:Redis自带的命令行客户端
  • Jedis:Java语言的Redis客户端库
  • hiredis:一个C语言的Redis客户端库
  • Jedis:Java语言的Redis客户端。
  • Redis-py:Python语言的Redis客户端。
  • Node_redis:Node.js语言的Redis客户端。
  • PhpRedis:PHP语言的Redis客户端。

Redis的Java客户端

Java语言有多个Redis客户端可以使用,以下是其中一些常用的客户端

  • Jedis:Jedis是一个流行的Java Redis客户端库,它提供了简单而直观的API来与Redis进行交互。
  • Lettuce:Lettuce是另一个功能强大的Java Redis客户端库,它是基于Netty的异步、线程安全和可伸缩的库。
  • Redisson:Redisson是一个功能丰富的Java Redis客户端和分布式对象库。

使用Jedis客户端连接Redis

Jedis基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void test() {
// 建立连接,Jedis(主机地址,端口号)
Jedis jedis = new Jedis("localhost", 6379);
// 输入密码
jedis.auth("123456");
// 选择库
jedis.select(0);
// 测试连接
String pong = jedis.ping();
System.out.println("连接成功:" + pong);
// Redis的命令是什么,Jedis的方法就是什么
jedis.set("name","小明");
jedis.get("name")
// 关闭连接
jedis.close();
}

Jedis连接池使用

(1)配置jedis连接池类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class JedisConnectionFactory {
private static final JedisPool jedisPool;

static {
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);// 最多允许创建多少个连接
poolConfig.setMaxIdle(0);// 最大空闲连接
poolConfig.setMinIdle(0);// 最小空闲数
poolConfig.setMaxWaitMillis(1000);// 当连接池空了之后,多久没获取到Jedis对象,就超时

// 创建连接池
jedisPool = new JedisPool(
poolConfig,
"192.168.10.100",
6379,
1000,
"123456"
);

}

// 通过连接池获取jedis对象
public static Jedis getJedis() {
return jedisPool.getResource();
}
}

(2)使用连接池测试连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JedisConnectionFactoryTest {
@Test
public void testString() {
// 通过Jedis连接池建立连接
Jedis jedis = JedisConnectionFactory.getJedis();
// 选择库
jedis.select(0);
// 测试连接
String pong = jedis.ping();
System.out.println("连接成功:" + pong);
// 添加、修改键值对
String set = jedis.set("name", "小明");
// 获取对应键值
System.out.println(jedis.get("name"));
// 关闭连接
jedis.close();
}
}

Spring Data Redis

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,用于简化与Redis数据库的交互。

主要功能

  • 连接管理:Spring Data Redis可以自动管理Redis连接池,确保高效地与Redis服务器进行交互,并提供连接的复用和连接的线程安全性。

  • 客户端整合:Spring Data Redis支持整合不同的Redis客户端,如Jedis、Lettuce和Redisson。从Spring Boot 2.X开始,默认使用Lettuce作为Redis客户端技术,如果需要切换到Jedis,可以引入相应的依赖库。

  • 数据访问:Spring Data Redis提供了高度封装的RedisTemplate类,统一了操作Redis的API,方便进行数据的存储和读取等操作。

  • 缓存支持:Spring Data Redis集成了Spring框架的缓存抽象,通过简单的配置即可将数据存储到Redis缓存中,提高应用程序的性能和响应速度。

  • 对象映射:Spring Data Redis 通过使用 RedisTemplate 和 RedisSerializer 接口,将对象序列化和反序列化到 Redis 数据库中。你可以将 Java 对象直接存储到 Redis 中,并且支持复杂的对象结构。

  • 发布订阅:Spring Data Redis支持Redis的发布和订阅功能,可以实时地将消息广播给多个消费者。

  • 事务支持:Spring Data Redis提供了对Redis事务的支持,可以将多个操作包装在一个事务中进行原子性的执行。

  • 分布式锁:Spring Data Redis提供了分布式锁的实现,通过Redis的原子操作来保证多个进程/线程之间的数据一致性和互斥性。

  • 序列化/反序列化:Spring Data Redis提供了多种可选择的策略(RedisSerializer)来对数据进行序列化和反序列化,以便将Java对象存储到Redis中,并支持复杂的对象结构。

RedisTemplate类

(1)Spring Data Redis提供了一个高度封装的RedisTemplate类,统一API来操作Redis

API简介
redisTemplate.opsForValue();操作String类型数据
redisTemplate.opsForHash();操作Hash类型的数据
redisTemplate.opsForList();操作List类型的数据
redisTemplate.opsForSet();操作set类型数据
redisTemplate.opsForZSet();操作zset类型数据

(2)RedisTemplate 类提供了对 key 的 bound(绑定) 便捷化操作 API,可以通过 bound 封装指定的 key,然后进行一系列的操作而无须显式的再次指定 Key。

API简介
redistempalate.boundValueOps绑定 string 类型的 key
redistempalate.boundListOps绑定 list 类型的 key
redistempalate.boundHashOps绑定 hash 即 map 类型的 key
redistempalate.boundSetOps绑定 set 类型的 key
redistempalate.boundZSetOps绑定 zset 类型的 key

使用案例

(1)创建maven工程,引入pom依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- Redis依赖:使用Redis和Spring Data Redis,以及Jedis 客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redis依赖:使用Redis和Spring Data Redis Reactive,以及Lettuce 客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- 集成redis所需common-pool2对象池(lecttuce缓存连接池)-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>

(2)添加application.yaml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spring:
redis:
host: 192.168.10.100 # Redis服务器地址
port: 6379 # Redis服务器连接端口
password: 123456 # Redis服务器连接密码(默认为空)
database: 0 # Redis数据库索引(默认为0)
timeout: 3000ms # 连接超时时间(毫秒)
client-type: lettuce # 配置客户端技术类型,设置为lettuce
lettuce:
pool:
max-active: 8 # 连接池最大连接数,默认8(使用负值表示没有限制)
max-wait: 100ms # 连接池最大阻塞等待时间,默认-1(使用负值表示没有限制)
max-idle: 8 # 连接池最大空闲连接,默认8
min-idle: 0 # 连接池中最小空闲连接,默认0
jedis:
pool:
max-active: 8 # 连接池最大连接数,默认8(使用负值表示没有限制)
max-wait: 100ms # 连接池最大阻塞等待时间,默认-1(使用负值表示没有限制)
max-idle: 8 # 连接池最大空闲连接,默认8
min-idle: 0 # 连接池中最小空闲连接,默认0

(3)创建启动类

1
2
3
4
5
6
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}

(4)创建Controller包,创建RedisTestController类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@RestController
@RequestMapping("/Redis")
public class RedisTestController {
@Autowired
private RedisTemplate<String, String> redisTemplate;

@GetMapping
public HashMap<Object, Object> test() {
// 获取操作对象
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();// 操作Redis的iStrng类型
HashOperations<String, String, Object> hashOperations = redisTemplate.opsForHash();// 操作Redis的Hash类型
ListOperations<String, String> listOperations = redisTemplate.opsForList();// 操作Redis的List类型
SetOperations<String, String> setOperations = redisTemplate.opsForSet();// 操作Redis的Set类型
ZSetOperations<String, String> zSetOperations = redisTemplate.opsForZSet();// 操作Redis的ZSet类型

// 存入数据
valueOperations.set("stringKey", "stringValue");// 存储键值对到Redis的String类型中
hashOperations.put("hashKey", "field", "hashValue");// 存储字段和值到Redis的Hash类型中
listOperations.leftPush("listKey", "listValue");// 将元素插入到列表的左侧
setOperations.add("setKey", "setValue");// 向Redis的Set类型中添加元素
zSetOperations.add("zSetKey", "zSetValue", 0);// 向Redis的ZSet类型中添加元素,并指定分数

// 获取对应键值
String stringValue = valueOperations.get("stringKey");// 获取Redis中指定键的值
Object hashValue = hashOperations.get("hashKey", "field");// 获取Redis中指定Hash类型的键和字段的值
List<String> listValue = listOperations.range("listKey", 0L, -1L);// 获取列表元素范围内的所有元素
Set<String> setValue = setOperations.members("setKey");// 获取集合中的所有元素
Set<ZSetOperations.TypedTuple<String>> zSetValue = zSetOperations.rangeWithScores("zSetKey", 0L, -1L);// 获取有序集合中指定范围内的元素及其分数

// 放入列表并返回
HashMap<Object, Object> resultMap = new HashMap<>();
resultMap.put("stringKey", stringValue);
resultMap.put("hashKey", hashValue);
resultMap.put("listKey", listValue);
resultMap.put("setKey", setValue);
resultMap.put("zSetKey", zSetValue);

return resultMap;
}
}

(5)启动服务并访问http://localhost:8080/Redis查看页面数据,并打开Redis客户端对比数据是否一致

序列化与反序列化

(1)Redis序列化与反序列化是指将数据在Redis中进行存储和读取时的编码和解码过程

  • 序列化:将数据对象转换为字节流,并将其存储到Redis中的过程
  • 反序列化:将存储在Redis中的字节流,重新转换为原始数据对象的过程

(2)在Redis中,常见的序列化格式有以下几种

  • JDK序列化:使用Java自带的序列化机制将对象序列化为字节流存储到Redis中

  • JSON序列化:将数据对象转换为JSON字符串存储在Redis中,在需要时从Redis中获取JSON字符串并反序列化回原始对象

  • 字符串序列化:将数据对象直接转换为字符串形式存储在Redis中

  • MessagePack序列化:将数据对象序列化为紧凑的二进制数据进行存储和传输。

  • Protobuf序列化:使用Google的Protobuf协议将数据对象序列化为二进制数据,适用于高性能和跨平台需求。

(3)Spring Data Redis(SDR)提供了多种序列化/反序列化策略(RedisSerializer)来处理数据存储和读取操作,可以通过设置RedisTemplate的valueSerializer属性来选择使用合适的序列化方式,提高Redis的存储效率和读取速度,常见的序列化方式包括以下几种

序列化器简介
JdkSerializationRedisserializer默认的序列化方式,使用Java的标准序列化机制,将对象转换为字节数组进行存储 适用于需要存储Java对象的场景
StringRedisSerializer简单的字符串序列化,将对象转换为字符串进行存储,是最轻量级和高效的策略 适用于Key或者Value为字符串的场景,根据指定的字符集对数据的字节序列进行编码成字符串
GenericToStringSerializer将对象转换为字符串进行存储,适用于简单对象的序列化和反序列化。
GenericFastJsonRedisSerializer使用FastJson库将对象转换为JSON格式进行存储【需要引入FastJson依赖包】
GenericJackson2JsonRedisSerializer使用Jackson库将对象转换为JSON格式进行存储【需要引入Jackson依赖包】
JacksonJsonRedisSerializer使用Jackson库将对象转换为JSON格式进行存储【需要引入Jackson依赖包】
Jackson2JsonRedisSerializer使用Jackson库将对象转换为JSON格式进行存储【需要引入Jackson依赖包】
OxmSerializer使用XML格式进行存储,提供了将Java Bean与XML之间的转换能力【需要spring-oxm模块的支持】 使用此策略编程会有一定的难度,而且效率较低,不推荐使用

Redis集群

主从复制模式(Master-Slave Replication)

(1)主从同步/复制是最基本的 Redis 集群模式之一

(2)在这种模式下,存在一个主节点(Master)和一个或多个从节点(Slaves)

  • 主节点(Master)负责处理写操作和读操作,并将写操作的数据同步到所有从节点上
  • 从节点(Slaves)只能执行读操作,通过定期同步主节点的数据,以便在主节点故障时提供高可用性和读取负载均衡

(3)在主从复制模式下,主节点(Master)宕机了,从节点(Slaves)是不能变为主服务器进行写操作的(另外一个隐患)

哨兵模式(Sentinel Mode)

(1)哨兵模式是在主从同步/复制模式的基础上进一步扩展的

(2)哨兵模式引入了哨兵节点(Sentinels),任务是监视主节点的状态,并在主节点故障时,使用投票机制来选举新的主节点,进行故障转移,确保系统的高可用性

(3)在一个一主多从的集群中,可以启用多个哨兵进行监控以保证集群足够稳健,这种情况下,哨兵不仅监控主从服务,哨兵之间也会相互监控,建议哨兵至少3个并且是奇数

集群模式(Cluster Mode)

(1)Cluster模式是Redis 3.0版本引入的新特性,它是Redis集群的原生分布式方案。

(2)Cluster模式通过分片(Sharding)将数据分散存储在多个节点上,并在节点之间进行自动数据重分布和故障转移。

(3)Cluster模式中没有明确的主节点和从节点的区分,每个节点都是平等的,都可以处理读写操作,都会负责一部分数据的存储和处理

(4)Cluster模式中的每个节点都知道整个集群的状态和数据分布,当某个节点宕机时,其他节点会自动检测到该节点的状态变化,并负责接管它负责的槽位(slot)

(5)Cluster模式支持自动扩展和缩容,当集群中的节点发生故障或新节点加入时,集群会自动进行数据迁移和重新平衡,实现集群规模的动态调整,保证数据的可用性和负载均衡

Redis消息队列

消息队列(点对点模式)

一对一,消费者主动拉取数据,消息收到后消息清除

(1)消息生产者生产消息发送到队列(Queue)中,然后消息消费者从队列(Queue)中取出并且消费消息。

(2)消息被消费以后,队列(Queue) 中不再有存储,所以消息消费者不可能消费到已经被消费的消息。

(3)队列(Queue) 支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费

消息队列(发布/订阅模式)

一对多,主题给订阅的消费者分发消息,消费者消费数据之后不会清除消息

(1)消息生产者将消息发布到 主题(topic) 中,同时有多个消息消费者订阅(subscribe)消费该消息。

(2)和点对点方式不同,发布到 主题(topic) 的消息会被所有订阅者消费。

(3)发布到 topic 的消息会保存7天

Redis发布订阅使用案例

(1)在两个窗口启动Redis

1
2
3
4
# 指定配置文件启动Redis
redis-server /opt/redis.conf
# 使用redis-cli连接Redis,启动Redis客户端
redis-cli -a 123456

(2)一个窗口订阅频道channel(消费者)

1
2
3
4
5
127.0.0.1:6379> subscribe channel
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel"
3) (integer) 1 # 返回值为当前已订阅的频道数量

(3)另一个窗口向频道channel发布消息hello(生产者)

1
2
127.0.0.1:6379> publish channel "hello"
(integer) 1 # 返回值为订阅当前频道的客户端数量

(4)打开第一个客户端可以看到发送的消息

1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> subscribe channel [channel ...]
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel"
3) (integer) 1 # 返回值为当前已订阅的频道数量

# 以下是客户端接收到的订阅消息
1) "message"
2) "channel"
3) "hello"

Redis持久化

Redis持久化简介

Redis的数据默认保存在内存中,如果不做任何持久化操作,当Redis关闭时数据将会丢失。为了防止数据丢失,Redis提供了两种持久化方式(RDB和AOF),会异步将数据保存在硬盘上

方式简介
RDB(Redis DataBase)快照方式(默认)
AOF(Append Only File)写日志方式

Redis持久化流程

RDB持久化

RDB持久化流程

  1. 触发条件:根据配置文件中的save参数设置触发持久化的条件,或者通过执行SAVE或BGSAVE命令手动触发持久化操作。
  2. 快照生成:当触发持久化条件满足时,Redis会创建一个子进程来进行快照生成工作。在创建子进程之前,Redis会使用复制-on-write机制来保证在持久化过程中不会对数据进行修改。
  3. 子进程工作:子进程会遍历整个数据集,并将数据写入到一个临时的RDB文件中(默认为dump.rdb)。这个过程是在内存中进行的,不会阻塞主进程。
  4. 文件写入:当子进程完成数据写入后,Redis会用新的RDB文件替换原来的旧文件。这个过程是原子性的,确保持久化文件的完整性。
  5. 持久化完成:持久化过程完成后,Redis会向客户端发送一个事件通知,告知持久化的结果以及生成的RDB文件的路径。
  6. 宕机恢复:假如Redis宕机,下次重启时可以通过读取这个快照文件来恢复数据

RDB备份

将*.rdb的文件拷贝到别的地方即可

RDB恢复

关闭Redis,把备份的文件拷贝到工作目录下,重新启动Redis,会自动检查dump.rdb进行恢复备份数据

RDB触发方式

(1)自动方式:通过配置文件中的save配置项,Redis会根据配置的情况进行自动持久化

1
2
3
4
5
# 多个save仅是设置多个条件,是一个if elseif的关系(save 间隔时间 更新的key的数量)
# save "" # 给空值表示不启用RDB,主从机制的时候,不能关闭 RDB 机制
save 900 1 # 每 900 秒,有 1 个数据更改就触发一次
save 300 10 # 每 300 秒,有 10 个数据更改就触发一次
save 60 10000 # 每 60 秒,有 10000 个数据更改就触发一次

(2)手动方式:当要进行持久化的时候,输入save或bgsave命令进行持久化

save和bgsave命令本质上差不多,一个同步运行一个在异步运行,都是单线程阻塞(执行保存时不能执行其他命令)

触发方式简介
save同步运行,执行save命令后,会阻塞当前Redis服务器,不能处理其他命令,RDB过程完成后,如果存在老的RDB文件,就把新的替代掉旧的。 实际生产环境客户端可能都有几万或者是几十万,这种方式显然不可取。(不建议)
bgsave异步运行,执行bgsave命令后,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求 具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。 阻塞只发生在fork阶段,一般时间很短,基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令

RDB优点

  1. 性能高:RDB持久化是在内存中进行的操作,生成的快照文件紧凑,恢复数据速度较快,适合用于备份和灾难恢复。
  2. 适用于大量数据:由于RDB文件是二进制格式,相比于AOF文件,占用较小的磁盘空间,适用于保存大规模的数据集。
  3. 简单并易于使用:RDB持久化配置相对简单,只需通过设置save参数来控制触发持久化的条件即可。
  4. 兼容性好:RDB文件是一个二进制格式的文件,可以在不同版本的Redis之间进行兼容和迁移。

RDB缺点

  1. 数据丢失:由于RDB持久化是异步的,Redis意外崩溃时可能会导致最近的数据丢失。RDB文件生成的时间间隔较长,如果Redis在最近一次生成RDB文件之后崩溃,那么这个时间段内的数据就无法恢复。
  2. 不适合实时数据:由于RDB持久化是定期生成快照,数据只能保持到最后一次快照生成时的状态,并不能实时保存每个写操作,因此不适合要求数据实时一致性的场景。
  3. 恢复时间较长:当使用RDB文件进行恢复时,需要将整个RDB文件加载到内存中,对于大规模的RDB文件,恢复的时间可能比较长。
  4. 不利于数据修改频繁的场景:由于RDB持久化是全量备份,在每次持久化时都需要遍历整个数据集,对于数据修改频繁的场景,持久化过程可能会对性能产生影响。

AOF持久化

AOF持久化流程

客户端的请求写命令(读操作不记录)会被append追加到AOF缓冲区内(只许追加文件但不可以改写文件),AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;Redis服务重启时,会重新load加载AOF文件中的写操作,从前到后执行达到数据恢复的目的;

AOF备份

将*.aof的文件拷贝到别的地方,启动系统即加载

AOF恢复

关闭Redis,把备份的文件拷贝到工作目录下,重新启动Redis,会自动检查appendonly.aof进行恢复备份数据

AOF异常恢复

如遇到AOF文件损坏,可通过命令进行恢复

1
/usr/local/bin/redis-check-aof--fix appendonly.aof

AOF实现方式

(1)AOF默认不开启,打开配置文件,找到appendonly配置项,配置开启

1
2
appendonly yes  # 开启AOF,AOF文件的保存路径,同RDB的路径一致
appendfilename "appendonly.aof" #默认生成文件的名称

(2)AOF的追加同步频率设置(appendfsync)

1
2
3
# appendfsync always  # 如果要高可靠,就选择 Always 策略
appendfsync everysec # 如果允许数据丢失一点,但又想性能高,就选择 Everysec 策略(默认)
# appendfsync no # 如果要高性能,就选择 No 策略
策略概括优点缺点
always每次操作都同步写入日志(不推荐)安全,始终同步,不丢失数据慢,IO开销较大
everysec每秒同步写入日志(推荐)快,每秒同步,每秒一次fsync如果宕机,可能丢1秒数据
no把同步时机交给操作系统(不推荐)快,不用管不可控,持久化没保证

AOF重写机制

(1)AOF采用文件追加方式,文件会越来越大,为避免出现此种情况,Redis提供了重写机制,当 AOF 文件大小或文件大小的增长率到一定程度时,Redis 能够调用 bgrewriteaof 对日志文件进行重写 (或者手动调用 bgrewriteaof 进行重写)

(2)重写可以节约大量磁盘空间,减少恢复时间,但是每次重写有一定的负担,可以通过配置改进,建议生产环境配置到 G 的级别,因为默认 64mb 太小了,触发重写机制会 fork 进程,造成阻塞

1
2
auto-aof-rewrite-min-size 64mb  # 设置重写的基准值,当AOF 文件大小大于该配置项时自动开启重写
auto-aof-rewrite-percentage 100 # 设置重写的基准值,超过原文件大小的100%时自动开启重写

(3)指定是否在后台 bgrewriteaof 期间调用 fsync,默认为 no

配置简介
no-appendfsync-on-rewrite=yes不写入aof文件只写入缓存,用户请求不会阻塞, 如果宕机,会丢失这段时间的缓存数据。(降低数据安全性,提高性能)
no-appendfsync-on-rewrite=no表示要调用 fsync,还是会把数据往磁盘里刷 但是遇到重写操作,可能会发生阻塞,不会造成数据丢失(数据安全,但是性能降低)

AOF重写流程

(1)bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。

(2)Redis 主进程先进行 fork() ,创建出一个子进程执行重写操作,保证主进程不会阻塞。

(3)子进程遍历Redis内存中数据到临时文件,

(4)主进程的写请求,同时写入aof_buf内存缓冲区和aof_rewrite_buf重写缓冲区,保证原AOF文件完整,以及新AOF文件生成期间的新的数据修改动作不会丢失(即使 rewrite 失败了,也能保证旧数据是正确的)

(5)子进程写入结束后,向主进程发信号,主进程把aof_rewrite_buf中的数据写入到新的AOF文件。

(6)使用新的AOF文件覆盖旧的AOF文件,完成AOF重写(原子操作,不会被中断)

AOF优点

备份机制更稳健,丢失数据概率更低 可读的日志文本,通过操作AOF稳健,可以处理误操作

AOF缺点

比起RDB占用更多的磁盘空间、恢复备份速度要慢 每次读写都同步的话,有一定的性能压力。 存在个别Bug,造成不能恢复

Redis应用问题解决

缓存穿透

什么是缓存穿透?

用户查询数据的过程中,如果 redis 数据库没有数据,就会穿透到数据库中查询,如果大量的请求都直接查找数据库,就会造成对数据库很大的压力,最终导致数据库压力过大而崩溃

缓存穿透的原因

可能原因一:Redis查询不到数据库,数据库中的数据无法同步到缓存 ,从而穿透到数据库中查询

可能原因二:黑客攻击,黑客使用非正常url持续访问

缓存穿透的解决方案

方案简介
缓存空值当请求的数据不存在 Redis 也不存在数据库的时候,设置一个缺省值(null或None)进行缓存 当后续再次进行查询则直接返回空值或者缺省值,设置空结果的过期时间会很短,最长不超过五分钟
设置可访问的名单(白名单)使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量, 每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问
采用布隆过滤器在数据写入数据库的同时将这个id 同步到到布隆过滤器中,当请求的 id 不存在布隆过滤器中, 说明该请求查询的数据一定没有在数据库中保存,就不要去数据库查询了
进行实时监控当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

布隆过滤器(Bloom Filter)

(1)布隆过滤器(Bloom Filter)是1970年由布隆提出的,实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数),由于布隆过滤器(Bloom Filter)需要缓存全量的 key,所以要求全量的 key 数量不大,100 亿条数据(约3.5GB内存)以内最佳

优缺点简介
优点空间效率和查询时间都远远超过一般的算法
缺点有一定的误识别率和删除困难

(2)布隆过滤器的原理

1
2
3
1、首先分配一块内存空间做 bit 数组,数组的 bit 位初始值全部设为 0
2、加入元素时,采用 k 个相互独立的 Hash 函数计算,然后将元素 Hash 映射的 K 个位置全部设置为 1
3、检测 key 是否存在,仍然用这 kHash 函数计算出 k 个位置,如果位置全部为 1,则表明 key 存在,否则不存在。

(3)布隆过滤器的误判:哈希函数会出现碰撞,所以布隆过滤器会存在误判。

误判率:布隆过滤器判断某个 key 存在,但它实际不存在的概率

因为存的是 key 的 Hash 值,而非 key 的值,所以有概率存在这样的 key,它们内容不同,但多次 Hash 后的 Hash 值都相同。

布隆过滤器判断不存在的 key ,则是 100% 不存在的

布隆过滤器判断存在的 key,不一定真的存在,因为如果这个 key 存在,那它每次 Hash 后对应的 Hash 值位置肯定是 1,而不会是 0。

缓存击穿

缓存击穿的简介

某个key在redis中过期,此时若有大量并发请求访问这个过期的key(秒杀),请求发现缓存过期,会从数据库加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把数据库压垮。

缓存击穿的特点

数据库压力瞬间升高、Redis是正常运行的、 Redis中并没有大量key过期

缓存击穿的解决方案

方案简介
预设置在Redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
实时调整现场监控哪些数据热门,实时调整key的过期时长
使用锁给数据库加上锁,当发现缓存失效的时候,不是立即从数据库加载数据,而是先获取分布式锁, 获取锁成功才执行数据库查询和写数据到缓存的操作, 获取锁失败则说明当前有线程在执行数据库查询操作,当前线程睡眠一段时间在重试。 这样只让一个请求去数据库读取数据(效率低)
设置key永不过期对于热点数据,不设置过期时间,把请求都放在缓存中处理,充分把 Redis 高吞吐量性能利用起来(不推荐)
过期时间+随机值设计缓存的过期时间时,使用公式:过期时间=baes 时间+随机时间。 即相同业务数据写缓存时,在基础过期时间之上,再加一个随机的过期时间, 让数据在未来一段时间内慢慢过期,避免瞬时全部过期,对 DB 造成过大压力

缓存雪崩

缓存雪崩的简介

缓存中的key大批量在同一时刻过期,此时大量的用户访问这些数据,只能到数据库中进行大量查询,导致数据库查询压力瞬间增大,与缓存击穿不同的是,缓存雪崩是大批量的key同时过期

缓存雪崩的解决方案

方案简介
构建多级缓存架构nginx缓存 + redis缓存 + 其他缓存(ehcache等)
使用锁或队列用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写
从而避免失效时大量的并发请求落到底层存储系统上(不适用高并发情况)
设置过期标志更新缓存记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存
将缓存失效时间分散开比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机
这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件
接口限流如果访问的不是核心数据的时候,在查询的方法上加上接口限流保护(比如设置 10000 req/s)
如果访问的是核心数据接口,缓存不存在允许从数据库中查询并设置到缓存中
这样的话,只有部分请求会发送到数据库,减少了数据库的压力
限流是指在业务系统的请求入口前端控制每秒进入系统的请求数,避免过多的请求被发送到数据库。

Redis宕机导致的缓存雪崩

(1)一个 Redis 实例能支撑 10 万的 QPS,而一个数据库实例只有 1000 QPS,一旦 Redis 宕机,会导致大量请求打到数据库,从而发生缓存雪崩。

(2)Redis宕机导致的缓存雪崩解决方案

方案简介
服务熔断和接口限流在业务系统中,针对高并发的使用服务熔断来有损提供服务从而保证系统的可用性
服务熔断是从缓存获取数据发现异常,直接返回错误数据给前端,防止所有流量打到数据库导致宕机
服务熔断和限流属于在发生了缓存雪崩,如何降低雪崩对数据库造成的影响的方案。
构建高可用的缓存集群如果 Redis 的主节点故障宕机了,从节点还可以切换成为主节点,继续提供缓存服务
避免了由于缓存实例宕机而导致的缓存雪崩问题,使用Redis 哨兵集群或者 Redis Cluster 集群

缓存一致性