首页 > 数据库 >Redis

Redis

时间:2022-12-19 13:57:40浏览次数:62  
标签:AOF 缓存 Redis redis key 数据

Redis

1. Redis概述

Redis是一种基于键值对(key-value)的NoSQL数据库,是由C语言编写。

1.1 Redis特性

  • 速度快
    • 所有数据都是存放在内存中的。
    • Redis是用C语言实现的,执行速度相对会更快。
    • Redis使用了单线程架构,预防了多线程可能产生的竞争问题。
  • 基于键值对的数据结构服务器
  • 丰富的功能
    • 键过期功能。
    • Lua脚本功能。
    • 流水线(Pipeline)功能,这样客户端能将一批命令一次性传到Redis,减少了网络的开销。
  • 简单稳定
    • Redis的源码很少。
    • Redis使用单线程模型,使得客户端开发变得简单。
  • 客户端语言多
    • 支持Redis的客户端语言也非常多,几乎涵盖了主流的编程语言,例如Java、PHP、Python、C、C++、Nodejs等 。
  • 持久化
    • RDB和AOF。
  • 主从复制
    • 实现了多个相同数据的Redis副本。
  • 高可用和分布式

1.2 Redis使用场景

  • 缓存
    • 对于一些热数据进行缓存,降低后端数据源的压力,而且也提供了键值过期时间设置。
  • 排行榜
    • 照发布时间的排行榜,按照各种复杂维度计算出的排行榜,Redis提供了列表和有序集合数据结构,合理地使用这些数据结构可以很方便地构建各种排行。
      榜系统
  • 计算器
    • 如播放数和浏览量,如果并发量很大对于传统关系型数据的性能是一种挑战。
  • ...

2. Redis的安装与启动(Linux)

2.1 安装Redis

我们使用Redis3.0.7版本为例来安装Redis

$ wget http://download.redis.io/releases/redis-3.0.7.tar.gz
$ tar xzf redis-3.0.7.tar.gz
$ ln -s redis-3.0.7 redis
$ cd redis
$ make
$ make install
  1. 下载Redis指定版本的源码压缩包到当前目录。
  2. 解压缩Redis源码压缩包。
  3. 建立一个redis目录的软连接,指向redis-3.0.7。
  4. 进入redis目录。
  5. 编译(编译之前确保操作系统已经安装gcc)。
  6. 安装。

第3步:建立了一个redis目录的软链接,相当于C语言中的指针,这样做是为了不把redis目录固定在指定版本上,有利于Redis未来版本升级。

第6步:安装是将Redis的相关运行文件放到/usr/local/bin/下,这样就可以在任意目录下执行Redis的命令。可以在任何目录执行redis-cli -v查看Redis的版本。(需要先启动redis服务)

2.2 启动Redis

Redis安装之后,/usr/local/bin/就有了很多执行文件。其中redis-server就是用来启动redis服务的。

cd /usr/local/bin/

启动Redis有如下三种方式:

1) 默认配置

使用默认配置来使用。可以看到默认端口为6379。

[root@localhost bin]# redis-server 
57597:C 08 Dec 16:30:05.831 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
57597:M 08 Dec 16:30:05.832 * Increased maximum number of open files to 10032 (it was originally set to 1024)
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 3.0.7 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 57597
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

57597:M 08 Dec 16:30:05.841 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
57597:M 08 Dec 16:30:05.841 # Server started, Redis version 3.0.7
57597:M 08 Dec 16:30:05.841 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
57597:M 08 Dec 16:30:05.841 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
57597:M 08 Dec 16:30:05.841 * The server is now ready to accept connections on port 6379

因为直接启动无法自定义配置,所以这种方式是不建议生产环境中使用的。

2) 运行配置

redis-server加上要修改配置名和值(可以是多对),没有设置的配置将使用默认配置。

# redis-server --configKey1 configValue1 --configKey2 configValue2

例如,如果要用6380作为端口启动Redis,那么可以执行:

# redis-server --port 6380

虽然运行配置可以自定义配置,但是如果需要修改的配置较多或者希望将配置保存到文件中,不建议使用这种方式。

3) 配置文件

将配置写到指定文件里,例如我们将配置写到了/opt/redis/redis.conf中。

# mkdir /opt/redis
# cp /home/soft/redis/redis.conf /opt/redis/redis.conf

修改/opt/redis/redis.conf中的daemonize为yes,以守护线程启动;取消# requirepass foobared,设置密码。

daemonize yes
requirepass 123456

输入以下命令来启动Redis:

# redis-server /opt/redis/redis.conf

一些重要配置如下:
img

守护进程就是后台运行。

Redis目录下都会有一个redis.conf配置文件,里面就是Redis的默认配置,通常来讲我们会在一台机器上启动多个Redis,并且将配置集中管理在指定目录下,而且配置不是完全手写的,而是将redis.conf作为模板进行修改。

2.3 Redis客户端

2.3.1 命令行客户端

redis-cli -h 127.0.0.1 -p 6379

2.3.2 window图形化工具

我使用的是:Another-Redis-Desktop-Manager.1.5.8。Gitee地址:https://gitee.com/qishibo/AnotherRedisDesktopManager/releases

3. Redis的API和数据结构

3.1 数据库管理

许多关系型数据库,例如MySQL支持在一个实例下有多个数据库存在的,但是与关系型数据库用字符来区分不同数据库名不同,Redis只是用数字作为多个数据库的实现。Redis默认配置中是有16个数据库。

3.1.1 切换数据库

select dbIndex

3.1.2 一些建议

  • 多数据库的使用方式,会让调试和运维不同业务的数据库变的困难,假如有一个慢查询存在,依然会影响其他数据库,这样会使得别的业务方定位问题非常的困难。如果要使用多个数据库功能,完全可以在一台机器上部署多个Redis实例,彼此用端口来做区分。

3.2 键管理

下表是一些对键管理的常用命令。

命令 功能
keys * 查看所有键
dbsize 键总数
exists key 检查键是否存在
del key [key ...] 删除键
expire key seconds 键过期
ttl 返回键的剩余过期时间。有3种返回值。大于等于0的整数:键剩余的过期时间。-1:键没设置过期时间。-2:键不存在
expire key seconds 键过期
type key 键的数据结构类型

3.3 五个基础数据类型

3.3.1 字符串

字符串类型是Redis最基础的数据结构首先键都是字符串类型,而且其他几种数据结构都是在字符串类型基础上构建的。

1) 设置值

set key value [ex seconds] [px milliseconds] [nx|xx]

选项如下:

  • ex seconds:为键设置秒级过期时间。同setex。
  • px milliseconds:为键设置毫秒级过期时间。
  • nx:键必须不存在,才可以设置成功,用于添加。同setnx,可以做分布式锁,官方文档http://redis.io/topics/distlock
  • xx:与nx相反,键必须存在,才可以设置成功,用于更新。
2) 获取值

get key

3) 删除值

del key [key ...]

3.3.2 哈希(h)

哈希类型是指键值本身又是一个键值对结构,形如value={{field1,value1},...{fieldN,valueN}}。

1) 设置值

hset key field value

2) 获取值

hget key field

3) 获取所有值

hvals key

4) 删除field

hdel key field [field ...]

3.3.3 列表(l)

列表(list)类型是用来存储多个有序的字符串。

1) 从右边插入元素

rpush key value [value ...]

2) 从左边插入元素

rpush key value [value ...]

3) 获取指定范围内的元素列表

索引下标有两个特点:

  • 第一,索引下标从左到右分别是0到N-1。
  • 第二,lrange中的end选项包含了自身。

lrange key start end

4) 获取列表指定索引下标的元素

lindex key index

5) 从列表左侧弹出元素

lpop key

6) 从列表右侧弹出

rpop key

7) 删除指定元素

lrem key count value

  • count>0,从左到右,删除最多count个元素。
  • count<0,从右到左,删除最多count绝对值个元素。
  • count=0,删除所有。
8) 修改指定索引下标的元素

lset key index newValue

9) 获取列表长度

llen key

3.3.4 集合(s)

集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。

1) 添加元素

sadd key element [element ...]

2) 删除元素

srem key element [element ...]

3) 查看全部元素

smembers key

3.3.5 有序集合(z)

它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置一个分数(score)作为排序的依据。

1) 添加元素

zadd key score member [score member ...]

2) 删除元素

zrem key member [member ...]

3) 返回指定排名范围的成员
zrange key start end [withscores]
zrevrange key start end [withscores]

3.4 一些建议和注意事项

  • 批量操作命令可以有效提高开发效率。

以上只简单介绍了5个最基本数据类型的操作,更多的可以去官网查找如下: 官方英语中文

4 redis提供的功能

4.1 慢查询分析

所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间,当超过预设阀值,就将这条命令的相关信息(例如:发生时间,耗时,命令的详细信息)记录下来。

4.2 Pipeline

4.2.1 Pipeline概念。

Redis提供了批量操作命令(例如mget、mset等),有效地节约RTT。但大部分命令是不支持批量操作的。它能将一组Redis命令进行组装,通过一次RTT传输给Redis,再将这组Redis命令的执行结果按顺序返回给客户端。这样就能大大减少网络传输的消耗。

Round Trip Time(RTT,往返时间)

4.2.2 原生批量命令与Pipeline对比

  • 原生批量命令是原子的,Pipeline是非原子的。
  • 原生批量命令是一个命令对应多个key,Pipeline支持多个命令。

4.2.3 一些建议和注意事项

  • 每次Pipeline组装的命令个数不能没有节制,否则一次组装Pipeline数据量过大,一方面会增加客户端的等待时间,另一方面会造成一定的网络阻塞,可以将一次包含大量命令的Pipeline拆分成多次较小的Pipeline来完成。
  • Pipeline只能操作一个Redis实例。

4.3 事务与Lua

4.3.1 事务

Redis提供了简单的事务功能,将一组需要一起执行的命令放到multi和exec两个命令之间。在执行exec之前,其他客户端是看不到用户a和用户b的。

> multi
OK
> sadd user:a:follow user:b
QUEUED
> sadd user:b:fans user:a
QUEUED
> exec
1
1
1) 事务中命令错误
  • 命令错误:如将set写成了sett,属于语法错误,会造成整个事务无法执行。
  • 运行时错误:如使用sadd操作一个有序集合,就会报错,但是Redis是不支持回滚的,错误命令之前的都会执行。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> sadd user:a:follow user:b
QUEUED
127.0.0.1:6379> zadd user:b:fans 1 user:a
QUEUED
127.0.0.1:6379> exec
1) (integer) 1
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> sismember user:a:follow user:b
(integer) 1

4.3.2 Lua

Lua语言是在1993年由巴西一个大学研究小组发明,其设计目标是作为嵌入式程序移植到其他应用程序,它是由C语言实现的,虽然简单小巧但是功能强大,所以许多应用都选用它作为脚本语言。Redis将Lua作为脚本语言可帮助开发者定制自己的Redis命令。

4.3.3 Redis与Lua

Lua脚本功能如下三个好处:

  • Lua脚本在Redis中是原子执行的,执行过程中间不会插入其他命令。
  • Lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这些命令常驻在Redis内存中,实现复用的效果。
  • Lua脚本可以将多条命令一次性打包,有效地减少网络开销。

在Redis中执行Lua脚本有两种方法:eval和evalsha。

1) eval

语法与案例如下:

eval 脚本内容 key 个数 key 列表 参数列表
> eval 'return "hello " .. KEYS[1] .. ARGV[1]' 1 redis world
hello redisworld

如果Lua脚本较长,还可以使用redis-cli--eval直接执行文件。

2) evalsha

除了使用eval,Redis还提供了evalsha命令来执行Lua脚本。首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和,evalsha命令使用SHA1作为参数可以直接执行对应Lua脚本,避免每次发送Lua脚本的开销。这样客户端就不需要每次执行脚本内容,而脚本也会常驻在服务端,脚本功能得到了复用。

语法与案例如下:

evalsha 脚本 SHA1 值 key 个数 key 列表 参数列表
[root@localhost he]# redis-cli -a 123456 script load "$(cat lua_get.lua)" 
"7413dc2440db1fea7c0a0bde841fa68eefaf149c"
> evalsha 7413dc2440db1fea7c0a0bde841fa68eefaf149c 1 redis world
hello redisworld

4.3.4 Lua脚本的管理

命令 说明
script load 将Lua脚本加载到Redis内存
script exists 判断Lua脚本是否加载到Redis内存
script flush 清除Redis内存已经加载的所有Lua脚本
script kill 杀掉正在执行的Lua脚本

5 持久化

Redis支持RDB和AOF两种持久化机制,持久化功能有效地避免因进程退出造成的数据丢失问题,当下次重启时利用之前持久化的文件即可实现数据恢复。

5.1 RDB(Redis Database Backup file)

RDB持久化是把当前进程数据生成快照保存到硬盘的过程,触发RDB持久化过程分为手动触发和自动触发,是Redis默认的持久化方式。

5.1.1 手动触发

  1. save命令
    阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。
  2. bgsave命令
    Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。

5.1.2 自动触发

  1. 使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave。
  2. 如果从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点。
  3. 执行debug reload命令重新加载Redis时,也会自动触发save操作。
  4. 默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave。

5.1.3 RDB文件的处理

  • 保存:RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配置指定。可以通过执行config set dir {newDir}config set dbfilename {newFileName}运行期动态执行,当下次运行时RDB文件会保存到新目录。
  • 压缩:Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小,默认开启,可以通过参数config set rdbcompression {yes|no}动态修改。
  • 校验:如果Redis加载损坏的RDB文件时拒绝启动,这时可以使用Redis提供的redis-check-dump工具检测RDB文件并获取应的错误报告。

5.1.4 RDB的优缺点

优点:

  • RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。
  • Redis加载RDB恢复数据远远快于AOF的方式。因为AOF需要一条条去执行命令。

缺点:

  • RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。
  • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题。

5.2 AOF(append only file)

以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。

5.2.1 AOF使用

开启AOF功能需要设置配置:appendonly yes,默认不开启,并配置 dir 自己的目录,不然每次启动时都会以执行命令的相对路径存储持久化文件 。AOF工作流程操作如下:

  1. 命令写入:所有的写入命令会追加到aof_buf(缓冲区)中。
  2. 文件同步:AOF缓冲区根据对应的策略向硬盘做同步操作。
  3. 重写机制:随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的。
  4. 重启加载:当Redis服务器重启时,可以加载AOF文件进行数据恢复。

5.2.2 命令写入

AOF为什么把命令追加到aof_buf中?

  • Redis使用单线程响应命令,如果每次写AOF文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载。
  • Redis可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡。

5.2.3 文件同步

Redis提供了多种AOF缓冲区同步文件策略,由配置参数appendfsync控制,不同值的含义如下:

img

系统调用write和fsync说明:

  • write操作会触发延迟写(delayed write)机制。Linux在内核提供页缓冲区用来提高硬盘IO性能。write操作在写入系统缓冲区后直接返回。同步硬盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
  • fsync针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化。

三种配置说明:

  • 配置为always时,每次写入都要同步AOF文件,在一般的SATA硬盘上,Redis只能支持大约几百TPS写入,显然跟Redis高性能特性背道而驰,不建议配置。
  • 配置为no,由于操作系统每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提升了性能,但数据安全性无法保证。
  • 配置为everysec,是建议的同步策略,也是默认配置,做到兼顾性能和数据安全性。理论上只有在系统突然宕机的情况下丢失1秒的数据

5.2.4 重写机制

AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程,并且会优化命令和清理掉无效命令,这样更小的AOF文件可以更快地被Redis加载。重写后的AOF文件为什么可以变小?有如下原因:

  1. 进程内已经超时的数据不再写入文件。
  2. 旧的AOF文件含有无效命令,如del key1、hdel key2、srem keys、set a111、set a222等。重写使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
  3. 多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list
    c可以转化为:lpush list a b c。

5.2.5 重启加载

AOF持久化开启且存在AOF文件时,优先加载AOF文件。AOF关闭或者AOF文件不存在时,加载RDB文件。
流程如下:
img

11 缓存设计

11.1 缓存的受益和成本

收益如下:

  • 加速读写:因为缓存通常都是全内存的(例如Redis、Memcache),而存储层通常读写性能不够强悍(例如MySQL),通过缓存的使用可以有效地加速读写,优化用户体验。
  • 降低后端负载:帮助后端减少访问量和复杂计算(例如很复杂的SQL语句),在很大程度降低了后端的负载。

成本如下:

  • 数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关。
  • 代码维护成本:加入缓存后,需要同时处理缓存层和存储层的逻辑,增大了开发者维护代码的成本。
  • 运维成本:以Redis Cluster为例,加入后无形中增加了运维成本。

11.2 缓存更新策略

缓存中的数据通常都是有生命周期的,需要在指定时间后被删除或更新,这样可以保证缓存空间在一个可控的范围。但是缓存中的数据会和数据源中的真实数据有一段时间窗口的不一致,需要利用某些策略进行更新。下面将分别从使用场景、一致性、开发人员开发/维护成本三个方面介绍三种缓存的更新策略。

11.2.1 LRU/LFU/FIFO算法剔除

  • 使用场景:剔除算法通常用于缓存使用量超过了预设的最大值时候,如何对现有的数据进行剔除。例如Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的剔除策略。
  • 一致性:要清理哪些数据是由具体算法决定,开发人员只能决定使用哪种算法,所以数据的一致性是最差的。
  • 维护成本:算法不需要开发人员自己来实现,通常只需要配置最大maxmemory和对应的策略即可。开发人员只需要知道每种算法的含义,选择适合自己的算法即可。
算法 说明
LRU 最近使用页置换算法
LFU 最不经常使用页置换算法
FIFO 先入先出算法

11.2.2 超时剔除

  • 使用场景:超时剔除通过给缓存数据设置过期时间,让其在过期时间后自动删除,例如Redis提供的expire命令。如果业务可以容忍一段时间内,缓存层数据和存储层数据不一致,那么可以为其设置过期时间。在数据过期后,再从真实数据源获取数据,重新放到缓存并设置过期时间。例如一个视频的描述信息,可以容忍几分钟内数据不一致,但是涉及交易方面的业务,后果可想而知。
  • 一致性:一段时间窗口内(取决于过期时间长短)存在一致性问题,即缓存数据和真实数据源的数据不一致。
  • 维护成本:维护成本不是很高,只需设置expire过期时间即可,当然前提是应用方允许这段时间可能发生的数据不一致。

11.2.3 主动更新

  • 使用场景:应用方对于数据的一致性要求高,需要在真实数据更新后,立即更新缓存数据。例如可以利用消息系统或者其他方式通知缓存更新。
  • 一致性:一致性最高,但如果主动更新发生了问题,那么这条数据很可能很长时间不会更新,所以建议结合超时剔除一起使用效果会更好。
  • 维护成本:维护成本会比较高,开发者需要自己来完成更新,并保证更新操作的正确性。

缓存的三种常见更新策略的对比如下:
img

11.2.4 一些建议和注意事项

  • 低一致性业务建议配置最大内存和淘汰策略的方式使用。
  • 高一致性业务可以结合使用超时剔除和主动更新,这样即使主动更新出了问题,也能保证数据过期时间后删除脏数据。

11.3 缓存粒度控制

缓存粒度问题,就是是缓存全部属性还是只缓存部分重要属性呢?下面将从通用性、空间占用、代码维护三个角度进行说明。

  • 通用性:存全部数据比部分数据更加通用,但从实际经验看,很长时间内应用只需要几个重要的属性。
  • 空间占用:缓存全部数据要比部分数据占用更多的空间,可能存在以下问题:
    • 全部数据会造成内存的浪费。
    • 全部数据可能每次传输产生的网络流量会比较大,耗时相对较大,在极端情况下会阻塞网络。
    • 全部数据的序列化和反序列化的CPU开销更大。
  • 代码维护:全部数据的优势更加明显,而部分数据一旦要加新字段需要修改业务代码,而且修改后通常还需要刷新缓存数据。

缓存全部数据和部分数据对比如下:
img

11.4 缓存穿透

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中,通常出于容错的考虑,如果从存储层查不到数据则不写入缓存层。整个过程分为如下3步:

  1. 缓存层不命中。
  2. 存储层不命中,不将空结果写回缓存。
  3. 放回空结果。

缓存穿透将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义。造成缓存穿透的基本原因有两个:

  • 第一,自身业务代码或者数据出现问题。
  • 第二,一些恶意攻击、爬虫等造成大量空命中。

11.4.1 解决方案一:缓存空对象

当第2步存储层不命中后,仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。但是缓存空对象会有两个问题:

  • 空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间。比较有效的方法是针对这类数据设置一个较短的过期时间,让其自动剔除。
  • 缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为5分钟,如果此时存储层添加了这个数据,那此段时间就会出现缓存层和存储层数据的不一致。

11.4.2 解决方案二:布隆过滤器拦截

在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截。
如果布隆过滤器认为该用户id不存在,那么就不会访问存储层,在一定程度保护了存储层。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。

布隆过滤器参考:https://juejin.cn/post/6844904007790673933#heading-0

缓存空对象和布隆过滤器方案对比:
img

11.5 无底洞优化

//todo

11.6 缓存雪崩

由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。有以下三种解决方案:

  • 保证缓存层服务高可用性:如果缓存层设计成高可用的,即使个别节点、个别机器、甚至是机房宕掉,依然可以提供服务。
  • 依赖隔离组件为后端限流并降级:无论是缓存层还是存储层都会有出错的概率,可以将它们视同为资源。作为并发量较大的系统,假如有一个资源不可用,可能会造成线程全部阻塞(hang)在这个资源上,造成整个系统不可用。降级机制在高并发系统中是非常普遍的:比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成前端页面是开天窗。在实际项目中,我们需要对重要的资源(例如Redis、MySQL、
    HBase、外部接口)都进行隔离,让每种资源都单独运行在自己的线程池中,即使个别资源出现了问题,对其他服务没有影响。
  • 提前演练:在项目上线前,演练缓存层宕掉后,应用以及后端的负载情况以及可能出现的问题,在此基础上做一些预案设定。

11.7 缓存击穿之热点key

开发人员使用“缓存+过期时间”的策略既可以加速数据读写,又保证数据的定期更新,这种模式基本能够满足绝大部分需求。但是有两个问题如果同时出现,可能就会对应用造成致命的危害:

  • 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。
  • 重建缓存不能在短时间完成,可能是一个复杂计算,例如复杂的SQL、多次IO、多个依赖等。

在缓存失效的瞬间,造成后端负载加大,甚至可能会让应用崩溃。有以下几个解决方案:
1) 互斥锁:此方法只允许一个线程重建缓存,其他线程等待重建缓存的线程执行完,重新从缓存获取数据即可。代码如下:

String get(String key) {
    // 从 Redis 中获取数据
    String value = redis.get(key);
    // 如果 value 为空,则开始重构缓存
    if (value == null) {
        // 只允许一个线程重构缓存,使用 nx ,并设置过期时间 ex
        String mutexKey = "mutext:key:" + key;
        if (redis.set(mutexKey, "1", "ex 180", "nx")) {
            // 从数据源获取数据
            value = db.get(key);
            // 回写 Redis ,并设置过期时间
            redis.setex(key, timeout, value);
            // 删除 key_mutex
            redis.delete(mutexKey);
        }
        // 其他线程休息 50 毫秒后重试
        else {
            Thread.sleep(50);
            get(key);
        }
    }
    return value;
}
  1. 永不过期
    “永远不过期”包含两层意思:
  • 从缓存层面来看,确实没有设置过期时间,所以不会出现热点key过期后产生的问题,也就是“物理”不过期。
  • 从功能层面来看,为每个value设置一个逻辑过期时间,当发现超过逻辑过期时间后,会使用单独的线程去构建缓存。

此方法有效杜绝了热点key产生的问题,但唯一不足的就是重构缓存期间,会出现数据不一致的情况,这取决于应用方是否容忍这种不一致。代码如下:

String get(final String key) {
    V v = redis.get(key);
    String value = v.getValue();
    // 逻辑过期时间
    long logicTimeout = v.getLogicTimeout();
    // 如果逻辑过期时间小于当前时间,开始后台构建
    if (v.logicTimeout <= System.currentTimeMillis()) {
        String mutexKey = "mutex:key:" + key;
        if (redis.set(mutexKey, "1", "ex 180", "nx")) {
            // 重构缓存
            threadPool.execute(new Runnable() {
                public void run() {
                    String dbValue = db.get(key);
                    redis.set(key, (dbvalue,newLogicTimeout));
                    redis.delete(mutexKey);
                }
            });
        }
    }
    return value;
}

两种解决方法对比如下:
img

死锁:如果在redis.delete(mutexKey)发生错误,会造成锁无法释放。

线程池堵塞:如果造成死锁,线程资源就不会得到释放。

SpringBoot下Redis的使用

redis是单线程的,但是当我多个同时连接redis时不是要排队吗?然后执行命令,和我们只使用一个全局连接直接去执行redis指令,有什么区别吗?

  • 对redis而言,有两个性能所在,一个是计算机执行命令的速度,另一个是网络通信性。很显然,执行命令速度不是redis的性能瓶颈,通信才是其瓶颈。如果只使用一个连接,通信的时间中,等待下一个指令的到来的空闲时间,会造成性能浪费。

删除缓存还是更新缓存?

标签:AOF,缓存,Redis,redis,key,数据
From: https://www.cnblogs.com/theheboy/p/16991946.html

相关文章

  • Redis——01 学习
    Redis——01主要学习目标:Redis的特点以及使用场景Redis单机版Redis常用命令~持久化策略~主从复制哨兵集群JedisSpringDataRedisRedis的特点以及使用场......
  • Redis——02 学习
    Redis——02前面了解了Redis以及在Linux的安装,下面了解一些Redis常用的命令。Redis常用命令:Redis是Key-Value形式,Key为字符串类型,而Value的取值类型如下:......
  • Redis——03 学习
    Redis——03Redis持久化策略Redis不仅仅是一个内存型数据库,还具备持久化能力。这个持久化并不是Redis数据库读写的主要内容,跟MySQL不一样,这个持久化只是为了备份,......
  • Redis——05 学习
    Redis——05之前了解了主从复制以及哨兵,接下来了解集群模式。集群(Cluster)先来聊一下前面学的哨兵Sentinel,一般利用哨兵对master节点继续监控,如果master发生异常,则......
  • Redis——04 学习
    Redis——04之前讲了redis的主从复制模型的优点以及搭建效果,但是依然有自己的缺陷,如果主节点宕机,那么其他从节点可能还有运行的,如果此时任意一个从节点升级生了主节点那......
  • Redis——06 学习
    Redis——06将Redis的基本使用以及三种模式进行了学习和了解。接下来就学习如何在Java中以及SpringBoot框架中使用Redis。JedisRedis在Java上的操作多半是集......
  • 高性能Redis服务器注意事项
    摘要昨天简单理了理安装与配置相关的但是很多比较重要的核心性能参数并没有进行学习与探讨就基于昨天理解不深入的地方进行进一步的学习与了解希望能够提高Redis-Serve......
  • Redis7.0.7的简单安装与学习
    Redis7.0.7的简单安装与学习摘要2022.12.18世界杯决赛另外是我感染奥密克戎第五天.高烧已经没了,但是嗓子巨疼.睡不着觉,肝胆学习一下最新的Redis7.0.7第一部分......
  • ubuntu redis sentinel安装部署
    1.命令行安装sudoaptupdatesudoaptinstallredis-serversudoaptinstallredis-sentinel2.查看安装版本#redis-cli--versionredis-cli5.0.73.配置修改......
  • 3《Redis DevOps》三:小功能大用处-常用工具
    ##概述1.慢查询分析,找到有问题的命令进行优化2.RedisShell3.Pipeline,提高客户端性能4.事务与Lua脚本,自定义原子命令5.Bitmaps,字符串的位操作,节省内存6.HyperLogLo......