404频道

学习笔记

北京灵山

题图为北京灵山主峰,海拔2303米,北京最高峰。登上主峰的时候,恰巧一头牛就在山顶悠闲,拍照的时候,牛哥把我带上去的枣、葡萄、花生米全部吃光了,甚至连橘子皮都没剩下,吃完后牛哥又悠闲的去吃草了,让我见识了啥叫吃葡萄不吐葡萄皮。

教程

  1. 《Kubernetes权威指南 企业级容器云实战》

kubernetes的书籍并不多,该书八月份刚初版,内容较新,并不是一本kubernetes的入门书籍,而是讲解kubernetes在企业落地为PASS平台时需要做的工作,建议对kubernetes有一定了解后再看。

书的内容为HPE的多名工程师拼凑而成,有些部分的内容明显是没有经过实践验证的理论派想法,但总体来看值得一读。

书中提到了很多kubernetes较新版本才有的特性、微服务、service mesh、lstio,对于补充自己已经掌握的知识点有一定帮助。

书的后半部分反而显的干货少了非常多,我仅草草的过了一遍。

  1. 《Go语言高级编程》

Golang中相对进阶的中文教程,我还没来得及看。

  1. Gorilla

facebook发表的分布式的时序数据库论文,如果英文看起来吃力,可以看一下小米运维公众号中的翻译版本。facebook并未提供开源的实现,但在github上能找到一些开源的实现。

  1. 《深入剖析Kubernetes》

极客时间app的专栏,本来购买之前没有报特别大的预期,但读完头几篇文章后被作者的文字功底折服,将PASS、容器的来龙去脉、docker的发展讲解的很到位,超出了我的预期。期待后面更新的专栏能够保持搞水准。

  1. SDN手册

一本介绍SDN相关知识的开源电子书。

资源

  1. M3DB

监控领域还是比较缺少特别好用的分布式时间序列存储数据库,性能特别优异的数据库往往都是单机版的,缺少高可用的方案,比如rrdtool、influxdb、graphite等。OpenTSDB、KairosDB、Druid等虽为分布式的时序数据库,但使用或者运维起来总有各种不方便的地方。uber开源的m3在分布式时序数据库领域又多了一个方案,并可作为prometheus的远程存储。

  1. cilium

使用BPF(Berkeley Packet Filter)和XDP(eXpress Data Path)内核技术来提供网络安全控制的高性能开源网络方案。

  1. kubeless

kubernetes平台上的Serverless项目,Faas(功能即服务)一定是云计算发展的一个趋势。目前CNCF中还没有Serverless项目,期待CNCF下能够孵化一个Serverless项目。

工具

  1. vagrant

还在使用virturalbox的你,是时候使用vagrant了。vagrant作为对虚拟机的管理,虽然引入了一些概念带来了更大的复杂性。但同时功能上也更强大,比如对box的管理,可以将box理解为docker image,便于将虚拟机的环境在不同的主机上分发。

公众账号推荐

  1. 小米运维

开通时间不算特别长,但文章的质量不错,都是比较接地气的干货,看得出确实是在工作中遇到的问题或者是总结经验,值的一读。

  1. 开柒

曾经公众号的名字为开八,江湖人称八姐,忘记为何更改为开柒了,曾经的搜狐记者。总能非常及时的爆料很多互联网的内幕,消息来源往往非常准确,可见八姐在圈内的人脉非同一般。

  1. 毕导

打发时间非常好的公众号,用理科男的思维方式进行恶搞,是不是拿出冗长的数学公式来证明日常生活中的小尝试,语言诙谐幽默,绝对是公众号中的一股清流。可惜每篇文章都很长,我没有太多时间把每一篇文章都看一遍。

晚上在工位上安静地看着技术文档,不知为何脑中突然闪现了小时候打场的场景,那段记忆并无特殊之处,但大概是那片金黄色的背景和村民脸上丰收的喜悦以及村民之间的和谐相处的画面突然浮现在了我的眼前,让我不由得想写点什么以便回忆那段近乎忘却的回忆。

说起“打场”这个词,可能很多人都不太熟悉,其实这个词也已经很多年我都没有接触过了,我甚至都不知道现在农村老家的村民们还是否会时长提起这个词语。我甚至不知道这个词是不是我们那地域性的方言,这些都不重要了。以下内容摘自百度百科:

打场[dǎ cháng],指把收割下来带壳的粮食平摊在场院里,用马拉磙子,或者用小型拖拉机,碾压这些粮食,使之脱去外壳,这一系列活动就叫打场。

我的家在山东,小的时候还没有那么多的农业经济作物,地里更多的农作物以小麦和玉米为主,一年两季。农作物的种植周期跟二十四节气是息息相关的,每年的二十四节气的芒种左右是小麦丰收的季节,时间大概在高考前后。比我大一点的应该还有麦假,就是在收麦子的时候不上学了回家帮着收麦子,我没怎么有印象我有过麦假。

小时候的机械化程度低,甚至谈不上机械化,收麦子这种活自然完全是人工完成的。在机械化程度还可以的今天,收麦子这种活在当时还是有着稍微繁琐的工序,也比较耗费人力,一到收麦子的时候,几乎家里所有的劳力都要出来干活了。

村里场地基本上都是挨着的,这应该是生产队时代的产物,那时候土地是公有的,场地自己要连成一片比较容易。后来土地开始分割给个人,这块场地仍然是保留的,只不过是场地被划分成了很多份,每家分上一点。

在打场那个年代,我是几乎很少参与过收麦子的活动,那时我还小,就是在场地里捣乱和瞎跑,还时不时让在场地里干活的村民调侃一番,毕竟小孩子活泼好动,村民们调侃一下解解乏也算是为他们做的一点点微不足道的贡献了。

收麦子的活第一步是割麦子,割麦子这个活我还正儿八经得干过几次,虽然每次干的时间都非常短。关于割麦子的画面,我脑海中浮现的是烈日当头,家人们带着帽子弓着背拿着镰刀一人一沟麦子往前赶,一会儿麦秸在怀里就抱不下了,然后将麦秸放下继续往前赶。那时候的帽子几乎全部是用草编织成的,两根绳子沿着帽檐搭下来系在下巴上,很是牢固,即使低着头帽子也不会掉下来。我干活的时候每次负责的那一沟麦子总是割的最慢的,几乎要慢一倍的样子,而且是干的最差的,背后总会留下一些麦子没有割下来,到现在我也仍然好奇,他们是怎么干的这么快的,我感觉当时已经尽最大的努力了,而这仅是他们的正常速度。

在麦子割完后,就需要将麦子放到集中的晒干了。之所以要晒干,是因为要是麦秸不晒干,麦粒是很难从麦秸上拖不下来的。而要晒干,那么多麦秸就需要一片地方来晾晒麦秸和麦粒。那时候还没怎么有柏油路,家里也很少有水泥的房顶,为了能够有晾晒的地方,村里有一片地就是场地,在收麦子的时候专门用来收麦子的,收完麦子后再种其他作物。之所以场地是集中式的,这还要从生产队时代说起,那时候土地都是公有的,自然场地就会划到一块比较容易,后来土地慢慢私有后,这些土地仍然是作为场地,只是被划的一小块一小块的。

割麦子的时候偶尔会碰到鹌鹑蛋,这也是一些额外的小收获,但遗憾的是从没看到过鹌鹑。

场地要想能晾晒麦秸和麦粒,自然就需要特别的平整,而要想土地特别的平整,在石器时代,仅有一个办法,那就是用轱辘一遍一遍的撵,一般至少需要两个人用绳子拉着轱辘满地里转,要想将土地撵的特别平整且不能有缝隙,哪怕一个麦粒掉在地上能够看的见捡的起来,想想都不是一件特别容易的事情,尤其是刚开始土地特别不平的时候。

并非所有的麦秸都是需要在场地里暴晒的,这个要看麦秸的干燥程度,有些麦秸割完后已经非常干燥了,就不需要再暴晒了。

干燥的麦秸在干燥后就需要将麦粒从麦秸上分离出来,毕竟农民们想要的是麦粒,那才是实打实的粮食。要想麦粒从麦秸上分离,这时候仍然是重量级的轱辘上场,这时候场地相对平整了,自然拉起来会省力气很多,我也曾经拉过一次,但每次都是拉一会就不知道去哪里玩了,小孩子干这么枯燥的事情自然没有耐心。

拖完粒的麦秸一般是就地放到的场地的地头上,为了省空间,会将麦秸垛起来,一个剁的高度差不多在两三米高的样子,差不多是农民拿着叉子往上扔麦秸扔不上去的高度,叉子的长度差不多在两米高的样子。

白天的时间是晒麦粒的最佳时机,到了傍晚时分就需要将晒的麦粒堆起来盖住,这个时候也是场上人最多的时候,几乎每家的地里都有人在忙着。我脑中闪现的场景就是在这个时候,村民们在场地上忙来忙去,边干活边聊着天,扯东扯西的。我在场地上跑来跑去,从这个地里跑到另外一家,时不时村民们还会那我来开个玩笑。累了靠着垛儿打个滚,不一会体力就恢复了,想来那时体力真是好。那时虽然很多事情还不懂,但我确实能从中体会村民们丰收的喜悦。

麦子收完后,用轱辘碾压了无数遍的场地也就失去了存在的价值,村民们在场地上该种点其他作物就种上其他作物了,总之场地不会空闲特别久。场地上总会落下一些麦粒,雨后空闲的场地上的缝隙处时长会生出新的绿油油的麦苗。

现在随着机械化的进步,收麦子这种事自然不需要手工割麦子了,晒麦子的地方也开始变多了,柏油路上,自家的房顶上,自家的院子里都是及其不错的晒麦子的地方,打场这种原始的方式也自然就退出了历史的舞台,而且再也不会回来。

但就是那幅金黄色的画面却在我的记忆中留下了一道抹不去的色彩,让我时长回忆起来那温馨的画面。纵科技的发展,村民们的亲情却在变淡,该进城的都进城了,该出去打工的都出去打工了,一年村民们都见不上几回。也许将来村落的概念会消失,也许人与人之间的关系会更淡,但我曾经经历过村落时代的美好,也许这就足够了。

sync.Cond类似于pthread中的条件变量,但等待的为goroutine,而不是线程。比较难理解的为Wait函数,在调用该函数时必须L为Lock状态,调用Wait函数后,goroutine会自动解锁,并等待条件的到来,等条件到来后会重新加锁。

代码量并不多,下面是去掉注释后的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package sync

import (
"sync/atomic"
"unsafe"
)

type Cond struct {
noCopy noCopy

// L is held while observing or changing the condition
L Locker

notify notifyList
checker copyChecker
}

func NewCond(l Locker) *Cond {
return &Cond{L: l}
}

func (c *Cond) Wait() {
c.checker.check()
t := runtime_notifyListAdd(&c.notify)
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
c.L.Lock()
}

func (c *Cond) Signal() {
c.checker.check()
runtime_notifyListNotifyOne(&c.notify)
}

func (c *Cond) Broadcast() {
c.checker.check()
runtime_notifyListNotifyAll(&c.notify)
}

// copyChecker holds back pointer to itself to detect object copying.
type copyChecker uintptr

func (c *copyChecker) check() {
if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
uintptr(*c) != uintptr(unsafe.Pointer(c)) {
panic("sync.Cond is copied")
}
}

// noCopy may be embedded into structs which must not be copied
// after the first use.
//
// See https://github.com/golang/go/issues/8005#issuecomment-190753527
// for details.
type noCopy struct{}

// Lock is a no-op used by -copylocks checker from `go vet`.
func (*noCopy) Lock() {}

具体的使用例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package main

import (
"fmt"
"sync"
)

func main() {
mutex := &sync.Mutex{}
cond := sync.NewCond(mutex)

wg := &sync.WaitGroup{}

wait := func(i int, c chan int) {
defer wg.Done()
fmt.Println("start chan ", i)
cond.L.Lock()
defer cond.L.Unlock()
fmt.Printf("chan %d wait before\n", i)
c <- i
// Wait是理解起来稍微麻烦的点,Cond.Wait会自动释放锁等待信号的到来,当信号到来后,第一个获取到信号的Wait将继续往下执行并从新上锁
cond.Wait()
fmt.Printf("chan %d wait end\n", i)
}

signal := func(count int, c chan int) {
defer wg.Done()
for i := 0; i < count; i++ {
fmt.Printf("read chan %d ready\n", <-c)
}
fmt.Println("call signal")
cond.Signal()
}

broadcast := func(count int, c chan int) {
defer wg.Done()
for i := 0; i < count; i++ {
fmt.Printf("read chan %d ready\n", <-c)
}
fmt.Println("call broadcast")
cond.Broadcast()
}

c := make(chan int)
wg.Add(2)
go wait(0, c)
go signal(1, c)
wg.Wait()
fmt.Println("signal test finished\n\n")

count := 3
for i := 0; i < count; i++ {
wg.Add(1)
go wait(i, c)
}
wg.Add(1)
go broadcast(count, c)
wg.Wait()
}

北京青龙峡

自从阮一峰的博客中增加了每周分享栏目,自己每周五都是主动的浏览一下阮老师的每周分享,一来阮老师的涉猎非常广泛,可以提高自己的视野;二来,阮老师的文章都特别容易懂,给人一种一直想看下去的冲动。

我个人平常也会看很多的技术类文章,也会遇到各种工具或者特别不错的文章,也有分享的冲动,也想搞一些分享的文章,当然我没有阮老师勤奋和涉猎广泛,但也期望能够对他人有所帮助,哪怕文中的一条分享能够让读者觉得有价值,那么也是值的做的一件事情。

具体的分享版块可能不会特别固定,分享的间隔也不会特别勤快,很难做到阮老师的一周一次的频次。

教程

1. The Go Memory Model

Golang的内存模型,建议Golang开发者读一遍。

2. 《极客时间》-左耳听风

知名博客酷壳的作者陈皓的技术专栏,花钱购买一下专栏还是非常值得的,尤其是最近写的程序员练级攻略系列,能提供大量有价值的学习资料及方向指导,非常赞。


http://

3. 深入解析 kubernetes 资源管理,容器云牛人有话说

对kubernetes的资源管理讲解的非常到位和深入,文章略长,需要花点时间才能读完,值的一看。

4. 《TCP/IP详解 卷1: 协议》

网络方面的经典著作,每个工程师必读,虽然是写给工程师看的,但很多的学术著作中引用到了该书中内容。

5. 《深入解析GO》

对于go的内部实现原理讲解的挺到位,对于理解go的原理挺有帮助。涉及少量汇编,我不太懂汇编,涉及汇编的地方直接跳过了。

6. Red Hat Enterprise Linux Document

RedHat官方的Linux文档,我个人还没怎么读过。

资源

1. 腾讯大学-CEO来了

货真价实的互联网CEO的视频分享,谈创业、谈感悟,目前已经有蔚来汽车、VIPKID、每日优鲜、快手、Keep、知乎的CEO的分享。

2. nginx-upsync-module

新浪微博开源的nginx module,用于动态更改upstream server。

3. 语义化版本规范SemVer

软件版本在取名上会比较混乱,有的使用1.0.1,有的使用1.0等,SemVer用于规范软件版本的命名。

4. 《见识》

吴军老师的最新图书,内容整理自吴军的专栏《硅谷来信》,每篇文章一个主题,值的一读。

5. 嗨!济南

又听到了一首关于济南的歌曲,曾在济南生活多年,必须要分享一下。

6. termtosvg

Github上的开源项目,将命令行工具单独保存为SVG动画。

7. teleport

提供了ssh的审计和回放,基于SSH的RBAC管理,同时还有一个带管理功能的ui界面,目的是用于取代系统自带的sshd。

工具

1. cloc

统计代码行数的工具,下面是kubernetes项目的v1.11.2版本的代码行数统计,go的代码行数已经超过了100万行。

2. SpaceVim

我个人不是vim工具党,刚毕业那会曾经一度热衷于将vim打造成为一个开发C++的IDE,但经过复杂的配置后仍然难以达到CLion这种IDE的水平。最近偶然看到SpaceVim,心中为之一振,这就是我想要的vim,虽达不到IDEA的高度,但已经可以跟vscode的易用度差不多了。

SpaceVim的强大之处在于Space键的使用,默认情况下按下空格键会给出快捷键的提示,类似于桌面系统中的菜单功能。

macvlan的原理是在宿主机物理网卡上虚拟出多个子接口,每个子接口有独立的mac地址,通过不同的MAC地址在数据链路层(Data Link Layer)进行网络数据转发的。达到的效果类似,一块物理网卡上有多个IP地址,多个IP地址有自己的mac地址。

它是比较新的网络虚拟化技术,需要较新的内核支持(Linux kernel v3.9–3.19 and 4.0+)。

macvlan设备跟物理设备之间并不直接互通。

macvlan不以交换机端口来划分vlan,一个交换机端口可接收来自多个mac地址的数据。

一个交换机端口要处理多个vlan的数据,需要开启trunk模式。

四种模式

以下四种模式为每个macvlan设备单独配置,而不是一个物理设备就只有一个配置。

VEPA

所有发送出去的报文都经过交换机,交换机再发送到对应的目标地址。默认模式。物理网卡接收到macvlan设备的数据后,总是将数据发送出去,即使是发往本设备上其他macvlan设备的数据包。这样在交换机设备上可以看到所有网络的流量。如果是本机的macvlan设备流量仍然是发往本机的macvlan设备,可能会被交换机的生成树协议阻止。需要交换机开启hairpin模式或者reflective relay模式,该模式在目前的交换机上未广泛支持,vepa模式的应用较少。

linux的网桥支持hairpin模式。

bridge

最常用,同一个物理设备上的不同macvlan设备间的通讯可以直接转发,不再需要经过外部的交换机。转发非常快速,macvlan设备相对固定,不需要生成树协议。

Private

本质上是VEPA模式,但同一个物理设备上的macvlan设备之间无法直接通讯,不常用。

Passthru

后来增加的模式,比较少用

vepa和passthru都会将不同macvlan接口之间的数据发送到交换机,然后发回,对性能的影响比较明显。

物理网卡收到包后,根据包的mac地址来判断这个包交给哪个虚拟接口。

image

手工创建实践

以下实验为在virturalbox虚拟机下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 创建两个network namespace net1和net2
ip netns add net1
ip netns add net2

# 创建macvlan接口
# enp0s8相当于物理网卡eth0
ip link add link enp0s8 mac1 type macvlan

# 可以看到创建了接口mac1@enp0s8
[root@localhost vagrant]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
link/ether 08:00:27:6c:3e:95 brd ff:ff:ff:ff:ff:ff
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
link/ether 08:00:27:cf:b0:b4 brd ff:ff:ff:ff:ff:ff
4: docker_gwbridge: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT
link/ether 02:42:8e:cf:a9:da brd ff:ff:ff:ff:ff:ff
5: br-b3e83aa45886: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT
link/ether 02:42:bf:b4:e5:36 brd ff:ff:ff:ff:ff:ff
6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT
link/ether 02:42:25:e7:40:32 brd ff:ff:ff:ff:ff:ff
19: br-ec6c4e77321d: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT
link/ether 02:42:e0:d8:38:6d brd ff:ff:ff:ff:ff:ff
24: mac1@enp0s8: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT
link/ether 3e:b8:e9:1d:3b:c8 brd ff:ff:ff:ff:ff:ff

创建macvlan接口的格式为:ip link add link <PARENT> <NAME> type macvlan是macvlan接口的父接口名称,name是新创建的macvlan接口名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 将mac1放入到net1 namespace中
[root@localhost vagrant]# ip link set mac1 netns net1
[root@localhost vagrant]# ip netns exec net1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
24: mac1@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT
link/ether 3e:b8:e9:1d:3b:c8 brd ff:ff:ff:ff:ff:ff link-netnsid 0

# 在net1中将mac1接口命名为eth0
[root@localhost vagrant]# ip netns exec net1 ip link set mac1 name eth0
[root@localhost vagrant]# ip netns exec net1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
24: eth0@if3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT
link/ether 3e:b8:e9:1d:3b:c8 brd ff:ff:ff:ff:ff:ff link-netnsid 0

# 在net1中分配eth0网卡的ip地址为192.168.8.120
[root@localhost vagrant]# ip netns exec net1 ip addr add 192.168.8.120/24 dev eth0
[root@localhost vagrant]# ip netns exec net1 ip link set eth0 up
[root@localhost vagrant]# ip netns exec net1 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
24: eth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT
link/ether 3e:b8:e9:1d:3b:c8 brd ff:ff:ff:ff:ff:ff link-netnsid 0

在docker上的实践

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost vagrant]# docker network create -d macvlan --subnet=10.0.2.100/24 --gateway=10.0.2.2 -o parent=enp0s3 mcv
5c637798d559471bd8d1036cdd947d3949e1973f724568da066d9c60c00fb5e6

[root@localhost vagrant]# docker network ls
NETWORK ID NAME DRIVER SCOPE
b6a128f1730e bridge bridge local
a65957cd6c3f docker_gwbridge bridge local
540bb390028a host host local
b3e83aa45886 isolated_nw bridge local
ec6c4e77321d local_alias bridge local
5c637798d559 mcv macvlan local
3d247d0414d0 none null local

reference

Some notes on macvlan/macvtap

pushd和popd命令的用法

在编写shell的时候,经常会在目录之间进行切换,如果使用cd命令经常会切换错误,pushd和popd使用栈的方式来管理目录。

dirs

用于显示当前目录栈中的所有记录。

pushd

将目录加入到栈顶部,并将当前目录切换到该目录。若不加任何参数,该命令用于将栈顶的两个目录进行对调。

popd

删除目录栈中的目录。若不加任何参数,则会首先删除目录栈顶的目录,并将当前目录切换到栈顶下面的目录。

命令格式:pushd [-N | +N] [-n]

  • +N 将第N个目录删除(从左边数起,数字从0开始)
  • -N 将第N个目录删除(从右边数起,数字从0开始)
  • -n 将目录出栈时,不切换目录

example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost tmp]# mkdir /tmp/dir{1,2,3,4}
[root@localhost tmp]# pushd /tmp/dir1
/tmp/dir1 /tmp
[root@localhost dir1]# pushd /tmp/dir2
/tmp/dir2 /tmp/dir1 /tmp
[root@localhost dir2]# pushd /tmp/dir3
/tmp/dir3 /tmp/dir2 /tmp/dir1 /tmp
[root@localhost dir3]# pushd /tmp/dir4
/tmp/dir4 /tmp/dir3 /tmp/dir2 /tmp/dir1 /tmp
# dirs的显示内容跟pushd完成后的输出一致
[root@localhost dir4]# dirs
/tmp/dir4 /tmp/dir3 /tmp/dir2 /tmp/dir1 /tmp

[root@localhost dir4]# popd
/tmp/dir3 /tmp/dir2 /tmp/dir1 /tmp

# 带有数字比较容易出错
[root@localhost dir3]# popd +1
/tmp/dir3 /tmp/dir1 /tmp

# 清除目录栈
[root@localhost dir3]# dirs -c
[root@localhost dir3]# dirs
/tmp/dir3

docker image结构

docker可以通过命令docker image inspect ${image}来查看image的详细信息,其中包含了所使用的底层文件系统及各层的信息。

docker container的存储结构分为了只读层、init层和可读写层。

只读层跟docker image的层次结构恰好对应,主要包含操作系统的文件、配置、目录等信息,不包含操作系统镜像。

init层在只读层和读写层中间,用来专门存放/etc/hosts /etc/resolv.conf等信息,这些文件往往需要在启动的时候写入一些指定值,但不期望docker commit命令对其进行提交。

可读写层为容器在运行中可以进行写入的层。

overlay

image

采用了两层结构,lowerdir为镜像层,只读。upperdir为容器层。

每层都会在/var/run/docker/overlay创建一个文件夹,文件夹中为实际层的内容,文件采用硬链接的方式链接到真实层中的文件,每一层都包含该层该拥有的所有文件,而该文件的真实存储可能是采用硬链接的方式链接到上层中的真实文件,因此比较耗费inode。

创建一个容器时,会新增两个目录,一个为读写层,一个为初始层。初始层中保存了容器初始化时的环境信息,如hostname、hosts文件等。读写层用于记录容器的所有改动。

overlay2

为了规避overlay消耗inode节点过多的问题,overlay2采用在每层中增加lower文件的方式来记录所有底层的信息,类似于链表的形式。

docker pull ubuntu

1
2
3
4
5
6
7
8
9
10
[root@localhost runc]# docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
c64513b74145: Pull complete
01b8b12bad90: Pull complete
c5d85cf7a05f: Pull complete
b6b268720157: Pull complete
e12192999ff1: Pull complete
Digest: sha256:3f119dc0737f57f704ebecac8a6d8477b0f6ca1ca0332c7ee1395ed2c6a82be7
Status: Downloaded newer image for ubuntu:latest

会在/var/run/docker/overlay2目录下创建如下文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@localhost overlay2]# tree -L 2
.
|-- 664ae13f1c21402385076025d68476eb8d1cc4be6c6a218b24bd55217ac62672 // 第0层
| |-- diff
| `-- link // MZUEUOFHBNVTRCJYJEG7QY4VWT
|-- 783ad02709b67ac47b55198e9659c4592f0972334987ab97f42fd10f1784cbba // 第2层
| |-- diff
| |-- link // HXATFASQ4E2JBG434DUEN54EZZ
| |-- lower // l/WUZC5WSTQTPJUJ4KFAYCUT5IPD:l/MZUEUOFHBNVTRCJYJEG7QY4VWT
| |-- merged
| `-- work
|-- 89f7a20dda3d868840e20d9e8f1bfe20c5cca51c27b07825f100da0f474672f6 // 第3层
| |-- diff
| |-- link // 5PHT7S3MCZTTQOXVPA4CKJRRFD
| |-- lower // l/HXATFASQ4E2JBG434DUEN54EZZ:l/WUZC5WSTQTPJUJ4KFAYCUT5IPD:l/MZUEUOFHBNVTRCJYJEG7QY4VWT
| |-- merged
| `-- work
|-- backingFsBlockDev
|-- bad073a2d1f79a03af6caa0b3f51a22e6762cebbc0c30e45458fe6c1ff266f68 // 第4层
| |-- diff
| |-- link // QMKHIPSDT4JTPE4FLT7QGJ33ND
| |-- lower // l/5PHT7S3MCZTTQOXVPA4CKJRRFD:l/HXATFASQ4E2JBG434DUEN54EZZ:l/WUZC5WSTQTPJUJ4KFAYCUT5IPD:l/MZUEUOFHBNVTRCJYJEG7QY4VWT
| |-- merged
| `-- work
|-- cb40b5b47c699050305676b35b1cea1ce08b38604dd68243c4be48934125b1a3 // 第1层
| |-- diff
| |-- link // WUZC5WSTQTPJUJ4KFAYCUT5IPD
| |-- lower // l/MZUEUOFHBNVTRCJYJEG7QY4VWT
| |-- merged
| `-- work
`-- l
|-- 5PHT7S3MCZTTQOXVPA4CKJRRFD -> ../89f7a20dda3d868840e20d9e8f1bfe20c5cca51c27b07825f100da0f474672f6/diff
|-- HXATFASQ4E2JBG434DUEN54EZZ -> ../783ad02709b67ac47b55198e9659c4592f0972334987ab97f42fd10f1784cbba/diff
|-- MZUEUOFHBNVTRCJYJEG7QY4VWT -> ../664ae13f1c21402385076025d68476eb8d1cc4be6c6a218b24bd55217ac62672/diff
|-- QMKHIPSDT4JTPE4FLT7QGJ33ND -> ../bad073a2d1f79a03af6caa0b3f51a22e6762cebbc0c30e45458fe6c1ff266f68/diff
`-- WUZC5WSTQTPJUJ4KFAYCUT5IPD -> ../cb40b5b47c699050305676b35b1cea1ce08b38604dd68243c4be48934125b1a3/diff

l目录下为超链接,缩短后的目录,为了避免mount时超出页大小限制。

每一层中的diff文件夹包含实际内容。

每一层中都有一个link文件,内容为l目录中的超链接,超链接实际指向当前层目录中的diff文件夹。

除去最底层的目录外,其余每一层中包含一个lower文件,包含了该层的所有更底层名称和顺序,可以根据该文件构建出整个镜像的层次结构。

work目录用于OverlayFS内部使用。

最底层只有link文件,无lower文件,因此664ae13f1c21402385076025d68476eb8d1cc4be6c6a218b24bd55217ac62672为最底层。

以上五层为lower,只读。

当使用docker run -it ubuntu:latest /bin/bash启动一个容器后,在overlay2目录下会多出两个文件夹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@localhost overlay2]# tree -L 1 0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08-init 0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08 l
0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08-init
|-- diff
|-- link // ZJVMGTB2IOJ6QF57TYM5O7EWXW
|-- lower // l/QMKHIPSDT4JTPE4FLT7QGJ33ND:l/5PHT7S3MCZTTQOXVPA4CKJRRFD:l/HXATFASQ4E2JBG434DUEN54EZZ:l/WUZC5WSTQTPJUJ4KFAYCUT5IPD:l/MZUEUOFHBNVTRCJYJEG7QY4VWT
|-- merged
`-- work
0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08
|-- diff
|-- link
|-- lower // l/ZJVMGTB2IOJ6QF57TYM5O7EWXW:l/QMKHIPSDT4JTPE4FLT7QGJ33ND:l/5PHT7S3MCZTTQOXVPA4CKJRRFD:l/HXATFASQ4E2JBG434DUEN54EZZ:l/WUZC5WSTQTPJUJ4KFAYCUT5IPD:l/MZUEUOFHBNVTRCJYJEG7QY4VWT
|-- merged
`-- work
l
|-- 5PHT7S3MCZTTQOXVPA4CKJRRFD -> ../89f7a20dda3d868840e20d9e8f1bfe20c5cca51c27b07825f100da0f474672f6/diff
|-- AOLYGFOHIAHWU5CBAJFULNAXI7 -> ../0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08/diff
|-- HXATFASQ4E2JBG434DUEN54EZZ -> ../783ad02709b67ac47b55198e9659c4592f0972334987ab97f42fd10f1784cbba/diff
|-- MZUEUOFHBNVTRCJYJEG7QY4VWT -> ../664ae13f1c21402385076025d68476eb8d1cc4be6c6a218b24bd55217ac62672/diff
|-- QMKHIPSDT4JTPE4FLT7QGJ33ND -> ../bad073a2d1f79a03af6caa0b3f51a22e6762cebbc0c30e45458fe6c1ff266f68/diff
|-- WUZC5WSTQTPJUJ4KFAYCUT5IPD -> ../cb40b5b47c699050305676b35b1cea1ce08b38604dd68243c4be48934125b1a3/diff
`-- ZJVMGTB2IOJ6QF57TYM5O7EWXW -> ../0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08-init/diff

0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08-init用于存放容器初始化时的信息,通过下面查看更直观。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost overlay2]# tree 0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08-init
0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08-init
|-- diff
| |-- dev
| | `-- console
| `-- etc
| |-- hostname
| |-- hosts
| |-- mtab -> /proc/mounts
| `-- resolv.conf
|-- link
|-- lower
|-- merged
`-- work
`-- work

0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08的直接底层为init层,更详细的目录结构如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@localhost overlay2]# tree -L 2  0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08
0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08
|-- diff
|-- link
|-- lower
|-- merged
| |-- bin
| |-- boot
| |-- dev
| |-- etc
| |-- home
| |-- lib
| |-- lib64
| |-- media
| |-- mnt
| |-- opt
| |-- proc
| |-- root
| |-- run
| |-- sbin
| |-- srv
| |-- sys
| |-- tmp
| |-- usr
| `-- var
`-- work
`-- work

merged文件夹中内容较多,为overlay2的直接挂载点,对容器的修改会反应到该目录中。例如在容器中增加/root/hello.txt文件,在merged目录下会增加root/hello.txt文件。

1
2
3
[root@localhost overlay2]# mount  | grep overlay2
/dev/mapper/centos-root on /var/lib/docker/overlay2 type xfs (rw,relatime,attr2,inode64,noquota)
overlay on /var/lib/docker/overlay2/0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08/merged type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/ZJVMGTB2IOJ6QF57TYM5O7EWXW:/var/lib/docker/overlay2/l/QMKHIPSDT4JTPE4FLT7QGJ33ND:/var/lib/docker/overlay2/l/5PHT7S3MCZTTQOXVPA4CKJRRFD:/var/lib/docker/overlay2/l/HXATFASQ4E2JBG434DUEN54EZZ:/var/lib/docker/overlay2/l/WUZC5WSTQTPJUJ4KFAYCUT5IPD:/var/lib/docker/overlay2/l/MZUEUOFHBNVTRCJYJEG7QY4VWT,upperdir=/var/lib/docker/overlay2/0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08/diff,workdir=/var/lib/docker/overlay2/0326c1da0af912a6ea5efda77b65b04e796993e0f111ed8f262c55b2716f1c08/work)

ref

本文绝大多数题目来源于网络,部分题目为原创。

slice相关

以下代码有什么问题,说明原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type student struct {
Name string
Age int
}

func pase_student() {
m := make(map[string]*student)
stus := []student{
{Name: "zhou", Age: 24},
{Name: "li", Age: 23},
{Name: "wang", Age: 22},
}
for _, stu := range stus {
m[stu.Name] = &stu
}
}

每次遍历的时候stu变量为值拷贝,stu变量的地址未改变,即&stu未改变,遍历结束后stu指向stus中的最后一个元素。

使用reflect.TypeOf(str)打印出的类型为main.student,如果使用stu.Age += 10这样的语法是不会修改stus中的值的。

可修改为如下形式:

1
2
3
for i, _ := range stus {
m[stus[i].Name] = &stus[i]
}

有一个slice of object, 遍历slice修改name为指定的值

1
2
3
4
5
6
7
8
type foo struct {
name string
value string
}

func mutate(s []foo, name string) {
// TODO
}

意在考察range遍历的时候是值拷贝,以及slice的内部数据结构,slice的数据结构如下:

1
2
3
4
5
6
struct    Slice
{ // must not move anything
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; // allocated number of elements
};

执行append函数后会返回一个新的Slice对象,新的Slice对象跟旧Slice对象共用相同的数据存储,但是len的值并不相同。

该题目中,可以通过下面的方式来修改值:

1
2
3
4
5
6
7
8
9
// range方式
for i, _ := range s {
s[i].name = name
}

// for i形式
for i:=0; i<len(s); i++ {
s[i].name = name
}

从slice中找到一个元素匹配name,并将该元素的指针添加到一个新的slice中,返回新slice

1
2
3
4
func find(s []foo, name string) []*foo {
// TODO

}

仍旧是考察range是值拷贝的用法,此处使用for i 循环即可

1
2
3
4
5
6
7
8
9
10
11
func find(s []foo, name string) []*foo {
res := []*foo{}

for i := 0; i < len(s); i++ {
if s[i].name == name {
res = append(res, &(s[i]))
break
}
}
return res
}

下面输出什么内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func m(s []int) {
s[0] = -1
s = append(s, 4)
}

func main() {
s1 := []int{1, 2, 3}
m(s1)
s2 := make([]int, 3, 6)
m(s2)
s2 = append(s2, 7)
s3 := [3]int{1, 2, 3}
fmt.Println(s1)
fmt.Println(s2)
fmt.Println(s3)
}

slice的函数传递为值拷贝方式,在函数m中对下标为0的元素的修改会直接修改原slice中的值,因为slice中的指针指向的地址是相同的。

append之后的slice虽然可能是在原数组上增加了元素,但原slice中的len字段并没有变化。

make([]int, 3, 6)虽然指定了slice的cap,但对于append没有影响,还是会在slice中最后一个元素的下一个位置增加新元素。

数组由于是值拷贝,对新数组的修改不会影响到原数组。

输出内容如下:

1
2
3
[-1 2 3]
[-1 0 0 7]
[1 2 3]

下面输出什么内容

该题目为我自己想出来的,非来自于互联网,意在考察对slice和append函数的理解。

1
2
3
4
5
6
7
8
9
func f() {
s1 := make([]int, 2, 8)
fmt.Println(s1)
s2 := append(s1, 4)
fmt.Println(s2)
s3 := append(s1, 5)
fmt.Println(s3)
fmt.Println(s2)
}

输出结果如下,在执行第二个append后,第一个append在内存中增加的元素4会被5覆盖掉。执行结果可以通过fmt.Println(s1, cap(s1), &s1[0])的形式将第一个元素的内存地址打印出来查看。

1
2
3
[0 0 4]
[0 0 5]
[0 0 5]

goroutine

以下代码输出内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"runtime"
)

func main() {
runtime.GOMAXPROCS(1)
go func() {
fmt.Println(1)
}()
for {
}
fmt.Println(1)
}

不会有任何输出

下面输出的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type People struct{}

func (p *People) ShowA() {
fmt.Println("showA")
p.ShowB()
}
func (p *People) ShowB() {
fmt.Println("showB")
}

type Teacher struct {
People
}

func (t *Teacher) ShowB() {
fmt.Println("teacher showB")
}

func main() {
t := Teacher{}
t.ShowA()
}

输出

1
2
showA
showB

有点出乎意料,可以举个反例,如果ShowA()方法会调用到Teacher类型的ShowB()方法,假设People和Teacher并不在同一个包中时,编译一定会出现错误。

Go中没有继承机制,只有组合机制。

下面代码会触发异常吗?请详细说明

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
runtime.GOMAXPROCS(1)
int_chan := make(chan int, 1)
string_chan := make(chan string, 1)
int_chan <- 1
string_chan <- "hello"
select {
case value := <-int_chan:
fmt.Println(value)
case value := <-string_chan:
panic(value)
}
}

会间歇性触发异常,select会随机选择。

以下代码能编译过去吗?为什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
)

type People interface {
Speak(string) string
}

type Student struct{}

func (stu *Student) Speak(think string) (talk string) {
if think == "bitch" {
talk = "You are a good boy"
} else {
talk = "hi"
}
return
}

func main() {
var peo People = Student{}
think := "bitch"
fmt.Println(peo.Speak(think))
}

不能编译过去,提示Stduent does not implement People (Speak method has pointer receiver),将Speak定义更改为func (stu Stduent) Speak(think string) (talk string)即可编译通过。

main的调用方式更改为如下也可以编译通过var peo People = new(Stduent)

func (stu *Stduent) Speak(think string) (talk string)*Student类型的方法,不是Stduent类型的方法。

下面输出什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"time"
"runtime"
)

func main() {
runtime.GOMAXPROCS(1)
arr := [10000]int{}
for i:=0; i<len(arr); i++ {
arr[i] = i
}
for _, a := range arr {
go func() {
fmt.Println(a)
}()
}
for {
time.Sleep(time.Second)
}
}

一直输出9999.涉及到goroutine的切换时机,仅系统调用或者有函数调用的情况下才会切换goroutine,for循环情况下一直没有系统调用或函数切换发生,需要等到for循环结束后才会启动新的goroutine。

以下代码打印出来什么内容,说出为什么。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"fmt"
)

type People interface {
Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func live() People {
var stu *Student
return stu
}

func main() {
if live() == nil {
fmt.Println("AAAAAAA")
} else {
fmt.Println("BBBBBBB")
}
}

打印BBBBBBB

byte与rune的关系

  • byte alias for uint8
  • rune alias for uint32,用来表示unicode
1
2
3
4
5
6
7
8
func main() {
// range遍历为rune类型,输出int32
for _, w:=range "123" {
fmt.Printf("%T", w)
}
// 取数组为byte类型,输出uint8
fmt.Printf("%T", "123"[0])
}

写出打印的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type People struct {
name string `json:"name"`
}

func main() {
js := `{
"name":"11"
}`
var p People
p.name = "123"
err := json.Unmarshal([]byte(js), &p)
if err != nil {
fmt.Println("err: ", err)
return
}
fmt.Println("people: ", p)
}

打印结果为people: {123}

下面函数有什么问题?

1
2
3
func funcMui(x,y int)(sum int,error){
return x+y,nil
}

函数返回值命名 在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。 如果返回值有有多个返回值必须加上括号; 如果只有一个返回值并且有命名也需要加上括号; 此处函数第一个返回值有sum名称,第二个为命名,所以错误。

以下函数输出什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package main

func main() {
println(DeferFunc1(1))
println(DeferFunc2(1))
println(DeferFunc3(1))
println(DeferFunc4(1))
}

func DeferFunc1(i int) (t int) {
t = i
defer func() {
t += 3
}()
return t
}

func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t
}

func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}

func DeferFunc4(i int) (t int) {
t = 10
return 2
}

输出结果为: 4 1 3 2

return语句不是一个原子指令,分为两个阶段,执行return后面的表达式和返回表达式的结果。defer函数在返回表达式之前执行。

  1. 执行return后的表达式给返回值赋值
  2. 调用defer函数
  3. 空的return

DeferFunc1在第一步执行表达式后t=1,执行defer后t=4,返回值为4

DeferFunc2在第一步执行表达式后t=1,执行defer后t=4,返回值为第一步表达式的结果1

DeferFunc3在第一步表达式为t=2,执行defer后t=3,返回值为t=3

DeferFunc4在第一步执行表达式后t=2,返回值为t=2

是否可以编译通过?如果通过,输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import (
"fmt"
)

func main() {
sn1 := struct {
age int
name string
}{age: 11, name: "qq"}

sn2 := struct {
age int
name string
}{age: 11, name: "qq"}

sn3 := struct {
name string
age int
}{age: 11, name: "qq"}

if sn1 == sn2 {
fmt.Println("sn1 == sn2")
}

if sn1 == sn3 {
fmt.Println("sn1 == sn3")
}


sm1 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}

sm2 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}

if sm1 == sm2 {
fmt.Println("sm1 == sm2")
}
}

结构体比较 进行结构体比较时候,只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。

还有一点需要注意的是结构体是相同的,但是结构体属性中有不可以比较的类型,如map,slice。 如果该结构属性都是可以比较的,那么就可以使用“==”进行比较操作。

是否可以编译通过?如果通过,输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func Foo(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}

func main() {
var x *int = nil
Foo(x)
}

输出“non-empty interface”

交替打印数字和字母

使用两个 goroutine 交替打印序列,一个 goroutine 打印数字, 另外一个 goroutine 打印字母, 最终效果为: 12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package main

import (
"fmt"
"sync"
)


func main() {
number, letter := make(chan bool), make(chan bool)
wait := new(sync.WaitGroup)

go func () {
num := 1
for {
<-number
fmt.Printf("%d%d", num, num+1)
num += 2
letter <- true
if num > 28 {
break
}
}
wait.Done()
}()

go func () {
begin := 'A'
for {
<- letter
if begin < 'Z' {
fmt.Printf("%c%c", begin, begin+1)
begin+=2
number <- true
} else {
break
}
}

wait.Done()
}()

number <- true

wait.Add(2)

wait.Wait()
}

struct类型的方法调用

假设T类型的方法上接收器既有T类型的,又有T指针类型的,那么就不可以在不能寻址的T值上调用T接收器的方法。

请看代码,试问能正常编译通过吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import (
"fmt"
)
type Lili struct{
Name string
}
func (Lili *Lili) fmtPointer(){
fmt.Println("poniter")
}
func (Lili Lili) fmtReference(){
fmt.Println("reference")
}
func main(){
li := Lili{}
li.fmtPointer()
}

能正常编译通过,并输出”poniter”

请接着看以下的代码,试问能编译通过?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import (
"fmt"
)
type Lili struct{
Name string
}
func (Lili *Lili) fmtPointer(){
fmt.Println("poniter")
}
func (Lili Lili) fmtReference(){
fmt.Println("reference")
}
func main(){
Lili{}.fmtPointer()
}

不能编译通过。
“cannot call pointer method on Lili literal”
“cannot take the address of Lili literal”

其实在第一个代码示例中,main主函数中的“li”是一个变量,li的虽然是类型Lili,但是li是可以寻址的,&li的类型是Lili,因此可以调用Lili的方法。

golang context包的用法

  1. goroutine之间的传值
  2. goroutine之间的控制

在单核cpu的情况下,下面输出什么内容?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"sync"
)

func main() {
wg := sync.WaitGroup{}
for _, i:=range []int{1, 2, 3, 4, 5} {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(i)
} ()
}
wg.Wait()
}

考察golang的runtime机制,goroutine的切换时机只有在有系统调用或者函数调用时才会发生,本例子中的for循环结束之前不会发生goroutine的切换,所以最终输出结果为5.

下面输出什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
)

type People interface {
Speak(string) string
}

type Stduent struct{}

func (stu *Stduent) Speak(think string) (talk string) {
if think == "bitch" {
talk = "You are a good boy"
} else {
talk = "hi"
}
return
}

func main() {
var peo People = Stduent{}
think := "bitch"
fmt.Println(peo.Speak(think))
}

编译不通过,仅*Student实现了People接口,更改为var peo People = &Student{}即可编译通过。

下面输出什么

1
2
3
4
5
6
7
8
9
10
package main

const cl = 100

var bl = 123

func main() {
println(&bl, bl)
println(&cl, cl)
}

编译失败,常量cl通常在预处理阶段会直接展开,无法取其地址。

以下代码是否存在问题,请解释你的判断和理由

1
2
3
4
5
6
7
8
import "sync"

func f(m sync.Mutex) {
m.Lock()
defer m.Unlock()

// Do something...
}

Mutex对象不能被值拷贝,后续传递需要使用指针的形式

以下代码输出是什么 解释一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func    main()  {
case1()
case2()
}

func case1() {
s1 := make([]string, 1, 20)
s1[0] = "hello"
p1 := &s1[0]
s1 = append(s1, "world")
*p1 = "hello2"
fmt.Printf("value of p1 is %s, value of s1[0] is %s \n", *p1, s1[0])
}

func case2() {
s1 := make([]string)
s1[0] = "hello"
p1 := &s1[0]
s1 = append(s1, "world")
*p1 = "hello2"
fmt.Printf("value of p1 is %s, value of s1[0] is %s \n", *p1, s1[0])
}

本题意在考察string和slice的数据结构,string的数据结构如下:

http://research.swtch.com/godata2.png

case1的内存结构变化情况如下:

case2由于s1默认长度为0,直接使用s1[0]复制会出现panic错误。

ref

通过使用Google的登陆二步验证(即Google Authenticator服务),我们在登陆时需要输入额外由手机客户端生成的一次性密码。大大提高登陆的安全性。

实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。

google实现了基于时间的TOTP算法(Time-based One-time Password),客户端实现包括了android和ios。

算法为公开算法,google没有提供服务端的实现,各个语言都有单独的实现。自己系统使用可以直接使用网上的代码。

linux下有libpam-google-authenticator模块,可以使用yum或者源码编译安装,github上有源码,编译出来的为so文件,可以加到sshd的配置文件中,用于给sshd提供二次认证机制。

客户端和服务端存在时间差的问题,google authenticator的超时时间为30s,服务端可以使用两个30s的时间来验证,包括当前和上一个30s。

曾经使用多说和网易云评论作为博客的评论系统,不幸都相继倒闭后,博客就一直没有评论系统。虽博客的访问量可以忽略不计,但本着折腾和好奇的原则,还是折腾一下gitment。

更新hexo-theme-next主题

最新版本的next主题已经默认支持gitment,需要将next主题升级到最新版本。

我的hexo-theme-next使用单独的git项目进行管理,git地址为:https://github.com/kuring/hexo-theme-next。接下来需要将fork出来的git项目跟next的git项目进行同步。

  1. 在本地创建名字为upstream的remote,指向地址为:git remote add upstream https://github.com/theme-next/hexo-theme-next.git

  2. 拉取next项目到本地分支,本地的分支,执行git fetch upstream

  3. 将upsteam/master分支合并到master分支上

1
2
3
4
5
6
7
8
9
10
git checkout master

# 由于修改了_config.yml文件,存在冲突,合并失败
lvkai@osx:~/blog/kuring/themes/hexo-theme-next% git merge upstream/master 128 ↵
Removing source/css/_common/components/third-party/gentie.styl
Removing layout/_third-party/comments/gentie.swig
Auto-merging _config.yml
CONFLICT (content): Merge conflict in _config.yml
Removing README.en.md
Automatic merge failed; fix conflicts and then commit the result.
  1. 解决冲突后提交并将master分支push到github仓库

注册gitment

前往:https://github.com/settings/profile

Developer settings -> Register a new application

在界面中输入如下内容:

image

获取到Client ID和Client Secret.

新建github repo

创建新的github项目:https://github.com/kuring/gitment-comments

在next主题中设置gitment

next主题的配置文件为theme/next/_config.yml,修改其中的gitment设置如下,

  1. client_id为在github中注册所获取到的client id
  2. client_secret为在github中注册所获取到的client secret
  3. github_repo为上面新创建的github repo名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Gitment
# Introduction: https://imsun.net/posts/gitment-introduction/
gitment:
enable: true
mint: true # RECOMMEND, A mint on Gitment, to support count, language and proxy_gateway
count: true # Show comments count in post meta area
lazy: false # Comments lazy loading with a button
cleanly: false # Hide 'Powered by ...' on footer, and more
language: # Force language, or auto switch by theme
github_user: kuring # MUST HAVE, Your Github ID
github_repo: gitment-comments # MUST HAVE, The repo you use to store Gitment comments
client_id: xxx # MUST HAVE, Github client id for the Gitment
client_secret: xxxx # EITHER this or proxy_gateway, Github access secret token for the Gitment
proxy_gateway: # Address of api proxy, See: https://github.com/aimingoo/intersect
redirect_protocol: # Protocol of redirect_uri with force_redirect_protocol when mint enabled

执行hexo clean && hexo g && hexo s重新生成页面并在本地运行,可以看到gitment组件已经可以显示了,但是提示Error: Comments Not Initialized错误,点击login,然后允许认证,即可消除该错误。

在界面上添加评论后,可以在github repo的issuse中看到,整个搭建完毕。

0%