• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

LockGit/Go: leetcode with golang && go practice

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

LockGit/Go

开源软件地址:

https://github.com/LockGit/Go

开源编程语言:

Go 100.0%

开源软件介绍:

Go

以下为相关问题记录的目录:(可以复制相关问题关键字在本网页中搜索查看具体描述)
  • 数据结构与算法(汇总,一些算法的golang实现,本项目algorithm文件夹下)
  • 中英文翻译互转 (抓的有道chrome扩展数据包)
  • 命令行下的dns信息工具 (golang简单实现类似dig的功能)
  • 实现socks5代理来访问看上去不能访问的资源 (网络边界突破,实现类似lcx,ngrok的功能……)
  • 服务编排 && 容器调度,k8s与mesos在实际项目中的应用 (系统架构)
  • 一次针对线上微服务goroutine泄露的问题排查 (goroutine leak)
  • 常用寄存器(假设是x86架构) && 常用汇编指令
  • golang对于两个数之和取一半的汇编代码差异(如何防止整型溢出)
  • 两种cache淘汰策略 lru.go (nginx,redis,squid中都有lru的身影)
  • 内存对齐在golang中的表现 (证明cpu读取内存是按块读取的)
  • golang shell tools (golang实现的交互式shell,实现一个简易版的交互终端)
  • golang实现一个正向后门 (backdoor,基于go的正向后门)
  • golang实现一个反向后门 (backdoor,基于go的反向后门)
  • golang实现一致性hash (从单节点走向分布式节点)
  • golang实现类似nginx基于权重的upstream的轮询调度(负载均衡)
  • golang的互斥锁如何实现公平
  • etcd watch机制的补充 (微服务)
  • 微服务系统中的优雅升级
  • 讨论rpc && 调用链追踪
  • 限流(固定时间窗口限流,滑动时间窗口限流,令牌桶,漏桶) && 熔断
  • 敏感词的检测 (tire树,ac自动机)
  • 布隆过滤器 && hash表 && Btree
  • golang的map,interface,defer,return,goroutine,slice
  • golang的两个nil可能不相等
  • golang的两个空结构体比较为什么不相等 (逃逸分析)
  • golang mcrypt_rijndael_256 aes解密 (填坑记录)
  • http-proxy.go 使用go实现一个http代理
  • golang的gc机制
  • redis相关数据结构及如何将具体内存地址有关的数据结构存储到磁盘中 && 二叉搜索树的索引如何存储
  • diff算法(寻找diff的过程抽象成表示为图的搜索,类似git diff的差异比较,复杂!)

数据结构与算法(汇总,一些算法的golang实现,本项目algorithm文件夹下)

名称 代码位置
数组 algorithm/05_array (数组的内部实现)
链表 algorithm/06_linked_op_rev (链表操作,反转,递归反转等)
链表 algorithm/07_lined_loop_merge (包含链表合并,链表环的检测等)
队列 algorithm/08_queue (包含单调队列,循环队列等)
algorithm/09_stack (包含单调栈,计算器等)
递归 algorithm/10_recursion (递归,回溯中常使用到的方法)
基本排序 algorithm/11_sort (原地排序,稳定排序与非稳定排序)
合并快排 algorithm/12_sort (快速+归并,分治思想)
基数桶排 algorithm/13_sort (基数+桶排+希尔)
计数排序 algorithm/14_sort (计算排序的优化版)
二分搜索 algorithm/15_binary_search (递归+非递归的二分)
二分搜索变体 algorithm/16_binary_search (基于二分的变体,左右边界搜索)
跳表 algorithm/17_skiplinked (一个活跃在redis中的底层数据结构)
哈希表 algorithm/18_22_lru_hash_table (hash表的与链表的组合应用lru)
二叉树 algorithm/23_binary_tree (二叉树的相关操作,最近公共祖先)
二叉搜索树 algorithm/24_binary_search_tree (数据索引中常用数据结构)
algorithm/28_heap (一个完全二叉树,优先级队列的常用数据结构)
优先级队列 algorithm/29_heap_priority_queue (基于堆的优先级队列实现)
algorithm/30_31_graph (例如git diff 功能最终就是抽象为图的搜索)
字符串bf,rk,bm算法 algorithm/32_33_bf_rk_bm_string (暴力搜索+bm模式串搜索[后缀匹配])
kmp算法 algorithm/34_kmp_string ([前缀匹配],对比bm,bm属于贪心算法,适应于实际应用。kmp是稳定算法,不在乎特例。)
trie树 algorithm/35_36_trie (一个常用与敏感词检测的数据结构)
贪心 algorithm/37_greedy (当下是最好的结果,最终也将是最好的结果)
回溯 algorithm/38_39_traceback (暴力穷举,无限回溯)
动态规划 algorithm/40_42_dynamic_plan (上帝视角,自底向上递推结果)
位图 algorithm/45_bitmap (布隆过滤器,redis等常用的数据结构)
滑动窗口 algorithm/46_slide_window (一个常用与字符串处理的方法)
技巧杂项汇总 algorithm/47_summary (各种数据结构杂项组合)

Chinese and English translation tools in the command line(命令行下中英文翻译工具)

dict is a command line tool by go

一键安装(OneKey Install)

wget https://raw.githubusercontent.com/LockGit/Go/master/go-dict/dict.go && go build -o /usr/local/bin/dict dict.go 

使用(Usage)

英译中(English To Chinese)

~ dict I love you
您的输入: I love you
*******英汉翻译:*******
我爱你。
*******网络释义:*******
I Love You : 我爱你
I really love you : 真的爱你
I Do love you : 我是爱你的

中译英(Chinese To English)

~ dict 我爱你
您的输入: 我爱你
*******英汉翻译:*******
I love you
*******网络释义:*******
我爱你 : I Love You
我也爱你 : I Love You Too
我就爱你 : The Arrangement

命令行下的dns信息工具

一键安装(OneKey Install)

wget https://raw.githubusercontent.com/LockGit/Go/master/fdns/FindDnsRecordFast.go && go build -o /usr/local/bin/fdns FindDnsRecordFast.go 

使用(Usage)

实现socks5代理来访问看上去不能访问的资源

假设有如下网络情况:
a----->b<------>c

a可以访问b
b不能访问a
b与c之间可以互相访问
实现c访问a上的服务

具体实现:
proxy_server.go 为服务端,proxy_client.go 为客户端
在b上运行:go run proxy_server.go 8000 7999 (其中8000,7999为自定义监听的端口)
在a上运行:go run proxy_client.go b的ip:8000  (其实是a主动连接b的8000端口,为后续的流转发铺垫)
然后c使用b ip的7999端口作为socks5代理即可访问a上的内容
更多场景,可自行探索

服务编排 && 容器调度,k8s与mesos在实际项目中的应用 (系统架构)

k8s,mesos作为服务编排,容器调度的框架之一,在实际项目中也都有涉及,最开始mesos在公司内部兴起的时间早于k8s,后期渐渐被k8s赶上,
毕竟k8s有着Google的背景,也是当前比较热门的服务编排与容器调度方案,我也把应用从mesos迁移到了k8s之上。
容器化的好处除了环境的一致性,在动态扩缩容时也变的十分方便,包括在发生升级故障时支持快速的的版本回退等一些列好处,
在实际工作场景中也经受了亿级流量,万级QPS的压力考验。
mesos体系:

dns --> lvs四层代理 --> nginx-lb七层代理 --> marathon-lb --> go micro api网关 ---> micro service

这是早期mesos时代的流量流向,marathon-lb 是所有流量进入mesos集群的入口,后面的api网关流量来自 marathon-lb,
其实 marathon-lb 也可以认为是一层应用层代理。
而后面的 api网关 在流量进入微服务体系之前有统一入口,可用作 鉴权,熔断,限流 等操作。

k8s体系:

dns --> lvs四层代理 --> nginx-lb七层代理 --> ingress --> services ---> go micro api网关 --> pod (micro service)

k8s中的services是针对pod的一层抽象层,对于完全无状态的应用确实可以部分实例部署在mesos上,部分部署在k8s上。
当前我使用的微服务框架而言不可以这样,本身微服务基于etcd做为服务发现,而k8s集群与mesos集群中的节点ip本身就是隔离开的,
上报到etcd中的节点ip之间自然是网络不通的,如果存在容器间的服务实例调用,那么在网络层面就肯定是失败的。
对与存在类似依赖的就必须全部迁移比较合适,而不是部分运行在mesos上,部分运行在k8s上。

一次针对线上微服务goroutine泄露的问题排查

发现线上服务在运行一段时间(快1个月)之后,老是会出现莫名其妙的故障,比如基于twitter的snowflakeId发号器服务会出现发号失败,
平常的一些基于go-micro应用服务也会有一些响应不过来,导致最终504错误,因为微服务加入了prometheus相关metrics,通过grafana
监控发现有几个应用实例的goroutine数量在几十万之上,起初我一直以为是微服务框架在服务发现上可能存在未知bug,
所以出现这种问题,一般由于业务的重要性,不可能长时间保留现场,刚开始的一次,通过重新发布应用到mesos,问题快速解决。
但是一直没有时间查看具体原因。最近发现了第二次此类情况,于是着重对于此情况分析了一遍并暂时确定该问题的成因为goroutine泄露。

背景:
  微服务框架基于go-micro v2.9.1,起初在etcd集群之前增加了一个grpc proxy,对外使用vip,grpc proxy可以优化一些watch。
对于应用中有大量watch的地方,使用grpc proxy是可以起到一定的优化作用,但是grpc proxy存在缓存,go-micro结合这个在升级服务
时,可能会cache一些老的实例,所以后面把3个grpc proxy中的2个实例暂时移除了。

复现:
  在测试环境通过pprof也可以看到goutine的数量在随着时间的增长而增长,只不过测试环境由于开始测试需要,经常重新编译重启,导致一时
不能快速看到goutine的增长。
  访问:http://127.0.0.1:8090/debug/pprof/goroutine?debug=1 定位到第一个goutine的代码为框架中的memory.go文件96行发出
于是顺着这个文件翻了一下框架源码。

修复:
翻看框架的源码发现,我在之前针对etcd由于网络io,磁盘io等等问题重新选主,导致的watch通道关闭后,
kv变化无效导致应用无法感知到etcd的kv变化的问题做过一次优化(这个地方看像是框架watch异常了没有再次watch导致)
因为可能的原因有很多,当时使用了定时补偿应用针对etcd的watch通道关闭后 kv变化无效问题:
大致代码如下:(每1分钟重新获取最新的kv,用于针对watch失效的补充,如果这里不用这个方法那么就只能动框架的watch代码来修改尝试,作为watch补充机制,当前规模场景中影响还不大)
tk := time.NewTicker(1 * time.Minute)
defer tk.Stop()
for {
    <-tk.C
    err := Conf.Load(etcdSource)
    if err != nil {
        logrus.Fatal("reload etcd config error: %s", err.Error())
    }
}
项目中使用到了以上代码,问题也就出现在了以上代码之中:Conf.Load(etcdSource) 
https://github.com/asim/go-micro/blob/v2.9.1/config/loader/memory/memory.go#L306
之中可以看到Load方法的实现,其中go m.watch(idx, source),在不停的创建协程又得不到释放
for _, source := range sources {
    set, err := source.Read()
    ...
    go m.watch(idx, source)
}
所以运行一段时间后,这个定时补偿的watch机制就会给系统创建大量的goroutine,当前的机制相当于每1分钟增加一个goroutine。
所以线上服务在运行一段时间之后,会出现莫名其妙的故障,这个应该就是问题的成因了,我错误的使用了Load方法,这个方法只需要
在应用初始化的时候执行一次就可以了!
在这个文件中(https://github.com/asim/go-micro/blob/v2.9.1/config/loader/memory/memory.go#L209)
我发现了一个Sync方法,它的描述是:Sync loads all the sources, calls the parser and updates the config
正好是满足我的需求的。
修复方法就是将 Conf.Load(etcdSource) 改成 Conf.Sync()即可,一段时间后通过监控并没有发现goroutine数量随着时间的递增儿递增,
而是恢复到了正常的阈值,不像之前动辄几十万goroutine
线上go_memstats_heap_ojbjects监控如下:

常用寄存器(假设是x86架构) && 常用汇编指令

记忆方法:e开头,第二个字母为英文单词首字母

寄存器名 名称 主要功能
eax 累加寄存器 (accumulation) 运算(计算机中没有减法)
ebc 基址寄存器(basic) 存储内存地址
ecx 计数寄存器 (count) 计算循环次数
edx 数据寄存器(data) 存储数据
esi 源基址寄存器(source) 存储数据发送源的内存地址
edi 目的基址寄存器(destination) 存储数据发送目标的内存地址
ebp 扩展基址指针寄存器(basic) 存储数据存储领域基点的内存地址
esp 扩展栈指针寄存器(stack) 存储栈中最高位数据内存地址
mov ebp,esp
mov eax,dword ptr [ebp+8]
mov ebp,esp中,esp寄存器中的值直接存储在了ebp中,也就是,如果esp寄存器的值是100的话,那么ebp中寄存器的值也是100。
而在 mov eax,dword ptr [ebp+8] 这条指令中,ebp寄存器的值+8后会被解析称为内存地址。如果ebp=100,那么eax寄存器就是100+8地址的值。
dword ptr 也叫做 double word pointer,简单的解释就是从内存地址中读取4字节的数据。

pop,push为cpu出栈,入栈指令,假设在32位x86系列CPU中,一次push或pop即可处理32位(4字节)的数据。

_DATA 初始化的全局变量,汇总命名到_DATA的段定义中
_BSS 没有初始化的全局变量,汇总命名到_BSS的段定义中
_TEXT 汇编代码
操作码 操作数 功能
add A,B A,B相加赋值给A
call A 调用函数A
cmp A,B A,B比较,结果自动存入标志寄存器
inc A 对A的值+1
ige 标签名 与cmp组合使用,跳转到标签行
jl 标签名 与cmp组合使用,跳转到标签行
jle 标签名 与cmp组合使用,跳转到标签行
jmp 标签名 与cmp组合使用,跳转到标签行
mov A,B 把B的值赋给A
pop A 从栈中读取数值并存入A
push A 把A的值存入栈中
ret 将处理返回到调用源
xor A,B A,B的位进行异或运算,结果存入A
		xor ebx,ebx		;	将寄存器清0
@4		call _MyFunc		;	调用MyFunc函数
		inc ebx			;	ebx寄存器的值+1
		cmp	ebx,10		;	将ebx寄存器的值和10比较
		jl	short @4	; 	如果小于10就跳转到 @4

golang对于两个数之和取一半的汇编代码差异

以下两份代码,作用都是对于取两个数之和取一半(left < right)

mid1.go

func main() {
	left := 1000
	right := 2000
	mid := (left + right) / 2
	_ = mid
}

mid2.go (防止整形溢出)

func main() {
	left := 1000
	right := 2000
	mid := left + (right - left) >> 1
	_ = mid
}
在禁止内联和禁止编译器优化的场景下生成的汇编代码对比
go tool compile -N -l -S mid1.go 
go tool compile -N -l -S mid2.go 
对比可以发现,在禁止内联和禁止编译器优化的场景下。
mid2.go生成的汇编指令比mid1.go生成的汇编指令少一次cpu指令操作,且两边的操作指令是有差异的。
测试以上代码时运行环境(mac os x86_64)

假如left,right值分别如下,那么mid.go在执行时会int溢出,而mid2.go成功执行
left := uint64(2)
right := uint64(18446744073709551614)

对与两个数之和取一半防止整形溢出可以使用:left + (right - left) >> 1 
即right-left之后进行左移1位然后加上left

两种cache淘汰策略 algorithm/lru.go

1,如果数据最近被访问过,那么将来被访问的几率也更高(访问后,把当前节点调整到链表头,新加入的Cache直接加到链表头中)
2,如果访问的频率越高,将来被访问的几率更高。(需要一个计数器,计数器排序后,调整链表位置,淘汰无用Cache,如果要实现O(1)复杂度,需要结合hash表)
go run lru.go

内存对齐在golang中的表现

思考:声明一个结构体的时候,结构体的内部成员顺序是否会对内存的占用产生影响?
首先了解一下:bool,int8,uint8,int32,uint32,int64,uint64,byte,string 类型占用多少字节。
fmt.Printf("bool size: %d\n", unsafe.Sizeof(bool(true)))
fmt.Printf("int8 size: %d\n", unsafe.Sizeof(int8(0)))
fmt.Printf("uint8 size: %d\n", unsafe.Sizeof(uint8(0)))
fmt.Printf("int32 size: %d\n", unsafe.Sizeof(int32(0)))
fmt.Printf("uint32 size: %d\n", unsafe.Sizeof(uint32(0)))
fmt.Printf("int64 size: %d\n", unsafe.Sizeof(int64(0)))
fmt.Printf("uint64 size: %d\n", unsafe.Sizeof(uint64(0)))
fmt.Printf("byte size: %d\n", unsafe.Sizeof(byte(0)))
fmt.Printf("string size: %d\n", unsafe.Sizeof("lock"))
得到如下:
bool size: 1
int8 size: 1
uint8 size: 1
int32 size: 4
uint32 size: 4
int64 size: 8
uint64 size: 8
byte size: 1
string size: 16

那么下面这个结构体理论上共占用 1+4+1+8+1=15 byte
type Test struct {
	a bool  // 1 byte
	b int32 // 4 byte
	c int8  // 1 byte
	d int64 // 8 byte
	e byte  // 1 byte
}

执行如下:
test := Test{}
fmt.Println(unsafe.Sizeof(test))
//output 32 
最终输出为占用32个字节,这与前面所预期的结果完全不一样,这充分地说明了先前的计算方式是错误的!

更改结构体成员顺序:
type Test2 struct {
	e byte  // 1 byte
	c int8  // 1 byte
	a bool  // 1 byte
	b int32 // 4 byte
	d int64 // 8 byte
}

执行:
test2 := Test2{}
fmt.Println(unsafe.Sizeof(test2))
//output 16 
最终输出为占用16个字节!两次输出结果不一样。

分析: CPU读取内存是一块一块读取的,块的大小可以为 2、4、6、8、16 字节等大小。

针对第一个输出32byte的实验:

成员变量 类型 偏移量 自身占用
a bool 0 1
字节对齐 1 3
b int32 4 4
c int8 8 1
字节对齐 9 7
d int64 16 8
e byte 24 1
字节对齐 25 7
总占用大小 - - 32

针对第二个输出16byte的实验:

成员变量 类型 偏移量 自身占用
e byte 0 1
c int8 1 1
a bool 2 1
字节对齐 3 1
b int32 4 4
d int64 8 8
总占用大小 - - 16

以上过程由于字节对齐原因, 也就是说CPU读取内存是一块一块读取的,而不是一次读取一个offset,所以造成了两次结果不一致。

golang shell tools (golang实现的交互式shell,实现一个简易版的交互终端)

本项目shell文件夹下
使用golang实现的shell工具

golang 实现一个正向后门 (backdoor,基于go的正向后门)

本项目backdoor目录下
例如:
    服务端执行:go run backdoor_server.go 0:9999 开启后门程序,等待连接
    客户端执行:go run backdoor_client.go 0:9999 链接后门,执行任意命令

golang 实现一个反向后门 (backdoor,基于go的反向后门)

本项目backdoor_reverse目录下
例如:
    客户端执行:nc -l 9999 客户端监听自己的9999端口,等待shell反弹
    服务端执行:go run backdoor_reverse.go 0:9999 服务端将shell反弹至客户端监听的端口
与正向后门的区别是反向后门客户端自己需要提前做好端口监听等待shell反弹

golang实现一致性hash (从单节点走向分布式节点)

本项目hash_node文件下
解决数据倾斜问题,引入虚拟节点,hash环
虚拟节为一个真实节点对应多个虚拟节点。虚拟节点扩充了节点的数量,解决了节点较少的情况下数据容易倾斜的问题。  

golang实现类似nginx基于权重的upstream的轮询调度(负载均衡)

参看本项目下的round_node文件夹下的算法实现
实际场景中有很多基于权重的选择节点算法,比如nginx的加权轮询,参看本项目下的round_node中的算法实现
1,每个节点用它们的当前值加上自己的权重。
2,选择当前值最大的节点为选中节点,并把它的当前值减去所有节点的权重总和。     

golang的互斥锁如何实现公平

初版的Mutex:
Russ Cox在2008年在github commit记录中是提交第一版的Mutex实现。
通过cas对Mutex结构体的key字段进行加一, 如果key的值是从0加到1, 则直接获得了锁。否则通过semacquire进行sleep, 被唤醒的时候就获得了锁。

2012年,commit dd2074c8 做了一次大的改动:
将waiter count(等待者的数量)和锁标识分开来(内部实现还是合用使用state字段),新来的goroutine占优势,会有更大的机会获取锁。

2015年,commit edcad863
Go1.5中mutex实现为全协作式的,增加了spin机制,一旦有竞争,当前goroutine就会进入调度器。在临界区执行很短的情况下可能不是最好的解决方案。

2016年,commit 0556e262
Go1.9中增加了饥饿模式,让锁变得更公平,不公平的等待时间限制在1毫秒,并且修复了一个大bug,唤醒的goroutine总是放在等待队列的尾部会导致更加不公平的等待时间。

目前mutex实现是相当的复杂的,互斥锁有两种状态:正常状态和饥饿状态。
在正常状态下,所有等待锁的goroutine按照FIFO顺序等待。唤醒的goroutine不会直接拥有锁,而是会和新请求锁的goroutine竞争锁的拥有。
新请求锁的goroutine具有优势:它正在CPU上执行,而且可能有好几个,所以刚刚唤醒的goroutine有很大可能在锁竞争中失败。
在这种情况下,这个被唤醒的goroutine会加入到等待队列的前面。 如果一个等待的goroutine超过1ms没有获取锁,那么它将会把锁转变为饥饿模式。
在饥饿模式下,锁的所有权将从unlock的gorutine直接交给交给等待队列 

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
samber/lo: 发布时间:2022-06-13
下一篇:
d0k3/GodMode9: GodMode9 Explorer - A full access file browser for the Nintendo 3 ...发布时间:2022-06-13
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap