学习笔记W1

1207-1213

Go知识图谱

图片链接

代码规范

[曹大博客] (https://mp.weixin.qq.com/s/MRZZOX7cZPIJPelcuihXUw)
主要总结了初级程序员容易犯的代码不规范问题:

  • 命名规范: 业务内部逻辑变量命名讲究单词打全,函数名尽量能让人清晰明了其功能.
  • 魔法数字: 在配置文件中集中维护特殊变量,可以引入各种方便的配置系统.可以即时修改和配置下发无需系统上下线. 如java中常用的disconf,或借助现在流行的etcd/zookeeper自己开发
  • 配置文件长度: 对配置文件进行业务逻辑分类(掌握好粒度,配置文件最好有对应的comment进行检索,将系统模块拆分不同模块管理自己的配置文件)
  • 函数内容长度: 根据功能进行简单的小函数划分
  • 滥用回调:使用消息队列来解耦.如果系统里发生一个事件,其他模块对事件的数据有依赖,那么就让其订阅系统里产生的消息即可.(Kafka)不过可能会由于引入基于消息队列的分布式系统产生可靠性问题,saga pattern可以解决部分问题.
  • 分层结果返回没有明确界限和定义: 设计模式修炼,但是要避免滥用设计模式
  • 公共接口不可重入
  • 访问数据库不做批量: 即使存在数据库连接池,也要尽可能批量操作提升性能
  • 尽量少用if else多层嵌套
  • 数据库查询使用开源builder来减少函数
  • 工作流系统update前判断修改前的状态
  • 考虑线程安全问题:有全局map之类的变量访问时考虑加锁
  • open资源用后关闭

    栈内存管理原理分析

    链接
    介绍了源码runtime/stack.go中是如何管理栈内存的

堆内存管理

C语言申请内存通过调用malloc函数指定分配大小向操作系统直接申请,会涉及用户态和内核态的切换,导致性能下降.
Golang的内存管理基于tmalloc模型设计,但是局部缓存并不分配给进程/线程,而是分配给P;GC是STW,并不是每个进程单独GC.

  • span: golang内存管理的基本单位,管理指定规格(page为单位,67种)的内存块. 最大32KB,超过32KB大小的由特殊的class表示,该classID=0.每个class只包含一个对象.
  • 内存管理单元: 内存分配器器由mcache,mcentral,mheap组成
    • mcache绑定在P上,用来给协程分配对象存储空间,不需要锁.初始化时没有任何mspan资源,使用过程中动态申请,通过双向链表连接;
    • mcentral是公共资源,会有多个mcache向它申请mspan,需要加锁.
    • mheap,当mcentral空间不足会向mheap申请新的页,也需要加锁.
  • 分配原则:
    • tiny对象,直接向mcache的tiny对象分配器申请,空间不足时向mcache的tinySpanClass规格的span链表申请,没有继续向mcentral申请对应规格mspan,依旧没有则向mheap申请,最后都不存在向操作系统申请;
    • 小对象内存分配,先向本线程mcache申请,mspan没有空闲空间->mcentral->mheap->OS page
    • 大对象内存分配,直接向mheap申请spanclass=0,没有向操作系统申请
  • GC改进:
    • 1.12版本对mheap结构添加了reclaimCredit成员变量,每次mcentral向mheap申请新的page创建span时,先扫描arenas里面的heapArena,清理GC需回收的page数量空间,由于扫描不可能刚好相等,多清理的page大小存到reclaimCredit里.下次再次扫描时先抵消这部分不够再扫描.防止mheap使用率过快增长.
    • 1.12版本mheap引入scavengeCredit成员变量,当向操作系统申请内存空间的时候,会先去扫描free这个二叉树堆,span从大到小的扫描,释放所需大小的空间给os,多余释放的存储scavengeCredit中,下次再次扫描的时候会先扣除这个值。

      全局栈内存初始化

      go程序启动时调用stackinit()初始化,涉及两个重要全局变量stackpool/stackLarge, mspanList初始化头尾为nil.

      申请内存

  • _StackMin:值为2048,表示go 代码使用最小的栈空间大小
  • stackalloc()的stacksize 是一个根据当前系统计算出来的值,win64=8kb、win32=4kb、plan9=4kb、其他系统如linux/bsd/drawin=2kb
  • 从空闲池中分配栈空间,如果池为空,则向mheap申请内存,并把多余的空间缓存到池中

    栈扩容

    linux系统下协程初始栈空间大小为仅仅只有2KB很小,仅分配了保障协程运行的最小空间。go的策略是按需申请,动态扩容,尽量减少内存浪费,每次扩容时会调用运行时方法runtime.morestack(SB) ->runtime.newstack(SB) 。

    栈收缩

    栈收缩是由gc触发执行的源码位置runtime/mgcmark.go,收缩时调用方法shrinkstack

深入理解StatefulSet

深入理解StatefulSet

StatefulSet是一种特殊的Deployment,它使用K8S里的两个标准功能Headless Service和PVC实现对拓扑状态和存储状态的维护。通过Headless Service管控每个Pod创建一个固定不变的DNS域名作为Pod在集群的网络标识。加上为Pod编号并严格按照编号顺序进行Pod调度,保证了StatefulSet对维护应用拓扑状态的支持。StatefulSet定义文件中的volumeClainTemplates声明Pod使用的PVC,它创建出来的PVC会以名称编号与Pod进行绑定,借助PVC独立于Pod的生命周期完成对应用存储状态的维护。

分布式事务的实现原理

分布式事务的实现原理

数据库事务拥有ACID四大特性:

  • Atomicity 原子性
  • Consistency 一致性
  • Isolation 隔离性
  • Durability 持久性

为了保证事务能在执行任意过程中回滚(原子性)并且提交的事务会永久保存在数据库中,使用事务日志来存储事务执行过程中数据库的变动,每个事务日志包含事务ID、当前被修改的元素、变动前后的值。当事务尝试对数据库进行修改时,先生成一条日志并刷新到磁盘中(append追加速度比较快),然后才会向数据库写入或更新相应的记录。

MYSQL中常用的存储引擎InnoDB中,事务日志有undo log、redo log两种。

并发执行SQL过程中可能无法保证数据库对隔离性的要求,为了避免并发带来的一致性问题、满足数据库对隔离性的要求,数据库系统往往都会使用并发控制机制来充分利用效率。常用的有:锁、时间戳、MVCC

分布式事务

单机中相对可靠的方法调用以及进程间通信方式没办法使用,由于网络不稳定服务之间的传递会出现障碍。分布式事务就是在不可靠的通信下实现事务的特性,分布式事务也会依赖数据库、Zookeeper、ETCD等服务追踪事务的执行过程,保证事务特性。

  • 2PC: 两阶段提交分为投票阶段和提交阶段,在投票阶段协调者向事务的参与者询问是否可以执行操作的请求,等待参与者相应。参与者执行相应的事务操作并记录重做和回滚日志,所有执行成功的参与者向协调者发送AGGREMENT/ABORT表示执行操作结果。协调者根据投票结果向参与者发送提交或回滚指令。两阶段提交是一种阻塞协议,如果事务执行过程中协调者宕机,事务的一部分参与者将永远无法完成事务,可能出现参与者状态不一致的问题。
  • 3PC:为了解决上述问题,引入超时机制和准备阶段。如果协调者或者参与者在规定时间内没有接收到来自其他节点的响应根据当前的状态选择提交或终止整个事务。当参与者向协调者发送 ACK 后,如果长时间没有得到协调者的响应,在默认情况下,参与者会自动将超时的事务进行提交,不会像两阶段提交中被阻塞住;

MySQL 的 InnoDB 引擎其实能够支持分布式事务,即 XA 事务;XA 事务就是用了2PC实现分布式事务,其中事务管理器为协调者,而资源管理器就是分布式事务的参与者。XA 确实能够保证较强的一致性,但是在 MySQL XA 的执行过程中会对相应的资源加锁,阻塞其他事务对该资源的访问,如果事务长时间没有 COMMIT 或者 ROLLBACK,其实会对数据库造成比较严重的影响。

Saga

两阶段提交其实可以保证事务的强一致性,但是在很多业务场景下,我们其实只需要保证业务的最终一致性,在一定的时间窗口内,多个系统中的数据不一致是可以接受的,在过了时间窗口之后,所有系统都会返回一致的结果。放弃满足ACID,选择实现 BASE(Basic Availability, Soft, Eventual consistency) 事务。

Saga 其实就一种简化的分布式事务解决方案,它将一系列的分布式操作转化成了一系列的本地事务,在每一个本地事务中我们都会更新数据库并且向集群中的其他服务发送一条的新的消息来触发下一个本地的事务;一旦本地的事务因为违反了业务逻辑而失败,那么就会立刻触发一系列的回滚操作来撤回之前本地事务造成的副作用。

Sagas解决长事务问题。

一周算法汇总

lc55_jump-game_Medium
实现

lc51_nqueen_Hard
实现

lc322_coin-change_Medium
实现
lc862_shortest-subarray-with-sum-at-least-k_Hard
实现

lc198_house-robber_Easy
实现
lc1622_fancy-sequence_Hard
实现

面试问题汇总:

  1. new 和 make的区别
    答:
    参考这篇文章Go语言中的make和new
    make是初始化内置的数据结构,如切片,哈希表,channel;
    在编译期间的类型检查阶段,GO语言将代表make关键字的OMAKE节点根据参数类型不同转换为OMAKESLICE,OMAKEMAP,OMAKECHAN三种节点,这些节点调用不同的运行时函数来初始化相应数据结构.

    • slice := make([]int, 0, 100)
    • hash := make(map[int]bool, 10)
    • channel := make(chan int, 5)
      new的作用是根据传入的类型分配一片内存空间,并返回这片内存空间的指针.
      对于new关键字编译器会在中间代码生成阶段先转换为ONEWOBJ类型的阶段,然后根据申请内存空间大小:0返回表示空指针的zerobase变量,反之转换成runtime.newobject函数在堆上申请内存.
      其中runtime.newobject函数会是获取传入类型占用空间的大小,调用runtime.malloc并返回指向这片内存的指针
    • i := new(int)
    • var v int | i := &v
  2. go 引用类型有哪些
    答:
    值类型有int,float, bool, string, 数组, struct
    引用类型有 指针,slice切片,map,channel,函数等
    两者的区别是,值类型变量直接存储值,内存通常在栈中分配;引用类型变量通常存储的是一个地址,这个地址对应的内存空间存储真正的数值,内存通常在堆中分配。

  3. go的内存模型中,为什么小对象多了会造成gc压力
    答: 小对象过多会导致GC三色法消耗过多的GPU

  4. 将10阶对称矩阵压缩存储到一维数组A中,则数组A的长度最少为 10*10/2

  5. golang中selct的多个case同时成立时,选择的是哪一个?
    答: select在遇到多个Channel同时响应时会随机挑选case执行, 这个设计是十多年前select提交引入并一直保留到现在的。如果多个条件满足时我们按顺序执行,那么后面的条件将会得不到执行,随机的引入是为了避免饥饿问题的发生。

  6. golang 中除了使用 sync 锁,还可以如何保证并发安全? atomic 是什么?
    答: golang的auomic院子操作是所有锁机制中效率最高的,能够使用原子操作的地方尽量使用。

  7. sync.Map 对键的类型有什么要求么?
    答: go语言规定键类型的值之间必须支持==和!=操作符判断,而函数类型,字典类型,切片类型不支持判等操作,因此键类型不能是上述几种类型。

  8. 如何避免死锁? golang 中如何检测死锁?
    答:

  9. 不使用 goroutine 怎么实现监听三个 channel,合并三个 channel,变成一个新的 channel,这三个任意有消息的时候都要给 新 channel 传递,都没消息的时候要关闭那个新的 channel?

    func merge(ins …<-chan string) <- chan string {
    var wg sync.WaitGroup
    out := make(chan string)

    p := func (in <-chan string) {

    defer wg.Done()
    for c := range in {
      out <-c
    }
    

    }
    }