gin+vue实战

后端:

  • 用户管理
    • 用户列表
    • 登录/登出
  • 商品管理
    • 商品的增上改查
  • 活动管理
    • 商品关联
    • 成功率
    • redis队列,不成功的回到队列继续,成功的从队列删除

结束难点:

  • 代码和部署完全隔离
  • 怎么避免雪崩
  • 根据后端承载能力,进行限流和过载保护
  • 使用redis承载海量QPS
  • mysql性能瓶颈
  • 可扩展行(机器的扩展等)
  • 长连接

秒杀业务逻辑:

img

接入层功能:

img

实战技术选型

一、实战名称:gin+vue3+微服务打造秒杀商城

二、技术选型

1.前端:vue3,antdv,vue-router,axios

2.后端

  • gin框架
  • gorm
  • 微服务:集群部署
    • web
    • srv

gin+vue+微服务打造秒杀商城

一、流程

img

二、技术难点

  • 代码和部署完全隔离(微服务)
  • 怎么避免雪崩(微服务)
  • 根据后端承载能力,进行限流和过载保护(限流熔断)
  • 使用redis承载海量QPS(redis队列)
  • mysql性能瓶颈(sql优化,拆库,拆表)
  • 可扩展性(机器的扩展等)
  • 长连接(先不管)

三、项目分类

1.前端:zhiliao_vue_gin

2.micro-web:zhiliao_web

3.用户管理服务:zhiliao_user_srv

4.商品和活动管理服务:zhiliao_product_srv

5.秒杀服务:zhiliao_seckill_srv

表设计

一、管理员表

表名:sys_admin

字段名称类型长度说明
Idint11主键
UserNamevarchar64用户名
Passwordvarchar64密码,md5加密
Descvarchar255用户描述
Statusint2用户状态
CreateTimedatetime0用户创建时间

二、用户表(注册)

表名:sys_user

字段名称类型长度说明
Idint11主键
Emailvarchar64邮箱地址
Passwordvarchar64密码,md5加密
Descvarchar255用户描述
Statusint2用户状态
CreateTimedatetime0用户创建时间

三、商品表

表名:sys_product

字段名称类型长度说明
Idint11主键
Namevarchar64商品名称
Pricedecimal11,2价格,保留两位小数
Numint11商品数量
Unitvarchar32商品单位
Picvarchar255商品图片
Descvarchar255商品描述
CreateTimedatetime0用户创建时间

四、活动表

表名:sys_product_seckill

字段名称类型长度说明
Idint11主键
Namevarchar64活动名称
Pricedecimal11,2活动价格,保留两位小数,小于等于商品价格
Numint11参与秒杀的数量,小于等于商品数量
PIdint11商品外键
StartTimedatetime0秒杀开始时间
EndTimedatetime0秒杀结束时间
CreateTimedatetime0活动创建时间

五、订单表

表名:sys_orders

字段名称类型长度说明
Idint11主键
OrderNumvarchar64订单编号
Uidint11用户外键,关联sys_user
SIdint11活动的外键,关联sys_product_seckill
PayStatusint2支付状态
CreateTimedatetime0订单创建时间

数据校验

一、vue中使用rules校验数据

1.使用

1.form中绑定rules
     <a-form :model="form" :rules="rules">

2.在要校验的item上设置prop属性,这里的prop是第三中的key(高版本的antdv用name代替了prop)
     <a-form-item label="邮箱地址" name="mail">    // 这里和下面的mail必须一致,不然获取不到值
           <a-input v-model:value="form.mail" placeholder="请输入正确的邮箱地址">

   注意:prop对应的不单单是rules规则里面的验证项,同时应该对应着我们form-item下的v-model的值  

3.在data中定义rules
     rules:{    // 这里的rules就是前面绑定的rules
        email:[{required: true,message: "必填",trigger: "blur"}]
      }

2.使用自定义验证器

1.定义验证器:新版的antdv返回Promise

let validateEMail = async(rule, value) => {
      const reg = /^([a-zA-Z0-9]+[-_.]?)+@[a-zA-Z0-9]+\.[a-z]+$/;
      if (value == "" || value == undefined || value == null) {
        // callback(new Error("请输入邮箱"));
        return Promise.reject((new Error("请输入邮箱")));
      } else {
        if (!reg.test(value)) {
          return Promise.reject((new Error("请输入正确的邮箱")));
        } else {
          return Promise.resolve();
        }
      }
    };

如果报错:Warning: callback is deprecated. Please return a promise instead则使用下面的



2.使用自定义验证器
  {required: true,validator: validateEMail,trigger: "blur"}


正则中的符号含义:
  + :匹配前面的子表达式一次或多次。要匹配 + 字符,请使用 \+。
  ?:匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?

3.表单提交的时候校验

1.form中加ref属性
    <a-form :model="form" :rules="rules" ref="form">
2.提交按钮传参,参数和前面的ref中的值一致
    <a-button type="primary" @click="onSubmit('form')">提交</a-button>

3.函数中校验
    sendMail(ruleForm) {
      alert(this.form.mail);
      this.$refs[ruleForm].validate().then(()=>{
        alert("校验通过");
      }).catch(() => {
        alert("校验不通过");
      })

常用校验正则:https://www.cnblogs.com/lieone/p/11856330.html

二、golang中验证邮箱

func VerifyEmailFormat(email string) bool {
      pattern := `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` //匹配电子邮箱
      reg := regexp.MustCompile(pattern)
      return reg.MatchString(email)
 }


 VerifyEmailFormat("12345@126.com")  // true

发送邮件

一、生成随机数

import (
    "fmt"
    "math/rand"
    "strings"
    "time"
)
func GenEmailCode(width int) string {
    numeric := [10]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    r := len(numeric)
    rand.Seed(time.Now().UnixNano())

    var sb strings.Builder
    for i := 0; i < width; i++ {
        fmt.Fprintf(&sb, "%d", numeric[ rand.Intn(r) ])
    }
    return sb.String()
}

二、发送邮件

// 使用beego下utils下的NewEMail
func SendEmail(to_email, msg string) {
        username := "2713058923@qq.com"    // 发送者的邮箱地址
        password := "xxx"      // 授权密码
        host := "smtp.qq.com"  // 邮件协议
        port := "587"          // 端口号

        emailConfig := fmt.Sprintf(`{"username":"%s","password":"%s","host":"%s","port":%s}`, username, password,host,port)
        fmt.Println("emailConfig", emailConfig)
        emailConn := utils.NewEMail(emailConfig)  // beego下的
        emailConn.From = strings.TrimSpace(from_email)
        emailConn.To = []string{strings.TrimSpace(to_email)}
        emailConn.Subject = "知了传课注册验证码"
        //注意这里我们发送给用户的是激活请求地址
        emailConn.Text = msg
        err := emailConn.Send()
        fmt.Println(err)
    }


使用:
// 生成六位随机数
session_email_code := utils.GenEmailCode(6)
// 消息内容
email_msg := fmt.Sprintf("您的注册验证码为:%s",session_email_code)
// 发送邮件
utils.EmailSend(mail, email_msg)


// 缓存邮件和随机数
"github.com/patrickmn/go-cache"

//初始化
c := cache.New(30*time.Second, 10*time.Second)
//使用
c.Set("Title", "Spring Festival", cache.DefaultExpiration)

jwt-token认证

一、什么是JWT?

JSON Web Token:是一种跨域认证解决方案,它规定了一种Token实现方式,多用于前后端分离等场景

二、为什么需要JWT?

1.前后端不分离的验证逻辑

  • 前端提交数据
  • 后端校验存session,通过后保存session,生成session_id标识
  • 服务端返回响应时将上一步的session_id写入用户浏览器的Cookie
  • 前端每次请求都会自动携带包含session_id的Cookie
  • 服务端通过请求中的session_id就能找到之前保存的该用户那份session数据

2.使用jwt认证

服务端完成登录校验后,会生成一个令牌(就是token)再发回给用户,用户后续请求只需要带上这个Token,服务端解密之后就能获取该用户的相关信息了。

三、使用JWT

使用jwt-go完成生成JWT和解析JWT的功能

go get github.com/dgrijalva/jwt-go

1.生成jwt

  • 定义结构体,这个就是要返回给前端的

        type FrontUserToken struct { 
          // jwt的匿名字段 
          jwt.StandardClaims
          // 要返回给前端的用户信息
          Username string `json:"user_name"`
          UserId int `json:"user_id"`
        }
    
  • 定义JWT过期时间

// 过期时间1小时,n小时的话 * n
const TokenExpireDuration = time.Hour
  • 定义加密的盐,生成token和解析的时候都需要用到,使用同一个
var TokenSecret = []byte("gin_vue_token")
  • 生成token
type UserToken struct {
    jwt.StandardClaims
    // 自定义的用户信息
    UserName string `json:"user_name"`

}

// 前端用户token过期时间
var FrontUserExpireDuration = time.Hour
var FrontUserSecretKey = []byte("front_user_token")


// 管理端用户token过期时间
var AdminUserExpireDuration = time.Hour * 2
var AdminUserSecretKey = []byte("admin_user_token")


// 生成token
func GenToken(UserName string,expireDuration time.Duration,secret_key []byte)  (string,error){

    user := UserToken{
        jwt.StandardClaims{
            // 现在 + 加上传的过期时间
            ExpiresAt:time.Now().Add(expireDuration).Unix(),
            Issuer:"micro_gin_vue",
        },
        UserName,

    }

    token := jwt.NewWithClaims(jwt.SigningMethodHS256,user)
    return token.SignedString(secret_key)

}

2.解析jwt

// 认证token
func AuthToken(tokenString string,secretKey []byte) (*UserToken, error){

    // 解析token
    token,err := jwt.ParseWithClaims(tokenString,&UserToken{}, func(token *jwt.Token) (key interface{}, err error) {
        return secretKey,nil
    })

    if err != nil {
        return nil,err
    }

    clasims,is_ok := token.Claims.(*UserToken)

    // 验证token
    if is_ok && token.Valid { // 正常的
        return clasims,nil
    }

    return nil,errors.New("token valid err")

}

3.中间件,在所有需要验证的路由中加上该解析,如果没有token则不让访问

func JwtTokenValid(ctx *gin.Context)  {

    auth_header := ctx.Request.Header.Get("Authorization")

    if auth_header == "" {
        ctx.JSON(http.StatusOK,gin.H{
            "code":401,
            "msg":"请携带token",
        })
        ctx.Abort()
        return
    }

    auths := strings.Split(auth_header," ")

    bearer := auths[0]
    token := auths[1]

    if len(token) == 0 || len(bearer) == 0 {
        ctx.JSON(http.StatusOK,gin.H{
            "code":401,
            "msg":"请携带正确格式的token",
        })
        ctx.Abort()
        return
    }

    user, err := utils.AuthToken(token,utils.AdminUserSecretKey)

    if err != nil {
        ctx.JSON(http.StatusOK,gin.H{
            "code":401,
            "msg":"无效的token",
        })
        ctx.Abort()
        return
    }

    ctx.Set("user_name",user.UserName)
    ctx.Next()

}

key:Authorization

value:token值

前端状态管理

一、自定义状态管理

const front_token_key = "front_token"
const front_user_key = "front_user_name"

const admin_token_key = "admin_token"
const admin_user_name_key = "admin_user_name"


class Auth {
    // 构造函数浏览器刷新的情况下从localStorage加载
    constructor(){
        this.token = localStorage.getItem(front_token_key)
        this.username = localStorage.getItem(front_user_key)
        this.admin_token = localStorage.getItem(admin_token_key)
        this.admin_user_name = localStorage.getItem(admin_user_name_key)

    }

    // 用户端存储信息
    setFrontAuth(token,username){
        this.token = token
        this.username = username

        // 用户端的管理端需要区分开
        localStorage.setItem(front_token_key,token)
        localStorage.setItem(front_user_key,username)


    }

    // 管理端存储信息
    setAdminAuth(admin_token,admin_user_name){
        this.admin_token = admin_token
        this.admin_user_name = admin_user_name

        localStorage.setItem(admin_token_key,admin_token)
        localStorage.setItem(admin_user_name_key,admin_user_name)

    }

    // 用户端清空缓存信息
    delFrontAuth(){
        this.token = null,
        this.username = null

    }

    // 管理端清空缓存信息
    delAdminAuth(){
        this.admin_token = null
        this.admin_user_name = null

    }
    // 也可以定义导航守卫
    is_authed(){
        if(this.token && this.username){
            return true
        }else{
            return false
        }
    }
}

// 导出,单例
const auth = new Auth()
export default auth


在main.js中定义全局变量


router.js中定义导航守卫
    // 定义导航守卫
    router.beforeEach((to, from, next) => {
      if (to.path === '/login' || to.path === '/register') {
        next('/');
      } else {
        let token = localStorage.getItem('token');

        if (token === 'null' || token === '') {
          next('/login');
        } else {
          next();
        }
      }
    });

二、使用vuex第三方库,适用于中大型项目

vue3.0不支持vuex,参考:https://www.codingsky.com/doc/2020/7/31/1024.html

1.vue2中使用vuex示例代码

npm install vuex --save

vuex/vuex.js中:
    import Vuex from 'vuex';

    const store = new Vuex.Store({
        state: {
            // 没有获取到设置为null
            token:localStorage.getItem('token')?localStorage.getItem('token') : null
            user:localStorage.getItem('user')?localStorage.getItem('user') : null

        },
        mutations: {
            setAuth(state,token,user){
                state.token = token;
                localStorage.setItem('token', user.Authorization);
            },
            delAuth(state){
                state.token = null;
                state.user = null;
                localStorage.removeItem("token")
                localStorage.removeItem("user")

            }

        }

    export default store



main.js中use下
    import vuex from './vuex/vuex.js'

    app.use(vuex);

    设置全局变量:
    app.config.globalProperties.$vuex = vuex


router.js中:
    // 定义导航守卫
    router.beforeEach((to, from, next) => {
      if (to.path === '/login') {
        next();
      } else {
        let token = localStorage.getItem('token');

        if (token === 'null' || token === '') {
          next('/login');
        } else {
          next();
        }
      }
    });

state:数据仓库,主要存储共享的数据,不是存储的过程,是存储的结果,

getters:获取数据

mutactions:存数据

actions:对数据先进行处理,再存储到仓库,也可以不用进行处理

*执行流程:*后端返回数据,使用actions先进行数据处理(也可以不处理),然后通过

mutation 把处理后的数据放入数据仓库state中,想使用数据就通过
getters从数据仓库state中取。

antdv分页

结合table使用分页

一、在table标签上加属性

 <a-table :pagination="users_pagenation">

二、在data中指定users_pagenation

front_users:[],
columns,
position:top,
users_pagenation:{
    current:1,
    pageSize:5,
    pageSizeOptions:['10','20','30'], // 可选每页显示几条
    showSizeChanger:true,
    total:11
}

三、绑定change事件

<a-table @change="chanPage">

四、定义chanPage事件

chanPage(pagination){
    this.users_pagenation.current = pagination.current
    this.users_pagenation.pageSize = pagination.pageSize
},

jmeter压测工具

一、介绍

1.免费的

2.使用简单

3.能满足大多数压测要求

二、环境准备及软件安装

1.jdk环境搭建

  • 环境配置:
    • Java_Home:jdk安装路径
    • %Java_Home%\bin;%Java_Home%\jre\bin;
  • 验证:
    • java -version

2.软件安装

  • 下载地址:http://jmeter.apache.org/download_jmeter.cgi,注意jdk版本对应上
  • 解压即可,不需要安装

3.启动:双击解压文件夹bin目录下的jmeter.bat,启动之后会有两个窗口,一个cmd窗口,一个JMeter的 GUI,不要使用GUI运行压力测试,GUI仅用于压力测试的创建和调试;执行压力测试请不要使用GUI。使用下面的命令来执行测试:

jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]

4.通过 【Options】->【Choose Language】变更为简体中文

5.修改字体大小

选项—>外观—>windows

  • 编辑bin目录下的jmeter.properties文件,修改jsyntaxtextarea.font.size的值,并将注释取消
  • 在jmeter.bat文件中添加如下代码
set JVM_ARGS=%JVM_ARGS% -Dswing.plaf.metal.controlFont=Dialog-20
set JVM_ARGS=%JVM_ARGS% -Dswing.plaf.metal.systemFont=Dialog-20
set JVM_ARGS=%JVM_ARGS% -Dswing.plaf.metal.userFont=SansSerif-20
set JVM_ARGS=%JVM_ARGS% -Dswing.plaf.metal.smallFont=SansSerif-20

三、创建测试

Jmeter-http接口测试添加步骤:

1.创建线程组

在左侧的"TestPlan"上右键 【添加】–>【Threads(Users)】–>【线程组】,设置线程数和循环次数。只设置这两个即可,比如1000的线程数,1次循环

2.配置元件

在我们刚刚创建的线程组上右键 【添加】–>【配置元件】–>【HTTP请求默认值】。只需要配置协议、地址和端口这三项即可,这样后面所有的请求都是基于现在的这个进行的,比如http://127.0.0.1:8080,后面的的请求只需要使用path即可

3.http请求

在“线程组”右键 【添加-】->【samlper:取样器】–>【HTTP 请求】设置我们需要测试的API的请求路径和数据。我这里是用的json

4.添加请求头

线程组上右键 【添加】–>【配置元件】–>【HTTP信息头管理器】

5.添加断言

线程组上右键 【添加】–>【断言】–>【响应断言】,根据响应的数据来判断请求是否正常。比如只根据状态码判断是否正常。

要测试的响应字段:响应代码

模式匹配规则:Equales

要测试的模式:200

错误提示信息:“出错啦!”

6.添加察看结果树

线程组上右键 【添加】–>【监听器】–>【察看结果树】。点击工具栏上的运行按钮就可以看到结果了

7.添加Summary Report

线程组上右键 【添加】–>【监听器】–>【Summary Report:汇总报告】。点击工具栏上的运行按钮就可以看到结果了

以上的测试计划已构建完整,点击左上角的报错按钮保存下

8.执行测试计划

cmd中执行:进入jmeter的bin目录,执行下面的命令

jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]

  • jmx file:测试计划文件路径
  • results file:测试结果文件路径
  • Path to web report folder:web报告保存路径

e.g.:jmeter -n -t [testplan/RedisLock.jmx] -l [testplan/result/result.txt] -e -o [testplan/webreport]

秒杀接口压测

一、需要的添加及使用的jmeter技术

1.需要满足的条件:

  • 第一步使用邮箱地址、密码登录获取到返回的token
  • 第二步携带token请求秒杀接口

需要邮箱地址、密码及返回的token

2.jmeter测试需要用到的技术

  • jmeter操作数据库,读取邮箱地址和密码
  • jmeter关联,使用第一步返回的token作为参数执行第二步,jmeter关联就可以保存这个token信息

二、jmeter操作数据库

1.下载mysql-connector-java-5.1.7-bin.jar,地址:https://dev.mysql.com/downloads/connector/j/,

  • 选择Platform Independent
  • 选择ZIP文件进行下载

2.解压,把里面的jar包放到jmeter的lib目录下

3.配置连接信息

  • 线程组右键添加“配置原件”–“JDBC Connection Configuration”
  • 线程组右键添加“samlper:取样器” – “JDBC Request”
  • 在TestPlan页面,点击浏览 ,将目录或jar添加到类路径 Add directory or jar to classpath。此处选择我们刚刚放在lib下的jar即可
  • JDBC Connection Configuration页面配置连接信息
    • 数据库:mysql
    • DriverName–>com.mysql.jdbc.Driver
    • URL–>jdbc:mysql://host:port/{dbname}?allowMultiQueries=true&serverTimezone=UTC
    • 用户名、密码

4.使用

在JDBC Request 页面

三、jmeter关联

1.添加关联

  • 在某个请求上右键添加”后置处理器“ – ”json提取器“
  • 设置
    • 响应字段:主体
    • 引用名称:token
    • 正则表达式:$.key1.key2

2.获取关联数据

  • parameters中获取:${token}

四、压测指标

  1. 压测前要明确压测功能和压测指标,一般需要确定的几个问题:
  2. 固定接口参数进行压测还是进行接口参数随机化压测?
  3. 要求支持多少并发数?
  4. TPS(每秒钟处理事务数)目标多少?响应时间要达到多少?
jdbc request:设置结果集存储的变量:user

BeanShell 后置处理器:

var email = vars.getObject("user").get(0).get("email");
vars.put("email",email.toString());

测试问题记录:

  • 并发查询怎么确保每个线程一个用户?使用计数器,不勾选“与每用户独立的跟踪计数器”选项
select email from front_user WHERE email not in (SELECT uemail from orders) limit ${user_offset},1
  • 相隔时间很小的时候,可能一个用户会下单两次都成功

五、需要明确的问题

有错误率同开发确认,确定是否允许错误的发生或者错误率允许在多大的范围内;

5.1、Throughput吞吐量每秒请求的数大于并发数,则可以慢慢的往上面增加;

若在压测的机器性能很好的情况下,出现吞吐量小于并发数,说明并发数不能再增加了,可以慢慢的往下减,找到最佳的并发数;压测结束,登陆相应的web服务器查看CPU等性能指标,进行数据的分析;

5.2、最大的tps:不断的增加并发数,加到tps达到一定值开始出现下降,那么那个值就是最大的tps。

5.3、最大的并发数:最大的并发数和最大的tps是不同的概率,一般不断增加并发数,达到一个值后,服务器出现请求超时,则可认为该值为最大的并发数。

**5.4、**压测过程出现性能瓶颈,若压力机任务管理器查看到的cpu、网络和cpu都正常,未达到90%以上,则可以说明服务器有问题,压力机没有问题。

5.5、影响性能考虑点包括:数据库、应用程序、中间件(tomact、Nginx)、网络操作系统等方面。

秒杀优化

一、前端优化

可以使用静态化的方式,把常用的资源加载到缓存中,不要经常访问后端,减轻一部分服务器压力

二、后端优化

1.秒杀功能独立,不受其他接口的影响,这里我们使用的微服务,秒杀是单独的服务

2.秒杀数量限制(限流):每隔一段时间发放一个有效的抢购

3.消息队列:减小数据库压力,生产者生产任务存放在队列中,消费者从队列中取任务消费,可以有效降低数据库压力

消息队列的几种模式:

简单模式:生产者生产任务放到队列中,消费者从队列中取任务消费

工作模式:生产者生产任务放到队列中,多个消费者从队列中取任务消费

订阅模式:

路由模式:

话题模式:

RPC模式:

rabbitmq

一、环境安装

1.windows安装:https://www.cnblogs.com/JustinLau/p/11738511.html

2.linux安装

安装:sudo apt-get install rabbitmq-server

查看状态:service rabbitmq-server status / systemctl status rabbitmq-server

开放端口:

  • 15672:管理后台
  • 5672:连接服务的端口

3.web可视化配置:

  • sudo rabbitmq-plugins enable rabbitmq_management
  • service rabbitmq-server restart # 重启服务
  • 访问:
    • 本地:localhost:15672 用户名和密码都是guest,只适用于本地访问
    • 其他机器访问: ip:15672 必须得创建用户并授权

4.配置用户及授权

查看用户:sudo rabbitmqctl list_users

添加用户:sudo rabbitmqctl add_user admin(用户名) admin(密码)

授权:sudo rabbitmqctl set_user_tags admin(用户名) administrator(角色)

  • administrator(超级管理员)
  • monitoring(监控者):可以查看rabbitmq节点的相关信息
  • policymaker(策略制定者):无法查看节点的相关信息
  • management(普通管理者):无法看到节点信息,也无法对策略进行管理
  • none(其他):无法登陆管理控制台,通常就是普通的生产者和消费者

5.rabbitmqctl服务的使用

查看当前用户列表:sudo rabbitmqctl list_users

添加用户:sudo rabbitmqctl add_user admin(用户名) admin(密码)

删除用户:sudo rabbitmqctl delete_user admin(用户名)

修改用户密码:sudo rabbitmqctl change_password admin(用户名) admin1(新密码)

设置vhost:

  1. sudo rabbitmqctl add_vhost /myvhost
  2. sudo rabbitmqctl set_permissions -p /myvhost myuser “." ".” “.*”

vhost说明:virtual host相当于一个单独的rabbitmq服务器,每个virtual是独立的,不可互通的,相当于mysql中的数据库,都是独立的,可以单独设置权限,Virtual Name一般以/开头

6.几种交换机

  • direct:直连,通过routingKey和exchange决定的那个唯一的queue可以接收消息
    • routing_key和exchange对应起来
  • fanout:发布/订阅,所有bind到此exchange的queue都可以接收消息
    • routing_key可以省略
  • topic:和direct类似,但是在匹配规则上进行了扩展,支持通配符的方式
  • headers:通过headers 来决定把消息发给哪些queue

二、go使用rabbitmq

1.下载第三方库:go get github.com/streadway/amqp

2.连接

// 连接rabbitmq
conn,_ := amqp.Dial("amqp://用户名:密码@IP:端口号")   // 端口号:5672
defer conn.close

// 打开通道
ch, err := conn.Channel()
defer ch.Close()

// 声明队列
queue,err_q := ch.QueueDeclare("mysql_queue",false,false,false,false,nil)
fmt.Println(err_q)

// 生产任务:生产者
ch.Publish("",queue.Name,false,false,amqp.Publishing{
    ContentType:"text/plain",
    Body:[]byte("hello world"),
})

// 消费者
msgs,err_c := ch.Consume("mysql_queue","my_consumer",false,false,false,false,nil)
fmt.Println(err_c)

for msg := range msgs{  // chan类型
    // DeliveryTag:唯一标识
    fmt.Println(msg.DeliveryTag,string(msg.Body))
}

3.流程

  • 生产者创建channel发送消息到交换机
  • 交换机根据bind队列将消息发送到队列
  • 队列存储消息并负责分发消息到消费者
  • 消费者使用channel获取消息并消费,也可以选择拒收消息,消息重新打回队列,在规定时间后重试

4.确保服务器重启不会清空队列

// 1.创建队列设置持久化:durable表示是否持久化
queue,err_q := ch.QueueDeclare("my_queue",true,false,false,false,nil)

// 2.生产者设置持久化,DeliveryMode

// 3.消费者持久化:如果生产者这边设置了持久化,那么消费者同样也需要设置成持久化。
amqp.Publishing{
        ContentType:"text/plain",
        Body:[]byte("hello world"),
        DeliveryMode:amqp.Persistent,
    }

5.消息确认机制

// 消费者消费的时候会出现两种情况,消费完成和消费失败,消费失败的要再次回到队列,重新分配

// 1.在消费的时候autoAck设置未false,表示不自动确认,当消息消费失败会再次放到队列

// 2.消费成功后手动确认,这样就会从队列中删减掉改任务,不会重复执行
deliveries,err_c := ch.Consume("my_queue","my_consumer",false,false,false,false,nil)
fmt.Println(err_c)

for delivery := range deliveries{
    delivery.Ack(true)
    // delivery.Ack(false)  // 失败重新放入队列中,注意这里需要加延迟时间,不然接收到的消息还是这个失败的,
        // 可以加重试次数,使用缓存计数的方式
    // delivery.Ack(true)  // 拒绝接收并且重新放回队列

}

6.使用交换机

// 创建两个队列
queue1,err_q1 := ch.QueueDeclare("first_queue",true,false,false,false,nil)
queue2,err_q2 := ch.QueueDeclare("second_queue",true,false,false,false,nil)

// 创建两个交换机
err1 := ch.ExchangeDeclarePassive("frist_exchange","direct",true,false,false,false,nil)
err2 := ch.ExchangeDeclarePassive("second_exchange","direct",true,false,false,false,nil)

// queue和交换机绑定
err3 := ch.QueueBind(queue.Name,"frist_routingKey","frist_exchange",false,nil)
err4 := ch.QueueBind(queue.Name,"second_routingKey","second_exchange",false,nil)
// 第一个参数是队列名称,第二个参数是routingKey,第三个参数是交换机名称

// 生产者:第一个参数是交换机名称,第二个参数routingKey
err_p := ch.Publish("frist_exchange","frist_routingKey",false,false,amqp.Publishing{
        ContentType:"text/plain",
        Body:[]byte("hello world"),
        DeliveryMode:amqp.Persistent,
})

// 消费者:使用queue name
deliveries,err_c := ch.Consume("my_queue","my_consumer",false,false,false,false,nil)

7.限流:确保消费者每次只能消费一个任务,消费完成后再分配任务,ack后再继续接收任务

第一种方式:

//设置每次从消息队列获取任务的数量
err = ch.Qos(
    1,    //预取任务数量,这里可以理解为线程的数量
    0,    //预取大小
    false,    //全局设置
)

if err != nil {
    //无法设置Qos
    return err
}

8.notify确认

生产者的任务是否成功入列

// 在publish之前
ret := <- ch.NotifyReturn(make(chan amqp.Return))
if (string(ret.Body) != ""){
    // 获得body重新发,注意这里得需要异步执行,不然会卡死:ret.Body
}

参数说明:

创建队列:


创建交换机:


生产者:

exchange:交换机名称
key:routingKey
mandatory:如果生产者生产的任务没有正常进入队列中,设置为true会返还给生产者,设置为false会直接丢弃
immediate:
msg:发送的消息,amqp.Publishing类型的数据

消费者:

queue:队列名称
consumer:消费者名称
autoAck:自动确认
exclusive:
noLocal:
noWait:
args:参数

redis的使用

下载第三方库:github.com/garyburd/redigo/redis

一、连接redis

conn,err := redis.Dial("tcp","10.1.210.69:6379")

defer conn.Close()

二、设置过期时间

设置key过期时间
conn.Do("expire", "name", 10) //10秒过期

二、设置key、value

conn.Do("SET", "name", "hallen")

三、获取key对应的值

redis.String(conn.Do("GET", "name"))

git版本控制

gitlab

一、介绍

二、安装服务

1.安装依赖包

sudo apt-get update
sudo apt-get install -y curl openssh-server ca-certificates

2.邮件配置

sudo apt-get install -y postfix

选择Internet site

3.添加镜像

curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh | sudo bash

4.安装gitlab

sudo apt-get install gitlab-ce   /gitlab-ee


如果报错:
    Unable to fetch some archives, maybe run apt-get update or try with --fix-missing?
执行:
    sudo apt-get update
    sudo apt-get upgrade

出现如下说明安装成功了img

5.修改访问路径及端口号,端口号必须开放,否则访问不了

sudo vim /etc/gitlab/gitlab.rb

external_url 修改为:http://ip:端口号
unicorn['port'] = 8080修改为自己的备用端口号,不能和上面的重复

6.更新配置

sudo gitlab-ctl reconfigure

7.重启服务

sudo gitlab-ctl restart

8.打开 sshd 和 postfix 服务

service sshd start
service postfix start

9.查看状态

sudo gitlab-ctl status

访问:http://ip:端口号

第一次访问会默认以root管理员用户登陆,需要输入两遍密码

10.本地设置git的用户名密码

git config --global user.name "hallen"
git config --global user.email "1277405413@qq.com"

11.访问502错误

  • 内存不够,至少需要2G内存
  • 端口被占用:netstat -ntpl
  • gitlab占用内存太多,导致服务器崩溃。尤其是使用阿里云服务器最容易出现502
    • swap

查看日志:sudo gitlab-ctl tail -f unicorn

初始化项目

一、仓库已有项目,本地怎么拉取

git clone 地址

二、本地已有项目,怎么提交到远程

初始化仓库:git init
// 手动的为你的远程仓库的地址在本地起一个别名:
git remote add origin 仓库地址
// 从远程分支拉取master分支并与本地master分支合并
git pull origin master:master
//提交本地分支到远程分支
git push -u origin master
// 提交本地代码:
git add -A
git commit -m ''
git push --set-upstream origin master

gin项目部署

windows部署

window上部署:bee pack -be GOOS=windows

  • 进入到项目目录,执行:bee pack -be GOOS=windows
    • 如果发生错误:
      • SET CGO_ENABLED=0
      • SET GOOS=windows
      • SET GOARCH=amd64
      • bee pack -be GOOS=windows
    • 将打包好的项目包拷贝到要存放的路径下,解压
    • 安装nssm服务管理工具: 支持Windows 7, Windows 8 and Windows 10
      • 管理员身份打开cmd,进入到nssm软件存放exe文件的目录
      • nssm install servicename就是你要添加的服务的名称
      • 然后在弹出的框中选择第二步解压文件夹中的exe文件
    • 开始中搜服务,找到 ,启动即可
    • 修改服务的指向路径:
      • 1.进入服务,查看路径,【开始】=>【运行】=>【services.msc】
      • 2.进入注册表,修改服务路径【开始】=>【运行】=>【regedit】,打开HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\【服务名称】,找到要修改的服务名称,然后修改【ImagePath】中数据值即可

vue项目部署

linux上部署

一、独立部署

1.进入项目目录使用bee工具打包

bee pack -be GOOS=linux

2.将打包文件放在linux中

安装:sudo apt-get install lrzsz

3.修改权限

chmod 777 -R /home/go

4.进入到该目录

nohup 命令启动:nohup ./项目名称 &
指定日志路径:nohup ./项目名称 >run.log 2>&1 &

二、supervisor部署

1.安装supervisor

sudo apt-get install supervisor

2.创建配置文件

1.在/etc/supervisor/conf.d目录下新建文件:supervisord.conf
2.配置内容如下:
    [program:zhiliao_web]
    # 项目文件夹
    directory = /home/go/src/zhiliao_web
    # 项目可执行文件位置
    command = /home/go/src/zhiliao_web/zhiliao_web
    autostart = true
    startsecs = 5
    user = root
    redirect_stderr = true
    # 输出日志文件的位置
    stdout_logfile = /home/go/src/zhiliao_web/supervisor.log

    port=127.0.0.1:9000
    #登录web用的用户名和密码
    username=user
    password=admin

3.supervisor命令

  • 启动supervisor服务:supervisord -c /etc/supervisor/supervisord.conf
  • 如果提示有程序已经在运行,先把服务停了:systemctl stop supervisor.service
  • 重启supervisord:supervisorctl reload
  • 进入supervisor客户端:supervisorctlstart program_namestop xxxrestart xxx

三、vue项目部署

1.下载node文件

uname -a查看系统位数(x86_64表示64位系统, i686 i386表示32位系统)

https://nodejs.org/en/download/ 下载对应位数的编译好的文件

将文件放到linux服务器并解压

解压:tar -xvf   node-v6.10.0-linux-x64.tar.xz  
重命名(软连接要用):mv node-v6.10.0-linux-x64  nodejs

2.建立软连接

npm:ln -s /home/hallen4/go/node/node-v14.15.1/bin/npm /usr/local/bin/
node:ln -s /home/hallen4/go/node/node-v14.15.1/bin/node /usr/local/bin/

3.验证

node -v

4.linux文件监听限额
cd /proc/sys/fs/inotify/

临时限额:

sudo sysctl fs.inotify.max_user_watches = 524288 
sudo sysctl -p

永久限额

echo fs.inotify.max_user_watches = 524288 | sudo tee -a /etc/sysctl.conf 
sudo sysctl -p

1.在/etc/supervisor/conf.d目录下新建文件:supervisord.conf
2.配置内容如下:
[program:zhiliao_web]
# 项目文件夹
directory = /home/go/src/zhiliao_web
# 项目可执行文件位置
command = /home/go/src/zhiliao_web/zhiliao_web
autostart = true
startsecs = 5
user = root
redirect_stderr = true
# 输出日志文件的位置
stdout_logfile = /home/go/src/zhiliao_web/supervisor.log

port=127.0.0.1:9000
#登录web用的用户名和密码
username=user
password=admin

3.supervisor命令

- 启动supervisor服务:supervisord -c /etc/supervisor/supervisord.conf
- 如果提示有程序已经在运行,先把服务停了:systemctl stop supervisor.service
- 重启supervisord:supervisorctl reload
- 进入supervisor客户端:supervisorctlstart program_namestop xxxrestart xxx

## 三、vue项目部署

1.下载node文件

uname -a查看系统位数(x86_64表示64位系统, i686 i386表示32位系统)

[https://nodejs.org/en/download/ ](https://nodejs.org/en/download/下载对应位数的编译好的文件)下载对应位数的编译好的文件

将文件放到linux服务器并解压

解压:tar -xvf node-v6.10.0-linux-x64.tar.xz
重命名(软连接要用):mv node-v6.10.0-linux-x64 nodejs


2.建立软连接

npm:ln -s /home/hallen4/go/node/node-v14.15.1/bin/npm /usr/local/bin/
node:ln -s /home/hallen4/go/node/node-v14.15.1/bin/node /usr/local/bin/


3.验证

node -v


4.linux文件监听限额
cd /proc/sys/fs/inotify/

临时限额:

sudo sysctl fs.inotify.max_user_watches = 524288
sudo sysctl -p


永久限额

echo fs.inotify.max_user_watches = 524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐