Golang 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)
}
}
}
个人
2022-10-27
CSDN 会拦截复制,提示需要登录。
近日在网上学到一招,在控制台输入一行代码就好了:
document.designMode = "on";
计算机网络
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.5
,233.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
总结:
- hosts 没有特殊配置
- 改成可用 DNS,问题没有修复
ipconfig /flushdns
也不管用
- 重启也没有好
那就奇怪了,这个 ping 里面的本地回环地址是哪里来的呢?
最后,
ipconfig /displaydns
可以看到,还是用的回环地址
ipconfig /all
发现还有一个 DNSv6 配置。。。
DNSv6 设置成 AliDNS 的 IPv6 地址:2400:3200::1
,2400: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 网络配置。
这就不管了。
日志
2022-10-07
简单的日志系统
我还没有用过 ELK 这样的系统(只实验性使用过 Graylog),使用过这些日志管理方案:
- 日志就通过文件存放在服务器上,然后登录服务器进行日志文件分析,排查问题。
- 再进一步就是,将日志文件定期 rsync 到一台服务器上,方便日志管理与日志搜索。
-
服务直接写 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
-
不要忘了,日志写入数据库在某些场景下也是一个可选方案,比如内部管理系统的登录日志、操作日志等。
理论上日志可以直接写入远程日志系统,但是我想应该不会有线上服务这样做。网络稳定性问题(可能丢失日志、服务阻塞)、性能消耗、增加系统复杂性等。
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
- 服务写 syslog,通过 rsyslog 同步到
接收,解析,处理,转换,格式化
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)
}
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
Golang
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())
}
Golang
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
)一样的东西。
- Don't communicate by sharing memory, share memory by communicating. 别通过共享内存通信,要通过通信共享内存。
- Concurrency is not parallelism. 并发不是并行。
- Channels orchestrate; mutexes serialize.
- The bigger the interface, the weaker the abstraction. 接口越大, 抽象越弱。
- Make the zero value useful.
interface{}
says nothing.
- Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 别争论风格问题,不管喜不喜欢,用 gofmt 就是了。
- A little copying is better than a little dependency. 少量的复制好过引入新的依赖。
- Syscall must always be guarded with build tags.
- Cgo must always be guarded with build tags.
- Cgo is not Go. 别用 cgo。
- With the unsafe package there are no guarantees. 慎用 unsafe 包(安全无保障)
- Clear is better than clever. 清晰的代码比聪明的代码好。
- Reflection is never clear. 慎用反射(代码不清晰)。
- Errors are values.
- Don't just check errors, handle them gracefully.
- Design the architecture, name the components, document the details.
- Documentation is for users. 文档是给用户的(注意代码本身的可读性)。
- Don't panic. 不要滥用
panic
。
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}
}
Protobuf
2022-09-12
相较于文本编码的 JSON
- 速度快(序列化和反序列化)
- 数据小(二进制数据流更加紧凑)
- 数据类型丰富(enum,map...)
- 可读性差
数据类型
Protobuf 类型 |
描述 |
Go 对应类型 |
int32 |
有符号 32 位整数,采用可变长度编码,适合较小的整数 |
int32 |
int64 |
有符号 64 位整数,采用可变长度编码 |
int64 |
uint32 |
无符号 32 位整数,采用可变长度编码 |
uint32 |
uint64 |
无符号 64 位整数,采用可变长度编码 |
uint64 |
sint32 |
有符号 32 位整数,采用 ZigZag 编码,适合负数较多的情况 |
int32 |
sint64 |
有符号 64 位整数,采用 ZigZag 编码 |
int64 |
fixed32 |
无符号 32 位整数,固定 4 字节编码,对于大整数比 uint32 更高效 |
uint32 |
fixed64 |
无符号 64 位整数,固定 8 字节编码 |
uint64 |
sfixed32 |
有符号 32 位整数,固定 4 字节编码 |
int32 |
sfixed64 |
有符号 64 位整数,固定 8 字节编码 |
int64 |
float |
单精度 32 位浮点数 |
float32 |
double |
双精度 64 位浮点数 |
float64 |
bool |
布尔值,取值为 true 或 false |
bool |
string |
包含 UTF - 8 编码或 7 位 ASCII 文本的字符串,长度不能超过 2^32 |
string |
bytes |
任意的字节序列,长度不能超过 2^32 |
[]byte |
message |
自定义的数据结构,由多个字段组成,可以嵌套定义 |
struct |
enum |
用于定义一组命名的常量值 |
Go 枚举实现 |
repeated |
在字段前加上 repeated 关键字表示该字段是一个数组 |
Go 数组 |
map<KeyType, ValueType> |
从 proto3 开始支持的映射类型,用于表示键值对 |
map |
-
枚举类型,有另一篇文章可供参考:Protobuf 枚举类型的实现
-
数组(Repeated)示例:
message Person {
repeated string hobbies = 5;
}
-
映射(Map)示例:
message Person {
map<string, string> contacts = 6;
}
这里 contacts
是一个键为字符串、值也为字符串的映射。
基本流程
- 定义数据结构(.proto 文件)
- 使用编译器生成目标编程语言代码
-
在程序中引入生成的代码
-
Protobuf 编译器: protoc,作用是生成目标语言的数据定义代码
生成的代码
需要到 GitHub 下载。
-
Protobuf 运行时,作用是序列化和反序列化的基础库
Lang |
Repo |
C++ |
https://github.com/protocolbuffers/protobuf/blob/main/src |
Java |
https://github.com/protocolbuffers/protobuf/blob/main/java |
Python |
https://github.com/protocolbuffers/protobuf/blob/main/python |
Golang |
https://github.com/protocolbuffers/protobuf-go |
示例
-
proto 文件
syntax = "proto3";
package example;
message Person {
enum Gender {
UNKNOWN = 0;
MALE = 1;
FEMALE = 2;
}
string name = 1;
int32 age = 2;
Gender gender = 3;
}
-
编译:
$ protoc --go_out=. --go_opt=Mexample.proto=./generated example.proto
$ find .
.
./example.proto
./generated
./generated/example.pb.go
-
调用 proto 生成的 Go 代码
package main
import (
"fmt"
"log"
pb "example/generated" // 导入生成的包
"google.golang.org/protobuf/proto"
)
func main() {
// 创建一个 Person 实例
person := &pb.Person{
Name: "Alice",
Age: 30,
Gender: pb.Person_FEMALE,
}
// 序列化
data, err := proto.Marshal(person)
if err != nil {
log.Fatalf("Failed to marshal: %v", err)
}
// 反序列化
newPerson := &pb.Person{}
err = proto.Unmarshal(data, newPerson)
if err != nil {
log.Fatalf("Failed to unmarshal: %v", err)
}
fmt.Printf("Deserialized person: %+v\n", newPerson)
}
$ go mod init example
$ go mod tidy
$ go build -o main.exe .
$ ./main.exe
Deserialized person: name:"Alice" age:30 gender:FEMALE