Redis复制(replica)

image-20240401155246193

就是主从复制,master以写为主,Slave以读为主

当master数据变化的时候,自动iang新的数据异步同步到其他的slave数据库

能干什么

读写分离

写找master 读找slave

容灾恢复

数据备份

水平扩容支持高并发

怎么操作

配置从库不配置主库

image-20240401155846349

master 如果配置了requirepass参数,需要密码登录

那么slave就要配置masterauth来设置校验密码,否则master就会拒绝slave的访问请求。

info replication

建立了主从关系之后可以查看复制节点的主从关系和配置信息

replicaof 主库IP 主库端口

在从机上写清楚,找哪个为主机,一般写入redis.conf配置文件内的

slaveof 主库IP 主库端口

有些类似于上一个指令(写进配置文件的) 的命令版本

每次与master断开之后,都要重新连接,除非你配置进redis,conf文件

==在运行期间修改slave节点的消息,如果该数据库已经是某个主数据库的从数据库,那么就会停止和原主数据库的同步关系转而和新的数据库同步,重新设置主数据库==

slaveof no one

image-20240401160806518

使当前数据库停止与其他数据库的同步,转为主数据库

复制的原理和工作流程

1 slave 启动成功连接到master后会发送一个syncm’l

​ slave首次全新连接master,一次完全同步,(全量复制)将会被自动的执行,slave自身原有数据会被master数据覆盖

image-20240401163543131

image-20240401163830466

复制的缺点

复制延时,信号衰减

master挂了怎么办?

image-20240401165735857

管道pipelining

redis事一种基于客户端-服务端模型以及请求/响应协议的TCP服务,一个请求会遵循一下的步骤:

  1. 客户端向服务端发送命令分四步(发送命令->命令排队->命令执行->返回结果),并监听socket返回,通常以阻塞模式等待服务端的响应。
  2. 服务端处理命令,并将结果返回给客户端

==以上两部称为 Round Trip Time==

image-20240401151605018

image-20240401151638019

管道的解决思路: 管道可以一次性的发送多条命令给服务端,服务端依次处理完毕之后,通过 一条响应一次寻的将结果返回,通过减少客户端与redis的通信次数来实现降低往返延时时间

将几个频繁往返的命令合起来

pipeline实现的原理是队列,先进先出特性就保证了数据的顺序性。

image-20240401151958228

==批处理命令变种的优化措施,类似于Redis的原生批命令(mget和mset)==

image-20240401152633289 image-20240401152656048

总结

Pipeline 与原生批量命令对比

  • 原生命令是原子性的(例如mset、mget) 管道是非原子性的
  • 原生批量命令只能执行一种命令,pipeline可以执行不同种的命令
  • 原生批命令是服务端实现的 ,而pipeline需要服务端和客户端一起实现。

Pipeline与事务对比

  • 事务具有原子性,管道是不具有原子性的
  • 管道一次性将多条命令发送给服务器,事务是一条一条法的,事务只有在接收到exec命令之后才会执行
  • 执行事务时会阻塞其他命令的执行,而执行管道中的命令时不会。

使用Pipeline的注意事项

事务

其实就是同一组指令需要一起执行。

一个队列中、一次性、顺序性、排他性的执行一系列的命令

redis与数据库事务的区别

image-20240401135206539

怎么使用

case1:正常执行

MULTI

开启事务

image-20240401141119017

后续的命令要加入这个是事务当中

EXEC 结束

case2:放弃事务

MULTI

DISCARD

这个输入之后就是全体放弃,之前执行一半的事务全部撤销

image-20240401141941014

相当于一个是编译报错一个是运行时报错

case3:全体连坐

一条命令出错全部打回去

语法出错了,直接编译就存在问题,当然执行不了

image-20240401142255133

case4:冤有头债有主

对的执行,错的停止,类似于运行时异常,当一个命令失败了,

类似于语法没有检查出错误

image-20240401142554856

case5:watch监控

redis会使用watch来提供乐观锁定,类似于CAS(check-and-Set)

乐观锁:optimistic lock 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下再次期间别人有没有去更新这个数据

乐观锁的策略: 提交版本必须大于记录当前版本才能执行更新

拿一个watch监控一个key,如果这个key在监控期间没有人动过,皆大欢喜,否则,本次执行的操作就会设置成一个null。

watch 初始化k1 和balance两个key,先监控再开启multi,保证两个key变动在同一个事务当中。

image-20240401144932380

有加塞篡改

case6:unwatch

image-20240401145239659

放弃监控

总结

一旦执行了exec之前加的监控锁都会被取消掉,当客户端连接丢失的时候(比如退出连接),所有的东西都会被取消监控

  • 开启:以MULTI 开启一个事务
  • 入队:将多个命令入队到事务当中去,接到这些命令并不会立即的执行,而是会放到等待执行的事务队列中
  • 执行:由EXEC命令触发事务

持久化

就是把数据写到磁盘里面。在实际的应用场景中,会存在服务器突然的宕机,数据全部打到mysql上,这个对于我们来说是灾难性的,所以我们需要让redis可以自己把数据写到服务器中

image-20240331212516033

RDB(Redis DataBase)

RDB持久性以指定的时间间隔执行数据集的时间点快照。

实现类似于照片记录效果的方式,就是把某个时刻的数据与状态以文件的形式写到了磁盘的上面,也就是快照。这样一来,即使是服务器故障宕机,快照文件也不会丢失,数据的可靠性也得到了保证。

==这个快照文件也被称为了RDB文件(dump.rdb),其中,RDB就是Redis Database的缩写==

会直接去读这个dump.rdb文件,重新写回redis的缓存里

Redis6与Redis7

在Redis7里面,把持久话RDB的保存规则发生了改变,尤其是时间记录额度的变化

vim redis7.conf,因为底层做了优化,所以不需要像之前一样记录的那么频繁了

image-20240331213643968

image-20240331213709629

自动触发

redis 默认使用

**image-20240331230827587image-20240331231302890

执行flashall/flushdb命令也会产生dump.rdb文件,但是里里面是空的,没有意义

手动触发

满足不了要求,但是希望是立马就备份,产生rdb文件,就需要开启手动的备份

redis提供了两个命令来生成RDB文件,分别是save喝bdsave

相当于在后台开启了一个子进程,可以立即的开启备份

image-20240331232230372

生产上只能用bgsave,绝对不能用save

save

在主程序中执行回阻塞当前的redis服务器,直到持久化工作完成执行save命令期间,redis是不能处理其他命令的,线上是禁止使用的

bgsave(默认)

image-20240331232606065

image-20240331232850986

image-20240331232902475

什么是fork

在Linux系统中,fork会产生一个与父进程完全向同伴的一个子进程,但是子进程会在此后多会exec系统调用,处于效率考虑,尽量的避免膨胀

lastsave

可以通过lastsave命令去获取最后一次成功执行快照的时间

image-20240331233019774

RDB 优缺点

优点

  • 适合大规模的数据恢复
  • 按照业务定时进行备份
  • 对于数据的完整性喝一致性的要求不高
  • RDB文件在内存中的加载速度要比AOF快得多

缺点

如果你需要在redis停止工作时,比如断电之后,把损失降低到最低,那么rdb并不好

RDB需要经常的fork()以便使用子进程在磁盘上持久话,如果数据集很大,fork()可能会很耗时,并且数据集很大而且CPU性能不是很好的时候,可能会导致Redis停止为客户端服务几毫秒甚至于疫苗

如何检查并修复我们的RDB文件

image-20240331234106128

如何禁用快照

image-20240331234255878

image-20240331234309380

RDB优化配置项详解snapshotting模块

image-20240331234534060

image-20240331234747105

image-20240331234813084

总结

image-20240331234853713

AOF(Append Only file)

如果宕掉了,就load 数据 写回实例里。会以日志的形式记录下每个写的操作,然后将Redis执行过的所有写指令记录下来,(读操作不记录),只允许追加文件不允许改写文件,redis在启动之初会读取该文件重新构建数据。

—==redis重启的话就会根据日志文件的内任凭将写指令从前到后执行一次以完成数据的恢复工作==

默认情况下使用的AOF,开启AOF功能需要配置appendonly yes

image-20240401095600434

三种写回策略

Always

everysec

no

image-20240401095655676 image-20240401095950257

redis6与redis7

image-20240401101931550

优点缺点

优点:更好的保护数据不被丢失,性能高,可以做紧急恢复

  • 使用AOF redis更加的持久,你可以设计不同的fsync策略(三种写回策略),即使是每秒写入,写入的性能依旧是不错的,fsync是使用后台线程执行的,当没有fsync正在进行的时候,主线程会努力的执行写入,只会丢失一秒钟的写入。
  • AOF日志是一个仅附加日志,因此不会出现寻道问题,也不会在断电的时候出现损坏的问题,即使由于某种原因,磁盘已经满了或者其他的原因日志以写一般的命令结尾,redis-check-aof也能很轻松的修复。
  • 当AOF变得太大的时候,redis能够在后台重写AOF,重写是完全安全的,因为当redis继续附加到旧文件时,会创建当前数据集所需的最少操作集生成一个全新的文件,一旦第二个文件准备就绪,redis就会切换两者并开始附加到新的那一个。
  • AOF以易于理解喝解析的格式依次包含所有操作的日志,您甚至可以轻松的导出AOF文件,如歌不小心使用了flushall命令刷新了所有的内容,只要在此期间内没有执行日志重写工作,您仍然可以通过停止服务器,删除最新命令并重新启动redis来保存数据集。

打开appendonly.aof.1.incr.aof里面一定有个flushall,把这个命令删掉

缺点

  • 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度要慢于rdb
  • aof运行的效率要慢于rdb,每秒同步策略的效率较好,不同步效率与rdb相同

AOF重写机制

由于AOF持久话是redis不断将写命令记录到AOF文件中,随着redis的不断的进行,AOF的文件就会越来越大,占用的服务器内存越大,以及AOF恢复时间要求越长。

==为了解决这个问题,redis新增了重写机制,当AOF的文件的大小超过了所设定的峰值时,redis就会自动的启动AOF文件内容压缩,只保留恢复数据的最小指令集,或者手动使用命令bgrewriteaof==

峰值

image-20240401104852636 image-20240401104918329

总结

image-20240401110224729

image-20240401110249259

RDB-AOP混合持久化

可以同时共存 , AOF说的算

image-20240401111518010

在这种情况下,redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存。

设置aof-use-rdbpreamble都值为yes,yes表示开启设置no表示禁用

RDB镜像做全量吃就话,AOF做增量持久化

image-20240401130628985

纯缓存模式

Linux下安装使用Redis

image-20240331183148156

image-20240331183151856

1. 打开/关闭redis命令

1
2
3
4
redis-cli -a 123456 -p 6379
quit//退出
redis-cli -a 123456 shutdown//单实例关闭
redis-cli -a 123456 shutdown //多实例关闭

Redis的十大数据结构

1. String 字符串

2.List 列表

3.Hash哈希表

4.Set集合

5.ZSet redis有序集合

6.GEO 地理空间

7.HyperLogLog 基数统计

8.bitmap位图

9.bitfield位域

10.Stream流

Redis基础操作

image-20240331174848465

del key 是原子的

unlink key 是非阻塞删除

redis默认是16个库,默认使用的是0号库

keepTTL

image-20240331181959700

String

最常用

set、get、ttl

同时设置/获取多个键值

1
2
3
4
MSET Key value [key value]
MGET key [key ...]
mset/mget/msetnx
msetnx:不存在的时候才创建,此外,这个命令有点像事务的完整性,只有全部成功才会创建

image-20240331182938441

image-20240331183123246

获取指定区间范围内的值

GETRANGE k1 0 -1

1
2
GETRANGE k1 0 -1
// 对于string就是获得子串 类似于substring

image-20240331183849993

SETRANGE k1 xxyy

从第一位开始, 用‘xxyy’全部覆盖

image-20240331184034033

数值的增减

INCR K1

表示自增1

DECR

表示自减

DECRBY k1 5

image-20240331194044855

image-20240331194106413

分布式锁

image-20240331194142112

之前的sync/lock/unlock 只能在一个jvm中保证加锁,

image-20240331194937034

setex k1 15 hello

设置 key 过期时间 value

image-20240331195300058

getset

先get再set

List Redis 列表

底层是一个==双端链表==的结构,大概有40多个亿,主要功能有push/pop等,一般用在栈、队列、消息队列等场景中。

left和right都可以加入

如果键不存在,就创建一个新的连别

如果键存在,就新增内容

如果值全部被移除了,对应的键也会消失

对于两端的操作性能是很强的,对于索引下标的操作中间的节点性能就会比较差

lpush/rpush/lrange

image-20240331201214038

lpop/rpop

image-20240331201351026

lindex 按照索引的下标获得元素(从上至下)

image-20240331201559339

llen 获取列表中元素的个数

lrem key 数字N给定值v1 (删除N个值等于V1的元素)、

image-20240331201925609

删除list1里面三个值为1的元素

ltrim key 开始index 结束index ,截取指定范围内的值再赋值给key

image-20240331202135633

rpoplpush 源列表 目的列表

image-20240331202408163

lset key index value

image-20240331202503414

linset key before /after 已有的值插入新的值

image-20240331202637152

Hash()

kv键值对的结构不变,但是v是一个新的键值对

1
Map<String , Map<Object,Object>>

hset/hget/hmset/hmget/hgetall/hdel

1
hset key 字段1 value1 字段2 value2 字段3 value3...
image-20240331203248639
1
2
没有什么区别
hmset/hmget
image-20240331203424491
1
hgetall/hdel

hgetall类似于遍历,hdel就是删掉

image-20240331203632378

hlen 获取某个key中的全部的数量

image-20240331203807798

hexists key 判断在key里面的某个值的key

image-20240331203900374

hkeys/hvals

罗列key和value

image-20240331204001831

hincrby/hincrbyfloat

增加

image-20240331204104566

image-20240331204313354

hsetnx (就是set的性质

image-20240331204340967

image-20240331204227917

购物车

set

单个值可以有多个value ,但是每个值都不能重复

image-20240331204829210

SADD 自动去重

image-20240331205105464

SMEMEBERS set1遍历

image-20240331205144592 image-20240331205332306

集合运算

image-20240331205619186

image-20240331205705964

image-20240331205758175

Zset(sorted set)有序集合

bitmap

HyperLogLog

GEO

Stream(redis 版本的MQ)消息中间件

MQ 包含Kafka RabbitMQ rocketmq等

先去学了MQ

一、容器

什么是容器

将软件打包成标准化单元,以用于开发、交付与部署

二、Docker

Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码在我机器上没问题啊” 这类问题;——一致的运行环境

可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间

避免公用的服务器,资源会容易受到其他用户的影响。——隔离性

善于处理集中爆发的服务器使用压力;——弹性伸缩,快速扩展

可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便

使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署

三 容器VS虚拟机

简单来说:容器和虚拟机具有相似的资源隔离和分配优势,但功能有所不同,因为容器虚拟化的是操作系统,而不是硬件,因此容器更容易移植,效率也更高。

3.1 两者对比图

传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。


传统的虚拟机技术是虚拟出一套硬件之后,在其上运行一套完整的操作系统,然后在该操作系统上再运行所需要的应用进程;而容器内的应用进程直接运行于宿主的内核,容器中没有自己的内核,而且也没有进行硬件虚拟,因此容器要比传统虚拟机更加的轻便。

image-20240318111036086

3.2 容器与虚拟机总结

image-20240318111314460

容器是一个应用层抽象,用于将代码和依赖资源打包在一起。 多个容器可以在同一台机器上运行,共享操作系统内核,但各自作为独立的进程在用户空间中运行 。与虚拟机相比, 容器占用的空间较少(容器镜像大小通常只有几十兆),瞬间就能完成启动

虚拟机 (VM) 是一个物理硬件层抽象,用于将一台服务器变成多台服务器。 管理程序允许多个 VM 在一台机器上运行。每个 VM 都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源,因此 占用大量空间 。而且 VM 启动也十分缓慢

通过 Docker 官网,我们知道了这么多 Docker 的优势,但是大家也没有必要完全否定虚拟机技术,因为两者有不同的使用场景。虚拟机更擅长于彻底隔离整个运行环境。例如,云服务提供商通常采用虚拟机技术隔离不同的用户。而 Docker 通常用于隔离不同的应用 ,例如前端,后端以及数据库

3.3容器与虚拟机两者是可以共存的

image-20240318111806112

四、Docker基本概念

Docker 中有非常重要的三个基本概念,理解了这三个概念,就理解了 Docker 的整个生命周期。

  • 镜像(Image)
  • 容器(Container)
  • 仓库(Repository)

理解了这三个概念,就理解了 Docker 的整个生命周期

image-20240318111954221

镜像:一个特殊的文件系统

操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。

Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。 镜像不包含任何动态数据,其内容在构建之后也不会被改变。

Docker 设计时,就充分利用 Union FS 的技术,==将其设计为分层存储的架构 。镜像实际是由多层文件系统联合组成。==

==镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。== 比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是==仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。==因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以==用之前构建好的镜像作为基础层==,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

容器:镜像运行时的实体

Docker Registry 公开服务 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。

最常使用的 Registry 公开服务是官方的 Docker Hub ,这也是默认的 Registry,并拥有大量的高质量的官方镜像,网址为:https://hub.docker.com/open in new window 。官方是这样介绍 Docker Hub 的


著作权归JavaGuide(javaguide.cn)所有 基于MIT协议 原文链接:https://javaguide.cn/tools/docker/docker-intro.html

服务保护和分布式事务

image-20240317214812012有个服务很高 一个服务就所有的线程和进程全占了

image-20240317215027907

雪崩问题

微服务调用链路中的某个服务故障,引起整个链路中的所有微服务都不可用,这就是雪崩。

image-20240317215511651

雪崩问题产生的原因是什么?

  • 微服务相互调用,服务提供者出现故障或阻塞。
  • 服务调用者没有做好异常处理,导致自身故障。
  • 调用链中的所有服务级联失败,导致整个集群故障

解决问题的思路有哪些?

  • 尽量避免服务出现故障或阻塞。
1
2
3
4
5
保证代码的健壮性;

保证网络畅通;

能应对较高的并发请求;
  • 服务调用者做好远程调用异常的后备方案,避免故障扩散

服务保护方案

- 请求限流

限制访问接口的请求的并发量,避免服务因流量激增出现故障。

image-20240317220549992

线程隔离

线程隔离:也叫做舱壁模式,模拟船舱隔板的防水原理。通过限定每个业务能使用的线程数量而将故障业务隔离,避免故障扩散。

image-20240317220702031

服务熔断

服务熔断:由断路器统计请求的异常比例或慢==调用比例==,如果超出阈值则会熔断该业务,则拦截该接口的请求。

熔断期间,所有请求快速失败,全都走fallback逻辑。

image-20240317223937333

总结

请求限流:限制流量在服务可以处理的范围,避免因突发流量而故障

线程隔离:控制业务可用的线程数量,将故障隔离在一定范围

失败处理:定义fallback逻辑,让业务失败时不再抛出异常,而是走fallback逻辑

服务熔断:将异常比例过高的接口断开,拒绝所有请求,直接走fallback

服务保护技术

Sentinel Hystrix
线程隔离 信号量隔离 线程池隔离/信号量隔离
熔断策略 基于慢调用比例或异常比例 基于异常比率
限流 基于 QPS,支持流量整形 有限的支持
Fallback 支持 支持
控制台 开箱即用,可配置规则、查看秒级监控、机器发现等 不完善
配置方式 基于控制台,重启后失效 基于注解或配置文件,永久生效

Sentinel是阿里巴巴开源的一款微服务流量控制组件。官网地址: https://sentinelguard.io/zh-cn/index.html

image-20240317224540969

使用

step1 导包

image-20240317224630323

step2 配置控制台

image-20240317224644608

step3 重启访问

image-20240317224807401

簇点链路

就是单机调用链路。是一次请求进入服务后经过的每一个被Sentinel监控的资源链。默认Sentinel会监控SpringMVC的每一个Endpoint(http接口)。限流、熔断等都是针对簇点链路中的资源设置的。而资源名默认就是接口的请求路径:

image-20240317224937069

熔断什么的都是针对簇点资源做的

Restful风格的API请求路径一般都相同,这会导致簇点资源名称重复。因此我们要修改配置,把请求方式+请求路径作为簇点资源名称:

image-20240317225139867

分布式事务

在分布式系统中,如果一个业务需要多个服务合作完成,而且每一个服务都有事务,==多个事务必须同时成功或失败,这样的事务就是分布式事务。其中的每个服务的事务就是一个分支事务。整个业务称为全局事务。==

image-20240318095044645

初识Seata

解决分布式事务,各个子事务之间必须能感知到彼此的事务状态,才能保证状态一致。

Seata事务管理中有三个重要的角色:

TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态

image-20240318095501708

部署TC服务

微服务集成Seata

XA模式

AT模式

网关

把项目拆分成了多个部分,在进行交换的时候,如何验证安全性和yjwt这些呢?

如果每个都分发密钥的话,又会存在风险

网关:就是网络的关口,负责请求的路由、转发、身份校验。

image-20240317102541199

image-20240317102936133

快速入门

step1 创建新模块

image-20240317103308256

image-20240317104815549

step2 引入网关依赖

1
2
3
4
5
6
7
8
9
10
11
12
spring:  
cloud:
gateway:
routes:
- id: item # 路由规则id,自定义,唯一
uri: lb://item-service # 路由目标微服务,lb代表负载均衡
predicates: # 路由断言,判断请求是否符合规则,符合则路由到目标 -
Path=/items/** # 以请求路径做判断,以/items开头则符合
- id: xx
uri: lb://xx-service
predicates:
- Path=/xx/**

step3 编写启动类

image-20240317104833164

step4 配置路由规则

去resources 配置,在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port:8080
前端请求发出,发给网关
spring:
application:
name:gateway
cloud:
nacos:
server-addr:192.168.150.101:8848//这个就是nacos的安装地址
gateway:
routes:
-id:item-service
路由的唯一标识,服务名字。应该是那个微服务的yaml写的
uri:lb://item-service(lb表示协议名字)
predicates:
-Path=/item/**,/search/**

-id: user-service
uri:lb://user-service
predicates:
-Path=/addresses/**,/users/**

image-20240317111829319

路由属性

网关路由对应的Java类型是RouteDefinition,其中常见的属性有:

  • id:路由唯一标示
  • uri:路由目标地址
  • predicates:路由断言,判断请求是否符合当前路由。
  • filters:路由过滤器,对请求或响应做特殊处理。

image-20240317142249280

image-20240317142344314

名称 说明 示例
After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie 请求必须包含某些cookie - Cookie=chocolate, ch.p
Header 请求必须包含某些header - Header=X-Request-Id, \d+
Host 请求必须是访问某个host(域名) - Host=.somehost.org,.anotherhost.org
Method 请求方式必须是指定方式 - Method=GET,POST
Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 - Query=name, Jack或者- Query=name
RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24
Weight 权重处理 - Weight=group1, 2
XForwarded Remote Addr 基于请求的来源IP做判断 - XForwardedRemoteAddr=192.168.1.1/24
AddRequestHeader 给当前请求添加一个请求头 AddrequestHeader=headerName,headerValue
RemoveRequestHeader 移除请求中的一个请求头 RemoveRequestHeader=headerName
AddResponseHeader 给响应结果中添加一个响应头 AddResponseHeader=headerName,headerValue
RemoveResponseHeader 从响应结果中移除有一个响应头 RemoveResponseHeader=headerName
RewritePath 请求路径重写 RewritePath=/red/?(?.*), /${segment}
StripPrefix 去除请求路径中的N段前缀 StripPrefix=1,则路径/a/b转发时只保留/b

网关登录校验

image-20240317152011836

image-20240317144533991

  • 如何在网关转发之前做登录校验?

  • 网关如何将用户信息传递给微服务?

  • 如何在微服务之间传递用户信息?

在最开始的pre部分就进行JWT校验,然后保存到请求头,因为网关和微服务的联系是http协议的,用保存到请求头是最方便的

自定义过滤器

原理:

网关过滤器有两种,分别是:

  • GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。

  • GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。

两种过滤器的过滤方法签名完全一致:

image-20240317152329925

GlobalFilter步骤

step1自定义过滤器

image-20240317153953039

step2保证过滤器在nettyroutingfilter前执行

ctrl h

nettyroutingfilter 实现了globalfilter 、ordered

image-20240317154417252

image-20240317154550771

GatewayFilter开发比较麻烦

实现登录校验

image-20240317161207641

获取request

判断是否需要做登记

获取token

image-20240317161043431

校验并解析token

image-20240317161242789

传递用户信息

image-20240317161231553

放行

image-20240317161224211

image-20240317161216758

image-20240317175833120

实现登录校验

一、在网关的登录校验过滤器中,把获取到的用户写入请求头

需求:修改gateway模块中的登录校验拦截器,在校验成功后保存用户到下游请求的请求头中。
提示:要修改转发到微服务的请求,需要用到ServerWebExchange类提供的API,示例如下:

1
2
3
4
exchange.mutate() // mutate就是对下游请求做更改        
.request(builder -> builder.header("user-info", userInfo))
.build();

image-20240317203444339

OpenFeign传递用户

OpenFeign中提供了一个拦截器接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求:

1
2
3
4
public interface RequestInterceptor {  /**   * Called for every request. Add data using methods on the supplied {@link RequestTemplate}.   */  
void apply(RequestTemplate template);
}

其中的RequestTemplate类中提供了一些方法可以让我们修改请求头:

image-20240317204509497

image-20240317205048040

image-20240317205446309

配置管理

微服务重复配置过多,维护成本高

业务配置经常变动,每次修改都要重启服务

网关路由配置写死,如果变更要重启网关

image-20240317205539196

监听配置的变更,变更了就推送给相应的微服务

实现配置的热更新

image-20240317205830739

配置共享

一.添加配置到Nacos

添加一些共享配置到Nacos中,包括:Jdbc、MybatisPlus、日志、Swagger、OpenFeign等配置

image-20240317210450372

添加一些共享配置到Nacos中,包括:Jdbc、MybatisPlus、日志、Swagger、OpenFeign等配置

image-20240317210509795

image-20240317210623417

二.拉取共享配置

基于NacosConfig拉取共享配置代替微服务的本地配置。

image-20240317211045349

①引入依赖

1
2
3
4
5
6
7
8
9
<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency> <!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

②新建bootstrap.yaml

image-20240317211200919

配置热更新

配置热更新:当修改配置文件中的配置时,微服务无需重启即可使配置生效。

前提条件:

①nacos中要有一个与微服务名有关的配置文件。

image-20240317212050543

image-20240317211842338

②微服务中要以特定方式读取需要热更新的配置属性

1
2
3
4
5
@Data
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
private int maxItems; }

1
2
3
4
5
6
@Data
@RefreshScope
public class CartProperties { @Value("${hm.cart.maxItems}")
private int maxItems;
}

image-20240317212657760

image-20240317212751287

image-20240317213403685

动态路由

实现动态路由需要将路由配置保存到Nacos,然后在网关监听Nacos中的路由配置,并实现配置热更新。然而网关路由并不是自定义业务配置属性,本身不具备热更新功能!参考:CompositeRouteDefinitionLocator

因此我们需要自己完成两件事情:

①监听Nacos配置变更的消息

②当配置变更时,将最新的路由信息更新到网关路由表

image-20240317214014466

image-20240317214021404

image-20240317214029557

image-20240314203146347

微服务01

微服务:一种软件架构风格,他是专注于单一职责的很多小型项目为基础,组合出复杂的大型项目

单体架构

将业务的所有功能都集中在一个项目中开发,打包成一个包部署

优点:

架构简单;部署成本低

缺点:

团队协作成本高;系统的发布效率低;系统可用性差

微服务

微服务架构,是服务化思想指导下的一套最佳实践架构方案。

服务化,**==就是把单体架构中的功能模块拆分为多个独立项目。==**

  • 粒度小

  • 团队自治

  • 服务自治:分别编译打包;分别部署;数据隔离

    image-20240314205902699

SpringCloud

SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。

SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:

image-20240314211045953

SpringCloud****版本 SpringBoot****版本
2022.0.x aka Kilburn 3.0.x
2021.0.x aka Jubilee 2.6.x, 2.7.x (Starting with 2021.0.3)
2020.0.x aka Ilford 2.4.x, 2.5.x (Starting with 2020.0.3)
Hoxton 2.2.x, 2.3.x (Starting with SR5)
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

微服务拆分

image-20240314211735588

image-20240314211911736

服务拆分原则

什么时候拆分

创业型项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐渐拆分。

确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦。

怎么拆分

从拆分目标来说,要做到:

  • 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
  • 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖。

从拆分方式来说,一般包含两种方式:

  • 纵向拆分:按照业务模块来拆分
  • 横向拆分:抽取公共服务,提高复用性

拆分服务

独立project

Maven聚合

image-20240314214402573

dto :表单交互

远程调用

Spring给我们提供了一个RestTemplate工具,可以方便的实现Http请求的发送。使用步骤如下:

①注入RestTemplate到Spring容器

image-20240315141015868

②发起远程调用

image-20240315141033725

requestArgsConstructor注解 ,对于所有的final的字段创建构造函数

服务治理

服务远程调用时存在的问题

image-20240315151455307

在远程调用的时候,不知道调哪个服务器,如果那个服务器挂了也不知道怎么换

注册中心原理

所有的服务提供者都会在注册中心注册,然后当服务调用者要调用的时候,就会把注册的信息传输过去;

为了实现负载均衡,可能使用了轮询等

image-20240315152502417

image-20240315152553563

心跳续约

所有的服务都会定期和注册中心请求一下,说明自己还活着

总结

服务治理中的三个角色分别是什么?

​ ==服务提供者:暴露服务接口,供其它服务调用==

​ ==服务消费者:调用其它服务提供的接口==

​ ==注册中心:记录并监控微服务各实例状态,推送服务变更信息==

消费者如何知道提供者的地址?

​ 服务提供者会在启动时注册自己信息到注册中心,消费者可以从注册中心订阅和拉取服务信息

消费者如何得知服务状态变更?

​ 服务提供者通过心跳机制向注册中心报告自己的健康状态,当心跳异常时注册中心会将异常服务剔除,并通知订阅了该服务的消费者

当提供者有多个实例时,消费者该选择哪一个?

​ ==消费者可以通过负载均衡算法,从多个实例中选择一个==

Nacos注册中心

Nacos是目前国内企业中占比最多的注册中心组件。它是阿里巴巴的产品,目前已经加入SpringCloudAlibaba中。

image-20240315154400243

image-20240315155652332

服务注册

服务注册步骤如下:

①引入nacos discovery依赖:

image-20240315160950577

②配置Nacos地址

image-20240315160955964

注意:每个微服务都要进行一个注册

在idea里配置多个启动项就能实现多个服务

用云服务器的话,记得把三个端口全部开放:8848、9848、9849,要不然会报错

image-20240315161713629

服务发现

消费者需要连接nacos以拉取和订阅服务,因此服务发现的前两步与服务注册是一样,后面再加上服务调用即可:

①引入nacos discovery依赖

②配置nacos地址

③服务发现

image-20240315162004885

1
2
3
4
5
6
7
8
9
10
11
private final DiscoveryClient discoveryClient;
private void handleCartItems(List<CartVO> vos) {
// 1.根据服务名称,拉取服务的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
// 2.负载均衡,挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
//这个是随机负载均衡
// 3.获取实例的IP和端口
URI uri = instance.getUri();
// ... 略
}

nacos为这些提供了API

image-20240315170136819

OpenFeign

快速入门

OpenFeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来。官方地址:https://github.com/OpenFeign/feign

其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送。

image-20240315171700078

① 引入依赖,包括OpenFeign和负载均衡组件SpringCloudLoadBalancer

1
2
3
4
5
6
7
8
9
10
11
12
<!--OpenFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡-->
<!--这个依赖里面有各种负载均衡的一些,早期用ribbon,后期用这个loadbalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

②通过@EnableFeignClients注解,启用OpenFeign功能

1
2
3
4
@EnableFeignClients
@SpringBootApplication
public class CartApplication { // ... 略 }

③编写FeignClient

1
2
3
4
5
@FeignClient(value = "item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}

④使用FeignClient,实现远程调用

1
List<ItemDTO> items = itemClient.queryItemByIds(List.of(1,2,3));

连接池

OpenFeign对Http请求做了优雅的伪装,不过其底层发起http请求,依赖于其它的框架。这些框架可以自己选择,包括以下三种:

  • HttpURLConnection:默认实现,不支持连接池
  • Apache HttpClient :支持连接池
  • OKHttp:支持连接池

具体源码可以参考FeignBlockingLoadBalancerClient类中的delegate成员变量。

OpenFeign整合OKHttp的步骤

①引入依赖
1
2
3
4
5
6
<!--ok-http-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>

②开启连接池功能
1
2
3
4
feign:  
okhttp:
enabled: true # 开启OKHttp连接池支持

最佳实践

image-20240316093447481

image-20240316093502128

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:

方式一:指定FeignClient所在包

image-20240316094114690

方式二:指定FeignClient字节码

image-20240316094121599

日志

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

NONE:不记录任何日志信息,这是默认值。

BASIC:仅记录请求的方法,URL以及响应状态码和执行时间

HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息

FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

由于Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。

总结

如何利用OpenFeign实现远程调用?

•引入OpenFeign和SpringCloudLoadBalancer依赖

•利用@EnableFeignClients注解开启OpenFeign功能

•编写FeignClient

如何配置OpenFeign的连接池?

•引入http客户端依赖,例如OKHttp、HttpClient

•配置yaml文件,打开OpenFeign连接池开关

OpenFeign使用的最佳实践方式是什么?

•由服务提供者编写独立module,将FeignClient及DTO抽取

如何配置OpenFeign输出日志的级别?

•声明类型为Logger.Level的Bean

•在@FeignClient或@EnableFeignClients注解上使用

0%