Go 定时器

Go有一个package名字叫time,通过这个package可以很容易的实现与时间有关的操作。time package中有一个ticker结构,可以实现定时任务。

func main(){
    var ticker *time.Ticker = time.NewTicker(1 * time.Second)

    go func() {
        for t := range ticker.C {
            fmt.Println("Tick at", t)
        }
    }()

    time.Sleep(time.Second * 5)  
    ticker.Stop()     
    fmt.Println("Ticker stopped")
}

上面的打印方法会每隔一分钟把当前时间打印出来。修改间隔时间和要执行的函数,就能实现定时任务。


Go 初学者的一些琐碎问题

写了一个 Go 语言的 hello world 来练手。可以在 github 上查看 https://github.com/kelvinblood/ping-pong-go

这个 hello world 主要是 server-client 形式的应用。 功能很简单, client 给 server 发送 ping/pong 的消息,server 回复 pong/ping 消息。

这篇文章记录一些写这个hello world 时遇到的小问题,初学者可以看一看。文末附上源代码。

单双引号

字符串由双引号包裹。单引号内只能有一个字符,例如’c’,输出会返回这个字符的ascii码

字符串相同却不相等

一开始特别奇怪,明明两个变量一摸一样,字符串判断时候却认为他们不一样。。。颇有真假孙悟空的感觉。

接下来就是寻找解决办法的过程

  • 是不是类型不一样:

    • 使用 reflect 包的 reflect.TypeOf 方法,发现都是 string 类型
    • switch 下用Go的空接口: // 建一个函数t 设置参数i 的类型为空接口,空接口可以接受任何数据类型 func t(i interface{}) { //函数t 有一个参数i switch i.(type) { //多选语句switch case string: //是字符时做的事情 case int: //是整数时做的事情 } return }

        // _i.(type)_ 只能在switch中使用
      
  • 是不是像 c 一样数组还有 \n 的诡异操作:

    查看str的长度:

    终于发现了问题所在。

所以解决的办法是只将前面一部分拿来比较:

if strings.EqualFold(recieve[0:len(recieveDefault)],recieveDefault) {
	go send(conn,sendDefault)
  	}else if strings.EqualFold(recieve[0:len(sendDefault)],sendDefault) {
	go send(conn,recieveDefault)
  	}

这里有一篇对比 golang 的 string和[]byte 的区别,写的很好:golang string和[]byte的对比

既然string就是一系列字节,而[]byte也可以表达一系列字节,那么实际运用中应当如何取舍?

  • string可以直接比较,而[]byte不可以,所以[]byte不可以当map的key值。
  • 因为无法修改string中的某个字符,需要粒度小到操作一个字符时,用[]byte。
  • string值不可为nil,所以如果你想要通过返回nil表达额外的含义,就用[]byte。
  • []byte切片这么灵活,想要用切片的特性就用[]byte。
  • 需要大量字符串处理的时候用[]byte,性能好很多。

用到的包

可以直接查看官方文档: https://golang.org/pkg/

  • “fmt”
  • “net”
  • “strings”
  • “time”
  • “math/rand”
  • “reflect”

一些参考资料:

Go语言fmt包Printf方法详解

附源代码

package main

import (
    "fmt"
    "net"
    "strings"
    "time"
    "math/rand"
//    "reflect"
)

func main(){
	_, isClient,err := clientPre()
	
        if err != nil {
		server()
		return
	}

	if isClient {
		client()
		return
	}

	server()
}

func clientPre()(conn net.Conn, isClient bool, err error) {
    //打开连接:
    conn, err = net.Dial("tcp", "localhost:18080")
    if err != nil {
	isClient = false
        fmt.Println(err.Error())
        return
    }

    isClient = true

    conn.Close()
    return
}

func client(){
	sendContent := [2]string{"ping","pong"}
    conn, err := net.Dial("tcp", "localhost:18080")

    fmt.Println("client: ",conn.LocalAddr(),"server: ",conn.RemoteAddr()," ",rand.Intn(100)," ",sendContent[rand.Intn(100)%2])
	
	_,err = send(conn,sendContent[rand.Intn(100)%2])

	if err != nil {
        	fmt.Println(err.Error())
		return
	}
	
    doServerStuff(conn)
    return
}

func server() {
    listener, err := net.Listen("tcp", "0.0.0.0:18080")
    fmt.Println("server listen 18080")
    if err != nil {
        fmt.Println("开启端口错误,终止进程", err.Error())
        return //终止程序
    }
    // 监听并接受来自客户端的连接
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting", err.Error())
            return // 终止程序
        }

        go doServerStuff(conn)
    }
}

func doServerStuff(conn net.Conn) {
    count := 0
    recieveDefault := "ping"
    sendDefault := "pong"

    for {
	count += 1
	recieve,err := rev(conn)

        if err != nil {
            return 
        }

  	fmt.Printf("%d ", count)
  	if strings.EqualFold(recieve[0:len(recieveDefault)],recieveDefault) {
		go send(conn,sendDefault)
  	}else if strings.EqualFold(recieve[0:len(sendDefault)],sendDefault) {
		go send(conn,recieveDefault)
  	}
    }
}

func send(conn net.Conn, content string)(send string, err error){
	time.Sleep(100 * time.Millisecond)
        _, err = conn.Write([]byte(content))
	if err != nil {
		fmt.Println("发送错误.", err.Error())
		return
	}
	send = content

	fmt.Println("send", send)

	return
}

func rev(conn net.Conn)(recieve string, err error){
        buf := make([]byte, 512)
        _, err = conn.Read(buf)
        if err != nil {
	    fmt.Println("接收停止,原因:", err.Error())
            return 
        }

	recieve = string(buf)
  	fmt.Printf("recieving: %v ", recieve) 

	return
}

Rancher 上线应用商店的基本流程

在 Rancher 中,编写好 docker-commpose.yaml 和 rancher-compose.yaml 后,我们就可以部署应用了。 然而当我们给客户提供容器模板时候,更简单的方式还是走应用商店。

Rancher提供了一个应用商店,通过商店中的应用程序模版的可以简化部署复杂应用的过程。这篇文章简单记录一下创建私有应用商店的步骤。

可以查看 github 上的社区商店帮助理解:https://github.com/rancher/community-catalog

  • Cattle 调度引擎:templates文件夹
  • Swarm 调度引擎: swarm-templates文件夹
  • Mesos 调度引擎: mesos-templates文件夹

私有商店项目

-- templates (Or any of templates folder)
  |-- cloudflare
  |   |-- 0
  |   |   |-- docker-compose.yml
  |   |   |-- rancher-compose.yml
  |   |-- 1
  |   |   |-- docker-compose.yml
  |   |   |-- rancher-compose.yml
  |   |-- catalogIcon-cloudflare.svg
  |   |-- config.yml
...
  • 私有仓库至少要包含一个模板目录,明明为前文描述的那几个templates。这个目录确定 Rancher 的调用引擎。
  • 在这个templates目录下就放置我们的应用模板了。假设是 cloudflare
  • cloudflare文件夹中包含0、1、2等文件夹。第一个版本为0,后续每个版本加1。每增加一个新版本的文件夹,你就可以使用这个新版本的应用模版来升级应用了。另外,你也可直接更新0文件夹中的内容并重新部署应用。
  • cloudflare文件夹中还包含两个文件,一个是 config.yml,包含了应用模板的详细信息。另一个是模板的logo,以 catalogIcon- 开头。

以我的 hello world 为例,config.yml 的内容如下,单词的解释也蛮清楚得了:

name: Pingpong
description: kelu's ping pong hello world
version: v0.01
category: Entertainment
maintainer: kelvin blood <xxx@xxx.org>

将这个项目部署到 Rancher 可以访问的 git 服务器上,在 Rancher 设置中添加好就可以使用了。可以查看我的: https://github.com/kelvinblood/community-catalog/tree/init

Rancher 添加

在 Rancher 中一共有四种应用商店:

  • 个人应用商店
  • 共享应用商店
  • 社区应用商店
  • 官方应用商店

作为管理员可以将社区和官方应用商店关闭掉。如果以管理员身份添加的的应用商店,即为共享应用商店:

个人用户添加的应用商店入口在这:

参考资料


rancher-compose.yml

查了很多官方的文档,也没有见到 rancher-compose.yml reference. 可以从官方和社区的 rancher-compose.yml 里找到一些案例来学习一下。https://github.com/rancher/community-catalog

这个项目实际上是 Rancher Catelog 的项目。每个应用基本上就是 docker-compose.yml 和 rancher-compose.yml 构成。这两个文件定义了整个应用。同时我们也可以从老环境的应用中导出这两个文件,新环境直接部署即可使用,非常的便利。

询问了 Rancher 官方的开发者,他们的建议是理解概念以后使用 UI 界面进行配置后导出,不建议直接编写。

最简单的例子如下:

# Reference the service that you want to extend
version: '2'
services:
  web:
    scale: 2
  db:
    scale: 1

参考资料


美团点评业务之技术解密,日均请求数十亿次的容器平台 - infoq

转载自 InfoQ

本文介绍美团点评的 Docker 容器集群管理平台(以下简称容器平台)。该平台始于 2015 年,基于美团云的基础架构和组件而开发的 Docker 容器集群管理平台。目前该平台为美团点评的外卖、酒店、到店、猫眼等十几个事业部提供容器计算服务,承载线上业务数百个,容器实例超过 3 万个,日均线上请求超过 45 亿次,业务类型涵盖 Web、数据库、缓存、消息队列等等。

容器到来之前

在容器平台实施之前,美团点评的所有业务都是运行在美团私有云提供的虚拟机之上。美团点评大部分的线上业务都是面向消费者和商家的,业务类型多样,弹性的时间、频度也不尽相同,这对弹性服务提出了很高的要求。

在这一点上,虚拟机已经难以满足需求,主要体现以下两点:

  • 虚拟机弹性能力较弱,部署效率低,认为干预较多,可靠性差。

  • IT 成本高。由于虚拟机弹性能力较弱,业务部门为了应对流量高峰和突发流量,普遍采用预留大量机器和服务实例的做法。资源没有得到充分使用产生浪费。

容器时代

架构设计

美团点评将容器管理平台视作一种云计算模式,因此云计算的架构同样适用于容器。

如前所述,容器平台的架构依托于美团私有云现有架构,其中私有云的大部分组件可以直接复用或者经过少量扩展开发,如图所示。