前提:
当项目逐渐变得庞大起来,简单的 spring 框架可能就不够用了,所以就需要用到分布式架构,我们这里简单介绍一下 springcloud 以及 springcloud 需要依赖的一些组件
目录:
1、分布式简介
2、Eureka 注册中心
3、Ribbon 负载均衡
4、Nacos 注册中心
5、Feign
6、gateway 网关
7、docker
8、RabbitMQ
9、SpringAMQP
1、分布式简介
问题一:什么是微服务
首先微服务≠SpringCloud,微服务是分布式架构的一种。所谓分布式架构就是把服务做拆分。而拆分的过程中会产生各种各样的问题需要去解决,springcloud只是解决了服务拆分时的服务治理问题,至于服务拆分后更为复杂的问题,并未给出解决方案,所以一个完整的微服务技术,包含的不仅仅是springcloud。我们下面看看微服务技术除了springcloud还包含哪些?
首先:原本所有的功能都写在一起,随后的版本越来越多,功能耦合的更加强,将来想升级维护就会很困难,所以一些大型的互联网技术都要进行服务的拆分。微服务在拆分的过程中会根据业务的功能模块,把大项目拆分成多个独立的项目,每个独立的项目独立进行开发和部署,每个独立的项目被称之为服务,每个大型的项目,一般包括数百上千的服务,最终形成服务集群
当一个请求来了,假如通过服务A调用服务B,服务B又调用服务C,这样当服务量比较多的时候,服务间的一些配置(比如服务的 ip )靠人会很难记住,所以引入注册中心,有了注册中心,服务之间互相调用所需要的一些配置,直接去找注册中心就ok了,去注册中心去拉取对应的服务信息,每个服务都有自己的配置文件,如果那些服务需要去更改配置文件,难道需要我们人工去逐一更改么,这样就太麻烦了,所以就会引入配置中心,通过配置中心来统一管理整个集群中成千上百的配置管理,配置中心可以支持配置的热更新。当微服务运行起来后,用户就可以访问我们的微服务了,这个时候还需要一个服务网关,整个集群这么多服务,用户哪里知道要访问哪个,而且也不是随便什么人都可以访问服务,服务网关一方面是对用户身份进行校验,另一方面可以把用户的请求路由到具体的服务上,路由过程中可以做一些负载均衡的操作,此时服务接收到请求后,处理请求,该去访问数据库访问数据库(数据库一般采用集群的方式,由于用户量普遍大于数据库容量,所以也会采取缓存数据库(基于内存的数据库),为了应对高并发,还需要把缓存数据库做成分布式缓存(也是集群),服务先去请求缓存,缓存未命中再去请求数据库)(数据库充当的角色一般是做数据的写操作,和事务的一些操作,总之是对数据安全要求较高的一些数据存储),随后再把查询到的数据返回给用户就ok了。当我们的业务可能会涉及到一些海量数据的搜索,此时基于缓存也无法实现,届时就需要引入分布式搜索。还需要一些异步通信的消息队列组件,其实对于分布式的服务,业务往往会跨越多个服务,一个请求来了,服务A调服务B,服务B调服务C整个业务的链路会很长,此时的业务执行时长就是整个链路的执行时长之和,所以其实性能会有一定下降,所以通过消息队列,不是去调用B而是去通知B你去执行啥啥啥,此时就不需要服务A再去等服务B执行完毕,就可以提前结束了,减少了整个链路的等待时间(大大提高了吞吐能力)。当服务出现异常时,引入分布式服务日志来对其进行记录(统一的进行统计,存储分析)将来出现问题也比较好解决了,同时还引入系统监控链路追踪(实时监控整个链路每个服务结点内存CPU的占用等等情况,一旦出现任何问题,就可以快速定位到某一个方法)
此时我们的微服务集群就会非常庞大,靠人去部署时不现实的,需要引入自动化部署,此时通过 Jenkins 对项目进行自动化的编译,基于 docker 再去进行打包形成镜像,再去通过 k8s 或者 rancher 进行部署,此时微服务技术 + 后续的持续集成,这就是完整的微服务技术栈!!!!!
整体微服务技术栈大体分为五部分:
微服务治理:也是我们常说的 springcloud
分布式缓存:
异步通信:MQ等技术
分布式搜索技术:
持续集成的技术:
架构演变:
最早系统的架构一般采用的是单体架构(将业务的所有功能集中在一个项目中开发,打成一个包部署)优点:架构简单,不用去搞复杂的架构理念,打成一个包部署成本低,此架构更适合用于一些面向企业内部的一些简单项目,因为单体架构一个严重缺陷:耦合度高!大型互联网项目,功能非常多,当改动一个功能模块时,可能会导致其他功能模块的崩溃,所以代码不方便动,由此可知单体架构是不适合大型的互联网项目的,
大型业务一般会采取分布式架构,每个功能作为独立的项目去进行开发,称为一个服务,拆分功能模块后,耦合度降低了,但是随之也会引发一定的问题,比如订单模块需要依赖于商品信息模块,但是这明显是俩服务,咋互相调用?除非能够一个服务向另一个服务发送请求(远程调用,跨越机器,跨越服务)
为了解决这些问题,提出了很多解决方案,近几年最火的莫过于微服务技术,微服务就是一种经过良好架构设计的分布式架构方案,微服务架构特征
三、认识springcloud,springcloud可以被认为是国内外使用最广泛得微服务框架,spring cloud对以下组件做出了整合,无需复杂配置,更方便使用
四、服务拆分:单体架构按照功能模块进行拆分,变成多个服务就ok了,生产种功能模块可能会继续变多,又需要我们去拆
练习:
练习:
现在有两个服务,一个是获取用户信息的服务,一个是获取订单信息的服务,这两个服务是分开的,各自有各自的数据库,那么如何实现根据订单id查询订单详情的同时获取到用户信息呢,这就是远程调用
首先我们可以想到的是用户服务可不可以调用订单信息的数据库,显然不行,因为要减少重复开发并且各个服务之间的数据库是隔离的,无法互相查看
所以这里需要用户服务远程调用订单服务,这样的方法我们之前是没有学过的,所以我们看一下如何实现
其实就是订单模块模拟浏览器一样 发送 http 请求,请求用户信息对应的接口获取到用户信息,最后根据订单的信息一拼接就ok了,所以问题就变成了如何在 java 代码中发起 http 请求,能发请求就能远程调用了,此时需要 spring 提供的 RestTemplate 发送 http 请求
两个概念:服务的提供者与消费者
问题:
一个服务既可以是消费者也可以是提供者
2、Eureka 注册中心
硬编码的问题
不做硬编码,如何获取提供者地址呢?
通过负载均衡来区别调用哪个具体的服务,为了防止其他服务挂了,会有对应的心跳机制
动手搭建:
创建模块
创建对应的三个文件
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
server:
port: 10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
启动类:
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
启动后:
这个就是注册的实例:可以看到Eureka把自己也注册到了里面
服务注册:
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
重启即可!
可以发现服务已经被注册进来了
同一个服务怎么引入两遍
输入--- -Dserver.port=8082 即可
启动后:
服务发现:
此后通过发送请求
两个user服务器都有日志,也就说明负载均衡把请求分摊到两个服务器上了
3、Ribbon
负载均衡原理:
springcloud 中实现负载均衡是通过 Ribbon 实现的
首先通过访问
肯定是访问不到user的两个服务的,所以途中一定有相关的请求方式的处理,那么 Ribbon 就是充当这个角色,Ribbon 拦截请求后,去找到真是的 ip 端口,第一步需要先知道服务的名称,也就是去找 Eureka 去找服务
调整负载均衡的方案:全局和局部
针对某个微服务:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule# 负载均衡规则
Ribbon的一个策略
可以看到发这样一个请求的时间是 809 ms,首次请求的时候,Ribbon需要请求Eureka 获取服务列表,首次请求时间较长,之后此服务列表就会被缓存到内存中,所以之后的请求速度就会加快,因为直接从内存去查就 ok 了,这就是懒加载
ribbon:
eager-load:
enabled: true
clients: userservice
修改成饿加载后:随着 Tomcat 的初始化就开始获取注册列表,那么首次发请求的时候就会提高响应的速度,第二次的响应速度明显比第一个响应快的多
4、Nacos 注册中心
接下来我们介绍另外一个注册中心:Nacos 是阿里巴巴旗下产品,相比 Eureka 功能更加丰富
Nacos 下载:按照包,解压,通过控制台执行命令:
startup.cmd -m standalone
启动成功后,就可以通过 http://192.168.31.64:8848/nacos/index.html 进行访问了
转换为 Nacos 注册中心:
先找到父工程的 pom 文件,添加如下依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
再改动服务端:
cloud:
nacos:
server-addr: localhost:8848
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
重新启动可以看到三个服务已经被注册到了 nacos 上
3、nacos服务多级存储模型
之前通过上面的实例我们可以得知,一个服务可以对应多个实例(占用不同的端口即可),不过随着业务规模的越来越扩大,我们就会考虑更多的问题,我们把所有的实例都部署到一个机房,这就像把鸡蛋都放在一个篮子里,篮子翻了,鸡蛋也就都碎了,机房如果因为天灾人祸出了问题,那整个服务不就完蛋了,那么如何解决问题,我们会把一个服务的多个实例部署到多个机房(部署到各地)
这样避免风险的能力被称之为容灾能力,nacos 借助这样的分地区的理念,每一个地区的实例被称之为集群
在nacos里面一级是服务,二级是集群,三级是实例,那为何nacos要引入这样的层级关系呢?我原来直接用服务找实例不好么,为什么要多加一个地域集群的划分
那么假设我有杭州的如下的这样的一个机房,部署了 oder-service 和 user-service 的实例,上海的机房具有同样的配置
那么我的 oder-service 要访问我的 user-service 那么他有两种访问的方式,一种是在本地的局域网内访问,另一种是去外面的机房的访问,那不用说肯定选本地的,局域网内的访问跨越的距离短,访问的速度更快,延迟比较低,跨越集群的访问,比如杭州访问上海的集群,跨越几百公里,延迟是非常高的,所以在访问过程中,尽可能访问本地的集群,在本地集群不可用的时候,再去访问其他地区的集群,但是我们目前还没有配置过集群属性,我们回到nacos控制台来看
进入详情可以看见
也就是没有集群,下面我们来看一下如何去配置实例的集群属性
首先在yml配置文件中添加:
模拟一下跨集群的部署方式
编辑 userservice 中 application 的 yml 文件
重启这两个服务,那么他就是 HZ 的集群了
那么服务3如何跑到上海集群
此时三个服务都启动起来了,我们回到nacos控制台看一下
可以发现HZ集群和SH集群都配置完毕成功了
上面的集群我们实现完成了,那么我下面要实现的是:orderservice 远程调用 userservice 优先选择本地的集群,因此 orderservice 同样需要配置一个集群属性
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
下一步我们回到nacos的控制台
可以发现 orderservice 也已经位于 HZ 集群了,也就是说 orderservice 和 userservice 8081 8082 位于同一集群,我们期望的是,orderservice 在远程调用 userservice 时优先远程调用 8081 和 8082,下面我们通过浏览器访问 order 服务器看是不是我们想的那样
不同的 orderId 访问三次,返回我们查看 IDEA 的日志
通过日志可以看到,8081,8082,8083,都有对应的日志信息,说明他们都被访问了,明显看到并没有优先的选择本地集群,依旧采用的是轮询方式。我们知道选择服务的时候都是通过负载均衡来进行选择的,因为我们这里没有配置规则,所以默认就是轮询方式,所以要想实现优先访问本地服务的负载均衡规则,就需要去配置其规则,那么怎么修改呢
同样在 yml 文件去修改,在 order 服务的 yml 中加:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
重启 order 服务,再次通过浏览器访问五次 order 服务
可以看到 8083 的日志文件是空的,此时说明已经实现了优先访问本地集群的负载均衡规则了,
清空日志后我们再查看:
此时 8081 和 8082 所接受的请求 id 是不同上面的 5 次 请求
nacos 负载均衡的规则是:优先选择本地的集群,在本地集群多个实例中随机选择
此时如果停止 8081 8082 只剩下 8083,8083 在上海,8080在杭州,我们通过 nacos 控制台可以看到
userservice 的健康实例只有一个,再次访问
可以看到本地没有集群也可以得到相应,返回本地查看 IDEA 日志文件
可以看到请求被 8083 承担了
可以看到 order 服务的 日志文件中 报了一个警告,是提醒进行了一次跨界访问,提醒运维人员去查看
3、我们在日常企业中会遇到一些问题,设备的迭代,有性能好的机器,也有老弱病残的机器,所以我们会希望好的机器能够承受更多的用户请求,性能差一点的承担少一点的请求,我们目前看到的是,集群优先而后做随机,不管性能好坏,此时性能差的可能就会出问题,那么如何控制不同服务的请求量呢,通过修改服务实例的权重,可以控制访问频率,那么我们可以把性能好的集群,权重设置的大一点,性能差的机器,权重设的小一点
配置方法:找到 nacos 控制台,找到编辑,我们看到 user 服务杭州集群的两个实例的权重都是 1,权重一样,就是随机访问了
我们假设8081是老机器,8082 是新机器,我们给 8081 的权重调低
这样 8081 和 8082 的权重比就是 1 : 10 所以理论上来讲,8081 和 8082 被访问到的次数比也应该是 1 : 10,我们实际去跑一下
一个页面我们刷新 20 次,看看 IDEA 的日志
8081:
8082:
明显看到8081只被访问了一次,剩下的都被 8082 承担下来了
那么我们如果修改 8081 的权重为 0 会发生什么
可以看到请求都在 8082,也就是说权重调成 0 后,8081 压根就不会接受到请求了,这有啥作用,在我们以前我们想要对其做版本的升级,我们需要重启服务器,但是重启服务器用户都还访问着了,直接重启有点不好,所以我们版本升级需要等到月黑风高的时候用户都下线了,把服务器停机,偷偷的去做升级,但是现在有了权重,我有多个服务器,我先把 8081 的权重调成 0,然后渐渐的 8081 不承担用户请求了,我对它进行停机,用户并不会有感知,8081 停机后做版本的升级,然后做重启,先从小权重设置,这个时候放出少数用户做个测试,看看有没有问题,那么依次升级,用户是无感知的,可以做到平滑升级
4、nacos 环境隔离(namespace)
nacos 首先是一个注册中心,那么 nacos 其实还是一个数据中心,所以在 nacos 里面,做数据和服务的管理,他会有一个隔离的概念,有这么几个东西
这里的隔离是为了不同环境的服务是相互隔离开的,不同环境的数据也会隔离开。nacos 已经把服务划分成集群、实例,为啥还要划分隔离。服务、实例划分是基于业务去进行划分(地域),那么一些生产环境(开发环境、生产环境)namespace 就是来做这个事情的,至于 group ,我们可以把业务相关度比较高的服务放到一个组(订单和支付服务,相关度比较高可以放到一起)。这个模型是不强制的,可以设计成这样,也可以不设计成这样,下面演示一样,回到 nacos 控制台
这里我们没去配置,是没有分组的
我们点开命名空间,可以看到默认的命名空间叫做 public
回到服务列表,可以看到这俩服务是 public 的
没设置命名空间的情况下,都是为 public,那下面我们新建一个命名空间
此时回到服务列表,此时已经出现了两个命名空间
点击 dev 可以发现是没有任何服务的,是因为没有配置,我们所有的服务都是在 public 下配置的,那我怎么去修改服务的命名空间,需要到代码区域
我们需要复制下来刚刚命名空间的 id
此后重启 order 服务器,可以发现只有 order 是在 dev 命名空间下的,其他三个服务我们没有改
此时 order 服务爆红,说明此时的 order 服务已经挂掉了
可以发现此时的 orderservice 已经是 dev 命名空间下的了,此时 order 服务和 user 服务已经是两个空间下人了,已经没有机会了,不信我们访问试一下
明显看到报错了,看下 IDEA 中的日志
也就是说此时的 user 服务和 order 服务已经阴阳两隔,没有关系了,所以这里无法访问,这就是环境隔离
5、Nacos 注册中心和 Eureka 的对比
服务消费者会有对应的 服务列表缓存 首次拉取服务后,会缓存到对应的服务列表中,方便之后消费,为了防止服务信息的变更,所以服务列表每隔 30s 会向注册中心拉取服务信息,消费者拿到服务信息以后,再去负载均衡,再去发起调用即可,但是在 nacos 中还是和 Eureka 中有些许区别,目前为止两者是一样的,差别是服务提供者的健康检测,那么 nacos 会把服务的提供者,划分为临时实例和非临时实例,我们查看一下 nacos 控制台
可以看到所有实例默认情况下都是临时实例,临时实例 和 非临时在 nacos 中健康检测是不一样的,因为是临时的,所以我们将来可能随时把服务停掉,所以临时实例在 nacos 健康检测中使用的心跳机制(和 Eureka 一样),不跳了 直接踢掉,非临时就不会要求去找心跳检测,此时是 nacos 主动发送请求去询问:“你还活着吗”,即使非临时不在存活,nacos 也会等待其复活,也不会从注册中心中剔除,这就是差别之一
差别二就是:这个消费者是每隔 30s 拉取一次,如果在 30s 有提供者挂了,消费者不知道,再去消费就会出问题,所以 Eureka 做服务拉取,服务的更新效率比较差,更新的不够及时,nacos 做的是消息推送,也就是说 nacos 做的是 push + pull Eureka 只是做 pull,所以一旦有提供者挂了,就会立刻去给消费者 push 主动告诉消费者服务变更,这样时效性更好
下面通过代码去测试一下,临时实例和非临时实例的差别:
如何设置临时和非临时实例
通过:
ephemeral: false # 非临时实例
此时我们把 order 服务关掉
此时控制台中就没有此服务了,此时再次启动 order 服务(此次就是非临时的实例了)
这时候,再把 order 服务停掉,一瞬间就会爆红,而且服务不会被剔除,等着你重启(除非你手动删除)
这时候我们再启动起来,就是健康状态了~
主动检测这边服务器压力比较大,所以更加推荐临时实例
Nacos 配置管理
目前我们了解到了的微服务架构
随着我们微服务越来越多,生产环境中,可能会达到成千上百的服务器,这个时候如果配置文件需要做一些修改,而这个配置文件和我们数十个微服务都有关系,那这个时候需要逐个微服务去调整这个配置,很麻烦。调整完配置关联的服务都需要重启,生产环境中,服务重启的影响还是很大的,所以说我们的需求是我们希望配置文件可以统一管理,同一改动并且不用重启,就可以更新这叫配置的热更新。我们需要配置管理的服务(他会记录微服务中一些核心的配置),服务启动起来会先去读取配置管理服务中的服务再去和本地的配置结合作为完成配置去使用,将来的核心配置发生修改,找到配置管理的服务,把需要改变的配置改一改就ok了,并且会主动通知各个服务我的配置发生了修改,配置变了,赶紧去修改,实现配置的热更新。我们借助 nacos 就能实现
演示一下,如何通过 nacos 实现配置的管理,我们回到 nacos 控制台
已经把配置文件发布到 nacos 上了,那么如何去拉取配置呢
没有 nacos 的时候如何获取配置文件:
我们现在多了 nacos ,多了一个本地 yml 和 nacos 的 yml 的合并过程:
读取 nacos 的配置文件,需要知道 nacos 的地址,然而 nacos 的地址又在 application.yml 文件中,所以需要提前知道 nacos 地址,所以借助 bootstrap.yml 文件来获取 nacos 地址,此 yml 文件比 application.yml 文件的优先级高很多,所以项目启动后先读取 bootstrap.yml
所以 nacos 的配置信息,都需要放入到 bootstrap.yml
找到 userservice 的 pom 文件添加 nacos 配置管理依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
添加 bootstrap.yaml 文件
spring:
application:
name: userservice
profile:
active: dev #环境
cloud:
nacos:
server-addr: localhost:8848 # nacos 地址
config:
file-extension: yaml #文件后缀名
删除 application 中重复的配置内容
测试是否读取到了 nacos 中的配置文件内容,通过 value 注解实现
可以看到 userservice 成功从 nacos 中拉取到了配置
2、nacos 配置的热更新
我们现在更新 nacos 里面的配置文件
再次访问 user 服务
发现没有更新还是刚刚的配置,我们希望的是,刷新后立马就要发生变化,所以需要我们手动配置一下
这是重启后的结果,但是我们再次修改配置文件:
刷新后直接就 ok 了
第二种方法:
也可以实现,所以这里更加推荐第二种注册方法
3、微服务的配置共享,有一个配置属性,在开发生产测试等环境下的值是一样,像这样的配置,每个配置文件都去写一份,有点浪费,如果要改动,每个配置文件都要改,所以希望在一个地方改,其他也就 ok 了,所以这就是微服务多环境共享
回到控制台,加一个配置
测试:8081位于开发环境,8082位于测试环境
可以发现 8082 只能读取到共享属性值
我们在看本地如果有一个叫做 name 的属性值:这里可以成功读取到
如果本地的 name 和共享环境冲突:优先读取共享环境中的 name
如果开发环境中也有重复的 name
4、nacos 集群搭建:
前面是 nacos 的基本用法,我们的 nacos 一直采用的是单点的方式,我们自己测试的时候还是可以的,企业中是不可以的,企业中 nacos 一点要做一个集群
5、Feign
Http 客户端 Feign
RestTemplate 方式调用存在的问题
这里请求的方式是通过 url 来进行访问的,resttemplate 代替我们去方式请求,再把结果转换成对应的类型,虽然这里的代码已经优化了,但是还是存在一些问题
1、代码可读性差,编程体验不统一
2、参数复杂 URL 难以维护,当 URL 的参数增多时,维护的难度很大
声明式就是在配置文件中声明好了,其他的事情都不需要你管了,都交给 Feign 来处理,就和 Spring 的声明式是一样的。咱们现在不是要发起 http 请求嘛,发起来麻烦,那么把发请求的信息声明出来就 ok 了,剩下的事情由 Feign 来做
具体做法:
第一步找到 orderservice 的依赖文件,在依赖文件中加入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步:开启自动装配的功能,找到启动类
第三步:编写客户端做接口声明
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
通过访问可以发现,feign 远程调用成功
可以发现,三个 user 服务都有收到请求,那么可以证明,这里 feign 里面自动做了负载均衡,因为 feign 中封装好了 ribbon
2、feign 的自定义配置
修改配置有两种方式:通过修改配置文件
可以看到这里的 feign 的信息是非常全的,这是通过 yaml 文件修改的
feign:
client:
config:
default:
loggerlevel: FULL
通过 java 代码修改:
我们看到 basic 模式只有请求头和相应行
3、feign 性能优化
推荐使用 连接池的 方式,减少建立连接所消耗的时间
第一种优化方式,替换成连接池
第一步:现在 order 服务中的 pom 中添加配置文件
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
第二步:在 order 服务中的 yaml 文件中添加配置
feign:
httpclient:
enabled: true #支持 httpclient的开关
max-connections: 200 #最大连接
max-connections-per-route: 50 #平均路径的最大连接数
优化到这里就ok了,但是实际业务中还是会根据业务的压测等等的结果来判定 yaml 文件的后两个参数的数值是否合适
3、feign 实践
可以发现这俩个方法几乎是一致的,order 服务之所以能够访问到 user 服务,就是通过 userclient 实现的,基于 userclient 发送了一个 http 请求,如果方法不一致是无法成功发送请求的,所以这俩方法一样不是巧合,是必须一样,那我们是不是可以做一个继承
方法一:定义一个 userapi 统一接口,所以直接 userclient 和 user 服务中的内容分别继承和实现这个接口就行了,不需要重复写了。优点:实现了解耦合,接口信息的改变,直接在 userapi 中做修改即可,但是对 springmvc 是无法实现的(mvc 有部分注解无法在实现的接口中使用,所以实现接口后还需要,再次重写方法)
方法二:可能我们不需要这么多的方法,但是都引入进来了,也不是太好
实现方式二:
后面把 order 服务里面的 copy 过来的内容都删除掉,然后重新导入 feign-api中包中内容,最后重启项目就 ok 了
可以发现访问没有任何问题
6、GateWay 网关
gateway 网关:
1、为什么需要有网关,当微服务内部有相互调用关系的时候,可以通过 feign 来实现,但是如果有外部用户需要来访问,让他直接发请求到微服务就 ok 了,微服务摆在这里允许任何人来发起请求访问,是不是有点不太安全,所以需要对身份进行验证,网关就是做这件事情的,验证通过就可以放行到微服务中,网关能处理用户的请求吗,那肯定不行,所以需要网关把请求发送到不同的微服务上,这时候网关充当寻找服务路由的功能,并且如果存在多个同样的服务,还需要网关去做负载均衡的操作。同时网关还可以做请求限流的功能
实现方式:
2、网关搭建:网关是一个独立的服务
创建网关的步骤:网关本身也是个微服务,所以也需要把网关注册到 nacos 中,做服务注册或者服务拉取
<!--网关依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.1.4</version>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
整个网关的处理流程
再次访问通过 10010 端口就可以访问到我们的 user 和 order 服务了
2、路由断言:
路由断言是设置的规则,那么路由断言工厂又是什么,我们在 yaml 文件中的配置的路由断言仅仅只是一个字符串,那个字符串是个条件,如何去把这个字符串转变成路由请求,就是交给断言工厂来做的,所以作用是用来读取用户定义的断言规则,把他解析出来,并作出判断
尝试一下 after 和 before 工厂
具体内容:
before:
after:
尝试通过过滤器给请求添加请求头,找到 gateway 的 yml 文件,添加配置
重启服务,通过网关访问 user 服务,可以发现发现此请求头
当前的过滤器是对 user 服务生效的,如果想要全局生效,可以这样去实现:
通过这样的配置就可以看到,order 和 user 服务都实现了通过过滤器添加请求头
3、全局过滤器
之前都是通过配置的方式来进行修改的,通过这个方法就可以通过代码的方式来实现过滤器
实现网关的拦截规则后,可以发现如果请求不带参数,或者带了错误的参数都无法访问,只能是带有 authorization=admin 的参数才可以访问服务
4、过滤器执行顺序
刚刚我们已经了解了三个过滤器,我们看一下三个过滤器的执行顺序是怎样的
确定路由之后才能确定过滤器,不同路由有不同的过滤器
由于存在过滤器适配器,所以三个不同的路由器可以被放到同一集合中
那如何进行排序:
5、网关的 cors 跨域配置
跨域问题处理,微服务中所有的请求都需要先经过网关再进行路由,也就是请求不需要在每个微服务中处理,但是网关和我们的实现不同,不是基于 servlet 实现的,网关中如何处理?
解决方法:CORS,问一下浏览器允不允许跨域,至于网关中如何实现,网关底层已经给我封装好了,我们做一个简单的配置即可
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
不配置的时候,不能通过 ajax 进行跨域访问
配置后
7、docker
前面的学习,我们已经基本掌握了微服务技术,我们发现微服务部署是个让人头疼问题,部署起来很麻烦,所以今天通过 docker 解决部署的问题
1、什么是 docker
一个大型的项目的组件会非常多,尤其是微服务项目,各种各样的微服务还会依赖各种各样的应用,比如前端部分肯定会依赖于 node,js,而我们的服务端还需要数据库 mysql,redis,mq 等等,将来这些应用都会部署到服务器上,而大多数的服务器都会采用 Linux 操作系统,应用在安装到操作系统上的时候都会有自己需要的依赖和函数库,每一个不同的应用,他们所需要的依赖和函数库可能会有差异,这样的复杂的函数库,很容易出现兼容性的问题,你废了好半天力气,解决了现在的问题,那么后续可能还会有开发环境,生产环境,测试环境等等,再等着你,这些环境的 Linux 操作系统可能还会不同,这些问题会导致我们部署的效率非常低
那么 docker 是如何解决这样的问题呢?
我们知道每个应用有自己对应的函数库,那为什么不将这些函数和依赖都一起打包呢?所以 docker 就干了这件事情
为了知道为什么 docker 可以跨系统运行(Linux 系统有很多种),我们先需要知道 os 的结构
针对如上的问题,docker 又干了怎样的事情内,docker 的解决方案,就是将用户程序与所需要调用的系统函数一起打包,比如我要打包 mysql,mysql 需要一些依赖,可能还需要 Ubuntu 的函数库,那么 docker 就把 mysql 的依赖和 Ubuntu 的函数库都包成一个整体,这样再把这个包放到任何一个Linux操作系统上只要内核是 Linux,直接调用打包好的函数库,而这个函数库直接调用 os 的内核,内核直接去调用硬件,不需要管系统应用是什么,因为包内本身就有该应用,所以 docker 所打的包可以部署到任何一个 Linux 操作系统上
2、docker 和虚拟机的区别:
docker 我们上面知道可以打包部署,虚拟机也可以达到类似的效果,那么他们有何区别?
3、docker 的架构
注意 docker 为了防止容器往镜像中乱写,只允许基于镜像去创建容器,容器可以从里面去读数据,那将来 mysql 容器从哪去读数据呢,容器从镜像中拷贝一份到自己的独立文件系统中,写数据就直接写到自己的容器中,不会对镜像或者其他的容器造成影响,后一些日志的配置都写到自己的容器中
那镜像如何去共享呢?
需要借助 dockerhub 和 GitHub 其实是类似的
3、docker 的安装
Linux 系统的最低环境要求,centos 7 内核 3.10 版本
①、首先需要大家虚拟机联网,安装yum工具
yum install -y yum-utils \
device-mapper-persistent-data \
lvm2 --skip-broken
②、然后更新本地镜像源:
# 设置docker镜像源
yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
yum makecache fast
③、然后输入命令:
yum install -y docker-ce
④、启动 docker
Docker应用需要用到各种端口,逐一去修改防火墙设置。非常麻烦,因此建议大家直接关闭防火墙!
启动docker前,一定要关闭防火墙后!!
启动docker前,一定要关闭防火墙后!!
启动docker前,一定要关闭防火墙后!!
# 关闭
systemctl stop firewalld
# 禁止开机启动防火墙
systemctl disable firewalld
⑤、通过命令启动docker:
systemctl start docker # 启动docker服务
systemctl stop docker # 停止docker服务
systemctl restart docker # 重启docker服务
⑥、然后输入命令,可以查看docker版本:
docker -v
⑦、配置镜像加速:
docker官方镜像仓库网速较差,我们需要设置国内镜像服务:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://8u9l65qn.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
2、docker 基本命令
①、docker 操作镜像:
练习:尝试从 dockerHub 上拉取 nginx 的镜像
可以看到 nginx 的镜像被拉取成功,那么如何查看本地已经拉取的镜像:
命令不需要记住,通过查看帮助文档即可解决
3、docker 的容器命令
练习:创建运行一个 nginx 容器
输入 run 命令后,就会生成一个字符串,这个字符串是容器的唯一 id,全局唯一
通过命令 docker ps 就可以查看容器的状态:
然后通过你的虚拟机地址 + 80 端口号就可以访问到 nginx 的欢迎页面:
这样就证明,nginx 容器创建成功了,再去通过 docker logs 命令就可以查看到对应的日志:
那我们继续是否可以进入到 nginx 容器中,在其 html 页面里面增加一条”欢迎您“语句
输入命令后,发现进入了容器,通过 ls 命令可以查看到内部的一些信息,可以看到容器内是有自己一套文件系统的,只不过是阉割版的,只有 nginx 运行所需要的文件
通过如下命令修改 index.html 文件的内容
sed -i 's#Welcome to nginx#欢迎您#g' index.html
sed -i 's#<head>#<head><meta charset="utf-8">#g' index.html
删除容器命令:
4、数据卷命令:
通过上面的学习,可以了解到,容器中的数据和文件都是和容器耦合在一起的,因此给我们带来很多问题,比如我们需要去修改数据,我们不得不进入到 docker 容器内部,并且内部还没有高级的编辑工具,修改起来非常麻烦,数据还是不复用的,所有修改的数据都是对外不可见的,你在容器一通改,别人看不到,你也记不住,对于别的容器你做的修改也记不住,升级维护困难,每做一次升级,可能会导致容器中的数据全被删了,所以需要想办法解决数据和容器耦合的问题,这里使用的是数据卷
数据共享问题,某一个容器的配置的修改挂在 conf 文件内,那么另外一个容器如果想要同样的配置,那么也挂在 conf 数据卷下面即可,这样数据共享问题就迎刃而解了!
那么如果容器被删了呢?没事,因为容器删了,数据卷还是在的,通过数据卷就可以恢复
查看有哪些数据卷
数据卷的基本练习
可以看到通过数据卷的方式,不需要进入数据卷,就可以修改文件了,而且还有高级的修改方式:vim,通过 vim 修改后,我们看下是否成功修改
这里我们不创数据卷,直接将宿主机目录挂载到容器
1、将 mysql.tar 拖到 tmp 目录中
2、把 mysql.tar 加载为镜像
3、创建两个目录,将 hmy.conf 文件导入 conf 中
4、运行命令:
docker run \
--name mysql \
-e MYSQL_ROOT_PASSWORD=1234 \
-p 3306:3306 \
-v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /tmp/mysql/date:/var/lib/mysql \
-d \
mysql:5.7.25
5、测试是否连接成功,尝试通过 navicat 建立连接
测试远程连接之前,需要配置 mysql 的远程连接权限:添加链接描述
navicat 显示连接成功:
dockerfile 自定义镜像:
镜像结构:
以 mysql 镜像为例
dockerfile 基本语法:
刚刚的构建的配置需要 9 步
更新后:只有三步即可
构建指令:
启动指令:
查看运行的 docker 容器:
DockerCompose:当微服务的数量比较大的时候,我们不可能手动去一个个构建,所以需要一种集群部署的手段
dockercompose 就是把 docker run 各种指令放在了文件里,docker compose 的安装,需要先将 docker - compose 拖入虚拟机中,然后依次执行如下命令即可
chmod +x /usr/local/bin/docker-compose
echo "199.232.68.133 raw.githubusercontent.com" >> /etc/hosts
curl -L https://raw.githubusercontent.com/docker/compose/1.29.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
部署微服务集群:
提前准备好相同名称的文件夹:cloud_demo
要部署的服务是网关,user 服务,order 服务,文件夹中只有 dockerfile
所需要拷贝的 jar 包,文件夹里面是没有的,需要我们手动打包,然后放到文件夹里面
docker-compose 文件,里面配置好 nacos、mysql 的基本配置,将来 docker-compose 一旦执行就会去启动 MySQL、nacos,然后基于三个服务的 dockerfile 实现镜像构建,以及自动部署,这样一次性就可以完成所有服务的部署
现在每个服务中缺的就是 jar 包,首先我们需要先修改一些 nacos mysql的地址,因为以前项目都是在 localhost ,现在是集群部署,mysql 和 nacos 不一定在同一台机器,那将来怎样知道对方的地址,所以这里地址不能写死,在 docker-compose服务之间都可以通过 服务名 互相访问,比如 user 服务访问 ,mysql 服务,直接写服务名即可(修改操作就是把 gateway 服务,order 服务,user 服务 yaml 文件中所有的 localhost 都改成对应的服务名称即可)
下一步是用 maven 给每个微服务都打一个包(app.jar,因为刚刚 dockerfile 中写的拷贝名称是 app.jar),给每一个服务都加上这样的 build 标签
打包后可以发现,所有 target 底下都挂了一个 app.jar 的 jar 包,依次把 jar 包拷贝到 cloud_demo(刚刚截图的文件夹),现在 jar 包都拷贝好了,只要执行 docker - compose 命令就可以完成部署
先压缩,传上去再解压
5、镜像的种类有很多种,需要有一个统一的平台去管理:Docker 镜像管理(比如 DockerHub 这是公有的镜像仓库)
我们现在尝试搭建一个私有的镜像仓库
搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现。
官网地址:https://hub.docker.com/_/registry
Docker官方的Docker Registry是一个基础版本的Docker镜像仓库,具备仓库管理的完整功能,但是没有图形化界面。由于不太美观,所以我们这里就不实现这种方式了。
我们实现带图形化界面的:
使用DockerCompose部署带有图象界面的DockerRegistry,命令如下:
配置Docker信任地址:
我们的私服采用的是http协议,默认不被Docker信任,所以需要做一个配置:
# 打开要修改的文件
vi /etc/docker/daemon.json
# 添加内容:
"insecure-registries":["http://192.168.150.101:8080"]
# 重加载
systemctl daemon-reload
# 重启docker
systemctl restart docker
查看成功启动:
目前是没有镜像,我们需要手动上传镜像
使用 push 命令上传,如图上传成功
如何拉取镜像:先删除本地镜像
可以
可以看到镜像被拉取下来了
8、RabbitMQ
服务异步通信:实用篇 - rabbitmq
同步通信的优缺点
同步问题,比如第一个订单服务需要 150 ms 那么需要等到第一个服务执行完毕,再执行第二个服务,整个流程下来耗时非常多,也就是说 1s 内也就能执行几个服务,所以数以百万计的请求过来了,这系统扛得住么,所以这也是同步通信的一个问题,其实大部分时间都花费在等待的时间中了(大量的资源浪费)
2、异步调用
异步通信中,只是通过支付服务去告知 broker(消息管理系统),只负责告知,告知后此服务就完事了,无需再等待调用其他服务,调用的任务交给 broker 订单服务、仓储服务、短信服务都需要订阅一下 broker 即可,由 broker 去负责发消息,那个服务处理完请求,就返回给 broker,再由 broker 返回给支付服务,这样就省去了支付服务在此过程中,等待的耗时了
大多数情况下使用的都是同步通信,不需要有太高的并发,而是需要有较好的时效性,查询到信息在下面的信息中要用到
3、mq 常见技术介绍
Kafka 吞吐量大,但是不稳定,适合于海量数据并且不要求很高的安全性(Kafka 容易丢失数据)
RQ,AQ 稳定性高,吞吐量适中,适合安全性要求较高的业务
RQ的安装
1、docker pull rabbitmq:3-management
2、安装 MQ
docker run \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
3、通过 15672 端口就可以成功访问到 rabbitmq 了
4、账号密码是我们上面配置好的
5、登录后,进入到 rabbitmq 的管理平台
①、overview 是总览,主要就是 mq 结点的详细信息,目前是单节点运行
②、connection 是连接,消息的消费者和生产者都需要和 mq 建立连接
③、channels 是通道,建立连接后,要建立通道,消费者和生产者要基于通道来发送消息,channel 是 mq 做各种操作的通道,每一个连上来的人都要建立一个或者多个通道
④、Exchanges 是交换机
⑤、queue 是队列,存储信息
⑥、admin 是管理
3、消息模型介绍
首先消息的生产者和 rq 建立连接,然后创建队列,将消息发送到队列
消费者也同样去连接 rq 然后创建频道、队列(以防消息消费者先启动),订阅消息(采用异步处理的方式,如果有消息在回调函数),可以看到 mq 中有一个 queue
消费者消费后,就没有消息了
上面是 mq 的官方 api 实现的,但是我们发现官方提供的 api 很麻烦,因此我们需要借助 springamqp,大大简化我们使用消息发送接收的 api
9、SpringAMQP
SpringAMQP 基本介绍
便捷的消息接受,便捷的消息发送,以及自动化队列的声明
在消息发送者的测试类中加入一个测试类:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSendMessageSimpleQueue(){
String queueName = "simple.queue";
String message = "hello,spring amqp!";
rabbitTemplate.convertAndSend(queueName, message);
}
}
在 mq 中可以看到如下信息:可以看到啊利用一个简单的 api 就成功发送了一条 message 到队列中
前面是发送消息,后面我们要做的就是接受消息
这里需要关心的就是往哪个队列去获取消息,干什么事情(封装到方法中)
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String message){
System.out.println("消费者接受到simple.queue的消息" + message);
}
}
可以看到消息的接受者这里成功的接受到了消息,我们从浏览器中 mq 的客户端可以看出,消息确实已经被消费掉了
4、work queue 工作队列
当一个消费者的时候,当这个 publisher 传输速度过快,消费者无法及时得消耗掉,那么就会有大量得 message 堆在 queue 中,那么当队列中要满了,新的 message 就放不进去了,肯定就会出问题,这时候采取工作队列的模型,不再是挂一个队列,而是挂两个队列,这样消费的速度上来后,就不会在 queue 中堆积太多的 message,作用就是提高消费消息的速度
消息的消费者:
package cn.itcast.mq.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.time.LocalTime;
@Component
public class SpringRabbitListener {
// @RabbitListener(queues = "simple.queue")
// public void listenSimpleQueue(String message){
// System.out.println("消费者接受到simple.queue的消息" + message);
// }
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue1(String message) throws InterruptedException {
System.out.println("消费者1接受到simple.queue的消息" + "[" +message + "]" + LocalTime.now());
Thread.sleep(20);
}
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue2(String message) throws InterruptedException {
System.out.println("消费者2................接受到simple.queue的消息" + "[" +message + "]" + LocalTime.now());
Thread.sleep(200);
}
}
消息的发送者:循环发送 50 条消息
@Test
public void testSendMessageWorkQueue() throws InterruptedException {
String queueName = "simple.queue";
String message = "hello,message__!";
for (int i = 1; i <= 50; i++) {
rabbitTemplate.convertAndSend(queueName, message + i);
Thread.sleep(20);
}
}
可以发现运行结果,是消费者 1 消费了奇数的 msg,我们可以发现其实消费者 1 和消费者 2 的消费能力是有差距的,明显消费者 1 消费能力更高,但是可以看到,能者没有多劳,这是因为 mq 内部的消息预取机制,消费者 1 2 的 channel 会在队列中进行预取,这里预取一人一个的拿,最后就是平均分配这 50 条消息,但是消费者 2 的消费能力比较差,消费者 1 的消费能力强,所以 1 很快就完事了,但是 2 需要比较久,所以就会导致总时长会超出预取,所以希望能力差的就少拿一点不就完了
修改控制预取消息上线的 yml 配置
修改后可以发现,消费能力更强的消费者 1 消费了更多的消息:
5、发布订阅模型介绍:
上面的消息消费,我们知道了一个消息只会被一个消费者消费,但是在微服务中,我们想要多个服务都可以消费同一条消息,这就需要用到发布订阅模型
在发布订阅模型,不关心消费者这边怎能绑定,以前咋样还咋样,而我们现在关心的是消息的发送者如何将消息发送出去,在消息发送者这里引入了一个 exchange 交换机,publisher 生产的消息都放到交换机中,之后如何去分配消息都是依靠交换机来决定的,如果交换机将消息转发到多个队列中,这不就做到了一个消息被多个消费者消费(由交换机的类型决定)
6、fanout exchange
将两个队列绑定到交换机上,然后重启项目
@Configuration
public class FanoutConfig {
//itcast.fanout
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
//fanout.queue1
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
//绑定队列 1 到交换机
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
//fanout.queue2
@Bean
public Queue fanoutQueue2(){
return new Queue("fanout.queue2");
}
//绑定队列 2 到交换机
@Bean
public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
交换机:
队列:
修改消费者:
创建测试类,测试发送消息,可以发现一次的消息发送,两个队列都收到了消息
@Test
public void testSendFanoutExchange(){
// 交换机名称
String exchangeName = "itcast.fanout";
// 消息
String message = "hello, every one";
//发送消息
rabbitTemplate.convertAndSend(exchangeName, "", message);
}
7、directExchange
两个队列可以绑定多个 key,可以绑定相同的 key,这样两边都可以获取到消息,此队列相对更灵活一些,但是需要指定 key 会有点麻烦
声明一个交换机,一个队列,绑定好 key:
我们去浏览器看:可以看到交换机和队列里面都有新增了 direct
编写测试类,发送消息:绑定 key 为 blue 指定发送给绑定了 blue 的队列
@Test
public void testSendDirectExchange(){
// 交换机名称
String exchangeName = "itcast.direct";
// 消息
String message = "hello, blue";
//发送消息
rabbitTemplate.convertAndSend(exchangeName, "blue", message);
}
可以看到是 q1 接受到了消息
再改成 red 就会两个队列都会收到
8、topicExchange
同样的步骤先声明:
测试消息发送:里面包含 china 和 news
@Test
public void testSendTopicExchange(){
// 交换机名称
String exchangeName = "itcast.topic";
// 消息
String message = "hello word";
//发送消息
rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}
可以看到两个队列都可以接受到 msg
改成 weather 就不包含 news 了,所以 q2 收不到 msg
9、消息转换器:
先声明一个队列:
可以看到队列中包含了新声明的 object queue
测试发送对象类型:
@Test
public void testSendObjectExchange(){
Map<String, Object> msg = new HashMap<>();
msg.put("name", "王心怡");
msg.put("age", 28);
rabbitTemplate.convertAndSend("object.queue", msg);
}
可以发现发送成功了!!
回到浏览器看一下:
可以发现 payload 是一堆英文,我刚写的不是王心怡么?看上面上面的类型是 java 序列化,发送 msg 原生的 api 只能支持字节,spring 支持我们发送对象,说明会将对象做序列化,用的是 jdk 的序列化,首先给我们的感觉就是很长(占用大空间,传输慢),不安全(有注入的问题)
父工程+注释:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
消息发布者这里声明消息类型:
@Bean
public MessageConverter messageConverter(){
return new Jackson2JsonMessageConverter();
}
再发一次消息
就可以看到心怡了~
这边消息的接收者的接受方式改变:
@RabbitListener(queues = "object.queue")
public void listenObjectQueue(Map<String,Object> msg){
System.out.println("接收到object.queue的消息" + msg);
}
这里也是按照 json 的格式接受了!