微服务重构步骤(微服务学习笔记)

一、认识微服务1、微服务技术栈

微服务重构步骤(微服务学习笔记)(1)

相关技术:

微服务治理:SpringCloud包含的技术

注册发现、远程调用、负载均衡、配置管理、网关路由、系统保护、流量控制、服务授权、熔断降级、分布式事务、TCC模型、AT模型、Seata

异步通信:

MQ消息模型、SpringAMQP、消息堆积问题、消息可靠性、仲裁队列、延迟队列、镜像集群、数据持久化

缓存技术:

缓存穿透(雪崩)、SpringDataReidis、Redis主从复制、OpenResty、缓存数据同步、Nginx本地缓存、Redis持久化、多级缓存分层、Redis分片集群、Lua脚本、Redis数据结构

分布式搜索技术:

DSL语句、ES集群、Rest API、集群脑裂、竞价排名、聚合统计、自动补全、地理坐标、拼音分词

DevOps持续集成:

Dockerfile、DockerCompose、GrayLog、Jenkins、SkyWalking、Docker使用、Kubernetes

技术的阶段分类

1.基础技术

微服务治理:springcloud包含的技术

eureka、Nacos、Open Feign、网关Gateway、配置中心Nacos

DevOps:持续集成

Docker原理、Docker使用、Dockerfile、DockerCompose

异步通信:

同步和异步、MQ技术选型、SpringAMQP、消息者限流

分布式搜索:

DSL语法、竞价排名、HighLevelClient、地理搜索、拼音搜索、聚合统计、自动补全、分片集群

2.高级技术

微服务保护:

流量控制、系统保护、熔断降级、服务授权

分布式事务:

XA模式、TCC模式、AT模式、Saga模式

分布式缓存:

数据持久化、Redis主从集群、哨兵机制、Redis分片集群

多级缓存:

多级缓存分层、Nginx缓存、Redis缓存、Canal数据同步

可靠消息服务:

消息三方确认、惰性队列、延迟队列、镜像集群、仲裁队列

3.理论知识

Nacos源码:

Nacos的服务发现原理、Nacos服务注册原理、Nacos心跳机制、Nacos与Eureka差异

Sentine源码:

Sentinel滑动窗口算法、令牌桶算法、漏桶算法

Redis热点问题:

分布式锁问题、缓存穿透、缓存击穿、缓存雪崩

技术很多慢慢来

2、服务架构演变

在了解微服务之前先来看一下项目的几种架构

2-1. 单体架构

单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署

微服务重构步骤(微服务学习笔记)(2)

优点:

架构简单

部署成本低

缺点:

耦合度高

说明:如果项目庞大还是单体架构,光是编译打包就会占用大量时间。模块之间的代码你中有我我中有你,边界模糊。可能你在修改一处代码,很多地方的代码也会受影响,轻易不敢动代码

2-2. 分布式架构

分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,成为一个服务。

微服务重构步骤(微服务学习笔记)(3)

优点:

  • 降低服务耦合
  • 有利于服务升级拓展

拆分后的服务治理问题:

服务拆分粒度如何?

服务集群地址如何维护?

服务之间如何实现远程调用?

服务健康状态如何感知?

目前解决上面问题最常见的方案——微服务

2-3. 微服务

微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:

微服务重构步骤(微服务学习笔记)(4)

1)单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发

说明:上面分布式示例图中,将项目拆分成 支付模块、用户模块、商品模块、订单模块,如果用在像拼多多这样的电商项目中太过粗糙,比如:用户模块还可以拆分会员模块(不同会员享受不同折扣)、积分模块等。使每一个模块只做单一职责,好处是每个服务业务更少了,影响的范围更小了,这就是单一职责

2)面向服务:微服务对外暴露业务接口

说明:单一职责中将项目拆分成一个个单一职责的模块后,那么模块之间的调用就需要暴露接口来实现了。比如上面积分模块必须暴露出查询积分的接口,将来用户模块才能远程调用到积分模块的查询功能

3)自治:团队独立、技术独立、数据独立、部署独立

说明:团队独立,每个模块拥有专门的前端、后端、测试、运维、沟通更方便,比较像敏捷开发。技术独立,因为每个服务模块相对独立,可以根据业务需求选择更适合自己的技术,可以选择自己更擅长的技术。数据独立,每个服务可以拥有自己独立的数据库,实现了数据的解耦。部署独立,可以实现独立部署

4)隔离性强:服务调用做好隔离、容错、降级、避免出现级联问题

说明:由于是面向服务,那么服务之间相互调用时,提供者挂了,可能会对消费者产生影响,为了避免这些影响就需要隔离,隔离故障,做好容错,避免提供者宕机而导致消费者也宕机的级联影响

总结:

单体架构特点:

简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统

分布式架构特点:

松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝

微服务:一种良好的分布式架构方案

优点:拆分粒度更小,服务更独立,耦合度更低

缺点:结构非常复杂,运维、监控、部署难度提高

3、微服务技术对比

3-1. 微服务结构

微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。国内最知名的就是SpringCloud和阿里巴巴的Dubbo

微服务重构步骤(微服务学习笔记)(5)

简单介绍上图:

不管是哪一种技术,服务之间的调用关系错综复杂,一定需要去维护,但不可能靠人去维护调用,太复杂,所以在微服务里都会有一个注册中心,它可以去维护微服务中每一个节点的信息,并且去监控这些节点的状态

如果将来微服务越来越多,有些配置需要去修改,手动的去微服务中修改显然不现实,所以在微服务中往往会有一个配置中心,可以统一的去管理整个微服务群的配置,如果有变更,我们也可以利用通知的方式,让对应的服务监控到配置的变化,从而实现配置的热更新。

在微服务部署上线用户访问时,如此之多的微服务用户怎么知道访问哪一个,微服务群往往会有一个网关,用户访问它,然后网关通过路由到微服务群,在路由中还可以做负载均衡

路由和服务之间的调用,我们还要做好容错的处理,避免因为服务故障造成级联失败,做好保护、降级、隔离等等措施

3-2. 微服务技术对比

微服务重构步骤(微服务学习笔记)(6)

简单介绍:

Dubbo技术早在2012年左右就由阿里巴巴开源出来了,那时微服务技术并未普及,Dubbo也不是为了处理微服务而发明的,它的核心就是服务的远程调用,所以Dubbo的技术体系并不是特别完整。核心只有注册中心和服务远程调用,而且注册中心也不是自己实现的,而是使用zookeeper、Redis等,这些并不是专业的注册中心,Redis是做缓存的,zookeeper是做集群管理的,所以并不能做到完善的注册中心功能

springCloud并不是发明而是整合,它把全球各公司开源的微服务技术整合进来了,而后形成一套完整的微服务技术体系,成了一个集大成者。

SpringCloudAlibaba这套技术栈是相当于SpringCloud的一部分,因为它实现了SpringCloud的接口规范,并整合进了Dubbo,实现了自己的注册中心Nacos,即支持Dubbo调用也支持Feign调用,SpringCloudAlibaba兼容了SpringCloud和Dubbo两种结构,也是越来越火的原因

3-3. 企业需求

在企业中微服务的使用大致分以下几种

微服务重构步骤(微服务学习笔记)(7)

4、SpringCloud简介

SpringCloud

1、SpringCloud是目前国内使用最广泛的微服务框架。官网地址:Spring Cloud

2、SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:

微服务重构步骤(微服务学习笔记)(8)

那么为什么人们不使用微服务开源技术,而使用SpringCloud?因为SpringCloud是基于SpringBoot的,而SpringBoot最擅长的是自动装配,SpringCloud就是把官方原生开源的组件给整合进来了,并且基于Spring Boot做了自动装配,而你可以拿过来就用,无需复杂配置

3、SpringCloud和SpringBoot的版本兼容关系:

微服务重构步骤(微服务学习笔记)(9)

二、服务拆分及远程调用1、服务拆分

服务拆分注意事项

不同微服务,不要重复开发相同业务

微服务数据独立,不要访问其他微服务的数据库

微服务可以将自己的业务暴露为接口,供其他微服务调用

服务拆分说起来很简单,一个单体架构按照功能模块进行拆分,变成多个服务就行了

微服务重构步骤(微服务学习笔记)(10)

以订单模块为例,查询订单,需要访问数据库,再想获取用户详情与商品详情的话,按照之前的开发思路,再直接查询用户与商品数据库就行了。如果用到什么就查询什么,那么拆分就没有意义了,而且用户模块与商品模块也再做这样的查询,是一种重复开发,微服务要避免重复开发。

为了做好这样的限制,同时也会有一定的要求,比如微服务的数据独立,每个模块使用自己的数据库,订单模块访问不到用户数据,从根源上杜绝了做这中耦合性的业务。

如果在做订单模块查询又想把用户和商品查询出来的话,如何去做呢?微服务拆分时还要做一件事,就是将自己的部分业务暴露为接口供其他微服务使用。

总结:

1、微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务

2、微服务可以将业务暴露为接口,供其他微服务使用

3、不同微服务都应该有自己独立的数据库**

2、服务间调用

远程调用demo

创建项目cloud-demo

创建用户和订单两个服务模块user-service和order-service

微服务重构步骤(微服务学习笔记)(11)

微服务重构步骤(微服务学习笔记)(12)

微服务重构步骤(微服务学习笔记)(13)

需求:在查询订单时查询用户信息

模块之间是不能直接调用方法的,只能使用远程调用

可以使用Spring框架spring-web模块中的RestTemplate类

微服务重构步骤(微服务学习笔记)(14)

微服务重构步骤(微服务学习笔记)(15)

三、Eureka注册中心1、提供者与消费者

服务调用关系

服务提供者:暴露接口给其他微服务调用

服务消费者:调用其他微服务提供的接口

提供者与消费者角色其实是相对的

一个服务可以同时是服务提供者和服务消费者

2、Eureka介绍

微服务重构步骤(微服务学习笔记)(16)

问题:首先采用硬编码的方式显然不行,并且用户服务可能会是多服务的集群,访问哪个服务?服务是否健康?等都是问题

微服务重构步骤(微服务学习笔记)(17)

消费者该如何获取服务提供者具体信息?

服务提供者启动时向eureka注册自己的信息

eureka保存这些信息

消费者根据服务名称向eureka拉取提供者信息

如果有多个服务提供者,消费者如何选择?

服务消费者利用负载均衡算法,从服务列表中挑选一个

消费者如何感知服务提供者健康状态?

服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态

eureka会更新记录服务列表信息,心跳不正常被剔除

消费者就可以拉取到最新的消息

总结

在Eureka架构中,微服务角色有两类

EurekaServer:服务端,注册中心

记录服务信息

心跳监控

EurekaClient:客户端

Provider:服务提供者,例如案例中的user-service

注册自己的信息到EurekaServer

每隔30秒向EurekaServer发送心跳

Consumer:服务消费者,例如案例中的order-service

根据服务名称从EurekaServer拉取服务列表

基于服务列表做负载均衡,选中一个微服务后发起远程调用

3、Eureka使用实例

1)搭建EurekaServer

2)将user-service、order-service注册到eureka

3)在order-service中完成服务拉取、然后通过负载均衡挑选一个服务,实现远程调用

搭建Eureka服务需要一个独立的微服务

微服务重构步骤(微服务学习笔记)(18)

1、创建项目后,引入spring-cloud-starter-netflix-eureka-server的依赖

微服务重构步骤(微服务学习笔记)(19)

2、编写启动类,添加@EnableEurekaServer

微服务重构步骤(微服务学习笔记)(20)

3、添加application.yml文件,编写下面的配置:

微服务重构步骤(微服务学习笔记)(21)

启动服务,并访问

微服务重构步骤(微服务学习笔记)(22)

这样EurekaServer就搭建完成了

进行服务注册

1、在user-service、order-service项目引入spring-cloud-starter-netflix-eureka-client

微服务重构步骤(微服务学习笔记)(23)

2、在application.yml文件,已user-service为例,编写下面的配置:

微服务重构步骤(微服务学习笔记)(24)

启动服务,并访问

也可以copy服务,模仿分布式集群效果

微服务重构步骤(微服务学习笔记)(25)

微服务重构步骤(微服务学习笔记)(26)

完成服务注册

在order-service完成服务拉取

服务拉取是基于服务名称获取服务列表,然后在服务列表做负载均衡

1、修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:

微服务重构步骤(微服务学习笔记)(27)

2、在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:

微服务重构步骤(微服务学习笔记)(28)

这样就完成了服务拉取

四、Ribbon负载均衡

微服务重构步骤(微服务学习笔记)(29)

像这种请求http://userservice/user/1,不是真正的域名ip端口,是无法请求到服务器的,所以中间一定有人拦截处理,找到真实地址,那处理的这个人就是Ribbon

1、Ribbon负载均衡原理

发起的服务请求是如何拦截的,找到这个类LoadBalancerinterceptor

微服务重构步骤(微服务学习笔记)(30)

微服务重构步骤(微服务学习笔记)(31)

他实现了ClientHttpRequestInterceptor接口,而ClientHttpRequestInterceptor就是拦截http请求的,实现了intercept方法,debug在intercept方法中断点

微服务重构步骤(微服务学习笔记)(32)

可以看到拦截的请求是http://userservice/user/1,通过getHost()就是获取主机名userservice,而他下一步就应该去找Eureka完成服务拉取,debug进入execute()方法中

微服务重构步骤(微服务学习笔记)(33)

getLoadBalancer(), 结果ILoadBalancer对象中,有allServerList,可见getLoadBalancer()通过服务名称拉取服务列表

getServer(), 就是Ribbon重要的负载均衡,获取列表后使用哪一个服务,再跟进getServer()

微服务重构步骤(微服务学习笔记)(34)

继续跟进chooseServer()

微服务重构步骤(微服务学习笔记)(35)

this.rule.choose(key);字面意思说明是根据一种规则进行负载均衡的,我们查看一下rule类型

微服务重构步骤(微服务学习笔记)(36)

ctrl H查看IRule的实现类,定义了负载均衡的几种规则

总结:

微服务重构步骤(微服务学习笔记)(37)

2、Ribbon负载均衡策略

上面可以看出Ribbon的负载均衡是IRule负载均衡规则实现的,下图为IRule接口的继承关系图

微服务重构步骤(微服务学习笔记)(38)

内置负载均衡策略 规则描述

微服务重构步骤(微服务学习笔记)(39)

了解几种负载均衡的规则,如何使用规则?

通过定义IRule实现可以修改负载均衡规则

第一种方式:

在order-service中的OrderApplication类中,定义一个新的IRule:

微服务重构步骤(微服务学习笔记)(40)

这种方案是作用于全局的,order-service不管是调用哪个服务的接口都是随机的

第二种方式

在application.yml文件中,添加新的配置也可以修改规则:

微服务重构步骤(微服务学习笔记)(41)

这种方案是针对某个服务进行配置的

3、Ribbon饥饿加载

Ribbon饥饿加载策略

先来看一个现象:

重启order-service服务,发送请求

微服务重构步骤(微服务学习笔记)(42)

微服务重构步骤(微服务学习笔记)(43)

第一次请求耗时达到了581毫秒,第二次请求32毫秒

原因:Ribbon默认是采用的懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

微服务重构步骤(微服务学习笔记)(44)

五、Nacos注册中心1、认识安装Nacos

Nacos:Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

这里以1.4.1版本为例

1、window安装:

1-1、解压nacos-server-1.4.1.zip到任意非中文目录下

微服务重构步骤(微服务学习笔记)(45)

  • bin:启动脚本
  • conf:配置文件

1-2、端口设置:

Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。

如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件application.properties中的端口:

微服务重构步骤(微服务学习笔记)(46)

1-3、启动,进入bin目录下,

startup.cmd -m standalone

微服务重构步骤(微服务学习笔记)(47)

1-4、访问地址:

默认的账号和密码都是nacos,登录

微服务重构步骤(微服务学习笔记)(48)

2、linux安装

2-1、安装JDK

Nacos依赖于JDK运行,所以Linux上也需要安装JDK才行。

2-2、解压缩:

命令解压缩安装包:

微服务重构步骤(微服务学习笔记)(49)

然后删除安装包:

微服务重构步骤(微服务学习笔记)(50)

2-3、端口设置:

与windows中类似

2-4、启动:

在nacos/bin目录中,输入命令启动Nacos:

微服务重构步骤(微服务学习笔记)(51)

2、Nacos简单介绍

在使用Nacos之前

微服务重构步骤(微服务学习笔记)(52)

  • 在springcloud组件中的Spring Cloud Commons定义了通用的接口规范,其中就有服务发现和服务注册接口,Eureka和Nacos都遵循了接口规范。
  • 所以将Eureka更换为Nacos时服务的提供者和消费者代码是不需要变化的
  • 需要修改的地方:

引入的依赖

1、在父工程中添加spring-cloud-alibaba的管理依赖,这样有关的所有依赖版本就不需要操心了

微服务重构步骤(微服务学习笔记)(53)

2、添加nacos的客户端依赖:

微服务重构步骤(微服务学习笔记)(54)

3、服务注册到Nacos

修改user-service和order-service中的application.yml文件,去掉之前的eureka地址,添加nacos地址

微服务重构步骤(微服务学习笔记)(55)

重启user-service和order-service,查看Nacos成功注册

微服务重构步骤(微服务学习笔记)(56)

发请求测试结果,之前的负载均衡配置也依然生效

3、Nacos服务分级存储模型

微服务重构步骤(微服务学习笔记)(57)

像阿里京东这样的大型互联网公司,为保护服务的安全,全国各地部署服务器,做到容灾

微服务重构步骤(微服务学习笔记)(58)

nacos做分级的意义是什么,比如上图如果在拉取服务时,杭州的order-service访问的上海的user-service,会有延迟,显然不如访问杭州本地的局域网内的user-service

  • 服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
  • 只有在本地集群不可访问时,再去访问其他集群

演示

1、下面我们创建三个user-service实例,分两个集群(杭州两个、上海一个)

微服务重构步骤(微服务学习笔记)(59)

微服务重构步骤(微服务学习笔记)(60)

2、orderservice添加到杭州HZ集群中

微服务重构步骤(微服务学习笔记)(61)

总结:

Nacos服务分级存储模型

  • 一级是服务,例如userservice
  • 二级是集群,例如杭州或上海
  • 三级是实例,例如杭州机房的某台部署了userservice的服务器
4、NacosRule负载均衡

配好集群后请求,发现杭州的orderservice没有只请求杭州的userservice,依然采用轮询方案

说明依然采用的ribbon配置的负载均衡规则,只需要修改配置文件:

微服务重构步骤(微服务学习笔记)(62)

重启orderservice服务,再发几次请求

微服务重构步骤(微服务学习笔记)(63)

微服务重构步骤(微服务学习笔记)(64)

现在orderservie只请求杭州的8082或8081的userservice了,并且在userservice集群中没有规律

说明:NacosRule采用的是先按配置的本地集群访问,然后在集群中采用随机方案

如果将8082和8083关掉会怎么样?

微服务重构步骤(微服务学习笔记)(65)

微服务重构步骤(微服务学习笔记)(66)

请求依然成功,查看一下orderservice控制台

微服务重构步骤(微服务学习笔记)(67)

一个警告,发生了跨集群的调用,HZ访问的SH

总结:

NacosRule负载均衡策略

  • 优先选择同集群服务实例列表
  • 本地集群找不到提供者,才去其他集群寻找,并且会报警告
  • 确定了可用实例列表后,再采用随机负载均衡挑选实例
5、权重负载均衡

根据权重负载均衡

实际部署中会出现这样的场景

NacosRule先根据集群而后随机

  • 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求

Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高

1、在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮

微服务重构步骤(微服务学习笔记)(68)

2、将其中一个权重设置为0.1,测试可以发现8083被访问到的频率大大降低

(如果权重设置为0,那么将不会访问到此实例)

微服务重构步骤(微服务学习笔记)(69)

总结

权重控制

  • Nacos控制台可以设置实例的权重值,0~1之间
  • 同集群内的多个实例,权重越高被访问的频率越高
  • 权重设置为0则完全不会被访问
6、环境隔离 - Namespace

Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离

微服务重构步骤(微服务学习笔记)(70)

上图中

最外层namespace是一个隔离的空间,可以有多个,之间是相互隔离开的

在Namespace内部有Group属性,同一个隔离空间中可以分多个组

Group内就是具体的服务或数据了,而服务service内就是前面介绍的集群再到实例等东西了

所以环境隔离就是对服务或数据进行隔离,不同环境不同命名空间的服务之间不能互相访问

环境隔离的意义?为什么服务实例有了集群、服务还要再包一层环境隔离呢?

原因,之前服务划分和实例划分是基于业务或地域进行的划分,而现实中还会有开发环境、生产环境、测试环境,需要对环境的变化去进行隔离,namespace就是做这些的,Group是分组的意思,可以将业务相关度比较高的服务放在一个组中,比如订单和支付服务,但不是强制使用的

下面创建一个环境隔离示例

进入Nacos管理页面

微服务重构步骤(微服务学习笔记)(71)

新建一个dev命名空间

微服务重构步骤(微服务学习笔记)(72)

微服务重构步骤(微服务学习笔记)(73)

之前的服务都在public中

微服务重构步骤(微服务学习笔记)(74)

下面将order-service放在dev命名空间中,这里需要修改order-service的application.yml文件,写上生成的命名空间id

微服务重构步骤(微服务学习笔记)(75)

重启order-service

微服务重构步骤(微服务学习笔记)(76)

下面发送一次请求,请求失败,没有发现user-service实例

微服务重构步骤(微服务学习笔记)(77)

总结

  • namespace用来做环境隔离
  • 每个namespace都有唯一id
  • 不同namespace下的服务不可见
7、Nacos和Eureka对比

1、Nacos注册中心细节分析

微服务重构步骤(微服务学习笔记)(78)

上图简单介绍:

注册中心都会有的共同点:

1、服务启动时服务提供者都会将服务信息注册到注册中心,注册中心将信息保留下来

2、当消费者消费时,就会找注册中心去要这些信息,这就是服务拉取发现。但并不是每次访问都会去注册中心拉取,它会将服务列表缓存起来,之后在缓存中获取服务列表,缓存并不是一直不变会每隔30秒去注册中心拉取一次更新,然后负载均衡做远程调用

Nacos与Eureka不同点:

1、对于服务提供者的健康检测

Nacos会将服务提供者划分成临时实例和非临时实例,默认为临时实例

临时实例和Eureka一样采用心跳,提供者每隔一段时间发一次请求到Nacos,如果发现没有心跳检测出服务异常会将服务在列表中剔除。

非临时实例是Nacos主动请求提供者发送请求,询问服务是否健康,如果服务异常,并不会将服务从列表中剔除,而是标记成不健康状态

2、对于消费者服务发现

对于Eureka消费者只会每隔一段时间去注册中心拉取一次更新列表,如果缓存列表中有服务挂掉了,则会出现问题。

Nacos则就通过pull和push两种方式,不尽自己去注册中心拉取,注册中心也会推送服务信息,如果发现哪个提供者挂掉了,会第一时间推送信息,更具时效性

2、代码测试非临时实例

order-service中application.yml文件,配置ephemeral

微服务重构步骤(微服务学习笔记)(79)

再不加此配置时,停掉服务,发现nacos已经剔除了order-service服务

微服务重构步骤(微服务学习笔记)(80)

启动order-service

微服务重构步骤(微服务学习笔记)(81)

启动成功并且临时实例为false,然后停掉服务

微服务重构步骤(微服务学习笔记)(82)

停掉服务,发现已经报红,但是并不会被剔除,一直等待服务启动

总结

  • Nacos与Eureka的共同点
  1. 都支持服务注册和服务拉取
  2. 都支持服务提供者心跳方式做健康检测
  • Nacos与Eureka的区别
  1. Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  2. 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  3. Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  4. Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
六、Nacos配置管理

随着微服务越来越多,生产环境中可能会达到数十上百的服务,如果配置文件需要修改,而且配置文件又与每个微服务有关系,首先调整配置文件太多很麻烦,第二调整完配置文件需要重启,生产环境下重启服务影响还是挺大的。现在的需求就是,配置文件统一修改而不是逐个去修改,并且修改完立即生效无需重启做到热更新。下面介绍Nacos的配置管理

1、统一配置管理

1-1、新建配置管理

微服务重构步骤(微服务学习笔记)(83)

微服务重构步骤(微服务学习笔记)(84)

新建配置,点击发布

微服务重构步骤(微服务学习笔记)(85)

1-2、拉取配置

微服务重构步骤(微服务学习笔记)(86)

在项目启动时,先读取nacos配置文件然后与application.yml配置合并,之前nacos地址都在application.yml中,现在必须将nacos地址和配置相关信息,写入到优先级比application.yml高的bootstrap.yml文件中

1)userservice中引入Nacos的配置管理客户端依赖:

微服务重构步骤(微服务学习笔记)(87)

2)在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml

微服务重构步骤(微服务学习笔记)(88)

将application.yml文件中重复的配置删掉

配置属性对应nacos配置文件名称规则

微服务重构步骤(微服务学习笔记)(89)

3)代码测试以下是否能够获取到配置信息

微服务重构步骤(微服务学习笔记)(90)

微服务重构步骤(微服务学习笔记)(91)

微服务重构步骤(微服务学习笔记)(92)

成功获取到了配置信息

总结

  • 在Nacos中添加配置文件
  • 在微服务中引入nacos的config依赖
  • 在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件
2、配置热更新

Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:

  • 方式一:在@Value注入的变量所在类上添加注解@RefreshScope

微服务重构步骤(微服务学习笔记)(93)

方式二:使用@ConfigurationProperties注解

微服务重构步骤(微服务学习笔记)(94)

总结

Nacos配置更改后,微服务可以实现热更新,方式:

  1. 通过@Value注解注入,结合@RefreshScope来刷新
  2. 通过@ConfigurationProperties注入,自动刷新

注意事项:

  1. 不是所有的配置都适合放到配置中心,维护起来比较麻烦
  2. 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
3、多环境配置共享

应用场景:简单说明,有一个配置属性,它在开发、生产和测试等环境下的值是一样的,像这样的配置在每个配置文件里都去写一份,有些浪费,而且将来如果有改动,还要在每个配置文件里都去改,这样显然不合适,不管环境怎么变,这个配置都可以被加载,这就是多环境配置共享的需求了

微服务启动时会从nacos读取多个配置文件:

微服务重构步骤(微服务学习笔记)(95)

多种配置的优先级:

nacos中的配置优先本地配置

微服务重构步骤(微服务学习笔记)(96)

总结:

  1. [服务名]-[spring.profile.active].yaml,环境配置
  2. [服务名].yaml,默认配置,多环境共享

优先级:

1、[服务名]-[环境].yaml > [服务名].yaml > 本地配置

4、Nacos集群搭建

1.集群结构图

微服务重构步骤(微服务学习笔记)(97)

其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。

我们计划的集群结构:

微服务重构步骤(微服务学习笔记)(98)

三个nacos节点的地址:

微服务重构步骤(微服务学习笔记)(99)

搭建集群的基本步骤:

2.搭建集群

  • 搭建集群的基本步骤:
  • 搭建数据库,初始化数据库表结构
  • 下载nacos安装包
  • 配置nacos
  • 启动nacos集群
  • nginx反向代理

2.1.初始化数据库

Nacos默认数据存储在内嵌数据库Derby中,不属于生产可用的数据库。

官方推荐的最佳实践是使用带有主从的高可用数据库集群,主从模式的高可用数据库

这里我们以单点的数据库为例来讲解。

首先新建一个数据库,命名为nacos,而后导入下面的SQL:

CREATE TABLE `config_info` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',

`data_id` varchar(255) NOT NULL COMMENT 'data_id',

`group_id` varchar(255) DEFAULT NULL,

`content` longtext NOT NULL COMMENT 'content',

`md5` varchar(32) DEFAULT NULL COMMENT 'md5',

`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',

`src_user` text COMMENT 'source user',

`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',

`app_name` varchar(128) DEFAULT NULL,

`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',

`c_desc` varchar(256) DEFAULT NULL,

`c_use` varchar(64) DEFAULT NULL,

`effect` varchar(64) DEFAULT NULL,

`type` varchar(64) DEFAULT NULL,

`c_schema` text,

PRIMARY KEY (`id`),

UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';

/******************************************/

/* 数据库全名 = nacos_config */

/* 表名称 = config_info_aggr */

/******************************************/

CREATE TABLE `config_info_aggr` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',

`data_id` varchar(255) NOT NULL COMMENT 'data_id',

`group_id` varchar(255) NOT NULL COMMENT 'group_id',

`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',

`content` longtext NOT NULL COMMENT '内容',

`gmt_modified` datetime NOT NULL COMMENT '修改时间',

`app_name` varchar(128) DEFAULT NULL,

`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',

PRIMARY KEY (`id`),

UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';

/******************************************/

/* 数据库全名 = nacos_config */

/* 表名称 = config_info_beta */

/******************************************/

CREATE TABLE `config_info_beta` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',

`data_id` varchar(255) NOT NULL COMMENT 'data_id',

`group_id` varchar(128) NOT NULL COMMENT 'group_id',

`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',

`content` longtext NOT NULL COMMENT 'content',

`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',

`md5` varchar(32) DEFAULT NULL COMMENT 'md5',

`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',

`src_user` text COMMENT 'source user',

`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',

`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',

PRIMARY KEY (`id`),

UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';

/******************************************/

/* 数据库全名 = nacos_config */

/* 表名称 = config_info_tag */

/******************************************/

CREATE TABLE `config_info_tag` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',

`data_id` varchar(255) NOT NULL COMMENT 'data_id',

`group_id` varchar(128) NOT NULL COMMENT 'group_id',

`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',

`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',

`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',

`content` longtext NOT NULL COMMENT 'content',

`md5` varchar(32) DEFAULT NULL COMMENT 'md5',

`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',

`src_user` text COMMENT 'source user',

`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',

PRIMARY KEY (`id`),

UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';

/******************************************/

/* 数据库全名 = nacos_config */

/* 表名称 = config_tags_relation */

/******************************************/

CREATE TABLE `config_tags_relation` (

`id` bigint(20) NOT NULL COMMENT 'id',

`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',

`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',

`data_id` varchar(255) NOT NULL COMMENT 'data_id',

`group_id` varchar(128) NOT NULL COMMENT 'group_id',

`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',

`nid` bigint(20) NOT NULL AUTO_INCREMENT,

PRIMARY KEY (`nid`),

UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),

KEY `idx_tenant_id` (`tenant_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';

/******************************************/

/* 数据库全名 = nacos_config */

/* 表名称 = group_capacity */

/******************************************/

CREATE TABLE `group_capacity` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',

`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',

`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',

`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',

`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',

`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',

`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',

`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',

`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',

PRIMARY KEY (`id`),

UNIQUE KEY `uk_group_id` (`group_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';

/******************************************/

/* 数据库全名 = nacos_config */

/* 表名称 = his_config_info */

/******************************************/

CREATE TABLE `his_config_info` (

`id` bigint(64) unsigned NOT NULL,

`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`data_id` varchar(255) NOT NULL,

`group_id` varchar(128) NOT NULL,

`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',

`content` longtext NOT NULL,

`md5` varchar(32) DEFAULT NULL,

`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,

`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,

`src_user` text,

`src_ip` varchar(50) DEFAULT NULL,

`op_type` char(10) DEFAULT NULL,

`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',

PRIMARY KEY (`nid`),

KEY `idx_gmt_create` (`gmt_create`),

KEY `idx_gmt_modified` (`gmt_modified`),

KEY `idx_did` (`data_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';

/******************************************/

/* 数据库全名 = nacos_config */

/* 表名称 = tenant_capacity */

/******************************************/

CREATE TABLE `tenant_capacity` (

`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',

`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',

`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',

`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',

`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',

`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',

`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',

`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',

`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',

`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',

PRIMARY KEY (`id`),

UNIQUE KEY `uk_tenant_id` (`tenant_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';

CREATE TABLE `tenant_info` (

`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',

`kp` varchar(128) NOT NULL COMMENT 'kp',

`tenant_id` varchar(128) default '' COMMENT 'tenant_id',

`tenant_name` varchar(128) default '' COMMENT 'tenant_name',

`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',

`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',

`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',

`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',

PRIMARY KEY (`id`),

UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),

KEY `idx_tenant_id` (`tenant_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';

CREATE TABLE `users` (

`username` varchar(50) NOT NULL PRIMARY KEY,

`password` varchar(500) NOT NULL,

`enabled` boolean NOT NULL

);

CREATE TABLE `roles` (

`username` varchar(50) NOT NULL,

`role` varchar(50) NOT NULL,

UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE

);

CREATE TABLE `permissions` (

`role` varchar(50) NOT NULL,

`resource` varchar(255) NOT NULL,

`action` varchar(8) NOT NULL,

UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE

);

INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);

INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');

2.2.下载nacos

nacos在GitHub上有下载地址:https://github.com/alibaba/nacos/tags,可以选择任意版本下载。

本例中才用1.4.1版本:

2.3.配置Nacos

将这个包解压到任意非中文目录下,如图:

微服务重构步骤(微服务学习笔记)(100)

目录说明:

  • bin:启动脚本
  • conf:配置文件

进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:

微服务重构步骤(微服务学习笔记)(101)

然后添加内容:

微服务重构步骤(微服务学习笔记)(102)

然后修改application.properties文件,添加数据库配置

spring.datasource.platform=mysql

db.num=1

db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC

db.user.0=root

db.password.0=123

2.4.启动

将nacos文件夹复制三份,分别命名为:nacos1、nacos2、nacos3

微服务重构步骤(微服务学习笔记)(103)

然后分别修改三个文件夹中的application.properties,

nacos1:

微服务重构步骤(微服务学习笔记)(104)

nacos2:

微服务重构步骤(微服务学习笔记)(105)

nacos3:

微服务重构步骤(微服务学习笔记)(106)

然后分别启动三个nacos节点:命令

微服务重构步骤(微服务学习笔记)(107)

2.5.nginx反向代理

微服务重构步骤(微服务学习笔记)(108)

修改conf/nginx.conf文件,配置如下:

微服务重构步骤(微服务学习笔记)(109)

java代码中application.yml文件配置如下:

微服务重构步骤(微服务学习笔记)(110)

而后在浏览器访问:http://localhost/nacos即可。

2.6.优化

  • 实际部署时,需要给做反向代理的nginx服务器设置一个域名,这样后续如果有服务器迁移nacos的客户端也无需更改配置
  • Nacos的各个节点应该部署到多个不同服务器,做好容灾和隔离

总结:

集群搭建步骤:

  1. 搭建MySQL集群并初始化数据库表
  2. 下载解压nacos
  3. 修改集群配置(节点信息)、数据库配置
  4. 分别启动多个nacos节点
  5. nginx反向代理
七、Feign远程调用1、简单介绍与使用

1-1、Feign介绍

RestTemplate方式调用存在的问题

先来看我们以前利用RestTemplate发起远程调用的代码:

微服务重构步骤(微服务学习笔记)(111)

这个请求是通过url地址指明访问的服务名称,请求路径,以及请求参数信息,请求方式和返回值类型,由RestTemplate发起请求,转成对应类型返回,这块代码已经在Ribbon的基础上做了优化的了,但是依然存在问题。

存在下面的问题:

  • 代码可读性差,编程体验不统一
  • 参数复杂URL难以维护

Feign的介绍:

Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign

其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题

1-2、Feign使用

使用步骤:

1、order-service引入依赖:

微服务重构步骤(微服务学习笔记)(112)

2、在order-service的启动类添加注解开启Feign的功能:@EnableFeignClients

微服务重构步骤(微服务学习笔记)(113)

3、编写Feign客户端:

微服务重构步骤(微服务学习笔记)(114)

主要是基于SpringMVC的注解来声明远程调用的信息,比如:

  • 服务名称:userservice
  • 请求方式:GET
  • 请求路径:/user/{id}
  • 请求参数:Long id
  • 返回值类型:User

修改order-service:

微服务重构步骤(微服务学习笔记)(115)

发送请求测试,发现不仅远程调用成功,并且实现了负载均衡

微服务重构步骤(微服务学习笔记)(116)

查看pom依赖,说明feign已经集成了ribbon实现了负载均衡

总结:

  1. 引入依赖
  2. 添加@EnableFeignClients注解
  3. 编写FeignClient接口
  4. 使用FeignClient中定义的方法代替RestTemplate
2、自定义Feign的配置

springbooot虽然帮我们实现了自动装配,但它是允许我们覆盖默认配置的

Feign可以修改的配置如下(部分):

微服务重构步骤(微服务学习笔记)(117)

一般我们需要配置的就是日志级别

配置Feign日志有两种方式:

方式一:配置文件方式

1、全局生效:

微服务重构步骤(微服务学习笔记)(118)

2、局部生效:

微服务重构步骤(微服务学习笔记)(119)

方式二:java代码方式,需要先声明一个Bean:

微服务重构步骤(微服务学习笔记)(120)

1、全局生效:则把它放在@EnableFeignClients这个注解中

微服务重构步骤(微服务学习笔记)(121)

2、局部生效:则把它放在@FeignClient这个注解中

微服务重构步骤(微服务学习笔记)(122)

总结:

1、方式一是配置文件,feign.client.config.xxx.loggerLevel

  • 如果xxx是default则代表全局
  • 如果xxx是服务名称,例如userservice则代表某服务

2、方式二是java代码配置Logger.Level这个Bean

  • 如果在@EnableFeignClients注解声明则代表全局
  • 如果在@FeignClient注解中声明则代表某服务
3、Feign的性能优化

Feign底层的客户端实现:

  • URLConnection:默认实现,不支持连接池
  • ApacheHttpClient:支持连接池
  • OKHttp:支持连接池

因此优化Feign的性能主要包括:

  1. 使用连接池代替默认的URLConnection
  2. 日志级别,最好用basic或none

Feign添加HttpClient的支持

引入依赖:

微服务重构步骤(微服务学习笔记)(123)

配置连接池:

微服务重构步骤(微服务学习笔记)(124)

总结:

Feign的优化:

1、日志级别尽量用basic

2、使用HttpClient或OKHttp代替URLConnection

  • 引入feign-httpClient依赖
  • 配置文件开启httpClient功能,设置连接池参数
4、Feign的最佳实践

方式一(继承): 给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。

消费者的FeignClient:

微服务重构步骤(微服务学习笔记)(125)

提供者的controller

微服务重构步骤(微服务学习笔记)(126)

微服务重构步骤(微服务学习笔记)(127)

在消费者的FeignClient和提供者的controller中整个方法(除了名称)的声明是一样的,由于必须声明一样才能访问到,我们就可以做抽取,定义一个接口叫做UserAPI,这个接口将方法的声明写好了,那么消费者的FeignClient就可以直接继承,提供者的controller就可以直接实现这个接口,定义统一标准,这就是继承

但是这样做也有一定的问题,一般情况下服务和客户端之间不推荐去共享接口,因为它会造成紧耦合,都实现了相同的接口,在API层面都已经耦合了,所以它是紧耦合,一旦UserAPI发生改变,那么两边都要一起去修改。而且这种继承方案对springmvc不起作用,springmvc在声明GetMapping之外还有对参数的声明,而方法参数是继承不下来的,尽管存在缺点,但是相对来讲它遵循的面向契约编程的思想,在企业应用还是很多的

方式二(抽取): 将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

微服务重构步骤(微服务学习笔记)(128)

user-service服务提供者写好接口后,每个需要调用的消费者都写UserClient,重复开发,有点浪费,所以消费者都不自己写UserClient,准备一个feign-api(独立项目模块)帮消费者写UserClient,也可以将User的实体类、feign的默认配置等等放在feign-api中,消费者在使用时,直接引用feign-api依赖,实现远程调用

但是也有缺点,就是在feign-api中将所有方法封装进来了,而order-service只需要其中的某一两个方法,而把所有方法全引进来了,有一些多余方法

总结:

Feign的最佳实践:

  1. 让controller和FeignClient继承同一接口
  2. 将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用
5、抽取FeignClient

实现最佳实践方式二的步骤如下:

  1. 先创建一个module,命名为feign-api,然后引入feign的starter依赖
  2. 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
  3. 在order-service中引入feign-api的依赖
  4. 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
  5. 重启测试

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。

方式一:指定FeignClient所在包

微服务重构步骤(微服务学习笔记)(129)

方式二:指定FeignClient字节码

微服务重构步骤(微服务学习笔记)(130)

八、Gateway服务网关1、网关作用介绍

微服务重构步骤(微服务学习笔记)(131)

网关功能:

  • 身份认证和权限校验
  • 服务路由、负载均衡
  • 请求限流

网关的技术实现

在SpringCloud中网关的实现包括两种:

  • gateway
  • zuul

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能

2、网关搭建

搭建网关服务的步骤:

1、创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖

微服务重构步骤(微服务学习笔记)(132)

2、编写路由配置及nacos地址

微服务重构步骤(微服务学习笔记)(133)

微服务重构步骤(微服务学习笔记)(134)

总结:

网关搭建步骤:

  1. 创建项目,引入nacos服务发现和gateway依赖
  2. 配置application.yml,包括服务基本信息、nacos地址、路由

路由配置包括:

  1. 路由id:路由的唯一标识
  2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
  3. 路由断言(predicates):判断路由的规则
  4. 路由过滤器(filters):对请求或响应做处理
3、路由断言工厂
  • 我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
  • 例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
  • 像这样的断言工厂在SpringCloudGateway还有十几个

Spring提供了11中基本的Predicate工厂:

微服务重构步骤(微服务学习笔记)(135)

springcloud官网:Spring Cloud Gateway

  • PredicateFactory的作用:

读取用户定义的断言条件,对请求做出判断

4、路由过滤器

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:

微服务重构步骤(微服务学习笔记)(136)

给所有进入userservice的 请求添加一个请求头

实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:

微服务重构步骤(微服务学习笔记)(137)

在UserController中

微服务重构步骤(微服务学习笔记)(138)

结果:

微服务重构步骤(微服务学习笔记)(139)

配置默认过滤器,全局生效,所有微服务都将

微服务重构步骤(微服务学习笔记)(140)

5、全局过滤器GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。

区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。

定义方式是实现GlobalFilter接口

微服务重构步骤(微服务学习笔记)(141)

过滤器的执行顺序

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增
  • 当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GlobalFilter的顺序执行
6、跨域问题处理

跨域:域名不一致就是跨域,主要包括:域名不同、端口不同

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

解决方案:CORS

微服务重构步骤(微服务学习笔记)(142)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页