当前位置:   article > 正文

【实用篇】SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式,系统详解springcloud分布式_springcloud + ribbitmq + docker + rebis

springcloud + ribbitmq + docker + rebis

文章目录

学习资料百度网盘: https://pan.baidu.com/s/1LxIxcHDO7SYB96SE-GZfuQ
密码:dor4

一、服务拆分

1.1 服务拆分Demo

两个服务放在同一个项目里面,然后分别启动,从而实现了服务拆分
在这里插入图片描述

1.2 微服务远程调用

1. 在配置类中注册RestTemplate

@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication{
	public static void main(String[] args){
		SpringApplication.run(OrderApplication.class, args);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2. 在OrderService中远程调用UserService服务

@Service
public class OrderService{
	@Autowired
	private OrderMapper orderMapper;
	@Autowired
	private RestTemplate restTemplate;

	public OrderqueryOrderById(Long orderId){
		// 1. 查询订单
		Order order = orderMapper.findById(orderId);
		// 2. url路径
		String url  = "http://localhost:8081/user/" + order.getUserId();
		// 3. 利用RestTemplate发起http请求,查询用户
		User user = restTemplate.getForObject(url, User.class);
		// 4. 封装User到Order
		order.setUser(user);
		// 5. 返回order
		return order;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

二、Eureka

2.1 Eureka原理

在这里插入图片描述

2.2 Eureka-server服务搭建

1. 在cloud-demo父工程下,创建一个子模块eureka-server
在这里插入图片描述
2. 引入eureka-server依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4

3. 编写启动类

@SpringBootApplication
@EnableEurekaServer		//该启动类设置为eureka-server服务
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

4. 编写配置文件application.yml

# eureka服务信息配置
server:
  port: 10086
spring:
  application:
    name: eureka-server
    
# 注册到eureka中
eureka:
  client:
    service-url: #eureka的地址信息
      defaultZone: http://127.0.0.1:10086/eureka
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

5. 启动微服务,然后在浏览器访问:http://127.0.0.1:10086
在这里插入图片描述

2.3 eureka-client服务注册

1. 在user-service和order-service服务模块中,引入eureka-client依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4

2. 修改文件application.yml

# 设置服务信息
spring:
  application:
    name: user-service

# 将服务注册到http://127.0.0.1:10086/eureka服务中
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3. 启动eureka-server服务及user-service服务,然后在浏览器访问:http://127.0.0.1:10086
启动eureka-server服务及user-service服务,我们发现当前已在Eureka中注册的实例栏中有了user-service的信息。
在这里插入图片描述

2.4 eureka-client服务复制

复制之后并不是又多了一个user-service模块,而是只是多了一个UserApplication启动服务
在这里插入图片描述
在这里插入图片描述
可以看到eureka-server管理页面数值变化
在这里插入图片描述

2.5 eureka服务发现

1. 修改orde-service代码用服务名称代替ip端口
我们要去http://127.0.0.1:10086/eureka中拉取user-service服务的实例列表,并通过负载均衡找到其中一个实例。不过这些动作不用我们去做,只需要添加一些注解@LoadBalance即可。
在这里插入图片描述
2. 在order-service的OrderApplication中,给RestTemplate这个Bean添加一个@LoadBalanced注解

在这里插入图片描述
3. 重启order-service服务,再次访问http://localhost:8080/order/101
发现user-service服务的两个实例都打印了sql信息,并且选择其中一个发送请求。所以我们发现spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡。
在这里插入图片描述

三、Ribbon负载均衡

上述操作已经完成了负载均衡了,这里就说说关于负载均衡的SpringCloud底层完成负载均衡的Ribbon组件。

3.1 负载均衡原理

SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改
在这里插入图片描述

3.2 负载均衡策略

负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类
在这里插入图片描述
默认的实现就是ZoneAvoidanceRule,是一种轮询方案.
在这里插入图片描述

3.3 自定义负载均衡策略

配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则(针对某个服务的负载均衡策略)

user-service: # 给某个微服务配置负载均衡规则,这里是user-service服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 
  • 1
  • 2
  • 3

3.4 饥饿加载与懒加载

Ribbon采用懒加载:第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
Ribbon采用饥饿加载:会在项目启动时创建LoadBalanceClient,降低第一次访问的耗时。

文件位置:例如order-service要访问user-service服务,那就在order-service的application.yml中配置user-service服务

# 配置单个clients
ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    clients: user-service # 开启饥饿加载的服务名称
  • 1
  • 2
  • 3
  • 4
  • 5
ribbon:
  eager-load:
    enabled: true 
    clients: 
      - user-service # 配置多个饥饿加载服务
      - other-service
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

四、Nacos

4.1 Nacos安装教程

  1. 进入GitHub主页:https://github.com/alibaba/nacos
  2. 下拉点击Release Version的全部发行版
    在这里插入图片描述
  3. 下载自己想要的版本
    .zip是windows版本 .tar.gz是Linux版本
    在这里插入图片描述
  4. 解压nacos-service,并且进入bin目录
    在这里插入图片描述
  5. 启动startup.cmd,但是不要双击startup.cmd文件启动nacos服务,因为默认是集群模式,使用命令进行单体启动.
startup.cmd -m standalone
  • 1

但是我的电脑需要输入这样的命令

.\ startup.cmd -m standalone
  • 1

在这里插入图片描述

  1. 在浏览器输入地址:http://127.0.0.1:8848/nacos即可访问,默认账号密码都是nacos:
    在这里插入图片描述
    在这里插入图片描述

4.2 服务注册到nacos

因为项目是在eureka的基础上创建的,所以引入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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 如果依赖中有如下的eureka依赖,那就删除或注释掉。
        <!--eureka客户端依赖-->
        <!--<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>-->
  • 1
  • 2
  • 3
  • 4
  • 5
  1. user-serviceorder-service中的pom文件中引入nacos-discovery依赖
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  1. 配置nacos地址:在user-service和order-service的application.yml中添加nacos地址,不要忘了注释掉eureka地址
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
  • 1
  • 2
  • 3
  • 4
  1. 重启微服务后,登录nacos管理页面,可以看到微服务信息
    在这里插入图片描述

4.3 服务分级存储模型

一个服务可以有多个实例,例如我们的user-service,可以有

127.0.0.1:8081
127.0.0.1:8082
127.0.0.1:8083
  • 1
  • 2
  • 3

假如这些实例分布于全国各地的不同机房

127.0.0.1:8081(在上海机房)
127.0.0.1:8082(在上海机房)
127.0.0.1:8083(在杭州机房)
  • 1
  • 2
  • 3

也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成Nacos服务分级存储模型
在这里插入图片描述

给user-service配置集群

  1. 把UserApplication和UserApplication2添加到杭州集群,先找到user-service模块下的application.yml,集群配置如下:
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称-杭州
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 启动UserApplication和UserApplication2实例,我们可以在nacos控制台看到下面结果
    在这里插入图片描述

  2. 把UserApplication3添加到杭州集群,先找到user-service模块下的application.yml,集群配置如下:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: SH # 集群名称-上海
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  1. 启动UserApplication3实例,我们可以在nacos控制台看到下面结果
    在这里插入图片描述

4.4 同集群优先的负载均衡

默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。

给order-service配置集群信息,修改order-service的application.yml文件,添加集群配置:

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 集群名称
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

修改order-service的application.yml文件,修改负载均衡规则为优先选择同集群

user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 
  • 1
  • 2
  • 3

4.5 权重配置

实际部署中会出现这样的场景

  • 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。
  • 但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。
  • 因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。

在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重
注意:如果权重修改为0,则该实例永远不会被访问
在这里插入图片描述
在这里插入图片描述

4.6 环境隔离namespace

不同namespace之间相互隔离,例如不同namespace的服务互相不可见

1. 默认情况下所有的服务都在一个名为public的namespace
在这里插入图片描述
2. 我们可以点击页面新增按钮,添加一个namespace
在这里插入图片描述
3. 然后填写表单,命名空间ID不填,让它自动生成
在这里插入图片描述
4. 就能在页面看到一个新的namespace
在这里插入图片描述
5. 给微服务配置namespace

spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ
        namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填nacos上面生成的ID
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

6. 重启order-service后,访问控制台,可以看到下面的结果
在这里插入图片描述
在这里插入图片描述
7. 此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错
在这里插入图片描述

4.7 配置管理

1. 为什么需要nacos配置管理
当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。

其实也不是说服务的所有配置都交给nacos管理,只是一些经常改动的配置需要nacos统一管理。例如数据库的驱动器、地址、账号、密码就不是经常改动的,所以就不用放在nacos统一管理了。

在这里插入图片描述
2. 怎样在nacos添加配置管理
在nacos首页找到配置列表,并且点击添加
在这里插入图片描述
然后在弹出的表单中,填写配置信息,然后发布
在这里插入图片描述
3. 在服务模块user-service中,引入nacos-config依赖

<!--nacos配置管理依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

4. 配置bootstrap.yaml文件
在user-service中\src\main\resources添加一个bootstrap.yaml文件

spring:
  application:
    name: user-service # 服务名称
  profiles:
    active: dev #开发环境,这里是dev 
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5. 测试是否能通过bootstrap.yaml拉取到配置

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Value("${pattern.dateformat}")		//通过value读取配置中的pattern.dateformat
    private String dateformat;
    
    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在页面访问http://localhost:8081/user/now,可以看到效果
在这里插入图片描述

4.8 配置热更新

我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新。

方式一:在@Value注入的变量所在类上添加注解@RefreshScope
在user-service的Controller层添加@RefreshScope

@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {

    @Autowired
    private UserService userService;

    @Value("${pattern.dateformat}")		//通过value读取配置中的pattern.dateformat
    private String dateformat;
    
    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

方式二:使用@ConfigurationProperties注解代替@Value注解
在user-service服务中,添加一个类,读取patterrn.dateformat属性

@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
    private String dateformat;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在UserController中使用这个类代替@Value

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private PatternProperties patternProperties;

    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

查看效果
启动UserApplication启动类,这时候再去改动nacos统一管理配置,就会热配置到User Application中,而不需要重新启动UserApplication。

项目启动:
第一次的配置文件如下
在这里插入图片描述
第一次访问形式如下
在这里插入图片描述

项目依然保持运行状态,不能重新启动
第二次的配置文件如下
在这里插入图片描述
第二次访问形式如下
在这里插入图片描述

4.9 多环境配置共享

1. 我们重新来看一下bootstrap.yaml配置文件

spring:
  application:
    name: user-service # 服务名称
  profiles:
    active: dev #开发环境dev 
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2. 多环境配置共享规则
我们知道user-service服务需要经过dev(开发)、test(测试)、release(发布)等过程,我们当然知道不同的过程就会有不同的环境配置,但是这些不同的环境配置中也有一些相同的参数值,可不可以把这些相同的参数值放到一个地方,尽量一改动,这个服务的环境全部改动呢?

其实上述bootstrap.yaml文件是加载了user-service.yaml配置文件和user-service-dev.yaml配置文件。是的,无论你是加载user-service-dev.yaml、还是user-service-test.yaml,配置文件是默认会加载的。那么user-service.yaml就成为了多环境共享配置文件了。

那么这个user-service.yaml配置文件在nacos的配置就跟其他配置文件一样了,具体操作可在上面找到。

3. 当nacos、服务本地同时出现相同属性时,优先级有高低之分
在这里插入图片描述

4.10 nacos集群搭建

1. 配置Nacos
进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf,然后添加如下内容。

127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847
  • 1
  • 2
  • 3

在这里插入图片描述

2. 修改nacos\conf\application.properties文件,添加数据库配置

spring.datasource.platform=mysql

db.num=1

db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3. 将nacos文件夹复制三份,分别命名为:nacos1、nacos2、nacos3
在这里插入图片描述
4. 分别修改三个文件夹中的application.properties
nacos1

server.port=8845
  • 1

nacos2

server.port=8846
  • 1

nacos1=3

server.port=8847
  • 1

5. 分别启动三个nacos节点
这一次直接双击nacos\bin\startup.cmd就可以启动了。

注意:后面的启动报错,有可能是内存不足

6. 为三个naocs节点配置nginx反向代理
找到nginx/conf/nginx.conf文件,配置如下

upstream nacos-cluster {			# nginx就在这三个nacos进行负载均衡
    server 127.0.0.1:8845;
	server 127.0.0.1:8846;
	server 127.0.0.1:8847;
}

server {
    listen       80;
    server_name  localhost;

    location /nacos {						#访问http://localhost/nacos
        proxy_pass http://nacos-cluster;	#就会跳到下面http://localhost/nacos-cluster,即上面的三个地址
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

7. 启动nginx服务器
在这里插入图片描述
8. 输入localhost/nacos,访问浏览器
在这里插入图片描述

五、Feign远程调用

5.1 Feign替代RestTemplate

1. 引入依赖
我们在order-service服务的pom文件中引入feign的依赖

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

2. 添加注解
在order-service的启动类添加注解开启Feign的功能

@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
	// ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3. 编写Feign客户端
在order-service中新建一个接口,内容如下:
并且在@FeignClient会对user-service模块的实例进行Ribbon负载均衡

@FeignClient("user-service")		//远程调用user-service服务模块下的实例
public interface UserClient {
    @GetMapping("/user/{id}")	//匹配到user-service模块下的/user/{id}对应的方法
    User findById(@PathVariable("id") Long id);	//所以这里findById方法对应user-service的queryById方法
}
  • 1
  • 2
  • 3
  • 4
  • 5

4. 远程调用

@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.findById(order.getUserId());
        // 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

5. 测试
在这里插入图片描述

5.2 Feign自定义配置

1. Feign可以支持很多的自定义配置
一般情况下,默认值就能满足我们使用,如果要自定义时,下有两种Feign自定义的方式。下面以日志为例来演示如何自定义配置。
在这里插入图片描述
2. 基于application.yml自定义Feign

# 可以针对单个服务
feign:  
  client:
    config: 
      userservice: # 针对某个微服务的配置
        loggerLevel: FULL #  日志级别NONE、BASIC、HEADERS、FULL 


# 也可以针对多个服务
feign:  
  client:
    config: 
      default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
        loggerLevel: FULL #  日志级别 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3. 基于Java代码方式自定义Feign

// 如果想要全局生效
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class) 
public class DefaultFeignConfiguration  {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC; // 日志级别为BASIC
    }
}

// 如果想要局部生效
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class) 
public class DefaultFeignConfiguration  {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC; // 日志级别为BASIC
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

5.3 Feign使用优化

1. 为什么Feign需要优化?

Feign底层发起http请求,依赖于其他框架,包括:
- URLConnection:不支持连接池;
- Apache HttpClient:支持连接池;
- OKHttp:支持连接池;
Feign底层默认使用URLConnection
  • 1
  • 2
  • 3
  • 4
  • 5

2. 如何优化Feign?

使用支持连接池的Apache HttpClient或者OKHttp代替默认的URLConnection
  • 1

3. 使用Apache HttpClient替代URLConnection
1. 在order-service的pom文件中引入Apache HttpClient依赖

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

2. 在order-service的application.yml中添加配置

# 这里包含了对日志级别的优化,一般选择BASIC或NONE
feign:
  client:
    config:
      default: # default全局的配置
        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息

# 这里包含了对底层连接的优化,使用Apache HttpClient来替代URLConnection
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3. 测试是否使用了Apache HttpClient
在FeignClientFactoryBean中的loadBalance方法中打断点
在这里插入图片描述
Debug方式启动order-service服务,可以看到这里的client,底层就是Apache HttpClient
在这里插入图片描述

六、Gateway统一网关

6.1 为什么使用Gateway统一网关

上面演示的过程中,我们通过访问的order-service服务模块获取数据,但是一般情况下不能直接访问。
因为有些微服务模块是不给外人访问的,所以我们需要用Gateway统一网关来限定外部访问。
  • 1
  • 2

在这里插入图片描述

6.2 搭建Gateway服务

1. 创建Gateway服务模块
在这里插入图片描述
2. 引入依赖

<!--网关-->
<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

3. 编写启动类

@SpringBootApplication
public class GatewayApplication {

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

4. 编写基础配置和路由规则
我们将 /user/**开头的请求,代理到lb://userservice,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。

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://user-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

5. 重启测试
访问http://localhost:10010/user/1时,符合/user/**规则,请求转发到uri:http://userservice/user/1。nacos根据userservice服务名发现服务,给出服务列表,并根据负载均衡返回其中一个实例。
在这里插入图片描述
6. 总体流程
在这里插入图片描述

6.3 断言工厂

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件,例如Path=/user/**是按照路径匹配,这个规则是由PathRoutePredicateFactory类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个,我们只需要掌握Path这种路由工程就可以了。
在这里插入图片描述

6.4 过滤器工厂(路由过滤器、默认过滤器)

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
在这里插入图片描述
1. 添加AddRequestHeader过滤器
Spring提供了31种不同的路由过滤器工厂,下面我们以AddRequestHeader为例来讲解

只需要修改gateway服务的application.yml文件,即可添加过滤器
路由过滤器

# 当前过滤器卸载userservice路由下,因此仅仅对的访问userservice的请求有效
spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://user-service 
        predicates: 
        - Path=/user/** 
        filters: # 过滤器
        - AddRequestHeader=Truth, bcb is freaking awesome! # 添加请求头
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

默认过滤器

# 当前过滤器写在gateway下,则可以对所有路由都生效
spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://user-service 
        predicates: 
        - Path=/user/**
      default-filters: # 默认过滤项
      - AddRequestHeader=Truth, bcb is freaking awesome! 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2. 测试:来到UserController修改queryById方法,代码如下

 @GetMapping("/{id}")
 public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Truth", required = false) String truth) {
  		log.info(truth);
        return userService.queryById(id);
}
  • 1
  • 2
  • 3
  • 4
  • 5

重启网关服务以及user-service服务,访问http://localhost:10010/user/1,可以看到控制台打印出了对应的日志
在这里插入图片描述

6.5 全局过滤器(GlobalFilter)

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。

1. 实现GlobalFilter接口

@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public 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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

6.6 过滤器执行顺序

1. 不同类型过滤器之间的优先级,如下
在这里插入图片描述
2. 同一类型过滤器的优先级

默认过滤器:按照在配置文件中的排序,order从1开始计算
路由过滤器:按照在配置文件中的排序,order从1开始计算
全局过滤器:在GlobalFilter实现类的类名加上注解的@Order()
  • 1
  • 2
  • 3

6.7 跨域问题

1. 什么是跨域问题

浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
- 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com
- 域名相同,端口不同:localhost:8080和localhost8081
  • 1
  • 2
  • 3

2. 跨域报错演示
在这里插入图片描述
3. 解决上述跨域报错问题

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 # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

七、Docker容器

7.1 认识Docker容器

1. 微服务部署问题及其解决办法。

问题1:依赖关系复杂,容易出现兼容性问题
- 将应用的Libs(函数库)、Deps(依赖)、配置、应用一起打包
问题2:操作系统环境差异问题
- 因为无论是Ubuntu还是CentOS环境,都是基于Linux内核,那就直接把Linux内核打包就好了
  • 1
  • 2
  • 3
  • 4

2. Docker与虚拟机的区别
在这里插入图片描述

7.2 Docker的架构

1. Docker重要组件

1. 注册器(Registry)

  • 一个 Docker Registry 中可以包含多个仓库(Repository);

2. 仓库(Repository)

  • 一个仓库会包含同一个软件不同版本的镜像,我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。

3. 镜像(Image)

  • 回忆一下虚拟机VM安装的时候是不是要导入centos镜像,这个centos镜像就是Image。其他的应用也有各自的镜像,例如MySQL等等。Image需要从Repository拉取到Docker容器中才能运行。

4. 容器(Container)

  • 镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

5. Dockerfile

  • Dockerfile 是一个用来构建镜像Image的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。

2. Docker采用C/S模式
在这里插入图片描述

7.3 Centos7安装Docker

1. 版本问题
Docker CE支持64位版本CentOS7,并且要求内核版本不低于3.10,CentOS7满足最低内核的要求,所以我们在CentOS 7安装Docker

2. 如果之前安装过旧版本的Docker,可以使用下面命令卸载

yum remove docker \
					docker-client \
					docker-client-latest \
					docker-common \
					docker-latest \
					docker-latest-logrotate \
					docker-logrotate \
					docker-selinux \
					docker-engine-selinux \
					docker-engine \
					docker-ce					
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述

3. 安装yum工具

yum install -y yum-utils \
	device-mapper-persistent-data \ 
	lvm2 --skip-broken
  • 1
  • 2
  • 3

在这里插入图片描述

4. 更新本地镜像源

# 设置docker镜像源
yum-config-manager \
	--add-repo \
	https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo

yum makecache fast 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
5. 下载docker

yum install -y docker-ce
  • 1

在这里插入图片描述

7.4 启动Docker

Docker应用需要用到各种端口,逐一去修改防火墙装置。非常麻烦,因此建议大家直接关闭防火墙

# 关闭
systemctl stop firewalld

# 禁止开机启动防火墙
systemctl disable firewalld
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

启动docker

# 启动docker
systemctl start docker

# 查看是否启动成功
systemctl status docker
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述

7.5 配置Docker镜像

# 创建/etc/docker文件
sudo mkdir -p /etc/docker
# 向文件写入内容
sudo tee /etc/docker/daemon.json <<-'EOF'
{
	"registry-mirrors": ["https://n0dwemtq.mirror.aliyuncs.com"]
}
EOF
# 重新加载
sudo systemctl daemon-reload
# 重新启动
sudo systemctl restart docker
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里插入图片描述

7.6 镜像基本操作

在这里插入图片描述
1. 拉取、查看镜像
首先去镜像仓库搜索nginx镜像,比如DockerHub:
在这里插入图片描述
根据查看到的镜像名称,拉取自己需要的镜像,通过命令:docker pull nginx
通过命令:docker images 查看拉取到的镜像
在这里插入图片描述

2. 保存、导入镜像
保存命令:docker save -o [保存的目标文件名称] [镜像名称]

# 把镜像nginx:latest保存到nginx.tar文件
docker save -o  nginx.tar nginx:latest
  • 1
  • 2

在这里插入图片描述
导入命令:先删除本地的nginx镜像

docker rmi nginx:lateset
  • 1

在这里插入图片描述

然后运行命令,加载本地文件

docker load -i nginx.tar
  • 1

在这里插入图片描述

7.7 容器基本操作

docker run:创建并运行一个容器,处于运行状态
docker pause:让一个运行的容器暂停
docker unpause:让一个容器从暂停状态恢复运行
docker stop:停止一个运行的容器
docker start:让一个停止的容器再次运行
docker rm:删除一个容器
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里插入图片描述
1. 创建并运行一个容器

docker run --name containerName -p 80:80 -d nginx
  • 1
docker run :创建并运行一个容器
–name : 给容器起一个名字,比如叫做mn
-p :将宿主机端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口
-d:后台运行容器
nginx:镜像名称,例如nginx
  • 1
  • 2
  • 3
  • 4
  • 5

2. 创建nginx镜像

docker run --name mn -p 80:80 -d nginx
  • 1

在这里插入图片描述

docker ps
  • 1

在这里插入图片描述
3. 向nginx所在的服务区发起访问
访问http://192.168.133.128:80
在这里插入图片描述

7.8 进入容器

进入容器
在我看来就是进入了虚拟机终端里面的另一个终端,仔细品一下吧

# 我要进入mn容器中,并且能够进入输入输出,并且打开交互命令
docker exec -it mn bash
  • 1
  • 2
- docker exec:进入容器,执行一个命令
- -it:给当前进入的容器创建一个标准输入、输出终端、允许我们与容器交互
- mn:要进入的容器的名称
- bash:进入容器后执行的命令,bash是一个linux终端交互命令
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述

7.9 Docker的数据卷

1. 原先的Docker是容器与数据耦合
在之前的nginx案例中,修改nginx的html页面时,需要进入nginx内部,并且因为没有编辑器,修改文件也很麻烦,这就是容器与数据耦合带来的后果。

- 不便于修改:当我们要修改Nginx的html内容时,需要进入容器内部修改,很不方便
- 数据不可复用:在容器内的修改对外是不可见的,所有修改对新创建的容器是不可复用的
- 升级维护困难:数据在容器内,如果要升级容器必然删除旧容器,所有数据都跟着删除了
  • 1
  • 2
  • 3

要解决这个问题,必须将数据与容器解耦,这就是数据卷。

2. 数据卷原理
数据卷(volume)是一个虚拟目录,指向宿主机文件系统中的某个目录。

一旦完成数据卷挂载,对容器的一切操作都会作用在数据卷对应的宿主机目录了。
这样,我们操作宿主机的/var/lib/docker/volumes/html目录,就等于操作容器内的/usr/share/nginx/html目录了
  • 1
  • 2

在这里插入图片描述
3. 数据卷基本操作命令

# 创建数据卷
docker volume create html

# 查看所有数据
docker volume ls

# 查看数据卷详细信息卷
docker volume inspect html
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4. 指定数据卷挂载的目录

docker run \
  --name mn \
  -v html:/root/html \
  -p 8080:80
  nginx \
  • 1
  • 2
  • 3
  • 4
  • 5

7.10 Dockerfile自定义镜像

1. 创建Dockerfile文件
/docker目录下,新建一个Dockerfile 文件,并在文件内添加以下内容:

`FROM` 定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。
`RUN` 用于执行后面跟着的命令行命令(等同于,在终端操作的 shell 命令)
  • 1
  • 2

在这里插入图片描述

2. 开始构建镜像
在 Dockerfile 文件的存放目录/docker下,执行docker build -t nginx:v3 .命令。
nginx:v3:镜像名称:镜像标签
. :代表本次执行的上下文路径
在这里插入图片描述

7.11 构建Java项目

需求:搭建一个JavaWeb镜像,然后在上面运行Java代码
步骤1:新建一个空文件夹docker-demo
步骤2:拷贝课前资料中的docker-demo.jar文件到docker-demo这个目录
步骤3:拷贝课前资料中的jdk8.tar.gz文件到docker-demo这个目录
步骤4:拷贝课前资料提供的Dockerfile到docker-demo这个目录
在这里插入图片描述
Dockerfile内容如下

# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local

# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar

# 安装JDK
RUN cd $JAVA_DIR \
 && tar -xf ./jdk8.tar.gz \
 && mv ./jdk1.8.0_144 ./java8

# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin

# 暴露端口
EXPOSE 8090
# 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

步骤5:将准备好的docker-demo上传到虚拟机任意目录,然后进入docker-demo目录下
步骤6:运行命令docker build -t javaweb:1.0 .
步骤7:运行镜像docker run --name web -p 8090:8090 -d javaweb:1.0
步骤8:最后访问 http://ip:8090/hello/count,其中的ip改成你的虚拟机ip,结果如下:
在这里插入图片描述

7.12 DockerCompose下载安装

1. 什么是DockerCompose
Docker Compose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器,Compose文件不是一个文本文件,通过指令定义集群中的每个容器如何运行。

2. DockerCompose的使用实例
还记得Docker启动镜像吗?其实就是将Docker run启动命令都放在Docker Compose文件中,只是语法稍有差异。

version: "3.8"
 services:
  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:
    build: .
    ports:
     - "8090:8090"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3. DockerCompose的下载安装
1. 百度网盘下载docker-compose文件,通过xftp放到/usr/local/bin目录下

2. 修改文件权限

chmod +x /usr/local/bin/docker-compose
  • 1

执行后文件变绿了,代表文件可以执行了
在这里插入图片描述
3. Base自动补全命令

curl -L https://raw.githubusercontent.com/docker/compose/1.29.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
  • 1

在这里插入图片描述

如果出错了,无法下载,那就执行如下命令

echo "199.232.68.133 raw.githubusercontent.com" >> /etc/hosts
  • 1

7.13 DockerCompose部署微服务集群

1. 查看课前资料提供的cloud-demo文件夹,里面已经编写好了docker-compose文件
在这里插入图片描述
2. 查看docker-compose.yml文件

version: "3.2"

services:	# 包含了下面五个服务nacos、mysql、userservice、orderservice、gateway
  nacos:	# 由于所有服务都需要注册到nacos,所以nacos先启动
    image: nacos/nacos-server	# 镜像
    environment:
      MODE: standalone		# 单点模式
    ports:
      - "8848:8848"		# 端口
  mysql:
    image: mysql:5.7.25		# 镜像
    environment:
      MYSQL_ROOT_PASSWORD: 123  # 密码
    volumes:	# 数据挂载
      - "$PWD/mysql/data:/var/lib/mysql"	# $PWD是相对于/cloud-demo/docker-compose.yml文件下的目录的当前
      - "$PWD/mysql/conf:/etc/mysql/conf.d/"
  userservice:
    build: ./user-service
  orderservice:
    build: ./order-service
  gateway:
    build: ./gateway
    ports:
      - "10010:10010"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3. 修改自己的cloud-demo项目,将数据库、nacos地址都命名为docker-compose中的服务名
具体可以看视频讲解

4. 打包
接下来需要将我们的每个微服务都打包。因为之前查看到Dockerfile中的jar包名称都是app.jar,因此我们的每个微服务都需要用这个名称。

可以通过修改pom.xml中的打包名称来实现,每个微服务都需要修改:

<build>
  <!-- 服务打包的最终名称 -->
  <finalName>app</finalName>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
  </plugins>
</build>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

5. 拷贝jar包到部署目录
编译打包好的app.jar文件,需要放到Dockerfile的同级目录中。注意:每个微服务的app.jar放到与服务名称对应的目录,别搞错了。

6. 部署
最后,我们需要将文件整个cloud-demo文件夹上传到虚拟机中,理由DockerCompose部署。上传到任意目录
在这里插入图片描述
进入cloud-demo目录,然后运行下面的命令:docker-compose up -d

7. 这时我们发现有的服务启动失败了
在这里插入图片描述

究其原因,原来是因为nacos还未启动成功,导致其他微服务同时启动时无法注册成功,这时我们可以运行

docker-compose restart gateway userservice orderservice
  • 1

8. 重新启动项目
重启这三个微服务,重启完成后可以查看日志,看看服务是否正常启动了.
在这里插入图片描述

9. 开启项目
最后可以访问http://10.100.2.210:10010/order/101?authorization=admin,ip记得改成虚拟机ip,结果如图,说明服务正常启动了.
在这里插入图片描述

八、RabbitMQ消息队列

8.1 同步通讯与异步通讯

1. 同步通讯问题

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

在这里插入图片描述
2. 异步通讯解决了同步通讯的问题

- 服务解耦:支付服务不再调用订单服务来,而是支付服务直接发布消息,订单服务订阅到消息并且执行服务
- 性能提升:支付服务发送订单消息后,直接做其他的事情了,而不是还在等待订单服务执行
- 解决级联失败问题:服务没有了相互调用,就不用担心级联失败问题
- 流量削峰:由Broker承担流量压力分配
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述

8.2 MQ技术对比

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

8.3 RabbitMQ下载和安装

1. 下载镜像

docker pull rabbitmq:3-management
  • 1

2. 安装MQ

docker run \
 -e RABBITMQ_DEFAULT_USER=itcast \		# MQ页面登陆的账号
 -e RABBITMQ_DEFAULT_PASS=123321 \		# MQ页面登陆的密码
 --name mq \			# 给MQ起个名字
 --hostname mq1 \		# 起个主机名,分布式集群的时候使用
 -p 15672:15672 \		# MQ提供一个管理平台的UI,通过这个端口访问
 -p 5672:5672 \			# 用来消息通信的端口
 -d \					# 后台运行
 rabbitmq:3-management	# 镜像的名称
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3. 运行后访问
运行后访问http://192.168.133.128:15672/,其中ip需要改成自己虚拟机的ip,可以看到
在这里插入图片描述

8.4 RabbitMQ组件的介绍

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

在这里插入图片描述

8.5 RabbitMQ消息模型

- BasicQueue和WorkQueue都是基于Queue队列来完成消息发送,并没有使用到交换机。
- 发布订阅使用到Exchange交换机。
  • 1
  • 2

在这里插入图片描述

8.6 简单队列模型

1. 课前资料提供了一个Demo工程,mq-demo
在这里插入图片描述
2. 查看项目结构
在这里插入图片描述
3. 代码查看
publisher实现

package cn.itcast.mq.helloworld;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class PublisherTest {
    @Test
    public void testSendMessage() throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.133.128");
        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.发送消息
        String message = "hello, rabbitmq!";
        channel.basicPublish("", queueName, null, message.getBytes());
        System.out.println("发送消息成功:【" + message + "】");

        // 5.关闭通道和连接
        channel.close();
        connection.close();

    }
}
  • 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

consumer实现

package cn.itcast.mq.helloworld;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerTest {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.133.128");
        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
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

4. 运行
先运行PublisherTest,再运行ConsumerTest,在ConsumerTest控制台中可以获得如下结果

九、SpringAMQP

SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。

9.1 Basic Queue简单队列模型

1. 在父工程中引入mq-demo依赖

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

2. 消息发送
首先配置MQ地址,在publisher服务的application.yml中添加配置

spring:
  rabbitmq:
    host: 10.100.2.210 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后在publisher服务中编写测试类SpringAmqpTest,并利用RabbitTemplate实现消息发送:

package cn.itcast.mq.spring;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue() {
        // 队列名称
        String queueName = "simple.queue";
        // 消息
        String message = "hello, spring amqp!";
        // 发送消息
        rabbitTemplate.convertAndSend(queueName, message);
    }
}
  • 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

3. 消息接收
首先配置MQ地址,在comsumer服务的application.yml中添加配置:

spring:
  rabbitmq:
    host: 10.100.2.210 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后在consumer服务的cn.itcast.mq.listener包中新建一个类SpringRabbitListener,代码如下:

package cn.itcast.mq.listener;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class SpringRabbitListener {

    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueueMessage(String msg) throws InterruptedException {
        System.out.println("spring 消费者接收到消息:【" + msg + "】");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

9.2 WorkQueue消息队列

Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。

1. 在父工程中引入mq-demo依赖

<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5

2. 消息发送
首先配置MQ地址,在publisher服务的application.yml中添加配置

spring:
  rabbitmq:
    host: 10.100.2.210 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后在publisher服务中编写测试类SpringAmqpTest,并利用RabbitTemplate实现消息发送:

/**
     * workQueue
     * 向队列中不停发送消息,模拟消息堆积。
     */
@Test
public void testWorkQueue() throws InterruptedException {
    // 队列名称
    String queueName = "simple.queue";
    // 消息
    String message = "hello, message_";
    for (int i = 0; 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
  • 17

3. 消息接收
首先配置MQ地址,在comsumer服务的application.yml中添加配置:

spring:
  rabbitmq:
    host: 10.100.2.210 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后在consumer服务的cn.itcast.mq.listener包中新建一个类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());
    Thread.sleep(200);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4. 测试
启动ConsumerApplication后,在执行publisher服务中刚刚编写的发送测试方法testWorkQueue。可以看到消费者1很快完成了自己的25条消息。消费者2却在缓慢的处理自己的25条消息。
也就是说消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。这样显然是有问题的。

在spring中有一个简单的配置,可以解决这个问题。我们修改consumer服务的application.yml文件,添加配置:

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
  • 1
  • 2
  • 3
  • 4
  • 5

运行consumer服务可以看到,结果是处理消息快的处理的次数也多,即能者多劳.

其他比较复杂的MQ模型,在工作遇到时再学习

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

闽ICP备14008679号