创业公司更适合用 Go 语言

幸运草
幸运草
幸运草
896
文章
3
评论
2020年4月13日18:33:51 评论 265

创业公司更适合用 Go 语言

对于创业公司来说,人少资源少、产品又要求快速上线。选择合适的技术栈非常重要,本文就谈谈我们早期选择后端语言时的考量。

流行的后端语言,有 Java/Python/Ruby/PHP 等,首先排除的是 Python/Ruby,在明星创业公司里面,这两种语言用的贼多,比如我的老东家友盟是 Ruby 起家,知名论坛知乎是 Python 起家,那为啥要排除这哥俩呢?太小众了人难招。

于是 PHP 也被拿出来讨论,说实话这是我最不愿意的一门语言,但是考虑到人好招人呀,3000 块钱招一个 PHP 码农还会写模版不香嘛。但是研究了PHP 半天后就放弃了,为啥?PHP 已经变得很复杂了!

现在的 PHP 已经堪比 Java 了还多了好多 Java 没有的功能,本来是希望PHP门槛低好招人,这么一来人是招到了却无法掌握 PHP 那有何用呢,岂不是很没面子?

然后就是 Java,说实话我们最应该选 Java 的。因为我本身就是 Java 出身,写过 Android、 写过 Spring,还给 SpringBoot 写过好多入门文章。说不上大牛但是用起来必然没啥问题,但是毕竟是搞过的人,太了解 Spring 了,所以不用。

为啥呢?

Java 程序员工资高,虽然好招但是不好养。

另外,用 Spring 心智负担高,我觉得 Spring 的优点就是它的缺点,Spring 太面面俱到了。你能用到的功能 Spring 都可以通过 “简单的” 配置解决。这正是问题所在,干啥都要查文档、看文档做配置,对于老手来说经验多了配置起来很快,但是新手就难了。

一个很小的功能要阅读一篇很长的文档,了解这个功能的前前后后,然后写下两行配置。我觉得这是反人类的,我们是写代码的又不是写配置的,把自己搞成配置工程师不悲哀吗!另一个问题是,过一段时间我竟然忘记了那个配置的精妙之处。真不怨我不努力,打开 spring.io 你就知道啥是汪洋大海、啥是星斗满天。把 Spring 的文档都打印出来,感觉要把我们的办公室都塞满了。

关于 Java 的另一个问题是部署,Java 部署还要装容器,说实话我第一次知道 Java 部署还要装容器的时候是很惊讶的!你作为一个网络语言为啥不能自己跑呢?你知道 Tomcat 嘛?你知道 HotSpot 嘛?你知道 Servlet 嘛?全是心智负担,其实在开发端本来不应该关注这些的,但是你不了解你就不是一个成熟的Java工程师。

于是只能选 Go 了。

开发简单!

简单到一周学会就可以写线上代码。首先这是由于 Go 语言本身的设计,Go 语言在过去10年间几乎没有大的变化,从设计之初就秉承这样的设计理念 —— LESS is MORE!

创业公司更适合用 Go 语言

所以无论你之前的用的是 C、Java 还是 PHP 都能够快速的上手 Go 语言。这给招人提供了广阔的空间。同样 Go 的生态也秉承了 LESS is MORE 的 Go 语言哲学,大部分的 Go 相关库都是开箱既用,只需要查阅少量的文档就可以使用了。

由于简单,心智负担少,开发也变得更快、更容易。比如实现一个 OAuth 系统,用 Java 我需要看一上午的文档再写 3-400 百行代码。但是用 Go 直接写下 500-600 行代码就搞定了,这就是没心智负担。如果遇到了小程序那种看起来像 OAuth 但是实际不是 OAuth 的接口,用 Spring 就要自定义一下,但是用 Go 还是原来那样——只关注业务逻辑自身,就是快。

部署简单

用 Java 真是苦呀!在创业公司开发就是运维,一个人要干三个人的活,用 Java 干不过来呀。但是用 Go 就简单了,本地打包一个二进制传到服务运行就好了没有虚拟机没有容器,这让我想起来曾经年轻的时候在一台破烂的服务器上部署 WordPress 的那个下午 —— 上传、解压、搞定。

如果你们公司用 Docker,那就更爽了,用 Docker 来部署 Go 应用是最快的。并且容器技术 Docker 本身也是用 Go 写的,k8s 也是用 Go 写的。真的没见过比 Go 更容易部署的语言了。

跑得快!

Go 的性能在以上所有语言里都是最快的。Python/Ruby/PHP 这都是动态语言系的,存在各种性能问题而且内存占用大。Java 是编译+解释的语言,虽然比上面那些都好,但是毕竟还要运行个JVM,一个空对象都要占用十几个字节,整体内存消耗必然也高,从而导致以上这些语言对服务器会有更高的要求。要配置更大的内存、更好的 CPU 才能达到 Go 的水平。

通常大公司都还会有专门的 JVM 调优的岗位,让 JVM 在某些场景下跑的更快。但是对于 Go 来说,跑得快是白送的,曾经听一个老司机说过,把 Ruby 换成 Go 之后,快到不好意思…

对于创业公司来说,初期要严格控制成本的,能节省的尽量节省,我们用一台 Go 服务器达到了原来两台 Python 服务器才能做到的事情,难道不香吗?

关于生态

肯定有人会出来说,Go 的生态不行呀,你搞个分布式试试。说实话 Go 搞分布式轮子确实没有 Java 造的好、造的多。但是对于创业公司来说,前期的重点在于快,快速出产品验证IDEA,如果产品成功,我们可以慢慢招人过渡到大型系统,而不是一开始就搞一个可能会用到的大型系统。

另外,Go 的生态虽然没有 Java 那么强盛但是也不弱,基本你能用到的都有成熟的解决方案,比如用 go-kit、go-micro 也是分分钟搭建分布式系统。

目前我们的后台系统跑了6、7个服务,全部是用 Go 写的,这些服务每天都会有新的发布,我们一次发布大概只需要不到10秒的时间。系统核心是一个基于 NATs 的高吞吐量的消息系统,Web 框架我们用的是 一个叫 Echo 的框架。

创业公司更适合用 Go 语言

不过 Go 语言的框架都很快,没必要执着。我们的所有服务都是基于 API 的,没有使用模版渲染,前端调用 API 自己渲染。架构参考了一个叫 Drone 的开源项目,感觉它的架构很好值得学习。

对于创业公司来说,我觉得可以考虑 Go 了,其实大公司也很多用 Go 的了,比如知乎已经把很多原来的 Python 服务用 Go 重写了,知名二次元网站B站已经全部用 Go 了。招人门槛低、开发心智负担少、开发速度快、部署快难道这不是最理想的语言吗?

特别声明:以上文章内容仅代表作者本人观点,不代表变化吧观点或立场。如有关于作品内容、版权或其它问题请于作品发表后的30日内与变化吧联系。

转载请注明:{{title}}-变化吧
  • 赞助本站
  • 微信扫一扫
  • weinxin
  • 赞助本站
  • 支付宝扫一扫
  • weinxin
幸运草
Go语言接口规则 前端框架

Go语言接口规则

Go语言接口规则 接口是一个或多个方法签名的集合。任何类型的方法集中只要拥有该接口对应的全部方法签名。就表示它 "实现" 了该接口,无须在该类型上显式声明实现了哪个接口。对应方法,是指有相同名称、参数列表 (不包括参数名) 以及返回值,该类型也可以有其他方法。 接口赋值 对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针。 package main import "fmt" type User struct {     id   int     name string } func main() {     u := User{18, "oldboy"}     var i interface{} = u     u.id = 20     u.name = "Golang"     fmt.Printf("u : %vn", u)     fmt.Printf("i.(User) : %vn", i.(User)) } 运行结果: u : {20 Golang} i.(User) : {18 oldboy} 接口转型返回临时对象,只有使用指针才能修改其状态。 package main import "fmt" type User struct {     id   int     name string } func main() {     u := User{18, "oldboy"}     var vi, pi interface{} = u, &u     // vi.(User).name = "Golang"     pi.(*User).name = "Golang"     fmt.Printf("vi.(User) : %vn", vi.(User))     fmt.Printf("pi.(*User) : %vn", pi.(*User)) } 空接口 只有当接口存储的类型和对象都为nil时,接口才等于nil。 package main import (     "fmt" ) func main() {     var i interface{}     fmt.Printf("i => %vn", i)     fmt.Printf("(i == nil) => %vn", i == nil)     var p *int = nil     // i 指向 p,指向的对象是个nil,但是存在类型不是nil,是个指针     i = p     fmt.Printf("i => %vn", i)     fmt.Printf("(i == nil) => %vn", i == nil) } 运行结果: i => <nil> (i == nil) => true i => <nil> (i == nil) => false 接口实现 接口只有方法声明,没有数据字段,没有实现,也不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。 package main import (     "fmt" ) type Info interface {     GetAge() int     GetName() string } type User struct {     name string     age  int } func (u User) GetAge() int {     return u.age } func (u User) GetName() string {     return u.name } func main() {     var user Info = User{"oldboy", 18}     age := user.GetAge()     name := user.GetName()     fmt.Println(age, name) } 如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口。 package main import (     "fmt" ) type Age interface {     GetAge() int } type Name interface {     GetName() int } type User struct {     name string...
Go语言中处理 HTTP 服务器 前端框架

Go语言中处理 HTTP 服务器

1 概述 包 net/http 提供了HTTP服务器端和客户端的实现。本文说明关于服务器端的部分。 快速开始: package main import (   "log"   "net/http" ) func main() {   // 设置 路由   http.HandleFunc("/", IndexAction)   // 开启监听   log.Fatal(http.ListenAndServe(":8888", nil)) } func IndexAction(w http.ResponseWriter, r *http.Request) {  w.Write(byte(`<h1 align="center">来自变化吧的问候</h1>`)) } 运行程序,在浏览器上请求: localhost:8888,你会看到我们的结果 Go语言构建HTTP服务器还是很容易的。深入说明。 2 http.Server 类型 HTTP 服务器在 Go 语言中是由 http.Server 结构体对象实现的。参考 http.ListenAndServe() 的实现: // 文件:src/net/http/server.go // ListenAndServe always returns a non-nil error. func ListenAndServe(addr string, handler Handler) error {   server := &Server{Addr: addr, Handler: handler}   return server.ListenAndServe() } 可见过程是先实例化 Server 对象,再完成 ListenAndServe 。其中 Serve 对象就是表示 HTTP 服务器的对象。其结构如下 : // 文件:src/net/http/server.go type Server struct {   Addr    string  // TCP 监听地址, 留空为:":http"   Handler Handler // 调用的 handler(路由处理器), 设为 nil 表示 http.DefaultServeMux   TLSConfig *tls.Config // TLS 配置对象   ReadTimeout time.Duration // 请求超时时长   ReadHeaderTimeout time.Duration // 请求头超时时长   WriteTimeout time.Duration // 响应超时时长   IdleTimeout time.Duration // 请求空闲时长(keep-alive下两个请求间)   MaxHeaderBytes int // 请求头的最大长度   TLSNextProto mapfunc(*Server, *tls.Conn, Handler) // NPN 型协议升级出现时接管TLS连接的处理器函数映射表   ConnState func(net.Conn, ConnState) // 状态转换事件处理器   ErrorLog *log.Logger // 日志记录对象   disableKeepAlives int32     // accessed atomically.   inShutdown        int32     // accessed atomically (non-zero means we're in Shutdown)   nextProtoOnce     sync.Once // guards setupHTTP2_* init   nextProtoErr      error     // result of http2.ConfigureServer if used   mu         sync.Mutex   listeners  mapstruct{}   activeConn mapstruct{}   doneChan   chan struct{}   onShutdown func() } 可见 Server 定义了服务器需要的信息。 实例化了 Server 对象后,调用其 (srv *Server) ListenAndServe() error 方法。该方法会监听 srv.Addr 指定的 TCP 地址,并通过 (srv *Server) Serve(l net.Listener) error 方法接收浏览器端连接请求。Serve 方法会接收监听器 l 收到的每一个连接,并为每一个连接创建一个新的服务进程。 该 go...
go语言动态库的编译和使用 前端框架

go语言动态库的编译和使用

本文主要介绍go语言动态库的编译和使用方法,以linux平台为例,windows平台步骤一样,具体环境如下: $ echo $GOPATH /media/sf_share/git/go_practice $ echo $GOROOT /usr/lib/golang/ $ tree $GOPATH/src /media/sf_share/git/go_practice/src |-- demo |   `-- demo.go `-- main.go 1 directory, 2 files 在$GOPATH/src目录,有demo包和使用demo包的应用程序main.go,main.go代码如下: package main import "demo" func main() {    demo.Demo() } demo包中的demo.go代码如下: package demo import "fmt" func Demo() {    fmt.Println("call demo ...") } 由于demo.go是$GOPATH/src目录下的一个包,main.go在import该包后,可以直接使用,运行main.go: $ go run main.go call demo ... 现在,需要将demo.go编译成动态库libdemo.so,让main.go以动态库方式编译,详细步骤如下: 1 将go语言标准库编译成动态库 $ go install -buildmode=shared -linkshared  std 在命令行运行go install -buildmode=shared -linkshared  std命令,-buildmode指定编译模式为共享模式,-linkshared表示链接动态库,成功编译后会在$GOROOT目录下生标准库的动态库文件libstd.so,一般位于$GOROOT/pkg/linux_amd64_dynlink目录: $ cd $GOROOT/pkg/linux_amd64_dynlink $ ls libstd.so libstd.so 2 将demo.go编译成动态库 $ go install  -buildmode=shared -linkshared demo $ cd $GOPATH/pkg $ ls linux_amd64_dynlink/ demo.a  demo.shlibname  libdemo.so 成功编译后会在$GOPATH/pkg目录生成相应的动态库libdemo.so。 3 以动态库方式编译main.go $ go...
go语言 - Scheduler原理以及查看Goroutine执行 前端框架

go语言 - Scheduler原理以及查看Goroutine执行

最近看了看go scheduler的基本原理,本文介绍go语言scheduler的基本原理以及如何查看go代码中的go routine的执行情况。 0)Scheduler(调度器) 熟悉go语言的小伙伴应该都使用过goroutine。goroutine就是Go语言提供的一种用户态线程。Scheduler是调度goroutine的调度器。 Go的调度器内部有三个重要概念:M,P,G。 M (machine): 代表真正的内核操作系统里面的线程,和POSIX里的thread差不多,也是真正执行goroutine逻辑的部分。 G (Goroutine): 代表一个goroutine。 P (Processor): 代表调度的上下文,可以理解成一个局部调度器。 Go语言实现了多个Goroutine到多个Processor的映射(调度)。注意的是,针对X个Processor,Scheduler可能创建多于X个M(有些M可能会暂时被block)。还需要理解额外两个概念:GRQ(Global Running Queue)以及 LRQ(Local Running Queue)。未指定Processor的Goroutine会存放在GRQ上,在调度到合适的Processor后,会将一个Goroutine从GRQ移动到LRQ。 Go程序中发生了四类事件,允许调程序做出调度决策。 a. 使用关键字go b. 垃圾收集 c. 系统调用 d. 同步 1)Processor的个数 Processor的个数可以通过GOMAXPROCS环境变量设置。GOMAXPROCS默认值是CPU的核数。Processor的个数可以通过如下的go代码进行查询: package main import ( "fmt" "runtime" ) func main() { // NumCPU returns the number of logical // CPUs usable by the current process. fmt.Println(runtime.NumCPU()) } 也就是通过runtime.NumCPU函数可以获得Processor的个数。查看go语言的源代码(runtime/os_linux.c),NumCPU函数的实现函数如下:  func getproccount() int32 { const maxCPUs = 64 * 1024 var buf byte r := sched_getaffinity(0, unsafe.Sizeof(buf), &buf) if r < 0 { return 1 } n := int32(0) for _, v := range buf { for v != 0 { n += int32(v...