青云开发

学习、记录、总结、侃大山

引言

Zuul is the front door for all requests from devices and web sites to the backend of the Netflix streaming application. As an edge service application, Zuul is built to enable dynamic routing, monitoring, resiliency and security. It also has the ability to route requests to multiple Amazon Auto Scaling Groups as appropriate.

—— Zuul 官方wiki

Zuul 简介

Zuul 是 Netflix OOS 中的一员,是一个基于 JVM 路由和服务端的负载均衡。提供路由、监控、弹性、安全等方面的服务框架。Zuul 能够与 Eureka 、Ribbon 、Hystrix 等组件配合使用。

Zuul 的核心是过滤器,通过这些过滤器我们可以扩展出很多功能,比如:

  • 动态路由:动态的将客户端的请求路由到后端的不同服务,做一些逻辑处理,比如聚合多个服务的数据返回。
  • 请求监控:可以对整个系统的请求进行监控,记录详细的请求响应日志,可以实时统计出当前系统的访问量以及监控状态。
  • 认证鉴权:对每一个访问的请求做认证,拒绝非法请求,保护好后端的服务。
  • 压力测试:压力测试是一项很重要的工作,像电商公司需要模拟更多的真实的用户并发量来保证重大活动时系统的稳定。通过 Zuul 可以动态地将请求转发到后端服务的集群中,还可以识别测试流量和真实流量,从而做一些特殊处理。
  • 灰度发布:灰度发布可以保证系统的稳定,在初始灰度的时候就可以发现、调整问题、以保证其影响度。

——《spring cloud 微服务 入门、进阶与实战》第103页

使用 Zuul 构建微服务网关

简单使用

建立一个 spring cloud 项目。配置如下:

  • pom添加依赖
1
2
3
4
 <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
  • properties 配置:
1
2
3
4
5
6
7
spring.application.name=zuul-demo
server.port=2103

#拦截匹配了 /demo1/** 规则的url 转发到 https://blog.wu-zy.com/
#这里 routes.demo1 的 demo1 是自定义的
zuul.routes.demo1.path=/demo1/**
zuul.routes.demo1.url=https://blog.wu-zy.com/
  • 启动类加上 @EnableZuulProxy 注解
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableZuulProxy
public class ZuulDemoApplication {

public static void main(String[] args) {
SpringApplication.run(ZuulDemoApplication.class, args);
}
}

启动项目我们访问:http://localhost:2103/demo1/myblog

结果跳转到了我的博客:https://blog.wu-zy.com/myblog/

集成 Eurekas

结合 Eureka 实现动态路由

  • 加入 Eureka 的依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • 配置注册中心
1
2
#注册中心集群
eureka.client.serviceUrl.defaultZone=http://WuZhiYong:123456@localhost:8761/eureka/,http://WuZhiYong:123456@localhost:8762/eureka/
阅读全文 »

引言

Hystrix is a latency and fault tolerance library designed to isolate points of access to remote systems, services and 3rd party libraries, stop cascading failure and enable resilience in complex distributed systems where failure is inevitable.

翻译:Hystrix是一个延迟和容错库,旨在隔离对远程系统,服务和第三方库的访问点,停止级联故障,并在不可避免发生故障的复杂分布式系统中实现弹性。

—— Hystrix GitHub官网 介绍

Hystrix介绍

Hystrix [hɪst’rɪks],中文含义是豪猪,因其背上长满棘刺,从而拥有了自我保护的能力。本文所说的Hystrix是Netflix开源的一款容错框架,同样具有自我保护能力。为了实现容错和自我保护,下面我们看看Hystrix如何设计和实现的。

Hystrix设计目标:

  • 对来自依赖的延迟和故障进行防护和控制——这些依赖通常都是通过网络访问的
  • 阻止故障的连锁反应
  • 快速失败并迅速恢复
  • 回退并优雅降级
  • 提供近实时的监控与告警

Hystrix遵循的设计原则:

  • 防止任何单独的依赖耗尽资源(线程)
  • 过载立即切断并快速失败,防止排队
  • 尽可能提供回退以保护用户免受故障
  • 使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一个依赖的影响
  • 通过近实时的指标,监控和告警,确保故障被及时发现
  • 通过动态修改配置属性,确保故障及时恢复
  • 防止整个依赖客户端执行失败,而不仅仅是网络通信

Hystrix如何实现这些设计目标?

  • 使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行;
  • 每个依赖都维护着一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。
  • 记录请求成功,失败,超时和线程拒绝。
  • 服务错误百分比超过了阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。
  • 请求失败,被拒绝,超时或熔断时执行降级逻辑。
  • 近实时地监控指标和配置的修改。

—— AbeJeffrey的个人空间

在spring cloud 中使用Hystrix

  • 添加 Hystrix 依赖
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

主要步骤

image-20200507193617495

通过gitee 上面的简单帮助我们可以看到主要分两大部

Step 1、绑定你已备案的域名

Step 2、通过解析域名的DNS服务商,配置域名CNAME解析到gitee.gitee.io

Step 1

填写好想配置的域名

image-20200507193918111

申请证书

image-20200507163153819

点击免费申请HTTPS证书(跳转到第三方生成证书)

第三方网站生成证书

比如我 购买了一个域名 wu-zy.com

阅读全文 »

引言

​ Feign是一个声明式REST客户端,它能让REST调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。

而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。 Spring Cloud对 Feign进行了封装,使其支持 Springmvc标准注解和Httpmessage Converters Feign可以与 Eureka和 Ribbon组合使用以支持负载均衡。

——《spring cloud 微服务 入门、进阶与实战》第70页

在spring cloud 中集成Feign

  • 添加Feign starter
1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 定义Feign客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
/**
* @ClassName UserRemotClient
* @Author wuzhiyong
* @Date 2020/5/2 23:16
* @Version 1.0
**/
@FeignClient(value = "eureka-client-service")
public interface UserRemoteClient {

@GetMapping("/user/hello")
String hello();

}

引言

目前主流的负载方案分为两种:一种是集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如F5),也有软件的(比如 Nginx)。另一种则是客户端自己做负载均衡,根据自己的请求情况做负载, Ribbon就属于客户端自己量负载。如果用一句话介绍,那就是: Ribbon是 Netflix开源的一款用于客户端负载均衡的工具软件。

——《spring cloud 微服务 入门、进阶与实战》第52页

与spring cloud集成

在spring cloud 项目中集成Ribbon 只需要在pom.xml 中添加下面的依赖即可,如果用了Eureka也可以不用配置,因为Eureka 中已经引用了Ribbon

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

RestTemplate负载均衡

新建一个spring cloud项目 命名为 springcloud-ribbon

阅读全文 »

引言

Spring Cloud Eureka是 Spring Cloud Netflix微服务套件的一部分,基于 Netflix Eurd做了二次封装,主要负责实现微服务架构中的服务治理功能。 Spring Cloud Eureka是一个基于REST的服务,并且提供了基于Java的客户端组件,能够非常方便地将服务注到Spring Cloud Eureka中进行统一管理。

注册中心带来的好处就是,不需要知道有多少提供方,你只需要关注注册中心即可,就像顺客不必关心有多少火车在开行,只需要去12306网站上看有没有票就可以了。为什么 Eureka比 Zookeeper更适合作为注册中心呢?主要是因为 Eureka是基于AP原则构建的,而 Zookeeper是基于CP原则构建的。在分布式系统领域有个著名的CAP定理,即C为数据一致性;A为服务可用性;P为服务对网络分区故障的容错性。这三个特性在任何分布式系统中都不能同时满足,最多同时满足两个。

Zookeeper有一个 Leader,而且在这个 Leader无法使用的时候通过 Paxos(ZAB)算法选举出一个新的 Leader.。这个 Leader的任务就是保证写数据的时候只向这个 Leader写入,Leader会同步信息到其他节点。通过这个操作就可以保证数据的一致性。

总而言之,想要保证AP就要用 Eureka,想要保证CP就要用 Zookeeper。Dubo中大部分都是基于 Zookeeper作为注册中心的。 Spring Cloud中当然首选 Eureka

——《spring cloud 微服务 入门、进阶与实战》第33页

开始

创建一个spring cloud 项目springcloud-eureka-server。(这里创建一个spring boot 再引入 spring cloud 的依赖也是一样)

阅读全文 »

引言

spring boot 的便利体现在,它简化了很多繁琐的配置,这对开发人员来说是一个福音。只要引入框架的starter就可以使用框架的功能了。不过当出现错误时,排查问题的难度就上升了。因为自动配置的逻辑都在spring boot starter中,想要快速定位问题,就必须了解spring boot starter的内部原理。下面我们自己动手来实现一个spring boot starter

——《spring cloud 微服务 入门、进阶与实战》25页

spring boot starter 项目创建

创建一个项目 spring-starter-demo。

注意!!!

这里创建的不是spring boot ,也不是 maven web。因为试过,即使 maven install 成功生成了一个starter的 jar 包,但在spring boot 项目中却不能正常注入。经查多篇博客发现这里创建的是maven quick start 项目

阅读全文 »

场景

代码部署总是需要一个端口,虽然我们可以在配置文件中定义与修改,也可以在部署的时候手动指定。为避免端口冲突何不用代码来为我们自动随机一个端口呢?

方法一

设置server.port=0,当应用启动的时候会自动的分配一个随机端口,但是该方式在注册到Eureka的时候会一个问题:所有实例都使用了同样的实例名(如:Lenovo-test:hello-service:0),这导致只出现了一个实例。所以,我们还需要修改实例ID的定义,让每个实例的ID不同,比如使用随机数来配置实

1
2
server.port=0
eureka.instance.instance-id=${spring.application.name}:${random.int}

方法二

除了上面的方法,实际上我们还可以直接使用random函数来配置server.port。这样就可以指定端口的取值范围,比如:

阅读全文 »

部分代码来自《spring cloud 微服务 入门、进阶与实战》

简单使用

spring 中想要方法是异步执行只需要加上@Async注解即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class MyAsyncMethod {
@Async
public void test1() throws InterruptedException {
int i = 0;
while (i<5){
System.out.println(i);
i++;
}
}
public void test2() throws InterruptedException {
System.out.println("test222222");
Thread.sleep(10L);
System.out.println("test333333");
}
}
1
2
myAsyncMethod.test1();
myAsyncMethod.test2();

控制台输出

1
2
3
4
5
6
7
test222222
0
1
2
3
4
test333333

配置线程池

有时我们想要自定义线程池的参数我们可增加如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
@ConfigurationProperties(prefix = "spring.task.pool")
public class TaskThreadPoolConfig {

/**核心线程数*/
private int corePoolSize = 5;

/**最大线程数*/
private int maxPoolSize = 50;

/**线程池维护线程所允许的空闲时间*/
private int keepAliveSeconds = 60;

/**队列长度*/
private int queueCapacity = 10000;

/**线程名称前缀*/
private String threadNamePrefix = "FSH-AsyncTask-";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Configuration
public class AsyncTaskExecutePool implements AsyncConfigurer {
private Logger logger = LoggerFactory.getLogger(AsyncTaskExecutePool.class);

@Autowired
private TaskThreadPoolConfig config;

@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(config.getCorePoolSize());
executor.setMaxPoolSize(config.getMaxPoolSize());
executor.setQueueCapacity(config.getQueueCapacity());
executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
executor.setThreadNamePrefix(config.getThreadNamePrefix());
/**
* 线程池拒绝策略:如果不配置并超过了负荷会造成溢出
* AbortPolicy:直接抛出异常 会丢弃任务
* CallerRunsPolicy 先由主线程执行当前任务,下个任务继续交给线程池来执行
*/
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
// 异步任务中异常处理
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) {

logger.error("==========================" + arg0.getMessage() + "=======================", arg0);
logger.error("exception method:" + arg1.getName());
}
};
}
}

然后在配置文件中配置相关参数

1
spring.task.pool.corePoolSize=9

注意

1
异步执行方法需在外部调用,在异步类中通过 this.xxx() 方式调用会无效

概述

对于接口的定义往往规范后会有一个统一的格式如:

1
2
3
4
5
6
{
"code": 200,
"status": true,
"message": "success",
"data": {xxx}
}

但由于用户请求以及代码逻辑运行的不确定性造成的异常,系统可能返回的数据并不是我们规范的格式,甚至返回一个页面。

如访问一个不存在的接口得到:

1
2
3
4
5
6
7
{
"timestamp": "2020-04-25T08:09:43.538+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/testException/2/55"
}

所以我们需要把这些异常返回做集中(统一)式处理。

代码准备

  • CustomException.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //自定义异常类
    public class CustomException extends RuntimeException {

    public CustomException() {
    }

    public CustomException(String message) {
    super(message);
    }
    }
  • ResponseData.class

    1
    2
    3
    4
    5
    6
    7
    8
    //统一返回的数据格式
    public class ResponseData {
    private int code = 200;
    private boolean status = true;
    private String message = null;
    private Object Data = null;
    ...此处省略 get set 方法
    }
  • HelloController.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //用于测试统一异常处理的接口
    @GetMapping("testException/{type}") HelloController
    public String testException(@PathVariable("type") String type ) throws Exception {
    switch (type){
    case "1":
    throw new Exception("default Exception");
    case "2":
    throw new CustomException("custom Exception");
    case "3":
    throw new NullPointerException("null Exception");
    default:
    return "0";
    }
    }
  • GlobalExceptionHandler.class

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // 统一异常处理类
    @ControllerAdvice
    public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ResponseBody
    @ExceptionHandler(value = CustomException.class)
    /** @ ExceptionHandler({CustomException.class,Exception.class}) 多个异常配置 */
    public ResponseData CustomExceptionHandler(HttpServletRequest request, CustomException e){
    logger.info(e.getMessage());
    ResponseData responseData = new ResponseData();
    responseData.setCode(405);
    responseData.setMessage(e.getMessage());
    responseData.setStatus(false);
    return responseData;
    }

    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public ResponseData defaultExceptionHandler(HttpServletRequest request, Exception e){
    logger.info(e.getMessage());
    ResponseData responseData = new ResponseData();
    if (e instanceof NullPointerException){
    responseData.setCode(407);
    }else {
    responseData.setCode(406);
    }
    responseData.setMessage(e.getMessage());
    responseData.setStatus(false);
    return responseData;
    }
    }

测试

请求:http://localhost:8082/testException/1

响应:

1
2
3
4
5
6
{
"code": 406,
"status": false,
"message": "default Exception",
"data": null
}

请求:http://localhost:8082/testException/2

响应:

1
2
3
4
5
6
{
"code": 405,
"status": false,
"message": "custom Exception",
"data": null
}

请求:http://localhost:8082/testException/3

响应:

1
2
3
4
5
6
{
"code": 407,
"status": false,
"message": "null Exception",
"data": null
}

请求:http://localhost:8082/testException/3/xxx (没有此接口)

响应:

1
2
3
4
5
6
7
{
"timestamp": "2020-04-25T08:30:12.922+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/testException/3/xxx"
}

发现请求不存在的接口返回的不是我们统一的格式我们需要程序对这种不存在的资源不能映射到404

增加配置

  • application.properties

    1
    2
    3
    4
    #出现错误时 直接抛出异常
    spring.mvc.throw-exception-if-no-handler-found=true
    #不对资源文件做映射
    spring.resources.add-mappings=false

    再次请求得到响应:

    1
    2
    3
    4
    5
    6
    {
    "code": 406,
    "status": false,
    "message": "No handler found for GET /testException/3/xxx",
    "data": null
    }
0%