• [区域初赛赛题问题] 比赛语言支持 Go 语言吗?
    如题请问 2025 年比赛可以使用 Go 语言吗?
  • [专题汇总] 2025年开年的第一篇合集来了。速进
    大家好,2025年开年的第一篇合集,本次带来的是Python,Java,MySql,Golang,JSON,等等希望可以帮到大家。1.Python判断for循环最后一次的方法【转】https://bbs.huaweicloud.com/forum/thread-0248173698858425071-1-1.html2.使用Python实现高效的端口扫描器【转】https://bbs.huaweicloud.com/forum/thread-0248173699028101072-1-1.html3.使用Python实现操作mongodb详解【转】https://bbs.huaweicloud.com/forum/thread-02109173699263711070-1-1.html4.一文详解Python中数据清洗与处理的常用方法【转】https://bbs.huaweicloud.com/forum/thread-02109173699342905071-1-1.html5.Go中sync.Once源码的深度讲解【转】https://bbs.huaweicloud.com/forum/thread-0271173699402065058-1-1.html6.从源码解析golang Timer定时器体系【转】https://bbs.huaweicloud.com/forum/thread-0251173701525255062-1-1.html7.golang1.23版本之前 Timer Reset方法无法正确使用【转】https://bbs.huaweicloud.com/forum/thread-02127173701584637057-1-1.html8.Python文件读写实用方法小结【转】https://bbs.huaweicloud.com/forum/thread-02104173701685566070-1-1.html9.mysql外键创建不成功/失效如何处理【转】https://bbs.huaweicloud.com/forum/thread-02109173701958630072-1-1.html10.Redis的Zset类型及相关命令详细讲解【转】https://bbs.huaweicloud.com/forum/thread-02109173702031434073-1-1.html11.大数据小内存排序问题如何巧妙解决【转】https://bbs.huaweicloud.com/forum/thread-02127173702077058058-1-1.html12.Redis多种内存淘汰策略及配置技巧分享【转】https://bbs.huaweicloud.com/forum/thread-0272173702166312062-1-1.html13.MySQL通过binlog实现恢复数据【转】https://bbs.huaweicloud.com/forum/thread-02109173702268081074-1-1.html14.MySQL如何将一个表的字段更新到另一个表中【转】https://bbs.huaweicloud.com/forum/thread-0272173702328248063-1-1.html15.JSON字符串转成java的Map对象详细步骤【转】https://bbs.huaweicloud.com/forum/thread-02109173702572327075-1-1.html
  • [技术干货] golang1.23版本之前 Timer Reset方法无法正确使用【转】
    ,为了防止文档更新而导致内容变动,这里粘贴出来:Before Go 1.23, the only safe way to use Reset was to [Stop] and explicitly drain the timer first. See the NewTimer documentation for more details. 在Go 1.23 之前,唯一安全使用Reset函数的方式是:在使用之前调用Stop函数并且明确的从timer的channel中抽取出东西。虽然文档中已经告诉了正确使用的方式,但是实际上在真正的代码中无法达到这个要求,参考下方代码//consumer    go func() {        // try to read from channel, block at most 5s.        // if timeout, print time event and go on loop.        // if read a message which is not the type we want(we want true, not false),        // retry to read.        timer := time.NewTimer(time.Second * 5)        for {            // timer may be not active, and fired            if !timer.Stop() {                select {                case <-timer.C: //try to drain from the channel,尝试抽取,由于使用select,因此这里可以保证:不阻塞 + 一定抽取成功                default:                }            }            timer.Reset(time.Second * 5)  //重置定时器            select {            case b := <-c:                if b == false {                    fmt.Println(time.Now(), ":recv false. continue")                    continue                }                //we want true, not false                fmt.Println(time.Now(), ":recv true. return")                return            case <-timer.C:                fmt.Println(time.Now(), ":timer expired")                continue            }        }    }()在上面的代码中,我们按照文档的要求,在 timer.Reset ​之前已经调用了 Stop ​函数,且如果 Stop 成功(返回 true),还尝试抽取 timer,看起来似乎没问题的代码中仍然存在问题。问题的关键在于:当 Ticket 触发的时候,设置定时器状态的操作和发送 channel 的操作并不是原子的golang1.23 之前到底应该如何正确的使用 Reset​实际上简洁点就这么写,每次一个新的局部变量 Timer​ 结构体没压力,非要复用使用 Reset 的可读性太差了,对维护者不友好,而且习惯了不好的写法,哪天一不小心就写出问题了~go func() {for {func() {timer := time.NewTimer(time.Second * 2)defer timer.Stop() select {case b := <-c:if !b {fmt.Println(time.Now(), "work...")}case <-timer.C: // BBB: normal receive from channel timeout eventfmt.Println(time.Now(), "timeout")}}()}}() 
  • [技术干货] 从源码解析golang Timer定时器体系【转】
    Timer、Ticker使用及其注意事项在刚开始学习golang语言的时候就听说Timer、Ticker的使用要尤其注意,很容易出现问题,这次就来一探究竟。本文主要脉络:介绍定时器体系,并介绍常用使用方式和错误使用方式源码解读timer、ticker是什么?timer和ticker都是定时器,不同的是:timer是一次性的定时器ticker是循环定时器,在底层实现上是timer触发后又重新设置下一次触发时间来实现的正确的使用姿势Timer对于Timer,可以通过三种函数创建:time.NewTimer​、time.AfterFunc​、time.After​。其使用范例如下:// FnTimer1 Timer的使用用法func FnTimer1() {timer := time.NewTimer(time.Second * 5) //返回生成的timerfmt.Printf("timer 的启动时间为:%v\n", time.Now()) expire := <-timer.C //注意这个channel是一个容量为1的channel!fmt.Printf("timer 的触发时间为:%v\n", expire)} func FnTimer2() {ch1 := make(chan int, 1)select {case e1 := <-ch1://如果ch1通道成功读取数据,则执行该case处理语句fmt.Printf("1th case is selected. e1=%v",e1)case <- time.After(2 * time.Second): //time.After直接返回生成的timer的channel,所以一般用于超时控制fmt.Println("Timed out")}} func FnTimer3() {_ = time.AfterFunc(time.Second*5, func() { //返回的也是timer,不过可以自己传入函数进行执行fmt.Printf("定时器触发了,触发时间为%v\n", time.Now())}) fmt.Printf("timer 的启动时间为:%v\n", time.Now())time.Sleep(10 * time.Second) // 确保定时器触发} 在底层原理上,三种不同的创建方式都是调用的time.NewTimer​,不同的是:​time.After​直接返回的是生成的timer的channel,而time.NewTimer​是返回生成的timer。​time.NewTimer​定时触发channel的原理就是定时调用一个sendTime​函数,这个函数负责向channel中发送触发时间;time.AfterFunc​就是将定时调用的sendTime​函数换成了一个自定义的函数。补充:返回的channel的容量为1,因此是一个asynchronous的channel,即在定时器fired(触发)、stop(停止)、reset(重启)之后,仍然有可能能收到数据~垃圾回收:在go1.23之前,对于处于active(没有触发且没有显式调用Stop)的Timer,gc是无法回收的,Ticket也是同样的道理。因此在高并发的场景下需要显式搭配​​defer time.Stop()​​来解决暂时的“内存泄露”的问题。上述两点在Golang 1.23得到了解决,且可能在未来的版本中channel的容量会改为0(由asynchronous改成sync),在Go 1.23相关源码的注释部分有对应的说明。Ticketticket相比于timer,其会循环触发,因此通常用于循环的干一些事情,like:// FnTicket2 Ticker的正确的使用方法!!func FnTicket2() {ticker := time.NewTicker(1 * time.Second)stopTicker := make(chan struct{})defer ticker.Stop()go func() {for {select {case now := <-ticker.C:// do somethingfmt.Printf("ticker 的触发时间为:%v\n", now)case <-stopTicker:fmt.Println("ticker 结束")return}}}() time.Sleep(4 * time.Second)stopTicker <- struct{}{}}注意:代码块中使用stopTicker​ + select​的方式是必须的,由于Stop函数只是修改定时器的状态,并不会主动关闭channel,因此如果直接使用循环(见​我的github仓库​ )会直接导致Ticker永远不退出而导致内存泄露!补充:TIcket = 触发时自动设置下一次时间的Timer,因此上面提到的Go1.23之前的Timer存在的问题依然存在。返回的channel容量为1垃圾回收:由于Ticket会一直循环触发,因此如果不显式调用​​time.Stop​​方法的话,会永久内存泄露。此外,对于Ticket,还有一些额外的注意事项:需要使用一个额外的channel+select的方式来正确停止Ticket。Reset函数的问题,由于比较长,单独整理在golang1.23版本之前 Timer Reset无法正确使用的问题,这里说个结论 :​Reset​​函数由于内部非原子,因此无法完美的使用,建议使用goroutine+Timer的方式替代!使用的注意事项由于Golang 1.23版本对定时器timer、ticker做了很大的改进,因此要分成1.23之前和1.23及其之后的版本分开考虑:以下是1.23版本关于timer、ticker部分的修改:未停止的定时器和不再被引用的计时器可以进行垃圾回收。在 Go 1.23 之前,未停止的定时器无法被垃圾回收,直到定时器超时,而未停止的计时器永远无法被垃圾回收。Go 1.23 的实现避免了在不使用 t.Stop​ 的程序中出现资源泄漏。定时器通道现在是同步的(无缓冲的),这使 t.Reset​ 和 t.Stop​ 方法具有更强的保证:在其中一个方法返回后,将来从定时器通道接收到的任何值都不会观察到与旧定时器配置相对应的陈旧时间值。在 Go 1.23 之前,无法使用 t.Reset​ 避免陈旧值,而使用 t.Stop​ 避免陈旧值需要仔细使用 t.Stop​ 的返回值。Go 1.23 的实现完全消除了这种担忧。总结一下:1.23版本改进了timer、ticker的垃圾回收和 停止、重置的相关方法(Reset、Stop)。这也就意味着在1.23版本之前,我们在使用的时候要注意:垃圾回收和停止、重置相关方法的使用。由于Reset、Stop方法的外在表现本质上上是跟缓冲区由 有缓冲 改为 无缓冲 相关,因此如果有涉及读取缓冲区我们也需要注意相关特性。具体来说,对于1.23之前:垃圾回收:TImer的回收只会在定时器触发(expired)或者Stop​之后;Ticker只显式触发Stop​之后才会回收;​Reset​、Stop​使用:对于Timer,没有完美的做法,无论怎么样Reset​和Stop​都可能存在一些问题;对于Ticker,记得使用完之后显式的Stop​;源码解读源码解读版本:release-branch.go1.8​运作原理timer(ticket)的运作依赖于struct p​,源码位置:src/runtime/runtime2.go:603。所有的计时器timer都以最小四叉堆的形式存储在struct p​的timers​字段中。并且也是交给了计时器都交由处理器的网络轮询器和调度器触发,这种方式能够充分利用本地性、减少上下文的切换开销,也是目前性能最好的实现方式。一般来说管理定时器的结构有3种:双向链表、最小堆、时间轮(很少)。双向链表:插入|修改时间:需要遍历去寻找插入|修改的位置,时间复杂度O(N);触发:链表头触发即可,时间复杂度O(1)。最小堆:插入|修改时间:O(logN)的时间复杂度;触发:队头触发,但是触发之后需要调整堆以保证堆的特性,O(logN)的时间复杂度。时间轮:插入|修改时间|触发的时间复杂度都是O(1)。但是需要额外维护时间轮的结构,其占据空间随着需要维护的未来时间长度、时间精度增加。涉及结构体定时器体系有两种timer:一次性触发的Timer​和循环触发的Ticker​,这两种的底层结构是相同的,触发逻辑是类似的。毕竟Ticker​的功能包含Timer​的功能。
  • [技术干货] Go中sync.Once源码的深度讲解【转】
    概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次。它在多线程环境中非常有用,尤其是在需要初始化共享资源或执行某些一次性任务时。简单示例当我们在web服务访问某个路由时,如果需要事先获取某些配置,往往会写一个loadConfig函数,获取一个cfg配置项。多次路由访问所需要获取的配置项通常是相同的,如果对于每次路由访问,都加载一次loadConfig函数,会导致产生一些不必要的开销。如果loadConfig涉及到读取文件、解析配置、网络请求时,有可能会额外增加的请求响应时间,降低服务的吞吐量。使用sync.Once包提供的Do函数,就可以只在第一次请求时调用loadConfig函数加载配置,之后的请求都复用第一次请求的配置,缩短响应时间。package mainimport (    "log"    "net/http"    "sync")type Config struct {    APIKey   string    LogLevel string}var (    config *Config    once   sync.Once)func loadConfig() {    // 模拟从文件或环境变量加载配置    config = &Config{       APIKey:   "secret-key",       LogLevel: "debug",    }    log.Println("Configuration loaded")}func GetConfig() *Config {    once.Do(loadConfig) // 仅第一次访问时会执行loadConfig函数    return config}func handler(w http.ResponseWriter, r *http.Request) {    cfg := GetConfig()    log.Printf("Request handled with API key: %s", cfg.APIKey)    w.Write([]byte("OK"))}func main() {    http.HandleFunc("/", handler)    log.Fatal(http.ListenAndServe(":8888", nil))}源码解读源码文件:src/sync/once.go (go 1.23 版本)package syncimport (    "sync/atomic")type Once struct {    done atomic.Uint32 // 是否已执行标识位,0-未执行 1-已执行    m    Mutex         // 互斥锁,确保并发安全}func (o *Once) Do(f func()) {    // 第一次执行Do函数时,原子操作检查o.done==0,执行doSlow函数后,o.done==1    // 第二次及之后执行Do函数,原子操作检查o.done标识位为1,Do函数不执行任何功能,确保了f函数只在第一次被执行    if o.done.Load() == 0 {       o.doSlow(f) // 调用doSlow函数执行f方法。第一次执行时,同一时间可能有多个goroutine尝试同时执行doSlow函数    }}func (o *Once) doSlow(f func()) {    o.m.Lock() // 加锁保护,避免多个goroutine同时绕过之前的原子操作检查,并发修改o.done的值    defer o.m.Unlock()    // 二次检查o.done的值,同一时间并发执行doSlow函数的goroutine,在第一个goroutine将o.done置为1并解除互斥锁后,    // 剩下的goroutine识别到自身的o.done已经被设为1,无法绕过二次检查    if o.done.Load() == 0 {        defer o.done.Store(1) // 需要在f()函数执行完成之后,原子性地将o.done设为1       f() // 执行f方法,一定只有一个goroutine会调用这个方法    }}可以看到,once.go文件的代码非常精炼。仅定义了一个含2个非导出字段done和m的结构体Once,并提供了一个doSlow方法用于执行f函数。当我们调用Do方法时,程序经历了几个关键步骤:判断done标志位是否等于0,如果是,说明f函数还没有被执行,执行doSlow方法mu互斥锁加锁,防止多个goroutine并发操作double-check done标志位是否等于0,如果是,说明f函数还没有被执行,执行f函数f函数执行完成之后,再将done标志位原子性设为1。使用原子操作是从内存可见性的角度出发,如果done使用uint32而不是atomic.Uint32,done修改可能不会立即被其它goroutine感知,解锁后仍有可能存在goroutine的done等于0,重复执行f函数mu互斥锁解锁。此时进入到doSlow函数的其它goroutine也感知到了o.done等于1,不会重复执行f函数了
  • [技术干货] 基于Go语言实现压缩文件处理【转】
    Go 语言压缩文件处理在现代的应用开发中,处理压缩文件(如 .zip 格式)是常见的需求。Go 语言提供了内置的 archive/zip 包来处理 .zip 文件的读写,但有时我们需要封装一些常用操作,使得代码更加简洁、易用。本文将介绍如何使用 Go 语言封装一个 ziputil 包,来处理文件的压缩和解压操作。1. 压缩文件:Zip函数在 Go 语言中,压缩文件通常需要使用 archive/zip 包。我们将对文件夹或文件进行遍历,创建一个新的 .zip 文件,并将文件或文件夹逐个添加到压缩包中。 package ziputil  import (     "archive/zip"     "go-admin/app/brush/utils"     "sync"     "io"     "os"     "path/filepath"     log "github.com/go-admin-team/go-admin-core/logger" )  // Zip 将指定的文件夹或文件压缩为 .zip 文件 func Zip(source, zipFile string) error {     // 创建一个新的 zip 文件     zipFileWriter, err := os.Create(zipFile)     if err != nil {         return err     }     defer func(zipFileWriter *os.File) {         err := zipFileWriter.Close()         if err != nil {             log.Errorf("关闭 zip 文件失败: %s", err)         }     }(zipFileWriter)      // 创建 zip 写入器     zipWriter := zip.NewWriter(zipFileWriter)     defer func(zipWriter *zip.Writer) {         err := zipWriter.Close()         if err != nil {             log.Errorf("关闭 zip 写入器失败: %s", err)         }     }(zipWriter)      // 获取源文件的绝对路径     absSource, err := filepath.Abs(source)     if err != nil {         return err     }      // 遍历文件夹并添加到 zip 文件中     return filepath.Walk(absSource, func(path string, info os.FileInfo, err error) error {         if err != nil {             return err         }          // 计算文件相对路径         relPath, err := filepath.Rel(absSource, path)         if err != nil {             return err         }          // 如果是目录,则在 zip 文件中创建一个目录项         if info.IsDir() {             if relPath != "." {                 _, err := zipWriter.Create(relPath + "/")                 if err != nil {                     return err                 }             }             return nil         }          // 否则将文件添加到 zip 文件         return addFileToZip(zipWriter, path, relPath)     }) }  // addFileToZip 将单个文件添加到 zip 写入器 func addFileToZip(zipWriter *zip.Writer, file string, relPath string) error {     f, err := os.Open(file)     if err != nil {         return err     }     defer func(f *os.File) {         err := f.Close()         if err != nil {             log.Errorf("关闭文件失败: %s", err)         }     }(f)      // 在 zip 文件中创建该文件     writer, err := zipWriter.Create(relPath)     if err != nil {         return err     }      // 将文件内容写入 zip     _, err = io.Copy(writer, f)     if err != nil {         return err     }      return nil } 2. 解压文件:UnZip 函数解压 .zip 文件时,我们需要将 .zip 文件中的每个文件提取到指定的目录中。UnZip 函数不仅能够提取文件,还能够处理文件夹结构,保证提取后的目录结构不丢失。 // UnZip 解压 zip 文件到目标目录 func UnZip(zipFile, destDir string) error {     log.Debugf("解压文件: %s 到 %s", zipFile, destDir)     r, err := zip.OpenReader(zipFile)     if err != nil {         return err     }      defer func(r *zip.ReadCloser) {         err := r.Close()         if err != nil {             log.Errorf("关闭 zip 文件失败: %s", err)         }     }(r)      log.Debugf("总共 %d 个文件", len(r.File))      // 并发解压每个文件     wg := sync.WaitGroup{}     for _, f := range r.File {         wg.Add(1)         go func(rf *zip.File, w *sync.WaitGroup) {             defer w.Done()             if err := unzipFile(rf, destDir); err != nil {                 log.Errorf("解压文件 [%s] 失败: %v", rf.Name, err)             }         }(f, &wg)     }      wg.Wait()      return nil }  // unzipFile 解压单个文件到目标目录 func unzipFile(f *zip.File, destDir string) error {     // 将文件名转换为 UTF-8     filename := utils.ConvertToUTF8([]byte(f.Name))     filePath := filepath.Join(destDir, filename)      // 创建文件夹     if f.FileInfo().IsDir() {         return os.MkdirAll(filePath, os.ModePerm)     }      // 创建文件的父目录     if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {         log.Errorf("创建目录 [%s] 失败: %v", filepath.Dir(filePath), err)         return err     }      // 打开文件     file, err := f.Open()     if err != nil {         log.Errorf("打开文件 [%s] 失败: %v", filePath, err)         return err     }      defer func(file io.ReadCloser) {         err := file.Close()         if err != nil {             log.Errorf("关闭文件 [%s] 失败: %v", filePath, err)         }     }(file)      // 创建文件     outFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())     if err != nil {         log.Errorf("创建文件 [%s] 失败: %v", filePath, err)         return err     }      defer func(outFile *os.File) {         err := outFile.Close()         if err != nil {             log.Errorf("关闭文件 [%s] 失败: %v", filePath, err)         }     }(outFile)      // 将文件内容写入     _, err = io.Copy(outFile, file)     if err != nil {         log.Errorf("复制文件 [%s] 失败: %v", filePath, err)         return err     }      return nil } 3. 小结通过 ziputil 包,我们可以方便地进行文件和文件夹的压缩和解压操作。该包使用了 Go 内置的 archive/zip 包来处理 .zip 文件,并通过 sync.WaitGroup 实现了解压过程的并发处理,提高了解压效率。对于较大的压缩文件或包含大量文件的压缩包,使用并发处理可以显著提升性能。
  • [技术干货] 使用Go语言中的Context取消协程执行的操作代码【转】
    使用 Go 语言中的 Context 取消协程执行在 Go 语言中,协程(goroutine)是一种轻量级的线程,非常适合处理并发任务。然而,如何优雅地取消正在运行的协程是一个常见的问题。本文将通过一个具体的例子来展示如何使用 context 包来取消协程的执行,特别是处理嵌套任务中的取消问题。问题描述假设我们有一个长时间运行的任务,该任务包含一个外层循环和一个内层任务。我们需要在外层循环接收到取消信号时,能够立即终止内层任务。以下是一个示例代码: package main  import (     "context"     "fmt"     "time" )  // longRunningTask 是一个模拟长时间运行的任务。 func longRunningTask(ctx context.Context) {     for {         select {         case <-ctx.Done(): // 监听 ctx.Done() 以获取取消信号             fmt.Println("任务被取消:", ctx.Err())             return // 接收到取消信号后退出         default:             currentTime := time.Now().Format("2006-01-02 15:04:05") // 获取并格式化当前时间             fmt.Printf("任务进行中... 当前时间:%s\n", currentTime)             for {                 fmt.Printf("111")                 time.Sleep(1 * time.Second) //             }         }     } }  func main() {     // 创建一个可以取消的 context     ctx, cancel := context.WithCancel(context.Background())      // 启动一个新的 goroutine 执行任务     go longRunningTask(ctx)      // 模拟一段时间后取消任务     time.Sleep(3 * time.Second)     fmt.Println("取消任务...")     cancel() // 发送取消信号      // 等待一段时间让任务有时间处理取消信号并退出     time.Sleep(10 * time.Second) } 在这个示例中,当我们取消任务时,外层循环会接收到取消信号并退出,但内层循环会继续运行,因为我们没有在内层循环中检查取消信号。解决方案为了确保内层任务也能响应取消信号,我们需要在内层任务中也检查 ctx.Done() 通道。以下是修改后的代码: package main  import (     "context"     "fmt"     "time" )  // longRunningTask 是一个模拟长时间运行的任务。 func longRunningTask(ctx context.Context) {     for {         select {         case <-ctx.Done(): // 监听 ctx.Done() 以获取取消信号             fmt.Println("任务被取消:", ctx.Err())             return // 接收到取消信号后退出         default:             currentTime := time.Now().Format("2006-01-02 15:04:05") // 获取并格式化当前时间             fmt.Printf("任务进行中... 当前时间:%s\n", currentTime)             // 启动内层任务             runInnerTask(ctx)         }     } }  // runInnerTask 是一个模拟内层长时间运行的任务。 func runInnerTask(ctx context.Context) {     for {         select {         case <-ctx.Done(): // 内层任务也监听 ctx.Done()             fmt.Println("内层任务被取消:", ctx.Err())             return // 接收到取消信号后退出         default:             fmt.Printf("111")             time.Sleep(1 * time.Second)         }     } }  func main() {     // 创建一个可以取消的 context     ctx, cancel := context.WithCancel(context.Background())      // 启动一个新的 goroutine 执行任务     go longRunningTask(ctx)      // 模拟一段时间后取消任务     time.Sleep(3 * time.Second)     fmt.Println("取消任务...")     cancel() // 发送取消信号      // 等待一段时间让任务有时间处理取消信号并退出     time.Sleep(10 * time.Second) } 解释外层循环:外层循环使用 select 语句来监听 ctx.Done() 通道。如果接收到取消信号,任务会打印一条消息并退出。内层任务:内层任务也使用 select 语句来监听 ctx.Done() 通道。如果接收到取消信号,内层任务会打印一条消息并退出。通过这种方式,我们可以确保无论是在外层循环还是内层任务中,任务都能响应取消信号并优雅地退出。总结在 Go 语言中,使用 context 包来管理协程的生命周期是非常重要的。通过在每个需要响应取消信号的地方检查 ctx.Done() 通道,我们可以确保任务能够及时响应取消信号并优雅地退出。这对于构建健壮和可靠的并发应用程序至关重要。
  • [专题汇总] 你想知道的这里都有,10月份干货合集,速进
     大家好,四季度的第一个干货合集来了,这次带来的东西主要涉及到golang,python,MySQL,redis,PostgreSQL,docker,minio,鸿蒙等等,希望可以帮助到到家 1. golang gin ShouldBind的介绍和使用示例详解【转载】 https://bbs.huaweicloud.com/forum/thread-02127165743740541019-1-1.html  2.golang flag介绍和使用示例【转载】 https://bbs.huaweicloud.com/forum/thread-0205165743832841012-1-1.html  3.Go语言中的格式化输出占位符的用法详解【转】 https://bbs.huaweicloud.com/forum/thread-0242165743874465013-1-1.html  4.Python中格式化字符串的方法总结【转载】 https://bbs.huaweicloud.com/forum/thread-02107165744051753023-1-1.html  5.Python使用进程池并发执行SQL语句的操作代码【转载】 https://bbs.huaweicloud.com/forum/thread-02111165744119162009-1-1.html  6.Mysql转PostgreSQL注意事项及说明【转】 https://bbs.huaweicloud.com/forum/thread-0205165745689855016-1-1.html  7.一文彻底讲清该如何处理mysql的死锁问题【转载】 https://bbs.huaweicloud.com/forum/thread-02111165745597896010-1-1.html  8.Redis实现分布式事务的示例【转载】 https://bbs.huaweicloud.com/forum/thread-0242165745553696016-1-1.html  9.MySQL服务无法启动且服务没有报告任何错误解决办法【转载】 https://bbs.huaweicloud.com/forum/thread-02127165745354539021-1-1.html      10.Python+OpenCV实现火焰检测【转载】 https://bbs.huaweicloud.com/forum/thread-0205165744941700014-1-1.html  11.Python Word实现批量替换文本并生成副本【转载】 https://bbs.huaweicloud.com/forum/thread-02127165744886949020-1-1.html  12.Python实现OFD文件转PDF【转载】 https://bbs.huaweicloud.com/forum/thread-0286165744755315017-1-1.html  13.Python中将文件从一个服务器复制到另一个服务器的4种方法【转】 https://bbs.huaweicloud.com/forum/thread-0242165744680560015-1-1.html  14.Python实现将pdf文档保存成图片格式【转载】 https://bbs.huaweicloud.com/forum/thread-0242165744483484014-1-1.html  15.Python通过keyboard库实现模拟和监听键盘【转载】 https://bbs.huaweicloud.com/forum/thread-0205165744195219013-1-1.html  16.docker 配置国内镜像源 https://bbs.huaweicloud.com/forum/thread-0286165634815059009-1-1.html  17.Typora 代码块Mac风格化 https://bbs.huaweicloud.com/forum/thread-02111165634917052004-1-1.html  18.MinIO上传和下载文件及文件完整性校验 https://bbs.huaweicloud.com/forum/thread-0286165634713872008-1-1.html  19.鸿蒙系统特性 https://bbs.huaweicloud.com/forum/thread-02127165634384647014-1-1.html  20.Java EasyExcel 导出报内存溢出如何解决 https://bbs.huaweicloud.com/forum/thread-02111165634289077003-1-1.html 
  • [技术干货] Go语言中的格式化输出占位符的用法详解【转】
    在 Go 语言中,格式化输出是一个非常常用的功能,特别是在处理字符串、数字和其他数据类型时。Go 提供了丰富的格式化选项,通过占位符来控制输出的格式。本文将详细介绍 Go 语言中常用的格式化占位符及其用法。基本格式化函数Go 语言提供了几个内置的格式化函数,这些函数主要位于 fmt 包和 log 包中:fmt 包fmt.Printf(format string, a ...interface{}):格式化输出到标准输出。fmt.Sprintf(format string, a ...interface{}):格式化输出到字符串。fmt.Fprintf(w io.Writer, format string, a ...interface{}):格式化输出到 io.Writer。log 包log.Printf(format string, v ...interface{}):格式化输出到日志。常用的格式化占位符字符串格式化占位符描述示例%s格式化字符串fmt.Printf("%s", "Hello")%q格式化字符串,用双引号包围fmt.Printf("%q", "Hello")数字格式化整数占位符描述示例%d十进制整数fmt.Printf("%d", 123)%b二进制整数fmt.Printf("%b", 123)%o八进制整数fmt.Printf("%o", 123)%x十六进制整数(小写字母)fmt.Printf("%x", 123)%X十六进制整数(大写字母)fmt.Printf("%X", 123)%cASCII 码对应的字符fmt.Printf("%c", 65)%UUnicode 码点fmt.Printf("%U", 'A')浮点数占位符描述示例%f浮点数fmt.Printf("%f", 123.456)%e科学记数法(小写字母 e)fmt.Printf("%e", 123.456)%E科学记数法(大写字母 E)fmt.Printf("%E", 123.456)%g根据值的大小选择 %e 或 %ffmt.Printf("%g", 123.456)%G根据值的大小选择 %E 或 %ffmt.Printf("%G", 123.456)布尔值占位符描述示例%t布尔值fmt.Printf("%t", true)其他类型占位符描述示例%p指针地址fmt.Printf("%p", &value)%T类型名称fmt.Printf("%T", value)特殊格式化占位符描述示例%v格式化任何值,默认格式fmt.Printf("%v", "Hello")%+v格式化任何值,包含结构体字段fmt.Printf("%+v", struct{ Name string }{"Alice"})%#v格式化任何值,Go 语法格式fmt.Printf("%#v", "Hello")自定义宽度和精度占位符描述示例%wd设置最小宽度,不足部分用空格填充fmt.Printf("%5d", 123)%0wd设置最小宽度,不足部分用零填充fmt.Printf("%05d", 123)%wf设置浮点数的小数位数fmt.Printf("%.2f", 123.456)对齐方式占位符描述示例%-w左对齐fmt.Printf("%-5d", 123)%+w右对齐(默认)fmt.Printf("%+5d", 123)
  • [技术干货] golang flag介绍和使用示例【转载】
    在 Go 语言中,flag 包用于解析命令行标志。它提供了一种简单的方法来处理程序的输入参数。以下是对 flag 包的介绍和使用示例。1. 基本概念标志(Flag):命令行参数,通常以短划线 - 开头,用于控制程序的行为。解析(Parse):读取和解析命令行参数。2. 常用函数flag.StringVar:定义一个字符串标志。flag.IntVar:定义一个整数标志。flag.BoolVar:定义一个布尔标志。flag.Parse():解析命令行参数。3. 示例代码下面是一个简单的示例,演示如何使用 flag 包:123456789101112131415161718192021222324package mainimport (    "flag"    "fmt")type Options struct {    Name string    Age  int    DB   bool}func main() {    // 创建一个 Options 结构体实例    var option Options    // 定义标志    flag.StringVar(&option.Name, "name", "Guest", "用户名称")    flag.IntVar(&option.Age, "age", 18, "用户年龄")    flag.BoolVar(&option.DB, "db", false, "初始化数据库")    // 解析命令行参数    flag.Parse()    // 输出参数    fmt.Printf("Name: %s\n", option.Name)    fmt.Printf("Age: %d\n", option.Age)    fmt.Printf("DB initialized: %v\n", option.DB)}4. 如何运行假设文件名为 main.go,可以通过命令行运行:1go run main.go -name=John -age=30 -db5. 输出结果运行以上命令后,输出将会类似于:123Name: JohnAge: 30DB initialized: true6. 帮助信息可以通过添加 -h 或 --help 参数查看帮助信息:1go run main.go -h输出将显示所有定义的标志及其说明。7. 小结flag 包提供了一种方便的方式来处理命令行参数。使用 flag 可以定义不同类型的标志,并在解析后使用这些参数。记得调用 flag.Parse() 来解析命令行参数。
  • [技术干货] golang gin ShouldBind的介绍和使用示例详解【转载】
    在 Go 语言的 Gin 框架中,ShouldBind 是用于将请求中的数据绑定到结构体的一个方法。它简化了从请求中提取参数的过程,支持多种数据格式(如 JSON、表单、查询参数等)。以下是 ShouldBind 的介绍和使用示例。1. 基本概念ShouldBind: 这个方法根据请求的 Content-Type 自动选择合适的绑定方式,将请求数据绑定到指定的结构体上。如果绑定成功,它返回 nil,否则返回错误信息。2. 支持的数据格式JSON: 适用于 application/json 的请求。表单数据: 适用于 application/x-www-form-urlencoded 的请求。查询参数: 适用于 URL 中的查询参数。3. 使用示例以下是一个简单的示例,展示如何使用 ShouldBind 绑定 JSON 数据到结构体。示例代码package main import (     "github.com/gin-gonic/gin"     "net/http" ) // 定义一个结构体用于绑定请求数据 type User struct {     Name  string `json:"name" binding:"required"`     Email string `json:"email" binding:"required,email"` } func main() {     router := gin.Default()     router.POST("/user", func(c *gin.Context) {         var user User         // 使用 ShouldBind 绑定请求数据         if err := c.ShouldBindJSON(&user); err != nil {             // 如果绑定失败,返回错误信息             c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})             return         }         // 绑定成功,返回成功响应         c.JSON(http.StatusOK, gin.H{"message": "用户创建成功", "user": user})     })     // 启动服务器     router.Run(":8080") }4. 运行示例将上述代码保存为 main.go。在终端中运行 go run main.go 启动服务器。使用工具(如 Postman 或 curl)发送 POST 请求到 http://localhost:8080/user,并在请求体中包含 JSON 数据,例如:1234{    "name": "John Doe",    "email": "john@example.com"}5. 响应如果请求成功,你将收到如下响应:1234567{    "message": "用户创建成功",    "user": {        "name": "John Doe",        "email": "john@example.com"    }}如果请求数据不符合要求(如缺少字段或格式错误),将返回相应的错误信息。6. 小结ShouldBind 是 Gin 中用于简化请求数据绑定的强大工具。支持多种数据格式,可以根据请求的 Content-Type 自动选择合适的绑定方式。通过结构体标签可以轻松定义验证规则,提高数据处理的安全性和可靠性。虽然 ShouldBind 可以处理多种类型的请求数据,但 ShouldBindUri、ShouldBindJSON 和 ShouldBindQuery 这些方法仍然有其独特的用途和优势。以下是它们存在的原因及各自的优点:1. 更明确的绑定方式ShouldBindUri:专门用于从 URL 路径参数中提取数据,适用于处理 RESTful API 中的动态路由。明确表示你只想从 URI 中获取参数,避免了可能的混淆。ShouldBindJSON:专门处理 JSON 数据,适合 Content-Type 为 application/json 的请求。提供了更好的错误提示和特定的绑定逻辑,确保 JSON 数据的正确解析。专门处理 URL 查询参数,适合 GET 请求中的参数解析。明确表示你只想从查询字符串中获取参数,便于阅读和维护。2. 提高代码可读性使用特定的绑定方法(如 ShouldBindJSON 和 ShouldBindQuery)可以让代码的意图更明确,使后续维护和阅读更容易。其他开发者可以迅速理解这段代码是处理什么类型的数据。3. 错误处理和反馈各个专用方法能够提供更详细的错误信息。例如,如果 JSON 解析失败,ShouldBindJSON 能够提供关于 JSON 格式的问题,而 ShouldBindQuery 则会专注于查询参数的错误。4. 性能优化虽然在大多数情况下性能差异不明显,但特定的绑定方法可能在某些场景下提供更优的性能,因为它们只关注特定的数据源。ShouldBindQuery:
  • [技术干货] GO中公平锁和非公平锁的具体使用【转】
    公平锁和非公平锁是计算机科学中的两种锁机制,它们主要用于多线程编程,以控制对共享资源的访问。一、公平锁 (Fair Lock)1. 概念公平锁是一种按照请求顺序授予锁的机制,即先请求锁的线程会先获得锁,后请求锁的线程会后获得锁。这种锁通过维护一个队列来管理等待的线程,确保每个线程都能公平地获取到锁。2. 优点避免饥饿:所有线程都有机会获得锁,不会出现某些线程长期得不到锁的情况。可预测性:锁的获取是按顺序进行的,具有较好的可预测性。3. 缺点性能开销:由于需要维护一个队列,公平锁在管理上有一定的性能开销。上下文切换增加:由于公平锁可能需要频繁地切换线程,导致上下文切换的次数增加,影响性能。二、非公平锁 (Unfair Lock)1. 概念非公平锁是一种不按照请求顺序授予锁的机制,即任何线程都有可能在任何时候获得锁,而不考虑请求顺序。这种锁通常会优先考虑当前已经持有锁的线程,以提高系统的吞吐量。2. 优点高性能:由于没有队列管理的开销,非公平锁通常性能较高,特别是在高并发场景下。减少上下文切换:非公平锁可以减少线程之间的上下文切换,提升效率。3. 缺点可能导致饥饿:某些线程可能长时间得不到锁,导致线程饥饿。不可预测性:锁的获取是随机的,具有较低的可预测性。三、Go语言中的实现Go语言中的锁主要通过sync包提供,常用的锁有Mutex(互斥锁)和RWMutex(读写互斥锁)。Go的sync.Mutex默认实现的是一种非公平锁,但也可以实现公平锁。1. 非公平锁的实现Go标准库中的sync.Mutex是非公平锁的实现。它的主要结构和实现方式如下: type Mutex struct {     state int32     sema  uint32 }  func (m *Mutex) Lock() {     // 快速路径:尝试直接获取锁     if atomic.CompareAndSwapInt32(&m.state, 0, 1) {         return     }     // 慢速路径:获取不到锁时,调用lockSlow方法     m.lockSlow() }  func (m *Mutex) Unlock() {     // 快速路径:尝试直接释放锁     if atomic.CompareAndSwapInt32(&m.state, 1, 0) {         return     }     // 慢速路径:释放锁时,调用unlockSlow方法     m.unlockSlow() } 2. 公平锁的实现Go标准库不直接提供公平锁的实现,但我们可以通过其他方式实现公平锁,比如通过条件变量(sync.Cond)来维护等待的队列,从而实现公平锁。 type FairMutex struct {     mu       sync.Mutex     cond     *sync.Cond     waiting  []chan struct{} }  func NewFairMutex() *FairMutex {     fm := &FairMutex{}     fm.cond = sync.NewCond(&fm.mu)     return fm }  func (fm *FairMutex) Lock() {     fm.mu.Lock()     defer fm.mu.Unlock()      ch := make(chan struct{})     fm.waiting = append(fm.waiting, ch)      if len(fm.waiting) > 1 {         <-ch     } }  func (fm *FairMutex) Unlock() {     fm.mu.Lock()     defer fm.mu.Unlock()      if len(fm.waiting) > 0 {         fm.waiting = fm.waiting[1:]         if len(fm.waiting) > 0 {             close(fm.waiting[0])         }     } } 四、总结公平锁:按请求顺序授予锁,避免饥饿,维护队列,开销较大。非公平锁:随机授予锁,高性能,可能导致饥饿。
  • [技术干货] Go语言实现广播式并发聊天服务器【转】
    实现功能每个客户端上线,服务端可以向其他客户端广播上线信息;发送的消息可以广播给其他在线的客户支持改名支持客户端主动退出支持通过who查找当前在线的用户超时退出变量用户结构体 保存用户的管道,用户名以及网络地址信息type Client struct {     C    chan string //用于发送数据的管道     Name string      //用户名     Addr string      //网络地址 } 保存在线用户的map表var onlineMap map[string]Client 消息通道var message = make(chan string) 主协程监听客户端的连接请求listener, err := net.Listen("tcp", "127.0.0.1:8000")当客户端有消息发送,就向当前用户列表中所有在线用户转发消息go Manager()接受客户端的请求conn, err1 := listener.Accept()处理用户连接go HandleConn(conn) func main() {     //监听     listener, err := net.Listen("tcp", "127.0.0.1:8000")     if err != nil {         fmt.Println("net.Listen.err=", err)         return     }      defer listener.Close()      //新开一个协程,转发消息,只要有消息,就遍历map,给每个成员发送消息     go Manager()     //主协程,循环阻塞等待用户连接     for {         conn, err1 := listener.Accept()         if err1 != nil {             fmt.Println("listener.Accept.err1=", err1)             continue         }          //处理用户连接         go HandleConn(conn)     }  } 处理用户连接子协程获取客户端的网络地址cliAddr := conn.RemoteAddr().String()创建一个用户结构体,默认:用户名和网络地址一样cli := Client{make(chan string), cliAddr, cliAddr},加入map表给客户端发送信息go WriteMsgToClient(cli, conn)广播某个人在线message <- MakeMsg(cli, "login")提示当前用户 cli.C <- MakeMsg(cli, "I am here")判断用户状态isQuit hasData接收用户的请求,查看当前用户who,改名rename,发送消息message func HandleConn(conn net.Conn) {     cliAddr := conn.RemoteAddr().String()     cli := Client{make(chan string), cliAddr, cliAddr}      //把结构体添加到map     onlineMap[cliAddr] = cli      //新开一个协程,给客户端发送信息     go WriteMsgToClient(cli, conn)      //广播某个人在线     message <- MakeMsg(cli, "login")     //提示当前用户     cli.C <- MakeMsg(cli, "I am here")      isQuit := make(chan bool) //对方是否主动退出      hasData := make(chan bool) //对方是否有数据      //新开一个协程,接收用户的请求     go func() {         buf := make([]byte, 2048)         for {             n, err := conn.Read(buf)             if n == 0 {                 //对方断开或者出问题                 isQuit <- true                 fmt.Println("conn.Read.err=", err)                 return             }             msg := string(buf[:n-1])             if len(msg) == 3 && msg == "who" {                 //遍历map,给当前用户发送所有成员                 conn.Write([]byte("user list:\n"))                 for _, tmp := range onlineMap {                     msg := tmp.Addr + ":" + tmp.Name + "\n"                     conn.Write([]byte(msg))                 }             } else if len(msg) >= 8 && msg[:6] == "rename" {                 name := strings.Split(msg, "|")[1]                 cli.Name = name                 onlineMap[cliAddr] = cli                 conn.Write([]byte("rename ok\n"))              } else {                 message <- MakeMsg(cli, msg)             }              hasData <- true //代表有数据          }      }()     for {         //通过select检测channel的流动         select {         case <-isQuit:             delete(onlineMap, cliAddr)           //当前用户从map移除             message <- MakeMsg(cli, "login out") //广播谁下线了             return         case <-hasData:         case <-time.After(60 * time.Second):             delete(onlineMap, cliAddr)             message <- MakeMsg(cli, "time out leave out")             return         }     }  } 给客户端发送信息 func WriteMsgToClient(cli Client, conn net.Conn) {     for msg := range cli.C {         conn.Write([]byte(msg + "\n"))      }  } 发送消息func MakeMsg(cli Client, msg string) (buf string) {     buf = "[" + cli.Addr + "]" + cli.Name + ":" + msg     return } 转发消息子协程有消息到来就进行广播给map分配空间onlineMap = make(map[string]Client)遍历在线用户列表,转发消息;没有消息之前message通道会阻塞 func Manager() {     //给map分配空间     onlineMap = make(map[string]Client)      for {         msg := <-message //没有消息前,会阻塞         for _, cli := range onlineMap {             cli.C <- msg         }     } } 
  • [技术干货] Go重写http请求重定向的方法【转】
    一、背景当使用 Go 语言进行 HTTP 请求时,默认情况下,http.Client 会自动处理服务器返回的重定向响应(3xx 状态码)。但有时候,我们可能需要在请求中禁止自动的重定向。本文将详细介绍如何在 Go 中实现禁止 HTTP 请求的重定向、限制重定向次数以及添加自定义重定向策略。二、默认值http.Client 的 CheckRedirect 字段是用于处理重定向策略的函数,如果 CheckRedirect 不是 nil,则客户端会在遵循 HTTP 重定向之前调用它。参数 req 和 via 是即将到来的请求和已经发出的请求,最早发出的请求在前面。如果 CheckRedirect 返回错误,则 Client 的 Get 方法将返回前一个 Response(其 Body 关闭)和 CheckRedirect 的错误(包装在 url.Error),而不是继续发出重定向请求。作为一种特殊情况,如果 CheckRedirect 返回 ErrUseLastResponse,则返回的最新响应体的 body, 且 body 未关闭,并返回 nil 错误。如果 CheckRedirect 为 nil,则客户端使用其默认重定向策略,即在连续 10 个请求后停止。相关源码如下,来自src/net/http/client.go。func defaultCheckRedirect(req *Request, via []*Request) error {     if len(via) >= 10 {         return errors.New("stopped after 10 redirects")     }     return nil } 三、禁止重定向通过设置 http.Client 的 CheckRedirect 字段为一个为一个自定义的函数,可以控制重定向的行为。这个函数接收一个 *http.Request 和一个 []*http.Request 参数,前者代表当前正在处理的请求,后者代表已经请求的重定向请求链,返回 http.ErrUseLastResponse 错误,收到这个错误后,http.Client 不会再继续重定向请求,并且返回一个 nil 错误给上游,如下:func forbidRedirect(req *http.Request, via []*http.Request) (err error) {     // 返回一个错误,表示不允许重定向     return http.ErrUseLastResponse } 五、自定义重定向策略通过对重定向函数的重写,添加一些自定义的逻辑,并将该函数其赋值给 http client 的CheckRedirect,可以实现自定义重定向策略,其中 req.Response 参数表示导致该次重定向的返回。 func myRedirect(req *http.Request, via []*http.Request) error {     // 限制重定向次数     if len(via) >= 10 {         return errors.New("stopped after 10 redirects")     }     if req == nil || req.URL == nil || req.Response == nil || !strings.HasPrefix(req.Response.Status, "3") {         return http.ErrUseLastResponse     }     // 禁止重定向下载 apk 文件     if strings.HasSuffix(req.URL.Path, "apk") {         return fmt.Errorf("invalid redirect url, path: %s", req.URL.Path)     }      // 限制重定向请求类型     contentType := req.Response.Header.Get("Content-Type")     if strings.Contains(contentType, "octet-stream") {         return fmt.Errorf("invalid redirect url, type: %s", contentType)     }      // 限制重定向请求体长度     contentLength := req.Response.Header.Get("Content-Length")     if contentLength != "" {         length, _ := strconv.Atoi(contentLength)         if length > 1000 {             return fmt.Errorf("invalid redirect url, len: %s", contentLength)         }     }      // 限制重定向请求传输编码     transferEncoding := req.Response.Header.Get("Transfer-Encoding")     if strings.Contains(transferEncoding, "chunked") {         return fmt.Errorf("invalid redirect url, encoding: %s", transferEncoding)     }      return http.ErrUseLastResponse } 
  • [技术干货] 基于Go实现TCP长连接上的请求数控制【转】
    一、背景在服务端开启长连接的情况下,四层负载均衡转发请求时,会出现服务端收到的请求qps不均匀的情况,或是服务重启后会长时间无法接受到请求,导致不同服务端机器的负载不一致,qps高的机器过载的问题;该问题的原因是只有在新建连接时才会触发负载四层负载均衡器的再均衡策略,客户端随机与不同的服务器新建 TCP 连接,否则现有的 TCP 连接够用时,会一致被复用,在现有的 TCP 连接上传输请求,出现 qps 不均匀的情况;因此需要服务端定期主动断开一些长连接,触发四层转发连接的再均衡策略,实现类似于七层负载均衡 Nginx 中的 keepalive_requests 字段的功能,即同一个 TCP 长连接上的请求数达到一定数量时,服务端主动断开 TCP 长连接。二、基本介绍1,TCP 的 Keepalive:即 TCP 保活机制,是由 TCP 层(内核态) 实现的,位于传输层,相关配置参数在 /proc/sys/net/ipv4目录下:tcp_keepalive_intvl:保活探测报文发送时间间隔,75s;tcp_keepalive_probes:保活探测报文发送次数,9次,9次之后直接关闭;tcp_keepalive_time:保活超时时间,7200s,即该 TCP 连接空闲两小时后开始发送保活探测报文;TCP 连接传输完数据后,不会立马主动关闭,会先存活一段时间,超过存活时间后,会触发保活机制发送探测报文,多次探测确认没有数据继续传输后,再进行 TCP 四次挥手,关闭 TCP 连接;在 go 语言中,建立 TCP 连接时,默认设置的 keep-alive 为 15s,详见 go1.21 src/net/tcp/tcpsocket.go2,HTTP 的 Keep-Alive即 HTTP 长连接,是由应用层(用户态) 实现的,位于应用层;需要在 HTTP 报文的头部设置以下信息:12* Connection: keep-alive* Keep-Alive: timeout=7200上面信息表示,http 采用长连接,且超时时间为7200s;http 协议 1.0 默认采用短连接,即每次发送完数据后会设置 Connection: close 表示需要主动关闭当前 TCP 连接,进行四次挥手后关闭;下次再发送数据前,又需要先进行三次握手建立 TCP 连接,才能发送数据;循环往复,每次建立的 TCP 连接都只能发送一次数据,每次发送数据都需要进行三次握手与四次挥手,每次建立连接与断开连接会导致网络耗时变长ttp 协议 1.1 开始默认采用长连接;即每次发送完数据后会设置 Connection: keep-alive 表示需要复用当前 TCP 连接,建立一次 TCP 连接后,可以发送多次的 HTTP 报文,即多次发送数据也只需要一遍三次握手与四次挥手,省去了每次建立连接与断开连接的时间3,四层负载均衡四层负载均衡是一种在网络层(第四层)上进行负载均衡的技术,通过传输层协议 TCP 或 UDP,将传入的请求分发到多个服务器上,以实现请求的负载均衡和高可用性;四层负载均衡主要基于目标IP地址和端口号对请求进行分发,不深入分析请求的内容和应用层协议,通常使用负载均衡器作为中间设备,接收客户端请求,并将请求转发到后端服务器;负载均衡器可以根据预定义的算法(例如轮询、最小连接数、哈希、随机、加权随机等)选择后端服务器来处理请求;4,七层负载均衡Nginx 可以用于七层负载均衡器,客户端与 Nginx 所在的服务器建立起 TCP 连接,通过解析应用层中的内容,选择对应的后端服务器,Nginx 所在的机器再与后端服务器建立起 TCP 连接,将应用层数据转发后端服务器上,这就是所谓的七层负载均衡,即根据应用层的信息进行转发;在 Nginx 中,keepalive_requests 指令用于设置在长连接上可以处理的最大请求数量,一旦达到这个数量,Nginx 将关闭当前连接并等待客户端建立新的连接以继续处理请求;通过限制每个持久连接上处理的请求数量,keepalive_requests 可以帮助控制服务器资源的使用,并防止连接过度占用服务器资源,也可以帮助避免潜在的连接泄漏和提高服务器的性能;该值设置得过小,会导致经常需要 TCP 三次握手和四次挥手,无法有效发挥长连接的性能;该值设置得过大,会无法发挥该值的作用,导致长连接上的请求过多;具体的大小,要根据实际请求的 QPS 和响应耗时来设置;七层负载均衡能够根据应用层的请求内容实现更惊喜的请求分发和处理,但是需要建立两次 TCP 连接,以及每次将报文逐步解析到应用层再又逐步封装链路层,会导致耗时和失败率上涨;三、具体实现1,代码示例 package main  import (     "sync"     "time"      "github.com/labstack/echo" )  type QpsBalance struct {     mu   sync.Mutex     data map[string]int // key: ip:port     num  int            // 通过配置文件来配置 }  // Update 返回 true 表示当前的 tcp 连接上的请求数超过限制,需要断开连接 // 如果是某个 tcp 连接长时间没有后续请求了,默认 15s 之后会发送保活报文, func (q *QpsBalance) Update(k string) bool {     q.mu.Lock()     defer q.mu.Unlock()      num := q.data[k] + 1     if num >= q.num {         q.data[k] = 0         return true     }     q.data[k] = num     return false }  // Reset 通过定时任务每天3点重置,避免上游多次不同的扩容ip形成脏数据 func (q *QpsBalance) Reset() {     q.mu.Lock()     defer q.mu.Unlock()      q.data = make(map[string]int) }  func (q *QpsBalance) Init(n int) {     q.mu.Lock()     defer q.mu.Unlock()      q.data = make(map[string]int)     q.num = n }  func main() {     balancer := &QpsBalance{}     balancer.Init(200)     e := echo.New()     e.PUT("/handle", func(c echo.Context) error {         if balancer.Update(c.Request().RemoteAddr) {             c.Response().Header().Set("Connection", "close")         }         // do other         return nil     })      go func(b *QpsBalance) {         ticker := time.NewTicker(time.Hour)         for {             t := <-ticker.C             if t.Hour() == 3 {                 b.Reset()             }         }     }(balancer) } 2,基本原理当同一个 TCP 连接上的请求数达到一定限制时,设置返回头部为 Connection: close,主动关闭 TCP 连接,并重置计数器;需要注意的是,客户端如果也是服务器,并且存在自动扩容,那么需要定期清理计数的 map,避免多次不同的扩容ip形成脏数据;以及某些 TCP 连接可能没有达到计数的阈值,便不再被复用了,经过一段时间后会主动断开,这些 TCP 的计数依然存在 map 中,形成了脏数据;四、数据验证通过服务记录的监控,查看上游请求过来的平均耗时、P99 耗时、失败率是否有明显的变化,以及不同服务器收到的请求 QPS 是否均匀;通过 tcpdump src port 28080 -A | grep Connection 命令查看服务端响应的HTTP报文头部是否有 Connection: close 字段,即是否会主动关闭 TCP 连接;通过 netstat -antp | grep main | grep :28080 命令查看不同服务器上的服务建立的长连接数是否均匀;选取某个长连接,多次查看其存在的时间,是否符合预期;预期时间:假如 keepalive_requests 设置为 100,客户端记录的响应耗时 20ms(包括网络耗时和服务端耗时),那么平均一个长连接一秒能够发送 5 个请求,约 20s 后能够处理 100 个,那么该长连接能够存活 20s;注意不能查看某个长连接对应的 socket 创建的时间,因为同一个 socket 会被不同的长连接复用,一般不会被关闭;在 TCP 连接中,使用四元组来标记的一个唯一的 TCP 连接,即源ip、源端口、目的ip、目的端口,现在是在服务端进行计数的,所以目的ip和目的端口都是一样的,仅仅通过源ip和源端口便可以分别出 TCP 连接;
总条数:192 到第
上滑加载中