5.1什么是SpringCloud?
(1)SpringCloud是一个微服务系统架构的一站式解决方案。
(2)SpringCloud对微服务基础框架NetFlix的多个开源组件进行了封装,又实现了和SpringBoot开发框架以及云端平台的集成。
(3)SpringCloud为微服务开发涉及的服务注册发现、客户端负载均衡、断路器、服务网关、分布式配置等操作提供了一种简单的开发方式。
(4)SpringCloud就是一个微服务架构全家桶。
5.2什么是微服务?
微服务就是一个分布式系统,它是将单体应用划分成小的服务单元,微服务之间通过http的api进行访问,比如(restTemplate)。每个微服务都独立运行在自己的进程中,独立部署,而且每个微服务都可以使用单独的语言开发以及不同的数据库存储。
优点:
(1)服务的独立部署:每个服务都是一个独立的项目,可以独立部署,不依赖于其他服务,耦合性低;
(2)服务的快速启动:拆分之后服务的启动速度肯定比拆分之前快很多,因为依赖的库少了,代码量也少了。
(3)服务可以动态按需扩容:当某个服务的访问量较大时,我们只需要将这个服务扩容就行了。
(4)服务的数据库独立:分库分表,正因为单体架构的服务都在同一个数据库里面会给数据库造成压力,所以在数据库达到性能瓶颈的时候可以采用分库分表的方式来解决。
(5)更加适合敏捷开发:敏捷开发以用户的需求进化为核心,采用迭代、循序渐进的方式来进行。服务拆分以后可以快速开发新版本,修改哪个服务值需要发布对应的服务即可,不需要整体重新打包发布。
缺点:
(1)微服务之间的调用存在网络问题、延迟开销、带宽、安全等问题。
(2)如果是独立的数据库的话,会存在分布式事务的问题,每个微服务都有自己的数据库。
(3)运维的难度增加:在采用传统的单体应用的时候,运维人员可能只需要关注一个Tomcat的集群、一个Mysql的集群就可以了,但是在这种微服务架构下是行不通的。当业务增加时,服务也越多越多,服务的部署以及监控会变得更加复杂。
5.3说说SpringBoot和SpringCloud的区别?
SpringBoot是一个快速开发的框架,可以快速搭建一个项目(内置Tomcat、一键启动、简化配置)等等。
SpringCloud是一系列微服务框架的集合。SpringBoot能离开SpringCloud单独使用,但SpringCloud离不开SpringBoot。
5.4介绍一下SpringCloud的服务发现框架
Eureka是NetFlix公司开发的服务发现框架,本身是一个基于Rest的服务,SpringCloud将其集成,以实现SpringCloud的服务发现功能。Eureka中包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册的功能,所有的微服务的提供者都必须注册到Eureka Server中;各个服务节点启动后,会在Eureka Server中进行注册,这样Eureka Server的服务注册表单中将会存储所有可用服务节点的信息,服务节点的信息可用在可视化界面中直观看到。
Eureka Client是一个java客户端,用于简单与Eureka Server的交互。
服务的提供者:
(1)服务注册(register):启动后,向注册中心发起register请求,注册服务,它提供自身的元数据,比如IP地址、端口、serverId(服务名称)等信息;
(2)服务租约(renew):Eureka Client会每隔30s(默认情况下)发送一次心跳来续约,通过续约来告知Eureka Server该Client依然存在,没有出现问。正常情况下,如果Eureka Server在90s内没有收到Eureka客户的续约,它将会该服务实例从注册表中剔除。
(3)服务下线(cancel):Eureka客户端在程序关闭时向Eureka服务器发送取消请求。发送该请求后,该客户端的实例信息将从服务器的实例注册表中删除。
服务的消费者:
(1)(Fetch Registries)启动后,从注册中心拉取服务注册信息,并将它缓存到本地。消费者会使用该信息查找其他服务,进行远程调用。注册中心的服务列表定期(每隔30s)更新一次。每次返回的注册列表信息可能与消费者的缓存信息不同。如果由于墨中原因导致注册列表信息无能及时匹配,Eureka的消费者会重新获取整个注册表的信息并更新到本地列表。
【Eureka的自我保护机制】
Eureka Server在运行期间会去统计心跳续约的比例。例如在15分钟之内是否低于85%,如果低于85%,Eureka Server会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果刚好这个服务提供者非正常下线了的话,此时服务消费者就会拿到一个无效的服务实例,此时就会调用失败,对于这个问题就需要服务的消费者有一些容错机制,比如重试:断路器等。
意义:Eureka Server不会从注册列表中剔除因长时间没收到心跳导致租约过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式的目的是避免网络分区故障导致服务不可用的问题。比如:两个客户端实例C1和C2的连通性是良好的,但是由于网络故障,C2未能及时向Eureka发送心跳续约,这时候EurekaServer不能简单地将C2从注册表中剔除。因为如果剔除了,C1就无法从Eureka服务器中获取C2注册的服务,但这时候C2的服务是可用的。简单来说:就是为了高可用,避免因为网络抖动的原因,导致服务提供者莫名其妙得被下线。
【Eureka和Zookeeper、Nacos的区别】
(1)Zookeeper
Zookeeper保证的是CAP理论中的一致性和分区容错性。任何时刻对Zookeeper的读请求都能得到一致性的结果。但是Zookeeper不能保证每次请求的可用性。打个比方:在Leader选举过程中或者半数以上的机器不可用的时候服务就是不可用的。
(2)Eureka
Eureka保证的则是可用性和分区容错性。Eureka在设计的时候就是优先保证可用性,在Eureka中不存在Leader节点、Slave节点,每个节点的地位都是平等的。因此Eureka不会像Zookeeper那样出现新的Leader节点选举过程或者半数以上机器不可用就是服务不可用的情况。Eureka保证即使大部分节点挂掉也不会影响正常提供服务注册和拉取服务的功能,只要有一个节点是可用的就行了。只不过这个节点上的数据可能并不是最新的,因为毕竟注册中心的服务列表默认每隔30s才更新一次嘛,而且注册表的信息也有可能与消费者端缓存的本地信息不同。
(3)Nacos
Nacos不仅支持CP也能支持AP,取决于架构师针对整个分布式系统的选择。
5.5介绍一下Ribbon组件?
Ribbon是Netflix公司的一个开源的负载均衡项目,是一个运行在消费者端的负载均衡器,它的工作原理就是消费者端获取到了所有的服务列表之后,在其内部使用负载均衡算法,进行多个系统的调用。当某个服务的访问量较大时,我们可以将这个服务进行按需的动态扩容,来组成一个微服务集群,以达到整个系统的高可用。打个比方,如果在商城项目中的商品秒杀微服务被调用的次数太多,我们就可以为这个秒杀微服务搭建一个集群,order1、order2、order3组成一个集群,当然这时候如果没有一些负载均衡的策略,请求一直打到某一个微服务上,那这个微服务很快就会崩溃,就体现不了系统的高可用。所以就需要一些负载均衡的策略。
负载均衡的意义:就是优化资源使用,以获得最大吞吐量、最低响应延迟并且避免单一资源的过载。简单来说就是,让多台服务器并发处理请求,并快速响应,避免单台服务器压力太大而挂掉。
【Nginx与Ribbon的对比】
Nginx是服务器端的负载均衡器,所有请求发送到nginx之后,再由nginx通过反向代理的功能以及负载均衡算法分发到不同的服务器上。
而ribbon是一种工作在客户端消费者端的负载均衡器。他是通过将Eureka注册中心上的服务拉取出来,缓存在本地,在本地通过负载均衡算法,进行微服务的调用。
【Ribbon的几种负载均衡算法】
负载均衡,不管是Nginx还是Ribbon都是需要算法支持的,Nginx使用的是轮询和加权轮询的算法。而在Ribbon中有更多的负载均衡调度算法,默认使用的是轮询策略。
轮询策略:把所有的provider看做是一个一个的节点,把这些节点组成一个圆,按照顺时针选择一个provider进行调用。
随机策略:从可用的provider中随机选择一个进行调用。
重试策略:先按照轮询策略获取provider,如果获取失败,则在指定的时限内重试,默认的时限为500ms。
另外,在Ribbon中也可以自定义负载均衡算法,只需要实现IRule接口,然后修改配置文件即可。
5.6介绍一下什么是Open Feign?
Feign是SpringCloud组件中的一个轻量级Rest Ful的http服务客户端。
RestTemplate是Spring提供的一个访问Http服务的客户端类,就是说微服务之间的调用使用的是RestTemplate。
@Autowired
private RestTemplate restTemplate;
// 这里是提供者A的ip地址,但是如果使用了 Eureka 那么就应该是提供者A的名称
private static final String SERVICE_PROVIDER_A = "http://localhost:8081";
@PostMapping("/judge")
public boolean judge(@RequestBody Request request) {
String url = SERVICE_PROVIDER_A + "/service1";
// 是不是太麻烦了???每次都要 url、请求、返回类型的
return restTemplate.postForObject(url, request, Boolean.class);
}
有了Eureka,RestTemplate和Ribbon我们就可以进行服务间的调用了,但是使用RestTemplate这样调用属实是不太方便的,因为每次都需要写URL地址和调用RestTemplate的API。但如果有Open Feign这个组件,我们就可以像本地调用一样进行微服务的调用了。Feign的中文意思就是伪装,进行伪装调用。
使用方式就是通过使用FeignClient的注解+服务注册中心中的服务名。然后通过消费者需要调用提供者的请求路径,请求方式,请求参数进行映射,然后定义接口,调用这个接口,就可以调用服务注册中心的服务了。
// 使用 @FeignClient 注解来指定提供者的名字
@FeignClient(value = "eureka-client-provider")
public interface TestClient {
// 这里一定要注意需要使用的是提供者那端的请求相对路径,这里就相当于映射了
@RequestMapping(value = "/provider/xxx",
method = RequestMethod.POST)
CommonResponse<List<Plan>> getPlans(@RequestBody planGetRequest request);
}
@RestController
public class TestController {
// 这里就相当于原来自动注入的 Service
@Autowired
private TestClient testClient;
// controller 调用 service 层代码
@RequestMapping(value = "/test", method = RequestMethod.POST)
public CommonResponse<List<Plan>> get(@RequestBody planGetRequest request) {
return testClient.getPlans(request);
}
}
就好像在本地Controller层调用Service层一样了。
5.7说说SpringCloud中的组件Hystrix
在分布式系统中,一个请求进来可能会经过许多个服务,这些服务共同构成一条完整的链路,每个经过的微服务称为这个链路上的一个节点。在这个链路环节中,如果有一个节点因为网络问题、网络抖动、异常甚至宕机,其他链路上的节点也会受到影响,随着流量越来越多,整个系统的资源就会被占用得越多越多,进而导致整个系统的异常、崩溃、瘫痪,这也称为微服务的雪崩效应。为了解决这个问题,我们就可以把出现问题的这个服务节点给隔离起来。而hystrix就是一个容错组件,实现了超时机制和断路器模式,它可以提高整个系统的容错率。
简单来说,hystrix就是一个保险丝,当下游服务不可用时,快速返回失败或者进行降级处理,而不是傻傻得等待响应。
在这个留言板项目中,我也使用了hystrix的断路器功能,首先article服务在显示文章详情页面的时候是为调用user服务的,因为要在帖子的详情页面去显示用户的头像以及昵称。我配置了服务提供者熔断和服务调用者熔断,首先在服务提供者中,被调用的方法通过声明@HystrixCommand注解,就表示就当前方法添加了一个熔断的机制,并且可以在里面可以通过声明另外一个方法也就是降级替补的方法来提供给调用方来使用,降级的方法就可以直接返回空的user对象集合,这样其实也是可行的,因为这个文章页面用户一般关心的是文章的内容,不会去关心用户的头像昵称等等信息。只要这个方法异常了,就会进入到降级的方法里面。
此外,除了配置服务提供者的熔断和降级机制之外可能还不行,如果是被调用的方法有异常就会启用降级预案,但是如果是单个user微服务或者是整个user微服务集群都宕机了,那么用户在访问文章详情页面的时候还是会返回500的信息,这个是用户不能接受的,所以还需要在服务的调用者配置熔断和降级预案。服务的调用方配置hystrix断路器是可以通过feign来做的,就是在我们注入的controller接口中,FallBackFactory,通过重写调用的接口中的方法来实现熔断和降级的效果。
断路器的工作原理:
跳闸机制:一开始断路器是处于一个关闭的状态,用户请求接口是可以正常访问的,但是一部分请求如果失败了并且超过了一定的阈值,比如说有10个请求,有5个以上的请求失败了,那么断路器就会自动开启。
回退机制(快速失败、保护资源):如果断路器处于开启状态,不管请求是可以正常访问还是异常都会返回一个降级的预案,全局的降级方法也可以通过配置来实现,比如返回一个当前系统繁忙,请稍微再试的消息给用户。
自我恢复机制:在断路器开启之后的一段时间间隔后,断路器会处于一个半开的状态,断路器会释放一个请求过来去访问这个接口,如果这个接口是能够正常访问的,那请求就会放行;如果检测成功的话,就会关闭断路器,如果检测失败的话,断路器还是会再次打开,开启降级处理。
5.8说说SpringCloud中的组件Zuul
Zuul是SpringCloud中的微服务网关,它是所有流量,所有请求的入口,介于用户端与服务器端之间,用于对用户请求进行鉴权、限流、路由、监控等功能。
动态路由映射就是根据需要将请求路由分发到不同的后端集群处。具体就是
1.客户端发送请求,会到达网关的DispatcherHandler处理,匹配到RoutePredicateHandlerMapping。
2.根据RoutePredicateHandlerMapping匹配到具体的路由策略。
3.FilteringWebHandler获取路由的GatewayFilter数组,创建GatewayFilterChain处理过滤请求。
4.通过匹配路由规则,就是微服务的id就可以实现动态的路由转发,然后就可以执行我们的代理业务逻辑访问。
在微服务架构中,后端服务往往不直接开放给调用端,而是通过一个API网关根据请求的url,路由到相应的服务。当添加API网关后,在第三方调用端和服务提供方之间就创建了一面墙,这面墙直接与调用方通信进行权限控制,后将请求均衡分发给后台服务端。
另外zuul还有过滤器的功能,我使用这个功能实现了一个ip黑名单访问限制的功能,这个功能需要配合redis来共同实现。过滤器的业务实现:如果一个客户端的ip访问次数在10s内达到了1000次,我们就需要限制这个ip的访问时间,比如半小时后这个用户才能继续发送请求。所以需要在redis中设置两个值,并且附带有效时间,setnx。redisVisCount和redisLimitRestTime。
获取请求上下文对象,然后从请求中就获取经过网关的ip地址,我们可以把使用redis的incr命令将这个ip的访问次数+1,如果是初次访问会返回requestCount数值为1,然后我们就可以使用expire命令来设置访问的允许时间。如果在这段允许时间内,当前用户ip的请求次数达到了10次,就将当前用户ip地址存储到redis中,设置有效时间为半小时,并返回错误信息,停止继续理由。如果在这段限制访问的时间内,用户经过继续到达网关层,就通过获取redis的ttl可以获得当前用户ip的解除限制时间,如果当前限制ip的key还存在剩余时间,说明这个ip不能访问,需要继续等待,停止zuul继续向下路由,禁止请求通信并返回当前系统繁忙等类似的提示信息返回给客户端。
5.9说说SpringCloud的配置管理
当我们的微服务系统开始慢慢庞大起来,那么多的Consumer、Provider、EurekaServer、Zuul系统都会持有自己我配置,这个时候我们在项目运行的时候可能会需要更改某些应用的配置,如果我们不进行配置的统一管理,那就只能去每个应用的寻找配置文件然后修改再重启应用。
而Spring Cloud Config就是能将各个应用/系统/模块的配置文件存放到统一的地方然后进行管理,比如Git或者SVN。我们的应用程序只有在启动的时候才会进行配置文件的加载,那就可以使用Spring Cloud Config暴露的接口给应用程序来拉取它想要的配置,应用获取到配置文件后再进行它的初始化工作。Config Server可以向git直接上传配置文件,在Config Client需要的时候,直接向Config Server发起请求,这时候Config Server就可以从git上拉取配置文件,并返回给Client。