赞
踩
注册中心与生产者,消费者之间的原理图:
Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.0.6.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
-
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <java.version>1.8</java.version>
- <spring-cloud.version>Finchley.SR2</spring-cloud.version>
- </properties>
-
- <!-- SpringCloud依赖管理 -->
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>${spring-cloud.version}</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
- </dependency>
- </dependencies>
- server:
- port: 10087 # 端口
- spring:
- application:
- name: eureka-server # 应用名称,会在Eureka中显示
- eureka:
- client:
- service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
- defaultZone: http://127.0.0.1:${server.port}/eureka
-
- # 不注册自己
- # register-with-eureka: false
- # 不拉取服务
- # fetch-registry: false
(1)eureka.client.registerWithEureka :表示是否将自己注册到Eureka Server,默认为true。由于当前这个应用就是Eureka Server,故而设为false。
(2)eureka.client.fetchRegistry :表示是否从Eureka Server获取注册信息,默认为true。因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,故而设为false。
2.3 修改引导类,在类上添加@EnableEurekaServer注解:
- @SpringBootApplication
- @EnableEurekaServer // 声明当前springboot应用是一个eureka服务中心
- public class YhEurekaApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(YhEurekaApplication.class, args);
- }
- }
概念:注册服务,就是在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中。
其中还有一个重要的知识点,暂时先不讲:RestTemplate
流程:
在pom.xml中,添加springcloud的相关依赖。
在application.yml中,添加springcloud的相关依赖。
在引导类上添加注解,把服务注入到eureka注册中心。
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.0.6.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
-
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <java.version>1.8</java.version>
- </properties>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>Finchley.SR1</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-jdbc</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.2</version>
- </dependency>
- <-- 上面是springboot的基本依赖 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- </dependencies>
- server:
- port: 8081
- spring:
- datasource:
- url: jdbc:mysql://localhost:3306/lxw
- username: root
- password: root
- driverClassName: com.mysql.jdbc.Driver
- application:
- name: service-provider # 应用名称,注册到eureka后的服务名称
- mybatis:
- type-aliases-package: cn.yh.service.pojo
- eureka:
- client:
- service-url: # EurekaServer地址
- defaultZone: http://127.0.0.1:10087/eureka
通过添加@EnableDiscoveryClient
来开启Eureka客户端功能,@EnableDiscoveryClient该注解是netflix公司提供的,后期推荐使用springcloud提供的注解@EnableDiscoveryClient,功能相同。
- @SpringBootApplication
- @EnableDiscoveryClient
- public class YhServiceProviderApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(YhServiceApplication.class, args);
- }
- }
Eureka架构中的三个核心角色:
服务注册中心
Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的yh-eureka。
服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的yh-service-provider。
服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的yh-service-consumer。
Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。
多个Eureka Server之间也会互相注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。
操作流程:
1. 启动第一个eurekaServer,我们修改原来的EurekaServer配置中的端口号,
2. 复制一个启动器,然后在启动服务,就会有两个端口的客户端(都是生产者/消费者)
5.3客户端注册服务到集群
因为EurekaServer不止一个,因此注册服务的时候,service-url参数需要变化:
- eureka:
- client:
- service-url: # EurekaServer地址,多个地址以','隔开
- defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。
5.3.1 服务注册
服务提供者在启动时,会检测配置属性中的:eureka.client.register-with-eureka=true
参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,Eureka Server会把这些信息保存到一个双层Map结构中。
Map<serviceId,Map<服务实例名,实例对象instance>>
第一层Map的Key就是服务id,一般是配置中的spring.application.name
属性
第二层Map的key是服务的实例id。一般host+ serviceId + port,例如:locahost:service-provider:8081
值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。
5.3.2 服务续约
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);
有两个重要参数可以修改服务续约的行为:
- eureka:
- instance:
- lease-expiration-duration-in-seconds: 90
- lease-renewal-interval-in-seconds: 30
lease-renewal-interval-in-seconds:服务续约(renew)的间隔,默认为30秒
lease-expiration-duration-in-seconds:服务失效时间,默认值90秒
也就是说,默认情况下每个30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会从服务列表中移除,这两个值在生产环境不要修改,默认即可。
但是在开发时,这个值有点太长了,经常我们关掉一个服务,会发现Eureka依然认为服务在活着。所以我们在开发阶段可以适当调小。
- eureka:
- instance:
- lease-expiration-duration-in-seconds: 10 # 10秒即过期
- lease-renewal-interval-in-seconds: 5 # 5秒一次心跳
获取服务列表
当服务消费者启动时,会检测eureka.client.fetch-registry=true
参数的值,如果为true,则会拉取Eureka Server服务的列表只读备份,然后缓存在本地。并且每隔30秒
会重新获取并更新数据。我们可以通过下面的参数来修改:
- eureka:
- client:
- registry-fetch-interval-seconds: 5 #默认每隔30秒会从注册中心服务列表中重新拉取一次
生产环境中,我们不需要修改这个值。
但是为了开发环境下,能够快速得到服务的最新状态,我们可以将其设置小一点。
6.1 服务下线
当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。服务中心接受到请求之后,将该服务置为下线状态。
6.2 失效剔除
有些时候,我们的服务提供方并不一定会正常下线,可能因为内存溢出、网络故障等原因导致服务无法正常工作。Eureka Server需要将这样的服务剔除出服务列表。因此它会开启一个定时任务,每隔60秒对所有失效的服务(超过90秒未响应)进行剔除。
可以通过eureka.server.eviction-interval-timer-in-ms
参数对其进行修改,单位是毫秒,生产环境不要修改。
这个会对我们开发带来极大的不便,你对服务重启,隔了60秒Eureka才反应过来。开发阶段可以适当调整,比如:10秒
6.3 自我保护
我们关停一个服务,就会在Eureka面板看到一条警告:
这是触发了Eureka的自我保护机制。当一个服务未按时进行心跳续约时,Eureka会统计最近15分钟心跳失败的服务实例的比例是否超过了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka就会把当前实例的注册信息保护起来,不予剔除。生产环境下这很有效,保证了大多数服务依然可用。
但是这给我们的开发带来了麻烦, 因此开发阶段我们都会关闭自我保护模式:(yh-eureka)
- eureka:
- server:
- enable-self-preservation: false # 关闭自我保护模式(缺省为打开)
- eviction-interval-timer-in-ms: 1000 # 扫描失效服务的间隔时间(缺省为60*1000ms)
1. 概念:
Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP客户端的行为。为Ribbon配置服务提供者提供地址列表后,Ribbon就可基于某种负载均衡算法,自助地帮助服务消费者去请求。Ribbon默认为我们提供了很多的负载均衡算法,例如轮询,随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法。
2. 开启负载均衡
因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖,直接修改代码。
修改yh-service-consumer的引导类,在RestTemplate的配置方法上添加@LoadBalanced
注解:
- @Bean
- @LoadBalanced
- public RestTemplate restTemplate() {
- return new RestTemplate();
- }
3. 修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用:
- @Controller
- @RequestMapping("consumer/user")
- public class UserController {
-
- @Autowired
- private RestTemplate restTemplate;
-
- //@Autowired
- //private DiscoveryClient discoveryClient; // 注入discoveryClient,通过该客户端获取服务列表
-
- @GetMapping
- @ResponseBody
- public User queryUserById(@RequestParam("id") Long id){
- // 通过client获取服务提供方的服务列表,这里我们只有一个
- // ServiceInstance instance = discoveryClient.getInstances("service-provider").get(0);
- String baseUrl = "http://service-provider/user/" + id;
- User user = this.restTemplate.getForObject(baseUrl, User.class);
- return user;
- }
-
- }
Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- </dependency>
注解:@EnableCircuitBreaker --->@SpringCloudApplication=@SpringBootApplication+@EnableDiscoveryClient+@EnableCircuitBreaker
1. 实现单个降级方法:@HystixCommond(fallbackMethod = "降级要执行的方法名")注到要降级的方法上,然后编写要降级的方法,参数和返回值要一致,方法名没有要求。
2.
2.1 实现多个降级方法:在Controller类上添加@DefaultProperties(defaultFallback = "降级要执行的方法名") // 指定一个类的全局熔断方法
2.2 接着在要降级的方法上添加 @HystixCommond注解,然后再编写要降级的方法,参数和返回值要一致,方法名没有要求
- - @DefaultProperties(defaultFallback = "defaultFallBack"):在类上指明统一的失败降级方法
- - @HystrixCommand:在方法上直接使用该注解,使用默认的降级方法。
- - defaultFallback:默认降级方法,不用任何参数,以匹配更多方法,但是返回值一定一致
设置超时时间:请求在超过这个时间之后会报错,默认是1秒
我们可以通过`hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds`来设置Hystrix超时时间:
- hystrix:
- command:
- default:
- execution:
- isolation:
- thread:
- timeoutInMilliseconds: 6000 # 设置hystrix的超时时间为6000ms
熔断器,也叫断路器,其英文单词为:Circuit Breaker
熔断是降级下的一个小的模块,当至少访问20次,且有一般的概率出错时,会触发熔断,此时熔断处于断开状态,不论是正确的请求还是错误的请求都无法通过,
默认会休眠5秒,5秒之后,熔断会进入半开状态,此时会关闭一部分请求路径,如果请求正确,那么就可以通过,拿去数据,之后就可以进入关闭状态,所有正确的请求都可以通过。
可以通过抛异常让熔断进入打开状态
- Closed:关闭状态,所有请求都正常访问。
- Open:打开状态,所有请求都会被降级。Hystix会对请求情况计数,当一定时间内失败请求百分比达到阈值,则触发熔断,断路器会完全打开。默认失败比例的阈值是50%,请求次数最少不低于20次。
- Half Open:半开状态,open状态不是永久的,打开后会进入休眠时间(默认是5S)。随后断路器会自动进入半开状态。此时会释放部分请求通过,若这些请求都是健康的,
则会完全关闭断路器,否则继续保持打开,再次进行休眠计时
- - requestVolumeThreshold:触发熔断的最小请求次数,默认20
- - errorThresholdPercentage:触发熔断的失败请求最小占比,默认50%
- - sleepWindowInMilliseconds:休眠时长,默认是5000毫秒
Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。
0. 在启动器application类中添加@EnableFeignClients注解
1. 创建一个feign在feign目录下的UserClient接口,接着添加@FeignClient(value = "service-provider")注解,value是生产者的服务名。
2. 定义抽象方法,返回值类型,参数类型与生产者的方法一致,@PathVariable注解也不能省略,方法名可以任意,RequestMapping与生产者的controller的uri一致
- @FeignClient(value = "service-provider") // 标注该类是一个feign接口
- public interface UserClient {
- @GetMapping("user/{id}")
- User queryById(@PathVariable("id") Long id);
- }
- 首先这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像
- `@FeignClient`,声明这是一个Feign客户端,类似`@Mapper`注解。同时通过`value`属性指定服务名称
- 接口中的定义方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果
然后在Controller里面
- @Autowired
- private UserClient userClient;
- userClient.queryUserById(id);
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
Feign默认也有对Hystrix的集成:
只不过,默认情况下是关闭的。我们需要通过下面的参数来开启:(在Yh-service-consumer工程添加配置内容)
- feign:
- hystrix:
- enabled: true # 开启Feign的熔断功能
- 这个时候就不可以用@SpringCloudApplication注解了,因为@SpringCloudApplication包含了@EnableCircuitBreaker,这个注解的意思就是开启熔断
首先,我们要定义一个类UserClientFallback,实现刚才编写的UserClient,作为fallback的处理类
- @Component
- public class UserClientFallback implements UserClient {
-
- @Override
- public User queryById(Long id) {
- User user = new User();
- user.setUserName("服务器繁忙,请稍后再试!");
- return user;
- }
- }
然后在UserFeignClient中,指定刚才编写的实现类
- @FeignClient(value = "service-provider", fallback = UserClientFallback.class) // 标注该类是一个feign接口
- public interface UserClient {
-
- @GetMapping("user/{id}")
- User queryUserById(@PathVariable("id") Long id);
- }
Zuul是Netflix开源的微服务网关,他可以和Eureka,Ribbon,Hystrix等组件配合使用。Zuul的核心是一系列的过滤器。
这些过滤器可以完成一下功能:
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
- </dependency>
- server:
- port: 10010 #服务端口
- spring:
- application:
- name: api-gateway #指定服务名
给启动类添加@EnableZuulProxy注解,表示开启网关功能
- 第一种方式:
- zuul:
- routes:
- service-provider: # 这里是路由id,随意写
- path: /service-provider/** # 这里是映射路径
- url: http://127.0.0.1:8081 # 映射路径对应的实际url地址
- 第二种方式
- zuul:
- routes:
- service-provider: # 这里是路由id,随意写
- path: /service-provider/**
- url: http://127.0.0.1:8081
- service-id: service-provider #指定服务名称
- 第三种方式:推荐使用
- zuul:
- routes:
- service-provider: /service-provider/**
- service-consumer: /service-consumer/**
- prefix: /api
- 第四种方式:
- 不配置,即默认配置,默认情况,每个微服务的服务名,即是访问映射地址
Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的
ZuulFilter是过滤器的顶级父类。在这里我们看一下其中定义的4个最重要的方法:
- 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
:请求在被路由之前执行
route
:在路由请求时调用
post
:在route和errror过滤器之后调用
error
:处理请求时发生错误调用
filterOrder
:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。
6.2.1 正常流程
请求到达首先会经过pre类型过滤器,而后到达route类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
6.2.2 异常流程
整个过程中,pre或者route过滤器出现异常,都会直接进入error过滤器,在error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
如果是error过滤器自己出现异常,最终也会进入POST过滤器,将最终结果返回给请求客户端。
如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和route不同的是,请求不会再到达POST过滤器了。
请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
异常处理:一般会在error类型和post类型过滤器中结合来处理。
服务调用时长统计:pre和post结合使用。
定义过滤器类,继承ZuulFilter抽象类,并重写抽象方法。
- @Component
- public class LoginFilter extends ZuulFilter {
- /**
- * 过滤器类型,前置过滤器
- * @return
- */
- @Override
- public String filterType() {
- return "pre";
- }
-
- /**
- * 过滤器的执行顺序
- * @return
- */
- @Override
- public int filterOrder() {
- return 1;
- }
-
- /**
- * 该过滤器是否生效
- * @return
- */
- @Override
- public boolean shouldFilter() {
- return true;
- }
-
- /**
- * 登陆校验逻辑
- * @return
- * @throws ZuulException
- */
- @Override
- public Object run() throws ZuulException {
- // 获取zuul提供的上下文对象
- RequestContext context = RequestContext.getCurrentContext();
- // 从上下文对象中获取请求对象
- HttpServletRequest request = context.getRequest();
- // 获取token信息
- String token = request.getParameter("access-token");
- // 判断
- if (StringUtils.isBlank(token)) {
- // 过滤该请求,不对其进行路由
- context.setSendZuulResponse(false);
- // 设置响应状态码,401
- context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
- // 设置响应信息
- context.setResponseBody("{\"status\":\"401\", \"text\":\"request error!\"}");
- }
- // 校验通过,把登陆信息放入上下文信息,继续向后执行
- context.set("token", token);
- return null;
- }
- }
Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:
- hystrix:
- command:
- default:
- execution:
- isolation:
- thread:
- timeoutInMilliseconds: 2000 # 设置hystrix的超时时间为6000ms
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。