前言

  分布式环境下,服务直接相互调用,一个复杂的业务可能要调用多个服务,例如A -> B -> C -> D,当某个服务出现异常(调用超时、调用失败等)将导致整个流程阻塞崩溃,严重的整个系统都会崩掉,为了实现高可用,必要的保护机制必不可少

  本文记录限流、熔断、降级的实现处理

  限流

  我们采用令牌桶限流法,并自己实现一个简单令牌桶限流

  有个任务线程以恒定速率向令牌桶添加令牌

  一个请求会消耗一个令牌,令牌桶里的令牌大于0,才会放行,反正不允许通过

/**
* 简单的令牌桶限流
*/
public class RateLimiter { /**
* 桶的大小
*/
private Integer limit; /**
* 桶当前的token
*/
private static Integer tokens = 0; /**
* 构造参数
*/
public RateLimiter(Integer limit, Integer speed){
//初始化桶的大小,且桶一开始是满的
this.limit = limit;
tokens = this.limit; //任务线程:每秒新增speed个令牌
new Thread(() ->{
while (true){
try {
Thread.sleep(1000L); int newTokens = tokens + speed;
if(newTokens > limit){
tokens = limit;
System.out.println("令牌桶满了!!!");
}else{
tokens = newTokens;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
} /**
* 根据令牌数判断是否允许执行,需要加锁
*/
public synchronized boolean execute() {
if (tokens > 0) {
tokens = tokens - 1;
return true;
}
return false;
}
}

  main简单测试

    public static void main(String[] args) {
//令牌桶限流:峰值每秒可以处理10个请求,正常每秒可以处理3个请求
RateLimiter rateLimiter = new RateLimiter(10, 3); //模拟请求
while (true){
//在控制台输入一个值按回车,相对于发起一次请求
Scanner scanner = new Scanner(System.in);
scanner.next(); //令牌桶返回true或者false
if(rateLimiter.execute()){
System.out.println("允许访问");
}else{
System.err.println("禁止访问");
}
}
}

  在SpringCloud分布式下实现限流,需要把令牌桶的维护放到一个公共的地方,比如Zuul路由,当然也可以同时针对具体的每个服务进行单独限流

  另外,guava里有现成的基于令牌桶的限流实现,引入

        <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>26.0-jre</version>
</dependency>

  具体用法这里就不阐述了

  我们找出之前的springcloud项目,在zuul-server中的AccessFilter过滤器进行限流,其他的都不变,只需要做如下修改

  PS:我这里为了方便测试,调小了令牌桶的大小,跟速率,正常情况下要服务器的承受能力来定

/**
* Zuul过滤器,实现了路由检查
*/
public class AccessFilter extends ZuulFilter {
//令牌桶限流:峰值每秒可以处理10个请求,正常每秒可以处理3个请求
//PS:我这里为了方便测试,调小了令牌桶的大小,跟速率,正常情况下按服务器的承受能力来定
private RateLimiter rateLimiter = new RateLimiter(2, 1); //业务不变,省略其他代码... /**
* 过滤器的具体逻辑
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response = ctx.getResponse(); //限流
if(!rateLimiter.execute()){
try {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(200); //直接写入浏览器
response.setContentType("text/html;charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.println("系统繁忙,请稍后在试!<br/>System busy, please try again later!");
writer.flush();return null;
} catch (Exception e) {
e.printStackTrace();
}
} //业务不变,省略其他代码..
}
}

  按照我们设置的值,一秒能处理一个请求,峰值一秒能处理两个请求,下面疯狂刷新进行测试

 

  熔断

  yml配置开启Hystrix熔断功能,进行容错处理

feign:
hystrix:
enabled: true

  设置Hystrix的time-out时间

hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 5000 #毫秒
#或者设置从不超时
#timeout:
# enabled: false

  在使用Feign调用服务提供者时配置@FeignClient的 fallback,进行容错处理(服务提供者发生异常),如果需要获取到异常信息,则要配置fallbackFactory<T>

@FeignClient(name = "sso-server", path = "/",/*fallback = SsoFeign.SsoFeignFallback.class,*/fallbackFactory = SsoFeign.SsoFeignFallbackFactory.class)
    /**
* 容错处理(服务提供者发生异常,将会进入这里)
*/
@Component
public class SsoFeignFallback implements SsoFeign { @Override
public Boolean hasKey(String key) {
System.out.println("调用sso-server失败,进行SsoFeignFallback.hasKey处理:return false;");
return false;
}
}
    /**
* 只打印异常,容错处理仍交给 SsoFeignFallback
*/
@Component
public class SsoFeignFallbackFactory implements FallbackFactory<SsoFeign> {
private final SsoFeignFallback ssoFeignFallback; public SsoFeignFallbackFactory(SsoFeignFallback ssoFeignFallback) {
this.ssoFeignFallback = ssoFeignFallback;
} @Override
public SsoFeign create(Throwable cause) {
cause.printStackTrace();
return ssoFeignFallback;
}
}

  FallbackFactory也可以这样写

    /**
* 容错处理
*/
@Component
public class SsoFeignFallbackFactory implements FallbackFactory<SsoFeign> { @Override
public SsoFeign create(Throwable cause) {
//打印异常
cause.printStackTrace(); return new SsoFeign() {
@Override
public Boolean hasKey(String key) {
System.out.println("调用sso-server失败:return false;");
return false;
}
};
}
}

  因为我们没有启动Redis,报错,但我们进行容错处理,所以还是返回了false

  降级

  当调用服务发送异常,容错处理的方式有多种,我们可以:

  1、重连,比如服务进行了限流,本次连接被限制,重连一次或N次就可以得到数据

  2、直接返回一个友好提示

  3、降级调用备用服务、返回缓存的数据等

  后记

  降级也可以叫做“备胎计划”...

  代码开源

  代码已经开源、托管到我的GitHub、码云:

  GitHub:https://github.com/huanzi-qch/springCloud

  码云:https://gitee.com/huanzi-qch/springCloud

最新文章

  1. codevs 1082 线段树练习 3(区间维护)
  2. Codeforces Round #256 (Div. 2)
  3. mvc项目controller重命名了,用原网页url访问不了了,怎么办?
  4. java产生随机数的几种方式
  5. discuz 系列产品 在ie9下注册成功后不跳转bug处理
  6. Easyui扩展或者重载(方法和属性)
  7. 通过Calendar 类获取前一个月的第一天
  8. SQLite使用教程6 创建表
  9. poj 1190 DFS 不等式放缩进行剪枝
  10. POJ 2050 Searching the Web
  11. Genymotion 插件在 Eclipse 和 Android Studio 中点击后无法初始化 Initialize Engine: failed 解决方法
  12. stable_sort()与sort
  13. 50个PHP程序性能优化的方法
  14. 将 Net 项目升级 Core项目经验:(二)修复迁移后Net Standard项目中的错误
  15. AngularJS进阶(十二)AngularJS常用知识汇总(不断更新中....)
  16. .net简介(一)
  17. 我们如何用Go来处理每分钟100万复杂请求的场景
  18. springboot调用http方式
  19. nodejs的__dirname,__filename,process.cwd()区别
  20. ActiveReports 报表应用教程 (12)---交互式报表之贯穿钻取

热门文章

  1. Unity3D它Button包
  2. VC++中的C运行时库浅析(控制台程序默认使用单线程的静态链接库,而MFC中的CFile类已暗藏了多线程)
  3. EF CodeFirst的步骤
  4. 1 min 数据查询 SQL 优化
  5. This problem will occur when running in 64 bit mode with the 32 bit Oracle client components installed.
  6. NPOI在无Office环境下,对Office文件的操作
  7. jq自定义下拉菜单,当用户点击非自身元素(下拉菜单)本身时关闭下拉菜单
  8. VS2013环境里安装QT插件-“X86”与目标计算机类型“x64”冲突
  9. java设计模式(2)
  10. 下载Cloudera Repo