SpringCloud
微服务架构,一个全家桶式的技术栈
Eureka 8761
-
注册中心,基于AP,不保证强一致性
-
Eureak Server:注册中心服务端,用于注册和发现,提供了两层的缓存结构来提高响应效率(@EnableEurekaServer)
- 第一层:是一个只读缓存,就是一个ConcurrentHashMap,主要定时同步于第二层缓存,默认30s
- 第二层:是一个可读写缓存,数据同步于数据存储层
- 数据存储层保存了所有的注册信息,缓存层就是经过包装后的数据,可以在client调用的时候直接返回
-
Eureka Client:注册中心客户端,用来注册服务用的,30s发一个心跳包去跟server续约,会定时去拉取服务端的注册信息,缓存到本地,用的时候优先从本地拿,所以即使server挂了,程序还是可以跑,保证可用性,但是信息可能会不一致。(@DiscoveryClient)
-
Eureka Server 有自我保护机制,默认90s内没有收到客户端的心跳会自动剔除,但是当捕获到大量的心跳失败的时候,可能认为是网络问题,Server会进入自我保护机制,防止误杀客户端
-
Eureka Server 集群,在yml里配置其他可用Server的ServiceUrl就可以了,他们会定时的同步,每个server都有一个独立完整的注册表,Server集群节点平等,没主从之分,客户端注册的时候,如果失败了,会自动切换到其他节点,只要有一台Server正常,就能保证服务可用
Nacos 8848
-
一个分布式的注册中心和配置中心,相当于=Eureka+Config,集群默认基于AP,不保证强一致性,但是保证最终一致性,有主从之分,但是在选举过程中不会影响新服务的注册,也支持CP,只能用put命令去改
-
Nacos 分为服务端和客户端,服务端是单独的一个服务,客户端是java程序,5秒会发一个心跳包,去跟server续约,包内包括实例名,IP,端口,权重等。
-
Nacos客户端有一个长轮询的任务,去监听对应的配置项信息,默认30s,这是一个不停轮询的过程,期间有更新的时候,服务端会返回变更配置项(dataId+group),客户端会把变更的配置项放到一个cacheData对象里,然后更新md5的值(服务端就是通过比较md5的值去检查是否有更新的),再从server端把整个的配置项拿下来,放到本地缓存起来,跟Eureka Client一样的机制,都是先从本地缓存拿注册信息。dataId : application.name+profile.avtive+格式(proterties,yaml)
-
Raft算法:是一种通过对日志进行复制来保证数据最终一致性的算法,leader发起,follower进行复制
-
Leader:唯一负责处理客户端写请求的节点,也可处理读,同时负责日志复制工作
-
Canidate:Leader候选人
-
Follower:负责处理客户端的读请求,同步来自Leader的日志,可参与投票,Leader挂了,转变为候选人参与选举
-
Term:任期,一个新的Leader有一个新的term,是上一届leader的term+1
-
选举发生在两个阶段:集群启动的时候,leader挂了之后
-
所有节点在集群启动时都是follower状态,然后转换成canidate候选人状态,term+1,发起选举,通知其他节点投票
- 票数过半,选举成功;
- 票数不过半,重新发起选举;
- 期间收到其他节点选举成功的通知,自己就切换成follower状态,避免脑裂(多个leader) Redis-Sentinel选举也是Raft
-
-
Consul 8500
Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储(配置中心)、多数据中心方案,不再需要依赖其它工具(比如 ZooKeeper 等),使用起来也较为简单。
Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、Windows 和 Mac OS);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合
服务发现以及注册:当服务 Producer 启动时,会将自己的 Ip/host 等信息通过发送请求告知 Consul,Consul 接收到 Producer 的注册信息后,每隔 10s(默认)会向 Producer 发送一个健康检查的请求,检验 Producer 是否健康
服务调用:当 Consumer 请求 Product 时,会先从 Consul 中拿到存储 Product 服务的 IP 和 Port 的临时表(temp table),从temp table 表中任选一个· Producer 的 IP 和 Port, 然后根据这个 IP 和 Port,发送访问请求;temp table 表只包含通过了健康检查的 Producer 信息,并且每隔 10s(默认)更新。
Zookeeper 2181
-
分布式系统的应用程序协调服务,可以实现数据发布/订阅,master选举,分布式锁
-
matster选举:slave发现master状态down掉后,epoch+1,参加选举,通过zab原子广播协议通知其他节点
-
ZAB原子广播协议
- 广播通知:只有一个Leader节点去处理客户端的所有请求,然后通过ZAB进行广播通知
- 崩溃恢复:它会选举zxid(事务id)最大的节点当新的leader
-
选举参数:逻辑时钟-事务id(zxid)-服务器id(myid) 值越大,选举的权重越大
-
区别
- Eureka:基于AP,节点平等,只要有一个server存活,服务就能保证可用性
- Nacos:默认AP,主从之分,选举过程中不会影响新服务的注册
- Zookeeper:基于CP,不保证高可用,有主从之分,选举过程中会导致服务不可用
Hystrix-Sentinel
-
Hystrix:feign集成,直接yml中开启对feign的支持就可以了,实现fallbackFactory接口,添加断路器,请求过来都是走Hystrix线程池,不同的服务有不同的线程池,互不影响,避免服务雪崩(一个服务阻塞,一系列服务不可用)
-
Sentiel哨兵,可以做熔断降级,可以配合网关做限流,提供了一个控制台,有实时监控,还可以配置很多规则:流控,熔断,热点,授权。微服务里添加依赖,yml文件中配置端口去跟sentinel做交互就可以了;配置规则持久化:可以配合nacos做持久化,sentinel支持json格式的规则;
spring: cloud: sentinel: transport: port: 8719 # 用来跟Sentinel控制台做交互的端口 dashboard: localhost:8080 # 控制台地址端口 -
区别
- Hystrix 线程池隔离和信号量隔离,关注点在隔离和熔断,超时或熔断会快速失败,并提供fallback机制
- Sentiel 信号量隔离,流量控制,熔断降级,系统负载保护,实时监控和控制台
- 线程池隔离:给每个服务单独开一个线程池,保持跟其他服务的隔离。有线程上下文切换,资源消耗大;可以异步,可以同步;适用于并发大,耗时长操作;
- Semaphore信号量:用于并发线程数的控制,只支持同步,就是个计数器,资源消耗小;不支持超时熔断,如果阻塞,只能通过调用协议(socket超时才能返回),适用于并发大,耗时短的操作;
Feign
- 声明式微服务调用,集成了ribbon和hystrix
- 基于动态代理,根据注解和选择的机器去拼接url地址,发起请求
- 一般用OpenFeign,添加了springMVC注解的支持
- 定义client接口,添加@FeignClient注解,注解上添加要调用的服务名,还有实现FallbackFatory接口,用于熔断降级
- 启动类加@EnableFeignClients注解,激活Feign组件
- gzip压缩数据,减少网络传输的字节数,加快响应速度,yml中开启compression
- http连接池,减少http连接的资源消耗,提升吞吐量
- undertow替换tomcat,undertow在并发环境下性能比tomcat高,undertow是基于NIO的web嵌入式服务器
- 请求超时:yml中配置ribbon的请求超时处理
-- 输出 http 请求头
创建配置类FeignConfig :
@Bean
Logger.Level deviceSendCmdFeignLoggerLevel() {
return Logger.Level.FULL;
}
在@FeignClient上添加配置: configuration = FeignConfig.class
配置文件application.yml中添加
logging:
level:
com......: debug
Ribbon
-
是一种客户端负载均衡的方式,由客户端根据策略从服务列表里选择服务去调用
-
负载均衡策略默认是轮询,可以通过IRule接口去自定义,有随机,重试,加权
-
在Config-Server里面加个配置类,去返回个新的规则
` @Bean public IRule myLoad(){` ` return new RandomRule(); }` -
服务端负载均衡:Nginx,一个请求过来经过Nginx的均衡策略去选择机器执行,客户端是不知道具体提供服务的是哪台
-
负载均衡是通过拦截器机制实现的,LoadBalancerInterceptor会拦截RestTemplate(spring提供的一个http请求工具,post/get请求)的请求,然后通过Request获取UrI对象中的服务名,LoadBalancerClient会根据服务名去选择具体的服务去执行
-
负载均衡三大组件:负载均衡规则;ping任务(看服务是否可用);服务列表,后台有个线程定时刷新检查
Sleuth
-
Sleuth是一种分布式链路追踪解决方案,可以配合ZipKin,来进行可视化的链路追踪,Sleuth将完整的调用链路输出到日志里,zipkin是将日志聚合然后通过web界面进行可视化展示(系统依赖关系和系统耗时)
-
服务间有调用时,会被Sleuth的监听器监听,并生成相应的trace(一次完整的链路)和span(一次单独的调用链)信息通过消息总线或者web的方式发送给zipkin服务端进行展示
-
默认情况下zipkin的链路追踪日志信息都在内存中,关闭服务后就没有了,可以配合es或者mysql进行持久化,导入zipkin的初始化脚本,三张表(zipkin_spans,annotaions,dependencies),启动zipkin-server的时候去指定mysql数据库
-
导入依赖,启动zipkin服务端,yml配置zipkin服务地址,传输方式,配置sleuth收集数据百分比等等
# 配置zipkin zipkin: base-url: http://localhost:9411 # 服务端地址 sender: type: kafka #数据传输方式,web基于http报文 # 配置sleuth sleuth: sampler: probability: 1.0 # 收集数据百分比,默认0.1(10%)
Config
-
是一个分布式系统的配置中心;用服务端和客户端的方式来提供配置服务。
-
Server服务端:存储配置文件,以接口的形式将配置文件的内容提供出去,默认Git @EnableConfigServer
-
Client客户端:需要指定服务端的service-id,通过接口获取数据,初始化自己的应用
- 服务端是通过建立一个RestController来接收读取配置请求,然后会返回一个Json
- 客户端通过getRemoteEnvironment方法,去获取Server返回的Json串,进行反序列化
-
服务端和客户端提供服务的Rest接口,就是通过SpringBoot的自动装配来实现的,config-server和config-client的Jar包里都有spring.factories文件,SpringBoot会自动去加载
spring:
cloud:
config:
name: consumer-server-config # 配置文件名称,对应 git 仓库中配置文件前半部分
label: master # git 分支
profile: pro # 指定环境
discovery:
enabled: true # 开启
service-id: config-server # 指定配置中心服务端的 service-id
Bus
- 消息总线,集成了RabbitMQ和Kafka等消息代理。它会连接微服务系统中所有拥有Bus总线机制的节点,数据变更的时候,会通过消息中间件广播通知所有的微服务节点更新数据,例如:微服务配置文件更新等 (配置刷新类要加@RefreshScope)
- 服务端全部通知刷新 post方式访问 http://Config-Server的IP:端口/actuator/bus-refresh 会通过广播至全部bus客户端节点
- 服务端指定服务刷新 post方式访问 http://Config-Server的IP:端口/actuator/bus-refresh/指定服务的applicationName:端口
- 服务端指定集群刷新 post方式访问 http://Config-Server的IP:端口/actuator/bus-refresh/指定服务的applicationName:** (比如订单微服务集群,name相同,端口不同)
Stream
-
消息驱动,用来整合消息中间件,降低消息中间件和系统的耦合性,可以通过配置binder绑定不同的消息中间件,程序内部的具体实现不用修改
-
producer消息生产者,通过Source.java 将消息序列化成Json发送到channel消息通道里
-
consumer消息消费者,通过Sink.java 从channel中取出消息,反序列化成消息对象,交给消息监听处理@StreamListener
-
Binder:目标绑定器,目标指的是 Kafka 还是 RabbitMQ
-
@Output:输出通道,发布消息
-
@Input:输入通道,接收消息
-
@StreamListener:消费者监听队列
-
@EnableBinding:注解标识绑定,将信道
channel和交换机exchange绑定在一起。 -
exchange交换器,Source.java 的@OutPut,Sink.java的@Input 的值默认是绑定的exchange交换器,可以在yml中配置
// 发送消息 myProcessor.sourceOutput().send(MessageBuilder.withPayload(sourceMessage).build()); // 接收消息 @StreamListener(MyProcessor.EMAIL_MESSAGE) -
可以自定义消息通道,@Compoment,@OutPut - MessageChannel,@Input - SubscribableChannel
-
消息分组,避免重复消费,直接在yml中配置group,组内是竞争关系
-
消息分区,producer配置分区键的表达式规则,可以用payload,设置分区数量(消费者数量),consumer配置消费者总数,当前消费者索引,开启分区支持;为了保证生产者发送的同一种消息始终由同一个消费者消费(比如做统计的时候,要保证拿到全部的数据)
# 消费者 spring: cloud: stream: bindings: demo_input: # exchange交换器 destination: demo # 交换器名称,不写默认为demo_input group: service-d # 分组 consumer: partitioned: true # 开启分区支持 instance-count: 2 # 消费者总数 instance-index: 1 # 当前索引 # 生产者 producer: partitionKeyExpression: payload # 分区键表达式规则 partitionCount: 2 # 消费者总数
Security
-
认证和授权,可以在网关做jwt验签,做限流
-
Oauth2认证协议,分为认证服务器和资源服务器(网关),网关是所有微服务统一的门面,一个请求过来先去认证服务器认证,登陆成功后,给用户发一个token令牌,只有带着这个token才能去访问网关,token有时间限制,过期失效。
-
JWT:JSON WEB TOKEN,就是一个带有签名的字符串,分为3个部分
- header:包括加密算法,对称HS256,非对称的RS256,类型就是JWT
- payload:有效载荷,可以存放用户信息
- signature:签名,对header和payload进行加密签名,认证服务器用私钥签名,网关通过公钥验签
Gateway
-
微服务API网关(应用级防火墙),大部分功能是通过过滤器来实现,可以做统一的降级,认证授权
-
高可用网关:主要手段是数据冗余备份和服务的失效转移,做集群,使用Nginx做负载均衡
-
Route路由:包括id,url,一组predicate断言和一组filter过滤器
-
Predicate断言:路由的匹配规则,比如匹配path,header,method等;一个路由下的所有规则同时满足才被执行;一个请求满足多个路由条件时,请求只会被第一个成功匹配的路由转发;
-
Filter过滤器:在Predicate规则匹配后,负责做一些请求前或后的处理,比如鉴权,限流
-
pre前置过滤器(参数校验,权限校验)和post后置过滤器(对响应内容做一些修改)
-
网关过滤器:GatewayFilter,可以配置在具体的路由上,也可以配置在全局路由上,对所有路由起作用
具体路由:routes.filters 全局路由:default-filters 有20多种过滤器工厂,像头部过滤器(header),路径过滤器(path),重写请求url过滤器(PrefixPath为匹配的url添加前缀,StripPrefix剥离路径个数) -
全局过滤器:GlobalFilter,不需要配置,系统初始化时加载,作用在每个路由上,可以做统一的权限认证
-
自定义网关过滤器实现接口GatewayFilter,Ordered(过滤器执行顺序,order值越小,优先级越高)
-
自定义全局过滤器实现GlobalFilter,Ordered,添加@Component注解即可。
-
计数器算法,会有临界问题和资源浪费
-
令牌桶算法,以一定速率生产令牌,放到桶里,桶满直接丢,所有请求只有拿到令牌才能执行;
-
Gateway可以通过RedisRateLimiter来实现令牌桶限流;导入Redis-reactive依赖,
创建KeyResolver来指定限流的Key,有请求路径限流,IP限流,请求参数限流,yml中配置Redis和限流的规则
/**
* 根据请求路径限流
*/
@Bean
@Primary
public KeyResolver pathKeyResolver() {
//写法1
return exchange -> Mono.just(
exchange.getRequest()
.getPath()
.toString()
);
}
/**
* 根据请求IP限流
*/
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest()
.getRemoteAddress()
.getHostName()
);
}
/**
* 根据请求参数中的userId进行限流
*/
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(
exchange.getRequest()
.getQueryParams()
.getFirst("userId")
);
}
Yml配置:
filters:
- name: RequestRateLimiter
args:
burstCapacity: # 桶容量
replenishRate: # 每秒填充速率
key-resolver: # 限流解析器的对象名