简介
springcloud微服务通常以多个服务组成一个集群一起提供服务,通常会通过各自不同的业务领域进行拆分,独立的服务、独立的DB、资源独占、各司其职,但是很多时候实现一些业务接口通常需要多个服务之间相互配合才能实现。通常我们使用ribbon或者feign的方式进行服务间的接口调用,个人比较喜欢feign,习惯了doubbo的声明式接口服务,用不惯ribbon,太繁琐了,而feign用起来则更符合高内聚、低耦合的设计思想。
本文将使用feign组件来实现服务间的接口调用,内容有
- 服务注册
- 服务接口暴露与业务功能的实现
- 消费者服务如何使用feign接口
- 启用服务熔断机制
- 故障转移
- 负载均衡
- 断路器的使用
- 服务调用超时时间的最佳实践
- 服务熔断监控面板的使用
- 改造老项目,提供feign接口,无缝迁移
原文:传送门
注:本文基于springcloud2.1.3 Greenwich.RELEASE 版本
1、服务关系关系图
2、微服务搭建流程
2.1、maven依赖引入
<!-- 接口声明包,用于服务接口声明,A、B服务均需要引用此包 -->
<dependency>
<groupId>com.danyuanblog.demo</groupId>
<artifactId>service-api</artifactId>
</dependency>
<!-- Hystrix-dashboard熔断器监控工具 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- 熔断相关依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- feign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- rest api web包-->
<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>
2.2、配置信息
spring:
application:
#配置信息服务名配置
name: service-A
server:
#配置服务端口
port: 8081
#配置注册中心信息,详情请参考本系列springcloud注册中心搭建相关教程
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8763/eureka/
instance:
prefer-ip-address: true
leaseRenewalIntervalInSeconds: 10
- service-B与其配置类似,只需把服务名改为:service-B,端口号改一下即可
2.3、服务启动类
//启用注册中心客户端
@EnableDiscoveryClient
//启用熔断监控面板
@EnableHystrixDashboard
//启用熔断
@EnableHystrix
//启用feign,如果feign接口所在包路径在启动类所在包路径的下面,则不需要配置包扫码路径,否则需要配置feign接口所在的包路径
@EnableFeignClients(basePackages={"com.danyuanblog.demo"})
@SpringBootApplication
public class StartServerApplication {
public static void main(String[] args) {
SpringApplication.run(StartServerApplication.class, args);
}
}
- A、B服务启动类均如上,内容一致即可
3、微服务间接口依赖与调用
3.1、接口声明模块项目 service-api
- 依赖引入
<!-- rest api web包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 声明Service-A服务提供的接口
public interface ServiceAController {
@RequestMapping(value = "/helloA", method = RequestMethod.GET)
public String helloA();
}
- 声明Service-B务提供的接口
public interface ServiceBController {
@RequestMapping(value = "/helloB, method = RequestMethod.GET)
public String helloB();
}
3.2 接口实现类
- Service-A服务接口实现类
@RestController
public class ServiceAControllerImpl implements ServiceAController {
public String helloA(){
return "welcome to service A ,hello world !";
}
}
- Service-B服务接口实现类
@RestController
public class ServiceBControllerImpl implements ServiceBController {
public String helloB(){
return "welcome to service B ,hello world !";
}
}
3.3 利用feign实现服务间的调用
3.3.1 Service-A调用Service-B的ServiceBController
- feign接口声明
//value值为需要调用的提供者服务名,fallback为当service-B服务不可用时,当前微服务实现的替代解决方案实现类
@FeignClient(value="service-B",
fallback=ServiceBControllerHystrix.class)
public interface ServiceBCient extends ServiceBController {
}
- 实现熔断策略类,当接口调用失败后,就会调用熔断类中对应的方法
@Component
public class ServiceBControllerHystrix implements ServiceBCient {
public String helloB(){
return "invoke service-B.helloB failed !";
}
}
- 利用feign调用service-B的接口
@RestController
public class ServiceAConsumerController{
//此处注入接口即可
//spring容器会自动扫描该接口的实例进行注入,体现了依赖接口编程的优秀设计风格
@Autowired
private ServiceBController serviceBController;
@RequestMapping(value = "/consume, method = RequestMethod.GET)
public String consume(){
return "service A invoke service B,content:"+serviceBController.helloB();
}
}
3.3.2 接口测试
- 调用接口: http://localhost:8081/consume 即可查看结果
3.4.1 Service-B调用Service-A的ServiceAController
- 步骤与上面类似,不做过多描述了
4、feign接口调用相关配置信息
4.1、启用熔断相关配置
- 如果不启用熔断,接口调用失败后,将直接抛出调用失败异常;
- 启用熔断后,可以根据具体的业务,对失败场景进行处理
- 比如不重要的业务可以打印一个警告日志并忽略结果;
- 重要业务可以告知客户端,后端某个微服务不可用,让其发起重试
- 如果是查询数据的接口,可以开启接口缓存,失败后,熔断类直接返回上一次接口调用返回信息
- 熔断、断路器相关配置
feign:
hystrix:
enabled: true #启用熔断
shareSecurityContext: true
hystrix: #熔断相关配置
command:
default:
fallback:
enabled: true #启用熔断类处理失败feign接口
circuitBreaker: #断路器相关配置
sleepWindowInMilliseconds: 5000 #进入熔断状态后休息5秒后再重试
forceClosed: false #启用断路器
requestVolumeThreshold: 50 #10s内达到50个失败就熔断
errorThresholdPercentage: 10 #失败率达到10%后就进入熔断
- 接口调用超时时间配置、故障转移相关配置
ribbon: #feign底层使用了ribbon,所以需要配置ribbon
ReadTimeout: 2000 #线上环境配置2s已经足够了,通常调用一个接口需要在2s内返回
ConnectTimeout: 2000
#最大自动重试次数
MaxAutoRetries: 0 #无法满足幂等性的接口需要关闭自动重试
#换实例重试次数,故障转移,具体个数根据你同一服务的实例数N-1来设置即可
MaxAutoRetriesNextServer: 1 #我们的集群里同一实例有两个服务,所以配置为1
feign: #feign超时时间配置
httpclient: #如果启用httpclient则需要配置如下
enabled: true
config:
default: #默认超时时间
connectTimeout: 3000
readTimeout: 3000
service-A: #对服务单独配置超时时间
connectTimeout: 5000
readTimeout: 5000
service-B:
connectTimeout: 6000
readTimeout: 6000
client: #如果使用feign默认的client,则如下配置
config:
default: #默认超时时间
connectTimeout: 2000
readTimeout: 2000
service-A: #对服务单独配置超时时间
connectTimeout: 5000
readTimeout: 5000
service-B:
connectTimeout: 6000
readTimeout: 6000
hystrix: #熔断超时时间配置
threadpool:
default:
coreSize: 20 #并发执行的最大线程数,默认10
maxQueueSize: 2000 #BlockingQueue的最大队列数,默认值-1
queueSizeRejectionThreshold: 2000 #即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝,默认值5
command:
default:
fallback:
enabled: true
isolation:
semaphore:
maxConcurrentRequests: 500
execution:
timeout:
#将熔断的超时机制关闭,交给ribbon去控制
enabled: false
isolation:
strategy: SEMAPHORE #熔断隔离策略使用默认的信号量进行隔离
thread:
timeoutInMilliseconds: 8000 #最大超时时间*2*(自动重试次数+1)
semaphore:
maxConcurrentRequests: 200
service-A:
fallback:
enabled: true
isolation:
semaphore:
maxConcurrentRequests: 50
execution:
timeout:
#将熔断的超时机制关闭,交给ribbon去控制
enabled: false
isolation:
strategy: SEMAPHORE
thread:
timeoutInMilliseconds: 20000
semaphore:
maxConcurrentRequests: 50
5、启用服务熔断,熔断监控面板的使用
5.1、服务启动顺序
- 服务开启熔断后无需关心服务启动顺序,A、B服务可以按任意顺序启动,只要注册中心等基础服务是启动的状态即可
5.2、熔断面板的使用
- 先进入service-A的监控面板
- 地址:http://localhost:8081/hystrix
- 浏览器打开该地址后,再输入:http://localhost:8081/actuator/hystrix.stream 即可看到如下界面
6、适配老项目或者集群外的Http接口
6.1 老项目服务示例
@RestController
public class OldController{
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test(){
return "wellcome to old test service !";
}
}
6.2 实现feign接口
- feign接口声明
//value值为需要调用的提供者服务名,fallback为当service-B服务不可用时,当前微服务实现的替代解决方案实现类
@FeignClient(name="oldService", url="${config.oldServiceUrl}", fallback=VsimCoreAccountHystrix.class)
public interface OldServiceCient extends OldController {
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test();
}
- 配置文件
config:
oldServiceUrl: http://localhost:8083
- 使用feign服务方式与前面讲到的feign使用方式一致,这里只需注入OldController接口即可调用远程接口啦
7 feign如何实现负载均衡、故障转移
- 其实feign的负载均衡和故障转移均通过ribbon实现的
- feign是基于ribbon的,当然具有ribbon的所有特性啦
- 我们只需要将服务全注册到注册中心,启动统一实例的多个服务,便自动组成集群了
- 由于我们集群内服务,feign通过服务名消费,自然可以做到负载均衡和故障转移
- 利用feign消费集群外服务接口,可以使用nginx等等工具做负载均衡,多个实例服务暴露统一的接口地址出来即可