从 Python 到 Golang

Why Python

Python是一个很酷的编程语言,它极致的简洁,极易上手,我们可以用它来做云计算、大数据分析,运维自动化,当然还可以写网站、做爬虫。

Why not Python

但我们也清楚地知道它会受限于GIL,还好我们有gevent,我们可以很愉快地monkey.patch_all(),然后用greenlet来处理,但即使这样,在并发量极高的情况下,它的效率就有些令我们担心了。

Golang or OpenResty

当我们开始关注高性能高并发服务端实现的时候,我们自然而然地将目光转向一些现代化的技术,这就是Golang和OpenResty,当然他们的核心都是将IO复用与协程进行有机的融合,但却有着截然不同的表现风格。如果你要问我更喜欢哪个,我会说both。当然下文我会介绍Golang。

Golang

我的哲学是,要熟悉一个编程语言,就是要从最简单的代码开始写起来。百试不爽。
万年不变的hello world先放这。
hello.go

package main //包声明

import "fmt" //导入包

func main() {
  fmt.Println("Hello, 世界")
}

用golang写个mongodb的api出来。
https://github.com/Hevienz/gokule
main.go

package main

import (
   "encoding/json"
   "github.com/cloudflare/conf"
   "gopkg.in/macaron.v1"  //Web框架Macaron
   "labix.org/v2/mgo"
   "log"
   "net/http"
   "os"
   "strconv"
)

// 声明包级别的变量
var ( 
   MONGO_URL string
   MONGO_DB  string
)

// 包初始化函数init
func init() {
   c, err := conf.ReadConfigFile("config.conf") //配置文件见config.conf
   e(err, true)
   MONGO_URL = c.GetString("MONGO_URL", "127.0.0.1:27017")
   MONGO_DB = c.GetString("MONGO_DB", "test")
}

func e(err error, fatal bool) (ret bool) {
   if err != nil {
      log.Println(err.Error())
      if fatal {
         os.Exit(1)
      }
      return true
   } else {
      return false
   }
}

func main() {
   ms, err := mgo.Dial(MONGO_URL)
   e(err, true)
   defer ms.Close() //延迟到函数将要返回时执行
   ms.SetMode(mgo.Monotonic, true)
   mdb := ms.DB(MONGO_DB)
   m := macaron.Classic()
   m.Map(mdb)
   m.Get("/", func(mdb *mgo.Database) string {
      names, err := mdb.CollectionNames()
      e(err, false)
      body, err := json.Marshal(names)
      e(err, false)
      return string(body)
   })
   m.Get("/:col", func(mdb *mgo.Database, ctx *macaron.Context, req *http.Request) string {
      req.ParseForm()
      mcol := mdb.C(ctx.Params(":col"))
      limit := 20
      offset := 0
      if t := req.FormValue("limit"); t != "" {
         limit, err = strconv.Atoi(t)
         e(err, false)
      }
      if t := req.FormValue("offset"); t != "" {
         offset, err = strconv.Atoi(t)
         e(err, false)
      }
      total_count, err := mcol.Find(nil).Count()
      e(err, false)
      items := []map[string]interface{}{}
      iter := mcol.Find(nil).Skip(offset).Limit(limit).Iter()
      for {
         item := map[string]interface{}{} 
         if iter.Next(&item) {
            // item := map[string]interface{}{} 不能写在这里,因为Map是引用类型
            items = append(items, item)
         } else {
            break
         }
      }
      dict := map[string]interface{}{
         "meta": map[string]int{"total_count": total_count, "offset": offset, "limit": limit},
         "data": items,
         "msg":  "success", 
        "code": 0,
      }
      body, err := json.Marshal(dict)
      e(err, false)
      return string(body)
   })
   m.Run()
}

config.conf

MONGO_URL=127.0.0.1:27017
MONGO_DB=devmgmt

继续看一个Kill MySQL慢查询的小工具。
https://github.com/Hevienz/stareMySQL
main.go

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql" //匿名导入
    "io/ioutil"
    "log"
    "os"
    "regexp"
    "strings"
    "time"
)

func e(err error, fatal bool) (ret bool) {
    if err != nil {
        log.Println(err.Error())
        if fatal {
            os.Exit(1)
        }
        return true
    } else {
        return false
    }
}

var (
    DBS = map[string]string{}
    dbs = map[string]*sql.DB{}
)

func init() {
    log.SetFlags(log.Flags() | log.Lshortfile)
    bytes, err := ioutil.ReadFile("config.conf") //见config.conf
    e(err, true)
    str := string(bytes)
    lines := strings.Split(str, "\n")
    reg, err := regexp.Compile(`\s+`)
    e(err, true)
    for num, line := range lines {
        if line == "" {
            continue
        }
        if strings.HasPrefix(line, "#") {
            continue
        }
        fields := reg.Split(line, -1)
        if len(fields) != 2 {
            log.Printf("Line %d is not match the config format.", num+1)
            continue
        }
        dbname := fields[0]
        dburi := fields[1]
        DBS[dbname] = dburi
        dbs[dbname] = init_db(dburi)
    }
}

func init_db(uri string) *sql.DB {
    db, err := sql.Open("mysql", uri)
    e(err, true)
    return db
}

func task(dbname string, db *sql.DB) {
    for {
        rows, err := db.Query("select id,info,time from information_schema.PROCESSLIST where time > 5 and command = 'Query';")
        if err != nil {
            log.Println(err.Error())
            dbs[dbname] = init_db(DBS[dbname])
            task(dbname, dbs[dbname])
            break
        }
        for rows.Next() {
            var id string
            var info string
            var time string
            err = rows.Scan(&id, &info, &time)
            if err != nil {
                log.Println(err.Error())
            } else {
                log.Printf("[FOUND SQL]: '%s'@'%s' (ConnectionId: %s, Time: %s)", info, dbname, id, time)
                _, err := db.Exec(fmt.Sprintf("kill %s;", id))
                if err != nil {
                    log.Println(err.Error())
                } else {
                    log.Printf("[KILLED SQL]: '%s'@'%s' (ConnectionId: %s, Time: %s)", info, dbname, id, time)
                }
            }
        }
        time.Sleep(1 * time.Second)
    }
}

func main() {
    for dbname, db := range dbs {
        go task(dbname, db) //用go关键字创建goroutine
    }
    c := make(chan int)
    <-c // 阻塞main goroutine
}

config.conf

# 配置文件以#开头的为注释
# 配置行 为 数据库昵称和数据库uri的组合,中间以若干空白为分隔

db1(127.0.0.1)      root:root.com@tcp(127.0.0.1:3306)/mysql?charset=utf8
vhost1              ds:root.com@tcp(192.2.3.143:3306)/mysql?charset=utf8

看个panic和recover的代码片段。

package main

import (
   "fmt"
)

func PanicAndRecover(input string) (res string) {
   defer func() {
      if p := recover(); p != nil { //从panic中恢复
         fmt.Printf("%T, %#v\n", p, p)
         res = "error" //defer中修改函数的返回值
      }
   }()
   panic(input) //触发panic
   return "normal"
}

func main() {
   err := PanicAndRecover("ds")
   fmt.Println(err)
}

面向对象的例子。
https://github.com/Hevienz/go-mongo
mongo.go

package go_mongo

import (
    "gopkg.in/mgo.v2"
    "log"
    "os"
)

type Mongo struct{
    session *mgo.Session
}

func New(murls string) *Mongo {
    ms, err := mgo.Dial(murls)
    if err != nil {
        log.Printf("mgo.Dial, %s\n", err)
        os.Exit(1)
    }
    return &Mongo{session: ms}
}

func (self *Mongo) Insert(mdbs string, mcols string, docs ...interface{}) error { //*Mongo类型的方法,docs是可变参数
    mdb := self.session.DB(mdbs)
    mcol := mdb.C(mcols)
    return mcol.Insert(docs...)
}

在main.go中使用包github.com/Hevienz/go-mongo。
main.go

package main
import (
   "github.com/Hevienz/go-mongo"
   "github.com/Hevienz/go-utils" //下文介绍
   "log"
)
func main() {
   doc := map[string]interface{}{"ds": "cc"}
   m := go_mongo.New("127.0.0.1:27017")
   go_utils.Dir(m) //调用go_utils包的Dir方法
   err := m.Insert("test", "ds", doc)
   if err != nil {
      log.Println(err)
   }
}

用反射实现类似Python中的build-in函数dir。
https://github.com/Hevienz/go-utils
dir.go

package go_utils

import (
    "reflect" //导入反射包
    "fmt"
    "strings"
)

func Dir(x interface{}) {
    v := reflect.ValueOf(x)
    t := v.Type()
    fmt.Printf("type %s\n", t)

    for i := 0; i < v.NumMethod(); i++ {
        methType := v.Method(i).Type()
        fmt.Printf("func (%s) %s%s\n", t, t.Method(i).Name,
            strings.TrimPrefix(methType.String(), "func"))
    }
}

其它重要的特性包括:接口,类型断言,类型分支,channel,互斥锁等,此处不再介绍。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,421评论 6 428
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    小迈克阅读 2,926评论 1 3
  • Astronomygonova - A wrapper for libnova -- Celestial Mech...
    JumboWu阅读 8,579评论 0 41
  • #幸福是需要修出来的~每天进步1%~幸福实修12班~3.~思娴 20170929(D4) 【幸福三朵玫瑰】 陪孩子...
    幸福实修思娴阅读 195评论 0 2