go语言基础-----24-----命令行解析Go flag、uuid唯一ID

一 命令行解析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 = 692左移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是这一点是非常关键的。面试官可能会给你加分。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值