Zookeeper:
- 应用场景:
- 分布式协调服务,配置修改
- 分布式锁,数据一致性
- 无状态化实现
- zoo.cfg配置文件:
- tickTime:时间单位
- initLimit:follower链接leader最大时长
- syncLimit:follower节点与leader节点数据同步最大时长
- dataDir:数据存储以及日志保存目录
- clientPort:对客户端提供的端口号
- maxClientCnxns:单个客户端与zk最大并发连接数
- autopurge.snapRetainCount:保存的数据快照数量
- autopurge.purgeInterval:自动触发清除任务时间间隔,小时为单位
- 节点类型
- 持久节点
- 持久序号节点
- 临时节点,创建完成后session链接不断开是会持续续约的,临时节点不会被删除,断开连接后,达到超时时间后临时节点被删除,可用于服务的注册和发现
- 临时序号节点
- Container节点,节点下没有孩子节点后,会自动被定期删除
- TTL节点,可以指定节点的到期时间,到期后会被ZK自动删除
- zookeeper的持久化机制
- 事务日志
- 数据快照
- 节点元数据
- cZxid:创建节点的事务ID
- mZxid:修改节点的事务ID
- pZxid:添加和删除子节点的事务ID
- ctime:节点创建时间
- mtime:节点最近修改时间
- dataVersion:节点内数据的版本,每更新一次数据,版本会加1
- aclVersion:此节点的权限版本
- ephemeralOwner:如果当前节点是临时节点,该值是当前节点所有者的sessionid.如果不是临时节点,则该值为0
- dataLength:节点内数据长度
- numChildren:该节点子节点个数
- java开发zookeeper客户端:Curator
- 分布式锁(临时序号节点+watch机制)
- 读锁
创建一个临时序号节点,节点的数据是read,表示是读锁
获取当前zk中序号比自己小的所有节点
判断最小节点是否是读锁:如果不是读锁,则上锁失败,为最小节点设置监听。阻塞等待,zk的watch机制会当最小节点发生变化时通知当前节点,于是在执行第二步的流程;如果是读锁的话,上锁成功。
- 写锁
创建一个临时序号节点,节点的数据是write,表示是写锁
获取zk中所有的子节点
判断自己是否是最小的节点:如果是,则上写锁成功;如果不是,说明前面还有锁,则上锁失败,监听最小的节点,如果最小节点有变化,则回到第二步。
- 羊群效应
如果用上述上锁方式,只要有节点发生变化,就会触发其他节点的监听事件,这样对zk的压力非常大,可以调整为链式监听。
- zookeeper的watch机制
当zookeeper的zkNode发生了改变,也就是调用了create、delete、setData方法的时候,将会触发Znode上的注册的事件
- zookeeper集群
- leader:处理集群的所有事物请求
- follower:只能处理读请求,参与leader选举
- observer:只能处理读请求,提升集群读的性能,但不能参与leader选举
- ZAB协议
- 什么是ZAB协议:ZAB(Zookeeper Acomic Broadcast)协议,这个协议解决了zookeeper的崩溃恢复和主从数据同步问题
- ZAB协议定义的4中节点状态:Looking(选举状态),Following(Follower节点所处的状态),Leading(Leader节点所处的状态),Observering(观察者节点所处的状态)
当集群的第一个节点启动的时候处于Looking状态,选票为myid:1,事务id为:0,第二个节点启动的时候处于Looking状态,选票为myid:2,事务id为:0,选举策略为选举事务id大的节点,如果事务ID相同,选择myid大的节点,会进行两轮投票,第一轮投票节点会把票投给自己发送给另一个节点,第一轮选举会在各自节点生成选票为myid:2,事务id为:0的节点的票,当前两个节点的投票箱里面只有一票不过半需要进行第二轮投票,第二轮投票会将第一轮选举的结果投给另一个节点,这样选票为myid:2,事务id为:0的节点就得到了两票,选票过半成为leader节点,第三个节点启动后发现集群已经有leader节点了,直接成为follower节点。
- 崩溃恢复
Leader节点会周期性的往follower节点发送ping命令,如果一段时间发现leader节点没有发送ping消息,follower节点会尝试从socek里面读取数据,如果读取失败,follower节点由原来的Folloering状态变为Looking状态进行Leader选举。
- 主从数据同步
主节点负责所有数据的写,如果client端链接的是leader节点,leader节点直接处理写请求,如果client链接的是follower节点,follower节点会将写请求转发给leader节点处理。主节点和从节点都可以处理client端的读请求。
两段提交:client端的写请求到达leader,leader会将数据写到数据文件并返回一个ACK,让后将数据同步给follower节点,follower节点将数据写到数据文件返回一个ACK给leader节点,当leader节点收到半数以上的ACK后会向follower节点发送commit请求,然后leader和follower节点将数据写入内存,主从数据同步完成。
- zookeeper中的NIO和BIO的应用
- NIO用于被客户端连接的2181端口,使用的是NIO模式与客户端建立连接
- NIO用于客户端开启watch时,等待zookeeper服务器的回调。
- BIO用于集群选举时,多个节点之间的投票通信端口
- CAP理论
一致性、可用性、分区容错性
Zookeeper是满足顺序一致性
Redis:
https://www.zhihu.com/tardis/bd/art/487583440?source_id=1001
https://zhuanlan.zhihu.com/p/427496556
- redis是单线程还是多线程
6.0版本之前的单线程指的是其网络I/O和键值对读写是由一个线程完成的
6.0版本之后多线程指的是网络请求采用了多线程,而键值对读写命令仍然是单线程处理的,所以Redis是并发安全的。
2、redis单线程为什么还能这么快
(1)命令执行基于内存,一条命令在内存里操作的时间是几十纳秒
(2)命令执行是单线程操作
(3)基于IO多路复用机制提升redis的I/O利用率
(4)高效的数据存储结构:全局hash表以及多种高效数据结构,比如:跳跃表,压缩列表,链表
3、redis key值过期了为什么内存没有释放
(1)惰性删除:当读/写一个已经过期的key时,会触发惰性删除策略,判断key是否过期,如果过期了直接删除掉这个key.
(2)定期删除:由于惰性删除策略无法保证冷数据被及时删除掉,所以Redis会定期主动淘汰一批已经过期的key,这里的一批只是部分过期的key,所以可能会出现部分key已经过期但还没有被清理掉的情况,导致内存没有被释放。
(3)定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
4、Redis Key没有设置过期时间为什么被Redis主动删除了
内存不够被删除
最近最少使用;最不经常使用
5、删除Key的命令会阻塞Redis吗,会阻塞
删除str类型的key,时间复杂度为0(1)
删除单个列表、集合、有序集合或哈希表类型的key,时间复杂度为O(m)
6、Redis主从、哨兵、集群架构优缺点比较
(1)主从模式:一个master节点对外提供服务,多个slave节点同步数据。主节点挂掉后,通过主动运维接入进行倒换,将slave节点切换为master节点,master节点恢复后成为slave节点。
(2)哨兵模式:在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点状态,如果master节点出现异常,则会主从切换,将某一台的slave作为master,哨兵模式的配置略微复杂,并且性能和高可用性等各方面表现一般,特别是在主从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持很高的并发,且单个主节点内存也不宜设置过大(最多不要超过10G),否则会导致持久化文件过大,影响数据恢复或主从同步的效率。
(3)集群模式:redis集群是由多个主从节点群组组成的分布式服务器群,它具有复制、高可用和分片特性。Redis集群不需要sentine哨兵也能完成节点移除和故障专一的功能。需要将某个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000节点)。Redis集群的性能和高可用性均优于之前的哨兵模式,且集群配置非常简单。
7、Redis集群数据hash分片算法是怎么回事
Redis Cluster将所有数据划分为16384个slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。
槽位定位算法:HASH_SLOT = CRC16(key)mode 16384
8、Redis执行命令竟然有死循环阻塞BUG
Randomkey:从当前数据库中随机返回一个key
Redis对过期key的清理策略是定时删除和惰性删除两种方式结合来做的,而randomkey在随机拿出一个key后,首先会检查这个key是否过期,如果该key已经过期,那么Redis会删除它,这个过程就是惰性删除。但清理完还不能结束,Redis还要找出一个没有过期的key,返回给客户端。当redis中有大量key已经过期还未来得及清理时,这个循环就会持续很久才能结束,导致Randomkey耗时过长,影响redis性能。如果在slave节点上,情况会更严重,因为slave针对过期的key是不会主动删除的。
这个是redis的一个BUG在5.0版本修复,修复方案就是限制在slave中查找的次数。
9、redis主从切换导致了缓存雪崩
Slave节点的机器时钟比master走的快的多,redis在master里面设置了key的过期时间,从slave节点来看,可能很多在master里面没有过期的数据已经过期了。如果此时进行主从切换,把slave提升为新的master,他成为master后会开始大量清理过期的key,此时就会导致以下结果:(1)master大量清理过期的key,主线程可能会发生阻塞,无法及时处理客户端请求。(2)Redis中数据大量过期,引发缓存雪崩。
10、Redis持久化RDB、AOF、混合持久化是怎么回事
RDB快照(snapshot):在默认情况下,Redis将内存数据库快照保存在名字为dump.rdb的二进制文件中。你可以对redis进行设置,让它在N秒内数据集至少有M个改动这一条件满足时,自动保存一次数据。也可以通过save和bgsave生成dump.rdb文件,每次命令执行会将redis所有的内存快照存到一个rdb文件中,并覆盖原有的rdb快照文件。Save执行失时,redis不能处理写命令,Bgsave是Redis借助操作系统的写时复制技术,在生成快照的同事,依然可以正常处理写命令。Bgsave子进程是由主线程fork生成的,可以共享主线程的所有内存数据。如果主线程在bgsave的过程中执行写命令,这块数据会被复制一份,生成该数据的副本。然后bgsave子进程会把这个副本数据写入RDB文件,而在这个过程中,主线程仍然可以直接修改原来的数据。
AOF:会将写命令持久化到.aof文件中,可配置(always/everysec/no),AOF重写,当aof文件达到配置文件中的大小时触发重写,只关注最终结果,不关注中间过程,对写操作进行压缩。数据恢复优先选择AOF恢复,数据恢复慢,但相对安全。
混合持久化:如果开启混合持久化机制,只要触发了一次AOF的重写会将内存中的二进制数据写到.aof文件中,如果中间有写命令执行,会以aop的形式写到.aof文件中
11、redis的持久化策略如何设置
如果对性能要求比较高,最好不要在master节点做持久化,可以在某个slave节点开启AOF备份数据,策略设置为每秒同步一次即可。
12、redis线上数据如何备份
(1)写crontab定时调用脚本,每小时都copy轶峰rdb或者aof文件到另一台机器中去,保留最近48小时的备份
(2)每日都保存轶峰当日的日志到另一个目录中去,可以保留最近一个月的备份
(3)每次copy备份的时候,都把太旧的备份删除掉
13、redis集群网络抖动导致频繁主从切换怎么处理
Cluster-node-timeout表示当某个节点持续timeout的时间失联时,才认为该节点出现故障
- redis集群为什么至少需要3个master节点
因为新的master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的
- redis集群为什么推荐奇数个节点
机器资源利用率方面考虑
- redis支持批量操作命令吗
对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有的key落在统一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上{XXX},这样参数数据分片hash计算呢的只会十大括号里面的值,这样能确保不同的key能落到同一slot里面去
- redission分布式锁的实现原理(性能比zookeeper好)
redlock实现原理:超过半数的redis节点加锁成功才算加锁成功。
- redis缓存架构
- 修改DB的时候同步修改redis缓存
- 修改DB的时候将redis中的缓存删除,当客户端查询的时候先查缓存,缓存不存在再去DB中获取
- 海量数据冷热分离
每个缓存数据设置过期时间,然后通过get方法访问的时候延期数据超时时间
- 缓存击穿
指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。使用互斥锁方案。
- 缓存穿透
大量请求不存在的数据。将不存在数据存一个特殊值。
- 缓存雪崩
批量数据导入,设置相同的失效时间会导致同时失效,这是如果有请求过来会直接击穿缓存,到达数据库。加入随机时间,让每个过期时间都不一样。
- 缓存数据库双写不一致
两个线程并发修改数据库和缓存,使用分布式锁解决此问题。
- 多级缓存
解决大并发量的问题
- 协议是Gossip协议
Rabbitmq:
- 事务消息
通过对信道设置实现:
- channel.txSelect():同时服务起开启事务模式;服务端会返回Tx.Select-OK
- channel.basicPublish:发送消息,可以是多条,可以是消费消息提交ack
- chnnel.txCommit():提交事务
- chnnel.txRollback():回滚事务
消费者使用事务:
- autoACK=false,手动提交ack,以事务提交或回退为准。
- autoACK=true,不支持事物的,也就是说你即使在收到消息之后在回滚事务也是于事无补的,队列已经把消息移除了。
如果其中任意一个环节出现问题,就会抛出IOException异常,用户可以拦截异常进行事务回滚,或决定要不要重新消费。事务消息会降低rabbitmq的性能。
- 普通集群模式
元数据:
队列元数据:队列名称和他的属性
交换器数据:交换器名称、类型和属性
绑定元数据:一张简单的表格展示了如何将消息路由到队列
Vhost元数据:为vhost内的队列、交换器和绑定提供命名空间和安全属性
为什么只同步元数据:
存储空间,每一个节点都保存全量数据,影响消息堆积能力
性能,消息的发布者需要将消息复制到每一个集群节点
客户端连接的是非队列数据所在节点:该节点会进行路由转发,包括发送和消费
集群节点类型:
磁盘节点:将配置信息和元数据存储在磁盘上
内存结点:将配置信息和元数据存储在内存中。性能优于磁盘节点。依赖磁盘节点进行持久化
RabbitMQ要求集群中至少有一个磁盘节点,当节点加入和离开集群时,必须通知磁盘节点(如果集群中唯一的磁盘节点崩了,则不能进行创建队列、创建交换器、创建绑定、添加用户、更改权限、添加和删除集群节点)。如果唯一的磁盘节点崩溃,集群是可以保持运行的,但不能更改任何东西。因此建议在集群中设置两个磁盘节点,只要一个可以,就能正常操作。
- 架构设计(基于AMOP协议)
Broker:rabbitmq的服务节点
Queue:队列,时RabbitMQ的内部对象,用于存储消息。RabbitMQ消息只能存储在队列中。生产者投递消息到队列,消费者从队列中获取消息并消费。多个消费者可以订阅同一个队列,这是消息队列中的消息会被平均分摊(轮询)给多个消费者进行消费,而不是每个消费者都能收到所有的消息进行消费。(注意:RabbitMQ不支持队列层面的广播消费,如果需要广播消费,可以采用一个交换器通过路由key绑定多个队列,由多个消费者来订阅这些队列的方式)
Exchange:交换器。生产者将消息发送到Exchange,有交换器将消息路由到一个或多个队列中。如果路由不到,或返回给生产者,或直接丢弃,或做其他处理。
RoutingKey:路由Key。生产者将消息发送给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则。这个路由Key需要与交换器类型和绑定键联合使用才能最终生效。在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时通过指定RoutingKey来决定消息流向哪里。
Binding:通过绑定交换器和队列关联起来,在绑定的时候一般会指定一个绑定健,这样RabbitMQ就可以指定如何正确的路由到队列了。交换器和队列实际上时多对多关系。就像关系数据库中的两张表。他们通过BindingKey做关联(多对多关系表)。在投递信息时,可以通过Exchange和RoutingKey(对应BindingKey)就可以找到相应的队列。
信道:信道是建立在Connection之上的虚拟连接。当应用程序与Rabbit Broker建立TCP连接的时候,客户端紧接着可以建立一个AMQP信道,每个信道都会被指派一个唯一的ID。RabbitMQ处理的每条AMOP指令都是通过信道完成的。信道就像电缆里的光纤束,允许所有的连接通过多条光纤束进行传输和接受。
- 交换机类型
Fanout:扇形交换机,不再判断routekey,直接将消息分发到所有队列
Direct:判断routekey的规则是完全匹配模式,即发送消息时指定的routekey要等于绑定的routekey
Topic:判断routekey的规则是模糊匹配模式
Header:绑定队列与交换器的时候指定一个键值对,当交换器在分发消息的时候会先解开消息体里的headers数据,然后判断里面是否有所设置的键值对,如果发现匹配成功,才将消息分发到队列中;这种交换器类型在性能上相对来说较差,在实际工作中很少用到。
- 持久化机制
- 交换机持久化:exchange_declare创建交换机时通过参数来指定
- 持久化队列:queue_declare创建队列时通过参数指定
- 消息持久化:newAMQPMessage创建消息时通过参数指定
Append的方式写文件,根据大小自动生成新文件,rabbitmq启动时会创建两个进程,一个负责持久化消息的存储,另一个负责非持久化消息的存储(内存不够时)消息存储时会在ets表中记录消息在文件中的映射以及相关信息(包括id、偏移量,有效数据,左边文件,右边文件),消息读取时根据该信息到文件中读取、同时更新信息。消息删除时只从ets删除,变为垃圾数据,当垃圾数据超出比例(默认50%),并且文件数达到三个,触发垃圾回收,锁定左右两个文件,整理左边文件有效数据、将右边文件有效数据写入左边,更新文件信息,删除右边,完成合并。当一个文件有用数据等于0时,删除该文件。写入文件前先写buffer缓冲区,如果buffer已满,则写入文件(此时只是操作系统的叶存),每个25ms刷一次磁盘,不管buffer满没满,都将buffer和叶存中的数据罗盘,每次消息写入后,如果没有后续写入请求,则直接刷盘。
- 死信队列、延迟队列
- 消息被消费方否定确认,使用channel.basicNack或channel.basicReject,并且此时requeue属性被设置为false.
- 消息在队列存活时间超过设置的TTL时间
- 消息队列的消息数量已经超过最大队列长度
那么该消息将成为“死信”消息会被Rabbitmq进行特殊处理,如果配置了死信队列信息,那么该消息会将被丢进死信队列中,如果没有配置,则该消息将被丢弃。为每个需要使用死信的业务队列配置一个死信交换机,这里同一个项目的死信交换机可以共用一个,然后为每个业务队列分配一个单独的路由key,死信队列只不过是绑定在死信交换机上的队列,死信交换机也不是什么特殊的交换机,只不过是用来接受死信的交换机,所以可以为任意类型。
- 如何保证消息的可靠性传输
- 使用事务消息
- 使用消息确认机制
发送方确认:
Channel设置为confirm模式,则每条消息会被分配一个唯一id
消息投递成功,信道会返回ack给生产者,包含了id,回调ConfirmCallback接口
如果发生错误导致信息丢失,发生nack给生产者。回调ReturnCallback接口
Ack和nack只有一个触发,且只有一次,异步触发。可以继续发送消息
接收方确认:
声明队列时,指定noack=false,broker会等待消费者手动返回ack、才会删除消息,否则立刻删除。
Broker的ack没有超时机制,只会判断连接是否断开,如果没有断开、消息会被重新发送。
- 确保消息到MQ:使用发送方确认模式。
- 确保消息路由到正确的队列:开启路由失败通知。
- 确保消息在队列正确的存储:交换器、队列、消息都需要持久化。
- 确保消息从队列中正确的投递到消费者:手动确认->交给消费者来确认。
- 消息生产者可以直连队列吗
可以
生产者和消费者使用相同的参数去声明队列。重复声明不会改变队列。
- 镜像队列
GM负责消息广播,所有的的GM组成gm_group,形成链表结构,负责监听相邻节点状态,以及传递消息到相邻节点,master的GM收到消息时代表消息同步完成
Mirror_queue_master/slave负责消息的处理,操作blockingQueue,Queue负责AMQP协议(commit、rollback\ack等)
Master负责读写处理
Master挂掉后选举存活时间最长的slave升为master
- 如何保证消息顺序
- 协议是AMQP协议
Kafka:
1、消息队列如何保证消息的可靠传输(消息可靠传输代表两层意思,既不能多,也不能少)
(1)为了保证消息不多发。也就是消费不能重复,也就是消费者不能重复生产消息,或者消费者不能重复消费消息。首先要保证消息不多发,这个不常出现,也比较难控制,因为如果出现了多发,很大的原因是生产者自己的原因,如果要避免出现问题,就需要在消费端控制。要避免不重复消费,最保险的机制就是消费者实现幂等性,也能解决生产者重复发送消息的问题。
(2)消息不能少,意思就是消息不能丢失,生产者发送的消息,消费者一定能消费到,对于这问题,需要考虑两个方面:生产者发送消息 时,要确认broker确实收到并持久化这条消息,比如RabbitMQ的confirm机制和kafka的ack机制都可以保证生产者能正常将消息发送给broker;broker要等待消费者真正确认消费到了消息时才删除掉消息,这里通常就是消费端的ack机制,消费者收到一天消息后,如果确认没有问题,就可以给broker发送一个ack,broker接收到ack后才会删除消息。
2、消息队列的优缺点
优点:
(1)解耦:使用消息队列来作为两个系统之间的通信方式,两个系统不需要相互依赖了。
(2)异步:系统A给消息队列发送完消息之后,就可以继续做其他事情了。
(3)流量消锋:如果使用消息队列的方式来调用某个系统,那么消息将在队列中排队,由消费者自己控制消费速度。
缺点:
(1)增加了系统的复杂度,幂等重复消费、消息丢失等问题带入
(2)系统可用性降低,mq故障会影响系统的可用性
(3)一致性,消息可能消费失败
场景:日志采集、发布订阅
3、如何保证消息不被重复消费(幂等: 一个数据或者请求,重复来多次,确保对应的数据是不会改变的,不能出错)
(1)如果是写redis,就没有问题,反正每次都是set,天然幂等性。
(2)生产者发送消息的时候带上一个全局唯一的id,消费者拿到消息后,现根据这个id去redis里查一下,之前有没有用消费国,没有消费过就处理,并且写入这个id到redis,如果消费过了,则不处理。
(3)基于数据库的唯一建。
4、死信队列、延迟队列
(1)死信队列也是一个消息队列,它用来存放那些没有消费成功的消息,通常可以用来作为消息重试。
(2)延时队列就是用来存放需要指定时间被处理的元素的队列,通常可以用来处理一些具有过期性操作的业务,比如十分钟之内未支付则取消订单。
5、kafka的rebalance机制(consumer group中的消费者与topic下的partion重新匹配的过程)
何时会产生rebalance:
- consumer group中的成员个数发生变化
- consumer消费超时
- group订阅的topic个数发生变化
- group订阅的topic的分区个数发生变化
coordinator:通常是partition的leader节点所在的broker,负责监控group中consumer的存活,consumer维持到coordinator的心跳,判断consumer的消费超时。
Coordinator通过心跳返回通知consumer进行rebalance
Consumer请求coordinator加入组,coordinator选举产生leader consumer
Leader consumer从coordinator获取所有的consumer,发送syncGroup(分配信息)给到coordinator
Coordinator通过心跳机制将sysncGroup下发给consumer
完成rebanlance
Leader consumer监控topic的变化,通知coordinator触发rebalance
如果C1消费消息超时,触发rebalance,重新分配后、该消息会被其他消费者消费,此时C1消费完成提交offset、导致错误
解决:coordinator每次rebalance,会标记一个Generation给到consumer,每次rebalance该Generation会+1,consumer提交offset时,coordinator会对比Generation,不一致拒绝提交。
6、kafka的副本同步机制
LEO:下一条待写入位置
FirstUnstableOffset:第一条未提交数据
LastStableOffset:最后一条已提交数据
LogstartOffset:其实位置
Isolation.level=read_committed:只能消费到LastStableOffset,read_uncommitted可以消费到HW的上一条
一个partition对应的ISR中最小的LEO作为分区的HW,consumer最多只能消费到HW所在的位置
Leader收到消息后会更新本地的LEO,leader还会维护follower的LEO即remote LEO,follwer发出fetch同步数据请求时(携带自身的LEO)、leader会更新remote LEO,更新分区的HW,然后将数据相应给follower、follwer更新自身HW(取响应中的HW和自身的LEO中的较小值),LEO+1
ISR:如果一个follower落后leader不超过某个时间阈值,那么则在ISR中,否则将放在OSR中。
同步副本时,follower获取leader的LEO和LogStartOffset,与本地对比,如果本地的LogStartOffset超出了leader的值,则超过这个值的数据删除,在进行同步,如果本地的小于leader的、则直接同步。
7、kafka的架构设计
Consumer Group:消费者组,消费者组内每个消费者负责消费不同分区的数据,提高消费能力,逻辑上是一个订阅者
Topic:可以理解为一个队列,Topic将信息分类,生产者和消费者面向的是同一个Topic
Partition:为了实现扩展性,提高并发能力,一个topic以多个partition的方式分布在多个broker上,每个Partition是一个有序的队列。一个Topic的每个Partition都有若干个副本(replica),一个Leader和若干个follower。生产者发送数据的对象,以及消费者消费的对象,都是leader。Follower只负责从leader中同步数据,保持和leader的数据同步。Leader发生故障时follower还会成为新的leader节点。
8、kafka中zk的作用(新版本中zk都被踢掉了)
/brokers/ids:临时节点,保存所有broker节点信息,存储broker的物理地址、版本信息、启动时间等,节点名称为brokerID,broker定时发送心跳到zk,如果断开则brokerID会被删除。
/broker/topics:临时节点,节点保存broker节点下所有的topic信息,每一个topic节点下包含一个固定的partitions节点,partition的子节点就是topic的分区,每个分区下保存一个state节点、保存当前的leader分区和ISR的brokeriID,state节点由leader创建,若leader宕机该节点会被删除,直到有新的leader选举产生、重新生成state节点。
/consumer/[group_id]/owners/[topic]/[broker_id-partition_id]:维护 消费者和分区的注册关系
/consumer/[group_id]/offsets/[topic]/[broker_id-partition_id]:分区消息的消费进度offset
Client通过topic找到topic树下的state节点,获取leader的brokerID,到broker树中找到broker的物理地址,但是client不会直连ZK,而是通过配置的broker获取到zk中的信息。
9、kafka中高性能的原因(kafka不是基于内存,而是基于硬盘存储,因此消息堆积能力更强)
(1)顺序写:利用磁盘的顺序访问速度可以接近内存,kafka的消息都是appaend操作,partition是有序的,节省了磁盘的寻道时间,同时通过批量操作、节省写入次数,partition物理上分为多个segment存储,方便删除。
(2)传统:
读取磁盘文件数据到内核缓冲区
将内核缓冲区的数据copy到用户缓冲区
将用户缓冲区的数据copy到socket的发送缓冲区
将socket发送缓冲区的数据发送到网卡、进行传输
- 零拷贝
直接将内核缓冲区的数据发送到网卡传输
使用的是操作系统的指令支持
Kafka不太依赖jvm,主要由操作系统的pageCache,如果生产消费速率相当,则直接使用pageCache交换数据,不需要经过磁盘IO
10、kafka消息高可靠解决方案
消息发送:
Ack:0、不重试,1、leader写入成功就返回,all/-1、等待ISR同步完成再返回
Unclean.leader.election.enable:false,禁止选举ISR以外的follower为leader
Tries>1,重试次数
Min.insync.replicas>1:副本同步数,没有满足该值前、不提供读写服务、写操作会异常。
消费:
手工提交offset
Broker:减小刷盘间隔
事务消息
11、kafka高吞吐量的原因
Kafka的生产者采用的是异步发送消息的机制,当发送一条消息时,消息并没有发送到Broker而是缓存起来,然后直接向业务返回成功,当缓存的消息达到一定数量时再批量发送给broker,这种做法减少了网络IO,从而提高了消息的发送吞吐量,但是如果消息生产者发生宕机,会导致消息丢失,业务出错,所以理论上kafka利用此机制提高了吞吐量降低了可靠性。
12、kafka的push、pull优略势分析(kafka采用的pull)
Pull模式:consumer主动拉取数据
根据consumer的消费能力进行数据拉取,可以控制速率
可以批量拉取,也可以单条拉取
可以设置不同的提交方式,实现不同的传输语义
缺点:如果kafka没有数据,会导致consumer空循环,消耗资源
解决:通过参数设置,consumer拉取数据为空或者没有达到一定数量时进行阻塞
Push模式:不会导致consumer循环等待
缺点:速率固定、忽略了consumer的消费能力,可能导致拒绝服务或者网络阻塞等情况
Mysql:
- B树和B+树的区别,为什么Mysql使用B+树
B树的特点:
- 节点排序
- 一个节点可以存多个元素,多个元素也排了序
B+树的特点:
- 拥有B树的特点
- 叶子节点有指针
- 非叶子节点上的元素在叶子节点上都冗余了,也就是叶子节点存储了所有的元素,并且排了序。
Mysql索引使用的时B+树,因为索引是用来加快查询的,而B+树通过对数据进行排序所以是可以提高查询速度的,然后通过一个节点中可以存储多个元素,从而可以使得B+树的高度不会太高,在Mysql中一个innodb页就是一个B+树节点,一个innodb页默认为16kb,所以一般情况下一棵两层的B+树可以存2000万行左右数据,然后通过利用B+树叶子节点存储了所有数据并且进行了排序,并且叶子节点之间有指针,可以很好的支持全表扫描,范维查找。
- 为什么使用B+树而不使用二叉树或红黑树
- 数据量大的时候树的高度太高不可控,查询次数不稳定
- 数据顺序会让二叉树退化成链表
- innodb、myisam
myisam:
- 使用非聚集索引,索引和数据分开存放
- 支持表级锁,每次操作对整个表加锁
- 存储表的总行数
- 一个表有三个文件:索引文件、表结构文件、数据文件
- 采用非聚集索引,索引文件的数据或存储指向数据文件的指针。辅助索引与主索引基本一致,但是辅助索引不能保证唯一性
- 不支持事务,但是每次查询都是原子的
innodb:
- 使用聚集索引,数据和索引放在一起
- 支持事务,支持事务的四种隔离级别
- 支持行级锁及外键约束:因此支持写并发
- 一个InnoDB引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可以分布在多个文件里),也有可能为多个(设置为独立表空间,表大小受操作系统控制,一般为2G),受操作系统文件大小限制。
- 主键索引采用聚集索引(索引的数据域存储数据文件本身),辅助索引的数据域存储主键的值;因此从辅助索引查找数据,需要先通过辅助索引找到主键值,在访问辅助索引;最好使用自增主键,防止插入数据时,为维持B+树结构,文件大小调整。
- 为什么建议innodb表必须建主键,并且推荐使用整形自增主键
- 非主键索引是非聚集索引,先通过索引找到主键,然后通过主键索引找数据
- 如果没有主键,数据是按照隐藏列进行建立索引
- 整形存储空间小,比较大小效率高
- 如果不是自增的会有节点分裂,耗费性能
- 使用雪花算法解决分库分表自增问题,雪花算法是趋势递增
- mysql支持hash索引(一般不使用)
hash:hash表+链表(很多时候hash的效率比B+树还要高,仅能满足“=”,”IN”,不支持范围查询,存在hash冲突问题)
- 联合索引
最左前缀法则
- Explain(执行计划)
Id:查询语句中每出现一个select关键字,MySQL就会为它分配一个唯一的ID值,某些子查询会被优化为join查询,那么出现的id会一样
Select_type:select关键字对应的那个查询类型
Table:表名称
Possible_key:可能用到的索引
Key:实际上用到的索引
Ley_len:实际用到的索引长度
Ref:当使用索引列等值查询时,与索引列进行等值匹配的对象信息
Row:预估的需要读取的记录条数
Filterled:某个表经过搜索条件过滤后剩余记录条数的百分比
Extra:一些额外的信息。
- innoDB是怎么实现事务的
InnoDB通过Buffer Pool,LogBuffer,Redo Log,Undo Log来实现事务,以一个update语句为例:
- InnoDB在收到一个update语句后,会先根据条件找到数据所在的页,并将该页缓存在Buffer Pool中。
- 执行update语句,修改BufferPool中的数据,也就是内存中的数据。
- 针对update语句生成一个RedoLog对象,并存入LogBuffer中。
- 针对update语句生成undolog日志,用于事务回滚。
- 如果事务提交,那么则把RedLog对象进行持久化,后续还有其他机制将Buffer Pool中所修改的数据页持久化到磁盘那中。
- 如果事务回滚,则利用underolog日志进行回滚。
- Mvcc
事务多版本并发控制
- 分库分表
- 锁
按锁粒度分类:
- 行锁:锁某行数据,锁力度最小,并发度最高
- 表锁:锁整张表,锁的粒度最大,并发度低
- 间隙锁:锁的是一个区间
还可以分为:
- 共享锁:也就是读锁,一个事务给某行数据加了读锁,其他事务也可以读,但是不能写。
- 排他锁:也就是写锁,一个事务给某行数据加了写锁,其他事务不能读,也不能写。
在事务的隔离级别实现中,就需要利用锁来解决幻读。
- SQL优化
- Mqsql慢查询优化
- 检查是否走了索引,如果没有则优化SQL利用索引
- 检查所利用的索引,是否是最优索引
- 检查所查字段都是必须的,是否查询了过多的字段,查出了多余的数据
- 检查表中的数据是否过多,是否需要进行分库分表
- 检查数据库实例所在机器的性能配置,是否太低,是否可以增加资源。
- 索引覆盖
索引覆盖就是一个SQL在执行时,可以利用索引来快速查找,并且此SQL所要查询的字段在当前索引对应的字段中都包含了,那么就表示此SQL走完索引后不用回表,所需要的字段都在当前索引的叶子节点上存在,可以直接作为结果返回。
- 雪花算法原理
第一位符号为固定0,41位时间戳,10位workid,12位序列号,位数可以有不同实现
缺点:强依赖时钟
- 事物的基本特性和隔离级别
事务基本特性ACID分别是:
原子性:指的是一个事务中的操作要么全部成功,要么全部失败
一致性:指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。
隔离性:指的是一个书屋的修改在最终提交前,对其他事务是不可见的。
隔离性有四个隔离级别,分别是:
- read uncommit:读未提交,可能灰度到其他事物未提交的数据,也叫脏读。(解决方案:使用事务的隔离级别来避免脏读。)
- read commit:读已提交,两次读取结果不一致,叫做不可重复读。不可重复读解决了脏读的问题,他只会读取已经提交的事务。(解决方案:使用事务的隔离级别来避免幻读。与脏读类似,通过设置合适的隔离级别可以解决幻读问题。此外,还可以使用行级锁或表级锁来限制查询的范围,从而避免幻读的发生。)
- repeatable read:可重复读,这是mysql默认级别,就是每次读取结果都一样,但是有可能产生幻读。(解决方案:同样可以使用事务的隔离级别和行级锁来避免不可重复读)
- serializable:串行,一般是不会使用的,他会给每一行读取的数据枷锁,会导致大量超时和锁竞争问题。
持久性:指的是一旦事务提交,所做的修改会永久保存在数据库中。
- 什么是InnoDB中的页,有什么作用
页大小为16KB
索引页
回表
Linux:
常用命令:
- Free
- Top
- vmstat
- less、more、cat、tail vim vi
- crontab
- rpm
- yum
- ip addr show
- ifconfig
- mount
- lsblk
- netstate
- ps
- kill
- find
- grep、zgrep
- cd
- ls、ll
- cp
- mv
- rm
- systemctl
- awk
- sed
- ping
- arping
- chmod
- chowner
- tar
- df –h
- curl
- wget
- scp(全量、加密、安全)、rsync(增量、非加密、不安全、耗资源)
- 简述ISO/OSI七层模型的分层与应用
第七层:应用层,为用户提供服务,给用户一个操作界面,协议:HTTP、FTP、TFTP、SMTP、DNS
第六层:表示层,数据提供表示(将图片、声音翻译成二进制)、加密、压缩
第五层:会话层,确定是否需要进行网络传递,如果是需要网络传输,就传给传输层,否则本地保存
第四层:传输层,对报文进行分组(发送时)、组装(接收时);提供传输协议的选择:TCP(传输控制协议,可靠的,面向连接的传输协议,可靠,准确但是慢),UDP(用户数据报协议,不可靠的面向无连接的传输协议,快但是不可靠);端口封装(源端口,目标端口);差错校验。协议:TCP、UDP
第三层:网络层,IP地址编址;路由选择(静态路由:由管理员指定,效率高,配置复杂,动态路由:配置简单,根据路由协议自己选择,但是需要消耗路由器资源)。协议:IP、ARP(地址解析协议)、RARP(反向地址解析协议)。
第二层:数据链路层,MAC地址编制;MAC地址寻址(同网段访问);差错校验。
第一层:物理层,数据实际传输;电器特性定义
- 简述TCP三次握手过程
序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
确认号:Ack序号,占32位,只有ACK标记位为1时,确认序号字段才有效,Ack=Seq+1
标志位:ACK:确认序号有效,SYN:发起一个新连接,FIN:释放一个链接
Client发送syn=1 seq=j到server,并将自己的状态改为syn_send,server接收到client发过来的连接建立请求,将自己状态置为syn_rcvd并将syn=1 ACK=1 ack=j+1 se1=k回复给client端,client接收到server返回的消息将自己状态置为established并将ACK=1 ack=k+1返回给server端,server端收到client端的回复后将自己的状态置为established,连接建立成功
- 简述TCP四次挥手过程
Client端发送fin=1 seq=m到server端,将自己的状态置为fin_wait1,server端收到请求后将自己的状态置为close_wait并回复ACK=1 ack=m+1给client,client收到server的回复后将自己状态置为fin_wait_2,server端发送成功后将自己状态置为last_ack将fin=1,ACK=1 ack=m+1 seq=n发送给client,client接收到server发送的消息后将自己状态置为time_wait并回复ACK=1 ack=n+1给server,server接受到client的回复将自己状态置为closed
- linux权限优化
- 注意权限分离(Linux系统权限、数据库权限不要掌握在同一个部门)
- 权限在满足使用的情况下,最小优先
- 减少使用root用户,尽量用”普通用户“+sudo提权进行日常操作
- 重要系统文件,如:/etc/password、/etc/shadow、/etc/fstab、/etc/sudoers等,日常建议用chattr锁定,需要操作时再打开
- 使用脚本检查系统中新增的SUID、SGID文件
- 开启SSH服务密钥对登录,修改SSH服务端口
- 资源隔离
- 备份策略
完整备份:
增量备份:每次备份以前一次备份作为参照(备份工具:xfsdump)
差异备份:每次备份以第一次备份作为参照
日志转储工具logrotate
- 简述Linux启动过程
- 服务器上电,加载BIOS信息,BIOS进行系统检测
- 加载启动引导程序
- 加载系统内核
- 系统内核重新自检,加载硬件驱动
- 由内核启动系统第一个进程/sbin/init
- 由/sbin/init进程调用/etc/init/rcS.conf,进行系统初始化配置
- 由/etc/init/rcS.conf调用/etc/inittab,确定系统默认运行级别
- 确定系统默认运行级别后,调用/etc/init/rc.conf配置文件
- 运行相应的运行级别目录/etc/rc[0-6].d/中的脚本
- 在启动登录界面之前,执行/etc/rc.d/rc.local中的程序
- SSH服务
登录验证模式修改为秘钥登录
登录端口修改为非22端口以及指定监听IP
禁止root用户远程登录
设置无操作时自动断开连接
设置登录失败后登录次数为6次
编写防火墙规则,使用白名单机制方形ssh服务的监听端口
- DHCP
客户机请求IP(客户机发DHCPDISCOVER广播包)
服务器响应(服务器发DHCPOFFER广播包)
客户机选择IP(客户机发DHCPREQUEST广播包)
服务器确定租约(服务器发DHCPACK/DHCPNACK广播包)
- DNS
- 客户机首先查看本地hosts文件是否有解析记录,有则直接用来访问web server
- 没有则向网卡中记录的首选DNS(本地DNS)发起查询请求
- 本地DNS若有记录则返回给客户端,客户端收到后直接访问web server
- 若没有,则本地DNS向根域服务器发起请求,请求解析对应顶级域名的IP地址
- 本地DNS得到顶级域服务器IP后,再向顶级域服务器发起请求,请求解析权威DNS服务器的IP地址
- 本地DNS服务器获取到权威DNS服务器IP地址后,再向其查询具体的完整域名的对应解析记录
- 最终本地DNS将查询到的对应域名的解析记录发送给客户端,并在本地记录一份
python:
(1)Python 3 和 Python 2 有什么区别
Python 3是Python语言的最新版本,与Python 2相比,Python 3具有许多改进和升级,例如更好的语法、更好的Unicode支持、更好的异常处理和更高的性能。
(1)pdb
非侵入式方法:
python3 -m pdb filename.py
# 导入
import pdb
# 设置断点
pdb.set_trace()
# -------------------
# 断点命令
p + 变量名 # 打印变量 或者直接变量名
n # 执行下一行
s # 进入函数
r # 执行函数到结束
c # 停止调试并继续运行
q # 退出调试
# enter 重复上一次命令
侵入式方法:
import pdb pdb.set_trace()
(2)list底层实现
Python中的列表(list)是一种非常常用的内置数据类型,它可以存储任意类型的元素值,而且其大小可以动态地变化
结构体:双向链表前后指针(python内部管理内存的方式)、引用计数器、动态指针数组、列表容量、当前大小
切片是创建了新的列表对象
插入复杂度是O(n)是将对象的地址指针存到动态数组中,当大小不够时会动态扩展,扩展的是连续的存储空间,如果不够,就要做整体迁移
append复杂度是O(1)
pop(0)的时间复杂度为O(n),pop()的时间复杂度为O(1),删除元素,空的比较多的时候会动态回收
clear就是将list结构体中相关变量的值重置为零
销毁列表del,引用计数器-1,如果引用计数器>0,表示还有其他变量使用此列表,之后不做任何操作:如果引用计数器=0,表示没人使用此列表,之后需要做如下事宜,如果列表中有数据,则先处理元素,其实就是找到每个元素,将元素的引用计数器-1,处理完元素后,ob_item=NULL 、ob_size=0 、ob_allocated=0,将列表从管理内存的双向环状链表中移除,认为这个对象没人使用了,现在可以被销毁了
extend:将元素一个一个添加到指定list复杂度是O(n)
(3)dict底层实现
字典的查询、添加、删除的平均时间复杂度都是O(1)
Python字典的底层原理是使用哈希表实现的,它使用哈希函数将键映射到一个唯一的索引,然后将值存储在该索引处,时间复杂度在常数级别O(1),使用开放地址法解决冲突
数据结构:键、值、hash值
字典添加:如果使用的插槽数+虚拟插槽数大于数组大小的2/3,则会调用insertdict()以添加新的键/值对,并调整字典的大小
移除字典:请注意,如果使用的插槽数比插槽总数少得多,则删除项操作不会触发数组调整大小。但是,当添加键/值对时,需要根据使用的插槽数+虚拟插槽数来调整大小,因此也可以缩小数组
(4)set底层实现
remove:如果删除的元素不在集合,会抛出异常
discard:即使元素不存在,也不会抛出异常
在Python 3中,set的哈希表采用开放寻址(open addressing)的方式来解决哈希冲突,而不是使用链表。
Python 3中的哈希表采用稀疏化(sparseness)技术(一个基于行的压缩矩阵来进行处理),使得哈希表可以使用更少的空间来存储元素,从而减少空间占用。
如果哈希表中已经存在了一个具有相同哈希值的元素,则使用等价性测试检查这两个元素是否相等。如果它们不相等,就在这个桶中继续寻找下一个空闲的位置。如果桶中没有空闲的位置,则需要重新哈希,这会增加哈希表的大小并将所有元素重新分配到新的桶中。
(5)元组
Py_ssize_t,一个整型数据类型。
ob_refcnt,表示对象的引用记数的个数,这个对于垃圾回收很有用处,后面我们分析虚拟机中垃圾回收部分在深入分析。
ob_type,表示这个对象的数据类型是什么,在 python 当中有时候需要对数据的数据类型进行判断比如 isinstance, type 这两个关键字就会使用到这个字段。
ob_size,这个字段表示这个元组当中有多少个元素。
ob_item,这是一个指针,指向真正保存 python 对象数据的地址
(5)UT
Unittest库的执行先后顺序
setUp() ——每个用例执行前清理环境
tearDown() ——每个用例执行后清理环境
setUpClass() ——所有用例执行前运行一次
tearDownClass() ——所有用例执行后运行一次
顺序:setUpClass() -> setUp() -> test1() -> tearDown() -> setUp() -> test2() -> tearDown() ->…-> tearDownClass
a. test fixture:setUp(前置条件)、tearDown(后置条件),用于初始化测试用例及清理和释放资源
b. test case:测试用例,通过集成 unttest.TestCase,来实现用例的继承,在 Unitfest 中,测试用例都是通过 test 来识别的,测试用例命名 test_xxx
c. test suite:测试套件,也称之为测试用例集
d. test runner:运行器,一般通过 runner 来调用 suite 去执行测试
TestCase-->由TestLoader加载TestCase到TestSuit-->由TestRunner来运行TestSuite,将运行结果保存在TextTestResult中
测试用例执行顺序由用例的字典序决定,如果要改变执行顺序需要通过
# 创建一个测试套件 list
suite = unittest.TestSuite()
# 方法一,添加测试用例(子元素)到测试套件(集合)
suite.addTest(MyTestCase('test_3'))
suite.addTest(MyTestCase("test_1"))
UT用例技巧
判断 是否被调用一次,而且调用时的参数是否一致
mock_set.assert_called_once_with("test", operation_info=xxx)
判断是否被调用多次,参数是期望调用的列表
from mock import call
expect_create_args_list = [call("/cps/switch"),
call("/cps/switch/enable_cps_hub",
data={'value': 'True', 'hub_proxy_instance': 'cps-hub-nginx2'})]
mock_createZkNode.assert_has_calls(expect_create_args_list)
判断被调用次数
real_call_count_list = []
for mock_obj in check_obj_list:
real_call_count_list.append(mock_obj.call_count)
当函数的入参是个类实例时,由于实例id不一致,我们先通过ANY判断,再解析调用的时的参数拿到实例,通过isinstance,以及判断实例内属性是否一致来达到目的
mock_delete_software_package.assert_called_once_with("fpl_test.tar.gz", mock.ANY)
捕获异常用例
with self.assertRaises(Exception) as context:
self.component_host_zkao.list_instance_id_by_component_name("cps.cps-client", False)
# Then
self.assertEqual(error_msg, str(context.exception))
(6)用例覆盖
语句覆盖:每条语句至少执行一次。
判定覆盖:每个判定的所有可能结果至少出现一次。(又称“分支覆盖”)
条件覆盖:每个条件的所有可能结果至少执行一次。
判定/条件覆盖:一个判定中的每个条件的所有可能结果至少执行一次,并且每个判断本身的所有可能结果至少执行一次。
多重条件覆盖(组合覆盖):每个判定中的所有可能的条件结果的组合,以及所有的入口点都至少执行一次。(注意“可能”二字,因为有些组合的情况难以生成。)
完全路径覆盖:每条路径至少执行一次。
基本路径覆盖:根据流图计算环复杂度,得到基本路径覆盖的用例数。
分割后的完全路径覆盖:每条路径至少执行一次,每个条件的所有可能结果至少执行一次。
路径覆盖 > 多重条件覆盖 > 判定/条件覆盖 > 条件覆盖 > 判定覆盖 > 语句覆盖
1. 路径覆盖是覆盖率最高的。语句覆盖最弱。
2. 满足多重条件覆盖准则的测试用例集,同样满足判定覆盖准则、条件覆盖准则和判定/条件覆盖准则。
(7)迭代器、迭代对象
可迭代对象包含迭代器。
如果一个对象拥有__iter__方法,其是可迭代对象;如果一个对象拥有__next__方法,其是迭代器。
定义可迭代对象,必须实现__iter__方法;定义迭代器,必须实现__iter__和__next__方法。
Next()是初始化创建一个实例,该实例可以被调用,也就是 Next()()时会调用 Next().call()
列表,元组,集合,字典,字符串都是可迭代对象,所有可以用for循环遍历的对象,都是可迭代对象,甚至包括文件对象
(8)多线程
面向对象 vs. 面向过程:threading是面向对象的多线程模块,而thread是面向过程的多线程模块。使用threading模块创建线程更加方便,使用起来也更加简单。
功能更丰富:threading模块提供了许多thread模块不具备的功能,如线程锁、信号量、事件等。这些功能的引入使得在使用线程时更加方便和灵活。
兼容性:threading模块可以在Python 2和Python 3中使用,而thread模块只能在Python 2中使用。这使得使用threading模块更加具有通用性
https://zhuanlan.zhihu.com/p/428786719
https://www.ngui.cc/zz/2431225.html?action=onClick
(9)多进程
https://www.php.cn/faq/557361.html
https://zhuanlan.zhihu.com/p/604064435
multiprocessing
(9)python底层是用C写的,不是C++。
Python 在执行代码时,会使用到解释器。Python 有三种解释器 CPython、JPython、Pypy。
其中 CPython 就是使用 C 语言编写的,解释器的实现方式可以有很多种。
(10)deque
(11)OrderedDict
(12)sort
(13)cleancode
(14)手写装饰器
(15)作用域
local->enclosed->global->builtin
(16)高阶函数
zip:接受一系列可迭代对象作为参数,将对象中对应的元素打包成一个个tuple(元组),然后返回由这些tuples组成的list(列表)。若传入参数的长度不等,则返回list的长度和参数中长度最短的对象相同
map:map函数会根据提供的函数对指定序列做映射。map(lambda x: x ** 2, [1, 2, 3, 4, 5]) 返回结果为:[1, 4, 9, 16, 25],map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10]),当函数为None时,操作和zip相似:map(None, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])返回结果为:[(1, 2), (3, 4), (5, 6), (7, 8), (9, 10)]
filter:filter函数会对序列参数sequence中的每个元素调用function函数,最后返回的结果包含调用结果为True的元素
reduce:function参数是一个有两个参数的函数,reduce依次从sequence中取一个元素,和上一次调用function的结果做参数再次调用function。reduce(lambda x, y: x + y, [2, 3, 4, 5, 6], 1),结果为21( (((((1+2)+3)+4)+5)+6) ),reduce(lambda x, y: x + y, [2, 3, 4, 5, 6])
常用设计模式:
单例模式:只有一个实例,通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。线程安全,没有加任何锁、执行效率比较高。类加载的时候就初始化,不管后期用不用都占着空间,浪费了内存。
工厂模式:通过将创建对象的代码和使用对象的代码解耦,工厂能够降低应用维护的复杂度。工厂方法(Factory Method),它是一个方法(或以Python术语来说,是一个函数),对不同的输入参数返回不同的对象,它是一组用于创建一系列相关事物对象的工厂方法。
优点:工厂模式可以提高代码的灵活性和可维护性,客户端代码和具体类的实现之间松耦合,当需要新增或更改某个具体类时,只需要修改工厂类即可,无需修改客户端代码。缺点:由于增加了工厂类的复杂性,会增加代码的复杂性和理解难度。应用场景:当需要实例化多个相似的对象时,可以使用工厂模式来封装实例化过程。同时,工厂模式还可以用于隐藏对象的实现细节,提高程序的安全性
抽象工厂模式(Abstract Factory Pattern):提供一个接口或基类,用于创建一系列相关或依赖的对象,而无需指定具体的类。
观察者模式:观察者模式是一种行为型模式,用于在对象之间建立一种依赖关系,使得当一个对象的状态发生变化时,所有依赖于它的对象都会收到通知并自动更新
模板方法模式:模板方法模式是一种行为型模式,用于定义一个算法的骨架,而将一些步骤延迟到子类中实现。在Python中,可以使用抽象基类来实现模板方法模式
适配器模式(Adapter Pattern):将一个类的接口转换为客户端所期望的另一个接口,使得原本不兼容的接口能够一起工作。
装饰器模式(Decorator Pattern):动态地为对象添加额外的功能,不改变其接口。
代理模式(Proxy Pattern):为其他对象提供一种代理,控制对该对象的访问,并在访问前后进行一些额外的操作。
策略模式(Strategy Pattern):定义一系列的算法,并将其封装成独立的类,使得它们可以互相替换,使算法的变化独立于使用算法的客户端。
迭代器模式(Iterator Pattern):提供一种方法来顺序访问聚合对象中的各个元素,而无需暴露其内部表示。
责任链模式(Chain of Responsibility Pattern):将请求的发送者和接收者解耦,通过一条责任链传递请求,直到有一个对象能够处理它为止。
状态模式(State Pattern):允许对象在其内部状态发生改变时改变其行为,使其看起来像是改变了类。
数据库:
mysql:
https://blog.csdn.net/YJ000312/article/details/123790957
https://baijiahao.baidu.com/s?id=1761402527311101533&wfr=spider&for=pc
gaussdb:
https://blog.csdn.net/weixin_39598796/article/details/113720737
中间件:
redis:与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。Redis是一个开源,内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
redis内部整体的存储结构是一个大的hashmap,内部是数组实现的hash,key冲突通过挂链表去实现,每个dictEntry为一个key/value对象,value为定义的redisObject
https://www.zhihu.com/tardis/bd/art/487583440?source_id=1001
https://zhuanlan.zhihu.com/p/427496556
kafka:
优点:控制了流量 缺点:会让流程变慢
生产者:Producer 往Kafka集群生成数据
消费者:Consumer 往Kafka里面去获取数据,处理数据、消费数据 Kafka的数据是由消费者自己去拉去Kafka里面的数据
主题:topic
分区:partition 默认一个topic有一个分区(partition),自己可设置多个分区(分区分散存储在服务器不同节点上)
https://zhuanlan.zhihu.com/p/446774729
https://blog.csdn.net/wudidahuanggua/article/details/127086186
mq
zookeeper
https://baijiahao.baidu.com/s?id=1765960923918704613&wfr=spider&for=pc
算法:
2、动态规划
3、背包
(1)01背包
(2)完全背包
(3)多重背包
4、树
(1)中序遍历
(2)后续遍历
(3)前序遍历
(4)层次遍历
(5)二叉搜索树
(6)平衡二叉树
(7)完全二叉树
(8)红黑树(Java的TreeMap实现,C++的STL,map和set都是用红黑树实现的、著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块)
每个结点是黑色或者红色。
根结点是黑色。
每个叶子结点(NIL)是黑色。 [注意:这里叶子结点,是指为空(NIL或NULL)的叶子结点!]
如果一个结点是红色的,则它的子结点必须是黑色的。
每个结点到叶子结点NIL所经过的黑色结点的个数一样的。[确保没有一条路径会比其他路径长出俩倍,所以红黑树是相对接近平衡的二叉树的!]
(9)深度遍历
(10)B+树
5、图
(1)并查集
(2)深搜
(3)广搜
(4)最短路径(dijkstra、Bellman-Ford)
(5)最小生成树(Prim、kruskal)
(6)关键路径
6、排序
(1)归并排序
(2)堆排序
(3)插入排序
(4)冒泡排序
(5)二分查找
7、哈希
8、链表
(1)双指针
(2)快慢指针
9、栈
(1)前缀表达式、中缀表达式、后缀表达式
(2)后缀表达式求值
10、队列
11、常用函数
heapq.heappop()、heapq.heappush() 优先队列]