-----日志
1、日志格式不一样怎么收集
2、日志量过大会堆积吗?怎么处理?
3、日志落盘到机器,怎么采集
4、go里成熟的日志框架
-----redis
Redis:从应用到底层,一文帮你搞定 - 腾讯云开发者社区-腾讯云
教程学习:key-value存储系统,跨平台的非关系型数据库
可基于内存、分布式、可选持久性的键值对存储数据库,
数据结构服务器,value可以是string、hash、list、sets、sorted sets
string类型的值最大能存储512MB
特点:
-
数据持久化,内存数据保存在磁盘,重启可以再次加载
-
不仅支持简单的key-value类型的数据,还提供list、set、zset、hash等数据结构的存储
-
Redis支持数据的备份,即master-slave模式的数据备份
优势:
-
性能高 110000/s 写的速度是810000次/s
-
丰富的数据类型
-
所有操作都是原子性的,多个操作支持事务
-
丰富的特性,redis还支持pub/sub,通知。key过期等特性
应用场景:
-
缓存系统,减轻主数据的压力
-
技术场景
-
热门排行榜
-
LIST实现队列功能
-
HyperLogLog统计UV、PV数据
-
geospatial index 进行地理位置相关查询
1、redis发布订阅
pub/sub是一种消息通信模式;发送者发送消息,订阅者接受消息
Redis客户端可以订阅任意数量的频道
(打开redis-cli客户端 SUBSCRIBE runoobChat / PUBLISH runoobChat "Redis PUBLISH test")
6、redis高可用
通过设计减少系统不能提供服务的时间,分布式系统的基础也是保障系统可靠性的重要手段。
-
数据持久化(redis和memcached的主要区别,只有redis提供)
-
主从数据同步
-
Redis哨兵模式(见识Redis主从服务器,当Redis的主从服务器发生故障,Redis哨兵提供自动容灾修复功能)(每秒1次的频率,向已知的主服务器和从服务器,发送ping命令,最后一次有效回复ping命令的时间,超过配置的最大下线时间,默认30s,则被哨兵表级为下线)(主服务器被标记为下线,所有监视主服务器的哨兵,1次/秒的频率确认主服务器是否进入主观下线的状态,quorum个证实后进入客观下线状态)
-
Redis集群()
7、redis主从复制机制
Redis多机运行中最基础的功能,多个redis节点组成一个Redis集群,在这个集群中1个主节点进行数据的操作,其它从节点同步主节点的内容,提供数据查询。
写操作发送到主节点,度操作发送到从节点,减轻运行压力;升主机制
8、Redis分布式锁的实现方式
秒杀下单、抢红包等场景,需要分布式锁。
Redis适合作为分布式锁使用;
多机器部署时,相同请求并发访问时的资源竞争问题。
请求到达每个tomcat时,先去redis中注册锁,注册成功返回true说明获得了锁,
可以继续处理相关的业务。处理完成后释放锁,同一时刻只能有1个tomcat获得锁。
对于分布式场景,我们可以使用分布式锁,它是控制分布式系统之间互斥访问共享资源的一种方式
获得锁时,2个操作,存储key-value,设置过期时间,防止死锁。2个操作做非原子,可能第二部失败,出现死锁。后提供了过期时间。
9、redis的setnx底层怎么实现(使用第三方工具Redisson
SETNX是set if not exists的简写,指令SETNX key value
SETNX key seconds value 将值value关联到key,并将key的生存时间设置为多少秒。
setnx是一个atomic操作
用一个表示唯一性的字符串一如uuid配合SETNX实现加锁
解锁:
用LUA脚本 支持原子性操作;中间不被其他请求插入
(get key value\判断value是否属于当前线程\删除锁)
基于setnx的特性,可以实现最简单的分布式锁;
解决问题:
value设置成唯一值,每个线程的value都不一样,判断value是否是自己线程的,是则删除;否则不删除
重入锁的效果,一个线程获取到锁后,在没有释放这把锁之前,当前线程自己也无法再获得这把锁,影响系统的性能。
------go
1、go协程进程线程区别
进程:一个程序一次动态执行的过程;操作系统进行资源分配和调度的独立单位。
进程互相独立,同一个进程下的各个线程之间
线程:程序执行中一个单一的顺序控制流程,程序执行流的最小单元。处理器调度和分派的基本单位。线程共享程序的内存空间;线程是一个进程中代码的不同执行路线;
(时间片轮转调度)
协程:线程切换由操作西永调度,协程用户自己调度,减少上下文切换;内存默认线程为1MB,协程为2KB;在同一个线程上,避免竞争关系用锁;被阻塞且大量并发的场景,更好使用线程
线程和进程都是抽象的概念,线程是一种比进程更小的抽象;
-
内存占用
-
创建和销毁
thread创建和销毁都有大量消耗,因为与操作系统打交道,是内核级的。
用线程池解决,而goroutine是go runtime负责管理,创建和销魂的消耗非常小,是用户级。
-
切换
线程切换1000-1500 纳秒需要保存各种寄存器,goroutines切换 200 ns只需要保存3个寄存器
2、gmp模型
再来看 M,取 machine 的首字母,它代表一个工作线程,或者说系统线程。G 需要调度到 M 上才能运行,M 是真正工作的人。结构体 m 就是我们常说的 M,它保存了 M 自身使用的栈信息、当前正在 M 上执行的 G 信息、与之绑定的 P 信息……
当 M 没有工作可做的时候,在它休眠前,会“自旋”地来找工作:检查全局队列,查看 network poller,试图执行 gc 任务,或者“偷”工作。
P,取 processor 的首字母,为 M 的执行提供“上下文”,保存 M 执行 G 时的一些资源,例如本地可运行 G 队列,memeory cache 等
一个 M 只有绑定 P 才能执行 goroutine,当 M 被阻塞时,整个 P 会被传递给其他 M ,或者说整个 P 被接管。
抢占式调度:
3、gc算法
三色标记法,标记清扫的垃圾回收。(并发标记清除)
反复标记可达对象,最终把白色不可达对象回收。
4、gc原理,STW的时机 runtime.GC
STW时万物精致,在垃圾回收回收过程中为保证实现的正确性、防止无止境的内存增长等问题,需要停止赋值器进一步操作对象图。
进入STW时,需要通知让所有的用户态代码停止。
-
清扫终止阶段,为下一个阶段并发标记做准备工作,启动写屏障(STW)
-
扫描标记,与赋值器并发执行,写屏障(并发)
-
标记终止,保证一个周期内标记任务完成(STW)
-
内存清扫,归还给堆(并发)
-
内存归还(并发)
5、内存泄漏
-
预期能被快速释放的内存因被跟对象引用而没有得到迅速释放(函数对象不小心附着全局对象)
-
goroutine泄漏,内存不会被释放,因此程序持续产生新的goroutine
6、内存逃逸
通俗的说,当一个对象的指针被多个方法和线程引用时,称这个指针发生了逃逸。
编译器执行静态代码分析后,对内存管理进行的优化和简化。
go:当函数返回变量的引用,发生逃逸。go的变量只有在编译器可以证明在函数返回后不会再被引用才分配到栈上,其它情况都是分配到堆上。
-
如函数外部没有引用,优先放到栈中
-
如函数外部存在引用,必定放到堆中
如果变量都分配到堆上,堆不像栈可以自动清理。会引起go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销。(cpu容量的25%)
有些函数的参数为interface类型,如fmt.Println(a ...interface{})编译期间很难确定确定具体类型,也会发生逃逸。
7、channel的底层实现原理
8、map的底层实现
go采用哈希查找表,使用链表解决哈希冲突;使用buckets存储key和value
bmap就是常说的桶,最多装8个key
8、make和new的区别
9、channel关闭后再send会发生什么,关闭已经关闭的channel
读写nil的channel会阻塞
读关闭的channel可以读到零值
关闭nil和重复关闭 channel以及写closeed的channel会panic
10、值类型和引用类型
6、select的用法
7、init函数执行时间
-
如果一个包导入了其它包,首先初始化导入的包
-
初始化当前包的常量
-
初始化当前包的变量
-
调用当前包的init()函数
8、多个init函数执行顺序能保证吗
作用:初始化
同一个包的init不确定
其它包按照导入的依赖关系决定执行顺序
9、return和defer的顺序
9、gin框架的路由怎么处理
gin是轻量级的go语言web框架;
10、微服务框架
go-micro、go-zero、go-kit
服务内部高内聚,服务之间松耦合
go-micro:微服务的插件式RPC框架,用于服务发现、客户端负载均衡、编码、同步和异步通信库。(提供将HTTP请求路由到对应微服务的API网关;
组件架构
-
Registry注册中心(提供可插入的服务发现库,查找正在运行的服务)
-
Selector负载均衡(负载均衡机制,客户端向服务端发出请求,西安查询注册中心,返回运行服务的节点列表,选择器选择)[循环法、随机哈希、黑名单]
-
Broker事件驱动(发布和订阅的可插入接口)(事件确定架构、nats、rabbitmq、http)
-
Transport消息传输(通过点对点传输消息的可插拔接口)
nats(订阅发布系统):[译]Golang 微服务教程(五) - 掘金
微服务完成任务,通知消息系统x已完成,不关心那些微服务监听;解耦。
10、怎么实现并发
11、channel的类型,区别
可以定义方向、数据类型、容量
不通过共享内存通信,而是通过通信实现信息传递。依赖CSP模型,基于channel实现。
当容量为0时,说明channel没有缓存,只有sender和receiver都准备好后才会发送通讯。
12、Context使用来干什么的,应用场景
12、内存管理
Golang-Internal-Notes/Go 内存管理.md at master · LeoYang90/Golang-Internal-Notes · GitHub
Go的内存分配器采用了跟tcmalloc库相同的实现,是一个带内存池的分配器,底层直接调用操作系统的mmap等函数。
Go是一个支持goroutine这种多线程的语言,所以它的内存管理系统必须也要考虑在多线程下的稳定 性和效率问题。在多线程方面,很自然的做法就是每条线程都有自己的本地的内存,然后有一个全局的分配链,当某个线程中内存不足后就向全局分配链中申请内存。这样就避免了多线程同时访问共享变量时的加锁。
13、实现goroutine pool
strikefreedom.top/archives/hi…
启动服务之时先初始化一个 Goroutine Pool 池,这个 Pool 维护了一个类似栈的 LIFO 队列 ,里面存放负责处理任务的 Worker,然后在 client 端提交 task 到 Pool 中之后,在 Pool 内部,接收 task 之后的核心操作是:
检查当前 Worker 队列中是否有可用的 Worker,如果有,取出执行当前的 task; 没有可用的 Worker,判断当前在运行的 Worker 是否已超过该 Pool 的容量:{是 —> 再判断工作池是否为非阻塞模式:[是 ——> 直接返回 nil,否 ——> 阻塞等待直至有 Worker 被放回 Pool],否 —> 新开一个 Worker(goroutine)处理}; 每个 Worker 执行完任务之后,放回 Pool 的队列中等待。
goroutine太多会导致调度性能下降,GC频繁、内存暴涨
type Task struct {
// 任务名
Name string
// 任务回调函数
Handler func(v ...interface{})
// 任务回调函数参数(如果有)
Params []interface{}
}
type State int32
const (
StateRunning State = iota
StateStopped
)
type TaskPool struct {
// 最大容量
capacity int32
// 协程池状态
state State
// 运行中的任务个数
runningTasks int32
// 任务管道
taskChannel chan *Task
}
func NewTaskPool(capacity int32) (*TaskPool, error) {
if capacity <= 0 {
return nil, errors.New("capacity less than 0")
}
return &TaskPool{
capacity: capacity,
state: StateRunning,
taskChannel: make(chan *Task, capacity),
}, nil
}
func (tp *TaskPool) run() {
// 运行中的数量+1
atomic.AddInt32(&tp.runningTasks, 1)
go func() {
defer func() {
// 运行中的任务-1
atomic.AddInt32(&tp.runningTasks, -1)
// 错误收集(暂时只打印)
if err := recover(); err != nil {
log.Printf("Worker error: %sn", err)
}
}()
for {
select {
case task, ok := <-tp.taskChannel:
if ok {
log.Printf("Task[%s] start execution...n", task.Name)
task.Handler(task.Params...)
}
}
}
}()
}
func (tp *TaskPool) AddTask(task *Task) error {
if tp.state == StateStopped {
return errors.New("task pool is closed")
}
runningTasks := atomic.LoadInt32(&tp.runningTasks)
capacity := atomic.LoadInt32(&tp.capacity)
// 如果当前运行的任务小于协程池最大限制,则通知消费者开始消费
if runningTasks < capacity {
tp.run() // 消费者启动
}
// 生产者将 task 放入管道
tp.taskChannel <- task
return nil
}
func (tp *TaskPool) Close() {
tp.state = StateStopped
for { // 一直阻塞,直到协程池中的所有 Task 被消费完毕,再关闭管道
if len(tp.taskChannel) <= 0 {
close(tp.taskChannel)
return
}
}
}
11
------mysql
1、mysql索引结构
数据库索引存储在磁盘上,当数据库量大时,不会把整个索引全部加载到内存,只能逐一加载每一个磁盘块,索引树越低,越矮胖,磁盘IO次数就少。
-
B+树索引->b+树节点大小一般设置为何磁盘快大小一致,通过一次磁盘I/O吧磁盘块的数据全部存储下来。
-
哈希索引
2、为什么采用B+树存储索引结构,B+和B数的区别
B+树,所有数据记录节点都是按照键值大小顺序存放在同一层的,非叶子节点只存储key值信息,可以大大增加每个节点存储的key值数量,降低B+树的高度
a.非叶子节点不存储data,只存储索引
b.叶子节点不存储指针
c.顺序访问指针,提高区间访问性能
d.非叶子节点中的索引最终还是会在叶子节点上存储一份,也就是叶子节点包含非叶子节点上的所有索引
e.一个父节点,左侧子节点都小于父节点的值,右侧子节点大于等于父节点的值
f.每一层节点从左往右都是递增排列,无论数值型还是字符型
->范围查询
3、聚集索引的存储
参考上面的主键索引和辅助索引
-
聚集索引也叫主键索引,叶子节点中的data存储的是该主键对应郑航数据,通常B+数高度为3;mysql会把B+树根节点放在内存中,根据主键索引查数据,只需要2次IO
-
非聚集索引
索引覆盖时,只需要遍历辅助索引的这一个B+数;
不能索引覆盖时,先2次IO找到主键值,再用主键再主键索引树找数据
-
联合索引
B+树种存索引时(多个索引)
(基于B+树的结构,决定必须要他要求有主键,没有的情况下也会生成默认的列作为主键列)(辅助索引结构叶子节点存储主键值,指向主键的节点,不用再存储一份相同的数据)(索引覆盖,可以遍历非聚集索引的树从key中获取)
4、mysql有哪些存储引擎,你用到什么存储引擎,区别是什么
-
InnoDB
InnoDB的聚集索引:
主键索引
叶子节点中,索引关键字和数据实在一起存放的
辅助索引
叶子节点存放,索引关键字和对应的主键
?先根据关键字找到对应的主键,再去主键索引树上找到对应的行记录数据
-
myIsam
索引文件和数据文件是分开的,存储引擎在磁盘中的文件三个
.frm 数据表定义、.MYI索引、.MYD 实际数据
myIsam存储引擎,无论主键索引还是非朱建索引,叶子节点中的data存储的都是磁盘指针
5、事务的ACID原则
(事务是由一个或一组SQL组成的,组成一个事务的SQL一般都是一个业务操作)
-
A/Atomicity: 原子性 (一组事务要么全部成功要么全部失败)(基于undo-log实现,在该日志中会生成相应的反SQL)
-
C/Consistency: 一致性 (数据变化都是一致的,宕机后通过分析日志的方式恢复数据)
-
I/Isolation: 隔离性 (多个事务之间独立,并发时,保证事务的顺序执行;基于MYSQL的锁机制和MVCC机制)
-
D/Durability: 持久性(事务一旦提交,会保持永久性)
6、事务的隔离机制 show variables (工作线程处理事务内的操作)
4个级别,越靠后并发控制越高,可靠性越强,性能越差,默认为可重复读
脏读:读到其它事务未提交的数据
幻读:事务处理数据时,其它操作插入处理的数据类型
不可重复读:同一事务中,多次读取,可能数据不同(其它事务在处理)
-
Read uncommitted: 读未提交(脏读、不可重复读、幻读都可能发生)
写互斥锁、读操作没有;
-
Read committed 读已提交(解决脏读;不可重复读和幻读可能发生)
-
Repeatable read 可重复读(解决脏读、不可重复读; 幻读可能发生)
-
Serializable 序列化(解决脏读、不可重复读、幻读;)
7、大规模的数据表中增加字段
-----容器化
1、容器是什么
就是1个进程包含依赖项
2、与虚拟化比,容器化的优势
容器提供实时配置和可伸缩性,但虚拟机;
3、
-----设计模式
1、单例模式
创建型设计模式,保证一个类只有一个实例,提供一个访问该实例的全局节点
(与全局变量相同的优缺点,尽管有用,破坏代码的模块性特性)
------计算机网络
1、输入一个url后发生了什么
第一步 浏览器查找该域名的 IP 地址 第二步 浏览器根据解析得到的IP地址向 web 服务器发送一个 HTTP 请求 第三步 服务器收到请求并进行处理 第四步 服务器返回一个响应 第五步 浏览器对该响应进行解码,渲染显示。 第六步 页面显示完成后,浏览器发送异步请求。 第七步 整个过程结束之后,浏览器关闭TCP连接。
2、两台机器之间怎么通信
底层协议
3、TCP/IP协议中,三次握手,四次回收,time_wait
三次握手,建立TCP连接。
-
第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
-
第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
-
第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
-
第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
-
第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我"同意"你的关闭请求;
-
第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
-
第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
4、protobuf
网络通信和通用数据交换等应用场景经常使用JSON和XML
微服务架构通常用另一个数据交换的协议工具protobuf
(协议缓冲区)json和xml基于文本格式 protobuf是二进制格式;更小更快更简单
5、RPC服务间通信
远程过程调用,一个节点请求另一个节点提供的服务
RPC依赖客户端和服务端建立socket链接,而http rest实现通讯的代价高
-
客户端告诉服务端需要调用的函数,函数和进程id存在映射,客户端远程调用需要查一下函数,找到对应的ID执行函数的代码
-
无法直接传递函数参数,转换为字节流,传给服务端,服务端将字节流转换成自身能读取的格式,是序列化和反序列化的过程
-
数据准备好后,网络传输层把调用的ID和序列化后的参数传给服务端,把计算好的结果序列化传给客户端。TCP层完成,grpc采用http2协议。
HTTP和RPC的区别:
-
http请求是使用具有标准语义的通用的接口定向到资源的;rpc的机制是根据语言的API(language API)来定义的
-
RPC:可以基于TCP协议,也可以基于HTTP协议
-
使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2协议,也可以很好的减少报文的体积,提高传输效率
-
rpc二进制传输 HTTP:大部分是通过json
-
RPC:基本都自带了负载均衡策略;http通过nginx实现
-
RPC:能做到自动通知
-
-----算法
1、快速排序
2、dfs
3、动态规划
4、红黑树、二叉树
goroutine顺序->waitGroup