Spring Cloud 微服务框架

Spring Cloud 微服务框架

一、 框架图与解析

在这里插入图片描述
框架中,网关服务、服务、配置中心服务都注册到Eureka服务中,它就相当于作为一个服务管理与调度中心,如果Eureka开启高可用模式,服务本身也要开启服务的注册与列表拉取;客户端发送请求,至Spring Cloud Gateway 网关服务处,网关负责请求过滤与转发(路由),支持通过Ribbon负载均衡与Hystrix熔断机制,并能配置解决跨域问题;请求分发至服务后,服务间通过Feign简化服务的调用,同时也内嵌支持ribbon负载均衡与Hystrix熔断机制,可以通过配置实现请求压缩;而Spring Cloud Config配置中心服务的目的在于,对各个服务的配置进行统一的管理,通过Git远程修改与推送;但是这种配置更新无法在服务中立即生效,需要重启服务才行,因此引入Spring Cloud bus,用轻量消息代理连接分布式节点,可以广播配置文件的更改或者服务的监控管理,开启bus-refresh节点,并在配置中心和服务A…中配置RabbitMQ,当配置修改推送后,通过向配置中心发送.../acutator/bus-refreshpost请求,使各个监听的服务得知需要更新配置文件了。

二、实践

各个模块的作用在上方已经说明,接下来就是来对每个模块的实践预配置,请根据目录把下列内容当做spec参考

下面这个demo各个模块介绍:

  • parent:对各子模块要用到的依赖做统一版本管理;
  • eureka server:服务的发现和注册中心,方便服务间的交互,可以配置服务失效剔除时间、自我保护等;可以将高可用、网关、服务、消费、配置中心都注册;
  • gateway server:负责对请求的路由(ip、断言、过滤),转发请求,同时可以配置负载均衡、熔断;跨域问题可以通过配置解决,过滤可以添加全局、服务、自定义(全局、服务)过滤器;
  • config server:可以对配置进行git统一管理,动态更新;
  • user service:服务提供方,可以直接或通过网关访问;
  • customer:服务消费方,可以使用RestTemplate或更简单的feign调用user服务,调用时可以开启负载均衡和熔断;

PS:项目源码下载地址

2.1 parent module pom.xml 版本控制

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <name>Parent</name>
    <description>Version Control</description>

    <groupId>com.coderwhat</groupId>
    <artifactId>parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>  <!-- 父工程要将默认jar打包改为pom -->

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- 父项目的pom.xml文件的相对路径,默认值为../pom.xml;而后再是本地仓库、远程仓库 -->
    </parent>

    <modules>   <!-- 子模块 -->
        <module>../eureka</module>
    </modules>

    <properties>
        <!-- 项目编码,自动识别 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!-- maven输出文件编码 -->
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
        <mapper-starter.version>2.1.5</mapper-starter.version>
        <mysql.version>5.1.46</mysql.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- 通用Mapper启动器 -->
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
                <version>${mapper-starter.version}</version>
            </dependency>
            <!-- mysql 驱动 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <!-- Maven默认编辑器插件maven-compiler-plugin采用的Java编译级别为1.5,需要调整到1.8 -->
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>    <!-- 与springboot dependencies中保持一致 -->
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.2</version>    <!-- 与springboot dependencies中保持一致 -->
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>
                                repackage  <!-- 打包时将引用jar包都打包成一个完整jar -->
                            </goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

2.2 Eureka Server

2.2.1 Eureka知识点

eureka是作为一个服务的注册、发现中心,帮助管理、调用服务的;
使用时有以下几点需要注意:

  • eureka依赖分serverclient的;
  • eureka服务器配置除非是高可用,否则需要关闭自身的注册与拉取;
  • eurekajar包中自带spring-cloud-starter-netflix-ribbon的,所以在配置ribbon负载均衡时无需再次引入。
  • eureka client如果需要拉取服务列表,除了在配置文件中配置eureka外,还需要在启动类注解@EnableDiscoveryClient
2.2.2 代码与配置文件
2.2.2.1 依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.coderwhat</groupId>
    <artifactId>eureka-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <artifactId>parent</artifactId>
        <groupId>com.coderwhat</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <!-- eureka server -->
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

</project>
2.2.2.2 启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer //声明Eureka Server服务
@SpringBootApplication
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}
2.2.2.3 配置文件:
server:
  port: ${eurekaport:10068} # 如果虚拟机中有eurekaport变量就引用,否则使用10068
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      # eureka服务地址,如果是集群的话,需要指定集群eureka地址
      defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}
    # 默认情况下,是会注册服务并拉取服务列表的,但在非集群(高可用)情况下,需要关闭
    register-with-eureka: false # 不注册本服务
    fetch-registry: false # 不拉取服务列表
      # 配置eureka高可用,需要配置如下两步:(10069为另一台eureka server)
      # 1.配置服务地址,有两种方式:
      # a.在当前服务VM Option中配置defaultZone变量:
      ## -Dport=10068 -DdefaultZone=http://127.0.0.1:10069/eureka
      # b.将配置中的defaultZone值改为:
      ## defaultZone: http://127.0.0.1/10068/eureka,http://127.0.0.1/10069/eureka
      # 2. 将eureka server注册服务和拉取列表都打开(默认打开)
  server:
    # 服务端配置服务失效剔除间隔,默认60s
    eviction-interval-timer-in-ms: 60000 # 单位ms
    # 关闭自我保护模式(默认打开) 这样就不会出现服务关闭,eureka猜测可能是网络原因导致依旧保留服务状态
    enable-self-preservation: false   # 开发时需要关闭,方便调试

2.3 Gateway Server

2.3.1 Spring Cloud Gateway 知识点

spring cloud gateway网关的主要作用是路由和过滤,具体又可以分为以下三种:

  • 断言(Predicate):允许开发人员自定义Http Request请求的任意信息,最常见的有请求头、请求参数;
  • 过滤(Filter):对请求或响应返回的消息做处理;
  • 路由(route) :路由信息的组成:由一个ID、一个目的URL、一组断言工厂、一组Filter组成。如果路由断言为
    真,说明请求URL和配置路由匹配。

配置网关需要注意一下几点:

  • 导入eureka client依赖,并在启动类上加上@EnableDiscoveryClient开启eureka发现;
  • 自定义路由过滤器可以参照已有过滤器实现来写,全局过滤器实现GlobalFilter即可,不需再配置文件中配置;
2.3.2 实践
2.3.2.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.coderwhat</groupId>
    <artifactId>gateway-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <artifactId>parent</artifactId>
        <groupId>com.coderwhat</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <!-- gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

</project>
2.3.2.2 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient  //开启Eureka客户端发现功能
@SpringBootApplication
public class GatewayServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayServerApplication.class, args);
    }
}
2.3.2.3 配置文件
server:
  port: 10010
spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      ## 默认过滤器,对所有路径都生效
      default-filters:
        - AddResponseHeader=test-request-message, hello
        - AddResponseHeader=how-are-you?, world
      ## 处理跨域问题
      globalcors:
        cors-configurations:
          '[/**]':
            allowedHeaders: "*"
            #allowedOrigins: * # 这种写法或者下面的都可以,*表示全部
            allowedOrigins:
              - "http://docs.spring.io"
            allowedMethods:
              - GET
              - POST
      ## 配置路由
      routes:
        - id: user-service-route  # 路由id,名字随意
          ## uri: http://127.0.0.1:7031  # 代理服务地址,写死的方式不好
          # lb: loadbalance表示从eureka server获取
          uri: lb://user-service  # 通过服务名,动态从eureka server获取
#          predicates:	# 标准设置的是所有满足下列path的都能发送请求
#            - Path=/user/**
          ## 下列则是想实现:所有路径且不带/uer的都能自动添加路径,并使用自定义过滤器获取name属性
          predicates:  # 路由断言:匹配映射地址,下方即为路径全匹配
            - Path=/**
          filters:   # 过滤器,对上方断言匹配请求过滤操作
            - PrefixPath=/user  # 请求路径添加指定前缀
            - GetParam=name     # 自定义过滤器,获取请求参数name
#             - StripPrefix=1   # 请求路径去除前缀,1表示一个路径,2表示两个,以此类推


## eureka配置
eureka:
  client:
    service-url:
      defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}
  # 使用ip注册、访问服务
  instance:
    prefer-ip-address: true

## 配置hystrix熔断策略
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000
      circuitBreaker:
        errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
        sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
        requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20

## 配置ribbon负载均衡
ribbon:
  ConnectTimeout: 1000 # 连接超时时长
  ReadTimeout: 2000 # 数据通信超时时长
  MaxAutoRetries: 0 # 当前服务器的重试次数
  MaxAutoRetriesNextServer: 0 # 重试多少次服务
  OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试
2.3.2.4 自定义路由过滤与全局过滤
  • 路由过滤器
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 这里定义的是断言后filters里的过滤器
 */
@Component
public class GetParamGatewayFilterFactory extends AbstractGatewayFilterFactory<GetParamGatewayFilterFactory.Config> {

    static final String PARAM_NAME = "param";

    public static class Config {
        //对应在配置过滤器的时候指定的参数名:name
        private String param;

        public String getParam() {
            return param;
        }

        public void setParam(String param) {
            this.param = param;
        }
    }

    public GetParamGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(PARAM_NAME);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // http://127.0.0.1:7031/8?name=zhangsan config.param ==> name

            //获取请求参数中param对应的参数名的参数值
            ServerHttpRequest request = exchange.getRequest();
            if (request.getQueryParams().containsKey(config.param)) {
                request.getQueryParams().get(config.param)
                        .forEach(value -> System.out.printf("----局部过滤器----%s = %s ----", config.param, value));

            }
            return chain.filter(exchange);
        };
    }
}
  • 全局过滤器
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 这里定义的是全局过滤器,不用单独配置,只用实现GlobalFilter即可
 */
@Component
public class DemoGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("------------全局过滤器MyGlobalFilter------------");
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        // 过滤请求参数不带token的
        if (StringUtils.isBlank(token)){
            //设置相应状态码为未授权
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        //值越小越先执行
        return 0;
    }
}

2.3 Config Server

2.3.1 Spring Cloud Config 知识点

配置中心是作为对服务的配置文件进行统一管理的组件,集成spring cloud bus实现配置文件的实时生效;
配置配置中心的时候,有以下几点要注意:

  • config配置依赖分为serverclient
  • config server启动类需要注解@EnableConfigServer,开启配置服务
  • @EnableDiscoveryClient注解是用来标注从eureka serve拉取服务列表的,与注册无关,所以像config server可以不用注解;
  • spring cloud bus除了在config server中暴露节点,需要进行统一配置管理的服务,也需要引入actuator依赖;
  • 配置的git仓库如果是需要权限校验则需要额外配置;
2.3.2 实践
2.3.2.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>config-server</artifactId>

    <parent>
        <artifactId>parent</artifactId>
        <groupId>com.coderwhat</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <!-- config server -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <!-- eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- spring cloud bus -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-bus</artifactId>
        </dependency>
        <!-- RabbitMQ -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
        </dependency>
    </dependencies>

</project>
2.3.2.2 配置文件
server:
  port: 10101

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          # 服务配置文件存放git地址
          uri: https://gitee.com/xxxx/xxxx-springcloud-config.git
    # 配置rabbitmq信息;如果是都与默认值一致则不需要配置
    rabbitmq:
      host: localhost
      port: 5672
      username: guest
      password: guest

eureka:
  client:
    service-url:
      defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}

## 集成 spring-cloud-bus 需要配置RabbitMQ并暴露bus-refresh节点
management:
  endpoints:
    web:
      exposure:
        # 暴露触发消息总线的地址
        include: bus-refresh

2.3.2.3 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableConfigServer //开启配置服务
@SpringBootApplication
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

2.4 User Service

2.4.1 服务(提供方)知识点

user-service服务作为服务的提供方,需要注册、拉取eureka,集成config clientspringcloud bus配置实现git托管动态更新,进行简单的数据库操作;
有以下几点要注意:

  • 集成config clientspringcloud bus实现动态刷新配置,需要在client端额外引入spring-boot-starter-actuator依赖;
  • 当新配置被push上后,发送post请求,content-typeapplication/json,访问../actuator/bus-refresh即可刷新配置立即生效;
  • 当服务中有使用到配置文件中的变量时,需要加上@RefreshScope刷新配置注解,这样才能在调用时实时生效;
2.4.2 实践
2.4.2.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.coderwhat</groupId>
    <artifactId>user-service</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <artifactId>parent</artifactId>
        <groupId>com.coderwhat</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <!-- web + tomcat -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 通用Mapper -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- config client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <!-- spring cloud bus -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-bus</artifactId>
        </dependency>
        <!-- RabbitMQ -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-stream-binder-rabbit</artifactId>
        </dependency>
        <!-- actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

</project>
2.4.2.2 配置文件(本地bootstrap.yml及git上user-dev.yml)
  • bootstrap.yml
    这里主要是配置springcloud configspringcloud bus所关联的RabbitMQeureka client
spring:
  cloud:
    config:
      ## git仓库中的配置文件命令要遵循规则:name-profile.yml
      ## 这里就要与仓库中的配置文件保持一致: user-dev.yml
      name: user  # 要与仓库中的配置文件的application保持一致
      profile: dev  # 要与仓库中的配置文件的profile(环境)保持一致
      label: master # 要与仓库中的配置文件所属的版本(分支)一致
      discovery:
        enabled: true # 使用配置中心
        service-id: config-server # eureka中配置中心的服务名
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

eureka:
  client:
    service-url:
      defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}
  • user-dev.yml
    存放在git服务器上
# 配置端口、服务名称、数据库连接
server:
  port: ${port:7031}
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springcloud
    username: root
    password: root
  application:
    name: user-service

# 配置mybatis
mybatis:
  type-aliases-package: com.coderwhat.user.pojo

# 配置eureka client、服务访问ip与eureka续约配置
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10068/eureka
      # 除了在VM option中配置defaultZone变量实现eureka高可用,还可以将在这里设置值为:
      # http://127.0.0.1:10068/eureka,http://127.0.0.1:10069/eureka
  instance:
    # 更倾向使用ip地址,而不是host名
    prefer-ip-address: true
    # ip地址
    ip-address: 127.0.0.1
    # 续约间隔,默认30s
    lease-renewal-interval-in-seconds: 5
    # 服务时效时间,默认90s
    lease-expiration-duration-in-seconds: 5

# 测试用字段
test: 
  name: user_lisi_zhaowu
2.4.2.3 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import tk.mybatis.spring.annotation.MapperScan;


@SpringBootApplication
@MapperScan("com.coderwhat.user.mapper") //扫描mapper包,自动生成dao接口实现类
@EnableDiscoveryClient  //开启Eureka客户端发现功能
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}
2.4.2.4 pojo
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;
import javax.persistence.Table;

@Data
@Table(name="user")
public class User {
    @Id
    //开启主键自动回填
    @KeySql(useGeneratedKeys = true)
    private Long id;

    // 用户名
    private String userName;

    // 密码
    private String password;

    // 姓名
    private String name;

    // 年龄
    private Integer age;
}

2.4.2.5 mapper
import com.coderwhat.user.pojo.User;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {
}
2.4.2.6 service

接口:

import com.coderwhat.user.pojo.User;

public interface UserService {
    User queryById(Long id);
}

实现:

import com.coderwhat.user.mapper.UserMapper;
import com.coderwhat.user.pojo.User;
import com.coderwhat.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 根据主键查询用户
     * @param id 用户id
     * @return  用户
     */
    @Override
    public User queryById(Long id) {
        return userMapper.selectByPrimaryKey(id);
    }
}
2.4.2.7 controller
import com.coderwhat.user.pojo.User;
import com.coderwhat.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
@RefreshScope   //刷新配置,因为这里使用的test.name调用了配置文件
public class UserServiceController {

    @Autowired
    private UserService userService;

    @Value("${test.name}")  //配置文件中的test.name字段
    private String name;

    @RequestMapping("/{id}")
    public User queryById(@PathVariable Long id){
        /*try {
            //测试2s延迟是否会造成网关、服务间调用的服务降级和熔断
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/

        System.out.println("配置文件中的test.name为:" + name);
        return userService.queryById(id);
    }

}
2.4.2.8 数据创建语句
create database if not exists `springcloud` character set `utf8`

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(100) DEFAULT NULL COMMENT '用户名',
  `password` varchar(100) DEFAULT NULL COMMENT '密码',
  `name` varchar(100) DEFAULT NULL COMMENT '姓名',
  `age` int(10) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

INSERT INTO `user` VALUES ('1', 'ux1', 'ux1', 'u1', '21');
INSERT INTO `user` VALUES ('2', 'ux2', 'ux2', 'u2', '22');
INSERT INTO `user` VALUES ('3', 'ux3', 'ux3', 'u3', '23');
INSERT INTO `user` VALUES ('4', 'ux4', 'ux4', 'u4', '24');
INSERT INTO `user` VALUES ('5', 'ux5', 'ux5', 'u5', '25');

select * from `user` where id < 6;

2.5 consumer

2.5.1 消费服务知识点

consumer作为i服务消费方,通过多方式去调用user-service服务,同样注册到eureka server,继承调用时的负载均衡和熔断;

实现如下:

  • RestTemplate调用服务:

1.服务调用:启动类实例化RestTemplate,然后采用拼接地址和路径的方式调用服务;也可以通过DiscoveryClient获取注册服务实例的方式,再拼接路径,调用服务;
2.负载均衡:@LoadBalanced注解RestTemplate即可;
3.熔断:启动类添加@EnableCircuitBreaker注解,然后配置文件中配置hystrix,并编写降级调用方法;

  • Feign调用服务:

1.服务调用:启动类添加@EnableFeignClients开启feign功能,然后配置相应服务调用的接口(feign会动态代理生成实现类,调用user服务);
2.负载均衡:feign默认集成和配置了,可以在配置文件中手动修改配置;
3.熔断:配置文件中开启熔断(hystrix可以套用之前的配置),然后编写专门的服务降级类;
4.请求压缩:配置即可;
5.配置日志级别:在配置文件中配置package的日志级别,然后编写配置类来配置@FeignClient代理的客户端的日志内容;

2.5.2 实例
2.5.2.1 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.coderwhat</groupId>
    <artifactId>consumer</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <artifactId>parent</artifactId>
        <groupId>com.coderwhat</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- hystrix -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

</project>
2.5.2.2 配置文件
server:
  port: 8989
spring:
  application:
    name: consumer
eureka:
  client:
    service-url:
      defaultZone: ${defaultZone:http://127.0.0.1:10068/eureka}

# 当请求延迟2s以上未响应时,就会触发consumer的服务降级
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000 # ms
      circuitBreaker:
        errorThresholdPercentage: 50 # 触发熔断错误比例阈值,默认值50%
        sleepWindowInMilliseconds: 10000 # 熔断后休眠时长,默认值5秒
        requestVolumeThreshold: 10 # 熔断触发最小请求次数,默认值是20

feign:
  # 开启feign的熔断功能
  hystrix:
    enabled: true
  # 配置feign的请求、响应压缩
  compression:
    request:
      enabled: true # 开启请求压缩
      mime-types: text/html,application/xml,application/json # 设置压缩的数据类型
      min-request-size: 2048 # 设置触发压缩的大小下限
    response:
      enabled: true # 开启响应压缩

# fegin内置的ribbon和自动配置,这里我们对配置信息做一些修改
ribbon:
  ConnectTimeout: 1000 # 连接超时时长
  ReadTimeout: 2000 # 数据通信超时时长
  MaxAutoRetries: 0 # 当前服务器的重试次数
  MaxAutoRetriesNextServer: 0 # 重试多少次服务
  OkToRetryOnAllOperations: false # 是否对所有的请求方式都重试

# 配置com.coderwhat包的日志级别为debug
logging:
  level:
    com.coderwhat: debug
2.5.2.3 pojo
import lombok.Data;

@Data
public class User {

    private Long id;

    // 用户名
    private String userName;

    // 密码
    private String password;

    // 姓名
    private String name;

    // 年龄
    private Integer age;
}
2.5.2.4 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

//@SpringBootApplication
//@EnableDiscoveryClient
//@EnableCircuitBreaker   //开启hystrix熔断
@SpringCloudApplication   //包含上方三个注解
@EnableFeignClients     //开启feign功能
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

    @Bean           //实例化RestTemplate
    @LoadBalanced   //注解即可,使其使用负载均衡算法ribbon
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}
2.5.2.5 RestTemplate 消费方式
2.5.2.5.1 Controller
import com.coderwhat.consumer.pojo.User;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("/restConsumer")
@Slf4j
@DefaultProperties(defaultFallback = "defaultFallback") //当前controller中@HystrixCommand默认降级方法
public class ConsumerRestController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * ①使用RestTemplate采用拼接地址的方式访问user服务
     * testPath:  http://localhost:8989/restConsumer/3?name=2&token=3
     * @param id 用户id
     * @return 用户信息
     */
    @GetMapping("/{id}")
    public User queryById(@PathVariable Long id) {
        String url = "http://127.0.0.1:7031/user/" + id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }

    /**
     * ②user-service服务已经注册到eureka server,可以从server服务列表中获取实例;
     * 尝试使用RestTemplate以及eureka实例的方式访问user服务
     * testPath:  http://localhost:8989/restConsumer/eureka/3?name=2&token=3
     * @param id
     * @return
     */
    @RequestMapping("/eureka/{id}")
    public User queryByIdEureka(@PathVariable Long id) {
        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        ServiceInstance serviceInstance = instances.get(0);

        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;
        User user = restTemplate.getForObject(url, User.class);
        return user;
    }

    /**
     * ③在使用获取eureka注册服务的方式访问后,熔断配置完后自定义对降级方法的支持
     * testPath:  http://localhost:8989/restConsumer/eureka/hystrix/4?name=2&token=3
     * @param id
     * @return
     */
    @RequestMapping("/eureka/hystrix/{id}")
    @HystrixCommand(fallbackMethod = "fallback")
    public String queryByIdEurekaWithHystrix(@PathVariable Long id) {
        //模拟异常发生
        if (id == 1){
            throw new RuntimeException("模拟异常");
        }

        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        ServiceInstance serviceInstance = instances.get(0);

        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

    /**
     * ④对当前controller中的@HystrixCommand添加默认降级方法
     * testPath:  http://localhost:8989/restConsumer/eureka/defaultHystrix/4?name=2&token=3
     * @param id
     * @return
     */
    @RequestMapping("/eureka/defaultHystrix/{id}")
    @HystrixCommand
    public String queryByIdEurekaWithDefaultHystrix(@PathVariable Long id) {
        //模拟异常发生
        if (id == 1){
            throw new RuntimeException("模拟异常");
        }

        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
        ServiceInstance serviceInstance = instances.get(0);

        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/user/" + id;
        String user = restTemplate.getForObject(url, String.class);
        return user;
    }

    /**
     * 重点:熔断方法的"参数列表"和"返回值声明"必须与调用方法相同;
     * 如果为全局指定默认熔断方法,则要保证"返回值类型"相同
     * @param id
     * @return
     */
    public String fallback(Long id){
        log.error("查询用户信息失败;id={}",id);
        return "对不起,网络太拥挤了";
    }

    /**
     * 引申:如果要携带参数,应该怎么做?
     * @return
     */
    public String defaultFallback(){
        return "对不起,全局网络太拥挤了";
    }
    
}
2.5.2.6 Feign 消费方式
2.5.2.6.1 服务调用接口
import com.coderwhat.consumer.client.fallback.UserClientFallback;
import com.coderwhat.consumer.config.FeignConfig;
import com.coderwhat.consumer.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

//声明当前类是一个Feign客户端,指定服务名为user-service
//Feign会通过动态代理,帮我们生成实现类,类似mybatis的mapper
@FeignClient(name = "user-service",
        fallback = UserClientFallback.class,
        configuration = FeignConfig.class)
public interface UserClient {

    /**
     * feign会根据服务名和注解中的后序路径拼接访问地址,例如:
     * http://  +  user-service  +  /user/9
     * @param id 请求参数报纸一直
     * @return
     */
    @RequestMapping("/user/{id}")
    User queryById(@PathVariable Long id);

}
2.5.2.6.2 服务降级处理类
import com.coderwhat.consumer.client.UserClient;
import com.coderwhat.consumer.pojo.User;
import org.springframework.stereotype.Component;

/**
 * 服务降级处理类
 */
@Component
public class UserClientFallback implements UserClient {

    //此方法若如法hystrix配置,则响应如下
    @Override
    public User queryById(Long id) {
        User user = new User();
        user.setId(id);
        user.setName("用户查询异常");
        return user;
    }
}
2.5.2.6.3 Controller
import com.coderwhat.consumer.client.UserClient;
import com.coderwhat.consumer.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/feignConsumer")
public class ConsumerFeignController {

    @Autowired
    private UserClient userClient;

    @RequestMapping("/{id}")
    public User queryById(@PathVariable Long id) {
        return userClient.queryById(id);
    }

}
2.5.2.6.4 日志配置类
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

//配置feign中的日志等级
@Configuration
public class FeignConfig {

    @Bean
    Logger.Level feignLoggerLever(){
        //记录所有的请求和响应明细
        return Logger.Level.FULL;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mitays

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值