背景想必大家经常会收到垃圾短信……短信中的链接一般都是短链接,类似下图:为什么这里的网址都是短的?什么好?你是怎么做到的?短网址的好处短信和很多平台(微博)都有字数限制,链接太长没办法写正文。好的。与很多未知参数相比,短链接更加简洁和友好。方便做一些统计。当你点击链接时,会有人记录和分析。安全。不公开访问参数。这就是为什么我们现在收到的大多数垃圾邮件都是短网址。那么短网址是如何工作的呢?短网址的基本原理短网址从生成到使用分为以下几个步骤:有一个短网址对应的服务会发给你。例如,www.baidu.com->www.t.cn/1。将短网址拼接到短信内容后发送。.当用户点击短URL时,浏览器以301/302重定向并访问相应的长URL。显示相应的内容。本文主要关注第一步,即如何将长URL映射为短URL。对于服务设计,如果你去想真正的长URL和短URL的对应关系,那你就走远了。理想的情况是:我们使用一种算法将每个长URL唯一地转换成一个短URL。反向转换的能力也得以保持。但是这是不可能的,如果有这样的算法,全世界的压缩算法都可以死在原地。正确的思路是建一个数字生成器,每有一个新的长URL进来,我们就加一并返回新的值。第一个返回的URL返回“www.x.cn/0”,第二个返回“www.x.cn/1”。接下来以问答的形式写几个小问题:对应关系如何存储?这个对应的数据必须放在磁盘上,而且不能每次重启系统都重新排列编号,所以可以存放在mysql等数据库中。而如果数据量小,qps低,可以直接使用数据库的自增主键来实现。如何保证长链接和短链接一一对应?根据上述发行者策略,不能保证长短链接一一对应。如果连续两次使用同一个URL请求,结果值会不一样。为了实现长短链接的一一对应,我们需要付出很大的空间,尤其是为了快速响应,我们可能需要在内存中做一层缓存,太浪费了。但是,可以实现一些变体来实现部分一一对应,例如将最近/最热的对应关系存储在K-V数据库中,这样可以节省空间并加快响应速度。短网址的存储我们返回的短网址一般都会转换成32进制数,这样可以更有效的缩短网址长度。那么,32进制数对于计算机来说只是字符串,如何存储呢?直接存储字符串对于等价值很容易找到,但是对于范围搜索来说太不友好了。其实可以直接存储十进制数,不仅占用空间小,而且支持搜索更好,也可以更方便的转换为多/少基进一步缩短URL。如果高并发直接存入MySQL,当并发请求增加时,数据库压力太大,可能会出现瓶颈。这个时候可以做一些优化。在长链接和短链接的一一对应中也提到了缓存。这里我们来这里是为了加快程序的处理速度。可以缓存流行的长链接(需要统计长链接进来的次数),最近的长链接(可以用redis保存最后一个小时)等等,存入内存或者内存中类似redis的数据库。如果请求的长URL命中缓存,则直接获取对应的短URL返回,无需再进行生成操作。批量发行,每发一个号,需要访问MySQL一次,获取当前最大数,获取到后更新最大数。这个压力比较大。我们每次可以从数据库中获取10000个数,然后在内存中下发。当剩余号码少于1000个时,我们再向MySQL申请10000个号码,在上一批号码发出后分批写入。这样就可以将数据库的持续操作转移到代码中,将获取和写入操作异步进行,保证服务的持续高并发。上面设计的分布式系统有一个单点,即数生成器是一个单点,很容易挂掉。可以使用分布式服务。如果是分布式的话,如果每个发号人发号后都需要同步给其他发号人,可能不会太麻烦。换个思路,发号的可以有两个,一个发单号,一个发双号。号码发出后不再加1,而是加2。可以类比,我们可以用1000个服务分别发出0-999结尾的号码,每次发出后加1000。这样做很简单,服务之间基本上不需要通信,做好自己的事情就好了。实现因为懒得写JDBC代码,更懒得玩Mybatis,代码里用到MySQL的地方就用Redis。packageutil;importredis.clients.jedis.Jedis;/***创建者pfliuon2019/06/23.*/publicclassShortURLUtil{privatestaticfinalStringSHORT_URL_KEY="SHORT_URL_KEY";privatestaticfinalStringLOCALHOST="http://localhost:4444/";privatestaticfinalStringSHORT_LONG_PREFIX="short_long_KEY"="cache_key_prefix_";privatestaticfinalintCACHE_SECONDS=1*60*60;privatefinalStringredisConfig;privatefinalJedisjedis;publicShortURLUtil(StringredisConfig){this.redisConfig=redisConfig;this.jedis=newJedis(this.redisConfig);}publicStringdecimal/ShortURLquery)(Stringdecimal/ShortURL)缓存Stringcache=jedis.get(CACHE_KEY_PREFIX+longURL);if(cache!=null){returnLOCALHOST+toOtherBaseString(Long.valueOf(cache),decimal.x);}//自增longnum=jedis.incr(SHORT_URL_KEY);//在数据库中保存短-长URL映射关系,MySQL中可以保存jedis.set(SHORT_LONG_PREFIX+num,longURL);//写缓存jedis.setex(CACHE_KEY_PREFIX+longURL,CACHE_SECONDS,String.valueOf(num));returnLOCALHOST+toOtherBaseString(num,decimal.x);}/***配置默认变量的配置*/finalstaticchar[]digits={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','','t','u','v','w','x','y','z'};/***具有特定字符串的可变10字符字符串*/privateStringtoOtherBaseString(longn,intbase){长数=0;如果(n<0){num=((long)2*0x7ffffffff)+n+2;}else{num=n;}char[]buf=newchar[32];intcharPos=32;while((num/base)>0){buf[--charPos]=digits[(int)(num%base)];num/=base;}buf[--charPos]=digits[(int)(num%base)];returnnewString(buf,charPos,(32-charPos));}enumDecimal{D32(32),D64(64);intx;Decimal(intx){this.x=x;}}publicstaticvoidmain(String[]args){for(inti=0;i<100;i++){System.出去。println(newShortURLUtil("localhost").getShortURL("www.baidudu.com",十进制.D32));System.out.println(newShortURLUtil("localhost").getShortURL("www.baidu.com",Decimal.D64));}}}
