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 | 短网址 ID |
originUrl | 长网址 |
第二步 获取 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 化。
“ 阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术公众号。”
Knative 实战:三步走!基于 Knative Serverless 技术实现一个短网址服务的更多相关文章
- git 三步走
git三步走: git add . (注:别忘记后面的.,此操作是把Test文件夹下面的文件都添加进来) git commit -m "提交信息" (注:“提交 ...
- Python学习笔记(一)三步走安装pip
pip是用来方便地管理Python的第三方包的,由于此前玩Python仅仅是浅尝辄止,用的是python(x,y),但是这里并不代表你想用什么包都能从里面找到的,所以我把python(x,y)卸了,然 ...
- 三步走起 提升 iOS 审核通过率 上篇
<ignore_js_op> Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明 ...
- 用powershell+excel行列转置三步走
本文重点讲解第一步,手动在excel表中输入公式,或者用powershell自动输入公式. 第二步,用powershell向excel中写入数据,略. 第三步,用powershell从excel中读取 ...
- 三步走起 提升 iOS 审核通过率 下篇
根据2015年的数据统计情况,并结合<苹果应用商店审核指南>,互娱 iOS 预审组通过细分将预审工作划为3大模块:客户端资源检查.应用内容检查和提审资源检查. 在上一篇文章中,Bugly ...
- mongodb安装和配置三步走
最近在重新学习node,所以和同事一起搞了个模仿新浪微博的项目,项目刚开始,所以其他的东西就暂时先不提.这里介绍下mongodb的安装.直接搜索可以看到很多介绍,但是我第一次是失败了,不过看了好几个还 ...
- 【php学习】图片处理三步走
前两天要对一张图片进行处理,其实很简单,就是在图片上加上字符串,一个图片而已,但是自己如同得了短暂性失忆似的,图片操作的函数一个都想不起来.所以就抽空整理了一下图片操作函数. 1. 创建画布 从文件中 ...
- 6、DockerFile解析:三步走、保留字指令
1.dockerfiel是什么 1.是什么 Dockerfile是用来构建Docker镜像的构建文件,是由一系列命令和参数构成的脚本. 2.构建三步骤 编写Dockerfile文件 docker bu ...
- 搞定 mybatis generator 三步走
基于idea 编辑器下maven项目使用mybatis generator快速生成持久层 添加插件:插件网址:http://www.mybatis.org/generator/running/runn ...
随机推荐
- Docker入门-docker compose的使用
Compose简介 Compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排.其代码目前在https://github.com/docker/compose 上开源. ...
- 决策树ID3原理及R语言python代码实现(西瓜书)
决策树ID3原理及R语言python代码实现(西瓜书) 摘要: 决策树是机器学习中一种非常常见的分类与回归方法,可以认为是if-else结构的规则.分类决策树是由节点和有向边组成的树形结构,节点表示特 ...
- SSH原理讲解与实践
一.简介 SSH全名Secure Socket Shell,安全外壳传输协议.专为远程登录会话和其他网络服务提供安全性的协议 二.加密算法 要了解SSH的原理,就要先知道目前主流的俩种加密算法 2.1 ...
- docker方式部署elk日志搜索平台
Docker部署ELKF操作文档 前提介绍 1.之前搭建elk+f+k使用原生系统软件安装方式,由于docker镜像日趋成熟,docker官网和elastic官网都有相关镜像和各自安装文档可供参考,各 ...
- Nginx服务部署
1 企业常用网站服务 处理静态资源:nginx.apache.Lighttpd处理动态资源:tomcat(java语言编写).php(php语言编写).python(python语言编写)nginx网 ...
- [系列] go-gin-api 规划目录和参数验证(二)
目录 概述 规划目录结构 模型绑定和验证 自定义验证器 制定 API 返回结构 源码地址 go-gin-api 系列文章 概述 首先同步下项目概况: 上篇文章分享了,使用 go modules 初始化 ...
- 基于随机游走的三维网格分割算法(Random Walks)
首先以一维随机游走(1D Random Walks)为例来介绍下随机游走(Random Walks)算法,如下图所示,从某点出发,随机向左右移动,向左和向右的概率相同,都为1/2,并且到达0点或N点则 ...
- vsftpd 530 Login incorrect问题处理
vsftpd 530 login incorrect 的N中情况 1.密码错误. 2.检查/etc/vsftpd/vsftpd.conf配置 vim /etc/vsftpd/vsftpd.conf 看 ...
- centos7搭建hadoop3.*.*系列
最近搭建这个hadoop踩过不少坑,先是配置JDK搞错路径(普通用户和root用户下的路径不同),再就是hadoop版本不同导致的启动错误,网上找到的是hadoop2.*.*的版本,但是我安装的had ...
- XMLHttpRequest的概述
XMLHttpRequest的概述 一.前言 XMLHttpRequest 最早是在IE5中以ActiveX组件的形式实现的.非 W3C 标准. 创建XMLHttpRequest对象(由于非标准所以实 ...