java32-redis笔记(2)

187 阅读17分钟

一、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) 机制中获益 (因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)

优缺点

优点:

  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
  2. fork进程的时候,会占用一定的内存空间

3.2 AOF(Append Only File)

将我们所有的命令都记录下来,history恢复的时候就把这个文件全部再执行一遍

  • 默认是不开启的,需要手动的进行开启,将append only改为yes

appendonly no yes则表示启用AOF
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof --fix 优点

  1. 每一次修改都会同步,文件的完整性会更加好
  2. 每秒同步一次,可能会丢失一秒的数据
  3. 从不同步,效率最高

缺点

  1. 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢
  2. 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的主从复制

主从复制,数据的复制是单向的!只能由主节点复制到从节点(主节点以为主、从节点以为主)。 作用

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
  • 故障恢复:当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
  • 负载均衡:在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下,通过多个从节点分担负载,提高并发量。
  • 高可用(集群):主从复制还是哨兵和集群能够实施的基础。

为什么使用集群 (公司中是必须要使用的)

  1. 单台服务器难以负载大量的请求
  2. 单台服务器故障率高,系统崩坏概率大
  3. 单台服务器内存容量有限。

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,设置不同的过期时间,让缓存失效的时间点尽量均匀。

参考:www.bilibili.com/video/BV1S5…