#每日精进#2020年7月28日

总结zkbhj 发表了文章 • 0 个评论 • 124 次浏览 • 2020-07-28 08:30 • 来自相关话题

开通微信公众号了,同学们可以关注:kiss-note。





 
【早读:《深入理解计算机系统》】
第一章 计算机系统漫游
计算机系统由硬件和系统软件组成;本书的目的是帮助你了解程序在系统上执行时,发生了什么以及为什么会这样;c的hello.c源程序开始,源程序实际上就是一个由值0和1组成的位序列,8个位为1组,称为字节,每个字节表示某些文本字符;像hello.c这样只由ASCII字符构成的文件称为文本文件,所有其他文件称为二进制文件;同样的字节序列可能表示不同的对象,唯一区分他们的办法是通过读取到这些数据对象时的上下文信息;C语言的起源:

贝尔实验室的Dennis Ritchie与1969年~1973年之间创建的,之后经历了ANSI C标准到现在的IOS标准。标准定义了C语言和一系列库函数(C标准库)。作者说C语言是“古怪的,有缺陷的,但同时也是一个巨大的成功”。
C语言与Unix操作系统关系密切。C从一开始就是作为一种用于Unix系统的程序语言开发出来的。Unix几乎全部使用C语言编写的;C语言 小而简单;C语言是为实践目的设计的。最初设计用来实现Unix操作系统,但是后来发现开发其他程序也很容易。
缺点:指针会造成困惑和麻烦。缺乏对抽象的显示支持,比如类、对象和异常。C++和Java解决了这些问题。

程序需要经过下面的命令,编译成低级机器语言指令才能被最终运行:
linux> gcc -o hello hello.c
编译系统由4个阶段的程序组成:预处理器(cpp)、编译器(ccl)、汇编器(as)、链接器(ld);

预处理阶段:根据#开头的命令,把包含进来的.h或者.c文件直接插入到相应位置,生成一个hello.i文件;
编译阶段:编译器将hello.i翻译成汇编语言程序,生成hello.s文件;
汇编阶段:汇编器将hello.s翻译成机器语言指令,把它们打包成一种可重定位目标程序,生成hello.o文件;
链接阶段:链接器将标准C库中的已经预编译好的.o文件(比如printf.o)以某种方式合并到我们的hello.o中,最终形成可执行文件hello。

汇编语言将不同的高级语言提供了通用的输出语言。相当于一个连接高级语言和机器语言的桥梁。
 
【成为一个优秀的人,可以从这些方面重点切入】
1、拥有强烈的好奇心:
你所能拥有的一切,都源自于探索;而探索的动力,都源自于好奇。正是因为这种好奇,驱动他们离开舒适区,获得更多新知识,不断拓展自己的边界。拥有好奇心的人,就像一块海绵。不断吸收新的知识,获得更大的成长。也因此,他们总是乐意接受更大的挑战。如果已经丢失了孩童时的好奇心,我们还能再捡起来吗?当然可以,当你对一件事情失去兴趣时,“我好奇如果我这么做了,会怎么样?”这么问自己!
 
2、面对不确定性,选择拥抱而不是怀疑:
人们厌恶不确定性,是因为厌恶不确定性背后巨大的风险。但是,不确定性背后,除了巨大的风险,也可能是巨大的收益。因为拥抱比怀疑,永远多一次机会。能成大事儿的人,在面对不确定性时,更容易选择拥抱,而不是怀疑。

3、相比赚钱,他们有更大的目标和想象力:
房子车子都只是附带品。最关键的是,我要不断成长,积累自己的价值,让自己变得稀缺,有能力去解决别人解决不了的问题。所以想要成大事儿,就要有更大的目标和想象力,专注于创造价值,而不是创造财富。
 
4、延迟满足,极度自律:
在这个世界上,要把一件事情做到极致,其实大部分时候都是很平淡,很枯燥的。延迟满足,能够让你在日复一日的枯燥中,不至于选择放弃。
 
5、不怕犯错,善于自省:
很多人不愿尝试做能力范围之外的事情,有一个原因是:害怕犯错。但其实只要不是什么致命错误,犯一些小错,反而是值得高兴的事。不怕犯错,善于自省,不断改进。这样的人,没有天花板。
 
希望我们都能不断成长为心目中那个,最好的自己。
https://mp.weixin.qq.com/s/9vGy6dc1vhBl58GA6F-ARw
 
【怎么理解美国关闭中国休斯顿使馆时间?本质是什么?】
说白了美方就是在故意找事。对美国好处很大,尤其对特朗普而言好处非常大。下面的视频很好的说明了这件政治事件。
https://player.youku.com/embed/XMTg5MDc5MzI3Ng==
对于这些在野党而言,今天的美国经济搞的再稀烂,那也是特朗普的锅。疫情越猛,经济越差,在野党越开心,当然要千方百计的破坏美国抗疫大局。
手段是合法的,用心是险恶的。所以不是特朗普不想消灭病毒,他是真的做不到。当然,特朗普也不是啥好货,他所有政治行为的唯一目的,也是为了选票和连任。其他一切东西,都没有这个重要,包括美国的未来。
这是人性,无关道德。
https://mp.weixin.qq.com/s/9m6Flh3X3wg9Xo-PJJzvuQ 
 
【Go语言核心36讲:第7节 数组与切片】
数组类型的值(以下简称数组)的长度是固定的(是其类型的一部分),而切片类型的值(以下简称切片)是可变长的。
我们其实可以把切片看做是对数组的一层简单的封装,因为在每个切片的底层数据结构中,一定会包含一个数组。数组可以被叫做切片的底层数组,而切片也可以被看作是对数组的某个连续片段的引用(所以Go 语言的切片类型属于引用类型)。
s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
s4 := s3[3:6]
fmt.Printf("The length of s4: %d\n", len(s4))
fmt.Printf("The capacity of s4: %d\n", cap(s4))
fmt.Printf("The value of s4: %d\n", s4)s3[3:6] 等同于数学表达式:[3, 6)。所以,s4的长度就是6-3=3。容量是从起始索引开始(无法向左扩展,只能从3开始,及0,1,2切片s4“看不到”),一直到“底层数组”最右端,所以,容量是 8-3=5。
 
切片容量如何增长?
它并不会改变原来的切片,而是会生成一个容量更大的切片,然后将把原有的元素和新元素一并拷贝到新切片中。在一般的情况下,你可以简单地认为新切片的容量(以下简称新容量)将会是原切片容量(以下简称原容量)的 2 倍。
但是,当原切片的长度(以下简称原长度)大于或等于1024时,Go 语言将会以原容量的1.25倍作为新容量的基准(以下新容量基准)。新容量基准会被调整(不断地与1.25相乘),直到结果不小于原长度与要追加的元素数量之和(以下简称新长度)。最终,新容量往往会比新长度大一些,当然,相等也是可能的。
另外,如果我们一次追加的元素过多,以至于使新长度比原容量的 2 倍还要大,那么新容量就会以新长度为基准。
 
切片的底层数组什么时候会被替换?
确切地说,一个切片的底层数组永远不会被替换。为什么?虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。它只是把新的切片作为了新底层数组的窗口,而没有对原切片,及其底层数组做任何改动。请记住,
 
在无需扩容时,append函数返回的是指向原底层数组的新切片,而在需要扩容时,append函数返回的是指向新底层数组的新切片。
 
https://time.geekbang.org/column/intro/100013101
  查看全部
开通微信公众号了,同学们可以关注:kiss-note。

WX20200728-075634@2x.png

 
【早读:《深入理解计算机系统》】
第一章 计算机系统漫游
  • 计算机系统由硬件和系统软件组成;
  • 本书的目的是帮助你了解程序在系统上执行时,发生了什么以及为什么会这样;
  • c的hello.c源程序开始,源程序实际上就是一个由值0和1组成的位序列,8个位为1组,称为字节,每个字节表示某些文本字符;
  • 像hello.c这样只由ASCII字符构成的文件称为文本文件,所有其他文件称为二进制文件;
  • 同样的字节序列可能表示不同的对象,唯一区分他们的办法是通过读取到这些数据对象时的上下文信息;
  • C语言的起源:


贝尔实验室的Dennis Ritchie与1969年~1973年之间创建的,之后经历了ANSI C标准到现在的IOS标准。标准定义了C语言和一系列库函数(C标准库)。作者说C语言是“古怪的,有缺陷的,但同时也是一个巨大的成功”。

  1. C语言与Unix操作系统关系密切。C从一开始就是作为一种用于Unix系统的程序语言开发出来的。Unix几乎全部使用C语言编写的;
  2. C语言 小而简单;
  3. C语言是为实践目的设计的。最初设计用来实现Unix操作系统,但是后来发现开发其他程序也很容易。

缺点:指针会造成困惑和麻烦。缺乏对抽象的显示支持,比如类、对象和异常。C++和Java解决了这些问题。


  • 程序需要经过下面的命令,编译成低级机器语言指令才能被最终运行:

linux> gcc -o hello hello.c

  • 编译系统由4个阶段的程序组成:预处理器(cpp)、编译器(ccl)、汇编器(as)、链接器(ld);


预处理阶段:根据#开头的命令,把包含进来的.h或者.c文件直接插入到相应位置,生成一个hello.i文件;
编译阶段:编译器将hello.i翻译成汇编语言程序,生成hello.s文件;
汇编阶段:汇编器将hello.s翻译成机器语言指令,把它们打包成一种可重定位目标程序,生成hello.o文件;
链接阶段:链接器将标准C库中的已经预编译好的.o文件(比如printf.o)以某种方式合并到我们的hello.o中,最终形成可执行文件hello。


  • 汇编语言将不同的高级语言提供了通用的输出语言。相当于一个连接高级语言和机器语言的桥梁。

 
【成为一个优秀的人,可以从这些方面重点切入】
1、拥有强烈的好奇心:
你所能拥有的一切,都源自于探索;而探索的动力,都源自于好奇。正是因为这种好奇,驱动他们离开舒适区,获得更多新知识,不断拓展自己的边界。拥有好奇心的人,就像一块海绵。不断吸收新的知识,获得更大的成长。也因此,他们总是乐意接受更大的挑战。如果已经丢失了孩童时的好奇心,我们还能再捡起来吗?当然可以,当你对一件事情失去兴趣时,“我好奇如果我这么做了,会怎么样?”这么问自己!
 
2、面对不确定性,选择拥抱而不是怀疑:
人们厌恶不确定性,是因为厌恶不确定性背后巨大的风险。但是,不确定性背后,除了巨大的风险,也可能是巨大的收益。因为拥抱比怀疑,永远多一次机会。能成大事儿的人,在面对不确定性时,更容易选择拥抱,而不是怀疑。

3、相比赚钱,他们有更大的目标和想象力:
房子车子都只是附带品。最关键的是,我要不断成长,积累自己的价值,让自己变得稀缺,有能力去解决别人解决不了的问题。所以想要成大事儿,就要有更大的目标和想象力,专注于创造价值,而不是创造财富。
 
4、延迟满足,极度自律:
在这个世界上,要把一件事情做到极致,其实大部分时候都是很平淡,很枯燥的。延迟满足,能够让你在日复一日的枯燥中,不至于选择放弃。
 
5、不怕犯错,善于自省:
很多人不愿尝试做能力范围之外的事情,有一个原因是:害怕犯错。但其实只要不是什么致命错误,犯一些小错,反而是值得高兴的事。不怕犯错,善于自省,不断改进。这样的人,没有天花板。
 
希望我们都能不断成长为心目中那个,最好的自己。
https://mp.weixin.qq.com/s/9vGy6dc1vhBl58GA6F-ARw
 
【怎么理解美国关闭中国休斯顿使馆时间?本质是什么?】
说白了美方就是在故意找事。对美国好处很大,尤其对特朗普而言好处非常大。下面的视频很好的说明了这件政治事件。
https://player.youku.com/embed/XMTg5MDc5MzI3Ng==
对于这些在野党而言,今天的美国经济搞的再稀烂,那也是特朗普的锅。疫情越猛,经济越差,在野党越开心,当然要千方百计的破坏美国抗疫大局。
手段是合法的,用心是险恶的。所以不是特朗普不想消灭病毒,他是真的做不到。当然,特朗普也不是啥好货,他所有政治行为的唯一目的,也是为了选票和连任。其他一切东西,都没有这个重要,包括美国的未来。
这是人性,无关道德。
https://mp.weixin.qq.com/s/9m6Flh3X3wg9Xo-PJJzvuQ 
 
【Go语言核心36讲:第7节 数组与切片】
数组类型的值(以下简称数组)的长度是固定的(是其类型的一部分),而切片类型的值(以下简称切片)是可变长的。
我们其实可以把切片看做是对数组的一层简单的封装,因为在每个切片的底层数据结构中,一定会包含一个数组。数组可以被叫做切片的底层数组,而切片也可以被看作是对数组的某个连续片段的引用(所以Go 语言的切片类型属于引用类型)。
s3 := []int{1, 2, 3, 4, 5, 6, 7, 8}
s4 := s3[3:6]
fmt.Printf("The length of s4: %d\n", len(s4))
fmt.Printf("The capacity of s4: %d\n", cap(s4))
fmt.Printf("The value of s4: %d\n", s4)
s3[3:6] 等同于数学表达式:[3, 6)。所以,s4的长度就是6-3=3。容量是从起始索引开始(无法向左扩展,只能从3开始,及0,1,2切片s4“看不到”),一直到“底层数组”最右端,所以,容量是 8-3=5。
 
切片容量如何增长?
它并不会改变原来的切片,而是会生成一个容量更大的切片,然后将把原有的元素和新元素一并拷贝到新切片中。在一般的情况下,你可以简单地认为新切片的容量(以下简称新容量)将会是原切片容量(以下简称原容量)的 2 倍
但是,当原切片的长度(以下简称原长度)大于或等于1024时,Go 语言将会以原容量的1.25倍作为新容量的基准(以下新容量基准)。新容量基准会被调整(不断地与1.25相乘),直到结果不小于原长度与要追加的元素数量之和(以下简称新长度)。最终,新容量往往会比新长度大一些,当然,相等也是可能的。
另外,如果我们一次追加的元素过多,以至于使新长度比原容量的 2 倍还要大,那么新容量就会以新长度为基准。
 
切片的底层数组什么时候会被替换?
确切地说,一个切片的底层数组永远不会被替换。为什么?虽然在扩容的时候 Go 语言一定会生成新的底层数组,但是它也同时生成了新的切片。它只是把新的切片作为了新底层数组的窗口,而没有对原切片,及其底层数组做任何改动。请记住,
 
  • 在无需扩容时,append函数返回的是指向原底层数组的新切片,
  • 而在需要扩容时,append函数返回的是指向新底层数组的新切片。

 
https://time.geekbang.org/column/intro/100013101
 

我的阅读分享:《贫穷的本质:我们为什么摆脱不了贫穷》

读后感zkbhj 发表了文章 • 0 个评论 • 126 次浏览 • 2020-07-27 16:06 • 来自相关话题

阅读书目:《贫穷的本质:我们为什么摆脱不了贫穷》
作者:阿比吉特·巴纳吉 著
书籍类型:经济著作
页数:256页
阅读开始时间:2020年7月26日
阅读结束时间:
如何发现这本书:西西弗书店
阅读次数:第1次
阅读类型:精读
推荐等级:
阅读建议:正在阅读
豆瓣地址:https://book.douban.com/subject/30161884/






  查看全部


阅读书目:《贫穷的本质:我们为什么摆脱不了贫穷》
作者:阿比吉特·巴纳吉 著
书籍类型:经济著作
页数:256页
阅读开始时间:2020年7月26日
阅读结束时间:
如何发现这本书:西西弗书店
阅读次数:第1次
阅读类型:精读
推荐等级:
阅读建议:正在阅读
豆瓣地址:https://book.douban.com/subject/30161884/



5b9106caNc9b79061.jpg

 

如何评估搜索的好坏?学习企查查搜索系统演进

搜索推荐zkbhj 发表了文章 • 0 个评论 • 128 次浏览 • 2020-07-27 11:43 • 来自相关话题

无论搜索怎么扩充信息,最终我们评估搜索的标准依然是回归到搜索的本身,即是否返回用户最想要的信息。怎么度量这个,如何评估搜索的好坏?如何确认搜索人员做到的精准,是否就是用户想要的?企查查对于搜索的评估主要基于如下4个指标Top3SuggestNullHitRate。我们持续关注上述这个指标的变化,并持续予以优化。

1. TOP3

我们的结果集常态的输入可能有数十条数百条,精准的搜索可能只有1条记录,最大的则能达到1万条。对于不同量级的结果集,我们关注前三条用户的点击比例,并把这个指标作为衡量整个搜索是否精度的最主要指标。

2. Suggest

用户每多输入一个字数,结果集都是在变化,他什么时候点击相当重要,如果持续不点击suggest意味着用户需要输入更多的字数,这时会判断推荐词是否是他想要的,如果该指标比例提高会有效减少用户的输入时间。这也是评估搜索是否精准的一个重要指标。

3. Null

无结果是搜索比较糟糕的体验,为了规避无结果的占比,我们除了分析用户的输入以推动纠错,兼容形近、音近等情况,还补全了诸如二次搜索等机制。

4. HitRate

整体的点击搜索比,宏观面的数据,既可以辅助分析用户的搜索行为,也可以反推当前的数据质量走势。

通过对上述指标的持续跟进,我们可以较为直观的知道整个搜索团队的工作是否在往更好的方向发展,同时也能分析到给用户的搜索习惯走势。
 
当然,关于搜索,我们除了上述各种预设的机制外,对于热门的搜索,我们也特别增加了人工干预,以规避新词更新不及时,热点事件排序不合理问题。
 
原文阅读:
https://developer.aliyun.com/article/720572
  查看全部
无论搜索怎么扩充信息,最终我们评估搜索的标准依然是回归到搜索的本身,即是否返回用户最想要的信息。怎么度量这个,如何评估搜索的好坏?如何确认搜索人员做到的精准,是否就是用户想要的?企查查对于搜索的评估主要基于如下4个指标Top3SuggestNullHitRate。我们持续关注上述这个指标的变化,并持续予以优化。

1. TOP3

我们的结果集常态的输入可能有数十条数百条,精准的搜索可能只有1条记录,最大的则能达到1万条。对于不同量级的结果集,我们关注前三条用户的点击比例,并把这个指标作为衡量整个搜索是否精度的最主要指标。

2. Suggest

用户每多输入一个字数,结果集都是在变化,他什么时候点击相当重要,如果持续不点击suggest意味着用户需要输入更多的字数,这时会判断推荐词是否是他想要的,如果该指标比例提高会有效减少用户的输入时间。这也是评估搜索是否精准的一个重要指标。

3. Null

无结果是搜索比较糟糕的体验,为了规避无结果的占比,我们除了分析用户的输入以推动纠错,兼容形近、音近等情况,还补全了诸如二次搜索等机制。

4. HitRate

整体的点击搜索比,宏观面的数据,既可以辅助分析用户的搜索行为,也可以反推当前的数据质量走势。

通过对上述指标的持续跟进,我们可以较为直观的知道整个搜索团队的工作是否在往更好的方向发展,同时也能分析到给用户的搜索习惯走势。
 
当然,关于搜索,我们除了上述各种预设的机制外,对于热门的搜索,我们也特别增加了人工干预,以规避新词更新不及时,热点事件排序不合理问题。
 
原文阅读:
https://developer.aliyun.com/article/720572
 

#每日精进#2020年7月27日

总结zkbhj 发表了文章 • 0 个评论 • 127 次浏览 • 2020-07-27 10:03 • 来自相关话题

【总结《一年顶十年》读后感】
https://ask.zkbhj.com/?/article/346
 
【最新的世界格局理解:世界的蛋糕已经不够分了】
全世界的发达国家,都在不同程度上陷入内噬。最直接冲击是就业。
各种冲突、内卷,全世界的蛋糕已经不够分了。
这背后本质,还是科技红利消失殆尽,而下一个科技革命仍然遥遥无期。
现在就仿佛一个炸药桶,各方情绪在里面酝酿。
所有人都不满意目前的世界格局。
但是现在发动战争的成本太高,且搞不好就变成了核战争。所以,对美国来说,中国就是一个标准的背锅对象。
所以,「逆全球化」其实是欧美的逆中国化,遏制中国的商品出口,不跟中国玩了。
逆中国化只能说很难,主要还是成本问题。中国是所有工业门类最齐全的国家,任何零部件都做得出来,甚至形成「供应链网络」,效率非常高。
既然美国要「逆中国化」,要拆解中国的产业链,遏制中国企业的出海路,那我们要做的,自然是高举全球化大旗,坚持改革开放。
对外是一带一路,是中非命运共同体,是欧亚大陆一体化,是推动中日韩自贸区;对内是新农村建设,是乡村振兴战略,把产业过剩能力转向内陆建设。
这就是对美国的最好反制。
你不开拓市场,别人就会抢占先机。
https://mp.weixin.qq.com/s/x6qDmU0-e6um6QiZH7trfA

【每日一个TED:安德鲁·斯坦顿】
在一个推荐系统解构分享PPT中的一句话发现的这个故事。
里面在解释为什么需要推荐系统时,陈述了一个“信息过载“的事实,里面引用了安德鲁·斯坦顿的一句话:

Don't give them 4, give them 2+2

所以查找了相关的原出处,是安德鲁·斯坦顿的一个TED演讲中讲到的,名字叫《一个伟大故事的线索》
https://www.ted.com/talks/andrew_stanton_the_clues_to_a_great_story/transcript?language=zh-cn
【推荐系统解构(电商篇)】
推荐的目的:帮助用户发现好商品,帮助高质量商品触达精准受众(背后对平台和商家就是促进转化)
数据闭环:
用户:浏览、点击、成交、评价、物流全链路闭环数据商品:潜力、新品、老品、衰落、下架
 
主要召回策略包括:

1) 实时行为召回:在线实时捕捉用户对商品的点击,收藏,加购,购买等反馈行为,并召回相似商品,迅速抓住用户的短期购物需求。
2) 历史行为召回:离线分析用户对商品的历史点击,收藏,加购,购买等反馈行为,并召回相似商品,兼顾用户的长期购物需求。
3) profile召回:从性别,年龄段,设备等多个维度,在线和离线同时对用户进行画像,并召回相对应的热门商品。
4) 热销&趋势召回:分析商品的长期和短期销量变化,召回爆款和近期热点商品。

排序模型建模(电商)相关:

Ctr: Expose -> Click
Cvr: Click -> Order 
GMV-Rate: Click -> GMV
GMV-Pv: Expose -> GMV

排序:一个指标不够—点击和转化:

基础排序公式:score = ctrα * cvrβ * priceγ

假设:
 ctr和cvr估计是准确的, α、β、γ均为1的排序公式才有可能最大化收益 item粒度的估计,而非组合力度的估计 
解决方案: 
通过调整三个因子的大小,如果三者相对稳定可以通过离线统一参数学习 的情况完成,如果不稳定,可通过强化学习来解决这个超参的设定。
复杂排序公式:

score= ctrα * cvrβ * priceγ * mathScoreδ * timeWeightScoreε * mathTypeScoreθ * ···

mathScore:召回分 
typeWeightScore:召回类型分
timeWeight:时间衰减
mathType:召回类型
 
覆盖率:通过类目,品牌,频道,场景等多粒度的打散重排,最大化各个维度的个性化覆盖率。 
疲劳度:通过引入一定的随机因子,针对不同个性化程度,建立合理的轮转机制,保证一定的新颖性。 (这个概念以前没有了解过)

 推荐演进路线:

复杂度:规则 -> 线性 -> 非线性 -> 融合 
时效性:天级 -> 时段级 -> 小时级 -> 实时

人工规则:人工经验提取规则,千人一 面,流量效率低 
线性模型:通过线性模型自动学习人工 提取的特征权重,简单的用 户特征,个性化推荐雏形
非线性模型:树模型及多模型融合,引入更多的上下文特征,加入用 户实时行为反馈,千人千面
深度学习、强化学习与在线学习:对象向量化表达,高维特征 自动提取,特征与模型实时在线更新
 
团队或产品是否应该做推荐功能的决策路径:
• 推荐功能对该产品有无价值(可以) 
• 价值多大(值得) 
• 成本和收益(现在) 
• 优先级(怎样)
比如推荐系统的实时性做到毫秒级是否有必要?
 
分群:根据不同维度对用户进行分群,从而进行针对性推荐
新老用户分群
购买力分群
兴趣分群(群体对类目的强偏好和弱偏好均可以在推荐中使用)
 
Query-Item 文本相关性

文本: 词->短语->语义->主题->句法 
图片: 图->标签&向量

或者把query看成id,与其它id做item2vector
 
PPT下载:
链接:https://pan.baidu.com/s/1YnYkWfpHW3-EokxHmoMrEQ 提取码:li44
 
【如何评估搜索的好坏?看哪些指标】
https://ask.zkbhj.com/?/article/356
 
【Go语言核心36讲:6 程序实体那些事儿】
一对不包裹任何东西的花括号{},除了可以代表空的代码块之外,还可以用于表示不包含任何内容的数据结构(或者说数据类型)。
类型断言表达式的语法形式是x.(T)。其中的x代表要被判断类型的值。
struct{},它就代表了不包含任何字段和方法的、空的结构体类型。interface{}则代表了不包含任何方法定义的、空的接口类型。对于一些集合类的数据类型来说,{}还可以用来表示其值不包含任何元素,比如空的切片值string{},以及空的字典值map[int]string{}。
 
类型字面量:所谓类型字面量,就是用来表示数据类型本身的若干个字符。

string是表示字符串类型的字面量,uint8是表示 8 位无符号整数类型的字面量。再复杂一些的就是我们刚才提到的string,用来表示元素类型为string的切片类型,以及map[int]string,用来表示键类型为int、值类型为string的字典类型。

类型转换表达式:T(x)
x可以是一个变量,也可以是一个代表值的字面量(比如1.23和struct{}{}),还可以是一个表达式。
 
类型转换中的几个陷阱:
对于整数类型值、整数常量之间的类型转换,原则上只要源值在目标类型的可表示范围内就是合法的。虽然直接把一个整数值转换为一个string类型的值是可行的,但值得关注的是,被转换的整数值应该可以代表一个有效的 Unicode 代码点,否则转换的结果将会是"�"(仅由高亮的问号组成的字符串值)string(-1)得到的就是?string类型与各种切片类型之间的互转
 
一个值在从string类型向byte类型转换时代表着以 UTF-8 编码的字符串会被拆分成零散、独立的字节。
一个值在从string类型向rune类型转换时代表着字符串会被拆分成一个个 Unicode 字符。 //别名类型,实际上是同一个类型
type myString = string

//类型再定义,实际上是两个不同的类型
type myString string 
【今日放学别走】

Review推荐系统的新版推荐接口。

排查问题,了解netstat命令显示的tcp和tcp6的区别。# netstat -tlnp | grep :22 tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1444/sshd tcp6 0 0 :::22 :::* LISTEN 1444/sshd可以看到,netstat 显示表示 sshd 既监听在 ipv4 的地址,又监听在 ipv6 的地址。tcp6表示监听的是IPv6的。

netstat 只是很真实的显示监听的端口而已,但是需要注意 ipv6 实际上在 Linux 上也支持 ipv4。

https://blog.csdn.net/weixin_4 ... 16951  查看全部
【总结《一年顶十年》读后感】
https://ask.zkbhj.com/?/article/346
 
【最新的世界格局理解:世界的蛋糕已经不够分了】
全世界的发达国家,都在不同程度上陷入内噬。最直接冲击是就业。
各种冲突、内卷,全世界的蛋糕已经不够分了。
这背后本质,还是科技红利消失殆尽,而下一个科技革命仍然遥遥无期。
现在就仿佛一个炸药桶,各方情绪在里面酝酿。
所有人都不满意目前的世界格局。
但是现在发动战争的成本太高,且搞不好就变成了核战争。所以,对美国来说,中国就是一个标准的背锅对象。
所以,「逆全球化」其实是欧美的逆中国化,遏制中国的商品出口,不跟中国玩了。
逆中国化只能说很难,主要还是成本问题。中国是所有工业门类最齐全的国家,任何零部件都做得出来,甚至形成「供应链网络」,效率非常高。
既然美国要「逆中国化」,要拆解中国的产业链,遏制中国企业的出海路,那我们要做的,自然是高举全球化大旗,坚持改革开放。
对外是一带一路,是中非命运共同体,是欧亚大陆一体化,是推动中日韩自贸区;对内是新农村建设,是乡村振兴战略,把产业过剩能力转向内陆建设。
这就是对美国的最好反制。
你不开拓市场,别人就会抢占先机。
https://mp.weixin.qq.com/s/x6qDmU0-e6um6QiZH7trfA

【每日一个TED:安德鲁·斯坦顿】
在一个推荐系统解构分享PPT中的一句话发现的这个故事。
里面在解释为什么需要推荐系统时,陈述了一个“信息过载“的事实,里面引用了安德鲁·斯坦顿的一句话:


Don't give them 4, give them 2+2


所以查找了相关的原出处,是安德鲁·斯坦顿的一个TED演讲中讲到的,名字叫《一个伟大故事的线索》
https://www.ted.com/talks/andrew_stanton_the_clues_to_a_great_story/transcript?language=zh-cn
【推荐系统解构(电商篇)】
推荐的目的:帮助用户发现好商品,帮助高质量商品触达精准受众(背后对平台和商家就是促进转化)
数据闭环:
  • 用户:浏览、点击、成交、评价、物流全链路闭环数据
  • 商品:潜力、新品、老品、衰落、下架

 
主要召回策略包括:


1) 实时行为召回:在线实时捕捉用户对商品的点击,收藏,加购,购买等反馈行为,并召回相似商品,迅速抓住用户的短期购物需求。
2) 历史行为召回:离线分析用户对商品的历史点击,收藏,加购,购买等反馈行为,并召回相似商品,兼顾用户的长期购物需求。
3) profile召回:从性别,年龄段,设备等多个维度,在线和离线同时对用户进行画像,并召回相对应的热门商品。
4) 热销&趋势召回:分析商品的长期和短期销量变化,召回爆款和近期热点商品。


排序模型建模(电商)相关:


Ctr: Expose -> Click
Cvr: Click -> Order 
GMV-Rate: Click -> GMV
GMV-Pv: Expose -> GMV


排序:一个指标不够—点击和转化:


基础排序公式:score = ctrα * cvrβ * priceγ


假设:
 ctr和cvr估计是准确的, α、β、γ均为1的排序公式才有可能最大化收益 item粒度的估计,而非组合力度的估计 
解决方案: 
通过调整三个因子的大小,如果三者相对稳定可以通过离线统一参数学习 的情况完成,如果不稳定,可通过强化学习来解决这个超参的设定。
复杂排序公式:


score= ctrα * cvrβ * priceγ * mathScoreδ * timeWeightScoreε * mathTypeScoreθ * ···


mathScore:召回分 
typeWeightScore:召回类型分
timeWeight:时间衰减
mathType:召回类型
 
覆盖率:通过类目,品牌,频道,场景等多粒度的打散重排,最大化各个维度的个性化覆盖率。 
疲劳度:通过引入一定的随机因子,针对不同个性化程度,建立合理的轮转机制,保证一定的新颖性。 (这个概念以前没有了解过)

 推荐演进路线:


复杂度:规则 -> 线性 -> 非线性 -> 融合 
时效性:天级 -> 时段级 -> 小时级 -> 实时


人工规则:人工经验提取规则,千人一 面,流量效率低 
线性模型:通过线性模型自动学习人工 提取的特征权重,简单的用 户特征,个性化推荐雏形
非线性模型:树模型及多模型融合,引入更多的上下文特征,加入用 户实时行为反馈,千人千面
深度学习、强化学习与在线学习:对象向量化表达,高维特征 自动提取,特征与模型实时在线更新
 
团队或产品是否应该做推荐功能的决策路径:
• 推荐功能对该产品有无价值(可以) 
• 价值多大(值得) 
• 成本和收益(现在) 
• 优先级(怎样)
比如推荐系统的实时性做到毫秒级是否有必要?
 
分群:根据不同维度对用户进行分群,从而进行针对性推荐
新老用户分群
购买力分群
兴趣分群(群体对类目的强偏好和弱偏好均可以在推荐中使用)
 
Query-Item 文本相关性


文本: 词->短语->语义->主题->句法 
图片: 图->标签&向量


或者把query看成id,与其它id做item2vector
 
PPT下载:
链接:https://pan.baidu.com/s/1YnYkWfpHW3-EokxHmoMrEQ 提取码:li44
 
【如何评估搜索的好坏?看哪些指标】
https://ask.zkbhj.com/?/article/356
 
【Go语言核心36讲:6 程序实体那些事儿】
一对不包裹任何东西的花括号{},除了可以代表空的代码块之外,还可以用于表示不包含任何内容的数据结构(或者说数据类型)。
类型断言表达式的语法形式是x.(T)。其中的x代表要被判断类型的值。
  • struct{},它就代表了不包含任何字段和方法的、空的结构体类型。
  • interface{}则代表了不包含任何方法定义的、空的接口类型。
  • 对于一些集合类的数据类型来说,{}还可以用来表示其值不包含任何元素,比如空的切片值string{},以及空的字典值map[int]string{}。

 
类型字面量:所谓类型字面量,就是用来表示数据类型本身的若干个字符。


string是表示字符串类型的字面量,uint8是表示 8 位无符号整数类型的字面量。再复杂一些的就是我们刚才提到的string,用来表示元素类型为string的切片类型,以及map[int]string,用来表示键类型为int、值类型为string的字典类型。


类型转换表达式:T(x)
x可以是一个变量,也可以是一个代表值的字面量(比如1.23和struct{}{}),还可以是一个表达式。
 
类型转换中的几个陷阱:
  1. 对于整数类型值、整数常量之间的类型转换,原则上只要源值在目标类型的可表示范围内就是合法的。
  2. 虽然直接把一个整数值转换为一个string类型的值是可行的,但值得关注的是,被转换的整数值应该可以代表一个有效的 Unicode 代码点,否则转换的结果将会是"�"(仅由高亮的问号组成的字符串值)string(-1)得到的就是?
  3. string类型与各种切片类型之间的互转

 
一个值在从string类型向byte类型转换时代表着以 UTF-8 编码的字符串会被拆分成零散、独立的字节。
一个值在从string类型向rune类型转换时代表着字符串会被拆分成一个个 Unicode 字符。 
//别名类型,实际上是同一个类型
type myString = string

//类型再定义,实际上是两个不同的类型
type myString string
 
【今日放学别走】

Review推荐系统的新版推荐接口。

排查问题,了解netstat命令显示的tcp和tcp6的区别。# netstat -tlnp | grep :22 tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1444/sshd tcp6 0 0 :::22 :::* LISTEN 1444/sshd可以看到,netstat 显示表示 sshd 既监听在 ipv4 的地址,又监听在 ipv6 的地址。tcp6表示监听的是IPv6的。

netstat 只是很真实的显示监听的端口而已,但是需要注意 ipv6 实际上在 Linux 上也支持 ipv4。

https://blog.csdn.net/weixin_4 ... 16951 

什么是变量的深拷贝和浅拷贝?

专业名词zkbhj 发表了文章 • 0 个评论 • 152 次浏览 • 2020-07-24 10:41 • 来自相关话题

通用深拷贝和浅拷贝的区别
 

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。

 假设B复制了A,修改A的时候,看B是否发生变化:

如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)

 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}
参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
  查看全部
通用深拷贝和浅拷贝的区别
 


深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用


 假设B复制了A,修改A的时候,看B是否发生变化:


如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)


 浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
 
Go语言中的深浅拷贝
 
1、深拷贝(Deep Copy):

拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。

值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。

2、浅拷贝(Shallow Copy):

拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。

引用类型的数据,默认全部都是浅复制,Slice,Map。
 
深拷贝示例:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {
fmt.Println("深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

fmt.Println("修改Robot1的Name属性值")
robot1.Name = "小白-X型-V1.1"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, &robot2)

}
深拷贝 内容一样,改变其中一个对象的值时,另一个不会变化。
Robot 1:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
修改Robot1的Name属性值
Robot 1:{小白-X型-V1.1 白色 小型} 内存地址:0xc000072330
Robot 2:{小白-X型-V1.0 白色 小型} 内存地址:0xc000072360
浅拷贝示例2:
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 使用new方式")
//new方式返回的是一个指针,是引用类型,所以会触发浅拷贝
robot1 := new(Robot)
robot1.Name = "小白-X型-V1.0"
robot1.Color = "白色"
robot1.Model = "小型"

robot2 := robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小蓝-X型-V1.2"
robot1.Color = "蓝色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)
}
浅拷贝 使用new方式
Robot 1:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
Robot 2:&{小白-X型-V1.0 白色 小型} 内存地址:0xc000068330
在这里面修改Robot1的Name和Color属性
Robot 1:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
Robot 2:&{小黑-X型-V1.2 黑色 小型} 内存地址:0xc000068330
另外一种浅拷贝方式:&取指针赋值
package main

import (
"fmt"
)

// 定义一个Robot结构体
type Robot struct {
Name string
Color string
Model string
}

func main() {

fmt.Println("浅拷贝 内容和内存地址一样,改变其中一个对象的值时,另一个同时变化。")
robot1 := Robot{
Name: "小白-X型-V1.0",
Color: "白色",
Model: "小型",
}
robot2 := &robot1
fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

fmt.Println("在这里面修改Robot1的Name和Color属性")
robot1.Name = "小黑-X型-V1.1"
robot1.Color = "黑色"

fmt.Printf("Robot 1:%s\t内存地址:%p \n", robot1, &robot1)
fmt.Printf("Robot 2:%s\t内存地址:%p \n", robot2, robot2)

}

参考文档:
https://www.cnblogs.com/mikeCao/p/8710837.html
https://www.cnblogs.com/guichenglin/p/12736203.html
 

#每日精进#2020年7月24日

总结zkbhj 发表了文章 • 0 个评论 • 95 次浏览 • 2020-07-24 10:30 • 来自相关话题

【Go函数理解】
https://ask.zkbhj.com/?/article/352

【理解变量的深拷贝和浅拷贝】
https://ask.zkbhj.com/?/article/354
 
【Go HTTPClient包坑】
// An error is returned if there were too many redirects or if there 
// was an HTTP protocol error. A non-2xx response doesn't cause an 
// error. Any returned error will be of type *url.Error. The url.Error 
// value's Timeout method will report true if request timed out or was 
// canceled.
非200状态码并不会抛出Error,需要手动显示处理





  查看全部
【Go函数理解】
https://ask.zkbhj.com/?/article/352

理解变量的深拷贝和浅拷贝】
https://ask.zkbhj.com/?/article/354
 
【Go HTTPClient包坑】
// An error is returned if there were too many redirects or if there 
// was an HTTP protocol error. A non-2xx response doesn't cause an 
// error. Any returned error will be of type *url.Error. The url.Error 
// value's Timeout method will report true if request timed out or was 
// canceled.
非200状态码并不会抛出Error,需要手动显示处理

QQ截图20200724175822.jpg

 

理解Golang中的函数、方法、闭包的本质

GoLangzkbhj 发表了文章 • 0 个评论 • 106 次浏览 • 2020-07-24 10:27 • 来自相关话题

函数的本质

在go的世界中,函数是一等公民,可以给变量赋值,可以作为参数传递,也可以直接赋值。
在go语言中将这样的变量、参数、返回值,即在堆空间和栈空间中绑定函数的值,称为function value。
函数的指令在编译期间生成,使用go tool compile -S main.go可以获取汇编代码, 以OSX 10.15.6,go 1.14为例,将看到下述汇编代码(下面只引用部分)...
"".B STEXT nosplit size=1 args=0x8 locals=0x0
0x0000 00000 (main.go:9) TEXT "".B(SB), NOSPLIT|ABIInternal, $0-8
0x0000 00000 (main.go:9) PCDATA $0, $-2
0x0000 00000 (main.go:9) PCDATA $1, $-2
0x0000 00000 (main.go:9) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
0x0000 00000 (main.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:9) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:11) PCDATA $0, $-1
0x0000 00000 (main.go:11) PCDATA $1, $-1
0x0000 00000 (main.go:11) RET
0x0000 c3
...运行时将存放在__TEXT段中,也就是存放在代码段中,读写权限为rx/rwx, 通过vmmap [pid]可以获取运行时的内存分布

==== Non-writable regions for process 13443
REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP] PRT/MAX SHRMOD PURGE REGION DETAIL
__TEXT 0000000001000000-0000000001167000 [ 1436K 1436K 0K 0K] r-x/rwx SM=COW .../test

使用otool -v -l [file]可以看到下述内容(下面只引用了一部分)...
Load command 1
cmd LC_SEGMENT_64
cmdsize 632
segname __TEXT
vmaddr 0x0000000001000000
vmsize 0x0000000000167000
fileoff 0
filesize 1470464
maxprot rwx
initprot r-x
nsects 7
flags (none)
Section
sectname __text
segname __TEXT
addr 0x0000000001001000
size 0x000000000009c365
offset 4096
align 2^4 (16)
reloff 0
nreloc 0
type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
reserved1 0
reserved2 0
...所以如果要问函数在go语言里的本质是什么,那么其实就是指向__TEXT段内存地址的一个指针。
 
函数调用的过程

在go语言中,每一个goroutine持有一个连续栈,栈基础大小为2kb,当栈大小超过预分配大小后,会触发栈扩容,也就是分配一个大小为当前栈2倍的新栈,并且将原来的栈拷贝到新的栈上。使用连续栈而不是分段栈的目的是,利用局部性优势提升执行速度,原理是CPU读取地址时会将相邻的内存读取到访问速度比内存快的多级cache中,地址连续性越好,L1、L2、L3 cache命中率越高,速度也就越快。

在go中,和其他一些语言有所不同,函数的返回值、参数都是由被caller保存。每次函数调用时,会在caller的栈中压入函数返回值列表、参数列表、函数返回时的PC地址,然后更改bp和pc为新函数,执行新函数,执行完之后将变量存到caller的栈空间中,利用栈空间中保存的返回地址和caller的栈基地址,恢复pc和sp回到caller的执行过程。

对于栈变量的访问是通过bp+offset的方式来访问,而对于在堆上分配的变量来说,就是通过地址来访问。在go中,变量被分配到堆上还是被分配到栈上是由编译器在编译时根据逃逸分析决定的,不可以更改,只能利用规则尽量让变量被分配到栈上,因为局部性优势,栈空间的内存访问速度快于堆空间访问。
 方法的本质

go里面其实方法就是语法糖,请看下述代码,两个Println打印的结果是一样的,实际上Method就是将receiver作为函数的第一个参数输入的语法糖而已,本质上和函数没有区别。type T struct {
name string
}

func (t T) Name() string {
return "Hi! " + t.name
}

func main() {
t := T{name: "test"}
fmt.Println(t.Name()) // Hi! test
fmt.Println(T.Name(t)) // Hi! test
}
 闭包的本质

前面已经提到在go语言中将这在堆空间和栈空间中绑定函数的值,称为function value。这也就是闭包在go语言中的实体。一个最简单的funcval实际上是通过二级指针指向__TEXT代码段上函数的结构体。

那我们来看下面这个闭包,也就是main函数中的变量ffunc getFunc() func() int {
a := 0
return func() int {
a++
return a
}
}

func main() {
f := getFunc()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}上面这段代码执行完后会输出1~10,也就是说f在执行的时候所使用的a会累计,但是a并不是一个全局变量,为什么f就变成了一个有状态的函数呢?其实这也就是go里面的闭包了。那我们来看go是如何实现闭包的。

首先来解释一下闭包的含义,闭包在实现上是一个结构体,需要存储函数入口和关联环境,关联环境包含约束变量(函数内部变量)和自由变量(函数外部变量,在函数外被定义,但是在函数内被引用,如例子中的变量a),和函数不同的是,在捕获闭包时才能确定自由变量,当脱离了捕捉变量的上下文时,也能照常运行。基于闭包可以很容易的定义异步调用的回调函数。

在 go 语言中,闭包的状态是通过捕获列表实现的。具体来说,有自由变量的闭包funcval的分配都在堆上,(没有自由变量的funcval在__DATA数据段上,和常量一样),funcval中除了包含地址以外,还会包含所引用的自由变量,所有自由变量构成捕获列表。对于会被修改的值,捕获的是值的指针,对于不会被修改的值,捕获的是值拷贝。
 
https://mp.weixin.qq.com/s/96Df9YGykE6daDREli9W1A
  查看全部
函数的本质

在go的世界中,函数是一等公民,可以给变量赋值,可以作为参数传递,也可以直接赋值。
在go语言中将这样的变量、参数、返回值,即在堆空间和栈空间中绑定函数的值,称为function value。
函数的指令在编译期间生成,使用go tool compile -S main.go可以获取汇编代码, 以OSX 10.15.6,go 1.14为例,将看到下述汇编代码(下面只引用部分)
...
"".B STEXT nosplit size=1 args=0x8 locals=0x0
0x0000 00000 (main.go:9) TEXT "".B(SB), NOSPLIT|ABIInternal, $0-8
0x0000 00000 (main.go:9) PCDATA $0, $-2
0x0000 00000 (main.go:9) PCDATA $1, $-2
0x0000 00000 (main.go:9) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
0x0000 00000 (main.go:9) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:9) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (main.go:11) PCDATA $0, $-1
0x0000 00000 (main.go:11) PCDATA $1, $-1
0x0000 00000 (main.go:11) RET
0x0000 c3
...
运行时将存放在__TEXT段中,也就是存放在代码段中,读写权限为rx/rwx, 通过vmmap [pid]可以获取运行时的内存分布

==== Non-writable regions for process 13443
REGION TYPE START - END [ VSIZE RSDNT DIRTY SWAP] PRT/MAX SHRMOD PURGE REGION DETAIL
__TEXT 0000000001000000-0000000001167000 [ 1436K 1436K 0K 0K] r-x/rwx SM=COW .../test

使用otool -v -l [file]可以看到下述内容(下面只引用了一部分)
...
Load command 1
cmd LC_SEGMENT_64
cmdsize 632
segname __TEXT
vmaddr 0x0000000001000000
vmsize 0x0000000000167000
fileoff 0
filesize 1470464
maxprot rwx
initprot r-x
nsects 7
flags (none)
Section
sectname __text
segname __TEXT
addr 0x0000000001001000
size 0x000000000009c365
offset 4096
align 2^4 (16)
reloff 0
nreloc 0
type S_REGULAR
attributes PURE_INSTRUCTIONS SOME_INSTRUCTIONS
reserved1 0
reserved2 0
...
所以如果要问函数在go语言里的本质是什么,那么其实就是指向__TEXT段内存地址的一个指针。
 
函数调用的过程

在go语言中,每一个goroutine持有一个连续栈,栈基础大小为2kb,当栈大小超过预分配大小后,会触发栈扩容,也就是分配一个大小为当前栈2倍的新栈,并且将原来的栈拷贝到新的栈上。使用连续栈而不是分段栈的目的是,利用局部性优势提升执行速度,原理是CPU读取地址时会将相邻的内存读取到访问速度比内存快的多级cache中,地址连续性越好,L1、L2、L3 cache命中率越高,速度也就越快。

在go中,和其他一些语言有所不同,函数的返回值、参数都是由被caller保存。每次函数调用时,会在caller的栈中压入函数返回值列表、参数列表、函数返回时的PC地址,然后更改bp和pc为新函数,执行新函数,执行完之后将变量存到caller的栈空间中,利用栈空间中保存的返回地址和caller的栈基地址,恢复pc和sp回到caller的执行过程。

对于栈变量的访问是通过bp+offset的方式来访问,而对于在堆上分配的变量来说,就是通过地址来访问。在go中,变量被分配到堆上还是被分配到栈上是由编译器在编译时根据逃逸分析决定的,不可以更改,只能利用规则尽量让变量被分配到栈上,因为局部性优势,栈空间的内存访问速度快于堆空间访问
 方法的本质

go里面其实方法就是语法糖,请看下述代码,两个Println打印的结果是一样的,实际上Method就是将receiver作为函数的第一个参数输入的语法糖而已,本质上和函数没有区别。
type T struct {
name string
}

func (t T) Name() string {
return "Hi! " + t.name
}

func main() {
t := T{name: "test"}
fmt.Println(t.Name()) // Hi! test
fmt.Println(T.Name(t)) // Hi! test
}

 闭包的本质

前面已经提到在go语言中将这在堆空间和栈空间中绑定函数的值,称为function value。这也就是闭包在go语言中的实体。一个最简单的funcval实际上是通过二级指针指向__TEXT代码段上函数的结构体。

那我们来看下面这个闭包,也就是main函数中的变量f
func getFunc() func() int {
a := 0
return func() int {
a++
return a
}
}

func main() {
f := getFunc()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
上面这段代码执行完后会输出1~10,也就是说f在执行的时候所使用的a会累计,但是a并不是一个全局变量,为什么f就变成了一个有状态的函数呢?其实这也就是go里面的闭包了。那我们来看go是如何实现闭包的。

首先来解释一下闭包的含义,闭包在实现上是一个结构体,需要存储函数入口和关联环境,关联环境包含约束变量(函数内部变量)和自由变量(函数外部变量,在函数外被定义,但是在函数内被引用,如例子中的变量a),和函数不同的是,在捕获闭包时才能确定自由变量,当脱离了捕捉变量的上下文时,也能照常运行。基于闭包可以很容易的定义异步调用的回调函数。

在 go 语言中,闭包的状态是通过捕获列表实现的。具体来说,有自由变量的闭包funcval的分配都在堆上,(没有自由变量的funcval在__DATA数据段上,和常量一样),funcval中除了包含地址以外,还会包含所引用的自由变量,所有自由变量构成捕获列表。对于会被修改的值,捕获的是值的指针,对于不会被修改的值,捕获的是值拷贝
 
https://mp.weixin.qq.com/s/96Df9YGykE6daDREli9W1A
 

我的阅读分享:《无证之罪》

读后感zkbhj 发表了文章 • 0 个评论 • 157 次浏览 • 2020-07-24 10:02 • 来自相关话题

阅读书目:《无证之罪》
作者:紫金陈 著
书籍类型:侦探推理小说
页数:366页
阅读开始时间:2020年7月24日
阅读结束时间:2020年8月1日14:00:00
如何发现这本书:西西弗书店
阅读次数:第1次
阅读类型:精读
推荐等级:★★★★
阅读建议:这本书也是在刷完了网剧版(秦昊、邓家佳主演,《无证之罪》8.2分 )之后来读的原著。电视剧版还是做了很大的改动的,整个作品作为侦探推理小说来讲还是可圈可点的,一名技术精尖的法医犯罪,可以把所有证据消灭掉;一个辞职了刑警队长的大学数学教授,可以把高次方程的理论代入到案件的推导中。最后的结局说实话竟然是团灭,真的没有想到。怎么说,个人不是很喜欢这个结局。喜欢推理的同学,可以一看。

豆瓣地址:https://book.douban.com/subject/25799686/






  查看全部


阅读书目:《无证之罪》
作者:紫金陈 著
书籍类型:侦探推理小说
页数:366页
阅读开始时间:2020年7月24日
阅读结束时间:2020年8月1日14:00:00
如何发现这本书:西西弗书店
阅读次数:第1次
阅读类型:精读
推荐等级:★★★★
阅读建议:这本书也是在刷完了网剧版(秦昊、邓家佳主演,《无证之罪》8.2分 )之后来读的原著。电视剧版还是做了很大的改动的,整个作品作为侦探推理小说来讲还是可圈可点的,一名技术精尖的法医犯罪,可以把所有证据消灭掉;一个辞职了刑警队长的大学数学教授,可以把高次方程的理论代入到案件的推导中。最后的结局说实话竟然是团灭,真的没有想到。怎么说,个人不是很喜欢这个结局。喜欢推理的同学,可以一看。

豆瓣地址:https://book.douban.com/subject/25799686/



7c8b6809d28830e9.jpg

 

Golang中的并发安全

GoLangzkbhj 发表了文章 • 0 个评论 • 121 次浏览 • 2020-07-23 17:05 • 来自相关话题

并发安全

并发安全也叫线程安全,在并发中出现了数据的丢失,称为并发不安全。

map和slice都是并发不安全的。
 

slice在并发执行中不会报错,但是数据会丢失;
map在并发执行中会直接报错(fatal error: concurrent map writes)。

 
切片并发不安全

场景: 10000个协程同时添加切片
var s []int

func appendValue(i int) {
s = append(s, i)
}

func main() {
for i := 0; i < 10000; i++ { //10000个协程同时添加切片
go appendValue(i)
}

for i, v := range s { //同时打印索引和值
fmt.Println(i, ":", v)
}
}输出:

 
没有到9999,说明有数据丢失

解决方法: 加锁
var s []int
var lock sync.Mutex //互斥锁
func appendValue(i int) {
lock.Lock() //加锁
s = append(s, i)
lock.Unlock() //解锁
}

func main() {
for i := 0; i < 10000; i++ {
go appendValue(i)
}
//sort.Ints(s) //给切片排序,先排完序再打印,和下面一句效果相同
time.Sleep(time.Second) //间隔1s再打印,防止一边插入数据一边打印时数据乱序
for i, v := range s {
fmt.Println(i, ":", v)
}
}
总结: slice在并发执行中不会报错,但是数据会丢失

map并发不安全

场景: 2个协程同时读和写
func main() {
m := make(map[int]int)
go func() { //开一个协程写map
for i := 0; i < 10000; i++ {
m[i] = i
}
}()

go func() { //开一个协程读map
for i := 0; i < 10000; i++ {
fmt.Println(m[i])
}
}()

//time.Sleep(time.Second * 20)
for {
;
}
}输出:

 
解决方法:尽量不要做map的并发,如果用并发要加锁,保证map的操作要么读,要么写。
var lock sync.Mutex
func main() {
m:=make(map[int]int)
go func() { //开一个协程写map
for i:=0;i<10000 ;i++ {
lock.Lock() //加锁
m[i]=i
lock.Unlock() //解锁
}
}()
go func() { //开一个协程读map
for i:=0;i<10000 ;i++ {
lock.Lock() //加锁
fmt.Println(m[i])
lock.Unlock() //解锁
}
}()
time.Sleep(time.Second*20)
}

总结: map在并发执行中会直接报错

原文链接:https://blog.csdn.net/weixin_4 ... 97247 查看全部
并发安全

并发安全也叫线程安全,在并发中出现了数据的丢失,称为并发不安全。

map和slice都是并发不安全的。
 


slice在并发执行中不会报错,但是数据会丢失;
map在并发执行中会直接报错(fatal error: concurrent map writes)。


 
切片并发不安全

场景: 10000个协程同时添加切片
var s []int

func appendValue(i int) {
s = append(s, i)
}

func main() {
for i := 0; i < 10000; i++ { //10000个协程同时添加切片
go appendValue(i)
}

for i, v := range s { //同时打印索引和值
fmt.Println(i, ":", v)
}
}
输出:

 
没有到9999,说明有数据丢失

解决方法: 加锁
var s []int
var lock sync.Mutex //互斥锁
func appendValue(i int) {
lock.Lock() //加锁
s = append(s, i)
lock.Unlock() //解锁
}

func main() {
for i := 0; i < 10000; i++ {
go appendValue(i)
}
//sort.Ints(s) //给切片排序,先排完序再打印,和下面一句效果相同
time.Sleep(time.Second) //间隔1s再打印,防止一边插入数据一边打印时数据乱序
for i, v := range s {
fmt.Println(i, ":", v)
}
}
总结: slice在并发执行中不会报错,但是数据会丢失

map并发不安全

场景: 2个协程同时读和写
func main() {
m := make(map[int]int)
go func() { //开一个协程写map
for i := 0; i < 10000; i++ {
m[i] = i
}
}()

go func() { //开一个协程读map
for i := 0; i < 10000; i++ {
fmt.Println(m[i])
}
}()

//time.Sleep(time.Second * 20)
for {
;
}
}
输出:

 
解决方法:尽量不要做map的并发,如果用并发要加锁,保证map的操作要么读,要么写。
var lock sync.Mutex
func main() {
m:=make(map[int]int)
go func() { //开一个协程写map
for i:=0;i<10000 ;i++ {
lock.Lock() //加锁
m[i]=i
lock.Unlock() //解锁
}
}()
go func() { //开一个协程读map
for i:=0;i<10000 ;i++ {
lock.Lock() //加锁
fmt.Println(m[i])
lock.Unlock() //解锁
}
}()
time.Sleep(time.Second*20)
}

总结: map在并发执行中会直接报错

原文链接:https://blog.csdn.net/weixin_4 ... 97247

什么是RPC?

专业名词zkbhj 发表了文章 • 0 个评论 • 161 次浏览 • 2020-07-23 16:52 • 来自相关话题

1.1 基本概念
 
RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?
 简单总结,RPC需要以下三个步骤来完成:

1、首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
2、客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
3、数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。

总结一下上述过程:
// Client端
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client





在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。






由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。

1.2 rpc demo










 
完整源码:https://github.com/guangxush/wheel/tree/master/RPC/src
 
2. gRPC的使用

2.1. gRPC与REST 
REST通常以业务为导向,将业务对象上执行的操作映射到HTTP动词,格式非常简单,可以使用浏览器进行扩展和传输,通过JSON数据完成客户端和服务端之间的消息通信,直接支持请求/响应方式的通信。不需要中间的代理,简化了系统的架构,不同系统之间只需要对JSON进行解析和序列化即可完成数据的传递。但是REST也存在一些弊端,比如只支持请求/响应这种单一的通信方式,对象和字符串之间的序列化操作也会影响消息传递速度,客户端需要通过服务发现的方式,知道服务实例的位置,在单个请求获取多个资源时存在着挑战,而且有时候很难将所有的动作都映射到HTTP动词。正是因为REST面临一些问题,因此可以采用gRPC作为一种替代方案,gRPC 是一种基于二进制流的消息协议,可以采用基于Protocol Buffer的IDL定义grpc API,这是Google公司用于序列化结构化数据提供的一套语言中立的序列化机制,客户端和服务端使用HTTP/2以Protocol Buffer格式交换二进制消息。gRPC的优势是,设计复杂更新操作的API非常简单,具有高效紧凑的进程通信机制,在交换大量消息时效率高,远程过程调用和消息传递时可以采用双向的流式消息方式,同时客户端和服务端支持多种语言编写,互操作性强;不过gRPC的缺点是不方便与JavaScript集成,某些防火墙不支持该协议。注册中心:当项目中有很多服务时,可以把所有的服务在启动的时候注册到一个注册中心里面,用于维护服务和服务器之间的列表,当注册中心接收到客户端请求时,去找到该服务是否远程可以调用,如果可以调用需要提供服务地址返回给客户端,客户端根据返回的地址和端口,去调用远程服务端的方法,执行完成之后将结果返回给客户端。这样在服务端加新功能的时候,客户端不需要直接感知服务端的方法,服务端将更新之后的结果在注册中心注册即可,而且当修改了服务端某些方法的时候,或者服务降级服务多机部署想实现负载均衡的时候,我们只需要更新注册中心的服务群即可。






参考文档:
https://www.jianshu.com/p/7d6853140e13
https://www.jianshu.com/p/eb66b0c4113d
 
  查看全部
1.1 基本概念
 
  • RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务
  • 本地过程调用:如果需要将本地student对象的age+1,可以实现一个addAge()方法,将student对象传入,对年龄进行更新之后返回即可,本地方法调用的函数体通过函数指针来指定。
  • 远程过程调用:上述操作的过程中,如果addAge()这个方法在服务端,执行函数的函数体在远程机器上,如何告诉机器需要调用这个方法呢?

 简单总结,RPC需要以下三个步骤来完成:

1、首先客户端需要告诉服务器,需要调用的函数,这里函数和进程ID存在一个映射,客户端远程调用时,需要查一下函数,找到对应的ID,然后执行函数的代码。
2、客户端需要把本地参数传给远程函数,本地调用的过程中,直接压栈即可,但是在远程调用过程中不再同一个内存里,无法直接传递函数的参数,因此需要客户端把参数转换成字节流,传给服务端,然后服务端将字节流转换成自身能读取的格式,是一个序列化和反序列化的过程。
3、数据准备好了之后,如何进行传输?网络传输层需要把调用的ID和序列化后的参数传给服务端,然后把计算好的结果序列化传给客户端,因此TCP层即可完成上述过程,gRPC中采用的是HTTP2协议。

总结一下上述过程:
// Client端 
// Student student = Call(ServerAddr, addAge, student)
1. 将这个调用映射为Call ID。
2. 将Call ID,student(params)序列化,以二进制形式打包
3. 把2中得到的数据包发送给ServerAddr,这需要使用网络传输层
4. 等待服务器返回结果
5. 如果服务器调用成功,那么就将结果反序列化,并赋给student,年龄更新

// Server端
1. 在本地维护一个Call ID到函数指针的映射call_id_map,可以用Map<String, Method> callIdMap
2. 等待服务端请求
3. 得到一个请求后,将其数据包反序列化,得到Call ID
4. 通过在callIdMap中查找,得到相应的函数指针
5. 将student(params)反序列化后,在本地调用addAge()函数,得到结果
6. 将student结果序列化后通过网络返回给Client

QQ截图20200723155851.jpg


在微服务的设计中,一个服务A如果访问另一个Module下的服务B,可以采用HTTP REST传输数据,并在两个服务之间进行序列化和反序列化操作,服务B把执行结果返回过来。

QQ截图20200723155953.jpg


由于HTTP在应用层中完成,整个通信的代价较高,远程过程调用中直接基于TCP进行远程调用,数据传输在传输层TCP层完成,更适合对效率要求比较高的场景,RPC主要依赖于客户端和服务端之间建立Socket链接进行,底层实现比REST更复杂。

1.2 rpc demo

QQ截图20200723160048.jpg


QQ截图20200723160056.jpg

 
完整源码:https://github.com/guangxush/wheel/tree/master/RPC/src
 
2. gRPC的使用

2.1. gRPC与REST 
  • REST通常以业务为导向,将业务对象上执行的操作映射到HTTP动词,格式非常简单,可以使用浏览器进行扩展和传输,通过JSON数据完成客户端和服务端之间的消息通信,直接支持请求/响应方式的通信。不需要中间的代理,简化了系统的架构,不同系统之间只需要对JSON进行解析和序列化即可完成数据的传递。
  • 但是REST也存在一些弊端,比如只支持请求/响应这种单一的通信方式,对象和字符串之间的序列化操作也会影响消息传递速度,客户端需要通过服务发现的方式,知道服务实例的位置,在单个请求获取多个资源时存在着挑战,而且有时候很难将所有的动作都映射到HTTP动词。
  • 正是因为REST面临一些问题,因此可以采用gRPC作为一种替代方案,gRPC 是一种基于二进制流的消息协议,可以采用基于Protocol Buffer的IDL定义grpc API,这是Google公司用于序列化结构化数据提供的一套语言中立的序列化机制,客户端和服务端使用HTTP/2以Protocol Buffer格式交换二进制消息
  • gRPC的优势是,设计复杂更新操作的API非常简单,具有高效紧凑的进程通信机制,在交换大量消息时效率高,远程过程调用和消息传递时可以采用双向的流式消息方式,同时客户端和服务端支持多种语言编写,互操作性强;不过gRPC的缺点是不方便与JavaScript集成,某些防火墙不支持该协议。
  • 注册中心:当项目中有很多服务时,可以把所有的服务在启动的时候注册到一个注册中心里面,用于维护服务和服务器之间的列表,当注册中心接收到客户端请求时,去找到该服务是否远程可以调用,如果可以调用需要提供服务地址返回给客户端,客户端根据返回的地址和端口,去调用远程服务端的方法,执行完成之后将结果返回给客户端。这样在服务端加新功能的时候,客户端不需要直接感知服务端的方法,服务端将更新之后的结果在注册中心注册即可,而且当修改了服务端某些方法的时候,或者服务降级服务多机部署想实现负载均衡的时候,我们只需要更新注册中心的服务群即可。


QQ截图20200723160510.jpg


参考文档:
https://www.jianshu.com/p/7d6853140e13
https://www.jianshu.com/p/eb66b0c4113d