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语言相关经验
依赖库
依赖于同一库的不同版本
向后不兼容 可以通过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()
}
}
- 一个出错,全部出错
21题
- 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