SpringCloud(Eureka+Ribbon+Nacos+Feign+Gateway+Docker+...)

一、SpringCloud简介

1.概述

        SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud。 
        SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配:
        

2.与SpringBoot的版本兼容关系

        

二、服务拆分及远程调用

        

1.案例介绍

  • cloud-demo:父工程,管理依赖
  • order-service:订单微服务,只负责订单相关业务
  • user-service:用户微服务,只负责用户相关业务
  • 微服务的要求:
  •         订单微服务和用户微服务都必须有各自的数据库,相互独立
  •         订单服务和用户服务都对外暴露Restful的接口
  •         订单服务如果需要查询用户信息,只能调用用户服务的Restful接口,不能查询用户数据库

2.服务拆分注意事项

  • 单一职责:根据业务模块拆分,做到单一职责,不要重复开发相同业务
  • 数据独立:不同微服务都应该有自己独立的数据库,不要访问其它微服务的数据库
  • 面向服务:将自己的业务暴露为接口,供其它微服务调用

3.基于RestTemplate远程调用

         实现跨服务远程调用,就是通过发送http请求的方式来调用另一个微服务。
        RestTemplate是由Spring框架提供的一个可用于应用中调用rest服务的类,它简化了与http服务的通信方式,统一了RESTFul的标准,封装了http连接,只需要传入url及其返回值类型即可。
(1)创建RestTemplate对象,并设为Bean
@Bean
public RestTemplate restTemplate(){
    return new RestTemplate();
}
(2)利用RestTemplate发送http请求,获取数据
@Autowired
private RestTemplate restTemplate;

@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
    //1.根据id查询订单
    Order order = orderService.queryOrderById(orderId);
    //2.查询用户
    //2.1 动态封装http请求
    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.返回order
    return order;
}
【tips】服务提供者其它微服务调用的服务(提供接口给其它微服务)。
             服务消费者调用其它微服务的服务(调用其它微服务提供的接口)。

4.Eureka

(1)上述远程调用的问题
        在上面代码的 url 中,调用服务的地址采用硬编码,这在后续的开发中肯定是不理想的,这就需要服务注册中心(Eureka)来解决这个事情。
(2)eureka的作用
        
  • 消费者如何得知提供者实例地址?
  •         提供者服务实例启动后,将自己的信息注册到 eureka-server(Eureka服务端),即服务注册。eureka-server 保存服务名称到服务实例地址列表的映射关系,消费者根据服务名称,拉取实例地址列表,即服务发现或服务拉取
  •         【tips】服务提供者和消费者都称为Eureka的客户端
  • 消费者如何从多个提供者实例中选择具体的实例?
  •         消费者从实例列表中利用负载均衡算法选中一个实例地址,向该实例地址发起远程调用。
  • 消费者如何得知某个提供者实例是否依然健康,是不是已经宕机?
  •         提供者会每隔一段时间(默认30秒)向 eureka-server 发起请求,报告自己状态,称为心跳续约。当超过一定时间没有发送心跳时,eureka-server 会认为该微服务实例故障,将该实例从服务列表中剔除,这样消费者拉取服务时,就能将故障实例排除了。

(3)eureka的使用

        1)搭建eureka注册中心
  • 创建新模块eureka-server,导入spring-cloud-starter-netflix-eureka-server坐标
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    </dependency>
  • 编写启动类,并开启注册中心自动装配注解
    @SpringBootApplication
    @EnableEurekaServer
    public class EurekaApplication {
        public static void main(String[] args) {
            SpringApplication.run(EurekaApplication.class, args);
        }
    }
  • 编写配置文件
    server:
      port: 10086 # 服务端口
    spring:
      application:
        name: eureka-server # eureka的服务名称
    eureka:
      client:
        service-url: # eureka的地址信息
          defaultZone: http://127.0.0.1:10086/eureka
    【tips】配置文件中的defaultZone 是因为前面配置类开启了注册中心所需要配置的 eureka 的地址信息,而且 eureka 本身也是一个微服务这里也要将自己注册进来,当后面 eureka 集群时,这里就可以填写多个,使用 “,” 隔开。
        2)服务注册
                将user-service服务注册到注册中心:
  • user-service模块中导入spring-cloud-starter-netflix-eureka-client坐标
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
  • 编写配置文件
    spring:
      application:
        name: user-service # user-service服务名称
    eureka:
      client:
        service-url: # eureka的地址信息
          defaultZone: http://127.0.0.1:10086/eureka
  • 【tips】IDEA模拟多实例部署:
        
        3)服务拉取
                在 order-service 中完成服务拉取,然后通过负载均衡挑选一个服务,实现远程调用,即让 order-service 向 eureka-server 拉取 user-service 的信息,实现服务拉取。
                服务拉取是基于服务名称获取服务列表,然后再对服务列表做负载均衡,获取服务信息。
  • 修改order-service中访问的url路径,用★服务名称★代替ip地址和端口
    String url = "http://user-service/user/"+order.getUserId();
  • 给启动类OrderApplication中的RestTemplate添加负载均衡注
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

5.Ribbon

(1)负载均衡的流程

        SpringCloud 底层提供了一个名为 Ribbon 的组件,来实现负载均衡功能。
        为什么只需要输入service 名称,不需要获取ip和端口就能访问了呢?
        ——SpringCloud Ribbon 底层采用了一个LoadBalancerInterceptor拦截器,拦截了 RestTemplate 发出的请求,对地址做了修改。 基本流程如下: 拦截 RestTemplate 请求 http://userservice/user/1,RibbonLoadBalancerClient 会从请求url中获取服务名称,也就是 user-service。 DynamicServerListLoadBalancer 根据 user-service 到 eureka 拉取服务列表,eureka 返回列表:localhost:8081、localhost:8082;IRule 利用内置负载均衡规则,从列表中选择一个,例如 localhost:8081;RibbonLoadBalancerClient 修改请求地址,用 localhost:8081 替代 userservice,得到 http://localhost:8081/user/1,发起真实请求。
        

(2)负载均衡策略

  • 负载均衡的策略都定义在 IRule 接口中,而 IRule 有很多不同的实现类
        
  • 常见的负载均衡策略:
内置负载均衡规则类
描述
RoundRobinRule
简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule
对以下两种服务器进行忽略:
(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。
(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。
并发连接数的上限,可以由客户端的<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit属性进行配置。
WeightedResponseTimeRule
为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule
以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。
BestAvailableRule
忽略那些短路的服务器,并选择并发数较低的服务器。
RandomRule
随机选择一个可用的服务器。
RetryRule
重试机制的选择逻辑。

(3)修改负载均衡策略

        默认是轮询,那么如何修改负载均衡策略呢?有两种方式:
  • 代码方式:在启动类或配置类中定义一个新的IRule Bean,返回想要的负载均衡策略。
    @Bean
    public IRule randomRule(){
            //随机选择策略
        return new RandomRule();
    }
  • 配置文件方式:在order-service的 application.yml 文件中,添加新的配置修改负载均衡策略:
    user-service: # 指定使用该负载均衡策略的微服务名称,比如这里是order-service调用user-service
      ribbon:
        NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
【tips】代码方式属于全局设置,无论调用哪个微服务都按此策略;配置文件方式是想调谁就配谁,只针对该微服务有效。

(4)懒加载与饥饿加载(eager-load)

        当启动 order-service,第一次访问时,请求时间会很长,这是因为 Ribbon 的懒加载机制
        Ribbon 默认采用懒加载,即第一次访问时才创建 LoadBalanceClient,拉取集群地址,所以请求时间会很长。 
        饥饿加载则会在项目启动时创建 LoadBalanceClient,降低第一次访问的耗时,可以通过以下配置开启饥饿加载:
ribbon:
  eager-load:
    enabled: true # 开启饥饿加载
    clients: user-service # 项目启动时直接去拉取user-service的集群

三、★Nacos注册中心

1.Nacos简介

        Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

2.Nacos安装

(1)下载安装包并解压即安装完成
        下载地址:https://github.com/alibaba/nacos
(2)启动
        进入bin目录,执行命令:
./startup.cmd -m standalone # 单机启动
【tips】默认端口8848

3.使用Nacos进行服务注册

        这里上来就直接服务注册,很多东西可能有疑惑,其实 Nacos 本身就是一个 SprintBoot 项目,这点从启动的控制台打印就可以看出来,所以就不再需要去额外搭建一个像 Eureka 的注册中心
(1)导入坐标
        1)在 cloud-demo 父工程中引入 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>
        2)如果已导入eureka依赖,需将其注释掉
        3)在微服务(order-service、user-service)中导入nacos客户端依赖,用来将微服务注册到nacos注册中心,包括发现其他微服务
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)配置nacos服务地址
spring:
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos服务地址
(3)实现服务注册与发现
        在启动类上使用 @EnableDiscoveryClient 注解来开启服务注册与发现功能(把该微服务注册到nacos):
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallCouponApplication {

4.服务分级存储模型

        
(1)简介
  • 一级是服务,例如user-service
  • 二级是集群,例如杭州或上海
  • 三级是实例,例如杭州机房的某台部署了user-service的服务器
(2)如何设置实例的集群属性?
        修改实例的 application.yml 文件,添加集群配置:
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ # 自定义集群名称,这里HZ代表杭州
        配置好集群后可以查看Nacos的服务列表,可以查看每个实例所属集群:
        

5.NacosRule负载均衡

        Ribbon默认的负载均衡策略是 ZoneAvoidanceRule,并不能实现根据同集群优先来实现负载均衡,改成 NacosRule 即可。
        用 order-service 调用 user-service,所以在 order-service 配置负载均衡策略。
user-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则 

6.根据权重负载均衡

        服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。但默认情况下 NacosRule 是同集群内随机挑选,不会考虑机器的性能问题。
        Nacos 提供了根据权重配置来控制访问频率,0~1 之间,权重越大则访问频率越高,权重修改为 0,则该实例永远不会被访问。 
        
        在 Nacos服务列表,找到实例列表编辑,即可修改权重。

7.环境隔离

        Nacos 提供了 namespace 来实现环境隔离功能。 Nacos 中可以有多个 namespace;namespace 下可以有 group、service 等;不同 namespace 之间相互隔离,例如不同 namespace 的服务不可相互调用
(1)创建命名空间
        默认情况下,所有 service、data、group 都在同一个名为 public(保留空间)的namespace。
        
(2)配置命名空间
spring:
  cloud:
    nacos:
      server-addr: localhost:8848
      discovery:
        cluster-name: HZ
        namespace: namespace_id # 命名空间ID

8.临时实例与非临时实例

        Nacos 的服务实例分为两种类型: 
  • 临时实例(默认):如果实例宕机超过一定时间,会被注册中心服务列表剔除。
  • 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。
        配置一个服务实例为永久实例:
spring:
  cloud:
    nacos:
      discovery:
        ephemeral: false # 设置为非临时实例,默认是临时实例(true)

9.Nacos与Eureka的比较

(1)共同点
  • 都支持服务注册服务拉取(pull)
  • 都支持服务提供者的心跳检测 
(2)区别
  • Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  • 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  • Nacos支持服务列表变更的消息推送(push)模式,服务列表更新更及时;Eureka只能进行pull
  • Nacos集群默认采用AP方式(可用性),当集群中存在非临时实例时,采用CP模式(一致性);Eureka采用AP方式。

10.Nacos统一配置管理——配置中心

        
        Nacos除了可以做注册中心,同样可以做配置管理来使用。 
        当微服务部署的实例越来越多时,逐个修改微服务配置就显得十分不便。因此需要一种统一配置管理的方案,可以集中管理所有实例的配置
(1)创建配置
        在Nacos中添加配置信息:
        
        
【tips】项目的核心配置和需要热更新的配置才有必要放到 nacos 中管理。基本不会变更的一些配置(例如数据库连接)还是保存在微服务本地比较好。
(2)拉取配置
        1)Nacos 读取配置文件的流程
                在没加入 Nacos 配置之前,微服务的配置信息都配置在各自的application.yml中,此时Nacos获取配置是这样:
                
                而加入Nacos统一配置管理后,它的读取是在配置文件之前,但此时Nacos服务端地址是配置在之前的配置文件中,现在 Nacos 无法根据地址去获取配置了。 因此,Nacos 服务端地址必须放在优先级更高的 bootstrap.yml 文件中:
                
        2)拉取配置的步骤
  • 导入nacos-config坐标
    <!--nacos配置管理依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
  • 添加 bootstrap.yml并配置nacos服务端信息,这些配置信息决定了项目启动时去nacos读取哪个文件
    spring:
      application:
        name: userservice # 服务名称
      profiles:
        active: dev #所处环境,这里是dev 
      cloud:
        nacos:
          server-addr: localhost:8848 # Nacos地址
          config:
            file-extension: yaml # 文件后缀名
     【tips】:bootstrap.yml中配置的信息要与Data ID一致:
       

11.配置热更新

(1)配置热更新:修改 nacos 中的配置信息后,微服务无需重启即可让配置生效,实现配置的自动刷新。
(2)配置热更新的两种方式
  • 用 @value 读取配置时,搭配 @RefreshScope
        
  • ★直接用 @ConfigurationProperties 读取配置(推荐)
  •         在 user-service 服务中,添加一个 PatternProperties 类,读取 patterrn.dateformat 属性
    @Data
    @Component
    @ConfigurationProperties(prefix = "pattern")
    public class PatternProperties {
        public String dateformat;
    }
    @Autowired
    private PatternProperties patternProperties;
    
    @GetMapping("now2")
    public String now2(){
        //格式化时间
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.dateformat));
    }

12.多环境配置共享

(1)在服务启动时,nacos 会读取多个配置文件:
  • [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
  • [spring.application.name].yaml,例如:userservice.yaml
        这里的 [spring.application.name].yaml 不包含环境,因此该配置文件可以被多环境共享
(2)配置优先级
        当 nacos 和服务本地有相同属性时,优先级有高低之分:
        

13.Nacos集群搭建

        搭建一个有三个nacos节点,一个nginx反向代理(负载均衡)的nacos集群,这里以单点数据库为例。三个nacos节点的地址为:
  • nacos1:127.0.0.1:8845
  • nacos2:127.0.0.1:8846
  • nacos3:127.0.0.1:8847
        

(1)初始化数据库

        Nacos 默认数据存储在内嵌数据库 Derby 中,不属于生产可用的数据库。官方推荐的最佳实践是使用带有主从的高可用数据库集群,主从模式的高可用数据库。这里我们以单点的数据库为例。
        首先新建一个数据库,命名为 nacos,导入资料中的SQL语句。

(2)配置nacos

        1)进入 nacos 的 conf 目录,修改配置文件 cluster.conf.example,重命名为 cluster.conf。
        2)在cluster.conf中添加三个nacos节点的地址:
#it is ip
#example
127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847
        3)修改 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=1234
        4)将 nacos 文件夹复制三份,分别命名为:nacos1、nacos2、nacos3,分别在各自application.properties 文件中修改端口号为8845、8846、8847。
        5)分别启动三个nacos节点:
./startup.cmd

(3)配置Nginx反向代理

        1)修改 nginx 文件夹下的 conf/nginx.conf 文件,配置如下:
upstream nacos-cluster {
    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 {
        proxy_pass http://nacos-cluster;
    }
}
        2)启动nginx,在浏览器访问:http://localhost/nacos,访问成功表示nacos集群搭建成功

四、★基于Feign远程调用

1.基于RESTTemplate远程调用的问题

        以前利用 RestTemplate 发起远程调用的代码:
        
  • 代码可读性差,编程体验不统一
  • 参数复杂URL难以维护

2.Feign简介

        Feign 是一个声明式的 http 客户端,官方地址:https://github.com/OpenFeign/feign
        作用就是解决上面提到的问题,实现 http 请求的发送。

3.使用Feign实现远程调用

        这里是order-service远程调用user-service,因此要在order-service中配置Feign客户端。
(1)导入坐标
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
(2)添加注释
        在 order-service 启动类添加@EnableFeignClients注解开启 Feign
(3)编写FeignClient接口
  • @FeignClient("user-service"):参数填写的是微服务名
  • @GetMapping("/user/{id}"):参数填写的是请求路径
  • 这个客户端主要是基于 SpringMVC 的注解 @GetMapping 来声明远程调用的信息
  • Feign可以帮助我们发送 http 请求,因此无需自己使用 RestTemplate 来发送了
@FeignClient("user-service")
public interface UserClient {
    
    @GetMapping("/user/{id}")
    User getById(@PathVariable("id") Long id);
}
(4)使用FeignClient中定义的方法代替RestTemplate
@Autowired
private UserClient userClient;

//基于Feign的远程调用
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
    //1.根据id查询订单
    Order order = orderService.queryOrderById(orderId);
    //2.查询用户
    User user = userClient.getById(orderId);
    //3.将user信息封装到order中
    order.setUser(user);
    //4.返回order
    return order;
}

4.自定义Feign的配置

        Feign 可以支持很多的自定义配置,如表所示:
        
【tips】日志级别分为四种:
  • NONE:不记录任何日志信息(默认值)
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
        一般情况下,默认值就能满足使用,如果要自定义配置,有两种方式。以修改日志级别为例来演示使用和自定义配置:
(1)法一:基于配置文件自定义配置
  • 全局生效,针对所有服务
    feign:  
      client:
        config: 
          default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
            loggerLevel: FULL #  日志级别 
  • 局部生效,针对某个微服务
    feign:  
      client:
        config: 
          user-service: # 针对某个微服务的配置
            loggerLevel: FULL #  日志级别 
(2)法二:基于Java代码自定义配置
        声明一个配置类,在配置类中声明一个 Logger.Level 的对象
public class DefaultFeignConfiguration  {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC; // 日志级别为BASIC
    }
}
  • 全局生效:将上面的配置类放到启动类的 @EnableFeignClients 注解中
    @EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class) 
  • 局部生效:将上面的配置类放到对应的 @FeignClient 注解中
    @FeignClient(value = "user-service", configuration = DefaultFeignConfiguration.class) 

5.性能优化

        Feign 底层发起 http 请求,依赖于其它框架。其底层客户端实现有:
  • URLConnection:默认实现,不支持连接池
  • Apache HttpClient :支持连接池
  • OKHttp:支持连接池
        因此提高 Feign 性能的主要手段有:
  • 使用连接池代替默认的 URLConnection
  • 日志级别应该尽量用 basic/none,可以有效提高性能。 这里用 Apache 的HttpClient来演示连接池。
(1)导入坐标
<!--httpClient的依赖 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>
(2)配置信息
feign:
  client:
    config:
      default: # default全局的配置
        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

6.Feign的最佳实践分析

        
        基于Feign实现远程调用时,在消费者order-service和提供者user-service中存在相同的代码,对于这些相同代码,我们有两种处理方式:

(1)继承接口

        
  • 定义一个 API 接口,利用定义方法,并基于 SpringMVC 注解做声明
  • order-service中的 Feign 客户端和user-service中的Controller 都继承该接口
        缺点:
  • 服务提供方和服务消费方存在紧耦合
  • 参数列表中的注解映射并不会继承,因此 Controller 中必须再次声明方法、参数列表、注解

(2)抽取模块

        
        将 FeignClient 抽取为一个独立模块,并把接口有关的 pojo、默认的 Feign配置都放到这个模块中,提供给所有消费者使用。 
        例如:将 UserClient、User、Feign 的默认配置都抽取到一个 feign-api 包中,让所有微服务都依赖这个模块,即可直接使用。
        1)创建新模块
        2)导入坐标
<!--Feign客户端依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
        3)将 order-service中 的 UserClient 和 pojo User 都复制到 feign-api 项目中
        
        4)扫描FeignClient
        当定义的 FeignClient 不在 SpringBootApplication 的扫描包范围下时,这些 FeignClient 就不能使用。 
        解决方案:修改 order-service 启动类上的 @EnableFeignClients 注解,指定FeignClient所在的包或字节码文件。
@EnableFeignClients(basePackages = "com.itcast.feign.clients")

五、★统一网关Gateway

1.Gateway简介

        Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。 
        Gateway 网关是服务的守门神,是所有微服务的统一入口。
        在 SpringCloud 中网关的实现包括两种:
  • gateway
  • zuul
        zuul 是基于 Servlet 实现,属于阻塞式编程。而 Spring Cloud Gateway 则是基于 Spring5 中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

2.网关的作用

(1)权限控制
        网关作为微服务入口,需要校验用户是否有请求资格,如果没有则进行拦截。
(2)路由和负载均衡
        一切请求都必须先经过 gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
(3)限流
        当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

3.网关的入门使用

(1)新建模块 gateway,引入网关和服务发现依赖坐标
<!--网关-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!--nacos服务发现依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
(2)编写启动类
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}
(3)编写路由配置、nacos地址和路由规则
server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
【tips】将符合 -Path 规则的一切请求,都代理到 uri参数指定的地址。上面的例子中,将 /user/** 开头的请求,代理到 lb://userservice,其中 lb 是负载均衡(LoadBalance),根据服务名拉取服务列表,实现负载均衡。
(4)启动网关服务进行测试
        

4.路由断言工厂(Route Predicate Factory)

        上面在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory 读取并处理,转变为路由判断的条件。 
        例如 Path=/user/** 是按照路径匹配,这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory 类来处理的。
        Spring提供的11中基本的断言工厂:
        

5.路由过滤器GateFilter

(1)概述
        
        GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求微服务返回的响应做处理。
        Spring提供了31种不同的路由过滤器工厂。官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories
        

(2)局部过滤器

        以 AddRequestHeader 为例,给所有进入 user-service 的请求添加一个请求头:Truth,China No.1!
        
        1)在gateway的配置文件中配置过滤器
gateway:
  routes: # 网关路由配置
    - id: user-service # 路由id,自定义,只要唯一即可
      # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
      uri: lb://user-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称
      predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
        - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
      filters:
        - AddRequestHeader=Truth,China No.1! # 添加请求头
        2)获取请求头信息
        在user-service中controller的任意方法添加一个参数:
@GetMapping("/{id}")
    public User queryById(@PathVariable("id") Long id, @RequestHeader String truth) {
        System.out.println("truth" + truth);
        return userService.queryById(id);
    }

(3)默认过滤器(default-filters)

        默认过滤器对所有的路由都生效,则可以将过滤器工厂写到 default-filters 下:
spring:
  cloud:
    gateway:
      default-filters:
        - AddRequestHeader=Key,value # 添加请求头

(4)全局过滤器(GlobalFilter)

        上面介绍的网关提供的 31 种过滤器工厂,每一种过滤器的作用都是固定。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。
        全局过滤器的作用也是处理一切进入网关的请求和微服务响应,这与 GatewayFilter 的作用一样。但区别在于 GlobalFilter 的逻辑可以写代码来自定义规则;而 GatewayFilter 通过配置定义,处理逻辑是固定的。
        因此,GlobalFilter也可以理解为全局自定义过滤器。
        下面以判断登录用户权限为例,演示GlobalFilter:
        1)需求
                定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件,如果同时满足则放行,否则拦截:
  • 参数中是否有 authorization authorization
  • 参数值是否为 admin
        2)代码实现
  • 创建自定义接口,实现GlobalFilter接口
  • 重写filter方法
    //@Order(-1) //设置过滤器优先级
    @Component
    public class AuthorizeFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //1.获取信息
            MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
            //2.获取第一个 authorization 对应的参数
            String authorization = params.getFirst("authorization");
            //3.进行判断
            if ("admin".equals(authorization)) {
                //4.满足要求,放行
                return chain.filter(exchange);
            }
            //5.不满足要求,拦截
            //5.1 设置拦截状态码
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            //5.2 设置拦截
            return exchange.getResponse().setComplete();
        }
    
        /**
         * 设置过滤器优先级,数越小优先级越高
         * @return
         */
        @Override
        public int getOrder() {
            return -1;
        }
    }

(5)过滤器顺序

        
        请求进入网关会碰到三类过滤器:DefaultFilter、当前路由过滤器、GlobalFilter。
        请求路由后,网关会将三者合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
        排序规则:
  • 每一个过滤器都必须指定一个 int 类型的 order 值,order 值越小,执行顺序越靠前
  •         GlobalFilter 通过实现 Ordered 接口,或者使用 @Order 注解来指定 order 值,由我们自己指定
  •         路由过滤器和 defaultFilter 的 order 由 Spring 指定默认是按照声明顺序从1递增
  • 当过滤器的 order 值一样时,会按照 default-filters > 路由过滤器 > GlobalFilter 的顺序执行。

(6)跨域问题处理

        1)什么是跨域?
                跨域:域名不一致就是跨域,主要包括:
                跨域问题:浏览器禁止请求发起者(客户端)服务端发生跨域ajax请求,请求被浏览器拦截的问题
        2)解决方案——CORS
                网关处理跨域问题采用的是CORS方案,只需要简单配置即可实现:
spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 allowedOrigins: “*” 允许所有网站
              - "http://localhost:8090"
            allowedMethods: # 允许跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

六、Docker

1.简介

        Docker是一个快速交付应用、运行应用的技术。目前大型项目组件较多,运行环境复杂,部署时会碰到一些问题:
  • 依赖关系复杂,容易出现兼容性问题
  • 开发、测试、生产环境有差异

(1)Docker解决依赖兼容问题

  • Docker允许开发中将应用、依赖、函数库、配置一起打包,形成可移植镜像
  • Docker应用运行在容器中,使用沙箱机制,相互隔离

(2)Docker解决不同生产环境的系统差异问题

  • Docker将用户程序与所需要调用的系统(比如Ubuntu)函数库一起打包
  • Docker运行到不同操作系统时,直接基于打包的库函数,借助于操作系统的Linux内核来运行

2.Docker与虚拟机的比较

  • docker是一个系统进程;虚拟机是在操作系统中的操作系统
  • docker体积小、启动速度快、性能好;虚拟机体积大、启动速度慢、性能一般
        

3.镜像和容器

(1)概念
  • 镜像(Image):Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像。
  • 容器(Container):镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器做隔离,对外不可见。
(2)Docker和DockerHub
        DockerHub是一个Docker镜像的托管平台,这样的平台称为Docker Registry。 国内也有类似于DockerHub 的公开服务,比如 网易云镜像服务、阿里云镜像库等。
(3)Docker架构
        Docker是一个C-S架构的程序,由两部分组成: 
  • 服务端(server):Docker守护进程,负责处理Docker指令管理镜像、容器
  • 客户端(client):通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令。
        

4.安装Docker

        将Docker安装到CentOS下。
(1)更新本地镜像源
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
(2)输入安装命令
yum install -y docker-ce
(3)开启指定端口或直接关闭***
(4)启动、停止、重启docker、查看状态、版本命令
systemctl start docker  # 启动docker服务

systemctl stop docker  # 停止docker服务

systemctl restart docker  # 重启docker服务

systemctl status docker #查看docker状态

docker -v # 查看docker版本
(5)配置镜像加速
        docker官方镜像仓库网速较差,需要设置国内镜像服务: 参考阿里云的镜像加速文档:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
(6)开启开机自动启动docker
systemctl enable docker

5.Docker基本操作命令

 (1)镜像操作命令

        
        1)案例1:从DockerHub中拉取一个nginx镜像并查看
  • 去镜像仓库(如DockerHub)搜索nginx镜像
        
  • 通过命令:docker pull nginx 拉取需要的镜像
        
【tips】镜像名称一般分两部分组成:[repository]:[tag],tag指的是版本,不写默认最新版latest。
  • 通过命令:docker images 查看拉取到的镜像
        
        2)案例2:将nginx镜像导出磁盘,将案例1拉取的镜像删除,然后再加载回来(练习帮助文档--help的使用)
  • 利用docker save --help命令查看打包命令docker save的用法
        
  • 使用打包命令docker save -o nginx.tar nginx:latest将镜像打包
        
  • 利用docker load --help命令查看docker load的用法
        
  • 通过命令 docker rmi 将案例1拉取的镜像删除
        
  • 通过命令 docker load -i 包名.tar 加载镜像nginx.tar
        

(2)容器操作命令

        
        1)案例1:创建并运行一个Nginx容器
  • 去docker hub查看Nginx的容器运行命令
    docker run --name myNginx -p 80:80 -d nginx
        --name myNginx:给创建的容器起名为myNginx
        -p 80:80:将宿主机端口与容器端口映射,左边是宿主机端口,右边是容器端口
        -d:后台运行容器
        nginx:基于nginx(:latest)镜像创建该容器
  • 通过命令 docker ps 查看所有正在运行的容器和状态
        
tips-a:通过docker ps -a 可以查看所有容器和状态,包括运行和没运行的。
  • 访问nginx,并通过 docker logs myNginx 查看myNginx容器日志
        
【tips】通过 docker logs -f myNginx 可以持续查看日志
        2)进入myNginx容器,修改HTML文件内容,添加“hello Docker”,退出容器,停止容器并删除该容器
  • 通过 docker exec -it myNginx bash 进入myNginx容器
        
        -it:给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互
        bash:进入容器后执行的命令,bash是一个linux终端交互命令。加上bash,进入容器后就可以用Linux的一些命令了。
  • 进入nginx的HTML所在目录 /usr/share/nginx/html
    cd /usr/share/nginx/html
  • 修改index.html的内容
    sed -i 's#Welcome to nginx#hello Docker#g' index.html
【tips】exec命令可以进入容器修改文件,但是一般不推荐在容器内修改文件。
  • 使用exit命令退出容器
  • 使用 docker stop myNginx 命令停止容器
  • 使用 docker rm  myNginx 命令删除容器

(3)数据卷(volume)操作命令

        数据卷(volume):是一个虚拟目录,指向宿主机文件系统中的某个目录,二者相互关联,在数据卷中进行的修改会保存到宿主机对应目录中,同样地在宿主机修改也会影响数据卷中的内容
        1)容器与数据的耦合问题
  • 不便于修改当要修改容器的html内容时,需要进入容器内部修改,很不方便
  • 数据不可复用:在容器内的修改对外是不可见的。所有修改对新创建的容器是不可复用的
  • 升级维护困难:数据在容器内,如果要升级容器必然删除旧容器,所有数据都跟着删除了
        2)作用
               将容器与数据分离,解耦合,方便操作容器内数据,保证数据安全。
        3)操作命令
docker volume create Name 创建一个名为Name的volume
docker volume inspect Name
显示指定Name或多个volume的信息
docker volume ls 列出所有volume
docker volume prune 删除所有未使用的volume
docker volume rm Name 删除指定Name或多个volume

(4)挂载数据卷

        1)案例1:创建并运行mn容器,使用数据卷操作修改HTML文件内容,添加“hello Docker”
  • 创建并运行mn容器,把html数据卷(位于宿主机)挂载到容器内的/usr/share/nginx/html这个目录
    docker run --name mn -p 80:80 -v html:/usr/share/nginx/html -d nginx
    -v html:/usr/share/nginx/html    将html这个数据卷挂载到容器内的/usr/share/nginx/html这个目录中
【tips】①如果在创建容器时指定的数据卷不存在,则该数据卷会自动被创建出来。
             ②目录挂载与数据卷挂载的语法是类似的:
                    -v [宿主机文件]:[容器内文件]
                    -v [宿主机目录]:[容器内目录]
  • 查看html数据卷在宿主机中的位置
    docker volume inspect html

  • 进入挂载点目录并修改文件
    #进入该目录 
    cd /var/lib/docker/volumes/html/_data 
    
    # 修改文件 
    vim index.html
        2)案例2:创建并运行一个MySQL容器,将宿主机目录直接挂载到容器目录
  • 将资料中的mysql.tar文件上传到虚拟机,通过load命令加载为镜像
  • 创建目录 /tmp/mysql/data 和目录 /tmp/mysql/conf,将资料提供的hmy.cnf文件上传到/tmp/mysql/conf
  • 创建并运行MySQL容器,起名为mysql
    docker run \
      --name mysql \
      -e MYSQL_ROOT_PASSWORD=1234 \  #设置mysql密码
      -p 3306:3306 \  #将宿主机端口与容器端口映射   -v /usr/local/mysqlDocker/data:/var/lib/mysql \  #目录挂载:目录挂目录
      -v /usr/local/mysqlDocker/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \  #目录挂载:文件挂文件
      -d \
      mysql:5.7.25
  • 打开Navicat测试数据库连接

6.基于Dockerfile自定义镜像

(1)镜像结构

        
  • 基础镜像层(BaseImage):包含基本的系统函数库、环境变量、文件系统 
  • 入口(Entrypoint):是镜像中应用程序启动的命令 
  • 其它层:在BaseImage基础上添加依赖、安装程序、完成整个应用的安装和配置

(2)Dockerfile

        Dockerfile就是一个文本文件,其中包含若干指令(Instruction),用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层Layer。
        常用指令:
        
        更新详细语法说明,参考官网文档: https://docs.docker.com/engine/reference/builder
# 指定基础镜像
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

(3)自定义镜像

        1)案例1:基于Ubuntu镜像构建一个新镜像,运行一个java项目
  • 新建一个空文件夹docker-demo
  • 拷贝资料中的docker-demo.jar文件到docker-demo目录
  • 拷贝资料中的jdk8.tar.gz文件到docker-demo目录
  • 拷贝资料提供的Dockerfile到docker-demo目录
  • 在docker-demo目录下执行命令
    docker build -t javaweb:1.0 .
        build:构建镜像
        -t:后面指定新镜像名和版本
        .:这个"."表示Dockerfile所在目录
  • 创建并运行web容器,并进行访问测试
    docker run --name web -p 8090:8090 -d javaweb:1.0
        2)案例2:基于java:8-alpine镜像构建一个新镜像,运行一个java项目
                如果再要构建一个新的Java镜像,那么案例1中Dockerfile文件安装jdk、配置环境变量等指令需要重复去写,java:8-alpine镜像将这些指令构建的层封装成一个镜像,以后构建java项目只需要基于java:8-alpine为基础镜像构建即可。
  • 新建一个空文件夹docker-demo2
  • 拷贝资料中的docker-demo.jar文件到docker-demo2目录
  • 重新编写Dockerfile文件:
    # 指定基础镜像
    FROM java:8-alpine
    #将app拷贝到镜像中
    COPY ./docker-demo.jar /tmp/app.jar
    # 暴露端口
    EXPOSE 8090
    # 入口,java项目的启动命令
    ENTRYPOINT java -jar /tmp/app.jar
  • 在docker-demo目录下执行命令
    docker build -t javaweb:2.0 .
  • 创建并运行web容器,并进行访问测试
    docker run --name web -p 8090:8090 -d javaweb:2.0
7.搭建私有镜像仓库
        镜像仓库( Docker Registry )有公共的和私有的两种形式: 
  • 公共仓库:例如Docker官方的 Docker Hub,国内也有一些云服务商提供类似于 Docker Hub 的公开服务,比如 网易云镜像服务、DaoCloud 镜像服务、阿里云镜像服务等。
  • 私有仓库:除了使用公开仓库外,用户还可以在本地搭建私有 Docker Registry。企业自己的镜像最好是采用私有Docker Registry来实现。
(1)私有仓库搭建
  • ***采用的是http协议,默认不被Docker信任,所以搭建私有仓库前需要做一个配置:
    # 打开要修改的文件
    vi /etc/docker/daemon.json
    # 添加内容:
    "insecure-registries":["http://192.168.152.100:8080"]
    # 重加载
    systemctl daemon-reload
    # 重启docker
    systemctl restart docker
  • 使用DockerCompose部署带有图象界面的DockerRegistry,命令如下:
    version: '3.0'
    services:
      registry:
        image: registry
        volumes:
          - ./registry-data:/var/lib/registry
      ui:
        image: joxit/docker-registry-ui:static
        ports:
          - 8080:80
        environment:
          - REGISTRY_TITLE=Docker_resp
          - REGISTRY_URL=http://registry:5000
        depends_on:
          - registry
  • 访问测试,出现以下页面说明搭建成功:
        
(2)操作私有仓库
        将nginx:latest推送到私有仓库:
  • 推送本地镜像到仓库前都必须重命名(docker tag)镜像,并以镜像仓库地址为前
    docker tag nginx:latest 192.168.152.100:8080/nginx:1.0 
  • 推送重命名后的镜像
    docker push 192.168.152.100:8080/nginx:1.0 
  • 拉取镜像:复制代码并拉取
    docker pull 192.168.152.100:8080/nginx:1.0
        

七、Docker Compose

1.简介

        Docker Compose可以基于Compose文件进行快速部署分布式应用。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"
【tips】可以看出Compose文件中的指令与Dockerfile文件中的指令是对应的。DockerCompose的详细语法参考官网:https://docs.docker.com/compose/compose-file/

2.安装DockerCompose

(1)下载DockerCompose或上传资料中的安装包
(2)修改文件权限为可执行(+x)
chmod +x /usr/local/bin/docker-compose
(3)配置Base自动补全命令
curl -L https://raw.githubusercontent.com/docker/compose/1.29.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
【tips】若报错,修改hosts文件:
echo "199.232.68.133 raw.githubusercontent.com" >> /etc/hosts

3.基于DockerCompose部署微服务

        将cloud-demo微服务集群利用DockerCompose部署到Linux虚拟机。
(1)实现思路
  • 查看提供的cloud-demo文件夹,里面已经编写好了docker-compose文件
  • 修改cloud-demo项目,将数据库、nacos地址都命名为docker-compose中的服务名
  • 使用maven打包工具,将项目中的每个微服务都打包为app.jar
  • 将打包好的app.jar拷贝到cloud-demo中的每一个对应的子目录中
  • 将cloud-demo上传至虚拟机,利用 docker-compose up -d 来部署
(2)具体实现
  • 资料提供的cloud-demo文件夹,里面已经编写好了docker-compose文件,而且每个微服务都准备了一个独立的目录:
          
  • docker-compose文件内容如下:
    version: "3.2"
    
    # 5个微服务
    services:
      # nacos:作为注册中心和配置中心
      nacos: # 自定义容器名,下同
        image: nacos/nacos-server # 基于nacos/nacos-server镜像构建
        environment:
          MODE: standalone # 单点模式启动
        ports:
          - "8848:8848"
      mysql:
        image: mysql:5.7.25 # 基于mysql 5.7.25版本构建镜像
        environment:
          MYSQL_ROOT_PASSWORD: 123
        # 数据卷挂载,这里挂载了mysql的data、conf目录
        volumes:
          - "$PWD/mysql/data:/var/lib/mysql"
          - "$PWD/mysql/conf:/etc/mysql/conf.d/"
      # userservice、orderservice、gateway:都是基于各自文件夹下的Dockerfile文件临时构建的镜像
      userservice:
        build: ./user-service
      orderservice:
        build: ./order-service
      gateway:
        build: ./gateway
        ports:
          - "10010:10010"
  • 查看微服务目录,可以看到每一个微服务都包含Dockerfile文件:
        
  • Dockerfile文件内容如下:
    FROM java:8-alpine # 基于java:8-alpine构建镜像
    COPY ./app.jar /tmp/app.jar	# 将当前目录下的app.jar包拷贝到/tmp目录下
    ENTRYPOINT java -jar /tmp/app.jar # 执行启动java项目的命令
  • 修改微服务配置:因为微服务要部署为docker容器,而容器之间互联不是通过IP地址,而是通过容器名。因此需要将order-service、user-service、gateway模块的mysql、nacos地址配置都修改为基于容器名的访问:
    spring:
      datasource:
        url: jdbc:mysql://mysql:3306/cloud_order?useSSL=false
        username: root
        password: 123
        driver-class-name: com.mysql.jdbc.Driver
      application:
        name: orderservice
      cloud:
        nacos:
          server-addr: nacos:8848 # nacos服务地址
  • 打包:将每个微服务都打包。因为之前查看到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>
  • 编译打包好的app.jar文件,需要放到Dockerfile的同级目录中
        
  • 将cloud-demo文件夹上传到虚拟机中,由DockerCompose部署:
    docker-compose up -d


全部评论

相关推荐

拉丁是我干掉的:把上海理工大学改成北京理工大学。成功率增加200%
点赞 评论 收藏
分享
3 8 评论
分享
牛客网
牛客企业服务