manager style about your preference

    #tech manager

    专精专业和冗余流程的管理应该在什么时候把握平衡

    在写程序和架构的过程中,经常会面临到底是以快速实现功能为目标取向的单点服务系统,还是在开始设计架构和功能的时候就考虑周全的分布式具有冗余的系统架构。 以技术角度看,得出合理的结论并不是特别难。在前期快速产品迭代争取竞争优势的时期,只要能保证基本的稳定性,当然是怎么快怎么来,单点未必就不是最佳选择。

    在技术管理的过程中是同样存在此类情形的,通常小创业公司,小团队在快速实现功能的时候,往往由于人员限制,是基本没有什么人员冗余考虑的,某些服务和架构几乎全部把持在单个技术人员手中,缺点是有的,如果人员忠诚度不高可能会造成一人流失,整个产品受影响,但换个角度这种情况能把持到一个人手中,通常不是技术合伙人也是核心员工,这种风险其实并不高,而且有较好的优势是技术核心人员成就感,责任心是爆棚的,这还不算什么,由于许多服务,代码都是核心的1,2个人员维护,迭代速度非常可观,给产品提供的技术驱动力就很可观。
    这种技术驱动力能持续多久,取决很多方面,其中有一方面就是在产品已经具有一定规模后,CXO究竟是认为自己的产品已经形成技术壁垒,能在一定时间内阻挡竞争者,还是认为自己的产品依然需要不断的迭代,进一步拉开自己于竞品的距离。前者,CXO们会开始着手开发流程,加入项目管理,开发过程管理,运维管理,权限回收,人员冗余,通常这些也会带来测试需求上升,测试地位拉高的情形,无论是经典互联网或是新创互联网,基本都会把流程偏向与传统的软件开发流程,形成比较固化的瀑布式开发,或稍微好点的是螺旋模型。一定程度上会让产品质量得到提高,但迭代速度因为各类流程,必然不可避免的降低,期间性价比就看各自的权衡了。 还有一种CXO们,会依然保持非常快速的产品功能迭代,由于人员规模变大,流程不规范,虽然依旧能仰仗那几个核心技术人员,但人员的沟通成本是上升的,开发过程缺少规范流程,软件质量有部分是有影响的,但好在产品迭代速度快,功能和其他运营方面的出色能遮盖这部分软件瑕疵,他们依然会在瑕疵并存的过程中快速成长。这个过程对人的仰仗是多余对开发流程规范的仰仗的,如果存在靠谱的核心团队,这种模式会具技术竞争力。

    没有哪种是所谓的绝对好的模式,CXO们当然不会比我笨。寥寥几笔全当笑话看吧。

    ===============================================================================

    transaction in micro service architecture

    #transaction #micro service #architecture

    微服务(分布式)架构下,关于事务补偿机制

    整个17年整理过不少遇到的case,在笔记内太凌乱,太懒没做整理,瞬间已经12月,17年算是结束了。整理一个笔记,给2017留下点回忆哈哈 :)  

    概述

    微服务,或是非集中式应用,也就是系统架构做拆分后,会涉及一个非常典型的问题,就是事务的问题。不管是特别在意一致性的金钱相关的,还是其他业务场景,都不同程度存在一个操作流转与多个服务之间的问题。     这其中涉及的分布式事务问题,随便搜一把,够看一礼拜了,不过多数都是重复的,TCC, 二阶段,三阶段,最重要还是在业务中挖掘发现自己试用的场景。 这里总结表述的是事务性数据补偿的方案。  

    期间调研过RocketMQ, 数据中间件方式,TCC等,但最终的方案,还是自己在业务基础上设计的简易的事务或叫数据最终一致性补偿。都是比较成熟的做法组合,核心思想就是local transaction + tx-coordinator service + 幂等支持的tx executor sdk   这个思想其实跟RocketMQ是比较像的,不同的是rocketMQ对于这个过程中产生的数据自有处理,而且一样设计了类似rocketMQ的confirm call befor retry逻辑,同样的利用db,也做到了recover after breakdown. 且提供幂等sdk。  几个部分的概述:   前提: 系统仅处理最终一致性数据的补偿,暂时不考虑TCC中涉及的rollback  

    1. transaction producer, 事务发起方。 也就是本次数据的起点。我们需要提供sdk封装给应用开发,由于我们涉及多语言,所以还得准备3份,比较苦逼。这里的封装不同语言实现时封装程度不一样,但最终目标是尽量减少代码入侵(实际上解决分布式事务时,目前还不存在完全不入侵代码的方案,即使在java系这种各种讲究深度封装也是),producer利用db的事务,本地完成业务逻辑的提交和分布式事务数据的提交,严格来说因为这两个db因为不在一个库,依然会有事务问题,解决方法是利用2PC(太重),提供confirm befor retry的逻辑给外部的事务协调服务回调。基本能把本地事务问题降低到很低的出现问题概率。produer在和补偿协调器约定时,需要提供一个业务层的唯一id,比如可能是订单id或其他唯一标示, 也可以选择用协调器自己生成的唯一id,作为本次事务的id。

    2. 外部的事务协调器服务,这个服务本身无状态,所有跟补偿相关的状态,过程,步骤,都存在db内,它会定时检查db,依照约定条件,当触发某条事务,它的跨服务最终一致性没有完成时,协调器会根据之前约定的数据,约定的方式,如果需要confirm callback则先回调producer询问是否确认本地事务已完成,然后按约定方式发起重试,约定的重试方式有通过消息队列给下游transaction executor服务发通知,或是直接调用下游服务的RPC接口。这取决于producer在begin这条事务的时候选择的是哪个模式,比如A服务它本来就是通过RPC的方式调用B服务,那么它可能选择的重试机制也是RPC模式,那么协调器就会使用约定的数据在重试的时候调用B服务的相应RPC接口。

    3. 第3部分依然是封装好的sdk(又要多个语言,再次苦逼),  sdk的封装内,提供接口,让tx executor方实现接口,或是调用现成的方法。 加入在producer中约定的是rpc调用,则实现rpc类型的executorImpl, 消息队列的则实现kafkaImpl,sdk封装好对幂等的检查,这里需要分场景,对于一些数据一致性非常敏感的业务,应当由业务再次检查幂等性,但带来了额外的业务开发。 否则使用协调器sdk已有的方法,sdk会先利用producer提供的事务id,完成幂等检查,如果发现是重复的调用或消息则忽略直接返回,否则进入正常流程处理,tx executor作为被调用方完成本地业务逻辑后,提交业务数据,然后再提交事务协调db的数据,完成本次过程。 这里同样涉及跨db的本地事务问题,处理方式和producer阶段一样。

    整个过程,与RocketMQ真有点像,应该说是一个弱化后的解决方案。另外这个事务补偿协调的过程可以扩展成支持多个步骤:A -> B -> C这样的调用过程。  方案的首要目标是首先完成逻辑正确性,然后是尽全力减少对应用层代码的入侵,毕竟一个东西需要推广就需要考虑成本。

    完…

    ===============================================================================

    a memory pool implement use golang

    #memory pool #golang

    用golang实现内存池

    用golang做一个预分配的内存池,代码可以很小量做到。 实现细节,主要是一个链表,用来保存内存块.
    定义2个值:
    num 每次需要增长内存池的时候,预分配的内存块数量
    size 预分配时,单个内存块的大小

    一共两个类型:
    Pool 内存池对象,管理grow动作, Get, Put等动作
    Buffer 一个链表

    初始化时:

    type Pool struct {
    	lock sync.Mutex
    	free *Buffer
    	max  int
    	num  int
    	size int
    }
    
    // 初始化内存池的时候,调用grow做一次内存预分配
    func (p *Pool) init(num, size int) {
    	p.num = num
    	p.size = size
    	p.max = num * size
    	p.grow()
    }
    
    
    func (p *Pool) grow() {
    	var (
    		i   int
    		b   *Buffer
    		bs  []Buffer
    		buf []byte
    	)
    	buf = make([]byte, p.max)  // 创建一个num * size大小的连续内存
    	bs = make([]Buffer, p.num) // 创建链表节点
    	p.free = &bs[0]
    	b = p.free
    	for i = 1; i < p.num; i++ {  // 完成链表节点的首尾相连,引用前面的大内存块中的逐个内存块
    		b.buf = buf[(i-1)*p.size : i*p.size]
    		b.next = &bs[i]
    		b = b.next
    	}
    	b.buf = buf[(i-1)*p.size : i*p.size]
    	b.next = nil  // 这是一个单向链表
    	return
    }
    
    

    链表对象结构是:

    type Buffer struct {
    	buf  []byte
    	next *Buffer // to next node
    }
    

    然后就是两个对外的接口, Get 和 Put

    // 获取一个内存块
    func (p *Pool) Get() (b *Buffer) {
    	p.lock.Lock()
    	if b = p.free; b == nil {
    		p.grow()
    		b = p.free
    	}
    	p.free = b.next
    	p.lock.Unlock()
    	return
    }
    
    // 释放一个内存块
    func (p *Pool) Put(b *Buffer) {
    	p.lock.Lock()
    	b.next = p.free
    	p.free = b
    	p.lock.Unlock()
    	return
    }
    

    size的大小就是内存块的大小, 因此应根据实际使用场景变化, 比如用在tcp socket 的write buffer上的时候, 那么size就是你希望能buff住的write buffer大小。

    ===============================================================================