Bowen's Blog

Respect My Authorita.

Introduction to Puppet

| Comments

最近做数据中心应用运维的工作比较多,接触到了puppet这个工具。看了下官方文档,了解了基本语法,原理。直观的感觉puppet是一个使用起来比较简单的配置管理工具,跨平台方面做的也挺好的,还支持osx,比起chef来,概念要少一些。 PPT如下:

Introduction to Nagios

| Comments

最近接了张从Nagios移植到PagerDuty的卡,活都让同事干了,自己就利用这个时间,看了看文档,基本理解了Nagios的监控的原理以及配置的方法,总结成PPT,在组内做了分享,如下。

Compressing and Uncompressing Files Under Linux

| Comments

针对Linux下常用的压缩和解压的命令,做了一次分享,tar这个命令很强大,但是命令组合比较难记,总结了下规律,按照ppt中的方式应该会很容易记忆。

Learning Shell: Navigation File Content

| Comments

Linux系统中浏览文件的命令很多,比如less,more, head,tail, cat等,其中less命令最为丰富,对于二进制文件和压缩文件,也有对应的命令去浏览。我整理了下,在组内做了一次分享,如下为主要内容。

less

这个命令可以查看一个文件的内容,和编辑器的打开要读取整个文件的方式不一样,less只读取部分,因此,它占用的内存较少。如果是在产品环境下追踪问题,需要打开很大的文档,用这个命令要好过编辑器。

移动

  • Ctrl + F(orward)/U(p)/B(ackward)/D(own) 或者 space 滚动
  • j/k 向上或者向下移动一行
  • g/G/q 跳到文件头/尾部, 退出浏览

Search

  • ? 反向搜索
  • / 正向搜索
  • n/N 跳到下一处/前一处匹配行
  • &pattern 只显示匹配 pattern 的行

有用的命令

  • :v 使用缺省的编辑器进入编辑模式,可以通过EDITOR环境变量修改
  • :F 类似tail -f的效果,等待文件即时的输出,但是tail命令是不能搜索的
  • ma 标记,使用a跳到标记处,``跳到上次浏览位置
  • ! 执行shell命令
  • :s file 将管道中的内容保存在file中
  • :h 显示所有快捷方式及命令

浏览多个文件

  • less file1 file2 file3
  • :n 查看下一个文件
  • :p 查看前一个文件
  • :e 打开一个文件

cat

查看文件内容,每次输出全部内容。tac命令和cat命令作用相反。

常用参数

  • -T 显示文件中的tab
  • -n 显示文件的行数
  • -b 显示行数,不包括空行

重定向

  • 清空文件: cat /dev/null > empty_the_file.txt
  • 合并文件: cat -n file1 file2 > file3
  • 重定向: cat ~/.ssh/id_rsa.pub | ssh root@remote “cat >> ~/.ssh/authorized_keys”

head/tail

取文件前/尾部 10(n)行内容,用法简单直接。 - head -n 10 file - tail -f -n 10 file - tail -n 10 - tail -r file(reversely)

strings

浏览二进制文件,strings命令可以抽取二进制文件中所有的字符串,在二进制文件没有提供很有效的信息的清空下,可以用这个命令获取到更多的信息,用法很简单。 - strings filename

Z命令

z系列命令特指z开头的一系列命令,包括:

  • zcat
  • zless/zmore
  • zgrep
  • zdiff(zcmp)

这些命令可以看做是变异版的cat/less等,其针对的对象是压缩文件,如tar.gz结尾的文件,在不解压的情况下,浏览/查找其内容,对比文件内容等。需要注意的是,这些命令只能操作单个文件压缩后的文件,如果是整个目录那就达不到效果了。 对于 bz2 结尾的压缩文件,也有对应的命令群,bz*,以此类推。

本次session的ppt:

Learning Scala: Function

| Comments

函数在Scala中是一等公民,这意味着你可以在任何时候定义函数,而且它不需要依附于类,在Class/Object中定义的函数叫做方法。

语法

函数由函数名称,参数以及函数体组成,未声明函数返回类型的,默认返回Unit。

1
2
3
4
5
6
7
8
9
10
11
scala> def printName(name: String) = println("Hello, " + name)
func: (name: String)Unit

scala> def double(num: Int):Int = num * 2
double: (num: Int)Int


scala> def iter(num: Int) = { if(num == 0) 1 else 1 + iter(num -1) }
<console>:7: error: recursive method iter needs result type
       def iter(num: Int) = { if(num == 0) 1 else 1 + iter(num -1) }
                                                      ^

对于递归函数,必须指定返回类型,否则编译无法通过。

默认参数和带名参数

可以给定函数的参数一个默认的值,调用函数时可以不用传入该参数。

1
2
3
4
5
scala> def printName(congrats:String, name: String = "default") = println(congrats + " " + name)
printName: (name: String, congrats: String)Unit

scala> printName("hello")
hello default

不过,如果函数有多个参数并且默认参数是第一个,只传入一个参数时,编译无法通过,应该是没有特别指明,直接被当成了第一个参数。解决的办法是通过带名参数。

1
2
3
4
5
scala> def printName(name: String = "default", congrats:String) = println(congrats + " " + name)
printName: (name: String, congrats: String)Unit

scala> printName(congrats = "hello")
hello default

由此我们可以看出,带名参数不需要和参数列表的顺序相同。

call by name / call by value

Scala中有两种参数替换模型,call by namecall by value,直译就是传名或者传值。对于下面的函数

1
def f(x:Int, y:Int) = { x }

如果是传值的方式,那么 f(2, 2 * 2) 首先会被转换为f(2, 4),也就是说在函数体中未调用之前就已经计算好了。如果是传名的方式,那么2 * 2始终不会被计算,除非该参数被调用。Scala中支持两种传参方式,上面的例子中实际上是call by namecall by value是用下面的方式声明函数。

1
2
def g(y: int) = { 2 * y}
def f(x:Int, y:Int => Int) = { x }

只要传入于参数一致的签名的函数即可。

变长参数

和其他编程语言一样,Scala也可以接受变长参数。函数会将变长参数转换参数序列。

1
2
3
4
5
6
7
8
9
scala> def sum(args: Int*) = {
        var result = 0
        for (arg <- args) result += arg
        result
      }
sum: (args: Int*)Int

scala> sum(1 ,2, 3, 4, 5)
res4: Int = 15

但是直接传入Seq会出错……,因为类型不匹配。

1
2
3
4
5
6
7
8
9
scala> val s = sum(1 to 5)


scala> sum( 1 to 5)
<console>:9: error: type mismatch;
 found   : scala.collection.immutable.Range.Inclusive
 required: Int
              sum( 1 to 5)
                     ^

所以还是需要转换称参数序列。

1
2
scala> sum(1 to 5: _*)
res1: Int = 15

匿名函数

举个简单的栗子好了。

1
2
scala> List(1, 2, 3).map {(x:Int) => (x * 2)}
res4: List[Int] = List(2, 4, 6)

过程

过程是返回Unit的函数,利用的是函数的副作用。从语法上来说,和函数的区别就在于它没有等号。

1
def process(job: String)  { run; println("running job" + job) }

当然也可以加上等号,显示的申明返回类型为Unit。

1
def process(job: String):Unit =  { run; println("running job" + job) }

Learning Scala: Control Structure

| Comments

任何语言的控制结构应当都是相似的,语言处理现实世界的问题,理应和现实世界对应。Scala的控制结构以及关键字与Java没什么不同,有区别的地方在于Scala中表达式可以有返回值,比如条件表达式。

条件表达式(if/else)

Scala中典型的条件表达式如下:

1
2
3
4
5
6
7
8
9
scala> val x = 1
x: Int = 1
scala> if (x > 0) 1 else -1
res0: Int = 1

scala> val y = -1
y: Int = -1
scala> if (y > 0) 1
res1: AnyVal = ()

需要注意的有如下几点: 1. Scala中,表达式不需要分号表示结束; 2. Scala中没有?:三元表达式; 3. 条件表达式都有返回值; 4. 返回值的超类是Any,if语句没有输出值时,返回值为Unit; 5. 表达式过长或者,可以将其放在大括号中,如下:

1
2
3
4
if (n > 0) {
  n = n * n
  n -= 1
}

循环(for/while)

Scala中的for/while循环和Java中没什么太大区别,但是Scala中没有类似Java中for(initialize; condition; update variable)的结构。

for循环的例子:

1
2
3
4
5
for(i <- 1 to 10)
  println(i)

for(c <- "hello, world")
  println(c)

变量<-表达式 这样的语法被称作生成器, 变量i/c为集合元素类型,循环用其依次获得集合中的元素,并处理。

while的例子:

1
2
3
4
5
6
var a = 10
var b = 1
while (a > 0) {
  b *= a
  a -= 1
}

Scala中没有break或者continue关键字,如果要中途退出循环,需要自己设置Boolean型变量或者使用Breaks对象的break方法。

for推导

for推导是包含卫语句(guard clause)的for表达式,比如:

1
for (i <- 1 to 3; j <- 1 to 3 if i != j) println((10 * i + j) + " ")

如果i 等于 j,表达式不会打印结果。

模式匹配

个人认为Scala最好用的特性之一,等理解透彻了专门写一篇,下面只是个简单的例子,可以认为是可以用来匹配类对象的switch

1
2
3
4
5
6
val a = true

a match {
  case true => println("yes, PPG!")
  case _ => println("failed!")
}

Allow CORS Under Apache

| Comments

处于安全的考虑,不允许Javascript跨站调用其他网站的服务。对于开放的API服务来说,允许跨站调用又是必须的。为了“解除”这个限制,需要在服务返回的header中添加Access-Control-Allow-Origin字段。 在Apache的配置中,<Directory>, <Location>, <Files> ,<VirtualHost>或者.htaccess任意一个section下添加如下内容:

1
  Header set Access-Control-Allow-Origin "*"

意为该服务允许接受任何跨站请求。当然你可以限制具体的请求网站,如:

1
  Header set Access-Control-Allow-Origin "www.example.com"

要设置允许多个请求源, 可以很没节操的,写上很多遍Header set Access-Control-Allow-Origin "www.xxx.com", 或者

1
2
3
4
<IfModule mod_headers.c>
   SetEnvIf Origin "http(s)?://(www.)?(domain1.com|domain2.com)$" AccessControlAllowOrigin=$0$1
   Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
</IfModule>

除了限制请求源,还可以限制http(s)请求的方法,添加“Access-Control-Allow-Methods”的白名单。

1
  Header set Access-Control-Allow-Methods "GET, POST"

References

1 2

Add Swap Space to Solve 'Can Not Allocate Memory' Issue

| Comments

这几天要把一个新的api服务扔到docker container里面,可能是因为把api和postgre数据库放在了一个container里面,出现了内存不足的情况。

1
2
3
4
5
6
OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000b0000000, 357892096, 0) failed; error='Cannot allocate memory' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (malloc) failed to allocate 357892096 bytes for committing reserved memory.
# An error report file with more information is saved as:
# /tmp/jvm-663/hs_error.log

首先尝试了增加虚拟机内存的方法,在Vagrantfile中修改了分配给virtualbox的内存:

1
2
3
4
5
  config.vm.provider :virtualbox do |vb, override|
    vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
    vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
    vb.memory = 1024
  end

错误依旧存在,于是尝试增加swap memory的空间:

1
2
3
4
dd if=/dev/zero of=/root/myswapfile bs=1M count=1024  #1g的swap空间
chmod 600 /root/myswapfile
mkswap /root/myswapfile
swapon /root/myswapfile

然后问题就解决了……, 更合理的方式应该是把数据库和应用放在两个不同的container里面。

Delete Old VMs From Vagrant

| Comments

想升级Vagrant配置文件中的Ubuntu,但是不想改变box的名字,因此需要删除旧的box,以及VM。

1
2
3
4
5
 ~> vagrant box list
centos  (virtualbox)
debian  (virtualbox)
lucid64 (virtualbox)
ubuntu  (virtualbox)   #需要删除的box

从Vagrant中删除box

1
2
3
4
5
6
7
 ~> vagrant box remove ubuntu
Removing box 'ubuntu' with provider 'virtualbox'...

 ~> vagrant box list
centos  (virtualbox)
debian  (virtualbox)
lucid64 (virtualbox)

从VirtualBox中删除虚拟机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#列出Virtualbox中当前的虚拟机
 ~> VBoxManage list vms
"centos_default_1389778387460_58997" {1b6e8415-ce81-4690-b229-981cf5c1f09f}
"debian_1386125850" {b9dd3091-d036-4fab-b9f4-5b6c6b2faf16}
"ubuntu_default_1394781826776_87151" {c1487df0-119a-411d-9d84-fcb6f983b200}
"VagrantChefHubot_default_1390458896900_28199" {d55d7983-3ab4-45b6-a20c-cb2125b4eff8}
"hubotVagrant_default_1390464057664_43197" {bc429ec4-0bf9-4486-a460-097329382135}
"graphite_default_1390985918603_95953" {e87ba34a-a00a-44ec-99f0-f19c37f35c18}
"riemann_default_1391836269341_83882" {e967375e-7498-4f48-a063-7641eddd53b1}
#unregister想要删除的虚拟机,加--delete可以删除vm
 ~> VBoxManage unregistervm ubuntu_default_1394781826776_87151 --delete
 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
#double check下虚拟机已经被删除
  ~> VBoxManage list vms
"centos_default_1389778387460_58997" {1b6e8415-ce81-4690-b229-981cf5c1f09f}
"debian_1386125850" {b9dd3091-d036-4fab-b9f4-5b6c6b2faf16}
"VagrantChefHubot_default_1390458896900_28199" {d55d7983-3ab4-45b6-a20c-cb2125b4eff8}
"hubotVagrant_default_1390464057664_43197" {bc429ec4-0bf9-4486-a460-097329382135}
"graphite_default_1390985918603_95953" {e87ba34a-a00a-44ec-99f0-f19c37f35c18}
"riemann_default_1391836269341_83882" {e967375e-7498-4f48-a063-7641eddd53b1}

Nicely done!

Learning Scala: Some Basics

| Comments

简单介绍Scala一些基本的概念,主要参考书籍为高宇翔翻译的《快学Scala》第一章。

声明变量:val和var的区别

val 是Scala中用来申明常量(常量变量)的关键字,对应的 var 是用来申明变量的关键字。当我们知道这个概念的时候,对它们区别应该就比较清晰了。Scala中的常量字面量必须是全大写字母。有例为证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#申明常量变量
scala> val a = "hello"
a: String = hello

scala> a = "hello, world"
<console>:8: error: reassignment to val
       a = "hello, world"
         ^
#声明变量
scala> var b = "hello"
b: String = hello

#声明常量字面量
scala> b = "hello, world"
b: String = hello, world
1
2
scala> val A = "hello"
A: String = hello

Scala会根据初始化的值,自动推断出常量/变量的类型,这个叫做“类型推断”,能想到的类似的概念是Ruby的duck typing,不过Scala是静态语言,并且类型安全(TypeSafe)。 当然你也可以显式的指明变量的类型,用过pascal的同学应该很熟悉这种方式:

1
2
3
4
scala> val c:String = "hello, world"
c: String = hello, world
scala> val c:String = null
c: String = null

Scala支持同时为多个变量赋同一个值,或者像Ruby一样并行赋值,只不过语法略有不同,:

1
2
3
4
5
6
7
8
9
10
scala> val d, e = "hello"
d: String = hello
e: String = hello
#类型不匹配会报错
scala> val d, e:Int = "hello"
<console>:7: error: type mismatch;
 found   : String("hello")
 required: Int
       val d, e:Int = "hello"
                      ^

并行赋值的例子,实际上是模式匹配:

1
2
3
scala> val (a: String, b: Int) = ("hello", 100)
a: String = hello
b: Int = 100

对比下Ruby的代码:

1
2
3
4
5
6
irb(main):004:0> a, b = "hello", 100
=> ["hello", 100]
irb(main):005:0> a
=> "hello"
irb(main):006:0> b
=> 100

常用类型

和Java一样,Scala有7种基本的数值类型(Byte, Char, Short, Double, Int, Long, Boolean),不同之处在于它们不是基本类型(Primary Type),全部都是Class。

1
2
3
4
5
6
7
8
scala> 1.getClass
res4: Class[Int] = int
scala> "hello".getClass
res1: Class[_ <: String] = class java.lang.String
scala> 1.5.getClass
res2: Class[Double] = double
scala> 1.to(5)
res6: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

Scala对这些数值类型做了扩展,功能远比Java的对应类型强大,比如在处理String对象的时候,Scala可以将String对象隐式转换成StringOps对象,使用一些高级的方法:

1
2
scala> "hello".intersect("world")
res3: String = lo

Scala中用方法进行类型转换,而不是和Java一样去强制类型转换。

1
2
3
4
scala> 1.1.toInt
res7: Int = 1
scala> 107.toChar
res8: Char = k

算术符和操作符重载

就结果和使用来说,Scala中的算术符(+-*/%&|^>><<)和Java/c/c++/ruby没什么区别,不过Scala的算术/操作符本质上是方法,刚开始觉得奇怪,后来想想这样做可以保证一致性,也很不错:

1
2
3
4
5
scala> 1 + 2
res9: Int = 3

scala> 1.+(2)
res10: Double = 3.0

Scala也没有提供++或者–操作符,+=和-=是等价的代替方式。为什么没有呢?书中给出的理由是实现这个特性不值得……不能理解,是觉得会像c++或者Java一样带来副作用么?

1
2
3
4
5
6
7
scala> var b = 1
b: Int = 1

scala> b += 1

scala> b
res20: Int = 2

在Scala中,操作符重载功能没有实现,给出的理由比较牵强,不明白为什么那么想。

定义和使用方法

Scala中用def关键字定义方法,可以显示的制定输入参数以及返回的类型。

1
2
3
4
5
def max(a: Int, b: Int): Int = {
  if (a > b) a
  else b
}
max: (a: Int, b: Int)Int

调用方法

1
2
scala> max(1, 2)
res0: Int = 2

定义和使用类

1
2
3
4
5
6
class Hello {
  val str = "hello, world"
}
object Hello {
  def world = println("hello, world!")
}

类和伴生类必须同时定义,所以,可以进入paste模式,将代码拷贝到Scala的解释器中。

1
2
3
4
5
6
7
8
scala> val a = new Hello
a: Hello = Hello@62150f9e

scala> a.str
res5: String = hello, world

scala> Hello.world
hello, world!

能想到的(其实是读过的……)基础就是这些了,下一章介绍控制结构好了。