介绍

springcloud有两种服务调用方式:

  • ribbon + restTemplate
  • feign(更好的选择)

ribbon实现了客户端负载均衡,通过restTemplate调用服务端

feign采用基于接口的注解,默认集成了ribbon,所以也可以实现负载,并且整合了Hystrix,具有熔断的能力

ribbon + restTemplate

示例代码,注册中心使用eureka示例中的单节点注册中心

示例服务端

服务端没有特别的地方,就是一个普通的springboot restapi服务,注册到eureka注册中心,跟ribbon还没有任何关系

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
//入口类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceHiApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceHiApplication.class,args);
    }
}

//示例controller
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HiController {
    @Value("${server.port}")
    String port;

    @GetMapping("/hi")
    public String sayHi(@RequestParam("name") String name){
        return String.format("Hi %s,i'm from port %s",name,port);
    }
}
spring.application.name=service-hi-ribbon
server.port=8861
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

首先启动注册中心,再启动两个示例服务端,端口分别为8861、8862。

示例ribbon客户端

客户端才集成ribbon组件,配合RestTemplate来调用服务端

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>
</dependencies>

添加RestTemplate,使用@LoadBalanced来支持负载均衡

//入口类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceHiClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceHiClientApplication.class,args);
    }

    @Bean
    @LoadBalanced
    RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

//示例controller,通过RestTemplate来调用服务端
//注意URL的格式
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class HiClientController {
    @Autowired
    RestTemplate restTemplate;

    @GetMapping("/callHiService")
    public String callHiService(){
        return restTemplate.getForObject("http://SERVICE-HI/hi?name=zhangsan",String.class);
    }
}
spring.application.name=service-hi-ribbon-client
server.port=8863
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

启动客户端,访问http://localhost:8863/callHiService,会看到返回结果在下面两个端口的服务之间轮询

# Hi zhangsan,i'm from port 8861
# Hi zhangsan,i'm from port 8862

feign

示例代码,注册中心使用eureka示例中的单节点注册中心

示例服务端

同上一节示例服务端,没有特别的地方,就是一个普通的springboot restapi服务,注册到eureka注册中心,跟feign还没有任何关系

同样首先启动注册中心,再启动两个示例服务端,端口分别为8861、8862。

示例feign客户端

maven添加feign依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>
//入口类添加@EnableFeignClients注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ServiceHiClientApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceHiClientApplication.class,args);
    }
}

//添加一个服务端的feign代理接口,指定服务端的应用名称,接口地址,参数
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "service-hi-feign")
public interface ServiceHi {
    @GetMapping("/hi")
    String callHiService(@RequestParam("name") String name);
}

//在controller中调用这个feign代理接口
import com.qigang.springcloud.f.feign.servicehi.client.proxy.ServiceHi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HiClientController {
    @Autowired
    ServiceHi serviceHi;

    @GetMapping("/callHiService")
    public String callHiService(){
        return serviceHi.callHiService("zhangsan");
    }
}
spring.application.name=service-hi-feign-client
server.port=8863
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/

启动客户端,访问http://localhost:8863/callHiService,会看到返回结果在下面两个端口的服务之间轮询,结果与ribbin+restTemplate的方式没有区别

# Hi zhangsan,i'm from port 8861
# Hi zhangsan,i'm from port 8862

修改负载均衡策略、超时时间

配置的方式:

# 以配置的方式来定义ribbon的负载均衡策略,超时时间等,这个是为某个服务单独配置的
# 修改服务的负载均衡策略
service-hi-feign.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.BestAvailableRule
# 请求连接超时时间
service-hi-feign.ribbon.ConnectTimeout=3000
# 请求处理超时时间
service-hi-feign.ribbon.ReadTimeout=3000
# 对所有请求都进行重试,false代表只对GET请求重试,对于写数据接口必须做幂等才行,谨慎配置!
service-hi-feign.ribbon.OkToRetryOnAllOperations=true
# 切换实例的重试次数,不包括首次调用
service-hi-feign.ribbon.MaxAutoRetriesNextServer=2
# 对当前实例的重试次数,不包括首次调用
service-hi-feign.ribbon.MaxAutoRetries=2

代码配置的方式如下,不过好像重试的配置没有办法区分每个实例的重试次数还有切换实例的重试次数:

import com.netflix.loadbalancer.BestAvailableRule;
import com.netflix.loadbalancer.IRule;
import feign.Request;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CustomRibbonConfig {
    /**
     * 负载均衡策略
     */
    @Bean
    public IRule ribbonRule(){
        return new BestAvailableRule();
    }

    //region 超时、重试次数设置
    public static int connectTimeOutMillis = 3000;
    public static int readTimeOutMillis = 3000;

    @Bean
    public Request.Options options() {
        return new Request.Options(connectTimeOutMillis, readTimeOutMillis);
    }

    @Bean
    public Retryer feignRetryer(){
        Retryer retryer = new Retryer.Default(100, 1000, 2);
        return retryer;
    }
    //endregion
}

问题

参考

springcloud官网

A-G各个版本的release notes

Finchley.RELEASE documentation

失败请求重试说明

Spring Cloud各组件重试总结

服务消费者(rest+ribbon)(Finchley版本)

服务消费者(Feign)(Finchley版本)

Spring Cloud底层原理

【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结