#847 服务丧尸?为什么

2022-10-31

发现一个问题,暂时没有任何思路:

一个线上执行了 66 天的服务,突然在前天凌晨 1:30 没有日志输出了,从外网连接不上,内网可以连上。

等后续有了进展再更新。

#846 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)
        }
    }
}

#845 CSDN 免登陆复制

2022-10-27

CSDN 会拦截复制,提示需要登录。

近日在网上学到一招,在控制台输入一行代码就好了:

document.designMode = "on";

#844 ping github.com 得到 127.0.0.1 问题

2022-10-27
git clone git@github.com:tornado/tornado
Cloning into 'tornado'...
ssh: connect to host github.com port 22: Connection refused
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
ping github.com

正在 Ping github.com [::1] 具有 32 字节的数据:
来自 ::1 的回复: 时间<1ms
来自 ::1 的回复: 时间<1ms
来自 ::1 的回复: 时间<1ms

::1 的 Ping 统计信息:
    数据包: 已发送 = 3,已接收 = 3,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 0ms,最长 = 0ms,平均 = 0ms
Control-C
ping -4 github.com

正在 Ping github.com [127.0.0.1] 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=64

127.0.0.1 的 Ping 统计信息:
    数据包: 已发送 = 3,已接收 = 3,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 0ms,最长 = 0ms,平均 = 0ms
Control-C
nslookup github.com
Server:         192.168.31.1
Address:        192.168.31.1#53

Non-authoritative answer:
Name:   github.com
Address: 127.0.0.1
Name:   github.com
Address: ::1

配置 DNS 为 AliDNS 223.5.5.5233.6.6.6

nslookup github.com
Server:         223.5.5.5
Address:        223.5.5.5#53

Non-authoritative answer:
Name:   github.com
Address: 20.205.243.166

问题依旧。

ping 20.205.243.166

正在 Ping 20.205.243.166 具有 32 字节的数据:
请求超时。
请求超时。
请求超时。

20.205.243.166 的 Ping 统计信息:
    数据包: 已发送 = 3,已接收 = 0,丢失 = 3 (100% 丢失),
Control-C
ping baidu.com

正在 Ping baidu.com [39.156.66.10] 具有 32 字节的数据:
来自 39.156.66.10 的回复: 字节=32 时间=23ms TTL=52
来自 39.156.66.10 的回复: 字节=32 时间=24ms TTL=52
来自 39.156.66.10 的回复: 字节=32 时间=23ms TTL=52

39.156.66.10 的 Ping 统计信息:
    数据包: 已发送 = 3,已接收 = 3,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 23ms,最长 = 24ms,平均 = 23ms
Control-C

总结

  1. hosts 没有特殊配置
  2. 改成可用 DNS,问题没有修复
  3. ipconfig /flushdns 也不管用
  4. 重启也没有好

那就奇怪了,这个 ping 里面的本地回环地址是哪里来的呢?

最后,

ipconfig /displaydns 可以看到,还是用的回环地址
ipconfig /all 发现还有一个 DNSv6 配置。。。

DNSv6 设置成 AliDNS 的 IPv6 地址:2400:3200::12400:3200:baba::1 再试,

ping github.com

正在 Ping github.com [20.205.243.166] 具有 32 字节的数据:
请求超时。
请求超时。
请求超时。
请求超时。

20.205.243.166 的 Ping 统计信息:
    数据包: 已发送 = 4,已接收 = 0,丢失 = 4 (100% 丢失),

终于好了。😂

事情的起源是 git clone 失败,最后 git clone 可以了。
ping 不通,可能是网络问题,也可能是 github 网络配置。
这就不管了。

#843 日志平台的设计

2022-10-07

简单的日志系统

我还没有用过 ELK 这样的系统(只实验性使用过 Graylog),使用过这些日志管理方案:

  1. 日志就通过文件存放在服务器上,然后登录服务器进行日志文件分析,排查问题。
  2. 再进一步就是,将日志文件定期 rsync 到一台服务器上,方便日志管理与日志搜索。
  3. 服务直接写 syslog,然后配置好 rsyslog 同步就行。

    graph LR
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    
    A(应用程序):::process -->|写入日志| B(syslog 接口):::process
    B -->|发送日志消息| C(本地 rsyslog 守护进程):::process
    C -->|存储本地日志| D(本地日志文件):::process
    C -->|配置转发规则| E{是否转发到远程?}:::process
    E -->|是| F(通过 UDP/TCP 发送):::process
    F -->|接收日志消息| G(远程 rsyslog 守护进程):::process
    G -->|存储远程日志| H(远程日志文件):::process
    E -->|否| D
    
  4. 不要忘了,日志写入数据库在某些场景下也是一个可选方案,比如内部管理系统的登录日志、操作日志等。

理论上日志可以直接写入远程日志系统,但是我想应该不会有线上服务这样做。网络稳定性问题(可能丢失日志、服务阻塞)、性能消耗、增加系统复杂性等。
syslog 是系统提供的日志接口,rsyslog 提供了 TCP 日志可靠传输、本地日志队列功能,而且是 Linux 世界广泛采用的基础服务,我觉得可以接受。

日志系统

  • ELK / EFK:

    Elasticsearch   # 搜索引擎
    Logstash        # 日志采集、过滤、预处理
    Kabana          # 数据可视化
    
    Filebeat        # Logstash 替代方案,更加轻量级
    
    graph LR
        Logs --> Filebeat --> ES
        Logs --> Logstash --> ES
    
  • FELK:Filebeat 将日志采集到 Logstash,处理之后导入 ES

    graph LR
        Logs --> Filebeat --> Logstash --> ES
    
  • FELK + Kafka

    Filebeat 将日志采集到 Kafka,再由 Logstash 从 Kafka 读取日志,处理完成之后导入 ES

    graph LR
        Logs --> Filebeat --> Kafka --> Logstash --> ES
    
  • Graylog:

    Filebeat
    Graylog Sidebar
    Graylog
    Elasticsearch
    MongoDB
    
  • LPG:

    Loki        # 搜索引擎
    Promtail    # 日志采集、过滤、预处理
    Grafana     # 数据可视化
    

设计

graph TD
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px

    A(服务):::process -->|生成日志| B(rsyslog):::process
    B --> C(Kafka):::process
    C --> D(ELK):::process
  1. 服务写 syslog,通过 rsyslog 同步到

接收,解析,处理,转换,格式化

#842 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)
}

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

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

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

#838 Protobuf 枚举类型的实现

2022-09-12
enum Gender {
    UNKNOWN = 0;
    MALE = 1;
    FEMALE = 2;
}

转换成 Golang 之后是这个样子:

type Person_Gender int32

const (
    Person_UNKNOWN Person_Gender = 0
    Person_MALE    Person_Gender = 1
    Person_FEMALE  Person_Gender = 2
)

// Enum value maps for Person_Gender.
var (
    Person_Gender_name = map[int32]string{
        0: "UNKNOWN",
        1: "MALE",
        2: "FEMALE",
    }
    Person_Gender_value = map[string]int32{
        "UNKNOWN": 0,
        "MALE":    1,
        "FEMALE":  2,
    }
)

func (x Person_Gender) Enum() *Person_Gender {
    p := new(Person_Gender)
    *p = x
    return p
}

func (x Person_Gender) String() string {
    return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}

func (Person_Gender) Descriptor() protoreflect.EnumDescriptor {
    return file_example_proto_enumTypes[0].Descriptor()
}

func (Person_Gender) Type() protoreflect.EnumType {
    return &file_example_proto_enumTypes[0]
}

func (x Person_Gender) Number() protoreflect.EnumNumber {
    return protoreflect.EnumNumber(x)
}

// Deprecated: Use Person_Gender.Descriptor instead.
func (Person_Gender) EnumDescriptor() ([]byte, []int) {
    return file_example_proto_rawDescGZIP(), []int{0, 0}
}