文章目录

  • Redis
    • Redis 安装
      • 1、Redis 安装教程
    • Redis操作
      • 1、redis hash分页获取
      • 2、redis序列化
      • 3、jedis和redisson区别和选择
      • 4、redis pipeline管道操作
    • Redis 原理和实战
      • 一、redis实现原理
        • redis 完全内存存储
        • Redis单线程处理网络请求
        • redis 使用连接池
        • redis 过期策略
        • lua 脚本操作redis指令集
        • redis 配置详解
      • 二、redis 数据类型
        • redis 存储原理
        • redis string
        • redis list
        • redis hash
        • redis set
        • 位图等其他特殊数据类型
        • redis sorted set
      • 三、redis 集群和高可用
        • redis 高可用
        • redis 数据持久化机制
          • RDB机制(默认开启):
          • AOF机制(默认关闭):
        • redis 复制和主从架构
        • redis 哨兵模式
        • redis 集群架构
      • 四、redis 实战
        • redis可以做什么
        • redis有效期设置
        • redis 进程与CPU进行绑定
        • redis 分布式锁
        • redis获取所有key
        • redis 查看当前库所有的key个数
        • redis 清楚当前库的所有key
      • 五、redis 高性能原理
        • redis 不重启将RDB模式切换到AOF模式
      • 六、redis面试题
        • Redis为什么这么快
        • redis 为什么使用单线程
        • redis 使用缓存常见问题
        • 缓存和数据库一致性问题
        • redis分布式锁超时问题
        • redis big key

Redis

Redis 安装

1、Redis 安装教程

https://blog.csdn.net/weixin_43883917/article/details/114632709
https://www.cnblogs.com/pengpengdeyuan/p/14435601.html
https://www.redis.com.cn/redis-installation.html

2、CentOS 7安装Redis
https://cloud.tencent.com/developer/article/1344011
注意:有两种安装方式,一个是软件包安装,一个是压缩文件自己解压缩编译安装

Redis操作

1、redis hash分页获取

https://www.cnblogs.com/xin-jun/p/11138326.html
注意:当hash中fileds数量较少时,分页不会生效只有达到一定数量才会生效

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

注:
redis中文官网
redis官网
redis使用手册
深入理解redis原理

2、redis序列化

https://www.jianshu.com/p/bbf684456eeb

3、jedis和redisson区别和选择

https://cloud.tencent.com/developer/article/1594456#:~:text=Jedis%E4%B8%8ERedisson.%20Jedis%E6%98%AFRedis%E7%9A%84Java%E5%AE%9E%E7%8E%B0%E7%9A%84%E5%AE%A2%E6%88%B7%E7%AB%AF%EF%BC%8C%E5%85%B6API%E6%8F%90%E4%BE%9B%E4%BA%86%E6%AF%94%E8%BE%83%E5%85%A8%E9%9D%A2%E7%9A%84Redis%E5%91%BD%E4%BB%A4%E7%9A%84%E6%94%AF%E6%8C%81%EF%BC%9B.,Redisson%E5%AE%9E%E7%8E%B0%E4%BA%86%E5%88%86%E5%B8%83%E5%BC%8F%E5%92%8C%E5%8F%AF%E6%89%A9%E5%B1%95%E7%9A%84Java%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%EF%BC%8C%E5%92%8CJedis%E7%9B%B8%E6%AF%94%EF%BC%8C%E5%8A%9F%E8%83%BD%E8%BE%83%E4%B8%BA%E5%A4%8D%E6%9D%82%EF%BC%8C%E4%B8%8D%E4%BB%85%E6%94%AF%E6%8C%81%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%93%8D%E4%BD%9C%EF%BC%8C%E4%B8%94%E8%BF%98%E6%94%AF%E6%8C%81%E6%8E%92%E5%BA%8F%E3%80%81%E4%BA%8B%E5%8A%A1%E3%80%81%E7%AE%A1%E9%81%93%E3%80%81%E5%88%86%E5%8C%BA%E7%AD%89Redis%E7%89%B9%E6%80%A7%E3%80%82.%20Redisson%E7%9A%84%E5%AE%97%E6%97%A8%E6%98%AF%E4%BF%83%E8%BF%9B%E4%BD%BF%E7%94%A8%E8%80%85%E5%AF%B9Redis%E7%9A%84%E5%85%B3%E6%B3%A8%E5%88%86%E7%A6%BB%EF%BC%8C%E4%BB%8E%E8%80%8C%E8%AE%A9%E4%BD%BF%E7%94%A8%E8%80%85%E8%83%BD%E5%A4%9F%E5%B0%86%E7%B2%BE%E5%8A%9B%E6%9B%B4%E9%9B%86%E4%B8%AD%E5%9C%B0%E6%94%BE%E5%9C%A8%E5%A4%84%E7%90%86%E4%B8%9A%E5%8A%A1%E9%80%BB%E8%BE%91%E4%B8%8A%E3%80%82.

Jedis与Redisson
Jedis是Redis的Java实现的客户端,其API提供了比较全面的Redis命令的支持;

Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为复杂,不仅支持字符串操作,且还支持排序、事务、管道、分区等Redis特性。

Redisson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。

编程模型
Jedis中的方法调用是比较底层的暴露的Redis的API,也即Jedis中的Java方法基本和Redis的API保持着一致,了解Redis的API,也就能熟练的使用Jedis。

而Redisson中的方法则是进行比较高的抽象,每个方法调用可能进行了一个或多个Redis方法调用。

如下分别为Jedis和Redisson操作的简单示例:

Jedis jedis = …;jedis.set("key", "value");List<String> values = jedis.mget("key", "key2", "key3");
Redisson操作map:Redisson redisson = …RMap map = redisson.getMap("my-map"); // implement java.util.Mapmap.put("key", "value");map.containsKey("key");map.get("key");

可伸缩性

Jedis使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。

Redisson使用非阻塞的I/O和基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作。

数据结构
Jedis仅支持基本的数据类型如:String、Hash、List、Set、Sorted Set。

Redisson不仅提供了一系列的分布式Java常用对象,基本可以与Java的基本数据结构通用。

还提供了许多分布式服务,其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service)。

在分布式开发中,Redisson可提供更便捷的方法。

第三方框架整合
Redisson提供了和Spring框架的各项特性类似的,以Spring XML的命名空间的方式配置RedissonClient实例和它所支持的所有对象和服务;
Redisson完整的实现了Spring框架里的缓存机制;
Redisson在Redis的基础上实现了Java缓存标准规范;
Redisson为Apache Tomcat集群提供了基于Redis的非黏性会话管理功能。该功能支持Apache Tomcat的6、7和8版。
Redisson还提供了Spring Session会话管理器的实现。

4、redis pipeline管道操作

https://blog.csdn.net/w1lgy/article/details/84455579
管道相对普通执行来说性能更好,但是不能保证一次管道整体执行的原子性,就是有可能有的命令执行失败,但是执行顺序还是不变的。

Redis 原理和实战

一、redis实现原理

redis 完全内存存储

首先要明白redis是一个数据库 redis是一个内存数据库, 所有数据基本上都存在于内存当中, 会定时以追加或者快照的方式刷新到硬盘中(这是redis为高可用做的持久化方案,redis正常读写数据还是基于内存的。), 由于redis是一个内存数据库, 所以读取写入的速度是非常快的, 所以经常被用来做数据, 页面等的缓存。

Redis单线程处理网络请求

注意:redis 单线程指的是网络请求模块使用了一个线程,即一个线程处理所有网络请求,其他模块仍用了多个线程。

因为CPU不是Redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽,既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。关于redis的性能,官方网站也有,普通笔记本轻松处理每秒几十万的请求。

多路I/O复用模型,非阻塞IO
下面举一个例子,模拟一个tcp服务器处理30个客户socket。
假设你是一个监考老师,让30个学生解答一道竞赛考题,然后负责验收学生答卷,你有下面几个选择:

  1. 第一种选择:按顺序逐个验收,先验收A,然后是B,之后是C、D。。。这中间如果有一个学生卡住,全班都会被耽误。
    这种模式就好比,你用循环挨个处理socket,根本不具有并发能力。
  2. 第二种选择:你创建30个分身,每个分身检查一个学生的答案是否正确。 这种类似于为每一个用户创建一个进程或者线程处理连接。
  3. 第三种选择,你站在讲台上等,谁解答完谁举手。这时C、D举手,表示他们解答问题完毕,你下去依次检查C、D的答案,然后继续回到讲台上等。此时E、A又举手,然后去处理E和A。。。
    这种就是IO复用模型,Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。
    这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。

redis的读取和处理性能非常强大,因为redis大部分场景只涉及读写,并没有复杂的算法,读写操作的复杂度为O(N)或则O(log(N)),所以一般服务器的cpu都不会是性能瓶颈。
redis的性能瓶颈主要集中在内存和网络方面
但是如果你确实需要充分使用多核cpu的能力,那么需要在单台服务器上运行多个redis实例(主从部署/集群化部署),并将每个redis实例和cpu内核进行绑定。

redis 使用连接池

Redis 是单进程单线程的,它利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。

Redis 是基于内存的数据库,使用之前需要建立连接,如果客户端进行大量的建立断开连接需要消耗大量的时间。

再假设 Redis 服务器与客户端分处在异地,虽然基于内存的 Redis 数据库有着超高的性能,但是底层的网络通信却占用了一次数据请求的大量时间,因为每次数据交互都需要先建立连接,假设一次数据交互总共用时 30ms,超高性能的 Redis 数据库处理数据所花的时间可能不到 1ms,也即是说前期的连接占用了 29ms,连接池则可以实现在客户端建立多个连接并且不释放,当需要使用连接的时候通过一定的算法获取已经建立的连接,使用完了以后则还给连接池,这就免去了数据库连接所占用的时间。

Jedis resource = jedisPool.getResource();

注意,这行代码。我们从 JedisPool 中获取的仅仅是一个连接。至于多个连接到达单进程单线程的 Redis 之后怎么处理,就与你的线程池无关了。

实际上,Redis 在收到多个连接后,采用的是非阻塞 IO,基于 epoll 的多路 IO 复用。

然后采用队列模式将并发访问变为串行访问,对于串行访问,本身操作内存就很快,Redis 采用一个线程来处理就再正常不过了!

redis 过期策略

lua 脚本操作redis指令集

使用lua脚本可以保证多个指令执行的原子性。其中redisson中就运用了大量的lua脚本来保证原子性操作。

使用案例:
由于redis分布式锁如果在锁超时后,会继续执行解锁操作,即删除key,这样会造成其他获取redis锁的线程在执行过程中被删除key,更甚引发连锁效应,导致后续所有锁都失效。

有一个稍微安全一点的方案是 将锁的 value 值设置为一个随机数,释放锁时先匹配随机数是否一致,然后再删除 key,这是为了 确保当前线程占有的锁不会被其他线程释放,除非这个锁是因为过期了而被服务器自动释放的。

redis 配置详解

二、redis 数据类型

redis 存储原理

redis string

二进制安全的字符串

redis list

按插入顺序排序的字符串元素的集合。他们基本上就是链表(linked lists)。

redis hash

由field和关联的value组成的map。field和value都是字符串的。这和Ruby、Python的hashes很像

redis set

不重复且无序的字符串元素的集合。

位图等其他特殊数据类型

Bit arrays (或者说 simply bitmaps): 通过特殊的命令,你可以将 String 值当作一系列 bits 处理:可以设置和清除单独的 bits,数出所有设为 1 的 bits 的数量,找到最前的被设为 1 或 0 的 bit,等等。
HyperLogLogs: 这是被用于估计一个 set 中元素数量的概率性的数据结构。别害怕,它比看起来的样子要简单…参见本教程的 HyperLogLog 部分。D

redis sorted set

Sorted sets,类似Sets,但是每个字符串元素都关联到一个叫score浮动数值(floating number value)。里面的元素总是通过score进行着排序,所以不同的是,它是可以检索的一系列元素。(例如你可能会问:给我前面10个或者后面10个元素)。

三、redis 集群和高可用

redis 高可用

在介绍Redis高可用之前,先说明一下在Redis的语境中高可用的含义。

我们知道,在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999% 等等)。但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务(如主从分离、快速容灾技术),还需要考虑数据容量的扩展、数据安全不会丢失等。

在Redis中,实现高可用的技术主要包括持久化、复制、哨兵和集群,下面分别说明它们的作用,以及解决了什么样的问题。

  • 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
  • 复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
  • 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。
  • 集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

redis 数据持久化机制

Redis为了提高数据的读写性能,将数据存放在内存中,但是内存中的数据会随着Redis Server的停止或者其他故障而丢失,为了防止这种情况的出现,我们需要将内存中的数据保存到磁盘上,以便Redis Server重启时能从磁盘中快速恢复原有数据。这个过程就叫做Redis持久化。

redis 客户端读写的数据都是在内存中,只有持久化的数据异步写到硬盘。

Redis持久化有三种机制:

  • 1、RDB机制(Redis DataBase):也叫快照机制,将某一时刻的内存快照数据以二进制方式写入磁盘

  • 2、AOF机制(Append Only File):也叫文件追加机制,将所有的操作命令按顺序记录下来,追加到特殊的文件中

  • 3、混合机制:顾名思义,为RDB和AOF机制的组合,综合了各自的优点,先将当前的数据以RDB方式写入文件,再将操作命令以AOF方式写入文件。

RDB机制(默认开启):

特点:

  1. 定期实现数据备份,但是可能丢失数据.
    解释:如果现实中redis写入1000次才进行一次备份,当redis宕机时,会有未完成备份的数据丢失。
  2. 该操作的执行的效率最高.
  3. 该持久化方式是redis的默认策略.
  4. RDB模式做的是内存的快照,能够有效的节省磁盘空间,控制持久化文件的大小!!!
    解释:相当于照相机,每次快照redis内存然后覆盖之前的.rdb文件
  5. RDB模式持久化文件是加密的

RDB机制会生成全量rdb文件,在指定的时间间隔内生成数据集快照(point-in-time snapshot),可通过redis.conf配置文件名和目录。

RDB机制有两种方式:

1、主动方式:即手动触发,涉及两个操作命令:SAVE 和 BGSAVE,区别在于前者会堵塞主进程,导致主进程不能处理其他命令,直到RDB过程完成;后者会创建子进程,利用COW技术,由子进程负责RDB过程,主进程可以继续处理其他命令。

2、自动方式:由配置文件来完成,自动触发 BGSAVE命令,在redis.conf配置文件中有如下配置:

bgsave 过程:

  • redis 使用fork函数复制一份当前的进程(父进程)的副本(子进程)
  • 父进程继续接受并处理客户端发来的命令, 而子进程开始把内存中的数据写入硬盘中的临时文件
  • 当子进程写入完成所有的数据后会用该临时文件替换旧的RDB文件,至此一次快照操作完成.
//RDB模式是默认开启 不需要显示的配置,如果AOF显示配置启动 那RDB模式和相关配置也就失效了
//RDB 可以设置多个save策略 满足任何一个策略都会触发RDB生成镜像文件
save 900 1 /*900秒内至少发生1次key变换*/save 60 10 /*60秒内至少发生10次key变换*/dbfilename dump.rdb /*快照文件名 固定生成此文件名的rdb文件 恢复数据也是找这个文件*/ dir ./ /*快照文件存放目录*/rdbcompression yes /*是否压缩*/

RDB模式 redis重新启动:Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存,一般情况下1GB的快照文件载入到内存的时间大约20-30分钟。

由于redis使用fork来复制一份当前进程,那么子进程就会占有和主进程一样的内存资源,比如说主进程8G内存,那么在备份的时候必须保证有16G内存,要不然会启用虚拟内存,性能非常差。

触发创建rdb镜像文件的几种情况:

  • 客户端向 Redis 发送 BGSAVE 命令,Redis 会调用 fork 创建一个子进程,然后子进程负责将快照写入硬盘,而父进程继续处理命令请求。
  • 客户端向 Redis 发送 SAVE 命令,此时 Redis 将开始创建快照,并且在完成之前不再响应其它命令。
  • 用户设置 save 配置选项,比如 save 60 10000,那么从 Redis 最近一次创建快照算起,当 “60 秒内有 10000 次写入” 这个条件被满足时, Redis 就会自动触发 BGSAVE 命令。如果用户设置了多个 save 配置选项,那么当任意一个 save 配置满足时,Redis 就会触发一次 BGSAVE 命令。
  • 当 Redis 通过 SHUTDOWN 命令接收到关闭服务器的请求时,或者接收到 TERM命令时,会执行一个 SAVE 命令,并且阻塞所有的客户端,不再执行任何请求。在 SAVE 命令执行结束后关闭服务器。
  • 当一个 Redis 服务器连接另一个 Redis 服务器,并向对方发送 SYNC 命令来开始一次复制操作的时候,如果主服务器没有在执行 BGSAVE 操作,或者主服务器并非刚执行完 BGSAVE,那么主服务器会执行 BGSAVE 命令。

RDB文件自动间隔保存原理
redis.conf中可以配置多个save配置,并且他们之间的关系是或,可以组成一个saveparams数组进行同时执行判断。

除了配置save的saveparams数组外,服务器状态还维持着一个dirty计数器,以及一个lastsave属性:

  • dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改

  • lastsave属性时一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间

Redis的服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足的话,就执行BGSAVE命令。

RDB文件结构

RDB文件的最开头是REDIS部分,这个部分的长度为5字节,保存着“REDIS”五个字符。通过这五个字符,程序可以在载入文件时,快速检查所载入的文件是否RDB文件。

db_version长度为4字节,它的值时一个字符串表示的整数,这个整数记录了RDB文件的版本号。

database部分包含着零个或任意多个数据库,以及各个数据库中的键值对数据:

  • 如果服务器的数据库状态为空(所有数据库都是空的),那个这个部分也为空,长度为0字节。

  • 如果服务器的数据库状态为非空,那么这个部分也为非空。

EOF常量的长度为1字节,这个常量标志着RDB文件正文内容的结束。

check_sum是一个8字节的无符号整数,保存着一个校验和,这个校验和是程序通过REDIS、db_version、databases、EOF四个部分的内容进行计算得出的。服务器在载入RDB文件时,会将载入数据所计算出的校验和与check_sum所记录的校验和进行对比,以此来检查RDB文件是否有出错或者损坏的情况出现。

databases部分

每个非空数据库在RDB文件中都可以保存为SELECTDB、db_number、key_value_pairs三个部分。

SELECTDB常量的长度为1字节,当读入程序遇到这个值的时候,它直到接下来要读入的将是一个数据库号码。

Db_number保存着一个数据库号码,根据号码的大小不同,这个部分的长度可以是1字节、2字节或者5字节。当程序读入db_number部分之后,服务器会调用SELECT命令,根据读入的数据库号码进行数据库切换,使得之后读入的键值对可以载入到正确的数据库中。

Key_value_pairs部分保存了数据库中的所有键值对数据,如果键值对带有过期时间,那么过期时间也会和键值对保存在一起。

key_value_pairs部分

不带过期时间的键值对在RDB文件中由TYPE、key、value三部分组成。

TYPE记录了value的类型,长度为1字节:

REDIS_RDB_TYPE_STRINGREDIS_RDB_TYPE_LISTREDIS_RDB_TYPE_SETREDIS_RDB_TYPE_ZSETREDIS_RDB_TYPE_HASHREDIS_RDB_TYPE_LIST_ZIPLISTREDIS_RDB_TYPE_SET_INTSETREDIS_RDB_TYPE_ZSET_ZIPLISTREDIS_RDB_TYPE_HASH_ZIPLIST

带过期时间的键值对在RDB文件中的结构

  • EXPIRETIME_MS常量的长度为1字节,它告知读入程序,接下来要读入的将是一个以毫秒为单位的过期时间。

  • ms是一个8字节长的带符号整数,记录着一个以毫秒为单位的UNIX时间戳。

value的编码

1、字符串对象2、列表对象3、集合对象4、哈希表对象5、有序集合对象6、INTSET编码的集合7、ZIPLIST编码的列表、哈希表或者有序集合

不包含任何键值对的RDB文件

当一个RDB文件没有包含任何数据库数据时,这个RDB文件将由以下四个部分组成:

五个字节的“REDIS”字符串。四个字节的版本号(db_version)。一个字节的EOF常量。八个字节的校验和(check_sum)。

RDB文件的压缩

RDB文件过大时,是可以压缩的,Redis默认开启压缩,当然也可以通过配置rdbcompression参数来禁用压缩。

压缩和不压缩的优缺点:
压缩可以减少磁盘空间占用,但是会消耗CPU资源;不压缩减少CPU消耗,但是会增大CPU的占用。具体适用不适用压缩要看需求和服务器资源。

RDB优缺点:

优点:RDB是一个紧凑压缩的二进制文件,代表了某个时间点的数据快照,可将文件存储文件服务器等,可用于某个时间点的快速容灾,恢复速度快

缺点:备份需要开启子进程,并且是全量备份,需要时间较长,无法做到秒级备份,两次备份之间数据容易丢失

AOF机制(默认关闭):

RDB机制是全量持久化,效率较低,我们需要一种更加高效的机制AOF,AOF是以命令的形式持久化,将所有的写操作命令以redis协议的格式追加到AOF文件的尾部,随着写入操作的不断增加AOF文件不断变大,这时候需要重写AOF文件(删除无效key,同一个key操作多次只保留最后一次等)

AOF在redis.conf配置文件中有如下配置:


#此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能
##只有在“yes”下,aof重写/文件同步等特性才会生效
appendonly yes#指定aof文件名称
appendfilename appendonly.aof#指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec
# appendfsync 三种策略只能配置其中一个策略 正常是每秒追加刷盘一次 everysec
appendfsync always /*一次命令追加一次,是同步执行,就是每次执行写入命令要同步把该命令写入到aof文件*/appendfsync everysec /*每秒追加刷盘一次,是异步执行,生产环境使用次模式较多,平衡了刷盘频率*/appendfsync no /*永不刷盘*/
#在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”
no-appendfsync-on-rewrite no#aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”
auto-aof-rewrite-min-size 64mb#相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。
#每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1 + p)之后
#触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。
auto-aof-rewrite-percentage 100

fsync 刷盘

AOF 日志是以文件的形式存在的,当程序对 AOF 日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将数据刷回到磁盘的。只有当数据被刷盘写入到硬盘,这时数据才是真正被持久化。

这就意味着如果机器突然宕机,AOF 日志内容可能还没有来得及完全刷到磁盘中,这个时候就会出现日志丢失。那该怎么办?

Linux 的 glibc 提供了 fsync(int fd) 函数可以将指定文件的内容强制从内核缓存刷到磁盘。只要 Redis 进程实时调用 fsync 函数就可以保证 AOF 日志不丢失。但是 fsync 是一个磁盘 IO 操作,它很慢!如果 Redis 执行一条指令就要 fsync 一次,那么 Redis 高性能的地位就不保了。

所以在生产环境的服务器中,Redis 通常是每隔 1s 左右执行一次 fsync 操作,这个 1s 的周期是可以配置的。这是在数据安全性和性能之间做的一个折中,在保持高性能的同时,尽可能使得数据少丢失。

Redis 同样也提供了另外两种策略,一个是永不 fsync——让操作系统来决定何时同步磁盘,这样做很不安全,另一个是来一个指令就 fsync 一次——结果导致非常慢。这两种策略在生产环境中基本很少使用,了解一下即可。

1、AOF文件:AOF机制将收到的每一个写命令按顺序追加到aof文件中。

由于每一个写命令都被顺序记录下来,我们只要将这个aof文件执行一遍,即可得到全部的数据。

2、AOF文件重写:AOF机制也带来了一个问题:AOF文件会越来越大。为此,Redis提供了一个AOF文件重写的功能,将内存中的全部数据以命令的方式全部重写为一个新的AOF文件,来替换之前的AOF文件。

AOF重写机制触发条件:

  • 手动触发:直接调用bgrewriteaof命令。

  • 自动触发:是按照已下两个条件来判断是否触发重写

    • 根据auto-aof-rewrite-min-size:当aof文件大于多少字节后触发重写,默认64mb
    • auto-aof-rewrite-percentage:当文件大小超过百分比触发。默认100,意味着文件超过2倍触发重写。

默认:第一次超过64mb重写,第二次超过64mb并且auto-aof-rewrite-min-size*2时重写—未验证

Redis 提供了 bgrewriteaof 指令(手动执行)用于对 AOF 日志进行瘦身。其原理就是开辟一个子进程对内存进行遍历,转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件中。序列化完毕后再将操作期间发生的增量 AOF 日志追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了。

auto-aof-rewrite-percentage 是设置aof rewrite触发时机的一个参数,当当前的aof文件大小超过上一次rewrite后aof文件的百分比后触发rewrite。

200 改为 800 ,即当前的aof文件超过上一次重写后aof文件的8倍时才会再次rewrite

27.0.0.1:6379> config get auto-aof-rewrite-percentage
1) "auto-aof-rewrite-percentage"
2) "200"
127.0.0.1:6379> config set auto-aof-rewrite-percentage 800
OK

举个例子:对于同一个key,我们分别执行了N次set操作,则AOF文件会记录N条记录,但是最终内存中的value是唯一的,只有最后一次set起到决定作用,那我们就可以根据内存中的key和value,将N条记录写成一条set操作命令记录,来替换之前的AOF文件,达到压缩目的


aof数据文件损坏的时候如何修复

对于错误格式的AOF文件,先进行备份,然后采用redis-check-aof–fix命令进行修复,修复后使用diff-u对比数据的差异,找出丢失的数据,有些可以人工修改补全。AOF文件可能存在结尾不完整的情况,比如机器突然掉电导致AOF尾部文件命令写入不全。Redis为我们提供了aof-load-truncated配置来兼容这种情况,默认开启。加载AOF时,当遇到此问题时会忽略并继续启动

存储模式性能和安全比较:

1.性能

Snapshot方式的性能是要明显高于AOF方式的,原因有两点:

采用2进制方式存储数据,数据文件比较小,加载快速。
存储的时候是按照配置中的save策略来存储,每次都是聚合很多数据批量存储,写入的效率很好,而AOF则一般都是工作在实时存储或者准实时模式下。相对来说存储的频率高,效率却偏低。

2.数据安全

AOF数据安全性高于Snapshot存储,原因:

Snapshot存储是基于累计批量的思想,也就是说在允许的情况下,累计的数据越多那么写入效率也就越高,但数据的累计是靠时间的积累完成的,那么如果在长时间数据不写入RDB,但Redis又遇到了崩溃,那么没有写入的数据就无法恢复了,但是AOF方式偏偏相反,根据AOF配置的存储频率的策略可以做到最少的数据丢失和较高的数据恢复能力。
说完了性能和安全,这里不得不提的就是在Redis中的Rewrite的功能,AOF的存储是按照记录日志的方式去工作的,那么成千上万的数据插入必然导致日志文件的扩大,Redis这个时候会根据配置合理触发Rewrite操作,所谓Rewrite就是将日志文件中的所有数据都重新写到另外一个新的日志文件中,但是不同的是,对于老日志文件中对于Key的多次操作,只保留最终的值的那次操作记录到日志文件中,从而缩小日志文件的大小。这里有两个配置需要注意:
auto-aof-rewrite-percentage 100 (当前写入日志文件的大小占到初始日志文件大小的某个百分比时触发Rewrite)
auto-aof-rewrite-min-size 64mb (本次Rewrite最小的写入数据良)

两个条件需要同时满足。

redis 生成rdb和aof的过程

1、BGSAVE命令执行时的服务器状态:

因为BGSAVE命令的保存工作是由子进程执行的,所以在子进程创建RDB文件的过程中,Redis服务器仍然可以继续处理客户端的命令请求,但是,在BGSAVE命令执行期间,服务器处理SAVE、BGSAVE、BGREWITEAOF三个命令的方式会和平时有所不同。SAVE 和 BGSAVE 是对应rdb文件生成,BGREWRITEAOF是对应aof文件生成命令。

  • 在BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝,服务器禁止SAVE命令和BGSAVE命令同时执行是为了避免父进程和子进程同时执行两个rdbSave调用,防止产生竞争条件。

  • 在BGSAVE命令执行期间,客户端发送的BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE命令也会产生竞争条件。

  • BGREWRITEAOF和BGSAVE另个命令不能同时执行:

  • 如果BGSAVE命令正在执行,客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行。

  • BGREWRITEAOF命令正在执行,客户端发送的BGSAVE命令会被服务器拒绝。

BGREWRITEAOF和BGSAVE都由子进程执行,都同时执行大量的磁盘写操作,对性能有影响。

redis 重启后rdb和aof加载过程

RDB文件的载入工作是在服务器启动时自动执行的,Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件存在,就会自动载入RDB文件。

在AOF开启这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件完整。

redis服务器在加载rdb或则aof文件的过程中,会一直处理阻塞状态,直到加载完成。

正常来说rdb加载更快,因为rdb是数据的内存映射,直接载入到内存,而aof是命令,需要逐条执行。

1、AOF持久化的数据恢复实验

(1)先仅仅打开RDB,写入一些数据,然后kill -9杀掉redis进程,接着重启redis,发现数据没了,因为RDB快照还没生成
(2)打开AOF的开关,启用AOF持久化
(3)写入一些数据,观察AOF文件中的日志内容
其实你在appendonly.aof文件中,可以看到刚写的日志,它们其实就是先写入os cache的,然后1秒后才fsync到磁盘中,只有fsync到磁盘中了,才是安全的,要不然光是在os cache中,机器只要重启,就什么都没了
(4)kill -9杀掉redis进程,重新启动redis进程,发现数据被恢复回来了,就是从AOF文件中恢复回来的
redis进程启动的时候,直接就会从appendonly.aof中加载所有的日志,把内存中的数据恢复回来

2、AOF破损文件的修复

如果redis在append数据到AOF文件时,机器宕机了,可能会导致AOF文件破损
用redis-check-aof --fix命令来修复破损的AOF文件

3、AOF和RDB同时工作
(1)如果RDB在执行snapshotting操作,那么redis不会执行AOF rewrite; 如果redis再执行AOF rewrite,那么就不会执行RDB snapshotting

(2)如果RDB在执行snapshotting,此时用户执行BGREWRITEAOF命令,那么等RDB快照生成之后,才会去执行AOF rewrite

(3)同时有RDB snapshot文件和AOF日志文件,那么redis重启的时候,会优先使用AOF进行数据恢复,因为其中的日志更完整

集群中的持久化策略:

  • 遍历整个内存,大块写磁盘会加重系统负载。
  • AOF 的 fsync 是一个耗时的 IO 操作,它会降低 Redis 性能,同时也会增加系统 IO 负担。

所以通常 Redis 的主节点是不会进行持久化操作,持久化操作主要在从节点进行。从节点是备份节点,没有来自客户端请求的压力,它的操作系统资源往往比较充沛。

redis 过期键删除

用户往往为 Redis 中的键设定过期时间,因此需要一定的策略来删除过期键,可以有三种策略:

  • 定时删除,即通过定时器在过期时间到达的时候删除过期的键。这种方式的优点是节省内存,不会因为大量的过期键占用内存资源,而缺点则是消耗 CPU 资源,尤其是过期键数量较多的时候,删除操作消耗太长时间,降低了 Redis 的响应时间。
  • 惰性删除,即在每次获取某个键的时候判断是否过期,如果未过期,则正常返回其值,否则删除这个键,返回空。这种方式的优点是节省 CPU 资源,但是消耗了内存。尤其是过期键数量较多的时候,大量内存被无效的键占用,相当于内存泄露。
  • 定期删除,即每隔一段时间周期对数据库中的键进行扫描,但是只扫描其中一部分,力求在内存和 CPU 之间达到一个平衡。
    从上面 3 种策略可以看出,单用第一个肯定是不行的,Redis 的响应时间至关重要。第二个则是比较好的方式,在获取键的时候判断是否过期并决定是否删除,它的缺点是很多键无法及时删除。如果一个过期键再也没有被访问,那么它将永远留在内存中,而第三种方式正好可以弥补。

Redis 中过期键的删除策略正是惰性删除与定期删除的结合。

持久化过程对过期键处理

了解了过期键的删除策略后,下面看下键的过期时间对持久化的影响。

在生成 RDB 文件的过程中,如果一个键已经过期,那么其不会被保存到 RDB 文件中。在载入 RDB 的时候,要分两种情况:

  • 如果 Redis 以主服务器的模式运行,那么会对 RDB 中的键进行时间检查,过期的键不会被恢复到 Redis 中。
  • 如果 Redis 以从服务器的模式运行,那么 RDB 中所有的键都会被载入,忽略时间检查。在从服务器与主服务器进行数据同步的时候,从服务器的数据会先被清空,所以载入过期键不会有问题。

对于 AOF 来说,如果一个键过期了,那么不会立刻对 AOF 文件造成影响。因为 Redis 使用的是惰性删除和定期删除,只有这个键被删除了,才会往 AOF 文件中追加一条 DEL 命令。在重写 AOF 的过程中,程序会检查数据库中的键,已经过期的键不会被保存到 AOF 文件中。

在运行过程中,对于主从复制的 Redis,主服务器和从服务器对于过期键的处理也不相同:

  • 对于主服务器,一个过期的键被删除了后,会向从服务器发送 DEL 命令,通知从服务器删除对应的键
  • 从服务器接收到读取一个键的命令时,即使这个键已经过期,也不会删除,而是照常处理这个命令。
  • 从服务器接收到主服务器的 DEL 命令后,才会删除对应的过期键。
    这么做的主要目的是保证数据一致性,所以当一个过期键存在于主服务器时,也必然存在于从服务器。

总结
本文对 Redis 的两种持久化方式进行了简要的梳理,分析了 Redis 删除过期键的策略以及对持久化的影响。理解了这部分内容不仅可以让我们对 Redis 的使用更加得心应手,对于学习 Redis 的其它内容如复制的过程也会很有帮助。

redis 复制和主从架构

使用master-slave的replication复制模式 和sentinal 哨兵模式,单这种仅仅是主从架构的高可用,不是集群高可用。但是redis集群功能强大,直接集成了replication和sentinal的功能。

redis 哨兵模式

redis 集群架构

redis集群能支持的最大节点数量16384 个。原因如下:

  • Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 算法计算的结果,对 16384 取模后放到对应的编号在 0-16383 之间的哈希槽,集群的每个节点负责一部分哈希槽。

四、redis 实战

注:redis大量的连接创建导致cpu飙高问题排查

redis可以做什么

注:redis应用场景及实例

  • 记录帖子的点赞数、评论数和点击数 (hash)。
  • 记录用户的帖子 ID 列表 (排序),便于快速显示用户的帖子列表 (zset)。
  • 记录帖子的标题、摘要、作者和封面信息,用于列表页展示 (hash)。
  • 记录帖子的点赞用户 ID 列表,评论 ID 列表,用于显示和去重计数 (zset)。
  • 缓存近期热帖内容 (帖子内容空间占用比较大),减少数据库压力 (hash)。
  • 记录帖子的相关文章 ID,根据内容推荐相关帖子 (list)。
  • 如果帖子 ID 是整数自增的,可以使用 Redis 来分配帖子 ID(计数器)。
  • 收藏集和帖子之间的关系 (zset)。
  • 记录热榜帖子 ID 列表,总热榜和分类热榜 (zset)。
  • 缓存用户行为历史,进行恶意行为过滤 (zset,hash)。
  • 记录编号增加当前值,并且进行编号进行后续累加操作(INCRBY和其他相似命令计数)。

redis有效期设置

可以通过expire key time来设置缓存有效期

  • 如果没有设置有效期,即使内存用完,redis 自动回收机制也是看设置了有效期的,不会动没有设定有效期的,如果清理后内存还是满的,就不再接受写操作。
Redis无论有没有设置expire,他都会遵循redis的配置好的删除机制,在配置文件里设置:
redis最大内存不足"时,数据清除策略,默认为"volatile-lru"。
volatile-lru  ->对"过期集合"中的数据采取LRU(近期最少使用)算法.如果对key使用"expire"指令指定了过期时间,那么
此key将会被添加到"过期集合"中。将已经过期/LRU的数据优先移除.如果"过期集合"中全部移除仍不能满足内存需求,将OOM.
allkeys-lru ->对所有的数据,采用LRU算法
volatile-random ->对"过期集合"中的数据采取"随即选取"算法,并移除选中的K-V,直到"内存足够"为止. 如果如果"过期集合"
中全部移除全部移除仍不能满足,将OOM
allkeys-random ->对所有的数据,采取"随机选取"算法,并移除选中的K-V,直到"内存足够"为止
volatile-ttl ->对"过期集合"中的数据采取TTL算法(最小存活时间),移除即将过期的数据.
noeviction ->不做任何干扰操作,直接返回OOM异常

redis 进程与CPU进行绑定

redis单线程执行过程中 “我们不能任由操作系统负载均衡,因为我们
自己更了解自己的程序,所以,我们可以手动地为其分配CPU核,而不会过多地占用CPU0,或是让我们关键进程和一堆别的进程挤 在一起。”。
在文章中提到了 Linux下的一个工具,taskset,可以设定单个进程运行的CPU。
以下均以redis-server
举例。

1)显示进程运行的CPU

命令taskset -p 21184

显示结果:

pid 21184’s current affinity mask: ffffff

注:21184是redis-server运行的 pid

显示结果的ffffff实际上是二进制24个低位均为1的bitmask,每一个1对应于1个CPU,表示该进程在24个CPU上运行

2)指定进程运行在某个特定的CPU上

命令taskset -pc 3 21184

显示结果:

pid 21184’s current affinity list: 0-23

pid 21184’s new affinity list: 3

注:3表示CPU将只会运行在第4个CPU上(从0 开始计数)。

3)进程启动时指定CPU

命令taskset -c 1 ./redis-server …/redis.conf

影响 Redis 性能的因素之一
CPU 是一个重要的影响因素,由于是单线程模型,Redis 更喜欢大缓存快速 CPU, 而不是多核
在多核 CPU 服务器上面,Redis 的性能还依赖 NUMA 配置和 处理器绑定位置。 最明显的影响是 redis-benchmark 会随机使用 CPU 内核。为了获得精准的结果,

需要使用固定处理器工具(在 Linux 上可以使用 taskset)。 最有效的办法是将客户端和服务端分离到两个不同的 CPU 来高校使用三级缓存

redis 分布式锁

重要 重要 重要

注:
redisson分布式锁实现
redisson和分布式锁介绍
redis锁的陷阱

redisson对于redis锁有很好的实现 可以使用或则参考redisson的源码设计
把redis分布式锁原理详细讲出,然后分别讲述各种异常情况的处理方式。!!!!

实际项目中使用的比较多的分布式锁的实现有redis+lua脚本的方式。其实现要点有三个:

  • set命令要用 setkey value px milliseconds nx;保证原子性
  • value要具有唯一性,释放锁时要验证value值,不能误解锁;
  • 解锁要使用lua脚本,也是为了保证原子性

分布式锁获取失败处理

  • 重试
  • 超时等待,可以看看redisson的实现,最常用的做法是,设置一个等待时间,等待时间过去后仍然获取不到锁,则返回失败(无法获取锁)。

RedLock

redis获取所有key

1、keys * 命令可以查询出所有的key并返回
2、scan cursor count 10 通过游标来控制范围遍历
scan cursor match * count 10 也可以通过match进行匹配

cursor初始可以设置为0,然后下次根据返回结果中的第一行来设置下一次的cursor

redis 查看当前库所有的key个数

dbsize (默认当前库)

select 1
dbsize

redis 清楚当前库的所有key

flushdb (默认当前库)
select 1
flushdb

flushall (删除所有库)

五、redis 高性能原理

Redis的高并发和快速原因

  1. redis是基于内存的,内存的读写速度非常快;

  2. redis是单线程的,省去了很多上下文切换线程的时间;

  3. redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。

redis 不重启将RDB模式切换到AOF模式

要实现不重启从rdb切换到aof,redis的版本必须要在2.2以上。

[root@pyyuc /data 22:23:30]#redis-server -v
Redis server v=4.0.10 sha=00000000:0 malloc=jemalloc-4.0.3 bits=64 build=64cb6afcf41664c

环境准备

redis.conf服务端配置文件

daemonize yes
port 6379
logfile /data/6379/redis.log
dir /data/6379
dbfilename  dbmp.rdb
save 900 1                    #rdb机制 每900秒 有1个修改记录
save 300 10                    #每300秒        10个修改记录
save 60  10000                #每60秒内        10000修改记录

启动redis服务端

redis-server redis.conf

登录redis-cli插入数据,手动持久化

127.0.0.1:6379> set name chaoge
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> set addr shahe
OK
127.0.0.1:6379> save
OK

检查RDB文件

[root@pyyuc /data 22:34:16]#ls 6379/
dbmp.rdb  redis.log

备份fdb文件,保证数据的安全

[root@pyyuc /data/6379 22:35:38]#cp dbmp.rdb /opt/

执行命令,开启aof持久化

127.0.0.1:6379> CONFIG set appendonly yes   #开启AOF功能
OK
127.0.0.1:6379> CONFIG SET save ""  #关闭RDB功能
OK

确保数据库的key数量正确

127.0.0.1:6379> keys *
1) "addr"
2) "age"
3) "name"

确保插入新的key,AOF文件会记录

127.0.0.1:6379> set title golang
OK

此时RDB已经正确切换AOF,注意还得修改redis.conf添加AOF设置,不然重启后,通过config set的配置将丢失,还需要再redis配置文件中配置aof配置,以供下次启动redis使用。

六、redis面试题

注:Redis 21问

Redis为什么这么快

1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO; IO多路复用,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争

5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;

6、另外,数据结构也帮了不少忙,Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。

7、还有一点,Redis采用自己实现的事件分离器,效率比较高,内部采用非阻塞的执行方式,吞吐能力比较大。

redis 为什么使用单线程

为什么Redis是单线程的
1.官方答案

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

2.性能指标

关于redis的性能,官方网站也有,普通笔记本轻松处理每秒几十万的请求。

3.详细原因

1)不需要各种锁的性能消耗,单线程可以避免更新数据时额外的加锁解锁操作。

Redis的数据结构并不全是简单的Key-Value,还有list,hash等复杂的结构,这些结构有可能会进行很细粒度的操作,比如在很长的列表后面添加一个元素,在hash当中添加或者删除

一个对象。这些操作可能就需要加非常多的锁,导致的结果是同步开销大大增加。

总之,在单线程的情况下,就不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。

2)单线程多进程集群方案

单线程的威力实际上非常强大,每核心效率也非常高,多线程自然是可以比单线程有更高的性能上限,但是在今天的计算环境中,即使是单机多线程的上限也往往不能满足需要了,需要进一步摸索的是多服务器集群化的方案,这些方案中多线程的技术照样是用不上的。

所以单线程、多进程的集群不失为一个时髦的解决方案。

3)CPU消耗

采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU。

但是如果CPU成为Redis瓶颈,或者不想让服务器其他CUP核闲置,那怎么办?

可以考虑多起几个Redis进程,Redis是key-value数据库,不是关系数据库,数据之间没有约束。只要客户端分清哪些key放在哪个Redis进程上就可以了。

redis 使用缓存常见问题

  • 写入问题

    • 缓存何时写入?并且写时如何避免并发重复写入?
    • 缓存如何失效
    • 缓存和 DB 的一致性如何保证?
  • 经典三连问
    • 如何避免缓存穿透的问题?
    • 如何避免缓存击穿的问题?
    • 如果避免缓存雪崩的问题?

注:穿透是大量不存在的key导致大量请求直接访问数据库,击穿是热点key过期失效直接访问数据库,雪崩是大量key同时失效导致大量请求直接访问数据库。(穿透和击穿主要区分是不存在key和热点key)

解决方案:

穿透: 因为主要是访问大量不存在的key(可能是因为黑客故意攻击短时间内访问大量不存在的key,或则是错误程序访问大量错误的key值或则已经不用的key)

  • 可以缓存空对象,当查询数据库返回结果为空是返回一个对应key值的空对象,并且具体的值可以设置标志,用来跟实际缓存的数据做区分,并且设置较短的过期时间,因为设置过期时间长了没意义。但是如果穿透的key是随机生成的或则量比较大时也会继续穿透到数据库。

  • 还可以使用布隆过滤器BloomFilter,在缓存的基础上投建BloomFilter,如果查询对应的key值不存在直接返回,不会查询数据库,否是是先查询缓存是否存在,如果不存在再查询数据库并重新更新缓存。

雪崩: 缓存由于某些原因大量key访问不到或则缓存服务宕机。(雪崩主要是因为缓存服务挂掉,可以通过搭建高可用架构来防止该问题)

  • 缓存高可用
  • 本地缓存 如果分布式缓存挂掉,也可以将DB查询的结果缓存到本地(如Ehcache、Guava Cache),避免后续请求到达DB中。(这也是通常说的多级缓存)
  • 访问DB限流 通过限制DB每秒的访问数,避免把DB也打挂了,这样做的好处是不至于服务完全挂掉,被限流的请求可以走服务降级,提供一些默认返回值、提示或则甚至是空白值。

击穿: 主要是某个极度热点key在某个时间点过期,恰好这个时间点有大量的并发请求这个key,这些请求发现缓存过期一般都会从DB加载数据回设到缓存,但是这时间大并发的请求可能瞬间会压垮DB