什么是服务治理
1、基于云的服务治理优点
- 更高的可用性:服务治理可以支持动态的服务实例集群环境,任何服务实例可以随时上线或下线。服务消费者只需要知道服务名称就可以调用相应的服务,而不需要具体的物理地址。服务提供者的实例信息由服务治理服务器进行统一管理,当一个服务实例不可用时,治理服务器可以将请求转给其他服务提供者,当一个新的服务实例上线时,也能够快速地分担服务调用请求。
- 负载均衡:服务治理可以提供动态的负载均衡功能,可以将所有请求动态地分布到其所管理的所有服务实例中进行处理。
- 提升应用的弹性:服务治理的客户端会定时从服务治理服务器中复制一份实例信息缓存到本地中,这样即使党发服务治理服务器不可用时,服务消费者也可以使用本地的缓存去访问相应的服务,而不至于中断服务。通过这种机制,极大地提高了应用的弹性。
- 高可用集群:可以构建服务治理集群,通过互相注册机制,将每个治理服务器所管辖的服务信息列表进行交换,使服务治理服务拥有更高的可用性。
2、Eureka中服务治理
- 服务治理服务器(Eureka服务器):服务注册中心,负责服务列表的注册、维护和查询等功能,也就是Eureka服务器。
- 服务注册代理(服务提供者):如果一个微服务是一个服务提供者,那么可以通过服务注册代理将服务配置信息注册到治理服务器上。服务注册代码可以理解为一个Eureka客户端,负责将微服务所提供的服务向Eureka服务器执行注册、续约和注销等操作,以便服务消费者可以发现并进行消费。在服务注册时需要向服务治理服务器提供服务名称、宿主服务器IP地址、服务端口号、域名等主要数据
服务发现客户端(服务消费者):也是一个Eureka客户端。它在启动时会默认从所服务治理服务器中获取所有的服务注册表信息,通过所获取到的服务注册列表信息来消费相应的服务。
构建服务治理——Eureka
1、搭建微服务Parent工程
Maven通过继承,可以在父pom.xml文件中对所使用的Spring Cloud及第三方依赖的版本进行统一管理。
构建微服务Parent工程,仅用来定义pom.xml文件,后续所开发的微服务中的pom.xml文件皆继承自该pom.xml文件。
packing配置设置为pom,仅仅是POM类型的项目,编译后可以作为其他项目的parent父项目。
注意:Spring Cloud的版本与Spring Boot的版本一定要对应上,不然会编译不通过。
2、搭建服务治理服务器——Eureka服务器
编写的pom.xml文件要继承上面的Parent项目,增加spring-cloud-starter-netflix-eureka-server依赖包。
注意:很多老的博客或者书中,用的是spring-cloud-starter-eureka-server依赖包,是废弃的。特别对于Spring Boot2.0以上的版本是无法使用的。
修改Spring Boot的应用引导类,在应用引导类中增加服务治理服务器的注解。
通过@EnableEurekaServer注解,Spring Boot在启动应用的时候就会自动构建一个默认的服务治理服务器。
增加Eureka相应的配置
eureka.client.register-with-eureka:用来控制当Spring Boot启动服务完成后是否将该服务注册到服务治理服务器上。因为该服务本身就是服务治理服务器,而且未构建服务治理集群,所以设置为false,表示不注册。
eureka.client.fetch-registry:应用启动后不需要从服务治理服务器上同步已注册的服务注册列表数据到本地。
eureka.server.wait-time-in-ms-when-sync-empty:在Eureka服务器获取不到集群里对等服务器上的实例时,需要等待的时间,单位为毫秒
启动项目,访问http://localhost:8260/可以查看Eureka的控制台
3、搭建服务提供者——注册服务
pom文件增加spring-cloud-starter-netflix-eureka-client依赖包,使用户微服务具有服务注册代码的肺功能。
引导类中增加@EnableDiscoveryClient,表示这是一个Eureka客户端,通过该注解,在Spring Boot启动完毕之后,就会根据配置中的信息尝试与服务治理服务器进行连接,连接成功之后进行服务注册或服务注册信息的同步。
项目的配置文件
eureka服务器的地址则是通过eureka.client.servicef-url.defaultZone属性设置。
一个服务实例注册到Eureka服务器时需要30s才能够在控制台中查看该服务。这是因为,Eureka要求服务提供者必须发送3次心跳才认为该服务实例是ok的。
说明USER-SERVICE服务已经在服务治理服务器中注册成功。
4、搭建服务消费者——获取服务
必须将eureka.client.fetch-registry的值设置为true,表示应用启动成功之后需要从服务治理服务器中获取已注册的服务列表到本地。
SpringBoot版本大于1.4 Spring Boot不在自动定义RestTemplate,而是定义了一个RestTemplateBuilder。所以创建RestTempateConfig自动扫描注入RestTempate Bean。
@LoadBalanced是必须要加的,不然会提示下面的错误
Controller代码示例
使用客户端负载均衡——Ribbon
在微服务架构出现之前,负载均衡的方案主要是集中式负载均衡,在服务消费者和服务提供者之间有一个独立的负载均衡系统,如F5、HAproxy、Nginx反向代理。
负载均衡器上有所有服务的地址映射表,当服务消费者调用某个目标服务时,先向负载均衡系统发起请求,由负载均衡系统以某种策略(如Round-Robin)做负载均衡后再将请求转发给目标服务。
集中式负载均衡的缺点:单点失败:一旦负载均衡宕机,整个应用将无法访问。(如果可以做集群提高其高可用性)
- 难扩展:人工介入
- 复杂:传统集中式负载均衡就是一个代理分发角色,将用户的请求映射到具体的物理服务器上,甚至有些需要做一些处理或者过滤。
1、什么是客户端负载均衡
- 集中式负载均衡方案
与传统的单体架构负载均衡实现原理相同。
- 进程内负载均衡方案
该方案将负载均衡处理功能以库的方式整合到服务消费者应用中,也称为客户端负载均衡方案。
需要配合服务发现功能,在服务消费者启动时需要从服务发现服务器中获取所有服务注册信息,并定时同步这些信息。当服务消费者需要访问某个服务时,内置的负载均衡器就会以某种负载均衡策略选择一个目标服务实例,然后在本地所缓存的服务注册表信息中查询该目标服务的具体地址,然后向目标服务发起请求。
- 主机独立负载均衡方案
与进程内负载均衡方案类似,不同之处在于将负载均衡和服务发现功能从服务消费者的进程内移出来,变成同一个主机上的独立进程,为该主机上的一个或多个服务消费者提供负载均衡处理。
2、启用Ribbon
我们对上一章的项目进行改造。
对于现在的spring-cloud-starter-netflix-eureka-client依赖包,里面已经依赖了spring-cloud-starter-netflix-ribbon,所以不需要再增加ribbon依赖。
并且项目中在创建RestTempate Bean时,必须加入@LoadBalanced注解。
Ribbon为Rest客户端的实现增加了客户端负载均衡公共能,其实现原理如下:
- Ribbon首先根据其所在Zone优先选择一个负载较少的Eureka服务器。
- 定期从Eureka服务器更新,并过滤服务实例列表
- 根据指定的负载均衡策略,从可用的服务实例列表中选择一个。
- 然后使用该地址,通过RestTemplate进行服务调用。
默认情况下,客户端负载均衡采用了轮询策略(RoundRobinRule)。
3、负载均衡测试
依次启动Eureka服务、两个用户微服务实例和商品微服务。两个微服务实例端口分别为8000和8001。
接下来,我们进入测试
使用Feign简化微服务调用
Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口并插入注解,就可以完成HTTP请求的参数、格式、地址等信息的声明。Feign整合了Ribbon和Hystrix。
Fegin特性:
- 可插拔的注解支持,包括Fegin注解和JAX-RS注解。
- 支持插拔的HTTP编码器和解码器。
- 支持Hystrix和它的回退功能
- 支持Ribbon的负载均衡
- 支持HTTP请求和相应的压缩处理。
我们继续对之前的项目进行改造。
同理我们在product项目pom文件中加入对feign的依赖包
对引导类增加@EnableFeignClients注解,用来开启Feigin相关功能。
增加UserService文件,应用启动时Feign就会使用动态代码机制根据我们所定义的用户服务接口生成相应的类实例,并注入到Spring的应用上下文中。在使用方式上,和普通Bean一样。
最后controller代码修改,将RestTemplate访问直接改成userService就可以。
深入Eureka
1、服务注册及相关原理
分布式领域CAP定理(布鲁尔定理)
- 一致性(Consistency):同一个数据在集群中的所有结点,同一时间的值是否相同。
- 可用性(Aavilability):集群中一部分节点故障后,集群整体是否正常处理请求。
- 分区容忍性(Partition tolerance):是否允许集群汇中的节点之间无法通信。
在任何分布式系统中都不可能同时满足,最多同时满足其中两个,比如涉及数据存储的场景,数据一致性是要必须的。如Zookeeper就是采用CP原则,任何时刻对Zookeeper的访问请求都能得到一致的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务请求的可用性。
对于微服务的治理来说,其核心主要就是服务的注册和发现,可用性比数据一致性更重要。因此Eureka的设计原则采用的是AP原则。
服务注册(Register)
启动时,会调用Eureka锁提供的服务注册相关方法,向Eureka服务器注册自己的信息。同时,在Eureka服务器会维护一个已注册的服务列表。
服务列表数据结构是一个HashMap,第一层为应用名称和对应的服务实例;第二层是服务实例机器对应的注册信息,包括宿主服务IP地址、服务端口、运行状况指示符、URL等数据。
当服务实例状态发生变化时,就会想Eureka服务器更新自己的服务状态,同时用replicateToPeers()向其他Eureka服务器节点做状态同步。
当我们在配置文件中将eureka.client.register-with-eureka属性配置为false时,则不进行注册。
服务续约(Renew)
当服务启动并成功注册到Eureka服务器后,Eureka客户端会默认以每30s的频率向Eureka服务器发送心跳,也可以修改配置文件中的eureka.instance.lease-renewal-interval-in-seconds修改属性。
发送心跳的目的就是执行服务续约操作,避免自己的注册信息被Eureka服务器剔除。续约的处理逻辑和服务注册逻辑基本一致:更新自身状态,然后同步到其他Eureka服务器节点。
对于Eureka服务器来说,在默认的时间内(90s),连续3次没有收到客户端的心跳,则会将该服务实例从所维护的服务注册表中剔除,禁止流向该实例的流量,eureka.instance.lease-expiration-duration-in-seconds。
如果当Eureka服务器处于自我保护模式,则不会清楚该服务实例信息。
服务下线与剔除
当服务实例关闭时,服务实例会先向Eureka服务器发送服务下线请求。该服务实例信息将从Eureka服务器的实例注册表中删除。
获取服务
Eureka客户端在启动时会从Eureka服务器中获取注册表信息,并缓存在本地。Eureka客户端会使用该信息查找相应的服务,进行调用。该注册列表信息每30s从Eureka服务器进行同步。
2、Eureka自我保护模式
在自我保护模式下,Eureka服务器会保护服务注册表中的信息,不会注销任何服务实例。当开启自我保护模式时,会以红色字体显示以下告警信息。
Eureka服务器自我保护模式开启的条件是:当Eureka服务器每分钟收到的心跳续约数量低于一个阈值,就会触发。当收到的心跳数恢复到阈值以上,则会自动退出自我保护模式。
可以通过eureka.server.enable_selft_preservation=false禁用。
3、注册一个服务实例需要的时间
三处缓存处理和一处延迟处理:
- Eureka服务器对服务注册列表进行缓存,默认时间为30s。
- Eureka客户端对注册的服务信息进行缓存,默认时间为30s。
- Ribbon负载均衡会从Eureka客户端获取服务列表,并将负载均衡后的结果缓存30s。
- 服务实例在启动时,不是立即向Eureka服务器注册。而是在一个延迟时间(默认40s)之后才向Eureka服务器注册。
4、Eureka高可用集群及示例
对于Eureka服务器可以通过运行多个实例来构建集群,解决单点问题,提高可用率。由于Eureka服务器采用的是Peer to Peer对等通信,一种去中心化的架构,没有master和slave之分,每个节点都是对等的。
实现Eureka服务高可用,必须让Eureka服务器之间能够互相复制、同步所注册服务的实例信息。
5、多网卡及IP指定
eureka.instance.prefer-ip-address=true,设置服务在注册时使用IP地址注册,而不是主机名称。服务消费者就会通过IP地址来调用相关的服务,而不是主机名称。(单机或Docker部署情况下)
eureka.instance.ip-address=127.0.0.1,手动指定IP。
6、Eureka服务访问安全
增加Eureka服务器用户认证,增加对应的pom依赖包。
无需任何配置,SpringSecurity已经帮你进行了简单的配置,你重新运行项目的时候会发现在控制台有一串随机的密码。
当再次访问的Eureka注册界面的时候。
默认用户名是user,密码则是在启动时,随机生成的密码。
深入Ribbon
1、Ribbon客户端负载均衡原理
对于一个客户端负载均衡实现方案,主要核心三个步骤:
- 服务发现:能够自动发现所依赖服务的列表
- 服务监听:能够监测到失败的服务,并高效地将失败服务从服务列表中移除
- 负载均衡策略:能够决定如何在多个服务实例中选择一个有效的服务实例,并进行相应的服务请求处理。
Ribbon在具体实现上,有以下组件:
服务器列表(ServerList)
服务器列表就是客户端负载均衡所使用的各服务的服务实例列表。Ribbon在实现上支持3中服务列表方式
- 静态服务器列表:通过Ribbon的BaseLoadBalancer所提供的setServerList()方法直接进行设置。
- 基于配置的服务器列表:需要在项目配置文件中通过<服务名称>.ribbon.listOfServers进行设置。(如user-service.ribbon.listOfServers=http://127.0.0.1:8000,http://127.0.0.1:8001)
- 基于服务发现的服务器列表:同时使用Ribbon和Eureka时,默认使用该方式,在应用启动时Ribbon就会从Eureka服务器中获取所有注册服务的列表数据,并保持同步。
服务器列表过滤(ServerListFilter)
该组件会对原始服务列表使用一定策略进行过滤,并返回有效的服务器列表给客户端负载均衡器使用。
- ZoneAffinityServerListFilter:基于区域感知的方式,实现对服务实例的过滤,仅返回与本身所处区域一直的服务提供者实例列表。
- ServerListSubsetFilter:该过滤器继承自ZoneAffinityServerListFilter,在进行区域感知过滤后,仅返回一个固定大小的服务列表。默认将返回20个服务实例,可以通过ribbon.ServerListSubsetFilter.size进行设置。
- ZonePreferenceServerListFilter:使用Eureka和Ribbon时默认的过滤器。实现通过配置或者Eureka所属区域来过滤出同区域的服务实例列表。
服务实例存活探测(IPing)
用来检测一个微服务实例是否有相应。Ribbon通过该组件来判断所持有的服务实例列表中各服务可用情况,如果检测到某服务实例不存在,则会从列表中及时移除。
- PingUrl:通过定期访问指定的URL判断
- PingConstant:不做任何处理,只返回一个固定值,用来表示该服务是否可用,默认值为true。
- NoOpPing:不做任何处理,直接返回true,表示该服务器可用,默认策略。
- DummyPing:直接返回true,但实现了initWithNiwsConfig方法。
- NIWSDiscoverPing:根据DiscoveryEnabledServer中InstanceInfo的InstanceStatus属性判断,如果该属性的值为InstanceStatus.UP,则表示服务器可用。
负载均衡策略(IRule)
负责选择一个最终服务实例地址作为负载均衡处理结果。Ribbon提供的选择策略有轮询、根据相应时间加权、断路器(当Hystrix可用时)等。
负载均衡器(ILoadBalancer)
Ribbon负载均衡主要是通过LoadBalancerClient类实现的,而LoadBalancerClient又将具体处理委托给ILoadBalancer处理。
ILoadBalancer通过配置IRule、IPing等信息,并通过ServerList获取服务器注册列表的信息,默认以每10s的频率想服务列表中每个服务实例发送ping请求,检测服务实例是否存活,最后使用负责均衡策略对ServerListFilter过滤得到最终可用的服务实例列表进行处理,并获取到最终要调用的服务实例,然后交给服务调用器进行调用。
ILoadBalance也是一个接口,提供了3个具体实现,分别是DynamicServerListLoadBalancer、ZoneAwareLoadBalancer和NoOpLoadBalancer。
DynamicServerListLoadBalancer继承自ILoadBalancer基础实现BaseLoadBalancer,在基础的负载均衡功能上增加了运行期间对服务实例动态更新和过滤的功能。
ZoneAwareLoadBalancer则是继承DynamicServerListLoadBalancer,在此基础上增加防止跨区域访问的问题。
服务调用器(RestClient)
就是负载均衡后,Ribbon向服务提供者发起REST请求的工具。
@LoadBalance注解
当给RestTemplate增加了@LoadBalance注解后,LoadBalancerAutoConfiguration就会对该RestTemplate进行处理,在RestTemplate的拦截器列表中添加一个LoadBalancerInterceptor拦截器,当通过RestTemplate进行请求请求时,LoadBalancerInterceptor中的拦截方法就会启动,通过LoadBalancerClient使请求具有负载聚恒功能。
- 2、Ribbon负载均衡策略及配置
- RoundRobinRule:轮询策略,默认策略
- RandomRule:随机策略
- BestAvailableRule:最大可用策略,即先过滤出故障服务实例后,选择一个当前并发请求数最小的。
- WeightedResponseTimeRule:带有加权的轮询策略,对各个服务实例响应时间进行加权处理,然后再采用轮询的方式获取相应的服务实例。
- AvailabilityFilteringRule:可用过滤策略,先过滤出有故障的或并发请求大于阈值的一部分服务实例,然后再以线性轮询的方式从过滤后的实例清单中选择一个。
- ZoneAvoidanceRule:区域感知策略,先使用主过滤条件(区域负载器,选择最优区域)对所有实例过滤并返回过滤后的实例,依次使用次过滤条件列表中的过滤条件对主过滤条件的结果进行过滤,判断最小过滤数和最小过滤百分比,最后对满足条件的服务实例使用轮询方式。
配置文件配置方式
代码使用方式
3、直接使用Ribbon API
注意:在使用lbcRestTemplate的时候,不能使用之前@LoadBanlanced的RestTemplate,否则在进行请求时restTempate会把服务实例的逻辑名称当做服务名称来使用。
深入Feign
1、Feign的参数绑定
Spring Cloud对Feign进行了增强,使得Feign支持Spring MVC注解。
Spring MVC常用注解:
- @RequestParanm:绑定单个请求参数值
- @PathVariable:绑定URI模板变量值
- @RequestHeader:绑定请求头数据
- @RequestBody:绑定请求的内容区数据并能进行自动类型转换。
2、Feign中的继承
虽然Fegin中是可以与正常的Service继承的。但在使用微服务架构开发时有一个原则,就是保持各微服务的自治性。如果进行继承点的话,就会造成服务提供方与服务消费方之间的代码紧耦合。因此不建议Fegin调用接口与业务Service继承相同的Service。
3、Feign与Swagger的冲突
如果项目中使用了Swagger,需要把Swgger升级到2.6.1版本以上。否则会发生冲突,项目无法启动。
微服务健康监控
Spring Actuator提供了对应用系统自身和监控的继承功能,包含了对应用系统进行配置查看、运行状态监控及相关功能统计等功能。这些监控统计数据通过REST端点方式暴露,并返回JSON数据。
当项目中添加spring-boot-starter-actuator依赖后,Spring Boot在启动系统后就会构建一系列的端点,通过这些端点就可以获取到服务的运行状态。
常用的端点:
- /autoconfig:用来获取Spring Boot自动化配置机制所产生的自动化配置信息报告,包含了自动化配置的候选项及每个候选项自动化条件是否满足等信息。
- /beans:列出所有由Spring Boot创建Bean的配置列表
- /configprops:用来获取应用中配置的属性信息报告
- /env:用来获取应用所有可用的环境属性报告,包含环境变量、JVM属性、应用的属性、命令行的参数等。
- /health:用来报告当前应用的健康状态报告信息,如应用是否启动、磁盘空间。
- /metrics:用来报告当前应用及宿主主机的各类重要度量指标,如CPU数量、内存信息、线程信息和垃圾回收信息等。
- /mappings:返回所有控制器映射关系报告。
- /info:用来返回应用自定义属性配置信息。
- /shutdown:可以关闭应用。
为什么actuator只看到了health和info呢?原因是actuator默认情况下只暴露了health和info端点,其他端点需要进行配置。
也可以指定开启某个端口