学习SpringCloud-基础入门
目录
1.认识微服务
1.0.学习目标
1.1.单体架构
1.2.分布式架构
1.3.微服务
1.4.SpringCloud
1.5.总结
2.服务拆分和远程调用
2.1.服务拆分原则
2.2.服务拆分示例
2.3.实现远程调用案例
2.3.1.案例需求:
2.3.2.注册RestTemplate
2.3.3.实现远程调用
2.4.提供者与消费者
3.Eureka注册中心
3.1.Eureka的结构和作用
3.2.搭建eureka-server
3.2.1.创建eureka-server服务
3.2.2.引入eureka依赖
3.2.3.编写启动类
3.2.4.编写配置文件
3.2.5.启动服务
3.3.服务注册
3.4.服务发现
4.Ribbon负载均衡
4.1.负载均衡原理
4.2.源码跟踪
4.3.负载均衡策略
4.3.1.负载均衡策略
4.3.2.自定义负载均衡策略
4.4.饥饿加载
5.Nacos注册中心
5.1.认识和安装Nacos
5.2.服务注册到nacos
5.3.服务分级存储模型
5.3.1.给user-service配置集群
5.3.2.同集群优先的负载均衡
5.4.权重配置
5.5.环境隔离
5.5.1.创建namespace
5.5.2.给微服务配置namespace
编辑5.6.Nacos与Eureka的区别
6.Nacos配置管理
6.1.统一配置管理
6.1.1.在nacos中添加配置文件
6.1.2.从微服务拉取配置
6.2.配置热更新
6.2.1.方式一
6.2.2.方式二
6.3.配置共享
6.4.搭建Nacos集群
1.集群结构图
7.Feign远程调用
8.Gateway服务网关
8.1.为什么需要网关
8.2.gateway快速入门
8.3.断言工厂
8.4.过滤器工厂
8.4.1.路由过滤器的种类
8.4.2.请求头过滤器
8.4.3.默认过滤器
8.4.4.总结
8.5.全局过滤器
8.5.1.全局过滤器作用
8.5.2.自定义全局过滤器
8.5.3.过滤器执行顺序
8.6.跨域问题
8.6.1.什么是跨域问题
8.6.2.模拟跨域问题
8.6.3.解决跨域问题
10.服务异步通讯-MQ
10.1.初始MQ-同步通讯的优缺点
10.2.初始MQ-异步通讯的优缺点
10.3.初始MQ-mq常见技术介绍
10.4.RabbitMQ快速入门-介绍和安装
10.5.RabbitMQ快速入门-消息模型介绍
10.6.SpringAMQP-基本介绍
10.7.SpringAMQP-入门案例的消息发送
10.8.SpringAMQP-WorkQueue模型
10.9.SpringAMQP-发布订阅模型
Fanout Exchange
DirectExchange
TopicExchange
消息转换器
11.elasticsearch基础-分布式搜索引擎
11.1.什么是elasticsearch?
11.2.初始ES-倒排索引
11.3.初始ES-ES与MySQL的概念对比
1.认识微服务
随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢?
1.0.学习目标
- 了解微服务架构的优缺点
1.1.单体架构
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。
单体架构的优缺点如下:
优点:
- 架构简单
- 部署成本低
缺点:
- 耦合度高(维护困难、升级困难)
1.2.分布式架构
分布式架构:根据业务功能对系统做拆分,每个业务功能模块作为独立项目开发,称为一个服务。
分布式架构的优缺点:
优点:
- 降低服务耦合
- 有利于服务升级和拓展
缺点:
- 服务调用关系错综复杂
分布式架构虽然降低了服务耦合,但是服务拆分时也有很多问题需要思考:
- 服务拆分的粒度如何界定?
- 服务之间如何调用?
- 服务的调用关系如何管理?
人们需要制定一套行之有效的标准来约束分布式架构。
1.3.微服务
微服务的架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责
- 自治:团队独立、技术独立、数据独立,独立部署和交付
- 面向服务:服务提供统一标准的接口,与语言和技术无关
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。
因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 。
但方案该怎么落地?选用什么样的技术栈?全球的互联网公司都在积极尝试自己的微服务落地方案。
其中在Java领域最引人注目的就是SpringCloud提供的方案了。
1.4.SpringCloud
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
其中常见的组件包括:
1.5.总结
-
单体架构:简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统
-
分布式架构:松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝
-
微服务:一种良好的分布式架构方案
①优点:拆分粒度更小、服务更独立、耦合度更低
②缺点:架构非常复杂,运维、监控、部署难度提高
- SpringCloud是微服务架构的一站式解决方案,集成了各种优秀微服务功能组件
2.服务拆分和远程调用
任何分布式架构都离不开服务的拆分,微服务也是一样。
2.1.服务拆分原则
- 不同微服务,不要重复开发相同业务
- 微服务数据独立,不要访问其它微服务的数据库
- 微服务可以将自己的业务暴露为接口,供其它微服务调用
2.2.服务拆分示例
以课前资料中的微服务cloud-demo为例,其结构如下:
cloud-demo:父工程,管理依赖
- order-service:订单微服务,负责订单相关业务
- user-service:用户微服务,负责用户相关业务
要求:
- 订单微服务和用户微服务都必须有各自的数据库,相互独立
- 订单服务和用户服务都对外暴露Restful的接口
- 订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库
2.3.实现远程调用案例
-
在order-service服务中,有一个根据id查询订单的接口:
-
根据id查询订单,返回值是Order对象
-
其中的user为null
-
在user-service中有一个根据id查询用户的接口:
2.3.1.案例需求:
修改order-service中的根据id查询订单业务,要求在查询订单的同时,根据订单中包含的userId查询出用户信息,一起返回。
因此,我们需要在order-service中 向user-service发起一个http的请求,调用http://localhost:8081/user/{userId}这个接口。
大概的步骤是这样的:
- 注册一个RestTemplate的实例到Spring容器
- 修改order-service服务中的OrderService类中的queryOrderById方法,根据Order对象中的userId查询User
- 将查询的User填充到Order对象,一起返回
2.3.2.注册RestTemplate
首先,我们在order-service服务中的OrderApplication启动类中,注册RestTemplate实例:
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(clients = UserClient.class,defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {public static void main(String[] args) {SpringApplication.run(OrderApplication.class, args);}/*创建RestTemplate并注入Spring容器*/@LoadBalanced@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}
2.3.3.实现远程调用
修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法:
2.4.提供者与消费者
在服务调用关系中,会有两个不同的角色:
服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
但是,服务提供者与服务消费者的角色并不是绝对的,而是相对于业务而言。
如果服务A调用了服务B,而服务B又调用了服务C,服务B的角色是什么?
- 对于A调用B的业务而言:A是服务消费者,B是服务提供者
- 对于B调用C的业务而言:B是服务消费者,C是服务提供者
因此,服务B既可以是服务提供者,也可以是服务消费者。
3.Eureka注册中心
假如我们的服务提供者user-service部署了多个实例,如图:
几个问题:
- order-service在发起远程调用的时候,该如何得知user-service实例的ip地址和端口?
- 有多个user-service实例地址,order-service调用时该如何选择?
- order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?
3.1.Eureka的结构和作用
这些问题都需要利用SpringCloud中的注册中心来解决,其中最广为人知的注册中心就是Eureka,其结构如下:
问题1:order-service如何得知user-service实例地址?
获取地址信息的流程如下:
- user-service服务实例启动后,将自己的信息注册到eureka-server(Eureka服务端)。这个叫服务注册
- eureka-server保存服务名称到服务实例地址列表的映射关系
- order-service根据服务名称,拉取实例地址列表。这个叫服务发现或服务拉取
问题2:order-service如何从多个user-service实例中选择具体的实例?
- order-service从实例列表中利用负载均衡算法选中一个实例地址
- 向该实例地址发起远程调用
问题3:order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?
- user-service会每隔一段时间(默认30秒)向eureka-server发起请求,报告自己状态,称为心跳
- 当超过一定时间没有发送心跳时,eureka-server会认为微服务实例故障,将该实例从服务列表中剔除
- order-service拉取服务时,就能将故障实例排除了
注意:一个微服务,既可以是服务提供者,又可以是服务消费者,因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端
3.2.搭建eureka-server
首先大家注册中心服务端:eureka-server,这必须是一个独立的微服务
3.2.1.创建eureka-server服务
在cloud-demo父工程下,创建一个子模块:
填写模块信息:
然后填写服务信息:
3.2.2.引入eureka依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3.2.3.编写启动类
给eureka-server服务编写一个启动类,一定要添加一个@EnableEurekaServer注解,开启eureka的注册中心功能:
package cn.itcast.eureka;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {public static void main(String[] args) {SpringApplication.run(EurekaApplication.class, args);}
}
3.2.4.编写配置文件
编写一个application.yml文件,内容如下:
server:port: 10086
spring:application:name: eureka-server
eureka:client:service-url: defaultZone: http://127.0.0.1:10086/eurekainstance:prefer-ip-address: trueinstance-id: 127.0.0.1:${server.port}
3.2.5.启动服务
启动微服务,然后在浏览器访问: http://127.0.0.1:10086
看到下面结果应该是成功了:
3.3.服务注册
下面,我们将user-service注册到eureka-server中去。
1)引入依赖
在user-service的pom文件中,引入下面的eureka-client依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)配置文件
在user-service中,修改application.yml文件,添加服务名称、eureka地址:
spring:application:name: userservice
eureka:client:service-url:defaultZone: http://127.0.0.1:10086/eurekainstance:prefer-ip-address: true #以IP地址注册到服务中心,相互注册使用IP地址,如果不配置就是机器的主机名instance-id: 127.0.0.1:${server.port} # instanceID默认值为主机名+服务名+端口
3)启动多个user-service实例
为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service。
首先,复制原来的user-service启动配置:
然后,在弹出的窗口中,填写信息:
现在,SpringBoot窗口会出现两个user-service启动配置:
不过,第一个是8081端口,第二个是8082端口。
启动两个user-service实例:
查看eureka-server管理页面:
3.4.服务发现
下面,我们将order-service的逻辑修改:向eureka-server拉取user-service的信息,实现服务发现。
1)引入依赖
之前说过,服务发现、服务注册统一都封装在eureka-client依赖,因此这一步与服务注册时一致。
在order-service的pom文件中,引入下面的eureka-client依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)配置文件
服务发现也需要知道eureka地址,因此第二步与服务注册一致,都是配置eureka信息:
在order-service中,修改application.yml文件,添加服务名称、eureka地址:
spring:application:name: orderservice
eureka:client:service-url:defaultZone: http://127.0.0.1:10086/eurekainstance:prefer-ip-address: trueinstance-id: 127.0.0.1:${server.port}
3)服务拉取和负载均衡
最后,我们要去eureka-server中拉取user-service服务的实例列表,并且实现负载均衡。
不过这些动作不用我们去做,只需要添加一些注解即可。
修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径,用服务名代替ip、端口:
spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡。
4.Ribbon负载均衡
上一节中,我们添加了@LoadBalanced注解,即可实现负载均衡功能,这是什么原理呢?
4.1.负载均衡原理
SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。
那么我们发出的请求明明是http://userservice/user/1,怎么变成了http://localhost:8081的呢?
4.2.源码跟踪
为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。
显然有人帮我们根据service名称,获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor,这个类会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。
我们进行源码跟踪:
1)LoadBalancerIntercepor
可以看到这里的intercept方法,拦截了用户的HttpRequest请求,然后做了几件事:
- request.getURI():获取请求uri,本例中就是 http://user-service/user/8
- originalUri.getHost():获取uri路径的主机名,其实就是服务id,user-service
- this.loadBalancer.execute():处理服务id,和用户请求。
这里的this.loadBalancer是LoadBalancerClient类型,我们继续跟入。
2)LoadBalancerClient
继续跟入execute方法:
代码是这样的:
- getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。
- getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8082端口的服务
放行后,再次访问并跟踪,发现获取的是8081:
果然实现了负载均衡。
3)负载均衡策略IRule
在刚才的代码中,可以看到获取服务使通过一个getServer方法来做负载均衡:
我们继续跟入:
继续跟踪源码chooseServer方法,发现这么一段代码:
我们看看这个rule是谁:
这里的rule默认值是一个RoundRobinRule,看类的介绍:
这是轮询的意思。
到这里,整个负载均衡的流程就清楚了。
4)总结
SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改。用一幅图来总结一下:
基本流程如下:
- 拦截我们的RestTemplate请求http://userservice/user/1
- RibbonLoadBalancerClient会从请求url中获取服务名称,也就是user-service
- DynamicServerListLoadBalancer根据user-service到eureka拉取服务列表
- eureka返回列表,localhost:8081、localhost:8082
- IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081
- RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求
4.3.负载均衡策略
4.3.1.负载均衡策略
负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:
不同规则的含义如下:
内置负载均衡规则类 | 规则描述 |
RoundRobinRule | 简单轮询服务列表来选择服务器。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的ActiveConnectionsLimit属性进行配置。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑 |
4.3.2.自定义负载均衡策略
通过定义IRule实现可以修改负载均衡规则,有两种方式:
1.代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){return new RandomRule();
}
2.配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
注意,一般用默认的负载均衡规则,不做修改。
4.4.饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:eager-load:enabled: true # 开启饥饿加载clients:- userservice # 指定饥饿加载的服务名称- xxxxservice # 如果需要指定多个,需要这么写
5.Nacos注册中心
国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心。
5.1.认识和安装Nacos
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
将这个包解压到任意非中文目录下:
目录说明:
- bin:启动脚本
- conf:配置文件
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件(application.properties)中的端口:
启动非常简单,进入bin目录,结构如下:
然后执行命令即可:
- windows命令:
startup.cmd -m standalone
在浏览器输入地址:http://127.0.0.1:8848/nacos即可:
默认的账号和密码都是nacos,进入后:
5.2.服务注册到nacos
Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。
主要差异在于:
- 依赖不同
- 服务地址不同
1)引入依赖
在cloud-demo父工程的pom文件中的<dependencyManagement>中引入SpringCloudAlibaba的依赖:
<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>
然后在user-service和order-service中的pom文件中引入nacos-discovery依赖:
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2)配置nacos地址
在user-service和order-service的application.yml中添加nacos地址:
spring:cloud:nacos:server-addr: localhost:8848
3)重启
重启微服务后,登录nacos管理页面,可以看到微服务信息:
5.3.服务分级存储模型
一个服务可以有多个实例,例如我们的user-service,可以有:
- 127.0.0.1:8081
- 127.0.0.1:8082
- 127.0.0.1:8083
假如这些实例分布于全国各地的不同机房,例如:
- 127.0.0.1:8081,在上海机房
- 127.0.0.1:8082,在上海机房
- 127.0.0.1:8083,在杭州机房
Nacos就将同一机房内的实例 划分为一个集群。
也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型,如图:
微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。例如:
杭州机房内的order-service应该优先访问同机房的user-service。
5.3.1.给user-service配置集群
修改user-service的application.yml文件,添加集群配置:
spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZ # 集群名称
重启两个user-service实例后,我们可以在nacos控制台看到下面结果:
我们再次复制一个user-service启动配置,添加属性:
-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH
启动UserApplication3后再次查看nacos控制台:
5.3.2.同集群优先的负载均衡
默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。
因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。
1)给order-service配置集群信息
修改order-service的application.yml文件,添加集群配置:
spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZ # 集群名称
2)修改负载均衡规则
修改order-service的application.yml文件,修改负载均衡规则:
userservice:ribbon:NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
5.4.权重配置
实际部署中会出现这样的场景:
服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。
但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。
因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。
在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重:
在弹出的编辑窗口,修改权重:
注意:如果权重修改为0,则该实例永远不会被访问
5.5.环境隔离
Nacos提供了namespace来实现环境隔离功能。
- nacos中可以有多个namespace
- namespace下可以有group、service等
- 不同namespace之间相互隔离,例如不同namespace的服务互相不可见
5.5.1.创建namespace
默认情况下,所有service、data、group都在同一个namespace,名为public:
我们可以点击页面新增按钮,添加一个namespace:
然后,填写表单:
就能在页面看到一个新的namespace:
5.5.2.给微服务配置namespace
给微服务配置namespace只能通过修改配置来实现。
例如,修改order-service的application.yml文件:
spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZnamespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID
重启order-service后,访问控制台,可以看到下面的结果:
此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错:

5.6.Nacos与Eureka的区别
Nacos的服务实例分为两种l类型:
- 临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。
- 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。
配置一个服务实例为永久实例:
spring:cloud:nacos:discovery:ephemeral: false # 设置为非临时实例
Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异:
Nacos与eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
Nacos与eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
6.Nacos配置管理
Nacos除了可以做注册中心,同样可以做配置管理来使用。
6.1.统一配置管理
当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。
Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。
6.1.1.在nacos中添加配置文件
如何在nacos中管理配置呢?
然后在弹出的表单中,填写配置信息:
注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。
6.1.2.从微服务拉取配置
微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。
但如果尚未读取application.yml,又如何得知nacos地址呢?
因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:
1)引入nacos-config依赖
首先,在user-service服务中,引入nacos-config的客户端依赖:
<!--nacos配置管理依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2)添加bootstrap.yaml
然后,在user-service中添加一个bootstrap.yaml文件,内容如下:
spring:application:name: userservice # 服务名称profiles:active: dev #开发环境,这里是dev cloud:nacos:server-addr: localhost:8848 # Nacos地址config:file-extension: yaml # 文件后缀名
这里会根据spring.cloud.nacos.server-addr获取nacos地址,再根据
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id,来读取配置。
本例中,就是去读取userservice-dev.yaml:
3)读取nacos配置
在user-service中的UserController中添加业务逻辑,读取pattern.dateformat配置:
完整代码:
package cn.itcast.user.web;import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Value("${pattern.dateformat}")private String dateformat;@GetMapping("now")public String now(){return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));}// ...略
}
在页面访问,可以看到效果:
6.2.配置热更新
我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新。
要实现配置热更新,可以使用两种方式:
6.2.1.方式一
在@Value注入的变量所在类上添加注解@RefreshScope:
6.2.2.方式二
使用@ConfigurationProperties注解代替@Value注解。
在user-service服务中,添加一个类,读取patterrn.dateformat属性:
package cn.itcast.user.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {private String dateformat;
}
在UserController中使用这个类代替@Value:
完整代码:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate PatternProperties patternProperties;@GetMapping("now")public String now(){return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));}// 略
}
6.3.配置共享
其实微服务启动时,会去nacos读取多个配置文件,例如:
-
[spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
-
[spring.application.name].yaml,例如:userservice.yaml
而[spring.application.name].yaml不包含环境,因此可以被多个环境共享。
下面我们通过案例来测试配置共享
1)添加一个环境共享配置
我们在nacos中添加一个userservice.yaml文件:
2)在user-service中读取共享配置
在user-service服务中,修改PatternProperties类,读取新添加的属性:
在user-service服务中,修改UserController,添加一个方法:
3)运行两个UserApplication,使用不同的profile
修改UserApplication2这个启动项,改变其profile值:
这样,UserApplication(8081)使用的profile是dev,UserApplication2(8082)使用的profile是test。
启动UserApplication和UserApplication2
访问http://localhost:8081/user/prop,结果:
访问
http://localhost:8082/user/prop,结果:
可以看出来,不管是dev,还是test环境,都读取到了envSharedValue这个属性的值。
4)配置共享的优先级
当nacos、服务本地同时出现相同属性时,优先级有高低之分:
6.4.搭建Nacos集群
Nacos生产环境下一定要部署为集群状态,部署方式参考课前资料中的文档:
1.集群结构图
官方给出的Nacos集群图:
其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。
我们计划的集群结构:
SpringCloud(黑马)笔记 | 赤 赤 博 客 (gitee.io)
7.Feign远程调用
8.Gateway服务网关
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
8.1.为什么需要网关
Gateway网关是我们服务的守门神,所有微服务的统一入口。
网关的核心功能特性:
- 请求路由
- 权限控制
- 限流
架构图:
身份认证和权限检验:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
服务路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
请求限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。
在SpringCloud中网关的实现包括两种:
- gateway
- zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
8.2.gateway快速入门
下面,我们就演示下网关的基本路由功能。基本步骤如下:
- 创建SpringBoot工程gateway,引入网关依赖
- 编写启动类
- 编写基础配置和路由规则
- 启动网关服务进行测试
1)创建gateway服务,引入依赖
创建服务:
引入依赖:
<!--网关-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2)编写启动类
package cn.itcast.gateway;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
3)编写基础配置和路由规则
创建application.yml文件,内容如下:
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/开头就符合要求
我们将符合Path 规则的一切请求,都代理到 uri参数指定的地址。
本例中,我们将 /user/**开头的请求,代理到lb://userservice,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。
4)重启测试
重启网关,访问http://localhost:10010/user/1时,符合`/user/**`规则,请求转发到uri:http://userservice/user/1,得到了结果:
5)网关路由的流程图
总结:
网关搭建步骤:
- 创建项目,引入nacos服务发现和gateway依赖
- 配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括:
- 路由id:路由的唯一标示
- 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
- 路由断言(predicates):判断路由的规则
- 路由过滤器(filters):对请求或响应做处理
接下来,就重点来学习路由断言和路由过滤器的详细知识
8.3.断言工厂
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例如Path=/user/**是按照路径匹配,这个规则是由
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来
处理的,像这样的断言工厂在SpringCloudGateway还有十几个:
我们只需要掌握Path这种路由工程就可以了。
PredicateFactory的作用是什么?
读取用户定义的断言条件,对请求做出判断
Path=/user/**是什么含义?
路径是以/user开头的就认为是符合的
8.4.过滤器工厂
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
8.4.1.路由过滤器的种类
Spring提供了31种不同的路由过滤器工厂。例如:
8.4.2.请求头过滤器
下面以AddRequestHeader 为例来讲解。
需求:给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!
只需要修改gateway服务的application.yml文件,添加路由过滤即可:
spring:cloud:gateway:routes:- id: user-service uri: lb://userservice predicates: - Path=/user/** filters: # 过滤器- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
8.4.3.默认过滤器
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:
spring:cloud:gateway:routes:- id: user-service uri: lb://userservice predicates: - Path=/user/**default-filters: # 默认过滤项- AddRequestHeader=Truth, Itcast is freaking awesome!
8.4.4.总结
过滤器的作用是什么?
- 对路由的请求或响应做加工处理,比如添加请求头
- 配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用是什么?
- 对所有路由都生效的过滤器
8.5.全局过滤器
上一节学习的过滤器,网关提供了31种,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。
8.5.1.全局过滤器作用
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GlobalFilter接口。
public interface GlobalFilter {/*** 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理** @param exchange 请求上下文,里面可以获取Request、Response等信息* @param chain 用来把请求委托给下一个过滤器 * @return {@code Mono<Void>} 返回标示当前过滤器业务结束*/Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
在filter中编写自定义逻辑,可以实现下列功能:
- 登录状态判断
- 权限校验
- 请求限流等
8.5.2.自定义全局过滤器
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
- 参数中是否有authorization
- authorization参数值是否为admin
如果同时满足则放行,否则拦截
实现:
在gateway中定义一个过滤器:
package cn.itcast.gateway.filters;import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取请求参数MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();// 2.获取authorization参数String auth = params.getFirst("authorization");// 3.校验if ("admin".equals(auth)) {// 放行return chain.filter(exchange);}// 4.拦截// 4.1.禁止访问,设置状态码exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);// 4.2.结束处理return exchange.getResponse().setComplete();}
}
8.5.3.过滤器执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:
排序的规则是什么呢?
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
- 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
详细内容,可以查看源码:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链
8.6.跨域问题
8.6.1.什么是跨域问题
跨域:域名不一致就是跨域,主要包括:
域名不同: http://www.taobao.com 和 http://www.taobao.org 和 http://www.jd.com和 http://miaosha.jd.com
域名相同,端口不同:localhost:8080和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS,这个以前应该学习过,这里不再赘述了。
不知道的小伙伴可以查看https://www.ruanyifeng.com/blog/2016/04/cors.html
8.6.2.模拟跨域问题
从localhost:8090访问localhost:10010,端口不同,显然是跨域的请求。
8.6.3.解决跨域问题
在gateway服务的application.yml文件中,添加下面的配置
spring:cloud:gateway:# 。。。globalcors: # 全局的跨域处理add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题corsConfigurations:'[/**]':allowedOrigins: # 允许哪些网站的跨域请求 - "http://localhost:8090"allowedMethods: # 允许的跨域ajax的请求方式- "GET"- "POST"- "DELETE"- "PUT"- "OPTIONS"allowedHeaders: "*" # 允许在请求中携带的头信息allowCredentials: true # 是否允许携带cookiemaxAge: 360000 # 这次跨域检测的有效期
10.服务异步通讯-MQ
本节内容:
- 初始MQ
- RabbitMQ快速入门
- SpringAMQP
10.1.初始MQ-同步通讯的优缺点
企业开发中有一种神奇的生物存在,就是产品经理,ta会不停地提出需求添加业务,这时候同步处理每个服务,处理耗时会被加长,如图 6-1-1所示,处理一个业务总耗时为500ms,也就是说一秒只能处理两个业务,性能下降、吞吐量下降。
假设图 6-1-1仓储服务扛不住压力,它挂了,请求来访问仓储服务必然是阻塞了,支付服务调用仓储调不通,卡在这里,一个请求卡住,后续的请求都得排队等待,卡得越来越多,支付服务资源被耗尽,请求就进不去支付服务了。
图 6-1-1
同步存在的问题:
耦合度高:每次加入新的需求,都要修改原来的代码;
性能下降:调用者需要等待服务提供者响应,如果调用链过长则响应时间等于每次调用的时间之和;
资源浪费:调用链中的每个服务在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资源;
级联失败:如果服务提供者出现问题,所有调用方都会跟着出问题,如同多米诺骨牌一样,迅速导致整个微服务群故障。
本节小结:
同步调用的优点:
时效性较强,可以立即得到结果
同步调用的问题:
- 耦合度高
- 性能和吞吐能力下降
- 有额外的资源消耗
- 有级联失败问题
10.2.初始MQ-异步通讯的优缺点
异步调用常见实现就是事件驱动模式
优势一:服务解耦
当有新的业务,不再需要修改支付服务的代码,和它没关系了,只需要新的服务去订阅Broker。如果需要去掉某一个业务,那只需要该业务的服务取消订阅Broker即可。
优势二:性能提升,吞吐量提高
优势三:服务没有强依赖,不担心级联失败问题
假设仓储服务挂了,也和支付服务没有关系,这边支付业务已经完成了,“钱到账了”通知发布出去了,后续怎么处理那就是其他服务各自的事情。
优势四:流量削峰
当来了多个请求,Broker有缓冲的作用,然后再排序安排执行,这时候并发量被砍平了,这就是流量削峰。
本节小结:
异步通信的优点:
·耦合度低
·吞吐量提升
·故障隔离
·流量削峰
异步通信的缺点:
·依赖于Broker的可靠性、安全性、吞吐能力
·架构复杂了,业务没有明显的流程线,不好追踪管理
异步同步各自都有优缺点,那什么时候使用同步?什么时候使用异步?
事实上大多都是使用同步,事实上对并发没有很高的要求,对时效性要求较高,就是我查询了一个信息,我立马需要在下一个业务中用到,比如查到了订单,马上要去查用户,需要立刻使用,那这得同步调用。异步只是通知了要做什么,什么时候做完不能及时反馈。
10.3.初始MQ-mq常见技术介绍
MQ(MessageQueue),中文是消息队列,字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。
10.4.RabbitMQ快速入门-介绍和安装
·RabbitMQ概述和安装
·常见消息模型
·快速入门
RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址: RabbitMQ: easy to use, flexible messaging and streaming — RabbitMQ
本节小结:
RabbitMQ中的几个概念:
- channel:操作MQ的工具
- exchange:路由消息到队列中
- queue:缓存消息
- virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组
10.5.RabbitMQ快速入门-消息模型介绍
MQ的官方文档中给出了5个MQ的Demo示例,对应了几种不同的用法:
官方的HelloWorld是基于最基础的消息队列模型来实现的,只包括三个角色:
- publisher:消息发布者,将消息发送到队列queue
- queue:消息队列,负责接受并缓存消息
- consumer:订阅队列,处理队列中的消息
生产者
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.150.101");
factory.setPort(5672); // RabbitMQ端口是5672,ui管理台控制台是15672
factory.setVirtualHost("/");
factory.setUsername("itcast");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();// 2.创建通道Channel
Channel channel = connection.createChannel();// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);// 4.发送消息
String message="hello,rabbitmq";
channel.basicPublish("",queueName,null,message.getBytes());
System.out.println("发送消息成功:["+message+"]");//关闭通道和连接
channel.close();
connection.close();
消费者
// 1.建立连接
ConnectionFactory factory = new ConnectionFactory();
// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
factory.setHost("192.168.150.101");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("itcast");
factory.setPassword("123321");
// 1.2.建立连接
Connection connection = factory.newConnection();// 2.创建通道Channel
Channel channel = connection.createChannel();// 3.创建队列
String queueName = "simple.queue";
channel.queueDeclare(queueName, false, false, false, null);// 4.订阅消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope,AMQP.BasicProperties properties, byte[] body) throws IOException {// 5.处理消息String message = new String(body);System.out.println("接收到消息:【" + message + "】");}
});
System.out.println("等待接收消息。。。。");
本节小结:
基本消息队列的消息发送流程:
1.建立connection(连接)
2.创建channel(通道,通过这个通道进行发送接收)
3.利用channel声明队列
4.利用channel向队列发送消息
基本消息队列的消息接收流程:
1.建立connection
2.创建channel
3.利用channel声明队列
4.定义consumer的消费行为handleDelivery()
5.利用channel将消费者与队列绑定
10.6.SpringAMQP-基本介绍
- Basic Queue简单队列模型
- Work Queue工作队列模型
- 发布、订阅模型-Fanout
- 发布、订阅模型-Direct
- 发布、订阅模型-Topic
- 消息转换器
AMQP全称:Advanced Message Queuing Protocol(高级消息队列协议)是用于在应用程序之间传递业务消息的开放标准。该协议与语言和平台无关,更符合微服务中独立性的要求。
Spring AMQP是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。
SpringAmqp的官方地址: https://spring.io/projects/spring-amqp
提供了以下内容:
用于异步处理入站消息的侦听器容器
RabbitTemplate 用于发送和接收消息
RabbitAdmin 用于自动声明队列、交换和绑定
10.7.SpringAMQP-入门案例的消息发送
利用SpringAMQP实现HelloWorld中的基础消息队列功能
流程如下:
1.在父工程中引入spring-amqp的依赖
2.在publisher服务中利用RabbitTemplate发送消息到simple.queue这个队列
3.在consumer服务中编写消费逻辑,绑定simple.queue这个队列
步骤一:引入AMQP依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
步骤二:在publisher中编写测试方法,向simple.queue发送消息
1.在publisher服务中编写application.yml,添加mq连接信息:
spring:rabbitmq:host: 192.168.150.101 # 主机名port: 5672 # 端口virtual-host: / #虚拟主机username: itcast #用户名password: 123321 #密码
2.在publisher服务中新建一个测试类,编写测试方法:(这段代码不会创建消息队列,会发到原有的消息队列,如果发现没有消息可能是这个原因,需要手动创建队列,选择的队列必须是rabbitmq里存在的队列)
public class SpringAmqpTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendMessage2SimpleQueue() {String queueName = "simple.queue"; // 队列名称String message = "hello, spring amqp!"; // 消息rabbitTemplate.convertAndSend(queueName, message); // 发送}
}
步骤三:在consumer中编写消费逻辑,监听simple.queue
1.在consumer服务中编写application.yml,添加mq连接信息:
spring:rabbitmq:host: 192.168.150.101 # 主机名port: 5672 # 端口virtual-host: / #虚拟主机username: itcast #用户名password: 123321 #密码
2. 在consumer服务中新建一个类,编写消费逻辑
@Component
public class SpringRabbitListener{@RabbitListener(queues="simple.queue")public void listenSimpleQueueMessage(String msg) throws InterruptedException{System.out.println("spring消费者接收到信息:["+msg+"]")}
}
本节小结:
什么是AMQP?
- 应用间消息通信的一种协议,与语言和平台无关。
- SpringAMQP如何发送消息?
- 引入amqp的starter依赖·配置RabbitMQ地址
- 利用RabbitTemplate的convertAndSend方法
SpringAMQP如何接收消息?
1.引入amqp的starter依赖
2.配置RabbitMQ地址
3.定义类,添加@Component注解
4.类中声明方法,添加@RabbitListener注解,方法参数就是消息
注意:消息一旦消费就会从队列删除,RabbitMQ没有消息回溯功能
10.8.SpringAMQP-WorkQueue模型
consumer:消费者
publisher:生产者
假设publisher一秒发送50条消息,consumer1只能处理40条消息,就会多出10条消息,那这个consumer1处理的完吗?显然处理不完,消息只能堆积在队列当中,队列是有存储上限的,堆满了之后,再有消息发送过来就进不去了。
增加一个consumer2,就可以合作处理消息,这就是挂两个消费者的原因。
Work queue不是一个新的队列类型,其实就是一个普通的队列,只是设计的时候多了几个消费者。
作用:可以提高消息处理速度,避免队列消息堆积。
Work Queue模型图
案例:
模拟WorkQueue,实现一个队列绑定多个消费者
基本思路如下:
1.在publisher服务中定义测试方法,每秒产生50条消息,发送到simple.queue
2.在consumer服务中定义两个消息监听者,都监听simple.queue队列
3.消费者1每秒处理50条消息,消费者2每秒处理10条消息
实现:
1.在publisher服务中定义测试方法,每秒产生50条消息,发送到simple.queue
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendMessage2WorkQueue() 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); }}
}
2.在consumer服务中定义两个消息监听者,都监听simple.queue队列
@Component
public class SpringRabbitListener {@RabbitListener(queues = "simple.queue")public void listenWorkQueue1(String msg) throws InterruptedException {System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());Thread.sleep(20);}@RabbitListener(queues = "simple.queue")public void listenWorkQueue2(String msg) throws InterruptedException {System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now()); // err 是为了打印颜色不一样,有所差异Thread.sleep(200);}
}
运行结果是:
总耗时处理了5秒钟,队列分配是消费者1都为偶数,消费者2都为奇数,由于消费者2处理速度较慢,所以导致处理速度被消费者2拖长,没有考虑消费者的能力。这是由于RabbitMQ内部机制造成的,消息预取机制。消息预取是当大量的消息到队列中,consumer1和consumer2都会提前把消息拿过来,不管它能不能处理,先拿了再说,所以就平均了所有的消息。但是consumer1处理得快,consumer2处理的慢,所以导致处理时长被拖长。
怎么控制呢?
消费预取机制
修改消费者consumer的application.yml文件,设置listener.simple.prefetch这个值,可以控制预取消息的上限:(设置为:1,每次只能获取一条消息,处理完成才能获取下一个消息)
spring:rabbitmq:host: 192.168.150.101 # rabbitMQ的ip地址port: 5672 # 端口username: itcastpassword: 123321virtual-host: /listener:simple:prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
Work模型的使用:
- 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
- 通过设置prefetch来控制消费者预取的消息数量
10.9.SpringAMQP-发布订阅模型
一次发送多个消费者接收
Fanout Exchange
消费者
@Component
public class SpringRabbitListener {@RabbitListener(queues = "fanout.queue1")public void listenFanoutQueue1(String msg) {System.out.println("消费者接收到fanout.queue1消息:【" + msg + "】" + LocalTime.now());}@RabbitListener(queues = "fanout.queue2")public void listenFanQueue2(String msg) {System.err.println("消费者2........接收到fanout.queue2消息:【" + msg + "】" + LocalTime.now());}
}
生产者
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendFanoutExchange(){//交换机名称String exchangeName="itcast.fanout";//消息String message="hello,everyone";//发送消息rabbitTemplate.convertAndSend(exchangeName,"",message);}
}
交换机的作用是什么?
- 接收publisher发送的消息
- 将消息按照规则路由到与之绑定的队列
- 不能缓存消息,路由失败,消息丢失
- FanoutExchange会将消息路由到每个绑定的队列
声明队列、交换机、绑定关系的Bean是什么?
分别为Queue FanoutExchange Binding
DirectExchange
描述下Direct交换机与Fanout交换机的差异
- Fanout交换机将消息路由给每一个与之绑定的队列
- Direct交换机根据RoutingKey判断路由给哪个队列
- 如果多个队列具有相同的RoutingKey,则与Fanout功能类似
基于@RabbitListener注解声明队列和交换机有哪些常用注解
@Queue @Exchange
TopicExchange
描述下Direct交换机和Topic交换机的差异
Topic交换机支持通配符
消息转换器
SpringAMQP中的消息的序列化和反序列化是怎么实现的
- 利用MessageConverter实现的,默认是JDK的序列化
- 注意发送方与接收方必须使用相同的MessageConverter
11.elasticsearch基础-分布式搜索引擎
- 初识elasticsearch
- 索引库操作
- 文档操作
- RestAPl
11.1.什么是elasticsearch?
- 了解ES
- 倒排索引
- es的一些概念
- 安装es、kibana
elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。
elasticsearch结合kibana、Logstash、Beats,也就是elastic stack (ELK)。被广泛应用在日志数据分析、实时监控等领域。
elasticsearch可以将日志可视化展示出来。比如在线上系统报错了,怎么找?运行的时候不可能打断点Debug,只能采用日志的方式。
实时监控,运行状态也是数据,CUP情况、内存情况、访问的频率等等,这些信息也会被elasticsearch展示出来。
elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
elasticsearch底层是Luncene。
本节小结:
什么是elasticsearch?
- 一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能
什么是elastic stack (ELK) ?
- 是以elasticsearch为核心的技术栈,包括beats、Logstash、kibana、elasticsearch
11.2.初始ES-倒排索引
传统数据库(如MySQL)采用正向索引
elasticsearch采用倒排索引:
- 文档(document) :每条数据就是一个文档
- 词条(term):文档按照语义分成的词语
保存每一个词条,比如存“手机”这个词条,在id为1和2出现过,就记录“1,2”。
之后有再多的词条也是这样记录,总会有重复的词条,但是不重复记录,只存唯一的一个,重复词条出现在文档后面记录id,这样可以确定词条不会有重复的。因为词条它的唯一性,可以为它创建索引了。
先根据用户输入的词条,去词条列表中查找对应的id;
第二次拿着文档id,去查找文档;
虽然查找了两次,但两次都是根据索引进行查询,所以查询效率比逐条扫描高很多。
比如查找“华为手机”,分词后得到“华为”和“手机”,它们保存的id,2出现最多,那索引2就会往前排,剩下1,3排序。
平时搜索的结果也可以看出是分词后进行查找。
正向索引是根据文档找到词,
倒排索引是根据词找到文档。
本节小结:
什么是文档和词条?
- 每一条数据就是一个文档
- 对文档中的内容分词,得到的词语就是词条(中文就按照中文含义分,英文就按照空格区分)
什么是正向索引?
- 基于文档id创建索引。查询词条时必须先找到文档,而后判断是否包含词条
什么是倒排索引?
- 对文档内容分词,对词条创建索引,并记录词条所在文档的信息。
查询时先根据词条查询到文档id,而后获取到文档
11.3.初始ES-ES与MySQL的概念对比
- 索引(index) :相同类型的文档的集合
- 映射( mapping):索引中文档的字段约束信息,类似表的结构约束
这边给出N多文档,根据字段相同进行分类,不同类型放到不同的索引库
概念对比
Mysql:擅长事务类型操作(ACID原则)可以确保数据的安全和一致性(还有隔离性等等)
Elasticsearch:擅长海量数据的搜索、分析、计算
可以互补
本节小结:
文档:一条数据就是一个文档,es中是Json格式
字段:Json文档中的字段
索引:同类型文档的集合
映射:索引中文档的约束,比如字段名称、类型
elasticsearch与数据库的关系:
- 数据库负责事务类型操作
- elasticsearch负责海量数据的搜索、分析、计算