12月 14, 2015
· Filed under 未分类
今天碰到了一个 recover() 的一个坑。
有一个操作预期可能出现 panic 而且不需要关心它是否panic.就建立了一个匿名函数将操作包裹起来并 追加了 defer recover。 原来都是 defer func(){_=recover()}(),这次省掉了匿名函数,直接 defer recover() 。
package main
func main(){
defer recover()
panic("123")
}
/*
panic: 123
goroutine 1 [running]:
main.main()
D:/golang/src/github.com/gamexg/go-test/鍩烘湰璇硶/recover娴嬭瘯/1/1.g
o:5 +0x97
exit status 2
*/
结果如上,直接挂了。recover 无效…
查看了 recover 的文档:recover 只能放到 defer 函数里面,不能放到子函数。实测直接 defer recover() 也不行。recover 是内置函数,源码需要到go的实现里面查,懒得去查了。
go 的很多设计感觉都是黑魔法,就像这里,如果 panic 是一个协程本地变量保存的,那么在什么地方调用 recover 都行。但是看情况不是,恐怕和函数调用搅到一起了。
v:=dict[key]、 v,ok:=dict[key] 这个设计也是使用的黑魔法,感觉是一开始只设计了第一种实现,但是由于需要获得是否存在key,就另加了第二个实现,很生硬的感觉。
chan 实现也比较奇怪,连接之类的关闭都是 conn.Close() 实现关闭,chan 却是 close(chan),设计并不统一,很别扭。
12月 12, 2015
· Filed under 未分类
有几个地方需要注意:for i + 协程时如果协程使用 i ,那么需要增加 i:=i 来防止多协程冲突;实际执行任务时需要用一个函数包起来,防止单个任务panic造成整个程序崩溃。
package main
import (
"sync"
"fmt"
)
/*
一个标准的协程+信道实现
*/
func main() {
taskChan := make(chan int)
TCount := 10
var wg sync.WaitGroup //创建一个sync.WaitGroup
// 产生任务
go func() {
for i := 0; i < 1000; i++ {
taskChan <- i
}
// 全部任务都输入后关闭信道,告诉工作者进程没有新任务了。
close(taskChan)
}()
// 告诉 WaitGroup 有 TCount 个执行者。
wg.Add(TCount)
// 启动 TCount 个协程执行任务
for i := 0; i < TCount; i++ {
// 注意:如果协程内使用了 i,必须有这一步,或者选择通过参数传递进协程。
// 否则 i 会被 for 所在的协程修改,协程实际使用时值并不确定。
i := i
go func() {
// 协程结束时报告当前协程执行完毕。
defer func() { wg.Done() }()
fmt.Printf("工作者 %v 启动...\r\n", i)
for task := range taskChan {
// 建立匿名函数执行任务的目的是为了捕获单个任务崩溃,防止造成整个工作者、系统崩溃。
func() {
defer func() {
err := recover()
if err != nil {
fmt.Printf("任务失败:工作者i=%v, task=%v, err=%v\r\n", i, task, err)
}
}()
// 故意崩溃,看看是不是会造成整个系统崩溃。
if task%100==0{
panic("故意崩溃啦")
}
// 这里的 task 并不需要通过参数传递进来。
// 原因是这里是同步执行的,并不会被其它协程修改。
fmt.Printf("任务结果=%v ,工作者id=%v, task=%v\r\n",task*task,i,task)
}()
}
fmt.Printf("工作者 %v 结束。\r\n", i)
}()
}
//等待所有任务完成
wg.Wait()
print("全部任务结束")
}
12月 9, 2015
· Filed under python
之前将服务迁移到 docker ,结果发现 socket.io-python-emitter 发送的消息全部发送不出去。经检查发现是 socket.io-redis 0.2.0 做了性能优化,与老版本不兼容了,无奈查源码自己打了个补丁(现已合并至主干)。
最开始是发现网站发起的推送延迟很厉害,而且推送 ping 日志显示丢包达到了80%以上。由于所有推送操作同时使用了极光推送及自建的 socket.io 推送。一开始没想到两套推送方案全部挂了,以为是测试设备出了问题,并且测试时设备日志记录了一次极光推送被强制关闭的log。同时发现极光推送控制台日志显示设备不在线(后来发现近期极光推送非常不稳定,这种延迟是常规状态了,由于原来自建推送可用, 所以没有注意)。
但是经检查发现设备没有问题,而且设备状态上报部分同样使用的 socket.io,也很正常。所以又测试了一下python向网页端推送,发现还是收不到推送,确认python发出的推送出现故障了。
无奈进 redis 数据库查看推送是否进入了数据库,结果发现python发出的推送消息的确进入了redis数据库,但是和socket.io本身发出消息的频道名称不一致。找了一下,没有当时的记录了,大体是执行的redis-cli命令,通过 PSUBSCRIBE * 订阅所有频道。
于是觉得可能是频道名字的问题,所以仿着 socket.io 消息的格式通过 PUBLISH 频道名称 消息 发了个消息,无效,并且命令返回了1,只有手工PSUBSCRIBE * 的订阅,也就是 socket.io 过呢本没有接收消息。觉得是 socket.io 有问题,岁查看代码,发现socket.io-redis 会根据房间名订阅消息,并不会订阅 #emitter 频道的消息,就很纳闷之前的系统为什么能工作。检查了下修订历史,发现是 0.1.4 -> 0.2.0 为了性能只订阅相关房间的消息,看了一下发现 python 的代码是2年前了,和当前的实现不兼容…
无奈只能自己修改了,中间又发现新版本同时还修改了包格式,不过都问题不大。
这次修bug理清了大部分 socket.io-redis 的流程。
socket.io-redis 接收消息是通过订阅 redis 上面相关的频道来实现的,会检查 uid 看看是不是本机发出的,如果不是就转到 socket.io 处理。不过这里有个问题,uid 是随机生成的6位字符串,也就是有可能碰到相同的uid,socket.io-redis 会抛弃相同uid的消息造成相同uid的 socket.io 之间无法通行。
发送消息是通过向 redis 发送消息,然后其他 socket.io-redis 订阅并接收实现的。广播消息会直接发送到 prefix + ‘#’ + packet.nsp + ‘#’ ;发送到指定房间的消息会对涉及到的房间都发送一条 prefix + ‘#’ + packet.nsp + ‘#’ + room + ‘#’ 频道的消息。