1.针对macOS盗版UltraEdit恶意软件的源码深度技术剖析
2.golang chan 最详细原理剖析,全面源码分析!剖析看完不可能不懂的源码!
3.golang本地缓存(bigcache/freecache/fastcache等)选型对比及原理总结
4.Golang源码剖析panic与recover,剖析看不懂你打我好了
5.39套实战教程KubernetesK8s CKA认证实战(完整版)BAT大厂基于K8s构建企业容器云平台
6.golang面试题库?
针对macOS盗版UltraEdit恶意软件的源码深度技术剖析
在近期,MalwareHunterTeam的剖析qtrpg游戏源码专家揭露了一起涉及盗版macOS程序的恶意软件事件。该程序文件名是源码“ultraedit.dmg”,内含一个名为“libConfigurer.dylib”的剖析恶意库。最初发现时间在年。源码本文将深入分析此次事件,剖析从磁盘镜像文件入手,源码进而探讨恶意动态库的剖析详细内容。
macOS用户熟悉UltraEdit,源码这是剖析一款功能强大的文本与十六进制编辑器,支持大型文件编辑。源码然而,盗版版的UltraEdit应用程序隐藏着风险。在VirusTotal平台,该样本被数十家反病毒软件标记,但标记名称并不特定,如“Trojan.MAC.Generic”或“Trojan-Downloader.OSX.Agent”,这使得识别恶意软件类型变得困难。
通过加载磁盘镜像,我们发现该盗版程序被挂载至/Volumes/UltraEdit .0.0.,且无任何签名信息,而正版UltraEdit应用程序则包含合法签名。在盗版软件中寻找恶意组件,通常较为困难,特别是对于大型应用而言。然而,我们成功识别了一个名为“libConfigurer.dylib”的恶意组件。
该组件为无符号位(Intel)dylib库,也被VirusTotal平台标记。它被添加为依赖库,意味着当用户启动盗版UltraEdit时,此库会自动加载。通过反汇编工具,我们发现其中包含了一个名为initialize的构造器,执行此构造器后,会下载并执行名为download.ultraedit.info的代码。
下载的代码文件被转储为/tmp/.test和/Users/Shared/.fseventsd,文件内容通过解码后,似乎与已知的恶意软件Khepri相关联,这是一款基于Golang和C++开发的开源跨平台代理+后渗透工具。同时,libConfigurer.dylib从download.ultraedit.info下载的文件也包含了一些混淆的Mach-O源码。
进一步分析显示,.test文件似乎是恶意软件的一部分,它使用/usr/local/bin/ssh执行,这可能用于远程控制或数据传输。而.fseventsd文件则通过文件监控器实现持久化,每次用户登录时自动启动或重启。
总结此次事件,盗版UltraEdit应用程序不仅引入了恶意动态库,还通过网络下载和执行了额外的恶意代码。恶意软件组件通过混淆和持久化技术,增加了检测和移除的难度。在分析过程中,我们使用了多种工具,包括VirusTotal、otool、nm、反汇编工具等,以及第三方安全资源和开源工具,以获得更全面的理解和证据。
面对此类恶意软件,用户应避免下载和使用盗版软件,以保护系统安全。同时,持续监控和更新防病毒软件也是防范恶意软件的重要措施。尽管本文中详细介绍了此次事件的分析过程,但请注意,恶意软件的变种和更新不断,因此保持警惕和采用多层安全防护策略是应对恶意软件的关键。最终,确保系统的安全性,避免遭受潜在的威胁。
golang chan 最详细原理剖析,全面源码分析!看完不可能不懂的!
大纲
概述
chan 是 golang 的核心结构,是与其他高级语言区别的显著特色之一,也是 goroutine 通信的关键要素。尽管广泛使用,但对其深入理解的人却不多。本文将从源码编译器的视角,全面剖析 channel 的用法。
channel 的本质
从实现角度来看,golang 的 channel 实质上是环形队列(ringbuffer)的实现。我们将 chan 称为管理结构,源码设计考试channel 中可以放置任何类型的对象,称为元素。
channel 的使用方法
我们从 channel 的使用方式入手,详细介绍 channel 的使用方法。
channel 的创建
创建 channel 时,用户通常有两种选择:创建带有缓冲区和不带缓冲区的 channel。这对应于 runtime/chan.go 文件中的 makechan 函数。
channel 入队
用户使用姿势:对应函数实现为 chansend,位于 runtime/chan.go 文件。
channel 出队
用户使用姿势:对应函数分别是 chanrecv1 和 chanrecv2,位于 runtime/chan.go 文件。
结合 select 语句
用户使用姿势:对应函数实现为 selectnbsend,位于 runtime/chan.go 文件中。
结合 for-range 语句
用户使用姿势:对应使用函数 chanrecv2,位于 runtime/chan.go 文件中。
源码解析
以上,我们通过宏观的用户使用姿势,了解了不同使用姿势对应的不同实现函数,接下来将详细分析这些函数的实现。
makechan 函数
负责 channel 的创建。在 go 程序中,当我们写类似 v := make(chan int) 的初始化语句时,就会调用不同类型对应的初始化函数,其中 channel 的初始化函数就是 makechen。
runtime.makechan
定义原型:
通过这个,我们可以了解到,声明创建一个 channel 实际上是得到了一个 hchan 的指针,因此 channel 的核心结构就是基于 hchan 实现的。
其中,t 参数指定元素类型,size 指定 channel 缓冲区槽位数量。如果是带缓冲区的 channel,那么 size 就是槽位数;如果没有指定,那么就是 0。
makechan 函数执行了以下两件事:
1. 参数校验:主要是越界或 limit 的校验。
2. 初始化 hchan:分为三种情况:
所以,我们看到除了 hchan 结构体本身的内存分配,该结构体初始化的关键在于四个字段:
hchan 结构
makechan 函数负责创建了 chan 的核心结构-hchan,接下来我们将详细分析 hchan 结构体本身。
在 makechan 中,初始化时实际上只初始化了四个核心字段:
我们使用 channel 时知道,channel 常常会因为两种情况而阻塞:1)投递时没有空间;2)取出时还没有元素。
从以上描述来看,就涉及到 goroutine 阻塞和 goroutine 唤醒,这个功能与 recvq,sendq 这两个字段有关。
waitq 类型实际上是一个双向列表的实现,与 linux 中的 LIST 实现非常相似。
chansend 函数
chansend 函数是在编译器解析到 c <- x 这样的代码时插入的,本质上就是把一个用户元素投递到 hchan 的 ringbuffer 中。chansend 调用时,一般用户会遇到两种情况:
接下来,我们看看 chansend 究竟做了什么。
当我们在 golang 中执行 c <- x 这样的代码,意图将一个元素投递到 channel 时,实际上调用的是 chansend 函数。这个函数分几个场景来处理,总结来说:
关于返回值:chansend 返回值标明元素是否成功入队,成功则返回 true,否则 false。
select 的提前揭秘:
golang 源代码经过编译会变成类似如下:
而 selectnbasend 只是一个代理:
小结:没错,chansend 功能就是这么简单,本质上就是一句话:将元素投递到 channel 中。
chanrecv 函数
对应的 golang 语句是 <- c。该函数实现了 channel 的元素出队功能。举个例子,编译对应一般如下:
golang 语句:
对应:
golang 语句(这次的区别在于是否有返回值):
对应:
编译器在遇到 <- c 和 v, ok := <- c 的语句时,会换成对应的 chanrecv1,chanrecv2 函数,这两个函数本质上都是一个简单的封装,元素出队的实现函数是 chanrecv,我们详细分析这个函数。
chanrecv 函数的返回值有两个值,selected,received,其中 selected 一般作为 select 结合的函数返回值,指明是否要进入 select-case 的代码分支,received 表明是否从队列中成功获取到元素,有几种情况:
selectnbsend 函数
该函数是 c <- v 结合到 select 时的函数,我们使用 select 的 case 里面如果是一个 chan 的表达式,那么编译器会转换成对应的 selectnbsend 函数,如下:
对应编译函数逻辑如下:
selectnbsend 本质上也就是个 chansend 的封装:
chansend 的内部逻辑上面已经详细说明过,唯一不同的就是 block 参数被赋值为 false,也就是说,在 ringbuffer 没有空间的情况下也不会阻塞,直接返回。划重点:chan 在这里不会切走执行权限。
selectnbrecv 函数
该函数是 v := <- c 结合到 select 时的函数,我们使用 select 的源码音乐试听 case 里面如果是一个 chan 的表达式,那么编译器会转换成对应的 selectnbsrecv 函数,如下:
对应编译函数逻辑如下:
selectnbrecv 本质上也就是个 chanrecv 的封装:
chanrecv 的内部逻辑上面已经详细说明过,在 ringbuffer 没有元素的情况下也不会阻塞,直接返回。这里不会因此而切走调度权限。
selectnbrecv2 函数
该函数是 v, ok = <- c 结合到 select 时的函数,我们使用 select 的 case 里面如果是一个 chan 的表达式,那么编译器会转换成对应的 selectnbrecv2 函数,如下:
对应编译函数逻辑如下:
selectnbrecv2 本质上是个 chanrecv 的封装,只不过返回值不一样而已:
chanrecv 的内部逻辑上面已经详细说明过,在 ringbuffer 没有元素的情况下也不会阻塞,直接返回。这里不会因此而切走调度权限。selectnbrecv2 与 selectnbrecv 函数的不同之处在于还有一个 ok 参数指明是否获取到了元素。
chanrecv2 函数
chan 可以与 for-range 结合使用,编译器会识别这种语法。如下:
这个本质上是个 for 循环,我们知道 for 循环关键是拆分成三个部分:初始化、条件判断、条件递进。
那么在我们 for-range 和 chan 结合起来之后,这三个关键因素又是怎么理解的呢?简述如下:
init 初始化:无
condition 条件判断:
increment 条件递进:无
当编译器遇到上面 chan 结合 for-range 写法时,会转换成 chanrecv2 的函数调用。目的是从 channel 中出队元素,返回值为 received。首先看下 chanrecv2 的实现:
chan 结合 for-range 编译之后的伪代码如下:
划重点:从这个实现中,我们可以获取一个非常重要的信息,for-range 和 chan 的结束条件只有这个 chan 被 close 了,否则一直会处于这个死循环内部。为什么?注意看 chanrecv 接收的参数是 block=true,并且这个 for-range 是一个死循环,除非 chanrecv2 返回值为 false,才有可能跳出循环,而 chanrecv2 在 block=true 场景下返回值为 false 的唯一原因只有:这个 chan 是 close 状态。
总结
golang 的 chan 使用非常简单,这些简单的语法糖背后其实都是对应了相应的函数实现,这个翻译由编译器来完成。深入理解这些函数的实现,对于彻底理解 chan 的使用和限制条件是必不可少的。深入理解原理,知其然知其所以然,你才能随心所欲地使用 golang。
golang本地缓存(bigcache/freecache/fastcache等)选型对比及原理总结
以下内容来自腾讯后台研发工程师jayden
导语:提到本地缓存大家都不陌生,只要是个有点经验的后台开发人员,都知道缓存的作用和弊端。本篇文章我们就来简单聊聊在golang做业务开发的过程中,本地缓存的一些可选的开源方案,分析它们的特点,以及内部的实现原理。
1.本地缓存需求分析
首先来梳理一下业务开发过程中经常面临的本地缓存的一些需求。我们一般做缓存就是为了能提高系统的读写性能,缓存的命中率越高,也就意味着缓存的效果越好。其次本地缓存一般都受限于本地内存的大小,所有全量的数据一般存不下。那基于这样的场景一方面是想缓存的数据越多,则命中率理论上也会随着缓存数据的增多而提高;另外一方面是想,既然所有的数据存不下那就想办法利用有限的内存存储有限的数据。这些有限的数据需要是经常访问的,同时有一定时效性(不会频繁改变)的。基于这两个点展开,我们一般对本地缓存会要求其满足支持过期时间、支持淘汰策略。最后再使用自动管理内存的语言例如golang等开发时,还需要考虑在加入本地缓存后引发的GC问题。
分析完我们日常本地缓存的诉求,再结合我们日常开发用到的golang语言,我们可以提炼得到golang本地缓存组件必须具备以下几个能力:
分析清楚了我们的需求,也明确了我们需要的能力。那自然优先考虑golang内置的标准库中是否存在这样的组件可以直接使用呢?很遗憾,没有。golang中内置的可以直接用来做本地缓存的无非就是map和sync.Map。而这两者中,map是非并发安全的数据结构,在使用时需要加锁;而sync.Map虽然是线程安全的。但是需要在并发读写时加锁。此外二者均无法支持数据的过期和淘汰,同时在存储大量数据时,又会产生比较频繁的GC问题,更严重的情况下导致线上服务无法稳定运行。
既然标准库中没有我们满足上述需求的本地缓存组件,那我们就想只有两种解决方案了
那首先面临的第一个问题就是方案的调研和选型,没有合适的方案时自己再来动手构建。下面我们就来给大家介绍下golang中哪些可以直接来使用的本地缓存组件吧。
2.golang本地缓存组件概览
golang中本地缓存方案可选的有如下一些:
下面通过笔者一段时间的调研和研究,将golang可选的开源本地缓存组件汇总为下表,方便大家在方案选型时作参考。
在上述方案中,freecache、求源码分享bigcache、fastcache、ristretto、groupcache这几个大家根据实际的业务场景首选,offheap有定制需求时可考虑。
通过上表的总结,个人想再此再谈几点关于本地缓存组件的理解:
(1)上述本地缓存组件中,实现零GC的方案主要就两种:
a.无GC:分配堆外内存(Mmap)
b.避免GC:map非指针优化(map[uint]uint)或者采用slice实现一套无指针的map
c.避免GC:数据存入[]byte slice(可考虑底层采用环形队列封装循环使用空间)
(2)实现高性能的关键在于:
a.数据分片(降低锁的粒度)
3. 主流缓存组件实现原理剖析
在本节中我们会重点分析下freecache、bigcache、fastcache、offheap这几个组件内部的实现原理。
3.1 freecache实现原理
首先分析下freecache的内部实现原理。在freecache中它通过segment来进行对数据分片,freecache内部包含个segment,每个segment维护一把互斥锁,每一条kv数据进来后首先会根据k进行计算其hash值,然后根据hash值决定当前的这条数据落入到哪个segment中。
对于每个segment而言,它由索引、数据两部分构成。
索引:其中索引最简单的方式采用map来维护,例如map[uint]uint这种。而freecache并没有采用这种做法,而是通过采用slice来底层实现一套无指针的map,以此避免GC扫描。
数据:数据采用环形缓冲区来循环使用,底层采用[]byte进行封装实现。数据写入环形缓冲区后,记录写入的位置index作为索引,读取时首先读取数据header信息,然后再读取kv数据。
在freecache中数据的传递过程是:freecache->segment->(slot,ringbuffer) 下图是freecache的内部实现框架图。
总结: freecache通过利用数据分片减小锁的粒度,然后再存储时索引并没有采用内置的map来维护而是采用自建map减少指针来避免GC,同时数据存储时采用预先分配内存然后后边循环使用。通过上述两种方法保证了在堆上分配内存同时减少GC对系统性能的影响。
3.2 bigcache实现原理
bigcache和freecache类似,也是一个零GC、高性能的cache组件,但是它的实现和freecache还是有些差异,这儿有篇 英文博客介绍bigcache设计原理的,内容稍长感兴趣的可以阅读下,下面我们介绍一下bigcache的实现原理。
bigcache同样是采用分片的方式构成,一个bigcache对象包含2^n 个cacheShard对象,默认是个。每个cacheShard对象维护着一把sync.RWLock锁(读写锁)。所有的数据会分散到不同的cacheShard中。
每个cacheShard同样由索引和数据构成。索引采用map[uint]uint来存储,数据采用entry([]byte)环形队列存储。索引中存储的是该条数据在entryBuffer写入的位置pos。每条kv数据按照TLV的格式写入队列。
不过值得注意的是,和bigcache和freecache不同的一点在于它的环形队列可以自动扩容。同时bigcache中数据的过期是通过全局的时间窗口维护的,每个单独的kv无法设置不同的过期时间。
下面是bigcache的内容实现原理框架图。
总结:bigcache思路和freecache大体相同,只不过在索引存储时更为巧妙,直接采用内置的map结构加上基础数据类型来实现。同时底层存储数据的队列也可以根据空间大小来决定是否扩容。唯一的缺陷是无法针对每个key进行设置不同的过期时间。这个个人认为如果想用bigcache同时想要这个特性,可以进行二次开发一下。
通过 性能测试数据来看,bigcache性能要比freecache稍微好一点。大家可以思考下他们性能的差异可能会在哪里呢?
3.3 fastcache实现原理
本节介绍下fastcache的实现原理,根据fastcache官方文档介绍,它的灵感来自于bigcache。所以整体的思路和bigcache很类似,数据通过bucket进行分片。fastcache由个bucket构成。每个bucket维护一把读写锁。在bucket内部数据同理是索引、数据两部分构成。索引用map[uint]uint存储。数据采用chunks二维的切片(二维数组)存储。不过值得注意的是fastcache有一个很大的特性是,它的内存分配是在堆外分配的,而不是在堆上分配的。堆外分配的内存。这样做也就避免了golang GC的影响。下图是fastcache内部实现框架图。
总结: fastcache一方面充分利用了分片来降低锁的粒度,另一方面在索引存储时采用了对map的优化,同时在分配内存时,直接从堆外申请内存,自己实现了分配和释放内存的火狐页面源码逻辑。通过上述手段使得GC的影响降到了最低。fastcache唯一的缺陷是官方提供的版本没有提供针对kv数据的过期时间这个特性。所以如果需要这个特性的话,需要自己动手二次开发。整体从性能上来看是比bigcache和freecache都更优。
3.4 offheap实现原理
本节介绍下offheap的相关内容,offheap其实功能就比较简单了,就是一个基于堆外内存构建的哈希表。它通过直接调用系统调用函数来分配内存。然后在内部通过数组来实现哈希表。实现过程中当发生哈希冲突时,它是采用探测法来解决。由于是在堆外分配的内存上构建的哈希表。导致它的GC开销非常的小。下图是offheap的内部实现框架图。
总结:offheap内部由于是采用探测法解决哈希冲突的,所以当哈希冲突严重时数据删除、查询都会带来非常复杂的处理流程。而且性能也会有一些损耗。可以作为学习和研究的项目还是非常不错的。
4.总结
本文主要从日常需求出发,分析了日常业务过程中对本地缓存的需求,再调研了业界可选的一些组件并进行了对比,希望对本地缓存选型上起到一些参考和帮助。最后再对其中比较重要的几个组件如freecache、bigcache、fastcache、offheap等做了内部实现的简单介绍。上述内容只是从架构层面展开介绍,后续有时间再从源码层面做一些分析。由于篇幅限制本篇内容并未对map、sync.Map、go-cache、groupcache进行介绍。感兴趣的读者可以自行搜索资料进行阅读。如果大致理解了上述原理的童鞋也可以自己动手实践起来,造个轮子看看。
5.参考资料
欢迎点赞分享,关注 @鹅厂架构师,一起探索更多业界领先产品技术。
Golang源码剖析panic与recover,看不懂你打我好了
哈喽,大家好,我是asong,今天与大家来聊一聊go语言中的"throw、try.....catch{ }"。如果你之前是一名java程序员,我相信你一定吐槽过go语言错误处理方式,但是这篇文章不是来讨论好坏的,我们本文的重点是带着大家看一看panic与recover是如何实现的。上一文我们讲解了defer是如何实现的,但是没有讲解与defer紧密相连的recover,想搞懂panic与recover的实现也没那么简单,就放到这一篇来讲解了。废话不多说,直接开整。
Go 语言中panic 关键字主要用于主动抛出异常,类似 java 等语言中的 throw 关键字。panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer;
Go 语言中recover 关键字主要用于捕获异常,让程序回到正常状态,类似 java 等语言中的 try ... catch 。recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;
recover只能在defer中使用这个在标准库的注释中已经写明白了,我们可以看一下:
这里有一个要注意的点就是recover必须要要在defer函数中使用,否则无法阻止panic。最好的验证方法是先写两个例子:
运行我们会发现example2()方法的panic是没有被recover住的,导致整个程序直接crash了。这里大家肯定会有疑问,为什么直接写recover()就不能阻止panic了呢。我们在 详解defer实现机制(附上三道面试题,我不信你们都能做对)讲解了defer实现原理,一个重要的知识点**defer将语句放入到栈中时,也会将相关的值拷贝同时入栈。**所以defer recover()这种写法在放入defer栈中时就已经被执行过了,panic是发生在之后,所以根本无法阻止住panic。
通过运行结果可以看出panic不会影响defer函数的使用,所以他是安全的。
这里我开了两个协程,一个协程会发生panic,导致程序崩溃,但是只会执行自己所在Goroutine的延迟函数,所以正好验证了多个 Goroutine 之间没有太多的关联,一个 Goroutine 在 panic 时也不应该执行其他 Goroutine 的延迟函数。
其实我们在实际项目开发中,经常会遇到panic问题, Go 的 runtime 代码中很多地方都调用了 panic 函数,对于不了解 Go 底层实现的新人来说,这无疑是挖了一堆深坑。我们在实际生产环境中总会出现panic,但是我们的程序仍能正常运行,这是因为我们的框架已经做了recover,他已经为我们兜住底,比如gin,我们看一看他是怎么做的。
我们先来写个简单的代码,看看他的汇编调用:执行go tool compile -N -l -S main.go就可以看到对应的汇编码了,我们截取部分片段分析:
上面重点部分就是画红线的三处,第一步调用runtime.deferprocStack创建defer对象,这一步大家可能会有疑惑,我上一文忘记讲个这个了,这里先简单概括一下,defer总共有三种模型,编译一个函数里只会有一种defer模式。在讲defer实现机制时,我们一起看过defer的结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:
简单介绍一下上面的字段:
上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。
下面就开始我们的重点吧~。
在讲defer实现机制时,我们一起看过defer的结构,其中有一个字段就是_panic,是触发defer的作用,我们来看看的panic的结构:简单介绍一下上面的字段:上面的pc、sp、goexit我们单独讲一下,runtime包中有一个Goexit方法,Goext能够终止调用它的goroutine,其他的goroutine是不受影响的,goexit也会在终止goroutine之前运行所有延迟调用函数,Goexit不是一个panic,所以这些延迟函数中的任何recover调用都将返回nil。如果我们在主函数中调用了Goexit会终止该goroutine但不会返回func main。由于func main没有返回,因此程序将继续执行其他gorountine,直到所有其他goroutine退出,程序才会crash。写个简单的例子:运行上面的例子你就会发现,即使在主goroutine中调用了runtime.Goexit,其他goroutine是没有任何影响的。所以结构中的pc、sp、goexit三个字段都是为了修复runtime.Goexit,这三个字段就是为了保证该函数的一定会生效,因为如果在defer中发生panic,那么goexit函数就会被取消,所以才有了这三个字段做保护。看这个例子:
英语好的可以看一看这个: github.com/golang/go/is...,这就是上面的一个例子,这里就不过多解释了,了解就好。
接下来我们再来看一看gopanic方法。
gopanic的代码有点长,我们一点一点来分析:
根据不同的类型判断当前发生panic错误,这里没什么多说的,接着往下看。
上面的代码都是截段,这些部分都是为了判断当前defer是否可以使用开发编码模式,具体怎么操作的就不展开了。
在第三部分进行defer内联优化选择时会执行调用延迟函数(reflectcall就是这个作用),也就是会调用runtime.gorecover把recoverd = true,具体这个函数的操作留在下面讲,因为runtime.gorecover函数并不包含恢复程序的逻辑,程序的恢复是在gopanic中执行的。先看一下代码:
这段代码有点长,主要就是分为两部分:
第一部分主要是这个判断if gp._panic != nil && gp._panic.goexit && gp._panic.aborted { ... },正常recover是会绕过Goexit的,所以为了解决这个,添加了这个判断,这样就可以保证Goexit也会被recover住,这里是通过从runtime._panic中取出了程序计数器pc和栈指针sp并且调用runtime.recovery函数触发goroutine的调度,调度之前会准备好 sp、pc 以及函数的返回值。
第二部分主要是做panic的recover,这也与上面的流程基本差不多,他是从runtime._defer中取出了程序计数器pc和栈指针sp并调用recovery函数触发Goroutine,跳转到recovery函数是通过runtime.call进行的,我们看一下其源码(src/runtime/asm_amd.s 行):
因为go语言中的runtime环境是有自己的堆栈和goroutine,recovery函数也是在runtime环境执行的,所以要调度到m->g0来执行recovery函数,我们在看一下recovery函数:
在recovery 函数中,利用 g 中的两个状态码回溯栈指针 sp 并恢复程序计数器 pc 到调度器中,并调用 gogo 重新调度 g , goroutine 继续执行,recovery在调度过程中会将函数的返回值设置为1。这个有什么作用呢? 在deferproc函数中找到了答案:
当延迟函数中recover了一个panic时,就会返回1,当 runtime.deferproc 函数的返回值是 1 时,编译器生成的代码会直接跳转到调用方函数返回之前并执行 runtime.deferreturn,跳转到runtime.deferturn函数之后,程序就已经从panic恢复了正常的逻辑。
在这里runtime.fatalpanic实现了无法被恢复的程序崩溃,它在中止程序之前会通过 runtime.printpanics 打印出全部的 panic 消息以及调用时传入的参数。
这就是这个逻辑流程,累死我了。。。。
结尾给大家发一个小福利,哈哈,这个福利就是如果避免出现panic,要注意这些:这几个是比较典型的,还有很多会发生panic的地方,交给你们自行学习吧~。
好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者持续创作更多优质内容的动力!
套实战教程KubernetesK8s CKA认证实战(完整版)BAT大厂基于K8s构建企业容器云平台
套实战教程KubernetesK8s CKA认证实战(完整版)BAT大厂基于K8s构建企业容器云平台
内容包括:K8s+Docker+DevOps+Jenkins+CICD+Git+Istio+Service Mesh云原生实战,云计算,微服务,容器架构师,全栈架构师,集群实战,部署落地,服务治理,服务网格,原理剖析,实战应用,云原生架构,CKA认证实战班,平台设计与开发教程。
网盘下载: soft.com/javajg/.html
总目录:涵盖套云原生实战训练营K8s,挑战年薪万K8s+Docker+DevOps+Jenkins+CICD+Git+Istio+Service Mesh云计算,微服务,容器架构师,全栈架构师,集群实战,部署落地,服务治理,服务网格,原理剖析,实战应用,云原生架构,CKA认证实战班,平台设计与开发视频教程。
第套:云原生高薪课,从零到一构建开源的企业级PaaS平台视频教程
第套:搭建企业私有云平台,实现云上亿级流Kubernetes+DevOps+Jenkins+Istio实战课程
第套:云原生实战Docker+K8s+Kubeshere+DevOps架构师必修课程
第套:云原生微服务架构实战精讲,微服务架构迁移和落地视频教程
第套:云原生微信小程序开发实战,云开发成为标配
第套:云原生+边缘计算项目实战,KubeEdge打造边缘管理平台,云边端一体化设计
第套:真正的云原生架构与云IDC实操业务,腾讯架构师工程师TCP认证课程,含DevOps
第套:世界强Kubernetes实战课程,全栈架构师基于K8s的实战教学
第套:K8S微服务与容器云架构师课程,Linux云计算微服务架构师讲解实际生产内容
第套:BAT大厂基于K8s构建企业容器云平台,CKA认证实战班
第套:Kubernetes原理剖析与实战应用,进阶高级架构师必须选项
第套:K8S集群实战,组件部署和使用,持久化存储与代码自动发布教程
第套:Docker与Kubernetes最佳实践,架构师必备技能
第套:全面Docker系统性入门,从基础到高阶实战视频教程
第套:DevOps平台设计和开发视频教程
第套:大厂进阶篇Docker与微服务实战,技术点全面覆盖
第套:DevOps落地笔记,优化研发流程,提高效率和质量
第套:Service Mesh实战,微服务架构实践和落地
第套:Service Mesh服务实战课程,微服务架构全方位解读
第套:Istio服务网格服务治理,微服务架构与设计全面解析
第套:大厂Istio基础与实践,一线实战分享视频教程
第套:KubernetesCKS认证实战班,架构+网络+存储+安全+监控+日志
第套:Kubernetes网络训练营,运维和开发进阶体系课
第套:Kubernetes实战与源码剖析,自动化运维管理集群
第套:阿里云平台构建云原生应用架构,全流程开发
第套:云原生架构师课程,顶级架构设计思维模型,架构设计哲学
第套:Jenkins工程实践,DevOps交付流水线落地
第套:Jenkins企业级持续集成持续部署,DI视频教程
第套:一站式搞定企业级云原生,专业技能+核心原理+方案设计+系统分析
第套:个人博客开发,全栈+实战,HOOKS+Redux+Webpack+Immer
第套:Serverless架构课程,概念篇+开发篇+进阶篇+场景案例
第套:Serverless进阶实战,成为专业工程师,云原生技术红利
第套:Kubernetes高可用集群安装,日常运维视频教程
第套:Kubernetes最佳实践课程,基于世界强的高薪实战
第套:Golang企业级运维,DevOps运维开发实战
第套:新版容器编排最佳实践,Kubernetes-Rancher2.x视频教程
第套:电商项目K8S部署与性能优化,基于K8S集群电商微服务项目
第套:Docker入门到进阶,全面掌握从基础到高级
第套:Golang企业级运维,高级运维必修课程
golang面试题库?
go面试题整理(附带部分自己的解答)
原文:
如果有解答的不对的,麻烦各位在评论写出来~
go的调度原理是基于GMP模型,G代表一个goroutine,不限制数量;M=machine,代表一个线程,最大1万,所有G任务还是在M上执行;P=processor代表一个处理器,每一个允许的M都会绑定一个G,默认与逻辑CPU数量相等(通过runtime.GOMAXPROCS(runtime.NumCPU())设置)。
go调用过程:
可以能,也可以不能。
因为go存在不能使用==判断类型:map、slice,如果struct包含这些类型的字段,则不能比较。
这两种类型也不能作为map的key。
类似栈操作,后进先出。
因为go的return是一个非原子性操作,比如语句returni,实际上分两步进行,即将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机正是跳转前,所以说defer执行时还是有机会操作返回值的。
select的case的表达式必须是一个channel类型,所有case都会被求值,求值顺序自上而下,从左至右。如果多个case可以完成,则会随机执行一个case,如果有default分支,则执行default分支语句。如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行。
break关键字可跳出select的执行。
goroutine管理、信息传递。context的意思是上下文,在线程、协程中都有这个概念,它指的是程序单元的一个运行状态、现场、快照,包含。context在多个goroutine中是并发安全的。
应用场景:
例子参考:
waitgroup
channel
len:切片的长度,访问时间复杂度为O(1),go的slice底层是对数组的引用。
cap:切片的容量,扩容是以这个值为标准。默认扩容是2倍,当达到的长度后,按1.倍。
扩容:每次扩容slice底层都将先分配新的容量的内存空间,再将老的数组拷贝到新的内存空间,因为这个操作不是并发安全的。所以并发进行append操作,读到内存中的老数组可能为同一个,最终导致append的数据丢失。
共享:slice的底层是对数组的引用,因此如果两个切片引用了同一个数组片段,就会形成共享底层数组。当sliec发生内存的重新分配(如扩容)时,会对共享进行隔断。详细见下面例子:
make([]Type,len,cap)
map的底层是hashtable(hmap类型),对key值进行了hash,并将结果的低八位用于确定key/value存在于哪个bucket(bmap类型)。再将高八位与bucket的tophash进行依次比较,确定是否存在。出现hash冲撞时,会通过bucket的overflow指向另一个bucket,形成一个单向链表。每个bucket存储8个键值对。
如果要实现map的顺序读取,需要使用一个slice来存储map的key并按照顺序进行排序。
利用map,如果要求并发安全,就用sync.map
要注意下set中的delete函数需要使用delete(map)来实现,但是这个并不会释放内存,除非value也是一个子map。当进行多次delete后,可以使用make来重建map。
使用sync.Map来管理topic,用channel来做队列。
参考:
多路归并法:
preclass="vditor-reset"placeholder=""contenteditable="true"spellcheck="false"pdata-block="0"(1)假设有K路ahref=""数据流/a,流内部是有序的,且流间同为升序或降序;
/ppdata-block="0"(2)首先读取每个流的第一个数,如果已经EOF,pass;
/ppdata-block="0"(3)将有效的k(k可能小于K)个数比较,选出最小的那路mink,输出,读取mink的下一个;
/ppdata-block="0"(4)直到所有K路都EOF。
/p/pre
假设文件又1个G,内存只有M,无法将1个G的文件全部读到内存进行排序。
第一步:
可以分为段读取,每段读取M的数据并排序好写入硬盘。
假设写入后的文件为A,B,C...
第二步:
将A,B,C...的第一个字符拿出来,对这个字符进行排序,并将结果写入硬盘,同时记录被写入的字符的文件指针P。
第三步:
将刚刚排序好的9个字符再加上从指针P读取到的P+1位数据进行排序,并写入硬盘。
重复二、三步骤。
go文件读写参考:
保证排序前两个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同的排序叫稳定排序。
快速排序、希尔排序、堆排序、直接选择排序不是稳定的排序算法。
基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。
参考:
head只请求页面的首部。多用来判断网页是否被修改和超链接的有效性。
get请求页面信息,并返回实例的主体。
参考:
:未授权的访问。
:拒绝访问。
普通的.ipv4.tcp_keepalive_intvl=//当探测没有确认时,重新发送探测的频度。缺省是秒。
net.ipv4.tcp_keepalive_probes=9//在认定连接失效之前,发送多少个TCP的keepalive探测包。缺省值是9。这个值乘以tcp_keepalive_intvl之后决定了,一个连接发送了keepalive之后可以有多少时间没有回应
net.ipv4.tcp_keepalive_time=//当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时。一般设置为分钟
修改:
可以
tcp是面向连接的,upd是无连接状态的。
udp相比tcp没有建立连接的过程,所以更快,同时也更安全,不容易被攻击。upd没有阻塞控制,因此出现网络阻塞不会使源主机的发送效率降低。upd支持一对多,多对多等,tcp是点对点传输。tcp首部开销字节,udp8字节。
udp使用场景:视频通话、im聊天等。
time-wait表示客户端等待服务端返回关闭信息的状态,closed_wait表示服务端得知客户端想要关闭连接,进入半关闭状态并返回一段TCP报文。
time-wait作用:
解决办法:
close_wait:
被动关闭,通常是由于客户端忘记关闭tcp连接导致。
根据业务来啊~
重要指标是cardinality(不重复数量),这个数量/总行数如果过小(趋近于0)代表索引基本没意义,比如sex性别这种。
另外查询不要使用select*,根据select的条件+where条件做组合索引,尽量实现覆盖索引,避免回表。
僵尸进程:
即子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程。
孤儿进程:
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
子进程死亡需要父进程来处理,那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时,子进程死亡时没父进程处理,这个死亡的子进程就是孤儿进程。
但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用ID,损害运行系统。
原文链接:
产生死锁的四个必要条件:
(1)互斥条件:一个资源每次只能被一个进程使用。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免方法:
端口占用:lsof-i:端口号或者nestat
cpu、内存占用:top
发送信号:kill-l列出所有信号,然后用kill[信号变化][进程号]来执行。如kill-。强制杀死进程
gitlog:查看提交记录
gitdiff:查看变更记录
gitmerge:目标分支改变,而源分支保持原样。优点:保留提交历史,保留分支结构。但会有大量的merge记录
gitrebase:将修改拼接到最新,复杂的记录变得优雅,单个操作变得(revert)很简单;缺点:
gitrevert:反做指定版本,会新生成一个版本
gitreset:重置到某个版本,中间版本全部丢失
etcd、Consul
pprof
节省空间(非叶子节点不存储数据,相对btree的优势),减少I/O次数(节省的空间全部存指针地址,让树变的矮胖),范围查找方便(相对hash的优势)。
explain
其他的见:
runtime2.go中关于p的定义:其中runnext指针决定了下一个要运行的g,根据英文的注释大致意思是说:
所以当设置runtime.GOMAXPROCS(1)时,此时只有一个P,创建的g依次加入P,当最后一个即i==9时,加入的最后一个g将会继承当前主goroutinue的剩余时间片继续执行,所以会先输出9,之后再依次执行P队列中其它的g。
方法一:
方法二:
[上传失败...(image-4ef-)]
方法1:to_days,返回给的日期从0开始算的天数。
方法2:data_add。向日期添加指定时间间隔
[上传失败...(image-bb-)]
面试问题总结(一)Golang
使用go语言的好处:go语言的设计是务实的,go在针对并发上进行了优化,并且支持大规模高并发,又由于单一的码格式,相比于其他语言更具有可读性,在垃圾回收上比java和Python更有效,因为他是和程序同时执行的.
1.进程,线程,协程的区别,协程的优势
2.讲一下GMP模型(重点)
3.Go的GC,混合写屏障(重点)
4.go的Slice和数组的区别,slice的扩容原理(重点)
5.讲一下channel,实现原理(重点)
6.讲一下Go的Map的实现原理,是否线程安全,如何实现安全(重点)
7.new和make的区别
8.说一下内存逃逸
9.函数传指针和传值有什么区别
.goroutine之间的通信方式
.测试是怎么做的(单元测试,压力测试)
.堆和栈的区别
golang面试题2之判断字符串中字符是否全都不同请实现个算法,确定个字符串的所有字符是否全都不同。这我们要求不允
许使额外的存储结构。给定个string,请返回个bool值,true代表所有字符全都
不同,false代表存在相同的字符。保证字符串中的字符为ASCII字符。字符串的
度于等于。
这有个重点,第个是ASCII字符,ASCII字符字符共有个,其中个是常
字符,可以在键盘上输。之后的是键盘上法找到的。
然后是全部不同,也就是字符串中的字符没有重复的,再次,不准使额外的储存结
构,且字符串于等于。
如果允许其他额外储存结构,这个题很好做。如果不允许的话,可以使golang内置
的式实现。
通过strings.Count函数判断:
使的是golang内置法strings.Count,可以来判断在个字符串中包含
的另外个字符串的数量
还有不同的方法同样可以实现,你了解吗?
推荐go相关技术专栏
gRPC-go源码剖析与实战_带你走进gRPC-go的源码世界-CSDN博客