3 网站上云

 

0x0 CloudNative概述

是什么:借助于大规模分布式系统在资源利用上的灵活使用、服务化拆分,演变出来的一种软件部署、架构、上线的流程方面的最佳实践

参考:百度百科

特点

松耦合(SOA)

每一个独立拆分的服务,具有独立完整的生命周期,不受别的服务影响和制约。

项目实现

无状态(Stateless)

RESTful中提到过,参考

伸缩性(scalability)

伸缩性分为:纵向的伸缩(scale up)、横向的伸缩(scale out)

冗余

只有一个节点提供服务,挂了就没了。用冗余做备用,提高可靠性

项目实现

无状态:我们的案例中,每一个Server已经是无状态的了,本身不存任何东西,只提供API(除了session之外)

伸缩和冗余:通过加LB(负载均衡)实现

平台无关性

AWS上的东东,只用非常少的成本,可以迁移到阿里云上

项目实现

OSS

我们之前把上传的视频直接存在native,这样不安全

正确的方法:使用当前云平台的存储服务 - 阿里云OSS 或 七牛云

Data Base

可以自己部署,也可以使用云平台的 - 云平台的会比较贵

部署和发布

自动化部署:能用机器的别用人手敲(有相关工具,我们在本项目中使用脚本进行半自动部署)

良好的迁移性:和CloudNative的「平台无关性」相呼应,可以很快的迁移到其他云平台上

多云共生:不要把鸡蛋放在同一个篮子里

0x1 使用OSS做视频上传及播放

阿里云OSS文档

SSO: Object Storage Service 使用OSS,您可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种非结构化数据文件

费用与购买

资费项计费项标准型单价低频访问型单价归档型单价
存储费用(注①)数据存储0.12元/GB/月0.08元/GB/月0.033/GB/月
流量费用(注①)内/外网流入流量(数据上传到OSS)免费免费免费

内网流出流量(通过ECS云服务器下载OSS的数据)免费免费免费

外网流出流量00:00-08:00(闲时):0.25元/GB
8:00-24:00(忙时):0.50元/GB
00:00-08:00(闲时):0.25元/GB
8:00-24:00(忙时):0.50元/GB
00:00-08:00(闲时):0.25元/GB
8:00-24:00(忙时):0.50元/GB

CDN回源流出流量0.15元/GB0.15元/GB0.15元/GB

跨区域复制流量0.50元/GB0.50元/GB0.50元/GB
请求费用所有请求类型0.01元/万次0.1元/万次0.1元/万次
数据处理费用 (注②)图片处理每月0-10TB:免费
>10TB:0.025元/GB
每月0-10TB:免费
>10TB:0.025元/GB

视频截帧0.1元/千张0.1元/千张

数据取回免费0.0325元/GB0.06元/GB

参考链接

存储费用可以买存储包,做活动99块钱1T 3年

流量费用的话,上传免费,下载内网流出免费...我们的视频网站其实是用web服务器做代理的,所以应该算是内网流量吧...应该是免费的...

不然就买个99块钱儿1T的吧...穷啊😂

ㄟ...其实9块钱买个40G1年的就行吧...老想贪小便宜啊🙈

买的存储包:

介绍及配置

Bucket:用来存放文件的地方

EndPoint:分为三个,用哪个地址访问它上传文件,取决于你的虚拟机在哪 - 内网访问流量免费~~

地址:我们的ESC在华北二,所以OSS也用华北二的

视频上传/下载代码改造

之前的上传、下载,是webserver作为代理,把真正的请求发给9001端口的StreamServer,StreamServer把文件存到本地 | 从本地读文件作为流传给webserver

改造方案

上传方案及改造

要给OSS传文件,一定不能直接通过js做上传 - 因为js相当于明文,会把OSS给的各种token暴露掉

三种安全的上传方案:

特点:我们选择最简单的第一种,安全性高,性能较低,不需要开启跨域访问

SDK:阿里云OSS

go get github.com/aliyun/aliyun-oss-go-sdk/oss


go的包管理:

可以使用vendor,把所有的第三方包都放到vendor文件夹下,vendor文件夹放在项目的根目录下


实现文件上传的util方法

package ossops

import (
   "github.com/aliyun/aliyun-oss-go-sdk/oss"
)

var EndPoint string
var AccessKeyId string
var AccessKeySecret string

func init() {
   EndPoint = ""
   AccessKeyId = ""
   AccessKeySecret = ""
}

func UploadToOss(filename string, path string, bucketname string) (ok bool) {
   client, err := oss.New(EndPoint, AccessKeyId, AccessKeySecret)
   if err != nil {
      // HandleError(err)
      return false
   }

   bucket, err := client.Bucket("my-bucket")
   if err != nil {
      // HandleError(err)
      return false
   }

   err = bucket.UploadFile(filename, path, 500 * 1024, oss.Routines(3))
   if err != nil {
      // HandleError(err)
      return false
   }

   return true
}

UploadFile(pa1, pa2, 上传的每个chunk大小, 并发上传数量)

func uploadHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
    // ...
    ok := ossops.UploadToOss("/videos/"+fn, uri, "daker-wang-video"); if !ok {
      fmt.Println("上传到云OSS失败", err)
      response.SendErrorResponse(writer, defs.ErrorInternalFaults)
      return
   } else {
      //  上传成功,干掉native文件
      os.Remove(uri)
   }

   //  报告结果
   response.SendNormalResponse(writer, "upload successful", http.StatusCreated)
}

上传到本地服务器成功后,往阿里云传。成功后删掉本地文件。

播放方案及改造

改造前:

func streamHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
   //  找到文件
   fn := params.ByName("vid-id")
   uri := filePath + fn
   //  打开文件 - 不是读二进制哦ioutil.ReadAll()
   file, e := os.Open(uri)
   defer file.Close()

   if e != nil {
      log.Error(e.Error())
      response.SendErrorResponse(writer, defs.ErrorInternalFaults)
      return
   }

   //  写头
   writer.Header().Set("Content-Type", "video/mp4")

   //  写流
   http.ServeContent(writer, request, "", time.Now(), file)
}

改造后:

func streamHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {
   filename := "/videos/" + params.ByName("vid-id")
   bucket := "daker-wang-video.oss-cn-beijing-internal.aliyuncs.com"
   uri := bucket + filename

   log.Info("☁️转发到OSS服务器 %s", filename)
   
   http.Redirect(writer, request, uri, http.StatusMovedPermanently)
}

0x2 配置修改

做部署的时候,一般会有一个统一的配置文件,用来根据不同的服务和模块做不同的配置。

我们需要把配置抽出来写在同一个地方管理,如一个文件中

微服务架构下,有成百上千的服务,只有一个文件会带来很多不便,需要一个配置服务器

待配置内容:

  1. 负载均衡服务器地址
    1. 按照我们之前的说明,三个后端服务都挂载LB上,前端服务通过LB访问后端服务(忘了的话可以返回去看一下顶部的架构图)
  2. OSS地址

使用Vendor管理公共资源

如果我们给每个server都复制粘贴一个Config.go文件,会很难维护。

可以使用Vendor文件夹来解决共有文件问题

Vendor文件夹,用来放第三方包 & 共用文件

config.go文件用来读取配置描述文件并把配置解析出来

package config

import (
   "os"
   "encoding/json"
)

type Configuration struct {
   LBAddress string `json:"lb_address"`
   OSSAddress string `json:"oss_address"`
}

var configuration *Configuration

func init() {
   file, _ := os.Open("./config.json ")
   defer file.Close()

   decoder := json.NewDecoder(file)
   config := &Configuration{}

   err := decoder.Decode(config); if err != nil {
      panic(err)
   }
}

func GetLBAddress() (lbAddress string) {
   return configuration.LBAddress
}

func GetOSSAddress() (ossAddress string) {
   return configuration.OSSAddress
}

引用Vendor中的资源

vendor文件夹中的引用,变的非常简单:通过根目录下的第一条子目录来索引需要的包即可

举个栗子🌰

前面讲到的ossops.go文件,引用vendor中的config示例

package ossops

import (
   "github.com/aliyun/aliyun-oss-go-sdk/oss"
   "azen/config" // 注意这里
)

var EndPoint string
var AccessKeyId string
var AccessKeySecret string

func init() {
   EndPoint = config.GetOSSAddress()
   AccessKeyId = ""
   AccessKeySecret = ""
}

0x3 负载均衡服务器

开始的时候,我们的api转发是交给web服务器来做的。为了实现Cloud Native的「伸缩性」和「冗余」,我们引入了LB服务器。

需要让三个后端服务器挂载在LB上,web服务通过LB和后端服务交互。

开通阿里云LB

实例类型选择「私网」,网络类型选择「专有网络」,实例规格为「性能共享型实例」可以内网免费使用负载均衡服务器

挂载好ESC实例

添加端口监听

前端协议默认为TCP,一般情况下是四层网络,需要配置连接什么的

运维新手可以选择HTTP,使用七层负载均衡,相对简单,前端使用http进来的请求,会分别轮询后端挂载的所有端口

端口号9000是我们的api server端口,绑定好就好。类似的还有stream server端口

第二步的「健康检查」可以关闭

已经开始监听了

改造web server

之前直接转发给对应的后端server,改变为转发给LB。

Proxy:


"http://localhost:9001" → "http://" + config.GetLBAddress() + ":9001"

转发:

u.Host = "localhost" + ":" + u.Port()  →   u.Host = config.GetLBAddress() + ":" + u.Port()

数据同步问题

负载均衡服务器为了保证服务的可用性,需要做「冗余处理」,api server 1和api server 2互为对方的冗余。

但是,由于我们的服务中有类似sessions这种在内存中存在的东东,如果1和2没做内存中的sessions同步,就会出现数据不一致的情况。

解决方案:

方案一:使用Redis独立服务同时作为1和2的缓存服务器,1和2都去Redis中找数据

方案二:Cache和db强制同步数据 - 1中如果没有数据,就去db中找

0x4 ECS配置

买服务的话买就好了...之前买了个简单的小服务,但是跑JAVA虚拟机说内存不够...升了配以后好贵啊🙈

配置的话注意配置一下「实例安全组」策略,如果发现内网的服务不能相互调到报超时,可以尝试修改下安全组策略。

0x5 Dispatcher代码改造

Dispatcher之前的删除操作,只删除了本地Video。需要使用OSS相关操作干掉OSS中的相关资源

代码比较简单,就不贴啦~

0x6 部署及上云

手工部署非常不划算,在点亮「k8s & docker」技能点之前,我们用半自动部署(脚本部署)

我们会做的事情:

  1. build production脚本:每次build出来,上传到gitlab
  2. deploy脚本:从云服务端拉下来build好的二进制包,部署在本地并把服务起起来
  3. sql脚本:创表的脚本


创建一个bin文件夹,放打包后的东西


bin目录下写好config.json文件,用于服务起起来以后读取配置(oss地址和BL地址)

创建脚本

buildprod.sh

#! /bin/bash

# 编译web、api、stream、dispatcher四个服务

cd ${GOPATH}/src/daker.wang/Azen/Go-execise/Streaming/api
env GOOS=linux GOARCH=amd64 go build -o ../../bin/api

cd ${GOPATH}/src/daker.wang/Azen/Go-execise/Streaming/dispatcher
env GOOS=linux GOARCH=amd64 go build -o ../../bin/dispatcher

cd ${GOPATH}/src/daker.wang/Azen/Go-execise/Streaming/stream
env GOOS=linux GOARCH=amd64 go build -o ../../bin/stream

cd ${GOPATH}/src/daker.wang/Azen/Go-execise/Streaming/web
env GOOS=linux GOARCH=amd64 go build -o ../../bin/web


deploy.sh

#!/usr/bin/env bash

# 部署脚本
cp ./template ./bin/

mkdir ./bin/videos

cd bin

nohup ./api &
nohup ./dispatcher &
nohup ./stream &
nohup ./web &


echo  "🎉 deploy done~ have fun~~~"

我们之前上传video会先传到本地,所以需要创建一个videos文件夹

起服务:


initdb.sql - 创表的

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)
);

注意:

1.session表设置主键很怪异,原因在于常规设置法无法把tinytext设置为主键。这样设置的目的是绕开这个限制

2.生产环境中,我们的库中不应该存pwd,而是应该存加密之后的串 - 几年前CSDN的密码被明文存在了数据库中导致泄密

开始部署

  1. 本地运行buildprod.sh
  2. push到git仓库
  3. 云服务器上从git仓库拉文件
  4. 编辑config.json,写正确的address们
  5. 运行deploy.sh
  6. 检查四个服务有木有跑起来
    1. ps aux | grep api ...


打完收工