go语言初体验

背景

目前后台业务系统的大部分接口都是以同步阻塞式的方式工作,资源利用率低,单机qps有限。因为go语言原生支持协程,能够同时满足开发效率和程序性能,于是决定引入go语言进行改造。

主要是分享以下三点心得:

  • C/C++库的封装
  • map内部成员赋值,以及protobuf协议的支持
  • 网络I/O超时处理

C/C++库的封装

环境搭建,语法这些就不赘述了。阅读 A tour of go 可以很好的认识go语言。

引入go语言,碰到的第一个问题是如何复用已有的经过了长期线上检验的C/C++基础库,所幸go语言通过Cgo可以很方便的调用C库中函数。具体方法见Command cgo。在使用过程中发现,Cgo可以支持C,但无法完美支持C++,在import C关键字上面include进来的头文件中,如果有包含C++的头文件时例如string,Cgo将会报错,另外C++的namespace也会让我们无法用go语言访问namespace下的函数或变量。解决办法是用C来包裹C++,通过C来调用C++,go来调用C解决。如我有一个用C++编写的库libtest.a和test.h,其中test.h中有包含C++中的string,这时候按照Command cgo中的方法,在test.go文件中引入:

packege test
// #cgo LDFLAGS: -L/usr/local/comm/lib -ltest -lstdc++
// #cgo CPPFLAGS: -I/usr/local/comm/include
// #include "test.h"

编译过程中会报错。注意,上面使用的都是绝对路径,这是因为当我们在build go项目的时候,相对路径是以执行build命令时的路径来作为起点的,否则一旦build时的目录改变,就会导致Cgo封装失败。

这时候,为了调用test库,我们只好再写一个t.h和t.cpp(随便取的)

其中t.h的内容

#ifdef __cplusplus
extern "C" {
#endif

void C_test();//我们通过该函数来调用test库中的函数,这里的命名都是随便取的

#ifdef __cplusplus
}
#endif

在t.cpp文件的内容:

#include "t.h"
#include "test.h"

#ifdef __cplusplus
extern "C" {
#endif

void C_test(){
    以C_test函数来封装test库中的函数
    ...
}

#ifdef __cplusplus
}
#endif

通过将t.cpp编译成目标文件:

g++ -c t.cpp -Iabcd -Lefg -ltest

有两种方式,一是将t.o打包进原来的libtest.a中,第二种当然就是将其打包为独立的一个静态库,这里推崇第二种方式:

ar -r libt.a t.o

这时候在对应的go文件中引入t.h和t.a即可完成go对C++库的调用。

packege test
// #cgo LDFLAGS: -L./ -lt -L/usr/local/comm/lib -ltest -lstdc++
// #cgo CPPFLAGS: -I./ -I/usr/local/comm/include
// #include "t.h"

map内部成员赋值,以及protobuf协议的支持

在go语言的使用过程中,发现map结构中的value为自定义类型时,无法对该自定义类型内部的成员进行进行"写"操作(map结构中返回的value不可对其成员寻址),如运行以下代码:

package main
import "fmt"
type A struct{
    T int
}
func main(){
    m := make(map[int]A)
    a := A{1}
    m[1] = a
    m[1].T = 2
    fmt.Println(m)
}

编译器会返回不可对m[1].T赋值的错误。但是当map中的value为指针时即可,如将第7,8,9行代码换成如下:

    m := make(map[int]*A)
    a := A{1}
    m[1] = &a

这时即可完成对m[1].T的赋值。另外在map结构中的value类型为go的原生类型时则不存在这个问题(即byte/int8/16/32/64 float32/64,string,map,slice等等),如:

    m := make(map[int]map[int]int)
    ma := make(map[int]int)
    ma[1] = 1
    m[1] = ma
    m[1][1] = -1
    fmt.Println(m)

这时读写都没问题。但是在slice中却不存在这个问题。

在go中,将protobuf协议文件生成相应的go文件之后,对于每个字段生成的变量都是指针类型。protobuf协议在git上有golang开源的protobuf协议(毕竟都是google的东西),详细使用可以参见链接这里简单谈一下对于用指针的理解,使用指针可以更大程度的达到节流的目的,(而这里的节流带来的好处是更快的编解码速度,数据包交互完成速度(因为要传输的数据量少了),节省带宽),我们通过判断指针值是否为nil,为nil时则直接跳过,不为nil时说明有数据才将其序列化。

网络I/O超时处理

由于复杂的网络环境,我们必须考虑对来自客户端的每个请求的收包和回包以及系统内部调用链之间的网络请求设置超时处理,否则系统可能会存在大量无法释放的连接。

1 http server与client之间的超时设置

对于http服务器一般都是使用go标准库中的http包,只需要简单几行代码即可创建一个http server,如:

http.HandleFunc("/hello",func(w http.ResponseWriter,r *http.Request){
        io.WriteString(w,"hello world")
    });
http.ListenAndServe()

这里其实是使用了内部的变量DefaultServeMux,但是它默认并不支持I/O超时,因此我们需要自己创建http.Server来提供服务:

svr := &http.Server{
    Addr:         "0.0.0.0:8080",
    ReadTimeout:  4 * time.Second,
    WriteTimeout: 4 * time.Second,
}
svr.ListenAndServe()

其中ReadTimeOut是从Accept请求后到RequestBody完全读取的时间,WriteTimeOut是从RequstBody开始读取到完整回包的时间。

2 系统内部调用链的超时设置

系统内部的服务之间经常需要协同服务才能完成对外的请求,因此通信无法避免。通过给建立好的连接对象net.Connd调用SetDeadline,SetReadDeadline,SetWriteDeadline三个方法设置Deadline可以达到对每次I/O进行超时控制,一旦超时后对该连接对象进行Close操作。顾名思义,就是分别对连接对象设置读写超时,读超时和写超时的函数,他们的设置是永久生效而不是只作用于一次I/O,一旦超时后将返回超时错误,因此每次I/O操作前都需要调用它们,这里贴一个简单的例子:

func SendOnePacket(conn net.Conn, packet []byte, timeout int64) error {
    conn.SetWriteDeadline(time.Now().Add(time.Duration(int64(time.Millisecond) * timeout)))
    for {
        n, err := conn.Write(packet)
        if err != nil {
            return errors.New("Write error:" + err.Error())
        }
        if n == len(packet) {
            return nil
        }
        packet = packet[n:]
    }
    return nil
}

func RecvOnePacket(conn net.Conn, timeout int64) ([]byte, error) {
    conn.SetReadDeadline(time.Now().Add(time.Duration(int64(time.Millisecond)*timeout)))
    recvBuf := bytes.NewBuffer(nil)
    var msgLen int = 0
    var buf [4096]byte
    for {
        n, err := conn.Read(buf[0:])
        recvBuf.Write(buf[0:n])
        if err != nil && err != io.EOF {
            str := string(recvBuf.Bytes())
            return nil, errors.New("Read Error:" + err.Error()+ " data:"+str)
        }
        msgLen = CheckPacket(recvBuf)
        if msgLen > 0 {
            break
        }
        if msgLen < 0 {
            return nil,errors.New("CheckPacket error,ret:" + strconv.FormatInt(int64(msgLen),10))
        }
    }
    packet := recvBuf.Bytes()
    return packet[:msgLen], nil
}

CheckPacket函数根据自己的通信协议来实现,用于检查包是否完整,返回负数代表出错,为0表示不完整,大于0表示是接收完成,且该值为该数据包的完整长度。其中RecvOnePacket中的buf数组其实非常讲究,它会决定每一次的系统调用---Read函数,最多读取多少个字节(如果传入Read函数的切片长度为0的话直接就返回了),这个示例并为对数据缓存,因此出现粘包的话就雪崩了。

总结

目前小组内部也是刚引入go语言,对go的特性,深层次的一些实现理解还很浅。

原文地址:https://www.cnblogs.com/unnamedfish/p/8460431.html

时间: 2024-10-27 07:11:32

go语言初体验的相关文章

初探go-golang语言初体验

2017/2/24 一.初体验 1.环境 wget https://storage.googleapis.com/golang/go1.8.linux-amd64.tar.gz tar -C /usr/local -xzf go1.8.linux-amd64.tar.gz cat <<'_EOF' >>/etc/profile #golang export PATH=$PATH:/usr/local/go/bin export GOPATH=/opt/go _EOF source 

python语言初体验

在学习这门程序设计语言之前,对python语言没有过了解,认为是和c语言类似的一种程序设计语言,当时c语言学的很吃力,学习的效果也并不好.因为c语言留下的阴影,在上课之前是有些忐忑的,但是在上了两次课之后发现目前看来python语言要比c语言简单一些,实验课做的例子也能理解,老师上次课堂上留的作业也能自己写出来,这也给我学习python语言带来了一些信心,虽然不知道以后的课程会不会很难,自己能不能学会. Python语言对输入的符号空格很严谨,稍不留神就会输入错误,或者是多删除了一些本来就有的符

每天一点GO语言——Linux环境下安装Go语言环境以及编写Go语言程序初体验

每天一点GO语言--Linux环境下安装Go语言环境以及编写Go语言程序初体验 一.安装Go语言环境 [[email protected] opt]# yum -y install wget git [[email protected] opt]# wget -c https://studygolang.com/dl/golang/go1.10.3.linux-amd64.tar.gz [[email protected] opt]# tar -zxvf go1.10.3.linux-amd64

【Spark深入学习 -15】Spark Streaming前奏-Kafka初体验

----本节内容------- 1.Kafka基础概念 1.1 出世背景 1.2 基本原理 1.2.1.前置知识 1.2.2.架构和原理 1.2.3.基本概念 1.2.4.kafka特点 2.Kafka初体验 2.1 环境准备 2.2 Kafka小试牛刀 2.2.1单个broker初体验 2.2.2 多个broker初体验 2.3 Kafka分布式集群构建 2.3.1 Kafka分布式集群构建 2.3.2 Kafka主题创建 2.3.3 生产者生产数据 2.3.4消费者消费数据 2.3.5消息的

WCF之初体验

什么是WCF? WCF的全称是:Windows通信基础(WindowsCommunication Foundation),本质来讲,他是一套软件开发包. WCF和WebService的区别 Webservice:严格来说是行业标准,不是一种技术,使用XML扩展标记语言来表示数据(这个是跨语言和平台的关键.) WCF其实一定程度上就是ASP.NET WebService,因为它支持Web Service的行业标准和核心协议,因此ASP.NET Web Service和WSE能做的事情,它几乎都能胜

Xamarin.iOS开发初体验

Xamarin是一个跨平台开发框架,这一框架的特点是支持用C#开发IOS.Android.Windows Phone和Mac应用,这套框架底层是用Mono实现的. Mono是一款基于.NET框架的开源工程,包含C#语言编译器.CLR运行时和一组类库,能运行于Windows.Linux.Unix.Mac OS和Solaris.对于.NET程序员来说,Xamarin是走向安卓.iOS.Mac跨平台开发的神器,不仅能用熟悉的C#来开发,还能使用Visual Studio作为IDE.本文内容是Xamar

腾讯云服务器初体验

腾讯云平台选购云服务器,我选购了suse10 64bit的系统,付款后大约2分钟提示已经分配完毕,根据IP和用户名密码登陆服务器. 第一步:初始用户是root,需要自己创建用户组和用户. groupadd -g 1000 zd useradd -g zd -s /bin/csh -d /home/zd -m -p z zd 详解一下命令 groupadd -g gid 指定用户组id,组id最小为501,0-999是系统保留组id,用户设id最好是从1000开始 -r 创建一个系统账户 -o 允

JNI笔记之 初体验

Java Native Interface提供了java与c语言写的代码之间互相调用的方式.在c语言方面jni.h中声明了许多的类型和方法,有很多java的数据类型和c语言类型的转换方法函数. java里的int,String,byte[]等对应于C方面的jint,jstring,jbyteArray.int可以直接赋给jint型的变量. Java的String和C++的string是不能对等起来的,所以jstring的操作较为繁琐,通常可转为c里面的char *,有两种方式,先上简单的: JN

屌丝就爱尝鲜头——java8初体验

Java8已经推出,让我们看看他的魅力.让我们看看他改变较大的部分. 一.java8概述 Java8是由Oracle(甲骨文)公司与2014年3月27日正式推出的.Java8同时推出有3套语言系统,分别是Java SE8.Java SE Emebbled 8.Java ME8. Java SE8较以往的系统增强的功能有: ①增强了对集合式操作语言--lambda表达式的支持,"Lambda 表达式"(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演