快捷搜索: 长连接 前端 源码 pan

Spring Cloud——声明式服务调用:Spring Cloud Feign

Spring Cloud Feign整合了Spring Cloud Ribbon与Spring Cloud Hystrix,除了提供这两者的强大功能之外,还提供了一种声明式的Web服务客户端定义方式。同时,Spring Cloud Feign具备可插拔的注解支持,包括Feign注解和JAX-RS注解。

(一)服务注册中心

创建一个Spring Boot工程,命名为cloud_eureka_server,并在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>

    <groupId>iwhale</groupId>
    <artifactId>cloud_eureka_server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>cloud_eureka_server</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.13.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

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

    <!--idea自动提示引入依赖,但是没有需要的版本,如spring-cloud-starter-eureka-server。这就是一个典型的版本老旧的问题。
    建议大家不易一个一个去定义,直接使用dependencyManagement 自动引入即可-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

在应用中添加注解@EnableEurekaServer

@EnableEurekaServer
@SpringBootApplication
public class CloudEurekaServerApplication {

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

在配置文件application.yml添加一下配置:

server:
  port: 8888

eureka:
  instance:
    hostname: localhost
    lease-expiration-duration-in-seconds: 30  #表示eureka server至上一次收到client的心跳后,等下一次心跳的超时时间,在这个时间内若没收到下一次心跳,则将移除该instance,默认90s。
    lease-renewal-interval-in-seconds: 10 #服务续约间隔时间
  server:
    enable-self-preservation: false #关闭保护机制,以确保注册中心可以将不可用的实例正确剔除
  client:
    register-with-eureka: false #不注册自己
    fetch-registry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

(二)服务提供者

创建一个Spring Boot工程,命名为cloud_service,pom.xml内容与注册中心一样

在应用中添加注解@EnableDiscoveryClient,获得服务发现的能力

@EnableDiscoveryClient
@SpringBootApplication
public class CloudServiceApplication {

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

在配置文件application.yml指定服务命名cloudservice,指定服务注册中心地址,添加一下配置:

server:
  port: 9881
#  context-path: /${spring.application.name}
#restTemplate.getForEntity("http://cloudservice/hello",String.class).getBody();    这样访问时需注释掉context-path

#############################spring配置#############################
spring:
  application:
    name: cloudservice

#############################eureka配置#############################
eureka:
  client:
    serviceUrl:
      defaultZone:  http://127.0.0.1:8888/eureka/
    eureka-server-read-timeout-seconds: 180

编写带函数请求处理接口,其中User对象包含name、age,以及settergetter方法,注意必须也包含User的默认构造函数。不然,Feign根据Json字符串转换User对象时会抛出异常。

@RestController
public class HelloController {

    @Autowired
    private DiscoveryClient client;

    @RequestMapping(value = "hello1",method = RequestMethod.GET)
    public String hello(@RequestParam String name){
        ServiceInstance instance=client.getLocalServiceInstance();
        return "ServiceId:"+instance.getServiceId()+
                ", port:"+instance.getPort()+",  参数为:"+name;
    }

    @RequestMapping(value = "hello2",method = RequestMethod.GET)
    public User hello(@RequestHeader String name, @RequestHeader Integer age){
        return new User(name,age);
    }

    @RequestMapping(value = "hello3",method = RequestMethod.POST)
    public String hello(@RequestBody User user){
        return "Hello "+user.getName()+", "+user.getAge();
    }
}

(三)服务消费者

创建一个Spring Boot工程,命名为cloud-web,并在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>

    <groupId>iwhale</groupId>
    <artifactId>cloud-web</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>cloud-web</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.13.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--全栈web开发模块,包含tomcat,SpringMVC-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--放在tomcat运行时,排除内置的tomcat-->
            <!--<exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>-->
        </dependency>

        <!--通用测试模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>
    </dependencies>

    <!--idea自动提示引入依赖,但是没有需要的版本,如spring-cloud-starter-eureka-server。这就是一个典型的版本老旧的问题。
    建议大家不易一个一个去定义,直接使用dependencyManagement 自动引入即可-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Edgware.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

application.yml配置如下:

server:
  port: 9884
  context-path: /${spring.application.name}

#############################spring配置#############################
spring:
  application:
    name: cloud-web

############################################################
# spring-boot-starter-actuator,监控管理,查看端点信息,部分端点的访问需要鉴权,可将安全校验关闭,生产环境下需设为true
management:
  security:
    enabled: true
  context-path: /actuator #为了端点安全,增加前缀

endpoints:
  health:
    path: /checkHealth #修改/health端点的原始路径

#############################eureka配置#############################
eureka:
  client:
    serviceUrl:
      defaultZone:  http://127.0.0.1:8888/eureka/
    eureka-server-read-timeout-seconds: 180
  instance:
    health-check-url-path: /${endpoints.health.path} #修改/health端点的原始路径

在应用主类添加注解@EnableDiscoveryClient,@EnableFeignClients

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class CloudWebApplication {
    public static void main(String[] args) {
        SpringApplication.run(CloudWebApplication.class, args);
    }
}

在HelloServer接口中,利用注解@FeignClient指定服务提供者的服务名,绑定参数时,利用@RequestParam、@RequestHeader、@RequestBody指定参数名称,value值不能少,也不能空。

@FeignClient("cloudservice")  //指定服务名来绑定服务
public interface HelloService {

    @RequestMapping(value = "hello1",method = RequestMethod.GET)
    String hello(@RequestParam("name") String name);

    @RequestMapping(value = "hello2",method = RequestMethod.GET)
    User hello(@RequestHeader("name") String name, @RequestHeader("age") Integer age);

    @RequestMapping(value = "hello3",method = RequestMethod.POST)
    String hello(@RequestBody User user);

}

创建一个类HelloController对上面接口进行调用

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @RequestMapping(value = "/helloFang",method = RequestMethod.GET)
    public String helloFang(){
        StringBuilder sb=new StringBuilder();
        sb.append(helloService.hello("hjy")).append("
");
        sb.append(helloService.hello("hjy",33)).append("
");
        sb.append(helloService.hello(new User("hjy",33))).append("
");
        return sb.toString();
    }
}

测试

启动服务注册中心、两个cloudservice服务以及服务消费者cloud-web,访问时我们发现会以轮询的方式访问9881和9882端口

(四)Ribbon配置

Spring Cloudn Feign的客户端负载均衡是通过Spring Cloud Ribbon实现的,在application.yml中添加对于cloudservice服务的重试策略

########重试策略########
cloudservice:
  ribbon:
    ConnectTimeout: 500  #请求连接的超时时间
    ReadTimeout: 2000  #请求处理的超时时间
    okToRetryOnAllOperations: true  #对所有操作都进行重试
    MaxAutoRetriesNextServer: 2 #切换实例的重试次数
    MaxAutoRetries: 1  #对当前实例的重试次数

将cloudservice服务的/hello1接口改造如下,然后启动9881端口为改造后的(即可能超时的),9882端口为改造前的。

@RequestMapping(value = "hello1",method = RequestMethod.GET)
    public String hello(@RequestParam String name) throws Exception{
        ServiceInstance instance=client.getLocalServiceInstance();
        //测试超时
        int sleepTime=new Random().nextInt(3000);
        Thread.sleep(sleepTime);
        System.out.println(sleepTime);
        return "ServiceId:"+instance.getServiceId()+
                ", port:"+instance.getPort()+",  参数为:"+name;
    }

测试发现当超时2s后,会再次访问9881端口一次,再超时时就会去访问9882的端口。

(五)Hystrix配置

在对Hystrix进行配置时,需要确认feign.hystrix.enables参数没有被设置为false,在application.yml配置熔断器全局超时时间(也可针对某个服务设置)

############熔断器配置##############
feign:
  hystrix:
    enable: true #开启熔断器
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 8000  #熔断器超时时间应大于ribbon的超时时间,不然不会触发重试

feign.hystrix.enables=false可以全局关闭Hystrix功能,但如果只想关闭某个服务客户端时,需要使用@Scope("prototype")注解为指定的客户端配置Feign.Builder实例。

@Configuration
public class DisableHystrixConfiguration {
    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder(){
        return Feign.builder();
    }
}

在HelloService的@FeignClient注解中,通过configuration参数引入上面的配置

@FeignClient(value = "cloudservice" ,configuration = DisableHystrixConfiguration.class)  //指定服务名来绑定服务
public interface HelloService {
    ......
}

Hystrix提供的服务降级是服务容错的重要功能,下面我们对服务消费者进行改造,为HelloService接口实现一个服务降级类HelloServiceFallback,其中每个重写方法的实现逻辑都可以用来定义相应的服务降级逻辑。

@Component
public class HelloServiceFallback implements HelloService{

    @Override
    public String hello(String name) {
        return "error";
    }

    @Override
    public User hello(String name, Integer age) {
        return new User("未知",0);
    }

    @Override
    public String hello(User user) {
        return "error";
    }
}

通过@FeignClient注解的fallback属性来指定对应的服务降级实现类

@FeignClient(value = "cloudservice",fallback = HelloServiceFallback.class)  //指定服务名来绑定服务
public interface HelloService {
    ......
}

启动服务注册中心和服务消费者,但不启动服务提供者,测试发现服务降级没生效,百度了很多解决方案,仍不行,什么鬼!!!

经验分享 程序员 微信小程序 职场和发展