2种机制,4种选项
redis的高性能是由于其将所有数据都存储在了内存中,为了使redis在重启之后仍能保证数据不丢失,需要将数据从内存中同步到硬盘(文件)中,这一过程就是持久化。redis 提供了一系列不同的持久化选项,包含: RDB、AOF、不持久以及RDB+AOF这四种选项
配置文件中设置redis内存的参数不设置或者设置为0,则redis默认的内存大小为:
一般推荐Redis设置内存为最大物理内存的四分之三,也就是0.75
RDB持久化即通过创建快照(压缩的二进制文件)的方式进行持久化,保存某个时间点的全量数据。RDB持久化是Redis默认的持久化方式,每次都是从 Redis 中生成一个快照进行数据的全量备份。
我们要研究的问题:什么时候拍摄快照
1.基于默认的时间间隔配置
save
redis.conf 配置文件中的配置,默认会进行3种定时转储:
配置 | 含义 |
---|---|
save 900 1 | 900秒(15分钟)内至少有一次修改则触发保存操作 |
save 300 10 | 300秒(5分钟)内至少有10次修改则触发保存操作 |
save 60 10000 | 60秒(1分钟)内至少有1万次修改则触发保存操作 |
2.手动调用命令进行快照 save(前台执行) 或者bgsave(后台执行)
save: 将redis内存中的数据采用快照的方式保存到磁盘文件中,它是一个同步操作,在它执行持久化的时候,所有redis客户端都不能连接redis服务器执行读写操作
bgsave:将redis内存中的数据采用快照的方式保存到磁盘文件中,它是一个异步操作,在它执行持久化的时候是采用子线程执行的,不会影响主线程处理客户端读写请求
3.调用flushall命令,清空redis16个数据库,并且对已清空的状态做一次快照(斩草除根)
产生的dump.rdb文件,但里面是空的,没有意义
4.正常关闭redis服务器,在redis客户端执行shutdown命令,退出之前redis就会执行一次持久化保存
配置项 | 取值 | 作用 |
---|---|---|
save | 900 1 | 设置RDB的快照时间点,如果为空则禁用RDB |
dbfilename | 文件名,例如:dump.rdb | 设置RDB机制下,指定快照文件的名字 |
dir | Redis工作目录路径 | 指定存放持久化文件的目录的路径(drb和aof都在这个目录)。注意:这里指定的必须是目录不能是文件名 |
# 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
# 满足以下条件将会同步数据:
# 900秒(15分钟)内有1个更改
# 300秒(5分钟)内有10个更改
# 60秒内有10000个更改
# Note: 可以把所有“save”行注释掉,这样就取消同步操作了
save 900 1
save 300 10
save 60 10000
# 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
# 指定本地数据库存放目录,文件名由上一个dbfilename配置项指定
# 注意,这里只能指定一个目录,不能指定文件名
dir ./
优势:
1.rdb文件的体积较小:相对aof的持久化文件
2.rdb文件数据恢复速度快:相对aof持久化文件
想听骚话吗?满足你
RDB是一个简洁的按时间点生成的文件,RDB 文件非常适合备份。例如,你可能希望在最近 24 小时内每小时存档一次 RDB 文件,并在 30 天内每天保存一个 RDB 快照。这使您可以在发生灾难时轻松恢复不同版本的数据集。
RDB 非常适合灾难恢复,它是一个可以传输到远程数据中心或者云服务器的简洁文件(文件体积不大),并且进行数据恢复时速度很快(便于数据恢复)
RDB 最大限度地不影响 Redis 的性能,因为 Redis 父进程为了持久化需要做的唯一工作是创建一个能完成所有持久化工作子进程。父进程永远不会执行磁盘 I/O 或类似操作。
在副本上,RDB 支持重启和故障转移后的部分重新同步。
redis要做几件事情:1.处理客户端的请求2.执行持久化
父进程:处理客户端请求
子进程:执行持久化
rdb缺点:
1.容易造成数据丢失
2.经常fork子进程 耗时
想听骚话吗?满足你
容易造成数据丢失,因为RDB是在一定的时间点才会去进行持久化,例如( 300秒内至少有10次修改则触发保存操作),那么此时如果发生不可预期的关机或断电,则会导致这几分钟之内的数据丢失
RDB 经常需要 fork() 以便使用子进程在磁盘上持久化。如果数据集很大,Fork() 可能会很耗时,如果数据集很大且 CPU 性能不是很好,则可能导致 Redis 停止为客户端服务几毫秒甚至一秒钟。 AOF 也需要 fork() 但你可以调整你想要重写日志的频率,而不会对持久性进行任何权衡。
它是在持久化文件中记录每一次写操作命令
AOF 持久化记录服务器收到的每个写操作,这些操作将在服务器启动时再次播放,重建原始数据集。注意只许追加文件,但不可以改写文件
命令使用与 Redis 协议本身相同的格式以仅附加的方式记录。当日志变得太大时,Redis 能够在后台重写日志。
redis.conf中AOF的基本配置:
配置项 | 取值 | 作用 |
---|---|---|
appendonly | yes | 启用AOF持久化机制 |
no | 禁用AOF持久化机制[默认值no] | |
appendfilename | "文件名" | AOF持久化文件名 |
dir | Redis工作目录路径 | 指定存放持久化文件的目录的路径。注意:这里指定的必须是目录不能是文件名 |
appendfsync | always | 每一次数据修改后都将执行文件写入操作,是最安全的方式但是速度缓慢。 |
everysec | 每秒执行一次写入操作。默认。 | |
no | 由操作系统在适当的时候执行写入操作,Redis性能最好,数据保存次数最少。 |
注意: 当 AOF 和 RDB 机制并存时,Redis 会优先采纳 AOF 机制
数据更难丢失,最多丢失1秒之内的数据
使用AOF可以让Redis更持久:你可以有不同的 fsync 策略:不进行fsync,每秒进行fsync,每次执行Redis命令都进行fsync。使用 fsync每秒写入性能的默认策略仍然很棒(fsync是使用后台线程执行的,当没有 fsync 正在进行时,主线程将努力执行写入。)但您只能丢失一秒钟的写入。
AOF 日志是仅附加日志,因此在断电时不会出现寻道或损坏问题。即使日志由于某种原因(磁盘已满或其他原因)以半写命令结束,redis-check-aof 工具也能够轻松修复它。
当 AOF文件变大时,Redis 能够在后台自动重写 AOF。重写是完全安全的,因为当 Redis 继续追加到旧文件时,会使用创建当前数据集所需的最少操作集生成一个全新的文件,一旦第二个文件准备就绪,Redis 就会切换这两个文件并开始追加到新的。
AOF以易于理解和解析的格式包含所有操作的日志。您甚至可以轻松导出 AOF 文件。例如,即使您不小心使用 FLUSHALL删除了所有内容,只要在此期间没有重写日志,您仍然可以通过停止服务器、删除最新命令并重新启动Redis 来保存您的数据集再次。
AOF的缺点
AOF文件通常比相同数据集的等效 RDB 文件大很多。
AOF持久化数据的恢复速度比RDB要慢很多
AOF可能在特定命令中遇到罕见的错误(例如,有一个涉及像 RPOPLPUSH 这样的阻塞命令)导致生成的 AOF在重新加载时无法重现完全相同的数据集。但这种错误是极其罕见的
原因:随着写入操作的执行 ,aof文件体积会越来越大
重写:经过计算以最少的命令表示当前redis内存中的字符集,从而达到减小AOF持久化文件体积的目的
文件重写:得到的是硬盘空间,消耗的是cpu和内存
什么时候会进行文件重写?
Redis会根据AOF文件的体积来决定是否进行AOF重写。参考的配置项如下:
Redis 会记录上次重写时的 AOF 大小,默认配置是当 AOF 文件大小是上次 rewrite 后大小的 1 倍且文件大于 64M 时触发。
做如下假设:
若第一次重写发生在64M,若重写后的文件大小为20M
那么第二次重写发生在64M(同时满足>40M && >60M),若重写后的文件大小为40M
那么第三次重写发生在80M(同时满足>80M && > 60M)
但是呢,老师的配置可不是60MB,老师配置了5G
Redis服务器启动时如果读取了损坏的AOF持久化文件会导致启动失败,此时为了让Redis服务器能够正常启动,需要对损坏的持久化文件进行修复
redis-check-aof --fix appendonly.aof
第一步:备份要修复的appendonly.aof文件
第二步:执行修复程序
/usr/local/redis/bin/redis-check-aof --fix /usr/local/redis/appendonly.aof
第三步:重启Redis
注意:所谓修复持久化文件仅仅是把损坏的部分去掉,而没法把受损的数据找回。
如果Redis仅仅作为缓存可以不使用任何持久化方式。
RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
如果开启AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
只要硬盘允许,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。
如果不开启AOF,仅靠Master-Slave Replication 实现高可用性能也不错。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时挂掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。
Spring-data-redis 是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装。
RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现
引入依赖
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
--------------
application.yml配置文件
SpringBoot2.0默认采用Lettuce客户端来连接Redis服务端的
默认是不使用连接池的,只有配置 redis.lettuce.pool下的属性的时候才可以使用到redis连接池
spring:
redis:
# Redis数据库索引(Redis默认情况下有16个分片,默认为0)
database: 0
# Redis服务器地址
host: localhost
# 连接超时时间(记得添加单位,Duration)
timeout: 10000ms
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制) 默认 8
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
max-wait: -1ms
# 连接池中的最大空闲连接 默认 8
max-idle: 8
# 连接池中的最小空闲连接 默认 0
min-idle: 0
StringRedisTemplate 只能对 key=String,value=String 的键值对进行操作,RedisTemplate 可以对任何类型的 key-value 键值对操作
StringRedisTemplate 继承了 RedisTemplate
public class StringRedisTemplate extends RedisTemplate
{ }
在测试类中 使用StringRedisTemplate
看的出来StringRedisTemplate可以操作对象,但是会涉及到json字符串的转换
@Autowired StringRedisTemplate stringRedisTemplate; @Test void contextLoads() throws JsonProcessingException { //使用jackson处理json格式的数据 ObjectMapper mapper = new ObjectMapper(); ValueOperations
opsForValue = stringRedisTemplate.opsForValue(); //向redis存入俩个键值数据 value分别是String和实体类 opsForValue.set("k1","xiaoyumao"); ResponseVo responseVo = new ResponseVo (); opsForValue.set("k2",mapper.writeValueAsString(responseVo)); //从redis中获取键值数据 System.out.println(opsForValue.get("k1")); System.out.println(mapper.readValue(opsForValue.get("k2"),ResponseVo.class)); } 控制台打印:
redis数据库中
以下保持怀疑的态度??
两者的数据是不共通的,StringRedisTemplate 只能管理 StringRedisTemplate 里面的数据,RedisTemplate 只能管理 RedisTemplate中 的数据
RedisTemplate 对五种数据结构分别定义了操作
redisTemplate.opsForValue(); 操作字符串
redisTemplate.opsForHash(); 操作hash
redisTemplate.opsForList(); 操作list
redisTemplate.opsForSet(); 操作set
redisTemplate.opsForZSet(); 操作有序set
测试
RedisTemplate 是将对象序列化,所以要求对象的类型必须实现序列化接口。Employee并没有实现序列化接口,在测试类中测试
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() throws JsonProcessingException {
ValueOperations opsForValue = redisTemplate.opsForValue();
//向redis存入实体类对象
Employee employee = new Employee("xiaoyumao",18, 18000.0);
opsForValue.set("employee:xiaoyumao",employee);
//从redis中获取键值数据
System.out.println(opsForValue.get("employee:xiaoyumao"));
}
不出意外的,控制台有异常,因为目前我们的Employee并没有实现序列化接口。。
给RedisTemplate配置键和值的序列化器(就用不在Employee类里实现序列化接口了)
@Configuration
public class RedisConfig {
@Autowired
RedisTemplate redisTemplate;
//RedisTemplate配置键和值的序列化器
@PostConstruct
public void init(){
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
}
}
原理: 就是将对象转为json字符串并在该json字符串中保存对象的全类名, 读取redis中的json数据时再还原为该类型对象
重新测试这个方法
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() throws JsonProcessingException {
ValueOperations opsForValue = redisTemplate.opsForValue();
//向redis存入实体类对象
Employee employee = new Employee("xiaoyumao",18, 18000.0);
opsForValue.set("employee:xiaoyumao",employee);
//从redis中获取键值数据
System.out.println(opsForValue.get("employee:xiaoyumao"));
}
控制台打印:
redis数据库中:
操作hash类型的数据: redis的hash一般存对象类型的数据
@Autowired
RedisTemplate redisTemplate;
@Test
public void testFastJson() {
//根据redis中的key获取 hash结构的操作对象 序列 sequence
BoundHashOperations ops = redisTemplate.boundHashOps("seq");
System.out.println("seq中的kv键值对数量:"+ ops.size());
//以下是《斩神》中的部分禁墟序列
ops.put("003" , "凡尘神域.....");
ops.put("015" , "不朽.....");
ops.put("018" , "梦岐.....");
ops.put("068" , "气闽.....");
ops.put("076" , "万象频动.....");
System.out.println("seq中的kv键值对数量:"+ ops.size());
//查看数据,可以获取所有的hashkey
for (Object key : ops.keys()) {
System.out.println(key);
}
//删除hash结构中某一个hashkey
ops.delete("076");
//查看数据,可以获取所有的entries
System.out.println("----删除hk=076后----");
ops.entries().forEach((k,v)->{
System.out.println(k+" , "+ v);
});
}
控制台打印:
redis数据库中
对于Redis而言,命令的原子性指的是:一个操作的不可以再分,操作要么执行,要么不执行。
Redis的操作之所以是原子性的,是因为Redis是单线程的。Redis本身提供的所有API都是原子操作,Redis中的事务其实是要保证批量操作的原子性。
redis事务控制目的:不是为了回滚,而是为了防止别的队列命令插队执行
Redis 的事务除了保证所有命令要不全部执行,要不全部不执行外,还能保证一个事务中的命令依次执行而不被其他命令插入
redis事务控制相关的命令
命令名 | 作用 |
---|---|
MULTI | 表示开始收集命令,后面所有命令都不是马上执行,而是加入到一个队列中。 |
EXEC | 执行MULTI后面命令队列中的所有命令。 |
DISCARD | 放弃执行队列中的命令。 |
WATCH | “观察“、”监控“一个KEY,在当前队列外的其他命令操作这个KEY时,放弃执行自己队列的命令 |
UNWATCH | 放弃监控一个KEY |
命令队列执行失败的俩种情况
1.入队过程中某个命令出现了报告错误,执行时整个队列的所有命令都会被取消。
当输入 MULTI 命令后,服务器返回 OK 表示事务开始成功,然后依次输入需要在本次事务中执行的所有命令,每次输入一个命令服务器并不会马上执行,而是返回”QUEUED”,这表示命令已经被服务器接受并且暂时保存起来。
2.错误在入队时检测不出来,整个队列执行时有错的命令执行失败,但是其他命令并没有回滚。
最后输入 EXEC 命令后,本次事务中的所有命令才会被依次执行,这里返回的结果与发送的命令是按顺序一一对应的。
官方解释如下:
如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。以下是这种做法的优点:
1.Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
2.因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。 有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。
举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。
悲观锁:当锁的持有者操作数据的时候,其它人都无法操作数据。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁:当我操作数据的时候,一点都不担心别人也会操作这个数据(不上锁)。有可能发生多个用户同时操作某一个数据的情况。
乐观锁版本号机制:
当最终要执行的时候,会检查数据的版本号,如果数据的版本号和之前加入队列时候的版本号一致(说明在这个过程中该数据没有被别人操作过),那么就真正执行队列。
如果数据的版本号和之前加入队列时候的版本号不一致(说明在这个过程中有别人已经抢先执行操作这个数据了),那么就将整个MULTI队列中的操作都被丢弃
乐观锁:抢占机制,效率高。 抢占机制:大家可以一起操作,谁先抢到就是谁的
在执行MULTI之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。这就是乐观锁的具体体现
主从复制,是指将一台 Redis 服务器(master)的数据,复制到其他的 Redis 服务器(slave)
一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
默认情况下,每台 Redis 服务器都是主节点。
主从复制属于redis集群策略的一种
redis集群策略:
- 主从模式
- 哨兵模式
- Cluster模式
在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载。尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
主从复制是哨兵和集群能够实施的基础,因此说主从复制是 Redis 高可用的基础。
只使用一台 Redis 是万万不能的,原因如下:
结构上:单个 Redis 服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大。
容量上:单个 Redis 服务器内存容量有限,一般来说,单台 Redis 最大使用内存不应该超过 20G。
Redis集群在运行时使用的是同一个可执行文件(redis-server),只是对应的配置文件(redis.conf)不同
每个配置文件相同的参数是:
#表示以后台进程的方式启动redis
daemonize yes // daemonize 英文直译:守护进程
# 设置redis的工作目录
dir /usr/local/cluster-redis
每个配置文件不同的参数是:
配置项名称 | 作用 | 取值 |
---|---|---|
port | Redis服务器启动后监听的端口号 | 6000 7000 8000 |
dbfilename | RDB文件存储文件名 | dump6000.rdb dump7000.rdb dump8000.rdb |
logfile | 日志文件位置 | /var/logs/redis6000.log /var/logs/redis7000.log /var/logs/redis8000.log |
pidfile | pid文件位置 | /var/run/redis6000.pid /var/run/redis7000.pid /var/run/redis8000.pid |
第一步:创建/usr/local/cluster-redis目录
mkdir /usr/local/cluster-redis
第二步:把原始未经修改的redis.conf复制到/usr/local/cluster-redis目录,并且改名为redis6000.conf
cp /usr/local/redis/redis.conf /usr/local/cluster-redis/redis6000.conf
第三步:按照既定计划修改redis6000.conf中的相关配置项
daemonize yes
dir
port
dbfilename
logfile
pidfile
第五步:复制redis6000.conf为redis7000.conf
cp redis6000.conf redis7000.conf
第六步:修改redis7000.conf中的相关配置项
把6000全部替换为7000
%s/6000/7000/g
第七步:复制redis6000.conf为redis8000.conf
第八步:修改redis8000.conf中的相关配置项
Redis集群在运行时使用的是同一个可执行文件(redis-server),只是对应的配置文(redis.conf)不同
/usr/local/redis/bin/redis-server /usr/local/cluster-redis/redis7000.conf
使用redis-cli连接指定服务器的命令格式如下:
/usr/local/redis/bin/redis-cli -h IP地址 -p 端口号
/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 7000
这样就创建好了3个redis客户端
使用redis-cli停止指定服务器的命令格式如下:
/usr/local/redis/bin/redis-cli -h IP地址 -p 端口号 shutdown
/usr/local/redis/bin/redis-cli -h 127.0.0.1 -p 7000 shutdown
刚刚启动的集群服务器中每一个节点服务器都认为自己是master。需要建立主从关系。
真实主从配置不是使用命令配置,而是在配置文件配置,才是永久的
查看主从关系 info replication
配置主从关系
在从机上指定主机的地址和端口号即可
slaveof 127.0.0.1 6000 #此时表示本机的
6000
端口是主机
取消主从关系
只需要在从机上执行命令
SLAVEOF NO ONE
即可,那么此时从机就自己变成了主机slaveof no one
我们可以在主机里写入数据,在从机里查看,当然主机也是可以查看的
但是在从机里是不能写入数据的,会报如下错误:
主机挂掉
主机挂掉以后,查看从机的状态
机的主机依然是之前的主机,只是此时主机的状态为down,只是没有写操作(从机只能读不能写)
主机挂掉以后再重启从机的状态
主从关系又恢复。从机依旧可以获取到主机写的信息
如果主机断开了连接,我们手动可以使用SLAVEOF no one让它变成主机!其他的节点就可以手动连接到最新的这个主节点
===========
从机挂掉
从机SHUTDOWN,此时主机写入数据,从机恢复启动查看状态。
如果是使用命令行配置主从,从机断了再次重启 会认为自己是主机。
此时从机里的数据
重新设定主从关系后,主机数据是否同步
我们把它变为6000的从机,看看数据是否同步
只要变为从机,立马从主机中获取值(全量复制)
主从复制原理
那么中间节点的既是从节点又是主节点,状态是什么呢
答:它依然是从节点,是从节点它就无法进行数据的写入
Q1. 当从机挂掉以后,重启从机,从机无法找到它原本的主机
Q2. 当主机挂掉之后,从机中无法自动重新选举出一台新的主机,从而导致整个集群都无法为客户端提供服务
试想有这样一个监控者监控整个集群
- 它监控到从机挂掉之后重启,就自动给从机找到主机
- 监控到主机挂掉,就会组织整个集群中的所有节点重新选举出一台新的主机
这个监控者就叫哨兵
哨兵是一个独立的进程,会独立运行。其原理是哨兵通过发送心跳数据包,服务器节点接收到心跳数据包之后,向哨兵响应心跳数据
========
通过哨兵(sentinel)服务器监控master/slave可以实现主从复制集群的自动管理
哨兵监控主机一定要用客观下线、监控从机只需要用主观下线
下线:当哨兵监控到服务器节点挂掉之后,将该服务器节点的状态置为down(下线状态)
主观下线:有一台哨兵监控到某个服务器节点挂掉了,就将这个服务器节点的状态置为down
客观下线:当监控到服务器节点挂掉的哨兵数量达到了阈值,就将该服务器节点的状态置为down,这个阈值后续会在哨兵的启动配置文件中指定
然而一个哨兵进程对Rdis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会互相进行监控,这样就形成了多哨兵模式。
创建一个哨兵服务器运行所需要的配置文件。
touch /usr/local/cluster-redis/sentinel.conf
编辑sentinel.conf文件:
格式 例子 sentinel monitor 为主机命名 主机IP 主机端口号 将主机判定为下线时需要Sentinel同意的数量 sentinel monitor mymaster 127.0.0.1 6000 1 要注意的是,如果master主服务器设置了密码,记得在哨兵的配置文件(sentinel.conf)里面配置访问密码
启动哨兵
1.进入/usr/local/redis/bin
目录中:
cd /usr/local/redis/bin
2.然后执行
./redis-server /usr/local/cluster-redis/sentinel.conf --sentinel
剩下的这几个就不是我测试的了,我没测试出来。。各种恶心我呀
6000是主机
7000/8000是从机
从机7000主观下线,则哨兵中打印的是
+sdown主观下线
再把从机7000启动,哨兵的监测结果
日志:-sdown 取消下线状态,并自动把7000转为6000的从机
主机6000客观下线,则哨兵中监控的是
+odown 客观下线
数据库出现故障时,可以自动将从数据库转化为主数据库,实现自动切换
自动切换主机为8000以后,并把6000和7000变为8000的从机 ,最让6000主观下线
======
6000再上线以后就变成8000的从机了
Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
redis集群通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。
容量不够,redis如何进行扩容?通过集群解决容量不够的问题
并发写操作,redis如何分摊一台服务器压力?用集群分担写的压力
任何一台服务器都可以作为集群的入口,这些服务之间可以互相连通。省掉了nginx反向代理
一个redis集群包含16384个插槽(hashslot),数据库中的每个键都属于这16384个插槽的其中一个。
集群使用公式CRC16(key)%16384来计算键key属于哪个槽,其中CRC16(key)语句用于计算键key的CRC16校验和, 集群中的每个节点负责处理一部分插槽。
具体范围:0-16383,
如果是一个redis集群中有3个主机,插槽分配如下
set k1 value1
会计算k1所在插槽,然后向插槽位置加入这个数据
插槽目的:把值平均分担到redis集群中不同的主机当中,平均分担压力。
获取数据的请求会先从redis缓存中获取数据,如果获取到直接返回,如果获取不到,则回源并更新缓存,最后返回页面当下次再次获取数据,直接缓存返回即可
把根据一级分类id查询到的2/3级分类存储到缓存中,再次请求查询相同的pid就查缓存,不再走数据库。
/** * 手动缓存管理 * @param pid * @return */ @Override public List
queryLvl2CategoriesWithSub(Long pid) { //1.先查询缓存 String key = "idx:cache:cates:"+pid; Object obj = redisTemplate.opsForValue().get(key); //缓存有的话直接返回 if(obj!=null){ return (List ) obj; } //2.缓存没有再查询数据库 ResponseVo > listResponseVo = pmsFeign.queryCategoriesWithSub(pid); //3.将查询到的值设置到redis缓存中 long ttl=1800+new Random().nextInt(200); //缓存空值-解决缓存穿透问题 if(CollectionUtils.isEmpty(listResponseVo.getData())){ //如果是空值,ttl有效期就设置的短一些 ttl=new Random().nextInt(500); } redisTemplate.opsForValue() .set(key,listResponseVo.getData(),ttl, TimeUnit.SECONDS); return listResponseVo.getData(); }
缓存穿透的问题:我们使用缓存空值来解决,空值缓存的时间短一些
缓存雪崩的问题:在要缓存的key的有效期加入随机数来解决
什么是redis的缓存穿透?如何防止穿透?
缓存穿透是指查询一个不存在的数据,由于缓存无法命中,将去查询数据库,但是数据库也无此记录,并且出于容错考虑,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
缓存穿透解决方案:
- 对不存在的数据进行数据空值缓存,弊端:会造成缓存中有大量的冗余数据
- 使用布隆过滤器,存在误判率且删除困难
- bitmaps,redis提供的一种数据类型
什么是redis的缓存雪崩?如何防止?
大量key集中过期,数据库短时访问量激增
缓存雪崩解决方案:
- 数据的过期时间使用随机值,分散过期时间
什么是redis的缓存击穿?如何防止?
突发热点访问时,热点数据在Redis缓存中不存在或已过期。大量的对热点数据的访问,都将直接访问数据库,造成数据库访问压力短时激,从而增造成故障。
缓存击穿解决方案:
- 使用锁
过期兜底:在任何情况下使用缓存,都需要给缓存添加过期时间,过期时间后做回源操作
双写模式: 先修改数据库,再修改缓存数据
弊端如下,由于卡顿原因,导致写缓存2在写缓存1前面,最终数据库数据是2,缓存数据是1,两者数据不一致,产生脏数据
但是这只是暂时性的脏数据问题,在数据稳定,缓存过期以后,又能得到最新的正确数据
失效模式: 先修改数据库再删除缓存 并且等待响应时间后,再次执行删除缓存操作(双删机制)
延迟的10s内,可能读的时候数据库中的数据(777)和线程2读的数据666不一样,但是最后10s到了后,删除了666缓存,再来请求读取的时候,就是读的数据库777了,并把777放入缓存,达到数据库和缓存的数据一致性
允许线程在延迟时间内数据不一致,但是过了延迟时间,数据是一致的
有一种极端情况(0.0001%),如果其他线程卡顿时间太长,超过了延迟双删时长,此时就需要使用过期兜底来返回真正的数据
=======
雷神版
这块很有争议。帖子,还有不同的老师版本都不一样,我真是服了,一下是雷神的版本。
雷神说的时效模式就只有:写数据,删缓存。等待下次主动查询更新,并不涉及到双删
弊端:最终数据的数据是2,缓存数据是1,数据不一致。。。。
不管哪一种,由于这些线程乱了,数据库最后一次更新没有映射到redis上。只要我们给缓存设置了过期时间,就会在数据稳定后,达到最终一致性
缓存不一致解决方案
采用延迟双删+过期兜底 策略 保证数据一致性 所谓过期兜底指的是缓存中数据超过过期时间之后做回源操作
使用canal中间件
该中间件相当于数据库的从库,会将数据库变化信息同步到缓存,不建议使用,该组件常用于大数据量场景,另外需要额外维护中间件,增加系统不稳定风险
Redis和Memcached都是内存数据存储系统,都用作内存中的键值数据存储。
Redis 相比 Memcached 来说,拥有更多的数据结构和并支持更丰富的数据操作
通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。 这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。
memecache 把数据所有存在内存之中,断电后会挂掉
redis有部份存在硬盘上,这样能保证数据的持久性
比较项
|
redis
|
memcache
|
存储方式
|
redis可以持久化其数据
|
Memecache把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小
|
数据支持
|
支持丰富的数据类型,提供list,set,zset,hash等数据结构的存储
|
memcached所有的值均是简单的字符串
|
底层模型
|
Redis直接自己构建了VM 机制
|
无
|
value值大小
|
Redis 最大可以达到 512M
|
value 不能超过 1M 字节
|
速度
|
Redis 采用单线程模式处理请求。这样做的原因有 2 个:一个是因为采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作 IO 时间不会太长,单线程可以避免线程上下文切换产生的代价
|
MC 处理请求时使用多线程异步 IO 的方式,可以合理利用 CPU 多核的优势,性能非常优秀
|
数据备份
|
Redis支持数据的备份,即master-slave模式的数据备份, 能够提供高可用服务
|
当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期 key 进行清理,还会按 LRU 策略对数据进行剔除。
|
应用场景
|
适用于对读写效率要求高、数据处理业务复杂、安全性要求较高的系统
|
适合多读少写,大数据量的情况(一些官网的文章信息等)
|