SpringCloud微服务最佳实践
技术选型
参考SpringCloud调研报告,我们的技术选型为:
- 注册中心:Consul集群 (为后续的部署和服务分离做准备)
- RPC组件:Ribbon + Hystrix + OpenFeign + Apache Httpclient (标准使用)
- 监控管理:Sleuth + SkyWalker + SpringBootAdmin (JVM监控、方法监控、链路跟踪、JMX远程管理)
具体实施
Consul集群搭建
Consul集群由一个boostrap实例(一个特殊的server实例) + 多个server实例 + 没有或者多个client实例(主要用来做监控管理)组成。
Consul默认会使用以下端口,所以如果有防火墙的话需要按需打开:
- 8300:agent服务relplaction、rpc(client-server)
- 8301:lan gossip
- 8302:wan gossip
- 8500:http api端口
- 8600:DNS服务端口
bootstrap实例
consul agent -bootstrap-expect 1 -server -ui -log-file /export/log/consul/consul.log -data-dir /export/data/consul -node=node1 -bind=10.28.6.41 -client 10.28.6.41 -config-dir /export/data/consul -enable-script-checks=true -datacenter=defaul
参数解析:
- -bootstrap-expect:最小适用集群,建议至少为集群总数量/2+1
- -server:作为server节点
- -ui:提供ui服务
- -log-file:指定日志目录
- -data-dir:数据临时目录
- -node:节点名称
- -bind:consul服务绑定ip
- -client:ui服务绑定ip,默认为127.0.0.1,只能本机访问
- -config-dir:配置临时目录
- -enable-script-checks:健康检查
- -datacenter:所属数据中心
server实例
consul agent -server -log-file /export/log/consul/consul.log -data-dir /export/data/consul -node=node2 -bind=10.28.6.79 -config-dir /export/data/consul -enable-script-checks=true -datacenter=default -retry-join 10.28.6.41
参数解析:
- -retry-join:加入到的集群ip,失败自动重试
SpringCloud实现
SpringCloud作为微服务的一种,其实在实施规范上和Dubbo有很多共通之处,首先可以参考 Dubbo最佳实践 。
分包
首先是最重要的分包,为了统一规范接口设计,同时也降低接入成本,虽然SpringCloud官方不推荐使用分包的策略共享api的domain,但是在实际实施中,基本都会共享api和domain(如果能够规范异常更好,能够形成统一的异常处理规范)。
建议的共享分包目录结构为:
+ -- api
+ -- package-service-a
api-a-1
api-a-2
+ -- namespace-b
api-b-1
api-b-2
+ -- domain
+ -- package-domain-a
domain-a-1
domian-a-2
+ -- package-domain-b
domain-b-1
domain-b-1
+ -- exception
+ -- util
通过共享统一的共享包来进行接口的对接,保证一致,同时通过共享包的版本控制,来确定服务的版本。
在接口规范上面,由于我们均是通过RestFul接口提供服务,所以可以在定义接口的时候,就确定各个具体接口的path的method,那么在服务提供和服务消费的时候就能够进行统一,参考:
@RequestMapping(value = "/serviceA")
public interface ServiceA {
@RequestMapping(value = "/invokeB", method = RequestMethod.GET)
TrackInfo invokeB();
...
}
服务发布
SpringMVC实现
参考之前的分包策略,服务方在实现业务的时候,只需要实现对应的接口,即可满足规范,但是需要注意的是 SpringMVC在做接口映射的时候,使用的是标准的Spring反射流程,所以没有实现对方法参数注解的继承,在实现方法的时候,还需要对方法参数的注解进行补填,参考:
@RestController
public class ServiceBController implements ServiceB {
@Override
public TrackInfo stop(@RequestBody TrackInfo trackInfo) {
...
return trackInfo;
}
}
服务发现
通过引入spring-cloud-starter-consul-all之后,服务发现基本能够达到开箱即用,不过SpringCloud的传统策略就是使用主机名作为应用实例,对于容器可能会遇到主机名重复的情况,参考配置如下:
spring:
application:
name: ${serviceId}
cloud:
consul:
host: 10.28.6.79
port: 8500
discovery:
##默认关闭,为主机名
prefer-ip-address: true
##默认为服务名+端口,没有主机名
instance-id: ${serviceId}:${spring.cloud.client.ip-address}:${server.port}
服务消费
服务发现
作为消费方,服务发现基本和服务方一致,只需要依赖spring-cloud-starter-consul-all即可。
RPC调用
由于之前进行了分包,所以在消费时候代码实现是非常方便的,直接使用继承+FeignClient即可:
@FeignClient(value = "${serviceId.serviceB}")
public interface ServiceBClient extends ServiceB{
}
但是OpenFeign/Ribbon/Hystrix都有大量的配置,都可能需要根据实际情况进行调整,下面是一些默认配置:
OpenFeign
feign:
httpclient:
#单点最大并发连接数,集群越大,连接数需要的越少,反之
max-connections-per-route: 10
#客户端最大并发连接数,和集群规模有关
max-connections: 500
hystrix:
#开启Hystrix
enabled: true
#开启压缩
compression:
request:
enabled: true
response:
enabled: true
Ribbon
ribbon:
eager-load:
#饥饿模式,启动即开始加载,需要配合clients
enabled: true
http:
client:
#启用Apache的httpclient代替默认的URLConnection
enabled: true
Hystrix
hystrix:
command:
default:
execution:
isolation:
thread:
#线程最大阻塞时间
timeoutInMilliseconds: 10000
threadpool:
default:
coreSize: 10
allowMaximumSizeToDivergeFromCoreSize: true
maximumSize: 50
maxQueueSize: 10
keepAliveTimeMinutes: 5
这里需要注意的是,当消费方本身又提供SpringMVC服务时,SpringMVC会在FeignClient加载的时候将之加入HandlerMapping中,因为我们在做分包的时候,给FeignClient都加入了RequestMapping,为了避免这个,需要针对SpringMVC做一个额外的配置,来屏蔽FeignClient接口中的RequestMapping,示例如下:
/**
* 在Spring bean被初始化的时候,默认会检查是否有RequestMapping,如果有则会加入SpringMVC的适配中,
* 而feignClient通过接口基础的时候明显是不需要加入对外是SpringMVC服务,所以需要过滤一下
* @return
*/
@Bean
public WebMvcRegistrations feignWebRegistrations() {
return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping(){
@Override
protected boolean isHandler(Class<?> beanType) {
return super.isHandler(beanType) &&
!AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class);
}
};
}
};
}
其他
actuator
actuator是一个不错的组件,对于查看和管理线上实时运行状态非常有帮助,但是如果对外暴露则有较大的安全隐患,所以参考调研报告,要么整体SpringSecurity进行鉴权,否则最好通过端口分离隔离:
management:
server:
port: 8101
或者也可以将之暴露简单的health接口用于进行监控检查,其他的服务全部关闭。