一、Eureka
什么是eureka?
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多 个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。
什么是注册中心?
服务注册中心本质上是为了解耦服务提供者和服务消费者。对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的。更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。
几大流行的服务注册中心对比:zookeeper一般偏保守的公司和一些较老的项目会用到,主要是和dubbo应用于soa架构上;eureka2.0不再开源了,慢慢会被淘汰,nacos应该会变成主流。
eureka集群的工作原理:
eureka自我保护机制:
默认情况下,如果 Eureka Server 在一定的 90s 内没有接收到某个微服务实例的心跳,会注销该实例。但是在微服务架构下服务之间通常都是跨进程调用,网络通信往往会面临着各种问题,比如微服务状态正常,网络分区故障,导致此实例被注销。固定时间内大量实例被注销,可能会严重威胁整个微服务架构的可用性。为了解决这个问题,Eureka 开发了自我保护机制。Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 即会进入自我保护机制。
Eureka Server 进入自我保护机制,会出现以下几种情况:
(1 Eureka 不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
(2 Eureka 仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即 保证当前节点依然可用)
(3 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
Eureka 自我保护机制是为了防止误杀服务而提供的一个机制。当个别客户端出现心跳失联时,则认为是客户端的问题,剔除掉客户端;当 Eureka 捕获到大量的心跳失败时,则认为可能是网络问题,进入自我保护机制;当客户端心跳恢复时,Eureka 会自动退出自我保护机制。如果在保护期内刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,即会调用失败。对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。
通过在 Eureka Server 配置如下参数,开启或者关闭保护机制,生产环境建议打开:
eureka.server.enable-self-preservation=true
搭建Eureka服务注册中心步骤:
前提:已经搭建好了springboot的相关项目,含服务消费方和服务提供方。
1.引入eureka依赖:
①在eureka工程中引入如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
②在其他需要注册和拉取服务的工程中引入如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.创建eureka服务的启动器类:在eureka启动类上加上@EnableEurekaServer
注解,在其他需要注册和消费的服务的启动类上加上@EnableDiscoveryClient
或@EnableEurekaClient
注解,然后在服务消费方的启动类上注册一个bean。
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
3.在配置文件中进行如下配置:
①eureka的配置文件:
server:
port: 10087
spring:
application:
name: eureka-service #服务名称
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka #服务的注册地址
#register-with-eureka: false #是否开启自己注册,默认为true
server:
eviction-interval-timer-in-ms: 30000 #设置宕机服务剔除时间
②服务提供方的配置文件:
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis
username: root
password: 123
application:
name: user-service
mybatis:
type-aliases-package: com.zxm.user.pojo
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka
instance: #非必要配置
prefer-ip-address: true
ip-address: 192.168.25.1 #指定IP地址
lease-renewal-interval-in-seconds: 30 #服务续约时间,默认3秒
lease-expiration-duration-in-seconds: 90 #服务过期时长,默认90秒
③服务消费方的配置文件:
server:
port: 8088
spring:
application:
name: CONSUMER-DEMO
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka
registry-fetch-interval-seconds: 3 #配置服务拉取的周期
instance:
prefer-ip-address: true
ip-address: 192.168.25.1 #指定IP地址
4.在服务消费方的controller中写入如下代码:
@RestController
@RequestMapping("consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
public User selectById(@PathVariable("id") Long id){
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
ServiceInstance instance = instances.get(0);
String url = "http://"+instance.getHost()+":"+instance.getPort()+"/user/"+id;
User user = restTemplate.getForObject(url, User.class);
return user;
}
}
最后,启动的时候,点击"Edit configurations",复制需要搭建集群的服务,在VM options中加上-Dserver.port=xxx
,修改端口号,然后依次启动服务,一个集群版的eureka项目就搭建完成了。
然而,instances.get(0)得到的服务实例是固定的,如果该服务地址对应的服务器宕机了,或者需要修改拉取服务的地址,就需要手动改写代码,这时就需要使用ribbon来进行负载均衡了!
二、Roibbon
什么是ribbon?
ribbon默认自带的负载规则:
轮询算法原理:
开启ribbon负载均衡步骤:
1.因为Eureka中已经集成了Ribbon,所以我们无需引入新的依赖。直接修改代码:
在RestTemplate的配置方法上添加@LoadBalanced
注解。
2.修改调用方式,不再手动获取ip和端口,而是直接通过服务名称调用,服务调用方controller层代码如下:
String url = "http://user-service/user/"+id;
User user = restTemplate.getForObject(url, User.class);
默认使用的负载均衡算法策略是轮询。
可在配置文件中配置负载均衡策略,方法如下:
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #设置ribbon负载均衡策略为随机
但是,如果服务出现错误宕机了怎么办呢?这时就需要对服务进行熔断和降级。
三、Hystrix
什么是Hystrix?
在分布式系统中,每个服务都可能会调用很多其他服务,被调用的那些服务就是依赖服务,有的时候某些依赖服务出现故障也是很常见的。
Hystrix中文含义是豪猪,因其背上长满棘刺,从而拥有了自我保护的能力。本文所说的Hystrix是Netflix开源的一款容错框架,它可以让我们在分布式系统中对服务间的调用进行控制,加入一些调用延迟或者依赖故障的容错机制。Hystrix 通过将依赖服务进行资源隔离,进而阻止某个依赖服务出现故障时在整个系统所有的依赖服务调用中进行蔓延;同时Hystrix 还提供故障时的 fallback 降级机制。
总而言之,Hystrix 通过这些方法帮助我们提升分布式系统的可用性和稳定性。
Hystrix熔断状态机模型:
默认是某一服务的最近20次请求内,如果有50%出现超时就会认为该服务有问题,就会把断路器打开。当用户再次请求该服务时,直接返回失败。熔断器打开状态默认会持续5秒钟,5秒后进入半开状态,此时会放一定的请求通过,测试请求是否正常,如果依然失败,进入打开状态,如果成功,进入关闭状态。
Hystrix实现步骤:
1.引入hystrix依赖
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-javanica</artifactId>
<version>1.5.12</version>
</dependency>
2.在服务消费方的启动类上加上@EnableCircuitBreaker
注解开启熔断。
以上三个注解可以用@SpringCloudApplication
代替,@SpringCloudApplication包含了以上三个注解。
3.编写降级熔断逻辑:
@RestController
@RequestMapping("consumer")
@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@HystrixCommand(commandProperties = {
//@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000"), //设置超时时间(单个方法),默认为1秒
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数10次
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//休眠时间为10s
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//失败比例为60%
})
@GetMapping("{id}")
public String selectById(@PathVariable("id") Long id){
String url = "http://user-service/user/"+id;
String user = restTemplate.getForObject(url, String.class);
return user;
}
public String selectByIdFallback(Long cust_id) {//一对一的,参数和返回值需保持一致
return "服务器繁忙,请稍后重试...";
}
public String defaultFallback() {//默认的fallback不用保持一致,空参
return "服务器繁忙,请稍后重试...";
}
}
4.在服务消费方的配置文件中写入如下代码配置全局超时时间:
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 #配置全局超时时间
四、Feign
什么是feign?
Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。
使用步骤:
1.服务消费方引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.服务消费方启动器加上@EnableFeignClients
注解,RestTemplate就不用注册了。
3.创建UserClient接口
@FeignClient(value = "user-service",fallback = UserClientFallback.class)
public interface UserClient {
@GetMapping("user/{id}")
User selectById(@PathVariable("id") Long id);
}
4.创建UserClientFallback类
@Component
public class UserClientFallback implements UserClient {
@Override
public User selectById(Long custId) {
User user = new User();
user.setUsername("未知用户!");
return user;
}
}
5.UserController类代码如下:
@RestController
@RequestMapping("consumer")
//@DefaultProperties(defaultFallback = "defaultFallback")
public class ConsumerController {
@Autowired
private UserClient userClient;
/*@HystrixCommand(commandProperties = {
//@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000"), //设置超时时间(单个方法),默认为1秒
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),//请求次数10次
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),//休眠时间为10s
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")//失败比例为60%
})*/
@GetMapping("{id}")
public User selectById(@PathVariable("id") Long id){
User user = userClient.selectById(id);
return user;
}
}
6.在服务消费方的配置文件中配置ribbon和hystrix,配置完之后feign就创建好了!
ribbon:
ConnectionTimeout: 500 #若500毫秒连接未建立,则抛出异常
ReadTimeout: 2000 #若已连接,但2000毫秒内未读取到数据也会抛出异常
feign:
hystrix:
enabled: true #开启feign的熔断功能
除此之外,feign还可以做请求压缩和控制日志级别
五、zuul
1.Zuul简介
Zuul相当于是第三方调用(app应用端和PC端)和服务提供方之间的防护门。作为前端服务(Edge Service也称边缘服务,前端服务的作用是对后端服务做必要的聚合和裁剪后暴露给外部不同的设备,如PC,Pad或者Phone),Zuul旨在实现动态路由,监控,弹性和安全性。它具备根据需求将请求路由到多个AWS自动弹性伸缩组的能力。
2.Zuul能做什么
Netflix API流量的量级和多样性随时可能导致生产环境故障而没有预警。因此需要一个系统能使我们迅速改变策略行为,以便应对各种情况。 Zuul使用一些不同类型的过滤器,使我们能够快速灵活地将功能应用于我们的前端服务。这些过滤器具有以下功能:
(1)- 权限控制和安全性--为每个请求提供身份认证,并拒绝不满足条件的请求。
(2)- 预警和监控--跟踪前端有意义的请求和统计数据,以便我们准确了解生产环境运行状况。
(3)- 动态路由--根据需求将请求动态地路由到不同的后端集群。
(4)- 压力测试--逐渐增大到集群的流量,以便进行性能评估。
(5)- 负载均衡--为每种类型的请求分配容量并丢弃超过限额的请求。
(6)- 静态资源处理--直接在Zuul处理静态资源并响应,而并非转发这些请求到内部集群中。
(7)- 多区域弹性--实现跨AWS区域请求路由,扩大了ELB的使用范围,并使前端服务更接近我们的成员。
3.zuulFilter过滤器
4.环境搭建
①创建zuul服务工程。
②引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
③创建启动类
@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
④配置配置文件
a.进行如下配置,访问路径为:http://localhost:10010/user-service/user/10
或http://localhost:8088/consumer/10或http://localhost:8080/user/10或http://localhost:10010/consumer-demo/consumer/10
server:
port: 10010
spring:
application:
name: gateway
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka
zuul:
routes:
user-service: /user-service/** #zuul会默认给每个服务都按此方式配置,这里其实不配也行,但是若你想要配置修改路径,如改为:/user/**,则需要配置,改完之后zuul的默认配置还是会生效,自己配置的路径也会生效
b.若你想要某个服务只能服务间进行调用,进行如下配置,可设置要忽略的服务
zuul:
ignored-services: #zuul默认会给每个服务都进行配置,这里可设置要忽略的服务
- consumer-demo
这时,就不能通过http://localhost:10010/consumer-demo/consumer/10进行访问了。
c.若你想要去除前缀,可进行如下配置:
zuul:
routes:
user-service:
path: /user/**
serviceId: user-service
strip-prefix: false #去除前缀
prefix: /api #设置全局前缀
strip-prefix: false #只能去除prefix设置的全局前缀
这时,访问路径就变成了http://localhost:10010/user/10
⑤创建过滤器
/**
* 模拟登录校验过滤器
*/
import javax.servlet.http.HttpServletRequest;
@Component
public class LoginFilter extends ZuulFilter {
@Override
//过滤器类型
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
//过滤器优先级,数字越小优先级越高
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER-1;
}
@Override
//是否过滤,默认否
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//获取请求上下文
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
//获取请求参数access_token
String token = request.getParameter("access_token");
//判断是否存在
if (StringUtils.isBlank(token)){
//不存在,未登录,则拦截
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
}
return null;
}
}
⑥配置hystrix和ribbon,配置完之后最终配置为:
server:
port: 10010
spring:
application:
name: gateway
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka,http://localhost:10087/eureka
zuul:
prefix: /api #设置全局前缀
routes:
#user-service: /user-service/** #zuul会默认给每个服务都按此方式配置,这里其实不配也行,但是若你想要配置修改路径,如改为:/user/**,则需要配置,改完之后zuul的默认配置还是会生效,自己配置的路径也会生效
user-service:
path: /user/**
serviceId: user-service
#url: http://192.168.25.1:8080
strip-prefix: false #去除前缀
#strip-prefix: false #只能去除prefix设置的全局前缀
ignored-services: #zuul默认会给每个服务都进行配置,这里可设置要忽略的服务
- consumer-demo
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000 #配置全局超时时间
ribbon:
ConnectionTimeout: 500
ReadTimeout: 2000 #ribbon的超时时长,真实值是(read+connect)*2,也就是5000,必须小于hynatix时长
MaxAutoRetriesNextServer: 0 #不重试,配置完之后ribbon的超时时长就是read+connect
⑦若要在ribbon中禁用eureka,配置如下
ribbon.eureka.enabled: false #取消Ribbon中使用Eureka
user-service:
ribbon:
listOfServers: http://192.168.25.1:8080,http://192.168.25.1:8082 #配置Ribbon能访问的微服务节点,多个节点用逗号隔开
ConnectionTimeout: 500
ReadTimeout: 2000 #ribbon的超时时长,真实值是(read+connect)*2,也就是5000,必须小于hynatix时长
MaxAutoRetriesNextServer: 0 #不重试,配置完之后ribbon的超时时长就是read+connect