-
准备工作日志记录对程序排查问题比较关键,记录下GO中日志选择,从以下出发点考虑:日志文件能自动切割,以免过大能记录从哪个文件哪行代码调用的,方便排查问题配置简单明了库文件使用人数较多,稳定经过一段时间摸索,最终选择了Logrus和lumberjack两个库,使用人数都比较多。安装两个库go get gopkg.in/natefinch/lumberjack.v2 go get github.com/sirupsen/logrus代码实际中一行配置就可以完成,后续只用调用logrus.Debug、logrus.Info同标准库一样只用log改为logrus十分简单明了 logrus.SetOutput(io.MultiWriter(os.Stdout, &lumberjack.Logger{ Filename: "go-log.log", MaxBackups: 10, MaxSize: 20, }))整体代码如下,基本能满足实际使用需求个别需求调整可以参考这两个文档 package main import ( "io" "os" "time" "github.com/sirupsen/logrus" "gopkg.in/natefinch/lumberjack.v2" ) func main() { // 同时输出到终端和文件 logrus.SetOutput(io.MultiWriter(os.Stdout, &lumberjack.Logger{ Filename: "go-log.log", MaxBackups: 10, MaxSize: 20, })) /*只输出到文件的话只用以下写法 logrus.SetOutput(&lumberjack.Logger{ Filename: "go-log.log", MaxBackups: 10, MaxSize: 20, }) */ // 这个在日志中记录代码位置,十分有用 logrus.SetReportCaller(true) // 设置输出等级,按实际需求设置,可以忽略 logrus.SetLevel(logrus.InfoLevel) logrus.Debug("debug message") logrus.Infof("info message at %s\n", time.Now().Format("2006-01-02 15:04:05")) logrus.Error("error message") }
-
自从2022年 Golang 1.18 发布至今已有一年多了,在1.18版本中增加了非常重磅的一个功能,那就是泛型!Golang官方也对泛型格外重视:“Generics are the biggest change we’ve made to Go since the first open source release”(泛型是自第一个开源版本以来我们对 Go 所做的最大改变)然而由于平时工作项目所用Go版本较为古老,一直没有大范围应用泛型这个特性,虽然之前刚发布时就了解学习过,但是也有些模糊了,这里还是希望能够记录一下Golang泛型,以备后续参考,我讲基于Golang官方文档、博客、youtube演讲等来进行学习。什么是泛型泛型是一种可以编写独立于使用的特定类型的代码的方法,可以通过编写函数或类型来使用一组类型中的任何一个。泛型为Golang增添了三个重要功能:函数和类型的类型参数将接口类型定义为类型集,包括没有方法的类型。也就是我们可以定义类型集和方法集类型推断,允许函数调用时省略类型参数我们从类型参数开始逐步了解类型参数 Type parameters类型参数让我们可以参数化函数或者具有类型的类型,与普通的参数列表类似,类型参数使用方括号来表示函数中使用类型参数这里有一个常见的取最小值函数,我们经常会在代码中写(新版的Golang官方库已经支持了max以及min):func Min(x, y float64) float64 { if x < y { return x } return y }我们可以通过类型参数来替换 float64 类型来使这个函数更加通用,让这个函数不仅仅适用于 float64 类型,可以这样来做:import "golang.org/x/exp/constraints" func Min[T constraints.Ordered](x, y T) T { if x < y { return x } return y } m := Min[int](2, 3)在这里我们使用了类型参数 T 来替换 float64 类型使得函数通用,由于 T 是一个新的类型,所以我们要在 [] 中声明它。函数定义好后,与普通的函数调用类似,我们需要传入函数的实参以及创建一个接收值来接受函数实际返回的结果,不同的是,我们需要在 [] 传入具体的类型值,向函数提供类型参数 int 成为实例化。实例化将会分两步进行:编译器将整个泛型函数或类型中的所有类型实参进行替换编译器验证每个类型参数是否满足了各自的约束如果编译器在第二步执行失败,实例化就会失败且程序会fail。我们也可以直接传入类型参数来实例化函数,而不需要传入具体实参进行实际调用,实例化过后我们就可以像普通函数调用一样来调用这个实例化过后的函数了:fmin := Min[float64] m := fmin(2.71, 3.14)类型中使用类型参数前面是一个函数中使用类型参数的例子,还有一个在类型中使用类型参数的例子:type Tree[T interface{}] struct { left, right *Tree[T] value T}func (t *Tree[T]) Lookup(x T) *Tree[T] { ... }var stringTree Tree[string]这是一个通用的二叉树类型定义,我们再次采用了类型参数 T 来作为一个通用性的数据类型,这里定义了类型 Tree[T] 同时定义了它所具有的方法 Lookup(x T) *Tree[T]。最后一行通过 var 对变量 stringTree 做了一次实例化,传入参数类型为 string类型集func min(x,y float64)float64func Gmin[T constraints.Ordered](x, y T) T普通函数 min() 每个参数值都有一个类型,例如min函数中,限定了 x,y 及 返回值只有在 float64 类型时才有效;而函数 Gmin() 类型参数列表中每个类型参数都有一个类型,由于类型参数本身就是一种类型,因此类型参数的类型定义了类型集,这种元类型告诉了我们那些类型对该参数类型有效,因此这个元类型实际定义了类型集,我们可以称之为类型约束。在 Gmin() 中,类型越是是从约束包中导入的,这个包也是 Golang 标准库中新增的包。这个Ordered约束描述了具有可排序值的所有类型的集合,或者换句话说,约束了能够使用 < 运算符(或 <= 、 > 等)进行比较的类型范围。所以只有具有可排序值的类型才能传递给GMin,在GMin函数体中,该类型参数的值可以用于与 < 等运算符进行比较。
-
go整合elasticsearch基于docker搭建开发环境在开发之前我们首先需要借助docker来构建我们的开发环境,先创建一个文件名称为docker-compose.yaml, 里面写入下面的内容:--- version: "3" services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:7.10.0 container_name: es01 environment: - node.name=es01 - cluster.name=docker-cluster - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - discovery.type=single-node ulimits: memlock: soft: -1 hard: -1 volumes: - esdata:/usr/share/elasticsearch/data ports: - 9200:9200 kibana: image: docker.elastic.co/kibana/kibana:7.10.0 ports: - 5601:5601 depends_on: - elasticsearch volumes: esdata: driver: local使用docker-compose up -d 启动容器,之后在浏览器中分别验证es和kibana的运行状态验证es:http://localhost:9200/验证kibana:http://localhost:5601检查客户端apipackage main import ( "fmt" "github.com/elastic/go-elasticsearch/v7" ) func main() { es, err := elasticsearch.NewDefaultClient() if err != nil { fmt.Println(err) return } res, err := es.Info() if err != nil { fmt.Println(err) return } defer res.Body.Close() fmt.Println(res) }索引相关操作创建索引package main import ( "context" "fmt" "github.com/elastic/go-elasticsearch/v7" "log" ) func main() { cfg := elasticsearch.Config{ Addresses: []string{ "http://localhost:9200", }, } es, err := elasticsearch.NewClient(cfg) if err != nil { log.Fatalf("Error creating the client: %s", err) } indexName := "test_20230726" res, err := es.Indices.Create( indexName, es.Indices.Create.WithContext(context.Background()), es.Indices.Create.WithPretty()) if err != nil { log.Fatalf("Error creating the index: %s", err) } defer res.Body.Close() fmt.Println(res.String()) }删除索引package main import ( "context" "fmt" "github.com/elastic/go-elasticsearch/v7" "log" ) func main() { cfg := elasticsearch.Config{ Addresses: []string{ "http://localhost:9200", }, } es, err := elasticsearch.NewClient(cfg) if err != nil { log.Fatalf("Error creating the client: %s", err) } indexName := "test_20230726" res, err := es.Indices.Delete( []string{indexName}, es.Indices.Delete.WithContext(context.Background()), es.Indices.Delete.WithIgnoreUnavailable(true), es.Indices.Delete.WithPretty(), ) if err != nil { log.Fatalf("Error deleting the index: %s", err) } defer res.Body.Close() fmt.Println(res.String()) }修改索引package main import ( "bytes" "context" "encoding/json" "fmt" "github.com/elastic/go-elasticsearch/v7" "github.com/elastic/go-elasticsearch/v7/esapi" "log" ) func main() { cfg := elasticsearch.Config{ Addresses: []string{ "http://localhost:9200", }, } es, err := elasticsearch.NewClient(cfg) if err != nil { log.Fatalf("Error creating the client: %s", err) } indexName := "your_index_name" documentID := "your_document_id" // 准备文档数据 doc := Document{ Title: "Document Title", Body: "This is the body of the document.", } // 将文档数据序列化为JSON字节 data, err := json.Marshal(doc) if err != nil { log.Fatalf("Error marshaling document: %s", err) } // 创建PUT请求 req := esapi.IndexRequest{ Index: indexName, DocumentID: documentID, Body: bytes.NewReader(data), } // 发送PUT请求 res, err := req.Do(context.Background(), es) if err != nil { log.Fatalf("Error indexing document: %s", err) } defer res.Body.Close() fmt.Println(res.String()) }查询索引列表package main import ( "encoding/json" "fmt" "github.com/elastic/go-elasticsearch/v7" "log" ) func main() { cfg := elasticsearch.Config{ Addresses: []string{ "http://localhost:9200", }, } es, err := elasticsearch.NewClient(cfg) if err != nil { log.Fatalf("Error creating the client: %s", err) } res, err := es.Indices.Get([]string{"_all"}) if err != nil { log.Fatalf("Error getting indices: %s", err) } defer res.Body.Close() if res.IsError() { log.Fatalf("Error response: %s", res.String()) } var result map[string]interface{} if err := json.NewDecoder(res.Body).Decode(&result); err != nil { log.Fatalf("Error parsing the response body: %s", err) } indices, ok := result["test_20230726"].(map[string]interface{}) if !ok { log.Fatalf("Invalid indices format in the response") } for index := range indices { fmt.Println(index) } }插入文档package main import ( "bytes" "context" "encoding/json" "fmt" "github.com/elastic/go-elasticsearch/v7" "github.com/elastic/go-elasticsearch/v7/esapi" "log" ) type Document struct { Title string `json:"title"` Body string `json:"body"` } func main() { cfg := elasticsearch.Config{ Addresses: []string{ "http://localhost:9200", }, } es, err := elasticsearch.NewClient(cfg) if err != nil { log.Fatalf("Error creating the client: %s", err) } indexName := "test_20230726" documentID := "20230726" doc := Document{ Title: "Document Title", Body: "This is the body of the document.", } data, err := json.Marshal(doc) if err != nil { log.Fatalf("Error marshaling document: %s", err) } req := esapi.IndexRequest{ Index: indexName, DocumentID: documentID, Body: bytes.NewReader(data), } res, err := req.Do(context.Background(), es) if err != nil { log.Fatalf("Error indexing document: %s", err) } defer res.Body.Close() fmt.Println(res.String())
-
原理遍历读取/proc/获取所有进程IDcat /proc/5181/stat中前四列分别为进程PID,进程名,进程状态,父进程PIDGo代码1.获取/proc/下面所有文件名+文件夹名为数字的名字2.读取/proc/xxx/stat获取进程信息输出package main import ( "fmt" "io/ioutil" "log" "regexp" "sort" "strconv" ) func main() { var process []int var validId = regexp.MustCompile("^[0-9]+$") infoList, err := ioutil.ReadDir("/proc") if err != nil { log.Println(infoList) } for _, info := range infoList { if info.IsDir() && validId.MatchString(info.Name()) { p, _ := strconv.Atoi(info.Name()) process = append(process, p) } } sort.Ints(process) statRe := regexp.MustCompile(`([0-9]+) \((.+?)\) [a-zA-Z]+ ([0-9]+)`) fmt.Printf("%6s\t%6s\t%s\n", "PID", "PPID", "NAME") for _, p := range process { b, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", p)) if err != nil { continue } matches := statRe.FindStringSubmatch(string(b)) fmt.Printf("%6s\t%6s\t%s\n", matches[1], matches[3], matches[2]) } }package main import ( "fmt" "io/ioutil" "log" "regexp" "sort" "strconv" ) func main() { var process []int var validId = regexp.MustCompile("^[0-9]+$") infoList, err := ioutil.ReadDir("/proc") if err != nil { log.Println(infoList) } for _, info := range infoList { if info.IsDir() && validId.MatchString(info.Name()) { p, _ := strconv.Atoi(info.Name()) process = append(process, p) } } sort.Ints(process) statRe := regexp.MustCompile(`([0-9]+) \((.+?)\) [a-zA-Z]+ ([0-9]+)`) fmt.Printf("%6s\t%6s\t%s\n", "PID", "PPID", "NAME") for _, p := range process { b, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/stat", p)) if err != nil { continue } matches := statRe.FindStringSubmatch(string(b)) fmt.Printf("%6s\t%6s\t%s\n", matches[1], matches[3], matches[2]) } }
-
堆(Heap)堆(Heap),又称为优先队列(Priority Queue)。尽管名为优先队列,但堆并不是队列。在队列中,我们可以进行的操作是向队列中添加元素和按照元素进入队列的顺序取出元素。而在堆中,我们不是按照元素进入队列的先后顺序,而是按照元素的优先级取出元素。问题背景在Linux内核中,调度器根据各个进程的优先级来进行程序的执行调度。在操作系统运行时,通常会有很多个不同的进程,各自优先级也不相同。调度器的作用是让优先级高的进程得到优先执行,而优先级较低的则需要等待。堆是一种适用于实现这种调度器的数据结构。需要提一下,现在Linux内核的调度器使用的是基于红黑树的CFS(Completely Fair Scheduler)。二叉堆的概念我们常用的二叉堆是一颗任意节点的优先级不小于其子节点的完全二叉树。完全二叉树的定义如下:若设二叉树的高度为h,除第h层外,其它各层(1~h-1)的结点数都达到最大个数,第h层从右向左连续缺若干结点,这就是完全二叉树。比如下图就是一颗完全二叉树: 10 / \ 15 30 / \ / \ 40 50 100 40现在假设保存的数值越小的节点的优先级越高,那么上图就是一个堆。我们将任意节点不大于其子节点的堆叫做最小堆或小根堆,将任意节点不小于其子节点的堆叫做最大堆或大根堆。因此,上图就是一个小根堆。优先级队列的实现通过使用Go语言中的container/heap包,我们可以轻松地实现一个优先级队列。这个队列可以用于解决许多问题,如任务调度、事件处理等。通过设置每个项的优先级,我们可以确保在处理队列时按照指定的顺序进行操作。Item通过定义Item结构体来表示优先级队列中的项。每个项具有值(value)和优先级(priority)。index表示项在优先级队列中的索引。// Item represents an item in the priority queue. type Item struct { value int // 项的值。 priority int // 项的优先级。 index int // 项在队列中的索引。 }PriorityQueuePriorityQueue是一个切片类型,实现了heap.Interface接口。它提供了用于操作优先级队列的方法,如插入、删除和修改。Len方法返回优先级队列的长度。Less方法比较两个项的优先级。Swap方法交换两个项在优先级队列中的位置。Push方法向优先级队列中添加一个项。Pop方法移除并返回优先级队列中的最小项。Update方法用于修改项的优先级并更新其在优先级队列中的位置。// PriorityQueue 实现了 heap.Interface 接口。 type PriorityQueue []*Item // Len 返回优先级队列的长度。 func (pq PriorityQueue) Len() int { return len(pq) } // Less 比较优先级队列中的两个项。 func (pq PriorityQueue) Less(i, o int) bool { return pq[i].priority < pq[o].priority } // Swap 交换优先级队列中的两个项。 func (pq PriorityQueue) Swap(i, o int) { pq[i], pq[o] = pq[o], pq[i] pq[i].index = i pq[o].index = o} // Push 向优先级队列中添加一个项。 func (pq *PriorityQueue) Push(x interface{}) { item := x.(*Item) item.index = len(*pq) *pq = append(*pq, item) } // Pop 移除并返回优先级队列中的最小项。 func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] old[n-1] = nil // 避免内存泄漏 item.index = -1 *pq = old[0 : n-1] return item } // Update 修改项的优先级并更新其在优先级队列中的位置。 func (pq *PriorityQueue) Update(item *Item, value, priority int) { item.value = value item.priority = priority heap.Fix(pq, item.index) }改进但是我们经常有一种场景,需要堆的快速求最值的性质,又需要能够支持快速的访问元素,特别是删除元素。 如果我们要查找堆中的某个元素,需要遍历一遍。非常麻烦。比如延迟任务的场景,我们可以使用堆对任务的到期时间戳进行排序,从而实现到期任务自动执行,但是它没办法支持删除一个延迟任务的需求。HeapMap一种能够快速随机访问元素的数据结构是哈希表。使用哈希表实现的map可以在O(1)的时间复杂度下进行随机访问。另外,堆结构可以在O(log(n))的时间复杂度下删除元素,前提是知道要删除的元素的下标。因此,我们可以将这两个数据结构结合起来使用。使用哈希表记录堆中每个元素的下标,同时使用堆来获取最值元素。在PriorityQueue中定义一个dataMapdataMap是一个用于存储队列中的项的映射表,它的好处是可以根据项的键快速地查找到对应的项。 在PriorityQueue中,有一个数据切片data,用于存储队列中的项,并且用一个索引值index来表示项在切片中的位置。dataMap则以项的键作为索引,将项的指针映射到该键上。使用dataMap的好处是可以快速地根据键找到对应的项,而不需要遍历整个切片。这对于需要频繁查找和修改项的场景非常重要,可以提高代码的效率。如果没有dataMap,想要根据键找到对应的项则需要遍历整个切片进行查找,时间复杂度将为O(n)。而使用dataMap可以将查找的时间复杂度降低到O(1),提高代码的性能。另外,需要注意的是dataMap必须与data切片保持同步,即在对切片进行修改时,需要同时更新dataMap,保持两者的一致性。否则,在使用dataMap时会出现不一致的情况,导致错误的结果。因此,在使用PriorityQueue时,需要确保维护dataMap和data切片的一致性。push在Push时需要保证Key值唯一func (pq *PriorityQueue) Push(i *Item) error { if i == nil || i.Key == "" { return errors.New("error adding item: Item Key is required") } pq.lock.Lock() defer pq.lock.Unlock() if _, ok := pq.dataMap[i.Key]; ok { return ErrDuplicateItem } // Copy the item value(s) so that modifications to the source item does not // affect the item on the queue clone, err := copystructure.Copy(i) if err != nil { return err } pq.dataMap[i.Key] = clone.(*Item) heap.Push(&pq.data, clone) return nil }popPopByKey方法可以根据Key查找并移除对应的元素// PopByKey searches the queue for an item with the given key and removes it // from the queue if found. Returns nil if not found. This method must fix the // queue after removing any key. func (pq *PriorityQueue) PopByKey(key string) (*Item, error) { pq.lock.Lock() defer pq.lock.Unlock() item, ok := pq.dataMap[key] if !ok { return nil, nil } // Remove the item the heap and delete it from the dataMap itemRaw := heap.Remove(&pq.data, item.index) delete(pq.dataMap, key) if itemRaw != nil { if i, ok := itemRaw.(*Item); ok { return i, nil } } return nil, nil }
-
基于终端库termbox-go做了个贪吃蛇游戏, 功能较简单,代码约160行左右一:原理介绍1. 绘制原理存储好蛇身和食物坐标都存储在Snake结构中定时300毫秒执行移动蛇身/生成食物,然后清空终端再重新根据坐标绘制点●达到模拟动画效果type Location struct { X int Y int } type Snake struct { Body []Location Food Location ...... } func Draw(s *Snake) { termbox.Clear(termbox.ColorDefault, termbox.ColorDefault) for _, location := range s.Body { termbox.SetCell(location.X, location.Y, '●', termbox.ColorGreen, termbox.ColorDefault) } termbox.SetCell(s.Food.X, s.Food.Y, '●', termbox.ColorRed, termbox.ColorDefault) termbox.Flush() }2.贪吃蛇移动过程原理很简单,根据当前行走方向,追加一个点到[]Localtion如果蛇头位置不是食物位置,删除[]Localtion第一个点, 添加一个点(最后一个点位置+1)相当于行走了如果恰好是食物位置,添加一个点(最后一个点位置+1),再随机生成食物// 移动一步, 如果碰壁返回false, 否则返回true func (s *Snake) Move() bool { head := s.GetHead() switch s.Direction { case DIRECTION_UP: s.Body = append(s.Body, Location{head.X, head.Y - 1}) case DIRECTION_DOWN: s.Body = append(s.Body, Location{head.X, head.Y + 1}) case DIRECTION_LEFT: s.Body = append(s.Body, Location{head.X - 1, head.Y}) case DIRECTION_RIGHT: s.Body = append(s.Body, Location{head.X + 1, head.Y}) } head = s.GetHead() // 蛇头到达食物位置时标记食物已吃,并且追加到蛇尾(s.Body[0]不用剔除, 否则剔除) if head == s.Food { s.FoodEated = true s.RandomFood() s.Score += 10 } else { s.Body = s.Body[1:] } return 0 <= head.X && head.X <= s.MaxX && 0 <= head.Y && head.Y <= s.MaxY }3.生成食物过程仅需要注意是否生成在蛇身本身,是的话再生成// 判断生成的食物坐标是否在蛇身上 func (s *Snake) isFoodInSnake(location Location) bool { for _, l := range s.Body { if l == location { return true } } return false } // 生成食物 func (s *Snake) RandomFood() { w, h := termbox.Size() // 上下两边留点空隙 location := Location{rand.Intn(w-10) + 5, rand.Intn(h-10) + 5} for s.isFoodInSnake(location) { location = Location{rand.Intn(w), rand.Intn(h)} } s.Food = location }
-
要用Go获取短信验证码,通常需要连接到一个短信服务提供商的API,并通过该API发送请求来获取验证码。由于不同的短信服务提供商可能具有不同的API和授权方式,我将以一个简单的示例介绍如何使用Go语言来获取短信验证码。在这个示例中,我们将使用中昱维信作为短信服务提供商。1.注册账号并获取API密钥注册并登录你的短信平台,然后获取AppID和AppKey,注册地址在代码里2.创建验证码模版创建验证码模版,获取验证码模版id3.使用Go代码调用短信服务接口// 平台注册地址 vip.veesing.com package main import ( "fmt" "strings" "net/http" "io/ioutil" ) func main() { url := "https://vip.veesing.com/smsApi/verifyCode" method := "POST" // 替换示例代码中的"YOUR_APP_ID"、"YOUR_APP_KEY"、"YOUR_TEMPLATE_ID"、"YOUR_PHONE"、"YOUR_CODE"为你在中昱维信账号中获得的实际值 payload := strings.NewReader("appId=YOUR_APP_ID&appKey=YOUR_APP_KEY&templateId=YOUR_TEMPLATE_ID&phone=YOUR_PHONE&variables=YOUR_CODE") client := &http.Client { } req, err := http.NewRequest(method, url, payload) if err != nil { fmt.Println(err) return } req.Header.Add("Content-Type", "application/x-www-form-urlencoded") res, err := client.Do(req) if err != nil { fmt.Println(err) return } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { fmt.Println(err) return } // 解析短信服务的响应response,根据返回结果判断是否发送成功 // 成功 {"returnStatus": "1 ", "message": "成功", "remainPoint": "241", "taskId": "3313746", "successCounts": "1"} // 失败 {"returnStatus": "0", "message": "参数错误", "remainPoint": null, "taskId": null, "successCounts": null} // 处理成功或失败的逻辑... fmt.Println(string(body)) }
-
在go的sync包中,有一个singleflight包,里面有一个 singleflight.go文件,代码加注释,一共200行出头,通过 singleflight可以很容易实现缓存和去重的效果,避免重复计算,接下来我们就给大家详细介绍一下sync.singleflight如何解决热点缓存穿透问题在 go 的 sync 包中,有一个 singleflight 包,里面有一个 singleflight.go 文件,代码加注释,一共 200 行出头。内容包括以下几块儿:Group 结构体管理一组相关的函数调用工作,它包含一个互斥锁和一个 map,map 的 key 是函数的名称,value 是对应的 call 结构体。call 结构体表示一个 inflight 或已完成的函数调用,包含等待组件 WaitGroup、调用结果 val 和 err、调用次数 dups 和通知通道 chans。Do 方法接收一个 key 和函数 fn,它会先查看 map 中是否已经有这个 key 的调用在 inflight,如果有则等待并返回已有结果,如果没有则新建一个 call 并执行函数调用。DoChan 类似 Do 但返回一个 channel 来接收结果。doCall 方法包含了具体处理调用的逻辑,它会在函数调用前后添加 defer 来 recover panic 和区分正常 return 与 runtime.Goexit。如果发生 panic,会将 panicwraps 成错误返回给等待的 channel,如果是 goexit 会直接退出。正常 return 时会将结果发送到所有通知 channel。Forget 方法可以忘记一个 key 的调用,下次 Do 时会重新执行函数。这个包通过互斥锁和 map 实现了对相同 key 的函数调用去重,可以避免对已有调用的重复计算,同时通过 channel 机制可以通知调用者函数执行结果。在一些需要确保单次执行的场景中,可以使用这个包中的方法。通过 singleflight 可以很容易实现缓存和去重的效果,避免重复计算,接下来,我们来模拟一下并发请求可能导致的缓存穿透场景,以及如何用 singleflight 包来解决这个问题:package main import ( "context" "fmt" "golang.org/x/sync/singleflight" "sync/atomic" "time" ) type Result string // 模拟查询数据库 func find(ctx context.Context, query string) (Result, error) { return Result(fmt.Sprintf("result for %q", query)), nil } func main() { var g singleflight.Group const n = 200 waited := int32(n) done := make(chan struct{}) key := "this is key" for i := 0; i < n; i++ { go func(j int) { v, _, shared := g.Do(key, func() (interface{}, error) { ret, err := find(context.Background(), key) return ret, err }) if atomic.AddInt32(&waited, -1) == 0 { close(done) } fmt.Printf("index: %d, val: %v, shared: %v\n", j, v, shared) }(i) } select { case <-done: case <-time.After(time.Second): fmt.Println("Do hangs") } time.Sleep(time.Second * 4) }在这段程序中,如果重复使用查询结果,shared 会返回 true,穿透查询会返回 false上面的设计中还有一个问题,就是在 Do 阻塞时,所有请求都会阻塞,内存可能会出现大的问题。此时,Do 可以更换为DoChan,两者实现上完全一样,不同的是,DoChan() 通过 channel 返回结果。因此可以使用 select 语句实现超时控制ch := g.DoChan(key, func() (interface{}, error) { ret, err := find(context.Background(), key) return ret, err }) // Create our timeout timeout := time.After(500 * time.Millisecond) var ret singleflight.Result select { case <-timeout: // Timeout elapsed fmt.Println("Timeout") return case ret = <-ch: // Received result from channel fmt.Printf("index: %d, val: %v, shared: %v\n", j, ret.Val, ret.Shared) }在超时时主动返回,不阻塞。此时又引入了另一个问题,这样的每一次的请求,并不是高可用的,成功率是无法保证的。这时候可以增加一定的请求饱和度来保证业务的最终成功率,此时一次请求还是多次请求,对于下游服务而言并没有太大区别,此时使用 singleflight 只是为了降低请求的数量级,那么可以使用 Forget() 来提高下游请求的并发。ch := g.DoChan(key, func() (interface{}, error) { go func() { time.Sleep(10 * time.Millisecond) fmt.Printf("Deleting key: %v\n", key) g.Forget(key) }() ret, err := find(context.Background(), key) return ret, err })当然,这种做法依然无法保证100%的成功,如果单次的失败无法容忍,在高并发的场景下需要使用更好的处理方案,比如牺牲一部分实时性、完全使用缓存查询 + 异步更新等。
-
golang 项目 引用 托管的代码库,导入代码:提示错误信息:codehub.devcloud.huaweicloud.com/zyfbpt/cas-server/spec: cannot find module providing package codehub.devcloud.huaweicloud.com/zyfbpt/cas-server/spec: unrecognized import path "codehub.devcloud.huaweicloud.com/zyfbpt/cas-server/spec": reading https://codehub.devcloud.huaweicloud.com/zyfbpt/cas-server/spec?go-get=1: 404 Not Found
-
华为云开发者插件英文名是 Huawei Cloud Toolkit,作为华为云围绕其产品能力向开发者桌面上的延伸,帮助开发者快速在本地连接华为云,打通华为云到开发者的最后一公里;支持VS Code、IntelliJ IDEA等主流IDE平台、以及华为云自研 CodeArts IDE ,帮助开发者更高效、便捷的搭建应用。致力于为开发者提供更稳定、快速、安全的编程体验。产品页地址:https://developer.huaweicloud.com/develop/toolkit.html产品页二维码:
-
以下 star数截止2023年6月份 1.Gin(69.1K) 项目简介:Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 它具有类似 Martini 的 API,但性能比 Martini 快 40 倍。 仓库地址: https://github.com/gin-gonic/gin https://github.com/gin-gonic/gin 官方文档地址: 文档 | Gin Web Framework Gin 是什么?Gin 是一个用 Go (Golang) 编写的 HTTP Web 框架。 它具有类似 Martini 的 API,但性能比 Martini 快 40 倍。如果你需要极好的性能,使用 Gin 吧。如何使用 Gin?我们提供了一些 API … https://gin-gonic.com/zh-cn/docs/ 2.Beego(29.8K) 项目简介:Beego用于在Go中快速开发企业应用程序,包括RESTful API、web应用程序和后端服务。它的灵感来源于Tornado, Sinatra and Flask。beego有一些特定于Go的特性,如接口和结构嵌入。 仓库地址: GitHub - beego/beego: beego is an open-source, high-performance web framework for the Go programming language. beego is an open-source, high-performance web framework for the Go programming language. - GitHub - beego/beego: beego is an open-source, high-performance web framework for the Go programming language. https://github.com/beego/beego 官方文档地址: Welcome to Beego | Beego The most easy use framework https://beego.gocn.vip/beego/zh/developing/ 3.Fiber(26.5K) 项目简介:Fiber是一个Go web框架,构建在Go最快的HTTP引擎Fasthttp之上。它的设计目的是为了在零内存分配和性能的情况下简化快速开发。 仓库地址: https://github.com/gofiber/fiber https://github.com/gofiber/fiber 官方文档地址: Welcome - Fiber https://docs.gofiber.io/ 4.Echo(25.8K) 项目简介:高性能、极简Go web框架 仓库地址: GitHub - labstack/echo: High performance, minimalist Go web framework High performance, minimalist Go web framework. Contribute to labstack/echo development by creating an account on GitHub. https://github.com/labstack/echo 官方文档地址: Echo - High performance, minimalist Go web framework Echo is a high performance, extensible, minimalist web framework for Go (Golang). https://echo.labstack.com/ 5.Iris(24K) 项目简介:Iris是一个高效且设计良好的跨平台web框架,具有强大的功能集。构建具有无限潜力和可移植性的高性能web应用程序和API。 仓库地址: GitHub - kataras/iris: The fastest HTTP/2 Go Web Framework. New, modern, easy to learn. Fast development with Code you control. Unbeatable cost-performance ratio | 谢谢 | #golang The fastest HTTP/2 Go Web Framework. New, modern, easy to learn. Fast development with Code you control. Unbeatable cost-performance ratio :leaves: :rocket: | 谢谢 | #golang - GitHub - kataras/iris: The fastest HTTP/2 Go Web Framework. New, modern, easy to learn. Fast development with Code you control. Unbeatable cost-performance ratio | 谢谢 | #golang https://github.com/kataras/iris 官方文档地址: Iris Docs The fastest HTTP/2 Go Web Framework. Iris provides a beautifully expressive and easy to use foundation for your next website, API, or distributed app. https://www.iris-go.com/docs/#/ ———————————————— 版权声明:本文为CSDN博主「深漂小码哥」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qq2942713658/article/details/127720799
-
如题,运行环境支持Golang 吗?
-
1. strcpy的基本用法详解 1.1 问题的提出 例如 我们要把字符串"hello"复制到数组arr[20]中去时,你会怎么操作; 首先 arr = "hello";//是错误的 arr数组名是首元素的地址,是个地址常量,是个编号;难道把hello放到这个编号上? 答案应该是放到编号所指向的空间中去; 其中 destination是目标空间的地址,source是源空间的地址 1.2 strcpy的基本原理: 把源指针指向的空间的数据拷贝到目的地指针指向的空间中去; char* p = "hello";//把首字符的地址放到p中,p就指向了这个字符串; strcpy(arr,"hello"); "hello"传参的时候传过去的是首字符'h'的地址,传给了source;其中destination指向了arr[20]整个数组,source指向了hello中'h'的地址;然后把source指向的hello拷贝放到destination指向的arr[20]中去; 1.3使用 strcpy的注意事项: 1.源字符串必须以 '\0' 结束 当拷贝"hello"时字符串的结束标志'\0'也会被拷贝过去,'\0'也是strcpy终止拷贝的一个条件; 2.会将源字符串中的 '\0' 拷贝到目标空间 3.目标空间必须足够大,以确保能存放源字符串 例如 arr[5]=0; strcpy(arr,"hello world");这是错误的 4.目标空间必须可变 例如 char* str = "123456789000"; char* p = "hello world"; strcpy(str,p);//这也是错误的 因为该目标空间是常量字符串,不可修改; 2. 模拟实现strcpy char *my_strcpy(char *destination, const char*source) { char *ret = destination; assert(destination != NULL); assert(source != NULL); while((*destination++ = *source++)) { ; } return ret; } ———————————————— 版权声明:本文为CSDN博主「记忆&碎片」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/YLG_lin/article/details/126690885
-
如果您是 Go 新手,那么您一定遇到过方法和函数的概念。让我们找出两者之间的区别-通过指定参数的类型、返回值和函数体来声明函数。type Person struct { Name string Age int }func NewPerson(name string, age int) *Person { return &Person{ Name: name, Age: age, } }方法只是一个带有接收器参数的函数。它使用相同的语法声明,并添加了接收者。func (p *Person) isAdult bool { return p.Age > 18 }在上面的方法声明中,我们在类型上声明了isAdult方法。*Person现在我们将看到值接收器和指针接收器之间的区别。值接收者复制类型并将其传递给函数。函数堆栈现在拥有一个相等的对象,但在内存上的不同位置。这意味着对传递的对象所做的任何更改都将保留在该方法的本地。原始对象将保持不变。指针接收器将类型的地址传递给函数。函数堆栈具有对原始对象的引用。因此对传递对象的任何修改都会修改原始对象。让我们通过示例来理解这一点-package mainimport ( "fmt")type Person struct { Name string Age int}func ValueReceiver(p Person) { p.Name = "John" fmt.Println("Inside ValueReceiver : ", p.Name)}func PointerReceiver(p *Person) { p.Age = 24 fmt.Println("Inside PointerReceiver model: ", p.Age)}func main() { p := Person{"Tom", 28} p1:= &Person{"Patric", 68} ValueReceiver(p)fmt.Println("Inside Main after value receiver : ", p.Name) PointerReceiver(p1)fmt.Println("Inside Main after value receiver : ", p1.Age)} ------------Inside ValueReceiver : JohnInside Main after value receiver : TomInside PointerReceiver : 24Inside Main after pointer receiver : 24这表明具有值接收者的方法修改了对象的副本,而原始对象保持不变。Like- 通过 ValueReceiver 方法将一个人的姓名从 Tom 更改为 John,但这种更改并未反映在 main 方法中。另一方面,带有指针接收器的方法会修改实际对象。Like- 通过 PointerReceiver 方法将人的年龄从 68 岁更改为 24 岁,同样的变化反映在 main 方法中。您可以通过在指针或值接收器操作之前和之后打印出对象的地址来检查事实。那么如何在 Pointer 和 Value 接收器之间进行选择呢?如果要更改方法中接收器的状态,操作它的值,请使用指针接收器。使用按值复制的值接收器是不可能的。对值接收器的任何修改对于该副本都是本地的。如果您不需要操作接收器值,请使用值接收器。指针接收器避免在每个方法调用上复制值。如果接收器是一个大型结构,这可能会更有效,值接收器是并发安全的,而指针接收器不是并发安全的。因此,程序员需要照顾它。汇总:如果接收者是 map、func 或 chan,不要使用指向它的指针。尽量对所有方法使用相同的接收器类型。如果接收者是一个切片并且该方法没有重新切片或重新分配切片,则不要使用指向它的指针。如果方法需要改变接收者,接收者必须是一个指针。如果接收者是包含sync.Mutex或类似同步字段的结构,则接收者必须是指针以避免复制。如果接收器是大型结构或数组,则指针接收器效率更高。大有多大?假设它相当于将其所有元素作为参数传递给方法。如果感觉太大,那么对于接收器来说也太大了。函数或方法是否可以同时或在从此方法调用时改变接收者?调用方法时,值类型会创建接收器的副本,因此外部更新不会应用于此接收器。如果更改必须在原始接收器中可见,则接收器必须是指针。如果接收器是结构体、数组或切片,并且它的任何元素都是指向可能发生变化的东西的指针,则更喜欢指针接收器,因为它会使读者更清楚意图。如果接收者是一个小数组或结构,它自然是一个值类型(例如,类似time.Time类型),没有可变字段和指针,或者只是一个简单的基本类型,如 int 或 string,则值接收器更好。值接收器可以减少可以生成的垃圾量;如果将值传递给值方法,则可以使用堆栈上的副本而不是在堆上分配。(编译器试图巧妙地避免这种分配,但它并不总是成功。)不要在没有首先进行分析的情况下选择值接收器类型。最后,当有疑问时,使用指针接收器。————————————————版权声明:本文为CSDN博主「hebiwen95」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/hebiwen95/article/details/126124140
-
1 软件介绍 minio为开源的高可用分部署对象存储服务组件,已经提供了分布式部署的解决方案,实现高可靠、高可用的资源存储,MinIO以极简主义为指导进行设计,追求极致的维护精简性和卓越的读写性能表现2 环境配置2.1 硬件平台 服务器TaiShan 200 2280处理器2*KunPeng 920内存NA硬盘NA网络NA 2.2 软件平台 软件名称版本号麒麟V10SP1MinioRELEASE.2022.03 3 系统配置3.1 关闭防火墙步骤 1 停止防火墙。# systemctl stop firewalld.service步骤 2 关闭防火墙。# systemctl disable firewalld.service----结束3.2 安装依赖若环境无外网条件,请配置本地源。步骤 1 安装依赖# yum -y install git----结束 4 软件编译4.1 Golang安装步骤 1 下载编译包。# wget https://golang.google.cn/dl/go1.17.11.linux-arm64.tar.gz --no-check-certificate步骤 2 解压到/usr/local下。# tar -zxvf go1.17.11.linux-arm64.tar.gz –C /usr/local步骤 3 配置go环境变量。# echo "export PATH=/usr/local/go/bin/:\$PATH" >> /etc/profile# sourced /etc/profile步骤 4 验证版本。# go version----结束4.2 MiniIO Server安装步骤 1 配置go代理。# export GOPROXY=https://goproxy.cn步骤 2 下载源码包。# git clone https://github.com/minio/minio.git步骤 3 切换版本# cd minio;git checkout RELEASE.2021-10-06T23-36-31Z步骤 4 编译# make步骤 5 验证版本号# ./minio --version----结束 4.3 MiniIO Client安装步骤 1 配置go代理。# export GOPROXY=https://goproxy.cn步骤 2 下载源码包。# git clone https://github.com/minio/mc.git步骤 3 切换版本# cd mc;git checkout RELEASE.2021-10-07T04-19-58Z步骤 4 编译# make步骤 5 查看版本时,发现版本并非2021-10-07T04-19-58Z,而为编译当天的日期。此问题非编译问题,而是源码本身的逻辑就是这样,详见FAQ 6.1 ----结束 5 运行验证5.1 运行MinIO服务端步骤 1 设置登录账号和密码# export MINIO_ROOT_USER=minio_test# export MINIO_ROOT_PASSWORD=minio_test步骤 2 初始化# mkdir -p /opt/data# ./minio server /opt/data步骤 3 打开网页,默认使用端口为9000,在浏览器输入地址 ip:9000输入刚才设置的账户密码 ----结束5.2 运行MinIO客户端 步骤 1 查看已添加的主机#./mc config host list 步骤 2 添加服务端./mc alias set myminio http://ip:9000 minioadmin minioadmin步骤 3 查看添加的服务端主机#./mc config host list6 FAQ6.1 mimio client查看版本号不对编译的版本是RELEASE.2021-10-07T04-19-58Z,但使用命令./mc -version看到的是当天日期版本查看Makefile,日期定义在变量BUILD_LDFLAGS---即变量LDFLAGS中LDFLAGS变量由buildscripts/gen-ldflags.go生成,查看buildscripts/gen-ldflags.go文件的源码,可以看到,版本号由当天时间定义,因此编译出来的版本显示为当天时间7 附录参考文献
上滑加载中
推荐直播
-
空中宣讲会 2025年华为软件精英挑战赛
2025/03/10 周一 18:00-19:00
宸睿 华为云存储技术专家、ACM-ICPC WorldFinal经验 晖哥
2025华为软挑赛空中宣讲会重磅来袭!完整赛程首曝+命题天团硬核拆题+三轮幸运抽奖赢参赛助力礼包,与全国优秀高校开发者同台竞技,直通顶尖赛事起跑线!
回顾中 -
华为开发者空间玩转DeepSeek
2025/03/13 周四 19:00-20:30
马欣 华为开发者布道师
同学们,想知道如何利用华为开发者空间部署自己的DeepSeek模型吗?想了解如何用DeepSeek在云主机上探索好玩的应用吗?想探讨如何利用DeepSeek在自己的专有云主机上辅助编程吗?让我们来一场云和AI的盛宴。
即将直播 -
华为云Metastudio×DeepSeek与RAG检索优化分享
2025/03/14 周五 16:00-17:30
大海 华为云学堂技术讲师 Cocl 华为云学堂技术讲师
本次直播将带来DeepSeek数字人解决方案,以及如何使用Embedding与Rerank实现检索优化实践,为开发者与企业提供参考,助力场景落地。
去报名
热门标签