杨 发布的文章

先上源码:

// growslice handles slice growth during append.
// It is passed the slice element type, the old slice, and the desired new minimum capacity,
// and it returns a new slice with at least that capacity, with the old data
// copied into it.
// The new slice's length is set to the old slice's length,
// NOT to the new requested capacity.
// This is for codegen convenience. The old slice's length is used immediately
// to calculate where to write new values during an append.
// TODO: When the old backend is gone, reconsider this decision.
// The SSA backend might prefer the new length or to return only ptr/cap and save stack space.
翻译过来:
Growslice处理附加过程中的切片增长。  
//传递slice元素类型、旧slice和所需的新最小容量,  
//它返回一个至少有相同容量的新片,包含旧的数据  
//复制到它里面。  
//新切片的长度设置为旧切片的长度,  
//没有到新的请求容量。  
//这是为了方便代码生成。 旧切片的长度立即被使用  
//计算在追加过程中写入新值的位置。  
// TODO:当旧的后端消失时,重新考虑这个决定。  
// SSA后端可能更喜欢新的长度,或者只返回ptr/cap,以节省堆栈空间。

func growslice(et *_type, old slice, cap int) slice {
    if raceenabled {
        callerpc := getcallerpc()
        racereadrangepc(old.array, uintptr(old.len*int(et.size)), callerpc, funcPC(growslice))
    }
    if msanenabled {
        msanread(old.array, uintptr(old.len*int(et.size)))
    }

    if cap < old.cap {
        panic(errorString("growslice: cap out of range"))
    }

    if et.size == 0 {
        // append should not create a slice with nil pointer but non-zero len.
        // We assume that append doesn't need to preserve old.array in this case.
        return slice{unsafe.Pointer(&zerobase), old.len, cap}
    }

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.cap < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
//检查0 < newcap检测溢出  
//和防止无限循环。 
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
//将newcap设置为请求的上限
//新上限计算溢出。
            if newcap <= 0 {
                newcap = cap
            }
        }
    }

    var overflow bool
    var lenmem, newlenmem, capmem uintptr
    // Specialize for common values of et.size.
    // For 1 we don't need any division/multiplication.
    // For sys.PtrSize, compiler will optimize division/multiplication into a shift by a constant.
    // For powers of 2, use a variable shift.
//特化et.size的公共值。  
//对于1,我们不需要任何除法/乘法。  
//对于sys。 PtrSize,编译器会将除法/乘法优化为一个常数的移位。  
//对于2的幂,使用变量shift。 
    switch {
    case et.size == 1:
        lenmem = uintptr(old.len)
        newlenmem = uintptr(cap)
        capmem = roundupsize(uintptr(newcap))
        overflow = uintptr(newcap) > maxAlloc
        newcap = int(capmem)
    case et.size == sys.PtrSize:
        lenmem = uintptr(old.len) * sys.PtrSize
        newlenmem = uintptr(cap) * sys.PtrSize
        capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
        overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
        newcap = int(capmem / sys.PtrSize)
    case isPowerOfTwo(et.size):
        var shift uintptr
        if sys.PtrSize == 8 {
            // Mask shift for better code generation.
            shift = uintptr(sys.Ctz64(uint64(et.size))) & 63
        } else {
            shift = uintptr(sys.Ctz32(uint32(et.size))) & 31
        }
        lenmem = uintptr(old.len) << shift
        newlenmem = uintptr(cap) << shift
        capmem = roundupsize(uintptr(newcap) << shift)
        overflow = uintptr(newcap) > (maxAlloc >> shift)
        newcap = int(capmem >> shift)
    default:
        lenmem = uintptr(old.len) * et.size
        newlenmem = uintptr(cap) * et.size
        capmem, overflow = math.MulUintptr(et.size, uintptr(newcap))
        capmem = roundupsize(capmem)
        newcap = int(capmem / et.size)
    }

    // The check of overflow in addition to capmem > maxAlloc is needed
    // to prevent an overflow which can be used to trigger a segfault
    // on 32bit architectures with this example program:
    //
    // type T [1<<27 + 1]int64
    //
    // var d T
    // var s []T
    //
    // func main() {
    //   s = append(s, d, d, d, d)
    //   print(len(s), "\n")
    // }
    if overflow || capmem > maxAlloc {
        panic(errorString("growslice: cap out of range"))
    }

    var p unsafe.Pointer
    if et.ptrdata == 0 {
        p = mallocgc(capmem, nil, false)
        // The append() that calls growslice is going to overwrite from old.len to cap (which will be the new length).
        // Only clear the part that will not be overwritten.
        memclrNoHeapPointers(add(p, newlenmem), capmem-newlenmem)
    } else {
        // Note: can't use rawmem (which avoids zeroing of memory), because then GC can scan uninitialized memory.
        p = mallocgc(capmem, et, true)
        if lenmem > 0 && writeBarrier.enabled {
            // Only shade the pointers in old.array since we know the destination slice p
            // only contains nil pointers because it has been cleared during alloc.
            bulkBarrierPreWriteSrcOnly(uintptr(p), uintptr(old.array), lenmem-et.size+et.ptrdata)
        }
    }
    memmove(p, old.array, lenmem)

    return slice{p, old.len, newcap}
}


<!--more-->

扩容策略看其中这几行代码:

    newcap := old.cap
    doublecap := newcap + newcap
    if cap > doublecap {
        newcap = cap
    } else {
        if old.cap < 1024 {
            newcap = doublecap
        } else {
            // Check 0 < newcap to detect overflow
            // and prevent an infinite loop.
            for 0 < newcap && newcap < cap {
                newcap += newcap / 4
            }
            // Set newcap to the requested cap when
            // the newcap calculation overflowed.
            if newcap <= 0 {
                newcap = cap
            }
        }
    }
  • 若新cap大于原cap的2倍,则新cap就是所需的cap。
  • 若新cap小于等于原cap的2倍:

    • 若原cap小于1024个字节,新cap为2倍的原cap
    • 若原cap大于等于1024字节,新cap为原cap的1.25倍;若溢出了,则为原cap的2倍。

- 阅读剩余部分 -

概念:

  • 内存地址空间:就是能索引到内存单元的地址合集。
  • 链接器:将应用程序与地址绑定。
  • 地址总线:CPU用来访问内存的方式
  • 地址译码器:CPU与内存地址的映射,可以是设备寄存器,也可以是内存单元。
  • 物理地址空间:就是地址总线位数所表示的数据范围。
  • 虚拟地址空间:除了CPU地址总线,操作系统都是使用虚拟地址。
  • MMU(内存管理单元):转换机构,将虚拟地址转为物理地址,CPU才能访问。

内存地址

内存地址空间

内存地址产生方式

  • 链接器对程序重定位后执行地址绑定,静态地址。
  • 代码中直接定义地址。
  • 动态分配内存,返回内存空间的首地址。

innoDB通过mvcc(多版本并发控制)来提高并发能力,通过版本快照,保证大部分读操作都不用加锁,性能很好。缺点是缺点是没行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。

- 阅读剩余部分 -

Redis 使用 RESP(REdis Serialization Protocol)协议定义了客户端和服务器端交互的命令、数据的编码格式。在 Redis 2.0 版本中,RESP 协议正式成为客户端和服务器端的标准通信协议。从 Redis 2.0 到 Redis 5.0,RESP 协议都称为 RESP 2 协议,从 Redis 6.0 开始,Redis 就采用 RESP 3 协议了。不过,6.0 版本是在今年 5 月刚推出的,所以,目前我们广泛使用的还是 RESP 2 协议。

- 阅读剩余部分 -

主从第一次同步

当我们启动多个 Redis 实例的时候,它们相互之间就可以通过 replicaof(Redis 5.0 之前使用 slaveof)命令形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步。

例如,现在有实例 1(ip:172.16.19.3)和实例 2(ip:172.16.19.5),我们在实例 2 上执行以下这个命令后,实例 2 就变成了实例 1 的从库,并从实例 1 上复制数据:


replicaof  172.16.19.3  6379

接下来,我们就要学习主从库间数据第一次同步的三个阶段了。你可以先看一下下面这张图,有个整体感知,接下来我再具体介绍。

主从第一次同步流程.png