#86 Go DNS 解析

2022-10-30

A 记录

package main

import (
    "fmt"
    "net"
)

func main() {
    // 方法 1
    // func ResolveIPAddr(network, address string) (*IPAddr, error)
    ipAddr, err := net.ResolveIPAddr("ip", "www.google.com")
    if err != nil {
        fmt.Println("解析IP地址失败:", err)

    } else {
        fmt.Println("IP地址是:", ipAddr.IP)
    }

    // 方法 2
    // func LookupMX(name string) ([]*MX, error)
    // func LookupTXT(name string) ([]string, error)
    // func LookupIP(host string) ([]IP, error)
    // func LookupHost(host string) ([]string, error) // only IPv4
    ips, err := net.LookupHost("www.google.com")
    if err != nil {
        fmt.Println("解析主机名失败:", err)
    } else {
        for _, ip := range ips {
            fmt.Println("IP地址是:", ip)
        }
    }
}

MX 记录

package main

import (
    "fmt"
    "net"
)

func main() {
    records, err := net.LookupMX("qq.com")
    if err != nil {
        fmt.Println("解析MX记录失败:", err)
    } else {
        fmt.Printf("%#v", records)
    }
}

指定 DNS 服务器

func queryMX(dns string, domain string) ([]*net.MX, error) {
    resolver := &net.Resolver{
        PreferGo: true,
        Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
            dialer := &net.Dialer{}
            return dialer.DialContext(ctx, "udp", dns)
        },
    }
    return resolver.LookupMX(context.Background(), domain)
}

完整版本

package main

import (
    "context"
    "fmt"
    "net"
    "strings"
    "time"
)

var dnsServers = []string{
    "223.5.5.5:53",
    "114.114.114.114:53",
    "8.8.8.8:53",
}

type IPType int

const (
    NotIP IPType = iota
    IPv4
    IPv6
)

const (
    DNSLookupTimeout = 500 * time.Millisecond // time.Duration
)

func GetIPType(host string) IPType {
    ip := net.ParseIP(host)
    if ip == nil {
        return NotIP
    }
    if ip.To4() != nil {
        return IPv4
    }
    if ip.To16() != nil {
        return IPv6
    }
    return NotIP
}

type Resolver struct {
    DNS      string
    Resolver *net.Resolver
}

func NewResolver(dns string) Resolver {
    resolver := Resolver{}
    resolver.DNS = dns
    resolver.Resolver = &net.Resolver{
        PreferGo: true,
        Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
            dialer := &net.Dialer{}
            return dialer.DialContext(ctx, "udp", dns)
        },
    }
    return resolver
}

func (r Resolver) QueryA(domain string) ([]string, error) {
    return r.Resolver.LookupHost(context.Background(), domain)
}

func (r Resolver) QueryMX(domain string) ([]*net.MX, error) {
    ctx, cancel := context.WithTimeout(context.Background(), DNSLookupTimeout)
    defer cancel()
    return r.Resolver.LookupMX(ctx, domain)
}

func (r Resolver) Query(domain string) ([]*net.MX, error) {
    addrs, err := r.QueryMX(domain)
    if err != nil {
        return nil, err
    }
    var result []*net.MX
    for _, addr := range addrs {
        // hostname := strings.TrimRight(addr.Host, ".")
        ipType := GetIPType(addr.Host)
        fmt.Printf("%s: %#v\n", addr.Host, ipType)
        if ipType == NotIP {
            ips, err := r.QueryA(addr.Host)
            if err != nil {
                continue
            }
            for _, ip := range ips {
                result = append(result, &net.MX{Host: ip, Pref: addr.Pref})
            }
        }
    }
    return result, nil
}

var resolvers map[string]Resolver

func Query(domain string) ([]*net.MX, error) {
    var addrs []*net.MX
    var err error
    for _, dns := range dnsServers {
        var r Resolver
        if r, ok := resolvers[dns]; !ok {
            r = NewResolver(dns)
            resolvers[dns] = r
        }
        addrs, err = r.Query(domain)
        if err == nil {
            break
        }
        fmt.Printf("Error: %s: %s: %#v\n", dns, domain, err)
    }
    return addrs, err
}

func QueryAsync(domain string, ch chan<- []*net.MX, errCh chan<- error) {
    addrs, err := Query(domain)
    if err != nil {
        errCh <- err
        return
    }
    ch <- addrs
}

func init() {
    resolvers = make(map[string]Resolver)
}

func main() {
    {
        domains := []string{"qq.com", "gmail.com", "google.com"}
        for _, domain := range domains {
            fmt.Println(strings.Repeat("=", 100))
            addrs, err := Query(domain)
            if err != nil {
                fmt.Printf("Error: %#v\n", err)
            } else {
                for _, addr := range addrs {
                    fmt.Printf("%#v\n", addr)
                }
            }
        }
    }

    {
        fmt.Println(strings.Repeat("=", 100))
        ch := make(chan []*net.MX)
        errCh := make(chan error)
        go QueryAsync("google.com", ch, errCh)
        select {
        case addrs := <-ch:
            fmt.Println("MX Records:")
            for _, addr := range addrs {
                fmt.Printf("%#v\n", addr)
            }
        case err := <-errCh:
            fmt.Println("Error:", err)
        }
    }
}

#85 gob:Golang 二进制编码格式(标准库)

2022-10-01
package main

import (
    "bytes"
    "encoding/gob"
    "fmt"
)

type Person struct {
    Name string
    Age  int
}

func main() {
    // 编码
    var buf bytes.Buffer
    enc := gob.NewEncoder(&buf)
    err := enc.Encode(Person{"John", 30})
    if err != nil {
        panic(err)
    }

    // 解码
    dec := gob.NewDecoder(&buf)
    var p Person
    err = dec.Decode(&p)
    if err != nil {
        panic(err)
    }

    fmt.Println(p)
}

#84 Golang 源码结构

2022-09-16
# tree -L 1 -d .
.
├── api
├── doc
├── lib
├── misc
├── src
└── test

6 directories
# tree -L 1 -d src/
src/
├── archive
├── bufio
├── builtin
├── bytes
├── cmd
├── compress
├── container
├── context
├── crypto
├── database
├── debug
├── embed
├── encoding
├── errors
├── expvar
├── flag
├── fmt
├── go
├── hash
├── html
├── image
├── index
├── internal
├── io
├── log
├── math
├── mime
├── net
├── os
├── path
├── plugin
├── reflect
├── regexp
├── runtime
├── sort
├── strconv
├── strings
├── sync
├── syscall
├── testdata
├── testing
├── text
├── time
├── unicode
├── unsafe
└── vendor

46 directories
tree -L 2 -d src/net/
src/net/
├── http
│   ├── cgi
│   ├── cookiejar
│   ├── fcgi
│   ├── httptest
│   ├── httptrace
│   ├── httputil
│   ├── internal
│   ├── pprof
│   └── testdata
├── internal
│   └── socktest
├── mail
├── rpc
│   └── jsonrpc
├── smtp
├── testdata
├── textproto
└── url

19 directories

#83 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())
}

#82 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

#81 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)
}
  • 支持默认值。

#80 好用的 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)
}

#79 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")))
}

#77 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. 如果想跳过一个值,可以使用下划线