├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── components ├── .DS_Store ├── constant.go ├── kv.go ├── magazine.go └── mist.go ├── go.mod ├── go.sum ├── handler └── sequence.go └── server.go /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asyncins/medis/1d040868dcbf17619c13982ccf70a46ba02a4d44/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 AsyncIns 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Medis 高性能的全局唯一 ID 发号服务 2 | 3 | Medis 是薄雾算法 Mist 的工程实践,其名取自 Mist 和 Redis。[薄雾算法](https://github.com/asyncins/mist/blob/master/README.md)是一款性能强到令我惊喜的全局唯一 ID 算法,我将它与业内同样高性能的 Redis 和 Golang 结合到一起,碰撞出了 TPS 为 2.5w/sec 这样超高性能的工程。 4 | 5 | 有了 Mist 和 Medis,你就拥有了和[美团 Leaf](https://tech.meituan.com/2017/04/21/mt-leaf.html)、[微信 Seqsvr](https://www.infoq.cn/article/wechat-serial-number-generator-architecture)、[百度 UIDGenerator](https://github.com/baidu/uid-generator) 性能相当(甚至超过)的全局唯一 ID 服务了。相比复杂的 UIDGenerator 双 Buffer 优化和 Leaf-Snowflake,薄雾算法 Mist 简单太多了。 6 | 7 | ### 分布式环境下的 CAP 选择? 8 | 9 | 你可以基于 Mist 算法打造一个 CP 或者 AP 的分布式服务架构,因为 Mist 足够简单,你只需要专心设计 CAP 的取舍即可。 10 | 11 | 无数的架构师说过,**分布式环境下的服务,简单即是强**。 12 | 13 | 14 | ### Medis 的性能测试 15 | 16 | 选用 Jmeter 作为性能测试工具,测试参数:`1 秒内启动100 并发无限循环`,即 17 | 18 | ``` 19 | Number of Threads 100 20 | Ramp-up period 1 21 | Loop Count Infinte 22 | ``` 23 | 24 | 在 Jmeter 测试的同时,我还用 Gorouting * 10 开启了对数据正确性的测试,数据正确无误; 25 | 26 | 小数值(十万级)测试会导致频繁的预读预写 Redis 和 Channel,性能约为 2w/sec TPS 27 | 28 | 大数值(百万级)测试贴近真是业务环境,以下是 Channel 容量为 500W 时的测试结果 29 | 30 | | Samples | Average | Median | 90% Line | 95% Line | 99% Line | Throughput | 31 | | ---- | ---- | ---- | ---- | ---- | ---- | ---- | 32 | | 30000000 | 3 | 2 | 4 | 6 | 12 | 27070/sec | 33 | | 50000000 | 4 | 3 | 7 | 10 | 21 | 22369/sec | 34 | 35 | 36 | ### Medis 的高性能从何而来? 37 | 38 | 作为开发者,你一定想知道 Medis 这 2.5w/sec 的 TPS 到底从何而来。实际上这不仅是薄雾算法本身超高性能带来的结果,我在设计上也做了很多尝试,例如: 39 | 40 | - 使用 Channel 作为**数据缓存**,这个操作使得发号服务性能提升了 7 倍; 41 | - 采用**预存预取**的策略保证 Channel 在大多数情况下都有值,从而能够迅速响应客户端发来的请求; 42 | - 用 **Gorouting** 去执行耗费时间的预存预取操作,不会影响对客户端请求的响应; 43 | - 采用 **Lrange Ltrim 组合**从 Redis 中批量取值,这比循环单次读取或者管道批量读取的效率更高; 44 | - 写入 Redis 时采用**管道**批量写入,效率比循环单次写入更高; 45 | - Seqence 值的计算**在预存前进行**,这样就不会耽误对客户端请求的响应,虽然薄雾算法的性能是纳秒级别,但并发高的时候也造成一些性能损耗,放在预存时计算显然更香; 46 | - 得益于 Golang Echo 框架和 Golang 本身的高性能,整套流程下来我很满意,如果要追求极致性能,我推荐大家试试 Rust; 47 | 48 | 49 | ### 预存预取是什么流程? 50 | 51 | 预存预取是 Medis 高性能的基础之一,待我补充流程图。 52 | 53 | ### 致谢 54 | 55 | 谢谢 *@青南* 在 Redis 读取环节提供的 Lrange Ltrim 建议,我替换掉了循环 RPOP 操作,这使得读性能飙升; 56 | 57 | 谢谢 *@Manjusaka* 和 *@夏溪辰* 在 Gorouting 触发环节提供的全局变量和定时器建议,这里选用了全局变量锁定 Gorouting,效果相当好; 58 | 59 | 谢谢 *@夜幕团队 @崔庆才 @大鱼 @Loco* 在性能测试和 Golang 方面的建议; 60 | -------------------------------------------------------------------------------- /components/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asyncins/medis/1d040868dcbf17619c13982ccf70a46ba02a4d44/components/.DS_Store -------------------------------------------------------------------------------- /components/constant.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | const Unit = 1e5 // 公共数字单位 4 | const ListKey = "medis" // 存储已生成数据的键名 5 | const MaxKey = "mdx" // 存储当前最大值的键名 6 | const Capacity = int(50 * Unit) // 信道容量 7 | const Persent = float64(0.7) // 信道容量阈值比 8 | const Multiple = 5 // 用于计算补充量的倍数 9 | const RandMax = 250 // 随机值右闭值 10 | 11 | var Freedom = 0 // 作为 Gorouting 锁 12 | -------------------------------------------------------------------------------- /components/kv.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "sort" 7 | "strconv" 8 | "sync" 9 | 10 | "github.com/gomodule/redigo/redis" 11 | ) 12 | 13 | var mutex sync.RWMutex 14 | var lock sync.Mutex 15 | 16 | func ConnectKv() (redis.Conn, error) { 17 | conn, err := redis.Dial("tcp", "localhost:6379") 18 | // redis.DialUsername("username"), 19 | // redis.DialPassword("password"), 20 | return conn, err 21 | } 22 | 23 | // 设置最大值 24 | func SetMax(key string, value int) error { 25 | conn, err := ConnectKv() 26 | defer conn.Close() 27 | if err != nil { 28 | return err 29 | } 30 | _, ers := conn.Do("SET", key, value) 31 | return ers 32 | } 33 | 34 | // 获取最大值 35 | func GetMax(key string) (int, error) { 36 | conn, err := ConnectKv() 37 | defer conn.Close() 38 | if err != nil { 39 | return 0, err 40 | } 41 | r, err := conn.Do("GET", key) 42 | if r == nil || r == 0 { 43 | return 0, err 44 | } 45 | value, err := strconv.Atoi(string(r.(interface{}).([]uint8))) 46 | if err != nil { 47 | return 0, errors.New("") 48 | } 49 | return value, err 50 | } 51 | 52 | // 查看余量 53 | func Surplus(key string) (int, error) { 54 | conn, err := ConnectKv() 55 | defer conn.Close() 56 | if err != nil { 57 | return 0, err 58 | } 59 | r, err := conn.Do("LLEN", key) 60 | if err != nil { 61 | return 0, err 62 | } 63 | return int(r.(int64)), nil 64 | } 65 | 66 | // 分批写入 写入数不低于公共数字单位,且要求为公共数字单位的整数倍 67 | func PushPipeline(supplement int) error { 68 | lock.Lock() 69 | conn, err := ConnectKv() 70 | defer conn.Close() 71 | current, _ := GetMax(MaxKey) 72 | if err != nil { 73 | return err 74 | } 75 | batch := int(math.Floor(float64(supplement) / Unit)) 76 | for i := 0; i < batch; i++ { 77 | for x := (current + 1); x < (current + int(Unit) + 1); x++ { 78 | sequence := Generate(int64(x)) // 预生成 预存 79 | conn.Send("LPUSH", ListKey, int(sequence)) 80 | } 81 | conn.Flush() 82 | current = current + int(Unit) 83 | } 84 | err = SetMax(MaxKey, current) // 将最大值写入 kv 85 | lock.Unlock() 86 | return err 87 | } 88 | 89 | // 分批读取 读取数不低于公共数字单位 性能约为单次读取的十倍 90 | func RpopPipeline(channel chan int, need int) error { 91 | mutex.Lock() 92 | conn, err := ConnectKv() 93 | defer conn.Close() 94 | if err != nil { 95 | mutex.Unlock() 96 | return err 97 | } 98 | // 获取远端存储当前余量 用于取值定位 99 | llen, err := conn.Do("LLEN", ListKey) 100 | if err != nil { 101 | mutex.Unlock() 102 | return err 103 | } 104 | spls := int(llen.(int64)) 105 | 106 | // 事务确保批量取值和批量减值正常进行 此操作相当于批量弹出 107 | conn.Send("MULTI") 108 | conn.Send("LRANGE", ListKey, spls-need, spls) 109 | conn.Send("LTRIM", ListKey, 0, spls-need-1) 110 | values, err := redis.Values(conn.Do("EXEC")) 111 | if err != nil { 112 | mutex.Unlock() 113 | return err 114 | } 115 | 116 | var sli []int // 便于排序 117 | 118 | // 返回多值 通过控制流和断言进行值的转换 119 | for _, value := range values { 120 | switch value.(type) { 121 | case string: 122 | continue 123 | case interface{}: 124 | for _, val := range value.([]interface{}) { 125 | if val != nil { 126 | 127 | mst, _ := strconv.Atoi((string(val.([]uint8)))) 128 | sli = append(sli, mst) 129 | } 130 | } 131 | default: 132 | continue 133 | } 134 | } 135 | // 升序排序 136 | sort.Slice(sli, func(i, j int) bool { 137 | return sli[i] < sli[j] 138 | }) 139 | // 按序推入信道 140 | for _, mst := range sli { 141 | channel <- mst 142 | } 143 | mutex.Unlock() 144 | return err 145 | } 146 | 147 | func KvToChannel(channel chan int, need, thresshold int) error { 148 | err := KvSupplement(thresshold) 149 | if err != nil { 150 | return err 151 | } 152 | RpopPipeline(channel, need) 153 | return nil 154 | } 155 | 156 | // 补充 157 | func KvSupplement(thresshold int) error { 158 | mutex.Lock() 159 | surplus, err := Surplus(ListKey) 160 | if err != nil { 161 | mutex.Unlock() 162 | return err 163 | } 164 | if surplus < thresshold { 165 | PushPipeline(instance.KvSupplement) 166 | } 167 | mutex.Unlock() 168 | return nil 169 | } 170 | -------------------------------------------------------------------------------- /components/magazine.go: -------------------------------------------------------------------------------- 1 | package components 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | var instance *Magazine 8 | var once sync.Once 9 | 10 | type Magazine struct { 11 | ListKey string // 存储已生成数据的键名 12 | MaxKey string // 存储当前最大值的键名 13 | Channel chan int // 信道 14 | Capacity int // 信道容量 15 | Threshold int // 信道容量阈值 低于此值时触发补充操作 16 | KvThreshold int // 存储容量阈值 低于此值时触发补充操作 17 | KvSupplement int // 触发存储补充操作时具体的补充量 18 | } 19 | 20 | func MagazineInstance(empty bool) *Magazine { 21 | once.Do(func() { 22 | capacity := Capacity 23 | channel := make(chan int, capacity) 24 | threshold := int(float64(capacity) * Persent) 25 | // 初始化薄雾结构体时生成第一批值,同时将最大值存入 kv 26 | if empty == false { 27 | for i := 1; i < capacity+1; i++ { 28 | // 预生成 预存 29 | sequence := Generate(int64(i)) 30 | channel <- int(sequence) 31 | } 32 | } 33 | // 存储容量的阈值为信道阈值的指定倍数 存储补充量为信道容量指定倍数 倍数由配置决定 34 | instance = &Magazine{ 35 | ListKey: ListKey, MaxKey: MaxKey, Capacity: capacity, 36 | Threshold: threshold, Channel: channel, KvThreshold: threshold * Multiple, 37 | KvSupplement: capacity * Multiple, 38 | } 39 | }) 40 | return instance 41 | } 42 | 43 | func GetMagazine() *Magazine { 44 | return instance 45 | } 46 | -------------------------------------------------------------------------------- /components/mist.go: -------------------------------------------------------------------------------- 1 | /* 2 | * 薄雾算法 3 | * 4 | * 1 2 48 56 64 5 | * +------+-----------------------------------------------------+----------+----------+ 6 | * retain | increas | salt | sequence | 7 | * +------+-----------------------------------------------------+----------+----------+ 8 | * 0 | 0000000000 0000000000 0000000000 0000000000 0000000 | 00000000 | 00000000 | 9 | * +------+-----------------------------------------------------+------------+--------+ 10 | * 11 | * 0. 最高位,占 1 位,保持为 0,使得值永远为正数; 12 | * 1. 高位数,占 47 位,高位数(必须是自增数)在高位能保证结果值呈递增态势,遂低位可以为所欲为; 13 | * 2. 随机因子一,占 8 位,上限数值 255,使结果值不可预测; 14 | * 3. 随机因子二,占 8 位,上限数值 255,使结果值不可预测; 15 | * 16 | * 编号上限为百万亿级,上限值计算为 140737488355327 即 int64(1 << 47 - 1),假设每天取值 10 亿,能使用 385+ 年 17 | */ 18 | 19 | package components 20 | 21 | import ( 22 | "crypto/rand" 23 | "math/big" 24 | ) 25 | 26 | const saltBit = uint(8) // 随机因子二进制位数 27 | const saltShift = uint(8) // 随机因子移位数 28 | const increasShift = saltBit + saltShift // 高位数移位数 29 | 30 | /* 生成唯一编号 */ 31 | func Generate(increas int64) int64 { 32 | randA, _ := rand.Int(rand.Reader, big.NewInt(255)) 33 | saltA := randA.Int64() 34 | randB, _ := rand.Int(rand.Reader, big.NewInt(255)) 35 | saltB := randB.Int64() 36 | mist := int64((increas << increasShift) | (saltA << saltShift) | saltB) // 通过位运算实现自动占位 37 | return mist 38 | } 39 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module medis 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gomodule/redigo v1.8.1 7 | github.com/labstack/echo v3.3.10+incompatible 8 | github.com/labstack/echo/v4 v4.1.16 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 4 | github.com/gomodule/redigo v1.8.1 h1:Abmo0bI7Xf0IhdIPc7HZQzZcShdnmxeoVuDDtIQp8N8= 5 | github.com/gomodule/redigo v1.8.1/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= 6 | github.com/gomodule/redigo/redis v0.0.0-do-not-use h1:J7XIp6Kau0WoyT4JtXHT3Ei0gA1KkSc6bc87j9v9WIo= 7 | github.com/labstack/echo v1.4.4 h1:1bEiBNeGSUKxcPDGfZ/7IgdhJJZx8wV/pICJh4W2NJI= 8 | github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= 9 | github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= 10 | github.com/labstack/echo/v4 v4.1.16 h1:8swiwjE5Jkai3RPfZoahp8kjVCRNq+y7Q0hPji2Kz0o= 11 | github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI= 12 | github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= 13 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 14 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 15 | github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= 16 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 17 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 18 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 19 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 20 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 21 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 22 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 23 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 24 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 25 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 26 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 27 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 28 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 29 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 30 | github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= 31 | github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 32 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 33 | golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw= 34 | golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 35 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 36 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= 37 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 38 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 39 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 40 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 41 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= 44 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 46 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 47 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 48 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 49 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 50 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 51 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 52 | -------------------------------------------------------------------------------- /handler/sequence.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "medis/components" 5 | "strconv" 6 | "sync" 7 | 8 | "github.com/labstack/echo/v4" 9 | ) 10 | 11 | var mutex sync.RWMutex 12 | var lock sync.Mutex 13 | 14 | func Seqence(ctx echo.Context) error { 15 | mutex.Lock() 16 | var magazine = components.GetMagazine() 17 | // 取值时从信道取值,每次判断信道余量,余量小于等于阈值时从 kv 拉取一批值 18 | if len(magazine.Channel) <= magazine.Threshold && components.Freedom == 0 { 19 | need := magazine.Capacity - len(magazine.Channel) 20 | // 用全局变量锁定 Gorouting 用完再释放 21 | components.Freedom = 1 22 | go func() { 23 | components.KvToChannel(magazine.Channel, need, magazine.KvThreshold) 24 | components.Freedom = 0 25 | }() 26 | } 27 | number := <-magazine.Channel 28 | seq := strconv.Itoa(number) 29 | mutex.Unlock() 30 | return ctx.String(200, seq) 31 | } 32 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "medis/components" 5 | "medis/handler" 6 | 7 | "github.com/labstack/echo/v4" 8 | ) 9 | 10 | func main() { 11 | server := echo.New() 12 | server.GET("/seqence", handler.Seqence) 13 | current, err := components.GetMax(components.MaxKey) 14 | if err != nil { 15 | server.Logger.Fatal(server.Start(":1558")) 16 | } 17 | if current == 0 { 18 | components.MagazineInstance(false) 19 | components.SetMax(components.MaxKey, components.Capacity) 20 | } else { 21 | components.MagazineInstance(true) 22 | } 23 | server.Logger.Fatal(server.Start(":1558")) 24 | } 25 | --------------------------------------------------------------------------------