Springcloud(demo)
Springcloud+eureka+feign+hystrix+zuul
Eureka服务注册与发现
eureka是做什么的
eureka主要负责完成微服务架构中的服务治理功能
为什么要使用eureka
当服务的模块越来越多,系统的功能越来越复杂,以前用到的静态配置就会变得越来越难以维护,会消耗巨大的人力,所以通过使用服务注册与发现来完成对微服务应用的自动化管理。
服务治理
用来实现各个微服务实例化的自动化注册与发现。
服务注册
首先会构建一个服务中心,之后的每个服务单元向这个服务中心提供主机,端口号等信息登记自己提供的服务。而注册中心通过服务名分类组织服务清单,并对其进行维护。服务注册中心需要以心跳的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除。
服务发现
服务之间不需要指定具体的实例地址,而是通过服务中心发起请求调用实现。所以,调用方并不需要知道服务提供方具体的位置,只需向注册中心发起请求,从而获取所有服务的实例清单,才能实现对具体服务实例的访问。
实际应用中,不会每次都向注册中心获取服务,使用了缓存和服务剔除等不同的策略。
搭建Eureka注册中心服务
1.搭建父项目
首先创建一个新的maven项目,默认next即可
项目名为cloud-parent,将生成的src目录删除,这个项目作为一父项目,用作集成公共的依赖即可。 springcloud是依托于springboot,所以必须用到springboot,两者的关联版本为
Release Train Boot version
Hoxton 2.2.x
Greenwich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x
cloud版本使用Honxton.SR7,如下,对应2.3.2的springboot;
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>cloud-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cloud-server-eureka</module>
<module>cloud-client-product</module>
<module>cloud-client-order</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.cloud-version>Hoxton.SR7</spring.cloud-version>
<spring.boot-version>2.3.2.RELEASE</spring.boot-version>
</properties>
<!--dependencyManagement 是只管理版本,不引入依赖版本号写在 properties 标签中-->
<dependencyManagement>
<!--spring boot 版本控制 2.3.2-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud 版本管理 Hoxton.SR7< -->
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
maven仓库:https://mvnrepository.com/
这是一个简单的父项目,springboot与cloud以及build打包方式都已经配置好了。
2.搭建eureka单机
1.创建maven项目
配置eureka注册中心,右键cloud-parent,新建module名为cloud-server-eureka的Maven项目
最终项目结构如下
2.pom
<?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>cloud-parent</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-server-eureka</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
3.application.yml
server:
port: 8866
eureka:
instance:
prefer-ip-address: true #以IP地址注册到服务中心
client:
register-with-eureka: false #是否将自己注册到Eureka Server
fetch-registry: false #是否从Eureka Server获取注册信息 #Eureka Server地址
service-url: #Eureka Server地址
defaultZone: http://127.0.0.1:8866/eureka/
spring:
application:
name: server-eureka
主要是通过如下配置将该微服务定位为Eureka注册中心
register-with-eureka: true #是否将自己注册到Eureka Server fetch-registry: false #是否从Eureka Server获取注册信息 #Eureka Server地址
4.启动类:
package com.cse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
//标记为服务端
@EnableEurekaServer
//标记自己为启动类
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class);
}
}
写好启动类,然后不用手动再配置如下图,直接启动后会自动配置好下图,如果需要另外配置jvm参数的另行配置即可;
访问Eureka地址: http://127.0.0.1:8866/,如图,启动成功
3.搭建Eureka集群
1.首先修改hosts文件,C:\Windows\System32\drivers\etc
添加如下内容,用于在本地模拟不同域名
127.0.0.1 www.eureka-node1.com
127.0.0.1 www.eureka-node2.com
127.0.0.1 www.eureka-node3.com
2.配置文件
注册中心是集群,需要相互注册,无论访问哪一个注册中心,都能看到其余的注册中心
其余代码不用改,只需要继续创建另外两个Module即可,修改端口以及对应的注册中心网址即可;
注册中心1号配置:
server:
port: 8861
eureka:
client:
service-url: #Eureka Server地址
defaultZone: http://www.eureka-node2.com:8862/eureka/,http://www.eureka-node3.com:8863/eureka/
instance:
hostname: www.eureka-node1.com
spring:
application:
name: server-eureka
注册中心2号配置:
server:
port: 8862
eureka:
client:
service-url: #Eureka Server地址
defaultZone: http://www.eureka-node1.com:8862/eureka/,http://www.eureka-node3.com:8863/eureka/,
instance:
hostname: www.eureka-node2.com
spring:
application:
name: server-eureka
注册中心3号配置:
server:
port: 8863
eureka:
client:
service-url: #Eureka Server地址
defaultZone: http://www.eureka-node1.com:8861/eureka/,http://www.eureka-node3.com:8862/eureka/,
instance:
hostname: www.eureka-node3.com
spring:
application:
name: server-eureka
4.服务提供者
最终项目结构如下:
注意:此处注册中心使用的是上个内容的单点模块
1.创建maven项目
在父项目cloud-parent的基础上,创建一个maven子项目cloud-client-product,引入如下依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
如上引入了eureka客户端,意思是该微服务是eureka的客户端,启动后需要将服务地址注册到注册中心上,这是相对于eureka来说的;
web用来支持spring注解;
2.配置yml文件
server:
port: 8081
spring:
application:
name: client-product
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:8866/eureka
#这里只写了一个注册中心网址,如果是集群,用逗号隔开可继续加
配置了eureka为了将服务注册到eureka上;
3.主程序
package com.ccp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@EnableEurekaClient新版本不需要加了,可省略
@SpringBootApplication
public class ProductApplication {
public static void main(String[] args) {
SpringApplication.run(ProductApplication.class);
}
}
4.controller数据接口
package com.ccp.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
@GetMapping("getProductById")
public String getProductById(Integer productId){
System.out.println("服务提供者》根据ID查询商品:"+productId);
return "这是你要的商品"+productId;
}
}
启动cloud-server-eureka注册中心,再启动cloud-client-product服务提供者
访问注册中心,可在页面中发现服务已经注册上来
当启动多个项目时,idea会提示如下
点击show,方便查看各个微服务项目;
如图,服务已经注册上去了
访问http://localhost:8081/getProductById?productId=2正常访问
5.自我保护模式
这时候如果将product服务停一会后,发现注册中心进入了自我保护模式
翻译下
紧急情况!尤里卡可能错误地声称,当实例未启动时,它们已启动。续订小于阈值,因此实例不会为了安全而过期。
原因:eureka的客户端,也就是这里的cloud-server-product会每个30秒发送一次心跳告诉eureka服务端(注册中心)还活着,eureka服务端接收eureka客户端心跳,持续90秒没有收到心跳,就会从注册列表中剔除掉该微服务,但是上图client-product服务已经停了却依旧在列表中,这是因为触发了eureka另外一个机制,也就是自我保护模式;
本地单机测试是最容易进入该模块的;
自我保护模式:在15分钟内,心跳失败率低于85%就会触发,eureka会保留该服务,保证服务的有效性;因为服务也许因为网络延迟等未知原因并没有挂掉,只是心跳发送的不那么正常,这时候不能直接就剔除掉,否则其余调用该服务的就调用不到了;
Feign服务调用
声明一个代理接口,服务调用者通过调用这个代理接口的方式来调用远程服务。
另外Feign整合了Ribbon和Hystrix,可以让我们不再需要显式地使用这两个组件。
服务消费者
最终项目结构如下:
1.创建maven项目
在父项目cloud-parent的基础上,创建一个maven子项目cloud-client-order,引入如下依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2.yml
server:
port: 8082
spring:
application:
name: client-order
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:8866/eureka/
3.启动类
@EnableFeignClients启用Feign组件
package com.cco;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class);
}
}
4.调用远程服务的Feign客户端接口
使用@FeignClient注册来指定这个接口所要调用的服务名,接口中定义的各个函数使用SpringMVC的注解就可以来绑定服务提供方的接口,并进行参数的传递,参数传递时@RequestParam必不可少;
package com.cco.remote;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "client-product")
public interface ProductRemote {
@GetMapping("/getProductById")
String getProductById(@RequestParam("productId") Integer productId);
}
这里需要调用client-product服务,使用feign,相当于搭了个桥,client-product这个名字来自如下yml配置,方法名也要与client-produc的一致;
5.controller接口调用Feign
package com.cco.controller;
import com.cco.remote.ProductRemote;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class OrderController {
@Resource
private ProductRemote productRemote;
@GetMapping("/getOrderDetail")
public Object getOrderDetail(){
String product = productRemote.getProductById(666);
System.out.println("服务消费者》获取到远程服务提供的商品信息");
return product;
}
}
启动order项目,访问
http://127.0.0.1:8082/getOrderDetail
成功调到了product服务的接口;
6.注册中心宕机
如果把product服务关闭,进入了保护模式,那么order是否还能调用到呢?
当然不能,
order调的不是eureka的注册中心的服务,而是通过先到注册中心找到product地址再通过feign调product服务,eureka保存的仅仅只是一个url地址列表而已。
如果eureka注册中心突然挂了,其余服务还会正常使用吗?
当然能
因为order已经拿到调用地址了,就不会走注册中心了,那么之后注册中心即使挂了也不会影响调用product服务;如果还没拿到就挂了,肯定无法调用到product服务。
Ribbon负载均衡
Ribbon已经被Feign整合,引用了Feign组件的项目,可以直接使用Ribbon
案例:重复启动同一个服务提供者(修改端口即可),然后进行远程调用,即可看到服务提供者在被依次的调用(默认的轮询策略--RoundRobinRule),一般BestAvailableRule用的比较多一些;
IRule的几个重要实现类
- com.netflix.loadbalancer.RoundRobinRule : 轮询
- com.netflix.loadbalancer.RandomRule: 随机
- com.netflix.loadbalancer .RetryRule: 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
- WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
上方order导入的openfeign已经整合了ribbon,可以直接启几个不同端口的product服务访问看后台是负载均衡的;
修改默认的负载均衡规则,主要有两种方式,需要对服务消费者进行改动:
方式1.yml配置中添加如下内容
client-product:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
写法规则为:
服务名:
ribbon:
NFLoadBalancerRuleClassName: 规则类名路径
方式2.添加一个配置文件,往Spring容器中注入指定的IRule实现类,这里用RandomRule规则;
package com.cco.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RibbonConfig {
@Bean
public IRule randomRule(){
return new RandomRule();
}
}
Hystrix熔断器/断路器
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,一条高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个服务预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会长时间、不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩;
目标
微服务架构是高度分布式的,必须防止单个服务(或服务实例)中的问题级联暴露给服务消费者
服务雪崩
一个服务失败导致整条链路的服务都失败的情形称为服务雪崩
在分布式系统中,如果服务调用层数很多,那么其中一个环节如果除了错,整个服务都会抛出异常,这就是雪崩效应;A为服务提供者, B为A的服务调用者, C和D是B的服务调用者. 当A的不可用,引起B的不可用,并将不可用逐渐放大C和D时, 服务雪崩就形成了;
服务降级
- 当下游服务因某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
- 当下游服务因某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!
服务熔断
- 当下游服务因某种原因突然变得不可用或响应过慢,上游服务为保证自己整体服务的可用性,不再继续调用目标服务,直接返回,快速释放资源,如果目标服务情况好转则恢复调用
使用Hystrix
Hystrix已经被Feign整合,引用了Feign组件的项目,可以直接使用Hystrix,因为熔断只是作用在服务调用这一端,所以使用Hystrix只需要在服务消费端进行处理。
1.在yml文件启用Hystrix
feign:
hystrix:
enabled: true
2.创建回调类,实现Feign调用接口
package com.cco.rollback;
import com.cco.remote.ProductRemote;
import org.springframework.stereotype.Component;
@Component
public class ProductRemoteFallBack implements ProductRemote {
@Override
public String getProductById(Integer productId) {
System.out.println("进入熔断处理......ProductRemoteFallBack");
System.out.println("熔断器Hystrix接收的参数productId:"+productId);
return "商品未获取到";
}
}
3.Feign客户端调用处添加fallback属性,指向回调类
package com.cco.remote;
import com.cco.rollback.ProductRemoteFallBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "client-product",fallback = ProductRemoteFallBack.class)
public interface ProductRemote {
@GetMapping("/getProductById")
String getProductById(@RequestParam("productId") Integer productId);
}
在product服务的方法里加个异常代码,比如:int i = 1/0;
启动服务,访问:http://localhost:8082/getOrderDetail
异常进行了熔断处理。
Config配置中心
待定
zuul网关(路由)
在微服务框架中,每个对外服务都是独立部署的,对外的api或者服务地址都不是不尽相同的。对于内部而言,很简单,通过注册中心自动感知即可。但我们大部分情况下,服务都是提供给外部系统进行调用的,不可能同享一个注册中心。同时一般上内部的微服务都是在内网的,和外界是不连通的。而且,就算我们每个微服务对外开放,对于调用者而言,调用不同的服务的地址或者参数也是不尽相同的,这样就会造成消费者客户端的复杂性,所以根据请求的url不同,路由到不同的服务上去,同时入口统一了,还能进行统一的身份鉴权、日志记录、分流等操作。
案例
最终项目结构如下
1.创建项目
在父项目cloud-parent的基础上,创建一个maven子项目cloud-gateway-zuul,引入如下依赖
<dependencies>
<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>
</dependencies>
2.yml配置
spring:
application:
name: gateway-zuul
server:
port: 6677
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8866/eureka/
3.启动类
添加@EnableZuulProxy注解启用Zuul
package com.cgz;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class);
}
}
测试
启动全部微服务,按如下路径访问网关,可以看到成功的将请求路由到了指定的服务接口
http://127.0.0.1:6677/client-product/getProductById?productId=666
路由规则
springcloud zuul在整合了Eureka之后,具备默认的服务器功能,当zuul项目启动并注册到Eureka之后,服务网关zuul发现Eureka上面注册的服务,这时候Zuul就会创建处对应的路由规则。
如:转发到 client-product 服务的请求规则为: /client-product/**
转发到 client-order 服务的请求规则为: /client-order/**
添加路由规则
在cloud-gateway-zuul网关项目的yml配置中添加以下内容
zuul:
routes:
client-product: /product/**
client-order: /order/**
添加后的效果即为:访问网关的请求路径中如果含有product,则路由到client-product的服务下
http://127.0.0.1:6677/product/getProductById?productId=666
Zuul网关(过滤)
zuul大部分功能都是通过过滤器来实现的,zuul定义了4种标准的过滤器类型,
- pre:可以在请求被路由之前调用
- routing: 路由请求时被调用
- post:在routing和error过滤器之后被调用
- error:处理请求时发生错误时被调用
除了默认的过滤器类型,zuul还允许创建自定义的过滤器类型。
zuul请求的生命周期如下,描述了各种类型过滤器的执行顺序
product服务加个方法,在地址中加个view
@GetMapping("view/getProductById2")
public String getProductById2(Integer productId){
System.out.println("服务提供者》根据ID查询商品:"+productId);
return "这是你要的商品"+productId;
}
zuul服务里创建过滤器,用来拦截含view的服务地址
package com.cgz.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class TokenFilter extends ZuulFilter {
/**
*
* 过滤的类型
* 返回pre为前置过滤,表示会在请求被路由之前执行
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 过滤器执行顺序
* 数字越小,优先级越高
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 该过滤器是否需要被执行
* true-执行
* false-不执行
* @return
*/
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//获取请求路径
String url = request.getRequestURL().toString();
System.out.println("用户请求地址:"+url);
//路径中包含"/view/",则执行该过滤器
return url.contains("/view/");
}
/**
* 过滤器具体处理
* @return
*/
@Override
public Object run() throws ZuulException {
System.out.println("ZuulFilter--服务网关过滤器开始处理------");
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String accessToken = request.getHeader("token");
if (StringUtils.isEmpty(accessToken)) {
ctx.setSendZuulResponse(false); //过滤该请求,不对其进行路由
ctx.setResponseStatusCode(401); //响应状态码
ctx.getResponse().setContentType("text/html;charset=utf-8"); //响应内容编码
ctx.setResponseBody("401!访问拒绝,请先登录"); //响应内容
}
return null;
//官网这里最后返回null
}
}
启动微服务,
访问http://127.0.0.1:6677/product/getProductById?productId=666
不含view,可以正常访问
访问http://127.0.0.1:6677/product/view/getProductById2?productId=666
如图,请求被网关中的过滤器拦截到了
更多推荐
所有评论(0)