4 分布式微服务及中间件
4.1 消息队列(RabbitMQ、Kafka、RocketMQ)
1、为什么消息队列?优缺点?业务场景?
优点:解耦、异步、削峰
缺点:
- 系统复杂度提高
- 解决幂等性问题
- 解决消息丢失问题
- 解决消息传递的顺序
- 一致性问题:ABC三个系统,AB成功、C失败
- 可用性问题:MQ挂了后面的业务全部不能用了
业务场景:
- 批量生成文件任务、多个执行器接受Kafka消息去任务
- 非核心业务,比如说:修改会员积分、发送短信、生成PDF
- 定时活动抢下载券或免费会员、采用MQ削峰
2、如何保证消息消费时的幂等性?
- 使用数据库表主键
- 使用Redis分布式锁
3、如何解决消息丢失?
- 生产者
Rabbitmq
(同步)可以选择用提供的事务功能,但是如果吞吐量上来,太耗性能
(异步)开启confirm模式,每个消息会分配唯一id,写入rabbitmq会给你回传一个ack消息,告知成功。如果rabbitmq没能处理这个消息会回调nack接口,告知失败,可以重试。
Kafka
由于Kafka的HA机制,生产者是不会丢失数据的
- 消息队列
Rabbitmq
开启rabbitmq的持久化到磁盘,和confirm机制配合,只有消息被持久化到磁盘之后才返回ack
Kafka
leader所在broker发生故障,进行leader切换时,数据会丢失,设置4个参数解决
replication.factor>1,保证每个partition必须有至少2个副本
min.insync.replicas>1,保证一个leader至少和一个follower保持联系
acks=all,必须是写入所有replica之后,才能认为是写成功了
retries=MAX,无限重试
- 消费者
RabbitMq
关闭rabbitmq自动ack,可以通过一个api来调用手动ack,当某一个consumer宕机会把这个消费分配给别的consumer去处理
Kafka
kafka会自动提交offset,设置为手动提交offset即可
4、消息队列的顺序消费问题?
场景:数据库同时对一个Id的数据进行了增、改、删三个操作,但是你消息发过去消费的时候变成了改,删、增,这样数据就不对了。
解决办法:
- RocketMQ: 一个topic下有多个队列,为了保证发送有序,RocketMQ提供了MessageQueueSelector队列选择机制:
- Hash取模法:让同一个订单发送到同一个队列中,再使用同步发送,只有同个订单的创建消息发送成功,再发送支付消息。这样,我们保证了发送有序。
- RocketMQ的topic内的队列机制,可以保证存储满足FIFO(First Input First Output 简单说就是指先进先出),剩下的只需要消费者顺序消费即可。
- RocketMQ仅保证顺序发送,顺序消费由消费者业务保证!!!
- Kafka
为了保证一个消费者中多个线程去处理时,不会使得消息的顺序被打乱,则可以在消费者中,消息分发至不同的线程时,加一个队列,消费者去做hash分发,将需要放在一起的数据,分发至同一个队列中,最后多个线程从队列中取数据;
写 N 个内存 queue,具有相同 key 的数据都分发到同一个内存 queue;
然后对于 N 个线程,每个线程分别消费一个内存 queue 即可,这样就能保证顺序性。
(https://blog.csdn.net/weixin_43934607/article/details/115631270?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162313161016780255271168%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162313161016780255271168&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_v2~rank_v29-1-115631270.pc_search_result_control_group&utm_term=Kafka%E7%9A%84%E9%A1%BA%E5%BA%8F%E6%B6%88%E8%B4%B9%E9%97%AE%E9%A2%98&spm=1018.2226.3001.4187)
5、Kafka、RabbitMQ、ActiveMQ、RocketMQ的区别和适用场景?
(1)区别
- ActiveMQ
完全兼容JMS规范
偶尔会有较低概率丢失消息
- RabbitMQ
Erlang语言开发,延时很低
延时最低、实现机制较重
- RocketMQ
Java开发、可以自己掌控
阿里开发的技术社区活跃一般
支持事务
- Kafka
超高吞吐量
可分布式任意扩展
(2)适用场景
ActiveMQ: 不推荐
RabbitMQ: 社区活跃高,中小公司推荐
RocketMQ: 社区活跃低,基础架构研发实力较强的公司推荐
Kafka: 适用于大数据量日志等
6、如何保证消息队列的高可用啊?
RabbitMQ的高可用性
单机模式
普通集群模式
镜像集群模式
Kafka的高可用性
- 存储结构
broker(一个即一个节点)
topic(主题(消息分类):一个topic的多个partition可能分布在不同机器上)
partition(一个文件夹:partition中的每条消息都会分配一个有序的id,即offset,其中包含多个segment)
segment(一对文件:一个索引文件,一个数据文件)
- HA机制(replica副本机制)
1、所有replica副本会选举一个leader,生产和消费都跟这个leader打交道
2、生产时写入leader之后,然后follower再去leader进行同步,所有follower返回ack即成功
3、消费时只读leader,只有被所有follower都同步成功返回ack的时消息才会被消费者读到
7、几百万消息在消息队列里积压了好几个钟怎么处理?
- 临时紧急扩容
1)先修复consumer的问题,确保其恢复消费速度,然后将现有consumer都停掉
2)新建一个topic,partition是原来的10倍,临时建立好原先10倍的queue数量
3)然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
4)接着临时征用10倍的机器来部署consumer,每一个consumer消费一个临时queue的数据
5)这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
6)等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息
- 由于RabbitMQ超时设置导致数据丢失问题
批量重导:在晚上少人用的时候写脚本手动发到mq里去补回丢失数据
8、Kafka专题
1.Kafka为什么快
- 写入数据:
1、顺序写入
2、Memory Mapped Files(内存映射文件)
工作原理:直接利用操作系统的Page来实现文件到物理内存的直接映射。完成映射之后你对物理内存的操作会被同步到硬盘上(操作系统在适当的时候),使用这种方式可以获取很大的I/O提升,省去了用户空间到内核空间复制的开销。
- 读取数据
1、基于sendfile实现Zero Copy(零拷贝)
对比:
- 传统模式(四次拷贝与四次上下文切换):以将磁盘文件通过网络发送为例:首先通过系统调用将文件数据读入到内核态 Buffer(DMA 拷贝),然后应用程序将内存态 Buffer 数据读入到用户态 Buffer(CPU 拷贝),接着用户程序通过 Socket 发送数据时将用户态 Buffer 数据拷贝到内核态 Buffer(CPU 拷贝),最后通过 DMA 拷贝将数据拷贝到 NIC Buffer。同时,还伴随着四次上下文切换。
- 零拷贝(基于sendfile和transferTo实现):数据通过 DMA 拷贝到内核态 Buffer 后,直接通过 DMA 拷贝到 NIC Buffer,无需 CPU 拷贝。这也是零拷贝这一说法的来源。除了减少数据拷贝外,因为整个读文件到网络发送由一个 sendfile 调用完成,整个过程只有两次上下文切换。
注: transferTo 和 transferFrom 并不保证一定能使用零拷贝。实际上是否能使用零拷贝与操作系统相关,如果操作系统提供 sendfile 这样的零拷贝系统调用,则这两个方法会通过这样的系统调用充分利用零拷贝的优势,否则并不能通过这两个方法本身实现零拷贝。
2、批量压缩
2.Kafka写入数据的流程
(1)producer先从broker-list的节点中找到该partition的leader;
(2)然后producer将消息发送给作为leader的partition;
(3)leader收到消息后,将消息写入本地log;
(4)followers从leader中pull消息,实现replication的副本备份机制,同样写入本地log;
(5)replication写入本地log后向leader发送ack(确认);
(6)leader收到所有的replication的ack之后,向producer发送ack;
(7)producer收到leader的ack,证明生产的数据已被kafka成功写入。
3.Kafka新增节点时,数据重分配问题
https://www.cnblogs.com/xionggeclub/p/9390037.html
4.Kafka分区原因和原则
- 分区原因
(1)从producer角度来看,kafka分区更便于在集群中扩展,一个topic可以有多个partition组成,不同partition存放在不同的broker物理节点上,如果往kafka上放大量数据,则只需要增加节点就行,整个kafka集群就可以适应任意大小的数据。
(2)从consumer来看,如果没有分区,一个consumer只能消费一个topic的数据;分区后可以提高并发,多个consumer共同构成一个consumer group,不同consumer对一个topic下的不同partition进行消费,这样可以实现并发消费,消费效率大大提高。
- 分区原则
(1)若指定了partition,则直接使用;
(2)若未指定partition但指定了key,通过key的value进行hash出一个partition;
(3)若partition和key都未指定,则使用轮询RoundRobin的方式选出一个partition。
4.2 分布式缓存Redis
1、为什么在项目里要用缓存呢?用了缓存存在什么问题?
1.为什么在项目里要用缓存呢?
- 高性能(比较常见结合场景)
1)共享会话存储用户信息
2)把需要经常用到的数据存在缓存,类似浏览量、日活等这些
- 高并发
有脑知识库:抢优惠券,抢免费会员等
2.用了缓存存在什么问题?
1)缓存与数据库双写不一致
2)缓存雪崩
3)缓存穿透
4)缓存并发竞争
2、Redis的线程模型?Redis单线程模型为什么效率高?
1.Redis的线程模型
1)文件事件处理器(因为这个是单线程的,所以redis是单线程)
- 结构:
server socket(多个socket客户端连接)
IO多路复用程序
队列
文件事件分派器
文件事件处理器
连接应答处理器
连接redis:socket关联连接应答处理器
命令请求处理器
写数据到redis:socket关联命令请求处理器
命令回复处理器
从redis读数据:socket关联命令回复处理器
- 流程:多个socket可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,但是会将socket放入一个队列中排队,每次从队列中取出一个socket给事件分派器,文件事件分派器会根据每个socket当前产生的事件,来选择对应的事件处理器来处理。事件处理完之后,IO多路复用程序才会将队列中的下一个socket给事件分派器。
2)文件事件
IO多路复用程序可以同时监听AE_REABLE和AE_WRITABLE两种事件,文件事件分派器优先处理AE_REABLE事件
2.单线程模型效率高的原因?
- 纯内存操作
- 核心是基于非阻塞的IO多路复用机制
- 单线程反而避免了多线程的频繁上下文切换问题(百度)
3、Redis内存淘汰机制
1)noeviction:当内存不足写入新数据直接报错(没人用)
2)allkeys-lru:当内存不足移除最近最少使用的key(这个是最常用的)
3)allkeys-random:当内存不足以容纳随机移除某个key(一般没人用)
4)volatile-lru:当内存不足时在设置了过期时间的键中移除最近最少使用的key(这个一般不太合适)
5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
4、Redis的雪崩问题和内存穿透问题
- 雪崩
事前:redis高可用,主从+哨兵,redis cluster,避免全盘崩溃
事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL被打死
事后:Redis持久化,快速恢复缓存数据
业务代码:缓存预热,缓存失效时间均匀分布
- 内存穿透
比如请求参数id=-99999在数据库查不到数据,这时候在返回之前在redis塞入key=-99999,value=null,下次请求就会先请求Redis
5、如何解决redis的并发竞争问题?如何保证缓存与数据库双写时的数据一致性?
1.Redis的并发竞争问题:
Redis事务的CAS方案:分布式锁确保同一时间只能有一个实例在操作,每次写之前对比value的时间戳是否比缓存的时间戳更新,是的话才可以写入。
2.保证缓存与数据库双写时的数据一致性
6、Redis Cluster
- 数据分布算法
(1)传统hash算法
缺点:一旦有一个master宕机,通过新的取模导致走的机器错位,几乎全部缓存失效,大量缓存重建(高并发场景是不允许的)
(2)一致性hash算法
缺点:通过hash环顺时针,虽然不会大量缓存重建,但是会导致丢失的数据涌入下一个master
+虚拟节点(自动负载均衡)---->解决分布不均匀
(3)hash slot算法
redis cluster有固定的16384个hash slot,对每个key计算CRC16值,然后对16384取模,可以获取key对应的hash slot
redis cluster中每个master都会持有部分slot,比如有3个master,那么可能每个master持有5000多个hash slot
hash slot让node的增加和移除很简单,增加一个master,就将其他master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去,
(移动hash slot的成本是非常低的,但会有短暂卡顿)
客户端的api,可以对指定的数据,让他们走同一个hash slot,通过hash tag来实现
- 核心原理
(1)节点间采用gossip协议(小道留言)通信
a.集中式和gossip协议对比
- 集中式:
- 优点: 元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到;
- 缺点: 所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力
- gossip协议:
- 优点: 元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;
- 缺点:元数据更新有延时,可能导致集群的一些操作会有一些滞后(reshard,去做另外一个操作,configuration error,还没有达成一致)
- 消息:
- meet: 某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信 redis-trib.rb add-node
- ping: 每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据
- pong: 返回ping和meet,包含自己的状态和其他信息,也可以用于信息广播和更新
- fail: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了
b.10000端口: 自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口
c.交换的信息
- 故障信息
- hash slot信息
- 节点的增加和移除
(2)smart jedis
a.是什么?
- 基于重定向的客户端,很消耗网络IO,比如 jedis 就是是smart的。本地维护一份hashslot -> node的映射表,缓存,大部分情况下,直接走本地缓存就可以找到hashslot -> node,不需要通过节点进行moved重定向
b.JedisCluster的工作原理
- 在JedisCluster初始化的时候,就会随机选择一个node,初始化hashslot -> node映射表,同时为每个节点创建一个JedisPool连接池
- 每次基于JedisCluster执行操作,首先JedisCluster都会在本地计算key的hashslot,然后在本地映射表找到对应的节点
- 如果那个node正好还是持有那个hashslot,那么就ok; 如果说进行了reshard这样的操作,可能hashslot已经不在那个node上了,就会返回moved,如果JedisCluter API发现对应的节点返回moved,那么利用该节点的元数据,更新本地的hashslot -> node映射表缓存
- 重复上面几个步骤,直到找到对应的节点,如果重试超过5次,那么就报错,JedisClusterMaxRedirectionException jedis老版本在某个节点故障还没自动切换恢复时,会过度的hash slot更新和ping节点检查活跃,导致大量网络IO开销,jedis新版本有进行优化,避免了该问题
c.hashslot迁移和ask重定向
- 如果hash slot正在迁移,那么会返回ask重定向给jedis,jedis接收到ask重定向之后,会重新定位到目标节点去执行,但是因为ask发生在hash slot迁移过程中,所以JedisCluster API收到ask是不会更新hashslot本地缓存,等到hashslot迁移完了,moved才会更新本地hashslot->node映射表缓存的
(3)高可用性和主备切换原理
a.判断节点宕机
- 在cluster-node-timeout内,某个节点一直没有返回pong,那么就被认为pfail,主观宕机(sdown)
- 如果一个节点认为某个节点pfail了,那么会在gossip ping消息中,ping给其他节点,如果超过半数的节点都认为pfail了,那么就会变成fail,客观宕机(odown)
(跟哨兵的原理几乎一样)
b.从节点超时过滤
- 对宕机的master node,从其所有的slave node中,选择一个切换成master node,检查每个slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没有资格切换成master(跟哨兵是一样的)
c.从节点选举
- 哨兵:对所有从节点进行排序,slave priority,offset,run id,每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举,所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 + 1)都投票给了某个从节点,那么选举通过,那个从节点可以切换成master,从节点执行主备切换,从节点切换为主节点。
d.与哨兵比较: 整个流程跟哨兵非常类似,所以说redis cluster功能强大,直接集成了replication(主从模式)和sentinal(哨兵模式)的功能
7、Redis集群模式和主从模式、哨兵模式对比有什么区别
主从是为了数据备份,sentinel着眼于高可用,Cluster提高并发量。
1.主从:读写分离,数据备份,负载均衡,一个Master可以有多个Slaves。
2.sentinel(哨兵):监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器。
3. Cluster(集群):为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。
- 哨兵模式监控权交给了哨兵系统,集群模式中是工作节点自己做监控;
- 哨兵模式发起选举是选举一个leader哨兵节点来处理故障转移,集群模式是在从节点中选举一个新的主节点,来处理故障的转移;
4.3 分布式搜索引擎Elasticsearch
1、ES是如何实现分布式?
1、index -> type -> mapping -> document -> field
- 一个索引可以拆分成多个shard,每个shard存储部分数据,这个shard的数据实际是有多个备份,就是说每个shard都有一个primary shard,负责写入数据,但是还有几个replica shard。primary shard写入数据之后,会将数据同步到其他几个replica shard上去。
- 通过这个replica的方案,每个shard的数据都有多个备份,如果某个机器宕机了,没关系啊,还有别的数据副本在别的机器上呢。
- es集群多个节点,会自动选举一个节点为master节点,这个master节点其实就是干一些管理的工作的,比如维护索引元数据拉,负责切换primary shard和replica shard身份之类的。
1、如果master节点宕机了,那么会重新选举一个节点为master节点。
2、如果是非master节点宕机了,那么会由master节点,让那个宕机节点上的primary shard的身份转移到其他机器上的replica shard。
3、如果修复了那个宕机机器,重启了之后,master节点会控制将缺失的replica shard分配过去,同步后续修改的数据之类的,让集群恢复正常。
2、生产环境上是怎么部署的?
(1)es生产集群我们部署了5台机器,每台机器是6核64G的,集群总内存是320G
(2)我们es集群的日增量数据大概是2000万条,每天日增量数据大概是500MB,每月增量数据大概是6亿,15G。目前系统已经运行了几个月,现在es集群里数据总量大概是100G左右。
(3)目前线上有5个索引(纳税人缴纳税纪录、服务纪录等等可以放es的),每个索引的数据量大概是20G,所以这个数据量之内,我们每个索引分配的是8个shard,比默认的5个shard多了3个shard。
3、ES写入和查询的工作流程?
(1)写入过程
1)客户端选择一个node发送请求过去,该node就是coordinating node(协调节点)
2)coordinating node,对document进行路由,将请求转发给对应的node(有primary shard)
3)实际的node上的primary shard处理请求,然后将数据同步到replica node
4)coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端
(2)查询过程
查询,GET某一条数据,你可以通过doc id来查询,会根据doc id进行hash,判断出来当时把doc id分配到了哪个shard上面去,从那个shard去查询
1)客户端发送请求到任意一个node,成为coordinate node
2)coordinate node对document进行路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡
3)接收请求的node返回document给coordinate node
4)coordinate node返回document给客户端
(3)es搜索数据过程
1)客户端发送请求到一个coordinate node
2)协调节点将搜索请求转发到所有的shard对应的primary shard或replica shard也可以
3)query phase:每个shard将自己的搜索结果(其实就是一些doc id),返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果
4)fetch phase:接着由协调节点,根据doc id去各个节点上拉取实际的document数据,最终返回给客户端
(4)写数据底层原理、核心概念:refresh、flush、translog、merge
1)数据先写入buffer,在buffer里的时候数据是搜索不到的;同时写入translog日志
2)refresh过程:如果buffer快满了,或每隔1秒,就会将buffer数据refresh到os cache,然后再进入segment file的磁盘文件的(只要数据进入os cache,此时就可以让这个segment file的数据对外提供搜索了)
3)commit操作:当translog达到一定长度时就会触发,将buffer中现有数据refresh到os cache中去,清空buffer,将一个commit point写入磁盘文件,里面标识着这个commit point对应的所有segment file,然后强行将os cache中目前所有的数据都fsync到磁盘文件中去
4)flush操作:将现有的translog清空,然后再次重启启用一个translog,此时commit操作完成。默认每隔30分钟会自动执行一次commit,但是如果translog过大,也会触发commit。整个commit的过程,也就是flush过程
5)merge过程:
如果是删除操作,commit的时候会生成一个.del文件,里面将某个doc标识为deleted状态,那么搜索的时候根据.del文件就知道这个doc被删除了
如果是更新操作,就是将原来的doc标识为deleted状态,然后新写入一条数据
buffer每次refresh一次,就会产生一个segment file,所以默认情况下是1秒钟一个segment file,segment file会越来越多,此时会定期执行merge,每次merge的时候,会将多个segment file合并成一个,同时这里会将标识为deleted的doc给物理删除掉,然后将新的segment file写入磁盘,这里会写一个commit point,标识所有新的segment file,然后打开segment file供搜索使用,同时删除旧的segment file。(有点像JVM垃圾回收的标记-复制算法)
4、几十亿数据级场景下如何优化查询性能?
(1)性能优化的杀手锏——filesystem cache
1)es的搜索引擎严重依赖于底层的filesystem cache,如果给filesystem cache更多的内存(至少一半),尽量让内存可以容纳所有的indx segment file索引数据文件,那么你搜索的时候就基本都是走内存的(至少半数走内存),性能会非常高。
2)es + hbase架构:搜索关键词 类似name,tag,这些放es,然后id放hbase,通过关键词找到id,再通过id直接去hbase找,尽量减少es的存储,非搜索字段不要放入es浪费内存
(2)数据预热
那些你觉得比较经常会有人访问的数据,最好做一个专门的缓存预热子系统,就是对热数据,每隔一段时间,你就提前访问一下,让数据进入filesystem cache里面去。这样下次别人访问的时候性能会好一些。
(3)冷热分离
类似于mysql的水平拆分,就是将冷数据写入一个索引中,然后热数据写入另外一个索引中,这样可以确保热数据在被预热之后,尽量都让他们留在filesystem os cache里,别让冷数据给冲刷掉。
假设你有6台机器,2个索引,一个放冷数据,一个放热数据,每个索引3个shard,
3台机器放热数据index;另外3台机器放冷数据index
(4)分页性能优化
1)不允许深度分页/默认深度分页性能很惨
2)类似于app里的推荐商品不断下拉出来一页一页的,采用scroll api,scroll的原理实际上是保留一个数据快照,scroll api是只能一页一页往后翻的,不能跳页
5、TF-IDF(term frequency–inverse document frequency)
TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与资讯探勘的常用加权技术。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。TF-IDF加权的各种形式常被搜寻引擎应用,作为文件与用户查询之间相关程度的度量或评级。除了TF-IDF以外,因特网上的搜寻引擎还会使用基于连结分析的评级方法,以确定文件在搜寻结果中出现的顺序。
原理:
- 在一份给定的文件里,词频 (term frequency, TF) 指的是某一个给定的词语在该文件中出现的次数。这个数字通常会被归一化(分子一般小于分母 区别于IDF),以防止它偏向长的文件。(同一个词语在长文件里可能会比短文件有更高的词频,而不管该词语重要与否。)
- 逆向文件频率 (inverse document frequency, IDF) 是一个词语普遍重要性的度量。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。
- 某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语。
- TFIDF的主要思想是:如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。TFIDF实际上是:TF * IDF,TF词频(Term Frequency),IDF反文档频率(Inverse Document Frequency)。TF表示词条在文档d中出现的频率(另一说:TF词频(Term Frequency)指的是某一个给定的词语在该文件中出现的次数)。IDF的主要思想是:如果包含词条t的文档越少,也就是n越小,IDF越大,则说明词条t具有很好的类别区分能力。如果某一类文档C中包含词条t的文档数为m,而其它类包含t的文档总数为k,显然所有包含t的文档数n=m+k,当m大的时候,n也大,按照IDF公式得到的IDF的值会小,就说明该词条t类别区分能力不强。(另一说:IDF反文档频率(Inverse Document Frequency)是指果包含词条的文档越少,IDF越大,则说明词条具有很好的类别区分能力。)但是实际上,如果一个词条在一个类的文档中频繁出现,则说明该词条能够很好代表这个类的文本的特征,这样的词条应该给它们赋予较高的权重,并选来作为该类文本的特征词以区别与其它类文档。这就是IDF的不足之处.
4.4 分布式架构
4.4.1 Dubbo+Zookeeper
1、dubbo都支持哪些通信协议以及序列化协议?
(1)dubbo协议(默认),单一长连接,NIO异步通信,基于hessian序列化协议
(2)rmi协议,java二进制序列化
(3)hessian协议,也是hessian序列化协议
(4)http协议,json序列化
(5)webservice协议,SOAP文本序列化
2、dubbo的负载均衡策略、集群容错策略、动态代理策略
(1)dubbo负载均衡策略
1)random loadbalance,随机调用实现负载均衡(可加权)
2)roundrobin loadbalance,默认均匀地将流量打到各个机器上去(可加权)
3)leastactive loadbalance,自动感知一下,如果某个机器性能越差,那么接收的请求越少,越不活跃,此时就会给不活跃的性能差的机器更少的请求
4)consistanthash loadbalance,一致性Hash算法,相同参数的请求一定分发到一个provider上去,provider挂掉的时候,会基于虚拟节点均匀分配剩余的流量,抖动不会太大。
(2)dubbo集群容错策略
1)(默认)failover cluster模式(失败自动切换、重试其他机器,常见于读操作)
2)failfast cluster模式(一次调用失败就立即失败,常见于写操作)
3)failsafe cluster模式(出现异常时忽略掉,常用于不重要的接口调用,比如记录日志)
4)failback cluster模式(失败了后台自动记录请求,然后定时重发,适合于写消息队列这种)
5)forking cluster(并行调用多个provider,只要一个成功就立即返回)
6)broadcacst cluster(逐个调用所有的provider)
(3)dubbo动态代理策略
默认使用javassist动态字节码生成,创建代理类,但是可以通过spi扩展机制配置自己的动态代理策略
3、注册中心Zookeeper
1.Zookeeper集群挂掉,发布者和订阅者之间还能通信吗?
可以通讯。启动 Dubbo 时,消费者会从 Zookeeper 拉取注册的生产者的地址 接口等数据,缓存在本地。每次调用时,按照本地存储的地址进行调用。
zk保证CP,所以在重新选举的时候会锁住,无法使用
watch机制,每次watch,session 节点消亡
- 启动选举和宕机选举有什么区别
- ZAB协议
- zk的三个角色
4.4.2 分布式锁
1、分布式锁实现方式,它们的区别?应用场景?
1.zk分布式锁
- 非公平锁
- 原理:某个节点尝试创建临时znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能注册个监听器监听这个锁。释放锁就是删除这个znode,一旦释放掉就会通知所有客户端,然后有一个等待着的客户端就可以再次重新加锁。
- 问题:如果在并发比较大的情况下,一个临时结点的消失,会造成很多线程同时会试图创建临时结点,这种方式会影响zk的稳定性,这个效应称为羊群效应。
- 公平锁
- 原理:每个线程在zk下创建一个临时序列结点,如果当前结点是临时序列结点中最小的一个就代表自己得到了锁,如果不是就监听比自己小的一个结点,每个线程只监听一个节点
- 优点:影响范围只有一个
- 共享锁-读写锁
- 原理:实际上写的时候用的是公平锁,读的时候前面是写请求则无法获取锁,是读的话直接获得锁
2.Redis分布式锁
(1)最普通的实现方式——SET NX PX:在redis里创建一个key算加锁,SET my:lock 随机值 NX PX 10000(NX就是当key不存在才能写入,PX就是超时时间10 000ms,10s)
setnx和expire(设置超时时间)不是原子操作,当setnx后,redis宕掉,会造成死锁,value:所有同一key的获取者(竞争者)这个值都不能一样,否则会导致超时后别人获得了锁,导致业务不一致。释放锁的时候,通过lua脚本告诉Redis只有key存在并且存储的值和我指定的值一样才能告诉我删除成功。
(2)官方推荐——RedLock算法
1、以毫秒为单位获取当前时间
2、尝试按顺序获取所有N个实例中的锁,在所有实例中使用相同的键名和随机值。当在每个实例中设置锁定时,客户端使用与总锁定自动释放时间相比较小的超时以获取它。例如,如果自动释放时间是10秒,则超时可以在~5-50毫秒范围内。这可以防止客户端长时间保持阻塞,试图与Redis节点进行通信,如果实例不可用,我们应该尝试尽快与下一个实例通话。
3、客户端通过从当前时间中减去在步骤1中获得的时间戳来计算获取锁定所需的时间。当且仅当客户端能够在大多数实例中获取锁定时(至少3个)并且获取锁定所经过的总时间小于锁定有效时间,认为锁定被获取。
4、如果获得了锁,则其有效时间被认为是初始有效时间减去经过的时间,如步骤3中计算的那样。
5、如果客户端由于某种原因(无法锁定N/2 +1实例或有效时间为负)未能获取锁定,它将尝试解锁所有实例(即使它认为不是能够锁定)。
3.Redis分布式锁和zk分布式锁的对比
- Redis分布式锁: 需要自己不断去尝试获取锁,比较消耗性能,如果是Redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间之后才能释放锁;
- zk分布式锁: 获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁;
4.分布式接口的幂等性、顺序性问题
- 幂等性:通过orderId之类的字段作为redis的key进行写入,每次请求都通过该key校验
- 顺序性:最好把两个操作合并成一个操作,或者避免这种逻辑
4.4.3 分布式事务
1.分布式事务有哪些方案?优缺点?
(1)两阶段提交方案/XA方案(不推荐,效率慢,容易卡死)
- 单点故障:由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞下去,尤其在第二阶段,协调者发生故障,那么所有的参与者都还处于锁定事务资源的状态中,而无法继续完成事务操作.如果协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者与阻塞状态的问题
- 数据不一致:在二阶段提交的阶段二中,当协调者向参与者发送请求之后,发成了局部网络异常或者再发从commit请求过程中协调者发生了故障,这回导致之后一部分参与者接受到了commit请求,而在这部分参与者接到commit请求之后会执行commit操作,但是其他部分未接到commit请求的机器则无法执行事务提交,于是整个分布式系统便出现了数据不一致的现象
- 事务的状态不确定:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了,那么及时协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没有人知道事务是否已经被提交了
(2)TCC补偿方案(一般是和钱有关才会用)
1)Try阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行锁定或者预留
2)Confirm阶段:这个阶段说的是在各个服务中执行实际的操作
3)Cancel阶段:如果任何一个服务的业务方法执行出错,那么这里就需要进行补偿,就是执行已经执行成功的业务逻辑的回滚操作
(3)本地消息表(国外ebay提出,一般没人用,高并发和扩展问题)
(4)可靠消息最终一致性方案(采用RocketMQ,因为它支持事务)
1)A系统先发送一个prepared消息到mq,如果这个prepared消息发送失败那么就直接取消操作别执行了
2)如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉mq发送确认消息,如果失败就告诉mq回滚消息
3)如果发送了确认消息,那么此时B系统会接收到确认消息,然后执行本地的事务
4)mq会自动定时轮询所有prepared消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认消息?那是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,别确认消息发送失败了。
5)这个方案里,要是系统B的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如B系统本地回滚后,想办法通知系统A也回滚;或者是发送报警由人工来手工回滚和补偿
(5)最大努力通知方案(MQ)
1)系统A本地事务执行完之后,发送个消息到MQ
2)这里会有个专门消费MQ的最大努力通知服务,这个服务会消费MQ然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统B的接口
3)要是系统B执行成功就ok了;要是系统B执行失败了,那么最大努力通知服务就定时尝试重新调用系统B,反复N次,最后还是不行就放弃
(6)阿里巴巴开源框架Seata
http://www.dreamwu.com/post-1741.html
- 几种模式对比
- AT模式:自动化事务,使用简单,对业务代码没有侵入性
- 优点:AT模式的一阶段、二阶段提交和回滚均由Seata框架自动生成,用户只需编写“业务SQL”,便能轻松接入分布式事务,AT模式是一种对业务无任何侵入的分布式事务解决方案。
- TCC模式:
- 优点:相对于 AT模式,TCC模式对业务代码有一定的侵入性,但是TCC模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。
- Saga模式:
- 优点:一阶段提交本地数据库事务,无锁,高薪能补偿服务即正向服务的“反向”,高吞吐参与者可异步执行,高吞吐。
- XA模式
- 优点:无侵入,将快照数据和行锁等通过XA指令委托给了数据库来完成
- AT模式的核心组件:
- 事务协调器TC:维护全局和分支事务的状态,指示全局提交或者回滚。
- 事务管理者TM:开启、提交或者回滚一个全局事务。
- 资源管理者RM:管理执行分支事务的那些资源,向TC注册分支事务、上报分支事务状态、控制分支事务的提交或者回滚
- 工作流程:
1.TM 请求 TC,开始一个新的全局事务,TC 会为这个全局事务生成一个 XID。
2.XID 通过微服务的调用链传递到其他微服务。
3.RM 把本地事务作为这个XID的分支事务注册到TC。
4.TM 请求 TC 对这个 XID 进行提交或回滚。
5.TC 指挥这个 XID 下面的所有分支事务进行提交、回滚。
4.4.5 分库分表
1、如何不停机迁移分库分表?
在线上系统里面,之前所有写库的地方,增删改操作,都除了对老库增删改,都加上对新库的增删改,这就是所谓双写,写的时候要根据gmt_modified这类字段判断这条数据最后修改的时间,除非是读出来的数据在新库里没有,或者是比新库的数据新才会写。有可能数据还是存在不一致,那么就程序自动做一轮校验,比对新老库每个表的每条数据,接着如果有不一样的,就针对那些不一样的,从老库读数据再次写。反复循环,直到两个库每个表的数据都完全一致为止
2、如何设计可以动态扩容的分库分表方案?
方案总结:
1.设定好几台数据库服务器,每台服务器上几个库,每个库多少个表,推荐是 32 库 * 32 表,对于大部分公司来说,可能几年都够了;
2.路由的规则,orderId 模 32 = 库,orderId / 32 模 32 = 表;
3.扩容的时候,申请增加更多的数据库服务器,装好 MySQL,呈倍数扩容,4 台服务器,扩到 8 台服务器,再到 16 台服务器;
4.由 DBA 负责将原先数据库服务器的库,迁移到新的数据库服务器上去,库迁移是有一些便捷的工具的;
5.修改一下配置,调整迁移的库所在数据库服务器的地址;
6.重新发布系统,上线,原先的路由规则变都不用变,直接可以基于 n 倍的数据库服务器的资源,继续进行线上系统的提供服务;
分表策略:
1、取模
2、范围分表-通常是时间
3、有明显业务特征的分表、比如城市
如何选择?
分库分表组件对比
1.MyCat(重量级的解决方案)
- 优点:
1、开发无感知
2、增删节点程序不需要重启
3、跨语言(java 、php)
- 缺点:
1、性能下降没因为多了一层
2、不支持跨数据库
2.Sharing-JDBC(轻量级的解决方案)
优点:
1、性能很好的
2、支持跨数据库jdbc
缺点:
1、增加了开发难度
2、不支持跨语言(Java)
3.对比:
1)mycat是一个中间件的第三方应用,sharding-jdbc是一个jar包
2)使用mycat时不需要改代码,而使用sharding-jdbc时需要修改代码
3、如何生成全局id?
snowflake雪花算法
1bit:为0,因为全部是正数
41 bit:时间戳
10 bit:记录工作机器id
高5个bit代表 机房id
低5个bit代表 机器id
12 bit:这个是用来记录同一个毫秒内产生的不同id
4、水平分表分页查询问题如何解决?
(1)建关联表
优点:通过服务层修改,扩大数据查询量,得到全局视野,业务无损,精确
缺点(显而易见):每个分库都需要返回更多的数据,增大网络传输量;除了数据库要按照time排序,服务层也需要二次排序,损耗性能;随着页码的增大,性能极具下降,数据量和排序量都将大增,性能平方级下降。
(2)二次查询
优点:可以精确得到业务数据,且每次返回的数据量都非常小,不会随着页码增加而数据量增大。
缺点:需要进行两次数据库查询
流程:(假设4个库,查询1001页,每页10条)pageSize=10,总offset =(1001-1)*10)=10000,起始位置=10000/4=2500
1、首次查询查询每个库 select * from table limit 2500,10 ;
2、对4个表中返回的记录进行id匹配(id如果非整型,自行用hashCode匹配),比较下每个表的首条记录的id值即可,获得了最小的minId,和4个表最大的那个值maxId, select * from db_x where id between minId and maxId 这样我们就获取到了遗漏的数据(当然有多余的数据)
3、记录minId出现的位置,如T1出现的位置为2500,T2出现的位置为2500-2=2048 ,T3出现的位置为2500-3=2047 ,T4出现的位置为2500-3=2047 则最终出现的记录数为:2500+2048+2047+2047=10000-2-3-3=9992,因此我们需要的查询的记录数需要从9992 依次往后取8个开始,然后再取10个就是所求的数据。
(3)用union all合并几个表的结果集,之后再进行分页查询。
(4) 假设3个分表,记录数分别为90,120,80,总记录290条,分页每页40条:
第1页:表一 1-40
第2页:表一 41-80
第3页:表一 81-90 + 表二 1-30
第4页:表二 31-7
...
(5)用sphinx(斯芬克司)先建立索引,然后分页查询。
5、跨库join问题如果解决?跨节点的count,order by,group by以及聚合函数问题?
跨库join:
通常使用两次查询,先查出主表的数据,然后再通过主表查出来的数据去做二次查询;
跨节点函数问题:
分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题;
4.5 微服务架构(SpringCloud)
4.5.1 注册中心
1、eureka、consul、nacos的区别
eureka
应用内/外:直接集成到应用中,依赖于应用自身完成服务的注册与发现,
CAP原则:遵循AP(可用性+分离容忍)原则,有较强的可用性,服务注册快,但牺牲了一定的一致性。
版本迭代:目前已经不进行升级
集成支持:只支持SpringCloud集成
访问协议:HTTP
雪崩保护:支持雪崩保护
consul
应用内/外:属于外部应用,侵入性小
CAP原则:遵循CP原则(一致性+分离容忍) 服务注册稍慢,由于其一致性导致了在Leader挂掉时重新选举期间真个consul不可用。
版本迭代:目前仍然进行版本迭代
集成支持:支持SpringCloud K8S集成
访问协议:HTTP/DNS
雪崩保护:不支持雪崩保护
nacos
应用内/外:属于外部应用,侵入性小
ACP原则:通知遵循CP原则(一致性+分离容忍) 和AP原则(可用性+分离容忍)
版本迭代:目前仍然进行版本迭代
集成支持:支持Dubbo 、SpringCloud、K8S集成
访问协议:HTTP/动态DNS/UDP
雪崩保护:支持雪崩保护
4.5.2 熔断 限流 降级(Hystrix)
1、Hystrix隔离策略
- 线程池隔离(默认):线程池机制,每个command运行在一个线程中,限流是通过线程池的大小来控制
- 信号量隔离:信号量机制,command是运行在调用线程中,但是通过信号量的容量来进行限流
- HystrixCommand.run()的资源隔离策略
- THREAD基于线程池: HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)
- 好处就是对于网络访问请求,如果有超时的话,可以避免调用线程阻塞住;
- SEMAPHORE是基于信号量: HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)
- 针对超大并发量的场景下,每个服务实例每秒都几百的QPS,那么此时你用线程池的话,线程一般不会太多,可能撑不住那么高的并发,如果要撑住,可能要耗费大量的线程资源,那么就是用信号量,来进行限流保护,基于信号量的话,性能会好很多;
- 允许访问的最大并发量(默认值是10设置的小一些,否则因为信号量是基于调用线程去执行command的,而且不能从timeout中抽离,因此一旦设置的太大,而且有延时发生,可能瞬间导致tomcat本身的线程资源本占满),超过这个最大并发量,请求直接被reject;
- command
command名称
每个command,都可以设置一个自己的名称,同时可以设置一个自己的组
command组
默认情况下,因为就是通过command group来定义一个线程池的,而且还会通过command group来聚合一些监控和报警信息,同一个command group中的请求,都会进入同一个线程池中
command线程池
threadpool key代表了一个HystrixThreadPool,用来进行统一监控,统计,缓存,默认的threadpool key就是command group名称
coreSize 设置线程池的大小,默认是10
2、Hystirx执行流程及工作原理(需看视频)
- 执行流程:
1、构建一个HystrixCommand或者HystrixObservableCommand
2、调用command的执行方法
3、检查是否开启缓存
4、检查是否开启了短路器
5、检查线程池/队列/semaphore是否已经满了
6、执行command
7、短路健康检查
8、调用fallback降级机制
9、不同的执行方式
- Hystrix断路器工作原理
- 熔断有几种状态
打开状态
半开状态
关闭状态
- 降级
通过普罗米修斯进行监控,配置报警发送邮件或者推送企业微信机器人
3、Hystirx源码
https://blog.csdn.net/loushuiyifan/article/details/82702522
1、Hystrix整个工作流如下:
1.构造一个 HystrixCommand或HystrixObservableCommand对象,用于封装请求,并在构造方法配置请求被执行需要的参数;
2.执行命令,Hystrix提供了4种执行命令的方法,后面详述;
3.判断是否使用缓存响应请求,若启用了缓存,且缓存可用,直接使用缓存响应请求。Hystrix支持请求缓存,但需要用户自定义启动;
4.判断熔断器是否打开,如果打开,跳到第8步;
5.判断线程池/队列/信号量是否已满,已满则跳到第8步;
6.执行HystrixObservableCommand.construct()或HystrixCommand.run(),如果执行失败或者超时,跳到第8步;否则,跳到第9步;
7.统计熔断器监控指标;
8.走Fallback备用逻辑
9.返回请求响应
2、Circuit Breaker主要包括如下6个参数:
1.circuitBreaker.enabled
是否启用熔断器,默认是TRUE。
2.circuitBreaker.forceOpen
熔断器强制打开,始终保持打开状态,不关注熔断开关的实际状态。默认值FLASE。
3.circuitBreaker.forceClosed
熔断器强制关闭,始终保持关闭状态,不关注熔断开关的实际状态。默认值FLASE。
4.circuitBreaker.errorThresholdPercentage
错误率,默认值50%,例如一段时间(10s)内有100个请求,其中有54个超时或者异常,那么这段时间内的错误率是54%,大于了默认值50%,这种情况下会触发熔断器打开。
5.circuitBreaker.requestVolumeThreshold
默认值20。含义是一段时间内至少有20个请求才进行errorThresholdPercentage计算。比如一段时间了有19个请求,且这些请求全部失败了,错误率是100%,但熔断器不会打开,总请求数不满足20。
6.circuitBreaker.sleepWindowInMilliseconds
半开状态试探睡眠时间,默认值5000ms。如:当熔断器开启5000ms之后,会尝试放过去一部分流量进行试探,确定依赖服务是否恢复。
3、熔断器工作的详细过程如下:
1.调用allowRequest()判断是否允许将请求提交到线程池
- 如果熔断器强制打开,circuitBreaker.forceOpen为true,不允许放行,返回。
- 如果熔断器强制关闭,circuitBreaker.forceClosed为true,允许放行。此外不必关注熔断器实际状态,也就是说熔断器仍然会维护统计数据和开关状态,只是不生效而已。
2.调用isOpen()判断熔断器开关是否打开
- 如果熔断器开关打开,进入第三步,否则继续;
- 如果一个周期内总的请求数小于circuitBreaker.requestVolumeThreshold的值,允许请求放行,否则继续;
- 如果一个周期内错误率小于circuitBreaker.errorThresholdPercentage的值,允许请求放行。否则,打开熔断器开关,进入第三步。
3.调用allowSingleTest()判断是否允许单个请求通行,检查依赖服务是否恢复
- 如果熔断器打开,且距离熔断器打开的时间或上一次试探请求放行的时间超过circuitBreaker.sleepWindowInMilliseconds的值时,熔断器器进入半开状态,允许放行一个试探请求;否则,不允许放行。
此外,为了提供决策依据,每个熔断器默认维护了10个bucket,每秒一个bucket,当新的bucket被创建时,最旧的bucket会被抛弃。其中每个blucket维护了请求成功、失败、超时、拒绝的计数器,Hystrix负责收集并统计这些计数器。
4.5.2 链路追踪
待补充
4.5.3 云容器Docker/K8s
1、k8s的常用组件
1.控制平面组件(Control Plane Components)
- kube-apiserver(api)
对外暴露k8s的api接口,是外界进行资源操作的唯一入口
提供认证、授权、访问控制、API注册和发现等机制
- etcd
- etcd 是兼具一致性和高可用性的键值数据库,可以作为保存 Kubernetes 所有集群数据的后台数据库。
- Kubernetes 集群的 etcd 数据库通常需要有个备份计划。
- kube-scheduler(seched)
控制平面组件,负责监视新创建的、未指定运行节点(node)的 Pods,选择节点让 Pod 在上面运行。
- kube-controller-manager(c-m)
在主节点上运行 控制器 的组件。这些控制器包括:
- 节点控制器(Node Controller): 负责在节点出现故障时进行通知和响应
- 副本控制器(Replication Controller): 负责为系统中的每个副本控制器对象维护正确数量的 Pod
- 端点控制器(Endpoints Controller): 填充端点(Endpoints)对象(即加入 Service 与 Pod)
- 服务帐户和令牌控制器(Service Account & Token Controllers): 为新的命名空间创建默认帐户和 API 访问令牌
- cloud-controller-manager(c-c-m)
- 云控制器管理器是指嵌入特定云的控制逻辑的 控制平面组件。云控制器管理器允许您链接聚合到云提供商的应用编程接口中,并分离出相互作用的组件与您的集群交互的组件。
- 下面的控制器都包含对云平台驱动的依赖
- 节点控制器(Node Controller): 用于在节点终止响应后检查云提供商以确定节点是否已被删除
- 路由控制器(Route Controller): 用于在底层云基础架构中设置路由
-服务控制器(Service Controller): 用于创建、更新和删除云提供商负载均衡器
2.Node组件
- kubelet
一个在集群中每个节点(node)上运行的代理。 它保证容器(containers)都 运行在 Pod 中。
- kube-proxy
kube-proxy 是集群中每个节点上运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。
kube-proxy 维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话与 Pod 进行网络通信。
- 容器运行时(Container Runtime)
容器运行环境是负责运行容器的软件。
Kubernetes 支持多个容器运行环境: Docker、 containerd、CRI-O 以及任何实现 Kubernetes CRI (容器运行环境接口)。