目录
6.3 路由断言工厂(Route Predicate Factory)
8.3.5 发布(Publish)、订阅(Subscribe)
1.Eureka注册中心
1.搭建Eureka
1、创建项目,引入spring-cloud-starter-netflix-eureka-server依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
2、编写启动类,类上添加注解@EnableEurekaServer
//开启自动配置
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
3、创建application.yml配置文件,编写配置(配置eureka地址)
server:
port: 10086 #服务端口
spring:
application:
name: eurekaserver #eureka的服务名称
eureka:
client:
service-url: #eureka的地址信息
defaultZone: http://localhost:10086/eureka
2. 服务注册
将我们的服务提供者user-service服务注册到EurekaServer步骤如下:
1、在user-service项目中导入spring-cloud-starter-netflix-eureka-client的依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2、在application.yml中配置eureka地址
spring:
application:
name: userservice #user服务的名称
eureka:
client:
service-url: #eureka的地址信息
defaultZone: http://localhost:10086/eureka
将我们的服务消费者order-service服务注册到EurekaServer步骤同上。
无论是提供者还是消费者,都引入eureka-client依赖,知道了eureka地址后,在配置文件中进行配置,都可以完成服务注册。
3. 服务发现
服务拉取是基于服务名称来获取服务列表,然后在对服务列表进行负载均衡算法。
1、在order-service完成服务拉取
- 修改order-service中的url访问路径,用服务名代替ip,端口
String url = "http://userservice/user/"+order.getUserId();
2.在order-service的启动类中的RestTemplate添加负载均衡注解@LoadBalanced
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
2.Ribbon负载均衡
1.负载均衡原理
1.负载均衡流程
2.负载均衡原理
2.负载均衡策略
Ribbon负载均衡的接口是IRule,默认实现是ZoneAvoidanceRule,即根据Zone选择服务列表,然后轮询。
通过定义IRule的实现可以修改负载均衡规则,有两种方式:
- 代码方式,即在消费者order-service的主程序类中,定义一个新的IRule。(全局配置,对所有服务都有效)
/*
修改负载均衡规则为RandomRule
*/
@Bean
public IRule randomRule(){
return new RandomRule();
}
- 配置文件方式,在order-service的配置文件中,添加新的配置(这种方式只对向某个服务发送请求有效)
userservice: #服务提供者的名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则
3. 饥饿加载
Ribbon默认采用懒加载,即第一次访问时才会创建RibbonLoadBalancerClient,请求时间会很长。
而饥饿加载则会在项目启动时就创建,减少第一次访问的时间, 通过添加配置开启饥饿加载。
ribbon:
eager-load:
clients: #指定按饥饿加载的服务名称
-userservice
enabled: true #开启饥饿加载
3.Nacos注册中心
3.1 Nacos服务搭建
3.1.1 Nacos概述
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎度更高
3.1.2 Nacos安装
1、下载安装包
- 在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
- GitHub主页:https://github.com/alibaba/nacos
- GitHub的Release下载页:https://github.com/alibaba/nacos/releases
2、解压( 解压到任意非中文目录下 )
3、端口配置
- Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
- 如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:
4、启动
- 启动非常简单,进入bin目录,然后执行命令即可:windows命令:startup.cmd -m standalone
5、访问
- 在浏览器输入地址:http://127.0.0.1:8848/nacos即可
- 默认的账号和密码都是nacos
3.2 Nacos服务注册和发现
1、引入Nacos依赖
- 在父工程中导入依赖
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.5.RELEASE
pom
import
- 在每个服务模块中导入Nacos依赖(记得先将之前的Eureka客户端依赖注释掉)
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
2、在配置文件中,配置Nacos地址(记得先将之前的Eureka配置注释掉)
spring:
application:
name: orderservice #order服务的名称
cloud:
nacos:
server-addr: localhost:8848 #Nacos服务地址
3.3 Nacos服务分级存储模型
Nacos将多个实例按照地域划分为集群,是为了防止出现服务跨集群调用问题。
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高。
当本地集群不可访问时,再去调用其他集群。
1、Nacos服务分级存储模型
- 一级是服务,例如userservice服务,一个服务有多个实例(实例的端口不同)
- 二级是集群,例如上海集群或杭州集群
- 三级是实例,例如user-service01,user-service02,杭州机房的某台部署了userservice的服务器
2、配置实例的集群属性
- 修改application.yml配置文件,添加配置即可
spring:
application:
name: userservice #user服务的名称
cloud:
nacos:
server-addr: localhost:8848 #Nacos服务地址
discovery:
cluster-name: HZ #配置集群名称,HZ表示杭州
3.4 NacosRule负载均衡
为了让服务消费者orderservice去调用userservice服务时优先调用本地集群的服务,所以需要给消费者也配置集群属性。
1、根据集群设置负载均衡规则 :
- 修改orderservice的配置文件,设置集群属性
spring:
application:
name: orderservice #order服务的名称
cloud:
nacos:
server-addr: localhost:8848 #Nacos服务地址
discovery:
cluster-name: HZ #配置集群名称,HZ表示杭州
- 在orderservice的配置文件中设置负载均衡的Rule为NacosRule,这个规则:优先寻找与自己同集群的服务,然后在集群内做随机调用。
userservice: #服务提供者的名称
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则(根据集群设置)
2、根据权重设置负载均衡:
在实际部署中:服务器设备性能有差异,我们希望性能好的机器承担更多的用户请求。
Nacos提供了权重设置来控制访问频率,权重越大则访问频率越高。
对服务的实例进行权重设置:
- Nacos控制台可以设置实例的权重值,0~1之间
- 同集群内的多个实例,权重越高则被访问的频率越高
- 权重设置为0则完全不会被访问
3.5 Nacos环境隔离
环境隔离-namespace
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离。
1、环境隔离具体步骤:
- 在Nacos控制台可以创建namespace,用来隔离不同环境。
- 修改配置文件,添加命名空间配置
spring:
application:
name: orderservice #order服务的名称
cloud:
nacos:
server-addr: localhost:8848 #Nacos服务地址
discovery:
cluster-name: HZ #配置集群名称,HZ表示杭州
namespace: 7s1982drui2 #配置命名空间,填ID
注意:
- 每个namespace都有唯一的ID
- 不同namespace下的服务不可见(即不可调用)
3.6 Nacos和Eureka的对比
1、Nacos和Eureka的共同点:
- 都支持服务注册和服务发现(拉取)
- 都支持服务提供者做心跳检测
2、Nacos和Eureka的区别
- Nacos支持服务端主动检测提供者的状态:临时实例采用发送心跳检测给服务端;非临时实例采用服务端主动检测模式
- 临时实例心跳不正常时会被剔除,而非临时实例不会。
- Nacos支持服务列表更新时的消息推送模式,即服务端向消费者主动推送消息,服务列表更新更及时。Eureka采用的是定时拉取,即消费者定时向服务端拉取服务列表信息。
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP模式。
4.Nacos配置管理
4.1 统一配置管理
具体流程:
- 在Nacos中添加配置管理文件(配置中心),填写配置
1.
- 配置文件ID:[服务名称]-[环境].yaml
-
分组,默认即可
-
微服务获取Nacos的配置管理文件中的信息(微服务拉取配置)
1.
1.
1. 引入Nacos的配置管理客户端依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
1.
1.
1. 在userservice中的resource目录下创建一个bootstrap.yml文件,这个文件是引导文件,优先级比application.yml高。
2. 在bootstrap.yml中添加配置,需要配置nacos地址,以及服务名称、当前环境、文件后缀名,这些配置决定了程序启动时去Nacos读取哪个配置文件。(注意将之前在application.yml中的相同配置删除掉)
spring:
application:
name: userservice #服务名称
profiles:
active: dev #环境,开发环境
cloud:
nacos:
server-addr: localhost:8848 #nacos地址
config:
file-extension: yaml #文件后缀名
- 当我们更改了Nacos配置管理文件后,微服务可以实现热更新,即无需重启就能感知到。
可以通过两种方式实现:
1.
1.
1. 通过@Value注解注入,然后结合@RefreshScope来刷新
2. 通过@Configurationproperties("前缀名")注解,自动刷新
注意:不是所有的配置都适合放在配置中心,维护起来比较麻烦。所以将一些关键参数,需要运行时调整的参数放到Nacos配置中心,一般都是自定义配置。
- 多环境配置共享,为了使多个环境将来能够共享相同的配置,所以创建一个多环境共享的配置文件,供不同环境下的微服务共享使用。
1.
1.
1. 在Nacos中添加配置管理文件,命名规则:[服务名].yaml
2. 添加共享的配置即可
多种配置文件的优先级:[服务名]-[环境].yaml > [服务名].yaml > 本地配置application.yaml
微服务会从Nacos中读取的配置文件:
1、[服务名]-[环境].yaml 环境配置文件
2、[服务名].yaml 默认配置,即多环境共享配置文件
- Nacos集群搭建
1.
1.
1. 搭建MySQL集群并初始化数据库
2. 下载解压Nacos
3. 修改集群配置(节点信息),数据库配置
4. 分别启动多个Nacos节点
5. nginx反向代理
5.基于Feign远程调用
5.1 Feign介绍
Feign是一个声明式的http客户端,官方地址:GitHub - OpenFeign/feign: Feign makes writing java http clients easier
其作用是帮助我们优雅地实现http请求的发送。
5.2 定义和使用Feign
1、引入依赖
<!-- Feign客户端依赖 -->
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-openfeign</artifactid>
</dependency>
2、在消费者order-service的启动类上添加注解@EnableFeignClients,开启Feign的功能
//开启Feign功能
@EnableFeignClients
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {...}
3、编写Feign客户端接口
@FeignClient("Userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
主要是基于SpringMVC的注解来声明远程调用的信息,如:
- 服务名称:userservice
- 请求路径:/user/{id}
- 请求方式:GET
- 请求参数:Long id
- 返回值类型:User
4、使用Feign客户端接口中定义的方法代替RestTemplate
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
/*@Autowired
private RestTemplate restTemplate;*/
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId){
// 1.查询订单
Order order = orderMapper.findById(orderId);
//2.利用Feign进行远程调用
User user = userClient.findById(order.getUserId());
//3.封装user
order.setUser(user);
//4.返回
return order;
}
}
5.3 Feign自定义配置
自定义Feign的配置有两种方式:
- 配置文件方式,如feign.client.config.xxx.LoggerLevel
*
- 如果xxx是default则代表全局配置
- 如果xxx是服务名,例如userservice代表某服务
- java代码方式,如配置Logger.Level 这个Bean
5.4 Feign性能优化
1、优化Feign的性能主要包括:
-
使用连接池代替默认的URLConnection
-
Feign底层的客户端实现:
*
- URLConnection:默认实现,不支持连接池
- Apache HttpClient:支持连接池
- OKHttp:支持连接池
- 日志级别,最好使用basic或者none
2、Feign性能优化-连接池配置:
- 引入依赖
<!-- httpclient依赖 -->
<dependency>
<groupid>io.github.openfeign</groupid>
<artifactid>feign-httpclient</artifactid>
</dependency>
- 配置连接池
feign:
client:
config:
default: #全局配置
loggerlevel: BASIC
httpclient:
enabled: true #开启
max-connections: 200 #最大连接数
max-connections-per-route: 20 #单个路径的最大连接数
5.5 Feign的最佳实践
1、抽取FeignClient的实现步骤:
- 创建一个module,命名为feign-api,然后引入feign的starter依赖
- 将服务消费者的Feign客户端接口,POJO,Feign默认配置类导入到feign-api中
- 在消费者的pom.xml中引入feign-api依赖
- 修改消费者中service层的import部分,改成导入feign-api中的包
- 重启测试
2、遇到的问题:当定义的FeignClient不在SpringBootApplication的扫描范围内,这些FeignClient无法使用。
方式一:指定FeignClient所在的包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:指定FeignClient所在的类
@EnableFeignClients(clients = {UserClient.class,...})
6.Gateway网关
6.1 网关介绍
1、为什么使用网关
- 对用户请求进行身份验证,权限校验
- 将用户请求路由到微服务,实现负载均衡
- 对用户请求限流
2、网关的技术实现
- gateway
- zuul
6.2 搭建网关服务
1、搭建网关服务的步骤:
- 创建新的module,引入Gateway的依赖和Nacos的客户端依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-gateway
- 配置application.yml,包括服务基本信息,nacos地址,路由信息(路由id,路由目标,路由断言,路由过滤器)
server:
port: 10010 #网关端口
spring:
application:
name: gateway #服务名称
cloud:
nacos:
server-addr: localhost:8848 #nacos地址
gateway:
routes:
- id: user-service #路由id,必须唯一
uri: lb://userservice #路由的目标地址 lb为负载均衡 userservice为服务名
predicates: #路由断言,判断请求是否符合路由规则
- Path=/user/** #路径断言,判断路径是否以/user开头,如果是则符合
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
6.3 路由断言工厂(Route Predicate Factory)
1、作用
我们在配置文件中配置的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为判断条件,对请求做判断
2、Spring提供了11种断言工厂,参照官方文档:Spring提供的断言工厂
6.4 路由过滤器 (GatewayFilter)
1、作用
GatewayFilter可以对进入网关的请求和微服务返回的响应做处理,比如给请求添加请求头。
2、Spring提供了31种路由过滤器工厂,参照官方文档:Spring提供的路由过滤器工厂
配置在单个路由下的过滤器只对当前路由生效,使用defaultFilters对所有路由生效。
6.5 全局过滤器(GlobalFilter)
1、与GatewayFilter的区别:
- GatewayFilter通过配置文件定义的,处理逻辑是固定的。
- GlobalFilter的逻辑需要自己写代码实现。
2、实现步骤
- 实现GlobalFilter接口
- 添加@order()注解或实现ordered接口,用于定义多个过滤器执行的顺序
- 编写处理逻辑
6.6 过滤器链执行顺序
1、原理
- 请求路由后,会将当前路由过滤器,defaultFilter,全局过滤器三类合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
- 路由过滤器,defaultFilter的order由Spring指定,默认按照声明顺序从1递增。
2、执行顺序
- order值越小,优先级越高
- 当order值一样时,执行顺序为:defaultFilter->当前路由过滤器->全局过滤器
6.7 网关的跨域问题处理
1、跨域:域名不一致就是跨域,主要包括:
- 域名不同:www.taobao.com和www.taobao.org和www.jd.com和miaosha.jd.com
- 域名相同,端口不同:localhost:8080和localhost:8081
2、跨域问题:浏览器禁止请求发起者与服务端发生ajax跨域请求,请求被浏览器拦截的问题。
3、解决方案:CORS
网关处理跨域问题采用的也是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 # 这次跨域检测的有效期
7.Docker
7.1 Docker简介
Docker如何解决大型项目依赖关系复杂,不同组件依赖的兼容性问题:
- Docker允许开发中将应用、依赖、库函数、配置一起打包,形成可移植镜像。
- Docker应用运行在容器中,使用沙箱机制,各个应用相互隔离,互不干扰。
Docker如何解决在生产、开发、测试环境中的差异问题:
- Docker镜像中包含完整运行环境,包括库函数,它仅依赖Linux内核,因此可以运行在任意Linux操作系统上
7.2 Docker和虚拟机区别
7.3 Docker架构
1、镜像:将应用、依赖、库函数、配置一起打包,形成镜像。
2、容器:镜像中的应用运行起来的进程就是容器,一个镜像可以运行多个容器。
3、Docker架构(CS架构):
- 服务端:负责接受和处理Docker指令,操作镜像和容器。
- 客户端:通过命令或RestApi向服务端发送指令。
4、DockerHub:Docker镜像的托管平台,即Docker Registry。
7.4 Docker安装
可以在Ubuntu上安装Docker,也可以在Centos上安装(推荐)。我用的是Ubuntu,所以可以百度下参考安装教程,这里就不做过多说明了。
这是我在安装时遇到的问题,可以参照:解决docker出现Error response from daemon:Get https***的问题_Cadence_D的博客-CSDN博客_error response from daemon: get
- systemctl start docker #启动docker
- systemctl stop docker #关闭docker
7.5 Docker基本操作
7.5.1 镜像操作
镜像托管平台:Docker Hub
命令可以参照文档:docker --help
7.5.2 镜像练习
1、去Dockerhub上搜索并拉取一个Redis镜像:
- docker pull redis #拉取最新的redis版本
- docker images #查看镜像
- docker save -o redis.tar redis:latest #将redis镜像打包成压缩包redis.tar
- docker load -i redis.tar #加载redis压缩包
7.5.3 容器操作
7.5.4 容器练习
1、创建并运行一个nginx容器
docker run --name containerName -p 8080:80 -d nginx
- docker run 创建并运行一个docker容器
- --name 给容器命名
- -p 将宿主机端口和容器端口进行映射
- 8080:80 宿主机端口:容器端口
- -d 开启后台运行
- nginx 镜像名称
具体参考文档:查看虚拟机IP地址,如果查看不到IP地址,则将虚拟机网络设置为桥接模式。
2、查看容器日志
docker logs -f 容器名
-f:可持续查看日志
3、查看容器运行状态
docker ps
-a:查看所有状态的容器
3、停止容器
docker stop 容器名
4、删除容器
docker rm 容器名
-f:强制删除
5、进入容器内部,进行文件修改
docker exec -it ng bash
- docker exec 进入容器内部
- -it 在容器内创建一个输入输出,与容器进行交互
- ng 容器名称
- bash 进入容器后执行的命令
访问nginx容器:
7.5.5 数据卷命令
1、数据卷是一个虚拟目录,它指向宿主机文件系统的某个目录。
作用:将容器与数据分离,解耦合,便于修改容器内数据,可复用,保证数据安全。
2、操作数据卷
docker volume [command] command为具体的命令操作
- create 创建一个数据卷
- inspect 查看一个或多个数据卷信息
- ls 列出所有的数据卷
- prune 删除未使用的数据卷
- rm 删除一个或多个指定的数据卷
3、挂载数据卷
在创建一个容器时,使用-v参数将一个数据卷挂载到某个容器目录。
(如果创建容器时数据卷不存在,那么会被自动创建。)
目录挂载(自己管理目录)和数据卷挂载(由docker管理目录)的语法类似:
- -v 宿主机目录:容器内目录
- -v 宿主机文件:容器内文件
7.6 自定义镜像
1、镜像是分层结构,每一层就是一个Layer。
2、Dockerfile就是一个文本文件,其中包含一个个的 指令(Instruction),用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层Layer。
7.7 DockerCompose-快速部署微服务
1、Docker Compose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器
2、Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。
version: "3.8"
services:
mysql: #创建运行一个mysql容器
image: mysql:5.7.25
environment:
MYSQL_ROOT_PASSWORD:123
volumes:
- /tmp/mysql/data:/var/lib/mysql
- /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf
web: #创建运行一个web容器
build: .
ports:
- 8090:8090
DockerCompose的详细语法参考官网: Compose specification | Docker Documentation
8.MQ-消息队列
8.1 MQ概述
1、关于同步调用
- 同步调用的优点:
*
- 时效性较强,可以立即得到结果
- 同步调用的问题:
*
- 耦合度高
- 性能和吞吐能力下降
- 有额外的资源消耗
- 有级联失败问题
2、关于异步调用
- 异步调用常用实现就是事件驱动模式(基于Broker)。
- 异步调用的优势
1.
- 服务解耦
- 性能提升,吞吐量提高
- 服务没有强依赖,不存在级联失败问题
-
流量削峰
-
异步调用的问题
1.
- 依赖于Broker的可靠性,安全性,吞吐能力
- 架构复杂,业务没有明显的流程线,不好追踪管理
3、什么是MQ
MQ(MessageQueue),消息队列,即存放消息的队列。也就是事件驱动架构中的Broker。
8.2 RabbitMQ
8.2.1 概述、安装
RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址: Messaging that just works — RabbitMQ
RabbitMQ中的几个概念:
- Publisher:消息发布者
- consumer:消费者,订阅队列,处理队列中的消息
- channel:操作MQ的工具
- exchange:将消息路由到队列中
- queue:接受并缓存消息
- virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组
8.2.2 常见消息模型
1、基本消息队列
2、工作消息队列
这两种消息模型:同一条消息只能被一个消费者接收。
3、发布订阅,又根据交换机类型不同分为:
- Fanout Exchange 广播
- Direct Exchange 路由
- Topic Exchange 主题
同一条消息可以被多个消费者接收。
基本消息队列的流程:
基本消息队列的消息发送流程:
1.建立connection
2.创建channel
3.利用channel声明队列
4.利用channel向队列发送消息
基本消息队列的消息接收流程:
1.建立connection
2.创建channel
3.利用channel声明队列(避免队列不存在)
4.定义consumer的消费行为handleDelivery()
5.利用channel将消费者与队列绑定
注意:消息一旦消费就会从队列中删除,RabbitMQ没有消息回溯功能。
8.3 SpringAMQP
8.3.1 SpringAMQP介绍
8.3.2 SpringAMQP如何发送消息
- 引入AMQP的starter依赖
- 在application.yml中添加RabbitMQ的配置项
- 利用RabbitTemplate的convertAndSend发送方法
8.3.3 SpringAMQP如何接收消息
- 引入AMQP的starter依赖
- 在application.yml中添加RabbitMQ的配置项
- 定义一个类,并在类上添加@Component注解
- 定义接收消息的方法,并在方法上添加一个监听指定队列的注解@RabbitListener,方法中的参数就是消息
8.3.4 Work Queue工作队列
工作队列:可以提高消息处理速度,避免消息在队列中堆积。
/*
模拟WorkQueue,实现一个队列绑定多个消费者
基本思路如下:
1.在publisher服务中定义测试方法,每秒产生50条消息,发送到simple.queue
2.在consumer服务中定义两个消息监听者,都监听simple.queue队列
3.消费者1每秒处理50条消息,消费者2每秒处理10条消息
*/
RabbitMQ中提供了消息预取机制,即队列中的消息会平均地分发给每个消费者。所以可以修改application.yml中的配置,设置preFetch的值,即消费者预取的消息数量,来控制消息预取的上限。(即实现能者多劳)
8.3.5 发布(Publish)、订阅(Subscribe)
发布订阅模式与之前的队列模型的区别就是允许将同一消息发送给多个消费者。
实现方式是加入了exchange(交换机)。
注意:exchange只负责接收消息和消息路由,而不存储消息,若路由失败则消息丢失。
1、Fanout Exchange(广播)
Fanout Exchange:会将接收到的消息路由到每一个跟它绑定的队列。
1.1 在SpringAMQP中使用Fanout Exchange:
步骤一:在Consumer服务中,创建一个类,添加@Configuration,声明FanoutExchange、Queue、Binding
@Configuration
public class FanoutConfig {
//声明FanoutExchange交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange ("itcast.fanout");
}
//声明第1个队列
@Bean
public Queue fanoutQueue1(){
return new Queue ("fanout.queue1");
}
//绑定队列和交换机
@Bean
public Binding bindingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
//...略,以相同方式声明第2个队列,并完成绑定
}
步骤二:在consumer服务中,编写两个消费方法,分别监听fanoutQueue1和fanoutQueue2
public class ConsumerRabbitListener{
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
system.out.println("消费者1接收到Fanout消息: [" +msg + "]");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
system.out.println("消费者2接收到Fanout消息: [" + msg + "]");
}
}
步骤三:在publisher服务中,编写发送方法,发送消息给交换机
@Test
public void testsendFanoutExchange() {
//交换机名称
String exchangeName = "itcast.fanout";
//消息
String message = "hello,every one !";
//发送消息给交换机
rabbitTemplate.convertAndSend(exchangeName,"",message);
}
2、DirectExchange(路由)
Direct Exchange:会将接收到的消息根据规则路由到指定的Queue。
- 每一个Queue都与Exchange设置一个BindingKey
- 发布者发送消息时,指定消息的RoutingKey
- Exchange将消息路由到BindingKey与消息RoutingKey一致的队列
注意:每一个Queue可以和Exchange设置多个BindingKey(类似于广播)。
2.1 在SpringAMQP中使用Direct Exchange:
步骤一:利用@RabbitListener注解声明Exchange、Queue、RoutingKey。
@RabbitListener(bindings = @QueueBinding (
//声明队列名称
value = @Queue(name = "direct.queue1"),
//声明交换机名称和类型
exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
//声明队列和交换机绑定的Bindingkey
key = { "red","blue"}
))
public void listenDirectQueue1(String msg){
system.out.println("消费者接收到direct.queue1的消息:[" + msg + "]");
}
/* 队列2的声明和绑定方式同理*/
步骤二:在publisher服务中,编写发送方法,发送消息给交换机,并指定RoutingKey。
@Test
public void testsendDirectExchange() {
//交换机名称
String exchangeName = "itcast.direct";
//消息
String message = "hello, blue!";
//发送消息给交换机,并指定RoutingKey
rabbitTemplate.convertAndSend(exchangeName,"blue",message);
}
3、TopicExchange(主题)
TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以 . 分割。
Queue与Exchange指定BindingKey时可以使用通配符:#,*
3.1 在SpringAMQP中使用Topic Exchange:
步骤一:利用@RabbitListener注解声明Exchange、Queue、RoutingKey。
/*队列1*/
@RabbitListener(bindings = @QueueBinding (
//声明队列名称
value = @Queue(name = "topic.queue1"),
//声明交换机名称和类型
exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
//声明队列和交换机绑定的Bindingkey
key = "china.#"
))
public void listenTopicQueue1(String msg){
system.out.println("消费者接收到topic.queue1的消息:[" + msg + "]");
}
/* 队列2*/
@RabbitListener(bindings = @QueueBinding (
value = @Queue(name = "topic.queue2"),
exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue1(String msg){
system.out.println("消费者接收到topic.queue2的消息:[" + msg + "]");
}
步骤二:在publisher服务中,编写发送方法,发送消息给交换机,并指定RoutingKey。
@Test
public void testsendTopicExchange() {
//交换机名称
String exchangeName = "itcast.topic";
//消息
String message = "hello, topicExchange,为您播报最新消息!";
//发送消息给交换机,并指定RoutingKey
rabbitTemplate.convertAndSend(exchangeName,"china.news",message);
}
8.3.6 SpringAMQP-消息转换器
1、在SpringAMQP的发送方法中,接收消息的类型是Object,也就是说我们可以发送任意对象类型的消息,最后
SpringAMQP会帮我们序列化为字节后发送。
2、Spring对消息对象的处理是由org.springframework.amqp.support.converter.MessageConverter来处理的。而默认实现是SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。
如果要修改则只需要定义一个MessageConverter类型的Bean即可。推荐用JSON方式序列化/反序列化,步骤如下:
(注意:消息发送者和接收者必须使用相同的MessageConverter)
- 在项目中引入jackson的依赖:
com.fasterxml.jackson.dataformat
jackson-dataformat-xml
2.9.10
- 在服务的配置类中声明MessageConverter:
@Bean
public MessageConverter jsonMessageconverter(){
return new Jackson2JsonMessageconverter();
}
- 在消费者服务中,编写消费方法,指定监听的队列,方法参数就是消息。
@RabbitListener(queues = "object.queue")
public void listenObjectQueue(Map msg) {
system.out.println("收到消息:[" +msg + "]");
}
9.ES-分布式搜索引擎
9.1 Elasticsearch概述
9.1.1 初识Elasticsearch
1、什么是Elasticsearch
1、elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。并且支持分布式,提供Restful接口,可以被任何语言调用。
2、elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
2、正向索引和倒排索引
- 传统数据库(如MySQL)采用正向索引:
- elasticsearch采用倒排索引:
*
- 文档(document):每一条数据就是一个文档。
- 词条(term):文档按照语义分成的词语。(词条不能重复)
原理:在存储数据时,如果词条重复,则记录对应的文档ID即可。
查询流程:
3、ES和MySQL的对比
- 文档
1.
- elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。
- 文档数据会被序列化为json格式后存储在elasticsearch中。
- 索引
1.
- 索引 ( index):相同类型的文档的集合。
- 映射( mapping):索引中文档的字段约束信息,比如字段名称、类型,类似表的结构约束。
- 对比
- 架构
*
- Mysql:擅长事务类型操作,可以确保数据的安全和一致性。
- Elasticsearch:擅长海量数据的搜索、分析、计算。
9.1.2 安装Elasticsearch
安装Elasticsearch包括:部署es、部署kibana、以及安装IK分词器。
9.1.3 操作索引库
- mapping映射属性
mapping是对索引库中文档的约束。常见的mapping属性包括:
- type:字段数据类型,常见的简单类型有:
*
- 字符串:text(可分词的文本)、keyword(精确值,如国家、地址、品牌,不可分词)
- 数值:long,integer,short,float,double
- 布尔:boolean
- 日期:data
- 对象:object
- index:是否创建索引,默认为true
- analyzer:使用哪种分词器
- properties:该字段的子字段
2.创建索引库
ES中通过Restful请求操作索引库、文档。请求内容用DSL语句来表示。创建索引库和mapping的DSL语法如下:
3.查看、操作索引库
- 查看索引库
GET /索引库名称
- 删除索引库
DELETE /索引库名称
- 修改索引库,索引库和mapping一旦创建无法修改,但是可以添加新的字段,语法如下:
9.1.4 文档操作
1.新增、查询、删除文档
- 新增文档
- 查询文档
GET /索引库名/_doc/文档id
- 删除文档
DELETE /索引库名/_doc/文档id
2.修改文档
- 全量修改:会删除旧文档,添加新文档(修改多个字段)
- 增量修改:修改指定字段值
9.1.5 RestClient操作
1.什么是RestClient
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http请求发送给ES。官方文档地址: Elasticsearch Clients | Elastic
2.RestClient操作索引库
整体流程:
- 编写DSL语句:创建索引库,定义mapping属性
1.
- 字段拷贝:即可以输入多个字段的值进行查询,例如输入name和brand的值查询酒店。
- 初始化JavaRestClient
1.
- 引入RestHighLevelClient依赖
org.elasticsearch.client
elasticsearch-rest-high-level-client
1.
- 因为SpringBoot默认的ES版本是7.6.2,所以需要覆盖默认的ES版本
1.8
7.12.1
1.
- 初始化RestHighLevelClient
RestHighLevelClient client = new RestHighLevelClient(Restclient.builder(
HttpHost.create("http://192.168.150.101:9200")); //初始化客户端的地址
- 创建索引库(DSL语句建议单独创建一个类中进行定义)
·
- 删除索引库
- 判断索引库是否存在
3.RestClient操作文档
整体流程:
- 初始化JavaRestClient
1.
- 引入RestHighLevelClient依赖
org.elasticsearch.client
elasticsearch-rest-high-level-client
1.
- 因为SpringBoot默认的ES版本是7.6.2,所以需要覆盖默认的ES版本
1.8
7.12.1
1.
- 初始化RestHighLevelClient
RestHighLevelClient client = new RestHighLevelClient(Restclient.builder(
HttpHost.create("http://192.168.150.101:9200")); //初始化客户端的地址
- 利用RestClient新增酒店数据
- 利用RestClient根据ID查询酒店数据
- 利用RestClient根据ID修改酒店数据
- 利用RestClient根据ID删除酒店数据
- 利用RestClient批量添加酒店数据到ES
9.1.6 DSL查询语句
1.常见的查询类型:
- 查询所有:查询出所有数据,一般测试用。例如:match_all
-
全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
-
match_query
-
multi_match_query
-
精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
-
ids
- range
-
term
-
地理(geo)查询:根据经纬度查询。例如∶
-
geo_distance
-
geo_bounding_box
-
复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
-
bool
- function_score
2.查询的基本语法:
3.全文检索查询
1、对输入的内容进行分词查询,常用于搜索框查询。
- match查询,查询一个字段(推荐使用)
- multi_match查询,同时查询多个字段
4.精确查询
1、精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有:
- term:根据词条精确值查询
- range:根据值的范围查询
5.地理查询
1、根据经纬度查询
- geo_bounding_box:查询geo_point值落在某个矩形范围的所有文档
- geo_distance:查询到指定中心点小于某个距离值的所有文档(推荐使用)
5.复合查询
复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑,例如:
- fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名。例如百度竞价
- 使用FunctionScoreQuery,可以修改文档的相关性算分(query score),根据新得到的算分排序。
案例:
-
BooleanQuery:布尔查询是一个或多个查询子句的组合。子查询的组合方式有:
-
must:必须匹配每个子查询,类似"与"
- should:选择性匹配子查询,类似"或"
- must_not:必须不匹配,不参与算分,类似"非"
- filter:必须匹配,不参与算分
案例:
9.1.7 搜索结果处理
1.排序
elasticsearch支持对搜索结果排序,默认是根据相关度算分(_score)来排序。可以排序字段类型有: keyword类型数值类型、地理坐标类型、日期类型等。
2.分页
elasticsearch默认情况下只返回top10的数据,而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:
3.高亮
高亮:就是在搜索结果中把搜索关键字突出显示。原理:将搜索结果中的关键字用标签标记出来,然后在页面中给标签添加css样式。
总结:搜索结果处理整体语法:
9.1.8 RestClient查询文档
查询的基本条件:
1.创建SearchRequest对象
2.准备Request.source().query()方法,里边使用QueryBuilders来构建查询条件
3.发送请求,得到结果
4.解析结果(参考]SON结果,从外到内,逐层解析)
1.查询类型
- match查询(内容分词查询)
- 精确查询(term查询、range查询)
- 复合查询-boolean查询
2.查询结果处理
- 排序和分页
- 高亮
Original: https://blog.csdn.net/qq_45813393/article/details/126669415
Author: 胡英俊学Java
Title: Springcloud个人总结