springcloud

164 阅读20分钟

认识微服务

微服务的演变

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

    • 优点:
      • 架构简单
      • 部署成本低
    • 缺点:
      • 耦合度高
  • 分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立的项目开发,称为一个服务

    • 优点:
      • 降低服务耦合
      • 有利于服务升级拓展
    • 服务治理:分布式架构要考虑的问题
      • 服务拆分粒度如何?
      • 服务集群地址如何维护?
      • 服务之间如何实现远程调用?
      • 服务健康状态如何感知?
  • 微服务架构:微服务是一种经过良好设计的分布式架构方案,

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

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

    • 自治:团队独立,技术独立,数据独立,部署独立

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

微服务的结构

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

  • 服务集群:都需要做微服务的拆分,形成服务集群,而集群中的每一个服务都要遵循单一职责的原则,并且要面向服务,对外暴露接口,这样服务之间就可以做一些相互的调用。服务间的调用关系错综复杂,单凭人为是不现实。所以在微服务中,往往有个注册中心来维护服务集群中每个节点的信息。
  • 注册中心:维护微服务中每个节点的信息并且监控这些节点的状态
  • 配置中心:统一管理微服务中的配置信息
  • 服务网关:用户访问的入口,网关把用户的请求路由到服务集群中。在路由的过程中还可以做负载均衡
  • 服务监控和保护:在服务集群中,众多单一服务相互调用,可能会存在出错,问题。要对其进行监控和保护措施

微服务技术对比

企业需求

  • SpringCloud+Feign

    • 使用SpringCloud技术栈
    • 服务接口采用Restful风格
    • 服务调用采用Feign方式
  • SpirngCloudAlibaba + Feign

    • 使用SpringCloudAlibaba技术栈
    • 服务接口采用Restful风格
    • 服务调用采用Feign方式
  • SpringCloudAlibaba+dubbo

    • 使用SpringCloudAlibaba技术栈

    • 服务接口采用dubbo协议标准

    • 服务调用采用dubbo方式

  • dubbo原始模式

    • 使用dubbo老技术栈体系

    • 服务接口采用dubbo协议标准

    • 服务调用采用dubbo方式

SpringCloud

  • SpringCloud是目前国内使用最为广泛的微服务框架。官网地址:spring.io/projects/sp…
  • SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱体验:
    • 服务注册发现:EureKa,Nacos,consul
    • 统一配置管理:SpringCloudConfig,Nacos
    • 服务远程调用:openfeign,dubbo
    • 统一网关路由:SpringCloudGateway,Zuul
    • 服务链路监控:Zipkin,Sleuth
    • 流控,降级,保护:Hystix,Sentinel
  • SpringCloud与SpringBoot版本兼容关系

服务拆分及远程调用

服务拆分注意事项

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

远程调用

  • 1.注册RestTemplate组件在spring中
  • 2.调用RestTemplate中的方法来发http请求
    • get请求:getForObject()
    • post请求:postForObject()
      • 第一个参数url路径
      • 第二个参数告诉返回的类型
  • 服务调用关系:
    • 服务提供者:暴露接口给其他微服务调用
    • 服务消费者:调用其他微服务提供的接口
    • 提供者和消费者的角色是相对的
    • 一个服务可以同时是消费者也可以是提供者

Eureka注册中心

Eureka作用

  • 消费者该如何获取服务提供者具体信息?
    • 服务提供者启动时向eureka注册自己的信息
    • eureka保存这些信息
    • 消费者根据服务名称向eureka拉去提供者的信息
  • 如果有多个服务提供者,消费者该如何选择
    • 服务消费者利用负载均衡算法,从服务提供列表中选择一个
  • 消费者如何感知提供者的健康状况
    • 服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
    • eureka会更新记录服务列表的信息,心跳不正常会被剔除
    • 消费者每次拉取(每隔三十秒定时拉取)的服务列表信息就是尽可能最新的信息
      • 注意每次拉取都会在DynamicServerListLoadBalancer中缓存服务列表所以不用时时拉取

总结

在Eureka注册中心中,微服务角色分为两类

  • EurekaServer:服务端,注册中心
    • 记录服务信息
    • 心跳监控
  • EurekaClient:客户端
    • Provider:服务提供者
      • 注册自己的信息到EurekaServer
      • 每隔30秒向EurekaServer发送心跳请求
    • Consumer: 服务消费者
      • 根据服务名称定时向eureka拉取服务列表
      • 基于服务列表做负载均衡,选中一个微服务后发起远程调用

搭建EurekaServer

  • 创建项目:引入eureka-server依赖
  • 编写启动类,添加@EnableEurekaServer注解
  • 添加application.yml,因为EurekaServer自己也是一个微服务,所以也要把自己注册到注册中心,方便后面注册中心集群之间通讯。

服务注册

  • 引入eureka-client依赖
  • 在application.yml中配置eureka地址
  • 无论是消费者还是提供者,引入eureka-client依赖,知道eureka注册中心地址后,都可以完成注册

服务拉取

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

  • 修改url的路径,用服务名称来代替ip地址和端口
  • 在项目启动类中的RestTemplate添加注解@LoadBalanced

Ribbon负载均衡

负载均衡流程

  • ClientHttpRequestInterceptor:客户端http请求拦截器,resttemplate正是一个发HTTP请求的客户端,所以会被这个接口的实现类(LoadBalancerInterceptor)拦截,接口中的方法是interceptor
  • LoadBalancerInterceptor:实现了CilenHttpRequestInterceptor接口,并重写了interceptor的方法,方法中拦截到了RestTemplate中的请求信息,并找到了服务名称,把服务名称交给了RibbonLoadBalancerClient去执行。
  • RibbonLoadBalancerClient:负载均衡客户端,通过getLoadBalancer(服务名称)方法得到DynamicServerListLoadBalancer(动态服务列表负载均衡器)对象,对象中封装了服务名称所对应的服务列表,通过getServer方法,里面通过调用IRule接口默认实现类(ZoneAvoidanceRule)的负载均衡算法规则,通过算法返回一个服务列表中的一个服务信息,并返回其IP地址和对应端口。最后替换服务名称,用真实的ip地址端口号去发送请求。

负载均衡策略

Ribbon的负载均衡规则是一个叫IRule的接口来定义的,没一个实现类都是一种规则;

常用负载均衡策略

修改负载均衡策略

  • (代码方式)定义一个新的IRule接口实现类并返回
    • 针对所有提供者的微服务,都采用这个负载均衡策略
  • (配置文件方式)
    • 指定某一个提供者的微服务,采用这种负载均衡策略

饥饿加载

Ribbon默认采用的是懒加载,即第一次访问时才回去创建LoadBalancerClient,请求时间会很长。而饥饿加载规则会在项目启动的时候创建,降低第一次访问的耗时。

Nacos注册中心

nacos服务搭建

  • 下载安装包
  • 解压
  • 在bin目录下执行 startup.cmd -m standalone命令

服务注册到nacos注册中心

  • 在父工程种添加spring-cloud-alibaba的管理依赖
  • 注释掉原有的Eureka依赖
  • 添加nacos的客户端依赖
  • 在application.yml中注释eureka地址,配置nacos地址

Nacos服务分级模型

  • Nacos服务分级存储模型
    • 一是服务,例如xxxservice
    • 二是集群,例如某个地方,上海北京
    • 三是实例,例如上海某个机房中部署了xxxservice服务器
  • 如何设置实例的集群属性
    • 修改application.yml中的spring.cloud.nacos.discovery.cluster-name属性

根据集群负载均衡

  • 在服务消费者中application.yml中设置集群
  • 然后在application.yml中配置负载均衡的策略(NacosRule),这个规则会优先会寻找与自己同集群的服务
  • 注意将服务消费者的权重都设为1

实例的权重访问

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

Nacos环境隔离

  • namespace用来做环境隔离
  • 每个namespace都有一个唯一的id
  • 不同namespace下的服务不可相互访问
    • 注意namespace的值是命名空间的id

临时实例和非临时实例

服务注册到Nacos时,可以选择注册为临时实例或者选择非临时实例,通过下面的配置来设置:

Nacos和Eureka区别

  • Nacos和Eureka的共同点
    • 都支持服务注册和服务拉取
    • 都支持服务提供者心跳方式做健康检测
  • Nacos和Eureka的不同点
    • Nacos支持Nacos注册中心主动检测提供者的健康状态:
      • 临时实例用心跳检测
      • 非临时实例采用主动检测模式
    • 临时实例心跳不正常,会被注册中心剔除。非临时实例被主动检测到不正常后并不会被注册中心剔除
    • Nacos支持服务列表变更的消息推送模式(当服务列表发生变化时,注册中心会及时把消息给服务消费者,让其迅速重新拉取一次),可以更及时更新服务列表
    • Nacos集群默认采用AP方式。当集群中存在非临时实例时,采用CP模式。Eureka采用AP模式

Nacos配置管理

统一配置管理

  • 在Nacos中添加配置信息
  • 在弹出表单中填写配置信息
  • 配置获取的步骤
    • 引入Nacos配置管理的客户端依赖
    • 在resource资源文件下添加bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml
    • 在controller中通过@value注解读取配置文件的信息 来验证
  • 注意我们要想读取到Nacos中的配置信息,我们要确定我们Nacos配置信息在哪个namespace下,如果不在默认的public命名空间下,我们就需要通过spring.cloud.nacos.config下的namespace属性来指定命名空间的id。和注册中心的namespace大同小异,只不过注册中心的命名空间设置是在discovery下。

配置热更新

  • 使用@Value注解注入,结合@RefreshScope注解进行配置热更新
  • 使用@ConfigurationProperties注解实现自动配置属性,实时更新

多环境配置共享

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

  • 【spring.application.name】-【spring.profiles.active】.yaml,例如:xxxservice-dev.yaml
  • 【spring.application.name】.yaml,例如:xxxservice.yaml

无论profiles如何变化,都会读取【spring.application.name】.yaml文件,因此多环境配置共享,把共享的配置写入该文件

多种配置的优先级:服务名-profiles.yaml > 服务名.yaml > 本地配置文件

Nacos集群搭建步骤(一台机器操作)

  • 下载解压nacos
  • 创建数据库(nacos_config),并导入nacos中的nacos-mysql.sql文件,并执行
  • 修改cluster.config.example为cluster.config文件,并在里面配置nacos节点信息
  • 数据库配置,打开application.properties文件,1.将spring.datasource.platform=mysql 取消注释表明用的mysql数据库,2.将db.num=1取消注释,表明只有一个数据库,
    • 并修改用户名和密码,并指定连接nacos_config数据库
  • 在cluster.config文件中配置了几个节点信息,就复制几份nacos,并修改各自的端口(在application.properties中修改)一 一与cluster中的端口对应
  • 下载解压nginx,使用nginx来做反向代理和负载均衡,进入nginx的config目录找到nginx.config,在http块中随意地方放一下配置,根据自己的修改
  • 分别启动配置的几个nacos节点,在各自的bin目录下使用 startup.cmd 启动,不加任何参数就是集群激动,然后再启动nginx。最后访问localhost:80/nacos就可以访问到nacos注册中心
  • 最后再Java代码中配置nacos地址的时候直接将端口号改为80,nginx会帮我们负载均衡选择一个去注册

HTTP客户端Feign

RestTemplate方式调用存在的问题

  • 以前利用RestTemplate发起远程调用的代码
    • 代码可读性十分差
    • 参数复杂,url难以维护

Feign的介绍

Feign是一个声明式的Http客户端,官网地址:github.com/OpenFeign/f… ,其作用就是帮我们优雅的实现http请求的发送,解决上面提到的问题

Feign的使用步骤

  • 1.引入依赖
  • 2.在启动类添加注解@EnableFeignClients开启Feign功能
  • 3.编写Feign客户端
  • 4.使用Feign客户端接口中定义的方法代替RestTemplate

自定义Feign的配置

Feign运行自定义配置来覆盖默认配置,可以修改的配置如下

配置Feign日志有两种方式

  • 配置文件方式
    • 全局生效(调用任何接口都会生效)
    • 局部生效(只有当调用指定服务名称的接口才会生效)
  • Java代码方式
    • 首先需要声明一个Bean
    • 其次,如果是全局配置就在@EnableFeignClients注解中声明这个注解类
    • 如果是局部配置,就在@FeignClient注解中声明这个注解类
    • 注意:@EnableFeignClients中的属性是defaultConfiguration,@FeignClient中的属性是configuration

Feign的性能优化

  • Feign底层的客户端实现:
    • URLConnection:默认实现,不支持连接池,连接每次创建时要三次握手,断开时要四次挥手,比较浪费性能,资源
    • Apache HttpClient:支持连接池
      • 引入依赖
      • 配置连接池
    • OKHttp:支持连接池
  • 因此优化Feign的性能主要包括两个方面:
    • 使用连接池代替默认的URLConnection
    • 日志级别,最好用basic或none

Feign的最佳实践

  • 方式一(继承):给消费者的FeignClient和提供者的Controller定义统一的父接口作为标准
    • 一般情况下不建议在提供者和消费者之间共享这个接口,因为会造成紧耦合(消费者和提供者都已经实现相同接口了,从api层面都已经紧耦合了),并且这种方案,父接口参数列表中的映射不会被继承
  • 方式二(抽取):将FeignClient抽取为独立模块,并且把接口有关的POJO,默认的Feign配置都放到这个模块中,提供给所有消费者使用
    • 1.首先创建一个module,命名为feign-api,然后引用feign的starter依赖
    • 2.把service中编写的xxxClient,相关pojo类,默认的feign配置类都copy到feign-api中
    • 在service中引入feign-api依赖
    • 修改service中的所有有关上诉三个组件的import部分,改成导入feign-api包中的
    • 重启测试
    • 注意当定义的FeignClient不在springBootApplication的扫描包范围时,这些FeignClient无法使用,没有被spring扫描到,有两种解决方式:
      • 方式一:在@EnableFeignClients注解中指定FeignClient所在包(创建所在包下所有FeignClient实例)
      • 方式二:在@EnableFeignClients注解中指定具体哪个FeignClient的字节码(只创建指定了的FeignClient得实例)

统一网关Gateway

网关功能

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

网关得技术实现

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

  • gateway
  • zuul

Zuul是基于servlet的实现,属于阻塞式编程,而SpringcloudGateway是基于spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能

搭建网关服务

  • 1.创建新的module,引入springcloudGateway和nacos的服务发现的依赖
  • 2.编写路由配置和nacos地址
    • 路由配置包括
      • 1.路由id:路由的唯一标识
      • 2.路由目标(url):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
      • 路由断言(predicates):判断路由的规则
      • 路由过滤器(filters):对请求或响应做处理

路由断言工厂Route Predicate Factory

官网地址:docs.spring.io/spring-clou…

  • 我们配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
  • 例如Path=/user/**是按照路径匹配,这个规则是由PathRoutePredicateFactory类来处理的
  • 像这样的断言工厂在SpringCloudGateway还有十几个

路由过滤器GatewayFilter

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

过滤器工厂GatewayFilterFactory

官网地址:docs.spring.io/spring-clou…

例如:

案例:给所有进入userservice的请求添加一个请求头:Truth=xxxxxxx

如果要对所有路由都生效,可以将过滤器工厂写到default-filters下

初识Docker

Docker如何解决大型项目依赖关系复杂,不同组件依赖的兼容性问题

  • Docker允许开发中将应用,依赖,函数库,配置一起打包,形成可移植镜像
  • Docker应用运行在容器中,使用沙箱机制,相互隔离

Docker如何解决开发,测试,生产环境有差异的问题

  • Docker镜像中包含完整运行环境,包括系统函数库,仅依赖linux内核,因此可以在任意linux操作系统上运行

总结:Docker是一个快速交付应用,运行应用的技术

  • 可以将其程序及其依赖,运行环境一起打包为一个镜像,可以迁移到任意linux操作系统
  • 运行时利用沙箱机制形成隔离容器,各个应用互不干扰
  • 启动,移除,都可以通过一行命令完成,方便快捷

Docker与虚拟机

差异:

  • docker是一个系统进程,虚拟机是在操作系统中的操作系统
  • docker体积小,启动速度快,性能好;虚拟机体积大,启动速度满,性能一般

镜像操作

  • docker images
  • docker rmi
  • docker pull
  • docker push
  • docker save
  • docker load

创建并运行一个容器

docker run –name containerName -p 80:80 -d nginx

常见容器命令

数据卷

是一个虚拟目录,指向宿主机系统中的某个目录

操作数据卷

总结

数据卷挂载

初识MQ

  • 同步通讯的优缺点
    • 优点:
      • 时效性较强,可以立即得到结果
    • 缺点:
      • 耦合度高
      • 性能和吞吐能力下降
      • 有额外的资源消耗
      • 有级联失败的问题
  • 异步通讯的优缺点:
    • 优点:
      • 耦合度低
      • 性能和吞吐能力提升
      • 故障隔离
      • 流量削峰
    • 缺点
      • 依赖于Broker的可靠性,安全性,吞吐能力
      • 架构复杂,业务没有明显的流程线,不好追踪管理

什么是MQ

MQ(message queue) 中文是消息队列,字面看就是存放消息的队列,也就是事件驱动架构中的broker

SpringAMQP

什么是SpringAMQP

官方地址:spring.io/projects/sp…

  • AMQP:Advanced Message Queuing Protocol。是用于在应用程序或之间传递业务消息的开放标准。该协议与语言和平台无关,更符合微服务中独立性的要求
  • Spring AMQP:Spring AMQP是基于AMQP协议定义的一套API规定,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现

向消息队列发送消息

  • 在父工程引入依赖
  • 在发送端中的application.yaml编写配置信息
  • 编写发送消息的代码

接收消息队列中的信息

  • 因为父工程已经引入依赖,所以不用在引入依赖
  • 在接收端编写配置信息

  • 在接收端创建一个类,编写消费逻辑

work模型的使用

  • 多个消费者绑定到同一个队列,同一条消息只会被一个消费者处理
  • 通过设置prefetch来控制消费者预取的消息数量

消息预取

消息队列存储消息有上限,当一个消费者消费的速度比消息发送的速度满的时候,总会导致队列消息占满,这时会考虑创建多个消费者,共同来处理,底层默认是平均平分消息队列中的消息,不管消费者处理的速度,就先分配好了处理的消息在消费者堆积。我们可以修改prefetch的值为1,表示消费者每次最多只能拿一条消息,处理完了再拿。这样就可以充分体现能者多劳,提高效率。

发布(publish)订阅(subscribe)模型

发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者,实现方式就是加入了exchange(交换机)。常见的exchange的类型

  • Fanout:广播
  • Direct:路由
  • Topic:话题

注意:exchange负责消息的路由,不负责储存,路由失败则消息丢失。

发布订阅-Fanout Exchange

Fanout Exchange会将收到的消息路由到每一个跟其绑定的queue

代码编写

  • 步骤1:在消费者服务声明Exchange,Queue,Binding
  • 步骤2:在消费者服务声明消费者
  • 步骤3:在发布者发送消息

发布订阅-Direct Exchange

Direct Exchange会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(routs)

  • 每一个Queue都与Exchange设置一个BindingKey
  • 发布者发布消息时,指定消息的Routingkey
  • Exchange将消息路由到Bindingkey与RoutingKey相同的Queue

实现思路

  • 利用@RabbitListener声明Exchange,Queue,RoutingKey
  • 在消费者服务中,编写两个消费方法,分别监听direct.queue1和direct.queue2
  • 在发布者中编写测试代码向exchange发送消息

发布订阅-Topic Exchange

TopicExchange与DirectExchange类似,区别在于routingkey必须是多个单词的列表,并且以.分割。queue与exchange指定bindingkey时可以用通配符

  • #:代表0个或多个单词
  • *:代表一个单词

实现思路:同上

消息转换器

elasticsearch

初识elasticsearch

  • 什么是elasticsearch?
    • 一个开源的分布式搜索引擎,可以用来实现搜索,日志统计,分析,系统监控等功能。
  • 什么是elastic stack(ELK)
    • 是以elasticsearch为核心的技术栈,包括beats,logstash,kibana,elasticsearch
  • 什么是Lucene
    • 是apache的开源搜索引擎类库,提供了搜索引擎的核心api

什么是正向索引和倒排索引

  • 什么是文档和词条
    • 每一条数据就是一个文档
    • 对文档中的内容进行分词,得到的词语就i是词条
  • 什么是正向索引
    • 基于文档id创建索引,查询词条时必须先找到文档,而后判断是否包含词条
  • 什么是倒排索引
    • 对文档内容分词,对词条创建索引,并记录词条所在文档的信息,查询时先根据词条查询到文档id,而后获取文档。

文档

elasticsearch是面向文档存储的,可以是数据库的一条商品数据,一条订单信息。文档数据会被序列化为json格式存储在elasticsearch中

索引

  • 索引:相同类型的文档集合
  • 映射:索引中文档的字段约束信息,类似表的结构约束

mysql和elasticsearch对比

mysql和elasticsearch各司其职

  • mysql:擅长事务类型操作,可以确保事务的安全和一致性
  • elasticsearch:擅长海量数据的搜索,分析,计算

索引库操作-mapping属性

  • mapping常见属性:
    • type:数据类型
    • index:是否索引
    • analyzer:分词器
    • properties:子字段
  • type常见类型:
    • 字符串:text keyword
    • 数字:integer long short byte double float
    • 布尔:boolean
    • 日期:date
    • 对象:object

索引库的操作

文档操作