每日新闻

每日新闻

GoCN每日新闻资讯
有问必答

有问必答

Go相关的问题,技术相关的问题
文章分享

文章分享

技术文章分享,让知识传播给更多的人
招聘应聘

招聘应聘

为Gopher服务的招聘应聘平台

Goland代码有自动补全吗?

有问必答zhangbitao 回复了问题 • 4 人关注 • 4 个回复 • 254 次浏览 • 2019-11-20 18:49 • 来自相关话题

多用户环境下GOPATH的设置问题

有问必答stirlingx 回复了问题 • 3 人关注 • 3 个回复 • 149 次浏览 • 2019-12-05 10:55 • 来自相关话题

GoCN每日新闻(2019-12-12)

每日新闻逍姚继凯 回复了问题 • 2 人关注 • 1 个回复 • 4680 次浏览 • 3 天前 • 来自相关话题

httplib.Post请求触发

开源程序spf 回复了问题 • 2 人关注 • 2 个回复 • 109 次浏览 • 2019-12-05 17:01 • 来自相关话题

GoCN每日新闻(2019-12-02)

每日新闻h12 回复了问题 • 2 人关注 • 1 个回复 • 13439 次浏览 • 2019-12-02 19:06 • 来自相关话题

GoCN每日新闻(2019-12-11)

回复

每日新闻yulibaozi 发起了问题 • 1 人关注 • 0 个回复 • 5111 次浏览 • 5 天前 • 来自相关话题

如何查一批数据的缓存?

有问必答astaxie 回复了问题 • 2 人关注 • 1 个回复 • 161 次浏览 • 2019-11-20 23:31 • 来自相关话题

GoCN每日新闻(2019-11-29)

回复

每日新闻DennisMao 发起了问题 • 1 人关注 • 0 个回复 • 13961 次浏览 • 2019-11-29 17:35 • 来自相关话题

beego 什么时候支持grpc

有问必答lrita 回复了问题 • 1 人关注 • 1 个回复 • 251 次浏览 • 2019-11-22 14:07 • 来自相关话题

Dev 日志 | 一次 Segmentation Fault 和 GCC Illegal Instruction 编译问题排查

文章分享NebulaGraph 发表了文章 • 0 个评论 • 794 次浏览 • 2019-11-20 10:33 • 来自相关话题

摘要

笔者最近在重新整理和编译 Nebula Graph 的第三方依赖,选出两个比较有意思的问题给大家分享一下。

Flex Segmentation Fault——Segmentation fault (core dumped)

在编译 Flex 过程中,遇到了 Segmentation fault:

make[2]: Entering directory '/home/dutor/flex-2.6.4/src'
./stage1flex -o stage1scan.c ./scan.l
make[2]: *** [Makefile:1696: stage1scan.c] Segmentation fault (core dumped)

使用 gdb 查看 coredump:

Core was generated by `./stage1flex -o stage1scan.c ./scan.l'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 flexinit (argc=4, argv=0x7ffd25bea718) at main.c:976
976 action_array[0] = '\0';
(gdb) disas
Dump of assembler code for function flexinit:
0x0000556c1b1ae040 <+0>: push %r15
0x0000556c1b1ae042 <+2>: lea 0x140fd(%rip),%rax # 0x556c1b1c2146
...
0x0000556c1b1ae20f <+463>: callq 0x556c1b1af460 <allocate_array> #这里申请了buffer
...
=> 0x0000556c1b1ae24f <+527>: movb $0x0,(%rax) # 这里向buffer[0]写入一个字节,地址非法,挂掉了
...
(gdb) disas allocate_array
Dump of assembler code for function allocate_array:
0x0000556c1b1af460 <+0>: sub $0x8,%rsp
0x0000556c1b1af464 <+4>: mov %rsi,%rdx
0x0000556c1b1af467 <+7>: xor %eax,%eax
0x0000556c1b1af469 <+9>: movslq %edi,%rsi
0x0000556c1b1af46c <+12>: xor %edi,%edi
0x0000556c1b1af46e <+14>: callq 0x556c1b19a100 <reallocarray@plt> # 调用库函数申请内存
0x0000556c1b1af473 <+19>: test %eax,%eax # 判断是否为 NULL
0x0000556c1b1af475 <+21>: je 0x556c1b1af47e <allocate_array+30># 跳转至NULL错误处理
0x0000556c1b1af477 <+23>: cltq # 将 eax 符号扩展至 rax,造成截断
0x0000556c1b1af479 <+25>: add $0x8,%rsp
0x0000556c1b1af47d <+29>: retq
...
End of assembler dump.

可以看到,问题出在了 allocate_array 函数。因为 reallocarray 返回指针,返回值应该使用 64 bit 寄存器rax,但 allocate_array 调用 reallocarray 之后,检查的却是 32 bit 的 eax,同时使用 cltq 指令将 eax 符号扩展 到 rax。原因只有一个:allocate_array 看到的 reallocarray 的原型,与 reallocarry 的实际定义不符。翻看编译日志,确实找到了 implicit declaration of function 'reallocarray' 相关的警告。configure 阶段添加 CFLAGS=-D_GNU_SOURCE 即可解决此问题。

注:此问题不是必现,但编译/链接选项 -pie 和 内核参数 kernel.randomize_va_space 有助于复现。

总结:

  • 隐式声明的函数在 C 中,返回值被认为是 int
  • 关注编译器告警,-Wall -Wextra 要打开,开发模式下最好打开 -Werror。

GCC Illegal Instruction——internal compiler error: Illegal instruction

前阵子,接到用户反馈,在编译 Nebula Graph 过程中遭遇了编译器非法指令的错误,详见(#978)[https://github.com/vesoft-inc/nebula/issues/978]

错误信息大概是这样的:

Scanning dependencies of target base_obj_gch
[ 0%] Generating Base.h.gch
In file included from /opt/nebula/gcc/include/c++/8.2.0/chrono:40,
from /opt/nebula/gcc/include/c++/8.2.0/thread:38,
from /home/zkzy/nebula/nebula/src/common/base/Base.h:15:
/opt/nebula/gcc/include/c++/8.2.0/limits:1599:7: internal compiler error: Illegal instruction
min() _GLIBCXX_USE_NOEXCEPT { return FLT_MIN; }
^~~
0xb48c5f crash_signal
../.././gcc/toplev.c:325
Please submit a full bug report,
with preprocessed source if appropriate.

既然是 internal compiler error,想必是 g++ 本身使用了非法指令。为了定位具体的非法指令集及其所属模块,我们需要复现这个问题。幸运的是,下面的代码片段就能触发:

#include <thread>
int main()
{
return 0;
}

非法指令一定会触发 SIGILL,又因为 g++ 只是编译器的入口,真正干活的是 cc1plus。我们可以使用 gdb 来运行编译命令,抓住子进程使用非法指令的第一现场:

$ gdb --args /opt/nebula/gcc/bin/g++ test.cpp
gdb> set follow-fork-mode child
gdb> run
Starting program: /opt/nebula/gcc/bin/g++ test.cpp
[New process 31172]
process 31172 is executing new program: /opt/nebula/gcc/libexec/gcc/x86_64-pc-linux-gnu/8.2.0/cc1plus
Thread 2.1 "cc1plus" received signal SIGILL, Illegal instruction.
[Switching to process 31172]
0x00000000013aa0fb in __gmpn_mul_1 ()
gdb> disas
...
0x00000000013aa086 <+38>: mulx (%rsi),%r10,%r8
...

Bingo!mulx 属于 BMI2 指令集,报错机器 CPU 不支持该指令集。
仔细调查,引入该指令集的是 GCC 的依赖之一,GMP。默认情况下,GMP 会在 configure 阶段探测当前机器的 CPU 具体类型,以期最大化利用 CPU 的扩展指令集,提升性能,但却牺牲了二进制的可移植性。解决方法是,在 configure 之前,使用代码目录中的 configfsf.guess configfsf.sub 替换或者覆盖默认的 config.guess 和 config.sub

总结:

  • 某些依赖可能因为性能或者配置的原因,造成二进制的不兼容。
  • 缺省参数下,GCC 为了兼容性,不会使用较新的指令集。
  • 为了平衡兼容性和性能,你需要做一些额外的工作,比如像 glibc 那样在运行时选择和绑定某个具体实现。

最后,如果你想尝试编译一下 Nebula 源代码可参考以下方式:

bash> git clone https://github.com/vesoft-inc/nebula.git
bash> cd nebula && ./build_dep.sh N

有问题请在 GitHub 或者微信公众号上留言,也可以添加 Nebula 小助手微信号:NebulaGraphbot 为好友反馈问题~

附录


GoCN每日新闻(2019-12-16)

回复

文章分享smallfish1 发起了问题 • 1 人关注 • 0 个回复 • 101 次浏览 • 6 小时前 • 来自相关话题

go 学习笔记之咬文嚼字带你弄清楚什么是 defer延迟函数

文章分享snowdreams1006 发表了文章 • 0 个评论 • 862 次浏览 • 2019-11-19 18:53 • 来自相关话题

https://mp.weixin.qq.com/s/t5tmqsjZ4y4Z_n6u4c9bMw通过本文,我们知道了延迟函数的执行时机以及一些细节,关键是理解 Each time a "defer" ...查看全部

https://mp.weixin.qq.com/s/t5tmqsjZ4y4Z_n6u4c9bMw

通过本文,我们知道了延迟函数的执行时机以及一些细节,关键是理解 Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. 这句话,绝对是重中之重!


简而言之,延迟函数在声明时会收集相关参数赋值拷贝一份入栈,时机合适时再从入栈环境中寻找相关环境参数,如果找不到就扩大范围寻找外层函数是否包含所需变量,执行过程也就是延迟函数的出栈.


有一个消防员专门负责保卫商场的安全,每天商场进进出出很多人流,总有一些重要人物也会来到商场购物,突然有一天,发生了火灾,正在大家惊慌失措中...


这个消防员到底干了什么才能保证重要人物安全的同时也能让他们不遭受财产损失?


go-error-defer-panic.png
go-error-defer-panic.png

请补充你的答案,感谢你的阅读与关注,下一节再见~


阅读延伸以及参考文档



  • Defer_statements[3]
  • Built-in_functions[4]
  • Go 语言规格说明书 之 内建函数(Built-in functions)[5]
  • go 语言快速入门:内建函数(6)[6]
  • 你知道 defer 的坑吗?[7]
  • golang 语言 defer 特性详解.md[8]
  • Golang 之轻松化解 defer 的温柔陷阱[9]

参考资料



[1]

go 学习笔记之解读什么是defer延迟函数: https://mp.weixin.qq.com/s/XttOuCEk7kgySKLOCqVMRQ



[2]

defer关键字: https://tiancaiamao.gitbooks.io/go-internals/content/zh/03.4.html



[3]

Defer_statements: https://golang.google.cn/ref/spec#Defer_statements



[4]

Built-in_functions: https://golang.google.cn/ref/spec#Built-in_functions



[5]

Go语言规格说明书 之 内建函数(Built-in functions): https://www.cnblogs.com/luo630/p/9669966.html



[6]

go语言快速入门:内建函数(6): https://blog.csdn.net/liumiaocn/article/details/54804074



[7]

你知道defer的坑吗?: https://www.jianshu.com/p/9a7364762714



[8]

golang语言defer特性详解.md: https://www.jianshu.com/p/57acdbc8b30a



[9]

Golang之轻松化解defer的温柔陷阱: https://segmentfault.com/a/1190000018169295





Go开发者调查2019

技术讨论kevin 发表了文章 • 1 个评论 • 323 次浏览 • 2019-11-21 10:10 • 来自相关话题

Todd Kulesza20 November 2019Help shape the future of GoSince 2016, thousands of Gop ...查看全部

Todd Kulesza
20 November 2019

Help shape the future of Go

Since 2016, thousands of Gophers around the world have helped the Go project by sharing your thoughts via our annual Go Developer Survey. Your feedback has played an enormous role in driving changes to our language, ecosystem, and community, including the gopls language server, new error-handling mechanics, the module mirror, and so much more from the latest Go 1.13 release. And of course, we publicly share each year's results, so we can all benefit from the community's insights.

Today we are launching the 2019 Go Developer Survey. We'd love to hear from everyone who uses Go, used to use Go, or is interested in using Go, to help ensure the language, community, and ecosystem fit the needs of the people closest to it. Please help us shape Go's future by participating in this 15-minute survey by December 15th: Take the 2019 Go Developer Survey.

Spread the word!

We need as many Gophers as possible to participate in this survey to help us better understand our global user base. We'd be grateful if you would spread the word by sharing this post on your social network feeds, around the office, at meet-ups, and in other communities. Thank you!

问卷地址: https://google.qualtrics.com/jfe/form/SV_b1xqnBCMpZAhJZ3


Nebula 架构剖析系列(二)图数据库的查询引擎设计

文章分享NebulaGraph 发表了文章 • 0 个评论 • 761 次浏览 • 2019-11-21 11:10 • 来自相关话题

摘要 上文(存储篇)说到数据库重要的两部分为存储和计算,本篇内容为你解读图数据库 Nebula 在查询引擎 Query Engine 方面的设计实践。 在 Nebula ...查看全部

摘要


上文(存储篇)说到数据库重要的两部分为存储和计算,本篇内容为你解读图数据库 Nebula 在查询引擎 Query Engine 方面的设计实践。


在 Nebula 中,Query Engine 是用来处理 Nebula 查询语言语句(nGQL)。本篇文章将带你了解 Nebula Query Engine 的架构。



上图为查询引擎的架构图,如果你对 SQL 的执行引擎比较熟悉,那么对上图一定不会陌生。Nebula 的 Query Engine 架构图和现代 SQL 的执行引擎类似,只是在查询语言解析器和具体的执行计划有所区别。


Session Manager


Nebula 权限管理采用基于角色的权限控制(Role Based Access Control)。客户端第一次连接到 Query Engine 时需作认证,当认证成功之后 Query Engine 会创建一个新 session,并将该 session ID 返回给客户端。所有的 session 统一由 Session Manger 管理。session 会记录当前 graph space 信息及对该 space 的权限。此外,session 还会记录一些会话相关的配置信息,并临时保存同一 session 内的跨多个请求的一些信息。


客户端连接结束之后 session 会关闭,或者如果长时间没通信会切为空闲状态。这个空闲时长是可以配置的。
客户端的每个请求都必须带上此 session ID,否则 Query Engine 会拒绝此请求。


Storage Engine 不管理 session,Query Engine 在访问存储引擎时,会带上 session 信息。


Parser



Query Engine 解析来自客户端的 nGQL 语句,分析器(parser)主要基于著名的 flex / bison 工具集。字典文件(lexicon)和语法规则(grammar)在 Nebula 源代码的 src/parser  目录下。设计上,nGQL 的语法非常接近 SQL,目的是降低学习成本。 图数据库目前没有统一的查询语言国际标准,一旦 ISO/IEC 的图查询语言(GQL)委员会发布 GQL 国际标准,nGQL 会尽快去实现兼容。
Parser 构建产出的抽象语法树(Abstrac Syntax Tree,简称 AST)会交给下一模块:Execution Planner。


Execution Planner



执行计划器(Execution Planner)负责将抽象树 AST 解析成一系列执行动作 action(可执行计划)。action 为最小可执行单元。例如,典型的 action 可以是获取某个节点的所有邻节点,或者获得某条边的属性,或基于特定过滤条件筛选节点或边。当抽象树 AST 被转换成执行计划时,所有 ID 信息会被抽取出来以便执行计划的复用。这些 ID 信息会放置在当前请求 context 中,context 也会保存变量和中间结果。


Optimization



经由 Execution Planner 产生的执行计划会交给执行优化框架 Optimization,优化框架中注册有多个 Optimizer。Optimizer 会依次被调用对执行计划进行优化,这样每个 Optimizer都有机会修改(优化)执行计划。最后,优化过的执行计划可能和原始执行计划完全不一样,但是优化后的执行结果必须和原始执行计划的结果一样的。


Execution



Query Engine 最后一步是去执行优化后的执行计划,这步是执行框架(Execution Framework)完成的。执行层的每个执行器一次只处理一个执行计划,计划中的 action 会挨个一一执行。执行器也会一些有针对性的局部优化,比如:决定是否并发执行。针对不同的 action所需数据和信息,执行器需要经由 meta service 与storage engine的客户端与他们通信。


最后,如果你想尝试编译一下 Nebula 源代码可参考如下方式:



有问题请在 GitHub(GitHub 地址:github.com/vesoft-inc/nebula) 或者微信公众号上留言,也可以添加 Nebula 小助手微信号:NebulaGraphbot 为好友反馈问题~


推荐阅读


ShowMeBug 核心技术内幕

文章分享rina_gmail 发表了文章 • 0 个评论 • 837 次浏览 • 2019-11-21 11:46 • 来自相关话题

ShowMeBug 是一款远程面试工具,双方可通过在线面试板进行实时沟通技术。所以关键技术要点在于 “实时同步”。关于实时同步,ShowMeBug 采用了以下技术。 OT 转换算法 ...查看全部

ShowMeBug 是一款远程面试工具,双方可通过在线面试板进行实时沟通技术。所以关键技术要点在于 “实时同步”。关于实时同步,ShowMeBug 采用了以下技术。



OT 转换算法


本质上,ShowMeBug 核心就是多人同时在线实时编辑,难点即在这里。因为网络原因,操作可能是异步到达,丢失,与他人操作冲突。想想这就是个复杂的问题。



经过研究,最好用户体验的方式是 OT 转换算法。此算法由 1989 年 C. Ellis 和 S. Gibbs 首次提出,目前像 quip,google docs 均用的此法。



OT 算法允许用户自由编辑任意行,包括冲突的操作也可以很好支持,不用锁定。它的核心算法如下:



文档的操作统一为以下三种类型的操作( Operation ):



  1. retain(n): 保持 n 个字符
  2. insert(s): 插入字符串 s
  3. delete(s): 删除字符串 s


然后客户端与服务端各记录历史版本,每次操作都经过一定的转换后,推送给另一端。



转换的核心是



S(o_1, o_2) = S(o_2, o_1)



换言之,把正在并发的操作进行转换合并,形成新的操作,然后应用在历史版本上,就可以实现无锁化同步编辑。



下图演示了对应的操作转换过程。



https://daotestimg.dao42.com/ipic/070918.jpg



这个算法的难点在于分布式的实现。客户端服务端均需要记录历史,并且保持一定的序列。还要进行转换算法处理。



OT Rails 侧的处理


本质上,这是一个基于 websocket 的算法应用。所以我们没有怀疑就选用 ActionCable 作为它的基础。想着应该可以为我们节省大量的时间。实际上,我们错了。



ActionCable 实际上与 NodeJS 版本的 socket.io 一样,不具备任何可靠性的保障,做一些玩意性的聊天工具还可以,或者做消息通知允许丢失甚至重复推送的弱场景是可以的。但像 OT 算法这种强要求的就不可行了。



因为网络传输的不可靠性,我们必须按次序处理每一个操作。所以首先,我们实现了一个互斥锁,也就是针对某一个面试板,准备一个锁,同时只有一个操作可以进行操作。锁采用了 Redis 锁。实现如下:



  def unlock_pad_history(lock_key)
logger.debug "[padable] unlock( lock_key: #{lock_key} )..."
old_lock_key = REDIS.get(_pad_lock_history_key)
if old_lock_key == lock_key
REDIS.del(_pad_lock_history_key)
else
log = "[FIXME] unlock_pad_history expired: lock_key=#{lock_key}, old_lock_key=#{old_lock_key}"
logger.error(log)
e = RuntimeError.new(log)
ExceptionNotifier.notify_exception(e, lock_key: lock_key, old_lock_key: old_lock_key)
end
end

# 为防止死锁,锁的时间为5分钟,超时自动解锁,但在 unlock 时会发出异常
def lock_pad_history(lock_key)
return REDIS.set(_pad_lock_history_key, lock_key, nx: true, ex: 5*60)
end

def wait_and_lock_pad_history(lock_key, retry_times = 200)
total_retry_times = retry_times
while !lock_pad_history(lock_key)
sleep(0.05)
logger.debug '[padable] locked, waiting 50ms...'
retry_times-=1
raise "wait_and_lock_pad_history(in #{total_retry_times*0.1}s) #{lock_key} failed" if retry_times == 0
end
logger.debug "[padable] locking it(lock_key: #{lock_key})..."
end




服务端的并发控制完毕后,客户端通过 “状态队列” 技术一个个排队发布操作记录,核心如下:



class PadChannelSynchronized {
sendHistory(channel, history){
channel._sendHistory(history)
return new PadChannelAwaitingConfirm(history)
}
}

class PadChannelAwaitingConfirm {
constructor(outstanding_history) {
this.outstanding_history = outstanding_history
}

sendHistory(channel, history){
return new PadChannelAwaitingWithHistory(this.outstanding_history, history)
}

receiveHistory(channel, history){
return new PadChannelAwaitingConfirm(pair_history[0])
}

confirmHistory(channel, history) {
if(this.outstanding_history.client_id !== history.client_id){
throw new Error('confirmHistory error: client_id not equal')
}
return padChannelSynchronized
}
}

class PadChannelAwaitingWithHistory {
sendHistory(channel, history){
let newHistory = composeHistory(this.buffer_history, history)
return new PadChannelAwaitingWithHistory(this.outstanding_history, newHistory)
}
}

let padChannelSynchronized = new PadChannelSynchronized()

export default padChannelSynchronized




以上,便实现了一个排队发送的场景。



除此之外,我们设计了一个 PadChannel 用来专门管理与服务器通信的事件,维护历史的状态,处理断线重传,操作转换与校验。



定义自己的历史(history) 协议


解决了编辑器协同的问题,才是真正的问题的开始。每次的 ”代码运行”,“编辑”,“清空终端”,“首次同步” 都是需要记录的历史操作。于是,ShowMeBug 定义了以下协议:



  # 包含以下: edit( 更新编辑器内容 ), run( 执行命令 ), clear( 清空终端 ), sync( 同步数据 )
# select( 光标 ), locate( 定位 )
# history 格式如下:
#
# {
# op: 'run' | 'edit' | 'select' | 'locate' | 'clear'
# id: id // 全局唯一操作自增id, 首次前端传入时为 null, 服务端进行填充, 如果返回时为空, 则说明此 history 被拒绝写入
# version: 'v1' // 数据格式版本
# prev_id: prev_id // JS端生成 history 时上一次收到服务端的 id, 用于识别操作序列
# client_id: client_id // 客户端生成的 history 的唯一标识
# creator_id: creator_id // 操作人的用户id, 为了安全首次前端传入时为 null,由中台填充
# event: { // op 为 edit 时, 记录编辑器 OT 转化后的数据, see here: https://github.com/Aaaaash/blog/issues/10
# [length, "string", length]
# // op 为 select 时, 记录编辑器选择区域(包括光标)
# }
# snapshot: {
# editor_text: '' // 记录当前编辑器内容快照, 此快照由服务端填充
# language_type: '' // 记录当前编辑器的语言种类
# terminal_text: '' // 记录当前终端快照
# }
# }
# created_at: created_at // 生成时间




值得说明的是,client_id 是客户端生成的一个8位随机码,用于去重和与客户端进行 ACK 确认。



id 是由服务端 Redis 生成的自增 id,客户端会根据这个判断历史是否是新的。prev_id 用来操作转换时记录所需要进行转换操作的历史队列。



event 是最重要的操作记录,我们用 OT 的转换数据进行存储,如: [length, "string", length]



通过上述的设计,我们将面试板的所有操作细节涵盖了,从而实现多人面试实时同步,面试题和面试语言自动同步,操作回放等核心功能。



总结


篇幅限制,这里只讲到 ShowMeBug 的核心技术,更多的细节我们以后继续分享。



ShowMeBug 目前承载了 3000 场面试记录,成功支撑大量的实际面试官的面试,可靠性已得到进一步保障。这里面有两种重要编程范式值得考虑:



  1. 如何在不可信链路上设计一种有序可靠的交付协议,定义清晰的协议数据和处理好异步事件是关键。
  2. 如何平衡研发效率与稳定性之间的关系,比如实现的忙等锁,允许一定原因的失败,但处理好用户的提示与重试。既高效完成了功能,又不影响到用户体验。


ShowMeBug( showmebug.com ) 让你的技术面试更高效,助你找到你想要的候选人。