Linux基于Redis实现短地址服务

一、应用场景
为什么要使用短地址服务,具体使用的业务场景如下:

URL 压缩,把原始长地址压缩成短地址,便于文本长度限制的场景使用(短信、社交网络、网络营销)

        — 营销短信有字数限制,链接太长会影响短信内容的条数(涉及到费用问题)。
        — 相对于长链接,短链接更安全,不暴露访问参数,同时可以做访问限制。
        — 方便短链接进行统计。例如网络新媒体营销渠道统计,点击量,访问用户使用设备等。
        — 短链接更简洁,不像长链接有一大堆参数。


二、技术方案与实现
2.1.实现原理
        短地址服务将使用的长链接转成成短链接;
        把短链接通过短信或网络新媒体发送出去;
        用户在收到短链接后,点击短链接跳转到短地址服务系统上;
        短地址服务根据短链接获取与其对应的长链接地址后采用 301 或 302 进行重定向到长地址;
        短地址项目核心就是将长链接转换成短链接,并保证每个长链接对应唯一的短链接。如果出现一个短链接对应2个长链接的话,将不能确定重定向的地址。

2.2.长链接转成短链接策略方法

2.2.41随机数策略
        通过生成一个随机数来表示一个短地址,然后查询数据库是否用过,用过就再随机,如此往复。缺点:生成性能太差,短链接和长链接信息越多,循环处理是否重复的次数就越大。


2.2.2 算法策略
        通过算法将长链接和短链接进行互转,这种方式的缺点是:会产生重复的链接,假设使用算法,那么算法的性能也不高。

2.2.3 算法+存储
        通过算法将长链接转成短链接并将长链接和短链接关系存入数据库。这个方式绕开了短链接转换成长链接,但是没有解决长链接转成短链接重复的问题。

2.2.4 Hash 算法
        使用 Hash 算法,Hash 碰撞后再对重复的进行标记区分。Hash算法中有一个相对比较可行的方法是:摘要算法。摘要算法,生成短码是固定4个,但是仍然存在重复几率。

摘要算法算法过程:

将长网址md5生成32位签名串,分为4段, 每段8个字节;
对这四段循环处理, 取8个字节, 将他看成16进制串与0x3fffffff(30位1)与操作, 即超过30位的忽略处理;
这30位分成6段, 每5位的数字作为字母表的索引取得特定字符, 依次进行获得6位字符串;
总的md5串可以获得 4个 6位串;取里面的任意一个就可作为这个长链接的短链接地址;

2.2.5 发号策略
        又叫自增序列算法或永不重复算法,通过设置一个初始的 Long 数值,每生成一个短链接将这个数值 +1 。数值与长链接一一对应,保证每个网址生成一个唯一的十进制ID,然后通过 base 62 将数字转换成相对比较短的短码(低进制转化为高进制时,字符数会减少的特性)

2.3 各种算法是否可行分析
        算法策略、算法+存储、随机数策略、hash 算法 都会产生重复的数据,也就是一个短码对应多个长链接。只有自增序列算法不会出现重复。但是也有采用摘要算法来实现,这种方式虽然会产生重复的短码,但是概率非常低。综合分析我们短地址服务采用自增序列算法来实现。

        同时对于链接高频访问性能问题:对于可能被高频访问短链接,通过增加缓存来提高自增号对应长链接的查找速度。查询走缓存大大降低数据库的压力。

        Redis的内存数据存储特性确保了即使是在高并发访问的情况下,短链接的查找速度也能保持快速和稳定。通过将短链接映射到长链接的键值对存储,Redis能够实现极速的响应时间。

缓存 kv 形式:

        自增号 -> 长链接 或 短码 -> 长链接。Redis 缓存并采用LRU 算法淘汰经常不访问的长链接或短码。并通过2级别缓存,提高缓存的高可用,本次短地址服务就使用Redis实现。

2.4 自增序列算法存在问题与解决方案
2.4.1 短码超用
        问题描述:因为采用的数值递增的方式,假设使用Long 值表示:long 最大值 9223372036854775807L == 2 的 63 次方 -1。理论上讲肯定会超。

        解决方案:短码超用的问题,首先是自增号 long 最大值 9223372036854775807L == 2 63次方 -1,假设我们短码用 6 位表示:就会产生约 62 的 6 次方约等于 568 亿,这么大的数量量基本不会用超。就算6位用完可以用7 位 、8位。

具体实现,附代码

---------------

1. 生成短地址,同时记录有效期、访问次数、创建时间、访问渠道(ip)、阅后即焚

2. 解析短地址

3. 数据显示

    - 删除短地址

    - 开通会员~增长有效期

4. 统计信息

5. 退出程序

#include <stdio.h>
#include <hiredis/hiredis.h>
#include <time.h>

void show();
void createTinyURL();
redisContext *connectRedis();
void analyzeTinyURL();
void dataDisplay();
void statisticalInformation();
void deleteTinyURL();
void riseURLTime();
void operationTinyURL();

int main(int argc, char const *argv[])
{

    while (1)
    {
        show();
        printf("请选择操作:");
        int action;
        scanf("%d", &action);

        switch (action)
        {
        case 1:
            createTinyURL();
            break;
        case 2:
            analyzeTinyURL();
            break;
        case 3:
            operationTinyURL();
            break;
        case 4:
            statisticalInformation();
            break;
        case 5:
            return 0;
        default:
            printf("请选择正确操作数(1-5)\n");
            break;
        }
    }

    return 0;
}

// 面板展示
void show()
{
    printf("=================短地址服务===================\n");
    printf("1. 生成短地址\n");
    printf("2. 解析短地址\n");
    printf("3. 数据显示\n");
    printf("4. 统计信息\n");
    printf("5. 退出程序\n");
    printf("=============================================\n");
}

// 生成短地址:唯一ID、有效期、次数,并与长地址存入数据库
void createTinyURL()
{
    printf("-----------------生成短地址--------------------\n");
    printf("请输入需要缩短的长地址:\n");
    char url[64];
    scanf("%s", url);
    char tiny_url[32] = {'0', '0', '0', '0', '0'};
    int i = 4;
    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "incr url_id");
    freeReplyObject(reply);

    reply = redisCommand(conn, "get url_id");
    int id = atoi(reply->str);
    freeReplyObject(reply);
    char c;
    while (id)
    {
        int tem = id % 62;
        if (tem > 35)
        {
            tem -= 36;
            c = tem + 'A';
        }
        else if (tem > 9)
        {
            tem -= 10;
            c = tem + 'a';
        }
        else
        {
            c = tem + '0';
        }
        tiny_url[i] = c;
        i--;
        id /= 62;
    }
    printf("生成的短地址为:ak.cn/%s\n", tiny_url);

    // 获取当前时间,并转为字符串
    time_t now = time(NULL);
    char time_str[20];
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", localtime(&now));

    // 存入短地址、长地址、创建时间、有效期、访问次数
    reply = redisCommand(conn, "hset ak.cn/%s url %s create_time %s days 7 num 0", tiny_url, url, time_str);
    freeReplyObject(reply);
    redisFree(conn);
}

// 创建数据库连接
redisContext *connectRedis()
{
    redisContext *conn = redisConnect("127.0.0.1", 6379);
    if (conn->err)
    {
        printf("connection error\n");
        redisFree(conn);
        return 0;
    }

    return conn;
}

// 解析短地址
void analyzeTinyURL()
{
    printf("-----------------解析短地址--------------------\n");
    printf("请输入需要解析的短地址:");
    char tiny_url[64];
    scanf("%s", tiny_url);

    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "hget %s url", tiny_url);
    printf("解析短地址成功,原地址为:%s\n", reply->str);
    freeReplyObject(reply);

    // 每次查询访问,访问次数加1
    redisCommand(conn, "hincrby %s num 1", tiny_url);
    redisFree(conn);
}

// 短地址、长地址、有效期,数据显示
void dataDisplay()
{
    printf("-----------------数据显示--------------------\n");
    printf("短地址\t\t原地址\t\t\t创建时间\t\t有效期\n");
    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "keys *");
    redisReply *reply_row;

    for (int i = 0; i < reply->elements; i++)
    {
        if (reply->element[i]->len > 6)
        {
            reply_row = redisCommand(conn, "hvals %s", reply->element[i]->str);
            printf("%s\t", reply->element[i]->str);        // 短地址名
            printf("%s\t", reply_row->element[0]->str);    // 原地址名
            printf("%s\t", reply_row->element[1]->str);    // 创建时间
            printf("%5s\t\n", reply_row->element[2]->str); // 有效期
        }
    }
    freeReplyObject(reply);
    redisFree(conn);
}

// 对显示数据进行操作,删除短地址、增长有效期
void operationTinyURL()
{
    int flag = 0;
    dataDisplay();
    while (1)
    {
        if (flag == 1)
        {
            dataDisplay();
        }
        printf("可选择操作:\n");
        printf("\t1. 删除短地址\n");
        printf("\t2. 开通会员~增长有效期\n");
        printf("\t3. 返回\n");
        printf("请选择操作:");
        int action;
        scanf("%d", &action);

        switch (action)
        {
        case 1:
            deleteTinyURL();
            break;
        case 2:
            riseURLTime();
            break;
        case 3:
            return;
        default:
            printf("请选择正确操作数(1-3)\n");
            break;
        }
        flag = 1;
    }
}

// 统计数据信息,短地址、被访问次数
void statisticalInformation()
{
    printf("-----------------统计信息--------------------\n");
    printf("短地址\t\t访问次数\n");
    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "keys *");
    redisReply *reply_row;

    for (int i = 0; i < reply->elements; i++)
    {
        if (reply->element[i]->len > 6)
        {
            reply_row = redisCommand(conn, "hvals %s", reply->element[i]->str);
            printf("%s\t", reply->element[i]->str);        // 短地址名
            printf("%5s\t\n", reply_row->element[3]->str); // 访问次数
        }
    }
    freeReplyObject(reply);
    redisFree(conn);
}

// 删除短地址
void deleteTinyURL()
{
    printf("请输入需要删除的短地址:\n");
    char tiny_url[64];
    scanf("%s", tiny_url);

    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "del %s", tiny_url);
    printf("删除成功~\n");

    freeReplyObject(reply);
    redisFree(conn);
}

// 增长短地址有效期
void riseURLTime()
{
    printf("请输入需要增长有效期的短地址:");
    char tiny_url[64];
    scanf("%s", tiny_url);
    int days;
    printf("请输入需要增长天数:");
    scanf("%d", &days);

    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "hincrby %s days %d", tiny_url, days);

    freeReplyObject(reply);
    redisFree(conn);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值