OpenFeign就是springCloud中的组件,一个优雅的远程调用组件,内部集成了ribbon负载均衡,我前期的博客貌似又写过它的集中负载方式,以及自定义负载
实例化restTemplater,自定义负载机制,如果要了解springCloud的创建过程,请转到这篇博客
@Configuration
public class ConfigBeans {
//boot -->spring applicationContext.xml --- @Configuration配置 ConfigBean = applicationContext.xml
@Bean
@LoadBalanced//Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
@Bean
public IRule myRule()
{
//return new RoundRobinRule();//该策略实现安装线性轮询的方式依次选择每个服务实例
return new RandomRule();//该策略实现安随机算法,
//return new RetryRule();//具备重试机制的实例选择功能morning采用的轮训
}
}
这里就不多说了,直接进入feign的打开方式
接着上一篇博客的故事开始
springCloud官网
springCloud中文文档
openFeign源码及文档
在公共项目中sca-common创建feign文件夹,专门用来对外暴露接口,供其他服务调用
创建BalanceFeignService,准备在account-service模块中调用balance-service的接口
注解@FeignClient
的属性
name = "payment-service" #被调用的服务名/
fallback = BalanceFeignService.BalanceFeignServiceFallback.class #服务出错时,调用该类的方法, 服务降级类
qualifier="paymentService" #限定服务调用名称,就是起了给名字
configuration = ServiceFeignConfiguration.class #feign的配置类,比如超时 等等
其他属性请自行去了解,
内部类中记得加@Component
注解,以便能被spring扫描加载
代码
package com.hc.scacommon.feign;
import com.hc.scacommon.pojo.Balance;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
//qualifier="paymentService",
//configuration = ServiceFeignConfiguration.class,
@FeignClient(name = "payment-service", fallback = BalanceFeignService.BalanceFeignServiceFallback.class)
public interface BalanceFeignService {
//远程调用
@RequestMapping(value = "/pay/balance", method = RequestMethod.GET)
Balance getBalance(@RequestParam("id") Integer id);
//请求不到,则转入降级
@Component
class BalanceFeignServiceFallback implements BalanceFeignService {
@Override
public Balance getBalance(Integer id) {
return new Balance(0, 0, 0, "服务降级,该服务已下线");
}
}
}
创建子项目account-service
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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.hc</groupId>
<artifactId>sca-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hc</groupId>
<artifactId>account-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>account-service</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
<alibaba.version>0.9.0.RELEASE</alibaba.version>
<spring-cloud-netflix.version>2.1.1.RELEASE</spring-cloud-netflix.version>
<spring-cloud-openfeign.version>2.1.1.RELEASE</spring-cloud-openfeign.version>
</properties>
<dependencies>
<dependency>
<groupId>com.hc</groupId>
<artifactId>sca-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${alibaba.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${alibaba.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
<version>${spring-cloud-openfeign.version}</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>
bootstrap.yml
spring:
application:
name: account-service
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
application.yml
spring:
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
server:
port: 8081
feign:
client:
config:
default: #default默认所有服务的超时时间
connect-timeout: 10000
read-timeout: 20000
# payment-service: #指定payment-service这个服务的超时时间
# connect-timeout: 10000
# read-timeout: 20000
controller
package com.hc.accountservice.controller;
import com.hc.scacommon.feign.BalanceFeignService;
import com.hc.scacommon.pojo.Balance;
import com.hc.scacommon.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class AccountController {
//@Qualifier("paymentService")
@Autowired
private BalanceFeignService balanceFeignService;
final static Map<Integer, User> userMap = new HashMap() {{
put(1, new User(1, "张三"));
put(2, new User(2, "李四"));
put(3, new User(3, "王五"));
}
};
@RequestMapping("/acc/user")
public User getUser(@RequestParam Integer id) {
if(id != null && userMap.containsKey(id)) {
User user = userMap.get(id);
Balance balance = balanceFeignService.getBalance(id);
if(balance != null){
user.setBalance(balance);
}
return user;
}
return new User(0, "");
}
}
启动类
package com.hc.accountservice;
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 AccountServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AccountServiceApplication.class, args);
}
}
启动类的注解少了点东西, 这里是个巨坑, 这里先卖个关子
启动两个服务
注意注意----接下来踩坑
当你启动account-service的时候报错
Field balanceFeignService in com.hc.accountservice.controller.AccountController required a bean of type 'com.hc.scacommon.feign.BalanceFeignService' that could not be found.
BalanceFeignService没有被发现, 意思是BalanceFeignService没有被加载
将@EnableFeignClients注解改为如下,将扫描com.hc.scacommon.feign这个包
@EnableFeignClients(basePackages= {"com.hc.scacommon.feign"})
重新启动account-service
莫慌, 还报错
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'accountController': Unsatisfied dependency expressed through field 'balanceFeignService';
nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.hc.scacommon.feign.BalanceFeignService': FactoryBean threw exception on object creation;
nested exception is java.lang.IllegalStateException: No fallback instance of type class com.hc.scacommon.feign.BalanceFeignService$BalanceFeignServiceFallback found for feign client payment-service
创建bean错误,通过字段“balanceFeignService”表示的未满足依赖项, 又是这个类
看最后一句错误: 找不到用于外部客户端payment-service的com.hc.scacommon.feign.BalanceFeignService$BalanceFeignService fallback类型的回退实例,找不到BalanceFeignService fallback这个实例, 这个内部类未被加载, 为什么呢
分析一下原因:
feign这个包在sca-common中被引入到account-service, 明明加了注解@Component
的呀
错就错在你不熟悉springBoot的@SpringBootApplication
这个注解
这个启动类注解扫描包的时候,只能扫描到该包,以及该包下的子包, 平级包都扫描不到
现在我们看看account-service启动类所在的包
再看看BalanceFeignService这个类在sca-common的包路径
BalanceFeignService跟启动类都不在一个包下面, 怎么可能扫描的到, 怎么可能被加载,
解决办法:
@SpringBootApplication(scanBasePackages = {"com.hc.accountservice", "com.hc.scacommon.feign"})
重新启动,完美解决
balance的信息来自feign服务调用
接下来验证feign集成ribbon负载
ribbon源码及文档
将payment-service以不同端口启动两个实例, 不知如何启动两个payment-service,请查看上一篇博文
多访问几次http://localhost:8081/acc/user?id=3, 会有轮询效果, 没有做ribbon的配置,默认就是轮询
这里我测试了7次
改变轮询策略
修改account-service的配置application.yml配置文件,添加:
payment-service: #指定payment-service这个服务的负载策略,也可以不用这一级,直接下面的配置 代表所有
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
ConnectTimeout: 500 #请求连接超时时间
ReadTimeout: 1000 #请求处理的超时时间
OkToRetryOnAllOperations: true #对所有请求都进行重试
MaxAutoRetriesNextServer: 2 #切换实例的重试次数
MaxAutoRetries: 1 #对当前实例的重试次数
#ribbon:
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #配置规则 随机
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #配置规则 轮询
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule #配置规则 重试
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #配置规则 响应时间权重
#NFLoadBalancerRuleClassName: com.netflix.loadbalancer.BestAvailableRule #配置规则 最空闲连接策略
#ConnectTimeout: 500 #请求连接超时时间
#ReadTimeout: 1000 #请求处理的超时时间
#OkToRetryOnAllOperations: true #对所有请求都进行重试
#MaxAutoRetriesNextServer: 2 #切换实例的重试次数
#MaxAutoRetries: 1 #对当前实例的重试次数
这里我配置的随机策略
其他模式,请自行测试
接下来说一下熔断,降级功能, 我上面的feign中已经写了payment-service这个服务的降级功能
现在我们来测试一下, 关掉payment-service的两个服务
然后访问http://localhost:8081/acc/user?id=3, 看能否降级成功
明显报错,没有payment-service这个客户端,降级失败
这个时候该alibaba的sentinel登场了, 现在我就不细讲他的作用了,下一篇博客我会细讲他的作用
alibaba之sentinel文档
修改account-service的application.yml配置文件,在feign下添加sentinel配置
feign:
sentinel:
enabled: true
添加sentinel依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${alibaba.version}</version>
</dependency>
启动account-service,关闭其他服务,再次访问http://localhost:8081/acc/user?id=3
降级成功
至此feign远程调用,ribbon负载均衡,sentinel熔断服务降级,已经搭建并测试完毕
下一篇alibaba之sentinel