在SpringCloud中,通常使用的是客服端发现作为负载均衡。SpringCloud全家桶中提供了Ribbon作为客户端负载的优秀框架。
服务器之间的调用直接使用Feign进行调用。Feign默认使用了Ribbon作为负载的实现。
通常来说,进行服务拆分后,每个服务之间都需要支持幂等性,因为可能由于网络的原因,一个接口可能会被重试多次,如果没有做好幂等性控制的话,容易出现数据重复等异常情况。
比如将Ribbon的连接和读取时间设置为2s,最大调用次数为4次,则有如下配置:

1
2
3
4
5
ribbon.ConnectTimeout=2000
ribbon.ReadTimeout=2000
ribbon.MaxAutoRetriesNextServer=1
ribbon.MaxAutoRetries=1
ribbon.OkToRetryOnAllOperations=true

在接口调用过程中,所有的异常都会自动重试,直到4次调用都失败后,才会抛出异常。这时可以使用try catch就非常简单的捕获到了异常信息。
但是如果前三次调用都失败了,第四次成功的话,try catch就获取不到中间的异常信息了,微服务之间应该是高可用的,因为都是内网的调用,出现异常的情况是比较少的,所以需要将所有失败的情况记录下来,以便及时发现问题。
首先,添加自己的LoadBalancedRetryFactory

1
2
3
4
5
6
7
@Configuration
public class RibbonConfiguration {
@Bean
public LoadBalancedRetryFactory loadBalancedRetryPolicyFactory(final SpringClientFactory clientFactory) {
return new DefaultRibbonLoadBalanceRetryFactory(clientFactory);
}
}

其次添加 DefaultRibbonLoadBalanceRetryFactory

1
2
3
4
5
6
7
8
9
10
public class DefaultRibbonLoadBalanceRetryFactory extends RibbonLoadBalancedRetryFactory {
public DefaultRibbonLoadBalanceRetryFactory(SpringClientFactory clientFactory) {
super(clientFactory);
}

@Override
public org.springframework.retry.RetryListener[] createRetryListeners(String service) {
return new RetryListener[]{new RibbonRetryListener()};
}
}

最后是重试监听的实现:RibbonRetryListener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public class RibbonRetryListener implements RetryListener {

private Logger logger = LoggerFactory.getLogger(getClass());

@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
return true;
}

@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {

}

@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
String service = ((LoadBalancedRetryContext) context).getServiceInstance().getServiceId();
logger.error(service + "微服务调用错误:", throwable);
}
}

这样,所有的失败调用都会log出来,这里可以根据实际业务情况落表或者进行报警处理。

测试使用

这里使用用户服务和订单服务作为测试,首先需要启动config-server,eureka-server两个基础服务。
用户服务中提供接口:

1
2
3
4
5
6
7
8
9
10
11

@RequestMapping("/retryListener")
@ResponseBody
public String testRetryListener() {
try {
return orderApi.testRetry();
} catch (Exception e) {
logger.error("", e);
return "error:" + e.getMessage();
}
}

这里实现非常简单,只是调用order的接口而已。
订单服务则模拟失败的情况,访问的次数不为3的倍数,则休眠2.1s(因为用户服务配置超时时间为2s)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private AtomicLong counter = new AtomicLong(1);

/**
* 每次請求計數器加一,当请求的次数不为3的倍数时,休眠2.1s返回
*
* @return
* @throws InterruptedException
*/
@ResponseBody
@RequestMapping("/testRetry")
public String testRetry() throws InterruptedException {
if (counter.incrementAndGet() % 3 != 0) {
Thread.sleep(2100);
}
return "port:" + port;
}

访问接口:http://localhost:8300/retryListener 可以看到返回了:port:8100数据,查看控制台:

异常数据完美记录下来了。

文章中的代码已提交到:
github:https://github.com/cmlbeliever/SpringCloudBucket
码云:https://gitee.com/cmlbeliever/springcloud
工程中包含了微服务的常用实现方式, 欢迎star与fork