0%

Golang流媒体网站笔记

httprouter的使用

restlet测试插件

api

3-6

总体思路:
handleer->validation{1.request,2.user}->business login ->response

  1. data model
  2. error handler

这个错误接口以后都可以这样写了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package defs

type Err struct {
Error string `json:"error"`
ErrorCode string `json:"error_code"`
}

type ErrResponse struct {
HttpSC int
Error Err
}

var (
ErrorRequestBodyParseFailed = ErrResponse{HttpSC: 400, Error: Err{Error: "Request body is not correct", ErrorCode: "001"}}
ErrorNotAuthUser = ErrResponse{HttpSC: 401, Error: Err{Error: "User authentication failed.", ErrorCode: "002"}}
ErrorDBError = ErrResponse{HttpSC: 500, Error: Err{Error: "DB ops failed", ErrorCode: "003"}}
ErrorInternalFaults = ErrResponse{HttpSC: 500, Error: Err{Error: "Internal service error", ErrorCode: "004"}}
)

3-7

好好理解这里的数据库为什么这样设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
create table comments (
id varchar(64) not null,
video_id varchar(64),
author_id int(10),
content text,
time datetime default current_timestamp, primary key(id)
);

create table sessions (
session_id tinytext not null,
TTL tinytext,
login_name text
);
alter table sessions add primary key (session_id(64));

create table users (
id int unsigned not null auto_increment,
login_name varchar(64),
pwd text not null,
unique key (login_name),
primary key (id)
);

create table video_del_rec (
video_id varchar(64) not null,
primary key (video_id)
);

create table video_info (
id varchar(64) not null,
author_id int(10),
name text,
display_ctime text,
create_time datetime default current_timestamp,
primary key (id)
);

第三范式(Third Normal Form,3rd NF)就是指表中的所有数据元素不但要能唯一地被主关键字所标识,而且它们之间还必须相互独立,不存在其他的函数关系。也就是说,对于一个满足2nd NF 的数据结构来说,表中有可能存在某些数据元素依赖于其他非关键字数据元素的现象,必须消除。

3-8

数据库连接还是参考自己写的这个吧demo吧。

3-10

api_test.go

参考Golang练习笔记23测试讲的更详细。

3-12

实现和验证video

3-13

Comments


评论是需要列表出现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func ListComments(vid string, from, to int) ([]*defs.Comment, error) {
stmtOut, err := dbConn.Prepare(`SELECT comments.id, users.login_name, comments.content FROM comments
INNER JOIN users ON comments.author_id = users.id
WHERE comments.video_id = ? AND comments.time > FROM_UNIXTIME(?) AND comments.time <= FROM_UNIXTIME(?)
ORDER BY comments.time DESC`)

var res []*defs.Comment

rows, err := stmtOut.Query(vid, from, to)
if err != nil {
log.Printf("%s", err)
return res, err
}

for rows.Next() {
var id, name, content string
if err := rows.Scan(&id, &name, &content); err != nil {
return res, err
}

c := &defs.Comment{Id: id, VideoId: vid, Author: name, Content: content}
res = append(res, c)
}

defer stmtOut.Close()

return res, nil
}

3-15

session会话,

session和cookie的区别?

session负责的流程图:

3-17

middleware

duck typing内容参考Golang练习笔记15。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type middleWareHandler struct {
r *httprouter.Router
}

func NewMiddleWareHandler(r *httprouter.Router) http.Handler {
m := middleWareHandler{}
m.r = r
return m
}

func (m middleWareHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
//check session
validateUserSession(r)

m.r.ServeHTTP(w, r)
}

func RegisterHandlers() *httprouter.Router {
router := httprouter.New()
router.POST("/user", CreateUser)
...
}
1
2
3
4
5
6
7
8
9
10
//response
type SignedUp struct {
Success bool `json:"success"`
SessionId string `json:"session_id"`
}
#添了后缀,在使用时,会返回统一的json格式,如下:
{
"success":XXXXX,
"session_id":XXXX
}

Streaming

4-1

Streaming(视频播放):

  • 静态视频,非RTMP(直播的那些都是RTMP)
  • 独立的服务,可独立部署
  • 统一的api格式

4-3

流控
token bucket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#共享通道 instead of shared memory

type ConnLimiter struct {
concurrentConn int
bucket chan int
}

func NewConnLimiter(cc int) *ConnLimiter {
return &ConnLimiter {
concurrentConn: cc,
bucket: make(chan int, cc),
}
}

func (cl *ConnLimiter) GetConn() bool {
if len(cl.bucket) >= cl.concurrentConn {
log.Printf("Reached the rate limitation.")
return false
}

cl.bucket <- 1
return true
}

func (cl *ConnLimiter) ReleaseConn() {
c :=<- cl.bucket
log.Printf("New connction coming: %d", c)
}

4-5

在http middleware中嵌入流控

scheduler

5-1

scheduler任务调度器,某些不能马上处理的任务放到scheduler,让它定期或者延时触发。

5-3

runner的生产消费者模型实现

5-5

task

api->videoid->mysql
dispatcher->mysql->videoid->datachannel
executor->datachannel->videoid->delete video

5-6

timer

setup->strat{runner task}

5-7

前端

6-1

Go的模版引擎

  • 模版引擎是将html解析和元素预置替换生成最终页面的工具
  • Go的模版有两种text/template和html/template
  • Go的模版采用动态生成的模式

6-2

前端代码架构

client.go就是起了proxy转发作用,避免跨域作用。

6-3

静态页面渲染

6-4

sh build.sh

6-6 api透传!!!

6-7 proxy转发

net/url

net/http/httputil

1
2
3
4
5
6
7
8
9
10
11
func proxyVideoHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
u, _ := url.Parse("http://" + config.GetLbAddr() + ":9000/")
proxy := httputil.NewSingleHostReverseProxy(u)
proxy.ServeHTTP(w, r)
}

func proxyUploadHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
u, _ := url.Parse("http://" + config.GetLbAddr() + ":9000/")
proxy := httputil.NewSingleHostReverseProxy(u)
proxy.ServeHTTP(w, r)
}

6-11

js

部署

7-4

公共配置,即各模块的config文件。

7-5 vendor

7-6 SLB讲解与配置

阿里云负载均衡配置。

7-7

session容错

7-8 ECS配置

7-10 部署脚本

在bin文件夹添加conf.json文件

1
2
3
4
{
"lb_addr": "127.0.0.1",
"oss_addr": "oss.aliyun.com"
}

buildprod.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#! /bin/bash

# Build web and other services

cd ~/work/src/github.com/avenssi/video_server/api
env GOOS=linux GOARCH=amd64 go build -o ../bin/api

cd ~/work/src/github.com/avenssi/video_server/scheduler
env GOOS=linux GOARCH=amd64 go build -o ../bin/scheduler

cd ~/work/src/github.com/avenssi/video_server/streamserver
env GOOS=linux GOARCH=amd64 go build -o ../bin/streamserver

cd ~/work/src/github.com/avenssi/video_server/web
env GOOS=linux GOARCH=amd64 go build -o ../bin/web

deploy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#! /bin/bash

cp -R ./templates ./bin/

mkdir ./bin/videos

cd bin

nohup ./api &
nohup ./scheduler &
nohup ./streamserver &
nohup ./web &

echo "deploy finished"

7-11 部署

sh buildprod.sh 编译完成后提交二进制文件。或者提交代码到服务器上直接编译

检查对应server是否启动ps aux | grep <api?stream?scheduler?web>