在 Java 中对一个 Map 按 Key 排序是很简单的一件事(TreeMap)。但是按 Value 排序的话,却略显麻烦。
在 Java 8 之前,对 Map 按 Value 排序一般有两种方法:
1 | // 1. TreeMap |
在 Java 中对一个 Map 按 Key 排序是很简单的一件事(TreeMap)。但是按 Value 排序的话,却略显麻烦。
在 Java 8 之前,对 Map 按 Value 排序一般有两种方法:
1 | // 1. TreeMap |
ELK 是 Elasticsearch、Logstash、Kibana 三个开源软件的组合。在实时数据检索和分析场合,三者通常是配合共用,而且又都先后归于 Elastic.co 公司名下,故有此简称。
ELK 在最近两年迅速崛起,成为机器数据分析,或者说实时日志处理领域,开源界的第一选择。和传统的日志处理方案相比,ELK 具有如下几个优点:
项目后台要加一个统计,产品只要求能看到数据就行了,但是做完后我发现只有文本不够直观,就想着用图表的形式的展现一下(顺便温习一下曾经用过的 JFreeChart XD)。JFreeChat 虽好,但也有些年头了,就去网上搜了搜有没有什么更好的图表生成的类库。
当在8 个华丽而实用的 Java 图表应用看到 ECharts 的时候,发现 JFreeChart 跟它比就不是一个级别的呀!至于开头说的温习嘛,就缓缓吧,先看一下 ECharts。
先看一下用 ECharts 做的柱状图:
Let’s Encrypt 是一个新的数字证书认证机构,它通过自动化的过程消除创建和安装证书的复杂性,为网站提供免费的 SSL/TLS 证书。Let’s Encrypt 项目获得了 Mozilla、思科、Akamai、IdenTrust 和 EFF 等组织的支持,由 Linux 基金会托管。它在9月中旬颁发了第一个证书,已经从12月3日起开放了 Beta 测试,它已经得了 IdenTrust 的交叉签名,这意味着其证书现在已经可以被所有主流的浏览器所信任。
你现在可以在此体验一下使用新的交叉签名的中级证书所签发证书的服务器。
继前两篇文章(Supervisor 基础、监控并报警)后,用 Supervisor 管理单个机器上的进程没啥问题了。但是 A 机器上的 Supervisor 只能管理 A 机器上的进程,管理不了 B 上边的。在当今这个云的时代,大家都玩集群了,如果用其管理的话,需要打开 N 个网页去各各机器上的进程显然是不现实的(OP 们要哭晕在厕所了)。
其实之前仔细看配置文件的话,应该会想到一种解决的办法——通过 XML-RPC 接口。
接触 Supervisor 还是学 Golang 的时候,用于把 Golang 应用的非守护进程转化为守护进程。不过这次到是要把 Supervisor 用到 Java 进程上,具体的操作和 Golang 应用的进程并无差别,毕竟 Supervisor 只要求被管理的进程是非守护进程即可。
Supervisor 的官方标语是
A Process Control System
它是用 Python 实现的,不过它能管理任何非 Daemon 的进程,并会帮你把被管理的进程转成 Daemon 进程(只要你交给 Supervisor 的进程是非 daemon 的,无需在原程序中增加任何用于实现 daemon 的代码,就能实现 daemon)。
是不是感觉很方便?废话不多说,直接动手搞起来。
安装 Supervisor 的方式多种多样,选择自己喜欢的方式吧1
2
3
4
5# easy_install supervisor // yum install python-setuptools
# apt-cache show supervisor // ubuntu
# yum info supervisor // redhat centos
# pip install supervisor --pre // pip >= 1.4
# pip install supervisor // pip < 1.4
还可以直接去官网下载后,python setup.py install
Rsync(remote synchronize)是一个远程数据同步工具,可通过LAN/WAN快速同步多台主机间的文件。Rsync使用所谓的“Rsync算法”来使本地和远 程两个主机之间的文件达到同步,这个算法只传送两个文件的不同部分,而不是每次都整份传送,因此速度相当快。
Rsync本来是用于替代rcp的一个工具,目前由rsync.samba.org维护,所以rsync.conf文件的格式类似于Samba的 主配置文件。Rsync可以通过rsh或ssh使用,也能以daemon模式去运行,在以daemon方式运行时Rsync server会打开一个873端口,等待客户端去连接。连接时,Rsync server会检查口令是否相符,若通过口令查核,则可以开始进行文件传输。第一次连通完成时,会把整份文件传输一次,以后则就只需进行增量备份。
Rsync支持大多数的类Unix系统,无论是Linux、Solaris还是BSD上都经过了良好的测试。此外,它在windows平台下也有相应的版本,如cwRsync和Sync2NAS等工具。
Rsync的基本特点如下:
今天写 Golang 代码时遇到重复声明同名变量的问题,发现这个还是挺有意思的。
这里说的重复声明不是指这种1
2
3x := 1
x := 2
fmt.Println(x)
这样的话肯定是不行的,会报错1
no new variables on left side of :=
而是指像下边这样的,这个是可以正常运行的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// code #1
x := 1
fmt.Println(x, &x)
x, y := 2, "???"
fmt.Println(x, &x, y)
// code #2
if x, y := 3, "!!!"; true {
fmt.Println(x, &x, y)
x := 4
fmt.Println(x, &x, y)
x, y := 5, "@@@"
fmt.Println(x, &x, y)
}
fmt.Println(x, &x, y)
用 Golang 写了一个小功能,想部署到的服务器上。这里就简单记录一下过程吧。
我现在用的 Go 的版本是1.5.1,直接用安装包安装的,Mac OS X 平台。
Golang 的交叉编译还是蛮方便的。
首先用go env
查看一下本机的 go 环境1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16$ go env
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/yibo/golang/lib:/Users/yibo/golang/work"
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GO15VENDOREXPERIMENT=""
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fno-common"
CXX="clang++"
CGO_ENABLED="1"
Docker 颠覆了容器技术,也将容器技术带到了新的高度。对于开发和运维人员(特别是运维),Docker 真是省了不少事儿。就我做开发的来说,比如前几天 mysql 5.7 GA 了,我想体验一下的其 Json 类型的支持,大可不必去下载安装包再在本机上安装(可能之前已经安装了较早版本的 mysql,还得考虑升级),用 docker 两个命令就可以愉快地玩耍了——这只是一个我个人使用中的小例子。Docker 对于部署、对于应用服务的横向扩展开说,都有很大的优势。
Docker 1.9(好像是从1.8的就开始了)没了 Boot2Docker,取而代之的是 Docker Machine。有一段时间没玩 Docker 了,更新个新版试试。不过也没感觉到什么区别,除了命令变了,能创建多个 VM,本质还是需要安装一个 VirtualBox 从而将 Docker 运行于 Linux 虚拟机内——毕竟 docker 还是得通过 linux 内核的 namespace 和 cgroups 机制实现资源隔离及限制。
本文系个人笔记,只做记录,不做详解。Docker 学习可以参考官方文档,或Docker 中文指南
用 LiteIDE 可以方便调试 Go 程序,它是用的 GDB 调试的,如果没有安装 GDB 的话,运行“调试”就会提示:1
211:21:45 GdbDebugger: /usr/local/bin/gdb was not found on system PATH (hint: is GDB installed?)
11:21:45 LiteDebug: Failed to start debugger
下边就说说怎么在 OS X 平台在安装 GDB。
用 Mac 的想必都知道这个了, 如果你还没有安装,去http://brew.sh/ 看看吧,或者直接用下边的命令安装
1 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" |
有了 brew 就方便了,直接执行下边的命令安装 gdb1
$ brew install homebrew/dupes/gdb
如果上边的命令不行的试试下边的这个:1
$ brew install https://raw.github.com/Homebrew/homebrew-dupes/master/gdb.rb
之后运行gdb --version
看看是否安装成功
有了 brew 后,安装 so easy。不过先别高兴的太早,在 LiteIDE 中随便找一个程序“调试”一下,会发现“控制台”中报错:1
2
310000012^error,msg="Unable to find Mach task port for process-id 42864: (os/kern) failure (0x5).
(please check gdb is codesigned - see taskgated(8))"
(gdb)
这是由于 OS X 的安全机制阻止了我们的 GDB 对要调试的程序进行完全控制,所以我们要对 GDB 赋予合适的权限。
首先创建一个系统代码签名信任证书:
打开菜单:钥匙串访问 -> 证书助理 -> 创建证书
输入证书名称,如:gdb-cert;
选择身份类型:自签名根证书 (Identity Type to Self Signed Root)
选择证书类型:代码签名 (Certificate Type to Code Signing)
勾选:让我覆盖这些默认签名 (select the Let me override defaults)
一路next,直到选择存放证书地址,选择:系统。这样证书就创建好了
其次,将证书授予gdb,执行命令
1 | $ which go |
如果你创建的证书名不是gdb-cert
的话注意自行替换
这样授权后,在 LiteIDE 中运行“调试”的话还是会提示错误please check gdb is codesigned
,只要重启一下就好了。
之后我们便可以用 gdb 调试 go 了,但是每次使用的时候都会提示输入管理员密码。这是由于之前制作的证书是在系统下面的,所以每次执行 gdb 都会提示管理员密码。
解决方法也很简单,打开钥匙串访问,将“系统”下面的 gdb-cert 拷贝一份放到“登录”下面就行了。
Go语言中的复合类型包括:数组(array)、切片(slice)、哈希表(map)、结构体(struct)。
函数是 Go 语言里面的核心设计。
这里结合网上的一些资料和自己的学习理解,记录一下,加深理解。
说复合类型之前先说一下指针,这样复合类型里边的一些概念就好理解了。
Go保留了指针,*T
表示T对应的指针类型,如果包含包名, 则应该是*.T
。
代表指针类型的符号*
总是和类型放在一起,而不是紧挨着变量名。
同样支持指针的指针**T
。
1 | var a, b *int |
操作符&
取变量地址,用*
透过指针变量间接访问目标对象
默认值是nil
,没有NULL常量
不支持指针运算,直接’.’选择符操作指针目标对象成员
可以在unsafe.Pointer和任意类型指针间进行转换
可以将unsafe.Pointer转换为uintptr,然后变相做指针运算,uintptr可以转换为整数
1 | package main |
结果:1
2
3100
&{100 Jack}
&{100 Jack} {100 Tom}
在 Go 语言中,数组是一个值类型(value type)。
所有的值类型变量在赋值和作为参数传递时都将产生一个复制动作。
如果作为函数的参数类型,则在函数调用时参数发生数据复制,在函数体中无法修改传入数组的内容。
1 | var VarName [n]type // n>=0 |
数组的长度是该数组类型的一个内置常量1
arrLength := len(arr)
注意,数组长度也是类型的一部分,因此不同长度数组为不同类型(内置常量)
即[3]int
和[4]int
是不同类型,并且数组不能改变长度
数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本(一次复制操作),而不是它的指针,如果要传入指针,使用slice
1 | for i:=0; i < len(array); i++ { |
可以用new
创建数组, 返回一个指向数组的指针1
p := new([10]int)
注意区分1
2
3
4
5
6
7// 指向数组的指针
a := [100]int{}
var p *[100]int = &a
// 指针数组
x, y = 1, 2
a := [...]*int{&x, &y}
切片(类似 Java 中的 ArrayList)就像一个指向数组的指针,但更复杂,实际上它拥有自己的数据结构,而不仅仅是指针(指向原生数组的指针 + 数组切片中元素个数 + 数组切片已分配的存储空间)。
切片是一个引用类型,总是指向一个底层array,声明可以向array一样,只是不需要长度。
slice就像一个结构体,包含三个元素:
通过 array 创建
1 | var myArray [10]int = [10]int{1,2,3,4,5,6,7,8,9,10} |
从数组或已存在的 slice 再次声明
1 | var ar [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} |
直接创建
1 | myslice1 := make([]int, 5) |
1 | for i:=0; i<len(mySlice); i++ { |
len
获取slice的长度cap
获取slice的最大容量
容量需要注意一下,数组值的容量总是等于其长度。而切片值的容量则往往与其长度不同。请看下图。
append
向 slice 里面追加一个或者多个元素,然后返回一个和 slice 一样类型的 slice
1 | //append |
特别注意(这是一个大坑):append
会改变 slice 所引用的数组的内容,从而影响到引用统一数组的其他 slice,但当 slice 中没有剩余空间,此时动态分配新的数组空间返回的 slice 数组指针将指向这个空间,而原数组的内容将保持不变,其他引用此数组的 slice 不受影响
可以用copy
从源 slice 的 src 中复制到目标 dst,并且返回复制元素的个数。1
2
3
4
5
6
7copy(dst, source) //会按短的个数复制
slice1 := []int{1,2,3,4,5}
slice2 := []int{5,4,3}
copy(slice2, slice1) //复制slice1前三个 1 -> 2
copy(slice1, slice2) //复制slice2的前三个 2 -> 1
默认开始位置0,ar[:n]
等价于ar[0:n]
第二个序列默认是数组长度ar[n:]
等价于ar[n:len(ar)]
从一个数组直接获取slice,可以是ar[:]
slice 是引用类型,所以当改变其中元素的时候,其他的所有引用都会改变。
1 | aSlice = array[3:7] |
Map 类型其实是哈希表的一个实现,类似 Java 中的 HashMap、Python 中字典的概念。
map是无序的,长度不固定,内置的len
可以用于 map,可以方便的修改。
初始化一个 Map
1 | map[keyType]valueType |
1 | rating := map[string]float32 {"c":5, "Go":4.5} |
1 | m["1234"] = PersonInfo{} |
1 | delete(m, "1234") |
struct,一组字段的集合,类似其他语言的 class。
Go 放弃了大量包括继承在内的面向对象特性,只保留了组合(composition)这个最基础的特性。
1 | type person struct { |
这是实现类似于继承的一种手段,但是其实不同于继承。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15type Human struct {
name string
age int
weight int
}
tyep Student struct {
Human //匿名字段,默认Student包含了Human的所有字段
speciality string
}
mark := Student(Human{"mark", 25, 120}, "Computer Science")
mark.name
mark.age
能够实现字段继承,当字段名重复的时候,优先取外层的,可以通过指定 struct 名还决定取哪个
1 | mark.Human = Human{"a", 55, 220} |
struct 不仅可以使用 struct 作为匿名字段,自定义类型、内置类型都可以作为匿名字段,而且可以在相应字段上做函数操作
1 | type Rect struct { |
Reciver 默认以值传递,而非引用传递,还可以是指针。
指针作为 Receiver 会对实例对象的内容发生操作,而普通类型作为Receiv er仅仅是以副本作为操作对象,而不对原实例对象发生操作。
如果一个 method 的 receiver 是*T
,调用时,可以传递一个T
类型的实例变量V
,而不必用&V
去调用这个 method
1 | func (r *Rect) Area() float64 { |
采用组合的方式实现继承
1 | type Human struct { |
还可以进行方法重写
1 | func (e *Student) SayHi() { |
make
用于内建类型(map, slice, channel) 的内存分配。
new
用于各种类型的内存分配。new
本质上和其他语言中同名函数一样,new(T)
分配了零值填充的T
类型的内存空间,并返回其地址,即一个*T
类型的值 即,返回一个指针,指向新分配的类型T
的零值。
make(T, args)
,只能创建 slice、map、channel,并返回一个有初始值(非零值)的T
类型,而不是*T
。 本质来讲,导致这三个类型有所不同的原因是,指向数据结构的引用在使用前必须被初始化。
函数是 Go 语言里面的核心设计,通过关键字func
来声明
1 | func funcName(input type1, input2 type2) (output1 type1, output2 type2) { |
1 | //一般函数 |
函数通过关键字func
声明
可以有一个或多个参数,每个参数后面带有类型。通过”,
“分隔函数可以返回多个值
返回值声明,可以只声明类型。
如果没有返回值,可以省略最后的返回信息;如果有返回值,必须在外层添加 return。
Go函数不支持嵌套(nested),重载(overload)和默认参数(default parameters)。
支持:
1. 无需声明原型
2. 不定长度变参
3. 多返回值
4. 命名返回值参数
5. 匿名函数
6. 闭包
注意:
func
开头,左大括号不能另起一行可以像 python 那样返回多个结果,只是非 tuple。对于不想要的返回值,可以扔垃圾桶_
。
如果用命名返回参数,return 语句可以为空。
1 | package main |
如果命名返回参数被代码块中的同名变量覆盖了,就必须使用显式 return 返回结果。
不需要强制命名返回值,但是命名后的返回值可以让代码更加清晰,可读性更强。
指针, Go保留指针,用.
而非”->”操作指针目标对象成员。
操作符:
&
取变量地址
*
通过指针间接访问目标函数
1 | func add1(a int) int { |
传指针的好处:
Go语言中,string、slice、map 这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传指针
注意,若函数需要改变 slice 长度,仍需要取地址传指针
变参本质上就是一个 slice,且必须是最后一个形参。
将 slice 传递给变参函数时,注意用…
展开(类似 Java 中的可变参数),否则会被当做单个参数处理,和 python 类似。
1 | package main |
...type
类型只能作为函数的参数类型存在,并且是最后一个参数
本质上是一个数组切片,即[]type
。
传入任意类型的不定参数:1
2func Printf(format string, args ...interface{}) {
}
1 | f := func(x,y int) int { |
在 Go 语言中,函数也是一种变量,可以通过type
来定义它,它的类型就是所有拥有相同的参数,相同的返回值的函数。
语法:1
type typeName func (input1 inputType1, input2 inputType2 [, ....]) (result1 resultType1 [,....])
用法1 (这种用法,在写接口的时候非常有用)
e.g.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19type testInt func(int) bool //声明了一个函数类型
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
}
func isOdd(integer int) bool {
if integer % 2 == 0 {
return false
}
return true
}
filter(a, isOdd)
用法2 可以定义函数类型,也可以将函数作为值进行传递(默认值nil
)
e.g.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22package main
//定义函数类型callback
type callback func(s string)
//定义一个函数,可以接收另一个函数作为参数
// sum为参数名称, func(int, int) int为参数类型
func test(a, b int, sum func(int, int) int) {
println( sum(a,b) )
}
func main(){
//演示1
var cb callback
cb = func(s string) {
println(s)
}
cb("hello world")
//演示2
test(1, 2, func(a, b int) int {return a + b})
}
结果:1
2hello world
3
###main
函数和init
函数
Go里面有两个保留的函数:init
函数(能够应用于所有的package
)和main
函数(只能应用于package main
)。这两个函数在定义时不能有任何的参数和返回值。虽然一个package
里面可以写任意多个init
函数,但这无论是对于可读性还是以后的可维护性来说,我们都强烈建议用户在一个package
中每个文件只写一个init
函数。
Go程序会自动调用init()
和main()
,所以你不需要在任何地方调用这两个函数。每个package
中的init
函数都是可选的,但package main
就必须包含一个main
函数。
程序的初始化和执行都起始于main
包。如果main
包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt
包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init
函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main
包中的包级常量和变量进行初始化,然后执行main
包中的init
函数(如果存在的话),最后执行main
函数。
参考资料:
谢大的《Go Web 编程》
wklken 的 Golang 笔记
我目前的主要开发语言是 Java。其实在 Java7 以后,使用 Java 开始起来已经是比较顺手的,特别是现在我使用的 Java8 中引入的 Lambda 表达式,更是让其开发效率有了明显的提升。
我也了解过 Python 一段时间,不过主要用它就是做 spider 或为了替代一部分 shell 脚本。用 Python 的时候深深的被它的效率所吸引,并曾准备了解一下 Flask 或 Django 以做 Web 开发。不过后来因为别的原因还是放弃了(并行、性能、语法糖等等)。
那为什么学习 Go 呢?
首先 Go 的背景很“硬”——一帮子大牛设计者、Google 出品。其设计理念很明确,就是将动态类型语言的编程容易度和静态类型语言的安全效率结合起来。
其次已经有很多公司和项目开始 Go 进行开发了(国外如 Google、AWS、CloudFlare、CoreOS、Docker 等,国内如七牛、阿里等),跟着世界级巨人的脚步应该不至于走错方向。
在这几天简单的学习了一下 Golang 后,我对其有以下几点浅薄理解:
公司有个新项目要部署在新的服务器上,几台服务器之间要用 Kerberos 来做身份验证,并由我来安装和配置。期间翻阅了不少网上的文章,也遇到了几个坑,故总结了一下其安装配置的过程,以作笔记。
Kerberos 简介:它是一个身份验证协议,提供一个在客户端跟服务器端之间或者服务器与服务器之间的身份验证机制 (并且是相互的身份验证机制)。
环境配置:
OS 版本: CentOS 7
Kerberos 版本: krb5-1.12.2
Server:grape.jie.hn
Client:peach.jie.hn