龙空技术网

短链Ukey如何设计?一种简洁Ukey生成算法!

猿习社 168

前言:

今天兄弟们对“itd算法程序包”大致比较讲究,同学们都需要了解一些“itd算法程序包”的相关内容。那么小编同时在网络上搜集了一些有关“itd算法程序包””的相关知识,希望我们能喜欢,咱们快快来学习一下吧!

一、背景

日常工作中短链用的是比较多的,短链中最重要的一个组成部分是ukey,类似链接

最后的4oZMbgcWl

关于ukey我们有多个项目中用到,但生成方式不统一,且大都与特定业务场景绑定,对它的需求大概可以归纳为以下几方面:

能存放多段数据信息,例如:活动链接不仅要识别出活动,还要能识别出角色;算法可逆:不仅能从业务标识生成ukey,有些业务还需要从ukey中还原出业务标识;简短:长度控制在12位字符以内较好 ;幂等:相同业务标识多次调用算法生成的ukey不变;安全:需要给伪造和破解制造一定难度,有些场景还需要支持加随机字符;

鉴于以上需求,我们就在想,能不能设计一种ukey算法不仅能满足目前已知的Case,还能给后面的扩展留有一定的余地?

二、方案设计

抛开实际项目可能依赖的DB不谈,单从ukey来说,需要做的就是设计数据结构、设计生成ukey算法以及解析ukey算法,我们就以此来展开。

2.1 生成ukey

可以把ukey内部设计一个结构,就像我们为业务数据设计结构一样,如:

type struct ukey {	meta int            // ukey的元信息,告诉我们如何解析这个ukey	segment_1 int       // 数据信息-1,ukey需要简短,所以我们都用整数来标识业务	segment_2 int       // 数据信息-2	segment_3 int       // 数据信息-3}

这里需要说明一下,我们的业务标识基本都使用整型,整型比字符串检索效率高很多,本篇剩余部分的介绍也都是基于业务标识为整型这个前提。

要生成一个可还原的ukey串,简单讲就是对已有信息中的字符作编码,首先要制作编码表。我们一般都使用A-Z,a-z,0-9这62个字符来制作编码表,如下:

var encodeTable = map[int]string{0: "0", 1: "a", 2: "2", 3: "3", 4: "4", 5: "b", 6: "6", 7: "7", 8: "8", 9: "9", 10: "1", 11: "5", 12: "c", 13: "d", 14: "e", 15: "f", 16: "g", 17: "h", 18: "i", 19: "j", 20: "k", 21: "l", 22: "m", 23: "n", 24: "o", 25: "p", 26: "q", 27: "r", 28: "s", 29: "t", 30: "u", 31: "v", 32: "w", 33: "x", 34: "y", 35: "z", 36: "A", 37: "B", 38: "C", 39: "D", 40: "E", 41: "F", 42: "G", 43: "H", 44: "I", 45: "J", 46: "K", 47: "L", 48: "M", 49: "N", 50: "O", 51: "P", 52: "Q", 53: "R", 54: "S", 55: "T", 56: "U", 57: "V", 58: "W", 59: "X", 60: "Y", 61: "Z"}
每个10进制数字都可以由1位或多位62进制字符来表示,理论上11位62进制字符即可表示完整的64位长整数;可以把10进制转成62进制(用from10To62函数表示),也可以把62进制还原成10进制(用from62To10函数表示);上面的数字、字母顺序可以任意打乱,以避免被猜测;

有了这个编码表后,那么ukey的生成其实就是将多段信息的62进制字符串拼起来,用伪代码表示如下:

from10To62(segment_1)  + from10To62(segment_2) + from10To62(segment_3) + form10To62(meta)
2.2 meta的设计

应该要具备以下几个要素:

meta本身的位置和长度需要固定,它是解析其它部分的基础;能反映出有几段信息,以及每段的长度;如果考虑以后调整,还要能区分新老格式,也就是meta中要预留几位版本信息;尽量简短,给数据部分多留些空间;

所以meta其实也是有结构的,如果用二进制表示:

我们用疑问的方式来反推这个设计是如何出来的。

Q1:为何每段数据长度用4个二进制位来表示?

A1: 64位长整数换算成62进制大概需要11位字符,我们用0-11即能表示每段数据的长度,3位二进制能表示0-7,4位二进制能表示0-15,所以要存下0-11范围的数字我们需要的最小二进制位数为4。

Q2: 为何放3段数据,而不是放更多?

A2: 够用就好,目前我们已知的场景,在ukey中放1~2段数据就能满足,设计3段的目的是在现有的基础上再预留一些,当然你有需要可以放4段甚至更多,不过不建议太长,太长就与ukey的短串理念不符了。

Q3: 如果有多于3段的数据信息要存放怎么办?

A3: 有多种解决方式:

方式1:多段数据信息时,一般有的数据长,有的数据短,可以在使用前将多段短的数据信息作二进制移位合并成一段数据信息;方式2:如果每段数据长度用不满64位,例如只用到32位整数,则可以将每段长度压缩到3位,这样meta就能存储更多段数据的长度信息;方式3:启用保留位,高5位的保留位也可以用来存储数据段信息的,根据需要设计即可;

当我们已经有3段数据,如何构造出这个meta呢?

上面的二进制meta如果用结构体表示:

	type meta struct {		version int // ukey格式的版本号,初始无版本可以为0		len1    int // segment1的62进制字符长度		len2    int // segment2的62进制字符长度		len3    int // segment3的62进制字符长度	}
每一段数据所用的62进制字符长度,我们可以轻易获得:
	len1 := len(from10To62(segment_1))	len2 := len(from10To62(segment_1))	len3 := len(from10To62(segment_1))
最后只需要将每段长度和版本号按上面的二进制位设计移位,即可得到meta:
        metaInt := version<<12 | len1<<8 | len2<<4 | len3
2.3 解析ukey

要把一个ukey中的原始数据还原出来,大概也要分为几步:

先按约定的位置和长度将meta读出来,先前我们将meta放到了最后3位字符

        metaChars: = ukey[len(ukey)-3:]        metaInt := from62To10(metaChars, 62)
从meta中读出当前ukey有几段,以及每段的长度;
        version := (metaInt >> 12) & 0x1F        len1 := (metaInt >> 8) & 0x0F        len2 := (metaInt >> 4) & 0x0F        len3 := metaInt & 0x0F
依次按长度取每段的字符子串,并使用from62To10()算法还原成10进制;
        segment1Chars := ukey[:len1]        segment2Chars := ukey[len1:len1+len2]        segment3Chars := ukey[len1+len2:len1+len2+len3]
用from62To10还原成10进制,即得到原始三段数据
        segment1 := from62To10(segment1Chars, 62)        segment2 := from62To10(segment2Chars, 62)        segment3 := from62To10(segment3Chars, 62)

短链ukey的设计基本就完成了,如果需要加随机数,可以在meta与数据之间加几位随机数,不会影响整体解析,这部分就不展开了。

短链ukey的生成思路就介绍到这里,有疑问欢迎留言。

附代码简单实现:

标签: #itd算法程序包