6分钟
速读仅需3分钟
在真实的业务中生成唯一数是常见的功能,也是面试必考题。今天说说在面试过程中面试官在问这个问题时最想得到怎样的答案。
大部分编程语言都提供了唯一数生成函数,可惜大部分并不好用,原因是使用条件不符合使用场景。比如你需要生成唯一的数字并且是按顺序增长的,但系统函数只能生成字符串,最后只能另辟蹊径。所以面试官会问通常都有哪些生成唯一数的方法?
一. 散列+时间+随机值
md5(time() . mt_rand(1,1000000));
如上 `time()` 函数获取当前时间戳,`mt_rand(1,1000000)` 函数获取一个1到1000000的随机值,看着好像生成的数是唯一的,但这行代码问题非常多。
在操作系统中时间是很不靠谱的参数,因为CPU计算太快,没有对应的时间单位。如果CPU 1秒内处理了2个请求,那么`time()`字段毫无意义。
在编程语言中随机数也并不随机,常见的随机数都需要有随机种子,而为了保证种子不被猜到,编程语言默认会使用当前系统时间作为种子。又变成了依赖时间的一个参数,所以这种方案不可取。
二. 微秒+进程编号
uniqid();
`uniqid()`函数可以得到一个基于微秒和进程编号的唯一ID。对于php-fpm来说,每个请求都独占一个进程,一个进程会串行的处理多个请求。所以通过进程编号+微秒看上去能生成唯一ID。但深究之后发现并不靠谱。
1秒等于100万微秒,现在问题会变成一个进程能在百万分之一秒内处理多个请求吗?答案是可以的,用当前最普通的CPU来说,单核心1秒就可以计算20亿次,1微秒可以计算2千次。从操作系统调度的角度来说,2千次同时处理到一个进程的两个请求是完全可能的。所以又变成了依赖时间的一个参数,这种方案也不可取。
你还能使用纳秒,皮秒等精度更高的时间参数,但以发展的远光看问题,未来CPU的算力会越来越快,依赖时间的唯一性会越来越差。无论怎么努力,只依靠编程语言自身得到唯一ID是非常困难的,也是我不推荐的。
三. 数据库
靠编程语言自己走不通,那就通过第三方工具,这种方案是比较靠谱的。实现起来也有很多种方法。
LOCK TABLES id_maker;
//拿到id
select id from id_maker;
//加1更新
update id_maker set id=id+1;
UNLOCK id_maker;
这是我曾经见过的一种id_maker用法,每次调用得到的id都是唯一并且有序的。但问题是需要锁表,性能不高,在高并发下很容易发生资源抢占导致数据库崩溃。
//id_maker表有自增id和内容data两个字段
insert into id_maker(data) values('');
select last_insert_id();
这种方式性能就高很多,insert语句不会锁表,自增id百分百保证唯一性且有序。唯一的问题是需要定期删除历史数据,对于大部分项目我都建议使用这种方式生成唯一ID。
除了MySQL还有MongoDB,Redis等其他数据库方案,方法大同小异。但是这个方案有局限性,当我们的业务发展到成千上万台服务器时通过一个数据库的一张表去生成ID会导致性能下降拖垮其他服务,还会形成单点依赖。
四. 微秒+服务器编号+计数器
Twitter开源了他的唯一ID生成方案,名字叫SnowFlake,这种方案的原理非常简单,很容易基于SnowFlake算法做扩展。比如PHP并不会常驻内存,计数器的实现比较麻烦,但只要原理清楚了使用扩展或者数据库很容易实现。
这个方案的最大优点就是在庞大的集群中,每个服务靠自己就能算出全局的唯一ID。微秒能保证ID有序,服务器编号能确定到具体机器,计数器(可以理解为只为当前服务器提供的`id_maker`)能保证当前机器所有请求的唯一性,通过这些参数生成的唯一编号有序并且无懈可击。
如上四点写的其实是一种思路,处理问题都是由简单到复杂,由一到二。如果直接给面试官说终极方案,面试官会基于终极方案问更多复杂的问题,更多离业务非常贴近的问题,如果没接触过相关业务直接就pass了。所以由简单到复杂,讲清楚技术原理和思考过程,offer离你就不远了。