为什么要用eureka?
1 传统的http客户端编码方式将URL地址硬编码进去,不利于修改和维护
2 服务间彼此运行状态不清楚,一个服务器宕机了,另一个没有及时的应对措施
3 无法实现负载均衡
1、工程创建
1.1 添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>leyou</artifactId>
<groupId>com.leyou.parent</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.common</groupId>
<artifactId>ly-registry</artifactId>
<dependencies>
<!-- Eureka客户端:在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.2 编写启动类
@EnableEurekaServer // 声明这个应用是一个EurekaServer
@SpringBootApplication
public class LyRegistry {
public static void main(String[] args) {
SpringApplication.run(LyRegistry.class);
}
}
相关注解:
-
@EnableEurekaServer
以下原理图摘自:源码剖析 @EnableEurekaServer注解
-
@SpringBootApplication
@SpringBootApplication = (默认属性)@Configuration + @EnableAutoConfiguration + @ComponentScan。
详解@SpringBootApplication -
@EnableDiscoveryClient/@EnableEurekaClient
如果使用eureka注册中心,可以使用@EnableEurekaClient
注解
如果使用其他的注册中心,可以使用@EnableDiscoveryClient
注解
1.3 配置文件
server:
port: 10086
spring:
application:
name: ly-registry #spring.application.name,这个很重要,这在以后的服务与服务之间相互调用一般都是根据这个name
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
2、Eureka注册中心
2.1 eureka
eureka做什么
- Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
- 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
- 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
- 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
注册到eureka的服务:
eureka的三个核心角色:
-
服务注册中心
Eureka的服务端应用,提供服务注册和发现功能
-
服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。
-
服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。
2.2 Ribbon
实际生产中,service服务往往会搭建集群,,此时我们的eureka中就有多个服务注册,并且eureka中已经帮我们实现了负载均衡组件:Ribbon,访问时默认的负载均衡算法是轮询。
修改均衡算法:
serviceName:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机
重试机制:
Eureka为了实现更高的服务可用性,牺牲了一定的一致性,极端情况下它宁愿接收故障实例也不愿丢掉健康实例。
- 重试的实现:
spring:
cloud:
loadbalancer:
retry:
enabled: true # 开启Spring Cloud的重试功能
serviceName:
ribbon:
ConnectTimeout: 250 # Ribbon的连接超时时间
ReadTimeout: 1000 # Ribbon的数据读取超时时间
OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
MaxAutoRetries: 1 # 对当前实例的重试次数
当访问到某个服务超时后,它会再次尝试访问下一个服务实例,如果不行就再换一个实例,如果不行,则返回失败。切换次数取决于MaxAutoRetriesNextServer
参数的值。
2.3 Hystrix
Hystrix,熔断器,是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。
分布式架构通常有很多依赖,当其中一个服务失败时,这个失败的行为会影响到这个服务的调用者,最终导致一个接一个的错误发生。
- 工作原理
类似于家庭电路的保险丝,当电路发生短路时立刻熔断电路,避免发生灾难。在分布式系统中,服务的调用方可以自己判断某些服务反应慢或者存在大量超时的情况下,从而主动熔断。
电路不会自动重新连接,但是Hystrix却可以实现弹性容错,当情况好转时,自动重新连接。
熔断机制与重试机制:
Ribbon的超时时间一定要小于Hystix的超时时间 !
如果不考虑上述时间,虽然熔断可以实现,但是重试机制不会生效,服务没有合理被利用。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMillisecond: 6000 # 设置hystrix的超时时间为6000ms
2.4 Feign
在后续的代码完善过程中,某些代码肯定会被重复的书写,例如查询用户的代码,Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。
示例:
@FeignClient("user-service")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User queryUserById(@PathVariable("id") Long id);
}
- 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像
@FeignClient
,声明这是一个Feign客户端,类似@Mapper
注解。同时通过value
属性指定服务名称- 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果
2.5 Zuul
到目前为止,我们服务的地址是直接暴露的,这样是否合理?
- 首先,破坏了服务无状态特点。
- 为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。
- 从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外考虑对接口访问的控制处理。
- 其次,无法直接复用既有接口。
- 当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。
为了解决上面这些问题,我们需要将权限控制这样的东西从我们的服务单元中抽离出去,而最适合这些逻辑的地方就是处于对外访问最前端的地方,我们需要一个更强大一些的均衡负载器的 服务网关。zuul的核心是一系列的过滤器,可以完成如下功能:
- 身份认证
- 动态路由
- 压力测试
- 负载分配
- …
映射配置:
因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能。
zuul:
routes:
user-service: # 这里是路由id,随意写
path: /user-service/** # 这里是映射路径
serviceId: user-service # 指定服务名称
简化:
zuul:
routes:
user-service: /user-service/** # 这里是映射路径
zuul过滤器: ZuulFilter
其中重要的四个方法:
public abstract ZuulFilter implements IZuulFilter{
abstract public String filterType();
abstract public int filterOrder();
boolean shouldFilter();// 来自IZuulFilter
Object run() throws ZuulException;// IZuulFilter
}
shouldFilter
:返回一个Boolean
值,判断该过滤器是否需要执行。返回true执行,返回false不执行。run
:过滤器的具体业务逻辑。filterType
:返回字符串,代表过滤器的类型。包含以下4种:pre
:请求在被路由之前执行routing
:在路由请求时调用post
:在routing和errror过滤器之后调用error
:处理请求时发生错误调用
filterOrder
:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
流程:
- 正常流程:
- 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
- 异常流程:
- 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
- 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
- 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。
使用场景:
- 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
- 异常处理:一般会在error类型和post类型过滤器中结合来处理。
- 服务调用时长统计:pre和post结合使用。
最终:
- 使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;
- 服务间通过Ribbon或Feign实现服务的消费以及均衡负载;
- 通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理;
- 为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延;
- 一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作,Zuul作为服务的统一入口。