深入学习spring cloud gateway 限流熔断

网友投稿 255 2023-01-21

深入学习spring cloud gateway 限流熔断

目前,Spring Cloud Gateway是仅次于Spring Cloud Netflix的第二个最受欢迎的Spring Cloud项目(就github上的星级而言)。它是作为Spring Cloud系列中Zuul代理的继任者而创建的。该项目提供了用于微服务体系结构的API网关,并基于反应式Netty和Project Reactor构建。它旨在提供一种简单而有效的方法来路由到API并解决诸如安全性,监视/度量和弹性之类的普遍关注的问题。

基于Redis限流

Spring Cloud Gateway为您提供了许多功能和配置选项。今天,我将集中讨论网关配置的一个非常有趣的方面-速率限制。速率限制器可以定义为一种控制网络上发送或接收的流量速率的方法。我们还可以定义几种类型的速率限制。Spring Cloud Gateway当前提供了一个Request Rate Limiter,它负责将每个用户每秒限制为N个请求。与Spring Cloud Gateway一起 使用时RequestRateLimiter,我们可能会利用Redis。Spring Cloud实现使用令牌桶算法做限速。该算法具有集中式存储桶主机,您可以在其中对每个请求获取令牌,然后将更多的令牌缓慢滴入存储桶中。如果存储桶为空,则拒绝该请求。

项目演示源码地址:https://github.com/1ssqq1lxr/SpringCloudGatewayTest

引入maven依赖

## spring cloud依赖

org.springframework.boot

spring-boot-starter-parent

2.2.1.RELEASE

UTF-8

UTF-8

11

Hoxton.RC2

org.springframework.cloud

spring-cloud-dependencies

${spring-cloud.version}

pom

import

## gateway 依赖

org.springframework.cloud

spring-cloud-starter-gateway

org.springframework.boot

spring-boot-starter-data-redis-reactive

org.projectlombok

lombok

org.springframework.boot

spring-boot-starter-test

test

org.testcontainers

1.12.3

test

org.mock-server

mockserver-client-java

3.10.8

test

com.carrotsearch

junit-benchmarks

0.7.2

test

限流器配置

使用Spring Cloud Gateway默认请求限流GatewayFilter(org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter)。使用默认的Redis限流器方案,你可以通过自定义keyResolver类去决定Redis限流key的生成,下面举常用几个例子:

根据用户: 使用这种方式限流,请求路径中必须携带userId参数

@Bean

KeyResolver userKeyResolver() {

return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));

}

根据获uri

@Bean

KeyResolver apiKeyResolver() {

return exchange -> Mono.just(exchange.getRequest().getPath().value());

}

由于我们已经讨论了Spring Cloud Gateway速率限制的一些理论方面,因此我们可以继续进行实施。首先,让我们定义主类和非常简单的KeyResolverbean,它始终等于一个。

@SpringBootApplication

public class GatewayApplication {

public static void main(String[] args) {

SpringApplication.run(GatewayApplication.class, args);

}

@Bean

KeyResolver userKeyResolver() {

return exchange -> Mono.just("1");

}

}

Gateway默认使用org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter限流器。 现在,如通过模拟Http请求,则会收到以下响应。它包括一些特定的header,其前缀为x-ratelimit。

x-ratelimit-burst-capacity:最大令牌值,

x-ratelimit-replenish-rate:填充的速率值,

x-ratelimit-remaining:剩下可请求数。

yaml配置:

server:

port: ${PORT:8085}

spring:

application:

name: gateway-service

redis:

host: localhost

port: 6379

cloud:

gateway:

routes:

- id: account-service

uri: http://localhost:8091

predicates:

- Path=/account/**

filters:

- RewritePath=/account/(?.*), /$\{path}

- name: RequestRateLimiter

args:

redis-rate-limiter.replenishRate: 10

redis-rate-limiter.burstCapacity: 20

Redis限流器实现

关键源码如下:

// routeId也就是我们的服务路由id,id就是限流的key

public Mono isAllowed(String routeId, String id) {

// 会判断RedisRateLimiter是否初始化了

if (!this.initialized.get()) {

throw new IllegalStateException("RedisRateLimiter is not initialized");

}

// 获取routeId对应的限流配置

Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);

if (routeConfig == null) {

throw new IllegalArgumentException("No Configuration found for route " + routeId);

}

// 允许用户每秒做多少次请求

int replenishRate = routeConfig.getReplenishRate();

// 令牌桶的容量,允许在一秒钟内完成的最大请求数

int burstCapacity = routeConfig.getBurstCapacity();

try {

// 限流key的名称(request_rate_limiter.{localhost}.timestamp,request_rate_limiter.{localhost}.tokens)

List keys = getKeys(id);

// The arguments to the LUA script. time() returns unixtime in seconds.

List scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",

Instant.now().getEpochSecond() + "", "1");

// allowed, tokens_left = redis.eval(SCRIPT, keys, args)

// 执行LUA脚本

Flux> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);

// .log("redisratelimiter", Level.FINER);

return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))

.reduce(new ArrayList(), (longs, l) -> {

longs.addAll(l);

return longs;

}) .map(results -> {

boolean allowed = results.get(0) == 1L;

Long tokensLeft = results.get(1);

Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));

if (log.isDebugEnabled()) {

log.debug("response: " + response);

}

return response;

});

}

catch (Exception e) {

log.error("Error determining if user allowed from redis", e);

}

return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));

}

测试Redis限流器

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)

@RunWith(SpringRunner.class)

public class GatewayRateLimiterTest {

private static final Logger LOGGER = LoggerFactory.getLogger(GatewayRateLimiterTest.class);

@Rule

public TestRule benchmarkRun = new BenchmarkRule();

@ClassRule

public static MockServerContainer mockServer = new MockServerContainer();

@ClassRule

public static GenericContainer redis = new GenericContainer("redis:5.0.6").withExposedPorts(6379);

@Autowired

TestRestTemplate template;

@BeforeClass

public static void init() {

System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");

System.setProperty("spring.cloud.gateway.routes[0].uri", "http://localhost:" + mockServer.getServerPort());

System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");

System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?.*), /$\\{path}");

System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "RequestRateLimiter");

System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.replenishRate", "10");

System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.burstCapacity", "20");

System.setProperty("spring.redis.host", "localhost");

System.setProperty("spring.redis.port", "" + redis.getMappedPort(6379));

new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort())

.when(HttpRequest.request()

.withPath("/1"))

.respond(response()

.withBody("{\"id\":1,\"number\":\"1234567890\"}")

.withHeader("Content-Type", "application/json"));

}

@Test

@BenchmarkOptions(warmupRounds = 0, concurrency = 6, benchmarkRounds = 600)

public void testAccountService() {

ResponseEntity r = template.exchange("/account/{id}", HttpMethod.GET, null, Account.class, 1);

LOGGER.info("Received: status->{}, payload->{}, remaining->{}", r.getStatusCodeValue(), r.getBody(), r.getHeaders().get("X-RateLimit-Remaining"));

// Assert.assertEquals(200, r.getStatusCodeValue());

// Assert.assertNotNull(r.getBody());

// Assert.assertEquals(Integer.valueOf(1), r.getBody().getId());

// Assert.assertEquals("1234567890", r.getBody().getNumber());

}

}

执行Test类: 发现超过20之后会被拦截返回429,运行过程中随着令牌的放入会不断有请求成功。

14:20:32.242 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[18]

14:20:32.242 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[16]

14:20:32.242 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[14]

14:20:32.242 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[15]

14:20:32.242 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[17]

14:20:32.242 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[19]

14:20:32.294 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[15]

14:20:32.297 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[19]

14:20:32.304 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[18]

14:20:32.308 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[16]

14:20:32.309 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[17]

14:20:32.312 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[14]

14:20:32.320 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[13]

14:20:32.326 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[12]

14:20:32.356 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[7]

14:20:32.356 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[10]

14:20:32.361 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[6]

14:20:32.363 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[8]

14:20:32.384 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[4]

14:20:32.384 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[11]

14:20:32.386 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[5]

14:20:32.390 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[9]

14:20:32.391 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[3]

14:20:32.392 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[2]

14:20:32.403 --- [pool-2-thread-6] : Received: status->429, payload->null, remaining->[0]

14:20:32.403 --- [pool-2-thread-4] : Received: status->429, payload->null, remaining->[0]

........

14:20:33.029 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[9]

14:20:33.033 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[8]

14:20:33.033 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[7]

14:20:33.037 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[6]

14:20:33.039 --- [pool-2-thread-5] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[5]

14:20:33.046 --- [pool-2-thread-6] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[4]

14:20:33.052 --- [pool-2-thread-5] : Received: status->429, payload->null, remaining->[0]

14:20:33.058 --- [pool-2-thread-6] : Received: status->429, payload->null, remaining->[0]

14:20:33.058 --- [pool-2-thread-1] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[2]

14:20:33.060 --- [pool-2-thread-5] : Received: status->429, payload->null, remaining->[0]

14:20:33.081 --- [pool-2-thread-4] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[1]

14:20:33.082 --- [pool-2-thread-3] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[0]

14:20:33.084 --- [pool-2-thread-2] : Received: status->200, payload->Account(id=1, number=1234567890), remaining->[3]

14:20:33.088 --- [pool-2-thread-5] : Received: status->429, payload->null, remaining->[0]

如果默认的限流器不能够满足使用,可以通过继承AbstractRateLimiter实现自定义限流器,然后通过RouteLocator方式去注入拦截器。

Resilience4J熔断器

引入依赖

org.springframework.cloud

spring-cloud-starter-gateway

org.springframework.boot

spring-cloud-starter-circuitbreaker-reactor-resilience4j

Resilience4J 断路器介绍

三个一般性状态

CLOSED:关闭状态,放过所有请求,记录请求状态。

OPEN:打开,异常请求达到阀值数量时,开启熔断,拒绝所有请求。

HALF_OPEN:半开,放开一定数量的请求,重新计算错误率。

两个特定状态

DISABLED:禁用

FORCED_OPEN:强开

状态之间转换

启动时断路器为CLOSE状态,在达到一定请求量至后计算请求失败率,达到或高于指定失败率后,断路进入open状态,阻拦所有请求,开启一段时间(自定义)时间后,断路器变为halfOpen状态,重新计算请求失败率。halfOpen错误率低于指定失败率后,断路进入close状态,否则进入open状态。

状态转换

通过Resilience4J启用Spring Cloud Gateway断路器

要启用构建在Resilience4J之上的断路器,我们需要声明一个Customizer传递了的bean ReactiveResilience4JCircuitBreakerFactory。可以非常简单地去配置设置,下面使用默认配置进行测试

@Bean

public Customizer defaultCustomizer() {

return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)

.circuitBreakerConfig(CircuitBreakerConfig.custom()

//统计失败率的请求总数

.slidingWindowSize(5)

//在半开状态下请求的次数

.permittedNumberOfCallsInHalfOpenState(5)

//断路器打开的成功率

.failureRateThreshold(50.0F)

//断路器打开的周期

.waitDurationInOpenState(Duration.ofMillis(30))

//属于慢请求的周期

.slowCallDurationThreshold(Duration.ofMillis(200))

//慢请求打开断路器的成功率

.slowCallRateThreshold(50.0F)

.build())

.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(200)).build()).build());

}

测试Resilience4J断路器

使用默认配置进行测试

@Bean

public Customizer defaultCustomizer() {

return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)

.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())

.circuitBreakerConfig(CircuitBreakerConfig.custom()

.slowCallDurationThreshold(Duration.ofMillis(200))

.build())

.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(200)).build())

.build());

}

执行下面Test用例

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)

@RunWith(SpringRunner.class)

public class GatewayCircuitBreakerTest {

private static final Logger LOGGER = LoggerFactory.getLogger(GatewayRateLimiterTest.class);

@Rule

public TestRule benchmarkRun = new BenchmarkRule();

@ClassRule

public static MockServerContainer mockServer = new MockServerContainer();

@Autowired

TestRestTemplate template;

final Random random = new Random();

int i = 0;

@BeforeClass

public static void init() {

System.setProperty("logging.level.org.springframework.cloud.gateway.filter.factory", "TRACE");

System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");

System.setProperty("spring.cloud.gateway.routes[0].uri", "http://localhost:" + mockServer.getServerPort());

System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");

System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?.*), /$\\{path}");

System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "CircuitBreaker");

System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.name", "exampleSlowCircuitBreaker");

// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.slowCallDurationThreshold", "100");

// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.slowCallRateThreshold", "9.0F");

// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.fallbackUri", "forward:/fallback/account");

MockServerClient client = new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort());

client.when(HttpRequest.request()

.withPath("/1"))

.respond(response()

.withBody("{\"id\":1,\"number\":\"1234567890\"}")

.withHeader("Content-Type", "application/json"));

// client.when(HttpRequest.request()

// .withPath("/2"), Times.exactly(3))

// .respond(response()

// .withBody("{\"id\":2,\"number\":\"1\"}")

// .withDelay(TimeUnit.SECONDS, 1000)

// .withHeader("Content-Type", "application/json"));

client.when(HttpRequest.request()

.withPath("/2"))

.respond(response()

.withBody("{\"id\":2,\"number\":\"1234567891\"}")

.withDelay(TimeUnit.SECONDS, 200)

.withHeader("Content-Type", "application/json"));

}

@Test

@BenchmarkOptions(warmupRounds = 0, concurrency = 1, benchmarkRounds = 600)

public void testAccountService() {

int gen = 1 + (i++ % 2);

ResponseEntity r = template.exchange("/account/{id}", HttpMethod.GET, null, Account.class, gen);

LOGGER.info("{}. Received: status->{}, payload->{}, call->{}", i, r.getStatusCodeValue(), r.getBody(), gen);

}

}

请求日志如下:当请求达到100次时候,此时失败率为50% 这时候系统开启断路器返回503!

20:07:29.281 --- [pool-2-thread-1] : 91. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:07:30.297 --- [pool-2-thread-1] : 92. Received: status->504, payload->Account(id=null, number=null), call->2

20:07:30.316 --- [pool-2-thread-1] : 93. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:07:31.328 --- [pool-2-thread-1] : 94. Received: status->504, payload->Account(id=null, number=null), call->2

20:07:31.345 --- [pool-2-thread-1] : 95. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:07:32.359 --- [pool-2-thread-1] : 96. Received: status->504, payload->Account(id=null, number=null), call->2

20:07:32.385 --- [pool-2-thread-1] : 97. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:07:33.400 --- [pool-2-thread-1] : 98. Received: status->504, payload->Account(id=null, number=null), call->2

20:07:33.414 --- [pool-2-thread-1] : 99. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:07:34.509 --- [pool-2-thread-1] : 100. Received: status->504, payload->Account(id=null, number=null), call->2

20:07:34.525 --- [pool-2-thread-1] : 101. Received: status->503, payload->Account(id=null, number=null), call->1

20:07:34.533 --- [pool-2-thread-1] : 102. Received: status->503, payload->Account(id=null, number=null), call->2

20:07:34.539 --- [pool-2-thread-1] : 103. Received: status->503, payload->Account(id=null, number=null), call->1

20:07:34.545 --- [pool-2-thread-1] : 104. Received: status->503, payload->Account(id=null, number=null), call->2

20:07:34.552 --- [pool-2-thread-1] : 105. Received: status->503, payload->Account(id=null, number=null), call->1

20:07:34.566 --- [pool-2-thread-1] : 106. Received: status->503, payload->Account(id=null, number=null), call->2

20:07:34.572 --- [pool-2-thread-1] : 107. Received: status->503, payload->Account(id=null, number=null), call->1

20:07:34.576 --- [pool-2-thread-1] : 108. Received: status->503, payload->Account(id=null, number=null), call->2

20:07:34.580 --- [pool-2-thread-1] : 109. Received: status->503, payload->Account(id=null, number=null), call->1

20:07:34.586 --- [pool-2-thread-1] : 110. Received: status->503, payload->Account(id=null, number=null), call->2

20:07:34.591 --- [pool-2-thread-1] : 111. Received: status->503, payload->Account(id=null, number=null), call->1

这时候我们修改下配置

@BeforeClass

public static void init() {

System.setProperty("logging.level.org.springframework.cloud.gateway.filter.factory", "TRACE");

System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");

System.setProperty("spring.cloud.gateway.routes[0].uri", "http://localhost:" + mockServer.getServerPort());

System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");

System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?.*), /$\\{path}");

System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "CircuitBreaker");

System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.name", "exampleSlowCircuitBreaker");

// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.slowCallDurationThreshold", "100");

// System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.slowCallRateThreshold", "9.0F");

System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.fallbackUri", "forward:/fallback/account");

MockServerClient client = new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort());

client.when(HttpRequest.request()

.withPath("/1"))

.respond(response()

.withBody("{\"id\":1,\"number\":\"1234567890\"}")

.withHeader("Content-Type", "application/json"));

client.when(HttpRequest.request()

.withPath("/2"), Times.exactly(3))

.respond(response()

.withBody("{\"id\":2,\"number\":\"1\"}")

.withDelay(TimeUnit.SECONDS, 1000)

.withHeader("Content-Type", "application/json"));

client.when(HttpRequest.request()

.withPath("/2"))

.respond(response()

.withBody("{\"id\":2,\"number\":\"1234567891\"}")

// .withDelay(TimeUnit.SECONDS, 200)

.withHeader("Content-Type", "application/json"));

}

新建一个回调接口,用于断路器打开后请求的地址。

@RestController

@RequestMapping("/fallback")

public class GatewayFallback {

@GetMapping("/account")

public Account getAccount() {

Account a = new Account();

a.setId(2);

a.setNumber("123456");

return a;

}

}

使用默认设置时,前3次请求触发断路器回调,后面正常请求成功

20:20:23.529 --- [pool-2-thread-1] : 1. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:20:23.777 --- [pool-2-thread-1] : 2. Received: status->200, payload->Account(id=2, number=123456), call->2

20:20:23.808 --- [pool-2-thread-1] : 3. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:20:24.018 --- [pool-2-thread-1] : 4. Received: status->200, payload->Account(id=2, number=123456), call->2

20:20:24.052 --- [pool-2-thread-1] : 5. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:20:24.268 --- [pool-2-thread-1] : 6. Received: status->200, payload->Account(id=2, number=123456), call->2

20:20:24.301 --- [pool-2-thread-1] : 7. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:20:24.317 --- [pool-2-thread-1] : 8. Received: status->200, payload->Account(id=2, number=1234567891), call->2

20:20:24.346 --- [pool-2-thread-1] : 9. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:20:24.363 --- [pool-2-thread-1] : 10. Received: status->200, payload->Account(id=2, number=1234567891), call->2

20:20:24.378 --- [pool-2-thread-1] : 11. Received: status->200, payload->Account(id=1, number=1234567890), call->1

20:20:24.392 --- [pool-2-thread-1] : 12. Received: status->200, payload->Account(id=2, number=1234567891), call->2

20:20:24.402 --- [pool-2-thread-1] : 13. Received: status->200, payload->Account(id=1, number=1234567890), call->1

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java并发编程之CountDownLatch源码解析
下一篇:开放api接口验证(开放api接口签名验证)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~