什么是Open Feign
有了 Eureka,RestTemplate,Ribbon我们就可以愉快地进行服务间的调用了,但是使用RestTemplate还是不方便,我们每次都要进行这样的调用。
@Autowiredprivate RestTemplate restTemplate;// 这里是提供者A的ip地址,但是如果使用了 Eureka 那么就应该是提供者A的名称private static final String SERVICE_PROVIDER_A = "http://localhost:8081";@PostMapping("/judge")public boolean judge(@RequestBody Request request) { String url = SERVICE_PROVIDER_A + "/service1"; // 是不是太麻烦了???每次都要 url、请求、返回类型的 return restTemplate.postForObject(url, request, Boolean.class);}
这样每次都调用RestRemplate的API是否太麻烦,我能不能像调用原来代码一样进行各个服务间的调用呢?
聪明的小朋友肯定想到了,那就用映射呀,就像域名和IP地址的映射。我们可以将被调用的服务代码映射到消费者端,这样我们就可以“无缝开发”啦。
OpenFeign 也是运行在消费者端的,使用 Ribbon 进行负载均衡,所以 OpenFeign 直接内置了 Ribbon。在导入了Open Feign之后我们就可以进行愉快编写 Consumer端代码了。
// 使用 @FeignClient 注解来指定提供者的名字@FeignClient(value = "eureka-client-provider")public interface TestClient { // 这里一定要注意需要使用的是提供者那端的请求相对路径,这里就相当于映射了 @RequestMapping(value = "/provider/xxx", method = RequestMethod.POST) CommonResponse<List<Plan>> getPlans(@RequestBody planGetRequest request);}
然后我们在Controller就可以像原来调用Service层代码一样调用它了。
@RestControllerpublic class TestController { // 这里就相当于原来自动注入的 Service @Autowired private TestClient testClient; // controller 调用 service 层代码 @RequestMapping(value = "/test", method = RequestMethod.POST) public CommonResponse<List<Plan>> get(@RequestBody planGetRequest request) { return testClient.getPlans(request); }}
必不可少的Hystrix
在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。
总体来说[Hystrix]就是一个能进行熔断和降级的库,通过使用它能提高整个系统的弹性。
那么什么是 熔断和降级 呢?再举个,此时我们整个微服务系统是这样的。服务A调用了服务B,服务B再调用了服务C,但是因为某些原因,服务C顶不住了,这个时候大量请求会在服务C阻塞。
服务C阻塞了还好,毕竟只是一个系统崩溃了。但是请注意这个时候因为服务C不能返回响应,那么服务B调用服务C的的请求就会阻塞,同理服务B阻塞了,那么服务A也会阻塞崩溃。
请注意,为什么阻塞会崩溃。因为这些请求会消耗占用系统的线程、IO 等资源,消耗完你这个系统服务器不就崩了么。
这就叫服务雪崩。妈耶,上面两个熔断和降级你都没给我解释清楚,你现在又给我扯什么服务雪崩?
别急,听我慢慢道来。更多的关于设计,原理知识点的问题,可以在码匠笔记后台回复实践获取。
不听我也得讲下去!
所谓熔断就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过断路器直接将此请求链路断开。
也就是我们上面服务B调用服务C在指定时间窗内,调用的失败率到达了一定的值,那么[Hystrix]则会自动将 服务B与C 之间的请求都断了,以免导致服务雪崩现象。
其实这里所讲的熔断就是指的[Hystrix]中的断路器模式,你可以使用简单的@[Hystrix]Command注解来标注某个方法,这样[Hystrix]就会使用断路器来“包装”这个方法,每当调用时间超过指定时间时(默认为1000ms),断路器将会中断对这个方法的调用。
当然你可以对这个注解的很多属性进行设置,比如设置超时时间。
但是,我查阅了一些博客,发现他们都将熔断和降级的概念混淆了,以我的理解,降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。这也就对应着[Hystrix]的后备处理模式。
你可以通过设置fallbackMethod来给一个方法设置备用的代码逻辑。比如这个时候有一个热点新闻出现了,我们会推荐给用户查看详情,然后用户会通过id去查询新闻的详情,但是因为这条新闻太火了,大量用户同时访问可能会导致系统崩溃,那么我们就进行服务降级,一些请求会做一些降级处理比如当前人数太多请稍后查看等等。
我在阅读 《Spring微服务实战》这本书的时候还接触到了一个舱壁模式的概念。在不使用舱壁模式的情况下,服务A调用服务B,这种调用默认的是使用同一批线程来执行的,而在一个服务出现性能问题的时候,就会出现所有线程被刷爆并等待处理工作,同时阻塞新请求,最终导致程序崩溃。而舱壁模式会将远程资源调用隔离在他们自己的线程池中,以便可以控制单个表现不佳的服务,而不会使该程序崩溃。
具体其原理我推荐大家自己去了解一下,本篇文章中对舱壁模式不做过多解释。当然还有[Hystrix]仪表盘,它是用来实时监控[Hystrix]的各项指标信息的,这里我将这个问题也抛出去,希望有不了解的可以自己去搜索一下。
微服务网关:zuul
1、什么是zuul?
ZUUL 是从设备和 web 站点到 Netflix 流应用后端的所有请求的前门。作为边界服务应用,ZUUL 是为了实现动态路由、监视、弹性和安全性而构建的。它还具有根据情况将请求路由到多个 Amazon Auto Scaling Groups(亚马逊自动缩放组,亚马逊的一种云计算方式) 的能力
在上面我们学习了 Eureka 之后我们知道了服务提供者是消费者通过[Eureka] Server进行访问的,即[Eureka] Server是服务提供者的统一入口。那么整个应用中存在那么多消费者需要用户进行调用,这个时候用户该怎样访问这些消费者工程呢?当然可以像之前那样直接访问这些工程。但这种方式没有统一的消费者工程调用入口,不便于访问与管理,而 Zuul 就是这样的一个对于消费者的统一入口。如果学过前端的肯定都知道 Router 吧,比如 Flutter 中的路由,Vue,React中的路由,用了 Zuul 你会发现在路由功能方面和前端配置路由基本是一个理。我偶尔撸撸 Flutter。
大家对网关应该很熟吧,简单来讲网关是系统唯一对外的入口,介于客户端与服务器端之间,用于对请求进行鉴权、限流、路由、监控等功能。
没错,网关有的功能,Zuul基本都有。而Zuul中最关键的就是路由和过滤器了,在官方文档中Zuul的标题就是
Router and Filter : Zuul
本来想给你们复制一些代码,但是想了想,因为各个代码配置比较零散,看起来也比较零散,我决定还是给你们画个图来解释吧。比如这个时候我们已经向[Eureka] Server注册了两个Consumer、三个Provicer,这个时候我们再加个Zuul网关应该变成这样子了。
emmm,信息量有点大,我来解释一下。关于前面的知识我就不解释了 。首先,Zuul需要向 Eureka 进行注册,注册有啥好处呢?你傻呀,Consumer都向[Eureka] Server进行注册了,我网关是不是只要注册就能拿到所有Consumer的信息了?拿到信息有什么好处呢?我拿到信息我是不是可以获取所有的Consumer的元数据(名称,ip,端口)?拿到这些元数据有什么好处呢?拿到了我们是不是直接可以做路由映射?比如原来用户调用Consumer1的接口localhost:8001/studentInfo/update这个请求,我们是不是可以这样进行调用了呢?localhost:9000/consumer1/studentInfo/update呢?你这样是不是恍然大悟了?
这里的url为了让更多人看懂所以没有使用 restful 风格。
上面的你理解了,那么就能理解关于Zuul最基本的配置了,看下面。
server: port: 9000eureka: client: service-url: # 这里只要注册 Eureka 就行了 defaultZone: http://localhost:9997/eureka
然后在启动类上加入@EnableZuulProxy注解就行了。没错,就是那么简单。
Zuul还可以指定屏蔽掉的路径 URI,即只要用户请求中包含指定的 URI 路径,那么该请求将无法访问到指定的服务。通过该方式可以限制用户的权限。
zuul: ignore-patterns: **/auto/**
这样关于 auto 的请求我们就可以过滤掉了。
** 代表匹配多级任意路径*代表匹配一级任意路径
默认情况下,像 Cookie、Set-Cookie 等敏感请求头信息会被 zuul 屏蔽掉,我们可以将这些默认屏蔽去掉,当然,也可以添加要屏蔽的请求头。
如果说,路由功能是Zuul的基操的话,那么过滤器就是Zuul的利器了。毕竟所有请求都经过网关(Zuul),那么我们可以进行各种过滤,这样我们就能实现限流,灰度发布,权限控制等等。
要实现自己定义的Filter我们只需要继承ZuulFilter然后将这个过滤器类以@Component注解加入 Spring 容器中就行了。在给你们看代码之前我先给你们解释一下关于过滤器的一些注意点。
过滤器类型:Pre、Routing、Post。前置Pre就是在请求之前进行过滤,Routing路由过滤器就是我们上面所讲的路由策略,而Post后置过滤器就是在Response之前进行过滤的过滤器。你可以观察上图结合着理解,并且下面我会给出相应的注释。
// 加入Spring容器@Componentpublic class PreRequestFilter extends ZuulFilter { // 返回过滤器类型 这里是前置过滤器 @Override public String filterType() { return FilterConstants.PRE_TYPE; } // 指定过滤顺序 越小越先执行,这里第一个执行 // 当然不是只真正第一个 在Zuul内置中有其他过滤器会先执行 // 那是写死的 比如 SERVLET_DETECTION_FILTER_ORDER = -3 @Override public int filterOrder() { return 0; } // 什么时候该进行过滤 // 这里我们可以进行一些判断,这样我们就可以过滤掉一些不符合规定的请求等等 @Override public boolean shouldFilter() { return true; } // 如果过滤器允许通过则怎么进行处理 @Override public Object run() throws ZuulException { // 这里我设置了全局的RequestContext并记录了请求开始时间 RequestContext ctx = RequestContext.getCurrentContext(); ctx.set("startTime", System.currentTimeMillis()); return null; }}// lombok的日志@Slf4j// 加入 Spring 容器@Componentpublic class AccessLogFilter extends ZuulFilter { // 指定该过滤器的过滤类型 // 此时是后置过滤器 @Override public String filterType() { return FilterConstants.POST_TYPE; } // SEND_RESPONSE_FILTER_ORDER 是最后一个过滤器 // 我们此过滤器在它之前执行 @Override public int filterOrder() { return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } // 过滤时执行的策略 @Override public Object run() throws ZuulException { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); // 从RequestContext获取原先的开始时间 并通过它计算整个时间间隔 Long startTime = (Long) context.get("startTime"); // 这里我可以获取HttpServletRequest来获取URI并且打印出来 String uri = request.getRequestURI(); long duration = System.currentTimeMillis() - startTime; log.info("uri: " + uri + ", duration: " + duration / 100 + "ms"); return null; }}
上面就简单实现了请求时间日志打印功能,你有没有感受到Zuul过滤功能的强大了呢?没有?好的,那我们再来。
当然不仅仅是令牌桶限流方式,Zuul只要是限流的活它都能干,这里我只是简单举个。
我先来解释一下什么是令牌桶限流吧。
首先我们会有个桶,如果里面没有满那么就会以一定固定的速率会往里面放令牌,一个请求过来首先要从桶中获取令牌,如果没有获取到,那么这个请求就拒绝,如果获取到那么就放行。
下面我们就通过Zuul的前置过滤器来实现一下令牌桶限流。
@Component@Slf4jpublic class RouteFilter extends ZuulFilter { // 定义一个令牌桶,每秒产生2个令牌,即每秒最多处理2个请求 private static final RateLimiter RATE_LIMITER = RateLimiter.create(2); @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return -5; } @Override public Object run() throws ZuulException { log.info("放行"); return null; } @Override public boolean shouldFilter() { RequestContext context = RequestContext.getCurrentContext(); if(!RATE_LIMITER.tryAcquire()) { log.warn("访问量超载"); // 指定当前请求未通过过滤 context.setSendZuulResponse(false); // 向客户端返回响应码429,请求数量过多 context.setResponseStatusCode(429); return false; } return true; } }
这样我们就能将请求数量控制在一秒两个,有没有觉得很酷?
Zuul的过滤器的功能肯定不止上面我所实现的两种,它还可以实现权限校验,包括我上面提到的灰度发布等等。
当然,Zuul作为网关肯定也存在单点问题,如果我们要保证Zuul的高可用,我们就需要进行Zuul的集群配置,这个时候可以借助额外的一些负载均衡器比如Nginx。
在Spring Cloud配置管理:config
当我们的微服务系统开始慢慢地庞大起来,那么多Consumer、Provider、[Eureka] Server、Zuul系统都会持有自己的配置,这个时候我们在项目运行的时候可能需要更改某些应用的配置,如果我们不进行配置的统一管理,我们只能去每个应用下一个一个寻找配置文件然后修改配置文件再重启应用。
首先对于分布式系统而言我们就不应该去每个应用下去分别修改配置文件,再者对于重启应用来说,服务无法访问所以直接抛弃了可用性,这是我们更不愿见到的。
那么有没有一种方法既能对配置文件统一地进行管理,又能在项目运行时动态修改配置文件呢?
那就是我今天所要介绍的Spring Cloud Config。能进行配置管理的框架不止Spring Cloud Config一种,大家可以根据需求自己选择(disconf,阿波罗等等)。而且对于Config来说有些地方实现的不是那么尽人意。
Spring Cloud Config为分布式系统中的外部化配置提供服务器和客户端支持。使用Config服务器,可以在中心位置管理所有环境中应用程序的外部属性。
简单来说,Spring Cloud Config就是能将各个 应用/系统/模块 的配置文件存放到统一的地方然后进行管理(Git 或者 SVN)。
你想一下,我们的应用是不是只有启动的时候才会进行配置文件的加载,那么我们的Spring Cloud Config就暴露出一个接口给启动应用来获取它所想要的配置文件,应用获取到配置文件然后再进行它的初始化工作。就如下图。
当然这里你肯定还会有一个疑问,如果我在应用运行时去更改远程配置仓库(Git)中的对应配置文件,那么依赖于这个配置文件的已启动的应用会不会进行其相应配置的更改呢?答案是不会的。
什么?那怎么进行动态修改配置文件呢?这不是出现了配置漂移吗?你个渣男,你又骗我!
别急嘛,你可以使用Webhooks,这是 github提供的功能,它能确保远程库的配置文件更新后客户端中的配置信息也得到更新。噢噢,这还差不多。我去查查怎么用。慢着,听我说完,Webhooks虽然能解决,但是你了解一下会发现它根本不适合用于生产环境,所以基本不会使用它的。
而一般我们会使用Bus消息总线 +Spring Cloud Config进行配置的动态刷新。
用于将服务和服务实例与分布式消息系统链接在一起的事件总线。在集群中传播状态更改很有用(例如配置更改事件)。
你可以简单理解为Spring Cloud Bus的作用就是管理和广播分布式系统中的消息,也就是消息引擎系统中的广播模式。当然作为消息总线的Spring Cloud Bus可以做很多事而不仅仅是客户端的配置刷新功能。
而拥有了Spring Cloud Bus之后,我们只需要创建一个简单的请求,并且加上@ResfreshScope注解就能进行配置的动态修改了,下面我画了张图供你理解。
总结
这篇文章中我带大家初步了解了Spring Cloud的各个组件,他们有
如果你能这个时候能看懂下面那张图,也就说明了你已经对Spring Cloud微服务有了一定的架构认识。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!