由于golang缺乏泛型,在许多情况下,为了代码的灵活性,难免要用到接口甚至空接口,而在对空接口中的数据进行操作之前,通常有一个类型断言的过程。我们经常听到一些“Golang”的反射性能很低的言论,那么与类型操作有关的类型断言这个操作性能到底如何呢?不妨来作一个Benchmark

定义四种操作场景

以下定义了一个类型和一个接口,分别以四种方法 具体类型接口空接口->具体类型空接口->接口 对一组数据进行求和(遍历)

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
package main
type Numer interface {
getNum() int64
}
type Num struct {
num int64
}
func (n *Num) getNum() int64 {
return n.num
}
func main() {
}
func sumConcrete(s []*Num) int64 {
var sum int64
for _, v := range s {
sum += v.getNum()
}
return sum
}
func sumInterface(s []Numer) int64 {
var sum int64
for _, v := range s {
sum += v.getNum()
}
return sum
}
func sum2concrete(s []interface{}) int64 {
var sum int64
for _, v := range s {
sum += v.(*Num).getNum()
}
return sum
}
func sum2interface(s []interface{}) int64 {
var sum int64
for _, v := range s {
sum += v.(Numer).getNum()
}
return sum
}

Benchmark 测试函数

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
package main
import (
"math/rand"
"testing"
)
func BenchmarkSumT(b *testing.B) {
//prepare data
length := 10000
data := make([]*Num, length)
its := make([]Numer, length)
eits := make([]interface{}, length)
for i := 0; i < length; i++ {
data[i] = &Num{int64(rand.Int31n(100))}
its[i] = data[i]
eits[i] = data[i]
}
b.Run("Concrete", func(b *testing.B) {
for i := 0; i < b.N; i++ {
sumConcrete(data)
}
})
b.Run("Interface", func(b *testing.B) {
for i := 0; i < b.N; i++ {
sumInterface(its)
}
})
b.Run("i2concrete", func(b *testing.B) {
for i := 0; i < b.N; i++ {
sum2concrete(eits)
}
})
b.Run("i2interface", func(b *testing.B) {
for i := 0; i < b.N; i++ {
sum2interface(eits)
}
})
}

运行测试

1
2
3
4
5
6
7
8
9
10
➜ vscode go test -bench Sum
goos: linux
goarch: amd64
pkg: pc.chen/test/vscode
BenchmarkSumT/Concrete-4 300000 4881 ns/op
BenchmarkSumT/Interface-4 50000 24830 ns/op
BenchmarkSumT/i2concrete-4 200000 7630 ns/op
BenchmarkSumT/i2interface-4 10000 101115 ns/op
PASS
ok pc.chen/test/vscode 5.645s

结果有些意外,Golang推崇的接口操作相比直接操作具体类型多出4倍多时间, 更意外的是空接口+断言意外比正常接口要快很多!!! 只是比具体类型多出不到一倍的时间。 最后,空接口接口后再调用方法是最慢的,相较接口又多出4倍多(较直接操作具体类型多出20倍多时间),这种场景现实中其实是有使用场景的,如此看来需要尽量在性能敏感的地方避免。

其它

最后,我还测试了直接用Num实现Numer,而不是用指针*Num实现Numer,结果也有微小的差异,其它情况不变的情况下,Benchmark下来成绩大概是:

1
2
3
4
5
6
7
8
9
10
➜ vscode go test -bench Sum
goos: linux
goarch: amd64
pkg: pc.chen/test/vscode
BenchmarkSumT/Num-4 300000 4513 ns/op
BenchmarkSumT/Numer-4 50000 30177 ns/op
BenchmarkSumT/toNum-4 200000 8354 ns/op
BenchmarkSumT/toNumer-4 10000 116636 ns/op
PASS
ok pc.chen/test/vscode 6.147s

如此看来使用指针实现接口性能可以高 15%-20%之间, 还是不可忽略的。

之前开发了一套基于akka cluster的调度|负载均衡系统,运行了半年均较稳定,但最近有节点两次down掉。由于没有太多经验,第一次down掉时经验主义地以为是内存吃紧导致程序异常(由于计算资源紧张,很多服务混布,内存确实非常紧张,时常有类似故障),第二次仔细检查了日志发现如下日志:

1
[WARN] [03/17/2018 21:51:38.769] [cluster-akka.remote.default-remote-dispatcher-91] [akka.remote.Remoting] Tried to associate with unreachable remote address [akka.tcp://cluster@10.104.3.35:7712]. Address is now gated for 5000 ms, all messages to this address will be delivered to dead letters. Reason: [The remote system has quarantined this system. No further associations to the remote system are possible until this system is restarted.]

同时在其它正常的节点上有如下日志

1
[INFO] [03/13/2018 21:36:12.659] [cluster-akka.remote.default-remote-dispatcher-35339] [akka.remote.Remoting] Quarantined address [akka.tcp://cluster@10.104.3.36:7712] is still unreachable or has not been restarted. Keeping it quarantined.

同时master上还有记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[ERROR] [03/17/2018 21:51:37.662] [cluster-akka.remote.default-remote-dispatcher-127527] [akka.remote.Remoting] Association to [akka.tcp://cluster@10.104.3.36:7712] with UID [1258718596] irrecoverably failed. Quarantining address.
java.util.concurrent.TimeoutException: Remote system has been silent for too long. (more than 48.0 hours)
at akka.remote.ReliableDeliverySupervisor$$anonfun$idle$1.applyOrElse(Endpoint.scala:383)
at akka.actor.Actor.aroundReceive(Actor.scala:517)
at akka.actor.Actor.aroundReceive$(Actor.scala:515)
at akka.remote.ReliableDeliverySupervisor.aroundReceive(Endpoint.scala:203)
at akka.actor.ActorCell.receiveMessage(ActorCell.scala:527)
at akka.actor.ActorCell.invoke(ActorCell.scala:496)
at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
at akka.dispatch.Mailbox.run(Mailbox.scala:224)
at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)
[03/17/2018 21:51:37.749] [cluster-akka.actor.default-dispatcher-105] [akka.tcp://cluster@10.104.3.35:7712/system/cluster/core/daemon] Cluster Node [akka.tcp://cluster@10.104.3.35:7712] - Marking node as TERMINATED [akka.tcp://cluster@10.104.3.36:7712], due to quarantine. Node roles [dc-default]

什么是quarantine

字面意思是隔离,(题外话:这个单词‘隔离’含义的起源是有典故的), 那么大致猜测是GC或者网络抖动导致集群认为此节点不健康,被驱逐。于是检索了一下资料。

akka cluster如果判定某节点会损害集群健康,就会把它隔离,可能的原因有如下三种:

  1. System message delivery failure 系统消息传递失败

  2. Remote DeathWatch trigger 远程死亡监控触发

  3. Cluster MemberRemoved event 集群移除节点

解决办法

根据akka的文档,可以调整akka.cluster.failure-detector.threshold来设定判定阈值,来避免因为偶然拉动而导致的误判,但也不宜过大。
另外,为了避免cluster系统与业务线程竞争,可为其设置单独的线程池. 在配置中增加

1
2
3
4
5
6
7
8
9
10
akka.cluster.use-dispatcher = cluster-dispatcher
cluster-dispatcher {
type = "Dispatcher"
executor = "fork-join-executor"
fork-join-executor {
parallelism-min = 2
parallelism-max = 4
}
}

akka.cluster.use-dispatcher的默认配置为空。

最后,以上办法都无法保证节点永远不down,最好的方式还是做好容错。

protobuf是google的语言、平台中立,可扩展的结构数据序列化机制。你可以定义数据结构一次,然后用专门生成的代码来方便地读写结构数据,支持多种语言。

proto3版本支持go语言。

下面以一个简单的例子来演示如何使用proto3来生成Go代码。

大致分为三步

  1. 定义数据结构
  2. 使用Protobuf编译器
  3. 使用Go协议buffer接口读写数据

相对于其它序列化方式,protobuf可以根据.proto格式的模板文件来生成自带高效的二进制格式的codec方法的类,同时类利用getter和setter管理数据读写的细节,
另外重要的数据可以扩展,方便以后的更新而不影响旧有功能。

Read More

前言

反射指程序在运行时检查自身结构(尤其 类型)的能力,是元程序设计的一种形式,同时也是很多人为这迷惑的地方。
本文针对Go来说明反射如何工作,不同语言反射模型不同,有些语言早根本不支持反射,所以本文内容仅适用Go.

类型与接口

因为反射建立在类型系统之上,首先回顾一下Go的类型。

Go是静态类型。 每个变量都有静态类型,意味着,编译时*即有唯一&确定的类型。

1
2
3
4
type MyInt int
var i int
var j MyInt

i与j有着不同的静态类型,它们虽然有着相同uderlying type,但未经强制转换不是不能直接相互赋值的。

接口代表指定的方法集合,是一种重要的类型。接口变量可以存储任何实现了接口所定义方法的类型的变量。比如说io.Reader io.Writer。

1
2
3
4
5
6
7
8
9
// Reader is the interface that wraps the basic Read method.
type Reader interface {
Read(p []byte) (n int, err error)
}
// Writer is the interface that wraps the basic Write method.
type Writer interface {
Write(p []byte) (n int, err error)
}

任何实现了Read方法的类型都可以赋给io.reader。

1
2
3
4
5
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
// and so on

需要明白的是,不管r被赋予何值,其类型永远是io.Reader。因为,Go是静态类型的,而r的静态类型是io.Reader.

一个极其重要的接口类型例子是空接口。

1
interface{}

它代表空方法集合,既然任何类型都有至少0个方法,那么自然都实现了interface{}接口。

有些人认为Go的接口是动态类型的,这是误解。接口也是静态类型的,一个接口变量永远有着固定的静态类型,虽然在运行其存储的值可以被改变类型,但其值一定是满足接口的。

Read More

Getters and Setters

Go也可以创建Getters and Setters,而且它们并不需要以get或set开头。

分号

Go大部分的;可以省略,是因为它的语法分析器会自动为你加上。

短声明

1
2
f, err := os.Open(name)
d, err := f.Stat()

在第二个语句中,d是新建变量,而err是重新赋值。

多重赋值

Go中没有逗号运算符,++--是声明而非语句,但你可以用并列多重赋值来在for循环中操作多个变量

1
2
3
for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 {
a[i], a[j] = a[j], a[i]
}

switch中合并多个case

使用逗号隔开即可

1
2
3
4
5
6
7
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+', '%':
return true
}
return false
}

Read More

简介

本文演示一个简单的Go包开发过程,并介绍`go tool—-获取,build,安装Go包的标准方式。

go tool要求以特定的方式组织代码,请仔细阅读本文,以便以最简单的方式运行安装你的代码。

代码组织

overview

  • 一般将所有代码保存在单个workspace
  • 单个workspace包含多个git仓库
  • 每个git仓库包含一个或多个package
  • 每个package包含一个或多个源文件
  • pacage的目录决定其import path

注意这和其它每个工程有单独的workspace、每个workspace都与git仓库关联的编程环境是不太相同的。

workspace

worksapce一个包含以下三个目录的目录结构。

  • src 源文件
  • pk 包目标文件
  • bin 可执行文件

go toolbuild源文package包到pkg目录,install目标二进制文件到Bin目录。

典型的src目录下包含多个git仓库,以跟踪每个package的开发。

以下例子是一个包含两个package的workspace :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bin/
hello # command executable
outyet # command executable
pkg/
linux_amd64/
github.com/golang/example/
stringutil.a # package object
src/
github.com/golang/example/
.git/ # Git repository metadata
hello/
hello.go # command source
outyet/
main.go # command source
main_test.go # test source
stringutil/
reverse.go # package source
reverse_test.go # test source
golang.org/x/image/
.git/ # Git repository metadata
bmp/
reader.go # package source
writer.go # package source
... (many more repositories and packages omitted) ...

Read More

本文将会介绍什么是mysql事务,以及如何用commit和rollback来管理事务。

mysql事务

为了方便理解,通过向样例数据库中添加一个订单来说明什么是事务:

  • 查询最新的订单号,并使用下一数字作为新增订单号
  • order表中增加订单
  • 在订单详情表orderdetails中添加订单货物
  • 查询orderorderdetails表验证结果

想象一下如果上述步骤中途因为数据库锁或其它原因出错会有什么后果?你可能会得到一个空订单并毫无察觉,你之后可能会要花费巨大的精力去修复这个错误。

mysql事务就能对付这种情况。所谓事务就是一组绝不会部分生效mysql操作:如果中途出错,那么状态会回滚至修改前;如果未出错,当然是一切变动都提交到数据库中。

使用mysql事务

在用事务实现上述用例之前,先了解一下最常用的语句。
start transaction开始事务,rollback撤销操作。
需要注意的是有一些语句是不能在事务中使用的,大部分是数据定义语句。

1
2
3
4
5
6
CREATE / ALTER / DROP DATABASE
CREATE /ALTER / DROP / RENAME / TRUNCATE TABLE
CREATE / DROP INDEX
CREATE / DROP EVENT
CREATE / DROP FUNCTION
CREATE / DROP PROCEDURE

commit将事务中的变动提交到数据库中,mysql会默认提交,如果要改为不自动提交,可以

1
SET autocommit = 0;

Read More

Kafka是由LinkedIn开发的一个分布式的消息系统,使用Scala编写,它以可水平扩展和高吞吐率而被广泛使用。目前越来越多的开源分布式处理系统如Cloudera、Apache Storm、Spark都支持与Kafka集成。

特点

Kafka是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下:

  • 以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上数据也能保证常数时间复杂度的访问性能。
  • 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒100K条以上消息的传输。
  • 支持Kafka Server间的消息分区,及分布式消费,同时保证每个Partition内的消息顺序传输。
  • 同时支持离线数据处理和实时数据处理。
  • Scale out:支持在线水平扩展。
  • 完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现负载均衡;
  • 支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案

    Read More

循环

scala支持whiledo-while循环,与java差别最大的是for循环及中断循环的控制。

1
2
3
4
5
6
7
while(){
}
--------
do{
}while();

范围for

tountil两种,主要差别是后者不包括上界
此处的<-称为generator,因为它在每次循环中产生值。

1
2
3
4
5
6
7
for(i <- 1 to 10){
}// 将循环1,2,3...10,共10次
-----
for(i <- 1 until 10){
}// 将循环1,2,3...9,共9次

多循环

可以在for中使用分隔条件实现多循环,循环条件将从后往前开始计算

1
2
3
4
for( a <- 1 to 3; b <- 1 to 3){
println( "Value of a: " + a );
println( "Value of b: " + b );
}

Read More

访问修饰符

package , class , object 的成员声明时使用privateprotected关键字可改变其访问权限,如果包含访问修饰,则是public

private

私有成员只被该类内部的代码可见。直接看例子。

1
2
3
4
5
6
7
8
9
class Outer {
class Inner {
private def f() { println("f") }
class InnerMost {
f() // OK
}
}
(new Inner).f() // Error: f is not accessible
}

(new Inner).f()非法而InnerMost的调用则是合法,因为InnerMostInner类的内部定义,java会允许内部类访问外部类的私有成员。

protected

scala的protected成员是真正地只被其子类访问。

而在java中,它还可以被同一package下的类访问。

Read More