一、目标
学习Redis基础必须掌握的内容:
- 了解 Redis 以及缓存的作用;
- 掌握 Redis 5 大基本数据类型的使用;
- 掌握常见Redis 面试题;
- 掌握 Redis 的持久化功能;
- 了解 Redis 集群功能。
二、什么是缓存?
缓存定义:缓存是⼀个高速数据交换的存储器,使用它可以快速的访问和操作数据。
当程序没有使用缓存:
这是多部分公司的普遍的架构流程图,因此当公司业务发展到⼀定规模之后,最可能出现性能瓶颈的地方就是数据库。
数据库的资源同时也是程序中最昂贵的资源,因此为了防止数据库被过度的浪费,我们就需要给它雇⼀ 个“助理”了,这个助理就是缓存系统。
加入缓存后的程序:
这样改造之后,所有的程序不会直接调用数据库,而是会先调用缓存,当缓存中有数据时会直接返回, 当缓存中没有数据时才去查询数据库,这样就大大的降低了数据库的压力,并加速了程序的响应速度。
1添加缓存后的优点
相比于数据库而言,缓存的操作性能更高,缓存性能高的主要原因有以下几个:
- 缓存⼀般都是 key-value 查询数据的,因为不像数据库⼀样还有查询的条件等因素,所以查询的性能⼀般会比数据库高;
- 缓存的数据是存储在内存中的,而数据库的数据是存储在磁盘中的,因为内存的操作性能远远大于磁盘,因此缓存的查询效率会高很多;
- 缓存更容易做分布式部署(当⼀台服务器变成多台相连的服务器集群),而数据库⼀般比较难实现 分布式部署,因此缓存的负载和性能更容易平行扩展和增加。
2缓存的分类
缓存大致可以分为两⼤类: 本地缓存、分布式缓存
①本地缓存
-
本地缓存常见使用:Spring Cache、Mybatis的缓存等。
-
本地缓存也叫单机缓存,也就是说可以应用在单机环境下的缓存。所谓的单机环境是指将服务部署到一台服务器上。
-
本地缓存的特征是只适⽤于当前系统。
举个栗子:本地缓存相当于每家企业的公司规定⼀样,不同的公司规定也是不同的,比如上班时间,不同的公司上班时间规定也是不同的,对于企事业单位来说⼀般要求 9:00-17:00 上班,而对于酒吧来说,这个时间就完全不适合了。
②分布式缓存
-
分布式缓存的常见使用:Redis和Memcached(已退出历史舞台),Redis属于分布式缓存的一种。
-
分布式缓存是指可以应用在分布式系统中的缓存。所谓的分布式系统是指将⼀套服务器部署到多台服务器,并且通过负载分发将用户的请求按照⼀定的规则分发到不同服务器。
举个栗子:分布式缓存相当于适用于所有公司的规定,比如无论是任何公司都不能偷税漏税,不能做违反法律的事情,这种情况就和分布式缓存很像,适用于所有的系统。 比如我们在分布式系统中的服务器 A 中存储了⼀个缓存 key=xiaoming,那么在服务器 B 中也可以读取 到 key=xiaoming 的数据,这样情况就是分布式缓存的作用。
3本地缓存:Spring Cache的使用
在 Spring Boot 项目,可以直接使用 Spring 的内置 Cache(本地缓存),只需要完成以下三个步骤就可以正常使用了:
①开启缓存
开启缓存只需要在启动类上添加如下代码:
@SpringBootApplication
@EnableCaching # 开启缓存功能
public class BiteApplication {
public static void main(String[] args) {
SpringApplication.run(BiteApplication.class, args);
}
}
②操作缓存
在 Service 层增加三个缓存操作的方法:添加缓存、修改缓存、删除缓存,示例代码如下:
import com.example.bittemplate.pojo.UserDO;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
/**
* 查询⽤户信息(⾛缓存)
*/
@Cacheable(cacheNames = "getuser", key = "#id")
public UserDO getUserById(int id) throws InterruptedException {
System.out.println("进⼊ get user ⽅法了。。。。。。。");
UserDO userDO = new UserDO();
userDO.setId(id);
userDO.setName("Java");
userDO.setAge(18);
return userDO;
}
/**
* 修改⽤户信息
*/
@CachePut(cacheNames = "getuser", key = "#id")
public UserDO updateUser(int id, String name) {
UserDO userDO = new UserDO();
userDO.setId(id);
userDO.setName(name);
return userDO;
}
/**
* 删除⽤户信息
*/
@CacheEvict(cacheNames = "getuser", key = "#id")
public boolean delUser(int id) {
return true;
}
}
③调用缓存
编写触发代码,在 controller 添加如下代码:
import com.example.bite.pojo.UserDO;
import com.example.bite.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
// 获得 UserService 对象
@Autowired
private UserService userService;
@RequestMapping("/getuser")
public UserDO getUserById(int id) throws InterruptedException {
return userService.getUserById(id);
}
@RequestMapping("/up")
public UserDO updateUserById(int id, String name) {
return userService.updateUser(id, name);
}
@RequestMapping("/del")
public boolean delUserById(int id) {
return userService.delUser(id);
}
}
以上步骤执行完之后,可以使用 Postman 模拟调用来查看缓存。
三、Redis基础知识
1.Linux安装Redis
①yum安装Redis
使用以下命令,直接将 redis 安装到 linux 服务器:
yum -y install redis
②启动 redis
redis-server /etc/redis.conf &
③操作Redis
使用以下命令启动 redis 客户端:
redis-cli![点击并拖拽以移动](data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)
④设置远程连接
- 将 redis 配置文件下载到本地:redis 配置文件是 linux 下的 /etc/redis.conf ;
- 将 redis.conf 中的 “bind 127.0.0.1”注释掉;
- 将 redis.conf 中的**“protected-mode yes” 改为“protected-mode no”**;
- 将修改后的 redis.conf 上传至 liunx 下的 /etc 目录;
- 使用命令
redis-cli shutdown
先关闭 redis 服务,再使用redis-server /etc/redis.conf &
启动 redis 服务。
Redis图形管理工具: Another Redis Desktop Manager
Redis管理工具无法连接的可能原因:
- 上述配置文件没有
- 服务器防火墙端口没有开放
2.Redis五大基础数据类型
-
String——字符串类型(常用)
-
Hash——字典类型(常用)
-
List——列表类型
-
Set——集合类型
-
ZSet——有序集合类型
①字符串类型
字符串类型(Simple Dynamic Strings 简称 SDS),译为:简单动态字符串,它是以键值对 keyvalue 的形式进⾏存储的,根据 key 来存储和获取 value 值,它的使用相对来说⽐较简单,但在实际项目中应非常⼴泛。
127.0.0.1:6379> set k1 v1 # 添加数据
OK
127.0.0.1:6379> get k1 # 查询数据
"v1"
127.0.0.1:6379> strlen k1 # 查询字符串的⻓度
(integer) 2
127.0.0.1:6379> set k1 v1 ex 1000 # 设置 k1 1000s 后过期(删除) exceed缩写
OK
字符串的常见使用场景:
- 存放用户(登录)信息( 可以使用ex设置无操作30min需要重新登陆);
- 存放文章详情和列表信息;
- 存放和累计网页的统计信息。 …
②字典类型
假如我们使⽤字典类型来存储⼀篇⽂章的详情信息,存储结构如下图所示:
127.0.0.1:6379> hset myhash key1 value1 # 添加数据
(integer) 1
127.0.0.1:6379> hget myhash key1 # 查询数据
"value1"
③列表类型
列表类型 (List) 是⼀个使用链表结构存储的有序结构,它的元素插入会按照先后顺序存储到链表结构中,因此它的元素操作 (插入和删除) 时间复杂度为 O(1),所以相对来说速度还是比较快的,但它的查询时间复杂度为 O(n),因此查询可能会比较慢。
127.0.0.1:6379> lpush list 1 2 3 # 添加数据
(integer) 3
127.0.0.1:6379> lpop list # 获取并删除列表的第⼀个元素
1
列表的典型使⽤场景有以下两个:
- 消息队列:列表类型可以使用 rpush 实现先进先出的功能,同时又可以使用 lpop 轻松的弹出(查询并删除)第⼀个元素,所以列表类型可以用来实现消息队列;
- ⽂章列表:对于博客站点来说,当⽤户和⽂章都越来越多时,为了加快程序的响应速度,我们可以把用户自己的文章存入到 List 中,因为 List 是有序的结构,所以这样又可以完美的实现分页功能,从而加速了程序的响应速度。
④ 集合类型
集合类型 (Set) 是⼀个无序并唯⼀的键值集合。
127.0.0.1:6379> sadd myset v1 v2 v3 # 添加数据
(integer) 3
127.0.0.1:6379> smembers myset # 查询集合中的所有数据
1) "v1"
2) "v3"
3) "v2"
集合类型的经典使⽤场景如下:
-
微博关注我的人和我关注的人都适合用集合存储,可以保证人员不会重复;
-
中奖人信息也适合用集合类型存储,这样可以保证⼀个人不会重复中奖。
集合类型(Set)和列表类型(List)的区别如下:
- 列表可以存储重复元素,集合只能存储非重复元素;
- 列表是按照元素的先后顺序存储元素的,而集合则是无序方式存储元素的。
⑤ 有序集合类型
有序集合类型 (Sorted Set) 相比于集合类型多了⼀个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,⼀个是有序结合的元素值,⼀个是排序值。有序集合的存储元素值也是不能重复的,但分值是可以重复的。
当我们把学生的成绩存储在有序集合中时,它的存储结构如下图所示:
127.0.0.1:6379> zadd zset1 3 golang 4 sql 1 redis # 添加数据
(integer) 3
127.0.0.1:6379> zrange zset 0 -1 # 查询所有数据,从0开始,-1是个特殊的值表示查询所有
1) "redis"
2) "mysql"
3) "java"
有序集合的经典使用场景如下:
- 学生成绩排名;
- 粉丝列表,根据关注的先后时间排序。
3.持久化的三种方式
所谓的持久化就是将数据从内存保存到磁盘的过程,它的目的就是为了防止数据丢失。因为内存中的数据在服务器重启之后就会丢失,而磁盘的数据则不会,因此为了系统的稳定起见,我们需要将数据进行持久化。
同时持久化功能⼜是 Redis 和 Memcached 最主要的区别之⼀,因为 Redis ⽀持持久化⽽ Memcached 不⽀持。
Redis 持久化的方式有以下 3 种:
1️⃣快照方式(RDB, Redis DataBase):将某⼀个时刻的内存数据,以⼆进制的方式写入磁盘;
2️⃣文件追加方式(AOF, Append Only File):记录所有的操作命令,并以文本的形式追加到文件中;
3️⃣混合持久化方式:Redis 4.0 之后新增的方式,混合持久化是结合了 RDB 和 AOF 的优点,在写入的时候,先把当前的数据以 RDB 的形式写入文件的开头,再将后续的操作命令以 AOF 的格式存入文件,这样既能保证 Redis 重启时的速度,⼜能减低数据丢失的风险。
持久化策略设置:
👊混合持久化:可以在 redis-cli
命令行中执行 config set aof-use-rdb-preamble yes
来开启混合持久化,当开启混合持久化时 Redis 就以混合持久化方式来作为持久化策略;
👊AOF:当没有开启混合持久化的情况下,使用 config set appendonly yes
来开启 AOF 持久化的策略;
👊RDB:当 AOF 和混合持久化都没开启的情况下默认会是 RDB 持久化的方式。
①RDB
优点
- RDB 的内容为⼆进制的数据,占⽤内存更小,更紧凑,更适合做为备份⽂件;
- RDB 对灾难恢复非常有用,它是⼀个紧凑的文件,可以更快的传输到远程服务器进行 Redis 服务 恢复;
- RDB 可以更⼤程度的提高 Redis 的运行速度,因为每次持久化时 Redis 主进程都会 fork() ⼀个子进程,进行数据持久化到磁盘,Redis 主进程并不会执行磁盘 I/O 等操作;
- 与 AOF 格式的⽂件相比,RDB ⽂件可以更快的重启。
缺点
- 因为 RDB 只能保存某个时间间隔的数据,如果中途 Redis 服务被意外终止了,则会丢失⼀段时间内的 Redis 数据;
- RDB 需要经常 fork() 才能使用子进程将其持久化在磁盘上。如果数据集很大,fork() 可能很耗时, 并且如果数据集很大且 CPU 性能不佳,则可能导致 Redis 停⽌为客户端服务几毫秒甚至一秒钟。
②AOF
优点
- AOF 持久化保存的数据更加完整,AOF 提供了三种保存策略:每次操作保存、每秒钟保存⼀次、 跟随系统的持久化策略保存。其中每秒保存⼀次,从数据的安全性和性能两方面考虑是⼀个不错的选择,也是 AOF 默认的策略,即使发生了意外情况,最多只会丢失 1s 钟的数据;
- AOF 采用的是命令追加的写入方式,所以不会出现文件损坏的问题,即使由于某些意外原因,导致了最后操作的持久化数据写⼊了⼀半,也可以通过 redis-check-aof ⼯具轻松的修复;
- AOF 持久化文件,⾮常容易理解和解析,它是把所有 Redis 键值操作命令,以文件的方式存入了 磁盘。即使不小心使用 flushall 命令删除了所有键值信息,只要使用 AOF 文件,删除最后的 flushall 命令,重启 Redis 即可恢复之前误删的数据。
缺点
- 对于相同的数据集来说,AOF 文件要大于 RDB 文件;
- 在 Redis 负载比较高的情况下,RDB 比 AOF 性能更好;
- RDB 使用快照的形式来持久化整个 Redis 数据,而 AOF 只是将每次执⾏的命令追加到 AOF ⽂件 中,因此从理论上说,RDB 比 AOF 更健壮。
③删混合持久化
优点
- 混合持久化结合了 RDB 和 AOF 持久化的优点,开头为 RDB 的格式,使得 Redis 可以更快的启动,同时结合 AOF 的优点,有减低了⼤量数据丢失的风险。
缺点
- AOF 文件中添加了 RDB 格式的内容,使得 AOF 文件的可读性变得很差;
- 兼容性差,如果开启混合持久化,那么此混合持久化 AOF 文件,就不能⽤在 Redis 4.0 之前版本 了。
四、Redis的使用
1.添加依赖
Spring Data Redis(Access+Driver)
2.修改配置
spring.redis.database=0
spring.redis.port=6379
spring.redis.host=82.157.146.10
#可省略
spring.redis.lettuce.pool.min-idle=5
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=1ms
spring.redis.lettuce.shutdown-timeout=100ms
3.手动代码方式操作
了解即可
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class RedisController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 在 redis 存储数据
@RequestMapping("/setrs")
public String setRedis(String name, String value) {
stringRedisTemplate.opsForValue().set(name, value,30, TimeUnit.SECONDS);
return "Set redis success.";
}
// 读取 redis 中的数据
@RequestMapping("/getrs")
public String getRedis(String name) {
Object valObj = stringRedisTemplate.opsForValue().get(name);
if (valObj != null) return valObj.toString();
return "Null";
}
}
3.注解方式操作
相关注解
@EnableCaching:开启全局注解缓存。
@Cacheable:查询/添加操作,判断Redis是否有缓存,如果没有那么就把当前放啊返回值存到Redis;如果存在缓存直接返回。
@CachePut:修改操作,将方法返回值更新到Redis缓存中。
@CacheEvict:删除操作,将Redis中的对应缓存删除。
1.开启全局注解缓存
package com.example.redis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@EnableCaching
@SpringBootApplication
public class RedisApplication {
public static void main(String[] args) {
SpringApplication.run(RedisApplication.class, args);
}
}
2.实现Redis的增删查改
package com.example.redis.controller;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RedisController {
/**
* Cacheable 查询后 有结果就返回结果
* 没有结果就 add一个缓存,value为返回值
* @param name 姓名 tel 电话
* @key 使用#传递参数,和下方参数相同,可以为多个,注意分隔方式
*/
@RequestMapping("/get-and-add")
@Cacheable(value = "cache", key = "#name+'-'+#tel")
public String getAndAdd(String name, String tel) {
System.out.println("执行了 getAndAdd");
//非空判断
if (!StringUtils.hasLength(name) && !StringUtils.hasLength(tel)) {
return "请先输入name和age";
}
//返回的内容即为这个key的value值
return "name" + name + " | age" + tel;
}
/**
* CachePut 通过传入的name和value定位需要修改的对象
* 修改后的结果为 返回值
*/
@RequestMapping("/put")
@CachePut(value = "cache", key = "#name+'-'+#tel")
public String put(String name, String tel) {
System.out.println("执行了 put");
return "[name=="+name+"#tel=="+tel+"]";
}
/**
* CacheEvict 删除这个对象
*/
@RequestMapping("/del")
@CacheEvict(value = "cache", key = "#name+'-'+#tel")
public void del(String name, String tel) {
System.out.println("执行了 del");
}
五、Spring Session持久化
1.添加依赖
2.修改配置
spring:
#Redis相关配置
redis:
database: 0
host: 120.53.20.213 #Redis的地址
port: 6379 #端口号,6379为默认的端口号
#Session相关配置
session:
store-type: redis #存放在 Redis 中,这样就不会存储到内存中了
timeout: 1800
redis:
flush-mode: on_save
namespace: spring:session #放置的位置,相当于文件夹
3.操作Session
其实操作和我们之前的操作一模一样。
设置默认保存位置为 Redis 后,框架会自动帮我们存储到 Redis 中,其实也就实现了持久化。
package com.example.redis.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@RestController("redis-session")
public class RedisSessionController {
private final static String SESSION_KEY = "USER_SESSION_KEY";//设置Key值
@RequestMapping("/login")
public String login(HttpSession session) {
//会自动存储到Redis中
session.setAttribute(SESSION_KEY, "liMing");
return "登录成功";
}
@RequestMapping("get")
public String get(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
//从Redis中获取用户
Object user = session.getAttribute(SESSION_KEY);
if (user != null) {
return user.toString();
}
}
return "暂无Session,未登录";
}
}
六、面试题
基础内容
在上文中已经提到
1.什么是缓存?
2.缓存的优点和分类?
3.Redis五大基础数据类型?
4.持久化的三种方式?三种方式的优缺点?
常问的
1.缓存雪崩
缓存雪崩是指在短时间内,有大量缓存同时过期,导致⼤量的请求直接查询数据库,从⽽对数据库造成 了巨大的压力,严重情况下可能会导致数据库宕机的情况叫做缓存雪崩。
正常情况下系统的执行流程如下图所示:
缓存雪崩的执行流程,如下图所示:
以上对比图可以看出缓存雪崩对系统造成的影响,那如何解决缓存雪崩的问题? 主要有 加锁排队、随机化过期时间、设置⼆级缓存。
加锁排队
加锁排队可以起到缓冲的作用,防止大量的请求同时操作数据库,但它的缺点是增加了系统的响应时间,降低了系统的吞吐量,牺牲了⼀部分用户体验。
随机化过期时间
为了避免缓存同时过期,可在设置缓存时添加随机时间,这样就可以极大的避免⼤量的缓存同时失效。 示例代码如下:
// 缓存原本的失效时间
int exTime = 10 * 60;
// 随机数⽣成类
Random random = new Random();
// 缓存设置
jedis.setex(cacheKey, exTime+random.nextInt(1000) , value);
设置二级缓存
⼆级缓存指的是除了 Redis 本身的缓存,再设置⼀层缓存,当 Redis 失效之后,先去查询⼆级缓存。
例如可以设置⼀个本地缓存,在 Redis 缓存失效的时候先去查询本地缓存而非查询数据库。
加⼊⼆级缓存之后程序执⾏流程,如下图所示:
2.缓存穿透
缓存穿透是指查询数据库和缓存都无数据,因为数据库查询无数据,出于容错考虑,不会将结果保存到缓存中,因此每次请求都会去查询数据库,这种情况就叫做缓存穿透。
缓存穿透执行流程如下图所示:(其中红⾊路径表示缓存穿透的执行路径,可以看出缓存穿透会给数据库造成很大的压力)
解决方案:缓存空结果
我们可以把每次从数据库查询的数据都保存到缓存中,为了提⾼前台用户的使用体验 (解决长时间内查询不到任何信息的情况),我们可以将空结果的缓存时间设置的短一些,例如 3-5 分钟。
3.缓存击穿
缓存击穿指的是某个热点缓存,在某⼀时刻恰好失效了,然后此时刚好有⼤量的并发请求,此时这些请求将会给数据库造成巨大的压力,这种情况就叫做缓存击穿。
缓存击穿的执行流程如下图所示:
解决方案有:加锁排队、设置永不过期
加锁排队
此处理⽅式和缓存雪崩加锁排队的⽅法类似,都是在查询数据库时加锁排队,缓冲操作请求以此来减少服务器的运行压力
设置永不过期
对于某些热点缓存,我们可以设置永不过期,这样就能保证缓存的稳定性,但需要注意在数据更改之后,要及时更新此热点缓存,不然就会造成查询结果的误差。
4.缓存预热
首先来说,缓存预热并不是⼀个问题,而是使用缓存时的⼀个优化方案,它可以提高前台用户的使用体验。
缓存预热指的是在系统启动的时候,先把查询结果预存到缓存中,以便用户后面查询时可以直接从缓存中读取,以节约用户的等待时间。
缓存预热的执行流程,如下图所示:
缓存预热的实现思路有以下三种:
- 把需要缓存的方法写在系统初始化的方法中,这样系统在启动的时候就会自动的加载数据并缓存数据;
- 把需要缓存的方法挂载到某个页面或后端接口上,⼿动触发缓存预热;
- 设置定时任务,定时自动进行缓存预热。
Redis集群
随着业务的不断发展,单机 Redis 的性能已经不能满足我们的需求了,此时我们需要将单机 Redis 扩 展为多机服务,Redis 多机服务主要包含以下 3 个内容:
- Redis 主从同步
- Redis 哨兵模式
- Redis 集群服务(Redis 3.0 新增功能)
下⾯我们分别来看这 3 部分的内容。
1.Redis 主从同步
主从同步 (主从复制) 是 Redis 高可用服务的基石,也是多机运行中最基础的⼀个。
我们把主要存储数据的节点叫做主节点 (master),把其他通过复制主节点数据的副本节点叫做从节点 (slave),如下图所示:
在 Redis 中⼀个主节点可以拥有多个从节点,⼀个从节点也可以是其他服务器的主节点,如下图所示:
主从服务器设置
在 Redis 运行过程中,我们可以使⽤ replicaof host port
命令,把自己设置为目标 IP 的从服务器,执行命令如下:
127.0.0.1:6379> replicaof 127.0.0.1 6380
OK
如果主服务设置了密码,需要在从服务器输入主服务器的密码,使⽤ config set masterauth
主服务密码命令的方式,例如:
127.0.0.1:6377> config set masterauth pwd654321
OK
主从同步优、缺点分析
主从同步具有以下 3 个优点:
- 性能方面:有了主从同步之后,可以把查询任务分配给从服务器,用主服务器来执行写操作,这样极大的提高了程序运行的效率,把所有压力分摊到各个服务器了;
- 高可用:当有了主从同步之后,当主服务器节点宕机之后,可以很迅速的把从节点提升为主节点,为 Redis 服务器的宕机恢复节省了宝贵的时间;
- 防止数据丢失:当主服务器磁盘坏掉之后,其他从服务器还保留着相关的数据,不至于数据全部丢失
主从同步的缺点:
-
这种模式本身存在⼀个致命的问题,当主节点奔溃之后,需要人工干预才能恢复 Redis 的正常使用。
解决这一问题我们可以使用哨兵模式
2.哨兵模式
假如晚上发生了主从服务器宕机的情况,尤其是在主从服务器比较多的情况下,如果需要人工恢复,那么需要的时间和难度是很大的,因此我们需要⼀个自动的⼯具——Redis Sentinel (哨兵模式) 来把手动的过程变成自动的,让 Redis 拥有自动容灾恢复 (failover) 的能力。
也就是说:使用哨兵模式可以用来监控主从同步服务器节点,并在主从服务器出现问题的时候实现自动容灾恢复。
哨兵模式如下所示(⼩贴⼠:Redis Sentinel 的最⼩分配单位是⼀主⼀从。):
哨兵工作原理
哨兵的⼯作原理是,首先每个 Sentinel 会以每秒钟 1 次的频率,向已知的主服务器、从服务器和以及其他 Sentinel 实例,发送⼀个 PING 命令。
如果最后⼀次有效回复 PING 命令的时间超过 down-after-milliseconds
所配置的值 (默认 30s),那么这个实例会被 Sentinel 标记为主观下线。
如果⼀个主服务器被标记为主观下线,那么正在监视这个主服务器的所有 Sentinel 节点,要以每秒 1 次的频率确认主服务器的确进入了主观下线状态。
如果有足够数量 (quorum 配置值) 的 Sentinel 在指定的时间范围内同意这⼀判断,那么这个主服务器被标记为客观下线。此时所有的 Sentinel 会按照规则协商自动选出新的主节点。
注意:⼀个有效的 PING 回复可以是:
+PONG
、-LOADING
或者-MASTERDOWN
。如果返回值非以上三种回复,或者在指定时间内没有回复 PING 命令, 那么 Sentinel 认为服务器返回的回复无效 (non-valid )。
Redis主从同步 + 哨兵模式 并不是 Redis 多机运行最完美的解决方案,集群模式才是现在常用的
3.Redis 集群服务
Redis 集群(Redis Cluster)是 Redis 多机运行最完美的终极方案,它是 Redis 3.0 之后推出的服务,它的出现可以让我们完全抛弃主从同步和哨兵模式来实现 Redis 多机运行。
Redis Cluster 是无代理模式去中心化的运行模式,客户端发送的绝大数命令会直接交给相关节点执行,这样大部分情况请求命令无需转发,或仅转发⼀次的情况下就能完成请求与响应,所以集群单个节点的性能与单机 Redis 服务器的性能是非常接近的,因此在理论情况下,当水平扩展⼀倍的主节点就相当于请求处理的性能也提高了⼀倍,所以 Redis Cluster 的性能是非常高的。
Redis Cluster 架构图如下所示:
如果⼀个主服务器被标记为主观下线,那么正在监视这个主服务器的所有 Sentinel 节点,要以每秒 1 次的频率确认主服务器的确进入了主观下线状态。
如果有足够数量 (quorum 配置值) 的 Sentinel 在指定的时间范围内同意这⼀判断,那么这个主服务器被标记为客观下线。此时所有的 Sentinel 会按照规则协商自动选出新的主节点。
注意:⼀个有效的 PING 回复可以是:
+PONG
、-LOADING
或者-MASTERDOWN
。如果返回值非以上三种回复,或者在指定时间内没有回复 PING 命令, 那么 Sentinel 认为服务器返回的回复无效 (non-valid )。
Redis主从同步 + 哨兵模式 并不是 Redis 多机运行最完美的解决方案,集群模式才是现在常用的
3.Redis 集群服务
Redis 集群(Redis Cluster)是 Redis 多机运行最完美的终极方案,它是 Redis 3.0 之后推出的服务,它的出现可以让我们完全抛弃主从同步和哨兵模式来实现 Redis 多机运行。
Redis Cluster 是无代理模式去中心化的运行模式,客户端发送的绝大数命令会直接交给相关节点执行,这样大部分情况请求命令无需转发,或仅转发⼀次的情况下就能完成请求与响应,所以集群单个节点的性能与单机 Redis 服务器的性能是非常接近的,因此在理论情况下,当水平扩展⼀倍的主节点就相当于请求处理的性能也提高了⼀倍,所以 Redis Cluster 的性能是非常高的。
Redis Cluster 架构图如下所示:
加粗样式
从上图可以看出 Redis 的主从同步只能有⼀个主节点,而 Redis Cluster 可以拥有无数个主从节点,因此 Redis Cluster 拥有更强大的平行扩展能力,也就是说当 Redis Cluster 拥有两个主从节点时,从理论上来讲 Redis 的性能相比于单机服务来说性能提升了 2 倍。