【上篇】Redis 基础篇
封面来源:碧蓝航线 永夜幻光 活动CG
Redis 命令参考:Redis 命令参考
本文参考:尚硅谷超经典Redis教程,redis实战,阳哥版从入门到精通
1. NoSQL 入门与概述(上)
1.1 为什么要有 NoSQL
单机 MySQL 的美好年代
在 90 年代,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。
在那个时候,更多的都是静态网站,动态交互类型的网站并不多。
上述架构下,可以清楚地看到数据存储的瓶颈:
1、数据量总大小:一个机器放不下时
2、数据的索引(B+ Tree):一个机器的内存放不下时
3、访问量(读写混合):一个实例不能承受
Memcached(缓存)+ MySQL + 垂直拆分
随着访问量的上升,几乎大部分使用 MySQL 架构的网站在数据库上都开始出现了性能问题,web 程序不再仅仅专注在功能上,同时也在追求性能。程序员们开始大量的使用缓存技术来缓解数据库的压力,优化数据库的结构和索引。开始比较流行的是通过文件缓存来缓解数据库压力,但是当访问量继续增大的时候,多台 web 机器通过文件缓存不能共享,大量的小文件缓存也带了了比较高的 IO 压力。在这个时候,Memcached 就自然的成为一个非常时尚的技术产品。
MySQL 主从读写分离
由于数据库的写入压力增加, Memcached 只能缓解数据库的读取压力。读写集中在一个数据库上让数据库不堪重负,大部分网站开始使用主从复制技术来达到读写分离,以提高读写性能和读库的可扩展性。MySQL 的 master-slave 模式成为这个时候的网站标配了。
分库分表 + 水平拆分 + MySQL 集群
在 Memcached 的高速缓存,MySQL 的主从复制, 读写分离的基础之上,这时 MySQL 主库的写压力开始出现瓶颈,而数据量的持续猛增,由于 MyISAM 使用表锁, 在高并发下会出现严重的锁问题,大量的高并发 MySQL 应用开始使用 InnoDB 引擎代替 MyISAM。
同时,开始流行使用分表分库来缓解写压力和数据增长的扩展问题。这个时候,分表分库成了一个热门技术,是面试的热门问题也是业界讨论的热门技术问题。也就在这个时候,MySQL推出了还不太稳定的表分区,这也给技术实力一般的公司带来了希望。虽然 MySQL 推出了 MySQL Cluster 集群, 但性能也不能很好满足互联网的要求,只是在高可靠性上提供了非常大的保证。
MySQL 的扩展性瓶颈
MySQL 数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常的慢,不容易快速恢复数据库。比如 1000 万 4KB 大小的文本就接近40GB的大小,如果能把这些数据从 MySQL 省去,MySQL 将变得非常的小。关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL 的扩展性差(需要复杂的技术来实现),大数据下 IO 压力大,表结构更改困难,正是当前使用 MySQL 的开发人员面临的问题。
现在是什么样子?
为什么要使用 NoSQL
今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易获取数据。用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那 SQL 数据库已经不适合这些应用了,NoSQL 数据库的发展也却能很好的处理这些大的数据。
1.2 什么是 NoSQL
NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,泛指非关系型的数据库。随着互联网web 2.0 网站的兴起,传统的关系数据库在应付 web 2.0 网站,特别是超大规模和高并发的 SNS 类型的 web 2.0 纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL 数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。
例如谷歌或 Facebook 每天为他们的用户收集万亿比特的数据。 这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。
1.3 NoSQL 特性
易扩展
NoSQL 数据库种类繁多,但是一个共同的特点是都去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展。也无形之间,在架构的层面上带来了可扩展的能力。
大数据量高性能
NoSQL 数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。
这得益于它的无关系性,数据库的结构简单。
一般 MySQL 使用 Query Cache,每次表的更新 Cache 就失效,是一种大粒度的 Cache,在针对 web 2.0 的交互频繁的应用,Cache 性能不高。而 NoSQL 的 Cache 是记录级的,是一种细粒度的 Cache,所以 NoSQL 在这个层面上来说就要性能高很多了。
多样灵活的数据模型
NoSQL 无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。
传统 RDBMS VS NOSQL
RDBMS (关系数据库管理系统)
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
NoSQL
- 代表着不仅仅是 SQL
- 没有声明性查询语言
- 没有预定义的模式
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非 ACID 属性
- 非结构化和不可预知的数据:
- CAP 定理
- 高性能,高可用性和可伸缩性
1.4 有哪些 NoSQL 数据库
- Redis
- Memcached
- MongDB
1.5 有什么用
-
KV
-
Cache
-
Persistence
-
…
2. NoSQL 入门与概述(下)
2.1 3V + 3 高
大数据时代的 3V
-
海量 Volume
-
多样 Variety
-
实时 Velocity
互联网需求的 3 高
-
高并发
-
高可括
-
高性能
2.2 当下的 NoSQL 经典应用
当下的应用是 SQL 和 NoSQL 一起使用,了解一下阿里巴巴中文站商品信息如何存放。
阿里巴巴架构发展历程:
演变过程
第五代网站架构
第五代架构使命
和我们相关的
和我们相关的,多数据源类型的存储问题
信息的存储方案
以阿里巴巴中文站商品信息存储为例:
1、商品基本信息
- 名称、价格,出厂日期,生产厂商等
- 关系型数据库,MySQL / Oracle 目前淘宝在去 O 化(也即拿掉 Oracle),注意,淘宝内部用的 MySQL 是里面的大牛自己改造过的。为什么去 IOE(在IT建设过程中,去除 IBM 小型机、Oracle 数据库及 EMC 存储设备) 简而意之,可不用穿脚链跳舞。
2、商品描述、详情、评价信息(多文字类)
- 多文字信息描述类,IO 读写性能变差
- 文档数据库 MongDB
3、商品的图片
- 商品图片展现类
- 分布式的文件系统中,比如:淘宝自家 TFS、Google 的 GFS、Hadoop 的 HDFS
4、商品的关键字
- 淘宝自家
- ISearch
5、商品的波段性的热点高频信息(如,情人节的玫瑰、巧克力)
- 内存数据库
- Tair、Redis、Memcache
6、商品的交易、价格计算、积分累计
- 外部系统,外部第 3 方支付接口
- 支付宝
总结大型互联网应用(大数据、高并发、多样数据类型)的难点和解决方案
1、难点:
- 数据类型多样性
- 数据源多样性和变化重构
- 数据源改造而数据服务平台不需要大面积重构
2、解决方法
- EAI
- UDSL 统一数据平台服务层
UDSL
什么是 UDSL?
UDSL 是什么样的?
UDSL 映射:
UDSL API:
UDSL 热点缓存:
2.3 NoSQL 数据模型简介
以一个电商客户、订单、订购、地址模型来对比关系型数据库和非关系型数据库。
传统关系型数据库如何设计?可以画好 ER 图进行设计(其中 1:1 / 1:N / N:N,主外键很常见)。
那么 NoSQL 又该怎么设计?
首先需要了解什么是 BSON:BSON 是一种类 JSON 的二进制形式的存储格式,简称 Binary JSON,它和 JSON 一样,支持内嵌的文档对象和数组对象。比如:
1 | { |
针对开头提出的业务需求,我们应该使用聚合模型来处理。
那为什么要使用聚合模型来处理?
- 高并发的操作是不太建议用关联查询的,互联网公司用冗余数据来避免关联查询
- 分布式事务是支持不了太多的并发的
那什么聚合模型又是什么?
- K-V 键值对
- BSON
- 列族:顾名思义,是按列存储数据的。最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一 列或者某几列的查询有非常大的 IO 优势。比如:
- 图形:比如现实的人际关系图中是很复杂的,仅仅试试二维的行列关系图也是很难表示的,这个时候就需要使用到图。比如:
3.3 NoSQL 数据库的四大分类
K-V 键值对 NoSQL 数据库
- 新浪:BerkeleyDB + Redis
- 美团:Redis + Tair
- 阿里、百度:Memcache + Redis
文档型数据库(BSON 格式居多)
-
CouchDB
-
MongoDB
MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
列存储数据库
-
Cassandra
-
HBase
图关系数据库
图关系数据库不是放图形的,放的是关系,比如:朋友圈社交网络、广告推荐系统社交网络、推荐系统等,它专注于构建关系图谱。常见的图关系数据库有:Neo4j、InfoGrid。
四者的对比
3.4 分布式数据库中 CAP 原理
回顾 ACID
先说说传统关系型数据库的 ACID 特性,这也是事务的四大特征:
1、原子性(atomicity):事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用
2、一致性(consistency):一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中
3、隔离性(isolation):可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏
4、持久性(durability):事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中
而在 NoSQL 数据库中,没有了 ACID,取而代之的是 CAP。
什么是 CAP?
- C:Consistency(强一致性)
- A:Availability(可用性)
- P:Partition tolerance(分区容错性)
CAP 理论的核心是:一个分布式系统不可能同时很好的满足一致性、可用性和分区容错性这三个要求,最多只能同时满足其中的两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三大类:
- CA:单点集群,满足一致性、可用性的系统,通常在可扩展性上不太强大
- CP:满足一致性、分区容错性的系统,通常性能不是特别高
- AP:满足可用性、分区容错性的系统,通常可能对一致性要求低一点
而由于当前的网络硬件肯定会出现延迟丢包等问题,所以 分区容错性 是我们必须需要实现的。所以我们只能在 一致性 和 可用性 之间进行权衡,没有 NoSQL系统能同时保证这三点。
特点的选择:
- CA:传统Oracle数据库
- AP:大多数网站架构的选择
- CP:Redis、MongoDB
注意:分布式架构的时候必须做出取舍。
一致性和可用性之间取一个平衡。多余大多数 WEB 应用,其实并不需要强一致性。因此牺牲 C 换取 P,这是目前分布式数据库产品的方向。
一致性与可用性的抉择
对于 WEB 2.0 网站来说,关系数据库的很多主要特性却往往无用武之地
数据库事务一致性需求
很多 WEB 实时系统并不要求严格的数据库事务,对读一致性的要求很低,有些场合对写一致性要求并不高,允许实现最终一致性。
数据库的写实时性和读实时性需求
对关系数据库来说,插入一条数据之后立刻查询,是肯定可以读出来这条数据的,但是对于很多 WEB 应用来说,并不要求这么高的实时性。比方说在微博发一条消息之后,过几秒乃至十几秒之后,我的订阅者才看到这条动态是完全可以接受的。
对复杂的SQL查询,特别是多表关联查询的需求
任何大数据量的 WEB 系统,都非常忌讳多个大表的关联查询,以及复杂的数据分析类型的报表查询,特别是 SNS 类型的网站,从需求以及产品设计角度,就避免了这种情况的产生。往往更多的只是单表的主键查询,以及单表的简单条件分页查询,SQL 的功能被极大的弱化了。
什么是 BASE
BASE 就是为了解决关系数据库强一致性引起的可用性降低而提出的解决方案。
BASE 其实是下面三个术语的缩写:
- 基本可用(Basically Available)
- 软状态(Soft State)
- 最终一致(Eventually Consistent)
它的思想是通过让系统放松对某一时刻数据一致性的要求来换取系统整体伸缩性和性能上改观。为什么这么说呢,缘由就在于大型系统往往由于地域分布和极高性能的要求,不可能采用分布式事务来完成这些指标,要想获得这些指标,我们必须采用另外一种方式来完成,这里 BASE 就是解决这个问题的办法。
简单了解一下分布式与集群
分布式系统(Distributed System)由多台计算机和通信的软件组件通过计算机网络连接(本地网络或广域网)组成。分布式系统是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。分布式系统可以应用在在不同的平台上如:PC、工作站、局域网和广域网上等。
简单来讲:
1、分布式:不同的多台服务器上面部署不同的服务模块(工程),他们之间通过 RPC / RMI 之间通信和调用,对外提供服务和组内协作。
2、集群:不同的多台服务器上面部署相同的服务模块,通过分布式调度软件进行统一的调度,对外提供服务和连接。
3. Redis 入门概述
3.1 入门概述
什么是 Redis
Redis 不是一个单词,它是几个单词的缩写。Remote Dictionary Server(远程字典服务器)是完全开源免费的,用 C 语言编写的,遵守 BSD 协议,是一个高性能的 Key / Value 分布式内存数据库,基于内存运行并支持持久化的 NoSQL 数据库,是当下最热门的 NoSQL 数据库之一,也被人们称为数据结构服务器。
Redis 与其他 key - value 缓存产品相比有以下三个特点:
1、Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
2、Redis 不仅仅支持简单的 Key - Value 类型的数据,同时还提供 List,Set,Zset,Hash 等数据结构的存储
3、Redis 支持数据的备份,即 master-slave 模式的数据备份
Redis 能干什么
1、内存存储和持久化:Redis 支持异步将内存中的数据写到硬盘上,同时不影响继续服务
2、取最新 N 个数据的操作,如:可以将最新的 10 条评论的 ID 放在 Redis 的 List 集合里面
3、模拟类似于 HttpSession 这种需要设定过期时间的功能
4、发布、订阅消息系统
5、定时器、计数器
去哪里下 Redis
Redis 怎么玩
1、数据类型、基本操作和配置
2、持久化和复制,RDB / AOF
3、事务的控制
4、复制
5、…
3.2 Redis 的安装
本节基于 Linux 下安装 Redis。
首先进入管理员权限(以后的每一步操作都请使用管理员进行操作):
1 | [mofan@cheny ~]$ su |
切换至根目录,并创建文件夹 Redis 用于放置 Redis 安装包,同时修改 Redis 文件夹权限:
1 | cd / |
然后将 Windows 下的 redis-6.0.8.tar.gz 安装包复制到 Linux 的 Redis 目录下(复制时不要使用直接拖拽的方式)。
解压 redis-6.0.8.tar.gz 至路径 /usr/local,并检查是否解压成功:
1 | tar -zxvf redis-6.0.8.tar.gz -C /usr/local |
将解压文件名 redis-6.0.8 修改为 redis(方便以后操作),并查看是否修改成功:
1 | mv ./redis-6.0.8 ./redis |
把 redis 目录权限赋予给 mofan(这是我的用户名,你可以赋予你自己的用户)用户:
1 | chown -R mofan ./redis |
进入 redis 目录,安装 Redis:
1 | cd redis |
但是在执行 make
命令的时候就出现了问题,提示未找到命令:
这时候需要安装 gcc(确保我们的虚拟机能够联网):
1 | [root@cheny redis]# yum install gcc-c++ |
但是这个时候又提示镜像的地址找不到,正在尝试其他镜像,并报错: Couldn’t resolve host
参考链接:linux的yum下载不了,老是出现尝试其他镜像该怎么解决?
一般来说这是 DNS 的问题,更换一下 DNS:
1 | vi /etc/resolv.conf |
我的 DNS 配置是这样的:
1 | nameserver 8.8.8.8 |
然后备份原配置文件:
1 | mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup |
下载镜像源,我们选择阿里云的镜像:
1 | wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo |
生成源缓存:
1 | yum makecache |
更新源信息:
1 | yum update -y |
如果网络不是太好,这个命令很耗时,可以出个饭再来看看。
然后再来安装 gcc:
1 | yum install gcc-c++ |
由于我们使用的是 Redis 6.0,它需要 gcc 5及其以上的版本,因此还需要升级 gcc,否则是无法安装成功的。依次执行:
1 | yum install centos-release-scl |
上述命令可能会执行失败,如果失败多试几次。
输入以下命令,查看 gcc 版本:
1 | gcc -v |
如果 gcc 版本大于了 5,这时候前往对应目录依次执行以下的目录完成 Redis 的安装:
1 | cd /usr/local/redis # Redis 的解压目录 |
如果执行 make
和 make install
没有出现错误提示(警告无所谓),就像下图所示的这样,就表示我们的 Redis 安装成功了:
没有联网安装 gcc
没有联网的安装就是使用光驱安装 gcc。
但这种安装方式只支持安装 Redis 6 以下的版本,因为 Redis 6 以上的版本需要 gcc 5及其以上的版本,而光驱内的 gcc 一般是 4.8 的。
打开虚拟机后,右击虚拟机,打开虚拟机设置:
指定 ISO 映像文件的位置,并将设备状态一栏都勾上。这时候,我们 CentOS 桌面上会出现一个“光盘”:
双击这个“光盘”,找到一个名为 Packages
的目录(可能目录名会不一样),进入这个目录,这个目录里有很多的 rpm 文件,这些文件就是光驱给我们提供的,这里面也有 gcc 相关的 rpm 文件。
在这个目录内用鼠标右击,点击 在终端打开,在终端打开这个目录,然后执行一下命令进行安装:
1 | rpm -ivh cpp-4.8.5-36.el7.x86_64.rpm |
需要注意的是,不同的光驱里的 rpm 文件版本可能不一样,如果不一样,找到自己光驱内对应的版本执行命令即可(可以通过 ls -l
命令寻找,列举出的文件按英文字母排序)。
执行命令 gcc -v
查看 gcc 版本。
前往 Redis 解压目录执行 make
命令进行安装,这时候又会出现以下问题:
运行以下命令再次安装:
1 | make distclean |
基本是到此就完成了 Redis 的安装。
拓展:配置 ISO 镜像作为 yum 源
参考链接:CentOS 本地iso挂载并配置iso镜像作为yum源安装软件
执行命令行挂载 ISO 镜像:
1 | ls -l /dev | grep cdrom # 找到光盘的完整路径 |
使用命令挂载:
1 | mount /dev/cdrom /mnt/ |
执行命令查看挂载状态:
1 | df -h |
使用本地 ISO 做 yum 源:
1 | cd /etc/yum.repos.d |
原本内容为:
修改后:
这样,CentOS 本地 ISO 挂载并配置 ISO 镜像作为 yum 源就设置完成了。
如果想要恢复使用网络上的源,再次把 CentOS-Base.repo.bak 重命名为 CentOS-Base.repo 就可以了,并把 CentOS-Media.repo 文件内容改回去就可以了。
3.3 Hello World
那我们的 Redis 安装在哪呢?
执行以下命令查看:
1 | cd /usr/local/bin |
我们可以修改一下 Redis 的配置文件,进入 Redis 的解压目录,复制 redis.conf 配置文件到 /Redis 目录下:
1 | cd /usr/local/redis |
然后修改 GENERAL 下的配置,使 Redis 可以作为 daemon 运行:
1 | vi redis.conf |
修改好了后,前往目录 /usr/local/bin
启动 Redis:
1 | cd /usr/local/bin |
这个时候的 Redis 服务是没用启动的。那我们就来启动:
1 | redis-server /Redis/redis.conf |
这时候的提示符会发生变化,我们可以测试下 Redis 是否启动成功(pingpong):
我们输入 ping
,界面回答了 PONG
,证明 Redis 服务启动成功。还可以新开一个终端通过以下命令查看:
1 | ps -ef|grep redis # 查看 Redis 服务进程是否启动 |
我们知道 Redis 是 K-V 型的 NoSQL 数据库,可以来尝试存入数据并获取:
如果要退出,可以这样:
1 | 127.0.0.1:6379> SHUTDOWN |
如果再次查看服务进程,会发现 Redis 的服务进程已经消失,就和最开始未启动服务一样:
3.4 启动后的杂项知识
Redis 自测
在 /usr/local/bin
目录下还有一个 redis-benchmark
,我们可以运行一下,就可以让 Redis 进行自测(或许也是一种电脑跑分?)。当然,运行它必须让 Redis 处于启动状态:
1 | redis-benchmark |
根据官网所说,Redis 写数据每秒 8W,读数据每秒 11W。你的电脑达到平均水平了吗?我的反正没达到。😭
命令语法:
1 | redis-benchmark [option] [option value] |
注意:该命令是在 redis 的目录下执行的,而不是 redis 客户端的内部指令。
可选参数如下所示:
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 socket | |
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 |
1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | –csv | 以 CSV 格式输出 | |
12 | -l | 生成循环,永久执行测试 | |
13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | -I | Idle 模式。仅打开 N 个 idle 连接并等待。 |
比如:
1 | redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -n 10000 -q |
表示:主机为 127.0.0.1,端口号为 6379,执行的命令为 set 和 lpush,请求数为 10000,通过 -q 参数让结果只显示每秒执行的请求数。
其他杂项知识
Redis 是单进程的。单进程模型来处理客户端的请求。对读写等事件的响应是通过对 epoll 函数的包装来做到的。Redis 的实际处理速度完全依靠主进程的执行效率
Epoll 是 Linux 内核为处理大批量文件描述符而作了改进的 epoll,是 Linux 下多路复用 IO 接口 select / poll 的增强版本, 它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。
Redis 默认 16 个数据库,类似数组下表从零开始,初始默认使用零号库,可在配置文件配置。可以通过以下命令对 Redis 数据库进行操作:
select
:命令切换数据库
dbsize
查看当前数据库的 key的数量
flushdb
:清空当前库
flushall
:通杀全部库
keys
:查找所有符合给定模式 pattern 的 key。比如:keys *
或者 keys k*
,第一种会查找到当前库下所有的 key,第二种则会查找到当前库下以 k 开头的所有 key。
Redis 拥有统一密码管理,16个库都是同样密码,要么都连上要么一个也连接不上。
Redis 索引都是从零开始。
Redis 的默认端口是 6379,至于为啥可以自行了解。
3.5 Windows 下安装 Redis
有些情况下会有这样的应用场景:在 Windows 本地搭建 Redis,添加到本地计算机的服务中,保证每次开机自动启动服务。
首先下载 Windows 环境下的 Redis,下载地址:Redis-x64-3.2.100.zip
然后选择一个你喜欢的路径,在该路径下解压下载的压缩包。
在 Redis 的根目录下运行以下命令以打开 Redis 服务:
1 | redis-server.exe redis.windows.conf --maxmemory 200M |
再在此路径下启一个命令行窗口,输入以下命令以打开 Redis 客户端:
1 | redis-cli.exe -h 127.0.0.1 -p 6379 |
如果想要偷懒,也可以直接运行 redis-cli.exe
。
成功打开客户端后,试试能不能正常添加数据,具体操作见下文。
我们还可以给客户端设置密码,先查看密码(如果没设置过的话,因为设置过肯定得先输入密码):
1 | CONFIG GET requirepass |
会看到默认密码为空,来设置一下密码:
1 | CONFIG SET requirepass 123456 |
这样就将密码设置成 123456,如果这个时候再查看密码,就会提示没有权限,得先使用密码:
1 | AUTH 123456 |
这样就相当于输入了密码,然后就可以进行其他的操作了!😁
接下来注册开机自启动 Redis 服务,当然要到安装 Redis 的根目录下执行下面的命令:
1 | redis-server --service-install redis.windows.conf --loglevel verbose |
打开 Windows 服务,找到 Redis 服务,这个时候的 Redis 服务是未启动的。可以先将它启动,下次开机时 Redis 服务就会自启动了。
如果一直要求 Redis 使用密码,可以修改 Redis 安装目录下 redis.windows.conf
配置文件,配置 requirepass
项:
requirepass 123456
为了更好地进行操作,建议在 Windows 下安装可视化界面管理 Redis 工具 RedisDesktopManager,具体下载与安装自行搜索。
Windows 下 Redis 服务无法启动,错误 1067 进程意外终止解决方案
在启动 Redis 服务时可能会出现这种情况,怎么办呢?
先看下默认端口 6379 是否被占用。以管理员身份运行 CMD,然后执行以下命令:
1 | netstat -ano | findstr :6379 |
看看有没有内容。
如果有,LISTENING 后边的数字就是进程的 ID(PID)。使用 任务管理器 可以查询到具体是哪个进程,看看是否已经运行了 redis-server.exe 程序。如果是其他程序,可以退出后重新启动服务。
如果还是不行,想想是否对 Redis 配置文件 redis.windows-service.conf
进行过大修改,如果有,可以还原到默认配置。怎么还原?
同目录下的 redis.windows.conf
文件就是默认的配置,复制一份改个名就完事了。
如果还是不行,看看 Windows 服务里的 Redis 服务是否是网络服务。如果是网络服务,直接双击此服务,修改为本地系统服务:
4. Redis 数据类型
4.1 Redis 的五大数据类型
String(字符串)
String 是 Redis 中最基本的类型,可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。
String 类型是二进制安全的。意思是 Redis 的 String 可以包含任何数据。比如 jpg 图片或者序列化的对象。
String 类型是 Redis 最基本的数据类型,一个 Redis 中字符串 value 最多 可以是 512M。
Hash(哈希,类似 Java 里的 Map)
Redis Hash 是一个键值对集合。
Redis Hash 是一个 String 类型的 field 和 value 的映射表,Hash 特别适合用于存储对象。
类似 Java 里面的 Map<String, Object>
List(列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。
它的底层就是个链表。
Set(集合)
Redis 的 Set 是 String 类型的无序集合,它是通过 HashTable 实现实现的。
Zset(sorted set:有序集合)
Redis Zset 和 Set 一样也是 String 类型元素的集合,且 不允许重复 的成员。
不同的是每个元素都会关联一个 double 类型的分数。
Redis 正是通过分数来为集合中的成员进行从小到大的排序。Zset 的成员是唯一的,但分数(score)是可以重复的。
那么去哪里获得 Redis 常见数据类型操作命令?
4.2 Key 关键字
常用的一些命令
命令 | 描述 |
---|---|
DEL key | 该命令用于在 key 存在时删除 key |
DUMP key | 序列化给定 key,并返回被序列化的值 |
EXISTS key | 检查给定 key 是否存在 |
EXPIRE key seconds | 为给定 key 设置过期时间,以秒计 |
EXPIREAT key timestamp | EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置过期时间。 不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp) |
PEXPIRE key milliseconds | 设置 key 的过期时间以毫秒计 |
PEXPIREAT key milliseconds-timestamp | 设置 key 过期时间的时间戳(unix timestamp)以毫秒计 |
KEYS pattern | 查找所有符合给定模式(pattern)的 key |
MOVE key db | 将当前数据库的 key 移动到给定的数据库 db 当中 |
PERSIST key | 移除 key 的过期时间,key 将持久保持 |
PTTL key | 以毫秒为单位返回 key 的剩余的过期时间 |
TTL key | 以秒为单位,返回给定 key 的剩余生存时间(TTL,time to live) |
RANDOMKEY | 从当前数据库中随机返回一个 key |
RENAME key newkey | 修改 key 的名称 |
RENAMENX key newkey | 仅当 newkey 不存在时,将 key 改名为 newkey |
SCAN cursor [MATCH pattern] [COUNT count] | 迭代数据库中的数据库键 |
TYPE key | 返回 key 所储存的值的类型 |
命令示例
keys *
:查找到当前库下所有的 key
exists key
:判断 key 在当前库是否存在
move key db
:将当前库的 key 移动到 db 库中,比如:
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> set k2 v2 OK 127.0.0.1:6379> set k3 v3 OK 127.0.0.1:6379> move k1 2 (integer) 1 127.0.0.1:6379> select 2 OK 127.0.0.1:6379[2]> get k1 "v1" 127.0.0.1:6379[2]> select 0 OK 127.0.0.1:6379> get k1 (nil)
expire key 秒钟
:为给定的 key 设置过期时间
ttl key
:查看 key 还有多少秒过期,-1 表示永不过期,-2 表示已过期
127.0.0.1:6379> EXPIRE k2 10 (integer) 1 127.0.0.1:6379> ttl k2 # 10 秒内 (integer) 7 127.0.0.1:6379> get k2 # 10 秒内 "v2" 127.0.0.1:6379> ttl k2 # 10 秒后 (integer) -2 127.0.0.1:6379> get k2 # 10 秒后 (nil) 127.0.0.1:6379> keys * 1) "k3"
key 达到生命周期的时候后,就会从内存中移除。
type key
:查看 key 是什么类型
127.0.0.1:6379> keys * 1) "k3" 127.0.0.1:6379> get k3 "v3" 127.0.0.1:6379> set k3 mofan OK 127.0.0.1:6379> get k3 "mofan" 127.0.0.1:6379> type k3 string
4.3 String 字符串
常用的一些命令
命令 | 描述 |
---|---|
SET key value | 设置指定 key 的值 |
GET key | 获取指定 key 的值 |
GETRANGE key start end | 返回 key 中字符串值的子字符 |
GETSET key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value) |
GETBIT key offset | 对 key 所储存的字符串值,获取指定偏移量上的位(bit) |
MGET key1 [key2…] | 获取所有(一个或多个)给定 key 的值 |
SETBIT key offset value | 对 key 所储存的字符串值,设置或清除指定偏移量上的位(bit) |
SETEX key seconds value | 将值 value 关联到 key ,并将 key 的过期时间设为 seconds(以秒为单位) |
SETNX key value | 只有在 key 不存在时设置 key 的值 |
SETRANGE key offset value | 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始 |
STRLEN key | 返回 key 所储存的字符串值的长度 |
MSET key value [key value …] | 同时设置一个或多个 key-value 对 |
MSETNX key value [key value …] | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 |
PSETEX key milliseconds value | 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位 |
INCR key | 将 key 中储存的数字值增一 |
INCRBY key increment | 将 key 所储存的值加上给定的增量值(increment) |
INCRBYFLOAT key increment | 将 key 所储存的值加上给定的浮点增量值(increment) |
DECR key | 将 key 中储存的数字值减一 |
DECRBY key decrement | key 所储存的值减去给定的减量值(decrement) |
APPEND key value | 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾 |
命令示例
String 简单来说就是单值单 value。
1、set / get / del / append / strlen
127.0.0.1:6379> set k4 v4 OK 127.0.0.1:6379> get k4 "v4" 127.0.0.1:6379> del k4 (integer) 1 127.0.0.1:6379> KEYS * 1) "k3" 127.0.0.1:6379> APPEND k3 qwer (integer) 9 127.0.0.1:6379> get k3 "mofanqwer" 127.0.0.1:6379> STRLEN k3 (integer) 9
2、Incr / decr / incrby / decrby, 一定要是数字才能进行加减
127.0.0.1:6379> set k2 2 OK 127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> INCR k2 (integer) 3 127.0.0.1:6379> INCR k2 (integer) 4 127.0.0.1:6379> get k2 "4" 127.0.0.1:6379> DECR k2 (integer) 3 127.0.0.1:6379> DECR k2 (integer) 2 127.0.0.1:6379> get k2 "2" 127.0.0.1:6379> DECRBY k2 5 (integer) -3 127.0.0.1:6379> INCRBY k2 10 (integer) 7
3、getrange 获取指定区间范围内的值, 类似 between…and 的关系,从零到负一表示全部,比如:
127.0.0.1:6379> GETRANGE k3 0 -1 "mofanqwer" 127.0.0.1:6379> GETRANGE k3 0 5 "mofanq"
4、setrange 设置指定区间范围内的值, 格式是 setrange key 值 具体值
,比如:
127.0.0.1:6379> SETRANGE k3 0 cheny (integer) 9 127.0.0.1:6379> get k3 "chenyqwer"
5、setex(set with expire),格式是 setex key 秒数 value
,意思是设置一个 key 和 value,且指定了它们的存活时间。比如:
127.0.0.1:6379> SETEX k4 15 v4 OK 127.0.0.1:6379> keys * 1) "k1" 2) "k3" 3) "k4" 4) "k2" 127.0.0.1:6379> get k4 "v4" 127.0.0.1:6379> ttl k4 # 15 秒前 (integer) 7 127.0.0.1:6379> ttl k4 (integer) -2 127.0.0.1:6379> get k4 (nil)
6、setnx(set if not exist), 格式是 setnx key value
,意思是当前库中如果不存在 key,就添加 key 和 value,否则不添加,而不会覆写。比如:
127.0.0.1:6379> SETNX k3 v333333 (integer) 0 127.0.0.1:6379> get k3 "chenyqwer" 127.0.0.1:6379> SETNX k4 v4444444 (integer) 1 127.0.0.1:6379> get k4 "v4444444"
7、mset / mget / msetnx,使用示例:
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 OK 127.0.0.1:6379> mget k1 k2 k3 1) "v1" 2) "v2" 3) "v3" 127.0.0.1:6379> KEYS * 1) "k1" 2) "k3" 3) "k4" 4) "k2" 127.0.0.1:6379> MSETNX k4 v4 k5 v5 (integer) 0 127.0.0.1:6379> get k5 (nil) 127.0.0.1:6379> MSETNX k5 v5 k6 v6 (integer) 1 127.0.0.1:6379> keys * 1) "k3" 2) "k2" 3) "k1" 4) "k6" 5) "k4" 6) "k5"
使用 mset
批量添加时,如果添加的 key 在库中已经存在,那么新添加的 value 会覆盖原来的 value。使用 msetnx
批量添加时,如果添加的 key 在库中已经存在,那么所有的数据都不会添加成功。
8、getset(先get再set)
4.4 List 列表
常用的一些命令
命令 | 描述 |
---|---|
BLPOP key1 [key2 ] timeout | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
BRPOP key1 [key2 ] timeout | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
BRPOPLPUSH source destination timeout | 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
LINDEX key index | 通过索引获取列表中的元素 |
LINSERT key BEFORE/AFTER pivot value | 在列表的元素前或者后插入元素 |
LLEN key | 获取列表长度 |
LPOP key | 移出并获取列表的第一个元素 |
LPUSH key value1 [value2] | 将一个或多个值插入到列表(不存在就创建)头部 |
LPUSHX key value | 将一个值插入到已存在的列表头部。列表不存在,操作无效 |
LRANGE key start stop | 获取列表指定范围内的元素 |
LREM key count value | 移除列表元素 |
LSET key index value | 通过索引设置列表元素的值 |
LTRIM key start stop | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 |
RPOP key | 移除列表的最后一个元素,返回值为移除的元素 |
RPOPLPUSH source destination | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
RPUSH key value1 [value2] | 将一个或多个值插入到列表(不存在就创建)的尾部(最右边) |
RPUSHX key value | 将一个值插入到已存在的列表尾部(最右边)。列表不存在,操作无效 |
命令示例
List 简单来说就是单值多 value。
记性测试之前先执行 FLUSHDB
命令清空当前库。
1、lpush / rpush / lrange
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> LPUSH list01 1 2 3 4 5 (integer) 5 127.0.0.1:6379> LRANGE list01 0 -1 1) "5" 2) "4" 3) "3" 4) "2" 5) "1" 127.0.0.1:6379> RPUSH list02 1 2 3 4 5 (integer) 5 127.0.0.1:6379> LRANGE list02 0 -1 1) "1" 2) "2" 3) "3" 4) "4" 5) "5"
LPUSH (相当于栈)数据后,进行 LRANGE 获取数据时,按照先进后出的规则获取;RPUSH (相当于队列)数据后,进行 LRANGE 获取数据时,按照先进先出的规则获取。
2、lpop / rpop
127.0.0.1:6379> LPUSH list01 1 2 3 4 5 (integer) 5 127.0.0.1:6379> RPUSH list02 1 2 3 4 5 (integer) 5 127.0.0.1:6379> lpop list01 "5" 127.0.0.1:6379> lpop list02 "1" 127.0.0.1:6379> rpop list01 "1" 127.0.0.1:6379> rpop list02 "5" 127.0.0.1:6379> LRANGE list01 0 -1 1) "4" 2) "3" 3) "2" 127.0.0.1:6379> LRANGE list02 0 -1 1) "2" 2) "3" 3) "4"
LPOP 相当于移除列表第一个元素,RPOP 相当于移除列表最后一个元素。
3、lindex,按照索引下标获得元素(从上到下)
127.0.0.1:6379> LRANGE list01 0 -1 1) "4" 2) "3" 3) "2" 127.0.0.1:6379> LRANGE list02 0 -1 1) "2" 2) "3" 3) "4" 127.0.0.1:6379> LINDEX list01 3 (nil) 127.0.0.1:6379> LINDEX list01 2 "2" 127.0.0.1:6379> LINDEX list02 2 "4"
4、llen(获取列表的长度)
127.0.0.1:6379> LLEN list01 (integer) 3 127.0.0.1:6379> LLEN list02 (integer) 3
5、lrem key 删 N 个 value
127.0.0.1:6379> RPUSH list03 1 1 1 2 2 2 3 3 3 4 4 4 5 6 7 (integer) 15 127.0.0.1:6379> LREM list03 2 3 (integer) 2 127.0.0.1:6379> LRANGE list03 0 -1 1) "1" 2) "1" 3) "1" 4) "2" 5) "2" 6) "2" 7) "3" 8) "4" 9) "4" 10) "4" 11) "5" 12) "6" 13) "7"
6、ltrim key 开始索引 结束索引
,可以截取指定范围的值后再赋值给 key
127.0.0.1:6379> del list01 (integer) 1 127.0.0.1:6379> LPUSH list01 1 2 3 4 5 6 7 8 (integer) 8 127.0.0.1:6379> LTRIM list01 0 4 OK 127.0.0.1:6379> LRANGE list01 0 -1 1) "8" 2) "7" 3) "6" 4) "5" 5) "4"
7、rpoplpush 源列表 目的列表
127.0.0.1:6379> LRANGE list01 0 -1 1) "8" 2) "7" 3) "6" 4) "5" 5) "4" 127.0.0.1:6379> LRANGE list02 0 -1 1) "2" 2) "3" 3) "4" 127.0.0.1:6379> RPOPLPUSH list01 list02 "4" 127.0.0.1:6379> LRANGE list02 0 -1 1) "4" 2) "2" 3) "3" 4) "4"
8、lset key index value
,把 index 索引位置的值设置成 value
127.0.0.1:6379> LRANGE list01 0 -1 1) "8" 2) "7" 3) "6" 4) "5" 127.0.0.1:6379> LSET list01 1 x OK 127.0.0.1:6379> LRANGE list01 0 -1 1) "8" 2) "x" 3) "6" 4) "5"
9、linsert key before/after 值1 值2
,给值 1 的前或后位置添加一个值 2
127.0.0.1:6379> LRANGE list01 0 -1 1) "8" 2) "x" 3) "6" 4) "5" 127.0.0.1:6379> LINSERT list01 before x Java (integer) 5 127.0.0.1:6379> LRANGE list01 0 -1 1) "8" 2) "Java" 3) "x" 4) "6" 5) "5" 127.0.0.1:6379> LINSERT list01 after x Oracle (integer) 6 127.0.0.1:6379> LRANGE list01 0 -1 1) "8" 2) "Java" 3) "x" 4) "Oracle" 5) "6" 6) "5"
性能总结
1、它是一个字符串链表,left、right 都可以插入添加;
2、如果键不存在,创建新的链表;
3、如果键已存在,新增内容;
4、如果值全移除,对应的键也就消失了。
5、链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。
6、注意区别命令前的 l 和 r,并理解其中的区别。
4.5 Set 集合
常用的一些命令
命令 | 描述 |
---|---|
SADD key member1 [member2] | 向集合添加一个或多个成员 |
SCARD key | 获取集合的成员数 |
SDIFF key1 [key2] | 返回给定所有集合的差集 |
SDIFFSTORE destination key1 [key2] | 返回给定所有集合的差集并存储在 destination 中 |
SINTER key1 [key2] | 返回给定所有集合的交集 |
SINTERSTORE destination key1 [key2] | 返回给定所有集合的交集并存储在 destination 中 |
SISMEMBER key member | 判断 member 元素是否是集合 key 的成员 |
SMEMBERS key | 返回集合中的所有成员 |
SMOVE source destination member | 将 member 元素从 source 集合移动到 destination 集合 |
SPOP key | 移除并返回集合中的一个随机元素 |
SRANDMEMBER key [count] | 返回集合中一个或多个随机数 |
SREM key member1 [member2] | 移除集合中一个或多个成员 |
SUNION key1 [key2] | 返回所有给定集合的并集 |
SUNIONSTORE destination key1 [key2] | 所有给定集合的并集存储在 destination 集合中 |
SSCAN key cursor [MATCH pattern] [COUNT count] | 迭代集合中的元素 |
命令示例
Set 简单来说就是单值多 value。
1、sadd / smembers / sismember
127.0.0.1:6379> SADD set01 1 1 2 2 3 3 (integer) 3 127.0.0.1:6379> SMEMBERS set01 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> SISMEMBER set01 1 (integer) 1 127.0.0.1:6379> SISMEMBER set01 x (integer) 0
2、scard key
,获取集合里面的元素个数
127.0.0.1:6379> SCARD set01 (integer) 3
3、srem key value
,删除集合中元素
127.0.0.1:6379> SREM set01 2 (integer) 1 127.0.0.1:6379> SMEMBERS set01 1) "1" 2) "3"
4、srandmember key 某个整数
,从集合中随机获取几个数
127.0.0.1:6379> sadd set02 1 2 3 4 5 6 7 8 (integer) 8 127.0.0.1:6379> SRANDMEMBER set02 3 1) "7" 2) "8" 3) "2" 127.0.0.1:6379> SRANDMEMBER set02 3 1) "6" 2) "8" 3) "2"
5、spop key
,随机移除集合中的某个元素
127.0.0.1:6379> SMEMBERS set02 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 6) "6" 7) "7" 8) "8" 127.0.0.1:6379> SPOP set02 "5" 127.0.0.1:6379> SPOP set02 "6" 127.0.0.1:6379> SMEMBERS set02 1) "1" 2) "2" 3) "3" 4) "4" 5) "7" 6) "8"
6、smove key1 key2 在key1里某个值
,作用是将 key1 里的某个值赋给 key2
127.0.0.1:6379> DEL set01 (integer) 1 127.0.0.1:6379> DEL set02 (integer) 1 127.0.0.1:6379> SADD set01 1 2 3 (integer) 3 127.0.0.1:6379> SADD set02 x y z (integer) 3 127.0.0.1:6379> SMOVE set01 set02 2 (integer) 1 127.0.0.1:6379> SMEMBERS set01 1) "1" 2) "3" 127.0.0.1:6379> SMEMBERS set02 1) "2" 2) "y" 3) "x" 4) "z"
7、数学集合类:差集(sdiff)交集(sinter)并集(sunion)
所谓 差集 就是在第一个 Set 里面而不在后面任何一个 Set 里面的项。
127.0.0.1:6379> DEL set01 (integer) 1 127.0.0.1:6379> DEL set02 (integer) 1 127.0.0.1:6379> SADD set01 1 2 3 4 5 (integer) 5 127.0.0.1:6379> SADD set02 1 2 3 a b (integer) 5 127.0.0.1:6379> SDIFF set01 set02 1) "4" 2) "5" 127.0.0.1:6379> SINTER set01 set02 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> SUNION set01 set02 1) "1" 2) "4" 3) "a" 4) "5" 5) "b" 6) "3" 7) "2"
4.6 Hash 哈希
常用的一些命令
命令 | 描述 |
---|---|
HDEL key field1 [field2] | 删除一个或多个哈希表字段 |
HEXISTS key field | 查看哈希表 key 中,指定的字段是否存在 |
HGET key field | 获取存储在哈希表中指定字段的值 |
HGETALL key | 获取在哈希表中指定 key 的所有字段和值 |
HINCRBY key field increment | 为哈希表 key 中的指定字段的整数值加上增量 increment |
HINCRBYFLOAT key field increment | 为哈希表 key 中的指定字段的浮点数值加上增量 increment |
HKEYS key | 获取所有哈希表中的字段 |
HLEN key | 获取哈希表中字段的数量 |
HMGET key field1 [field2] | 获取所有给定字段的值 |
HMSET key field1 value1 [field2 value2 ] | 同时将多个 field-value (域-值)对设置到哈希表 key 中 |
HSET key field value | 将哈希表 key 中的字段 field 的值设为 value |
HSETNX key field value | 只有在字段 field 不存在时,设置哈希表字段的值 |
HVALS key | 获取哈希表中所有值 |
HSCAN key cursor [MATCH pattern] [COUNT count] | 迭代哈希表中的键值对 |
命令示例
哈希简单来说就是 KV 模式不变,但 V 是一个键值对。
1、hset / hget / hmset / hmget / hgetall / hdel
127.0.0.1:6379> HSET user id 11 (integer) 1 127.0.0.1:6379> HGET user id "11" 127.0.0.1:6379> HMSET customer id 212 name mofan age 18 OK 127.0.0.1:6379> HMGET customer id name age 1) "212" 2) "mofan" 3) "18" 127.0.0.1:6379> HGETALL customer 1) "id" 2) "212" 3) "name" 4) "mofan" 5) "age" 6) "18" 127.0.0.1:6379> HSET user name z3 (integer) 1 127.0.0.1:6379> HDEL user id (integer) 1 127.0.0.1:6379> HGET user name "z3"
2、hlen,获取哈希的长度
127.0.0.1:6379> HLEN user (integer) 1 127.0.0.1:6379> HLEN customer (integer) 3
3、hexists key 在key里面的某个值的key
,判断哈希里面某个 key 是否存在
127.0.0.1:6379> HEXISTS customer name (integer) 1 127.0.0.1:6379> HEXISTS customer gender (integer) 0
4、hkeys / hvals
127.0.0.1:6379> HKEYS customer 1) "id" 2) "name" 3) "age" 127.0.0.1:6379> HVALS customer 1) "212" 2) "mofan" 3) "18"
5、hincrby / hincrbyfloat,第一个表示增加整数值,第二个表示增加小数值
127.0.0.1:6379> HINCRBY customer age 2 (integer) 20 127.0.0.1:6379> HINCRBY customer age 2 (integer) 22 127.0.0.1:6379> HGET customer age "22" 127.0.0.1:6379> HSET customer score 91.5 (integer) 1 127.0.0.1:6379> HINCRBYFLOAT customer score 0.6 "92.1" 127.0.0.1:6379> HGET customer score "92.1"
6、hsetnx,如果哈希中不存在添加的 key 就成功添加,如果存在就添加失败
127.0.0.1:6379> HSETNX customer age 100 (integer) 0 127.0.0.1:6379> HSETNX customer email cy.mofan@qq.com (integer) 1 127.0.0.1:6379> HGET customer email "cy.mofan@qq.com" 127.0.0.1:6379> HGET customer age "22"
4.7 ZSet 有序集合
常用的一些命令
命令 | 描述 |
---|---|
ZADD key score1 member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZCARD key | 获取有序集合的成员数 |
ZCOUNT key min max | 计算在有序集合中指定区间分数的成员数 |
ZINCRBY key increment member | 有序集合中对指定成员的分数加上增量 increment |
ZINTERSTORE destination numkeys key [key …] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
ZLEXCOUNT key min max | 在有序集合中计算指定字典区间内成员数量 |
ZRANGE key start stop [WITHSCORES] | 通过索引区间返回有序集合指定区间内的成员 |
ZRANGEBYLEX key min max [LIMIT offset count] | 通过字典区间返回有序集合的成员 |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] | 通过分数返回有序集合指定区间内的成员 |
ZRANK key member | 返回有序集合中指定成员的索引 |
ZREM key member [member …] | 移除有序集合中的一个或多个成员 |
ZREMRANGEBYLEX key min max | 移除有序集合中给定的字典区间的所有成员 |
ZREMRANGEBYRANK key start stop | 移除有序集合中给定的排名区间的所有成员 |
ZREMRANGEBYSCORE key min max | 移除有序集合中给定的分数区间的所有成员 |
ZREVRANGE key start stop [WITHSCORES] | 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
ZREVRANGEBYSCORE key max min [WITHSCORES] | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
ZREVRANK key member | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
ZSCORE key member | 返回有序集中,成员的分数值 |
ZUNIONSTORE destination numkeys key [key …] | 计算给定的一个或多个有序集的并集,并存储在新的 key 中 |
ZSCAN key cursor [MATCH pattern] [COUNT count] | 迭代有序集合中的元素(包括元素成员和元素分值) |
命令示例
Zset 是在 Set 基础上,加一个 score 值。 之前 Set 是k1 v1 v2 v3
, 现在 Zset 是k1 score1 v1 score2 v2
。
1、zadd / zrange(withscores)
127.0.0.1:6379> ZADD zset01 60 v1 70 v2 80 v3 90 v4 100 v5 (integer) 5 127.0.0.1:6379> ZRANGE zset01 0 -1 1) "v1" 2) "v2" 3) "v3" 4) "v4" 5) "v5" 127.0.0.1:6379> ZRANGE zset01 0 -1 withscores 1) "v1" 2) "60" 3) "v2" 4) "70" 5) "v3" 6) "80" 7) "v4" 8) "90" 9) "v5" 10) "100"
2、zrangebyscore key 开始score 结束score
- withscores
- 如果 score 带上
(
表示不包含指定的 score - 后面还可以使用 limit 用来限制返回(类似分页),使用方法是:
limit 开始下标步 多少步
127.0.0.1:6379> ZRANGEBYSCORE zset01 60 90 1) "v1" 2) "v2" 3) "v3" 4) "v4" 127.0.0.1:6379> ZRANGEBYSCORE zset01 60 (90 1) "v1" 2) "v2" 3) "v3" 127.0.0.1:6379> ZRANGEBYSCORE zset01 (60 (90 1) "v2" 2) "v3" 127.0.0.1:6379> ZRANGEBYSCORE zset01 60 90 1) "v1" 2) "v2" 3) "v3" 4) "v4" 127.0.0.1:6379> ZRANGEBYSCORE zset01 60 90 limit 2 2 1) "v3" 2) "v4" 127.0.0.1:6379> ZRANGEBYSCORE zset01 60 90 limit 2 2 withscores 1) "v3" 2) "80" 3) "v4" 4) "90"
3、zrem key 某score下对应的value值
,作用是删除元素
127.0.0.1:6379> ZREM zset01 v5 (integer) 1 127.0.0.1:6379> ZRANGE zset01 0 -1 withscores 1) "v1" 2) "60" 3) "v2" 4) "70" 5) "v3" 6) "80" 7) "v4" 8) "90"
4、计算个数、获取下标和获得分数:
zcard key
:获取 Zset 的元素个数zcount key score区间
:获得区间范围内的元素个数zrank key score值
:获取 score 值对应的下标zscore key 对应值
:获得 对应值的分数
127.0.0.1:6379> ZCARD zset01 (integer) 4 127.0.0.1:6379> ZCOUNT zset01 60 80 (integer) 3 127.0.0.1:6379> ZRANK zset01 v4 (integer) 3 127.0.0.1:6379> ZSCORE zset01 v4 "90"
5、zrevrank key values值
,逆序获得下标值
127.0.0.1:6379> ZRANGE zset01 0 -1 1) "v1" 2) "v2" 3) "v3" 4) "v4" 127.0.0.1:6379> ZREVRANK zset01 v4 (integer) 0
6、zrevrange 可以逆序遍历 Zset 中的值
127.0.0.1:6379> ZRANGE zset01 0 -1 1) "v1" 2) "v2" 3) "v3" 4) "v4" 127.0.0.1:6379> ZREVRANGE zset01 0 -1 1) "v4" 2) "v3" 3) "v2" 4) "v1"
7、zrevrangebyscore key 结束score 开始score
,逆序根据 score 返回获取对应的值
127.0.0.1:6379> ZREVRANGEBYSCORE zset01 90 60 1) "v4" 2) "v3" 3) "v2" 4) "v1" 127.0.0.1:6379> ZREVRANGEBYSCORE zset01 80 50 1) "v3" 2) "v2" 3) "v1" 127.0.0.1:6379> ZREVRANGEBYSCORE zset01 80 70 1) "v3" 2) "v2"
5. 配置文件解析
5.1 配置文件在哪
Redis 的配置文件位于 Redis 安装目录下,其名为 redis.conf,我们在前文安装 Redis 的时候就将这份配置文件进行了拷贝并将拷贝文件移动到其他目录,而没有修改原配置文件。
我们 Redis 的安装目录在 /usr/local/redis
下,在 Linux 中使用命令查看一下:
Redis 那么多配置项,我们可以根据 config get
命令来获取配置项,比如:
127.0.0.1:6379> config get port 1) "port" 2) "6379"
注意:本节配置项基于旧版本的 Redis,新版本 Redis 在部分配置项的所处位置或是否保留可能存在差异。
5.2 Redis 常见配置项
配置项 | 说明 |
---|---|
daemonize no |
Redis 默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进程(Windows 不支持守护线程的配置为 no ) |
protected-mode yes |
开启保护模式,需要配置 bind ip 或设置访问密码 |
pidfile /var/run/redis_6379.pid |
当 Redis 以守护进程方式运行时,Redis 默认会把 pid 写入 /var/run/redis_6379.pid 文件,可以通过 pidfile 指定 |
port 6379 |
指定 Redis 监听端口,默认端口为 6379,作者在自己的一篇博文中解释了为什么选用 6379 作为默认端口,因为 6379 在手机按键上 MERZ 对应的号码,而 MERZ 取自意大利歌女 Alessia Merz 的名字 |
bind 127.0.0.1 |
绑定的主机地址 |
timeout 300 |
当客户端闲置多长秒后关闭连接,如果指定为 0 ,表示关闭该功能 |
loglevel notice |
指定日志记录级别,Redis 总共支持四个级别:debug、verbose、notice、warning,默认为 notice |
logfile "" |
日志记录方式,默认为标准输出,如果配置 Redis 为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给 /dev/null |
databases 16 |
设置数据库的数量,默认数据库为 0,可以使用 SELECT 命令在连接上指定数据库 id |
save <seconds> <changes> Redis 默认配置文件中提供了三个条件:save 900 1 save 300 10 save 60 10000 |
分别表示 900 秒(15 分钟)内有 1 个更改,300 秒(5 分钟)内有 10 个更改以及 60 秒内有 10000 个更改。指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合 |
rdbcompression yes |
指定存储至本地数据库时是否压缩数据,默认为 yes,Redis 采用 LZF 压缩,如果为了节省 CPU 时间,可以关闭该选项,但会导致数据库文件变的巨大 |
dbfilename dump.rdb |
指定本地数据库文件名,默认值为 dump.rdb |
dir ./ |
指定本地数据库文件的存放目录 |
slaveof <masterip> <masterport> |
设置当本机为 slave 服务时,设置 master 服务的 IP 地址及端口,在 Redis 启动时,它会自动从 master 进行数据同步 |
masterauth <master-password> |
当 master 服务设置了密码保护时,slav 服务连接 master 的密码 |
requirepass foobared |
设置 Redis 连接密码,如果配置了连接密码,客户端在连接 Redis 时需要通过 AUTH <password> 命令提供密码,默认关闭 |
maxclients 128 |
设置同一时间最大客户端连接数,默认无限制,Redis 可以同时打开的客户端连接数为 Redis 进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis 会关闭新的连接并向客户端返回 max number of clients reached 错误信息 |
maxmemory <bytes> |
指定 Redis 最大内存限制,Redis 在启动时会把数据加载到内存中,达到最大内存后,Redis 会先尝试清除已到期或即将到期的 Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis 新的 vm 机制,会把 Key 存放内存,Value 会存放在 swap 区 |
appendonly no |
指定是否在每次更新操作后进行日志记录,Redis 在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis 本身同步数据文件是按上面 save 条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为 no |
appendfilename appendonly.aof |
指定更新日志文件名,默认为 appendonly.aof |
appendfsync everysec |
指定更新日志条件,共有 3 个可选值: no:表示等操作系统进行数据缓存同步到磁盘(快) always:表示每次更新操作后手动调用 fsync() 将数据写到磁盘(慢,安全) everysec:表示每秒同步一次(折中,默认值) |
vm-enabled no |
指定是否启用虚拟内存机制,默认值为 no,简单的介绍一下,VM 机制将数据分页存放,由 Redis 将浏览量较少的页即冷数据 swap 到磁盘上,浏览多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析 Redis 的 VM 机制) |
vm-swap-file /tmp/redis.swap |
虚拟内存文件路径,默认值为 /tmp/redis.swap,不可多个 Redis 实例共享 |
vm-max-memory 0 |
将所有大于 vm-max-memory 的数据存入虚拟内存,无论 vm-max-memory 设置多小,所有索引数据都是内存存储的(Redis 的索引数据 就是 keys),也就是说,当 vm-max-memory 设置为 0 的时候,其实是所有 value 都存在于磁盘。默认值为 0 |
vm-page-size 32 |
Redis swap 文件分成了很多的 page,一个对象可以保存在多个 page 上面,但一个 page 上不能被多个对象共享,vm-page-size 是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page 大小最好设置为 32 或者 64bytes;如果存储很大大对象,则可以使用更大的 page,如果不确定,就使用默认值 |
vm-pages 134217728 |
设置 swap 文件中的 page 数量,由于页表(一种表示页面空闲或使用的 bitmap)是在放在内存中的,,在磁盘上每 8 个 pages 将消耗 1byte 的内存。 |
vm-max-threads 4 |
设置连接swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4 |
glueoutputbuf yes |
设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启 |
hash-max-zipmap-entries 64 hash-max-zipmap-value 512 |
指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法 |
activerehashing yes |
指定是否激活重置哈希,默认为开启(后面在介绍 Redis 的哈希算法时具体介绍) |
include /path/to/local.conf |
指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件 |
5.3 Units 单位
在配置文件开头有这样一段字:
这是配置文件大小单位,开头定义了一些基本的度量单位,只支持 bytes,不支持 bit。
注意单位 带有 字母 b 和 不带有 字母 b 的区别。
单位对大小写不敏感。
5.4 INCLUDES 包含
在配置文件中,有一栏名为 INCLUDES:
这个很好理解,和我们的 MyBatis、Spring 等配置文件类似,可以通过 includes 包含,redis.conf 可以作为总闸,包含其他配置文件。
5.5 GENERAL 通用
tcp-backlog 配置项
默认值:511,在 Redis 6.0.8 版本中该项位于 NETWORK 中。
设置 Tcp 的 backlog,backlog 其实是一个连接队列,backlog 队列总和 = 未完成三次握手队列 + 已经完成三次握手队列。
在高并发环境下你需要一个高 backlog 值来避免慢客户端连接问题。注意 Linux 内核会将这个值减小到 /proc/sys/net/core/somaxconn
的值,所以需要确认增大 somaxconn 和 tcp_max_syn_backlog 两个值来达到想要的效果。
tcp-keepalive 配置项
默认值:300,在 Redis 6.0.8 版本中该项位于 NETWORK 中。
单位为秒,如果设置为 0,这不会进行 KeepAlive 检测,建议设置成 60。
系统日志项
# To enable logging to the system logger, just set 'syslog-enabled' to yes, # and optionally update the other syslog parameters to suit your needs. # syslog-enabled no
# Specify the syslog identity. # syslog-ident redis
# Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7. # syslog-facility local0
系统日志默认是关闭的,配置项为 syslog-enabled
,表示是否把日志输出到 syslog 中
如果将系统日志打开,那么日志将会以 redis 开头,配置项为 syslog-ident
,表示指定 syslog 里的日志标志
还可以指定 syslog 设置,值可以是 USER 或 LOCAL0 - LOCAL7,配置项是 syslog-facility
5.6 SNAPSHOTTING 快照
这个配置通常与持久化 RDB 配合使用。
RDB 是整个内存的压缩过的 Snapshot,由于 RDB 的数据结构,可以配置符合的快照触发条件。
配置方式是:save 秒数 写操作次数
,Redis 的配置文件中提供了默认的配置:
表示 15 分钟内修改了 1 次,或 5 分钟内修改了 10 次,或 1 分钟内修改了 1 万次。
如果想禁用 RDB 持久化的策略,只要不设置任何 save 指令,或者给 save 传入一个空字符串参数也可以,比如 save ""
。
如果遇到特别敏感或重要的数据想要立即持久化,可以直接执行 save
命令。
当执行 SHUTDOWN 和 FLUSHALL 命令时就会生成 dump.rdb 文件,这个 rdb 文件就存储了我们的数据信息,等到再次启动的时候就会使用这个文件恢复数据。
但是需要注意的是,执行 SHUTDOWN 和 FLUSHALL 命令生成的 rdb 文件内容是执行命令后的库中的数据。也就是说,执行 FLUSHALL 命令会生成 rdb 文件,但是由于清空了库,生成的 rdb 文件内是没有任何内容的,再次启动 Redis 会使用这个文件恢复数据,但由于文件内没有数据,因此也就恢复不了什么数据。
一般情况下,我们会对 dump.rdb 文件进行备份,且备份在另一台机器上,如果执行 FLUSHALL 了,只需要将另一台机器备份的 rdb 文件转移到本机上,并将名字修改为 dump.rdb,再次启动 Redis,那么还是可以恢复数据的。
其他相关配置
在这一栏中还有一些配置项,比如:
1、stop-writes-on-bgsave-error
,这个配置项默认值是 yes。如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制。
2、rdbcompression
,这个配置项默认值是 yes。它表示对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果设置为 yes,Redis 会采用 LZF 算法进行压缩。如果你不想消耗 CPU 来进行压缩的话,可以设置为 no 关闭此功能。
3、rdbchecksum
,这个配置项默认值是 yes。它表示在存储快照后,还可以让 Redis 使用 CRC64 算法来进行数据校验,但是这样做会增加大约 10% 的性能消耗,如果希望获取到最大的性能提升,可以设置为 no 关闭此功能。
4、dbfilename
,这个配置项默认值是 dump.rdb。它指定了本地数据库文件名,默认值为 dump.rdb,也就是我们用来进行数据恢复的文件名。
5、dir
,这个配置项的默认值是 ./
。它指定本地数据库文件的存放目录,也就是 dump.rdb 的存放位置,默认是存放在 Redis 启动位置的。如果在默认值的情况下,我现在 /usr/local/bin
目录下启动了 Redis,然后添加了一些数据,最后关闭 Redis,并在目录下生成 dump.rdb 文件。之后我在 /
目录下启动 Redis,执行 keys *
是获取不到上次添加的数据的,因为当前目录下没有上次生成 的 dump.rdb 文件。可以对这个配置项进行配置,指定存放目录,这样就不会出现这种情况了。
5.7 REPLICATION 复制
与主从复制相关,参考【Redis 基础 下篇】一文。
5.8 SECURITY 安全
在这一栏可以进行访问密码的查看、设置和取消。
当我们启动了 Redis 并连接 Redis 时,只需要输入两个命令就可以了,而不用输入密码,但是根据 MySQL 的使用经验,MySQL 连接是需要输入密码的,当然也可以通过设置而不输入密码。
Redis连接动默认是不需要输入密码的,当然也可以设置输入密码。
我们连接 Redis 使用命令 config get requirepass
来看看 Redis 的连接密码:
127.0.0.1:6379> config get requirepass 1) "requirepass" 2) ""
既然可以 get 获取配置,那也可以通过 set 来设置配置:
127.0.0.1:6379> config set requirepass "mofan212" OK 127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "mofan212"
然后重启 Redis,如果我们再次 ping,就无法显示 PONG,需要在输入命令前执行 auth 设置的密码
。
如果需要恢复原来的设置,需要执行 config set requirepass ''
5.9 LIMITS 限制
在 Redis 6.0.8 版本中该栏已经被删除,其配置项分布在其他栏中。
maxclients 配置项分布在 CLIENTS
栏中,表示 Redis 客户端最大连接数。
maxmemory 配置项分布在 MEMORY MANAGEMENT
栏中,表示 Redis 的最大占用内存,默认是没被设置的。
maxmemory-policy 配置项分布在 MEMORY MANAGEMENT
栏中。
maxmemory-samples 配置项分布在 MEMORY MANAGEMENT
栏中。
我们可以使用命令查看一下这些配置项的默认值:
127.0.0.1:6379> config get maxclients 1) "maxclients" 2) "10000" 127.0.0.1:6379> config get maxmemory 1) "maxmemory" 2) "0" 127.0.0.1:6379> config get maxmemory-policy 1) "maxmemory-policy" 2) "noeviction" 127.0.0.1:6379> config get maxmemory-samples 1) "maxmemory-samples" 2) "5"
配置文件中说明的缓存过期策略(maxmemory-policy
):
默认是永不过期(noeviction)的。对上述内容翻译一下:
volatile-lru:只对设置了过期时间的键使用 LRU 算法移除key。
allkeys-lru:使用 LRU 算法移除 key。
volatile-lfu:只对设置了过期时间的键使用 LFU 算法移除key。
allkeys-lfu:使用 LFU 算法移除 key。
volatile-random:只对设置了过期时间的键在过期集合中移除随机的 key
allkeys-random:移除随机的 key。
volatile-ttl:移除那些 TTL 值最小的 key,即那些最近要过期的 key。
noeviction:不进行移除。针对写操作,只是返回错误信息。
其中:
-
LRU:最近最久未使用
-
LFU:最近最少使用
-
TTL:Time To Live,生存时间值
maxmemory-samples
表示设置样本数量,LRU 算法和最小 TTL 算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,Redis 默认会检查这么多个 key 并选择其中 LRU 的那个
5.10 APPEND ONLY MODE 追加
这个配置通常与持久化 AOF 配合使用。
在配置文件 redis.conf 中可以看到,append only 默认是关闭的:
执行 cp redis.conf redis_aof.conf
命令,将配置文件拷贝一份,修改拷贝的文件,将 appendonly 打开,修改为 yes 就行。
在这个配置项的下面几行就可以看到 appendfilename "appendonly.aof"
配置项,它指定了保存的文件名,不做修改,使用默认的就行。
AOF 与 RDB
接下里前往 /usr/local/bin
目录删除生成的 rdb 文件: rm -f dump.rdb
然后在此目录下启动 Redis,就像:
[root@cheny bin]# redis-server /Redis/redis_aof.conf [root@cheny bin]# redis-cli -p 6379 127.0.0.1:6379> keys * (empty array)
启动 Redis 后,就会在当前目录下生成 appendonly.aof 文件。然后添加一些数据,并执行 FLUSHALL
命令,关闭 Redis:
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> set k2 v2 OK 127.0.0.1:6379> set k3 v3 OK 127.0.0.1:6379> set k4 v4 OK 127.0.0.1:6379> FLUSHALL OK 127.0.0.1:6379> SHUTDOWN not connected> exit
这之后还会在当前目录下生成 dump.rdb 文件,为了不必要的误会,我们干掉它:rm -f dump.rdb
我们可以执行 cat appendonly.aof
命令,看看这个文件中的内容,可以看到这个文件里记录了我们刚才所做的一些操作,同时也记录了最后的 FLUSHALL
操作。
因此当我们再次按照上述方式启动 Redis,并执行 ksys *
命令查看库中的 key 时,会读取 aof 文件进行数据恢复,但由于记录了 FLUSHALL
操作,因此库中还是空的。
既然如此,可以编辑一下 appendonly.aof
文件,将最后记录的 FLUSHALL
操作给删了(删除光标所在行的命令是 dd
)。
最后还是像前面的操作一样启动 Redis,执行 keys *
命令,可以看到库中有数据了:
[root@cheny bin]# redis-server /Redis/redis_aof.conf [root@cheny bin]# redis-cli -p 6379 127.0.0.1:6379> KEYS * 1) "k3" 2) "k1" 3) "k4" 4) "k2"
我们退出 Redis,然后修改 appendonly.aof
文件,在最后胡乱添加一些数据,模拟文件损坏。
然后再启动 Redis,接下来能够成功启动 Redis 吗?
[root@cheny bin]# redis-server /Redis/redis_aof.conf [root@cheny bin]# ps -ef|grep redis root 25428 23535 0 20:39 pts/1 00:00:00 grep --color=auto redis [root@cheny bin]# redis-cli -p 6379 Could not connect to Redis at 127.0.0.1:6379: Connection refused
可以看到是无法启动 Redis 的,这验证了 aof 文件和 rdb 文件是可以共存的,并且会优先加载 aof 文件。
在配置文件中也可以看到,RDB 和 AOF 是可以共存的:
由于 aof 文件损坏,因此是无法启动 Redis 的,那么可以修复吗?
那当然是可以的,只需要执行命令 redis-check-aof --fix appendonly.aof
,就像:
然后再启动 Redis,就能够成功启动了,并且还能够查询到前面添加的数据。🐮
如果查看 appendonly.aof
文件,甚至可以看到我们胡乱添加的数据已经被干掉了。
其他相关配置
1、appendfsync
表示追加同步,Redis 给定了三种方式:
- always:同步持久化。每次发生数据变更都会被立即记录到磁盘,性能较差但数据完整性比较好。
- everysec:出厂 默认 推荐,异步操作,每秒记录,如果一秒内宕机,会有数据丢失。
- no:从不同步
2、no-appendfsync-on-rewrite
:默认值是 no,表示重写时是否可以运用 appendfsync,用默认即可,保证数据安全性。
3、auto-aof-rewrite-min-size
:默认是 64 mb,用于设置重写的基准值
4、auto-aof-rewrite-percentage
:默认是 100,用于设置重写的基准值
6. 持久化之 RDB
6.1 什么是 RDB
RDB 全称是 Redis DataBase。
它就是在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的 Snapshot 快照,它恢复时是将快照文件直接读到内存里。
Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能。
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。
RDB 的缺点是最后一次持久化后的数据可能丢失。
Fork 的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程。
RDB 保存的是 dump.rdb 文件。
那在哪里进行配置呢?在 Redis 的配置文件 redis.conf 搜索 SNAPSHOTTING
,参考本文 5.6。
RDB 产生的快照文件是临时文件,当 Redis 关闭后,快照文件也会一并删除。因此常常需要将快照文件修改名称后并备份,等 Redis 重新启动后,再把名称改为 dump.rdb,此时备份数据会直接加载。
6.2 如何触发 RDB 快照
触发 RDB 快照有以下几种方式:
1、配置文件中 SNAPSHOTTING
一栏的默认的快照配置。dbfilename dump.rdb
指定了数据库文件名,一般会冷拷贝后重新使用,可以使用 cp dump.rdb dump_new.rdb
进行拷贝。
2、执行命令 save 或者是 bgsave 后:
- SAVE:save 时只管保存,其它不管,全部阻塞
- BGSAVE:Redis 会在后台异步进行快照操作, 快照同时还可以响应客户端请求。可以通过lastsave 命令获取最后一次成功执行快照的时间
3、执行 flushall 命令,也会产生 dump.rdb 文件,但里面是空的,无意义。
4、SHUTDOWN(正常关闭)时,如果没有开启 AOF,会触发 RDB。kill -9
意外宕机不会触发 RDB 持久化。
第一点属于自动触发,后面三点属于手动触发。
6.3 如何恢复数据
将备份文件(dump.rdb)移动到 Redis 安装目录并启动服务即可,可以使用 CONFIG GET dir
命令获取目录。
6.4 优势与劣势
优势
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高
- 节省磁盘空间
- 恢复速度快
劣势
- 在一定间隔时间做一次备份,所以如果 Redis 意外宕掉的话,就会丢失最后一次快照后的所有修改
- fork 的时候,内存中的数据被克隆了一份,大致 2 倍的膨胀性需要考虑
- 虽然 Redis 在 fork 时使用了写时拷贝技术,但如果数据过于庞大时还是比较消耗性能
6.5 如何停止
动态所有停止 RDB 保存规则的方法:redis-cli config set save ""
6.6 小结
- RDB 是一个非常紧凑的文件。
- RDB 在保存 RDB 文件时父进程唯一需要做的就是 fork 出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他 IO 操作,所以 RDB 持久化方式可以最大化 Redis 的性能。
- 与 AOF 相比,在恢复大的数据集的时候,RDB 方式会更快一一些。
- 数据丢失风险大。
- RDB 需要经常 fork 子进程来保存数据集到硬盘上,当数据集比较大的时候 fork 的过程是非常耗时的吗,可能会导致 Redis 在一些毫秒级不能回应客户端请求。
7. 持久化之 AOF
7.1 什么是 AOF
既然有了 RDB 为什么还要有 AOF 呢?AOF 作为新技术解决了 RDB 不能解决的什么问题?如果同时存在 RDB 和 AOF,它们会发生冲突,还是进行协作?
AOF 全称是 Append Only File。
它是以日志的形式来记录每个写操作,将 Redis 执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,Redis 启动之初会读取该文件重新构建数据,换言之,Redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF 保存的是 appendonly.aof 文件(可以在配置文件可修改文件名,就像 RDB 一样)
那在哪里进行配置呢? 在 Redis 的配置文件 redis.conf 搜索 APPEND ONLY MODE
,参考本文 5.10。
默认 AOF 是关闭的,只需修改 appendonly
配置项为 yes 就行。
AOF 的备份并恢复操作和 RDB 是一样的,都是拷贝备份文件,进行数据恢复时拷贝到 Redis 工作目录下,系统启动时自动加载。
7.2 启动、修复和恢复
正常恢复
启动:设置 yes。修改默认的 appendonly no,改为 yes 即可。
一般来说会对 aof 文件进行备份,将有数据的 aof 文件复制一份保存到对应目录(config get dir
)。
恢复:重启 Redis 然后重新加载。
异常恢复
启动:与正常恢复一样,设置 yes。修改默认的 appendonly no
,改为 yes 即可。
先备份被写坏的 aof 文件,然后执行 Redis-check-aof --fix aof文件
进行修复。
恢复:重启 Redis 然后重新加载。
7.3 Rewrite
什么是 rewrite
AOF 采用文件追加方式,文件会越来越大。为避免出现此种情况,新增了重写机制, 当 AOF 文件的大小超过所设定的阈值时,Redis 就会启动 AOF 文件的内容压缩, 只保留可以恢复数据的最小指令集。可以使用命令 bgrewriteaof
。
重写原理
AOF 文件持续增长而过大时,会 fork 出一条新进程来将文件重写(也是先写临时文件最后再 rename), 遍历新进程的内存中数据,每条记录有一条的 set 语句。重写 aof 文件的操作,并没有读取旧的 aof 文件, 而是将整个内存中的数据库内容用命令的方式重写了一个新的 aof 文件,这点和快照有点类似。
触发机制
Redis 会记录上次重写时的 aof 大小,默认配置是当 aof 文件大小是上次 rewrite 后大小的一倍且文件大于 64M 时触发。
配置文件 APPEND ONLY MODE 一栏中的配置项:
auto-aof-rewrite-min-size
:默认是 64 mb,rewrite 触发时 aof 文件的最小值
auto-aof-rewrite-percentage
:默认是 100,表示 100%,即一倍
7.4 优势与劣势
优势
- 每秒同步:appendfsync always(同步持久化)每次发生数据变更会被立即记录到磁盘,性能较差但数据完整性比较好
- 每修改同步:appendfsync everysec(异步操作,每秒记录)默认方式。如果一秒内宕机,有数据丢失
- 不同步:appendfsync no (从不同步)
劣势
- 相同数据集的数据而言 aof 文件要远大于 rdb 文件,恢复速度慢于 rdb
- AOF 运行效率要慢于 RDB,每秒同步策略效率较好,不同步效率和 RDB 相同
7.5 小结
- aof 文件是一个只进行追加的日志文件
- Redis 可以在 aof 文件体积变得过大时,自动地在后台对 aof 进行重写
- aof 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 Redis 协议的格式保存,因此 aof 文件的内容非常容易被人读懂,对文件进行分析也很轻松
- 对于相同的数据集来说,aof 文件的体积通常要大于 rdb 文件的体积
- 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB
7.6 持久化总结
两种持久化策略到底该选哪一种呢?
RDB 持久化方式能够在指定的时间间隔能对数据进行快照存储
AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF 命令以 Redis 协议追加保存每次写的操作到文件末尾
Redis 还能对 aof 文件进行后台重写,使得 aof 文件的体积不至于过大
只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式
同时开启两种持久化方式
这样会先加载 aof 文件。
在这种情况下,当 Redis 重启的时候会优先载入 aof 文件来恢复原始的数据,因为在通常情况下 aof 文件保存的数据集要比 rdb 文件保存的数据集更完整。
RDB 的数据不实时,同时使用两者时服务器重启也只会找 aof 文件。那要不要只使用 AOF 呢?
Redis 作者建议不要,因为 RDB 更适合用于备份数据库( AOF 在不断变化不好备份),快速重启,而且不会有 AOF 可能潜在的 bug, 留着作为一个万一的手段。
性能建议
因为 rdb 文件只用作后备用途,建议只在 slave 上持久化 rdb 文件,而且只要 15 分钟备份一次就够了,只保留 save 900 1
这条规则。
如果使用 AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只加载自己的 aof 文件就可以了。代价一是带来了持续的 IO,二是 AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少 AOF rewrite 的频率,AOF 重写的基础大小默认值 64M 太小了,可以设到 5G 以上。默认是超过原文件大小 100% 时进行重写可以修改到适当的数值。
如果不使用 AOF,仅靠 Master-Slave Replication (主从复制)实现高可用性也可以。能省掉一大笔 IO 也减少了 rewrite 时带来的系统波 动。代价是如果 Master / Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master / Slave 中的 rdb 文件,并载入较新的那个。新浪微博就选用了这种架构。