八股文

作者: Supreme_DJK | 来源:发表于2022-02-23 23:44 被阅读0次

[TOC]

map

map 的底层实现原理是什么 - 码农桃花源 (gitbook.io)

底层数据结构

// A header for a Go map.
type hmap struct {
    // 元素个数,调用 len(map) 时,直接返回此值
    count     int
    flags     uint8
    // buckets 的对数 log_2
    B         uint8
    // overflow 的 bucket 近似数
    noverflow uint16
    // 计算 key 的哈希的时候会传入哈希函数
    hash0     uint32
    // 指向 buckets 数组,大小为 2^B
    // 如果元素个数为0,就为 nil
    buckets    unsafe.Pointer
    // 扩容的时候,buckets 长度会是 oldbuckets 的两倍
    oldbuckets unsafe.Pointer
    // 指示扩容进度,小于此地址的 buckets 迁移完成
    nevacuate  uintptr
    extra *mapextra // optional fields
}
  • B:buckets数组的长度的对数,也就是说 buckets 数组的长度就是 2^B。bucket 里面存储了 key 和 value

  • buckets是一个指针,最终指向bmap这个数据结构

    type bmap struct {
        tophash [bucketCnt]uint8
    }
    
    // 但上述只是表面(src/runtime/hashmap.go)的结构,编译期间会给它加料,动态地创建一个新的结构:
    type bmap struct {
        topbits  [8]uint8
        keys     [8]keytype
        values   [8]valuetype
        pad      uintptr
        overflow uintptr
    }
    
  • bmap:即常说的桶。

    1. 桶里面最多装8个key,会根据 key 计算出来的 hash 值的高 8 位来决定 key 到底落入桶内的哪个位置
    2. 如若有第九个key进来,会再创建一个bucket,通过overflow指针连接
    3. bmap的内存模型,是key/key/.../value/value/...,这样可以减少额外的内存对齐所需要的空间
  • map的创建:

    1. map创建出来是一个指针,因此在作为函数参数传入时,内部的改动也会影响到map自身
    2. slice是一个结构体,因此作为函数参数传入时,不会影响到数据自身,但是由于数据是指针,因此可以改变指向的数据
  • map的遍历:是随机的

    go中range遍历map,不是固定的从bucket0开始遍历,每次会随机一个bucket开始遍历,并且bucket内也是会随机一个cell遍历

map扩容

为避免大量key落在一个桶中,退化成链表,导致查询效率变为O(n),装载因子被提出,用来衡量该情况。

loadFactor := count / (2^B)

count : map中元素个数

B:2^B表示buckets数目

  • 触发扩容的时机:在向map中插入元素时,符合下面两个条件,会触发扩容

    1. 装载因子超出阈值6.5(元素塞满了bucket)

    2. overflow的bucket过多:(元素没有塞满bucket了,bucket冗余空间过多)

      当 B 小于 15,也就是 bucket 总数 2^B 小于 2^15 时,如果 overflow 的 bucket 数量超过 2^B;当 B >= 15,也就是 bucket 总数 2^B 大于等于 2^15,如果 overflow 的 bucket 数量超过 2^15

  • 扩容的逻辑:“渐进式”扩容,原有的 key 并不会一次性搬迁完毕,每次最多只会搬迁 2 个 bucket。

    1. 分配新的buckets
    2. 搬迁到新的buckets,发生在插入、修改和删除时,会先检查oldbuckets是否为nil(nil则搬迁完成,不需要再搬迁了)
  • 扩容后的容量:针对扩容触发的条件1和2,有两种策略

    1. buckets数目翻倍:

      要重新计算 key 的哈希,才能决定它到底落在哪个 bucket。

    2. buckets数目相等:

      从老的 buckets 搬迁到新的 buckets,由于 bucktes 数量不变,因此可以按序号来搬,比如原来在 0 号 bucktes,到新的地方后,仍然放在 0 号 buckets。

context

context作用

  1. 在 一组 goroutine 之间传递共享的值
  2. 取消goroutine
  3. 防止goroutine泄露
  1. 不要将 Context 塞到结构体里。直接将 Context 类型作为函数的第一参数,而且一般都命名为 ctx。
  2. 不要向函数传入一个 nil 的 context,如果你实在不知道传什么,标准库给你准备好了一个 context:todo。
  3. 不要把本应该作为函数参数的类型塞到 context 中,context 存储的应该是一些共同的数据。例如:登陆的 session、cookie 等。
  4. 同一个 context 可能会被传递到多个 goroutine,别担心,context 是并发安全的。
  • context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。

  • 随着 context 包的引入,context 几乎成为了并发控制和超时控制的标准做法。

context.Value

type valueCtx struct {
    Context
    key, val interface{}
}
  1. key要求是可比较的

  2. 属于一个树结构

    <img src="C:\Users\Supreme\AppData\Roaming\Typora\typora-user-images\image-20220220230609169.png" alt="image-20220220230609169" style="zoom:50%;" />

  3. 取值过程,是会向上查找的

  4. 允许存在相同的key,向上查找会找到最后一个key相等的节点,即层数高的节点

Goroutine

  1. M必须拥有P才可执行G中的代码,P含有一个包含多个G的队列,P可以调度G交由M执行
  2. 所有可执行go routine都放在队列中:
    • 全局队列(GRQ):存储全局可运行的goroutine(从系统调用中恢复的G);
    • 本地可运行队列(LRQ):存储本地(分配到P的)可运行的goroutine
  3. workingschedule:各个P中维护的G队列很可能是不均衡的;空闲的P会查询全局队列,若全局队列也空,则会从其他P中窃取G(一般每次取一半)。

goroutine和线程的区别

  1. 内存占用:goroutine默认栈为2KB,线程至少需要1MB
  2. 创建和销毁:goroutine由go runtime管理,属于用户级别的,消耗小;线程是操作系统创建的,是内核级别的,消耗巨大
  3. 切换:goroutine切换只需要保存三个寄存器,线程切换需要寄存器

M:N模型(M个线程,N个goroutine)

  1. go runtime负责管理goroutine,Runtime会在程序启动的时候,创建M个线程(CPU执行调度的单位),之后创建的N个goroutine都会依附在这M个线程上执行。
  2. 同一时刻,一个线程只能跑一个goroutine,当goroutine发生阻塞时,runtime会把它调度走,让其他goroutine来执行,不让线程闲着

系统调用

同步调用

G1即将进入系统调用时,M1将释放P,让某个空闲的M2获取P并继续执行P队列中剩余的G(即M2接替M1的工作);M2可能来源于M的缓存池,也可能是新建的。当G1系统调用完成后,根据M1能否获取到P,将对G1做不同的处理:

  • 有空闲的P,则获取一个以继续执行G1;
  • 无空闲P,将G1放入全局队列,等待被其他P调度;M1进入缓冲池睡眠

异步调用

  1. M 不会被阻塞,G 的异步请求会被“代理人” network poller 接手,G 也会被绑定到 network poller
  2. 等到系统调用结束,G 才会重新回到 P 上。
  3. M 由于没被阻塞,它因此可以继续执行 LRQ 里的其他 G。

Redis:

slot

在redis集群内部,采用slot槽位的逻辑管理方式, 集群内部共有16384(2的14次方)个Slot,集群内每个Redis Instance负责其中一部分的Slot的读写。一个Key到底属于哪个Slot,由分片算法:

crc16(key) % 16384

决定。也正是通过此分片算法,将不同的key以相对均匀的方式分配到不同的slot上。

watch:当执行多键值事务操作时,Redis不仅要求这些键值需要落在同一个Redis实例上,还要求落在同一个slot上。

官方介绍MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令,事务可以一次执行多个命令,但是必须满足2个条件:

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。执行和是否成功是2个概念,并不是一个失败报错等,其他就失败。redis对事务是部分支持。如果最开始语法等就有提交错误,就相当于java的编译器都过不了,那么肯定全部不执行。如果在执行过程中报错,已经全部执行了,但是谁报错找谁,其他正常执行放行。各取所需!这里的事务并不是要么全部成功,要么全部失败,全部执行和全部成功(或者都失败)是2个概念。

hashtag

Hash Tag原理是:当一个key包含 {} 的时候,不对整个key做hash,而仅对 {}包括的字符串做hash。

Hash Tag可以让不同的key拥有相同的hash值,从而分配在同一个槽里;这样针对不同key的批量操作(mget/mset等),以及事务、Lua脚本等都可以支持。不过Hash Tag可能会带来数据分配不均的问题,这时需要:(1)调整不同节点中槽的数量,使数据分布尽量均匀;(2)避免对热点数据使用Hash Tag,导致请求分布不均。

bigkey

  • 涉及到bigkey的操作,网卡会成为瓶颈
  • 若需要删除bigkey,直接del,被操作的实例可能会直接卡死
  • 业务上对bigkey取余,将数据分散,避免生成bigkey

高可用

主从复制

  1. 一台redis服务的数据,复制到多台redis服务器。前者称为主节点,后者为从节点
  2. 数据的复制是单向的,只能从主节点复制到从节点
  • 作用:

    1. 数据冗余:实现了数据的热备份,是持久化之外的数据冗余方式
    2. 故障恢复:主节点失效,丛节点提供服务
    3. 负载均衡:实现读写分离,主节点写,丛节点读
    4. 高可用的基础:主从复制是哨兵和集群模式能够实施的基础
  • 数据同步:

    1. 主从节点连接建立后,便开始数据同步。
    2. 根据主从节点当前状态,分为全量和部分复制
    3. 具体执行方式:从节点朝主节点发送psync命令,开始同步
  • 命令传播:

    主从数据同步完成后,主节点将自己执行的写命令发送给丛节点(该过程是异步的),保证数据的一致性。

哨兵

  1. 能够自动完成故障发现和转移,从而实现高可用
  2. 由一组哨兵节点和一组(或多组)主从复制节点组成
  • 心跳机制
  • 故障转移
    1. 每个 Sentinel 都会定时进行心跳检查,当发现主节点出现心跳检测超时的情况时,此时认为该主节点已经不可用,这种判定称为主观下线
    2. 哨兵节点开始投票,当超过半数认为该主节点故障,会将其下线:基于raft算法,选取一个哨兵节点来执行该过程
      • 选取一个从节点作为主节点,将其他从节点和该节点绑定
      • 原来的主节点更新为从节点,对其监控,等恢复后,命令其去复制新的主节点

cluster集群

  1. 由多个主从复制的结构组成
  2. 每个主从复制的结构看做一个节点

持久化

RDB

优势:

  1. RDB 是一个非常紧凑(compact)的二进制文件,体积小,因此在传输速度上比较快,因此适合灾难恢复。
  2. 数据恢复速度比aof快

劣势:

  1. rdb出现故障丢的数据会比aof多。你通常会每隔5分钟或者更久做一次完整的保存,万一在 Redis 意外宕机,你可能会丢失几分钟的数据。
  2. rdb需要fork子进程来保存数据到硬盘,当数据集比较大时, fork比较耗时,从而导致redis主线程在一些毫秒级别内无法响应客户端

AOF

优势:

  1. 数据更完整,秒级丢失(无 fsync、每秒 fsync 、每次写的时候 fsync )

  2. 兼容性高,是基于redis通讯协议而形成的命令追加方式,无论何种版本的redis都兼容,

  3. aof文件是明文的,可阅读性较好。

劣势:

  1. 数据文件大,即使有重写机制(合并命令、删减无用命令),但是同样量级还是比rdb占用大
  2. 数据恢复慢
  3. aof更吃性能(需要频繁同步命令,虽然会先写到内存中,再同步到磁盘里)

混合持久化

混合持久化结合了RDB持久化 和 AOF 持久化的优点, 由于绝大部分都是RDB格式,加载速度快,同时结合AOF,增量的数据以AOF方式保存了,数据更少的丢失。

相关文章

  • 试作新八股文

    看了几十篇八股文,试作九篇八股文,不知合乎八股文的标准否?兼以阐发经义。 (一) 学而时习之,不亦悦乎(《论语·学...

  • 古人的高考

    说起古人考试,C位话题一定是“八股文”。今人提起八股文,张嘴就骂它“禁锢思想”“腐朽空洞”,但现今时代距离八股文太...

  • 被误读的八股文

    突然对八股文起了兴趣,科普了一番,才发现,我们都误读了八股文。 八股文的起源可追溯至唐朝的“帖括”,宋时,为了避免...

  • 面试知识梳理--mysql

    mysql八股文推荐链接:mysql八股文推荐链接[https://mp.weixin.qq.com/s/I0qB...

  • 悄无声息

    为什么我要写那些可恶的八股文。

  • Java 面试八股文之数据库篇(三)

    前言 这是系列文章【 Java 面试八股文】数据库篇的第三期。 【 Java 面试八股文】系列会陆续更新 Java...

  • Java 面试八股文之数据库篇(二)

    前言 这是系列文章【 Java 面试八股文】数据库篇的第二期。 【 Java 面试八股文】系列会陆续更新 Java...

  • 文学与自由

    原创: 寻虎纯文学 人生而自由,却无时不在罗网之中。 我们一边厌恶八股文,一边写着八股文。上学时候被迫按照作文套路...

  • 写作基础9文学与自由

    人生而自由,却无时不在罗网之中。我们一边厌恶八股文,一边写着八股文。 上学时候被迫按照作文套路来,等到自由了,可以...

  • 新知

    上个星期的《南方周末》,我现在才看。 有篇文章,讲的是八股文。 对八股文,我们一般都没有好印象,觉得这是科举时代折...

网友评论

      本文标题:八股文

      本文链接:https://www.haomeiwen.com/subject/cvnalrtx.html