开题
本文只是对SpringCloud核心服务主要功能进行讲解,让Spring Cloud整个框架成体系,作为一条故事加深理解。
为何使用Spring Cloud
如果说Springboot是在微服务、注解驱动的领域做了大改动,那么SpringCloud就是应分布式集群环境而生的框架。
CAP 理论
- C:数据一致性(consistency)
- 所有节点拥有数据的最新版本
- A:可用性(availability)
- 数据具备高可用性
- P:分区容错性(partition-tolerance)
- 容忍网络出现分区,分区之间网络不可达。
数据一致性,大部分只能选择最终一致性,对价值不高数据甚至可以选择弱一致性。
而绝大部分场景下,P都是必须保证的,因为CA两者必须取舍高低,目前大部分分布式集群都是选择了AP,SpringCloud也是。
SpringCloud的基础功能:
- 服务治理: Spring Cloud Eureka
- 客户端负载均衡: Spring Cloud Ribbon
- 服务容错保护: Spring Cloud Hystrix
- 声明式服务调用: Spring Cloud Feign
- API网关服务:Spring Cloud Zuul
- 分布式配置中心: Spring Cloud Config
一、Eureka
现在应用不再是以前一个工程就搞定,而是越来越庞大,所以分布式场景是趋势,自然避免不了远程调用,但在远程调用传统做法是配置服务地址,但在分布式集群的环境下,假设地址变了或者某个配置错误,效率很低。所以才诞生了将所有服务统一注册到服务注册中心Eureka。
Eureka是这样解决上面所说的情况的:
- 创建一个E服务,将A、B、C、D四个服务的信息都注册到E服务上,E服务维护这些已经注册进来的信息
A、B、C、D四个服务都可以拿到Eureka(服务E)那份注册清单。A、B、C、D四个服务互相调用不再通过具体的IP地址,而是通过服务名来调用!
- 拿到注册清单--->注册清单上有服务名--->自然就能够拿到服务具体的位置了(IP)。
- 其实简单来说就是:代码中通过服务名找到对应的IP地址(IP地址会变,但服务名一般不会变)
在Eureka Server一般我们会这样配置:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
Eureka Client分为服务提供者和服务消费者。
- 但很可能,某服务既是服务提供者又是服务消费者。
如果在网上看到SpringCloud的某个服务配置没有"注册"到Eureka-Server也不用过于惊讶(但是它是可以获取Eureka服务清单的)
- 很可能只是作者把该服务认作为单纯的服务消费者,单纯的服务消费者无需对外提供服务,也就无须注册到Eureka中了
eureka:
client:
register-with-eureka: false # 当前微服务不注册到eureka中(消费端)
service-url:
defaultZone:
http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
下面是Eureka的治理机制:
- 服务提供者
- 服务注册:启动的时候会通过发送REST请求的方式将自己注册到Eureka Server上,同时带上了自身服务的一些元数据信息。
- **服务续约:**在注册完服务之后,服务提供者会维护一个心跳用来持续告诉Eureka Server: "我还活着 ” 、
- 服务下线:当服务实例进行正常的关闭操作时,它会触发一个服务下线的REST请求给Eureka Server, 告诉服务注册中心:“我要下线了 ”。
- 服务消费者
- 获取服务:当我们启动服务消费者的时候,它会发送一个REST请求给服务注册中心,来获取上面注册的服务清单
- 服务调用:服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。在进行服务调用的时候,优先访问同处一个Zone中的服务提供方。
- Eureka Server(服务注册中心):
- 失效剔除:默认每隔一段时间(默认为60秒) 将当前清单中超时(默认为90秒)没有续约的服务剔除出去。
- 自我保护:。EurekaServer 在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%(通常由于网络不稳定导致)。 Eureka Server会将当前的实例注册信息保护起来, 让这些实例不会过期,尽可能保护这些注册信息。
二、RestTemplate 和 Ribbon
通过Eureka服务治理框架,我们可以通过服务名来获取具体的服务实例的位置了(IP)。一般在使用SpringCloud的时候不需要自己手动创建HttpClient来进行远程调用。
可以使用Spring封装好的RestTemplate工具类,使用起来很简单:
// 传统的方式,直接显示写死IP是不好的!
//private static final String REST_URL_PREFIX = "http://localhost:8001";
// 服务实例名
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
/**
* 使用 使用restTemplate访问restful接口非常的简单粗暴无脑。 (url, requestMap,
* ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。
*/
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/consumer/dept/add")
public boolean add(Dept dept) {
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class);
}
通过服务名调用接口,ribbon会根据策略在客户端负载均衡。
Ribbon实现客户端负载均衡,与(ngixn || zuul)不同的主要是:
- zuul是对外暴露的唯一接口相当于路由的是controller的请求,而Ribbonhe和Fegin路由了service的请求
- zuul做最外层请求的负载均衡 ,而Ribbon和Fegin做的是系统内部各个微服务的service的调用的负载均衡
三、Hystrix
针对上述问题, Spring Cloud Hystrix实现了熔断器、线程隔离等一系列服务保护功能。
- Fallback(失败快速返回):当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝), 向调用方返回一个错误响应, 而不是长时间的等待。这样就不会使得线程因调用故障服务被长时间占用不释放,避免了故障在分布式系统中的蔓延。
- 资源/依赖隔离(线程池隔离):它会为每一个依赖服务创建一个独立的线程池,这样就算某个依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响, 而不会拖慢其他的依赖服务。
Hystrix提供几个熔断关键参数:滑动窗口大小(20)、 熔断器开关间隔(5s)、错误率(50%)
- 每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。
- 直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。
PS:除此之外,还有Hystrix仪表盘,它主要用来实时监控Hystrix的各项指标信息,启动页面:
四、Feign
上面已经介绍了Ribbon和Hystrix了,可以发现的是:他俩作为基础工具类框架广泛地应用在各个微服务的实现中。我们会发现对这两个框架的使用几乎是同时出现的。
为了简化我们的开发,Spring Cloud Feign出现了!它基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix, 除了整合这两者的强大功能之外,它还提 供了声明式的服务调用(不再通过RestTemplate)。
Feign是怎么实现远程调用的:
// value --->指定调用哪个服务
// fallbackFactory--->熔断器的降级提示
@FeignClient(value = "MICROSERVICECLOUD-DEPT", fallbackFactory = DeptClientServiceFallbackFactory.class)
public interface DeptClientService {
// 采用Feign我们可以使用SpringMVC的注解来对服务进行绑定!
@RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET)
public Dept get(@PathVariable("id") long id);
@RequestMapping(value = "/dept/list", method = RequestMethod.GET)
public List<Dept> list();
@RequestMapping(value = "/dept/add", method = RequestMethod.POST)
public boolean add(Dept dept);
}
Feign中使用熔断器:
/**
* Feign中使用断路器
* 这里主要是处理异常出错的情况(降级/熔断时服务不可用,fallback就会找到这里来)
*/
@Component // 不要忘记添加,不要忘记添加
public class DeptClientServiceFallbackFactory implements FallbackFactory<DeptClientService> {
@Override
public DeptClientService create(Throwable throwable) {
return new DeptClientService() {
@Override
public Dept get(long id) {
return new Dept().setDeptno(id).setDname("该ID:" + id + "没有没有对应的信息,Consumer客户端提供的降级信息,此刻服务Provider已经关闭")
.setDb_source("no this database in MySQL");
}
@Override
public List<Dept> list() {
return null;
}
@Override
public boolean add(Dept dept) {
return false;
}
};
}
}
客户端可以通过DeptClientService.list(),像本地方法的方式直接调用服务提供接口。
五、Zuul
由上面说的搞框架,会有两个比较麻烦的问题:
- 路由规则与服务实例的维护间题:外层的负载均衡(nginx)需要维护所有的服务实例清单(Nginx需要管很多IP)
- 签名校验、 登录校验冗余问题:为了保证对外服务的安全性, 我们在服务端实现的微服务接口,往往都会有一定的权限校验机制,但我们的服务是独立的,我们不得不在这些应用中都实现这样一套校验逻辑,这就会造成校验逻辑的冗余。
为了解决上面这些常见的架构问题,API网关的概念应运而生。在SpringCloud中了提供了基于Netfl ix Zuul实现的API网关组件Spring Cloud Zuul。
Spring Cloud Zuul是这样解决上述两个问题的:
- SpringCloud Zuul通过与SpringCloud Eureka进行整合,将自身注册为Eureka服务治理下的应用,同时从Eureka中获得了所有其他微服务的实例信息。外层调用都必须通过API网关,使得将维护服务实例的工作交给了服务治理框架自动完成。
- 在API网关服务上进行统一调用来对微服务接口做前置过滤,以实现对微服务接口的拦截和校验。
Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。也就是说:Zuul也是支持Hystrix和Ribbon。
关于Zuul还有很多知识点(由于篇幅问题,这里我就不细说了):
- 路由匹配(动态路由)
- 过滤器实现(动态过滤器)
- 默认会过滤掉Cookie与敏感的HTTP头信息(额外配置)
六、SpringCloud Config
Spring Cloud Config项目是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并依据此数据初始化自己的应用。
- 简单来说,使用Spring Cloud Config就是将配置文件放到统一的位置管理(比如GitHub),客户端通过接口去获取这些配置文件。
- 在GitHub上修改了某个配置文件,应用加载的就是修改后的配置文件。
最后感谢知乎好文,本文很大部分都是得益于该文章总结的。