四、实现负载均衡(Ribbon)
4.1、认识负载均衡
负载均衡类型
负载均衡两种类型:
客户端负载均衡:Ribbon
服务端负载均衡:Nginx
对于客户端:在调用的时能够看到对应的所有节点,我依次去调用即可,由我调用的人去考虑问题。
一般用于服务之间内部进行调用会使用Ribbon,而不是nginx(使用的话无非将我们的链路增长),
对于服务端:所有的请求先来到nginx,由它来进行分发转发
像用户来发送请求,统一Nginx收取进行分发。
认识策略
负载均衡策略三种
RandomRule:表示随机策略,每次都会随机调用
RoundRobinRule:表示轮询策略,表示一个个进行调用,比较好的能够平衡各个节点压力
ResponseTimeWeightedRule加权:根据每一个Server的平均响应时间动态加权。也就是说会进行评估,例如三个节点,有一个节点机器比较差,后两个节点机器比较好,使用该策略就会去进行动态评估,机器不好的会少给几个请求
4.2、实操配置ribbon
如何配置策略?
通过在yaml中添加Ribbon.NFLoadBalancerRuleClassName进行配置。
实操配置:例如当前course-price服务要远程调用course-list服务,此时我们可以进行配置ribbon配置策略
course-price服务的配置内容:
# 服务id=>命名空间=>配置属性 course-list: # 当前远程调用的服务application名 ribbon: NFLoadBanlancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule # 轮询策略
course-list服务配置:
ribbon: eureka: enabled: true
五、断路器Hystrix
5.1、认识断路器
进行远程调用的时候出现了延迟或者服务不可用就能够起到保护的功能。
当某一个单元发生故障的时候,就可以利用断路器把它隔离出去。帮助我们快速优雅的构建断路的功能,当访问异常的时候就返回默认的响应,而不用长时间的让用户去等待,避免在分布式系统中出现故障导致蔓延。
下面是某个请求调用多个服务时某个服务出现故障导致延迟一定时间;右边则是多个请求对同一个服务进行请求时,若是该服务故障则会导致连接数量急剧增多,容易导致服务器瘫痪。
5.2、集成Hystrix
前提:一般的话是集成好了Feign之后,我们才会对某个服务使用断路器。
配置:
1、引入依赖
<!-- 引入断路器模块 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <!-- SpringCloud指定版本,springboot2.1.x --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR5</version> <type>pom</type> <scope>import</scope> </dependency>
2、yaml打开断路器
feign: hystrix: enabled: true # 远程调用断路器
3、springboot启动器添加注解
@EnableCircuitBreaker //打开断路器
4、对默认情况进行配置,找到指定client接口,对应的注解配置fallback指定类,该类则是调用异常默认返回的方法,这里写的就是发送错误时的方法
//指定fallback,也就是当远程调用的服务不可用时对应接口的默认返回 @FeignClient(value = "course-list",fallback = CourseListClientHystrix.class) public interface CourseListClient { @GetMapping("/course/list") List<Course> getList(); } //1、同样实现CourseListClient接口,重写其中的对应的接口方法,该方法用于作为服务不可调用时返回的默认值 @Component public class CourseListClientHystrix implements CourseListClient{ @Override public List<Course> getList() { Course course = new Course(); course.setId(0); course.setCourseId(0); course.setName("Java从入门到精通"); course.setValid(0); return Collections.singletonList(course); } }
效果:一旦getListz()接口远程调用能够访问就会返回调用远程接口返回的值,若是对应服务不可用或者其他问题时就会返回我们指定接口的默认值。
六、网关Zuul(统一管理)
为什么需要网关?
对于分布式远程调用,服务与服务之间都能够进行互相调用,若是我们在某个服务上做鉴权或者说单个自己开个服务用于鉴权,都是会有一定的问题的,单个服务上做鉴权,那么其他服务都需要额外再写一套;单个服务做鉴权,你还要考虑转发的问题。此时就会出现签名校验、登陆校验冗余等问题。
此时就出现了Zuul网关,用于统一管理多个服务,想要访问某个服务必须先要通过网关之后才能够访问到服务。
6.1、springboot集成Zuul网关
引入依赖:
<dependencies> <!-- 用于服务注册 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- zuul网关 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
添加一个启动器类:
import org.springframework.boot.SpringApplication; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; /** * @ClassName ZuulGatewayApplication * @Author ChangLu * @Date 2021/10/5 8:27 * @Description 网关启动类 */ @EnableZuulProxy //开启Zuul代理 @SpringCloudApplication //包含springboot启动器注解、开启服务注册发现、开启断路器 public class ZuulGatewayApplication { public static void main(String[] args) { SpringApplication.run(ZuulGatewayApplication.class,args); } }
配置文件:
spring: application: name: zuul-gateway # 注册到服务中心的应用名称 server: port: 9000 # 指定注册的服务中心地址,一般与eureka-server中配置的对应,此时该服务启动就会将自己注册到服务中心 eureka: client: service-url: defaultZone: http://localhost:8000/eureka/
此时我们就已经搭建了一个网关Zuul的服务,我们将项目启动:可以看到zuul网关也被注册到服务中心去了
此时我们就可以通过这个zuul网关来进行访问其他两个服务:
①http://localhost:9000/course-list/course/list:9000端口(网关)、服务名(application name)、uri接口路径
②http://localhost:9000/course-price/courseprice/courseprice:9000端口(网关)、服务名(application name)、uri接口路径
我们可以看到通过走9000端口的网关服务也能够去访问到同在注册中心的其他服务,在这里我们是没有对zuul服务的访问前缀作限制,这里就是/。
再说明一下,不走网关的话像之前一样只要你知道对应的ip地址端口号以及服务名你同样能够访问到该服务,这都是没有影响的:
6.2、配置路由地址(针对于服务)
在6.1中,我们通过搭建一个zuul网关服务将其注册到服务中心,此时我们就可以通过该网关来访问到其他的服务,默认访问官网服务是没有对应的前缀/xxx的,并且访问指定的服务需要根据在服务中心上注册的application name才能够进行访问到指定的服务,下面来进行配置zuul网关服务的前缀url以及对应其他服务的路由地址。
下面是在zuul服务的yaml配置中的内容:
# zuul的相关配置 zuul: prefix: /changlu # 访问zuul网关的前缀url(由原来的/ => /changlu) routes: # 路由配对,下面有两组服务 course-list: path: /list/** # 2、指定的服务路由转换 (例如由原来的/course-list => /list) service-id: course-list # 1、指定的服务名(application name):课程服务 course-price: path: /price/** service-id: course-price
配置好之后看效果:
# 配置之前访问服务地址 http://localhost:9000/course-list/course/list http://localhost:9000/course-price/courseprice/courseprice # 配置之后访问服务地址 http://localhost:9000/changlu/list/course/list http://localhost:9000/changlu/price/courseprice/courseprice
同样说明一下,不走网关直接访问服务也是ok的!
6.3、实现网关过滤器(统计请求时长)
介绍过滤器
首先介绍下面几个过滤器:
pre 过滤器在路由请求之前运行
route 过滤器可以处理请求的实际路由
post 路由请求后运行过滤器
error 如果在处理请求的过程中发生错误,则过滤器将运行
实现pre、post过滤器,统计请求执行时间
PreRequestFilter:前置请求过滤器
import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.stereotype.Component; /** * @ClassName PreRequestFilter * @Author ChangLu * @Date 2021/10/5 9:26 * @Description 前置请求过滤器 */ @Component public class PreRequestFilter extends ZuulFilter { @Override public String filterType() { //过滤器类型 return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { //过滤器顺序 return 0; //0-1000 从小到大顺序依次执行,这里表示第一个执行 } @Override public boolean shouldFilter() { return true; //是否启动过滤器 } //具体过滤器中执行的方法 @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); currentContext.set("startTime",System.currentTimeMillis()); System.out.println("前缀过滤器pre已经记录时间"); return null; } }
PostRequestFilter:后置器请求过滤器
import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.stereotype.Component; /** * @ClassName PostRequestFilter * @Author ChangLu * @Date 2021/10/5 9:31 * @Description 后置请求处理器 */ @Component public class PostRequestFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.POST_TYPE; } /** * filter执行顺序,值越小优先级越高 * 官方推荐使用x-1方式优先排序 * @return */ @Override public int filterOrder() { return FilterConstants.SEND_RESPONSE_FILTER_ORDER-1; //响应过滤器为1000,这里-1表示该过滤器越优先执行 } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); Long startTime = (Long) currentContext.get("startTime"); Long duration = System.currentTimeMillis() - startTime; String uri = currentContext.getRequest().getRequestURI(); System.out.println("uri:"+uri+",处理时长为:"+duration); return null; } }
效果:统计访问一个请求时处理的时长!