从分布式/微服务的角度而言,就是把我们一个大的项目分解成多个小的模块,这些小的模块组合起来,完成功能;而拆分出多个模块以后,就会出现各种各样的问题,而Spring Cloud提供了一整套的解决方案。
Spring cloud是一个基于Spring boot实现的云原生应用开发工具,它为基于JVM的云原生应用开发中涉及的配置管理、服务发现、熔断器、智能路由、微代理、控制总线、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
Spring Cloud的基础功能(核心组件):
(1)、服务治理:Spring cloud Eureka
(2)、客户端负载均衡:Spring cloud Ribbon
(3)、服务容错保护:SPring cloud Hystrix
(4)、声明式服务调用:Spring cloud Feign
(5)、API网关服务:Spring cloud Zuul
(6)、分布式配置中心:Spring cloud Config
(7)、消息总线:Spring cloud Bus
(8)、消息驱动的微服务:Spring cloud Stream
(9)、分布式服务跟踪:Spring cloud Sleuth
在服务众多的情况下,手动维护各服务的静态配置简直就是一个噩梦;为了解决微服务架构中的服务实例问题(IP地址),产生了 大量的服务治理框架和产品;这些框架和产品的实现都围绕着服务注册与服务发现机制来完成对微服务应用实例的自动化管理;而在Spring cloud中我们的服务治理框架一般使用的就是Eurake。
现在有A、B、C、D四个服务,它们之间会互相调用(而且IP地址很可能会发生变化),一旦某个服务的IP地址变了,那服务中的代码要跟着变,手动维护这些静态配置(IP)非常麻烦!
Eureka是这样解决:
创建一个E服务,将A、B、C、D四个服务的信息都注册到E服务上,E服务维护这些已经注册进来的信息
A、B、C、D四个服务都可以拿到Eureka(服务E)那份注册清单。A、B、C、D四个服务互相调用不再通过具体的IP地址,而是通过服务名来调用!
- 拿到注册清单--->注册清单上有服务名--->自然就能够拿到服务具体的位置了(IP)。
- 其实简单来说就是:代码中通过服务名找到对应的IP地址(IP地址会变,但服务名一般不会变)
Eureka专门用于给其他服务注册的称为Eureka Server(服务注册中心),其余注册到Eureka Server的服务称为Eureka Client。
在Eureka Server一般我们会这样配置:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
Eureka Client分为服务提供者和服务消费者。
如果在网上看到SpringCloud的某个服务配置没有"注册"到Eureka-Server也不用过于惊讶(但是它是可以获取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的治理机制:
参考文章:
通过Eureka服务治理框架,我们可以通过服务名来获取具体的服务实例的位置了(IP)。一般在使用SpringCloud的时候可以使用Spring封装好的RestTemplate工具类,不需要自己手动创建HttpClient来进行远程调用。
// 传统的方式,直接显示写死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);
}
为了实现服务的高可用,我们可以将服务提供者集群。比如说,现在一个秒杀系统设计出来了,准备上线了。在11月11号时为了能够支持高并发,我们开多台机器来支持并发量。
现在想要这三个秒杀系统合理摊分用户的请求(专业来说就是负载均衡),可能你会想到nginx。
其实SpringCloud也支持的负载均衡功能,只不过它是客户端的负载均衡,这个功能实现就是Ribbon!
负载均衡又区分了两种类型:
Ribbon是支持负载均衡,默认的负载均衡策略是轮询,我们也是可以根据自己实际的需求自定义负载均衡策略的。
@Configuration
public class MySelfRule
{
@Bean
public IRule myRule()
{
//return new RandomRule();// Ribbon默认是轮询,我自定义为随机
//return new RoundRobinRule();// Ribbon默认是轮询,我自定义为随机
return new RandomRule_ZY();// 我自定义为每台机器5次
}
}
实现起来也很简单:继承AbstractLoadBalancerRule类,重写public Server choose(ILoadBalancer lb, Object key)
即可。
SpringCloud 在CAP理论是选择了AP的,在Ribbon中还可以配置重试机制的
参考文章:
到目前为止,我们的服务看起来好像挺好的了:能够根据服务名来远程调用其他的服务,可以实现客户端的负载均衡。
但是,如果我们在调用多个远程服务时,某个服务出现延迟,会怎么样?
在高并发的情况下,由于单个服务的延迟,可能导致所有的请求都处于延迟状态,甚至在几秒钟就使服务处于负载饱和的状态,资源耗尽,直到不可用,最终导致这个分布式系统都不可用,这就是“雪崩”。
针对上述问题, Spring Cloud Hystrix实现了断路器、线程隔离等一系列服务保护功能。
Hystrix提供几个熔断关键参数:滑动窗口大小(20)、熔断器开关间隔(5s)、错误率(50%)
Hystrix仪表盘:它主要用来实时监控Hystrix的各项指标信息。通过Hystrix Dashboard反馈的实时信息,可以帮助我们快速发现系统中存在的问题,从而及时地采取应对措施。
启动时的页面:
监控单服务的页面:
我们现在的服务(监控单服务)是这样的:
除了可以开启单个实例的监控页面之外,还有一个监控端点 /turbine.stream
是对集群使用的。 从端点的命名中,可以引入Turbine, 通过它来汇集监控信息,并将聚合后的信息提供给 HystrixDashboard 来集中展示和监控。
参考文章:
上面已经介绍了Ribbon和Hystrix了,可以发现的是:他俩作为基础工具类框架广泛地应用在各个微服务的实现中。我们会发现对这两个框架的使用几乎是同时出现的。
为了简化我们的开发,Spring Cloud Feign出现了!它基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix, 除了整合这两者的强大功能之外,它还提供了声明式的服务调用(不再通过RestTemplate)。
Feign是一种声明式、模板化的HTTP客户端。在Spring Cloud中使用Feign, 我们可以做到使用HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个HTTP请求。
服务绑定:
// 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 list();
@RequestMapping(value = "/dept/add", method = RequestMethod.POST)
public boolean add(Dept dept);
}
Feign中使用熔断器:
/**
* Feign中使用断路器
* 这里主要是处理异常出错的情况(降级/熔断时服务不可用,fallback就会找到这里来)
*/
@Component // 不要忘记添加,不要忘记添加
public class DeptClientServiceFallbackFactory implements FallbackFactory {
@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 list() {
return null;
}
@Override
public boolean add(Dept dept) {
return false;
}
};
}
}
调用:
基于上面的学习,我们现在的架构很可能会设计成这样:
这样的架构会有两个比较麻烦的问题:
每个服务都有自己的IP地址,Nginx想要正确请求转发到服务上,就必须维护着每个服务实例的地址!
http://123.123.123.123
http://123.123.123.124
http://123.123.123.125
http://123.123.123.126
http://123.123.123.127
购物车和订单模块都需要用户登录了才可以正常访问,基于现在的架构,只能在购物车和订单模块都编写校验逻辑,这无疑是冗余的代码。
为了解决上面这些常见的架构问题,API网关的概念应运而生。在SpringCloud中了提供了基于Netfl ix Zuul实现的API网关组件Spring Cloud Zuul。
Spring Cloud Zuul是这样解决上述两个问题的:
Zuul天生就拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。也就是说:Zuul也是支持Hystrix和Ribbon。
关于Zuul还有很多知识点:
Zuul支持Ribbon和Hystrix,也能够实现客户端的负载均衡。我们的Feign不也是实现客户端的负载均衡和Hystrix的吗?既然Zuul已经能够实现了,那我们的Feign还有必要吗?
或者可以这样理解:
有了Zuul,还需要Nginx吗?他俩可以一起使用吗?
参考文章:
随着业务的扩展,我们的服务会越来越多,越来越多。每个服务都有自己的配置文件。
既然是配置文件,给我们配置的东西,那难免会有些改动的。
比如我们的Demo中,每个服务都写上相同的配置文件。万一我们有一天,配置文件中的密码需要更换了,那就得三个都要重新更改。
注意:在分布式系统中,某一个基础服务信息变更,都很可能会引起一系列的更新和重启
Spring Cloud Config项目是一个解决分布式系统的配置管理方案。它包含了Client和Server两个部分,server提供配置文件的存储、以接口的形式将配置文件的内容提供出去,client通过接口获取数据、并依据此数据初始化自己的应用。
SpringCloud Config其他的知识:
其他文章:Spring Cloud 微服务架构学习笔记与示例