#87 Golang UUID

2022-09-13

awesome-go 上面列出来的项目:

  • jakehl/goid  - Generate and Parse RFC4122 compliant V4 UUIDs.
  • twharmon/gouid  - Generate cryptographically secure random string IDs with just one allocation.
  • aidarkhanov/nanoid  - A tiny and efficient Go unique string ID generator.
  • muyo/sno  - Compact, sortable and fast unique IDs with embedded metadata.
  • oklog/ulid  - Go implementation of ULID (Universally Unique Lexicographically Sortable Identifier).
  • uniq - No hassle safe, fast unique identifiers with commands.
  • agext/uuid  - Generate, encode, and decode UUIDs v1 with fast or cryptographic-quality random node identifier.
  • gofrs/uuid  - Implementation of Universally Unique Identifier (UUID). Supports both creation and parsing of UUIDs. Actively maintained fork of satori uuid.
  • google/uuid  - Go package for UUIDs based on RFC 4122 and DCE 1.1: Authentication and Security Services.
  • edwingeng/wuid  - An extremely fast globally unique number generator.
  • rs/xid  - Xid is a globally unique id generator library, ready to be safely used directly in your server code.

我自己又在 GitHub 上搜罗了几个:

package main

import (
    "os/exec"

    guuid "github.com/google/uuid"
    suuid "github.com/satori/go.uuid"
)

func UseGoogle() string {
    id := guuid.New()
    return id.String()
}

func UseSatori() string {
    id := suuid.NewV4()
    return id.String()
}

func UseUuidgen() string {
    id, _ := exec.Command("uuidgen").Output()
    return string(id)
}

func main() {
    fmt.Println("Google UUID:", UseGoogle())
    fmt.Println("Satori UUID:", UseSatori())
    fmt.Println("Uuidgen UUID:", UseUuidgen())
}

#86 Go 谚语

2022-09-13

得知了 Go 谚语这么个东西,搜索一番,找到:

  • https://go-proverbs.github.io/
  • https://github.com/jboursiquot/go-proverbs
  • https://www.youtube.com/watch?v=PAAkCSZUG1c

原来是类似 Python 之禅(import this)一样的东西。

  1. Don't communicate by sharing memory, share memory by communicating. 别通过共享内存通信,要通过通信共享内存。
  2. Concurrency is not parallelism. 并发不是并行。
  3. Channels orchestrate; mutexes serialize.
  4. The bigger the interface, the weaker the abstraction. 接口越大, 抽象越弱。
  5. Make the zero value useful.
  6. interface{} says nothing.
  7. Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 别争论风格问题,不管喜不喜欢,用 gofmt 就是了。
  8. A little copying is better than a little dependency. 少量的复制好过引入新的依赖。
  9. Syscall must always be guarded with build tags.
  10. Cgo must always be guarded with build tags.
  11. Cgo is not Go. 别用 cgo。
  12. With the unsafe package there are no guarantees. 慎用 unsafe 包(安全无保障)
  13. Clear is better than clever. 清晰的代码比聪明的代码好。
  14. Reflection is never clear. 慎用反射(代码不清晰)。
  15. Errors are values.
  16. Don't just check errors, handle them gracefully.
  17. Design the architecture, name the components, document the details.
  18. Documentation is for users. 文档是给用户的(注意代码本身的可读性)。
  19. Don't panic. 不要滥用 panic

#85 Viper: Go 项目配置管理

2022-07-22
  • main.go
package main

import (
    "fmt"
    "os"

    "github.com/spf13/pflag"
    "github.com/spf13/viper"
)

func main() {
    // 1. 设置 Viper 配置
    viper.SetConfigName("config") // 配置文件名(不带后缀)
    viper.AddConfigPath(".")      // 配置文件路径
    viper.SetConfigType("yaml")   // 配置文件类型
    viper.AutomaticEnv()          // 自动读取环境变量

    // 2. 设置命令行参数
    pflag.String("name", "", "project name")
    pflag.String("host", "", "host address")
    pflag.String("port", "", "port number")
    pflag.String("config", "./config.yaml", "config file") // 配置文件参数
    pflag.Parse()
    viper.BindPFlags(pflag.CommandLine) // 将命令行参数绑定到 Viper

    // 3. 读取配置文件
    if configFile := viper.GetString("config"); configFile != "" {
        fmt.Println(configFile)
        if err := viper.ReadInConfig(); err != nil {
            fmt.Fprintf(os.Stderr, "读取配置文件失败:%v\n", err)
            os.Exit(1)
        }
    }

    // 4. 读取配置项
    projectName := viper.GetString("name")
    port := viper.GetInt("port")
    fmt.Printf("ProjectName: %s, Port: %d\n", projectName, port)
}
  • config.yaml
name: hello
host: 10.10.0.172
port: 9090
  • 支持环境变量、命令行参数、配置文件。
  • 支持多种配置文件,包括 JSON,YAML,TOML,INI 等。
  • 支持监控配置文件的变化,会自动加载新的配置。
  • 支持从远程加载配置,比如 etcd、zk、consul、redis 等,
    也可以通过 RemoteProvider 接口自定义远程数据源:
type RemoteProvider interface {
    Set(key string, value []byte) error
    Watch(key string) (chan *RemoteResponse, chan error)
    Get(key string) ([]byte, error)
}
  • 支持默认值。

#84 好用的 golang 库:hashring(一致性哈希)

2022-07-20

通过对 key 做哈希,来实现 Redis 服务器的分片。
用这个例子来演示这个小库的用法:

package main

import (
    "crypto/rand"
    "encoding/base64"
    "fmt"
    "log"
    "time"

    "github.com/go-redis/redis"
    "github.com/serialx/hashring"
)

func RandomString(length int) (string, error) {
    b := make([]byte, length)
    _, err := rand.Read(b)
    if err != nil {
        return "", err
    }
    return base64.URLEncoding.EncodeToString(b)[:length], nil
}

func main() {
    redisServers := map[string]int{
        "redis1:6379": 200,
        "redis2:6379": 100,
    }

    redisClients := []*redis.Client{}
    for uri, _ := range redisServers {
        redisClients = append(redisClients, redis.NewClient(&redis.Options{Addr: uri}))
    }

    log.Println(redisServers)
    ring := hashring.NewWithWeights(redisServers)

    stat := map[string]int{}
    for uri, _ := range redisServers {
        stat[uri] = 0
    }

    for i := 0; i < 100; i++ {
        randstr, _ := RandomString(8)
        key := "test:randkey:" + randstr
        node, ok := ring.GetNode(key)
        if !ok {
            log.Panicf("cannot assign a redis client by key: %#v", key)
        }
        log.Printf("%s -> %s", key, node)
        stat[node]++

        var client *redis.Client
        for _, _client := range redisClients {
            if node == _client.Options().Addr {
                client = _client
                break
            }
        }
        if client == nil {
            log.Panicf("redis client assigned error: %#v", node)
        }

        client.Set(key, 1, time.Minute)
    }

    fmt.Println(stat)
}

#83 Golang 布隆过滤器

2022-07-19

https://github.com/bits-and-blooms/bloom/v3

package main

import (
    "fmt"
    "github.com/bits-and-blooms/bloom/v3"
)

func main() {
    filter := bloom.New(1000000, 5)
    filter.Add([]byte("apple"))
    filter.Add([]byte("banana"))
    filter.Add([]byte("orange"))
    fmt.Println(filter.Test([]byte("apple")))
    fmt.Println(filter.Test([]byte("banana")))
    fmt.Println(filter.Test([]byte("orange")))
    fmt.Println(filter.Test([]byte("grape")))
    fmt.Println(filter.Test([]byte("watermelon")))
}

#81 Go iota

2022-06-10
type Weekday int

const (
    Sunday Weekday = iota // 0
    Monday                // 1
    Tuesday               // 2
    Wednesday             // 3
    Thursday              // 4
    Friday                // 5
    Saturday              // 6
)

func (day Weekday) String() string {
    names := [...]string{
        "Sunday",
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday",
    }

    if day < Sunday || day > Saturday {
        return "Unknown"
    }

    return names[day]
}
type FileSizeUnit int

const (
    _ = iota // 忽略第一个值 0,让后续常量从 1 开始递增
    KB FileSizeUnit = 1 << (10 * iota)  // 1 << (10*1) = 1 << 10 = 1024
    MB                                  // 1 << (10*2) = 1 << 20 = 1048576
    GB                                  // 1 << (10*3) = 1 << 30 = 1073741824
    TB                                  // 1 << (10*4) = 1 << 40 = 1099511627776
)
package main

import "fmt"

func main() {
    {
        const (
            a = iota
            b
            c
        )
        fmt.Printf("%+v\n", []int{a, b, c})
        // [0 1 2]
    }
    {
        const (
            a = 1
            b = iota
            c
        )
        fmt.Printf("%+v\n", []int{a, b, c})
        // [1 1 2]
    }
    {
        const (
            a = 1
            b = 2
            c = iota
        )
        fmt.Printf("%+v\n", []int{a, b, c})
        // [1 2 2]
    }
    {
        const (
            a = 1
            b = iota
            c = iota
        )
        fmt.Printf("%+v\n", []int{a, b, c})
        // [1 1 2]
    }
}

总结

  1. 首次出现是这个常量块中变量的顺序(0 开始)
  2. 后面的常量如果没有定义,就表示是按照前面的常量定义来,只是 iota 代表的值 + 1 了
  3. 如果想跳过一个值,可以使用下划线

#79 Golang GC 与 STW [编辑中]

2022-04-11

相信 Golang 开发者可能前段时间见过两位大佬关于 Golang 性能的技术讨论(撕X),其中一个很重要的点就是涉及 Golang GC 对性能的影响。

我之前的开发经验几乎全部集中于 PHP,JS,Python 等脚本型语言,较少需要涉及 GC(只有几次涉及服务内存占用的时候检查过 GC)。
接触到 Golang 之后,如果不去研究 GC 可能很多疑问是无法解决的,很多时候的优化也和 GC 密切相关。

#78 Golang 版本

2022-04-10

2007 Robert Griesemer、Rob Pike 和 Ken Thompson 三位巨头都在谷歌任职,开始设计一门全新的语言。

Release Status Release date Maintenance end
go1 End-of-Life 2012-03-28 2013-12-01
go1.1 End-of-Life 2013-05-13 2014-06-18
go1.2 End-of-Life 2013-12-01 2014-12-10
go1.3 End-of-Life 2014-06-18 2015-08-19
go1.4 End-of-Life 2014-12-10 2016-02-17
go1.5 End-of-Life 2015-08-19 2016-08-15
go1.6 End-of-Life 2016-02-17 2017-02-16
go1.7 End-of-Life 2016-08-15 2017-08-24
go1.8 End-of-Life 2017-02-16 2018-02-16
go1.9 End-of-Life 2017-08-24 2018-08-24
go1.10 End-of-Life 2018-02-16 2019-02-25
go1.11 End-of-Life 2018-08-24 2019-09-03
go1.12 End-of-Life 2019-02-25 2020-02-25
go1.13 End-of-Life 2019-09-03 2020-08-11
go1.14 End-of-Life 2020-02-25 2021-02-16
go1.15 End-of-Life 2020-08-11 2021-08-16
go1.16 End-of-Life 2021-02-16 2022-03-15
go1.17 End-of-Life 2021-08-16 2022-08-02
go1.18 End-of-Life 2022-03-15 2023-02-01
go1.19 Maintenance 2022-08-02 Q3 2023
go1.20 Current 2023-02-01 Q1 2024
go1.21 Planned Q3 2023 Q3 2024

1.16 及以前的版本

我开始学习 Golang 的时候,已经是 1.5 和 1.6 版本了,更早的版本只需要对重大特性引入时间做一个简单了解。
现在主要的开发版本已经升级到 1.18 和 1.20。

  • 1.0
  • 第一个正式版本,核心特性都已经包含在里面,承诺以后语法不会发生不兼容变化
  • 至今为止,承诺都实现了。后面的版本几乎很少有激动人心的功能,绝大多数时候都是各种改进
  • 1.1
  • 1.2
  • 支持 slice[low:high:max] 语法
  • 1.4
  • for range 语法引入
  • 1.5
  • 实现自举
  • 1.9
  • type alias 语法引入
  • 1.11
  • Go Modules 引入
  • 1.12
  • 开始支持 TLS 1.3
    • 需要通过 GODEBUG=tls13=1 开启(不能通过 tls Config MaxVersion 限制版本)
    • 0-RTT 不被支持(到 2023 年,依然没有提供支持
  • 1.13
  • TLS 1.3 默认开启
  • 1.14
  • TLS 1.3 成为默认选项,并且无法通过 GODEBUG 关闭
  • 异步可抢占 goroutine
  • 1.16
  • Go Modules 成为默认(GO111MODULE = on
  • 支持将静态文件打包进可执行文件(//go:embed
  • 弃用 io/ioutil

参考:

1.17

  • Module graph pruning(依赖图修剪)
  • 切片转数组指针

参考:

1.18

  • 泛型(Golang 发布以来最大的语法变更)
  • fuzzing(模糊测试)
  • TLS 客户端默认版本改为 1.2

参考:

1.19

没有特别值得关注的的改动。

参考:

1.20

  • 切片转数组

参考:

1.21

  • min, maxclear
  • 循环变量捕获 (loop variable capture)
    参见:2023/07/05,Go 1.21 for 语义变更
  • 标准库:log/slog, slices, maps, cmp

参考: