当前位置:   article > 正文

SpringCloud学习笔记,课程源自黑马程序员,笔记持续更新中..._黑马springcloud笔记

黑马springcloud笔记

@SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式:

1.服务拆分-服务远程调用:

微服务根据业务模块拆分,做到单一职责,这时一个业务模块对应一个数据库,当我订单需要查询是对应哪个用户,就需要跨库查询,那么该如何实现跨库查询呢?

在这里插入图片描述
在这里插入图片描述
先确立思路,在order模块中的pojo实体类封装了userId和user,可以通过userId到数据库中查询到该user的数据并封装,最后返回该订单信息

实现步骤:
第一步:创建RestTemplate并注入Spring容器

    /**
     * 创建RestTemplate并注入String容器
     */
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

代码位置:写在order模块的主类中
在这里插入图片描述
第二步:在order的service层中,找到查询的方法,利用restTemplate的getForObject(url,User.class)获得User
getForObject:
参数一:传递地址,查询的请求,查询到该用户
在这里插入图片描述
参数二:传递返回值对象的字节码

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);

        // 2.利用RestTemplate发送http请求,查询用户
        // 2.1.url路径
        String url = "http://localhost:8081/user/" + order.getUserId();

        // 2.2.发送http请求,实现远程调用
        User user = restTemplate.getForObject(url, User.class);

        // 3.封装user到Order
        order.setUser(user);

        // 4.返回
        return order;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

两步轻松搞定
重点在于restTemplate的getForObject方法
在这里插入图片描述


2.搭建eureka服务:

以上只是一个基础,明白一个概念,如果user服务增多,在restTemplate.getForObject的url中写死地址就不管用了

在这里插入图片描述
这时候就需要引入eureka,将微服务的信息注册进来,登记在eureka,eureka本身也是一个微服务也就会将自己注册进来。
每三十秒会发送一次注册信息,检查服务是否挂了。
在这里插入图片描述
搭建EurekaServer一共三步
第一步:创建新模块EurekaServer,引入eureka-server依赖

<dependencies>
    <!--eureka服务端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
</dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述

第二步:添加@EnableEurekaServer 注解

@EnableEurekaServer // 自动装配
@SpringBootApplication
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class,args);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述
第三步:在eureka的application.yml中配置eureka地址

server:
  port: 10086 # 服务端口

spring:
  application:
    name: eurekaserver #  eureka的服务名称

# eureka本身也是微服务,也会将自己注册到微服务上
eureka:
  client:
    service-url:
      # eureka的地址信息,有多个用逗号隔开,将来可能有多个eureka,组成eureka集群
      defaultZone: http://127.0.0.1:10086/eureka
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述

2.1.eureka服务注册-client

在我们的项目结构中,eureka是服务端,导入的依赖就是server,那么有服务端必然有客户端,其他的微服务就是一个个的客户端,所以导入的依赖是client,注意这个区别。
在这里插入图片描述
实现步骤,一共两步,和服务端相比只是少加一个注释
第一步:在user-service的pom.xml导入客户端的依赖

<!--eureka客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

第二步:在user-service微服务的application.yml中配置user-service的地址,第二步和前面配置eureka写的内容区别只是在于修改服务名称

#这边可能有同学复制粘贴后发现爆红,原因是yml中不能出现两段spring,合并他们就好了
spring:
   application:
     name: userservice #  userservice的服务名称
# 注册服务到eureka上
eureka:
  client:
    service-url:
      # eureka的地址信息,这边写的是将信息注册到哪?注册到 eureka 上
      defaultZone: http://127.0.0.1:10086/eureka
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

实现结果
这三服务我都写完后,重启这三个服务,访问eureka可以看到注册进来的服务。
在这里插入图片描述
在这里插入图片描述
过了三十秒后,我重新访问eureka,可以看到只有三条服务了。
在这里插入图片描述
模拟启动两个user-service:

第一步,选中服务复制配置
在这里插入图片描述
第二步:写上-Dserver.port=8082,注意避开端口冲突
在这里插入图片描述
第三步:多出了一个服务,启动它
在这里插入图片描述
第四步:重新访问eureka,可以看到user-service有两个端口了
在这里插入图片描述
小结:从大观上看,可以想象成一共就是两个微服务,eureka作为服务端,其他微服务都作为客户端,将自己的信息注册到eureka服务端上。同时,eureka本身也是一个微服务,所以也需要将自己的信息登记到eureka上。
实际操作的时候,eureka搭建完后,只需要重复2.1.小节的两个步骤。
能够理解并操作起来,恭喜你已经学会服务注册了!

2.2.eureka服务发现-服务拉取

第一步:修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口,不再写死代码。

// 2.1.url路径
String url = "http://userservice/user/" + order.getUserId();
  • 1
  • 2

原:参考第一节

// 2.1.url路径
String url = "http://localhost:8081/user/" + order.getUserId();
  • 1
  • 2

防止同学们看不懂或者忘记了,我贴出完整代码吧。注意是order-service的service层,是order去拉取user,userservice对应的是user-service的yml文件中spring.application.name服务名称

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);

        // 2.利用RestTemplate发送http请求,查询用户
        // 2.1.url路径
        String url = "http://userservice/user/" + order.getUserId();

        // 2.2.发送http请求,实现远程调用
        User user = restTemplate.getForObject(url, User.class);

        // 3.封装user到Order
        order.setUser(user);

        // 4.返回
        return order;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

第二步:在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:@LoadBalanced

@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    /**
     * 创建RestTemplate并注入String容器
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

学会后简直不要太爽!访问了两次不同的id,两个user-service都有查询,实现了负载均衡。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
战术小结
1.修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口。
2.在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:@LoadBalanced
不再写死地址,简单一句话:通过服务名访问目标服务。写上一个负载均衡的注解,即可实现负载均衡,如果有同学想专研什么是负载均衡,可以自行查找资料,本章只说简单明了的使用。
一共就两个步骤,轻松掌握!

第二节总结:

1.搭建EurekaServer

  • 引入eureka-server依赖
  • 添加@EnableEurekaServer // 自动装配
  • 在EurekaServer的application.yml中配置eureka地址

2.服务注册

  • 引入eureka-client依赖
  • 在其他微服务的application.yml中配置eureka地址

3.服务发现

  • 引入eureka-client依赖
  • 在其他微服务的application.yml中配置eureka地址(实际重复2,给每个微服务配置eureka地址)
  • 给RestTemplate添加@LoadBalancesd注解
  • 用服务提供者的服务名称远程调用(服务名称就是application.yml配置中的spring.application.name服务名称)再给大伙展示一次,加深记忆
spring:
  # 实际中大家肯定也有这些数据库的连接配置
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: 
    driver-class-name: com.mysql.jdbc.Driver
  #今天重点在这
  application:
    name: orderservice #  orderservice的服务名称
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.Ribbon负载均衡

削微写一下个人理解:Ribbon是负责微服务之间调用的负载均衡。后面学习的网关,它也有负载均衡,但他是对外的。

下一章的4.基于Feign远程调用提到的feign,它里面就集成了负载均衡,所以理解它就行,而且负载均衡也不需要涉及到底层代码,想了解的可以自行研究(注:快速了解Spring Cloud和笔记才是本文的作用)。

负载均衡前面的理论大家可以去黑马程序员的视频中了解吧,实际上也不会用到随机分配的形式,这里我稍微记录一下修改负载均衡规则的两种方式:
1.代码方式:在order-service中的OrderApplication类中,定义一个新的IRule
这种方式的服务范围是全体。

@Bean
public IRule randomRule() {
    return new RandomRule();
}
  • 1
  • 2
  • 3
  • 4

这里图片中停用,只是因为两种方式二选一,我选择了配置的方式,使用并没有任何问题。图片只是给大伙看一个结构和写的地方。
在这里插入图片描述
2.配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则。
这种方式只针对某个服务而言

userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule  
    # 配置负载均衡规则,写上全限定类名
  • 1
  • 2
  • 3
  • 4

3.1饥饿加载

Ribbon默认采用的是懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

# 还是在Order-service,只是举例,比如订单要拉取用户信息,所以修改订单服务的配置
ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    clients: # 指定饥饿加载的服务名称,clients是一个集合,可以指定多个服务
      - userservice
      # - xxxservice
      # - ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4.基于Feign远程调用

http客户端Feign
使用Feign的步骤如下三步:(依旧以order和user举例)
第一步:引入依赖。
找到order的pom.xml文件

<!--feign客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

第二步:在order-service的启动类添加注解@EnableFeignClients开启Feign的功能。

@SpringBootApplication
//添加这个注解,与前面的代码变化不大,复制注解即可,其他的无需多余复制
@EnableFeignClients  
public class OrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

第三步:声明一个远程调用,编写Feign客户端:
主要是基于SpirngMVC的注解来声明远程调用的信息,比如:

  • 服务名称:userservice
  • 请求方式:GET
  • 请求路径:/user/{id}
  • 请求参数:Long id
  • 返回值类型:User

创建客户端,做接口声明在这里插入图片描述
并添加@FeignClient注解,声明是user的微服务名称,并写上返回值的方法

//路径
@FeignClient("userservice")
public interface UserClient {
	//返回值
	@GetMapping("/user/{id}")
    User findUserById (@PathVariable("id") Long id);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

对照以前的代码

//路径
String url = "http://userservice/user/" + order.getUserId();
//返回值
User user = restTemplate.getForObject(url, User.class);
  • 1
  • 2
  • 3
  • 4

一一对应
在这里插入图片描述
接下来改造order的service层以前的代码

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);

        // 2.利用Feign远程调用
        User user = userClient.findUserById(order.getUserId());

        // 3.封装user到Order
        order.setUser(user);

        // 4.返回
        return order;
    }

    /*@Autowired
    private RestTemplate restTemplate;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);

        // 2.利用RestTemplate发送http请求,查询用户
        // 2.1.url路径
        String url = "http://userservice/user/" + order.getUserId();

        // 2.2.发送http请求,实现远程调用
        User user = restTemplate.getForObject(url, User.class);

        // 3.封装user到Order
        order.setUser(user);

        // 4.返回
        return order;
    }*/
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

对比
原先的代码
在这里插入图片描述
修改后
在这里插入图片描述
好处:不在代码中写url,url后面的参数可能会有很多,在代码中实现会有一长串的代码,把原来代码中的url放到接口中管理。
而且Feign集成了负载均衡的功能

战术小结
Feign的使用步骤

  1. 引入依赖
  2. 在主类上添加@EnableFeignClients注解
  3. 编写FeignClient接口
  4. 使用FeignClient中定义的方法代替RestTemplate

PS:以上讲解都是对消费者的操作,
比如user为order提供数据,就是提供者,
order拉取user的数据,就是消费者。

4.1.Feign自定义配置

配置Feign日志有两种方式:
方式一:配置文件方式

  1. 全局生效
feign:
  client:
    config:
      default: # 这里用default就是全局配置,如果写服务名称,则只针对某个微服务的配置
        loggerLevel: FULL # 日志级别
  • 1
  • 2
  • 3
  • 4
  • 5
  1. 局部生效
feign:
  client:
    config:
      userservice: # 这里用default就是全局配置,如果写服务名称,则只针对某个微服务的配置
        loggerLevel: FULL # 日志级别
  • 1
  • 2
  • 3
  • 4
  • 5

在控制台中,查询语句后就有了访问的日志
在这里插入图片描述
方式二:Java代码方式,需要声明一个Bean
在这里插入图片描述
还没加注解,所以还没生效,注解有两种方式

public class DefaultFeignConfiguration {
    @Bean
    public Logger.Level logLevel(){
        return Logger.Level.BASIC;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

主类,这是第一种注解配置,全局配置,放到@EnableFeignClients注解

@SpringBootApplication
//配置defaultConfiguration 这样就是全局有效了,DefaultFeignConfiguration是自定义的类,传递它的字节码
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    /**
     * 创建RestTemplate并注入String容器
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

第二种配置,局部配置,是放到@FeignClient注解

// 注意DefaultFeignConfiguration是自己定义的Bean,不要硬记
@FeignClient(value = "userservice",configuration = DefaultFeignConfiguration.class)
public interface UserClient {

    @GetMapping("/user/{id}")
    User findUserById (@PathVariable("id") Long id);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

战术小结
Feign日志配置:
1.方式一是配置文件,feign.client.config.xxx.loggerLevel
– 如果xxx是default则代表全局
– 如果xxx是服务名称,例如userservice则代表某服务

2.方式二是Java代码配置Logger.Level这个Bean
– 如果在@EnableFeignClients注释声明则代表全局
– 如果在@FeignClient注释中声明则代表某服务

4.2.Feign性能调优

Feign底层的客户端实现:
URLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池

因此优化Feign性能主要包括
1.使用连接池代替默认的URLConnection
2.日志级别,最好用basic或none(别用日志当然是最好的)

实现方式:
Feign的性能优化-连接池配置
引入依赖:

<!--引入HttpClient依赖-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

配置连接池,使用HttpClient或者OKHttp代替URLConnection
(这个不用我说应该知道放哪了吧)

feign:
  httpclient:
    enabled: true # 支持HttpClient的开关
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 单个路径的最大连接数
  • 1
  • 2
  • 3
  • 4
  • 5

就这两个内容了:
真正业务中需要对max-connections和max-connections-per-route进行压测,设置多少比较合适。
日志参考4.1.Feign自定义配置的内容

4.3.Feign的最佳实践

什么是最佳实践,简单来讲就是企业在使用一个东西的过程中,各种踩坑,最后总结出来一个相对比较好的一个使用方式,这就是最佳实践了。

方式一(继承)︰给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
(这句话听起来已经不是非常抽象了,是十分抽象,首先清楚什么是消费者什么是提供者,可以方便阅读一些,该项目结构需结合本章内容,仅供参考,因为我的order去拉取user的数据,所以order为消费者,另一方就是提供者)
在这里插入图片描述
order中的UserClient和user中的UserController有着相似的地方,一样的路径和参数,那么就可以做一个提取,中间做一个UserAPI接口作为规范,两边同时实现这个接口,但是有一个缺点是紧耦合,从图-4.3.3可以看出,虽然是紧耦合且官方也不推荐这种方式,但企业中还是经常使用这种方式。
在这里插入图片描述
图-4.3.1
在这里插入图片描述
图-4.3.2
在这里插入图片描述
图-4.3.3

方式二(抽取) ∶将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
在这里插入图片描述
但坏处依然存在,没有一个完美的解决方案,比如接口中有ABCD四个方法,order只需要AB两个方法,但是CD也会被同时引用进来,就有些多余了。

实现方式二 :(方式一没讲)
实现最佳实践方式二的步骤如下:
1.首先创建一个module,命名为feign-api,然后引入feign的starter依赖;
2.将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中;
3.在order-service中引入feign-api的依赖;
4.修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包;(因为全都移走了,所以要重新导入feign-api中的包)
5.重启测试。

第一步:新建module,选择maven,命名为feign-api,然后引入feign的starter依赖;
在这里插入图片描述
引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

第二步:将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中;

创建好路径
在这里插入图片描述
剪切图片中三个包到新的路径下
在这里插入图片描述
feign中
在这里插入图片描述
order肯定会报错,pom.xml重新引入feign的统一api就行了
第三步:在order-service中引入feign-api的依赖;
在这里插入图片描述
引入feign的统一api,注意这一段是根据新建的feign-api写

<!--引入feign的统一api-->
<dependency>
    <groupId>cn.itcast.demo</groupId>
    <artifactId>feign-api</artifactId>
    <version>1.0</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里插入图片描述
第四步:修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包;(因为全都移走了,所以要重新导入feign-api中的包)
回到报错的地方,alt+Enter,重新导入包
在这里插入图片描述
在这里插入图片描述
完成结果:
在这里插入图片描述
在这里插入图片描述
第五步:重启测试
这时候启动失败
在这里插入图片描述
报错原因是:OrderService中的UserClient所在的包变成feign的包,默认扫描的包是order包,所以扫描不到
在这里插入图片描述
在这里插入图片描述

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:方式一是全拿来,方式二指定哪一个
在这里插入图片描述
在这里插入图片描述
写上解决问题

@EnableFeignClients(clients = UserClient.class,defaultConfiguration = DefaultFeignConfiguration.class)
  • 1

在这里插入图片描述
总结
不同包的FeignClient的导入有两种方式:
1.在@EnableFeignClients注解中添加basePackages,指定FeignClient所在的包
2.在@EnableFeignClients注解中添加clients,指定具体FeignClient的字节码


5.Geteway网关

为什么需要网关?
所有的微服务暴露在外面,任何人都可以发请求访问,是不是有些不安全呢?所以就需要网关把守。
(补充:注册中心的负载均衡是微服务和微服务之间的调用,注册中心是对内做的,网关是对外做的)
在这里插入图片描述
请求限流:比如动物园访问人数可以承受5000人,这时候来了20000人,先进去一部分人,其他人外面排队等着,等里面的人出来了,排队的人再进去。

在spring cloud中网关的实现包括两种:(技术栈)

  • gateway(这是比较新的)
  • zuul(这是早期就有的)

Zuul是基于Servlet的实现,属于阻塞式编程。
SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

战术小结
网关的作用:
·对用户请求做身份认证、权限校验
·将用户请求路由到微服务,并实现负载均衡
·对用户请求做限流

5.1.Gateway网关-快速入门

搭建网关服务步骤:
1.创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖

<!--网关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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

由于我公司中没有使用到nacos,我暂时跳过了nacos部分,所以我自己尝试将nacos换成eureka。
理论上是gateway也是一个微服务,需要注册到nacos,实现服务注册和发现,所以我现在使用eureka,那就注册到eureka,没什么问题,主要是自己对于这个理解吧,本节主要还是学习掌握gateway。
Spring Cloud生态需要解决的有四个问题:
1.api网关
2.通信
3.服务注册和发现
4.熔断机制
无论使用什么技术,只要能够解决以上四个问题即可,至于哪个性能更好就是另外的问题了。
更有一些公司使用了K8S,里面集成了服务注册和发现。

<!--eureka客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

2.编写路由配置,application.yml配置上eureka

server:
  port: 10010

spring:
  application:
    name: gateway #  gateway的服务名称
  cloud:
#    nacos:
#      server-addr: nacos:8848 # nacos地址
    gateway:
      routes:
        - id: user-service # 路由标示,必须唯一
          uri: lb://userservice # 路由的目标地址
          predicates: # 路由断言,判断请求是否符合规则
            - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
        - id: order-service
          uri: lb://orderservice
          predicates:
            - Path=/order/**
      default-filters:
        - AddRequestHeader=Truth,Itcast is freaking awesome!

eureka:
  client:
    service-url:
      # eureka的地址信息,有多个用逗号隔开,将来可能有多个eureka,组成eureka集群
      defaultZone: http://127.0.0.1:10086/eureka
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

在这里插入图片描述
gateway配置参考图

断言:是编程术语,表示为一些布尔表达式。
uri:使用服务名称的时候就不是写http了,是写lb
lb:代表LoadBanlance
predicates:路由断言

3.创建Gateway启动类
当然创建module选择的是spring boot这步省了,选择maven就自己创建吧,写多了加深印象
在这里插入图片描述
启动类

@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class,args);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

启动运行,没问题,nacos或者eureka可以任自己选择,由于我对nacos了解的不多,所以我不好下定论。
在这里插入图片描述
在这里插入图片描述
一个运行的流程

1.微服务都会注册到注册中心;
2.由用户发起一个请求,假设请求是http://localhost:10010/user/1,网关是10010端口,所以请求会进入这个网关;
3.网关无法处理这个业务,只能根据路由规则去做判断,在配置文件中我定义了两个规则,/user/** 和 /order/** ,user开头的带到userservice,order开头的带到orderservice。按照前面用户发送的请求,所以会带到userservice;
4.网关自然会拿着userservice去注册中心里找到对应的地址;
5.最后就是做负载均衡,挑一个user服务。

战术小结
网关搭建步骤:
1.创建项目,引入nacos服务发现和gateway依赖
2.配置application.yml,包括服务基本信息、nacos地址、路由

路由配置包括:
1.路由id:路由的唯一标示
2.路由目标(uri) :路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
3.路由断言(predicates) :判断路由的规则,
4.路由过滤器(filters) :对请求或响应做处理(刚刚没有提到,配置中也是可以配的,后续会有说明)

5.2.Gateway网关-路由断言工厂

·我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件;
·例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的;
·像这样的断言工厂在SpringCloudGateway还有十几个(每个都有自己判断的规则和条件,参考下图)
在这里插入图片描述(上一节使用的就是Path)
可以参考官方文档,里面带有配置的示例,点击跳转官方文档
在这里插入图片描述
有时间可以自己玩一玩,照着官方文档抄就完事了

本节小结
PredicateFactory的作用是什么?
读取用户定义的断言条件,对请求做出判断
Path=/user/**是什么含义?
路径是以/user开头的就认为是符合的

5.3.Gateway网关-路由的过滤器配置

GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
在这里插入图片描述
期间做了什么处理呢?
官方31种过滤器文档(点击跳转),不用全记,看名字其实就可以看出是什么,都有语法和说明
在这里插入图片描述
实现方式 :在gateway中修改application.yml文件,给userservice的路由添加过滤器:

spring:
  application:
    name: gateway #  gateway的服务名称
  cloud:
    gateway:
      routes:
        - id: user-service # 路由标示,必须唯一
          uri: lb://userservice # 路由的目标地址
          predicates: # 路由断言,判断请求是否符合规则
            - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
          filters:
            - AddRequestHeader=iskey,isValue # 添加请求头,放上键和值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述
稍微修改一下user的Controller,获取请求头的参数,required设置可以不传参数,打印出值

public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 路径: /user/110
     *
     * @param id 用户id
     * @return 用户
     */
    @GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id,
                          //获取请求头的参数,required设置可以不传参数
                          @RequestHeader(value = "iskey",required = false) String iskey) {
        System.out.println(iskey);
        return userService.queryById(id);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

重启网关看结果,输出的值是正确的
在这里插入图片描述
添加全局过滤器,default-filters默认过滤器

spring:
  application:
    name: gateway #  gateway的服务名称
  cloud:
    gateway:
      routes:
        - id: user-service # 路由标示,必须唯一
          uri: lb://userservice # 路由的目标地址
          predicates: # 路由断言,判断请求是否符合规则
            - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
        - id: order-service
          uri: lb://orderservice
          predicates:
              - Path=/order/**
      default-filters: # 给全局添加过滤器,默认过滤器,会对所有路由请求都生效
        - AddRequestHeader=iskey,isValue # 添加请求头,键为iskey,值为isValue
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

本节小结
过滤器的作用是什么?
1.对路由的请求或响应做加工处理,比如添加请求头
2.配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用是什么?
1.对所有路由都生效的过滤器

5.4.Gateway网关-全局过滤器

全局过滤器GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。
而GlobalFilter的逻辑需要自己写代码实现。
定义方式实现 GlobalFilter接口。
在这里插入图片描述
这个接口只有一个方法,filter过滤
参数
exchange:请求上下文,从请求进入网关开始,一直到结束为止,整个流程过程中都可以共享exchange对象,这个对象可以拿到请求相关的信息、响应相关的信息,也可以往里存东西取一个东西都可以。
chian:是过滤器链,除了这个过滤器还有其它过滤器,就是放行,调用这个过滤器链,让它往后走,等于这里过滤器处理完了,要交给下一个过滤器处理了。

返回值
Mono:

案例
在这里插入图片描述
1.在gateway服务创建AuthorizeFillter
在这里插入图片描述
2.实现GlobalFilter接口,重写filter方法

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.lang.annotation.Annotation;

//@Order(-1) // 过滤器链执行的顺序,值越小,优先度越高,和Ordered接口二选一
@Component // 注入到容器
public class AuthorizeFillter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        ServerHttpRequest request = exchange.getRequest(); // 获取请求
        MultiValueMap<String, String> params = request.getQueryParams(); // 获取参数

        // 2.获取参数中的 authorization 参数
        String auth = params.getFirst("authorization"); // 获取第一个匹配的参数
        // 3.判断参数值是否等于 admin
        if("admin".equals(auth)){
            // 4.是,放行
            return chain.filter(exchange);
        }
        // 5.否,拦截
        // 5.1.设置状态码,UNAUTHORIZED未认证
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        // 5.2.拦截请求
        return exchange.getResponse().setComplete();
    }
    
    @Override
    public int getOrder() {
        return -1;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

运行查看结果
在这里插入图片描述
加上参数,访问成功
在这里插入图片描述
本节小结
全局过滤器的作用是什么?
对所有路由都生效的过滤器,并且可以自定义处理逻辑

实现全局过滤器的步骤?
1.实现GlobalFilter接口
2.添加@Order注解或实现Ordered接口,设置过滤器链执行顺序
3.编写处理逻辑

5.5.Gateway网关-过滤器链执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
这三个过滤器的执行顺序会是什么样呢?(可以直接看小结)
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
在这里插入图片描述
·每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
·GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
·路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
·当过滤器的order值一样时,会按照defaultFilter >路由过滤器>GlobalFilter的顺序执行。
在这里插入图片描述
本节小结
路由过滤器、defaultFilter、全局过滤器的执行顺序?
1.order值越小,优先级越高
2.当order值一样时,顺序是defaultFilter最先,然后是局
部的路由过滤器,最后是全局过滤器

5.6.Gateway网关-网关的cors跨域配置

跨域问题处理
跨域:域名不一致就是跨域,主要包括:

  • 域名不同: www.taobao.com和 www.taobao.org和www.jd.com和miaosha.jd.com
  • 域名相同,端口不同:localhost:8080和localhost8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题。
重点:“浏览器” 禁止跨域 “ajax”请求
所以orderservice、userservice和浏览器、Ajax没有任何关系,不会有跨域问题。
浏览器禁止跨域手段是拦截响应,实际上请求还是会发送到服务器。
解决方案:CORS

网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现:
在这里插入图片描述
配置yml

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 # 这次跨域检测的有效期
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

本章小结
不需要记,复制粘贴,只要会改配置文件就行了


6.服务异步通讯-MQ

本节内容:

  • 初始MQ
  • RabbitMQ快速入门
  • SpringAMQP

6.1.初始MQ-同步通讯的优缺点

企业开发中有一种神奇的生物存在,就是产品经理,ta会不停地提出需求添加业务,这时候同步处理每个服务,处理耗时会被加长,如图 6-1-1所示,处理一个业务总耗时为500ms,也就是说一秒只能处理两个业务,性能下降、吞吐量下降。
假设图 6-1-1仓储服务扛不住压力,它挂了,请求来访问仓储服务必然是阻塞了,支付服务调用仓储调不通,卡在这里,一个请求卡住,后续的请求都得排队等待,卡得越来越多,支付服务资源被耗尽,请求就进不去支付服务了。
在这里插入图片描述
图 6-1-1

同步存在的问题
耦合度高:每次加入新的需求,都要修改原来的代码;
性能下降:调用者需要等待服务提供者响应,如果调用链过长则响应时间等于每次调用的时间之和;
资源浪费:调用链中的每个服务在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资源;
级联失败:如果服务提供者出现问题,所有调用方都会跟着出问题,如同多米诺骨牌一样,迅速导致整个微服务群故障。

本节小结
同步调用的优点:

  • 性较强,可以立即得到结果

同步调用的问题:

  • 耦合度高
  • 性能和吞吐能力下降
  • 有额外的资源消耗
  • 有级联失败问题

6.2.初始MQ-异步通讯的优缺点

异步调用常见实现就是事件驱动模式
在这里插入图片描述
优势一:服务解耦
当有新的业务,不再需要修改支付服务的代码,和它没关系了,只需要新的服务去订阅Broker。如果需要去掉某一个业务,那只需要该业务的服务取消订阅Broker即可。
在这里插入图片描述
优势二:性能提升,吞吐量提高
在这里插入图片描述
优势三:服务没有强依赖,不担心级联失败问题
假设仓储服务挂了,也和支付服务没有关系,这边支付业务已经完成了,“钱到账了”通知发布出去了,后续怎么处理那就是其他服务各自的事情。
在这里插入图片描述
优势四:流量削峰
当来了多个请求,Broker有缓冲的作用,然后再排序安排执行,这时候并发量被砍平了,这就是流量削峰。
在这里插入图片描述
本节小结
异步通信的优点:
·耦合度低
·吞吐量提升
·故障隔离
·流量削峰

异步通信的缺点:
·依赖于Broker的可靠性、安全性、吞吐能力
·架构复杂了,业务没有明显的流程线,不好追踪管理

异步同步各自都有优缺点,那什么时候使用同步?什么时候使用异步?
事实上大多都是使用同步,事实上对并发没有很高的要求,对时效性要求较高,就是我查询了一个信息,我立马需要在下一个业务中用到,比如查到了订单,马上要去查用户,需要立刻使用,那这得同步调用。异步只是通知了要做什么,什么时候做完不能及时反馈。

6.3.初始MQ-mq常见技术介绍

MQ(MessageQueue),中文是消息队列,字面来看就是存放消息的队列。也就是事件驱动架构中的Broker
在这里插入图片描述

6.4.RabbitMQ快速入门-介绍和安装

·RabbitMQ概述和安装
·常见消息模型
·快速入门
RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址: https://www.rabbitmq.com/

安装RabbitMQ:(点击跳转)
这边需要补充docker,在后面的补充有docker的文章链接。

RabbitMQ的结构和概念:
在这里插入图片描述
本节小结

RabbitMQ中的几个概念:

  • channel:操作MQ的工具
  • exchange:路由消息到队列中
  • queue:缓存消息
  • virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组

6.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.订阅消息
channel.basicConsume(queueName, true, new DefaultConsumer(channel){
    @Override
    public 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

消费者

// 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){
    @Override
    public 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

本节小结
基本消息队列的消息发送流程:
1.建立connection(连接)
2.创建channel(通道,通过这个通道进行发送接收)
3.利用channel声明队列
4.利用channel向队列发送消息
基本消息队列的消息接收流程:
1.建立connection
2.创建channel
3.利用channel声明队列
4.定义consumer的消费行为handleDelivery05.利用channel将消费者与队列绑定

6.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 用于自动声明队列、交换和绑定

6.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>
  • 1
  • 2
  • 3
  • 4
  • 5

步骤二:在publisher中编写测试方法,向simple.queue发送消息
1.在publisher服务中编写application.yml,添加mq连接信息:

spring:
  rabbitmq:
    host: 192.168.150.101 # rabbitMQ的ip地址,注意改成自己的
    port: 5672 # 端口
    username: itcast
    password: 123321
    virtual-host: /
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.在publisher服务中新建一个测试类,编写测试方法:(这段代码不会创建消息队列,会发到原有的消息队列,如果发现没有消息可能是这个原因,需要手动创建队列,选择的队列必须是rabbitmq里存在的队列)

public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMessage2SimpleQueue() {
        String queueName = "simple.queue"; // 队列名称
        String message = "hello, spring amqp!"; // 消息
        rabbitTemplate.convertAndSend(queueName, message); // 发送
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述
测试
在这里插入图片描述
本节小结
什么是AMQP?
·应用间消息通信的一种协议,与语言和平台无关。
SpringAMQP如何发送消息?
·引入amqp的starter依赖·配置RabbitMQ地址
·利用RabbitTemplate的convertAndSend方法

6.8.SpringAMQP-入门案例的消息接收

步骤三:在consumer中编写消费逻辑,监听simple.queue
1.在consumer服务中编写application.yml,添加mq连接信息:(无论是接收消息还是发送消息,都要知道MQ在哪)

spring:
  rabbitmq:
    host: 192.168.150.101 # rabbitMQ的ip地址
    port: 5672 # 端口
    username: itcast
    password: 123321
    virtual-host: /
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

现在要清楚接收哪个队列?监听这个队列要做什么?就是什么行为,这两点要清楚

2.在consumer服务中新建一个类,编写消费逻辑:(加一个类,写一个方法,这个方法就是处理消息的行为,这个要告诉spring,所以加一个注解@Component,把类声明成一个Bean,spring就可以找到它了。)
(然后在方法上加一个注解@RabbitListener,告诉它监听哪个队列)

@Component
public class SpringRabbitListener {
    @RabbitListener(queues = "simple.queue")
    public void listenWorkQueue(String msg) throws InterruptedException {
        System.out.println("消费者接收到simple.queue的消息:【" + msg + "】" + LocalTime.now());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述
本节小结
SpringAMQP如何接收消息?
1.引入amqp的starter依赖
2.配置RabbitMQ地址
3.定义类,添加@Component注解
4.类中声明方法,添加@RabbitListener注解,方法参数就时消息
注意:消息一旦消费就会从队列删除,RabbitMQ没有消息回溯功能

6.9.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 {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public 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); 
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在这里插入图片描述
2.在consumer服务中定义两个消息监听者,都监听simple.queue队列

@Component
public class SpringRabbitListener {

    // @RabbitListener(queues = "simple.queue")
    // public void listenSimpleQueue(String msg) {
    //     System.out.println("消费者接收到simple.queue的消息:【" + msg + "】");
    // }

    @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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在这里插入图片描述
运行结果是:
总耗时处理了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: itcast
    password: 123321
    virtual-host: /
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述
关于SpringAMQP后续再补充,我先看ES了。


7.elasticsearch基础-分布式搜索引擎

  • 初识elasticsearch
  • 索引库操作
  • 文档操作
  • RestAPl

7.1.什么是elasticsearch?

  • 了解ES
  • 倒排索引
  • es的一些概念
  • 安装es、kibana

elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。

elasticsearch结合kibana、Logstash、Beats,也就是elastic stack (ELK)。被广泛应用在日志数据分析、实时监控等领域。

elasticsearch可以将日志可视化展示出来。比如在线上系统报错了,怎么找?运行的时候不可能打断点Debug,只能采用日志的方式。
在这里插入图片描述
实时监控,运行状态也是数据,CUP情况、内存情况、访问的频率等等,这些信息也会被elasticsearch展示出来。
在这里插入图片描述
elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。
elasticsearch底层是Luncene。
在这里插入图片描述
elasticsearch的发展

Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。
官网地址: https://lucene.apache.org/

Lucene的优势:

  • 易扩展(可以基于它做二次开发和定制,实现更高级的功能)
  • 高性能(基于倒排索引)

Lucene的缺点:

  • 只限于Java语言开发
  • 学习曲线陡峭(API复杂)
  • 不支持水平扩展(只考虑搜索,没考虑高并发也没有集群扩展的问题,想实现这个功能只能二次开发)

2004年Shay Banon基于Lucene开发了Compass
2010年Shay Banon重写了Compass,取名为Elasticsearch。
官网地址: https://www.elastic.co/cn/
相比与lucene,elasticsearch具备下列优势:

  • 支持分布式,可水平扩展
  • 提供Restful接口,可被任何语言调用

搜索引擎技术排名:

  1. Elasticsearch:开源的分布式搜索引擎
  2. Splunk:商业项目
  3. Solr: Apache的开源搜索引擎

在这里插入图片描述
在这里插入图片描述
本节小结:

什么是elasticsearch?

  • 一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能

什么是elastic stack (ELK) ?

  • 是以elasticsearch为核心的技术栈,包括beats、Logstash、kibana、elasticsearch

什么是Lucene?

  • 是Apache的开源搜索引擎类库,提供了搜索引擎的核心API

7.2.初始ES-倒排索引

传统数据库(如MySQL)采用正向索引
elasticsearch采用倒排索引:

  • 文档(document) :每条数据就是一个文档
  • 词条(term):文档按照语义分成的词语

保存每一个词条,比如存“手机”这个词条,在id为1和2出现过,就记录“1,2”。
之后有再多的词条也是这样记录,总会有重复的词条,但是不重复记录,只存唯一的一个,重复词条出现在文档后面记录id,这样可以确定词条不会有重复的。因为词条它的唯一性,可以为它创建索引了。
在这里插入图片描述
先根据用户输入的词条,去词条列表中查找对应的id;
第二次拿着文档id,去查找文档;
虽然查找了两次,但两次都是根据索引进行查询,所以查询效率比逐条扫描高很多。
比如查找“华为手机”,分词后得到“华为”和“手机”,它们保存的id,2出现最多,那索引2就会往前排,剩下1,3排序。
在这里插入图片描述
平时搜索的结果也可以看出是分词后进行查找。
在这里插入图片描述

正向索引是根据文档找到词,
倒排索引是根据词找到文档。

本节小结:

什么是文档和词条?

  • 每一条数据就是一个文档
  • 对文档中的内容分词,得到的词语就是词条(中文就按照中文含义分,英文就按照空格区分)

什么是正向索引?

  • 基于文档id创建索引。查询词条时必须先找到文档,而后判断是否包含词条

什么是倒排索引?

  • 对文档内容分词,对词条创建索引,并记录词条所在文档的信息。
    查询时先根据词条查询到文档id,而后获取到文档

7.3.初始ES-ES与MySQL的概念对比

elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。
文档数据会被序列化为json格式后存储在elasticsearch中。
在这里插入图片描述

  • 索引(index) :相同类型的文档的集合
  • 映射( mapping):索引中文档的字段约束信息,类似表的结构约束

这边给出N多文档,根据字段相同进行分类,不同类型放到不同的索引库
在这里插入图片描述
在这里插入图片描述
概念对比
在这里插入图片描述
Mysql:擅长事务类型操作(ACID原则)可以确保数据的安全和一致性(还有隔离性等等)
Elasticsearch:擅长海量数据的搜索、分析、计算
可以互补
在这里插入图片描述
本节小结
文档:一条数据就是一个文档,es中是Json格式

字段:Json文档中的字段

索引:同类型文档的集合

映射:索引中文档的约束,比如字段名称、类型

elasticsearch与数据库的关系:

  • 数据库负责事务类型操作
  • elasticsearch负责海量数据的搜索、分析、计算

7.4.初始ES-安装ES(未补充)

安装elasticsearch、kibana

7.5.初始ES-安装kibana(未补充)

暂时保留,后续补充

7.6.初始ES-安装IK分词器(未补充)

处理中文分词,一般会使用lK分词器。官方文档:https://github.com/medcl/elasticsearch-analysis-ik

7.7.初始ES-IK分词器的拓展和停用词典

ik分词器-拓展词库

要拓展ik分词器的词库,只需要修改一个ik分词器目录中的config目录中的lkAnalyzer.cfg.xml文件:
在这里插入图片描述
然后在名为ext.dic的文件中,添加想要拓展的词语即可

要禁用某些敏感词条,只需要修改一个ik分词器目录中的config目录中的lkAnalyzer.cfg.xml文件:
在这里插入图片描述
然后在名为stopword.dic的文件中,添加想要拓展的词语即可

分词器的作用是什么?

  • 创建倒排索引时对文档分词
  • 用户搜索时,对输入的内容分词

IK分词器有几种模式?

  • ik smart:智能切分,粗粒度
  • ik_max_word:最细切分,细粒度

IK分词器如何拓展词条?如何停用词条?

  • 利用config目录的lkAnalyzer.cfg.xml文件添加拓展词典和停用词典
  • 在词典中添加拓展词条或者停用词条

# 该学习还未结束,仍在更新中:

补充:

docker文章的链接

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/379974?site
推荐阅读
相关标签
  

闽ICP备14008679号