0%

新增一块存储设备

# 新增完之后可以用以下命令确认
[root@node1 ~]# fdisk -l

磁盘 /dev/sda:53.7 GB, 53687091200 字节,104857600 个扇区
Units = 扇区 of 1 * 512 = 512 bytes
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
磁盘标签类型:dos
磁盘标识符:0x0009f53f

   设备 Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048     2099199     1048576   83  Linux
/dev/sda2         2099200   104857599    51379200   8e  Linux LVM

# /dev/sdb 磁盘下无分区
磁盘 /dev/sdb:16.1 GB, 16106127360 字节,31457280 个扇区
Units = 扇区 of 1 * 512 = 512 bytes
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
磁盘标签类型:dos
磁盘标识符:0xefe51d7a
# 使用下面的命令 一样可以确认, sdb设备FSTYPE等信息为空
[root@node1 ~]# lsblk -f
NAME            FSTYPE      LABEL           UUID                                   MOUNTPOINT
sda                                                                                
├─sda1          xfs                         caa5e7a7-d9b3-4bcb-adc2-409153a686ad   /boot
└─sda2          LVM2_member                 VHdTmP-vi9x-LVfT-Tp2s-SAnp-CPlq-4ChakQ 
  ├─centos-root xfs                         e5b9a4e0-95a1-40f1-b7aa-1ce69e51c1d4   /
  └─centos-swap swap                        5d1b3899-2c21-4886-a187-31a644ef32a7   [SWAP]
sdb                                                                                
sr0             iso9660     CentOS 7 x86_64 2018-11-25-23-54-16-00  

为磁盘设置分区

# 1. 进入分区设置
[root@node1 ~]# fdisk /dev/sdb
欢迎使用 fdisk (util-linux 2.23.2)。

更改将停留在内存中,直到您决定将更改写入磁盘。
使用写入命令前请三思。


命令(输入 m 获取帮助)# 2. 输入n创建一个新的分区
命令(输入 m 获取帮助):n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): 

# 3. 输入p创建一个主分区, 注:硬盘主分区最多为4个,分区号从1到4,逻辑分区从5开始
分区号 (1-4,默认 1)# 4. 输入分区起始扇区, 默认开始位置
起始 扇区 (2048-31457279,默认为 2048)# 5. 输入分区结束扇区,默认结束位置, 可以指定大小, 如 +3G
Last 扇区, +扇区 or +size{K,M,G} (2048-31457279,默认为 31457279)# 6. 输入p确认分区
命令(输入 m 获取帮助):p

磁盘 /dev/sdb:16.1 GB, 16106127360 字节,31457280 个扇区
Units = 扇区 of 1 * 512 = 512 bytes
扇区大小(逻辑/物理):512 字节 / 512 字节
I/O 大小(最小/最佳):512 字节 / 512 字节
磁盘标签类型:dos
磁盘标识符:0xefe51d7a

   设备 Boot      Start         End      Blocks   Id  System
/dev/sdb1            2048    31457279    15727616   83  Linux

# 7. 输入w保存退出
命令(输入 m 获取帮助):w
The partition table has been altered!

Calling ioctl() to re-read partition table.
正在同步磁盘。
  • 删除分区
# 1. 进入分区设置
[root@node1 ~]# fdisk /dev/sdb
欢迎使用 fdisk (util-linux 2.23.2)。

更改将停留在内存中,直到您决定将更改写入磁盘。
使用写入命令前请三思。


命令(输入 m 获取帮助)# 2. 输入d,选择分区号删除分区
  • 取消挂载
# 取消挂载点
umount /dev/sdb1

格式化分区

# 1. 格式化:centos7.0开始默认文件系统是xfs, centos6是ext4,centos5是ext3
[root@node1 ~]# mkfs -t xfs /dev/sdb1
mkfs.xfs: /dev/sdb1 appears to contain an existing filesystem (xfs).
mkfs.xfs: Use the -f option to force overwrite.
# 2. 确认格式化结果
[root@node1 ~]# lsblk -f
NAME            FSTYPE      LABEL           UUID                                   MOUNTPOINT
sda                                                                                
├─sda1          xfs                         caa5e7a7-d9b3-4bcb-adc2-409153a686ad   /boot
└─sda2          LVM2_member                 VHdTmP-vi9x-LVfT-Tp2s-SAnp-CPlq-4ChakQ 
  ├─centos-root xfs                         e5b9a4e0-95a1-40f1-b7aa-1ce69e51c1d4   /
  └─centos-swap swap                        5d1b3899-2c21-4886-a187-31a644ef32a7   [SWAP]
sdb                                                                                
└─sdb1          xfs                         9a382567-246a-4c52-9451-fe819a9ee297   
sr0             iso9660     CentOS 7 x86_64 2018-11-25-23-54-16-00                 

挂载分区

# 1. 创建目录
[root@node1 ~]# mkdir /newdisk

# 2. 临时挂载分区,重启后失效
mount /dev/sdb1 /newdisk

# 3. 永久挂载
vim /etc/fstab

# 4. /etc/fstab增加下面一行内容
/dev/sdb1 /newdisk xfs defaults 0 0

# 5. 生效
mount -a

# 6. 确认
[root@node1 ~]# df -h
文件系统                 容量  已用  可用 已用% 挂载点
/dev/sdb1                 15G   33M   15G    1% /newdisk

语法

- 格式: {{ 模板表达式 }}
- 注释格式: {{/* 注释语法 */}}
- {{.字段名}}
- {{.字段名1.字段名2}}

减号

- 在左边增加减号和空格,表示删除左边空格: {{- 模板表达式 }}
- 在右边增加空格和减号,表示删除右边空格: {{ 模板表达式 -}}
- 删除表达式左右两边空格的写法: {{- 模板表达式 -}}

变量

定义变量
$title := "标题"

为变量赋值, 第二次为变量赋值,不需要冒号:
$title = "新标题"

引用变量
{{$title}}

流程

if

// 语法格式1:表达式为真,则执输出T1
{{if 表达式}} T1 {{end}}
// 语法格式2:表达式为真,则执输出T1, 否则输出T0
{{if 表达式}} T1 {{else}} T0 {{end}}
// 语法格式3:表达式1为真,则执输出T1, 否则如果表达式2为真,则输出T0
{{if 表达式1}} T1 {{else if 表达式2}} T0 {{end}}

range

titles := []string{"标题1", "标题2", "标题3"}
{{range .}}
{{.}}
{{end}}
{{range $index, $element := 数组或者map的引用}}
索引: {{$index}}
元素值: {{$element}}
{{end}}

with

// with语句主要用于struct类型数据的访问
user := User{Id:1001, UserName:"李大成"}
{{with .User}}
Id: {{.Id}}
Username: {{.UserName}}
{{end}}

模板

// 定义
{{define "子模板名字"}}
模板内容
{{end}}
// 引用
{{template "子模板名字" 参数}}

函数

关系运算函数

函数名 函数调用格式 对应关系运算 说明
eq eq arg1 arg2 arg1 == arg2 arg1等于arg2则返回true
ne ne arg1 arg2 arg1 != arg2 arg1不等于arg2则返回true
lt lt arg1 arg2 arg1 < arg2 arg1小于arg2则返回true
le le arg1 arg2 arg1 <= arg2 arg1小于等于arg2则返回true
gt gt arg1 arg2 arg1 > arg2 arg1大于arg2则返回true
ge ge arg1 arg2 arg1 >= arg2 arg1大于等于arg2则返回true

逻辑运算函数

函数名 函数调用格式 对应逻辑运算 说明
and and 表达式1 表达式2 表达式1 && 表达式2 表达式1和表达式2都为真的时候返回true
or or 表达式1 表达式2 表达式1 或者 表达式2 表达式1和表达式2其中一个为真的时候返回true
not not 表达式 !表达式 表达式为false则返回true, 反之返回false

接口说明

springboot集成Prometheus需要开发的接口有:

  • 监控JVM、tomcat等相关的指标;
  • 自定义监控程序相关指标;

监控JVM、tomcat等相关的指标

micrometer已经为我们做好了相关的接口,只需要引入依赖即可.

<!--集成Prometheus-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

设置application.yml

server:
  port: 9090

spring:
  application:
    name: application-name

management:
  endpoint:
  endpoints:
    web:
      exposure:
        include: '*'
@Bean
MeterRegistryCustomizer<MeterRegistry> configurer(@Value("${spring.application.name}") String applicationName) {
    return registry -> registry.config().commonTags("application", applicationName);
}

启动程序后,访问/actuator/prometheus即可获取相关指标.

使用Micrometer实现方法执行时间监控

@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
    return new TimedAspect(registry);
}
// 类上要开启@Aspect
// 在方法上添加 @Timed 注解即可
@GetMapping("/log/warn")
@Timed(value = "warn_methord",description = "健康检查接口")
public String warn() {
    log.warn("warn msg.");
    return "warn";
}

启动服务后,访问本地的/actuator/prometheus接口,就能看到如下的指标数据了,其中就有我们自定义的warn_methord的三个指标(count sum max)。

# HELP warn_methord_seconds 健康检查接口
# TYPE warn_methord_seconds summary
warn_methord_seconds_count{application="ggis",exception="None",method="GET",status="200",uri="/log/warn",} 3.0
warn_methord_seconds_sum{application="ggis",exception="None",method="GET",status="200",uri="/log/warn",} 0.0208932
# HELP warn_methord_seconds_max 健康检查接口
# TYPE warn_methord_seconds_max gauge
warn_methord_seconds_max{application="ggis",exception="None",method="GET",status="200",uri="/log/warn",} 0.01753

自定义监控程序相关指标

如果上面的接口返回的指标不够用,需要自己开发,可以参考下面的:

@GetMapping(value = "/metrics", produces = "text/plain")
@ResponseBody
String metrics() {
    // 这里产生的随机数,实际按需修改
    return "user_random{application=\"application\"} " + (int)(Math.random()*10);
}

然后配置到Prometheus的Targets中即可.

canal介绍

canal是阿里开源的数据库同步框架,采用非侵入式方式,解析mysql的binary log,再发送到目的地,目的地可是mq,hbase,mysql,es等.

本章流程

  1. 开启mysql的bin-log日志
  2. 创建mysql用户获取bin-log日志
  3. canal采集bin-log日志
  4. canal-client获取mysql变化信息

开启bin-log日志

只需要在mysqld.cnf新增配置

server-id=1
log-bin=mysql-bin

创建mysql用户

create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%';
FLUSH PRIVILEGES;

配置canal

# 配置文件1:canal-server/conf/canal.properties
# 端口
canal.port = 11111

# 配置文件2:canal-server/conf/example/instance.properties
# 数据库连接信息
canal.instance.master.address=192.168.41.128:3307
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
# 监听的表(正则表达式)
canal.instance.filter.regex=.*\\..*
# 主题
canal.mq.topic=example

启动mysql/canal

# 本地测试采用docker方式启动
docker run -d --name mysql -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 -v $PWD/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf  hub.c.163.com/library/mysql:5.7

docker run -p 11111:11111 --name canal -d docker.io/canal/canal-server

编写canal-client

package com.deri.stream.canal;

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;

import java.net.InetSocketAddress;
import java.util.List;

/**
 * @ClassName: Main
 * @Description: TODO
 * @Author: wuzhiyong
 * @Time: 2021/6/11 9:41
 * @Version: v1.0
 **/
public class Main {
    public static void main(String[] args) throws InterruptedException {
        CanalConnector connector = CanalConnectors.newSingleConnector(
                new InetSocketAddress("192.168.41.128",11111), "example", "", "");
        int batchSize = 1000;
        try {
            connector.connect();
            connector.subscribe(".*\\..*");
            connector.rollback();
            while (true) {
                Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
                long batchId = message.getId();
                int size = message.getEntries().size();
                if (batchId == -1 || size == 0) {
                    // 没有变化,等一秒钟再去拉取数据
                   Thread.sleep(1000);
                } else {
                    printEntry(message.getEntries());
                }
                connector.ack(batchId); // 提交确认
                // connector.rollback(batchId); // 处理失败, 回滚数据
            }
        } finally {
            connector.disconnect();
        }
    }

    private static void printEntry(List<CanalEntry.Entry> entrys) {
        for (CanalEntry.Entry entry : entrys) {
            if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
                continue;
            }

            CanalEntry.RowChange rowChage = null;
            try {
                rowChage = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
                        e);
            }

            CanalEntry.EventType eventType = rowChage.getEventType();
            System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
                    eventType));

            for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
                if (eventType == CanalEntry.EventType.DELETE) {
                    printColumn(rowData.getBeforeColumnsList());
                } else if (eventType == CanalEntry.EventType.INSERT) {
                    printColumn(rowData.getAfterColumnsList());
                } else {
                    System.out.println("-------&gt; before");
                    printColumn(rowData.getBeforeColumnsList());
                    System.out.println("-------&gt; after");
                    printColumn(rowData.getAfterColumnsList());
                }
            }
        }
    }

    private static void printColumn(List<CanalEntry.Column> columns) {
        for (CanalEntry.Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
        }
    }
}

参考连接

项目结构

  • GOROOT设置go安装目录;
  • GOPATH设置项目目录,目录默认包括srcbinpkgsrc下面是一个个创建的项目,pkg是项目引用的包,bin下面是可执行程序;
  • GO111MODULE=on
  • GOPROXY=https://goproxy.io,direct

命令

  • go build会在当前目录下生成可执行程序,window下是exe
  • go install会在%GOPATH%/bin目录下生成可执行程序

import使用

参考链接:Go import使用

  • import "fmt"最常用的一种形式
  • import "./test"导入同一目录下test包中的内容
  • import f "fmt"导入fmt,并给他启别名f
  • import . "fmt",将fmt启用别名".",这样就可以直接使用其内容,而不用再添加fmt,如fmt.Println可以直接写成Println
  • import _ "fmt" 表示不使用该包,而是只是使用该包的init函数,并不显示的使用该包的其他内容。注意:这种形式的import,当import时就执行了fmt包中的init函数,而不能够使用该包的其他函数。

命名规范

Go是一门区分大小写的语言.

参考链接:Go中的命名规范

任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则应该以小写字母开头;

  • 当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Analysize,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
  • 命名如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )

init函数

  • init函数会自动执行,用于初始化变量,不可被其他函数调用;
  • init函数可以有多个,执行顺序没有明确定义,不同包的init执行顺序由其依赖关系决定;

指针

  • & :取地址
  • * :取值

空接口使用

// 空接口类型的变量可以保存任何类型的值
var emptyInterface interface{}
fmt.Printf("emptyInterface is of type %T\n", emptyInterface)
emptyInterface = 100
fmt.Printf("emptyInterface is of type %T\n", emptyInterface)
emptyInterface = "Golang"
fmt.Printf("emptyInterface is of type %T\n", emptyInterface)
// 创建一个map
make(map[string]interface{})
// 
make(map[string]map[string]interface{})

创建对象的几种方式

// 对象
type People struct {
    Name string
}
// 1: 结果为指针类型
p := new(People)
// 2: 结果为值类型
p := People{}
// 3: 结果为指针类型
p := &People{}
// 4: 创建并初始化
p := People{Name: "userName"}
p := People{"userName"}
p := &People{"userName"}
// 5: 构造函数
func NewPeople(name string)*People  {
    return &People{name}
}

GO111MODULE

  • Go ModulesGo 语言的一种依赖管理方式,使用go module管理依赖后会在项目根目录下生成两个文件go.modgo.sum.
  • 要使用go module首先要设置GO111MODULE=onGO111MODULE有三个值,offonautoauto则会根据当前目录下是否有 go.mod文件来判断是否使用modules功能。
  • go module 默认不在 GOPATH 目录下查找依赖文件,其首先会在GOPATH/pkg/mod中查找有没有所需要的依赖,没有的直接会进行下载。可以使用go mod download下载好所需要的依赖。
  • go build 会将项目的依赖添加到 go.mod

go mod vendor

// 1. 使用 go mod vendor把依赖下载到本地调试
go mod vendor
// 2. 使用本地依赖
go build -mod=vendor main.go

为对象创建方法

type People struct{
    name string
}

func (p People) GetName() string{
    return p.name
}

go项目打包成docker镜像

直接执行go build或者go build -o xxx打包成的二进制文件是在window中运行的,如果要打包成在Linux中运行的,go build之前需要增加环境变量:

  • set GOOS=linux
  • set GOARCH=amd64
# Dockerfile文件
FROM alpine
MAINTAINER wuzhiyong wuzhiyong@deri.energy
WORKDIR /app
COPY restful /app

EXPOSE 8806
CMD ["./restful"]

说明

dockerfly提供一个web页面,可以非常方便的管理主机上镜像容器网络等。

安装

# 采用docker部署即可
docker pull helyho/dockerfly:latest

运行

  • 28083:web端口
  • 2735:管理端口
docker run --name dockerfly -d -v /var/run/docker.sock:/var/run/docker.sock --restart always -p 2735:2735 -p 28083:28083 helyho/dockerfly

访问