A*程序员枫哥✆ 呆喜瓜儿
关注数: 67 粉丝数: 166 发帖数: 701 关注贴吧数: 13
别再依赖雪花算法生成 ID 了!试试这些新方法吧! 大家好,我是枫哥!今天和大家探讨一下服务器环境中用户 ID 的生成问题。很多小伙伴可能都熟悉雪花算法(Snowflake),这是个经典的分布式 ID 生成工具。然而,当我们需要生成短数字 ID时,雪花算法就有些捉襟见肘了。接下来,给大家介绍几种生成短 ID 的新方法! 背景 最近的项目中,需要为用户生成一个账号 ID,最开始使用 UUID(全球唯一标识符),但由于其字符串过长,既影响存储和传输性能,也不利于用户记忆。因此,生成一个相对短的数字 ID 成为了首要任务。 雪花算法的局限性 雪花算法基于 Twitter 开发的开源工具,生成 64 位长的数字 ID,既支持分布式,又高效、稳定。然而,生成的 ID 过长,不便于用户记忆和输入,显然无法满足我们的需求。为此,我们开始尝试一些其他方案。 方案尝试与优化1. 自增 ID 方案 首先,我们想到利用数据库的自增 ID 功能。在登录系统中,用户的登录请求会上传第三方的标识信息(如 openid 和 token),服务器会查询数据库,如果没有注册则生成一个新的数字 ID。这样,所有账号 ID 都存储在一张 ID 表中,并设置自增特性,确保每个新增 ID 都是唯一且按顺序递增的。 然而,单独使用自增 ID 也存在一定问题。特别是当数据量巨大时,数据库的性能可能会下降,另外还可能会因为并发插入操作出现死锁。经过测试后,发现 MySQL 的replace into语句在并发操作时可能会触发死锁。这意味着自增 ID 方案并非万无一失。 2. 分布式数据库方案 为了解决死锁问题,我们参考了美团的方案——通过分布式数据库生成 ID。具体而言,我们可以部署多个 MySQL 服务器,每个服务器负责一部分 ID 的生成任务。这种方式能显著提高生成效率并减少并发压力,但其部署成本较高,也不太适合小规模的应用场景。多台服务器的协调也为运维带来了额外负担,因此最终放弃了此方案。 3. 分片和步长调整 另一种方式是利用 MySQL 自增步长的特性,通过设置不同的步长使不同的分片生成不重叠的 ID 范围。例如,10 张分片表中,每张表的起始 ID 相隔 1,000,000。这种方式在小规模应用中表现良好,但当需求频繁变动时,可能会产生较大的维护难度。因此我们决定再尝试更精细的控制方法。 4. 预申请 ID 方案(最终方案) 我们最终决定采用一种「预申请 ID」的方式。每次生成一批 ID(如 1,000 个),并存储在缓存中,当缓存中 ID 即将耗尽时,再向数据库申请下一批新的 ID。这样的方式既减少了数据库的压力,也避免了 ID 的浪费。此外,当系统扩容或发生意外宕机时,这种方式也能灵活调整,保障 ID 的连续性。 方案实施细节 每次 ID 生成时,服务端会从预申请的批次中取出一个 ID,当剩余 ID 数量较少时,系统会自动请求一批新的 ID 段,确保服务的连续性。这种方法有效避免了死锁,同时降低了数据库的访问频率。此外,通过优化逻辑,我们还可以避免 ID 的过度浪费,使整个方案更经济、高效。 总结 以上就是我们在唯一 ID 生成方案上的探索之路。从雪花算法到预申请 ID,我们尝试了多个方案,不断优化。希望大家在今后的开发中也能找到最适合自己的唯一 ID 生成方案!如果你有更多有趣的方案,欢迎留言分享,我们共同探讨进步! 感谢你的阅读,别忘了点赞、关注哦!
上海-拼多多电商部二面-持续更新中 原创:IT枫斗者,欢迎留言与评论 1 、并发编程三要素? (1)原子性原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。 (2)可见性可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。 (3)有序性有序性,即程序的执行顺序按照代码的先后顺序来执行。 2 、实现可见性的方法有哪些? (1)发挥多核 CPU 的优势多线程,可以真正发挥出多核 CPU 的优势来,达到充分利用 CPU 的目的,采用多线程的方式去同时完成几件事情而不互相干扰。 (2)防止阻塞从程序运行效率的角度来看,单核 CPU 不但不会发挥出多线程的优势,反而会因为在单核CPU 上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核 CPU 我们还是要应用多线程,就是为了防止阻塞。试想,如果单核 CPU 使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。 (3)便于建模这是另外一个没有这么明显的优点了。假设有一个大的任务 A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务 A 分解成几个小任务,任务 B、任务C、任务 D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。 4 、创建线程的有哪些方式? (1)继承 Thread 类创建线程类 (2)通过 Runnable 接口创建线程类 (3)通过 Callable 和 Future 创建线程 (4)通过线程池创建 5、创建线程的三种方式的对比 (1)采用实现 Runnable、Callable 接口的方式创建多线程。优势是:线程类只是实现了Runnable 接口或 Callable 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。劣势是:编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread()方法。 (2)使用继承 Thread 类的方式创建多线程优势是:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread()方法,直接使用 this即可获得当前线程。劣势是:线程类已经继承了Thread 类,所以不能再继承其他父类。 (3)Runnable 和 Callable 的区别 a、Callable 规定(重写)的方法是 call(),Runnable 规定(重写)的方法是 run()。 b、Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的。 c、Call 方法可以抛出异常,run 方法不可以。 d、运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future 对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
来自oppo社招Java一面 原创:IT枫斗者,欢迎留言与评论 1、项目相关 2、线程池参数及其作用。 corePollSize:核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。 maximumPoolSize:最大线程数。表明线程中最多能够创建的线程数量。 keepAliveTime:空闲的线程保留的时间。 TimeUnit:空闲线程的保留时间单位。 BlockingQueue:阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue可选。 ThreadFactory:线程工厂,用来创建线程 RejectedExecutionHandler:队列已满,而且任务量大于最大线程的异常处理策略。 ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。 3、Redis的持久化方式 两种持久化方式 RDB持久化:将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化 AOF(append only file)持久化:将Reids的操作日志以追加的方式写入文件 二者的区别 RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。4、过期键的删除策略。 定时删除:在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多个过期键,以及要检查多少个数据库,则由算法决定。 5、rocketmq用在什么场景。 异步处理,应用解耦,流量削锋和消息通讯四个场景 异步处理 场景说明:用户注册后需要发送注册邮件和短信。 串行方式 先保存用户注册信息, 然后发送注册邮件, 再发送注册短信。 并行方式 保存用户信息后, 同时发送注册邮件和短信。 应用解耦 场景说明: 用户下单后系统减库存,传统模式是订单系统调用库存接口,订单系 统与库存系统耦合,引入消息队列方案后,用户下单后,保存订单,将消息 写入消息队列,返回结果,库存系统订阅下单消息进行操作。 流量削峰 场景说明:秒杀活动中, 一般会因为流量过大导致应用挂掉, 加入消息队列可 以缓解压力首先将用户请求写入消息队列,队列长度超过最大数据就丢弃 掉请求,系统根据队列的请求消息再做后续处理。 6、四次挥手。 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据) 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
欢迎来到GitHub的世界 我将为你讲解GitHub是什么,以及为什么全世界的开发者都在使用它。同时,还会带您一起考察GitHub为开源软件世界带来了怎样的变革。 1、什么是GitHub GitHub是为开发者提供Git仓库的托管服务。这是一个让开发者与朋友、同事、同学、同事及陌生人共享代码的完美场所。 GitHub公司于octocat GitHub公司总部位于美国旧金山,拥有一只不知是章鱼还是猫的吉祥物octocat(图1)。图2中是被改编成各种造型的octocat们。图1图2 并不只是Git仓库的托管服务 GitHub除提供Git仓库的托管服务外,还为开发者或团队提供了一系列功能,帮助其高效率、高品质地进行代码编写。 GitHub的创始人之一Chris Wanstrath层有个愿望,那就是能有一个Git仓库的托管服务让自己与朋友轻松分享代码,而这便成为了GitHub诞生的契机。不过,他也曾表示:Git仓库的托管服务是GitHub项目的目标之一,这只是漫长路程上的一个点而已。 GitHub的使用情况 截至2013年12月,GitHub托管的仓库数已经超过1000万。全世界每时每刻都有开发者在使用它。 2、使用GitHub会带来哪些变化 GitHub的出现已使当今世界的软件开发现场发生了翻天腹地的变化。在这场可称之为革命的变革当中,中国也毫不例外地受到了影响。下面我将介绍将GitHub导入日常开发后会带来哪些变化,供尚未正式使用GitHub的开发者们加以了解。 协作形式变化 此前,用于辅助多人协同工作的软件层出不穷,然而它们中的大部分有一个个退出了历史的舞台。在这类软件汇总,群件(Groupware)和CRM(Customer RelationshipManagement,顾客关系管理)等脱颖而出,被全世界的商业人士所用。您所在的公司想必也导入了这类软件。 但是,以程序员为代表的软件开发者之间,一直都没有一个用来辅助多人协同编程的关键性软件。因此软件开发者们往往要将版本管理系统、BUG跟踪系统、代码审查工具、邮件列表、IRC等众多工具组合在一起,一实现多人协作。 开发者们对这种软件开发协作模式司空见惯,然而GitHub的出现为其带来了巨大变化。下面我就来介绍GitHub的及项功能。 在开发者之间引发化学反应的Pull Request 在GitHub这个聚集了世界各地软件开发者的地方,有个在过去绝对是无法想象的事正在飞速地进行着——素未谋面的开发者们隔这半个地球的距离共同开发软件。我们不妨称之为开发者之间的化学反应吧。这种事成为可能,都要归功于一个名为Pull Request的功能(图3)。 图3 PullRequest 的页面图3 Pull Request 是指开发者在本地对源代码进行更改后,向GitHub中托管的Git仓库请求合并的功能。开发者可以在Pull Request 上通过评论交流,例如“修正了BUG,可以合并一下吗?”以及“我试着做了这样一个功新功能,可以合并一下吗?”等。通过这个功能,开发者可以轻松更改源代码,并公开更改的细节,然后向仓库提交合并请求。而且,如果请求的更改与项目的初衷相违,也可以选择拒绝合并。 GitHub的Pull Reqeust 不但能轻松查看源代码的前后差别,还可以对指定的一行代码进行评论(图4)通过这一功能,开发者们可以针对具体的代码进行讨论,使代码审查的工作变得前所未有的惬意。图4 对特定用户进行评论 方便和快捷并不是Pull Request 的专利。任务管理和BUG报告可以通过Issue 进行交互。如果想让特定用户来看,只要用“@用户名”的格式书写,对方便会接到通知(Notifications),查看Issue(图5)。由于提供了Wiki功能,开发者可以轻松创建文档,进行公开、共享。Wiki更新的历史记录在Git中管理,可以让用户轻松更改。 图5写有“@用户名”的评论截图图5 GitHub FlavoredMarkDown 在GitHub上,用户所有用文字输入的功能都可以用GitHub Flavored Markdown(GFM)语法进行描述。这个语法可以让标记变得简单,以此写出的评论与文档也会更容易理解。只记住一语法便能在多种交流中使用,何乐而不为呢?它还有一个很特别的功能,那就是可以在评论中添加文字表情,使用户间的交流更加顺利。 随着GitHub的普及,正在有越来越多的服务开始兼容Markdown语法。 能看到更多其他团队的软件 GitHub快捷的环境为开发者带来的合作伙伴,并不值局限于自己团队内部。只要将感兴趣的仓库添加至Watch中,就可以在 News Feed查看该仓库的相关信息 比如,将全公司共用代码库的仓库添加到Watch中,便能在第一时间掌握最新版本的新功能或BUG修正的信息。当然,您也可以参与到讨论中去,积极地提出意见。入有必要,还可以通过PullRequest 提交代码。 将隔壁团队正在开发的仓库添加到Watch中,就可以每天查看他们都在开发什么功能。一旦发现有用的功能或者库,可以查看他们的开发团队。如果能进一步交流,分割出共用的库,从而建立起新的仓库,便成了不同开发者团队间的协作的美谈。 与开源软件相同的开发模式 将GitHub运用到企业中,便会带来与开源软件开发相同的开发模式。已经熟悉开源软件开发者不必专门去学习企业独自采用的工具,就可以直接加入到开发行列。 反过来说,只要在企业中运用到GitHub,即便是刚刚入职成为程序员的应届毕业生,也可以很快投身到开源软件开发的世界中。 也就是说,开源软件世界的软件开发与企业内的软件开发将不再隔阂。在某些企业中,这两者的区别恐怕就是仓库公开与否的区别了。
1 下一页