category
学习思考
date
Jul 20, 2022
icon
Origin
password
slug
knowledgeFragment
status
Published
summary
整理一些不成体系学习的知识,持续更新
tags
Tags
type
Post

遇到什么记录什么

 

1.@requestbody注解的作用

将前端传过来的json值 封装进 @requestbody 后面的实体类中

2.@pathvariable注解的作用

绑定url绑定的占位符。
一般
@GetMapping(value = "/payment/hystrix/timeout/{id}")
@PathVariable("id") Integer id
这么使用

3.Eureka的自我保护机制

某时刻eureka的服务不能用了,他也不会立即清除,依旧会对服务信息列表里信息进行保存。高可用的设计思想。默认开启自我保护,

4.RPC远程调用最核心的是什么

高可用 解决方法:搭建Eureka注册中心集群,实现负载均衡+故障容错。互相注册,相互守望,对外暴露出一个整体

5.@EnableEurekaServer注解的作用

激活Eureka服务的相关配置

6.EnableDiscoveryClient注解的作用

把服务信息暴露给消费端

7.Eureka、Zookeeper、Consul三个注册中心的异同点

C: Consistency (强一致性)
A: Availability (可用性)
P: Partition tolerance (分区容错性)
组件名
语言
CAP
服务健康检查
对外暴露接口
SpringCloud集成
Eureka
java
AP
可配支持
Http
已集成
Zookeeper
go
CP
支持
Http/DNS
已集成
Consul
java
CP
支持
客户端
已集成

8.Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡的区别

Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后nginx实现转发请求。即负载均衡是由服务端实现的。
Ribbon本地负载均衡,在调用服务接口的时候,会在注册中心哈桑获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

9.LB负载均衡(Load Balance)是什么

简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。
常见的负载均衡有软件Nginx,LVS,硬件F5等。
集中式LB:
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责吧访问请求通过某种策略转发至服务的提供方。
进程内LB:
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己在从这些地址中选择出一个核实的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。Ribbon是软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,比如eureka。

10.RestTemplate的getForObject()方法和getForEntity()方法的区别

getForObject():返回对象为响应体中数据转化成的对象,基本上可以理解为Json。
getForEntity():返回对象为RestTemplate对象,包含了响应中的一些重要信息,比如相应头、响应状态码、响应体等。

11.Ribbon的负载规则有哪些

7种,随机,轮询,轮询重试,轮询响应快的权重大,先过滤掉故障,复合判断
注意点:规则应该写在ComponentScan扫描不到的地方,即新建包,在启动类用@RibbonClient 注解引入。

12.@Component注解的作用

当我们的类不属于各种归类的时候(Service、controller)可以用这个注解进行标记

13.Integer的最大值

2147483647
notion image
notion image

13.OpenFeign 默认超时控制时间

默认feign客户端只等待 1s ,但是服务处理需要超过 1s ,导致 feign 客户端不想等待,直接返回错误。为了避免这样的请款,有时候我们需要设置feign客户端的超时控制。
需要在yml文件中 开启配置,配置如下:
ribbon: #指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间 ReadTimeout: 5000 #指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 5000

14.SpringCloud 远程调用逻辑:

1、如果我们有一个service:CouponFeignService,调用了他的CouponFeignService.saveSpuBounds(spuBoundsTo)方法;
1)、SpringCLoud做的第一步是将这个对象转为Json,通过@RequestBody注解,
2)、去注册中心找到远程服务,例如:“gulimall-coupon”,给调用接口上的地址发送请求,例如:@PostMapping("/coupon/spubounds/save")由于我们标注了@RequestBody,会将上一步的对象转的Json放在请求体位置发送请求。
3)、对方服务收到请求收到请求。实际上是收到了请求体里的Json数据,(@RequestBody SpuBoundsEntity spuBounds)将请求体的Json转为SpuBoundsEntity,只要两个实体类的属性有一一对应就可以转换
只要Json数据模型是兼容的,双方服务无须使用同一个to。

15.Idea设置每个服务的内存占用:

选择edit configuration,创建Compound,加入需要配置的服务,选择修改,配置VM options
配置:Xmx100m

16.MySQL事务tips:

事务没有提交之前数据是读不出来的,mysql默认的隔离级别是可重复读,最起码读到已经提交了的数据。
可以使用
set session transaction isolation level read UNCOMMITTED;
将当前的会话隔离等级设置为读未提交,当前的窗口就能读到没有提交的事务数据了

17.集成Mybatis-plus后分页不好用的问题:

需要添加Mybatis-plus分页插件配置,配置如下:
@Configuration @EnableTransactionManagement @MapperScan("com.atjixue.gulimall.ware.dao") public class WareMybatisConfig { @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false paginationInterceptor.setOverflow(true); // 设置最大单页限制数量,默认 500 条,-1 不受限制 paginationInterceptor.setLimit(1000); // 开启 count 的 join 优化,只针对部分 left join paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true)); return paginationInterceptor; } }

18.解决跨域origin重复问题(未使用nginx): Allow-Origin header contains multiple values... but only one is allowed:

网关配置增加如下配置:
filters: - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment} - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

19.分布式基础总结:

1、分布式基础概念:

-微服务:服务的独立自治,为不同功能的服务创建自己的项目,为不同的开发人员提供并行开发环境
-注册中心:对于微服务来说,将项目拆成微服务,如果不互相调用可以不需要注册中心。但是需要服务之间的互相调用,就需要一个注册中心实时感知服务的位置
-配置中心:无论是微服务还是单体服务都推荐有一个配置中心,好处就是服务上线之后,不需要修改本地源代码的配置,重新打包发布。而是在线上通过一个可视化界面,将配置信息进行修改,改完后服务自动更新使用最新配置。
-远程调用:使用Feign给对方服务发送请求,使用Feign需要导入Feign的相关依赖和开启远程调用功能,而想要实现远程调用,这些服务就必须注册到注册中心。需要在每一个服务配置配置中心地址。开启服务注册发现注解。
-网关:所有请求都发送给网关,由网关代理给其他服务,网关可以配置统一的跨域请求等。

2、基础开发:

-springboot 2.0:Springboot2.0 基于Spring5做了一个最大的变化,引入了reactor(反应式编程),给我们带来了web开发里面的webflux,可以非常容易的创建高性能高并发的web应用。
-SpringCloud、Mybatis-Plus、Vue组件化、阿里对象存储

3、环境:

Vagrant、Linux、Docker、MySql、Redis、逆向工程(人人开源)

4、开发规范:

-数据校验JSR303、全局异常处理、全局统一返回、全局跨域处理
-枚举状态、业务状态码、VO与TO与PO划分,逻辑删除
-Lombok :@Data @Slf4j

20.Elasticsearch

一个分布式的开源搜索和分析引擎,适用于所有类型的数据
Mysql 主要用于数据的持久化与存储 CRUD,Mysql如果单表达到百万以上的数据,搜索就会有延迟。
Elasticsearch 用于海量数据的搜索。提供了REST API的接口操作,开箱即用。

21.Feign的调用流程:

1、构造请求数据,将对象转为JSON,先会构造一个请求的模板
RequestTemplate template = buildTEmplateFreomArgs.create(argv);
2、发送请求进行执行(执行成功会解码响应数据)
executeAndDecode(tempte)
3、执行请求会有重试机制
while(true){
try{
executeAndDecode(tempte)
}catch{
retryer.continueOrPropagate(e);
throw ex
continue;
}
}

22.Nginx代理给网关:

Nginx代理给网关的时候,会丢失请求的host信息,需要配置Nginx让他不要丢失信息,加上一个设置项,设置头:proxy_set_header Host $host
notion image

23.压力测试性能指标:

响应时间(Response Time:RT):

响应时间指用户从客户端发起一个请求开始,到客户端接收到从服务器端返回的响应结束,整个过程所耗费的时间。

HPS(Hits Per Second):

每秒点击次数,单位是/秒

TPS(Transaction per Second):

系统每秒处理交易数,单位是笔/秒

QPS(Query per Second):

系统每秒处理查询次数,单位是次/秒。对于互联网业务中,如果某些业务有且仅有一个请求连接,那么TPS=QPS=HPS,一般情况下用TPS来衡量整个业务流程,用QPS来衡量接口查询次数,用HPS来表示对服务器单机请求。
无论TPS、QPS、HPS、此指标是衡量系统处理能力非常重要的指标,越大越好,根据经验,一般情况下:
金融行业:1000TPS~50000TPS,不包括互联网化的活动
保险行业:100TPS~100000TPS,不包括互联网化的活动
制造行业:10TPS~5000TPS
互联网电子商务:10000TPS~1000000TPS
互联网中型网站:1000TPS~50000TPS
互联网小型网站:500TPS~10000TPS

最大响应时间(Max Response Time):

指用户发出请求或者指令到系统做出反应(响应)的最大时间。

最小响应时间(Min Response Time):

指用户发出请求或者指令到系统做出反应(响应)的最大时间。

90%响应时间(90% Response Time):

是指所有用户的响应时间进行排序,第90%的响应时间

从外部看,性能测试主要关注如下三个指标:

吞吐量:每秒钟系统能够处理的请求数、任务数
响应时间:服务处理一个请求或者任务的耗时
错误率:一批请求中结果出错的请求所占比

24.影响性能的考虑

首先考虑自己的应用属于CPU密集型还是IO密集型
IO:网络IO、磁盘读写、数据库读数据、redis读数据。解决:固态硬盘、内存、缓存技术
CPU:大量的计算,排序、过滤、整合。解决:增加服务器,并行处理

25.性能监控分析:

JVM内存模型:

.java文件被编译成.class文件,被JVM的类装载器装载到JVM,所有的数据都在运行时数据区,优化的大部分都在运行时数据区。等所有数据都进入JVM之后会由执行引擎负责执行,会在虚拟机栈进行依次的入栈出栈。每一个线程都会有自己的虚拟机栈本地方法栈程序计数器。所有线程共享的是方法区。优化更多的是在

堆(Heap):

所有的对象实例以及数组都要在堆上分配。堆是垃圾收集器管理的主要区区域,也被称为“GC堆”;
也是我们优化考虑最多的地方。
堆可以细分为:
新生代: Eden空间、From Survivor空间、To Survivor空间
老年代
永久代/元空间:Java8以前永久代,受JVM管理,java8以后元空间,直接使用物理内存。因此,默认情况下,元空间的大小仅受本地内存限制。
垃圾回收流程:新创建的对象先会被分配到新生代,新生代分为两部分,伊甸园区和幸存者区,会先去伊甸园区判断内存够不够,如果够的话,直接分配内存在伊甸园区,如果不够会进行一次YoungGC(minor GC),主要来清理新生代的空间。假设伊甸园区有10个对象,9个没有,会把这9个踢出去,剩下一个进入幸存者区。如果还放不下,这个对象会被认为是个大对象, 会尝试放在老年代,如果老年代也放不下,会进行一次Full GC,然后在看老年代能不能放下,如果还放不下,会爆内存溢出异常(OOM)。Full GC是非常慢的,是性能慢10倍左右的GC,性能优化一定要注意Full GC。

26.Jconsole和Jvisualvm的使用

Jconsole:本机安装了java环境后打开控制台输入jconsole,选择需要监控的进程,界面如下
notion image
Jvisualvm:win系统打开控制台直接输入jvisualvm,mac系统参考如下:
界面如下:
notion image
notion image
其中线程栏:
notion image
运行:正在运行的线程
休眠:sleep
等待:wait
驻留:线程池里面的空闲线程
监视:正在阻塞的线程,正在等待锁的线程
notion image
SQL耗时越小越好,一般情况下微秒级别
命中率越高越好,一般情况下不能低于95%
锁等待次数越低越好,等待时间越短越好
中间件越多,性能损失越大,大多都损失在网络交互了
业务:DB(Mysql优化)、模板的渲染速度(缓存)、静态资源、
解决:开缓存、优化数据库(命中索引)、关日志

27.Nginx动静分离

把静态资源放在nginx,将所有项目的静态资源放在nginx里面,指定一个规则:/static/***所有请求都由nginx直接返回。
Nginx配置如下:将请求 /static/ 路径的请求进行处理,由Nginx直接返回。
notion image

28.配置JVM内存:

notion image
其中:Xmx代表应用程序(不是JVM)能够使用的最大内存数
Xms代表程序初始化的时候栈的大小
Xmn n就是新生代,就是伊甸园区➕幸存者区,请求临时对象多的时候需要调大。

29.缓存与分布式锁:

缓存:

为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而db承担数据落盘工作。
那些数据适合放入缓存?
即时性、数据一致性要求不高的
访问量大且更新频率不高的数据(读多、写少)
举例:电商类应用,商品分类、商品列表灯适合缓存并加一个失效时间(根据数据更新频率来定),后台如果发布一个商品,买家需要5分钟才能看到新的商品一般还是可以接受的。
本地缓存:和代码属于同一个进程,和代码运行在同一个项目,在同一个JVM。相当于在本地保存一个副本。在分布式系统下,每一个系统都自带本地缓存,每一个服务器都需要先生成一次缓存。最大的问题是当我们修改了数据,我们会修改缓存,这样我们因为负载均衡选择了一台服务器,只修改了一台服务器的本地缓存,这会导致,下次换一台服务器出现数据不一致的问题。所以分布式情况是不应该使用本地缓存的。
分布式缓存一般使用 Redis
整合redis:
引入data-redis-starter的依赖
简单配置redis的host等信息
使用springboot自动配置好的redisTemplate,来操作redis。

30.Redis exception; nested exception is io.lettuce.core.RedisException: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 46137344 byte(s) of direct memory (used: 58720256, max: 100663296)问题:

springboot 2.0 以后默认是使用lettuce作为操作redis的客户端。使用netty进行网络通信 ettuce的bug导致堆外内存溢出 -Xm300m netty如果没有指定堆外内存,默认使用-Xm300m 可以通过 -Dio.netty.maxDirectMemory进行设置 解决方案:不能使用 -Dio.netty.maxDirectMemory只去调大堆外内存。 1)、升级lettuce客户端。2)、切换使用jedis
 

31.缓存穿透、缓存雪崩、缓存击穿:

缓存穿透:

指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
风险:利用不存在的数据进行攻击,数据库压力瞬时增大,最终导致崩溃
结局:null结果缓存,并加入短暂过期时间

缓存雪崩:

缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩
解决:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

缓存击穿:

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。
如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都会落到db,我们称为缓存击穿。
解决:加锁,大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db

32.分布式下如何加锁:

所有服务去同一个地方“占坑”,如果占到,就执行逻辑,否则就必须等待,知道释放锁。“占坑”可以去redis,可以去数据库,可以去任何大家都能访问的地方。等待可以自璇的方式。

33.Redisson的使用:

整合redisson作为分布式锁等功能的框架:
1、引入依赖:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.12.0</version> </dependency>
2、配置redisson:
Redisson程序化的配置方法是通过构建Config对象实例来实现的。例如:
Config config = new Config(); config.setTransportMode(TransportMode.EPOLL); config.useClusterServers() //可以用"rediss://"来启用SSL连接 .addNodeAddress("redis://127.0.0.1:7181");
新增配置类,单节点redis 的配置如下:
public class MyRedissonConfig { /** *所有对Redisson的使用都要通过RedissonClient对象 * */ @Bean(destroyMethod="shutdown") RedissonClient redisson() throws IOException { // 创建配置 Config config = new Config(); // Redis url should start with redis:// or rediss:// config.useSingleServer().setAddress("redis://0.0.0.0:6379"); // 根据config对象创建出RedissonClient的实例 RedissonClient redissonClient = Redisson.create(config); return redissonClient; } }

可重入锁:

方法a调用了方法b,a加一把锁,b也想加,可重入锁就是a拿到锁之后b直接用就行,这是可重入锁。
方法a调用了方法b,a加一把锁,b也想加,但是b要等a释放锁才能够拿到锁,这就是不可重入锁。但是, a要等到b执行完了才能释放锁,这样就会死锁。
所以所有的锁都应该设计成可重入锁。
Redis的Redisson分布式可重入锁,实现了JUC接口。
示例代码:
@ResponseBody @GetMapping("hello") public String hello(){ //1、 获取一把锁,只要锁的名字一样就是用一把锁 RLock lock = redisson.getLock("my-lock"); //2、加锁 lock.lock(); // 阻塞式等待 try{ System.out.println("加锁成功,执行业务" + Thread.currentThread().getId()); Thread.sleep(3000); }catch (Exception e){ }finally { //解锁 System.out.println("释放锁..." + Thread.currentThread().getId()); lock.unlock(); } return "hello"; }
redisson有看门狗机制,在业务运行期间对锁自动续期,自动解锁:
1)、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长锁自动过期被删掉 2)、加锁的业务只要运行完成 就不会给当前锁续期,即使不手动解锁,锁也会默认在30s以后自动删除

34.缓存数据一致性问题:

双写模式:

更改完数据库,再去更改缓存。会有产生脏数据的风险,所以我们有并发写的时候需要加锁。为缓存数据加上过期时间,这就是暂时性的脏数据问题,具体看业务是否允许。读到的数据又延迟,但是最终一致性可以保证。

失效模式:

更改完数据库,直接删除缓存。如果数据经常修改的话,经常的上锁,还不如不加锁,直接查数据库。
无论是失效模式还是双写模式:设计上都应该为缓存增加过期时间,暂时的脏数据只要缓存时间一到,数据一清我们又能得到正确的数据。

解决方案:

无论是双写模式还是失效模式,都会导致缓存不一致的问题。即多个实例同时更新会出事。
1、如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
2、如果是菜单、商品介绍等基础数据,也可以去使用canal订阅binlog的方式
3、缓存数据 + 过期时间也足够解决大部分业务对于缓存的要求
4、通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不换新脏数据,允许临时脏数据可忽略)

总结:

我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
我们不应该过度设计,增加系统的复杂性
遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。

35.MD&MD5盐值加密

MD5
Message Digest algorithm 5 信息摘要算法
压缩性: 任意长度的数据,算出的MD5值长度都是固定的
容易计算:从原数据计算出MD5值很容易
抗修改行:对原数据进行任何改动,哪怕值修改1个字节,所得到的MD5值都有很大区别。
彩虹表:暴力破解,每天计算,存到库里 MD5不能直接进行密码的存储
强抗碰撞:想找到两个不同的数据,使他们具有相同的MD5值,是非常困难的。

加盐:

通过生成随机数与MD5生成字符串进行组合
数据库同时存储MD5值与salt值。验证正确性时使用salt进行MD5即可
使用BCryptPasswordEncoder 自动加盐 解析,验证只需要明文和密文进行批评

36.社交登录

OAuth2.0
OAuth是一个开放标准,允许用户授权第三方网站访问他们的存储在另外服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或者分享他们的数据的所有内容
OAuth2.0 对于用户相关的OpenApi(例如:获取用户信息,动态同步,照片,日志,分享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权

37.服务处于集群状态,session共享问题

1、session复制

优点:web-server(tomcat)原生支持,只需要修改配置文件
缺点:
session同步需要数据传输,占用大量网络带宽,降低服务器集群的业务处理能力
任意一台web-server保存的数据都是所有web-server的session总和,受到内存限制无法水平扩展更多的web-server
大型分布式集群情况下,由于所有web-server都全量保存数据,所以此方案不可取。

2 客户端存储

优点:
服务器不需要存储session,用户保存自己的session信息到cookie中,节省服务端资源
缺点:
都是缺点,这只是一种思路
缺点,具体如下:
每次http请求,携带用户在cookie中的完整信息,凉风网络带宽
session数据放在cookie中,cookie有长度限制4k,不能保存大量信息
session数据放在cookie中,存在泄漏,篡改,窃取等安全隐患。
这种方式不会使用。

3.hash一致性

通过hash一致性,让同一个人一直访问同一台服务器,这样这一台服务器,就会存在这个人所带的信息。
优点:
只需要更改nignx配置,不需要私改应用代码
负载均衡,只需要hash属性的值是分布式均匀的,多台web-server的负载就是均匀的
可以值支持web-server水平扩展(session同步法是不行的,首内存限制)
缺点:
session还是存在web-server中的,所以web- server重启可能导致部分session丢失,影响业务,如部分用户需要重新登录。
如果web-server水平扩展,rehash后session重新分布,也会有一部分用户路由不到正确的session
但是以上缺点问题也不是很大,因为session本来就是有时效期的。所以这两种反向代理的方式可以使用。

38.session共享问题-统一存储

优点:
没有安全隐患
可以水平扩展,数据库/缓存水平切分即可
web-server重启或者扩容都不会有session丢失
不足:
增加了一次网络调用,并且需要修改代码应用代码;如将所有的getsession方法替代为redis查数据的方式,redis获取数据比内存满很多
上面缺点可以用SpringSession完美解决。

39.Session共享问题结局-不同服务,子域session共享

40.SpringSession核心原理:

@EnableRedisHttpSession 导入RedisHttpSessionConfiguration.class 配置
1、给容器中添加了一个组件
RedisOperationsSessionRepository :redis操作session。session的增删改查的封装类
SessionRepositoryFilter Session存储过滤器。每个请求过来都必须经过filter
创建的时候就自动从容器中获取了RedisOperationsSessionRepository
原始的request,response都被包装。
以后获取session request.getSession
wrapperRequest.getSession() ==⇒中获取到
装饰者模式

41.ThreadLoacl - 统一线程共享数据

核心原理:Map<Thread,Object> threadLoacl

42.RabbitMQ -消息队列

应用:异步处理、应用解耦、流量控制

概述:

1、大多数应用中,可以通过消息服务中间件来提升系统异步通信、扩展解耦能力
2、消息服务中两个重要概念:
消息代理和目的地
当消息发送者发送消息以后,将由消息代理接管,消息代理保证消息传递到指定目的地。
3、消息队列主要有两种形式的目的地
队列(queue):点对点消息通信
主题(topic):发布(publish)/订阅(subscribe)消息通信

点对点式:

  • 消息发送者发送消息,消息代理将其放入消息队列,消息接收者从队列中获取消息内容,消息读取后被移除队列
  • 消息只有唯一的发送者和接受者,但并不是说只能有一个接受者

发布订阅式:

  • 发送者(发布者)发送消息到主题,多个接收者(订阅者)监听(订阅)这个主题,那么就会在消息达到时同时收到消息

JMS(Java Message Service)JAVA消息服务:

  • 基于JVM消息代理的规范。ActiveMQ、HornetMQ是JMS实现
AMQP(Advanced Message Queuing Protocol)
  • 高级消息队列协议,也是一种消息代码的规范,兼容JMS
  • RabbitMQ是AMQP的实现

Spring支持

  • spring-jms提供了对JMS的支持
  • spring- rabbit提供了对AMQP的支持
  • 需要ConnectionFactory的实现来链接消息代理
  • 提供JmsTemplate、RabbitTemplate来发送消息
  • @JmsLIstener(JMS)、@RabbitListener(AMQP)注解在方法上舰艇消息代理发布消息
  • @EnableJms 、 @EnableRabbit开启支持

Spring自动配置

  • JmsAutoConfiguration
  • RabbitAutoConfiguration

RabbitMQ简介:

RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。
核心概念:
  • Message:
    • 消息,消息不实具名的,它是由消息头和消息组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等
  • Publisher:
    • 消息的产生者,也是一个向交换器发布消息的客户端应用程序
  • Exchange:
    • 交换器,用来接收生产者发送的消息,并将这些消息路由给服务器中的队列。Exchange有四种类型:direct(默认)、fanout 、topic和headers,不同类型的Exchange转发消息的策略有所区别。
Spring整合:
  • 引入依赖:
    • <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
  • 编写配置类(消息序列化):
    • @Configuration public class MyRabbitConfig { @Bean public MessageConverter messageConverter(){ return new Jackson2JsonMessageConverter(); } }
  • 信息写入配置文件:
    •  
使用方法:
  • 如何创建exchange queue binding使用AmqpAdmin进行创建
创建exchange:
public void createExchange() { //amqpAdmin //Exchange // public DirectExchange(String name, boolean durable, boolean autoDelete, Map<String, Object> arguments) // 全参数构造器 DirectExchange directExchange = new DirectExchange("hello-java-exchange",true,false); amqpAdmin.declareExchange(directExchange); log.info("Exchange[{}]创建成功" ,"hello-java-exchange"); }
创建Queue:
public void createQueue(){ //public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) Queue queue = new Queue("hello-java-queue",true,false,false); amqpAdmin.declareQueue(queue); log.info("Exchange[{}]创建成功" ,"hello-java-queue"); }
进行绑定:
public void createBinding(){ //public Binding(String destination, DestinationType destinationType, String exchange, String routingKey, Map<String, Object> arguments) // 目的地 目的地类型 交换机 路由键 自定义参数 // 将exchange指定的交换机和destination目的地进行绑定,使用routkey作为路由键 Binding binding = new Binding("hello-java-queue",Binding.DestinationType.QUEUE, "hello-java-exchange","hello.java",null); amqpAdmin.declareBinding(binding); log.info("Exchange[{}]创建成功" ,"hello-java-binding"); }
发送信息:
public void sendMessageTest(){ // 发送消息 如果发送的消息是个对象 我们会使用序列化机制,将对象鞋出去 必须实现Serializable接口 OrderReturnReasonEntity orderReturnReasonEntity = new OrderReturnReasonEntity(); orderReturnReasonEntity.setId(1L); orderReturnReasonEntity.setCreateTime(new Date()); orderReturnReasonEntity.setName("hah"); String msg = "hello world"; // 发送的对象类型的消息 可以是一个JSON rabbitTemplate.convertAndSend("hello-java-exchange","hello.java",orderReturnReasonEntity); log.info("消息发送完成{}",orderReturnReasonEntity); }

43.幂等性处理

44.Feign远程调用丢失请求头问题

浏览器发送请求自动带了cookie,远程调用服务用feign调用,会创建一个新的请求,新的request里面什么都没有。相当于自己拿java代码发送一个请求,没有任何请求头。
解决请求丢失头的问题,可以增加Feign远程调用的请求拦截器

45.本地事物失效问题:

原因:同一个对象内事物方法互调默认失效,因为绕过了代理对象
事物是由代理对象控制的
解决:使用代理对象来调用事物方法
1)、引入aop-starter
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
引入了aspectj
2)、开启aspectj动态代理功能。动态代理启动类 main方法使用@EnableAspectJAutoProxy 注解。以后所有的动态代理都是aspectj创建的,即使没有接口也能代理。jdk默认的动态代理必须有接口。@EnableAspectJAutoProxy(exposeProxy = true) 设置exposeProxy = true 对外暴露代理对象
3)、用代理对象本类互调
AopContext.currentProxy();
OrderServiceImpl orderService = AopContext.currentProxy();
orderService.b(); //使用代理对象调取方法
orderService.c();

46.分布式事务:

为什么会有分布式事务:

分布式系统经常出现异常,及其宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失。

CAP定理与BASE理论

1、CAP定理
cap原则又称为cap定理,指的是在一个分布式系统中
  • 一致性(Consistency):
    • 在分布式系统中的所有数据备份,在同一时刻是否同样的值(等同于所有节点访问同一份最新的数据副本)
  • 可用性(Availability)
    • 在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
  • 区分容错性(Partition tolerance)
    • 大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)
      • 分区容错的意思是,区间通信可能失败。比如,一台服务器放在中国,另一台服务器放在美国,这就是两个区,他们之间可能无法通信。
cap原则指的是,这三个要素最多只能同时实现两个,不可能三者兼顾。
分布式系统中实现一致性的算法raft算法,paxos算法等。
raft算法演示

面临问题:

对于多数大型互联网应用的场景,主机众多、部署分散、而且现在的集群规模越来越大、所以节点故障、网络故障时常态,而且要保证服务可用性达到99.9999999%,及保证P和A,舍弃C

BASE理论:

是对CAP理论的延伸,思想是即使无法做到强一致性(CAP的一致性就是强一致性),但可以采用适当的采取弱一致性,即最终一致性。
BASE是指:
  • 基本可用(basically Available)
    • 基本可用是指分布式系统在出现故障的时候,允许损失部分可用性(例如响应时间、功能上的可用性),允许损失部分可用性。需要注意的是,基本可用绝不等价于系统不可用
      • 响应时间上的损失,正常情况下搜索引擎需要在0.5s之内返回给用户响应的查询结果,但是由于故障(比如系统部分机房发生断电或者断网故障),查询结果的响应时间增加到了1~2秒。
      • 功能上的损失,购物网站在购物高峰(如双十一)时,为了保护系统的稳定性,部分消费者可能会被引导到一个降级页面。
  • 软状态(Soft State)
    • 软状态指的是允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一本一份数据会有多个副本,允许不同副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。
  • 最终一致性(Eventual Consistency)
    • 最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态
      • 弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。

强一致性、弱一致性、最终一致性:

从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。如果能容忍后续的部分或者全部访问不到,则是弱一致性。如果经过一段时间后要求能访问到更新后的数据,则是最终一致性。

分布式事务几种方案:

1)、2PC模式
数据库支持的2PC【2 phase commit 二阶提价】,又叫做XA Transactions。MySQL从5.5版本开始支持,SQL Server2005开始支持,Oracle 7开始支持。其中XA是一种两阶段提交协议,该协议分为以下两个阶段:
第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反应时否可以提交。
第二阶段:事务协调器要求每个数据库提交数据
其中,如果有任何一个数据库否决此次提交,呢么所有数据库都会被要求回滚他们在此事务中的那部分信息。
 
  • XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低
  • XA性能不理想,特别是在交易下链路,往往并发量很高,XA无法满足高并发场景
  • XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换会导致主库与备库数据不一致
  • 许多nosql也没有支持XA。这让XA的应用场景变得非常狭隘
  • 也有3PC,引入了超时机制(无论协调者还是参与者,在向对方发送请求后,若长时间未收到回应则做出相应处理)
2)、柔性事务-TCC事务补偿型方案
刚性事务:遵循ACID原则,强一致性。
柔性事务:遵循BASE理论,最终一致性
与刚性事务不同,柔性事务允许一定时间内,不同节点的数据不一致,但要求最终一致。
一阶段 prepare 行为:调用 自定义 的prepare逻辑。
二阶段 commit 行为: 调用 自定义 的commit逻辑
二阶段 rollback 行为:调用 自定义 的rollback逻辑
所谓TCC模式。是指吧自定义的分支事务纳入到全局事务的管理中
3)、柔性事务-最大努力通知型方案
按规律进行通知,不保证数据一定能通知成功,但会提供可查询操作接口进行核对。这种方案主要用在与第三方系统通讯时,比如:调用微信或支付宝支付后的支付结果通知。这种方案也是结合MQ进行实现。比如,通过MQ发送http请求,设置最大通知次数。达到通知次数后即不再通知。
案例:银行通知、商户通知等(各大交易业务平台间的客户通知:多次通知、查询校对、对账文件),支付宝支付成功异步回调。
4)、柔性事务-可靠消息+最终一致性方案(异步确保型)
实现:业务处理服务在业务事务提交前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不是真正的发送。业务处理服务在业务事务提交之后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才会真正发送。

47.Seata

Seata控制分布式事务
1)、每一个微服务创建undo_log表(回滚日志表)
2)、安装事务协调器:seata-server  https://github.com/seata/seata/releases
3)、导入依赖 spring-cloud-starter-alibaba-seata seata-all 0.7.1
启动seata服务器
registry.conf 注册中心配置 修改注册中心配置 type = nacos
file.conf
4)、所有想要用到分布式事务的微服务,使用seata DataSourceProxy代理自己的数据源
5)、每个微服务都必须导入registry.conf file.conf
vgroup_mapping.gulimall-ware-fescar-service-group = "default"
6)、给分布式大事务的入口标注@GlobalTransactional
7)、给每一个远程的小事务用@Transactional

48.RabbitMQ-延迟队列(实现定时任务)

场景:

比如未付款订单,超过一定时间后,系统自动取消订单并释放占有物品

常用解决方案:

spring的schedule定时轮询数据库
缺点:消耗系统内存、增加了数据库的压力、存在较大的时间误差
解决:rabbitmq的消息TTL和死信Exchange结合

消息的TTL(Time To Live)

  • 消息的TTL就是消息的存活时间
  • RabbitMQ可以对队列和消息分别设置TTL
    • 对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信
    • 如果队列设置了,消息也设置了,那么会取小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果。

Dead Letter Exchanges(DLX)

  • 一个消息在满足如下条件下,会进死信路由,记住这里是路由而不是队列,一个路由可以对应很多队列。(什么是死信)
    • 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。(basic.reject/basic.nack)requeue = false
    • 上面的TTL到了,消息过期了
    • 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。
  • Dead Letter Exchanges其实是一种普通的exchange,和创建其他的exchange没有两样。只是在某一个设置Dead Letter Exchange的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange中去。
  • 我们既可以控制消息在一段时间后变成死信,又可以控制变成死信的消息被路由到某个指定的交换机,结合二者,其实就可以实现一个延时队列。

49.@RequestBody 只支持PostMapping

50.SpringBoot测试失败并报错: Unable to find a @SpringBootConfiguration, you need to use @ContextConfiguration

1.由于包名自动生成的缘故导致这两个包名不一致,引发以下报错
2.如果不想修改包名,那么需要在注解上加上@SpringBootTest(classes = xxx.class),来告诉springboot这是一个独立的测试类,注意xxx代表测试方法的类名。
但这里会产生额外的问题,因为此时springboot已经把该类当成一个独立的测试类了,这意味着这个测试类对应独立的IOC容器,所以此时我们无法在该测试类中注入springboot容器中的组件,案例如下,springboot项目的的路径是com.sobot.demo7(启动类就这这个包下),同理,测试类中com.sobot.demo7路径下测试类,可以正常装配userMapper组件。

51.java.lang.AssertionError: Response content

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
notion image
记录一次奇怪的问题排查健身计划2022

  • Twikoo
  • Giscus