当前位置:   article > 正文

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

sonyflake.newsonyflake

一 命令行解析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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

测试代码:

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

注意:

1)cmd -flag // 只支持bool类型
2)cmd -flag xxx // 只支持非bool类型
3)cmd -flag=xxx// 支持所有

其中cmd指go run xxx.go这些命令。
  • 1
  • 2
  • 3
  • 4
  • 5

验证上面三点:
例子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
  • 2

图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
  • 2

运行第一条命令,图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
  • 1

结果看到,说明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)
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200

结果:
在这里插入图片描述

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是转换成年。
  • 1
  • 2
  • 3

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
  • 1

2.3 索尼算法对雪花算法的改进的最大不同点:

答:sonyflake对于snowflake的改进是用空间换时间,时间戳位数减少,以从69年升至174年。但是1秒最多生成的ID从409.6w降至2.56w条。

2.4 如果面试问到:为什么改进后的索尼算法这么耗时呢?

答:这是因为改进后的雪花算法的时间戳变成了39bit,单位为10ms,由于序列号为8bit,所以在同一台机器中,10ms只能生成256个uuid。如果看代码的处理,非常粗暴,就是直接sleep对应的时间来限制,所以改进后的索尼算法是非常耗时的。讲出sleep是这一点是非常关键的。面试官可能会给你加分。
在这里插入图片描述

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号