Sentinel 和 Sleuth+Zipkin
Sentinel
官方文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
一、Sentinel 是什么?
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 的历史:
- 2012 年,Sentinel 诞生,主要功能为入口流量控制。
- 2013-2017 年,Sentinel 在阿里巴巴集团内部迅速发展,成为基础技术模块,覆盖了所有的核心场景。Sentinel 也因此积累了大量的流量归整场景以及生产实践。
- 2018 年,Sentinel 开源,并持续演进。
- 2019 年,Sentinel 朝着多语言扩展的方向不断探索,推出 C++ 原生版本,同时针对 Service Mesh 场景也推出了 Envoy 集群流量控制支持,以解决 Service Mesh 架构下多语言限流的问题。
- 2020 年,推出 Sentinel Go 版本,继续朝着云原生方向演进。
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。
二、基本概念及作用
基本概念:
资源:是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
规则:围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
主要作用:
- 流量控制
- 熔断降级
- 系统负载保护
我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:
- 定义资源
- 定义规则
- 检验规则是否生效
先把可能需要保护的资源定义好,之后再配置规则。也可以理解为,只要有了资源,我们就可以在任何时候灵活地定义各种流量控制规则。在编码的时候,只需要考虑这个代码是否需要保护,如果需要保护,就将之定义为一个资源。
三、springboot整合Sentinel
官方文档:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
1)、导入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--按需求导入-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-web-servlet</artifactId>
</dependency>
2)、application.yaml
配置 sentinel 控制台地址信息
spring:
cloud:
nacos:
discovery:
# Nacos服务注册中心地址
server-addr: localhost:8848
sentinel:
transport:
# sentinel dashboard 地址
dashboard: localhost:8080
# 启动该服务,会在应用程序的相应服务器上启动HTTP Server,并且该服务器将与Sentinel dashboard进行交互
port: 8719 # 默认为8719,如果被占用会自动+1,直到找到为止
# 本地机器ip
client-ip: 本机ip
# 暴露的 endpoint 路径为 /actuator/sentinel
management:
endpoints:
web:
exposure:
include: '*' # *号须加引号
3)、限流配置类
新版本:
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.lyh.common.exception.BizCodeEnum;
import com.lyh.common.utils.R;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义阻塞返回方法
*/
@Configuration
public class SentinelBlockHandlerConfig implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
R error = R.error(BizCodeEnum.TO_MANY_REQUEST.getCode(), BizCodeEnum.TO_MANY_REQUEST.getMessage());
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json");
httpServletResponse.getWriter().write(JSON.toJSONString(error));
}
}
旧版本:
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.lyh.common.exception.BizCodeEnum;
import com.lyh.common.utils.R;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Description: 自定义阻塞返回方法
**/
@Configuration
public class GulimallSeckillSentinelConfig {
public GulimallSeckillSentinelConfig() {
WebCallbackManager.setUrlBlockHandler(new UrlBlockHandler() {
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
R error = R.error(BizCodeEnum.TO_MANY_REQUEST.getCode(), BizCodeEnum.TO_MANY_REQUEST.getMessage());
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
response.getWriter().write(JSON.toJSONString(error));
}
});
}
}
4)、Endpoint 支持(可获取监控信息)
在使用 Endpoint 特性之前需要在 Maven 中添加 spring-boot-starter-actuator
依赖,并在配置中允许 Endpoints 的访问。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- Spring Boot 1.x 中添加配置
management.security.enabled=false
。暴露的 endpoint 路径为/sentinel
- Spring Boot 2.x 中添加配置
management.endpoints.web.exposure.include=*
。暴露的 endpoint 路径为/actuator/sentinel
Sentinel Endpoint 里暴露的信息非常有用。包括当前应用的所有规则信息、日志目录、当前实例的 IP,Sentinel Dashboard 地址,Block Page,应用与 Sentinel Dashboard 的心跳频率等等信息。
5)、自定义限流
1、代码
try (Entry entry = SphU.entry("seckillSkus")) {
//业务逻辑
} catch(Exception e) {}
2、基于注解
@SentinelResource(value = "getCurrentSeckillSkusResource",blockHandler = "blockHandler")
public List<SeckillSkuRedisTo> getCurrentSeckillSkus() {
}
// 降级调用的方法,参数必须和被调用方法一致
public List<SeckillSkuRedisTo> blockHandler(BlockException e) {
log.error("getCurrentSeckillSkusResource被限流了,{}",e.getMessage());
return null;
}
6)、gateway网关限流
官网:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel#spring-cloud-gateway-%E6%94%AF%E6%8C%81
1、引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、编写流控降级返回配置类
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.fastjson.JSON;
import com.lyh.common.exception.BizCodeEnum;
import com.lyh.common.utils.R;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Configuration
public class SentinelGatewayConfig {
public SentinelGatewayConfig() {
GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() {
//网关限流了请求,就会调用此回调
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
R error = R.error(BizCodeEnum.TO_MANY_REQUEST.getCode(), BizCodeEnum.TO_MANY_REQUEST.getMessage());
String errorJson = JSON.toJSONString(error);
Mono<ServerResponse> body = ServerResponse.ok().body(Mono.just(errorJson), String.class);
return body;
}
});
}
}
在控制台调整参数、【默认所有的流控规则保存在内存中,重启失效】(请看下面第八、规则持久化 可解决)
四、整合feign组件
1、 引入依赖:
引入feign及sentinel的依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、开启sentinel监控功能
application.yaml
feign.sentinel.enabled=true
3、代码示例
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class)
public interface EchoService {
@RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)
String echo(@PathVariable("str") String str);
}
class FeignConfiguration {
@Bean
public EchoServiceFallback echoServiceFallback() {
return new EchoServiceFallback();
}
}
class EchoServiceFallback implements EchoService {
@Override
public String echo(@PathVariable("str") String str) {
return "echo fallback";
}
}
在feign接口ProviderClient中指定熔断类:
使用Sentinel来保护feign远程调用,熔断;
1)、调用方的熔断保护:feign.sentinel.enable=true
2)、调用方手动指定远程服务的降级策略。远程服务被降级处理。触发我们的熔断回调方法
3)、超大浏览的时候,必须牺牲一些远程服务。在服务的提供方(远程服务)指定降级策略;
提供方是在运行,但是不允许自己的业务逻辑,返回的是默认的降级数据(限流的数据)
五、流量控制
1. 什么是流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
- 运行指标,例如 QPS、线程数等;
- 控制的效果,例如直接限流(快速失败)、冷启动(Warm Up)、匀速排队(排队等待)等。
Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
2. QPS流量控制
当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:直接拒绝、Warm Up、匀速排队。
1. 直接拒绝
直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT
)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException
。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
阈值类型选择:QPS
单机阈值:2
综合起来的配置效果就是,该接口的限流策略是每秒最多允许2个请求进入。
2. Warm Up(预热)
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
疯狂访问:http://localhost:18080/hi
可以发现前几秒会发生熔断,几秒钟之后就完全没有问题了
3. 匀速排队
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
测试配置:1s处理一个请求,排队等待,等待时间20s。
3. 关联限流
关联限流:当关联的资源请求达到阈值时,就限流自己。
4. 链路限流
一棵典型的调用树如下图所示:
machine-root
/ \
/ \
Entrance1 Entrance2
/ \
/ \
DefaultNode(nodeA) DefaultNode(nodeA)
上图中来自入口 Entrance1
和 Entrance2
的请求都调用到了资源 NodeA
,Sentinel 允许只根据某个入口的统计信息对资源限流。
5. 线程数限流
**并发线程数限流用于保护业务线程数不被耗尽。**例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。
配置如下:如果请求的并发数超过一个就限流
六、熔断降级
Sentinel除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException
)。
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
限流降级指标有三个:
-
平均响应时间(RT)
-
异常比例
-
异常数
1. 平均响应时间(RT)
平均响应时间 (DEGRADE_GRADE_RT
):当资源的平均响应时间超过阈值(DegradeRule
中的 count
,以 ms 为单位,默认上限是4900ms)之后,资源进入准降级状态。如果1s之内持续进入 5 个请求,它们的 RT 都持续超过这个阈值,那么在接下来的时间窗口(DegradeRule
中的 timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地返回(抛出 DegradeException
)。在下一个时间窗口到来时, 会接着再放入5个请求, 再重复上面的判断。
例如:配置 超时时间100ms,熔断时间10s
2. 异常比例
异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO
):当资源的每秒请求量 >= 5,且每秒异常总数占通过量的比值超过阈值(DegradeRule
中的 count
)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule
中的 timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。
3. 异常数
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT
):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。
七、sentinel环境搭建
1、docker安装搭建安装
- 拉取镜像
docker pull bladex/sentinel-dashboard:1.7.1
- 创建容器
docker run --name sentinel -d -p 8858:8858 bladex/sentinel-dashboard:1.7.1
# 随容器启动
docker update sentinel --restart=always
- 访问
http://localhost:8858/
2、jar启动环境
您可以从 release 页面 下载最新版本的控制台 jar 包。
jar包下载地址:https://github.com/alibaba/Sentinel/releases
下载的jar包(课前资料已下发),copy到一个没有空格或者中文的路径下,打开dos窗口切换到jar包所在目录。
执行:java -jar sentinel-dashboard-xxx.jar
在浏览器中访问sentinel控制台,默认端口号是8080。进入登录页面,管理页面用户名和密码:sentinel/sentinel
此时页面为空,这是因为还没有监控任何服务。另外,sentinel是懒加载的,如果服务没有被访问,也看不到该服务信息。
八、规则持久化
无论是通过硬编码的方式来更新规则,还是通过接入 Sentinel Dashboard 后,在页面上操作更新规则,都无法避免一个问题,那就是服务重启后,规则就丢失了,因为默认情况下规则是保存在内存中的。
我们在 Dashboard 上为客户端配置好了规则,并推送给了客户端。这时由于一些因素客户端出现异常,服务不可用了,当客户端恢复正常再次连接上 Dashboard 后,这时所有的规则都丢失了,我们还需要重新配置一遍规则,这肯定不是我们想要的。
持久化配置分以下3步:
- 引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 添加配置:
# 这里datasource后的consumer是数据源名称,可以随便写,推荐使用服务名
spring.cloud.sentinel.datasource.consumer.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.consumer.nacos.dataId=${spring.application.name}-sentinel-rules
spring.cloud.sentinel.datasource.consumer.nacos.groupId=SENTINEL_GROUP
spring.cloud.sentinel.datasource.consumer.nacos.data-type=json
# 规则类型,取值见:org.springframework.cloud.alibaba.sentinel.datasource.RuleType
spring.cloud.sentinel.datasource.consumer.nacos.rule_type=flow
- nacos中创建流控规则
配置内容如下:
[
{
"resource": "/hello",
"limitApp": "default",
"grade": 1,
"count": 2,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
resource:资源名称
limitApp:限流应用,就是用默认就可以
grade:阈值类型,0表示线程数,1表示qps
count:单机阈值
strategy:流控模式,0-直接,1-关联, 2-链路
controlBehavior:流控效果。0-快速失败,1-warm up 2-排队等待
clusterMode:是否集群
Sleuth+Zipkin 服务链路追踪
Spring Cloud Sleuth为springCloud实现了一个分布式链路追踪解决方案,大量借鉴了Dapper,Zipkin和HTrace等链路追踪技术。对于大多数用户而言,Sleuth应该是不可见的,并且您与外部系统的所有交互都应自动进行检测。您可以简单地在日志中捕获数据,也可以将其发送到远程收集器服务。
随着分布式系统越来越复杂,你的一个请求发过发过去,各个微服务之间的跳转,有可能某个请求某一天压力太大了,一个请求过去没响应,一个请求下去依赖了三四个服务,但是你去不知道哪一个服务出来问题,这时候我是不是需要对微服务进行追踪呀?监控一个请求的发起,从服务之间传递之间的过程,我最好记录一下,记录每一个的耗时多久,一旦出了问题,我们就可以针对性的进行优化,是要增加节点,减轻压力,还是服务继续拆分,让逻辑更加简单点呢?这时候springcloud-sleuth集成zipkin能帮我们解决这些服务追踪问题。
一. zipkin分布式监控客户端
Zipkin是一种分布式跟踪系统。它有助于收集解决微服务架构中的延迟问题所需的时序数据。它管理这些数据的收集和查找。Zipkin的设计基于Google Dapper论文。应用程序用于向Zipkin报告时序数据。Zipkin UI还提供了一个依赖关系图,显示了每个应用程序通过的跟踪请求数。如果要解决延迟问题或错误,可以根据应用程序,跟踪长度,注释或时间戳对所有跟踪进行筛选或排序。选择跟踪后,您可以看到每个跨度所需的总跟踪时间百分比,从而可以识别有问题的应用程序。
通过docker安装:docker run -d -p 9411:9411 openzipkin/zipkin
通过jar包安装:java -jar zipkin-server-*exec.jar
jar包下载地址:https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec
课前资料有已下载的jar包
在浏览器端访问:http://localhost:9411
二. 改造consumer/provider工程
对consumer和provider工程分别做如下操作:
- 引入sleuth的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
zipkin的启动器包含了sleuth的依赖。
- 配置zipkin的相关信息
# zipkin服务器的地址
spring.zipkin.base-url=http://localhost:9411
# 关闭服务发现,否则springCloud会把zipkin的url当作服务名称
spring.zipkin.discovery-client-enabled=false
# 数据发送的方式:ACTIVEMQ RABBIT KAFKA WEB
spring.zipkin.sender.type=web
# 设置抽样采集率,默认0.1(即10%),这里设置为100%
spring.sleuth.sampler.probability=1
- 重启consumer/provider服务后,访问消费者:http://localhost:18080/hi。查看zipkin客户端如下
这时候我们可以在zipkin的ui控制界面看看效果,可以发现,服务之间的调用关系,服务名称已经清晰展现出来了,同时包括服务之间的调用时常等详细信息以及更细的信息都可以通过控制台看到。
三. 基本概念
Span:基本工作单元。发送一个远程请求就会产生一个span,span通过一个64位ID唯一标识,trace以另一个64位ID表示,span还有其他数据信息,比如摘要、时间戳事件、关键值注释(tags)、span的ID、以及进度ID(通常是IP地址)。span在不断的启动和停止,同时记录了时间信息,当你创建了一个span,你必须在未来的某个时刻停止它。
Trace:一系列spans组成的一个树状结构。例如:发送一个请求,需要调用多个微服务,每调用一个微服务都会产生一个span,这些span组成一个trace
Annotation:用来及时记录一个事件的存在,一些核心annotations用来定义一个请求的开始和结束
- cs - Client Sent -客户端发起一个请求,这个annotion描述了这个span的开始
- sr - Server Received -服务端获得请求并准备开始处理它,如果将其sr减去cs时间戳便可得到网络延迟
- ss - Server Sent -注解表明请求处理的完成(当请求返回客户端),如果ss减去sr时间戳便可得到服务端需要的处理请求时间
- cr - Client Received -表明span的结束,客户端成功接收到服务端的回复,如果cr减去cs时间戳便可得到客户端从服务端获取回复的所有所需时间
四. Zipkin数据持久化
Zipkin 默认是将监控数据存储在内存的,如果 Zipkin 挂掉或重启的话,那么监控数据就会丢
失。所以如果想要搭建生产可用的 Zipkin,就需要实现监控数据的持久化。而想要实现数据
持久化,自然就是得将数据存储至数据库。好在 Zipkin 支持将数据存储至:
内存(默认)
-
MySQL
-
Elasticsearch
-
Cassandra
Zipkin 数据持久化相关的官方文档地址如下:
https://github.com/openzipkin/zipkin#storage-component
Zipkin 支持的这几种存储方式中,内存显然是不适用于生产的,这一点开始也说了。而使用
MySQL 的话,当数据量大时,查询较为缓慢,也不建议使用。Twitter 官方使用的是 Cassandra
作为 Zipkin 的存储数据库,但国内大规模用 Cassandra 的公司较少,而且 Cassandra 相关文
档也不多。
综上,故采用 Elasticsearch 是个比较好的选择,关于使用 Elasticsearch 作为 Zipkin 的存储数
据库的官方文档如下:
elasticsearch-storage:
https://github.com/openzipkin/zipkin/tree/master/zipkin-server#elasticsearch-storage
zipkin-storage/elasticsearch
https://github.com/openzipkin/zipkin/tree/master/zipkin-storage/elasticsearch
通过 docker 的方式
docker run --env STORAGE_TYPE=elasticsearch --env ES_HOSTS=192.168.10.123:9200
openzipkin/zipkin-dependencies