Go 1.7中值得关注的几个变化

零、从Release Cycle说起

从Go 1.3版本开始,Golang核心开发Team的版本开发周期逐渐稳定下来。经过Go 1.4Go1.5Go 1.6的实践,大神Russ CoxGo wiki上大致定义了Go Release Cycle的一般流程:

  1. 半年一个major release版本。
  2. 发布流程启动时间:每年8月1日和次年2月1日(真正发布日期有可能是这个日子,也可能延后几天)。
  3. 半年的周期中,前三个月是Active Development,then 功能冻结(大约在11月1日和次年的5月1日)。接下来的三个月为test和polish。
  4. 下一个版本的启动计划时间:7月15日和1月15日,版本计划期持续15天,包括讨论这个major版本中要实现的主要功能、要fix的前期遗留的bug。
  5. release前的几个阶段版本:beta版本若干(一般是2-3个)、release candidate版本若干(一般是1-2个)和最后的release版本。
  6. major release版本的维护是通过一系列的minor版本体现的,主要是修正一些导致crash的严重问题或是安全问题,比如major release版本Go 1.6目前就有go 1.6.1和go 1.6.2两个后续minor版本发布。

在制定下一个版本启动计划时,一般会由Russ Cox在golang-dev group发起相关讨论,其他Core developer在讨论帖中谈一下自己在下一个版本中要做的事情,让所有开发者大致了解一下下个版本可能包含的功能和修复的bug概况。但这些东西是否能最终包含在下一个Release版本中,还要看Development阶段feature代码是否能完成、通过review并加入到main trunk中;如果来不及加入,这个功能可能就会放入下一个major release中,比如SSA就错过了Go 1.6(由于Go 1.5改动较大,留给Go 1.6的时间短了些)而放在了Go 1.7中了。

个人感觉Go社区采用的是一种“民主集中制”的文化,即来自Google的Golang core team的少数人具有实际话语权,尤其是几个最早加入Go team的大神,比如Rob Pike老头、Russ Cox以及Ian Lance Taylor等。当然绝大部分合理建议还是被merge到了Go代码中的,但一些与Go哲学有背离的想法,比如加入泛型、增加新类型、改善错误处理等,基本都被Rob Pike老头严词拒绝了,至少Go 1兼容版本中,大家是铁定看不到的了。至于Go 2,就连Go core team的人也不能不能打包票说一定会有这样的新语言规范。不过从Rob Pike前些阶段的一些言论中,大致可以揣摩出Pike老头正在反思Go 1的设计,也许他正在做Go 2的语言规范也说不定呢^_^。这种“文化”并不能被很多开源开发者所欣赏,在GopherChina 2016大会上,大家就对这种“有些独裁”的文化做过深刻了辩论,尤其是对比Rust那种“绝对民主”的文化。见仁见智的问题,这里就不深入了。个人觉得Go core team目前的做法还是可以很好的保持Go语言在版本上的理想的兼容性和发展的一致性的,对于一门面向工程领域的语言而言,这也许是开发者们较为看重的东西;编程语言语法在不同版本间“跳跃式”的演进也许会在短时间内带来新鲜感,但长久看来,对代码阅读和维护而言,都会有一个不小的负担。

下面回归正题。Go 1.7究竟带来了哪些值得关注的变化呢?马上揭晓^_^。(以下测试所使用的Go版本为go 1.7 beta2)。

一、语言

Go 1.7在版本计划阶段设定的目标就是改善和优化(polishing),因此在Go语言(Specification)规范方面继续保持着与Go 1兼容,因此理论上Go 1.7的发布对以往Go 1兼容的程序而言是透明的,已存在的代码均可以正常通过Go 1.7的编译并正确执行。

不过Go 1.7还是对Go1 Specs中关于“Terminating statements”的说明作了一个extremely tiny的改动:

A statement list ends in a terminating statement if the list is not empty and its final statement is terminating.
=>
A statement list ends in a terminating statement if the list is not empty and its final non-empty statement is terminating.

Specs是抽象的,例子是生动的,我们用一个例子来说明一下这个改动:

// go17-examples/language/f.go

package f

func f() int {
    return 3
    ;
}

对于f.go中f函数的body中的语句列表(statement list),所有版本的go compiler或gccgo compiler都会认为其在”return 3″这个terminating statement处terminate,即便return语句后面还有一个“;”也没关系。但Go 1.7之前的gotype工具却严格按照go 1.7之前的Go 1 specs中的说明进行校验,由于最后的statement是”;” – 一个empty statement,gotype会提示:”missing return”:

// Go 1.7前版本的gotype

$gotype f.go
f.go:6:1: missing return

于是就有了gotype与gc、gccgo行为的不一致!为此Go 1.7就做了一些specs上的改动,将statements list的terminate点从”final statement”改为“final non-empty statement”,这样即便后面再有”;”也不打紧了。于是用go 1.7中的gotype执行同样的命令,得到的结果却不一样:

// Go 1.7的gotype
$gotype f.go
没有任何错误输出

gotype默认以源码形式随着Go发布,我们需要手工将其编译为可用的工具,编译步骤如下:

$cd $GOROOT/src/go/types
$go build gotype.go
在当前目录下就会看到gotype可执行文件,你可以将其mv or cp到$GOBIN下,方便在命令行中使用。

二、Go Toolchain(工具链)

Go的toolchain的强大实用是毋容置疑的,也是让其他编程语言Fans直流口水的那部分。每次Go major version release,Go工具链都会发生或大或小的改进,这次也不例外。

1、SSA

SSA(Static Single-Assignment),对于大多数开发者来说都是不熟悉的,也是不需要关心的,只有搞编译器的人才会去认真研究它究竟为何物。对于Go语言的使用者而言,SSA意味着让编译出来的应用更小,运行得更快,未来有更多的优化空间,而这一切的获得却不需要Go开发者修改哪怕是一行代码^_^。

在Go core team最初的计划中,SSA在Go 1.6时就应该加入,但由于Go 1.6开发周期较为短暂,SSA的主要开发者Keith Randall没能按时完成相关开发,尤其是在性能问题上没能达到之前设定的目标,因此merge被推迟到了Go 1.7。即便是Go 1.7,SSA也只是先完成了x86-64系统。
据实而说,SSA后端的引入,风险还是蛮大的,因此Go在编译器中加入了一个开关”-ssa=0|1″,可以让开发者自行选择是否编译为SSA后端,默认情况下,在x86-64平台下SSA后端是打开的。同时,Go 1.7还修改了包导出的元数据的格式,由以前的文本格式换成了更为短小精炼的二进制格式,这也让Go编译出来的结果文件的Size更为small。

我们可以简单测试一下上述两个优化后对编译后结果的影响,我们以编译github.com/bigwhite/gocmpp/examples/client/例:

-rwxrwxr-x 1 share share 4278888  6月 20 14:20 client-go16*
-rwxrwxr-x 1 share share 3319205  6月 20 14:04 client-go17*
-rwxrwxr-x 1 share share 3319205  6月 20 14:05 client-go17-no-newexport*
-rwxrwxr-x 1 share share 3438317  6月 20 14:04 client-go17-no-ssa*
-rwxrwxr-x 1 share share 3438317  6月 20 14:03 client-go17-no-ssa-no-newexport*

其中:client-go17-no-ssa是通过下面命令行编译的:

$go build -a -gcflags="-ssa=0" github.com/bigwhite/gocmpp/examples/client

client-go17-no-newexport*是通过下面命令行编译的:

$go build -a -gcflags="-newexport=0" github.com/bigwhite/gocmpp/examples/client

client-go17-no-ssa-no-newexport是通过下面命令行编译的:

$go build -a -gcflags="-newexport=0 -ssa=0" github.com/bigwhite/gocmpp/examples/client

对比client-go16和client-go17,我们可以看到默认情况下Go 17编译出来的可执行程序(client-go17)比Go 1.6编译出来的程序(client-go16)小了约21%,效果十分明显。这也与Go官方宣称的file size缩小20%~30%de 平均效果相符。

不过对比client-go17和client-go17-no-newexport,我们发现,似乎-newexport=0并没有起到什么作用,两个最终可执行文件的size相同。这个在ubuntu 14.04以及darwin平台上测试的结果均是如此,暂无解。

引入SSA后,官方说法是:程序的运行性能平均会提升5%~35%,数据来源于官方的benchmark数据,这里就不再重复测试了。

2、编译器编译性能

Go 1.5发布以来,Go的编译器性能大幅下降就遭到的Go Fans们的“诟病”,虽然Go Compiler的性能与其他编程语言横向相比依旧是“独领风骚”。最差时,Go 1.5的编译构建时间是Go 1.4.x版本的4倍还多。这个问题也引起了Golang老大Rob Pike的极大关注,在Russ Cox筹划Go 1.7时,Rob Pike就极力要求要对Go compiler&linker的性能进行优化,于是就有了Go 1.7“全民优化”Go编译器和linker的上百次commit,至少从目前来看,效果是明显的。

Go大神Dave Cheney为了跟踪开发中的Go 1.7的编译器性能情况,建立了三个benchmark:benchjujubenchkubebenchgogs。Dave上个月最新贴出的一幅性能对比图显示:编译同一项目,Go 1.7编译器所需时间仅约是Go 1.6的一半,Go 1.4.3版本的2倍;也就是说经过优化后,Go 1.7的编译性能照比Go 1.6提升了一倍,离Go 1.4.3还有一倍的差距。

img{}

3、StackFrame Pointer

在Go 1.7功能freeze前夕,Russ Cox将StackFrame Pointer加入到Go 1.7中了,目的是使得像Linux Perf或Intel Vtune等工具能更高效的抓取到go程序栈的跟踪信息。但引入STackFrame Pointer会有一些性能上的消耗,大约在2%左右。通过下面环境变量设置可以关闭该功能:

export GOEXPERIMENT=noframepointer

4、Cgo增加C.CBytes

Cgo的helper函数在逐渐丰富,这次Cgo增加C.CBytes helper function就是源于开发者的需求。这里不再赘述Cgo的这些Helper function如何使用了,通过一小段代码感性了解一下即可:

// go17-examples/gotoolchain/cgo/print.go

package main

// #include <stdio.h>
// #include <stdlib.h>
//
// void print(void *array, int len) {
//  char *c = (char*)array;
//
//  for (int i = 0; i < len; i++) {
//      printf("%c", *(c+i));
//  }
//  printf("\n");
// }
import "C"

import "unsafe"

func main() {
    var s = "hello cgo"
    csl := C.CBytes([]byte(s))
    C.print(csl, C.int(len(s)))
    C.free(unsafe.Pointer(csl))
}

执行该程序:

$go run print.go
hello cgo

5、其他小改动

  • 经过Go 1.5和Go 1.6实验的go vendor机制在Go 1.7中将正式去掉GO15VENDOREXPERIMENT环境变量开关,将vendor作为默认机制。
  • go get支持git.openstack.org导入路径。
  • go tool dist list命令将打印所有go支持的系统和硬件架构,在我的机器上输出结果如下:
$go tool dist list
android/386
android/amd64
android/arm
android/arm64
darwin/386
darwin/amd64
darwin/arm
darwin/arm64
dragonfly/amd64
freebsd/386
freebsd/amd64
freebsd/arm
linux/386
linux/amd64
linux/arm
linux/arm64
linux/mips64
linux/mips64le
linux/ppc64
linux/ppc64le
linux/s390x
nacl/386
nacl/amd64p32
nacl/arm
netbsd/386
netbsd/amd64
netbsd/arm
openbsd/386
openbsd/amd64
openbsd/arm
plan9/386
plan9/amd64
plan9/arm
solaris/amd64
windows/386
windows/amd64

三、标准库

1、支持subtests和sub-benchmarks

表驱动测试是golang内置testing框架的一个最佳实践,基于表驱动测试的思路,Go 1.7又进一步完善了testing的组织体系,增加了subtests和sub-benchmarks。目的是为了实现以下几个Features:

  • 通过外部command line(go test –run=xx)可以从一个table中选择某个test或benchmark,用于调试等目的;
  • 简化编写一组相似的benchmarks;
  • 在subtest中使用Fail系列方法(如FailNow,SkipNow等);
  • 基于外部或动态表创建subtests;
  • 更细粒度的setup和teardown控制,而不仅仅是TestMain提供的;
  • 更多的并行控制;
  • 与顶层函数相比,对于test和benchmark来说,subtests和sub-benchmark代码更clean。

下面是一个基于subtests文档中demo改编的例子:

传统的Go 表驱动测试就像下面代码中TestSumInOldWay一样:

// go17-examples/stdlib/subtest/foo_test.go

package foo

import (
    "fmt"
    "testing"
)

var tests = []struct {
    A, B int
    Sum  int
}{
    {1, 2, 3},
    {1, 1, 2},
    {2, 1, 3},
}

func TestSumInOldWay(t *testing.T) {
    for _, tc := range tests {
        if got := tc.A + tc.B; got != tc.Sum {
            t.Errorf("%d + %d = %d; want %d", tc.A, tc.B, got, tc.Sum)
        }
    }
}

对于这种传统的表驱动测试,我们在控制粒度上仅能在顶层测试方法层面,即TestSumInOldWay这个层面:

$go test --run=TestSumInOldWay
PASS
ok      github.com/bigwhite/experiments/go17-examples/stdlib/subtest    0.008s

同时为了在case fail时更容易辨别到底是哪组数据导致的问题,Errorf输出时要带上一些测试数据的信息,比如上面代码中的:”%d+%d=%d; want %d”。

若通过subtests来实现,我们可以将控制粒度细化到subtest层面。并且由于subtest自身具有subtest name唯一性,无需在Error中带上那组测试数据的信息:

// go17-examples/stdlib/subtest/foo_test.go

func assertEqual(A, B, expect int, t *testing.T) {
    if got := A + B; got != expect {
        t.Errorf("got %d; want %d", got, expect)
    }
}

func TestSumSubTest(t *testing.T) {
    //setup code ... ...

    for i, tc := range tests {
        t.Run("A=1", func(t *testing.T) {
            if tc.A != 1 {
                t.Skip(i)
            }
            assertEqual(tc.A, tc.B, tc.Sum, t)
        })

        t.Run("A=2", func(t *testing.T) {
            if tc.A != 2 {
                t.Skip(i)
            }
            assertEqual(tc.A, tc.B, tc.Sum, t)
        })
    }

    //teardown code ... ...
}

我们故意将tests数组中的第三组测试数据的Sum值修改错误,这样便于对比测试结果:

var tests = []struct {
    A, B int
    Sum  int
}{
    {1, 2, 3},
    {1, 1, 2},
    {2, 1, 4},
}

执行TestSumSubTest:

$go test --run=TestSumSubTest
--- FAIL: TestSumSubTest (0.00s)
    --- FAIL: TestSumSubTest/A=2#02 (0.00s)
        foo_test.go:19: got 3; want 4
FAIL
exit status 1
FAIL    github.com/bigwhite/experiments/go17-examples/stdlib/subtest    0.007s

分别执行”A=1″和”A=2″的两个subtest:

$go test --run=TestSumSubTest/A=1
PASS
ok      github.com/bigwhite/experiments/go17-examples/stdlib/subtest    0.007s

$go test --run=TestSumSubTest/A=2
--- FAIL: TestSumSubTest (0.00s)
    --- FAIL: TestSumSubTest/A=2#02 (0.00s)
        foo_test.go:19: got 3; want 4
FAIL
exit status 1
FAIL    github.com/bigwhite/experiments/go17-examples/stdlib/subtest    0.007s

测试的结果验证了前面说到的两点:
1、subtest的输出自带唯一标识,比如:“FAIL: TestSumSubTest/A=2#02 (0.00s)”
2、我们可以将控制粒度细化到subtest的层面。

从代码的形态上来看,subtest支持对测试数据进行分组编排,比如上面的测试就将TestSum分为A=1和A=2两组,以便于分别单独控制和结果对比。

另外由于控制粒度支持subtest层,setup和teardown也不再局限尽在TestMain级别了,开发者可以在每个top-level test function中,为其中的subtest加入setup和teardown,大体模式如下:

func TestFoo(t *testing.T) {
    //setup code ... ...

    //subtests... ...

    //teardown code ... ...
}

Go 1.7中的subtest同样支持并发执行:

func TestSumSubTestInParalell(t *testing.T) {
    t.Run("blockgroup", func(t *testing.T) {
        for _, tc := range tests {
            tc := tc
            t.Run(fmt.Sprint(tc.A, "+", tc.B), func(t *testing.T) {
                t.Parallel()
                assertEqual(tc.A, tc.B, tc.Sum, t)
            })
        }
    })
    //teardown code
}

这里嵌套了两层Subtest,”blockgroup”子测试里面的三个子测试是相互并行(Paralell)执行,直到这三个子测试执行完毕,blockgroup子测试的Run才会返回。而TestSumSubTestInParalell与foo_test.go中的其他并行测试function(如果有的话)的执行是顺序的。

sub-benchmark在形式和用法上与subtest类似,这里不赘述了。

2、Context包

Go 1.7将原来的golang.org/x/net/context包挪入了标准库中,放在$GOROOT/src/context下面,这显然是由于context模式用途广泛,Go core team响应了社区的声音,同时这也是Go core team自身的需要。Std lib中net、net/http、os/exec都用到了context。关于Context的详细说明,没有哪个比Go team的一篇”Go Concurrent Patterns:Context“更好了。

四、其他改动

Runtime这块普通开发者很少使用,一般都是Go core team才会用到。值得注意的是Go 1.7增加了一个runtime.Error(接口),所有runtime引起的panic,其panic value既实现了标准error接口,也实现了runtime.Error接口。

Golang的GC在1.7版本中继续由Austin Clements和Rick Hudson进行打磨和优化。

Go 1.7编译的程序的执行效率由于SSA的引入和GC的优化,整体上会平均提升5%-35%(在x86-64平台上)。一些标准库的包得到了显著的优化,比如:crypto/sha1, crypto/sha256, encoding/binary, fmt, hash/adler32, hash/crc32, hash/crc64, image/color, math/big, strconv, strings, unicode, 和unicode/utf16,性能提升在10%以上。

Go 1.7还增加了对使用二进制包(非源码)构建程序的实验性支持(出于一些对商业软件发布形态的考虑),但Go core team显然是不情愿在这方面走太远,不承诺对此进行完整的工具链支持。

标准库中其他的一些细微改动,大家尽可以参考Go 1.7 release notes。

本文涉及到的example代码在这里可以下载到。

闲话智慧城市

这一个月,因为工作关系,我接触到了“智慧城市”这个概念,这里打算把这一个月来对智慧城市的认知和“感受”记录下来,算是一个小的总结吧,希望能给大家带去点营养。

一、历程

关于智慧城市,我也是从零基础开始起步的。

这一个月来,我有幸聆听了IBM大中华区智慧城市首席规划师岳梅樱博士关于智慧城市的理解;粗读了岳博士主编的两本有关智慧城市的书《智慧城市顶层设计方法论与实践分享》《智慧城市:实践分享系列谈》;拜读了心理咨询师王成威老师关于智慧城市建设的顶层规划思路;与中国电科五十四所的专家们讨论过智慧城市建设方面的合作;与公司内部咨询策划同事一起了解了沈阳智慧城市建设的实际情况以及我们公司的参与情况;搜索和浏览了大量网络资料,算是对智慧城市,尤其是有中国特色的智慧城市建设有了一些初步的认知。

二、智慧城市到底是个什么鬼?

我参加岳博士交流会的那天恰是我接触智慧城市概念的第九天,而那时也恰是岳博士在大中华区推动智慧城市建设的第九年,差距有那么一点大哈^_^。

智慧城市到底是什么?很多人愿意以“没有标准定义”来开头,然后再给出自己的定义^_^。从城市发展的角度来说,智慧城市是“城市”发展的一个阶段。在这个阶段里,城市总体呈现出一种比之前各个阶段更为高级的形态。特别古老的城市阶段我们就不提了,想了解城市起源和发展的朋友可以看看美国著名学者刘易斯·芒福德的《城市发展史》。我们主要来说说近二十年左右的现代城市。

按照岳博士的城市断代(由于城市发展水平不同,有些城市在各个节点有重叠,就像中国的工业化和信息化建设就是重叠在一起的一样),现代城市发展经历了如下几个阶段:

1、数字城市

数字城市开启了城市发展的数字化阶段,是城市发展史上的新纪元。数字城市概念起源于美国政府提出的“数字地球”。数字城市旨在通过先进的IT技术和网络技术将以物理形态存在的城市的各种信息存储到磁盘上,形成一个数字化的虚拟城市。基于这些数字化后的信息,政府可以通过信息化手段来提高各行业管理效率和服务质量,并基于互联网形成初步的业务协同,提高城市运行效率。这一阶段起始于二十世纪九十年代末,并一直持续至今。不同的城市由于自身发展的水平差异,数字化的程度也有不同。

2、无线城市

提到无线城市,人们便想到了遍布大街小巷各个店铺中的各种Wi-Fi,各种运营商4G网络!没错,这就是无线城市在城市人们生活中的真实投射。无线城市让人和物更容易、更快捷、更高速的接入到城市网络和互联网中。满足了城市居民的社交需求,同时也让以前不能采集得到的数据(包括物产生的数据和人产生的数据)源源不断的汇聚到城市管理者那里以供分析、挖掘,辅助管理者决策。无线城市的概念依旧发起于美国,起始于2004年美国费城的“无线城市”计划,并一直持续至今。像“宽带中国”战略都可以理解成我们无线城市建设的一个组成部分。

3、智慧城市

有了数字城市和无线城市的铺垫,才会有智慧城市概念的出现。前面说过:智慧城市是城市发展的更高级形态。这里所谓的“高级”就是在无线城市感知的和收集的、数字城市存储的数据上面加入了一个“智慧”的辅助处理过程,以帮助城市管理者和运营者们快速准确的做出决策。当前阶段这个“智慧”主要就是通过大数据相关技术和机器学习实现的。智慧城市来源于2008年美国的那个蓝色大块头IBM提出的“智慧地球”概念,并在其后的若干年里得到全球城市管理者和建设者的认可。从现如今至未来的一段时间内,全球大部分发达城市都会处于智慧城市这一发展阶段。

智慧城市在全球的发展离不开IBM的大力推广。IBM为何要提出“智慧城市”呢?段子中的说法是这样的:自从IBM历史上最伟大的CEO之一:郭士纳带领IBM转型并走出泥潭之后,IBM进入了一个黄金发展期,股价连连攀升。IBM继续稳固其在金融、保险、通信等行业的领头羊位置,但在在面对城市、面向政府公共事业,IBM的开拓并不是那么顺利。而“智慧城市”让IBM有机会直面城市,直面政府核心,找到新的业绩增长点。

智慧城市离不开IBM,但IBM却是可以“抛弃”智慧城市的。你可能也逐渐感觉到一个奇怪的现象:”IBM在媒体上已经很少提及智慧城市了”,这是因为IBM已经进入了城市发展和建设的下一阶段:认知时代(the cognitive era)。IBM的蓝色基因存活百年(1911年开始),它可不是白活着。历史上IBM经历了几次波谷,无不是在自我调整中完成自我救赎。伟大的蓝色巨人总是那么先知先觉,在今天公司业绩再次进入一个下行通道时,再次主动寻求转型,将战略切换到“云+认知”的方向上去了。

如果说智慧城市是通过当前的大数据分析、挖掘,初级机器学习等技术充当“智慧”的话,那IBM的认知时代中的那个“智慧”的代言人就是IBM的Watson。Watson就是一段人工智能程序(背后可能是一个集群支撑),它的前身“深蓝”战胜过国际象棋世界冠军,它自己则在美国智力节目Jeopardy!上击败两位人类选手取得冠军。IBM已经将其应用于全球认知商业行业解决方案中,通过API支撑关系抽取、性格分析、情绪分析、概念扩展及权衡分析等智能特性。根据岳博士透露,IBM的认知计算已经开始应用于辅助法官断案和医生临床诊断等行业中去了。

img{512x368}
巴西里约的城市运营中心

三、有中国特色的智慧城市建设

彭明盛于2008年提出智慧地球(smart planet),后演变出智慧城市概念。之后,IBM开始在全球布道,大政府模式的中国大陆地区自然受到IBM青睐。这一时间段也恰逢我国十二五时期(2011-2015),经济上出现新常态、社会资源(人、财、物)面临更有效、更合理的重新配置,国家提出了新城镇化建设的目标,于是智慧城市这件漂亮的外衣就穿到了中国各级政府的身上,这也符合我们一贯跟在国外先进概念屁股后面走的模式。

近几年,智慧城市在中国可谓是“遍地开花”,你在搜索引擎中搜索“智慧+城市名”,你总是能找到各地关于智慧城市建设的xx年-yy年总体规划、实施规划、行动方案或顶层设计之类的文档,尤其是一线城市、国家中心城市、省会城市以及一些具有地方特色的小城市。那么中国的智慧城市建设到底处于一个什么样的水准呢?下面从主流思路、推动力量和建设效果等几个方面说明一下。

1、中国智慧城市建设所处阶段

中国的信息化具有起步晚、起点高的特点,中国工业化和信息化建设同步并行进行。与此类似,智慧城市与无线城市、数字城市的建设也是重叠并行的,只是在对外的叫法上我们现阶段多统一采用了“智慧城市”这一更高形态。

智慧城市概念自身也在不断演化,伴随着技术的进步,始作俑者IBM在中国智慧城市建设的理念上也有过从1.0到3.0版本的几次演化。和中国经济的地域发展差异很大一样,中国各地的智慧城市建设水平也是参差不齐的。一线城市以及一些国家中心城市经济相对好,基础设施优越,智慧城市建设走在了前面,已经开始着手按照3.0的理念建设了;而其他城市可能还处在智慧城市1.0版本徘徊:基础设施还不完善,网络无法延伸到城市各个角落。这些城市没有能力做更高版本的智慧城市。因此,智慧城市建设在中国会是一个长期的存在。

2、当前中国智慧城市建设主流思路

随着中央政府将智慧城市写入十三五规划,智慧城市得到了前所未有的政策眷顾。智慧城市建设正在将重点从城市基础设施和平台建设向数据互联互通、数据运营和城市运营方面转变,思维也逐渐从行政化走向市场化,这也是当前中国智慧城市的主流思路。政府的数据是智慧城市建设的灵魂,得数据者得天下。各大智慧城市厂商在与合作建设智慧城市时,也都希望能拿到各委办局的数据,并基于这些数据进行运营和创新,找到城市经济的新增长点;同时有了这些数据,厂商可以开发出更惠民的应用,让城市里的居民感受到“智慧”的气息。但从实际效果来看,政府数据开放虽然逐渐破冰,但政府开放数据之路还会很漫长,坎坷还有许多,需要一些耐心。

3、智慧城市建设的三股力量

中国智慧城市建设由三股力量推动。

首先自然是政府。城市的管理和发展是政府的首要职责,智慧城市是政府给城市发展选择的一个方向。政府在智慧城市中扮演着绝对的主导角色,无论是政策导向、法规支撑、资金投入、协调合作还是数据来源,离开了政府一切都玩不转。

其次是传统电信运营商、主机和网络设备提供商、基础设施云服务大数据服务提供商、解决方案提供商和集成商。比如联通、电信、浪潮、华为、中兴、东网科技、神州数码等。这些厂商是每个智慧城市建设的重要建设者、技术支持者和运营参与者。

最后是大体量的互联网公司,比如阿里、腾讯等。他们有一个共同的特点就是自己的产品已经涵盖了大部分城市人口,因此它们可以另辟蹊径。他们可以利用用户优势、入口优势(支付宝、微信)和技术优势打造类城市超级App,让生活在城市中的人们感觉更加智慧。当然这些公司也在寻求与政府的直接合作,但效果似乎并不是那么好。也许是这些公司的价值观与政府的低效、官僚有冲突吧。

4、智慧城市的建设效果

智慧城市涉及方方面面,其建设的主要目标是优化政府行政管理(善政)、改善民生(惠民)和持续推进城市经济发展(兴业)。因此,智慧城市的建设效果绝不仅仅是市民直观感受到的那些。当然民众的直接感受是评价智慧城市建设效果的最重要指标之一:出行方便了、路不堵了、到政府部门办事省心省时了、跑医院不用找黄牛了、生病的孩子在家里就可以通过视频参与到学校的课堂中了,这一切都是智慧城市建设效果在人们真实生活中的投射。

最新的智慧城市建设思路强调顶层设计,强调建立智慧城市评估指标体系,通过这些指标数据可以从微观层面反映出智慧城市建设的效果,尤其是对经济发展的推动作用。

5、与欧美智慧城市建设的差异

智慧城市概念来自欧美,想必欧美在智慧城市建设方面应该领先于我们吧?这个还真不一定。欧美智慧城市的建设思路与中国的智慧城市建设思路有差别。

东西方城市的发展历程不同,西方城市进入现代化时间更长,基础设施良好,城市的运行竟然有序,他们不需要大动干戈的对城市进行翻天覆地的重构,只需在某一领域或行业做持续优化和改进。因此他们在建设智慧城市时,往往打出的口号面向的都是“点”,也有自己的特色,比如柏林的2020年电动汽车行动计划(ActionPlanforElectromobilityBerlin2020),注册用户可以在大约250平方公里的区域内租用到配备了智能熄火/启动系统、空调和导航系统的smartfortwo车辆,并根据自己的意愿长时间驾驶这些汽车,然后在运营区域内的任何公共停车场归还汽车。

但中国在智慧城市建设过程中,一些城市不顾自身的基础和发展特点,而一味的效仿大而全的智慧城市建设方略,一哄而上,你有我有全都有。基本上一份顶层设计文档,把A城市的名字改为B城市的名字,就可以作为B城市的顶设方案了。这种建设方式不仅造成了严重资源浪费,透支了城市的发展潜力,而且往往是为了智慧而智慧,缺少对城市真实需求的了解,实际效果很差。

欧洲打法和中国打法没有谁更好之分,只有更适合。这一切都基于城市管理者对自己所管理城市的深入认知,对行政权力使用的精准判断,对市民需求的深入理解和对产业发展的高瞻远瞩。

从建设模式上来看,欧美以PPP(公私合作关系:Public-private Partnership)为主,国内则是在近两年才逐渐在政策上适当宽松,逐步引入PPP,但效果似乎不太理想。因为政府始终以老大自居,执行力弱、缺乏契约精神,不能降低姿态和企业平起平坐,不能做到主体对等,这让企业顾虑重重。

四、FAQ

1、智慧城市有炒作概念的成分么?

可以肯定的说,有。

从商业的角度,IBM等智慧城市解决方案厂商是要从政府分一杯羹的,在概念导入阶段,大家都飘在上层,落地的东西很少。

但从一个政府的角度来讲,IBM提出的这些概念也确实是未来城市的发展方向,但政府缺乏在这方面的专业知识、技能和人才,需要各个厂商去帮助他梳理思路,形成落地的可行方案。需要注意的是:政府也要尊重城市现实,不要一味的去做那些不必要的高大上的东西。

从民众的角度,是否智慧并不care。省事省力省钱,让我happy就ok。

在中国虽然也存在概念的泡沫空间,但中国智慧城市建设总体上应该是健康的。有一些公司是脚踏实地的去考虑如何帮助政府去建设一个智慧城市的。当然商业公司是要谋利的,但这是其应得的。

2、在现有政府行政权力机构设置下,智慧城市能运营做好吗?

个人对此事表示悲观。

现有的地方政府机构设置本身就存在各种问题:机构设置重复,职责划分不清,造成人浮于事,行政干预过多,服务职能弱化,重行政领导,轻便民服务。现在的机构设置已经成为了阻碍城市快速发展的绊脚石了。如果在智慧城市运营阶段,依旧旧瓶装新酒,只会大大削弱城市的发展潜力。

我们应该把一个智慧城市视为一个由多个互联互通的子系统构成的单一的宇宙飞船系统,而不是沿用目前这种按领域划分、条块儿分割的部门,这样才能保证智慧城市从全局层面上得到整齐划一的管理。

但这个问题不是一个厂商或许多厂商就能解决的,需要政府更深刻的认识到这一点才能做出调整。

3、智慧城市最需要什么样的人才?

城市是一个复杂的有机体,里面有各种人才在各自岗位上工作,从而使城市正常运转。智慧城市对城市运营人才提出了更高的要求,尤其是对城市统一指挥人才的需求。这样的人才就好比星际迷航中企业号的舰长,他要对城市中的每个环节了如指掌,洞察智慧城市汇聚的信息,快速做出正确的决策。所以我们的教育架构在应对智慧城市时,也应该顺势而动,设置城市综合指挥这样的专业,专门为城市输送这样的人力资源。

五、结语

一切仅仅是开始!

理解Unikernels

Docker, Inc在今年年初宣布收购Unikernel Systems公司时,Unikernel对大多数技术人员来说还是很陌生的。直到今天,知名问答类网站知乎上也没有以Unikernel为名字的子话题。国内搜索引擎中关于Unikernel的内容很少,实践相关的内容就更少了。Docker收购Unikernel Systems,显然不是为了将这个其未来潜在的竞争对手干掉,而是嗅到了Unikernel身上的某些技术潜质。和关注Docker一样,本博客后续将持续关注Unikernel的最新发展和优秀实践,并将一些国外的优秀资料搬(翻)移(译)过来供国内Unikernel爱好者和研究人员参考。

本文翻译自BSD Magazine2016年第3期中Russell Pavlicek的文章《Understanding Unikernels》,译文全文如下。

当我们描述一台机器(物理的或虚拟的)上的操作系统内核时,我们通常所指的是运行在特定处理器模式(内核模式)下且所使用的地址空间有别于机器上其他软件运行地址空间的一段特定的软件代码。操作系统内核通常用于提供一些关键的底层函数,这些函数被操作系统中其他软件所使用。内核通常是一段通用的代码,(有需要时)一般会被做适当裁剪以适配支持机器上的应用软件栈。这个通用的内核通常会提供各种功能丰富的函数,但很多功能和函数并不是内核支持的特定应用程序所需要的。

事实上,如果看看今天大多数机器上运行的整体软件栈,我们会发现很难弄清楚到底哪些应用程序运行在那台机器上了。你可能会发现即便没有上千,也会有成百计的低级别实用程序(译注:主要是指系统引导起来后,常驻后台的一些系统服务程序),外加许多数据库程序,一两个Web服务程序,以及一些指定的应用程序。这台机器可能实际上只承担运行一个单独的应用程序,或者它也可能被用于同时运行许多应用。通过对系统启动脚本的细致分析来确定最终运行程序的集合是一个思路,但还远非精准。因为任何一个具有适当特权的用户都可以去启动系统中已有应用程序中的任何一个。

Unikernel的不同之处

基于Unikernel的机器的覆盖面(footprint)是完全不同的。在物理机器(或虚拟机映像)中,Unikernel扮演的角色与其他内核是相似的,但实现特征显著不同。

例如,对一个基于Unikernel的机器的代码进行分析就不会受到大多数其他软件栈的模糊性的影响。当你考虑分析一个Unikernel系统时,你会发现系统中只存在一个且只有一个应用程序。那种标准的多应用程序软件栈不见了,前面提到的过多的通用实用程序和支持函数也不见了。不过裁剪并未到此打住。不仅应用软件栈被裁剪到了最低限度,操作系统功能也同样被剪裁了。例如,多用户支持、多进程支持以及高级内存管理也都不见了。

认为这很激进?想想看:如果整个独立的操作系统层也不见了呢!内核不再有独立的地址空间,应用程序也不再有独立的地址空间了。为什么?因为内核的功能函数和应用程序现在都成为了同一个程序的一部分。事实上,整个软件栈是由一个单独的软件程序构成的,这个程序负责提供应用程序所需的所有代码以及操作系统的功能函数。如果这还不够的话,只需在Unikernel中提供应用所需的那些功能函数即可,所有其他应用程序所不需要的操作系统功能函数都会被整体移除掉。

一个反映新世纪现实的软件栈

Unikernel的出现,其背后的目的在于对这个行业的彻底的反思。几十年来,在这个行业里我们的工作一直伴随着这样一个理念:机器的最好架构是基于一个通用多用户操作系统启动,加载一系列有用的实用工具程序,添加我们可能需要使用的应用程序。最后,再使用一些包管理软件来管理这种混乱的情况。

35年前,这种做法是合乎情理的。那个时候,硬件很昂贵,虚拟化的选择非常有限甚至是不可用。安全仅局限于保证计算中心坐在你身旁的人没有在偷看你输密码。一台机器需要同时处理许多用户运行的许多应用程序以保证较高的成本效益。当我还在大学(1、2千年前。 译注:作者开玩笑,强调那时的古老^_^)时,在个人计算机出现之前,学校计算机中心有一个超级昂贵的机器(以今天的标准来看) – 一台DEC PDP-11/34a,配置了248K字节的内存和25M磁盘,为全校的计算机科学、工程以及数学专业的学生使用。这台机器必须服务于几百名学生每个学期想出的每个功能。

对比计算机历史上那个远古时代的恐龙和现代的智能手机,你会发现手机拥有的计算能力高出那台机器几个数量级。这样一来,我们为什么还要用在计算机石器时代所使用的那些原则去创建机器内核映像呢?重新思考与新的计算现实相匹配的软件栈难道不是很有意义吗?

在现代世界,硬件十分便宜。虚拟化无处不在且运行效率很高。几乎所有计算设备都连接在一个巨大的、世界范围的且存在潜在恶意黑客的网络中。想想看:一台DNS服务器真的不需要上千兆的字节去完成它的工作;一台应用服务器也真的不需要为刚刚利用一个漏洞获得虚拟命令行访问权的黑客准备数千实用工具程序。 一个Web服务器并不需要验证500个不同的分时用户的命令行登录。那么为什么我们现在仍然在使用支持这些不需要的场景的过时的软件栈概念呢?

Unikernel的美丽新世界

那么一个现代软件栈应该是什么样子的呢?下面这个怎么样:单一应用映像,虚拟化的,高度安全的,超轻量的,具有超快启动速度。这些正是Unikernel所能提供的。我们逐一来说:

单一映像

叠加在一个通用内核上的数以百计的实用工具程序和大量应用程序被一个可执行体所替代。这个可执行体将所有需要的应用程序和操作系统代码放置在一个单一的映像中。它只包含它所需要的。

虚拟化的

就在几年前,你可以很幸运地在一台服务器上启动少量虚拟机。硬件的内存限制以及守旧的、吃内存的软件栈不允许你在一台服务器上同时启动太多虚机。今天我们有了配置了数千兆内存的高性能服务器,我们不再满足于每台机器仅能启动少量虚机了。如果每个虚机映像足够小,我们可以在一个服务器上同事运行数百个,甚至上千个虚机应用。

安全

在云计算时代,我们发现恶意黑客可以例行公事般入侵各地的服务器,即便是那些知名大公司和政府机构的服务器也不例外。这些违规行为常常是利用了某个网络服务的缺陷并进入了软件栈的更低层。从那开始,恶意入侵者可以利用系统中已有的实用程序或其他应用程序来实施他们的邪恶行为。在Unikernel栈中,没有其他软件可以协助这些恶意的黑客。黑客必须足够聪明才能入侵其中的应用程序,但接下来还是没有驻留的工具可以用来协助做坏事。虽然Unikernel栈不会使得软件彻底完全的变安全,但是它确能显著提升软件的安全级别。并且这是云计算时代长期未兑现的一种进步。

超轻量

一个正常的VM仅仅是为了能在网络中提供少量的服务就要占用千兆的磁盘和内存空间。若使用Unikernel,我们可以不再纠结于这些资源需求。例如,使用MirageOS(一个非常流行的Unikernel系统),我们可以构建出一个具备DNS服务功能的VM映像,其占用的磁盘空间仅仅为449K – 是的,还不到半兆。使用ClickOS,一个来自NEC实验室的网络应用Unikernel系统制作的网络设备仅仅使用6兆内存却可以成功达到每秒5百万包的处理能力。这些绝不是基于Unikernel的设备的非典型例子。鉴于Unikernels的小巧精简,在单主机服务器上启动数百或数千这类微小虚拟机的想法似乎不再遥不可及。

快速启动

普通VM的引导启动消耗较长时间。在现代硬件上启动一个完整操作系统以及软件栈直到服务上线需要花费一分钟甚至更多的时间。但是对于基于Unikernel的VM来说,这种情况却不适用。绝大多数的Unikernel VM引导启动时间少于十分之一秒。例如,ClickOS网络VM文档中记录的引导启动时间在30毫秒以下。这个速度快到足以在服务请求到达网络时再启动一个用于处理该请求的VM了(这正是Jitsu项目所要做的事情,参见http://unikernel.org/files/2015-nsdi-jitsu.pdf)。

但是,容器不已经做到这一点了吗?

在创建轻量级,快速启动的VM方面,容器已经走出了很远。但在幕后容器依然依赖着一个共享的、健壮的操作系统。从安全的角度来看,容器还有很多要锁定的地方。很明显我们需要加强我们在云中的安全,但不是去追求这些相同的、陈旧的、在云中就会快速变得漏洞百出的安全方法。除此之外,Unikernel的最终覆盖面仍然要比容器能提供的小得很多。因此容器走在了正确的方向上,而Unikernel则设法在这个未来云所需要的方向上走的更远。

Unikernels是如何工作的?

正如之前提到的,传统机器自底向上构建:你选择一个通用的操作系统内核,添加大量实用工具程序,最后添加应用程序。Unikernel正好相反:它们是自底向上构建的。聚焦在你要运行的应用程序上,恰到好处地添加使其刚好能运行的操作系统函数。大多数Unikernel系统依靠一个编译链接系统,这个系统编译应用程序源码并将应用程序所需的操作系统函数库链接进来,形成一个单独的编译映像。无需其他软件,这个映像就可以运行在VM中。

如何对结果进行调试?

由于在最终的成品中没有操作系统或实用工具程序,绝大多数Unikernel系统使用了一种分阶段的方法来开发。通常,在开发阶段一次编译会生成一个适合在Linux或类Unix操作系统上进行测试的可执行程序。这个可执行程序可以运行和被调试,就像任何一个标准程序那样。一旦你对测试结果感到满意,你可以重新编译,打开开关,创建独立运行在VM中的最终映像。

在生产环境机器上缺少调试工具并没有最初想象的那样糟糕。绝大多数组织不允许开发人员在生产机器上调试,相反,他们收集日志和其他信息,在开发平台重现失败场景,修正问题并重新部署。这个事实让调试生产映像的限制也有所缓和。在Unikernel世界中,这个操作顺序也已具备。你只需要保证你的生产环境映像可以输出足够多的日志以方便重构失败场景。你的标准应用程序可能正在做这些事情了。

有哪些可用的Unikernel系统?

现在有很多Unikernel可供选择,它们支持多种编程语言,并且Unikernel项目还在持续增加中。一些较受欢迎的Unikernel系统包括:

  • MirageOS:最早的Unikernels系统之一,它使用Ocaml语言;
  • HaLVM:另外一个早期Unikernels系统,由Haskell语言实现;
  • LING:历史悠久的项目,使用Erlang实现;
  • ClickOS:为网络应用优化的系统,支持C、C++和Python;
  • OSv:稍有不同的Unikernel系统,它基于Java,并支持其他一些编程语言。支持绝大多数JAR文件部署和运行。
  • Rumprun:使用了来自NetBSD项目的模块代码,目标定位于任何符合POSIX标准的、不需要Fork的应用程序,特别适合将现有程序移植到Unikernel世界。

Unikernel是灵丹妙药吗?

Unikernel远非万能的。由于他们是单一进程实体,运行在单一地址空间,没有高级内存管理,很多程序无法很容易地迁移到Unikernel世界。不过,运行于世界各地数据中心中的大量服务很适合该方案。将这些服务转换为轻量级Unikernel,我们可以重新分配服务器能力,任务较重的服务可以从额外的资源中受益。

转换成Unikernel的任务数量比你想象的要多。在2015年,Martin Lucina宣布成功创建了一个”RAMP”栈 – LAMP栈(Linux、Apache、MySQL和PHP/Python)的变种。RAMP栈使用了NGINX,MySQL和PHP,它们都构建在Rumprun之上。Rumprun是Rump内核的一个实例,而Rump内核则是基于NetBSD工程模块化操作系统功能函数集合的一个Unikernel系统。所以这种常见的解决方案堆栈可以成功地转化迁移到Unikernels世界中。

更多信息

要想学习更多有关Unikernels方面的内容,可以访问http://www.unikernel.org或观看2015年我在Southeast Linuxfest的演讲视频




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn

文章

评论

  • 正在加载...

分类

标签

归档











更多