目录
1. Redis 基本概念
2. Redis 的优势
3. Redis 适用场景
4. Redis-3.2.6 安装(未整理)与测试
5. 使用 Redis 的 Java API 客户端——Jedis
6. 数据结构
6.1 String -- 字符串
6.1.1 String 使用概述
6.1.2 String 常用操作
6.1.3 String 使用案例
6.2 List -- 列表
6.2.1 List 使用概述
6.2.2 List 应用案例
6.3 Set -- 集合
6.3.1 Set 使用概述
6.3.2 Set 使用案例
6.4 ZSet -- 有序集合
6.4.1 ZSet 使用概述
6.4.2 ZSet 使用案例
6.5 Hash -- 哈希表
6.5.1 Hash 使用概述
6.5.2 Hash 案例
6.6 键值相关的命令
6.7 服务器相关命令
6.8 Redis 事务
7. Redis 的高可用
8. Redis 的持久化
8.1 RDB 持久化
8.2 AOF 持久化
8.3 总结
1. Redis 基本概念
Remote Dictionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的开源的、高性能的、使用 ANSI C 语言编写的、遵守 BSD 协议、支持网络、可基于内存亦可持久化的日志型、Key-Value 存储系统,并提供多种语言的 API。
和 memcached 类似,Redis 常被称作是一款 Key-Value 内存存储系统或者内存数据库,同时由于它支持丰富的数据结构,又被称为一种数据结构服务器(Data Structure Server),因为其值(Value)可以是字符串(String)、哈希(Map)、列表(List)、集合(Set)和有序集合(Sorted Set)等类型。
Redis 与其他 Key-Value 缓存产品有如下三个特点:
1. Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
2. Redis 不仅仅支持简单的 Key-Value 类型的数据,同时还提供 List, Set, ZSet, Hash 等数据结构的存储
3. Redis 支持数据的备份,即 Master-Slave 模式的数据备份
2. Redis 的优势
1. 性能极高:Redis 读的速度可达 110000 次/s,写的速度可达 81000 次/s
2. 丰富的数据类型:Redis 支持二进制案例的 String, List, Hash, Set 以及 Sorted Set 数据类型的操作
3. 原子操作:Redis 的所有操作都是原子性的,同时 Redis 还支持对几个操作合并后的原子性执行
4. 丰富的特性:Redis 还支持 Publish/Subscribe,通知 Key 过期,支持高可用集群等特性
5. 数据持久化机制:持久化机制有两种:1)RDB 方式:定期将内存数据 dump 到磁盘 2)AOF(Append Only File) 持久化机制:用记日志的方式记录每一条数据更新操作,一旦出现灾难事件,可以通过日志重放来恢复整个数据库
3. Redis 适用场景
1. TopN 需求:取最新的 N 个数据,如读取作家博客最新的 50 篇文章,通过 List 实现对按时间排序的数据的高效读取
2. 排行榜应用:以特定条件为排序标准,将其设成 Sorted Set 的 score,进而实现高效获取
3. 需要精准设定过期时间的应用:把 Sorted Set 的 score 值设置成过期时间的时间戳,那么就可以简单地通过过期时间排序,定时清除过期数据了
4. 计数器应用:Redis 的命令都是原子性的,可以轻松地利用 INCR, DECR 命令来构建计数器系统
5. 去除大量数据中的重复数据:将数据放入 Set 中,就能实现对重复数据的排除
6. 构建队列系统:使用 List 可以构建队列系统,使用 Sorted Set 甚至可以构建有优先级的队列系统
7. 实时系统、反垃圾系统:通过上面说到的 Set 功能,可以知道有一个终端用户是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等
8. Publish/Subscribe 构建实时消息系统
9. 缓存(会话、商品列表、评论列表、经常查询的数据等)
以一个某技术社区为例:
记录帖子的点赞数、评论数和点击数(Hash) |
记录用户的帖子 ID 列表 (排序),便于快速显示用户的帖子列表(ZSet) |
记录帖子的标题、摘要、作者和封面信息,用于列表页展示(Hash) |
记录帖子的点赞用户 ID 列表,评论 ID 列表,用于显示和去重计数(ZSet) |
缓存近期热帖内容 (帖子内容空间占用比较大),减少数据库压力 (Hash) |
记录帖子的相关文章 ID,根据内容推荐相关帖子(List) |
如果帖子 ID 是整数自增的,可以使用 Redis 来分配帖子 ID(计数器) |
收藏集和帖子之间的关系(ZSet) |
记录热榜帖子 ID 列表,总热榜和分类热榜(ZSet) |
缓存用户行为历史,进行恶意行为过滤(ZSet,Hash) |
4. Redis-3.2.6 安装(未整理)与测试
官网:https://redis.io/ 下载安装包
假设安装已完成
启动 Redis Server
redis-server
后台启动
nohup redis-server 1>~/data/redis/redis_std.log 2>~/data/redis/redis_err.log &
启动客户端执行命令
redis-cli
检测 Redis 是否正常工作
PING
5. 使用 Redis 的 Java API 客户端——Jedis
1. 新建一个 Maven 工程,导入如下依赖:
<dependency> <groupId>redis.clients</groupId> <artiFactId>jedis</artiFactId> <version>2.9.0</version>
</dependency>
2. 编写一个测试类用来测试客户端是否可以访问服务器
package cn.gldwolf.jedis import redis.clients.jedis.Jedis; \public class JedisClientDriver { public static void main(String[] args) { // 创建一个 Jedis 客户端对象 Jedis client = new Jedis("hdp01", 6379); // 测试服务器是否连通 String response = client.ping(); Sytstme.out.println(response); // 若能 ping 通,则会返回一个 PONG }
}
6. 数据结构
6.1 String -- 字符串
6.1.1 String 使用概述
String 是 Redis 最基本的类型,可以理解成与 Memcached 一模一样的类型,一个 Key 对应一个 Value.
String 类型是二进制安全的。意思是 Redis 的 String 可以包含任何数据,比如 jpg 图片或者序列化的对象。
String 类型是 Redis 最基本的数据类型,一个键最大能存储 512MB.
命令 | 介绍 |
SET key value | 设置值 |
SETNX key value | 如果 Key 存在,返回 0 且修改不生效 |
SETEX key seconds value | 指定有效期为 seconds 秒 |
SETRANGE key offset value | 将 Key 对应的 Value 第 n 位后面的字符替换成 value |
MSET key value [key value ...] | 一次设置多个值 |
MSETNX key value [key value ...] | 类似 SETNX,设置多个值,如果 Key 存在,则返回 0 且修改不生效 |
GET key | 获取 Key 对应的值 |
GETSET key value | 设置 Key 的值,并返回 Key 的旧值 |
GETRANGE key start end | 获取索引位置从 start 到 end 的 Key 对应值的字符串 |
MGET key [key ...] | 一次获取多个 Key 对应的值,如果不存在,则返回 nil |
INCR key | 对 Key 的值作 +1 操作,如果 INCR 一个不存在的值,则对 Key 赋值为 1,如果 Key 对应值不是 Int 类型,则返回错误:-ERR value is not an integer or out of range |
INCRBY key increment | 加指定值 increment,Key 不存在的时候会设置 Key,并认为原来的 Value 为 0 |
DECR key | 对 Key 的值作 -1 操作,DECR 一个不存在的 Key,则设置 Key 为 -1 |
DECRBY key decrement | 减指定值 |
APPEND key value | 将 value 追加到 Key 对应的值的末尾,返回新字符串的长度 |
STRLEN key | 读取 Key 对应的 value 的长度 |
6.1.2 String 常用操作
1. 插入和读取一条 String 类型的数据
2. 对 String 类型数据进行增减(前提是这条数据的 Value 可以看作数字)
3. 一次性插入或者获取多条数据
4. 在插入一条 String 类型的数据的同时为它指定一个存活期限
如下所示:test 只有 7 秒的存活期,7 秒之后会自动删除
6.1.3 String 使用案例
1. 将对象序列化成 byte 数组
package cn.gldwolf.jedis;import redis.clients.jedis.Jedis;import java.io.*;/*** @author: Gldwolf* @email: ZengqiangZhao@sina.com* @date: 2019/6/16 22:47*/class Student implements Serializable { // 必须要实现序列化private String name;private String sex;private int age;public Student() {}public Student(String name, int age, String sex) {this.name = name;this.age = age;this.sex = sex;}@Overridepublic String toString() {return "Student name: " + name + ", age: " + age + ", sex: " + sex;}
}public class TestJedisObject {public static void main(String[] args) throws IOException, ClassNotFoundException {// 创建一个 Jedis 连接对象Jedis jedis = new Jedis("hdp01", 6379);Student student = new Student("唐伯虎", 23, "男");// 将对象序列化成字节数组ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);// 用对象序列化流来将 student 对象序列化,然后把序列化之后的二进制数据写到 baos 流中oos.writeObject(student);// 将 baos 转换成字节数组byte[] sBytes = baos.toByteArray();// 将对象序列化之后的 byte 数组存到 Redis 的 String 数据结构中jedis.set("student_Tang".getBytes(), sBytes);// 根据 Key 从 Redis 中取出对象的 byte 数据byte[] response = jedis.get("student_Tang".getBytes());// 将字节数据反序列化出对象ByteArrayInputStream bais = new ByteArrayInputStream(response);ObjectInputStream ois = new ObjectInputStream(bais);// 从对象读取流中读取出 responseStudent 对象Student responseStudent = (Student)ois.readObject();System.out.println(responseStudent);}
}
2. 将对象转换成 JSON 字符串进行存储
package cn.gldwolf.jedis;import com.google.gson.Gson;
import redis.clients.jedis.Jedis;import java.io.*;/*** @author: Gldwolf* @email: ZengqiangZhao@sina.com* @date: 2019/6/16 23:00*/class Student { // 这种方式就不需要实现序列化private String name;private String sex;private int age;public Student() {}public Student(String name, int age, String sex) {this.name = name;this.age = age;this.sex = sex;}@Overridepublic String toString() {return "Student name: " + name + ", age: " + age + ", sex: " + sex;}
}public class JedisClientDriver {public static void main(String[] args) throws IOException, ClassNotFoundException {// 创建一个 Jedis 连接对象Jedis jedis = new Jedis("hdp01", 6379);Student student = new Student("唐伯虎", 23, "男");// 利用 gson 将对象转成 json 串Gson gson = new Gson();String pJson = gson.toJson(student);// 将 json 串存入 Redisjedis.set("student_Tang", pJson);// 从 Redis 中取出对象的 json 串String responseJson = jedis.get("student_Tang");// 将返回的 json 串解析成 Student 对象Student responseStudent = gson.fromJson(responseJson, Student.class);System.out.println(responseStudent);}
}
6.2 List -- 列表
6.2.1 List 使用概述
Redis 列表是简单的字符串列表,按照插入顺序排序。我们可以添加一个元素到列表的头部(左边)或者尾部(右边)。
命令 | 介绍 |
LPUSH/RPUSH key value [value ...] | 从头/尾部向 list 添加值,返回 list 长度 |
LRANGE key start stop | 返回 list 对应索引区间的值 |
LINSERT/RINSERT key BEFORE|AFTER pivot value | 在 list 的 pivot 的前面/后面插入 value |
LSET key index value | 将特定索引的值设置为 value, 注意:如果 index 为负值,则从 list 尾部开始算起 |
LREM key count value | 从 list 中删除 count 个和 value 相同的值,若 count > 0,则从链头算起,若 count < 0,则从链尾算起,若 count = 0,则删除全部 |
LTRIM key start stop | 仅保留 list 中索引从 start 到 end 的值 |
LPOP/RPOP key | 从头部/尾部删除元素,同时返回该元素 |
RPOPLPUSH source destination | 从 source 的尾部移除元素并添加到 destination 的头部,最后返回被移除的元素值,整个操作是原子性的,如果 source 是空或者不存在则返回 nil |
LINDEX key index | 返回 list 中 index 索引位置的元素 |
LLEN key | 返回 list 的长度 |
6.2.2 List 应用案例
1. 需求描述
任务调度系统:生产者不断产生任务,放入 task-queue 排队,消费者不断拿出任务来进行处理,同时放入一个 temp-queue 队列暂存,如果任务处理成功,则清除 temp-queue,否则,将任务弹回 task-queue
2. 架构思路图解
3. 代码实现
TaskProducer.class
package cn.gldwolf.jedis;import redis.clients.jedis.Jedis;import java.util.Random;
import java.util.UUID;/*** @author: Gldwolf* @email: ZengqiangZhao@sina.com* @date: 2019/6/17 15:44*/public class TaskProducer {public static void main(String[] args) {Jedis jedis = new Jedis("hdp01", 6379);Random random = new Random();// 生成任务while (true) {try {// 生成任务的速度有一定的随机性,在 1-2 秒之间Thread.sleep(random.nextInt(1000) + 1000);// 生成一个 taskIdString taskId = UUID.randomUUID().toString();// 往任务队列 "task-queue" 中插入,第一次插入时,"task-queue" 还不存在// 但是 lpush 方法会在 Redis 库中创建一个新的 list 数据jedis.lpush("task-queue", taskId);System.out.println("在任务队列中加入一个新的任务:" + taskId);} catch (InterruptedException e) {e.printStackTrace();}}}
}
TaskConsumer.class
package cn.gldwolf.jedis;import redis.clients.jedis.Jedis;import java.util.Random;
import java.util.UUID;/*** @author: Gldwolf* @email: ZengqiangZhao@sina.com* @date: 2019/6/17 11:31*/public class TaskConsumerForRedisList {public static void main(String[] args) {Jedis jedis = new Jedis("hdp01", 6379);Random random = new Random();while (true) {try {// 获取 taskId,将 taskId 从 "task-queue" 中弹出,并放到 "temp-queue" 队列中String taskId = jedis.rpoplpush("task-queue", "temp-queue");// 模拟处理任务的耗时Thread.sleep(1000);// 模拟有成功有失败的情况int nextInt = random.nextInt(10);// 如果任务队列中没有任务,则等待 0.5 秒if (jedis.llen("task-queue") == 0) {Thread.sleep(500);} else {// 模拟失败的情况:当 taskId 为 8 时,设置任务处理失败if (nextInt == 8) {// 失败的情况下,需要将任务从 "temp-queue" 中弹回 "task-quque"jedis.rpoplpush("temp-queue", "task-queue");System.out.println("===== 任务处理失败,失败的任务 ID 为:" + taskId + " =====");} else {// 成功的情况下,将任务从 "temp-queue" 中删除jedis.rpop("temp-queue");System.out.println("***** 任务处理成功,任务 ID 为:" + taskId + " *****");}}} catch (InterruptedException e) {e.printStackTrace();}}}
}
6.3 Set -- 集合
6.3.1 Set 使用概述
Redis 的 Set 是 String 类型的无序集合。
集合是通过哈希表实现的,所以添加、删除、查找的时间复杂度都是 O(1)。
命令 | 介绍 |
SMEMBERS key | 查看 Set 中的元素 |
SADD key member [member ...] | 向 Set 中插入 value,成功插入则返回 1,如果 Set 中已有这个 Value 则失败并返回 0 |
SREM key member [member ...] | 删除 Set 中对应的 Value,删除成功则返回 1,若不存在则返回 0 |
SPOP key [count] | 随机删除 Set 中的 count 个元素,并返回其值 |
SDIFF key [key ...] | 返回 Set1 中在 Set2 里不存在的元素 |
SDIFFSTORE destination key [key ...] | 例:SDIFFSTORE set3 set1 set2 将 Set1 中不在 Set2 中的元素保存在 Set3 中 |
SINTER key [key ...] | 返回 Set1 和 Set2 中的共有元素 |
SINTERSTORE destination key [key ...] | 将 Set1 和 Set2 中的共有元素保存到 Set3 中 |
SUNION key [key ...] | 返回 Set1 和 Set2 的并集 |
SUNIONSTORE destination key [key ...] | 将 Set1 和 Set2 的并集保存到 Set3 中 |
SMOVE source destination member | 将 Set1 的 Value 移动到 Set2 中,若 Value 存在于 Set1,那么无论 Set2 是否已存在这个 Value,都返回 1(成功),若 Value 不存在于 Set1,则返回 0 (失败) |
SCARD key | 返回 Set 中的 Value 的个数 |
SISMEMBER key member | 判断 member 是否存在于 Set 中,存在则返回 1,不存在则返回 0 |
SRANDMEMBER key [count] | 随机返回 Set 中的 count 个元素 |
6.3.2 Set 使用案例
package cn.gldwolf.jedis;import redis.clients.jedis.Jedis;import java.util.Set;/*** @author: Gldwolf* @email: ZengqiangZhao@sina.com* @date: 2019/6/17 16:52*/public class SetTest {public static void main(String[] args) {Jedis jedis = new Jedis("hdp01", 6379);jedis.sadd("Hadoop", "HDFS", "MapReduce", "YARN", "Zookeeper");jedis.sadd("Spark", "Flume", "Kafka", "Redis", "Zookeeper");// 判断一个元素是否属于指定的集合System.out.println("----------- 判断一个集合中是否存在某个值——sismember ----------");Boolean isExist = jedis.sismember("Hadoop", "HDFS");System.out.println("Are there HDFS in the Hadoop: " + isExist);// 求两个集合的差集System.out.println("----------- 两个集合的差集——sdiff ----------");Set<String> diff = jedis.sdiff("Hadoop","Spark");for (String di : diff){System.out.println(di);}// 求两个集合的并集System.out.println("----------- 两个集合的并集——sunion ----------");Set<String> union = jedis.sunion("Hadoop","Spark");for(String un : union){System.out.println(un);}// 求两个集合的交集System.out.println("----------- 两个集合的交集——sinter ----------");Set<String> intersect = jedis.sinter("Hadoop","Spark");for(String inter : intersect){System.out.println(inter);}}
}
6.4 ZSet -- 有序集合
6.4.1 ZSet 使用概述
ZSet 和 Set 一样也是 String 类型元素的集合,且不允许存在重复的元素。
不同的是每个元素都会关联一个 double 类型的分数(score),Redis 正是通过分数来为集合中的成员进行从小到大的排序。
ZSet 的成员是唯一的,但分数(score)却可以重复。
命令 | 介绍 |
ZADD key [NX|XX] [CH] [INCR] score member [score member ...] | 如果 Zset 中不存在这个元素,则向 ZSet 中添加一个或多个元素,同时返回 1。或者对一个已经存在的元素更新其 score 同时返回 0 |
ZRANGE key start stop [WITHSCORES] | 根据 index 返回给定范围内的元素,WITHSCORES 为可选,表示是否同时显示元素对应的 score |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] | 根据 score 返回给定范围内的元素,WITHSCORES 为可选,表示是否同时返回元素对应的 score |
ZREM key member [member ...] | 删除 ZSet 中的一个或多个元素 |
ZINCRBY key increment member | 如果 ZSet 中存在这个元素,则对这个元素的 score 作 +increment 的操作,否则添加这个元素,并设置其 score 为 increment |
ZRANK key member | 返回这个元素在 ZSet 中根据 score 的排名,排名按 score 从小到大 |
ZREVRANK key member | 返回这个元素在 ZSet 中根据 score 的排名,排名按 score 从大到小 |
ZCOUNT key min max | 根据给定的 score 范围返回元素的个数 |
ZCARD key | 返回 ZSet 中的元素的个数 |
ZSCORE key member | 获取元素对应的 score |
ZREMRANGEBYRANK key start stop | 删除给定的索引范围内的所有元素(索引按 score 从小到大排序生成) |
ZREMRANGEBYSCORE key min max | 删除给定的 score 范围内的所有元素 |
6.4.2 ZSet 使用案例
1. 案例需求:KPL 中英雄使用率排行榜
1)在 Redis 中需要一个榜单所对应的 sorted set 数据
2)玩家每选择一个英雄打一场游戏,就对 sorted set 数据对应的英雄分数 +1
3)在查看榜单时,就调用 ZRANGE 来看榜单中的排序结果
2. 代码实现
模拟游戏玩家的选择
package cn.gldwolf.jedis;import redis.clients.jedis.Jedis;import java.util.Random;/*** @author: Gldwolf* @email: ZengqiangZhao@sina.com* @date: 2019/6/17 21:29*/public class KPLPlayer {public static void main(String[] args) throws InterruptedException {Jedis jedis = new Jedis("hdp01", 6379);Random random = new Random();String[] heros = {"李白", "孙悟空", "韩信", "赵云", "后羿", "鲁班", "妲己"};while (true) {int index = random.nextInt(heros.length);// 选择一个英雄String hero = heros[index];// 开始玩游戏Thread.sleep(100);// 给集合中的该英雄的出场次数 +1// 第一次添加的时候,集合不存在,此时使用 zincrby 方法会创建jedis.zincrby("Heros_rank", 1, hero);System.out.println("*** " + hero + " 出场了!!!***");}}
}
查看排行榜
package cn.gldwolf.jedis;import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;import java.util.Set;/*** @author: Gldwolf* @email: ZengqiangZhao@sina.com* @date: 2019/6/17 21:56*/public class KPLViewer {public static void main(String[] args) throws InterruptedException {Jedis jedis = new Jedis("hdp01", 6379);for (int i = 0; ; i++){// 每隔两秒查看一次榜单Thread.sleep(2000);System.out.println("===== 第 " + i + " 次查看榜单 =====");// 查看榜单的Set<Tuple> herosRankWithScore = jedis.zrevrangeWithScores("Heros_rank", 0, 5);for (Tuple hero : herosRankWithScore) {System.out.println(hero.getElement() + ": " + hero.getScore());}}}
}
6.5 Hash -- 哈希表
6.5.1 Hash 使用概述
Redis Hash 是一个键值对集合。
Redis Hash 类型可以看成是具有 String Key 和 String Value 的 Map 容器。
Redis Hash 是一个 String 类型的 field 和 value 的映射表,Hash 特别用于存储对象.
命令 | 介绍 |
HSET key field value | 给 object 对象添加属性和值 |
HSETNX key field value | 类似 HSET,如果 field 已存在,则返回 0 且修改不生效 |
HMSET key field value [field value ...] | 同时设置多个属性及其值 |
HGET key field | 获取对象的属性的值 |
HMGET key field [field ...] | 获取对象的多个属性的值 |
HINCRBY key field increment | 给对象的属性的值增加 increment |
HEXISTS key field | 判断对象指定的 field 是否存在,存在则返回 1,不存在则返回 0 |
HLEN key | 返回对象 fields 的数量 |
HDEL key field [field ...] | 删除对象的一个或多个 field |
HKEYS key | 返回对象所有的 field |
HVALS key | 返回对象所有的 field 对应的 value |
HGETALL key | 返回对象所有的 field 和 value |
6.5.2 Hash 案例
1. 案例需求:实现购物车
需求分析:1)加入购物车 2)查询购物车 3)修改购物车 4)清空购物车
2. 代码实现
package cn.gldwolf.jedis;import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;import java.util.Map;
import java.util.Set;/*** @author: Gldwolf* @email: ZengqiangZhao@sina.com* @date: 2019/6/18 10:12*/public class CartServiceByRedis {private Jedis jedis = null;@Beforepublic void init() {jedis = new Jedis("hdp01", 6379);}/*** 添加商品到购物车*/@Testpublic void addItemsToCart() {jedis.hset("ZZQ", "AirDots", "3");jedis.hset("Gldwolf", "AirPods", "10");jedis.hset("JJ", "Honor 20Pro", "1");}/*** 查询购物车的商品信息*/@Testpublic void getCartInfo() {Map<String, String> goodsInfo = jedis.hgetAll("JJ");Set<Map.Entry<String, String>> entrySet = goodsInfo.entrySet();for (Map.Entry<String, String> entry : entrySet) {System.out.println("JJ: " + entry.getKey() + " --> " + entry.getValue());}}/*** 更改购物车信息*/@Testpublic void modifyCart() {// 把 Gldwolf 选购的 AirPods 的数量增加 5jedis.hincrBy("Gldwolf", "AirPods", 5);}/*** 从购物车中删除商品*/@Testpublic void deleteGoods() {jedis.hdel("ZZQ", "AirDots");}@Afterpublic void closeRedis() {jedis.close();}
}
6.6 键值相关的命令
命令 | 介绍 | 返回值 |
KEYS pattern | 查询满足 pattern 的键 | 返回满足条件的 Key |
EXISTS key [key ...] | 判断一个 Key 是否存在 | 存在返回 1,不存在返回 0 |
DEL key [key ...] | 删除一个或多个 Key | 返回删除的 Key 的数量 |
EXPIRE key seconds | 设置一个 Key 的到期时间,以秒为单位 | 设置成功返回 1,Key 不存在或不能被设置返回 0 |
EXPIREAT key timestamp | 设置一个 Key 在指定时间戳之时到期 | 设置成功返回 1,Key 不存在或不能被设置返回 0 |
TTL key | 查询 Key 的有效时长 | 返回时长,如果该键不存在或没有超时设置,则返回 -1 |
MOVE key db | 将当前数据库中的 Key 移动到其他数据库中 | 成功返回 1,否则返回 0 |
PERSIST key | 移除给定 Key 的到期时间 | 成功返回 1,如果该 Key 不存在或没有设置过期时间则返回 0 |
RANDOMKEY - | 随机获取一个 Key | 返回一个随机获取的 Key |
RENAME key newkey | 重命名 Key,如果 newKey 已经存在则覆盖 | 如果 Key 不存在则返回错误信息,否则返回 OK |
RENAMENX key newkey | 如果 newKey 存在,则失败且返回 0,否则 重命名 Key | 成功返回 1,否则返回 0 |
TYPE key | 查询 Key 的类型 | 返回 Key 的类型 |
6.7 服务器相关命令
命令 | 介绍 |
SELECT index | 选择数据库(Redis 数据库编号为 0-15) |
QUIT - | 退出 |
ECHO message | 打印 message |
DBSIZE - | 返回当前数据库中 Key 的数量 |
INFO [section] | 获取服务器的信息和统计 |
FLUSHDB - | 删除当前选择数据库中的所有 Key |
FLUSHALL - | 删除所有数据库中的所有的 Key(跑路) |
6.8 Redis 事务
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按照顺序串行化执行而不会被其它命令插入,不允许加塞。
命令:
discard: 取消事务放弃执行事务块内的所有命令
exec: 执行事务块内的命令
multi: 标记一个事务块的开始
unwatch: 取消 watch 对所有 Key 的监视
watch: 监视一个或多个 Key,如果在事务执行之前被其他命令所改动,那么事务被打断
不保证原子性:Redis 同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
Note: 1)如果中间一条命令语法写错了,则全部撤销 2)运行时报出异常,只撤销异常的语句
7. Redis 的高可用
在 Web 服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%, 99.99% 等)。但是在 Redis 语境中,高可用的含义不仅仅是能提供正常的服务(如主从分离,快速容灾技术),还要保证数据的安全性以及数据容量的可扩展性。
在 Redis 中,实现高可用的技术主要包括:持久化、复制、哨兵和集群。解释说明如下:
1. 持久化
持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储到硬盘,保证数据不会因进程的退出而丢失。
2. 主从复制
复制是高可用 Redis 的基础,哨兵和集群都是在复制的基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
3. 哨兵
哨兵在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载;存储能力受到单机的限制。
4. 集群
通过集群,Redis 解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。
8. Redis 的持久化
持久化功能:Redis 是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将 Redis 中的数据以某种形式(数据或命令)从内存中持久化到磁盘。当下次重启 Redis 时,利用持久化的文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。
Redis 的持久化分为:
RDB 持久化
AOP 持久化
RDB 持久化原理:将 Redis 在内存中的数据库记录定时 dump 到磁盘上的 RDB 持久化
AOF 持久化原理:将 Redis 的操作日志依次以追加的方式写入磁盘文件(类似于 MySQL 的 binlog)
由于 AOF 持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此 AOF 是目前主流的持久化方式,不过 RDB 持久化仍然有其用武之地。
8.1 RDB 持久化
RDB 持久化是批在指定时间间隔内,将内存中的数据集快照写入磁盘(因此也称作快照持久化),实际操作过程是 fork 一个子进程,先将数据集写入临时文件,保存的文件后缀是 .rdb,写入成功后,再替换之前的文件,用二进制压缩存储。当 Redis 重新启动时,可以读取快照文件恢复数据。
有两个触发条件:
1. 手动方式
save 命令和 bgsave 命令都可以生成 RDB 文件。
save 命令会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,在 Redis 服务器阻塞期间,服务器不能处理任何命令请求。
而 bgsave 命令会创建一个子进程,由子进程来负责创建 RDB 文件,父进程(即 Redis 主进程)则继续响应处理请求。
bgsave 命令执行过程中,只有 fork 子进程时会阻塞服务器,而对于 save 命令,整个过程都会阻塞服务器,因此 save 已基本废弃,线上环境要杜绝 save 的使用。此外,在自动触发 RDB 持久化时,Redis 也会选择 bgsave 而不是 save 来进行持久化。
2. 自动触发
save m n
自动触发最常见的情况是在配置文件中通过 save m n,指定当 m 秒内发生 n 次变化时会触发 bgsave。
save 900 1 --> 在 900 秒内如果超过 1 个 Key 被修改,就发起快照保存
save 300 10 --> 在 300 秒内如果超过 10 个 Key 被修改,就发起快照保存
save 60 10000 --> 在 60 秒内如果超过 10000 个 Key 被修改,就发起快照保存
8.2 AOF 持久化
AOF 比快照方式有更好的持久化性,是由于在使用 AOF 持久化方式时,Redis 会将每一个收到的写命令都通过 write 函数追加到文件中(默认是 appendonly.aof)。当 Redis 重启时会通过重新执行文件中保存的命令来在内存中重建整个数据库的内容。
当然,由于 os 会在内核中缓存 write 做的修改,所以可能不是立即写到磁盘上,这样 AOF 方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉 Redis 我们想要通过 fsync 函数强制 os 写入到磁盘的时机。有如下三种方式(默认是每秒 fsync 一次):
appendonly yes # 启用 AOF 持久化方式 # appendfsync always # 每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用 appendfsync everysec # 每秒种强制写入磁盘一次,在性能和持久化方面做了很好的折衷,推荐 # appendfsync no # 完全依赖 os, 性能最好,但是持久化没有保证
8.3 总结
相同数据集的数据 AOF 文件要远大于 RDB 文件,恢复速度慢于 RDB
AOF 运行效率要慢于 RDB,每秒同步策略效率较好,不同步效率和 RDB 相同
Redis 还能对 AOF 文件进行后台重写,使得 AOF 文件的体积不至于过大
总结:
RDB 持久化方式能够在指定的时间间隔内对数据进行快照存储
AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF 命令以 Redis 协议追加保存每次写的操作到文件末尾