相关技术:
微服务治理: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
技术的阶段分类
技术很多慢慢来
在了解微服务之前先来看一下项目的几种架构
2-1. 单体架构
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署
优点:
架构简单
部署成本低
缺点
耦合度高
说明:如果项目庞大还是单体架构,光是编译打包就会占用大量时间。模块之间的代码你中有我我中有你,边界模糊。可能你在修改一处代码,很多地方的代码也会受影响,轻易不敢动代码
2-2. 分布式架构
分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,成为一个服务。
优点:
降低服务耦合
有利于服务升级拓展
拆分后的服务治理问题:
服务拆分粒度如何?
服务集群地址如何维护?
服务之间如何实现远程调用?
服务健康状态如何感知?
目前解决上面问题最常见的方案——微服务
2-3. 微服务
微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
1)单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
说明:上面分布式示例图中,将项目拆分成 支付模块、用户模块、商品模块、订单模块,如果用在像拼多多这样的电商项目中太过粗糙,比如:用户模块还可以拆分会员模块(不同会员享受不同折扣)、积分模块等。使每一个模块只做单一职责,好处是每个服务业务更少了,影响的范围更小了,这就是单一职责
2)面向服务:微服务对外暴露业务接口
说明:单一职责中将项目拆分成一个个单一职责的模块后,那么模块之间的调用就需要暴露接口来实现了。比如上面积分模块必须暴露出查询积分的接口,将来用户模块才能远程调用到积分模块的查询功能
3)自治:团队独立、技术独立、数据独立、部署独立
说明:团队独立,每个模块拥有专门的前端、后端、测试、运维、沟通更方便,比较像敏捷开发。技术独立,因为每个服务模块相对独立,可以根据业务需求选择更适合自己的技术,可以选择自己更擅长的技术。数据独立,每个服务可以拥有自己独立的数据库,实现了数据的解耦。部署独立,可以实现独立部署
4)隔离性强:服务调用做好隔离、容错、降级、避免出现级联问题
说明:由于是面向服务,那么服务之间相互调用时,提供者挂了,可能会对消费者产生影响,为了避免这些影响就需要隔离,隔离故障,做好容错,避免提供者宕机而导致消费者也宕机的级联影响
总结:
单体架构特点:
简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统
分布式架构特点:
松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝
微服务:一种良好的分布式架构方案
优点:拆分粒度更小,服务更独立,耦合度更低
缺点:结构非常复杂,运维、监控、部署难度提高
3-1. 微服务结构
微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。国内最知名的就是SpringCloud和阿里巴巴的Dubbo
简单介绍上图:
不管是哪一种技术,服务之间的调用关系错综复杂,一定需要去维护,但不可能靠人去维护调用,太复杂,所以在微服务里都会有一个注册中心,它可以去维护微服务中每一个节点的信息,并且去监控这些节点的状态
如果将来微服务越来越多,有些配置需要去修改,手动的去微服务中修改显然不现实,所以在微服务中往往会有一个配置中心,可以统一的去管理整个微服务群的配置,如果有变更,我们也可以利用通知的方式,让对应的服务监控到配置的变化,从而实现配置的热更新。
在微服务部署上线用户访问时,如此之多的微服务用户怎么知道访问哪一个,微服务群往往会有一个网关,用户访问它,然后网关通过路由到微服务群,在路由中还可以做负载均衡
路由和服务之间的调用,我们还要做好容错的处理,避免因为服务故障造成级联失败,做好保护、降级、隔离等等措施
3-2. 微服务技术对比
Dubbo | SpringCloud | SpringCloudAlibaba | |
---|---|---|---|
注册中心 | zookeeper、Redis | Eureka、Consul | Nacos、Eureka |
服务远程调用 | Dubbo协议 | Feign(http协议) | Dubbo、Feign |
配置中心 | 无 | SpringCloudConfig | SpringCloudConfig、Nacos |
服务网关 | 无 | SpringColudGateway、Zuul | SpringCloudGateway、Zuul |
服务监控和保护 | dubbo-admin(功能弱) | Hystrix | Sentinel |
简单介绍:
Dubbo技术早在2012年左右就由阿里巴巴开源出来了,那时微服务技术并未普及,Dubbo也不是为了处理微服务而发明的,它的核心就是服务的远程调用,所以Dubbo的技术体系并不是特别完整。核心只有注册中心和服务远程调用,而且注册中心也不是自己实现的,而是使用zookeeper、Redis等,这些并不是专业的注册中心,Redis是做缓存的,zookeeper是做集群管理的,所以并不能做到完善的注册中心功能
SpringCloud并不是发明而是整合,它把全球各公司开源的微服务技术整合进来了,而后形成一套完整的微服务技术体系,成了一个集大成者。
SpringCloudAlibaba这套技术栈是相当于SpringCloud的一部分,因为它实现了SpringCloud的接口规范,并整合进了Dubbo,实现了自己的注册中心Nacos,即支持Dubbo调用也支持Feign调用,SpringCloudAlibaba兼容了SpringCloud和Dubbo两种结构,也是越来越火的原因
3-3. 企业需求
在企业中微服务的使用大致分以下几种
SpringCloud
1、SpringCloud是目前国内使用最广泛的微服务框架。官网地址:http://spring.io/projects/spring-cloud
2、SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
那么为什么人们不使用微服务开源技术,而使用SpringCloud?因为SpringCloud是基于SpringBoot的,而SpringBoot最擅长的是自动装配,SpringCloud就是把官方原生开源的组件给整合进来了,并且基于Spring Boot做了自动装配,而你可以拿过来就用,无需复杂配置
3、SpringCloud和SpringBoot的版本兼容关系:
服务拆分注意事项
不同微服务,不要重复开发相同业务
微服务数据独立,不要访问其他微服务的数据库
微服务可以将自己的业务暴露为接口,供其他微服务调用
服务拆分说起来很简单,一个单体架构按照功能模块进行拆分,变成多个服务就行了
以订单模块为例,查询订单,需要访问数据库,再想获取用户详情与商品详情的话,按照之前的开发思路,再直接查询用户与商品数据库就行了。如果用到什么就查询什么,那么拆分就没有意义了,而且用户模块与商品模块也再做这样的查询,是一种重复开发,微服务要避免重复开发。
为了做好这样的限制,同时也会有一定的要求,比如微服务的数据独立,每个模块使用自己的数据库,订单模块访问不到用户数据,从根源上杜绝了做这中耦合性的业务。
如果在做订单模块查询又想把用户和商品查询出来的话,如何去做呢?微服务拆分时还要做一件事,就是将自己的部分业务暴露为接口供其他微服务使用。
总结:
1、微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务
2、微服务可以将业务暴露为接口,供其他微服务使用
3、不同微服务都应该有自己独立的数据库**
远程调用demo
创建项目cloud-demo
创建用户和订单两个服务模块user-service和order-service
需求:在查询订单时查询用户信息
模块之间是不能直接调用方法的,只能使用远程调用
可以使用Spring框架spring-web模块中的RestTemplate类
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args){
SpringApplication.run(OrderApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
@Service
public class OrderService {
@Autowired
OrderMapper orderMapper;
@Autowired
RestTemplate restTemplate;
public Order getOrderById(Long id){
Order order = orderMapper.getOrderById(id);
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);// 通过user服务地址发送get请求
order.setUser(user);
return order;
}
}
服务调用关系
服务提供者:暴露接口给其他微服务调用
服务消费者:调用其他微服务提供的接口
提供者与消费者角色其实是相对的
一个服务可以同时是服务提供者和服务消费者
问题:首先采用硬编码的方式显然不行,并且用户服务可能会是多服务的集群,访问哪个服务?服务是否健康?等都是问题
消费者该如何获取服务提供者具体信息?
服务提供者启动时向eureka注册自己的信息
eureka保存这些信息
消费者根据服务名称向eureka拉取提供者信息
如果有多个服务提供者,消费者如何选择?
服务消费者利用负载均衡算法,从服务列表中挑选一个
消费者如何感知服务提供者健康状态?
服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
eureka会更新记录服务列表信息,心跳不正常被剔除
消费者就可以拉取到最新的消息
总结
在Eureka架构中,微服务角色有两类
EurekaServer:服务端,注册中心
记录服务信息
心跳监控
EurekaClient:客户端
Provider:服务提供者,例如案例中的user-service
注册自己的信息到EurekaServer
每隔30秒向EurekaServer发送心跳
Consumer:服务消费者,例如案例中的order-service
根据服务名称从EurekaServer拉取服务列表
基于服务列表做负载均衡,选中一个微服务后发起远程调用
1)搭建EurekaServer
2)将user-service、order-service注册到eureka
3)在order-service中完成服务拉取、然后通过负载均衡挑选一个服务,实现远程调用
搭建Eureka服务需要一个独立的微服务
1、创建项目后,引入spring-cloud-starter-netflix-eureka-server的依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
2、编写启动类,添加@EnableEurekaServer
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args){
SpringApplication.run(EurekaApplication.class, args);
}
}
3、添加application.yml文件,编写下面的配置:
server:
port: 10086 # 服务端口
spring:
application:
name: eurekaserver # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
启动服务,并访问
这样EurekaServer就搭建完成了
进行服务注册
1、在user-service、order-service项目引入spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
2、在application.yml文件,已user-service为例,编写下面的配置:
spring:
application:
name: userservice
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
启动服务,并访问
也可以copy服务,模仿分布式集群效果
完成服务注册
在order-service完成服务拉取
服务拉取是基于服务名称获取服务列表,然后在服务列表做负载均衡
1、修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
String url = "http://userservice/user/" + order.getUserId();
2、在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
这样就完成了服务拉取
像这种请求http://userservice/user/1,不是真正的域名ip端口,是无法请求到服务器的,所以中间一定有人拦截处理,找到真实地址,那处理的这个人就是Ribbon
发起的服务请求是如何拦截的,找到这个类LoadBalancerInterceptor
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
LoadBalancerRequestFactory requestFactory) {
this.loadBalancer = loadBalancer;
this.requestFactory = requestFactory;
}
public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
// for backwards compatibility
this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
}
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
String serviceName = originalUri.getHost();
Assert.state(serviceName != null,
"Request URI does not contain a valid hostname: " + originalUri);
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
/**
* Intercepts client-side HTTP requests. Implementations of this interface can be
* {@linkplain org.springframework.web.client.RestTemplate#setInterceptors registered}
* with the {@link org.springframework.web.client.RestTemplate RestTemplate},
* as to modify the outgoing {@link ClientHttpRequest} and/or the incoming
* {@link ClientHttpResponse}.
*
* The main entry point for interceptors is
* {@link #intercept(HttpRequest, byte[], ClientHttpRequestExecution)}.
*
* @author Arjen Poutsma
* @since 3.1
*/
@FunctionalInterface
public interface ClientHttpRequestInterceptor {
ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException;
}
他实现了ClientHttpRequestInterceptor接口,而ClientHttpRequestInterceptor就是拦截http请求的,实现了intercept方法,debug在intercept方法中断点
可以看到拦截的请求是http://userservice/user/1,通过getHost()就是获取主机名userservice,而他下一步就应该去找Eureka完成服务拉取,debug进入execute()方法中
getLoadBalancer(), 结果ILoadBalancer对象中,有allServerList,可见getLoadBalancer()通过服务名称拉取服务列表
getServer(), 就是Ribbon重要的负载均衡,获取列表后使用哪一个服务,再跟进getServer()
继续跟进chooseServer()
this.rule.choose(key);字面意思说明是根据一种规则进行负载均衡的,我们查看一下rule类型
ctrl+H查看IRule的实现类,定义了负载均衡的几种规则
总结:
上面可以看出Ribbon的负载均衡是IRule负载均衡规则实现的,下图为IRule接口的继承关系图
内置负载均衡策略 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级的增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上线,可以由客户端的..ActiveConnectionsLimit属性进行配置 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器 |
RandomRule | 随机选择一个可用的服务器 |
RetryRule | 重试机制的选择逻辑 |
了解几种负载均衡的规则,如何使用规则?
通过定义IRule实现可以修改负载均衡规则
第一种方式:
在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule(); //随机
}
这种方案是作用于全局的,order-service不管是调用哪个服务的接口都是随机的
第二种方式
在application.yml文件中,添加新的配置也可以修改规则:
# 指定userservice的负载均衡规则
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
这种方案是针对某个服务进行配置的
Ribbon饥饿加载策略
先来看一个现象:
重启order-service服务,发送请求
第一次请求耗时达到了581毫秒,第二次请求32毫秒
原因:Ribbon默认是采用的懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的服务名称list
- userservice
- xxxservice #列表类型,可以指定多个服务饥饿加载
Nacos:Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
Nacos官方网站
Nacos下载地址
这里以1.4.1版本为例
1、window安装:
1-1、解压nacos-server-1.4.1.zip到任意非中文目录下
1-2、端口设置:
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件application.properties中的端口:
1-3、启动,进入bin目录下,
startup.cmd -m standalone
1-4、访问地址:http://127.0.0.1:8848/nacos
默认的账号和密码都是nacos,登录
2、linux安装
2-1、安装JDK
Nacos依赖于JDK运行,所以Linux上也需要安装JDK才行。
2-2、解压缩:
命令解压缩安装包:
tar -xvf nacos-server-1.4.1.tar.gz
然后删除安装包:
rm -rf nacos-server-1.4.1.tar.gz
2-3、端口设置:
与windows中类似
2-4、启动:
在nacos/bin目录中,输入命令启动Nacos:
sh startup.sh -m standalone
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.5.RELEASE
pom
import
2、添加nacos的客户端依赖:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
3、服务注册到Nacos
修改user-service和order-service中的application.yml文件,去掉之前的eureka地址,添加nacos地址
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
重启user-service和order-service,查看Nacos成功注册
发请求测试结果,之前的负载均衡配置也依然生效
像阿里京东这样的大型互联网公司,为保护服务的安全,全国各地部署服务器,做到容灾
nacos做分级的意义是什么,比如上图如果在拉取服务时,杭州的order-service访问的上海的user-service,会有延迟,显然不如访问杭州本地的局域网内的user-service
演示
1、下面我们创建三个user-service实例,分两个集群(杭州两个、上海一个)
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如:HZ,杭州
2、orderservice添加到杭州HZ集群中
总结:
Nacos服务分级存储模型
配好集群后请求,发现杭州的orderservice没有只请求杭州的userservice,依然采用轮询方案
说明依然采用的ribbon配置的负载均衡规则,只需要修改配置文件:
# 指定userservice的负载均衡规则
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
重启orderservice服务,再发几次请求
现在orderservie只请求杭州的8082或8081的userservice了,并且在userservice集群中没有规律
说明:NacosRule采用的是先按配置的本地集群访问,然后在集群中采用随机方案
如果将8082和8083关掉会怎么样?
请求依然成功,查看一下orderservice控制台
一个警告,发生了跨集群的调用,HZ访问的SH
总结:
NacosRule负载均衡策略
根据权重负载均衡
实际部署中会出现这样的场景
NacosRule先根据集群而后随机
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
1、在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮
2、将其中一个权重设置为0.1,测试可以发现8083被访问到的频率大大降低
(如果权重设置为0,那么将不会访问到此实例)
总结
权重控制
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
上图中
最外层Namespace是一个隔离的空间,可以有多个,之间是相互隔离开的
在Namespace内部有Group属性,同一个隔离空间中可以分多个组
Group内就是具体的服务或数据了,而服务service内就是前面介绍的集群再到实例等东西了
所以环境隔离就是对服务或数据进行隔离,不同环境不同命名空间的服务之间不能互相访问
环境隔离的意义?为什么服务实例有了集群、服务还要再包一层环境隔离呢?
原因,之前服务划分和实例划分是基于业务或地域进行的划分,而现实中还会有开发环境、生产环境、测试环境,需要对环境的变化去进行隔离,namespace就是做这些的,Group是分组的意思,可以将业务相关度比较高的服务放在一个组中,比如订单和支付服务,但不是强制使用的
下面创建一个环境隔离示例
进入Nacos管理页面
新建一个dev命名空间
之前的服务都在public中
下面将order-service放在dev命名空间中,这里需要修改order-service的application.yml文件,写上生成的命名空间id
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 配置集群名称,也就是机房位置,例如:HZ,杭州
namespace: 5523faa8-a869-4ccb-9414-c6d21d258b5e
重启order-service
下面发送一次请求,请求失败,没有发现user-service实例
总结
1、Nacos注册中心细节分析
上图简单介绍:
注册中心都会有的共同点:
1、服务启动时服务提供者都会将服务信息注册到注册中心,注册中心将信息保留下来
2、当消费者消费时,就会找注册中心去要这些信息,这就是服务拉取发现。但并不是每次访问都会去注册中心拉取,它会将服务列表缓存起来,之后在缓存中获取服务列表,缓存并不是一直不变会每隔30秒去注册中心拉取一次更新,然后负载均衡做远程调用
Nacos与Eureka不同点:
1、对于服务提供者的健康检测
Nacos会将服务提供者划分成临时实例和非临时实例,默认为临时实例
临时实例和Eureka一样采用心跳,提供者每隔一段时间发一次请求到Nacos,如果发现没有心跳检测出服务异常会将服务在列表中剔除。
非临时实例是Nacos主动请求提供者发送请求,询问服务是否健康,如果服务异常,并不会将服务从列表中剔除,而是标记成不健康状态
2、对于消费者服务发现
对于Eureka消费者只会每隔一段时间去注册中心拉取一次更新列表,如果缓存列表中有服务挂掉了,则会出现问题。
Nacos则就通过pull和push两种方式,不尽自己去注册中心拉取,注册中心也会推送服务信息,如果发现哪个提供者挂掉了,会第一时间推送信息,更具时效性
2、代码测试非临时实例
order-service中application.yml文件,配置ephemeral
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 集群名称
namespace: 5523faa8-a869-4ccb-9414-c6d21d258b5e # 命名空间
ephemeral: false # 是否是临时实例,默认为true
再不加此配置时,停掉服务,发现nacos已经剔除了order-service服务
启动order-service
启动成功并且临时实例为false,然后停掉服务
停掉服务,发现已经报红,但是并不会被剔除,一直等待服务启动
总结
随着微服务越来越多,生产环境中可能会达到数十上百的服务,如果配置文件需要修改,而且配置文件又与每个微服务有关系,首先调整配置文件太多很麻烦,第二调整完配置文件需要重启,生产环境下重启服务影响还是挺大的。现在的需求就是,配置文件统一修改而不是逐个去修改,并且修改完立即生效无需重启做到热更新。下面介绍Nacos的配置管理
新建配置,点击发布
1-2、拉取配置
在项目启动时,先读取nacos配置文件然后与application.yml配置合并,之前nacos地址都在application.yml中,现在必须将nacos地址和配置相关信息,写入到优先级比application.yml高的bootstrap.yml文件中
1)userservice中引入Nacos的配置管理客户端依赖:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
2)在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml
spring:
application:
name: userservice
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
将application.yml文件中重复的配置删掉
3)代码测试以下是否能够获取到配置信息
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Value("${pattern.dateformat}")
private String dateformat;
方式二:使用@ConfigurationProperties注解
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
总结
Nacos配置更改后,微服务可以实现热更新,方式:
1、通过@Value注解注入,结合@RefreshScope来刷新
2、通过@ConfigurationProperties注入,自动刷新
注意事项:
应用场景:简单说明,有一个配置属性,它在开发、生产和测试等环境下的值是一样的,像这样的配置在每个配置文件里都去写一份,有些浪费,而且将来如果有改动,还要在每个配置文件里都去改,这样显然不合适,不管环境怎么变,这个配置都可以被加载,这就是多环境配置共享的需求了
微服务启动时会从nacos读取多个配置文件:
1.集群结构图
其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。
节点 | ip | port |
---|---|---|
nacos1 | 192.168.150.1 | 8845 |
nacos2 | 192.168.150.1 | 8846 |
nacos3 | 192.168.150.1 | 8847 |
搭建集群的基本步骤:
2.搭建集群
搭建集群的基本步骤:
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
进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:
然后添加内容:
127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847
然后修改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
然后分别修改三个文件夹中的application.properties,
nacos1:
server.port=8845
nacos2:
server.port=8846
nacos3:
server.port=8847
然后分别启动三个nacos节点:命令
startup.cmd
2.5.nginx反向代理
修改conf/nginx.conf文件,配置如下:
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}
server {
listen 80;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
java代码中application.yml文件配置如下:
spring:
cloud:
nacos:
server-addr: localhost:80 # Nacos地址
而后在浏览器访问:http://localhost/nacos即可。
2.6.优化
实际部署时,需要给做反向代理的nginx服务器设置一个域名,这样后续如果有服务器迁移nacos的客户端也无需更改配置.
Nacos的各个节点应该部署到多个不同服务器,做好容灾和隔离
总结:
集群搭建步骤:
1、搭建MySQL集群并初始化数据库表
2、下载解压nacos
3、修改集群配置(节点信息)、数据库配置
4、分别启动多个nacos节点
5、nginx反向代理
1-1、Feign介绍
RestTemplate方式调用存在的问题
先来看我们以前利用RestTemplate发起远程调用的代码:
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
这个请求是通过url地址指明访问的服务名称,请求路径,以及请求参数信息,请求方式和返回值类型,由RestTemplate发起请求,转成对应类型返回,这块代码已经在Ribbon的基础上做了优化的了,但是依然存在问题。
存在下面的问题:
Feign的介绍:
Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题
1-2、Feign使用
使用步骤:
1、order-service引入依赖:
org.springframework.cloud
spring-cloud-starter-openfeign
2.2.7.RELEASE
2、在order-service的启动类添加注解开启Feign的功能:@EnableFeignClients
@EnableFeignClients
@MapperScan("xy.ysys.order.mapper")
@SpringBootApplication
public class OrderApplication {
3、编写Feign客户端:
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
主要是基于SpringMVC的注解来声明远程调用的信息,比如:
修改order-service:
@Autowired
UserClient userClient;
public Order getOrderById(Long id){
Order order = orderMapper.getOrderById(id);
User user = userClient.findById(order.getUserId());
order.setUser(user);
return order;
}
发送请求测试,发现不仅远程调用成功,并且实现了负载均衡
查看pom依赖,说明feign已经集成了ribbon实现了负载均衡
总结:
1、引入依赖
2、添加@EnableFeignClients注解
3、编写FeignClient接口
4、使用FeignClient中定义的方法代替RestTemplate
springbooot虽然帮我们实现了自动装配,但它是允许我们覆盖默认配置的
Feign可以修改的配置如下(部分):
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE(默认)、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign.Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign.Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
一般我们需要配置的就是日志级别
配置Feign日志有两种方式:
方式一:配置文件方式
1、全局生效:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
2、局部生效:
feign:
client:
config:
userservice: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
方式二:java代码方式,需要先声明一个Bean:
public class FeignConfiguration {
@Bean
public Logger.Level logLevel(){
return Logger.Level.BASIC;
}
}
1、全局生效:则把它放在@EnableFeignClients这个注解中
@EnableFeignClients(defaultConfiguration = FeignConfiguration.class)
@MapperScan("xy.ysys.order.mapper")
@SpringBootApplication
public class OrderApplication {
2、局部生效:则把它放在@FeignClient这个注解中
@FeignClient(value = "userservice", configuration = FeignConfiguration.class)
public interface UserClient {
总结:
1、方式一是配置文件,feign.client.config.xxx.loggerLevel
1)如果xxx是default则代表全局
2)如果xxx是服务名称,例如userservice则代表某服务
2、方式二是java代码配置Logger.Level这个Bean
1)如果在@EnableFeignClients注解声明则代表全局
2)如果在@FeignClient注解中声明则代表某服务
Feign底层的客户端实现:
因此优化Feign的性能主要包括:
1)使用连接池代替默认的URLConnection
2)日志级别,最好用basic或none
Feign添加HttpClient的支持
引入依赖:
io.github.openfeign
feign-httpclient
配置连接池:
feign:
httpclient:
enabled: true # 支持httpClient的开关
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 单个路径最大连接数
总结:
Feign的优化:
1、日志级别尽量用basic
2、使用HttpClient或OKHttp代替URLConnection
1)引入feign-httpClient依赖
2)配置文件开启httpClient功能,设置连接池参数
方式一(继承): 给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
消费者的FeignClient:
提供者的controller
在消费者的FeignClient和提供者的controller中整个方法(除了名称)的声明是一样的,由于必须声明一样才能访问到,我们就可以做抽取,定义一个接口叫做UserAPI,这个接口将方法的声明写好了,那么消费者的FeignClient就可以直接继承,提供者的controller就可以直接实现这个接口,定义统一标准,这就是继承
但是这样做也有一定的问题,一般情况下服务和客户端之间不推荐去共享接口,因为它会造成紧耦合,都实现了相同的接口,在API层面都已经耦合了,所以它是紧耦合,一旦UserAPI发生改变,那么两边都要一起去修改。而且这种继承方案对springmvc不起作用,springmvc在声明GetMapping之外还有对参数的声明,而方法参数是继承不下来的,尽管存在缺点,但是相对来讲它遵循的面向契约编程的思想,在企业应用还是很多的
方式二(抽取): 将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
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的默认配置都定义到一个项目中,供所有消费者使用
实现最佳实践方式二的步骤如下:
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所在包
@EnableFeignClients(basePackages = "xy.ysys.feign.clients")
方式二:指定FeignClient字节码
@EnableFeignClients(clients = UserClient.class)
网关的技术实现
在SpringCloud中网关的实现包括两种:
搭建网关服务的步骤:
1、创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-gateway
2、编写路由配置及nacos地址
server:
port: 5000
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848 # nacos 地址
gateway:
routes:
- id: user-service # 路由标识,必需唯一
uri: lb://userservice # 路由的目标地址
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言判断路径是否以user开头,如果是则说明符合规则
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
总结:
网关搭建步骤:
1、创建项目,引入nacos服务发现和gateway依赖
2、配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括:
1、路由id:路由的唯一标识
2、路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
3、路由断言(predicates):判断路由的规则
4、路由过滤器(filters):对请求或响应做处理
Spring提供了11中基本的Predicate工厂:
名称 | 说明 | 实例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2017-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2017-01-20T17:42:47.789-07:00[America/Denver] |
Between | 是某两个时间点之前的请求 | - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=.somehost.org,.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/{segment} |
Query | 请求参数必须包含指定参数 | - Query=green |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 | - Weight=group1, 2 |
springcloud官网:https://cloud.spring.io/spring-cloud-gateway/reference/html/#gateway-request-predicates-factories
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
给所有进入userservice的 请求添加一个请求头
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:
spring:
cloud:
gateway:
routes:
- id: user-service # 路由标识,必需唯一
uri: lb://userservice # 路由的目标地址
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言判断路径是否以user开头,如果是则说明符合规则
filters: # 过滤器
- AddRequestHeader=username, zhangsan
在UserController中
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader String username) {
System.out.println("请求头中的name:" + username);
return userService.queryById(id);
}
spring:
cloud:
gateway:
routes:
- id: user-service # 路由标识,必需唯一
uri: lb://userservice # 路由的目标地址
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言判断路径是否以user开头,如果是则说明符合规则
default-filters: # 全局过滤器
- AddRequestHeader=username, zhangsan
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GlobalFilter接口
//@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1、获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap params = request.getQueryParams();
// 2、获取参数中的authorization参数
String authorization = params.getFirst("authorization");
// 3、判断参数值是否等于 admin
if ("admin".equals(authorization)){
// 4、是,放行
return chain.filter(exchange);
}
// 5、否拦截
// 5.1、设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 5.2、拦截请求
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
// 过滤器执行顺序
return -1;
}
}
过滤器的执行顺序
跨域:域名不一致就是跨域,主要包括:域名不同、端口不同
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethids: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期