高性能的短链服务

redis、sanic、sanic-restful

Posted by TuoX on May 18, 2021

随着页面地址越来越长,在进行分享的时候,一长串的地址不友好,所以一个短的链接服务产生。那今天就跟大家介绍一下实现短链的几种方案。

1.数据库自增(使用数据库自带的自增id)
2.redis自增(incr实现)
3.hash算法
4.摘要算法

本文通过介绍redis自增与hash算法来实现

redis自增

思路:

1.判断传递的参数url是否存在
2.存在直接返回
3.不存在=》(1.进行incr 2.存储关系(url=>id,id=>url)3.返回id)
4.转64进制(讲解hash算法的时候会接触到)

想用redis来实现,lua脚本肯定是一个好选择(因为是原子性操作),以下是redis生成id的lua脚本例子

async def get_unionid(key):
    script = '''
    local key = 's:tag:%s'
    local id = redis.call('get',key)
    if(id)
    then
        return id
    else
        local new_id= redis.call('incr','s:unionid')
        redis.call('set',key,new_id)
        redis.call('set',new_id,key)
        return new_id
    end''' % key
    redis = await AioRedisService.get()
    s = await redis.eval(script)
    return int(s)

hash算法

思路:

1.使用murmurhash3来加密url(速度快,效果好)
2.转64进制(讲解hash算法的时候会接触到)得到 key
3.获取key对应的内容(极小概率会一致,为了防止冲突多一个判断)
4.不存在,直接赋值,并且返回key,存在则进行下一步
5.判断key对应的url与当前url是否一致,一致:直接返回,不一致:加个混淆参数,重新从第二个步骤开始

核心代码:

import mmh3
import time
from common.service.aioredis_service import AioRedisService


class ShortCore:

    table = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"

    short_key = 's:short:%s'

    salt_key = 'dd&&dd'

    def b64(self, n):
        '''转64进制
        '''
        n = abs(n)
        if n == 0:
            return '0'
        r = []
        while n > 0:
            r.append(self.table[n % 64])
            n //= 64
        return ''.join(r[::-1])

    def create(self, url):
        '''获取数据
        '''
        return self.b64(mmh3.hash(url))

    async def get_db(self, key):
        '''获取对应的url
        '''
        redis = await AioRedisService.get()
        s = await redis.get(self.short_key % key)
        if s:
            return s.decode().split(self.salt_key)[0]
        return ''

    async def set_db(self, key, url):
        '''设置短链关系
        '''
        redis = await AioRedisService.get()
        return await redis.set(self.short_key % key, url)

    async def setnx_db(self, key, url):
        '''设置短链关系
        '''
        redis = await AioRedisService.get()
        return await redis.setnx(self.short_key % key, url)

    def recreate(self, url):
        '''重新创建
        '''
        url = '%s%s%s' % (url, self.salt_key, int(time.time()*1000))
        return url, self.b64(mmh3.hash(url))

sanic-resuful 部分代码

1.创建短链

from apps.code import Code
from common.core.short_core import ShortCore
from sanic_restful_api import reqparse, Resource

parse = reqparse.RequestParser()
parse.add_argument('url', location='json',
                type=str, required=True, nullable=True)


class ShortUrl(Resource):

    async def post(self, request):
        '''生成短链
        '''
        args = parse.parse_args(request)
        core = ShortCore()
        key = core.create(args.url)
        url = await core.get_db(key)
        print('1', url)
        if not url:
            await core.set_db(key, args.url)
        else:
            if url != args.url:
                new_url, key = core.recreate(args.url)
                print('2', 'new_url')
                await core.set_db(key, new_url)
        return {'code': Code.SUCCESS, 'data': {'key': key}}

2.获取短链

from sanic.response import redirect
from common.core.short_core import ShortCore
from sanic_restful_api import reqparse, Resource

parse = reqparse.RequestParser()
parse.add_argument('url', location='json',
                type=str, required=True, nullable=True)


class Url(Resource):
    async def get(self, request, key):
        '''获取短链
        '''
        core = ShortCore()
        url = await core.get_db(key)
        if url:
            return redirect(url, status=302)
        else:
            return 'welcome to short service'

THE END

短链的核心就是用短的字符来表示一段长的链接,通过短的标识,获取到原始的长链接进行一个302跳转,提交了分享时的点击的成功效果,也美化文案长度。 用id或者用hash加密,都可以得到一个唯一字符,比如自增id不可以直接给id,容易被刷,所以会再经历一次十进制转64进制的过程,当处于大id时也会有一定的压缩效果。

最后还是那句话,如果有问题,欢迎随时留言讨论。