Rao's Blog

  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

数据库自治运维,不是DBA你也可以!

发表于 2021-05-12 | 更新于 2021-05-19 | 分类于 数据库

业务挑战及运维痛点

数据库是所有系统的基座,开发运维人员每天都需要对数据库进行操作或者查询,但是使用人员对数据库的了解程度参差不齐,数据库的稳定性也不断受到如下问题的挑战:

  • 业务快速迭代,数据库故障频发。

    • 随着数据量的增长,产生了大量慢 SQL,性能问题严重;
    • 表结构或者索引设计不当,频繁出现死锁;
    • CPU 和内存利用率突然飙满等;
  • 缺少专业 DBA,问题诊断解决困难。

  • 管理成本高:随着云的普及,将数据库部署在不同的环境中,多环境和多种数据库的管理挑战也随之而来。

  • 安全风险大:随着数据价值的提升,面临着越来越多内外部的风险,数据泄漏、数据丢失等问题层出不穷。

平台能力

智家数据库运维平台是基于公有云能力,集成在开发者平台,提供数据库一站式运维管理,核心能力包括:

  • 降低数据库运维门槛:无需 DBA 背景,开发可以使用平台自主解决数据库典型问题。
  • 多环境统一管理:云上云下数据库,统一管理、统一监控、统一告警。
  • 支持多种数据库:MySQL / MariaDB、Redis、MongoDB、PostgreSQL 等。
  • 保障数据库安全:基于数据库自治服务 DAS,提供高危 SQL 识别、SQL 注入检测、新增访问来源识别、敏感数据访问发现等服务;基于数据库安全登陆 DMS,提供操作安全审计能力,有效保障数据库安全。

典型场景 & 解决方案

场景一、紧急故障快速诊断及处理

  • 通过平台提供的自治入口,进入自治服务;
  • 查看实时会话,通过执行时间(单位是S)和状态(非Sleep)两个指标判断是否有会话阻塞;
  • 根据紧急程度,采取不同措施:状况紧急,直接结束会话;状况不紧急,点击优化分析 SQL 阻塞原因。

场景二、性能监控及告警处理

  • 监控指标:CPU 使用率、内存使用率、TPS/IOPS、磁盘空间、连接数、网络流量。
  • 告警配置:告警模板有 6 种告警,区分阈值和事件两种类型,支持钉钉/邮件/短信三种通知方式。
  • 告警处理:日常告警会同时通知 DBA 和业务,接收到告警应该进入平台及时查看监控和会话信息进行处理。

告警项 告警规则 发送间隔 告警类型 通知方式
数据库无法连接 连续 3 次则告警 30分钟 事件告警 短信 / 邮件 / 钉钉
CPU使用率(%) 连续 1 次总是大于等于 90 则告警 30分钟 阈值 短信 / 邮件 / 钉钉
内存使用率(%) 连续 1 次总是大于等于 95 则告警 30分钟 阈值 短信 / 邮件 / 钉钉
活跃连接数 连续 3 次总是大于等于 100 则告警 30分钟 阈值 短信 / 邮件 / 钉钉
磁盘使用率(%) 连续 3 次总是大于等于 90 则告警 30分钟 阈值 短信 / 邮件 / 钉钉
MySQL复制中断 连续 3 次则告警 30分钟 事件告警 短信 / 邮件 / 钉钉

场景三、慢日志抓取及 SQL 优化

  • 慢日志的查看和 SQL 优化如下图案例所示。
  • 优化策略:优先关注 Top10 执行次数和执行时间都较高的 SQL。
  • 优化方法:系统诊断推荐 + 优化 Explain 执行计划。

场景四、死锁诊断分析

  • 诊断:平台自治 -> 锁分析 -> 立即诊断。
  • 分析:根据诊断信息分析,处理方式一般有三种:拆分事务、添加索引、调整业务 SQL 执行顺序一致性。

场景五、安全管控

数据安全一般有三级保障策略:

  • 事前权限管控:白名单限制、授权粒度最小化原则。
  • 事中安全审计:操作审计、高风险 SQL 审计、SQL 注入等。
  • 事后备份恢复:海飞等第三方合作方。

如何接入平台?

如果需要接入平台,需要提供服务器登陆账号和数据库连接账号,详细咨询可联系杨楠 19028862。

HTTP应用:写一个完整的博客后端

发表于 2020-08-20 | 更新于 2020-08-24 | 分类于 Go

快速启动

  • 初始化项目 & 安装 gin
1
2
go mod init github.com/go-programming-tour-book/blog-service
go get -u github.com/gin-gonic/gin@v1.6.3
1
2
3
4
5
6
7
8
9
10
11
package main

import "github.com/gin-gonic/gin"

func main(){
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
r.Run()
}
  • 验证
1
2
$ curl http://127.0.0.1:8080/ping
{"message":"pong"}

后端技术

技术 版本 说明
gin v1.6.3 web 框架
viper v1.7.1 配置文件解析库
gorm v1.9.16 ORM 框架

项目设计

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.
├── config
├── docs
├── global
├── internal
│   ├── dao
│   ├── middleware
│   ├── model
│   ├── routers
│   └── service
├── main.go
├── pkg
├── scripts
├── storage
├── third_party
└── go.mod

数据库

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
37
38
39
40
41
CREATE TABLE `blog_tag` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT '' COMMENT '标签名称',
`state` tinyint(3) unsigned DEFAULT '1' COMMENT '状态 0为禁用、1为启用',
`created_on` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
`created_by` varchar(100) DEFAULT '' COMMENT '创建人',
`modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改时间',
`modified_by` varchar(100) DEFAULT '' COMMENT '修改人',
`deleted_on` int(10) unsigned DEFAULT '0' COMMENT '删除时间',
`is_del` tinyint(3) DEFAULT '0' COMMENT '是否删除,0为未删除,1为已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='文章标签管理'

CREATE TABLE `blog_article` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(100) DEFAULT '' COMMENT '文章标题',
`desc` varchar(255) DEFAULT '' COMMENT '文章简述',
`content` longtext COMMENT '文章内容',
`cover_image_url` varchar(255) DEFAULT '' COMMENT '封面图片地址',
`state` tinyint(3) unsigned DEFAULT '1' COMMENT '状态 0 为禁用、1 为启用',
`created_on` int(11) DEFAULT NULL COMMENT '创建时间',
`created_by` varchar(100) DEFAULT '' COMMENT '创建人',
`modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改时间',
`modified_by` varchar(255) DEFAULT '' COMMENT '修改人',
`deleted_on` int(10) unsigned DEFAULT '0' COMMENT '删除时间',
`is_del` tinyint(3) unsigned DEFAULT '0' COMMENT '是否删除,0为未删除,1为已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='文章管理'

CREATE TABLE `blog_article_tag` (
`id` int(10) NOT NULL,
`article_id` int(11) NOT NULL COMMENT '文章ID',
`tag_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '标签ID',
`created_on` int(10) unsigned DEFAULT '0' COMMENT '创建时间',
`created_by` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建人',
`modified_on` int(10) unsigned DEFAULT '0' COMMENT '修改时间',
`modified_by` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '修改人',
`deleted_on` int(10) unsigned DEFAULT '0' COMMENT '删除时间',
`is_del` tinyint(3) unsigned DEFAULT '0' COMMENT '是否删除,0为未删除,1为已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

model

model.go

1
2
3
4
5
6
7
8
9
10
11
package model

type Model struct {
ID uint32 `gorm:"primary_key" json:"id"`
CreatedBy string `json:"created_by"`
ModifiedBy string `json:"modified_by"`
CreatedOn uint32 `json:"created_on"`
ModifiedOn uint32 `json:"modified_on"`
DeletedOn uint32 `json:"deleted_on"`
IsDel uint8 `json:"is_del"`
}

tag.go

1
2
3
4
5
6
7
8
9
10
11
package model

type Tag struct {
*Model
Name string `json:"name"`
State uint8 `json:"state"`
}

func (t Tag) TableName() string {
return "blog_tag"
}

article.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package model

type Article struct {
*Model
Title string `json:"title"`
Desc string `json:"desc"`
Content string `json:"content"`
CoverImageUrl string `json:"cover_image_url"`
State uint8 `json:"state"`
}

func (a Article) TableName() string {
return "blog_article"
}

article_tag.go

1
2
3
4
5
6
7
8
9
10
11
package model

type ArticleTag struct {
*Model
TagID uint32 `json:"tag_id"`
ArticleID uint32 `json:"article_id"`
}

func (a ArticleTag) TableName() string {
return "blog_article_tag"
}

接口设计

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
package routers

import (
"github.com/gin-gonic/gin"
"github.com/go-programming-tour-book/blog-service/internal/routers/api/v1"
)

func NewRouter() *gin.Engine {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())

article := v1.NewArticle()
tag := v1.NewTag()

v1 := r.Group("/api/v1")
{
//标签管理
v1.POST("/tags", tag.Create)
v1.GET("/tags", tag.List)
v1.PUT("/tags/:id", tag.Update)
v1.DELETE("/tags/:id", tag.Delete)
v1.PATCH("/tags/:id/state", tag.Update)

//文章管理
v1.POST("/articles", article.Create)
v1.GET("/articles", article.List)
v1.GET("/articles/:id", article.Get)
v1.PUT("/articles/:id", article.Update)
v1.DELETE("/articles/:id", article.Delete)
v1.PATCH("/articles/:id/state", article.Update)
}

return r
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package v1

import "github.com/gin-gonic/gin"

type Article struct {
}

func NewArticle() Article {
return Article{}
}

func (a Article) Get(c *gin.Context) {}
func (a Article) List(c *gin.Context) {}
func (a Article) Create(c *gin.Context) {}
func (a Article) Update(c *gin.Context) {}
func (a Article) Delete(c *gin.Context) {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package v1

import "github.com/gin-gonic/gin"

type Tag struct {
}

func NewTag() Tag {
return Tag{}
}

func (t Tag) Get(c *gin.Context) {}
func (t Tag) List(c *gin.Context) {}
func (t Tag) Create(c *gin.Context) {}
func (t Tag) Update(c *gin.Context) {}
func (t Tag) Delete(c *gin.Context) {}

公共组件

  • 错误码标准化
1
2
3
4
5
6
7
8
9
10
11
var (
Success = NewError(0, "成功")
ServerError = NewError(100000, "内部服务器错误")
InvalidParams = NewError(100001, "入参错误")
NotFound = NewError(100002, "找不大")
UnauthorizedAuthNotExist = NewError(100003, "鉴权失败,找不到对应的 AppKey 和 AppSecret")
UnauthorizedTokenError = NewError(100004, "鉴权失败,Token 错误")
UnauthorizedTokenTimeout = NewError(100005, "鉴权失败,Token 超时")
UnauthorizedTokenGenerate = NewError(100006, "鉴权失败,Token 生成失败")
TooManyRequests = NewError(100007, "请求过多")
)
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package errcode

import (
"fmt"
"net/http"
)

// 错误的响应结果
type Error struct {
code int `json:"code"`
msg string `json:"msg"`
details []string `json:"details"`
}

// 全局错误码的存储载体
var codes = map[int]string{}

func NewError(code int, msg string) *Error {
if _, ok := codes[code]; ok {
panic(fmt.Sprintf("错误码 %d 已经存在,请更换一个", code))
}
codes[code] = msg
return &Error{code: code, msg: msg}
}

func (e *Error) Error() string {
return fmt.Sprintf("错误码:%d,错误信息:%s", e.code, e.msg)
}

func (e *Error) Code() int {
return e.code
}

func (e *Error) Msg() string {
return e.msg
}

func (e *Error) Msgf(args []interface{}) string {
return fmt.Sprintf(e.msg, args...)
}

func (e *Error) Details() []string {
return e.details
}

func (e *Error) WithDetails(details ...string) *Error {
newError := *e
newError.details = []string{}
for _, d := range details {
newError.details = append(newError.details, d)
}
return &newError
}

// 针对一些特定错误码进行状态码的转换
func (e *Error) StatusCode() int {
switch e.Code() {
case Success.Code():
return http.StatusOK
case ServerError.Code():
return http.StatusInternalServerError
case InvalidParams.Code():
return http.StatusBadRequest
case UnauthorizedAuthNotExist.Code():
fallthrough
case UnauthorizedTokenError.Code():
fallthrough
case UnauthorizedTokenGenerate.Code():
fallthrough
case UnauthorizedTokenTimeout.Code():
return http.StatusUnauthorized
case TooManyRequests.Code():
return http.StatusTooManyRequests
}
return http.StatusInternalServerError
}
  • 配置管理
1
2
export GOPROXY=https://goproxy.cn
go get -u github.com/spf13/viper@v1.4.0
  • 数据库连接
  • 日志写入
1
2
export GOPROXY=https://goproxy.cn
go get -u gopkg.in/natefinch/lumberjack.v2
  • 响应处理

从 0 到 1 解析技术架构平台

发表于 2020-08-19 | 更新于 2020-08-24 | 分类于 项目

系统简介

技术架构平台,提供系统全链路可用性监控和告警,服务器、应用、数据库等资源管理,应用打包部署,服务全生命周期管理及治理,是支撑 PSI 软件开发一站式平台,核心目标是让系统开发运维更便捷高效。

功能概览

  • 概览:近24小时可用性拨测、PV/UV、告警概览、资源概览、容器云资源概览、公告、FAQ

  • 监控

    • 监控面板
    • 云拨测
    • 站点统计
  • 告警

    • 当前告警
    • 自定义事件
    • 告警规则
    • 通知组
    • 告警备案
    • 历史告警
  • 应用分区

  • 应用

    • 全部应用
    • 无状态后端应用:容器云部署、服务器部署
    • 无状态前端应用:容器云部署、服务器部署
    • 定时任务
  • 持续集成:打包

  • 链路追踪

    • 追踪
    • 仪表盘
    • 拓扑图
    • 接入
    • 统计
  • 数据库

    • 阿里云 RDS
    • 自建 MySQL
    • Redis
  • 日志服务

    • 日志搜索
    • 日志下载
  • 容器镜像

    • 私有镜像
    • 公有镜像
  • 服务器

  • 阿里云费用:账单明细

  • 设置

    • 项目配置
    • 用户组
    • 操作日志
    • 个人设置
  • 工单

  • 文档

数据库表

  • console_db_mysql:自建 MySQL 基本信息表
  • console_db_mysql_backup:自建 MySQL 备份信息表
  • console_db_rds:阿里云 RDS 基本信息表

相关组件

YAPI:可视化 API 接口管理平台

Sentry:前端错误监控系统

SkyWalking:APM (应用性能管理) 工具,包括指标监控、分布式追踪、分布式系统性能诊断等功能。demo

前期准备

  • IDE:
    • Goland,激活方法
    • Visual Studio Code
  • GIT:
    • 教程
    • Pro Git
  • 代码:
    • cmdb:后端代码
    • aliyun-adaptor:与阿里云服务有交互的服务代码
    • docs:控制台文档
    • ui:前端代码

项目下载和运行

  • UI
1
2
3
4
5
6
7
8
9
# clone代码
git clone https://git.haier.net/console/ui.git
cd ui

# 安装依赖
npm install --registry=http://10.138.16.188:4873

# 开发模式运行
npm run serve
  • CMDB
1
2
# 环境变量
DB_MYSQL=console:suMuCaSu1e@tcp(10.138.228.243:3306)/console?parseTime=true&loc=Local;COMPASS_USER=admin;COMPASS_PASSWORD=Pwd123456;COMPASS_ADDRESS=10.135.7.71:6002

技术选型

前端技术

技术 版本 说明
Vue 2.5.22 前端框架
Vue-router 3.0.1 路由框架
Vuex 3.1.0 全局状态管理框架
Ant Design of Vue 1.5.3 前端 UI 框架
Axios 0.19.0 前端 HTTP 框架
Viser 2.3.3 前端图表框架

后端技术

技术 版本 说明
gin v1.5.0 HTTP Web 开发框架

代码结构

aliyun-adaptor

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
.
├── client // 阿里云各服务sdk客户端
│   └── aliyun_client.go
├── cmd // main函数入口
│   └── main.go
├── conf // yaml配置文件及解析
│   ├── config.go
│   ├── config.yml
│   └── config_test.go
├── db // 数据库连接池
│   ├── errors.go
│   └── init.go
├── docs // swagger文档
│   ├── docs.go
│   ├── swagger.json
│   └── swagger.yaml
├── models // 数据结构
│   ├── account.go
│   ├── account_manager.go
│   ├── aliyun.go
│   ├── database.go
│   ├── excel.go
│   └── resp.go
├── pkg // 业务逻辑
│   ├── aliyun_account
│   │   ├── account
│   │   │   ├── account_db.go
│   │   │   └── account_db_test.go
│   │   ├── account_manager
│   │   │   ├── excel.go
│   │   │   ├── manager.go
│   │   │   ├── manager_db.go
│   │   │   └── manager_test.go
│   │   ├── account_sync
│   │   │   ├── account_sync.go
│   │   │   └── aliyun_sdk.go
│   │   ├── account_test.go
│   │   └── excel
│   │   ├── excel.go
│   │   ├── excel_db.go
│   │   ├── excel_sync.go
│   │   └── oss_client.go
│   ├── aliyun_mysql // 自建MySQL
│   │   ├── mysql.go
│   │   └── mysql_test.go
│   ├── aliyun_rds // 阿里云RDS
│   │   ├── rds.go
│   │   └── rds_test.go
│   ├── aliyun_redis // Redis
│   │   ├── prometheustool.go
│   │   ├── redis.go
│   │   └── redis_test.go
│   ├── cmdb
│   │   ├── cmdb_client.go
│   │   └── cmdb_test.go
│   ├── compass // 容器云
│   │   ├── compass.go
│   │   ├── compass_db.go
│   │   └── compass_test.go
│   └── mail // 邮件
│   ├── email.go
│   ├── excel.go
│   ├── mail_db.go
│   ├── sendmail.go
│   ├── sendmail_test.go
│   ├── template.go
│   └── templates
│   └── template.html
├── router // 路由
│   ├── api
│   │   └── v1
│   │   ├── account.go
│   │   ├── account_manager.go
│   │   ├── mysql.go
│   │   ├── rds.go
│   │   ├── redis.go
│   │   └── v1.go
│   ├── app
│   │   └── gin.go
│   └── router.go
└── task // 定时任务
├── account_data
│   └── main.go
└── create_excel
└── main.go
├── Dockerfile // 构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明
├── Dockerfile.account_data // 构建定时账单数据的镜像文件
├── Dockerfile.create_excel // 构建excel的镜像文件
└── go.mod // 包管理工具

学习资料

  • go 基础教程
  • go 进阶教程

阿里云 · DBS

发表于 2020-08-08 | 更新于 2020-09-04 | 分类于 阿里云

DBS 简介

安装备份网关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 检查java环境
java -version

# 安装网关
cd /opt
wget -O aliyunDBSAgentInstaller.jar https://aliyun-dbs-cn-qingdao.oss-cn-qingdao.aliyuncs.com/installer/0.0.83/aliyunDBSAgentInstaller-0.0.83.jar && sudo java -Dregion=cn-qingdao -jar aliyunDBSAgentInstaller.jar

# 创建备份账号
GRANT all privileges on *.* to 'backup'@'%' identified by 'Backup@123!';

# 设置超时参数
set global wait_timeout=28800;
set global interactive_timeout=28800;

# 修改jvm内存
/usr/local/aliyun/dbs_agent/bin/aliyun-dbs-agent.sh
JVM_FLAGS="-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError "
./aliyun-dbs-agent.sh restart
1
2
sudo: java: command not found
yum install -y java

备份过程中遇到的问题

逻辑备份

  • 报错信息
1
2
异常信息: -1
java.lang.IllegalStateException: The RecordSplit 5 must be in FAILED or SUCCESS
  • 原因分析:数据库表损坏
1
java.sql.SQLException: Table 'base_scan_record_t' is marked as crashed and should be repaired
  • 解决办法:检查并修复表
1
2
3
4
5
6
7
8
mysql> check table base_scan_record_t;
+----------------------------------+-------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+----------------------------------+-------+----------+----------+
| cosmo_im_1008.base_scan_record_t | check | status | OK |
+----------------------------------+-------+----------+----------+

repair table base_scan_record_t

物理备份

  • 报错信息
1
2
异常信息: 999999
DBS-999999, message :Unable to find 'completed OK!' at the end of the file: /usr/local/aliyun/dbs_agent/dbbackup/2020-08-06/1ib0xmr67jbl0_dbbackup.log, msg: 200807 02:51:07 [04] ...done Error: failed to execute query SET SESSION lock_wait_timeout=31536000: MySQL server has gone away .
  • 原因分析:怀疑是 MySQL 连接超时导致的,MySQL 连接超时是由两个参数控制的interactive_timeout、wait_timeout,它的意思是某个 MySQL 长连接很久没有新的请求发起,达到了 server 端的 timeout,被 server 强行关闭。此后再通过这个 connection 发起查询的时候,就会报错 server has gone away。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MySQL [(none)]> show variables like '%timeout%';
+-----------------------------+----------+
| Variable_name | Value |
+-----------------------------+----------+
| connect_timeout | 10 |
| delayed_insert_timeout | 300 |
| innodb_flush_log_at_timeout | 1 |
| innodb_lock_wait_timeout | 50 |
| innodb_rollback_on_timeout | OFF |
| interactive_timeout | 300 |
| lock_wait_timeout | 31536000 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| rpl_stop_slave_timeout | 31536000 |
| slave_net_timeout | 3600 |
| wait_timeout | 300 |
+-----------------------------+----------+
  • 解决办法:将 interactive_timeout、wait_timeout 都调成默认值 28800

  • 异常报错:

1
999999: DBS-999999, message :Upload files to oss: urumz5gygew1/full/vbt0gyzeqh4k failed, msg: Java heap space.
  • 解决办法:
1
2
/usr/local/aliyun/dbs_agent/bin
./aliyun-dbs-agent.sh stop/start
  • 增量日志备份:
1
2
提醒:999999
DBS-999999, message :Upload files to oss: t4ygmdbe494h/continuous/1jetth0q6x1kp/3.trn.inc failed, msg: Could not read desire data len, dataLength: 6750208.
  • 安装备份网关失败:
1
2
3
4
2020-08-26 15:28:15 ERROR Ping:19 - hostname dbs.cn-hangzhou.aliyuncs.com telnet error: dbs.cn-hangzhou.aliyuncs.com
2020-08-26 15:28:15 ERROR Ping:19 - hostname dbs-inner.cn-qingdao.aliyuncs.com telnet error: dbs-inner.cn-qingdao.aliyuncs.com
2020-08-26 15:28:15 ERROR POPTransporter:187 - add endpoint to default profile failed
com.aliyuncs.exceptions.ClientException: Network is down?
1
2
3
4
5
[root@cosmoim-d-cqbl logs]# cat /etc/resolv.conf
nameserver 10.138.92.77
nameserver 10.138.92.76
nameserver 192.168.100.1
nameserver 192.168.100.2
1
2
yum provides '*/applydeltarpm'  
yum install deltarpm -y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
yum provides '*/applydeltarpm'  
yum install deltarpm -y

yum clean all
rm -rf /var/cache/yum/*
yum clean metadata

yum clean
yum clean all
yum clean all
yum makecache
yum install -y java
yum install java-1.8.0-openjdk -y
yum install java-1.8.0-openjdk-devel -y

异常报错:

错误码为 InvalidTimeStamp.Expired,错误信息为 Specified time stamp or date value is expired.

解决方法:

将本地环境(即调用SDK的应用程序所在的机器)的时钟调整准确即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost ~]# timedatectl 
Local time: 本地时间,与系统设置的时区有关系,北京时间简写CST
Universal time: 协调世界时,简写为UTC
RTC time: 硬件时间,默认显示时间是UTC时间
Time zone: 当前时区
NTP enabled: 是否设置NTP服务开机启动
NTP synchronized: NTP服务是否已经同步时间
RTC in local TZ: 硬件时间是否是本地时区
DST active: 夏令时是否可用 n/a(Not applicable,不可用)

# 修改时区
[root@localhost ~]# timedatectl set-timezone Asia/Shanghai

# 禁用NTP
[root@localhost ~]# timedatectl set-ntp false   

# 修改本地时间
[root@localhost ~]# timedatectl set-time "2022-10-10 11:11:11"
1
2
3
4
5
6
7
8
9
10
11
12
# UTC 时间
整个地球分为二十四时区,每个时区都有自己的本地时间。在国际无线电通信场合,为了统一起见,使用一个统一的时间,称为通用协调时(UTC, Universal Time Coordinated)。

# GMT 时间
格林威治标准时间 (Greenwich Mean Time)指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。(UTC与GMT时间基本相同,本文中不做区分)

# CST 时间
中国标准时间 (China Standard Time)
GMT + 8 = UTC + 8 = CST

# DST 时间
夏令时(Daylight Saving Time) 指在夏天太阳升起的比较早时,将时钟拨快一小时,以提早日光的使用。(中国不使用)

MySQL · 故障处理 · ibtmp1 文件过大

发表于 2020-08-05 | 分类于 MySQL

问题描述

ibtmp1 文件过大,大小 142G,磁盘即将占满

1
2
3
4
5
-rw-r----- 1 mysql mysql 5.4M Aug 19  2019 ib_buffer_pool
-rw-r----- 1 mysql mysql 1.0G Aug 5 14:32 ibdata1
-rw-r----- 1 mysql mysql 1.0G Aug 5 14:32 ib_logfile0
-rw-r----- 1 mysql mysql 1.0G Aug 5 14:32 ib_logfile1
-rw-r----- 1 mysql mysql 142G Aug 5 14:05 ibtmp1

原因分析

ibtmp1 是非压缩的 innodb 临时表的独立表空间,通过innodb_temp_data_file_path 参数指定文件的路径,文件名和大小,默认配置为 ibtmp1:12M:autoextend,也就是说在支持大文件的系统这个文件大小是可以无限增长的。

解决方法

  • 修改 my.cnf 配置文件

    1
    innodb_temp_data_file_path = ibtmp1:12M:autoextend:max:5G
  • 设置 innodb_fast_shutdown 参数

    1
    SET GLOBAL innodb_fast_shutdown = 0;
  • 关闭 mysql 服务

  • 删除 ibtmp1 文件(重启自动删除)

  • 启动 mysql 服务

注意:为了避免以后再出现类似的情况,一定要在限制临时表空间的最大值,如 innodb_temp_data_file_path = ibtmp1:12M:autoextend:max:5G

SQL 审核

发表于 2020-08-03

可选择方案

  • 阿里云 DMS
  • SOAR
  • 拌鱼技术团队

智家迁移上云

发表于 2020-07-22 | 更新于 2020-07-28

海知友

源信息 目标信息
实例 10.159.44.168 prd-paas-mysql-18
123.103.113.6 rm-2ze4fd3f195vg25a3
端口 3306 3306
账号 sre / xx rds / xx
迁移对象 vipcode、vipwechat
数据量 150GB / 43GB
1
select `t`.`id` AS `id`,`t`.`orderNo` AS `orderNo`,`t`.`createTime` AS `createTime`,`t`.`flowStatus` AS `flowStatus`,`t`.`mobile` AS `mobile`,`t`.`userId` AS `userId`,`t`.`userName` AS `userName`,`t`.`recognitionFlag` AS `recognitionFlag`,`t`.`recognitionCondition` AS `recognitionCondition`,`t`.`domainName` AS `domainName`,`t`.`appointType` AS `appointType`,`t`.`productType` AS `productType`,`t`.`userSource` AS `userSource`,`t`.`appointTime` AS `appointTime`,`t`.`productCategory` AS `productCategory`,`t`.`rightStatus` AS `rightStatus`,`t`.`buyMore` AS `buyMore`,`t`.`orderStartTime` AS `orderStartTime`,`t`.`oldfornewdbId` AS `oldfornewdbId`,`t`.`industry` AS `industry`,`t`.`buyIntention` AS `buyIntention`,`t`.`yearBrand` AS `yearBrand`,`t`.`hmcid` AS `hmcid`,`t`.`shopId` AS `shopId`,`t`.`mayEditCoupleOrderId` AS `mayEditCoupleOrderId`,`t`.`coupleSponsor` AS `coupleSponsor` from (select `a`.`id` AS `id`,`a`.`orderNo` AS `orderNo`,ifnull(`a`.`createTime`,`a`.`appointTime`) AS `createTime`,`a`.`flowStatus` AS `flowStatus`,`a`.`mobile` AS `mobile`,`a`.`userId` AS `userId`,`a`.`userName` AS `userName`,`a`.`recognitionFlag` AS `recognitionFlag`,`a`.`recognitionCondition` AS `recognitionCondition`,`a`.`domainName` AS `domainName`,`a`.`appointType` AS `appointType`,`a`.`productType` AS `productType`,`a`.`userSource` AS `userSource`,`a`.`appointTime` AS `appointTime`,`a`.`productCategory` AS `productCategory`,`a`.`rightStatus` AS `rightStatus`,0 AS `buyMore`,NULL AS `orderStartTime`,`a`.`oldfornewdbId` AS `oldfornewdbId`,`a`.`industry` AS `industry`,`c`.`buyIntention` AS `buyIntention`,`c`.`yearBrand` AS `yearBrand`,`a`.`hmcid` AS `hmcid`,NULL AS `shopId`,NULL AS `mayEditCoupleOrderId`,NULL AS `coupleSponsor` from (`vipcode`.`code_workflow` `a` left join `vipcode`.`code_oldfornewdb_count` `c` on(`a`.`oldfornewdbId` = `c`.`id`)) where `a`.`flowStatus` in ('0','6','7','8') and `a`.`isDelete` = 0 union all select `a`.`id` AS `id`,`a`.`orderNo` AS `orderNo`,`b`.`createTime` AS `createTime`,`a`.`flowStatus` AS `flowStatus`,`a`.`mobile` AS `mobile`,`a`.`userId` AS `userId`,`a`.`userName` AS `userName`,`a`.`recognitionFlag` AS `recognitionFlag`,`a`.`recognitionCondition` AS `recognitionCondition`,`a`.`domainName` AS `domainName`,`a`.`appointType` AS `appointType`,`a`.`productType` AS `productType`,`a`.`userSource` AS `userSource`,`a`.`appointTime` AS `appointTime`,`a`.`productCategory` AS `productCategory`,`a`.`rightStatus` AS `rightStatus`,`b`.`buyMore` AS `buyMore`,`b`.`createTime` AS `orderStartTime`,`a`.`oldfornewdbId` AS `oldfornewdbId`,`a`.`industry` AS `industry`,NULL AS `buyIntention`,NULL AS `yearBrand`,`a`.`hmcid` AS `hmcid`,`b`.`shopId` AS `shopId`,`b`.`mayEditCoupleOrderId` AS `mayEditCoupleOrderId`,`b`.`coupleSponsor` AS `coupleSponsor` from (`vipcode`.`code_workflow` `a` join `vipcode`.`code_portalorder` `b`) where `a`.`id` = `b`.`workFlowId` and `b`.`status` = 1 and `b`.`buyMore` = 1 and `a`.`isDelete` = 0 group by `a`.`id`) `t` order by `t`.`createTime` desc
1
2
TABLE_SCHEMA: vipcode
TABLE_NAME: listworkflow

会员中心

源信息 目标信息
实例 10.159.36.193 prd-paas-mysql-14
123.103.10.20 rm-2zeb87aj0804q44fi
端口 3307 3306
账号 sre / xx rds / xx
迁移对象 dbvipcenter
数据量 357GB

Functions:fn_getSplitSum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE FUNCTION `dbvipcenter`.`fn_getSplitSum`(f_string varchar(100)) RETURNS int(11)
BEGIN
DECLARE i int(11);
DECLARE total int(11);
DECLARE result int(11);
DECLARE resulttemp text;
SET i=1;
SET result = 0;
set total = 1+(LENGTH(f_string)-LENGTH(replace(f_string,',','')));

IF(f_string is NULL or LENGTH(f_string)=0) THEN
RETURN 0;
ELSE
WHILE i<=total DO
set resulttemp = reverse(substring_index(reverse(substring_index(f_string,',',i)),',',1));
set result = result + CAST(resulttemp as int);
set i = i+1;
END WHILE;
END IF;
RETURN result;
END

Views:vip_order_right_info

源信息 目标信息
实例 10.159.36.192 prd-paas-mysql-16
123.103.113.99 rm-2zehekw2nfux23a7b
端口 3306 3306
账号 sre / xx rds / xx
迁移对象 dbvipcentertask
数据量 128GB
源信息 目标信息
实例 10.159.36.194 prd-paas-mysql-16
123.103.113.102 rm-2zehekw2nfux23a7b
端口 3308 3306
账号 sre / xx rds / xx
迁移对象 dbvipcenterdata
数据量 54GB
1
2
3
4
5
报错:Specified key was too long; max key length is 767 bytes
措施:启用 innodb_large_prefix

show variables like 'binlog_format';
show variables like 'binlog_row_image';
1
2
3
4
5
6
SELECT table_schema, CONCAT(
TRUNCATE(SUM(data_length)/1024/1024/1024,2),'GB') AS data_size, CONCAT(
TRUNCATE(SUM(index_length)/1024/1024/1024,2),'GB') AS index_size
FROM information_schema.tables
GROUP BY table_schema
ORDER BY SUM(data_length) DESC;

Go 学习笔记

发表于 2020-07-14 | 更新于 2021-03-24 | 分类于 Go

Go 语言简介

  • 最大特色:执行性能和开发效率的平衡。
  • 核心特性:
    • 高效的并发编程
    • 内存回收(gc)
    • 编译速度快
    • 函数多返回值
    • 语言交互性
    • 没有异常处理

Go 环境搭建(Mac)

1
vim .bashs_profile
1
2
3
4
export GOROOT=/usr/local/go
export GOPATH=/Users/raohui/go
export GOBIN=$GOROOT/bin
export PATH=$PATH:$GOBIN
1
2
3
4
source ~/.bash_profiles

go verison
go env

Go 命令及其执行原理

Go 的源码文件

Go 的命令

  • go run:专门用来运行命令源码文件的命令。

  • go build:用于编译我们指定的源码文件或代码包以及他们的依赖包。

  • go install:用来编译并安装代码包或者源码文件。

  • go get:用于从远程代码仓库(比如 Github)上下载并安装代码包。

Go 开发工具

IDE:Goland

⚠️ IDE 中这两个位置的配置是否正确

Goland 常用快捷键

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
# 打开文件
CTRL + E,打开最近浏览过的文件。
CTRL + SHIFT + E,打开最近更改的文件。
CTRL + N,可以快速打开struct结构体。
CTRL + SHIFT + N,可以快速打开文件。

# 代码格式化
CTRL + ALT + T,可以把代码包在一个块内,例如if{…}else{…}。
CTRL + ALT + L,格式化代码。
CTRL + 空格,代码提示。
CTRL + /,单行注释
CTRL + SHIFT + /,进行多行注释。
CTRL + B,快速打开光标处的结构体或方法(跳转到定义处)。
CTRL + “+/-”,可以将当前方法进行展开或折叠。

# 查找和定位
CTRL + R,替换文本。
CTRL + F,查找文本。
CTRL + SHIFT + F,进行全局查找。
CTRL + G,快速定位到某行。

# 代码编辑
ALT + Q,可以看到当前方法的声明。
CTRL + Backspace,按单词进行删除。
SHIFT + ENTER,可以向下插入新行,即使光标在当前行的中间。
CTRL + X,删除当前光标所在行。
CTRL + D,复制当前光标所在行。
ALT + SHIFT + UP/DOWN,可以将光标所在行的代码上下移动。
CTRL + SHIFT + U,可以将选中内容进行大小写转化。

编码规范

命名规范

包命名

保持 package 的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为小写单词,不要使用下划线或者混合大小写。

1
2
package demo
package main

文件命名

尽量采取有意义的文件名,简短,有意义,应该为小写单词,使用下划线分隔各个单词。

1
my_test.go

结构体命名

  • 采用驼峰命名法,首字母根据访问控制大写或者小写
  • struct 申明和初始化格式采用多行,例如下面:
1
2
3
4
5
6
7
8
9
10
11
// 多行申明
type User struct{
Username string
Email string
}

// 多行初始化
u := User{
Username: "astaxie",
Email: "astaxie@gmail.com",
}

接口命名

  • 命名规则基本和上面的结构体类型
  • 单个函数的结构名以 “er” 作为后缀,例如 Reader , Writer 。
1
2
3
type Reader interface {
Read(p []byte) (n int, err error)
}

变量命名

  • 和结构体类似,变量名称一般遵循驼峰法,首字母根据访问控制原则大写或者小写,但遇到特有名词时,需要遵循以下规则:
    • 如果变量为私有,且特有名词为首个单词,则使用小写,如 apiClient
    • 其它情况都应当使用该名词原有的写法,如 APIClient、repoID、UserID
    • 错误示例:UrlArray,应该写成 urlArray 或者 URLArray
  • 若变量类型为 bool 类型,则名称应以 Has, Is, Can 或 Allow 开头
1
2
3
4
var isExist bool
var hasConflict bool
var canManage bool
var allowGitHook bool

常量命名

常量均需使用全部大写字母组成,并使用下划线分词

1
const APP_VER = "1.0"

如果是枚举类型的常量,需要先创建相应类型:

1
2
3
4
5
6
type Scheme string

const (
HTTP Scheme = "http"
HTTPS Scheme = "https"
)

关键字

下面的列表显示了Go中的保留字。这些保留字不能用作常量或变量或任何其他标识符名称。

break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

注释

包注释

每个包都应该有一个包注释,一个位于 package 子句之前的块注释或行注释。包如果有多个 go 文件,只需要出现在一个 go 文件中(一般是和包同名的文件)即可。 包注释应该包含下面基本信息(请严格按照这个顺序,简介,创建人,创建时间):

  • 包的基本简介(包名,简介)
  • 创建者,格式: 创建人: rtx 名
  • 创建时间,格式:创建时间: yyyy/MM/dd

例如 util 包的注释示例如下

1
2
3
// util 包, 该包包含了项目共用的一些常量,封装了项目中一些共用函数。
// 创建人: hanru
// 创建时间: 2019/04/19

结构(接口)注释

每个自定义的结构体或者接口都应该有注释说明,该注释对结构进行简要介绍,放在结构体定义的前一行,格式为: 结构体名, 结构体说明。同时结构体内的每个成员变量都要有说明,该说明放在成员变量的后面(注意对齐),实例如下:

1
2
3
4
5
// User ,用户对象,定义了用户的基础信息
type User struct{
Username string // 用户名
Email string // 邮箱
}

函数(方法)注释

每个函数,或者方法(结构体或者接口下的函数称为方法)都应该有注释说明,函数的注释应该包括三个方面(严格按照此顺序撰写):

  • 简要说明,格式说明:以函数名开头,“,”分隔说明部分
  • 参数列表:每行一个参数,参数名开头,“,”分隔说明部分
  • 返回值: 每行一个返回值

示例如下:

1
2
3
4
5
6
7
// NewtAttrModel , 属性数据层操作类的工厂方法
// 参数:
// ctx : 上下文信息
// 返回值:
// 属性操作类指针
func NewAttrModel(ctx *common.Context) *AttrModel {
}

代码逻辑注释

对于一些关键位置的代码逻辑,或者局部较为复杂的逻辑,需要有相应的逻辑说明,方便其他开发者阅读该段代码,实例如下:

1
2
3
4
// 从 Redis 中批量读取属性,对于没有读取到的 id , 记录到一个数组里面,准备从 DB 中读取
xxxxx
xxxxxxx
xxxxxxx

注释风格

统一使用中文注释,对于中英文字符之间严格使用空格分隔, 这个不仅仅是中文和英文之间,英文和中文标点之间也都要使用空格分隔,例如:

1
// 从 Redis 中批量读取属性,对于没有读取到的 id , 记录到一个数组里面,准备从 DB 中读取

上面 Redis 、 id 、 DB 和其他中文字符之间都是用了空格分隔。

  • 建议全部使用单行注释
  • 和代码的规范一样,单行注释不要过长,禁止超过 120 字符。

代码风格

缩进和折行

  • 缩进直接使用 gofmt 工具格式化即可(gofmt 是使用 tab 缩进的);
  • 折行方面,一行最长不超过 120 个字符,超过的请使用换行展示,尽量保持格式优雅。

我们使用 Goland 开发工具,可以直接使用快捷键:ctrl + alt + L,即可。

语句的结尾

Go 语言中是不需要类似于 Java 需要冒号结尾,默认一行就是一条数据

如果你打算将多个语句写在同一行,它们则必须使用 ;

括号和空格

括号和空格方面,也可以直接使用 gofmt 工具格式化(go 会强制左大括号不换行,换行会报语法错误),所有的运算符和操作数之间要留空格。

1
2
3
4
5
6
7
8
9
10
// 正确的方式
if a > 0 {

}

// 错误的方式
if a>0 // a ,0 和 > 之间应该空格
{ // 左大括号不可以换行,会报语法错误

}

import 规范

import 在多行的情况下,goimports 会自动帮你格式化,但是我们这里还是规范一下 import 的一些规范,如果你在一个文件里面引入了一个 package,还是建议采用如下格式:

1
2
3
import (
"fmt"
)

如果你的包引入了三种类型的包,标准库包,程序内部包,第三方包,建议采用如下方式进行组织你的包:

1
2
3
4
5
6
7
8
9
10
11
import (
"encoding/json"
"strings"

"myproject/models"
"myproject/controller"
"myproject/utils"

"github.com/astaxie/beego"
"github.com/go-sql-driver/mysql"
)

有顺序的引入包,不同的类型采用空格分离,第一种实标准库,第二是项目包,第三是第三方包。

在项目中不要使用相对路径引入包:

1
2
3
4
5
// 这是不好的导入
import “../net”

// 这是正确的做法
import “github.com/repo/proj/src/net”

但是如果是引入本项目中的其他包,最好使用相对路径。

错误处理

  • 错误处理的原则就是不能丢弃任何有返回 err 的调用,不要使用 _ 丢弃,必须全部处理。接收到错误,要么返回 err,或者使用 log 记录下来
  • 尽早 return:一旦有错误发生,马上返回
  • 尽量不要使用 panic,除非你知道你在做什么
  • 错误描述如果是英文必须为小写,不需要标点结尾
  • 采用独立的错误流进行处理
1
2
3
4
5
6
7
8
9
10
11
12
13
// 错误写法
if err != nil {
// error handling
} else {
// normal code
}

// 正确写法
if err != nil {
// error handling
return // or continue, etc.
}
// normal code

语法基础

基础知识

  • 变量
  • 常量
  • iota 关键字
  • 基本数据类型:布尔类型、数值类型、字符串
  • 程序的流程结构:顺序结构、分支结构(if、switch)、循环结构(for、break、continue)

练习

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
const (
A = iota
B
C
D = "haha"
E
F = 100
G
H = iota
I
)
const (
J = iota
)
println(A)
println(B)
println(C)
println(D)
println(E)
println(F)
println(G)
println(H)
println(I)
println(J)

// 运行结果
0
1
2
haha
haha
100
100
7
8
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 打印乘法表
for i := 1; i < 10; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d * %d = %d\t", i, j, i*j)
}
fmt.Println()
}

// 运行结果
1 * 1 = 1
2 * 1 = 2 2 * 2 = 4
3 * 1 = 3 3 * 2 = 6 3 * 3 = 9
4 * 1 = 4 4 * 2 = 8 4 * 3 = 12 4 * 4 = 16
5 * 1 = 5 5 * 2 = 10 5 * 3 = 15 5 * 4 = 20 5 * 5 = 25
6 * 1 = 6 6 * 2 = 12 6 * 3 = 18 6 * 4 = 24 6 * 5 = 30 6 * 6 = 36
7 * 1 = 7 7 * 2 = 14 7 * 3 = 21 7 * 4 = 28 7 * 5 = 35 7 * 6 = 42 7 * 7 = 49
8 * 1 = 8 8 * 2 = 16 8 * 3 = 24 8 * 4 = 32 8 * 5 = 40 8 * 6 = 48 8 * 7 = 56 8 * 8 = 64
9 * 1 = 9 9 * 2 = 18 9 * 3 = 27 9 * 4 = 36 9 * 5 = 45 9 * 6 = 54 9 * 7 = 63 9 * 8 = 72 9 * 9 = 81
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 素数:只能被1和本身整除的数。
for i := 2; i < 100; i++ {
flag := true //记录i是否为素数
for j := 2; j <= int(math.Sqrt(float64(i))); j++ { //判断到根号i就可以,不需要到i的前一个
if i%j == 0 {
flag = false //不是素数了
break
}
}
if flag {
fmt.Println(i)
}
}

// 运行结果
153
370
371
407
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 生成随机数
rand.Seed(time.Now().UnixNano())
for i := 1; i < 10; i++ {
fmt.Println(rand.Intn(90))
}

// 运行结果
53
91
70
57
78
88
33
59
34
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
// 数组
arr := [5]int{15, 23, 8, 10, 7}

// 遍历
for _, value := range arr {
fmt.Println(value)
}

// 冒泡排序
for i := 1; i < len(arr); i++ {
for j := 0; j < len(arr)-i; j++ {
if arr[j] > arr[j+1] {
arr[j], arr[j+1] = arr[j+1], arr[j]
}
}
fmt.Println(arr)
}

// 运行结果
15
23
8
10
7
[15 8 10 7 23]
[8 10 7 15 23]
[8 7 10 15 23]
[7 8 10 15 23]

复合数据类型

复合数据类型:array、slice、map、function、pointer、struct、interface、channel

函数、指针、接口、错误处理

学习资料

  • https://www.bilibili.com/video/BV1jJ411c7s3
  • https://github.com/rubyhan1314/Golang-100-Days

MySQL · 配置 Keepalived 实现双机热备

发表于 2020-04-27 | 分类于 MySQL

简介

Keepalived 是集群管理中保证集群高可用的一个服务软件,其功能类似于 heartbeat,用来防止单点故障。

Keepalived 是以 VRRP(Virtual Router Redundancy Protocol,虚拟路由冗余协议)协议为实现基础的,这个协议可以认为是实现了路由器高可用的协议,将多台提供相同功能的路由器组成一个路由器组。

  • 这里面有一个 MASTER 和多个 BACKUP;
  • MASTER 上面有一个对外提供服务的 Virtual IP (VIP);
  • MASTER 会发组播,当 BACKUP 收不到 VRRP 包时就认为 MASTER 宕机
  • 这时需要根据 VRRP 优先级来选举一个 BACKUP 为 MASTER,这样就保证路由器的正常使用了。

步骤

安装 Keepalived

Keepalived 可以使用 yum 直接安装,在 master 服务器和 backup 服务器执行:

1
$ yum install keepalived

配置 Master 服务器

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
[root@hop02 keepalived]# pwd
/etc/keepalived

[root@hop02 keepalived]# cat keepalived.conf
! Configuration File for keepalived

global_defs {
notification_email {
wangsen@haier.com
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id LVS_DEVEL
}

vrrp_script check_run {
script "/etc/keepalived/check_mysql.sh"
interval 5
}

vrrp_instance VI_1 {
state MASTER
interface bond0
virtual_router_id 51
priority 100
advert_int 1
mcast_src_ip 10.135.22.70

authentication {
auth_type PASS
auth_pass 7777
}

track_script {
check_run
}

virtual_ipaddress {
10.135.22.69
}
}

[root@hop02 keepalived]# cat check_mysql.sh
#!/bin/bash
MYSQL=/usr/local/mysql/bin/mysql
MYSQL_HOST=localhost
MYSQL_USER=root
MYSQL_PASSWORD='aea&Lch6bs27zSyT'
CHECK_TIME=3

#mysql is working MYSQL_OK is 1 , mysql down MYSQL_OK is 0
MYSQL_OK=1

function check_mysql_helth (){
$MYSQL -h $MYSQL_HOST -u $MYSQL_USER -p${MYSQL_PASSWORD} -e "show status;" >/dev/null 2>&1
if [ $? = 0 ] ;then
MYSQL_OK=1
else
MYSQL_OK=0
fi
return $MYSQL_OK
}
while [ $CHECK_TIME -ne 0 ]
do
let "CHECK_TIME -= 1"
check_mysql_helth
if [ $MYSQL_OK = 1 ] ; then
CHECK_TIME=0
exit 0
fi
if [ $MYSQL_OK -eq 0 ] && [ $CHECK_TIME -eq 0 ]
then
/etc/init.d/keepalived stop
exit 1
fi
sleep 1
done

配置 BACKUP 服务器

注意以下几点变动:

  • state 角色为 BACKUP
  • interface 为网卡的 ID,要根据机器确认
  • virtual_route_id 要与 MASTER 一致,默认为 51
  • priority 要比 MASTER 小
  • 设置 vrrp_strict 选项

配置并启动服务

配置 IP 转发,需要修改配置文件 /etc/sysctl.conf,默认只有 root 可以修改

1
2
3
4
5
$ su - root
Password:
# echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
# sysctl -p
# exit

防火墙添加规则,因为 VRRP 使用 224.0.0.18 这个组播地址

1
2
3
4
5
6
$ sudo firewall-cmd --direct --permanent --add-rule ipv4 filter INPUT 0 --in-interface em1 --destination 224.0.0.18 --protocol vrrp -j ACCEPT
success
$ sudo firewall-cmd --direct --permanent --add-rule ipv4 filter OUTPUT 0 --out-interface em1 --destination 224.0.0.18 --protocol vrrp -j ACCEPT
success
$ sudo firewall-cmd --reload
success

可以查看一下这两条规则

1
2
3
4
$ sudo firewall-cmd --direct --get-rules ipv4 filter INPUT
0 --in-interface em1 --destination 224.0.0.18 --protocol vrrp -j ACCEPT
$ sudo firewall-cmd --direct --get-rules ipv4 filter OUTPUT
0 --out-interface em1 --destination 224.0.0.18 --protocol vrrp -j ACCEPT

启动 MASTER 和 BACKUP 的 keepalived 服务,并设置开机启动

1
2
3
4
5
6
7
[root@hop02 etc]# cat /etc/redhat-release
CentOS release 6.5 (Final)

[root@hop02 etc]# service keepalived start
Starting keepalived: [ OK ]

$ systemctl enable keepalived
1
2
3
4
5
[root@hop02 etc]# ps -ef | grep keepalived
root 17660 1 0 09:42 ? 00:00:00 /usr/sbin/keepalived -D
root 17661 17660 0 09:42 ? 00:00:00 /usr/sbin/keepalived -D
root 17662 17660 0 09:42 ? 00:00:00 /usr/sbin/keepalived -D
root 17703 15307 0 09:42 pts/2 00:00:00 grep keepalived

查看 MASTER 网卡,可以发现 MASTER 服务器的 bond0 网卡上多了 10.135.22.69 这个虚拟 IP 地址。

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
[root@hop02 keepalived]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: em1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP qlen 1000
link/ether c8:1f:66:f2:09:35 brd ff:ff:ff:ff:ff:ff
3: em2: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP qlen 1000
link/ether c8:1f:66:f2:09:35 brd ff:ff:ff:ff:ff:ff
4: em3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether c8:1f:66:f2:09:37 brd ff:ff:ff:ff:ff:ff
inet 172.25.178.22/24 brd 172.25.178.255 scope global em3
inet6 fe80::ca1f:66ff:fef2:937/64 scope link
valid_lft forever preferred_lft forever
5: em4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether c8:1f:66:f2:09:38 brd ff:ff:ff:ff:ff:ff
inet 172.23.178.183/24 brd 172.23.178.255 scope global em4
inet6 fe80::ca1f:66ff:fef2:938/64 scope link
valid_lft forever preferred_lft forever
6: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
link/ether c8:1f:66:f2:09:35 brd ff:ff:ff:ff:ff:ff
inet 10.135.22.70/23 brd 10.135.23.255 scope global bond0
inet 10.135.22.69/32 scope global bond0
inet6 fe80::ca1f:66ff:fef2:935/64 scope link
valid_lft forever preferred_lft forever
7: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
link/ether 52:54:00:20:68:13 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
8: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 500
link/ether 52:54:00:20:68:13 brd ff:ff:ff:ff:ff:ff

漂移规则如下:

  • 默认使用 MASTER 服务器 10.135.22.70,虚拟 IP 为 10.135.22.69,此时 MASTER 服务器会有 2 个 IP。
  • 当 MASTER 出问题时,IP 会漂移到 BACKUP 服务器(10.135.22.71),此时 BACKUP 服务器会有 2 个 IP。
  • 当 MASTER 重新启动后,虚拟 IP 又会漂移回 MASTER 服务器。

附录

配置文件说明:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
! Configuration File for keepalived

global_defs {
notification_email {
# email 接收方
acassen@firewall.loc
failover@firewall.loc
sysadmin@firewall.loc
}
# email 发送方
notification_email_from Alexandre.Cassen@firewall.loc
# 邮件服务器, smtp 协议
smtp_server 192.168.200.1
smtp_connect_timeout 30
router_id app2
vrrp_skip_check_adv_addr
# 使用 unicast_src_ip 需要注释 vrrp_strict,而且也可以进行 ping 测试
#vrrp_strict
vrrp_garp_interval 0
vrrp_gna_interval 0
}

# vrrp实例
vrrp_instance VI_1 {
# 指定 keepalived 的角色,MASTER 表示此主机是主服务器,BACKUP 表示此主机是备用服务器
state MASTER

# 指定网卡
interface em1

# 虚拟路由标识,这个标识是一个数字,同一个vrrp实例使用唯一的标识。
# 即同一vrrp_instance下,MASTER和BACKUP必须是一致的
virtual_router_id 51

# 定义优先级,数字越大,优先级越高(0-255)。
# 在同一个vrrp_instance下,MASTER 的优先级必须大于 BACKUP 的优先级
priority 100

# 设定 MASTER 与 BACKUP 负载均衡器之间同步检查的时间间隔,单位是秒
advert_int 1

# 如果两节点的上联交换机禁用了组播,则采用 vrrp 单播通告的方式
unicast_src_ip 10.0.0.11
unicast_peer {
10.0.0.12
}

# 设置验证类型和密码
authentication {
#设置验证类型,主要有PASS和AH两种
auth_type PASS
#设置验证密码,在同一个vrrp_instance下,MASTER与BACKUP必须使用相同的密码才能正常通信
auth_pass 1111
}

#设置虚拟IP地址,可以设置多个虚拟IP地址,每行一个
virtual_ipaddress {
# 虚拟 IP
10.0.0.10/24 brd 10.0.0.255
}
}

# 虚拟服务器端口配置
virtual_server 10.0.0.10 80 {
delay_loop 6
lb_algo rr
lb_kind NAT
persistence_timeout 50
protocol TCP

real_server 10.0.0.11 80 {
weight 1
}
}

参考

MySQL · InnoDB 表结构

发表于 2020-04-21 | 更新于 2020-04-28 | 分类于 MySQL

简介

逻辑存储结构

从 InnoDB 存储引擎的逻辑存储结构看,所有数据都被逻辑地存放在同一个空间中,称为 表空间(tablespace)。表空间又由 段(segment)、区(extent)、页(page)组成。

表空间

表空间可以看作是 InnoDB 存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。

共享表空间:ibdata1

undo log、插入缓冲索引页、系统事务信息、二次写缓冲(Double write buffer)等

独立表空间:表名.ibd

数据、索引、插入缓冲 Bitmap 页

段

表空间是由各个段组成,常见的段有数据段、索引段、回滚段等。InnoDB 存储引擎表是索引组织的,因此数据即索引,索引即数据。那么数据段即为 B+ 树的叶子节点,索引段即为 B+ 树的非索引节点。

区

区是由连续页组成的空间,在任何情况下每个区的大小都是 1MB。为了保证区中页的连续性,InnoDB 存储引擎一次从磁盘申请 4~5 个区。在默认情况下,InnoDB 存储引擎页的大小是 16KB,即一个区中一共有 64 个连续的页。

InnoDB 1.2.x 版本新增了参数 innodb_page_size,通过该参数可以将默认页的大小进行调整,但不论页的大小怎么变化,区的大小总是为 1M。

页

页是 InnoDB 磁盘管理的最小单位。在 InnoDB 存储引擎中,默认每个页的大小为 16KB,常见的页类型有:

  • 数据页(B-tree Node)
  • undo 页(Undo Log Page)
  • 系统页(System Page)
  • 事务数据页(Transaction System Page)
  • 插入缓冲位图页(Insert Buffer Bitmap)
  • 插入缓冲空闲列表页(Insert Buffer Free List)
  • 未压缩的二进制大对象页(Uncompressed Blob Page)
  • 压缩的二进制大对象页(compressed Blob Page)

InnoDB 各个数据页可以组成一个 双向链表,而每个数据页中的记录会按照主键值从小到大的顺序组成一个 单向链表,每个数据页都会为存储在它里边儿的记录生成一个 页目录,在通过主键查找某条记录的时候可以在 页目录 中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。

行

InnoDB 存储引擎是面向列(row-oriented)的,也就是说数据是按行进行存储的。

行记录格式

InnoDB 存储引擎提供了 4 种不同类型的行格式,分别是 Compact、Redundant、Dynamic 和 Compressed 行格式来存放行记录数据,通过 Row_format 属性可以查询到表使用的行记录结构类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mysql> show table status like 'employees'\G
*************************** 1. row ***************************
Name: employees
Engine: InnoDB
Version: 10
Row_format: Dynamic
Rows: 299556
Avg_row_length: 50
Data_length: 15220736
Max_data_length: 0
Index_length: 0
Data_free: 0
Auto_increment: NULL
Create_time: 2020-04-16 11:27:41
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.01 sec)
1
2
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称

Compact 行记录格式

在 Compact 行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放。

MySQL 会为每条记录默认添加一些列:

列名 是否必须 占用空间 描述
row_id 否 6 字节 行 ID,唯一标识一条记录
transaction_id 是 6 字节 事务 ID
roll_pointer 是 7 字节 回滚指针

InnoDB 表对主键的生成策略:

  • 优先使用用户自定义主键作为主键;
  • 如果用户没有定义主键,则选取一个 Unique 键作为主键;
  • 如果表中连 Unique 键都没有定义的话,则 InnoDB 会为表默认添加一个名为 row_id 的隐藏列作为主键。

Redundant 行记录格式

使用 Redundant 行格式的 CHAR(M) 类型的列是不会产生碎片的,CHAR(M) 占用的真实数据空间就是该字符集表示一个字符最多需要的字节数和 M 的乘积。在列的值允许为 NULL 的情况下,gbk 字符集表示一个字符最多需要2 个字节,那在该字符集下,M 的最大取值就是 32766(也就是:65532/2),也就是说最多能存储 32766 个字符;utf8 字符集表示一个字符最多需要 3 个字节,那在该字符集下,M 的最大取值就是 21844,就是说最多能存储 21844(也就是:65532/3)个字符。

Dynamic 和 Compressed 行记录格式

这两种行格式类似于 COMPACT 行格式,只不过在处理行溢出数据时有点儿分歧,它们不会在记录的真实数据处存储字符串的前 768 个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。

另外,Compressed 行格式会采用压缩算法对页面进行压缩。

行溢出

一个页一般是 16KB,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为 行溢出。

数据页结构

在页的 7 个组成部分中,我们自己存储的记录会按照我们指定的行格式存储到 User Records 部分。

表中记录的行格式示意图就是这样的:

不论我们怎么对页中的记录做增删改操作,InnoDB 始终会维护一条记录的单链表,链表中的各个节点是按照主键值由小到大的顺序连接起来的。

小结:

  • InnoDB 存储引擎总是按照主键索引顺序进行存放。

  • InnoDB 为了不同的目的而设计了不同类型的页,我们把用于存放记录的页叫做 数据页。

  • 一个数据页可以被大致划分为 7 个部分,分别是

    • File Header:表示页的一些通用信息,占固定的 38 字节。
    • Page Header:表示数据页专有的一些信息,占固定的 56 个字节。
    • Infimum + Supremum:两个虚拟的伪记录,分别表示页中的最小和最大记录,占固定的 26 个字节。
    • User Records:真实存储我们插入的记录的部分,大小不固定。
    • Free Space:页中尚未使用的部分,大小不确定。
    • Page Directory:页中的某些记录相对位置,也就是各个槽在页面中的地址偏移量,大小不固定,插入的记录越多,这个部分占用的空间越多。
    • File Trailer:用于检验页是否完整的部分,占用固定的 8 个字节。
  • 每个记录的头信息中都有一个 next_record 属性,从而使页中的所有记录串联成一个 单链表。

  • InnoDB 会把页中的记录划分为若干个组,每个组的最后一个记录的地址偏移量作为一个 槽,存放在 Page Directory 中,所以在一个页中根据主键查找记录是非常快的,分为两步:

    • 通过二分法确定该记录所在的槽。
    • 通过记录的 next_record 属性遍历该槽所在的组中的各个记录。
  • 每个数据页的 File Header 部分都有上一个和下一个页的编号,所以所有的数据页会组成一个 双链表。

  • 为保证从内存中同步到磁盘的页的完整性,在页的首部和尾部都会存储页中数据的校验和和页面最后修改时对应的 LSN 值,如果首部和尾部的校验和和 LSN 值校验不成功的话,就说明同步过程出现了问题。

12…18
Hui Rao

Hui Rao

最好的成长是分享
173 日志
19 分类
14 标签
GitHub E-Mail
© 2021 Hui Rao
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Gemini v7.1.0
|