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

短网址顾名思义就是使用比较短的网址代替很长的网址。维基百科上面的解释是这样的:

短网址又称网址缩短、缩短网址、URL 缩短等,指的是一种互联网上的技术与服务,此服务可以提供一个非常短小的 URL 以代替原来的可能较长的URL,将长的 URL 位址缩短。用户访问缩短后的 URL 时通常将会重定向到原来的长 URL

<a name="f19742d4"></a>

起源

虽然现在互联网已经非常发达了,但还是有很多场景会对用户输入的内容有长度限制。比如 :

  • 微薄、Twitter 长度不能超过 140 个字
  • 一些早期的 BBS 文章单行的长度不能超过 78 字符等场景
  • 运营商短信的长度不能超过 70 个字

而现在很多媒体、电商平台的内容大多都是多人协作通过比较复杂的系统、框架生成的,链接长度几十个甚至上百字符都是很平常的事情,所以如果在上述的几个场景中传播链接使用短网址服务就是一个必然的结果。比如下面这些短信截图你应该不会陌生:<br /> file

<a name="3dbf0c11"></a>

应用场景

短网址服务的最初本意就是缩短长 url,方便传播。但其实短网址服务还能做很多其他的事情。比如下面这些:

  • 访问次数的限制,比如只能访问 1 次,第二次访问的时候就拒绝服务
  • 时间的限制,比如只能在一周内提供访问服务,超过一周就拒绝服务
  • 根据访问者的地域的限制
  • 通过密码访问
  • 访问量统计
  • 高峰访问时间统计等等
  • 统计访问者的一些信息,比如:
    • 来源城市
    • 访问时间
    • 使用的终端设备、浏览器
    • 访问来源 IP
  • 在营销活动中其实还可以对不同的渠道生成不通的短网址,这样通过统计这些短网址还能判断不同渠道的访问量等信息

<a name="84db27ce"></a>

基于 Knative Serverless 技术实现一个短网址服务

在 Knative 模式下可以实现按需分配,没有流量的时候实例缩容到零,当有流量进来的时候再自动扩容实例提供服务。<br />现在我们就基于阿里云容器服务的 Knative 来实现一个 serverless 模式的短网址服务。本示例会给出一个完整的 demo,你可以自己在阿里云容器服务上面创建一个 Knative 集群,使用本示例提供服务。本示例中实现一个最简单的功能

  • 通过接口实现长网址到短网址的映射服务
  • 当用户通过浏览器访问短网址的时候通过 301 跳转到长网址

下面我们一步一步实现这个功能

<a name="68051bf4"></a>

数据库

既然要实现短网址到长网址的映射,那么就需要保存长网址的信息到数据库,并且生成一个短的 ID 作为短网址的一部分。所以我们首先需要选型使用什么数据库。在本示例中我们选择使用阿里云的表格存储,表格存储最大的优势就是按量服务,你只需要为你使用的量付费,而且价格也很实惠。如下所示的按量计费价格表。1G 的数据保存一年的费用是3.65292元/年( 0.000417 _ 24 _ 365=3.65292) ,是不是很划算。

file

<a name="7f69ad70"></a>

短网址生成 API

我们需要有一个 API 生成短网址

/new?origin-url=${长网址}

  • origin-url 访问地址

返回结果

vEzm6v

假设我们服务的域名是 short-url.default.serverless.kuberun.com ,那么现在访问 http://short-url.default.serverless.kuberun.com/vEzm6v 就可以跳转到长网址了。

<a name="83175ad0"></a>

代码实现

1package main 2 3import ( 4 "crypto/md5" 5 "encoding/hex" 6 "fmt" 7 "log" 8 "net/http" 9 "os" 10 "strconv" 11 "time" 12 13 "strings" 14 15 "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore" 16) 17 18var ( 19 alphabet = []byte("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") 20 l = &Log{} 21) 22 23func ShortUrl(url string) string { 24 md5Str := getMd5Str(url) 25 var tempVal int64 26 var result [4]string 27 for i := 0; i < 4; i++ { 28 tempSubStr := md5Str[i*8 : (i+1)*8] 29 hexVal, _ := strconv.ParseInt(tempSubStr, 16, 64) 30 tempVal = 0x3FFFFFFF & hexVal 31 var index int64 32 tempUri := []byte{} 33 for i := 0; i < 6; i++ { 34 index = 0x0000003D & tempVal 35 tempUri = append(tempUri, alphabet[index]) 36 tempVal = tempVal >> 5 37 } 38 result[i] = string(tempUri) 39 } 40 return result[0] 41} 42 43func getMd5Str(str string) string { 44 m := md5.New() 45 m.Write([]byte(str)) 46 c := m.Sum(nil) 47 return hex.EncodeToString(c) 48} 49 50type Log struct { 51} 52 53func (log *Log) Infof(format string, a ...interface{}) { 54 log.log("INFO", format, a...) 55} 56 57func (log *Log) Info(msg string) { 58 log.log("INFO", "%s", msg) 59} 60 61func (log *Log) Errorf(format string, a ...interface{}) { 62 log.log("ERROR", format, a...) 63} 64 65func (log *Log) Error(msg string) { 66 log.log("ERROR", "%s", msg) 67} 68 69func (log *Log) Fatalf(format string, a ...interface{}) { 70 log.log("FATAL", format, a...) 71} 72 73func (log *Log) Fatal(msg string) { 74 log.log("FATAL", "%s", msg) 75} 76 77func (log *Log) log(level, format string, a ...interface{}) { 78 var cstSh, _ = time.LoadLocation("Asia/Shanghai") 79 ft := fmt.Sprintf("%s %s %s\n", time.Now().In(cstSh).Format("2006-01-02 15:04:05"), level, format) 80 fmt.Printf(ft, a...) 81} 82 83func handler(w http.ResponseWriter, r *http.Request) { 84 l := &Log{} 85 l.Infof("Hello world received a request, url: %s", r.URL.Path) 86 l.Infof("url:%s ", r.URL) 87 //if r.URL.Path == "/favicon.ico" { 88 // http.NotFound(w, r) 89 // return 90 //} 91 92 urls := strings.Split(r.URL.Path, "/") 93 originUrl := getOriginUrl(urls[len(urls)-1]) 94 http.Redirect(w, r, originUrl, http.StatusMovedPermanently) 95} 96 97func new(w http.ResponseWriter, r *http.Request) { 98 l.Infof("Hello world received a request, url: %s", r.URL) 99 l.Infof("url:%s ", r.URL) 100 originUrl, ok := r.URL.Query()["origin-url"] 101 if !ok { 102 l.Errorf("no origin-url params found") 103 w.WriteHeader(http.StatusBadRequest) 104 w.Write([]byte("Bad request!")) 105 return 106 } 107 108 surl := ShortUrl(originUrl[0]) 109 save(surl, originUrl[0]) 110 fmt.Fprint(w, surl) 111 112} 113 114func getOriginUrl(surl string) string { 115 endpoint := os.Getenv("OTS_TEST_ENDPOINT") 116 tableName := os.Getenv("TABLE_NAME") 117 instanceName := os.Getenv("OTS_TEST_INSTANCENAME") 118 accessKeyId := os.Getenv("OTS_TEST_KEYID") 119 accessKeySecret := os.Getenv("OTS_TEST_SECRET") 120 client := tablestore.NewClient(endpoint, instanceName, accessKeyId, accessKeySecret) 121 122 getRowRequest := &tablestore.GetRowRequest{} 123 criteria := &tablestore.SingleRowQueryCriteria{} 124 125 putPk := &tablestore.PrimaryKey{} 126 putPk.AddPrimaryKeyColumn("id", surl) 127 criteria.PrimaryKey = putPk 128 129 getRowRequest.SingleRowQueryCriteria = criteria 130 getRowRequest.SingleRowQueryCriteria.TableName = tableName 131 getRowRequest.SingleRowQueryCriteria.MaxVersion = 1 132 133 getResp, _ := client.GetRow(getRowRequest) 134 colmap := getResp.GetColumnMap() 135 return fmt.Sprintf("%s", colmap.Columns["originUrl"][0].Value) 136} 137 138func save(surl, originUrl string) { 139 endpoint := os.Getenv("OTS_TEST_ENDPOINT") 140 tableName := os.Getenv("TABLE_NAME") 141 instanceName := os.Getenv("OTS_TEST_INSTANCENAME") 142 accessKeyId := os.Getenv("OTS_TEST_KEYID") 143 accessKeySecret := os.Getenv("OTS_TEST_SECRET") 144 client := tablestore.NewClient(endpoint, instanceName, accessKeyId, accessKeySecret) 145 146 putRowRequest := &tablestore.PutRowRequest{} 147 putRowChange := &tablestore.PutRowChange{} 148 putRowChange.TableName = tableName 149 150 putPk := &tablestore.PrimaryKey{} 151 putPk.AddPrimaryKeyColumn("id", surl) 152 putRowChange.PrimaryKey = putPk 153 154 putRowChange.AddColumn("originUrl", originUrl) 155 putRowChange.SetCondition(tablestore.RowExistenceExpectation_IGNORE) 156 putRowRequest.PutRowChange = putRowChange 157 158 if _, err := client.PutRow(putRowRequest); err != nil { 159 l.Errorf("putrow failed with error: %s", err) 160 } 161} 162 163func main() { 164 http.HandleFunc("/", handler) 165 http.HandleFunc("/new", new) 166 port := os.Getenv("PORT") 167 if port == "" { 168 port = "9090" 169 } 170 171 if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err != nil { 172 log.Fatalf("ListenAndServe error:%s ", err.Error()) 173 } 174 175}

代码我已经编译成镜像,你可以直接使用 registry.cn-hangzhou.aliyuncs.com/knative-sample/shorturl:v1 此镜像编部署服务。

<a name="b8cdb0b0"></a>

三步走起!!!

<a name="cea7acd0"></a>

第一步 准备数据库

首先到阿里云开通表格存储服务,然后创建一个实例和表。我们需要的结构比较简单,只需要短 URL ID 到长 URL 的映射即可,存储表结构设计如下:

名称

描述

id

短网址 ID

originUrl

长网址

<a name="110252e6"></a>

第二步 获取 access key

登陆到阿里云以后鼠标浮动在页面的右上角头像,然后点击 accesskeys 跳转到 accesskeys 管理页面<br /> file <br />点击显示即可显示 Access Key Secret<br /> file

<a name="c3070031"></a>

第三步 部署服务

Knative Service 的配置如下, 使用前两步的配置信息填充 Knative Service 的环境变量。然后部署到 Knative集群即可

1apiVersion: serving.knative.dev/v1alpha1 2kind: Service 3metadata: 4 name: short-url 5 namespace: default 6spec: 7 template: 8 metadata: 9 labels: 10 app: short-url 11 annotations: 12 autoscaling.knative.dev/maxScale: "20" 13 autoscaling.knative.dev/minScale: "0" 14 autoscaling.knative.dev/target: "100" 15 spec: 16 containers: 17 - image: registry.cn-hangzhou.aliyuncs.com/knative-sample/shorturl:v1 18 ports: 19 - name: http1 20 containerPort: 8080 21 env: 22 - name: OTS_TEST_ENDPOINT 23 value: http://t.cn-hangzhou.ots.aliyuncs.com 24 - name: TABLE_NAME 25 value: ${TABLE_NAME} 26 - name: OTS_TEST_INSTANCENAME 27 value: ${OTS_TEST_INSTANCENAME} 28 - name: OTS_TEST_KEYID 29 value: ${OTS_TEST_KEYID} 30 - name: OTS_TEST_SECRET 31 value: ${OTS_TEST_SECRET}

使用上面的 knative service 部署服务,部署好以后可能是下面这样:

1└─# kubectl get ksvc 2short-url http://short-url.default.serverless.kuberun.com short-url-456q9 short-url-456q9 True

现在可以开始测试

curl 命令输出的结果 VR7baa 就是短网址的 ID

<a name="5db9fd7c"></a>

小结

本实战我们只需三步就基于 Knative 实现了一个 Serverless 的短网址服务,此短网址服务在没有请求的时候可以缩容到零节省计算资源,在有很多请求的时候可以自动扩容。并且使用了阿里云表格存储,这样数据库也是按需付费。基于 Knative + TableStore 实现了短网址服务的 Serverless 化。

“ 阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术公众号。”

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Python爬虫-爬取图片并且存储到MySQL数据库中

一、获取网址,解析网址​​(1)、for循环获取不同页的数据(2)、获取a标签中的网址(原因:外面图片分辨率太小,进入图片对应的网址寻找分辨率大点的图片)url网址https://www.moyublog.com/95200.htmlforiinrange(1):strvaluestr(i)页数url"htt

15款最好用的腾讯短链接(url.cn接口)批量在线生成接口

  短链接已经广泛应用于各种场景,电商,微信,短信等等。那么今天给大家推荐15款t.cn、url.cn的短链接短网址在线生成工具,都是我从市面数十款短链接短网址生成工具里面挑选的最为简便,最为稳定的短链接短网址生成工具。废话不多!1,青梅短网址(https://www.oschina.net/action/GoToLink?urlhttp

京东短网址高可用提升最佳实践 | 京东云技术团队

作者:京东零售郝彦军什么是短网址?短网址,是在长度上比较短的网址。简单来说就是帮您把冗长的URL地址缩短成8个字符以内的短网址。当我们在腾讯、新浪发微博时,有时发很长的网址连接,但由于微博只限制140个字,所以微博就自动把您发的长网址给转换成短网址了。在微

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