【微服务学习笔记】

一、认识微服务

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. 微服务技术对比

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. 企业需求
  在企业中微服务的使用大致分以下几种
【微服务学习笔记】_第6张图片


4、SpringCloud简介


SpringCloud
 1、SpringCloud是目前国内使用最广泛的微服务框架。官网地址:http://spring.io/projects/spring-cloud
 2、SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
【微服务学习笔记】_第7张图片
  那么为什么人们不使用微服务开源技术,而使用SpringCloud?因为SpringCloud是基于SpringBoot的,而SpringBoot最擅长的是自动装配,SpringCloud就是把官方原生开源的组件给整合进来了,并且基于Spring Boot做了自动装配,而你可以拿过来就用,无需复杂配置

 3、SpringCloud和SpringBoot的版本兼容关系:
在这里插入图片描述

二、服务拆分及远程调用

1、服务拆分

服务拆分注意事项
 不同微服务,不要重复开发相同业务
 微服务数据独立,不要访问其他微服务的数据库
 微服务可以将自己的业务暴露为接口,供其他微服务调用


服务拆分说起来很简单,一个单体架构按照功能模块进行拆分,变成多个服务就行了
【微服务学习笔记】_第8张图片
 以订单模块为例,查询订单,需要访问数据库,再想获取用户详情与商品详情的话,按照之前的开发思路,再直接查询用户与商品数据库就行了。如果用到什么就查询什么,那么拆分就没有意义了,而且用户模块与商品模块也再做这样的查询,是一种重复开发,微服务要避免重复开发。
 为了做好这样的限制,同时也会有一定的要求,比如微服务的数据独立,每个模块使用自己的数据库,订单模块访问不到用户数据,从根源上杜绝了做这中耦合性的业务。
 如果在做订单模块查询又想把用户和商品查询出来的话,如何去做呢?微服务拆分时还要做一件事,就是将自己的部分业务暴露为接口供其他微服务使用。


总结:
1、微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务
2、微服务可以将业务暴露为接口,供其他微服务使用
3、不同微服务都应该有自己独立的数据库**


2、服务间调用

远程调用demo
创建项目cloud-demo
创建用户和订单两个服务模块user-service和order-service
【微服务学习笔记】_第9张图片
【微服务学习笔记】_第10张图片
【微服务学习笔记】_第11张图片
需求:在查询订单时查询用户信息
模块之间是不能直接调用方法的,只能使用远程调用
可以使用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注册中心

1、提供者与消费者

 服务调用关系
  服务提供者:暴露接口给其他微服务调用
  服务消费者:调用其他微服务提供的接口
  提供者与消费者角色其实是相对
  一个服务可以同时是服务提供者和服务消费者

2、Eureka介绍

在这里插入图片描述
问题:首先采用硬编码的方式显然不行,并且用户服务可能会是多服务的集群,访问哪个服务?服务是否健康?等都是问题
【微服务学习笔记】_第12张图片
消费者该如何获取服务提供者具体信息?
 服务提供者启动时向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服务需要一个独立的微服务
【微服务学习笔记】_第13张图片
 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服务,模仿分布式集群效果
【微服务学习笔记】_第14张图片
【微服务学习笔记】_第15张图片
完成服务注册



在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();
}

这样就完成了服务拉取



四、Ribbon负载均衡

【微服务学习笔记】_第16张图片
像这种请求http://userservice/user/1,不是真正的域名ip端口,是无法请求到服务器的,所以中间一定有人拦截处理,找到真实地址,那处理的这个人就是Ribbon

1、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方法中断点
【微服务学习笔记】_第17张图片

可以看到拦截的请求是http://userservice/user/1,通过getHost()就是获取主机名userservice,而他下一步就应该去找Eureka完成服务拉取,debug进入execute()方法中
【微服务学习笔记】_第18张图片
getLoadBalancer(), 结果ILoadBalancer对象中,有allServerList,可见getLoadBalancer()通过服务名称拉取服务列表
getServer(), 就是Ribbon重要的负载均衡,获取列表后使用哪一个服务,再跟进getServer()
【微服务学习笔记】_第19张图片
继续跟进chooseServer()
【微服务学习笔记】_第20张图片
this.rule.choose(key);字面意思说明是根据一种规则进行负载均衡的,我们查看一下rule类型
【微服务学习笔记】_第21张图片
ctrl+H查看IRule的实现类,定义了负载均衡的几种规则
总结:
【微服务学习笔记】_第22张图片


2、Ribbon负载均衡策略

上面可以看出Ribbon的负载均衡是IRule负载均衡规则实现的,下图为IRule接口的继承关系图
【微服务学习笔记】_第23张图片

内置负载均衡策略 规则描述
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 # 负载均衡规则

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


3、Ribbon饥饿加载

Ribbon饥饿加载策略
先来看一个现象:
重启order-service服务,发送请求
在这里插入图片描述
在这里插入图片描述

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

ribbon:
  eager-load:
    enabled: true  # 开启饥饿加载
    clients: # 指定饥饿加载的服务名称list
      - userservice
      - xxxservice  #列表类型,可以指定多个服务饥饿加载




五、Nacos注册中心

1、认识安装Nacos

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

Nacos官方网站
Nacos下载地址
这里以1.4.1版本为例
1、window安装:
1-1、解压nacos-server-1.4.1.zip到任意非中文目录下
【微服务学习笔记】_第24张图片

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

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

如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件application.properties中的端口:
在这里插入图片描述
1-3、启动,进入bin目录下,
 startup.cmd -m standalone
【微服务学习笔记】_第25张图片
1-4、访问地址:http://127.0.0.1:8848/nacos
默认的账号和密码都是nacos,登录
【微服务学习笔记】_第26张图片
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



2、Nacos简单介绍

在使用Nacos之前
【微服务学习笔记】_第27张图片

  • 在springcloud组件中的Spring Cloud Commons定义了通用的接口规范,其中就有服务发现和服务注册接口,Eureka和Nacos都遵循了接口规范。
  • 所以将Eureka更换为Nacos时服务的提供者和消费者代码是不需要变化的
  • 需要修改的地方:
    引入的依赖
    1、在父工程中添加spring-cloud-alibaba的管理依赖,这样有关的所有依赖版本就不需要操心了

	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成功注册
【微服务学习笔记】_第28张图片
发请求测试结果,之前的负载均衡配置也依然生效


3、Nacos服务分级存储模型

【微服务学习笔记】_第29张图片像阿里京东这样的大型互联网公司,为保护服务的安全,全国各地部署服务器,做到容灾
【微服务学习笔记】_第30张图片
nacos做分级的意义是什么,比如上图如果在拉取服务时,杭州的order-service访问的上海的user-service,会有延迟,显然不如访问杭州本地的局域网内的user-service

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

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

spring:
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos服务地址
      discovery:
      	cluster-name: HZ  # 配置集群名称,也就是机房位置,例如:HZ,杭州

【微服务学习笔记】_第31张图片
2、orderservice添加到杭州HZ集群中
【微服务学习笔记】_第32张图片
总结:
Nacos服务分级存储模型

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


4、NacosRule负载均衡

配好集群后请求,发现杭州的orderservice没有只请求杭州的userservice,依然采用轮询方案
说明依然采用的ribbon配置的负载均衡规则,只需要修改配置文件:

# 指定userservice的负载均衡规则
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则

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

在这里插入图片描述

在这里插入图片描述
【微服务学习笔记】_第33张图片

现在orderservie只请求杭州的8082或8081的userservice了,并且在userservice集群中没有规律
说明:NacosRule采用的是先按配置的本地集群访问,然后在集群中采用随机方案

如果将8082和8083关掉会怎么样?
【微服务学习笔记】_第34张图片
请求依然成功,查看一下orderservice控制台
在这里插入图片描述
一个警告,发生了跨集群的调用,HZ访问的SH
总结:
NacosRule负载均衡策略

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


5、权重负载均衡

根据权重负载均衡
实际部署中会出现这样的场景
NacosRule先根据集群而后随机

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

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

1、在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮
【微服务学习笔记】_第35张图片
2、将其中一个权重设置为0.1,测试可以发现8083被访问到的频率大大降低
(如果权重设置为0,那么将不会访问到此实例)
【微服务学习笔记】_第36张图片
总结
权重控制

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


6、环境隔离 - namespace

Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
【微服务学习笔记】_第37张图片
上图中
最外层Namespace是一个隔离的空间,可以有多个,之间是相互隔离开的
在Namespace内部有Group属性,同一个隔离空间中可以分多个组
Group内就是具体的服务或数据了,而服务service内就是前面介绍的集群再到实例等东西了
所以环境隔离就是对服务或数据进行隔离,不同环境不同命名空间的服务之间不能互相访问

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

下面创建一个环境隔离示例
进入Nacos管理页面
【微服务学习笔记】_第38张图片
新建一个dev命名空间
在这里插入图片描述
在这里插入图片描述
之前的服务都在public中
【微服务学习笔记】_第39张图片
下面将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
【微服务学习笔记】_第40张图片
下面发送一次请求,请求失败,没有发现user-service实例
在这里插入图片描述
总结

  • namespace用来做环境隔离
  • 每个namespace都有唯一id
  • 不同namespace下的服务不可见



7、Nacos和Eureka对比

1、Nacos注册中心细节分析

【微服务学习笔记】_第41张图片
 上图简单介绍:
注册中心都会有的共同点:
  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服务
【微服务学习笔记】_第42张图片
启动order-service
在这里插入图片描述
启动成功并且临时实例为false,然后停掉服务
【微服务学习笔记】_第43张图片
停掉服务,发现已经报红,但是并不会被剔除,一直等待服务启动

总结

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



六、Nacos配置管理

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

1、统一配置管理

1-1、新建配置管理
在这里插入图片描述

【微服务学习笔记】_第44张图片
新建配置,点击发布
【微服务学习笔记】_第45张图片


1-2、拉取配置
【微服务学习笔记】_第46张图片
在项目启动时,先读取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文件中重复的配置删掉

配置属性对应nacos配置文件名称规则
【微服务学习笔记】_第47张图片


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

    @Value("${pattern.dateformat}")
    private String dateformat;


    @GetMapping("now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
    }

【微服务学习笔记】_第48张图片
【微服务学习笔记】_第49张图片
成功获取到了配置信息
总结

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

2、配置热更新

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配置中心,一般都是自定义配置

3、多环境配置共享

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

  • [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml
  • [spring.application.name].yaml,例如:userservice.yaml
    无论 profile如何变化,[spring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件
    共享配置文件
    多种配置的优先级:
    nacos中的配置优先本地配置
    【微服务学习笔记】_第50张图片
    总结:
    1、[服务名]-[spring.profile.active].yaml,环境配置
    2、[服务名].yaml,默认配置,多环境共享
    优先级:
    1、[服务名]-[环境].yaml > [服务名].yaml > 本地配置

4、Nacos集群搭建

1.集群结构图
【微服务学习笔记】_第51张图片
其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。

我们计划的集群结构:
【微服务学习笔记】_第52张图片
三个nacos节点的地址:

节点 ip port
nacos1 192.168.150.1 8845
nacos2 192.168.150.1 8846
nacos3 192.168.150.1 8847

搭建集群的基本步骤:

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

将这个包解压到任意非中文目录下,如图:
【微服务学习笔记】_第53张图片
目录说明:

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

进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:
【微服务学习笔记】_第54张图片
然后添加内容:

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
【微服务学习笔记】_第55张图片
然后分别修改三个文件夹中的application.properties,

nacos1:

server.port=8845

nacos2:

server.port=8846

nacos3:

server.port=8847

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

startup.cmd

2.5.nginx反向代理
【微服务学习笔记】_第56张图片
修改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反向代理



七、Feign远程调用


1、简单介绍与使用

1-1、Feign介绍
RestTemplate方式调用存在的问题

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

String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);

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

存在下面的问题:

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

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的注解来声明远程调用的信息,比如:

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

修改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;
    }

发送请求测试,发现不仅远程调用成功,并且实现了负载均衡
【微服务学习笔记】_第57张图片
查看pom依赖,说明feign已经集成了ribbon实现了负载均衡


总结:
 1、引入依赖
 2、添加@EnableFeignClients注解
 3、编写FeignClient接口
 4、使用FeignClient中定义的方法代替RestTemplate

2、自定义Feign的配置

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注解中声明则代表某服务


3、Feign的性能优化

Feign底层的客户端实现:

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

因此优化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功能,设置连接池参数


4、Feign的最佳实践

方式一(继承): 给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
消费者的FeignClient:
在这里插入图片描述
提供者的controller
在这里插入图片描述
【微服务学习笔记】_第58张图片

在消费者的FeignClient和提供者的controller中整个方法(除了名称)的声明是一样的,由于必须声明一样才能访问到,我们就可以做抽取,定义一个接口叫做UserAPI,这个接口将方法的声明写好了,那么消费者的FeignClient就可以直接继承,提供者的controller就可以直接实现这个接口,定义统一标准,这就是继承
但是这样做也有一定的问题,一般情况下服务和客户端之间不推荐去共享接口,因为它会造成紧耦合,都实现了相同的接口,在API层面都已经耦合了,所以它是紧耦合,一旦UserAPI发生改变,那么两边都要一起去修改。而且这种继承方案对springmvc不起作用,springmvc在声明GetMapping之外还有对参数的声明,而方法参数是继承不下来的,尽管存在缺点,但是相对来讲它遵循的面向契约编程的思想,在企业应用还是很多的


方式二(抽取): 将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
【微服务学习笔记】_第59张图片

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所在包

@EnableFeignClients(basePackages = "xy.ysys.feign.clients")

方式二:指定FeignClient字节码

@EnableFeignClients(clients = UserClient.class)



八、Gateway服务网关

1、网关作用介绍

【微服务学习笔记】_第60张图片
网关功能:

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


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

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


2、网关搭建

搭建网关服务的步骤:
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/**

【微服务学习笔记】_第61张图片
总结:
网关搭建步骤:
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工厂:

名称 说明 实例
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

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


4、路由过滤器

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
【微服务学习笔记】_第62张图片
给所有进入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


5、全局过滤器GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与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;
    }
}

过滤器的执行顺序

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

6、跨域问题处理

跨域:域名不一致就是跨域,主要包括:域名不同、端口不同
跨域问题:浏览器禁止请求的发起者与服务端发生跨域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 # 这次跨域检测的有效期

你可能感兴趣的:(微服务,java)