短网址顾名思义就是使用比较短的网址代替很长的网址。维基百科上面的解释是这样的:html
短网址又称网址缩短、缩短网址、URL 缩短等,指的是一种互联网上的技术与服务,此服务能够提供一个很是短小的 URL 以代替原来的可能较长的URL,将长的 URL 位址缩短。用户访问缩短后的 URL 时一般将会重定向到原来的长 URLgit
虽然如今互联网已经很是发达了,但仍是有不少场景会对用户输入的内容有长度限制。好比 :数据库
而如今不少媒体、电商平台的内容大多都是多人协做经过比较复杂的系统、框架生成的,连接长度几十个甚至上百字符都是很日常的事情,因此若是在上述的几个场景中传播连接使用短网址服务就是一个必然的结果。好比下面这些短信截图你应该不会陌生:<br /> api
短网址服务的最初本意就是缩短长 url,方便传播。但其实短网址服务还能作不少其余的事情。好比下面这些:微信
在 Knative 模式下能够实现按需分配,没有流量的时候实例缩容到零,当有流量进来的时候再自动扩容实例提供服务。<br />如今咱们就基于阿里云容器服务的 Knative 来实现一个 serverless 模式的短网址服务。本示例会给出一个完整的 demo,你能够本身在阿里云容器服务上面建立一个 Knative 集群,使用本示例提供服务。本示例中实现一个最简单的功能框架
下面咱们一步一步实现这个功能less
既然要实现短网址到长网址的映射,那么就须要保存长网址的信息到数据库,而且生成一个短的 ID 做为短网址的一部分。因此咱们首先须要选型使用什么数据库。在本示例中咱们选择使用阿里云的表格存储,表格存储最大的优点就是按量服务,你只须要为你使用的量付费,并且价格也很实惠。以下所示的按量计费价格表。1G 的数据保存一年的费用是3.65292元/年( 0.000417 _ 24 _ 365=3.65292) ,是否是很划算。
咱们须要有一个 API 生成短网址
/new?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 | 短网址 ID |
originUrl | 长网址 |
登录到阿里云之后鼠标浮动在页面的右上角头像,而后点击 accesskeys 跳转到 accesskeys 管理页面<br /> <br />点击显示便可显示 Access Key Secret<br />
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
本实战咱们只需三步就基于 Knative 实现了一个 Serverless 的短网址服务,此短网址服务在没有请求的时候能够缩容到零节省计算资源,在有不少请求的时候能够自动扩容。而且使用了阿里云表格存储,这样数据库也是按需付费。基于 Knative + TableStore 实现了短网址服务的 Serverless 化。
“ 阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,作最懂云原生开发者的技术公众号。”