一 命令行解析Go flag
1. 定义flag参数的相关函数
参数有三个:第一个为 参数名称,第二个为 默认值,第三个是 使用说明
(1)通过 flag.String(),Bool(),Int() 等 flag.Xxx() 方法,该种方式返回一个相应的指针。
var ip = flag.Int("flagname", 1234, "help message for flagname")
(2)通过 flag.XxxVar() 方法将 flag 绑定到一个变量,该种方式返回 值类型。
var flagvar int
flag.IntVar(&flagvar, "flagname", 1234, "help message for flagname")
(3)通过 flag.Var() 绑定自定义类型,自定义类型需要实现 Value 接口 (Receiver 必须为指针)
fmt.Println("flagvar has value ", flagvar)
测试代码:
package main
import (
"flag"
"fmt"
//"os"
)
// 例如输入:go run xxx.go -ok -id 11111 -port 8899 -name TestUser very goo
func main() {
//fmt.Println(os.Args)
// 1. 从命令行中获取参数。
// 1.1 通过 flag.String(),Bool(),Int() 等 flag.Xxx() 方法,该种方式返回一个相应的指针。
ok := flag.Bool("ok", false, "is ok") // 若命令行不设置ok参数, 则默认为参2,这里默认为false。若设置了ok参数但是没有设置值,默认为true,即-ok传进,而不是-ok=true。
id := flag.Int("id", 0, "id")
port := flag.String("port", ":8080", "http listen port")
// 1.2 通过 flag.XxxVar() 方法将 flag 绑定到一个变量,该种方式返回 值类型。该值会赋值给传进去的地址。这里为&name。
var name string
flag.StringVar(&name, "name", "Jack", "name")
fmt.Println("ok:", *ok)
fmt.Println("id:", *id)
fmt.Println("port:", *port)
fmt.Println("name:", name)
// 2. 解析命令行。只有解析了命令行,才能得到用户传进来的值,否则全是默认值即取参2的值。可以查看上面的打印。
flag.Parse()
// flag.Usage()
// 3. 将其它参数也获取,方便观察。
others := flag.Args()
fmt.Println("===============================")
fmt.Println("ok:", *ok)
fmt.Println("id:", *id)
fmt.Println("port:", *port)
fmt.Println("name:", name)
fmt.Println("other:", others)
}
注意:
1)cmd -flag // 只支持bool类型
2)cmd -flag xxx // 只支持非bool类型
3)cmd -flag=xxx// 支持所有
其中cmd指go run xxx.go这些命令。
验证上面三点:
例子1:先验证第1点,使用 ok 这个 flag 验证,这个例子首先说明支持bool类型。然后运行第二条命令,id为int类型,id的赋值去掉后,说明其它类型不支持,所以验证cmd -flag 只支持bool类型。
go run xxx.go -ok -id 11111 -port 8899 -name TestUser very goo
go run xxx.go -ok -id -port 8899 -name TestUser very goo
图1结果看到,命令行填了ok这个flag,但是不赋值,默认值是true(注意与代码的默认值false区别,代码的默认值只有-ok这个flag不存在才会取这个值)。
图2结果,说明不支持非bool。大家可以自己试试其它非bool类型的。
例子2:
go run 5-1-cli-flag.go -ok false -id 11111 -port 8899 -name TestUser very goo
go run 5-1-cli-flag.go -ok -id 11111 -port 8899 -name TestUser very goo
运行第一条命令,图1结果看到,虽然我在命令行给ok的flag赋值为false,但是打印还是true,并且导致后面的flag无法解析,说明bool类型不支持cmd -flag xxx的格式。
运行第二条命令,结果看到,int、string这些类型都是支持cmd -flag xxx的格式。
例子3:
go run 5-1-cli-flag.go -ok=false -id=11111 -port=8899 -name=TestUser very goo
结果看到,说明bool、int、string类型是支持cmd -flag=xxx这种写法的。能成功获取到所有的值。
二 uuid唯一ID
1. 代码测试
uuid唯一ID在实际开发会经常用到,主要是方便在分布式的场景生成uuid,例如生成订单号,因为订单号需要唯一。所有这个知识点非常重要。
测试代码:
package main
import (
"fmt"
"log"
"math/rand"
"reflect"
"time"
"gitee.com/GuaikOrg/go-snowflake/snowflake"
"github.com/chilts/sid"
"github.com/kjk/betterguid"
"github.com/oklog/ulid"
"github.com/rs/xid"
uuid "github.com/satori/go.uuid"
"github.com/segmentio/ksuid"
"github.com/sony/sonyflake"
)
const FOR_LOOP = 1000000
// 1. 通过"github.com/rs/xid"的算法生成uuid。
func genXid() {
id := xid.New()
fmt.Printf("github.com/rs/xid: %s, len:%d\n", id.String(), len(id.String()))
}
// 2. 通过"github.com/segmentio/ksuid"的算法生成uuid。
func genKsuid() {
id := ksuid.New()
fmt.Printf("github.com/segmentio/ksuid: %s, len:%d\n", id.String(), len(id.String()))
}
// 3. 通过"github.com/kjk/betterguid"的算法生成uuid。
func genBetterGUID() {
id := betterguid.New()
fmt.Printf("github.com/kjk/betterguid: %s, len:%d\n", id, len(id))
}
// 4. 通过"github.com/oklog/ulid"的算法生成uuid。
func genUlid() {
t := time.Now().UTC()
entropy := rand.New(rand.NewSource(t.UnixNano()))
id := ulid.MustNew(ulid.Timestamp(t), entropy)
fmt.Printf("github.com/oklog/ulid: %s, len:%d\n", id.String(), len(id.String()))
}
// 5. 通过"gitee.com/GuaikOrg/go-snowflake/snowflake"(雪花算法)的算法生成uuid。
// 源码链接:https://gitee.com/GuaikOrg/go-snowflake
func genSnowflake() {
flake, err := snowflake.NewSnowflake(int64(0), int64(0))
if err != nil {
log.Fatalf("snowflake.NewSnowflake failed with %s\n", err)
}
id := flake.NextVal()
fmt.Printf("gitee.com/GuaikOrg/go-snowflake:%x, type:%s\n", id, reflect.TypeOf(id))
}
// 6. 通过"github.com/sony/sonyflake"(索尼算法)的算法生成uuid。
func genSonyflake() {
flake := sonyflake.NewSonyflake(sonyflake.Settings{})
id, err := flake.NextID()
if err != nil {
log.Fatalf("flake.NextID() failed with %s\n", err)
}
fmt.Printf("github.com/sony/sonyflake: %x, type:%s\n", id, reflect.TypeOf(id))
}
// 7. 通过"github.com/chilts/sid"的算法生成uuid。
func genSid() {
id := sid.Id()
fmt.Printf("github.com/chilts/sid: %s, len:%d\n", id, len(id))
}
// 8. 通过"github.com/satori/go.uuid"的算法生成uuid。
func genUUIDv4() {
id, err := uuid.NewV4()
if err != nil {
fmt.Printf("get uuid error [%s]", err)
}
fmt.Printf("github.com/satori/go.uuid: %s, len:%d\n", id, len(id))
}
func testGenXid(n int) {
t0 := time.Now()
for i := 0; i < n; i++ {
_ = xid.New()
}
elapsed := time.Since(t0)
fmt.Println("github.com/rs/xid n:", n, "time:", elapsed)
}
func testGenKsuid(n int) {
t0 := time.Now()
for i := 0; i < n; i++ {
_ = ksuid.New()
}
elapsed := time.Since(t0)
fmt.Println("github.com/segmentio/ksuid n:", n, "time:", elapsed)
}
func testGenBetterguid(n int) {
t0 := time.Now()
for i := 0; i < n; i++ {
_ = betterguid.New()
}
elapsed := time.Since(t0)
fmt.Println("github.com/kjk/betterguid n:", n, "time:", elapsed)
}
func testGenUlid(n int) {
t0 := time.Now()
for i := 0; i < n; i++ {
t := time.Now().UTC()
entropy := rand.New(rand.NewSource(t.UnixNano()))
_ = ulid.MustNew(ulid.Timestamp(t), entropy)
}
elapsed := time.Since(t0)
fmt.Println("github.com/oklog/ulid n:", n, "time:", elapsed)
}
func testGenSnowflake(n int) {
t0 := time.Now()
flake, err := snowflake.NewSnowflake(int64(0), int64(0))
if err != nil {
log.Fatalf("snowflake.NewSnowflake failed with %s\n", err)
}
for i := 0; i < n; i++ {
_ = flake.NextVal()
}
elapsed := time.Since(t0)
fmt.Println("gitee.com/GuaikOrg/go-snowflake n:", n, "time:", elapsed)
}
func testGenSonyflake(n int) {
t0 := time.Now()
flake := sonyflake.NewSonyflake(sonyflake.Settings{}) // 注意这一行的位置
for i := 0; i < n; i++ {
_, err := flake.NextID()
if err != nil {
log.Fatalf("flake.NextID() failed with %s\n", err)
}
}
elapsed := time.Since(t0)
fmt.Println("github.com/sony/sonyflake n:", n, "time:", elapsed)
}
func testGenSid(n int) {
t0 := time.Now()
for i := 0; i < n; i++ {
_ = sid.Id()
}
elapsed := time.Since(t0)
fmt.Println("github.com/chilts/sid n:", n, "time:", elapsed)
}
func testGenUUIDv4(n int) {
t0 := time.Now()
for i := 0; i < n; i++ {
_, err := uuid.NewV4()
if err != nil {
fmt.Printf("get uuid error [%s]", err)
}
}
elapsed := time.Since(t0)
fmt.Println("github.com/satori/go.uuid n:", n, "time:", elapsed)
}
func main() {
// 一:简单效果展示。生成uuid并打印出来。
fmt.Printf("效果展示...\n")
// 1.
genXid()
genXid()
genXid()
// 2.
genKsuid()
// 3.
genBetterGUID()
// 4.
genUlid()
// 5.
genSnowflake()
// 6.
genSonyflake()
// 7.
genSid()
// 8.
genUUIDv4()
// 二:对上面8个生成uuid的算法进行性能测试,测试方法:生成同样多的uuid,不同的算法花费了多长的时间。
fmt.Printf("性能测试...\n")
testGenXid(FOR_LOOP)
testGenKsuid(FOR_LOOP)
testGenBetterguid(FOR_LOOP)
testGenUlid(FOR_LOOP)
testGenSnowflake(FOR_LOOP)
testGenSonyflake(FOR_LOOP)
testGenSid(FOR_LOOP)
testGenUUIDv4(FOR_LOOP)
}
结果:
2. 重点讲解
下面重点讲解比较重要的两个算法,一个是雪花算法,另一个是索尼算法。
2.1 雪花算法
snowflake算法使用的一个64 bit的整型数据,被划分为四部分。
- 1) 不含开头的第一个bit,因为是符号位,该bit实际未使用,一般默认是0值。
- 2)41bit来表示收到请求时的时间戳,精确到1毫秒。
- 3)5bit表示数据中心的id, 5bit表示机器实例id,共计10bit的机器位,因此能部署在1024台机器节点上生成ID。
- 4)12bit循环自增序列号,增至最大后归0,1毫秒最大生成唯一ID的数量是4096个。
(下面的这几句话是先根据机器位分析,然后根据同一台机器可以生成多少id,并分析这台机器多少年不出现重复的情况来总结的。)
所以根据上面的意思,那么在分布式场景下,最多可以有1024台机器生成唯一的id。
并且在一台机器上面,每毫秒可以最多生成4096个序列号。
根据41bits的时间戳长度限制,如果程序连续运行那么最多可以运行69年不出现重复的id,如果超过69年,那么这台机器就可能出现重复的uuid。
69年是这样算的:
(( ((2 << 40)/1000) / 3600 ) /24 ) / 365 = 69年
2左移40位是因为本身2就是相当于左移了一位,所以2<<40=2^41次方。你可以试试2<<2,结果等价于2^3=8。
然后除以1000是因为毫秒转换成秒。 除以3600是转成小时。 除以24是转换成天。 除以365是转换成年。
2.2 索尼算法
索尼算法是改进后的雪花算法。同样分成四部分,意义一样,只不过位数和一些组成部分的顺序调整了一下。
- 1) 不含开头的第一个bit,因为是符号位,该bit实际未使用,一般默认是0值。
- 2)这里时间戳用39位,精确到10ms,所以可以达到174年,比snowflake的长很久。
- 3)8bit 做为序列号,每10毫秒最大生成256个,1秒最多生成25600个,比原生的Snowflake少好多,如果感觉不够用,目前的解决方案是跑多个实例生成同一业务的ID来弥补。
- 4)16bit 做为机器号,默认的是当前机器的私有IP的最后两位。
所以根据上面的意思,那么在分布式场景下,最多可以有2^16=65536台机器生成唯一的id。
并且在一台机器上面,每10毫秒可以最多生成256个序列号。
根据39bits的时间戳长度限制,如果程序连续运行那么最多可以运行174年不出现重复的id,如果超过174年,那么这台机器就可能出现重复的uuid。
174年的求法和上面雪花算法一样,但需要注意单位:
(( ((2 << 38)/100) / 3600 ) /24 ) / 365 = 174年
2.3 索尼算法对雪花算法的改进的最大不同点:
答:sonyflake对于snowflake的改进是用空间换时间,时间戳位数减少,以从69年升至174年。但是1秒最多生成的ID从409.6w降至2.56w条。
2.4 如果面试问到:为什么改进后的索尼算法这么耗时呢?
答:这是因为改进后的雪花算法的时间戳变成了39bit,单位为10ms,由于序列号为8bit,所以在同一台机器中,10ms只能生成256个uuid。如果看代码的处理,非常粗暴,就是直接sleep对应的时间来限制,所以改进后的索尼算法是非常耗时的。讲出sleep是这一点是非常关键的。面试官可能会给你加分。