云原生训练营模块一 Go语言特性

Posted 果子哥丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了云原生训练营模块一 Go语言特性相关的知识,希望对你有一定的参考价值。

Go语言特性

如何学习云原生技术?

代码驱动

  • 掌握Go语言编程能力

从点到面

  • 学习容器技术
    • cgroup,namespace
    • 网络协议栈
    • 文件系统
  • 抓住核心掌控全局
    • 深入理解Kubernetes
      • API定义
      • 控制器模式
      • 核心组件
      • 二次开发(webhook、鉴权、控制器)
  • 大规模生产化
    • 多集群
    • 服务网格和多网格

构建高可用微服务架构统一思想

1、基准代码
一份基准代码,多份部署

2、依赖
显式声明依赖关系

3、配置
在环境中存储配置

4、后端服务
把后端服务当作附加资源

5、构建、发布、运行
严格分离构建和运行

6、进程
以一个或多个无状态进程运行应用

7、端口绑定
通过端口绑定提供服务

8、并发
通过进程模型进行扩展

9、易处理
快速启动或优雅终止可最大化健壮性

10、开发环境与线上环境等价
尽可能的保持开发,预发布,线上环境相同

11、日志
把日志当作事件流

12、管理进程
后台管理任务当作一次性进程运行

Go语言

Go语言特点

Go支持的特性:
Go语言是一个可以编译高效,支持高并发的,面向垃圾回收的全新语言。

  • 秒级完成大型程序的单节点编译
  • 依赖管理清晰
  • 不支持继承,程序员无需花费精力定义不同类型之间的关系
  • 支持垃圾回收,支持并发执行,支持多线程通讯
  • 对多核计算机支持友好

Go不支持的特性:

  • 不支持函数重载和操作符重载
  • 为了避免在C/C++开发中的一些Bug和混乱,不支持隐式转换
  • 支持接口抽象,不支持继承
  • 不支持动态加载代码
  • 不支持动态链接库
  • 通过recover和panic来替代异常机制
  • 不支持断言
  • 不支持静态变量

Go语言编译环境

  • Go安装文件以及源代码
    https://golang.google.cn/dl
  • 下载对应平台的二进程文件并安装
  • 环境变量
    • GOROOT
      • go的安装目录
    • GOPATH
      • src:存放源代码
      • pkg:存放依赖包
      • bin:存放可执行文件
    • 其他常用变量
      • GOOS、GOARCH、GOPROXY
      • 国内用户建议设置goproxy:export GOPROXY=https://goproxy.cn

IDE设置(VS Code)

  • 下载并安装Visual Studio Code
    https://code.visualstudio.com/download
  • 安装Go语言插件
    https://marketplace.visualstudio.com/items?itemName=golang.go

基本命令

  • bug
  • build 编译可执行文件
  • clean
  • doc
  • env
  • fix
  • fmt go fmt test.go
  • generate
  • get
  • install
  • list
  • mode
  • run 可以编译并运行命令源码文件。
  • test
  • tool
  • version
  • vet
go env用于打印Go语言的环境信息。
go run命令可以编译并运行命令源码文件。
go get可以根据要求和实际情况从互联网上下载或更新指定的代码包及其依赖包,并对它们进行编译和安装。
go build命令用于编译我们指定的源码文件或代码包以及它们的依赖包。
go install用于编译并安装指定的代码包及它们的依赖包。
go clean命令会删除掉执行其它命令时产生的一些文件和目录。
go doc命令可以打印附于Go语言程序实体上的文档。我们可以通过把程序实体的标识符作为该命令的参数来达到查看其文档的目的。
go test命令用于对Go语言编写的程序进行测试。
go list命令的作用是列出指定的代码包的信息。
go fix会把指定代码包的所有Go语言源码文件中的旧版本代码修正为新版本的代码。
go vet是一个用于检查Go语言源码中静态错误的简单工具。
go tool pprof命令来交互式的访问概要文件的内容。

go build

  • Go语言不支持动态链接,因此编译时会把所有依赖编译进同一个二进制文件。
  • 指定输出目录。
    • go build -o bin/mybinary
  • 常用环境设置编译操作系统和CPU架构。
    • GOOS=linux GOARCH=amd64 go build
  • 全支持列表。
    • $GOROOT/src/go/build/syslist.go

go test
Go语言原生自带测试

import "testing"
func TestIncrease(t *testting.T)
  t.Log("Start testing")
  increase(1,2)

go test ./… -v 运行测试
go test命令扫描所有*_test.go为结尾的文件,惯例是将测试代码与正式代码放在同目录,如foo.go的测试代码一般写在foo_test.go

go vet
检查编译器可能发现不出来的错误

代码版本控制

  • 下载并安装 Git command line
    • https://git-scm.com/downloads
  • Github
    • 本课程示例代码均上传在 https://github.com/cncamp/golang
  • 创建代码目录
    • mkdir -p $GOPATH/src/github.com/cncamp
    • cd $GOPATH/src/github.com/cncamp
  • 代码下载
    • git clone https://github.com/cncamp/golang.git
  • 修改代码
  • 上传代码
    git add filename
    git commit -m 'change logs'
    git push
    

在线环境
国内可直接访问的playgroup:https://goplay.tools

控制结构

If条件语句
基本形式:

if condition1 
  // do something
 else if condition2 
  // do something else
 else 
  // catch-all or default

if的简短语句
同for一样,if语句可以在条件表达式前执行一个简单的语句

if v := x - 100; v < 0 
  return v


intVal := 1 相等于:
var intVal int 
intVal =1 

switch分支语句

switch var1 
  case val1: // 空分支
  case val2:
      fallthrough // 执行case3中的f()
  case val3:
      f()
  default: //默认分支
      ...


如果case带有fallthrough,程序会继续执行下一条case,不会再判断下一条case的expr

for循环语句
Go只有一种循环结构:for循环

- 计入计数器的循环
for 初始化语句;条件语句;修饰语句 
for i := 0;i < 10; i++ 
  sum += i


- 初始化语句和后置语句酸可选的,此场景与while等价
for ; sum < 1000; 
  sum += sum


- 无限循环
for 
  if condition1 
      break
  

for-range遍历数组
遍历数组,切片,字符串,Map等

for index,char := range myString 
...


for key,value := range MyMap 
...


for index,value := range MyArray 
...

需要注意:如果for range遍历指针数组,则value取出的指针地址为原指针地址拷贝。

package main

import 
    "flag"
    "fmt"
    "os"


func main() 
  name := flag.String("name", "world", "specify the name you want to say hi")
  flag.Parse()
  fmt.Println("os args is:", os.Args).   // Args是go运行时传入的参数
  fmt.Println("input parameter is", *name)
  fullString := fmt.Sprintf("Hello %s from Go\\n", *name)
  fmt.Println(fullString) 



flag的话是 运行时,带上--参数。  --name jesse ,就会把jesse传进去,参数名与参数值, Hello jesses from Go。 如果不带上参数,按照上面逻辑是 Hello World from Go
go run test.go --name jesse

数据结构

变量与常量

  • 常量:const
  • 变量:var

var a,b,c bool
var i,j int = 1,2
函数内,简洁赋值语句 := 可在类型明确的地方代替var声明
函数外,每个语句都必须以关键字(var, func等等),因此 := 结构不能再函数外使用


类型转换:
var i int = 42
var f float64 = float64(i)

数组

  • 相同类型且长度固定连续内存片段
  • 以编号访问每个元素
  • 定义方法
    • var identifier [len] type
  • myArray := [3]int[1,2,3]

切片

  • 切片是对数据一个连续片段的引用
  • 数组定义中不指定长度的即为切片 var identifier [] type
  • 切片在未初始化之前默认为nil,长度为0
  • 常用方法
package main

import (
	"fmt"
)

func main() 
	myArray := [5]int1, 2, 3, 4, 5
	mySlice := myArray[1:3]
	fmt.Printf("mySlice %+v\\n", mySlice)
	fullSlice := myArray[:]
	remove3rdItem := deleteItem(fullSlice, 2)
	fmt.Printf("remove3rdItem %+v\\n", remove3rdItem)


// 没有原生的删除,通过append的前后组合到一起
func deleteItem(slice []int, index int) []int 
	return append(slice[:index], slice[index+1:]...)

Make和New

  • New返回指针地址
  • Make返回第一个元素,可预设内存空间,避免未来的内存拷贝

Map字典

package main

import (
	"fmt"
)

func main() 
	myMap := make(map[string]string, 10)
	myMap["a"] = "b"
	myFuncMap := map[string]func() int
		"funcA": func() int  return 1 ,
	
	fmt.Println(myFuncMap)
	f := myFuncMap["funcA"]
	fmt.Println(f())
	value, exists := myMap["a"]
	if exists 
		println(value)
	
	for k, v := range myMap 
		println(k, v)
	

关于切片的常见问题
(1)b假设达到空间上限,再新增元素,会导致开辟新的空间,b的地址和新的地址是不一样。 —— 这里需要保证前后的地址一致,即a地址赋值给a,而不是b赋值到a。
(2)假设有个切片是int型,当value*2的时候,这个变动会回去吗? 不会的,是因为把遍历的值临时赋给了value。
(3)通过取下标访问切片,访问的就是指针地址,这时会生效。

结构体和指针

  • 通过type-struct关键字自定义结构体
  • Go语言支持指针,但不支持指针运算
    • 指针变量的值为内存地址
    • 未赋值的指针为nil
  • 结构体里只能包含属性,接口只能定义行为
  • New返回指针地址

结构体标签

  • 结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag)
  • 使用场景:Kubernetes APIServer
package main

import (
	"encoding/json"
	"fmt"
	"reflect"
)

type MyType struct 
	Name string `json:"name"`
	Address

type Address struct 
	City string `json:"city"`

func main() 
	mt := MyTypeName: "test",Address: AddressCity: "shanghai"
	b, _ := json.Marshal(&mt)
	fmt.Println(string(b))
	
	myType := reflect.TypeOf(mt)
	name := myType.Field(0)
	tag := name.Tag.Get("json")
	println(tag)
	
	tb := TypeBP2: "p2", TypeA: TypeAP1: "p1"
	//可以直接访问 TypeA.P1
	println(tb.P1)


type TypeA struct 
	P1 string


type TypeB struct 
	P2 string
	TypeA

练习:
给定一个字符串数组:[“I”,“am”,“stupid”,“and”,“weak”],用for循环遍历该数组并修改为[“I”,“am”,“smart”,“and”,“strong”]

package main

import (
	"fmt"
)

func main() 
	arr := [5]string"I","am","stupid","and","weak"
        for i,v := range arr 
          switch v 
          case "stupid":
              arr[i] = "smart"
          case "weak":
              arr[i] = "strong"
          default:
              break
          
        
        fmt.Println(arr)

接下来计划:

  • 函数调用
  • 常用语法
  • 多线程
  • 深入理解channel
  • 基于channel编写一个生产者消费者程序



生产者与消费者代码

基于同步锁

import (
	"fmt"
	"sync"
)
var (
	productCount = 5
	mutex sync.Mutex
	cond = sync.NewCond(&mutex)
)
type producer struct 


func (r *producer) produce() 
	for 
		cond.L.Lock()
		if productCount < 10 
			productCount++
			fmt.Printf("生产者生产了一个产品,当前存量%d\\n", productCount)
		 else 
			fmt.Printf("仓库满了,当前容量%d\\n", productCount)
			cond.Wait()
		
		cond.L.Unlock()
		cond.Broadcast()
	

func (r *consumer) consumer() 
	for 
		cond.L.Lock()
		if productCount > 0 
			productCount--
			fmt.Printf("消费者消费了一个产品,当前存量%d\\n", productCount)
		 else 
			fmt.Printf("仓库空了,当前存量%d\\n", productCount)
			cond.Wait()
		
		cond.L.Unlock()
		cond.Broadcast()
	

type consumer struct 


func main() 
	var p = producer
	var c = consumer
	go p.produce()
	go c.consumer()
	time.Sleep(time.Microsecond * 10) //防止因为生产者消费者执行方法时主线程已经结束了,给主线程加个时延

基于通道实现
基于通道实现的这种方式需要注意避免从空通道中取数,否则就会出现死锁

var countChan chan int

type producer struct 


func (r *producer) produce() 
	for i := 0; i < 10; i++ 
		count := <-countChan
		if count < 10 
			count++
			fmt.Printf("生产者生产了一个产品,当前存量%d\\n", count)
		 else 
			fmt.Printf("仓库满了,当前容量%d\\n", count)
		
		countChan <- count
		var t = rand.Intn(100)
		time.Sleep(time.Duration(t))
	


func (r *consumer) consumer() 
	for i := 0; i < 10; i++ 
		count := <-countChan
		if count > 0 
			count--
			fmt.Printf("消费者消费了一个产品,当前存量%d\\n", count)
		 else 
			fmt.Printf("仓库空了,当前存量%d\\n", count)
		
		countChan <- count
		var t = rand.Intn(100)
		time.Sleep(time.Duration(t))
	


type consumer struct 


func main() 
	countChan = make(chan int, 1)
	countChan <- 0
	var p = producer
	var c = consumer
	go p.produce()
	go c.consumer()
	time.Sleep(1000)

基于select解决通道阻塞问题

import (
	"fmt"
	"math/rand"
	"time"
)

var countChan chan int

type producer struct 


func (r *producer) produce() 
	for i := 0; i < 10; i++ 
		select 
		case count := <-countChan:
			if count < 10 
				count++
				fmt.Printf("生产者生产了一个产品,当前存量%d\\n", count)
			 else 
				fmt.Printf("仓库满了,当前容量%d\\n", count)
			
			countChan <- count
			var t = rand.Intn(100)
			time.Sleep(time.Duration(t))
		default:
			fmt.Println("初始化产品池")
			countChan <- 0
		
	


func (r *consumer) consumer() 
	for i := 0; i < 10; i++ 
		select 
		case count := <-countChan:
			if count > 0 
				count--
				fmt.Printf("消费者消费了一个产品,当前存量%d\\n", count)
			 else 
				fmt.Printf("仓库空了,当前存量%d\\n", count)
			
			countChan <- count
			var t = rand.Intn(100)
			time.Sleep(time.Duration(t))
		default:
			fmt.Println("初始化产品池")
			countChan <- 0
		

	


type consumer struct 


func main() 
	countChan = make(chan int, 1)
	var p = producer
	var c = consumer
	go p.produce()
	go c.consumer()
	time.Sleep(10000)

以上是关于云原生训练营模块一 Go语言特性的主要内容,如果未能解决你的问题,请参考以下文章

云原生训练营模块二 Go语言进阶

云原生训练营模块二 Go语言进阶

云原生第一 语言我说是Go ,你觉得呢

助教招募|云原生训练营有偿招募兼职助教

go-chassis 为 grpc-go 带来高级云原生特性

Go语言和其他语言的不同之基本语法