思考总结技术知识

 

Kafka

引以为傲的特性:顺序写、零拷贝

顺序写保证高性能,写入速度快。其并不是直接刷到磁盘,而是先提交到内存缓冲区,不用太担心机器宕机数据丢失,因为有ISR成员,通过参数设置同时写入几个才代表生产者发送消息成功,只要这几个成员不同时宕机就不会有问题,为了容灾我们可以配置跨机房、跨地域!

零拷贝保证高性能消费。这是操作系统的特性,如果操作系统不支持那么也就没这个特性了。意思就是消费者消费的数据直接从内核态拷贝到网卡发送给消费者。如果消费者版本低V1版本,而生产者产生的消息都是V2版本的,那零拷贝就失效了,因为需要Broker中转一下将消息解析并转化成V1,效率极低,因此我们要保证生产者消费者Broker等消息是一致的。

压缩就不说了。就是降低传输量同时存储成本也会降低(压缩后传输数据小,带宽占用就小了),要权衡带宽和生产者CPU使用情况,如果CPU很充足带宽不充足时则开启压缩,如果CPU已经很繁忙了,还开启压缩,无谓“雪上加霜”
概念:

  1. Broker:kafka实例,多个Broker组成Kafka集群

    1. 处理过程

      1. 一个线程,进行Reactor响应式转发给网络线程池(默认3个线程)

      2. 网络线程池写入到共享队列中

      3. IO线程池(默认8个线程)取出共享队列中的请求进行处理

      4. 对于repli_min_factor为1的,处理完本机的写入后即返回给对应网络线程,否则先存入缓冲队列,等其余负责的ack再返回给对应网络线程

      5. 网络线程接收到了后直接返回响应即可

  2. Topic:

  3. Partition

    1. Leader副本

    2. Follower副本

    3. 分片在同一个消费者组下,只能被其中某个消费者消费

  4. Producer

    1. TCP连接

      1. 和bootstrap.server下配置的所有ip建立连接

      2. 可配参数设置的定时更新集群元数据时,发现与某些Broker没建立连接则会建立

      3. 发送消息时发现与某个broker没建立连接则会建立

      4. 断连:超过可配参数的时间内没与Broker通讯过,会断开连接

    2. 发送过程

      1. 经过序列化器、拦截器、压缩器、转发器 到本地收集器(和要发往的Topic分片一致)

      2. 由生产者刚启动时的Sender线程,去本地收集器,每个分片内是一块块存储的,当大小达到可配置的参数值或者时间超过了可配置的参数值时,会发送给对应Broker的分片,这些未接收到回应的消息存在暂存队列,等收到发送成功即真正成功

  5. Consumer Group

    1. TCP连接

      1. 向任意一个Broker发送FindCoordinator请求(组id的哈希 % 50),默认内部偏移量Topic50个分片,不建议修改

      2. 向刚刚找到的Coordinator的Leader副本建立连接

      3. 拉取消息时,即pull时建立连接

      4. pull的话就是在这个tcp上一直就请求的,那tcp就不会关了

    2. 拉取过程

      1. 根据偏移量从Leader副本上取数据,手动提交偏移量(Broker是无状态的,具体消费什么消息都是消费者自己根据偏移量索取的)

  6. Consumer

    1. 虽然发送和消费都是Leader副本,但是Consumer只能看到已提交的消息,对于当前Leader副本最新写入的消息不一定是可见的!

    2. Lead和Lag

      1. Lead(这个一般不看):消费者当前消费的偏移量和分片最早的消息之间的差值(当值消费特别慢,数据过期了导致消息丢失)

      2. Lag(这个看的多):消费者当前消费的偏移量和目前可消费的最新消息之间的差值(代表落后多少)


  7. Coordinator(协调者)

    1. 消费者组相关

    2. 位移管理

    3. Rebalance

      1. 增减topic

      2. 增减分片

      3. 增减消费者

      4. 上面2个是运维可接受

        下面那个防止消费者误判导致进行不必要的Rebalance。解决方案是:增大消费者向Coordinator的心跳时间,也不能太大,否则真正故障了这个分片一直没实例消费,Lag越来越大

  8. Controller

    1. Broker间

    2. Leader选举通知,元数据信息同步(它的元数据信息是从ZK获取的)

     

Redis

可以用来干什么?

TPC的模式,基本单位是RedisObject

  1. 缓存(高性能读写)

    1. 单线程IO复用

  2. 分布式锁(进程外共享)

    1. 单实例实现(value对应当前唯一id,实现简单,缺点是可靠性不高,实例挂了资源不可用)

      1. 删除要使用Lua脚本,否则如果当前读取value是当前id,但是刚好过期时间到了,锁被其他实例申请了,这时候删除锁就删错了!

    2. 多实例实现

      1. RedLock。依次向各节点申请加锁,半数以上实例成功代表申请成功,否则向全部实例发送释放锁请求

 

内部结构什么样?

  1. 支持String、List、Hash、Set、Sorted Set

    1. 对于节省内存,底层做了很多优化。RedisObject如果是Long则使用int编码,Zset如果个数小于[可配置的值(是不是能配置不确定,好像默认是1000)]使用压缩列表

  2. 持久化

    1. aof

      1. always、everysec、no 【选择每秒,实现是异步1s同步到磁盘上,最多丢失1s的数据,算是性能和可用性的权衡】

      2. aof重写

        1. bgsave。fork子进程完成

    2. rdb

      1. 二进制文件

      2. bgrewriterof。fork子进程(fork过程随着内存大小阻塞时间加长),过程变化使用操作系统的COW,因此对于写入特别多的,内存最多可能翻2倍!

 

高可靠怎么支持?

  1. 主从架构

    1. 复制缓冲区(每个从库接入主库时,主库都会触发fork进程生成rdb,同时期间以及后续增量的命令都是这个缓冲区发送的)

    2. 复制积压缓冲区(环状的缓冲区。用于判断主从同步进度,如果主库写入覆盖掉了从库还未写入的,那个从库会再次出发新接入时的操作,即主库rbd发送给它,它来加载这样)

  2. 主-从-从架构

    1. 减少主库fork进程、复制缓冲区用的主库内存,把一部分从挂在从库下

  3. 分片集群

    1. 避免单实例量过大。每个分片实例采用主从架构

  4. 哨兵机器(下线、选主、切新)

    1. 下线需大于等于qutonum判断主观下线

    2. 切新需要大于等于qutonum且半数以上哨兵同意(半数是集群最初启动的哨兵的半数,而不是现在存活哨兵的半数,即原来5个哨兵,挂了3,如果qutonum为2,那2个都判定主库下线,主库不可写入,但是切新是切不了的,因为判定主观下线的哨兵数2 < 3)

  5. 脑裂

    1. 由于主库CPU负载过高一直未响应哨兵的应答,导致误判下线,切换期间主库又恢复可用了,从而客户端向这个主库写入成功了。

      1. 解决:心跳应答超时时间设置长一些,但是也不能太长,否则出现故障了一直不执行切主,业务不可用时间就会边长。

 

Mysql

  1. Server层

    1. 连接器、缓存器、分析器、优化器、执行器

    2. binlog(二进制的逻辑日志)

    3. buffer pool 【数据页(干净页、脏页等)、change buffer(用于存放更新但是数据并未在内存数据页中记录,加速更新)】

  2. 执行引擎层

    1. innodb等

    2. 写入两阶段提交:redo log prepare、binlog、redolog commit.(是crash-safe的)

    3. redo log

    4. 索引B+树(聚簇索引、非聚簇索引)

  3. MVCC

    1. undo log 逻辑日志

    2. 快照读:select

    3. 当前读:增删改 和 查询加共享锁或者for update等

  4. ACID

    1. 看行锁就行。Mysql的锁是加在索引上的

    2. RR下有间隙锁。个人理解RR下可能得幻读有个情况是:update一条开始查不到,但是其他事务插入的记录,此时记录事务id是自己,再查询就能看到了,就是幻读

    3. RR下有间隙锁,这个想了解清楚还是得复习看看45讲,其实大致就是几个原则,45讲有写。

  5. 高可用

    1. 主从架构

      1. relay log(同步方式可以同步,也可以异步)异步的话从切主可能会数据不一致

  6. 为什么索引采用B+树而不是B数?

    1. B+树叶子节点有指针,范围查询效率更高。

    2. B+树的真实记录都存在叶子节点,这样每个数据块大小有限情况下,每个块容纳的索引个数更多,这样树的高度会更低,IO次数也就更少了。

  7. Mysql的Decimal(4,2):最长有4位,最多2位小数(即小数部分2位,整数部分最多[4-2]=2位)

    • 0 --> 0.00

    • 1.2 --> 1.20

    • 123.1 --> 异常无法入库

    • 1234.1 --> 异常无法入库

  8. explain的一些看法

    1. image.jpeg

  9. mysql之binlog与redolog落盘时机

    1. binlog之binlog_flush_flag(事务提交之后才会记录binlog)

      0:内存中,缓冲满了或者mysqk关闭才会落盘

      1:写入Page Cache

      2:直接强制刷到磁盘

      redolog之innodb_flush_log_at_trx_commit

      事务提交时点:将redolog缓冲区写入page cache/刷盘

      0:不写磁盘交操作系统管理(满了则写入page cache)

      1:事务提交则写入磁盘(说性能影响大,应该是直接刷到磁盘上了)

      2:每隔1s写入磁盘(1s刷一次盘)

  10. binlog_flush_flag 指的是 binlog 日志缓存使用的一种策略,其参数均默认为 0,表示优先将 binlog 日志记录在内存中,等到缓存满了,或者 MySQL 关闭的时候才落盘。

    • 当取值为 0 时,表示采用的是内存缓冲区刷盘的策略。当缓冲区满了或 MySQL 关闭时,才会将 binlog 日志落盘。

    • 当取值为 1 时,表示采用的是写入 Page Cache 的策略。每写入一条 binlog 记录,就会将其写入到 Page Cache 中,等到操作系统决定刷入磁盘。

    • 当取值为 2 时,表示采用的是直接强制刷到磁盘的策略,每写入一条 binlog 记录,就会直接强制将其刷到磁盘。

    innodb_flush_log_at_trx_commit 则是 InnoDB 引擎中控制事务日志(redo log)写入方式的参数。其默认取值为 1。

    • 当取值为 0 时,表示事务提交时不进行写入,而是等到操作系统按照 Page Cache 决定什么时候刷入磁盘,可能会有丢失数据的风险。这是性能最好的一种方案。

    • 当取值为 1 时,表示事务提交时立即将事务日志写入磁盘,确保数据的可靠性。这是性能最差但数据最稳定的一种方案。

    • 当取值为 2 时,表示事务提交时将事务日志写入内存中的缓冲区,每秒钟往磁盘中刷入一次事务日志。这种方案可以减轻写入磁盘对性能的影响,但是可能会出现一些数据丢失的情况。

    • draw.io

  11. kafka在rebalance为什么可能出现重复消费的问题?

    1. 在 Kafka 中,当消费者组成员发生变化或者有新的消费者加入消费者组时,就会触发 Rebalance 操作,以重新分配对分区的消费权。在 Rebalance 过程中,Kafka 会先将分区的消费权交给新成员或者新分配的消费者,然后再撤销旧成员或者原分配的消费者对分区的消费权。

      在 Rebalance 过程中可能会出现重复消费的问题,主要原因有两个:

      1. 原有消费者已经消费的消息在队列中仍然存在但未被提交(即消费过程中未发送 Acknowledge)。在 Rebalance 过程中,这些未提交的消息会被新的消费者重新消费。

      2. 新成员或者新分配的消费者在进行消费之前,该分区已经有了消息被原有消费者消费但还未被提交。这些消息也会被新成员或者新分配的消费者重新消费一遍。

      为了避免出现重复消费,可以将 Kafka 中的 auto.offset.reset 参数配置为 latest,这样消费者在消费时会从最新的 offset(即最新的消息)开始消费,跳过已经消费过的消息。此外,在消费过程中尽量避免长时间占用分区的消费权,及时将已经消费的消息提交 Acknowledge,避免在 Rebalance 过程中出现未提交的消息。

 

posted @ 2023-04-23 11:00  程序杰杰  阅读(21)  评论(0编辑  收藏  举报