sringcloud2.0学习-15- Hystrix推荐用法-定义统一fallback接口

定义统一fallback接口

在上面一篇文章微服务保护利器Hystrix
中,提到了@HystrixCommand 注解实现服务降级,熔断的缺点
sringcloud2.0学习-15- Hystrix推荐用法-定义统一fallback接口_第1张图片
有三个方面:

  • 每一个接口都要有一个对应的服务降级方法,如果接口多的情况下,要写很多个服务降级方法,麻烦,
  • 每一个加了@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服务的接口时造成超时:
sringcloud2.0学习-15- Hystrix推荐用法-定义统一fallback接口_第2张图片
启动eureka , order服务,member服务,访问接口: http://localhost:8005/orderToMemberUserInfoHystrix2
sringcloud2.0学习-15- Hystrix推荐用法-定义统一fallback接口_第3张图片
测试结果如上图,因为hystrix超时默认是开启的,orderToMemberUserInfoHystrix2 接口通过Feign客户端调用member服务的/getUserInfo 接口,该接口的实现中,加了1.5s睡眠,因此orderToMemberUserInfoHystrix2 会调用超时,这时,触发hystrix服务降级,调用MemberServiceFallback类中预先定义好的服务降级方法,给浏览器返回响应。从打印的线程池名称看,/orderToMemberUserInfoHystrix2接口与/getOrderInfo接口是由同一个线程池中的线程处理的,不再像之前那样,整个/orderToMemberUserInfoHystrix2接口开一个线程池处理。
sringcloud2.0学习-15- Hystrix推荐用法-定义统一fallback接口_第4张图片
代码地址
使用到的项目:
springcloud2.0-eureak-server
springcloud2.0-feign-parent

你可能感兴趣的:(SpringCloud2.0)