Go
make和new区别
1 2 3
| new: 分配内存清零并返回指针, 如果编译器发现 new 出来的内存在函数结束后就没有使用 且申请内存空间不是很大,那么 new 申请的内存空间还是会被分配在栈 make: 用于slice,map,和channel的初始化并返回对象
|
内存逃逸
- 如果函数外部没有引用,则优先放到栈中
- 如果函数外部存在引用,则必定放到堆中
1 2 3 4 5 6
| 指针逃逸: - 函数返回指针 - interface{} 动态类型逃逸 - 栈空间不足 - 闭包 - 在切片上存储指针或带指针的值的时候, 对应的变量会逃逸
|
range 的 Bug
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| v := []int{1,2,3,4,5} for _, v2 := range v { defer func () { fmt.Printf("v2: %v\n", v2) }() } // v1.22.0 版本以前会输出5,5,5,5,5 // v1.22.0 版本以后输出5,4,3,2,1
v := []int{1,2,3,4,5} for _, v2 := range v { fmt.Printf("v2: %v\n", v2) } // v1.22.0 版本以前会输出5,5,5,5,5 // v1.22.0 版本以后输出1,2,3,4,5
|
defer关键字
1 2 3 4
| 栈顺序先进后出 return 之后的语句先执行,defer 后的语句后执行 defer 最大的功能是 panic 后依然有效 defer 出现 panic 会覆盖掉前一个 panic 继续执行下一个 defer
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| func test() int { i := 9 defer func() { i++ }() return i }
func test() (i int) { i = 9 defer func() { i++ }() return i }
|
数组和切片
1 2
| 数组: 长度固定, 数组作为函数参数时,函数操作的是数组的一个副本,不会影响原始数组 切片: 长度可变, 当切片作为函数参数时,函数操作的是切片的引用,会影响原始切片
|
1
| 切片扩容: append后len大于cap会触发扩容, cap小于1024翻倍,超过1024后每次扩容1.25倍
|
Map
1
| 主要为bmap,每个bmap最多装8个key,当超过8个key会创建一个溢出桶指向新的bmap
|
1
| 扩容: 元素个数大于bmap*6.5 或者 溢出桶的数量过多
|
Sync Map
1 2 3 4 5 6 7 8 9 10
| 主要是空间换时间的概念,通过read和dirty两个map来实现 read读操作不加锁,读取不到数据后会对read加锁再读一次,然后再去dirty读取 1.当read miss次数过多会将原本read删除然后dirty提升为read 2.使用内置range函数当read和dirty不一致时也会触发dirty提升机制 3.删除元素read有直接删除,没有则去dirty执行删除 4.新增修改 - 在read中查找key,找到了则通过原子操作,尝试更新value - key在read中存在,但是被标记为已删除,则kv加入dirty中,并更新value值 - key在read中不存在,但在dirty中存在,则直接在dirty中更新value - key在read和dirty中都不存在,则直接在dirty中加入kv
|
Channel
1
| 主要由一个循环链表加上读写下标, 加上两个等待队列(双向链表)
|
GMP
线程由 CPU 调度是抢占式的,协程由用户态调度是协作式的,一个协程让出 CPU 后,才执行下一个协程
因为它是发生在操作系统的用户态的,不需要进入内核态进行系统调用,操作系统的上下文切换会带来很大的开销,切goroutine和线程一样,共享堆,不共享栈。
1 2
| 线程: 由1个用户态和1个内核态组成, 内存占用高, 线程调度消耗大 协程: 通过调度器将N个用户态和M个内核态组成, 占用内存更小(几kb可扩容), 调度更灵活
|
G
: Goroutine,它携带上下文运行的信息,是需要允许的任务
M
: Machine,即一个真正的系统线程
P
: Processor处理器,负责把Goroutine调度到M上
1 2 3 4
| 1.P在程序开始的时候就会创建,根据参数GOMAXPROCS(默认为cpu核数) 2.每次新建一个G时,都会尝试去唤醒其它的M,我们称它为M2,M2同样也会找一个P2去依附,但此时,P2本地没有可执行的G,那它这时候的策略就是去全局队列里面去偷n个G. 3.如果全局队列里面再没有G的话, 就去其他P的本地队列里面去偷一半的数量过来,这就是work-stealing机制。 4.如果其他P本地队列里面还是没有G的话,系统线程M就会进入自旋状态而不是销毁,因为我们希望我当有新的G创建时,能立刻有M运行它。
|
GC
1 2 3 4 5
| 白色对象 - 潜在的垃圾,表示还未搜索到的对象,其内存可能会被垃圾收集器回收
黑色对象 - 活跃的对象,表示搜索完成的对象,包括不存在任何引用外部指针的对象以及从根对象可达的对象
灰色对象 - 活跃的对象,表示正在搜索还未搜索完的对象,因为存在指向白色对象的外部指针,垃圾收集器会扫描这些对象的子对象
|
1 2 3 4 5
| 1.初始时所有对象都是白色的 2.从gc root对象出发,扫描所有可达对象标记为灰色,放入待处理队列 3.从队列取出一个灰色对象并标记为黑色,将其引用对象标记为灰色,放入队列 4.重复上一步骤,直到灰色对象队列为空 5.此时剩下的所有白色对象都是垃圾对象
|
强三色不等式
: 黑色不能直接指向白色
弱三色不等式
: 黑色可以指向白色,但是需要白色间接被灰色指向
删除屏障
: B对象失去A对象的引用时,如果B对象是个白色对象,那么它会变成灰色对象,这一点是为了满足弱三色不变式
插入屏障
: 实现强三色不变式,保证当一个黑色对象指向一个白色对象前,会先触发屏障将白色对象置为灰色
三色标级+混合屏障
: gc开始时所有栈标记为黑色, 以满足弱三色不等式
Gin
动态路由
通过字典树实现
中间件原理
http请求来到时先经过中间件,主要由一个函数切片通过index下标访问
Redis
基本数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| string - 底层: 动态字符串,最大为512M - 应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。 list - 底层: 双向链表 - 应用场景:消息队列 hash - 底层: 哈希表 - 应用场景:缓存对象、购物车等 set - 底层: 哈希表 - 应用场景:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等 zset - 底层: 跳表 - 应用场景:排序场景,比如排行榜、电话和姓名排序等。
|
为什么快
1 2 3
| 1.操作都是在内存中操作,再加上Redis自身的数据结构优化 2.采用单线程防止了多线程之间的竞争,避免线程切换带来的时间开销 3.采用IO多路复用即select/epoll机制,实现一个线程来处理多个IO
|
持久化
1 2 3 4
| AOF日志: 命令追加方式写入文件, 性能差,体积大,恢复速度慢,保证数据完整性
RDB快照: 某一时刻内存数据的快照保存, 保存频率高影响性能,频率低数据丢失 混合持久化: 集成了 AOF 和 RBD, 前半部分为RDB格式的全量数据,后半部分为AOF的增量数据
|
集群
1 2 3 4 5 6 7 8 9 10
| 1.主从复制 主服务器负责读写,从服务器负责只读 由于数据同步是异步的所以存在数据不一致的问题
2.哨兵模式 在主从的基础上增加了一个哨兵节点, 哨兵持续与服务器心跳交互 通过投票算法: 以配置文件的优先级 复制偏移量 runid大小进行判断
3.切片集群模式 类似bitmap的方式进行存储分配
|
过期删除与内存淘汰
1 2
| 惰性删除策略: key过期不做操作,当对key进行查询才会判断过期并删除返回null 定期删除策略: 每隔一段时间抽取一定量的key检查是否过期,如果过期率大于25%重复开头操作
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 1、不进行数据淘汰的策略(默认策略) 当运行内存超过最大设置内存时,不淘汰任何数据直接返回错误
2、进行数据淘汰的策略
在设置了过期时间的数据中进行淘汰: volatile-random:随机淘汰设置了过期时间的任意键值; volatile-ttl:优先淘汰更早过期的键值。 volatile-lru(Redis3.0 之前,默认的内存淘汰策略):淘汰所有设置了过期时间的键值中,最久未使用的键值; volatile-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,最少使用的键值;
在所有数据范围内进行淘汰: allkeys-random:随机淘汰任意键值; allkeys-lru:淘汰整个键值中最久未使用的键值; allkeys-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。
|
缓存雪崩、击穿、穿透
1 2 3 4 5 6 7 8
| 缓存雪崩: 大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机 - 解决方案: 互斥锁, 均匀设置过期时间 缓存击穿: 某个热点数据过期 - 解决方案: 互斥锁, 热点数据不设置过期时间
缓存穿透: 大量请求既不在缓存中,也不在数据库中的数据 - 解决方案: 缓存空值或者默认值, 布隆过滤器(类似bitmap)
|
Mysql
索引
1 2 3 4 5 6 7 8 9 10 11 12
| B+树: - 主键索引(聚簇索引): 根节点按顺序存放索引, 叶子节点双向链表并存放数据 - 二级索引: 根节点按顺序存放索引, 叶子节点双向链表只存放索引和主键 - 联合索引(复合索引): 最左匹配原则 - 根节点按最左侧字段顺序存放多个索引, 叶子节点双向链表并存放多个索引和主键 - 联合索引的最左匹配原则,在遇到范围查询(如 >、<)的时候,就会停止匹配 - 也就是范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引。 - 注意,对于 >=、<=、BETWEEN、like 前缀匹配的范围查询,并不会停止匹配
|
什么时候需要 / 不需要创建索引
索引最大的好处是提高查询速度,但是索引也是有缺点的,比如:
1 2 3
| 1.需要占用物理空间,数量越大,占用空间越大; 2.创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增大; 3.会降低表的增删改的效率,因为每次增删改索引,B+ 树为了维护索引有序性,都需要进行动态维护。
|
需要索引
1 2 3
| 1.字段有唯一性限制的,比如商品编码 2.经常经常where条件查询的字段 3.经常用于group by和order by的字段
|
不需要索引
1 2 3 4
| 1.极少作为查询条件的字段 2.大量重复数据 3.数据少 4.经常维护修改的数据
|
索引失效
1 2 3 4
| 1.左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效; 2.查询条件中对索引列做了计算、函数、类型转换操作,这些情况下都会造成索引失效; 3.使用联合索引没有按照最左匹配原则会导致失效 4.where语句中or存在没有添加索引的字段。
|
事务
1 2 3 4 5 6 7 8 9 10 11 12 13
| 读未提交: 指一个事务还没提交时,它做的变更就能被其他事务看到
读已提交: 指一个事务提交之后,它做的变更才能被其他事务看到 - 解决脏读: 一个事务还没提交的修改数据被读取到
可重复读(默认): 指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的 - 解决不可重复读: 一个事务第一次读取到的数据,在第二次读取之前被另一个事务提交修改 导致两次读取的数据不一样
串行化: 会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行 - 解决幻读: 一个事务第一次查询结果5条数据,在第二次读取之前另一个事务添加并提交了一条新数据 导致第二次查询结果为6条数据
|
网络
http,https和http2.0
http1.1
: 新增tcp长连接, 增加缓存处理, 断点续传
http2.0
: header压缩, 多个request共用一个连接(多路复用), 二进制格式传输, 服务器推送
https
: ca证书加密,端口修改