Knative 实战:三步走!基于 Knative Serverless 技术实现一个短网址服务
短網址顧名思義就是使用比較短的網址代替很長的網址。維基百科上面的解釋是這樣的:
短網址又稱網址縮短、縮短網址、URL 縮短等,指的是一種互聯網上的技術與服務,此服務可以提供一個非常短小的 URL 以代替原來的可能較長的URL,將長的 URL 位址縮短。用戶訪問縮短后的 URL 時通常將會重定向到原來的長 URL
f19742d4)起源雖然現在互聯網已經非常發達了,但還是有很多場景會對用戶輸入的內容有長度限制。比如 :
- 微薄、Twitter 長度不能超過 140 個字
- 一些早期的 BBS 文章單行的長度不能超過 78 字符等場景
- 運營商短信的長度不能超過 70 個字
而現在很多媒體、電商平臺的內容大多都是多人協作通過比較復雜的系統、框架生成的,鏈接長度幾十個甚至上百字符都是很平常的事情,所以如果在上述的幾個場景中傳播鏈接使用短網址服務就是一個必然的結果。比如下面這些短信截圖你應該不會陌生:
短網址服務的最初本意就是縮短長 url,方便傳播。但其實短網址服務還能做很多其他的事情。比如下面這些:
- 訪問次數的限制,比如只能訪問 1 次,第二次訪問的時候就拒絕服務
- 時間的限制,比如只能在一周內提供訪問服務,超過一周就拒絕服務
- 根據訪問者的地域的限制
- 通過密碼訪問
- 訪問量統計
- 高峰訪問時間統計等等
- 統計訪問者的一些信息,比如:
- 來源城市
- 訪問時間
- 使用的終端設備、瀏覽器
- 訪問來源 IP
- 在營銷活動中其實還可以對不同的渠道生成不通的短網址,這樣通過統計這些短網址還能判斷不同渠道的訪問量等信息
在 Knative 模式下可以實現按需分配,沒有流量的時候實例縮容到零,當有流量進來的時候再自動擴容實例提供服務。
現在我們就基于阿里云容器服務的 Knative 來實現一個 serverless 模式的短網址服務。本示例會給出一個完整的 demo,你可以自己在阿里云容器服務上面創建一個 Knative 集群,使用本示例提供服務。本示例中實現一個最簡單的功能
- 通過接口實現長網址到短網址的映射服務
- 當用戶通過瀏覽器訪問短網址的時候通過 301 跳轉到長網址
下面我們一步一步實現這個功能
68051bf4)數據庫既然要實現短網址到長網址的映射,那么就需要保存長網址的信息到數據庫,并且生成一個短的 ID 作為短網址的一部分。所以我們首先需要選型使用什么數據庫。在本示例中我們選擇使用阿里云的表格存儲,表格存儲最大的優勢就是按量服務,你只需要為你使用的量付費,而且價格也很實惠。如下所示的按量計費價格表。1G 的數據保存一年的費用是3.65292元/年( 0.000417 24 365=3.65292) ,是不是很劃算。
7f69ad70)短網址生成 API我們需要有一個 API 生成短網址
/new?origin-url=${長網址}
- origin-url 訪問地址
返回結果
vEzm6v假設我們服務的域名是 short-url.default.serverless.kuberun.com ,那么現在訪問 http://short-url.default.serverless.kuberun.com/vEzm6v 就可以跳轉到長網址了。
83175ad0)代碼實現 package mainimport ("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 int64var result [4]stringfor i := 0; i < 4; i {tempSubStr := md5Str[i*8 : (i 1)*8]hexVal, _ := strconv.ParseInt(tempSubStr, 16, 64)tempVal = 0x3FFFFFFF & hexValvar index int64tempUri := []byte{}for i := 0; i < 6; i {index = 0x0000003D & tempValtempUri = 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 = putPkgetRowRequest.SingleRowQueryCriteria = criteriagetRowRequest.SingleRowQueryCriteria.TableName = tableNamegetRowRequest.SingleRowQueryCriteria.MaxVersion = 1getResp, _ := 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 = tableNameputPk := &tablestore.PrimaryKey{}putPk.AddPrimaryKeyColumn("id", surl)putRowChange.PrimaryKey = putPkputRowChange.AddColumn("originUrl", originUrl)putRowChange.SetCondition(tablestore.RowExistenceExpectation_IGNORE)putRowRequest.PutRowChange = putRowChangeif _, 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 此鏡像編部署服務。
b8cdb0b0)三步走起!!! cea7acd0)第一步 準備數據庫首先到阿里云開通表格存儲服務,然后創建一個實例和表。我們需要的結構比較簡單,只需要短 URL ID 到長 URL 的映射即可,存儲表結構設計如下:
| id | 短網址 ID |
| originUrl | 長網址 |
登陸到阿里云以后鼠標浮動在頁面的右上角頭像,然后點擊 accesskeys 跳轉到 accesskeys 管理頁面
點擊顯示即可顯示 Access Key Secret
Knative Service 的配置如下, 使用前兩步的配置信息填充 Knative Service 的環境變量。然后部署到 Knative集群即可
apiVersion: serving.knative.dev/v1alpha1 kind: Service metadata:name: short-urlnamespace: default spec:template:metadata:labels:app: short-urlannotations: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:v1ports:- name: http1containerPort: 8080env:- name: OTS_TEST_ENDPOINTvalue: http://t.cn-hangzhou.ots.aliyuncs.com- name: TABLE_NAMEvalue: ${TABLE_NAME}- name: OTS_TEST_INSTANCENAMEvalue: ${OTS_TEST_INSTANCENAME}- name: OTS_TEST_KEYIDvalue: ${OTS_TEST_KEYID}- name: OTS_TEST_SECRETvalue: ${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 命令輸出的結果 VR7baa 就是短網址的 ID
- 訪問短網址
在瀏覽器中打開 http://short-url.default.serverless.kuberun.com/vEzm6v 就能跳轉到長 url 網址了。
本實戰我們只需三步就基于 Knative 實現了一個 Serverless 的短網址服務,此短網址服務在沒有請求的時候可以縮容到零節省計算資源,在有很多請求的時候可以自動擴容。并且使用了阿里云表格存儲,這樣數據庫也是按需付費。基于 Knative TableStore 實現了短網址服務的 Serverless 化。
“ 阿里巴巴云原生微信公眾號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh等技術領域、聚焦云原生流行技術趨勢、云原生大規模的落地實踐,做最懂云原生開發者的技術公眾號。”
總結
以上是生活随笔為你收集整理的Knative 实战:三步走!基于 Knative Serverless 技术实现一个短网址服务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从零开始入门 K8s| K8s 的应用编
- 下一篇: Kubernetes 从懵圈到熟练:集群