一、redis的事务
Redis的单条命令是保证原子性的,但是redis事务不能保证原子性。并且redis的事务没有隔离级别的概念
- 开启事务后,每一条命令都放在事务中,只有当事务运行的时候命令才会起作用 (命令一个个入队,先放在队列中的先执行)
- 如果中途退出事务,则之前的命令也不会有作用
相关的命令
multi //开启事务
dsicard //中断事务 (丢弃)
exec //提交事务
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做
监控事务(watch) 悲观锁:每一个操作都会进行上锁,效率贼低
乐观锁:获取version,只有在最后提交的时候才会进行更新比较version 典型的是CAS (面试常问) 使用watch来取实现
127.0.0.1:6379> watch money # 监视 money 对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> exec # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
1) (integer) 80
2) (integer) 20
//此时:重新开启一个连接,修改money的值,之前的事务没有提交,
现在的事务执行,则会导致错误
1. 如果发现事务执行失败,就先解锁 即unwatch
2. 获取最新的值,再次监视。watch
3. 再对比是否相同
二、jedis 使用java来操作Redis
- 创建一个空project
- 注意:配置的空的project需要配置JDK和版本
- setting位置设置Java的版本,搜索javac,设置为8
2.1 修改redis的密码
- firewall-cmd --zone=public --add-port=6379/tcp --permanent (--permanent让防火墙永久生效
- 重启防火墙 systemctl restart firewalld.service
- firewall-cmd --list-ports 查看是否端口生效 (linux指令)
- 找到redis的配置文件
- vim redis.conf,使用命令行模式搜索requirepass,回车,将注释进行改掉,改为 requirepass 123456 为对应的密码
- 重启redis的服务 redis-server kconfig/redis.conf
- 登录客户端 redis-cli -p 6379
- 让密码生效 config set requirepass 123456
- 密码生效,使用auth 123456验证
命令形式的修改密码:
config get requirepass 获取密码
set requirepass "123456"
登录密码 auth 123456
2.2 整合SpringBoot
使用SpringData,类似于之前的JDBC,SPring-redis来连接\
- jedis:采用的直连,多个线程操作的话是不安全的,如果想要避免不安全的,使用jedis pool连接池 更像BIO模式 阻塞的
- lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据,不需要开连接池了,更像NIO模式 异步请求
复习:查找源码
- SpringBoot的所有的配置类比如redis,都有一个自动配置类RedisAutoConfiguration,
- 自动配置类都会绑定一个properties配置文件@EnableConfigurationProperties(RedisProperties.class),点开这里检查我们可以在配置文件中配置哪些东西
- 点开properties可以看到在配置文件中需要添加的前缀
- 在redis的自动配置类中,有两个配置类RedisTemplate和StringRedisTemplate
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") //当不存在redisTemplate的时候,才生效
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的RedisTemplate没有过多的设置,redis的对象都是需要序列化的 底层是netty
//两个泛型都是Object,需要强转,则自定义RedisTemplate
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
//大部分的时候使用StringRedistemplate
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
2.3 具体的操作
- 在配置文件中配置相关的参数了,主机地址和port,前缀为Spring-redis
- 自动注入相关的类,private RedisTemplate redisTemplate;
- 具体的操作(主要是使用RedisTemplate来进行操作)
redisTemplate.opsForList().后面对应的是有关list的相关命令
redisTemplate.opsForValue. 后面设置对应的方法
除了基本操作,我们从常用方法可以直接通过redisTemplate直接操作
操作数据库,比如刷新数据库,获取连接的对象
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushAll();
2.4 自定义RedisTemplate类,代替自动配置的
//将之前的object转为string类型的
//其中的RedisConnectionFactory为默认的luttence
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
//序列化配置,进行传递java对象的,默认使用的是JDK的序列化
}
固定的模板,拿来直接用就可以,上班拿来用就可以
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer)
; // hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet(); return template;
} }
三、Redis的配置文件及日志文件
3.1 Redis的持久化
- 在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;
- 默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义
RDB (Redis DataBase)工作原理:
在进行 RDB 的时候,redis 的主线程是不会做 io 操作的,主线程会 fork 一个子线程来完成该操作;
Redis 调用forks。同时拥有父进程和子进程。 子进程将数据集写入到一个临时 RDB 文件中。 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件
这种工作方式使得 Redis 可以从写时复制(copy-on-write) 机制中获益 (因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
- fork进程的时候,会占用一定的内存空间。
3.2 AOF(Append Only File)
将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍
- 默认是不开启的,需要手动的进行开启,将append only改为yes
appendonly no yes则表示启用AOF
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof --fix
优点
- 每一次修改都会同步,文件的完整性会更加好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
- Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
注意:如果只做缓存的时候,就不需要进行持久化,数据只在服务器进行运行的时候存在
性能的建议:
- 因为RDB文件只用作后背用途,建议只在slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1 900秒内至少一个key进行了修改,这条规则
四、redis的发布订阅
类似于公众号的发布消息和读消息
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
需要的:
- 消息的发送者
- 频道
- 消息订阅者
------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "sakura"
3) (integer) 1
1) "message" # 接收到来自sakura频道的消息 "hello world"
2) "sakura"
3) "hello world"
1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"
--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1
-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"
原理:
比如微信的公众号,在redis-server里维护了一个字典,字典的key就是一个个平道(好多的公众号)!而字典的值就是一个个链表,链表保存了所有订阅这个channel的客户端(即每一个用户,保存了谁订阅的公众号)。subscribe命令的关键,就是将客户端(用户)添加到给定的channel的订阅链表中
五、redis的主从复制
主从复制,数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。 作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
- 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
- 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
- 高可用(集群):主从复制还是哨兵和集群能够实施的基础。
为什么使用集群 (公司中是必须要使用的)
- 单台服务器难以负载大量的请求
- 单台服务器故障率高,系统崩坏概率大
- 单台服务器内存容量有限。
5.1 环境的配置
只配置从库,不用配置主库
info replication 打印出redis节点的配置信息,查看库的信息
role:master # 角色
connected_slaves:0 # 从机数量
master_replid:3b54deef5b7b7b7f7dd8acefa23be48879b4fcff
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
搭建集群的方式及步骤: 既然需要启动多个服务,就需要多个配置文件。每个配置文件对应修改以下信息:
- 端口号 6379
- pid文件名 pid 6379
- 日志文件名 logfile
- rdb文件名 dumb6379.rdb
5.2 复制原理
Slave 启动成功连接到 master 后会发送一个 sync 同步命令,Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,master 将传送整个数据文件到 slave,并完成一次完全同步。
全量复制:slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master 继续将新的所有收集到的修改命令依次传给 slave,完成同步
注意:只要是重新连接 master,将自动执行全量复制。 接着的在执行增加的过程,就是执行的增量配置
5.3 另一种连接的方式
** 薪火相传** ,即从节点既可以作为从节点也可以作为主节点,但其本质还是从节点
问题:如果一个master主机进行宕机,将失去写的功能,使用哨兵模式(自动的变为主节点)进行选举一个主节点 slaveof no one 命令,让slave变为主节点,手动的变为主节点
5.4 哨兵模式(主从的启动进行切换)
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。Redis 从 2.8开始正式提供了 Sentinel(哨兵) 架构来解决这个问题。
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例。
这里的哨兵有两个作用:
- 通过发送命令,让 Redis 服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到 master 宕机,会自动将 slave 切换成 master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对 Redis 服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会互相进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行 failover 过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。
当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover[故障转移] 操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
总结:主观下线 只有一个哨兵检测到宕机进行下线 客观下线哨兵投票出进行下线
测试:配置哨兵的配置文件 sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1
// 被监控的名臣 主机名 端口号 后面的数字1 ,代表主机挂了,slave投票看让谁去接替成为主机,票数最多的成为主机
🚩 如果此时主机重新开启了,只能归并到新的主机下,当做从机,这就是哨兵模式的规则
哨兵模式优缺点
优点:
- 哨兵集群,基于主从复制模式,所有的主从复制优点,它全有
- 主从可以切换,故障可以转移,系统的可用性就会更好
- 哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点:
- Redis 不易于在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
- 实现哨兵模式的配置其实是很麻烦的,里面有很多选择
六 redis的缓存与雪崩
6.1 缓存穿透:(查不到导致的)
缓存穿透的概念很简单,用户想要查询一个数据,发现 redis 内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就出现了缓存穿透。
一句话:查缓存没有,直接去查询了Mysql数据库
比如:恶意攻击,传给一个空字符串,则会不断的请求数据库
解决的办法:
-
布隆过滤器 布隆过滤器是一种数据结构,对所有可能查询的参数以 hash 形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
-
缓存空对象 当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源
但是这种方法会存在两个问题: -
如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
-
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
6.2 缓存击穿 (比如微博热搜)
① 概念
这里需要注意和缓存击穿的区别,缓存击穿,是指一个 key 非常热点,在不停的扛着大并发,大并发集中
对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
当某个 key 在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
② 解决方案
Ⅰ **设置热点数据永不过期 ** (setex)
从缓存层面来看,不设置过期时间,就不会出现热点 key 过期后产生的问题。
Ⅱ 加互斥锁 (setnx)
分布式锁:使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
在缓存和mysql之间进行加锁,只有一个进程会访问到Mysql数据库
6.3 缓存雪崩
① 概念
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!
产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
其实缓存集中过期,倒不是非常致命,比较致命的缓存雪崩,**是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩, **一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
② 解决方案
Ⅰ Redis 高可用 (集群的概念)
这个思想的含义是,既然 redis 有可能挂掉,那我多增设几台 redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。
Ⅱ 限流降级 例如:微博热搜,一直刷新不进去,只让部分人进去
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。
Ⅲ 数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。