package com.acx.controller;
import com.acx.pojo.vo.ActorInfoVO;
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;
@RestController
@RequestMapping("user")
public class UserController {
@GetMapping("getUser/{id}")
public ActorInfoVO getActor(@PathVariable("id") int id) {
if ((id % 2) == 1) {
ActorInfoVO actorInfoVO = new ActorInfoVO();
actorInfoVO.setAge(34);
actorInfoVO.setGender("男");
actorInfoVO.setHead("http:localhost:8080/head/");
actorInfoVO.setNickname("别名:杀马特");
actorInfoVO.setUsername("张三");
return actorInfoVO;
}
return null;
}
}
package com.acx.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
package com.acx.controller;
import com.acx.pojo.vo.ActorInfoVO;
import com.acx.pojo.vo.OrderInfoVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("order")
public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
@Autowired
private RestTemplate restTemplate;
@GetMapping("getOne")
public OrderInfoVO getOne() {
logger.info("开始查询订单");
OrderInfoVO orderInfoVO = new OrderInfoVO();
orderInfoVO.setOrderName("订单123");
orderInfoVO.setOrderSn("046b399937ad4271bcd5ed275f2b4682");
orderInfoVO.setProductName("商品123");
orderInfoVO.setProductNum(23);
int userId = 1;
String getUserUrl = "http://127.0.0.1:8083/user/getUser/" + userId;
//服务发现
ActorInfoVO actor = restTemplate.getForObject(getUserUrl, ActorInfoVO.class);
orderInfoVO.setUser(actor);
return orderInfoVO;
}
}
{
"orderSn": "046b399937ad4271bcd5ed275f2b4682",
"orderName": "订单123",
"productNum": 23,
"productName": "商品123",
"user": {
"username": "张三",
"nickname": "别名:杀马特",
"head": "http:localhost:8080/head/",
"age": 34,
"gender": "男"
}
}
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced //开启ribbon负载均衡策略
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
package com.acx.controller;
import com.acx.pojo.vo.ActorInfoVO;
import com.acx.pojo.vo.OrderInfoVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("order")
public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
@Autowired
private RestTemplate restTemplate;
@GetMapping("getOne")
public OrderInfoVO getOne() {
logger.info("开始查询订单");
OrderInfoVO orderInfoVO = new OrderInfoVO();
orderInfoVO.setOrderName("订单123");
orderInfoVO.setOrderSn("046b399937ad4271bcd5ed275f2b4682");
orderInfoVO.setProductName("商品123");
orderInfoVO.setProductNum(23);
int userId = 1;
// String getUserUrl = "http://127.0.0.1:8083/user/getUser/" + userId;
String getUserUrl = "http://user-service/user/getUser/" + userId;
//服务发现
ActorInfoVO actor = restTemplate.getForObject(getUserUrl, ActorInfoVO.class);
orderInfoVO.setUser(actor);
return orderInfoVO;
}
}

public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
//执行负载均衡核心方法
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
return this.execute(serviceId, (LoadBalancerRequest)request, (Object)null);
}
public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
//根据服务名称获取具体的服务节点对象
Server server = this.getServer(loadBalancer, hint);
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
} else {
RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
}
}
protected Server getServer(ILoadBalancer loadBalancer, Object hint) {
return loadBalancer == null ? null : loadBalancer.chooseServer(hint != null ? hint : "default");
}
protected IRule rule = DEFAULT_RULE;
private final static IRule DEFAULT_RULE = new RoundRobinRule(); //轮询
public Server chooseServer(Object key) {
if (counter == null) {
counter = createCounter();
}
counter.increment();
if (rule == null) {
return null;
} else {
try {
//通过路由规则选择节点
return rule.choose(key);
} catch (Exception e) {
logger.warn("LoadBalancer [{}]: Error choosing server for key {}", name, key, e);
return null;
}
}
}
ZoneAvoidanceRule: 按区域轮询策略
RoundRobinRule: 轮询
RandomRule: 随机轮询
RetryRule: 重试轮询
//方式一
@Bean
public IRule retryRule(){
return new RetryRule();
}
ribbon: # 方式二
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule
ribbon:
eager-load:
enable: true # 开启饥饿加载
clients:
- user-service # 指明主要饥饿加载的服务
OpenFeign是一种声明式的HTTP客户端,而RestTemplate具有代码可读性差、调用参数复杂难维护等缺点。故选择OpenFeign更适合我们的微服务远程调用场景。
步骤一:Order服务调用方引入OpenFeign的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
步骤二:在启动类上加上@EnableFeignClients标签开启Feign功能
package com.acx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
步骤三:编写Feign客户端接口代码:使用@FeignClient()指明调用那个服务,接口按照spring mvc写就行。
package com.acx.client;
import com.acx.pojo.vo.ActorInfoVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("USER-SERVICE")
public interface UserClient {
@GetMapping("/user/getUser/{id}")
ActorInfoVO getUser(@PathVariable("id") int id);
}
最后:在业务代码里面使用Feign接口进行User服务调用
package com.acx.controller;
import com.acx.client.UserClient;
import com.acx.pojo.vo.ActorInfoVO;
import com.acx.pojo.vo.OrderInfoVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("order")
public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private UserClient userClient;
@GetMapping("getOne")
public OrderInfoVO getOne() {
logger.info("开始查询订单");
OrderInfoVO orderInfoVO = new OrderInfoVO();
orderInfoVO.setOrderName("订单123");
orderInfoVO.setOrderSn("046b399937ad4271bcd5ed275f2b4682");
orderInfoVO.setProductName("商品123");
orderInfoVO.setProductNum(23);
int userId = 1;
// String getUserUrl = "http://127.0.0.1:8083/user/getUser/" + userId;
// String getUserUrl = "http://user-service/user/getUser/" + userId;
//服务发现
// ActorInfoVO actor = restTemplate.getForObject(getUserUrl, ActorInfoVO.class);
ActorInfoVO actor = userClient.getUser(userId);
orderInfoVO.setUser(actor);
return orderInfoVO;
}
}
主要的可配置项
| 配置项 | 说明 |
|---|---|
| Level:日志级别 | 有4中级别:NONE(不输出远程调用日志) BASIC(只输出请求URL和响应状态码及请求 时间) HEADERS(将BASIC信息和请求头信息输出), FULL(输出完成的请求) |
| Decoder:响应结果解析器 | 默认使用SpringDecoder解码器,会调用Spring MVC 中的消息转换器HttpMessageConverter进行解码。 |
| Encoder:请求参数编码器 | 默认使用SpringEncoder编码器,它会调用Spring MVC 中的消息转换器(HttpMessageConverter)进行编码 |
| Contract:契约配置 | OpenFeign中默认使用的是springmvc的注解 |
| Retryer:失败重试机制 | 默认没有重试机制,可以使用Ribbon配置重置机制 |
文件方式配置
feign:
client:
config:
default: # 全局
loggerLevel: FULL
feign:
client:
config:
userservice: # 局部生效 userservice
loggerLevel: FULL
代码方式配置
package com.acx.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLevel() {
return Logger.Level.FULL;
}
}
package com.acx;
import com.acx.config.FeignConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = FeignConfig.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
package com.acx.client;
import com.acx.config.FeignConfig;
import com.acx.pojo.vo.ActorInfoVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "USER-SERVICE",configuration = FeignConfig.class)
public interface UserClient {
@GetMapping("/user/getUser/{id}")
ActorInfoVO getUser(@PathVariable("id") int id);
}
Feign的底层HTTP客户端选型:
URLConnection:feign默认集成的HTTP客户端,JDK自带,不支持连接池,性能不好
Apache HttpClient:支持连接池
OKHttp:支持连接池
Feign性能优化:
思路:使用支持连接池的客户端替换URLConnection
Order服务引入依赖:这个jar包包括了Apache HttpClient和OKHttp两大客户端,选择一种即可
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
feign:
httpclient:
enabled: true # 开启feign对Apache HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
feign:
okhttp:
enabled: true # 开启feign对Apache HttpClient的支持
方式一(耦合):服务提供者定义一个API接口,写一个Feign接口和Controller接口都集成此API接口,然后服务消费者引入服务提供者的依赖来调用Feign客户端。
缺点:服务提供方和服务消费方紧耦合了。参数列表中的注解映射并不会被集成,所以我们再Controller接口里面还要再次声明方法、参数列表、注解。故此方式不推荐使用。
方式二(抽取):将 FeignClient 抽取为独立模块,并且把接口有关的 POJO、默认的 Feign 配置都放到这个模块中,提供给所有消费者使用。

步骤一:新建feign-api包,并将前面的UserClient转移到这个包中,pom.xml配置如下:
<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">
<parent>
<artifactId>cloud-demoartifactId>
<groupId>com.acxgroupId>
<version>1.0.0version>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>com.acxgroupId>
<artifactId>feign-apiartifactId>
<version>1.0.0version>
<dependencies>
<dependency>
<groupId>com.acxgroupId>
<artifactId>cloud-commonartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
dependencies>
project>
步骤二:Order项目引入feign-api包进行微服务调用
<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">
<parent>
<artifactId>cloud-demoartifactId>
<groupId>com.acxgroupId>
<version>1.0.0version>
parent>
<modelVersion>4.0.0modelVersion>
<groupId>com.acxgroupId>
<artifactId>order-serviceartifactId>
<version>1.0.0version>
<packaging>jarpackaging>
<dependencies>
<dependency>
<groupId>com.acxgroupId>
<artifactId>cloud-commonartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>com.acxgroupId>
<artifactId>feign-apiartifactId>
<version>1.0.0version>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
project>
package com.acx;
import com.acx.config.FeignConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = FeignConfig.class,basePackages = "com.acx.client")
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
package com.acx;
import com.acx.client.UserClient;
import com.acx.config.FeignConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = FeignConfig.class,clients = {UserClient.class})
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}