定义统一fallback接口
在上面一篇文章微服务保护利器Hystrix
中,提到了@HystrixCommand 注解实现服务降级,熔断的缺点
有三个方面:
- 每一个接口都要有一个对应的服务降级方法,如果接口多的情况下,要写很多个服务降级方法,麻烦,
- 每一个加了@HystrixCommand 注解的接口方法,都会去开启一个新的线程池处理请求,占用资源多
- 有的情况下,我们可能期望 orderToMemberUserInfoHystrix方法应该在主线程里面执行,而对 Feign客户端调用的memberServiceFeign.getUserInfo()的方法才去开启一个线程池去做服务隔离和降级,但是使用了@HystrixCommand 注解后,它对orderToMemberUserInfoHystrix()整个方法都做了服务隔离, 整个orderToMemberUserInfoHystrix方法都放在了一个单独的线程池里面去执行
因此,hystrix更推荐另外一种实现方式,创建一个Feign客户端的实现类,定义统一的fallback接口,具体如下:
- 新建一个类实现Feign客户端接口,并加上@Component注解让spring托管,类中的方法就是对应接口的服务降级方法
- Feign客户端接口 类上面的注解) 指定服务降级要调用的方法所在的类, 如@FeignClient(name = “app-member”, fallback = MemberServiceFallback.class)
具体实现: - 新建Feign客户端接口的实现类,写接口服务降级方法
package com.lchtest.api.fallback;
import org.springframework.stereotype.Component;
import com.lchtest.api.entity.UserEntity;
import com.lchtest.api.feign.MemberServiceFeign;
import com.lchtest.common.base.BaseApiService;
import com.lchtest.common.base.ResponseBase;
/**
* 服务降级
* 只要调用MemberServiceFeign中定义的接口,就开启一个独立的线程池去处理接口请求
* 服务降级也只是针对MemberServiceFeign中定义的接口进行服务降级处理
* 这里继承BaseApiService仅仅是为了方便设置响应setResultError
* @author pc
*
*/
@Component
public class MemberServiceFallback extends BaseApiService implements MemberServiceFeign{
@Override
public UserEntity getMember(String name) {
// TODO Auto-generated method stub
return null;
}
// 这个方法相当于com.lchtest.api.service.impl.OrderServiceImpl类的orderToMemberUserInfoHystrixFallback方法
@Override
public ResponseBase getUserInfo() {
return setResultError("服务器繁忙,请稍后再试! 以类的方式进行服务降级");
}
}
- Feign客户端接口指定服务降级的类
原来注解是@FeignClient(“app-member”),
改成@FeignClient(name = “app-member”, fallback = MemberServiceFallback.class) ,MemberServiceFallback是服务降级方法的类
package com.lchtest.api.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.lchtest.api.entity.UserEntity;
import com.lchtest.api.fallback.MemberServiceFallback;
import com.lchtest.api.service.IMemberService;
/**
* Feign客户端定义,继承IMemberService,这样就可以避免类似下面这样的重复代码了: @RequestMapping("/getMember")
* public UserEntity getMember(String name);
*
* @author pc
*
*/
//@FeignClient("app-member")
@FeignClient(name = "app-member", fallback = MemberServiceFallback.class)
public interface MemberServiceFeign extends IMemberService {
}
- 业务代码修改
package com.lchtest.api.service.impl;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lchtest.api.entity.UserEntity;
import com.lchtest.api.fallback.MemberServiceFallback;
import com.lchtest.api.feign.MemberServiceFeign;
import com.lchtest.api.service.IOrderService;
import com.lchtest.common.base.BaseApiService;
import com.lchtest.common.base.ResponseBase;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
/**
* 订单服务继承会员服务接口,用来实现Feign客户端,减少重复接口代码!
*
* @author pc
*
*/
@RestController
public class OrderServiceImpl extends BaseApiService implements IOrderService {
@Autowired
private MemberServiceFeign memberServiceFeign;
@Value("${server.port}")
private String serverPort;
@GetMapping("/")
public String index(HttpServletRequest req) {
System.out.println("我是首页.....");
return "我是order服务" + serverPort;
}
/**
* http://localhost:8005/orderToMember?name=admin
*/
@RequestMapping("/orderToMember")
@Override
public String orderToMember(String name) {
UserEntity user = memberServiceFeign.getMember(name);
return user == null ? "no user find" : user.toString();
}
/**
* 测试服务雪崩效应 -没有解决雪崩效应 对该接口在member服务中的实现设置延迟时间,假设该接口需要1.5s才能处理完
* 没有com.lchtest.api.fallback.MemberServiceFallback这个类的情况下
* @return
*/
@RequestMapping("/orderToMemberUserInfo")
public ResponseBase orderToUserInfo() {
System.out.println("orderToMemberUserInfo: 当前线程池名称" + Thread.currentThread().getName());
return memberServiceFeign.getUserInfo();
}
/**
* 使用hystrix服务熔断方式解决服务雪崩效应: 1.
* application.yml开启Hystrix服务熔断:feign.hystrix.enabled=true 2.
* 主启动类加上@EnableHystrix 注解开启服务熔断 3. hystrix有两种方式配置保护服务:
* (1)通过@HystrixCommand注解形式,fallbackMethod表示服务降级执行;
* 注解@HystrixCommand默认是线程池隔离,注意,这个注解是对orderToMemberUserInfoHystrix方法开启一个新的线程池
* 我们期望的是orderToMemberUserInfoHystrix应该在主线程里面,而它调用的memberServiceFeign的方法才去开启一个线程池去处理
* 注解@HystrixCommand完成三件事:服务隔离,熔断,服务降级
* 注解@HystrixCommand缺点:
* 1.最大的缺点就是加了@HystrixCommand注解的整个方法都会开启一个新的线程池去处理,这样不合理
* 2.如果有多个方法需要进行服务降级,@HystrixCommand注解要写很多次,服务降级方法也要指定很多个,不太好
*
* (2)通过类的方式,参见orderToMemberUserInfoHystrix2()方法
*
* @return
*/
@RequestMapping("/orderToMemberUserInfoHystrix")
@HystrixCommand(fallbackMethod = "orderToMemberUserInfoHystrixFallback")
public ResponseBase orderToMemberUserInfoHystrix() {
/*
* 启动eureka8100,member服务和order服务,浏览器访问http://localhost:8005/
* orderToMemberUserInfoHystrix,
* 返回{"rtnCode":200,"msg":"返回友好提示:服务降级,服务器忙,稍后重试","data":null}
*/
System.out.println("orderToMemberUserInfoHystrix: 当前线程池名称" + Thread.currentThread().getName());
// 此处代表业务逻辑代码
// 希望仅针对memberServiceFeign.getUserInfo()接口做服务降级,在一个独立的线程池里面执行,而不是把上面的业务逻辑代码也给降级
return memberServiceFeign.getUserInfo();
}
public ResponseBase orderToMemberUserInfoHystrixFallback() {
System.out.println("orderToMemberUserInfoHystrix-服务降级调用的方法");
return setResultSuccess("返回友好提示:服务降级,服务器忙,稍后重试");
}
/**
* Hystrix 第二种写法:使用类定义统一的fallback接口
* @return
*/
@RequestMapping("/orderToMemberUserInfoHystrix2")
public ResponseBase orderToMemberUserInfoHystrix2() {
System.out.println("orderToMemberUserInfoHystrix2: 当前线程池名称" + Thread.currentThread().getName());
// 此处代表业务逻辑代码
// 希望仅针对memberServiceFeign.getUserInfo()接口做服务降级,在一个独立的线程池里面执行,而不是把上面的业务逻辑代码也给降级了
return memberServiceFeign.getUserInfo();
}
@RequestMapping("/getOrderInfo")
public String getOrderInfo() {
System.out.println("getOrderInfo: 当前线程池名称" + Thread.currentThread().getName());
return "getOrderInfo success.";
}
// 订单服务接口
@Override
public ResponseBase orderInfo() {
return setResultSuccess();
}
}
order服务,把禁用hystrix接口超时的配置注释掉,让order接口调用member服务的接口时造成超时:
启动eureka , order服务,member服务,访问接口: http://localhost:8005/orderToMemberUserInfoHystrix2
测试结果如上图,因为hystrix超时默认是开启的,orderToMemberUserInfoHystrix2 接口通过Feign客户端调用member服务的/getUserInfo 接口,该接口的实现中,加了1.5s睡眠,因此orderToMemberUserInfoHystrix2 会调用超时,这时,触发hystrix服务降级,调用MemberServiceFallback类中预先定义好的服务降级方法,给浏览器返回响应。从打印的线程池名称看,/orderToMemberUserInfoHystrix2接口与/getOrderInfo接口是由同一个线程池中的线程处理的,不再像之前那样,整个/orderToMemberUserInfoHystrix2接口开一个线程池处理。
代码地址
使用到的项目:
springcloud2.0-eureak-server
springcloud2.0-feign-parent