Loading

[02] Redis 概述

Redis:KV + Cache + Persistence(持久化)

1. 简介

Redis(REmote DIctionary Server)是一个开源的内存中的 key-value 存储系统,它可以用作:数据库、缓存和消息中间件

用 C 语言编写的,遵守 BSD 协议,是一个高性能的 key-value 分布式内存数据库,基于内存运行并支持持久化的 NoSQL 数据库,被称为“数据结构数据库”。

它支持多种类型的数据结构,如字符串(String),散列(Hash),列表(List),集合(Set),有序集合(Sorted Set 或是 ZSet),Bitmaps,Hyperloglogs 和地理空间(Geospatial)索引半径查询。其中常见的数据结构类型有:String、List、Set、Hash、ZSet 这 5 种。这些数据类型都支持 push/pop、add/remove 及取交集、并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis 支持各种不同方式的排序。

Redis 内置了复制(Replication),LUA 脚本(Lua scripting), LRU 驱动事件(LRU eviction),事务(Transactions) 和不同级别的磁盘持久化(Persistence),并通过 Redis 哨兵(Sentinel)和自动分区(Cluster)提供高可用性(High Availability)。

与 Memcached 一样,为了保证效率,数据都是缓存在内存中。Redis 也提供了持久化的选项,这些选项可以让用户将自己的数据保存到磁盘上面进行存储。根据实际情况,可以每隔一定时间将数据集导出到磁盘(快照),或者追加到命令日志中(AOF 只追加文件),他会在执行写命令时,将被执行的写命令复制到硬盘里面。您也可以关闭持久化功能,将 Redis 作为一个高效的网络的缓存数据功能使用。

Redis 不使用表,他的数据库不会预定义或者强制去要求用户对 Redis 存储的不同数据进行关联。

数据库的工作模式按存储方式可分为:硬盘数据库和内存数据库。Redis 将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度极快。

  • 硬盘数据库的工作模式
  • 内存数据库的工作模式

2. 应用场景

  1. 配合关系型数据库做高速缓存
  2. 由于其拥有持久化能力,利用其多样的数据结构存储特定的数据

3. 安装步骤

yum -y install wget gcc automake autoconf libtool make
wget https://download.redis.io/releases/redis-6.2.2.tar.gz
tar -zxvf /opt/software/redis-6.2.2.tar.gz -C /opt/module/
cd /opt/module/redis-6.2.2/
# 编译源码
make
# 启动服务端
./src/redis-server redis.conf
# 启动客户端
./src/redis-cli

解决 zmalloc.h:50:31: 致命错误:jemalloc/jemalloc.h:没有那个文件或目录 问题:① make MALLOC=libc;② make distclean + make

4. 启动客户端和服务端

4.1 查看默认安装目录

  1. redis-benchmark 性能测试工具,可以在自己本子运行,看看自己本子性能如何(服务启动起来后执行)
  2. redis-check-aof 修复有问题的 aof 文件 (rdb 和 aof 是 2 种持久化方式)
  3. redis-check-rdb 修复有问题的 rdb 文件
  4. redis-cli 客户端,操作入口
  5. redis-sentinel Redis 集群使用
  6. redis-server Redis 服务器启动命令

4.2 启动 Redis 服务器

头一回启动服务的时候,默认是前台启动,占用当前进程。现在来设置后台启动:

  1. 备份 redis.conf:拷贝一份 redis.conf 到其他目录,如 /opt/myRedis
  2. 修改 redis.conf:将里面的 daemonize no 改成 yes,让服务在后台启动
  3. 根据自己的配置文件启动 Redis:redis-server /opt/myRedis/redis.conf

4.3 启动 Redis 客户端

4.4 关闭服务器

除了 ps -ef | grep redis 直接 kill [pid] 以外的方式:

  • 在命令行
    • 单实例关闭:redis-cli shutdown
    • 多实例关闭(指定端口关闭):redis-cli -p 6379 shutdown
  • 在 Redis 服务端:shutdown

5. 基本概念

默认 16 个数据库,类似数组下标从 0 开始,初始默认使用 0 号库。使用命令 select <dbid> 来切换数据库:

统一密码管理,所有库都是同样密码,要么都 OK 要么一个也连接不上 // 没有用户,只有密码。

6. 性能测试

Redis 性能测试是通过同时执行多个命令实现的。

Redis 性能测试的基本命令:redis-benchmark [option] [option value]

Redis 性能测试工具可选参数如下所示:

测试示例:

redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -n 100000 -q

SET: 146198.83 requests per second
LPUSH: 145560.41 requests per second

以上示例中主机为 127.0.0.1,端口号为 6379,执行的命令为 set,lpush,请求数为 10000,通过 -q 参数让结果只显示每秒执行的请求数。若去除 -q 参数,查看结果:

7. 逐渐多线程

Redis 的版本很多,版本不同架构也是不同的,不限定版本问是否单线程也不太严谨。

  • 版本 3.x,最早版本,也就是大家口口相传的 Redis 是单线程;
  • 版本 4.x,严格意义来说也不是单线程,而是负责处理客户端请求的线程是单线程,但是开始加了点多线程的东西(异步删除)。
  • 6.0.x 后,告别了大家印象中的单线程,用一种全新的多线程来解决问题。

7.1 单线程

我们通常说 Redis 是单线程究竟何意?

主要是指 Redis 的网络 IO 和 KV 读写是由一个线程来完成的,Redis 在处理客户端的请求时包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。这也是 Redis 对外提供键值存储服务的主要流程。

但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等等,其实是由额外的线程执行的。Redis 工作线程是单线程的,但是,整个 Redis 来说,是多线程的;

Redis 3.x 单线程时代但性能依旧很快的主要原因?

  • 基于内存操作:Redis 的所有数据都存在内存中,因此所有的运算都是内存级别的,所以性能比较高;
  • 数据结构简单:Redis 的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是 O(1),因此性能比较高;
  • 多路复用和非阻塞 I/O:Redis 使用 I/O 多路复用功能来监听多个 socket 连接客户端,这样就可以使用一个线程连接来处理多个请求,减少线程切换带来的开销,同时也避免了 I/O 阻塞操作;
  • 避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的消耗,而且单线程不会导致死锁问题的发生。

简单来说,Redis 4.0 之前一直采用单线程的主要原因有以下 3 个:

  1. 使用单线程模型是 Redis 的开发和维护更简单,因为单线程模型方便开发和调试;
  2. 即使使用单线程模型也并发的处理多客户端的请求,主要使用的是多路复用和非阻塞 IO;
  3. 对于 Redis 系统来说,主要的性能瓶颈是内存或者网络带宽而并非 CPU

有俩个误区:① 高性能的服务器一定是多线程的;② 多线程一定比单线程效率高

Redis 是将所有的数据全部存在内存中,所以说使用单线程去操作就是效率最高的;为什么这么说呢?因为多线程会有 CPU 上下文切换的问题,而对于内存系统来说,没有上下文切换效率就是最高的,多次读写都是在一个 CPU 上!所以,在全内存情况下,单线程就是最佳的方案。

7.2 多线程

既然单线程这么好,为什么逐渐又加入了多线程特性?

正常情况下使用 del 指令可以很快的删除数据,而当被删除的 key 是一个非常大的对象时,例如时包含了成千上万个元素的 hash 集合时,那么 del 指令就会造成 Redis 主线程卡顿。

这就是 Redis3.x 单线程时代最经典的故障:大 key 删除的头疼问题。

由于 Redis 是单线程的,del bigKey 等待很久这个线程才会释放,类似加了一个 synchronized 锁,若发生在高并发环境下,程序将拥堵不堪。

使用惰性删除可以有效的避免 Redis 卡顿的问题

当 Redis 需要删除一个很大的数据时,因为是单线程同步操作,这就会导致 Redis 服务卡顿,于是在 Redis 4.0 中就新增了多线程的模块,当然此版本中的多线程主要是为了解决删除数据效率比较低的问题。把删除工作交给了后台的子线程异步来执行。

unlink <key>
flushdb [async]
flushall [async]

在 Redis 4.0 就引入了多个线程来实现数据的异步惰性删除等功能,但是其处理读写请求的仍然只有一个线程,所以仍然算是狭义上的单线程。

Redis 主要的性能瓶颈是内存或者网络带宽而并非 CPU → 初步定为:网络 IO

Unix 网络编程中的五种 IO 模型:

  • Blocking IO - 阻塞 IO
  • NoneBlocking IO - 非阻塞 IO
  • IO multiplexing - IO 多路复用
  • Signal driven IO - 信号驱动 IO
  • Asynchronous IO - 异步 IO

【IO 多路复用】这是 IO 模型的一种,即经典的 Reactor 设计模式,I/O 多路复用,简单来说就是通过监测文件的读写事件再通知线程执行相关操作,保证 Redis 的非阻塞 I/O 能够顺利执行完成的机制。

  • 多路指的是多个 socket 连接;
  • 复用指的是复用一个线程。

多路复用主要有 3 种技术:select,poll,epoll。epoll 是最新的也是目前最好的多路复用技术。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了 Redis 具有很高的吞吐量。

Redis 工作线程是单线程的,但是,整个 Redis 来说,是多线程的。

I/O 的读和写本身是堵塞的,比如当 socket 中有数据时,Redis 会通过调用先将数据从内核态空间拷贝到用户态空间,再交给 Redis 调用,而这个拷贝的过程就是阻塞的,当数据量越大时拷贝所需要的时间就越多,而这些操作都是基于单线程完成的。

在 Redis 6.0 中新增了多线程的功能来提高 I/O 的读写性能,他的主要实现思路是将主线程的 IO 读写任务拆分给一组独立的线程去执行,这样就可以使多个 socket 的读写可以并行化了,采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),将最耗时的 socket 的读取、请求解析、写入单独外包出去,剩下的命令执行仍然由主线程串行执行并和内存的数据交互。

结合上图可知,网络 IO 操作就变成多线程化了,其他核心部分仍然是线程安全的,是个不错的折中办法。

将网络数据读写、请求协议解析通过多个 IO 线程的来处理 ,对于真正的命令执行来说,仍然使用主线程操作,一举两得。

在 Redis6.0 中,多线程机制默认是关闭的。

如果需要使用多线程功能,需要在 redis.conf 中完成两个设置:

  1. 设置 io-thread-do-reads 配置项为 yes,表示启动多线程;
  2. 设置线程个数。关于线程数的设置,官方的建议是如果为 4 核的 CPU,建议线程数设置为 2 或 3,如果为 8 核 CPU 建议线程数设置为 6,线程数一定要小于机器核数,线程数并不是越大越好。

7.3 小结

Redis 3.x 基于内存操作、数据结构简单、多路复用和非阻塞 I/O、避免了不必要的线程上下文切换等特性,在单线程的环境下依然很快;但对于大数据的 key 删除还是卡顿厉害,因此在 Redis 4.0 引入了多线程 unlink <key>flushall [async] 等命令,主要用于 Redis 数据的异步删除;而在 Redis 6.0 中引入了 I/O 多线程的读写,这样就可以更加高效的处理更多的任务了,Redis 只是将 I/O 读写变成了多线程,而命令的执行依旧是由主线程串行执行的,因此在多线程下操作 Redis 不会出现线程安全的问题。

Redis 无论是当初的单线程设计,还是如今与当初设计相背的多线程,目的只有一个:让 Redis 变得越来越快。

posted @ 2020-09-04 12:32  tree6x7  阅读(132)  评论(0编辑  收藏  举报