`
大涛学长
  • 浏览: 110815 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

《Knative 实战:三步走!基于 Knative Serverless 技术实现一个短网址服务》

阅读更多
短网址顾名思义就是使用比较短的网址代替很长的网址。维基百科上面的解释是这样的:
短网址又称网址缩短、缩短网址、URL 缩短等,指的是一种互联网上的技术与服务,此服务可以提供一个非常短小的 URL 以代替原来的可能较长的URL,将长的 URL 位址缩短。用户访问缩短后的 URL 时通常将会重定向到原来的长 URL

起源
虽然现在互联网已经非常发达了,但还是有很多场景会对用户输入的内容有长度限制。比如 :
微薄、Twitter 长度不能超过 140 个字
一些早期的 BBS 文章单行的长度不能超过 78 字符等场景
运营商短信的长度不能超过 70 个字
而现在很多媒体、电商平台的内容大多都是多人协作通过比较复杂的系统、框架生成的,链接长度几十个甚至上百字符都是很平常的事情,所以如果在上述的几个场景中传播链接使用短网址服务就是一个必然的结果。比如下面这些短信截图你应该不会陌生:




应用场景
短网址服务的最初本意就是缩短长 url,方便传播。但其实短网址服务还能做很多其他的事情。比如下面这些:
访问次数的限制,比如只能访问 1 次,第二次访问的时候就拒绝服务
时间的限制,比如只能在一周内提供访问服务,超过一周就拒绝服务
根据访问者的地域的限制
通过密码访问
访问量统计
高峰访问时间统计等等
统计访问者的一些信息,比如:

来源城市
访问时间
使用的终端设备、浏览器
访问来源 IP

在营销活动中其实还可以对不同的渠道生成不通的短网址,这样通过统计这些短网址还能判断不同渠道的访问量等信息
基于 Knative Serverless 技术实现一个短网址服务
在 Knative 模式下可以实现按需分配,没有流量的时候实例缩容到零,当有流量进来的时候再自动扩容实例提供服务。
现在我们就基于阿里云容器服务的 Knative 来实现一个 serverless 模式的短网址服务。本示例会给出一个完整的 demo,你可以自己在阿里云容器服务上面创建一个 Knative 集群,使用本示例提供服务。本示例中实现一个最简单的功能
通过接口实现长网址到短网址的映射服务
当用户通过浏览器访问短网址的时候通过 301 跳转到长网址
下面我们一步一步实现这个功能
数据库
既然要实现短网址到长网址的映射,那么就需要保存长网址的信息到数据库,并且生成一个短的 ID 作为短网址的一部分。所以我们首先需要选型使用什么数据库。在本示例中我们选择使用阿里云的表格存储,表格存储最大的优势就是按量服务,你只需要为你使用的量付费,而且价格也很实惠。如下所示的按量计费价格表。1G 的数据保存一年的费用是3.65292元/年( 0.000417 24 365=3.65292) ,是不是很划算。



短网址生成 API
我们需要有一个 API 生成短网址
/new?origin-url=${长网址}
origin-url 访问地址
返回结果
vEzm6v
假设我们服务的域名是 short-url.default.serverless.kuberun.com ,那么现在访问 http://short-url.default.serverless.kuberun.com/vEzm6v 就可以跳转到长网址了。
代码实现
package main

import (
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "log"
    "net/http"
    "os"
    "strconv"
    "time"

    "strings"

    "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
)

var (
    alphabet = []byte("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    l        = &Log{}
)

func ShortUrl(url string) string {
    md5Str := getMd5Str(url)
    var tempVal int64
    var result [4]string
    for i := 0; i < 4; i++ {
        tempSubStr := md5Str[i*8 : (i+1)*8]
        hexVal, _ := strconv.ParseInt(tempSubStr, 16, 64)
        tempVal = 0x3FFFFFFF & hexVal
        var index int64
        tempUri := []byte{}
        for i := 0; i < 6; i++ {
            index = 0x0000003D & tempVal
            tempUri = append(tempUri, alphabet[index])
            tempVal = tempVal >> 5
        }
        result[i] = string(tempUri)
    }
    return result[0]
}

func getMd5Str(str string) string {
    m := md5.New()
    m.Write([]byte(str))
    c := m.Sum(nil)
    return hex.EncodeToString(c)
}

type Log struct {
}

func (log *Log) Infof(format string, a ...interface{}) {
    log.log("INFO", format, a...)
}

func (log *Log) Info(msg string) {
    log.log("INFO", "%s", msg)
}

func (log *Log) Errorf(format string, a ...interface{}) {
    log.log("ERROR", format, a...)
}

func (log *Log) Error(msg string) {
    log.log("ERROR", "%s", msg)
}

func (log *Log) Fatalf(format string, a ...interface{}) {
    log.log("FATAL", format, a...)
}

func (log *Log) Fatal(msg string) {
    log.log("FATAL", "%s", msg)
}

func (log *Log) log(level, format string, a ...interface{}) {
    var cstSh, _ = time.LoadLocation("Asia/Shanghai")
    ft := fmt.Sprintf("%s %s %s\n", time.Now().In(cstSh).Format("2006-01-02 15:04:05"), level, format)
    fmt.Printf(ft, a...)
}

func handler(w http.ResponseWriter, r *http.Request) {
    l := &Log{}
    l.Infof("Hello world received a request, url: %s", r.URL.Path)
    l.Infof("url:%s ", r.URL)
    //if r.URL.Path == "/favicon.ico" {
    //    http.NotFound(w, r)
    //    return
    //}

    urls := strings.Split(r.URL.Path, "/")
    originUrl := getOriginUrl(urls[len(urls)-1])
    http.Redirect(w, r, originUrl, http.StatusMovedPermanently)
}

func new(w http.ResponseWriter, r *http.Request) {
    l.Infof("Hello world received a request, url: %s", r.URL)
    l.Infof("url:%s ", r.URL)
    originUrl, ok := r.URL.Query()["origin-url"]
    if !ok {
        l.Errorf("no origin-url params found")
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte("Bad request!"))
        return
    }

    surl := ShortUrl(originUrl[0])
    save(surl, originUrl[0])
    fmt.Fprint(w, surl)

}

func getOriginUrl(surl string) string {
    endpoint := os.Getenv("OTS_TEST_ENDPOINT")
    tableName := os.Getenv("TABLE_NAME")
    instanceName := os.Getenv("OTS_TEST_INSTANCENAME")
    accessKeyId := os.Getenv("OTS_TEST_KEYID")
    accessKeySecret := os.Getenv("OTS_TEST_SECRET")
    client := tablestore.NewClient(endpoint, instanceName, accessKeyId, accessKeySecret)

    getRowRequest := &tablestore.GetRowRequest{}
    criteria := &tablestore.SingleRowQueryCriteria{}

    putPk := &tablestore.PrimaryKey{}
    putPk.AddPrimaryKeyColumn("id", surl)
    criteria.PrimaryKey = putPk

    getRowRequest.SingleRowQueryCriteria = criteria
    getRowRequest.SingleRowQueryCriteria.TableName = tableName
    getRowRequest.SingleRowQueryCriteria.MaxVersion = 1

    getResp, _ := client.GetRow(getRowRequest)
    colmap := getResp.GetColumnMap()
    return fmt.Sprintf("%s", colmap.Columns["originUrl"][0].Value)
}

func save(surl, originUrl string) {
    endpoint := os.Getenv("OTS_TEST_ENDPOINT")
    tableName := os.Getenv("TABLE_NAME")
    instanceName := os.Getenv("OTS_TEST_INSTANCENAME")
    accessKeyId := os.Getenv("OTS_TEST_KEYID")
    accessKeySecret := os.Getenv("OTS_TEST_SECRET")
    client := tablestore.NewClient(endpoint, instanceName, accessKeyId, accessKeySecret)

    putRowRequest := &tablestore.PutRowRequest{}
    putRowChange := &tablestore.PutRowChange{}
    putRowChange.TableName = tableName

    putPk := &tablestore.PrimaryKey{}
    putPk.AddPrimaryKeyColumn("id", surl)
    putRowChange.PrimaryKey = putPk

    putRowChange.AddColumn("originUrl", originUrl)
    putRowChange.SetCondition(tablestore.RowExistenceExpectation_IGNORE)
    putRowRequest.PutRowChange = putRowChange

    if _, err := client.PutRow(putRowRequest); err != nil {
        l.Errorf("putrow failed with error: %s", err)
    }
}

func main() {
    http.HandleFunc("/", handler)
    http.HandleFunc("/new", new)
    port := os.Getenv("PORT")
    if port == "" {
        port = "9090"
    }

    if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err != nil {
        log.Fatalf("ListenAndServe error:%s ", err.Error())
    }

}
代码我已经编译成镜像,你可以直接使用 registry.cn-hangzhou.aliyuncs.com/knative-sample/shorturl:v1 此镜像编部署服务。
三步走起!!!
第一步 准备数据库
首先到阿里云开通表格存储服务,然后创建一个实例和表。我们需要的结构比较简单,只需要短 URL ID 到长 URL 的映射即可,存储表结构设计如下:
名称描述id短网址 IDoriginUrl长网址
第二步 获取 access key
登陆到阿里云以后鼠标浮动在页面的右上角头像,然后点击 accesskeys 跳转到 accesskeys 管理页面




点击显示即可显示 Access Key Secret




第三步 部署服务
Knative Service 的配置如下, 使用前两步的配置信息填充 Knative Service 的环境变量。然后部署到 Knative集群即可
apiVersion: serving.knative.dev/v1alpha1
kind: Service
metadata:
  name: short-url
  namespace: default
spec:
  template:
    metadata:
      labels:
        app: short-url
      annotations:
        autoscaling.knative.dev/maxScale: "20"
        autoscaling.knative.dev/minScale: "0"
        autoscaling.knative.dev/target: "100"
    spec:
      containers:
        - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/shorturl:v1
          ports:
            - name: http1
              containerPort: 8080
          env:
          - name: OTS_TEST_ENDPOINT
            value: http://t.cn-hangzhou.ots.aliyuncs.com
          - name: TABLE_NAME
            value: ${TABLE_NAME}
          - name: OTS_TEST_INSTANCENAME
            value: ${OTS_TEST_INSTANCENAME}
          - name: OTS_TEST_KEYID
            value: ${OTS_TEST_KEYID}
          - name: OTS_TEST_SECRET
            value: ${OTS_TEST_SECRET}
使用上面的 knative service 部署服务,部署好以后可能是下面这样:
└─# kubectl get ksvc
short-url       http://short-url.default.serverless.kuberun.com       short-url-456q9       short-url-456q9       True
现在可以开始测试
生成一个短网址
└─# curl 'http://short-url.default.serverless.kuberun.com/new?origin-url=https://help.aliyun.com/document_detail/121534.html?spm=a2c4g.11186623.6.786.41e074d9oHpbO2'
vEzm6v
curl 命令输出的结果 VR7baa 就是短网址的 ID
访问短网址

在浏览器中打开 http://short-url.default.serverless.kuberun.com/vEzm6v 就能跳转到长 url 网址了。
小结
本实战我们只需三步就基于 Knative 实现了一个 Serverless 的短网址服务,此短网址服务在没有请求的时候可以缩容到零节省计算资源,在有很多请求的时候可以自动扩容。并且使用了阿里云表格存储,这样数据库也是按需付费。基于 Knative + TableStore 实现了短网址服务的 Serverless 化。
本文作者:一绿舟
原文链接:https://yq.aliyun.com/articles/718936?utm_content=g_1000077682
本文为云栖社区原创内容,未经允许不得转载。
分享到:
评论

相关推荐

    Knative Serverless技术架构剖析.pdf

    Knative 是一个基于 Kubernetes 的 Serverless 框架,旨在提供一个抽象的 Serverless 应用平台。Knative Deployment 是 Knative 的核心组件之一,负责管理 Serverless 应用的生命期。Knative Pod 是 Knative ...

    knative入门:Knative入门-构建基于Kubernetes的现代化Serverless应用-https:www.servicemesher.comgetting-started-with-knative

    knative入门:Knative入门-构建基于Kubernetes的现代化Serverless应用-https:www.servicemesher.comgetting-started-with-knative

    基于 RocketMQ + Knative 驱动云原生 Serverless 应用

    Knative 是一个基于 Kubernetes 的 Serverless 平台,用于构建、部署和管理现代 Serverless 工作负载。Knative 提供了一个统一的框架,用于构建、部署和管理 Serverless 应用程序。Knative 的核心模块包括 Eventing...

    藏经阁-基于 RocketMQ + Knative 驱动云原生 Serverless 应用.pdf

    Knative是一个基于Kubernetes的平台,用于构建、部署和管理现代Serverless工作负载。Knative的核心模块包括事件驱动框架、Serverless服务引擎、Configuration、Route等。Knative可以帮助开发者快速构建和部署...

    Serverless技术开发实战.pdf

    在"Serverless技术开发实战.pdf"中,我们深入探讨了如何利用Serverless进行快速开发,特别是构建一个基于分布式Puppeteer的网页截图服务。 首先,我们需要了解什么是函数计算(Function Compute)。函数计算是...

    Serverless技术开发实战.pptx

    对于Puppeteer网页截图服务,这是一个基于Chrome的无头浏览器工具,能够捕获网页的可视化表示,非常适合用于自动化测试、数据抓取或者生成网页预览。将其部署到函数计算,可以实现按需调用,减少资源浪费。 在实战...

    Knative 云原生应用开发指南&开发实战

    快速入门(跨平台Serverless编排框架、手动安装Knative、Serving Hello Word等)、Serving进阶(Serving健康检查机制分析、服务路由管理、流量灰度和版本管理)、Eventing进阶(关于Broker/ Trigger事件模型、事件...

    Serverless入门与实战.pdf

    ### Serverless入门与实战知识点概览 #### 一、Serverless概述 **1.1 架构演进** - **传统架构**:开发者需管理服务器硬件、操作系统等基础设施。 - **虚拟化技术**:通过虚拟机减少物理资源的管理负担。 - **...

    NBF:新零售服务开放的-Serverless架构与深度实践-冯微峰.pdf

    标题中提及的“NBF:新零售服务开放的Serverless架构与深度实践”指的是冯微峰作为阿里巴巴供应链中台基础技术团队负责人,在2016年开始基于盒马的业务场景设计的新零售服务开放框架NBF(New-Retail Business ...

    knative-重新定义serverless.pdf

    knative-重新定义serverless.pdf

    阿里云:释放算力潜能加速应用构建Serverless 为 AI 创新提速(1).pdf

    - **Serverless GPU技术**:采用虚拟化技术实现GPU资源的强隔离,降低资源占用率,同时提供按需付费的服务模式,有效控制成本。 - **两级资源池策略**:通过热资源池和IaaS GPU资源池的结合,保障GPU资源的快速...

    新一代Serverless平台:Knative在OpenShift上的实践

    Serverless部不是一个新的概念,Knative也不是第一种Serverless平台。以往的Serverless平台,有的不支持K8S,有的虽然支持,但与K8S配合并不紧密。我们知道K8S已经成了事实上的容器云标准,而从K8S到FaaS,需要一个...

    19-Knative和Istio在serverless公有云平台中的应用、实践和挑战-张龚1

    1. 基于Knative和Istio的Serverless公有云平台简介:Knative和Istio的结合使用,可以提供无服务器公有云平台的各种优点,如流量管理、可观察性、安全性等。 2. Istio在Knative平台中的应用、实践和挑战:Istio可以...

    Knative Serving技术架构介绍.pptx

    3. **Traffic Split**:Knative Serving 支持 Traffic Split 功能,允许将流量在多个 Revision 之间按比例分配,从而实现平滑的滚动更新和版本回滚。 4. **Lifecycle Management**:Knative Serving 提供生命周期...

    Knative 入门1

    《Knative入门1》是一本关于构建基于Kubernetes的现代化Serverless应用的指南,由ServiceMesher社区翻译自Pivotal公司赞助的O'Reilly电子书。Knative是一个建立在Kubernetes之上,用于构建、部署和管理现代...

    2021阿里云云原生 Serverless 技术实践营演讲PPT汇总.zip

    2021阿里云云原生 Serverless 技术实践营演讲PPT汇总,共5份。 此次活动分别从技术、产品、架构和开发者工具的视角围绕 Serverless 的发展趋势、落地实践与参会者零距离分享最佳实践心得,全程弹幕互动答疑,和到场...

    Labelhub基于腾讯云Serverless技术为人工智能企业提供数据与模型解决方案.docx

    ### Labelhub基于腾讯云Serverless技术的企业级解决方案 #### 一、Labelhub公司背景与解决方案概述 Labelhub作为一家专注于为人工智能企业提供数据与模型解决方案的公司,致力于帮助AI企业更好地管理数据,加速其...

    Serverless入门与实践 建立思维 技术选型及落地实践.pdf

    Serverless架构是一种新兴的云计算模型,它强调了“无服务器”的概念,即开发者无需关注服务器的管理和运维,只需专注于编写核心业务逻辑,而基础设施的自动扩展和资源管理由云服务商负责。这一模式的核心价值在于...

    2021年全球Serverless服务洞察.pdf

    不过,基于您提供的文件信息,我可以生成一些关于“2021年全球Serverless服务洞察”这一主题的知识点,这些知识点将涉及Serverless计算模型的基础、优势、发展动态以及在不同行业的应用情况。 Serverless计算是...

    Serverless前端技术应用实践.pdf

    随着Kubernetes(K8s)等云原生技术的发展,Serverless进一步发展为更广泛的平台即服务(PaaS)形式,如Knative这样的Serverless构建层,以及Service Mesh等服务网格,它们共同构建了一个自动化管理和调度的环境,...

Global site tag (gtag.js) - Google Analytics