【Go】Golang小技巧总结

2 篇文章 0 订阅

https://github.com/rubyhan1314/Golang-100-Days
继续学习go语言 + k8s

数据库相关

工具🔧

SQL->Go Struct:http://stming.cn/tool/sql2go.html
JSON -> Go Struct:https://mholt.github.io/json-to-go/
SQL -> ES:http://www.ischoolbar.com/EsParser/


好用的k8s管理工具k9s -> https://github.com/derailed/k9s

一些有意思的问题

为什么一些云原生的中间件是用Golang写的

  • 并发性能强:
    Golang内置了轻量级的协程和通道,这使得编写并发程序变得更加简单和高效。在云原生环境中,高并发是非常常见的,因此使用Golang可以充分发挥其并发性能的优势。

  • 内存管理效率高:Golang使用垃圾回收机制,可以更有效地管理内存,减少内存泄漏的风险。在云原生应用中,需要高效地使用资源,这使得Golang成为一种很好的选择。

  • 代码可读性好:
    Golang的语法简洁、清晰,易于阅读和理解。这使得团队协作更加高效,也可以降低代码维护成本。

  • 部署容易:
    Golang可以将程序编译为静态二进制文件,可以简化应用程序的部署和维护。此外,Golang的依赖管理和包管理工具也非常方便。

  • 跨平台性好:
    Golang可以在多种操作系统和硬件平台上运行,包括Linux、Windows、MacOS等,这使得Golang成为一种很好的跨平台语言。

Go是面向对象语言吗

官方回答:是也不是

https://medium.com/gophersland/gopher-vs-object-oriented-golang-4fa62b88c701

请添加图片描述
go语言中,虽然没有明确提出面向对象的概念,但是基于已有的语法设计,我们也可以写出面向对象的代码。go语言中的面向对象是借助struct结构体实现的。

Golang实现面向对象的两个关键类型是struct和interface,其中struct类似C++的普通类类型,interface则对应抽象类类型。

与C++采用public/protected/private指示成员和方法的可见性不同,Golang采用大小写标识可见性,即大写字母开头的成员/方法对外可见,小写开头的则属于类的私有成员,外部不可以直接访问。

此外,Golang与C++在类类型的定义上还有一个重要区别,那就是Golang在struct内只需要声明类的成员变量,而不需要在类定义体内声明或定义所有的方法,方法定义都在struct之外完成。

面向对象三大特征:封装、继承和多态

封装

go语言中没有对象(object)这个关键词。对象(object)仅仅是一个单词,重要的是它所表示的封装数据含义。尽管go中没有object这种类型,但是go中的struct有着跟object相同的特性。而在go中,采用了一种算是规定的方法:使用大小写来确定,大写是包外可见的,小写的struct或函数只能在包内使用。

如果要对小写内部属性操作,只能通过geter seter来实现

type rect struct {
    width int  // 不可导出
    Height int // 可导出
}
继承

Golang没有完整实现继承,而是通过组合的方式实现。组合类(子类)可以直接调用被组合类(基类)的公有方法,访问基类的公有属性,子类也可以定义自己的属性,以及实现自己特有的方法。

type User struct {
	 ID uint64
	 Name string
	 Score uint64
}

type Member struct {
	 User
	 Level uint8
}

func (u User) Information () string {
 	return fmt.Sprintf('ID:%d Name:%s Score:%d', u.ID, u.Name, u.Score)
}

我们定义了一个命名为 User 的 struct,它包含 3 个成员变量,然后定一个一个命名为 Member 的 struct,它包含 2 个成员变量,其中一个成员变量是嵌入的 User,通过组合的方式,类型 Member 就“继承”了类型 User 的属性(成员变量)和方法。

多态

Golang 语言中也有接口 interface,它的 interface 的实现方式是 duck type,它不需要像其他面向对象编程语言那样,使用关键字 implements 显式声明,而是只需要类型通过实现接口中的所有方法来实现接口。


type BronzeMember struct {
 Discount uint8
}

type SilverMember struct {
 Discount uint8
}


func (b *BronzeMember) Information () string {
 return fmt.Sprintf('Discount:%d', b.Discount)
}

func (s *SilverMember) Information () string {
 return fmt.Sprintf('Discount:%d', s.Discount)
}

func Price (m MemberRights) {
 fmt.Println(m.Information())
}

func main () {
 s := &SilverMember{8}
 Price(s)
 g := new(GoldMember)
 g.Discount = 7
 Price(g)
}

那些fasthttp优化性能的技巧

1. 大量使用sync.Pool

fasthttp中很多对象都通过使用sync.Pool达到复用的目的,一是减少内存分配,二是减少GC。复用的对象包括:

  • workerpool
  • RequestCtx
  • reader
  • writer

2. 用slice而非map来存储kv

众所周知,http请求的header以及body其实就是一个kv字典,所以一般用map[string]string或者map[string][]string来表示。但是fasthttp使用了[]slice来存储kv,这样做的好处是:当参数使用完需要清理时不用释放内存,只需将长度变为0,其申请的内存还在,下次使用的时候直接覆盖就可以了,这样便于复用,避免重新申请内存。

这样做有一个弊端:当要查询的时候,时间复杂度是O(N),因为要遍历整个slice。但是一般一个请求kv项不会特别多,所以查询的性能损耗不明显:

type kv struct {
    key []byte
    value []byte
}

type sliceMap []kv

func (sm *sliceMap) Add(k, v []byte) {
    kvs := *sm
    if cap(kvs) > len(kvs) {
        kvs = kvs[:len(kvs)+1]
    } else {
        kvs = append(kvs, kv{})
    }
    kv := &kvs[len(kvs)-1]
    kv.key = append(kv.key[:0], k...)
    kv.value = append(kv.value[:0], v...)
    *sm = kvs
}
减少内存分配

通过在现有的 slice 上进行操作,sliceMap 尽可能地复用内存。当容量足够时,它通过重新切片 kvs = kvs[:len(kvs)+1] 来扩展 slice,避免了额外的内存分配。

减少垃圾回收压力

由于 slice 的元素是连续存储的,它可以更有效地被垃圾回收器处理,减少了垃圾回收的开销。而且,由于内存是复用的,垃圾回收的次数也大大减少。

CPU 预加载特性

由于 slice 的内存布局是连续的,它符合 CPU 缓存的工作原理,即一次性加载相邻数据。这种连续性使得 CPU 在访问一个 slice 元素后,能预加载相邻元素到缓存中,提高后续访问的速度。

因此,顺序访问 slice 时,缓存命中率高,减少了对主内存的访问次数,从而提高了性能。

存储数据特性

在处理 HTTP 请求时,通常 headers、query 参数或 cookies 的数量并不多。这意味着即使使用线性搜索,查找效率也不会成为性能瓶颈。

相比之下,虽然 hash map 提供了理论上接近 O(1) 的查找效率,但实际使用中也有其开销和复杂性。

  • 首先,hash map 的哈希计算本身就需要时间。

  • 其次,哈希碰撞时,hash map 要额外处理来解决碰撞,这可能涉及到链表遍历或重新哈希等操作。

这些因素在元素数量较少时可能会抵消 hash map 在查找效率上的理论优势,而 slice 则才是更优质的选择。

3. 大量使用[]byte,而非string

在golang中,string是不可变的。也就是说要修改一个string,需要重新分配一块内存。而使用[]byte有两个好处:

  • []byte可以支持修改,不需要重新申请内存。
  • []byte使用后可以像以下一样将其长度置为0以表示清理,这样可以用来复用,避免下次使用重新申请。

4. []byte和string的转换

string的底层其实是StringHeader结构:

type StringHeader struct {
	Data uintptr
	Len  int
}

而slice底层是SliceHeader结构:

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

如果直接用类型强转,则需新申请一块内存来存放Data数据。

然而两者结构相似,只是slice多了一个Cap属性。fasthttp基于这一点优化了两者的相互转换。

[]byte转换为string时,只需要将指针强转即可,相当于丢弃了Cap属性:

func b2s(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

string转换为[]byte时,则新建一个string结构体,但是将[]byte的Data和Len赋给新建的结构体,无需新申请一块Data内存。

func s2b(s string) (b []byte) {
	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
	bh.Data = sh.Data
	bh.Cap = sh.Len
	bh.Len = sh.Len
	return b
}

三.性能优化(用golang开发一些细节)

池化

这个技术是老生常谈了——内存池、对象池、协程池、连接池等。

  • 内存池:golang已经用了tcmalloc组件,我觉得没必要再自己做一次了。
  • 对象池:参考第一章,不同的场景使用不同的对象池技巧。
  • 协程池:我认为要看场景。对一个特定的生产者消费者模式而言,协程数量与可用核数对齐是个好办法。其他的场景,要考虑管理协程和创建/销毁协程哪个的成本更高。大多数情况下,协程池这个设计比较鸡肋。
  • 连接池:似乎也没特别好说的,不过这篇分析文章让人耳目一新:《Golang 黑魔法之 4 倍性能提升》——每次都读完接收缓冲区的数据,使得连接池的复用率提升。

https://mp.weixin.qq.com/s/T8MoOdGd2REPJfd38XNtpQ

1.堆外内存

众所周知,golang中分配太多对象,会给GC造成很大压力,从而影响程序性能。
那么,我在golang runtime的堆以外分配内存,就可以绕过GC了。

可以通过mmap系统调用来使用堆外内存,具体请见:《Go Mmap 文件内存映射简明教程》
对于堆外内存的应用,在此推荐一个非常经典的golang组件:fastcache。具体请看这篇我对fastcache的分析文章:《介绍一个golang库:fastcache 》。

2.golang内存管理优化

个人认为GC扫描对象、及其GC引起的STW,是golang最大的性能杀手。本小节讨论优化golang GC的各种技巧。

2.1 对象复用

对象太多会导致GC压力,但又不可能不分配对象。因此对象复用就是减少分配消耗和减少GC的释放消耗的好办法。

海量微型对象

因此,海量微型对象的场景,这样解决:

分配一大块数组,在数组中索引微型对象
考虑fastcache、freecache、bigcache这样的组件,通过堆外内存绕过GC

当然,也有缺点:不好缩容。

大量小型对象的情况 sync.pool

对于大量的小型对象,sync.Pool是个好选择。

sync.Pool不如上面的方法节省内存,但好处是可以缩容。

sync.Pool 可以在多个 goroutine 中通过减少结构体实例化从而减少 GC。请注意,这不是单例模式,只是尽可能通过复用减少实例创建。

如果可以复用对象,就能大大节省对象创建和回收的开销,从而达到优化内存使用的目的。对大对象,频繁使用的对象来说,该优化很有必要。

注意:

在请求量越大且单个请求处理时间越短的时候越有效
Get 后一定要 reset 实例内容再使用,不保证每次 Get 回来的是同一个实例

sync.pool Gin框架案例:

gin 框架需要为每一个 http 请求分配一个 context,为了减少 context 的创建就用了这个方法。

func New() *Engine {
    engine := &Engine{

    }
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

func (engine *Engine) allocateContext() *Context {
    return &Context{engine: engine}
}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// 从池中拿到Context
    c := engine.pool.Get().(*Context)
	// Context赋值
    c.writermem.reset(w)
    c.Request = req
    // Context重置
    c.reset()

    engine.handleHTTPRequest(c)

	// Context返回池中
    engine.pool.Put(c)
}

Context初始化重置

func (c *Context) reset() {
    c.Writer = &c.writermem
    c.Params = c.Params[:0]
    c.handlers = nil
    c.index = -1

    c.fullPath = ""
    c.Keys = nil
    c.Errors = c.Errors[:0]
    c.Accepted = nil
    c.queryCache = nil
    c.formCache = nil
    *c.params = (*c.params)[:0]
    *c.skippedNodes = (*c.skippedNodes)[:0]
}

数量可控的中型对象 channel

有的时候,我们可能需要一些定额数量的对象,并且对这些对象复用。

这时可以使用channel来做内存池。需要时从channel取出,用完放回channel。

2.2 代码逻辑优化

避免容器空间动态增长(扩容)

对于slice和map而言,在预先可以预估其空间占用的情况下,通过指定大小来减少容器操作期间引起的空间动态增长。特别是map,不但要拷贝数据,还要做rehash操作。

func xxx(){
  slice := make([]byte, 0, 1024)  // 有的时候,golangci-lint会提示未指定空间的情况
  m := make(map[int64]struct{}, 1000)
}
用slice代替map

假设有一个很小的map需要插入和查询,那么把所有key-value顺序追加到一个slice中,然后遍历查找——其性能损耗可能比分配map带来的GC消耗还要小。

map变成slice,少了很多动态调整的空间

如果整个slice能够塞进CPU cache line,则其遍历可能比从内存load更加快速

避免栈逃逸

golang中非常酷的一个语法特点就是没有堆和栈的区别。编译器会自动识别哪些对象该放在堆上,哪些对象该放在栈上。

func xxx() *ABigStruct{
  a := new(ABigStruct)  // 看起来是在堆上的对象
  var b ABigStruct      // 看起来是栈上的对象
  // do something
  // not return a   // a虽然是对象指针,但仅限于函数内使用,所以编译器可能把a放在栈上
  return &b   // b超出了函数的作用域,编译器会把b放在堆上。
}

valyala大神的经验:先找出程序的hot path,然后在hot path上做栈逃逸的分析。尽量避免hot path上的堆内存分配,就能减轻GC压力,提升性能。

3.CPU使用层面的优化

声明使用多核

import _ "go.uber.org/automaxprocs"

特别是在容器环境运行的程序,要让程序利用上所有的CPU核。

在k8s的有的版本(具体记不得了),会有一个恶心的问题:容器限制了程序只能使用比如2个核,但是runtime.GOMAXPROCS(0)代码却获取到了所有的物理核。这时就导致进程的物理线程数接近逻辑CPU的个数,而不是容器限制的核数。从而,大量的CPU时间消耗在物理线程切换上。我曾经在腾讯云上测试过,这种现象发生时,容器内单核性能只有物理机上单核性能的43%。

因此,发现性能问题时,可以通过ls /proc/$(pidof xxx)/tasks | wc来查看进程的物理线程数,如果这个数量远远高于从容器要求的核数,那么在部署的时候建议加上环境变量来解决:export -p GOMAXPROC=2

4.并发层面

atomic.Value: 用于并发场景下需要切换的对象

有的对象很基础,可能需要频繁访问,且有时又会发生引用的切换。比如程序中的全局配置,很多地方都会引用,有时配置更新后,又会切换为最新的配置。

这种情况下,加锁的成本太高,不加锁又会带来风险。因此,使用sync.Value来保存全局配置的数据是个不错的选择。

type Configs map[string]string

var globalConfig atomic.Value

func GetConfig() Configs {
	v, ok := globalConfig.Load().(Configs)
	if ok{
		return v
	}
	return map[string]string{}
}

func SetConfig(cfg Configs){
	globalConfig.Store(cfg)
}

对数据加锁,而不是对过程加锁

读多写少的场景考虑读写锁

某些读写的场景下,读是可以并发的,而写是互斥的。这种场景下,读写锁是比互斥锁更好的选择。

降低锁的力度

数据分散,使用多锁,降低锁的力度

5.并发容器

sync.Map

并发map设计得很精巧,用起来也很简单。不过很可惜,sync.Map没有那么快,要避免将sync.Map用在程序的关键路径上。

当然,我上述的观点的区分点是:这是业务程序还是系统程序,如果是系统程序,尽量不要用。我实际使用中发现,sync.Map会导致CPU消耗高,且GC压力增大。

channel

channel当然也算一种并发容器,其本质上是无锁队列。

需要注意两点:

为了在多读多写条件下维持队列的数据结构,通常通过CAS+自旋等待来操作关键数据。
​ 因此在大并发下,入队出队操作是串行化的,CAS失败+自旋重试又会带来cpu使用率升高。

​ 同样的,channel没有那么快。要避免在剧烈竞争的环境下使用channel。

通常会使用channel来做生产者-消费者模式的并发结构。数据数据可以按照一定的规律分区,则可以考虑每个消费者对应一个channel,然后生产者根据数据的key来决定放到哪个channel。这样本质上减缓了锁的竞争。

6.不安全代码

string与[]byte的转换

string与slice的结构本质上是一样的,可以直接强制转换:

import (
	"reflect"
	"unsafe"
)

// copy from prometheus source code

// NoAllocString convert []byte to string
func NoAllocString(bytes []byte) string {
	return *(*string)(unsafe.Pointer(&bytes))
}

// NoAllocBytes convert string to []byte
func NoAllocBytes(s string) []byte {
	strHeader := (*reflect.StringHeader)(unsafe.Pointer(&s))
	sliceHeader := reflect.SliceHeader{Data: strHeader.Data, Len: strHeader.Len, Cap: strHeader.Len}
	return *(*[]byte)(unsafe.Pointer(&sliceHeader))
}

上面的代码可以避免string和[]byte在转换的时候发生拷贝。

注意:转换后的对象一定要立即使用,不要进一步引用到更深的层次中去。牢记这是不安全代码,谨慎使用。

强制类型转换

例如一个[]int64的数组要转换为[]uint64的数组,使用个指针强制转换就行了。

package main

import (
	"testing"
	"unsafe"
)

func TestConvert(t *testing.T) {
	int64Slice := make([]int64, 0, 100)
	int64Slice = append(int64Slice, 1, 2, 3)
	uint64Slice := *(*[]uint64)(unsafe.Pointer(&int64Slice))
	t.Logf("%+v", uint64Slice)
}

GO查看汇编与数据竞争(data race)

go tool complie -S *.go
go build -race *.go

好文:

使用 Go 语言开发的一些经验(含代码示例)

go语言相关经验

在这里插入图片描述

依赖库

依赖于同一库的不同版本

向后不兼容 可以通过branch来控制,如

require (
	github.com/robteix/testmod v1.0.1
	github.com/robteix/testmod/v2
)

go mod 依赖本地包

有一种可能出现的情况:项目的某个依赖包并不在Github或者其他代码托管网站上,而是在本地,此时需要修改go.mod文件引入本地依赖包。


require (
    3rd/module/testmod v0.0.0
)

replace 3rd/module/testmod => /usr/local/go/testmod

有一种现实情况是:内部构建系统是无网络环境的,也就是说所有的依赖项都需要纳入内部版本控制中,Go Modules提供了此功能:

go mod tidy && go mod vendor

该命令会将在当前项目根目录下创建vendor目录,然后将项目所有依赖项缓存此目录中,而此目录可以直接进入内部版本控制。在默认情况下go build将忽略vendor目录,如果要从vendor目录开始构建:

go build -mod vendor

json与 map转换为struct

package main

import (
	"encoding/json"
	"fmt"
	"github.com/goinggo/mapstructure"
)

func JsonToMap() {
	jsonStr := `
    {
        "name":"liangyongxing",
        "age":12
    }
    `
	var mapResult map[string]interface{}
	//使用 json.Unmarshal(data []byte, v interface{})进行转换,返回 error 信息
	if err := json.Unmarshal([]byte(jsonStr), &mapResult); err != nil {
		fmt.Println(err)
	}
	fmt.Println(mapResult)
}


type Person struct {
	Name string
	Age int
}

func MapToStruct() {
	mapInstance := make(map[string]interface{})
	mapInstance["Name"] = "liang637210"
	mapInstance["Age"] = 28
	fmt.Println(mapInstance)

	var person Person
	//将 map 转换为指定的结构体
	if err := mapstructure.Decode(mapInstance, &person); err != nil {
		fmt.Println(err)
	}
	fmt.Printf("map2struct后得到的 struct 内容为:%v", person)
}

func main(){
	//JsonToMap()
	MapToStruct()
}

验证

import 	"github.com/go-playground/validator/v10"

func Handler(c *gin.Context) {
	reqData := pb.TSSaleStaffListRequest{}
	c.Bind(&reqData)
	userInfo := reqData.GetUserInfo()
	startTime := reqData.GetRegisterStartTime()

	validate := validator.New()
	if err := validate.Struct(struct {
		Name     string `validate:"required"`
		UserInfo string `validate:"max=50"`
		PageSize int32  `validate:"min=1,max=1000"`
		PageNum  int32  `validate:"min=1"`
		PoolType string `validate:"oneof=1 2"`
		StartTime     string `validate:"datetime=2006-01-02 15:04:05"`
	}{userInfo, pageSize, pageNum}); err != nil {
		c.ProtoBuf(http.StatusOK, &pb.TSSaleStaffListResponse{
			ErrorCode: conf.StatusParamCheckFail,
			ErrorMsg:  err.Error(),
		})
		return
	}

缓存相关

golang版防缓存击穿利器–singleflight

https://www.huolg.net/backend/702

import (
	"golang.org/x/sync/singleflight"
)
var dynDlist singleflight.Group
//获取用户动态列表
func GetDynList(c *gin.Context) {
	user := c.GetStringMap("user")
	like_uid := user["uid"].(string) //此用户查看(主)

	uid := c.Query("uid") //查看此用户动态(被)
	//评论页码
	page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
	//每页显示条数
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
	start := (page - 1) * limit
	//data := models.GetDynList(uid, start, limit)

	var dynDlistData map[string]interface{}
	//先尝试从redis获取数据
	redisdynDlistKey := "dyn_dynlist_" + like_uid + "_" + uid + "_" + strconv.Itoa(page) + "_" + strconv.Itoa(limit)
	ttlDuration, err := models.Redis.Hander.TTL(redisdynDlistKey).Result()
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"errcode":   utils.ERROR,
			"errmsg":    utils.GetMsg(utils.ERROR),
			"timestamp": time.Now().UnixNano() / 1e6,
		})
		return
	}
	if int(ttlDuration) > 1 {
		data, err := models.Redis.Hander.Get(redisdynDlistKey).Bytes()
		json.Unmarshal(data, &dynDlistData)
		if err != nil {
			fmt.Println("Redis Connect:", err)
		}
	} else {
		//从数据库获取数据
		sign := make(chan struct{})
		fn := func() (interface{}, error) {
			//从数据库获取数据 确保 single
			dynDlistData = models.GetDynList(uid, like_uid, start, limit)
			return dynDlistData, nil
		}
		go func() {
			result, _, _ := dynDlist.Do("dynCache", fn)
			resBytes, _ := json.Marshal(result)
			models.Redis.Hander.Set(string(redisdynDlistKey), resBytes, 3*time.Second)
			json.Unmarshal(resBytes, &dynDlistData)
			//fmt.Println("caller get result :", string(resBytes))
			sign <- struct{}{}
		}()

		<-sign
	}

	c.JSON(http.StatusOK, gin.H{
		"errcode":   utils.SUCCESS,
		"errmsg":    utils.GetMsg(utils.SUCCESS),
		"timestamp": time.Now().UnixNano() / 1e6,
		"data":      dynDlistData,
	})
	return
}
singleflight 有哪些注意事项(避坑指南)?

1.一个阻塞,全员等待

使用 singleflight 我们比较常见的是直接使用 Do 方法,但是这个极端情况下会导致整个程序 hang 住,如果我们的代码出点问题,有一个调用 hang 住了,那么会导致所有的请求都 hang 住

这时候我们可以使用 DoChan 结合 select 做超时控制

func singleflightGetArticle(ctx context.Context, sg *singleflight.Group, id int) (string, error) {
	result := sg.DoChan(fmt.Sprintf("%d", id), func() (interface{}, error) {
		// 模拟出现问题,hang 住
		select {}
		return getArticle(id)
	})

	select {
	case r := <-result:
		return r.Val.(string), r.Err
	case <-ctx.Done():
		return "", ctx.Err()
	}
}
  1. 一个出错,全部出错

21题

  1. Go语言中int占多少字节?
func main()  {
	var x int = 100
	fmt.Println(unsafe.Sizeof(x)) // 8
	var y int64 = 1
	fmt.Println(unsafe.Sizeof(y)) // 8
	var y1 int32 = 1
	fmt.Println(unsafe.Sizeof(y1)) // 4
	var z uint64 = 1
	fmt.Println(unsafe.Sizeof(z)) // 8
	var z1 uint32 = 1
	fmt.Println(unsafe.Sizeof(z1)) // 4
}
 
// 结果
8
8
4
8
4

2.整型中有符号和无符号是什么意思?

int 是整数类型,用于定义变量的类型,有符号,unsigned int 是无符号的整数类型,直白点说有符号无符号整型就是能不能存放负数。
根据程序编译器的不同,整形定义的字节数不同。51类单片机的C语言中,int代表2个byte(16位);如果是32位ARM处理器的C语言中,则int代表4个byte(32位)。(如32位XP)把int定义为4 byte(32位)。 注意一下取值范围。若在32位系统中,signed int a, 则a范围[-2^31 , 2^31 -1] 即 [-2147483648,2147483647]。
所以一个int定义需要注意几个方面,一个是类型,一个是存储数据的大小范围。

3.整型可以表示的最大范围是多少?超出怎么办?

4.什么是nil?

Go的文档中说到,nil是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值,也就是预定义好的一个变量:

type Type int
var nil Type

1
2
是不是有点惊讶?nil并不是Go的关键字之一,你甚至可以自己去改变nil的值:

var nil = errors.New("hi")

5.十进制是以整型方式存在,其他进制则是以字符串的形式存在?如何实现进制之间的转换?

6.简述如下代码的意义

var v1 int

var v2 *int
var v3 = new(int)

7.浮点型为什么有时无法精确表示小数?

https://blog.csdn.net/mydriverc2/article/details/84344059

8.如何使用第三方包decimal?

https://www.jianshu.com/p/11ea544bab48

9.简述asci. unicode. utf-8的关系。

10.判断: Go语言中的字符串是Utf-8编码的字 节序列。

11.什么是rune?

12.判断:字符串是否可变?

s := "foobar阿斯蒂芬"
fmt.Println(s)
fmt.Println(&s)
s = "qweqweqweqweqwe"
fmt.Println(s)
fmt.Println(&s)

s[0] = '1'  // 不能这样

13.列举你记得的字符串的常见功能?

14.字符串和字节集合" . "rune集合” 如何实现相互转换?

rune是Go语言中一种特殊的数据类型,它是int32的别名,几乎在所有方面等同于int32,用于区分字符值和整数值

15.字符串如何实现高效的拼接?

https://www.cnblogs.com/mambakb/p/10352138.html

16.简述数组的存储原理。

go语言的数组只能存储同一种数据类型,数组必须制定它的数据的存储类型和存储数据的长度。

数组的长度是确定的,数组来声明的时候如果没有完全确定成员的数组,那么没有确定的值将使用当前数组存储的数据类型的零值来代替,所以数组是不支持增加和删除的操作的

结构体方法定义

方法的定义
在这里插入图片描述
由值作为接收者,被调用时,是对对象的拷贝,指针作为接收者,被调用时,是对对象本身的操作。

go web framework

这些框架基本上都是把自带的 http 包做了一次封装。 处理请求的时候就直接新建一个 goroutine 去处理, 可以理解为轻量级线程, 参考这个地方:
https://github.com/golang/go/blob/master/src/net/http/server.go#L2732

错误处理

在这里插入图片描述

语法糖

Go语言函数中有三个点 ... 表示为可变参数,这是Go的糖衣语法,表示可以接受任意个数但相同类型的参数。

:= 是Go的赋值与声明语法糖,它的功能是声明、赋值和类型推断。

orm

Scan find 区别

//scan类似Find都是用于执行查询语句,然后把查询结果赋值给结构体变量,区别在于scan不会从传递进来的结构体变量提取表名.

事务两种方式 Transaction

func WarehouseShelfUpdateHandler(c *gin.Context) {
	reqData := pb.TSWarehouseShelfUpdateRequest{}
	_ = c.Bind(&reqData)

	shelf := reqData.GetShelf()
	conn := lib.DBConn()
	if err := conn.Transaction(func(tx *gorm.DB) error {
		sh := model.Shelf{}
		if tx.First(&sh, shelf.GetRecordId()).RecordNotFound() {
			return errors.New("未找到记录")
		}

		if err := tx.Model(&sh).Updates(model.Shelf{
			WarnValue: shelf.GetPositionWarn(),
			Remark:    shelf.GetRemark(),
		}).Error; err != nil {
			return err
		}

		uid, _ := c.Get("uid")
		operator := model.AdminUser{}
		tx.First(&operator, uid)

		return tx.Create(&model.ShelfOperation{
			Operator: operator.Name,
			Event:    "修改货架",
			ShelfId:  sh.ID,
		}).Error
	}); err != nil {
		c.ProtoBuf(http.StatusOK, &pb.TSWarehouseShelfUpdateResponse{
			ErrorCode: conf.StatusOperationFailed,
			ErrorMsg:  err.Error(),
		})
		return
	}

每次数据库error 就 Rollback回滚

func createDoubleSeventhLotteryRecord(uid, prizeID, spendScore int32) error {
	conn := lib.DBConn()

	tx := conn.Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()
	var prize model.DoubleSeventhPrizePool
	if tx.Set("gorm:query_option", "FOR UPDATE").First(&prize, prizeID); prize.CurrentAmount > 0 && prize.TotalAmount > 0 {
		var activityData = map[string]interface{}{
			"current_amount": prize.CurrentAmount - 1,
			"total_amount":   prize.TotalAmount - 1,
		}
		if err := tx.Model(&prize).Updates(activityData).Error; err != nil {
			tx.Rollback()
			return err
		}
	}
	if err := tx.Create(&model.DoubleSeventhLotteryRecord{
		UID:        uid,
		PrizeID:    prizeID,
		SpendScore: spendScore,
	}).Error; err != nil {
		tx.Rollback()
		return err
	}
	if err := tx.Commit().Error; err != nil {
		tx.Rollback()
		return err
	}
	return nil
}

rpc

protoc message.proto --go_out=/message/

protoc-gen-go: program not found or is not executable
Please specify a program using absolute path or make sure the program is available in your PATH system variable
–go_out: protoc-gen-go: Plugin failed with status code 1.

go get -a github.com/golang/protobuf/protoc-gen-go
protoc --go_out=plugins=grpc:. *.proto                                                                                                                                       
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值