Etcd分布式锁实现原理raft算法
Etcd工作原理1.在etcd系统里创建一个key
2.如果创建失败,key存在,则监听key的变化事件,直到该key被删除,回到1
3.如果创建成功,则认为获得了锁
Redis持久化存储方式http server接受请求并转发给store进行处理,如果涉及节点修改,则交给raft进行状态变更、日志记录,然后同步给其他节点以确认提交,最后提交数据,并再次同步。其中etcd使用wal来进行持久化存储。
Redis队列rdb:(默认)定时快照存储。
aof:即时存储。
Memcached和Redis比较类型:list
新增:lpush
查询:blpop
Leveldbmemcached:数据结构单一,只能缓存数据不能持久化,适用多读少写。
redis:数据结构丰富,两个持久化方案,并可以数据恢复。
Mysql索引基于本地文件存储,数据存量是物理内存的3-5倍,将一部分分热区数据.log保存在内存,持久化数据.sst保存在磁盘上。
SQL优化和索引优化采用b+tree,b+tree采用二分查找法。
Mysql InnoDB和MyIsam存储引擎区别sql优化:
避免索引失效,全表扫描
适当使用in和exists(in先执行子查询,exists先执行外层,所以in适合外表大而内表小的情况,exitst适合外表小而内表大的情况)
避免返回不必要的列(*)和数据(用limit)
尽量避免子查询,使用join 用小表驱动大表,减少Nested Loop的循环提升性能。
尽量用inner join,少用left join,inner join 会自动选择小表去驱动大表,left join 一般使用场景是大表驱动小表。join 连接最好不超过2个 索引优化:
避免索引失效:
索引列上做操作,计算、函数、类型转换
!= 或<>,会导致后面索引失效
null
like 通配符前缀%,会导致后面索引失效 字符串不加单引号
Go Go语言优势innoDb:
支持外键
聚集索引,数据文件和索引绑在一起,必须要有主键,通过主键索引效率很高
不保存表的具体行数,不保存表的具体行数,执行select count(8) from table时需要全表扫描
不支持全文索引
支持事务
myIsam:
不支持外键
非聚集索引,数据文件和索引是分离的,索引保存数据文件的指针,
保存表的具体行数,用一个变量保存了整个表的行数,执行select count(*) from table语句时只需要读出该变量即可,速度很快
支持全文索引,查询效率高
不支持事务
Go为什么需要指针底层原生的多核并发技术
内存泄漏场景因为函数参数都是值传递,如果对实参进行修改,值类型只是复制了一份值,对参数没有影响,指针就可以,而且指针指向的是一个地址,内存占用小,其实go里面不存在引用类型,比如map,slice,interface底层都是指针类型。
由于逻辑问题导致资源一直无法被释放,增加阻塞
1.Goroutine
func mainI(){
ch := make(chan int)
go func(){
ch <- 1
}()
//<-ch
time.Sleep(time.Second)
}
解释:对于非缓冲管道,必须有接收者才能将数据放入管道,否则就会阻塞,从而导致内存泄漏。
2.Http
http请求resp.Body,需要defer resp.Body.Close()除了加Mutex锁以外还有哪些方式安全读写共享变量
Channel有无缓冲的区别可以通过 Channel 进行安全读写共享变量。
CSP并发模型无缓冲:发送和接收需要同步
有缓冲:不要求发送和接收同步,缓冲满时发送阻塞
Goroutine以通信的方式来共享内存,是通过Goroutine和Channel来实现的。
常用的并发模型goroutine是golang实际并发的实体,底层使用coroutine(协程)实现并发,coroutine是一种运行在用户态的用户线程,底层使用coroutine,是因为:
用户空间,避免了内核态和用户态的切换导致的成本 可以由语言和框架层进行调度
更小的栈空间允许创建大量的实例
特性:GMP
G:Goroutine M:线程 P:CPU
运行:
CPU开启一个线程执行Goroutine。单核情况,所有goroutine运行在同一个线程中,每个线程维护一个上下文,一个上下文只有一个goroutine,其他goroutine在runqueue中等待。多核情况,碰到goroutine阻塞,会再开启一个线程执行goroutine,以充分利用cpu资源。
Channel为什么是线程安全的channle:无缓冲,同步。
sync.WaitGroup:为了防止main函数结束结束goroutine,waitgroup会等待所有goroutine完成。
contex:上下文。包括程序的运行环境、现场和快照,主要处理多个goroutine之间共享数据及多个goroutine的管理
nil slice和空slice原子性,底层实现互斥锁来保证线程安全,出队入队都加了锁。
进程、线程、协程nil slice:未初始化的slice,不能赋值
空slice:初始化的slice
协程如何调度的进程: 系统进行资源分配和调度的基本单位,拥有独立的内存空间,不同进程通过进程间通信来通信,上下文切换开销(栈、寄存器、虚拟内存、文件句柄)大,相对比较安全。
线程:轻量级进程,不拥有系统资源,共享进程全部资源,线程间通信主要通过共享内存,上下文切换开销小,相比进程容易丢失数据。
协程:一种用户态的轻量级线程,由go运行时(runtime)管理,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。
从单进程到多进程提高了 CPU利用率;从进程到线程,降低了上下文切换的开销;从线程到协程,进一步降低了上下文切换的开销,使得高并发的服务可以使用简单的代码写出来
(进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。 协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度)
Buffer和Cache区别协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。进程和线程的操作是由程序触发系统接口,最后执行者是系统,协程的操作执行者则是用户自身程序。
垃圾回收GCBuffer:用于存放将要输出到disk(块设备)的数据,进行流量整形,把突发的大数量较小规模的IO整理成平稳的小数量较大规模的IO,以减少响应次数。
Cache:用于存放从disk上读出的数据,为了弥补高速设备和低速设备的鸿沟而引入的中间层,最终起到加速访问速度的作用。
两者都是为提高IO性能而设计的。
常用的分布式锁垃圾回收:指内存中不再使用的内存区域,自动发现和释放这种内存区域的过程就是垃圾回收。
三色标记原理:
初始所有对象白色。
从根(root)出发(全局指针和goroutine栈上的指针)扫描所有可达对象,标记为灰色,放入待处理队列。
从队列取出灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。
重复 3,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。
原理:
WB(write barrier:写屏障,go1.7Dijkstra写屏障,只监控堆上指针数据的变动(增量式垃圾回收时,新增的指针对象会被初始为白色,需要标记为黑色)re-scan。go1.8混合写屏障,只监控堆上指针数据的变动,无需re-scan)-STW(stop the world:暂停所有正在执行的用户线程/协程)-mark(三色标记,标记协程是并行的)-WB(关闭写屏障)-STW(挂起stw协程)-sweep(清除,唤醒清扫垃圾协程,该协程在应用启动后创建,创建后立即进入睡眠,被唤醒后把白色对象挨个清理掉,清扫协程和应用协程是并发进行的,清扫完成后再次进入睡眠状态)
回收方式:
非增量式:垃圾回收需要STW,在STW期间完成所有垃圾对象的标记,STW结束后慢慢的执行垃圾对象的处理。
增量式:垃圾回收需要STW,在STW期间完成部分垃圾对象的标记,然后结束STW继续执行用户线程,一段时间后再次执行STW再标记部分垃圾对象,这个过程会多次重复执行,直到所有垃圾对象标记完成。
GC触发条件:
辅助GC:在分配内存时,会判断当前的Heap内存分配量是否达到了触发一轮GC的阈值(每轮GC完成后,该阈值会被动态设置),如果超过阈值,则启动一轮GC。
调用runtime.GC()强制启动一轮GC。
sysmon是运行时的守护进程,当超过 forcegcperiod (2分钟)没有运行GC会启动一轮GC。
切片数据结构数据库、缓存(redis、memcached、tair)、zookeeper
切片和数组区别指向底层数组的指针、长度、容量
Slice扩容策略:数组:固定长度
切片:底层是指向数组的指针,动态
new和make区别首先判断,如果新申请容量大于2倍的旧容量,最终容量就是新申请的容量 否则判断,如果旧切片的长度小于1024,则最终容量就是旧容量的两倍
否则判断,如果旧切片长度大于等于1024,则最终容量从旧容量开始循环增加原来的 1/4, 直到最终容量大于等于新申请的容量
如果最终容量计算值溢出,则最终容量就是新申请容量扩容前后的Slice是否相同?
情况一:
原数组还有容量可以扩容(实际容量没有填充完),这种情况下,扩容以后的数组还是指向原来的数组,对一个切片的操作可能影响多个指针指向相同地址的Slice。
情况二:
原来数组的容量已经达到了最大值,再想扩容, Go 默认会先开一片内存区域,把原来的值拷贝过来,然后再执行 append()
操作。这种情况丝毫不影响原数组。 要复制一个Slice,最好使用Copy函数。
map底层实现new:new对象,分配零值内存,返回指针。
make:make slice、map、channel,分配非零值内存(并初始化成员结构),返回对象。
map扩容底层实现是一个散列表,主要有两结构体,hmap(a header for a go map)和bmap(a bucket for a go map,bucket),bucket中存储key和value。哈希值按照用途一分为二:低位和高位,低位用于寻找当前key属于hmap中哪个bucket,高位用于寻找bmap中的key对应的value。
defer当Go的map长度增长到大于加载因子所需的map长度时,Go语言就会将产生一个新的bucket数组,然后把旧的bucket数组移到一个属性字段oldbucket中。
注意:并不是立刻把旧的数组中的元素转到新的bucket当中,而是,只有当访问到具体的某个bucket的时候,会把bucket中的数据转移到新的bucket中。
go语言会将defer后面的语句进行延迟处理。编译时将defer后的语句追加到goroutine_defer链表最前面,运行goroutine_defer时从前到后依次执行。return后执行defer
用途:
关闭文件句柄
锁资源释放
数据库连接释放
经典例子:
func test(){
i := 0
defer fmt.Println("defer:",i)
i ++
return i
}
输出:defer:0 1
func test(){
i := 0
defer func(){
fmt.Println("defer:",i)
}()
i ++
return i
}
输出:defer:1 1
func returnValues()int {
var result int
defer func() {
result ++
fmt.Println("defer:",result)
}()
return result
}
输出:defer:1 0
func reReturnValues() (result int) {
defer func() {
result ++
fmt.Println("defer:",result)
}()
return result
}
输出:defer:1 1
sync.Once和init
反射init:文件包首次被加载的时候执行,且只执行一次
sync.Once:需要的时候执行,且只执行一次
捕获异常反射机制是指在程序运行时动态地捕获甚至改变变量的类型信息和值,但是在编译时并不知道这些变量的具体类型。
使用场景:
1.有时你需要编写一个函数,但是并不知道传给你的参数类型是什么,可能是没约定好;也可能是传入的类型很多,这些类型并不能统一表示。这时反射就会用的上了。
2.有时候需要根据某些条件决定调用哪个函数,比如根据用户的输入来决定。这时就需要对函数和函数的参数进行反射,在运行期间动态地执行函数。
IDE代码自动补全、json序列化、fmt函数、DeepEqual等缺点:
1.与反射相关的代码,经常是难以阅读的。在软件工程中,代码可读性也是一个非常重要的指标。
2.Go 语言作为一门静态语言,编码过程中,编译器能提前发现一些类型错误,但是对于反射代码是无能为力的。所以包含反射相关的代码,很可能会运行很久,才会出错,这时候经常是直接panic,可能会造成严重的后果。
3.反射对性能影响还是比较大的,比正常代码运行速度慢一到两个数量级。所以,对于一个项目中处于运行效率关键位置的代码,尽量避免使用反射特性。
interface: 存储实体的类型信息
reflect: TypeOf()函数返回一个接口,这个接口定义了一系列方法,利用这些方法可以获取关于类型的所有信息; ValueOf()函数返回一个结构体变量,包含类型信息以及实际值。
func f(){
defer func(){
if r:=recover();r!=nil{
fmt.Println("recovered in f:",r)
}
}()
//异常
g(0)
}
逃逸分析注:defer recover机制只针对当前函数以及直接调用的函数可能产生的panic,无法处理调用产生的其他协程的panic,这一点和try catch机制不一样。
分布式和微服务的区别编译时分析变量是否被外部引用,被外部引用分配在堆上,否则分配在栈上。
为什么要有堆栈:堆上动态内存分配比栈上静态内存分配,开销大很多。
堆栈回收:堆上一般手动回收,一般语言有自动垃圾回收机制, 栈上由操作系统自动(分配)回收
RabbitMQ和ActiveMQ微服务:将一个系统分别多个业务模块
分布式:将一个系统分为多个业务模块,业务模块分别部署在不同的机器上,各个业务模块之间通过接口进行数据交互。
处理高并发作用: 异步、解耦、销峰
ActiveMQ:
基于JMS(Java Message Service),java语言,
提供P2P和Pub/Sub(发布/订阅)两种消息模型
提供多种消息类型(TextMessage、MapMessage、BytesMessage、StreamMessage、ObjectMessage)
跨平台性差
RabbitMQ:
基于AMQP,erlang语言(并发强)
提供direct exchange、fanout exchange、topic exchange、headers exchange、system exchange 五种消息模型
只有byte[]消息类型
AMQP具有跨平台、跨语言特性
系统拆分:将一个系统拆分为多个子系统,dubbo。
缓存:读多写少情况。
MQ(消息队列):多写情况。
分库分表:一个数据库拆分为多个库,一个表拆分为多个表,每个表的数据量保持少一点,提高sql跑的性能。
读写分离:读多写少情况。主从架构,主库写入,从库读取。 SolrCloud(solr云):是Solr提供的分布式搜索方案,可以解决海量数据的分布式全文检索,因为搭建了集群,因此具备高可用的特性,同时对数据进行主从备份,避免了单点故障问题。可以做到数据的快速恢复。并且可以动态的添加新的节点,再对数据进行平衡,可以做到负载均衡。
持续更新…



