0%

MySQL 8.4 版本

不同版本配置不同!

  • server 1
server-id=1
log-bin=mysql-bin
binlog-format=ROW
max_binlog_size=1024M
slow_query_log=1
binlog_expire_logs_seconds =864000
# 异步落盘
sync-binlog=0
replica_skip_errors=all
# 自增ID偏移量设置
auto-increment-increment=2
auto-increment-offset=1
# 半同步复制
rpl_semi_sync_source_enabled=1
rpl_semi_sync_replica_enabled=1
# 开启GTID
gtid_mode=ON
enforce-gtid-consistency=ON

mysql_native_password=ON
  • server 2
server-id=2
log-bin=mysql-bin
binlog-format=ROW
max_binlog_size=1024M
slow_query_log=1
binlog_expire_logs_seconds =864000
# 异步落盘
sync-binlog=0
replica_skip_errors=all
# 自增ID偏移量设置
auto-increment-increment=2
auto-increment-offset=2
# 半同步复制
rpl_semi_sync_source_enabled=1
rpl_semi_sync_replica_enabled=1
# 开启GTID
gtid_mode=ON
enforce-gtid-consistency=ON

mysql_native_password=ON

启动半同步复制

-- 要求 MySQL 服务器支持动态加载
show variables like '%have_dynamic_loading%';

INSTALL PLUGIN rpl_semi_sync_source SONAME 'semisync_source.so';
INSTALL PLUGIN rpl_semi_sync_replica SONAME 'semisync_replica.so';

-- 确认安装是否成功:
SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE '%semi%';

-- 查看半同步监控 
SHOW STATUS LIKE 'Rpl_semi_sync%';

创建复制用户

CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

这个用户直接配置复制会报错。Message: Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection. 原因是用户默认密码加密方式是 caching_sha2_password, 需要改成 mysql_native_password.

ALTER USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
FLUSH PRIVILEGES;

通过GTID启动复制(2台MySQL分别执行)

-- 注意修改IP、端口等信息
CHANGE REPLICATION SOURCE TO SOURCE_HOST = '172.16.20.153',SOURCE_PORT = 3306,SOURCE_USER = 'repl',SOURCE_PASSWORD = 'password',SOURCE_AUTO_POSITION = 1;

启动

START REPLICA;

查看复制状态

SHOW REPLICA STATUS\G

双主模式下的读写分离

  1. 创建一个普通用户
CREATE USER 'test'@'%' IDENTIFIED BY 'Test@123456';
GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'test'@'%';
FLUSH PRIVILEGES;
  1. 超级用户可以在另一台MySQL动态设置只读
    这里需要开发一个动态设置的读VIP所在MySQL只读的工具.
-- 普通用户只能读,超级用户依然可以写
set global read_only = 1;

-- 检查设置的状态
show global variables like '%read_only%';

额外说明:super_read_onlyread_only不一样,super_read_only是所有用户都不能写。

1. 打开官网MySQL安装文档

2. 根据Linux版本下载yum源

cat /etc/os-release
yum localinstall mysql84-community-release-el7-1.noarch.rpm

3. 查看可安装的MySQL包

yum repolist enabled | grep mysql.*-community

4. 安装MySQL

# EL8 systems 需要先禁止.
yum module disable mysql
yum install mysql-community-server

5. 启动MySQL

systemctl start mysqld

6. 登录MySQL

# 查看生成的随机密码
cat /var/log/mysqld.log |grep password

mysql -uroot -p

7. 初始化操作

-- 重置root密码
ALTER USER 'root'@'localhost' IDENTIFIED BY 'Test@123';
-- 设置root可以远程登录
update mysql.user set  host='%' WHERE user='root';
-- 刷新权限
FLUSH PRIVILEGES;

问题: Error: 13 (Permission denied)

vim /etc/selinux/config 
# 关闭后重启系统
SELINUX=disabled

Raw和Exec

GORM 中,db.Rawdb.Exec 是执行 原生 SQL 查询的两种不同方法,它们有不同的用途和返回值。

  • db.Raw 用于执行原生 SQL 查询并返回记录。通常用于执行 SELECT 查询,需要使用 Scan 方法将结果扫描到指定的结构体或变量中
  • db.Exec 用于执行不需要返回记录的原生 SQL 查询,通常用于 INSERTUPDATEDELETE 语句,或者执行 DDL(数据定义语言)语句,如创建表或修改表结构, 不需要 Scan,可以直接检查 result.RowsAffectedresult.Error.

Raw

var users []User
result := db.Raw("SELECT id, name, email FROM users").Scan(&users)
if result.Error != nil {
    fmt.Println(result.Error)
    return
}

for _, user := range users {
    fmt.Printf("ID: %d, Name: %s, Email: %s\n", user.ID, user.Name, user.Email)
}

Exec

result := db.Exec("INSERT INTO users (name, email) VALUES (?, ?)", "John Doe", "john@example.com")
if result.Error != nil {
    fmt.Println(result.Error)
    return
}

fmt.Printf("Rows affected: %d\n", result.RowsAffected)

sync.Once

专门用于确保某些初始化代码只被执行一次,不论该代码由多少个 goroutine 调用.

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var once sync.Once
    for i := 0; i < 10; i++ {
        go func() {
            // sync.Once 提供了一个方法 Do,这个方法接受一个函数作为参数,并确保该函数只会被执行一次
            once.Do(func() {
                fmt.Println("once")
            })
        }()
    }

    time.Sleep(1 * time.Second)
}

常用于单例模式,如

package main

import (
    "fmt"
    "sync"
)

type Singleton struct {
    // Fields for the singleton
}

var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{}
        fmt.Println("Singleton instance created")
    })
    return instance
}

sync.WaitGroup

WaitGroup 用于等待一组 goroutine 完成。可以增加计数器,并在 goroutine 完成时减少计数器

package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            once.Do(func() {
                fmt.Println("once")
            })
        }()
    }

    wg.Wait()
}

sync.Mutex

Mutex 是一个互斥锁,用于保护临界区,确保同一时间只有一个 goroutine 能够访问被保护的代码.

package main

import (
    "fmt"
    "sync"
)

var count int
var mutex sync.Mutex

// 通过Mutex解决线程安全问题
func Increase() {
    mutex.Lock()
    count++
    mutex.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 500; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            Increase()
        }()
    }
    wg.Wait()
    fmt.Println(count)
}

sync.RWMutex

RWMutex 是一种读写互斥锁,允许多个读操作同时进行,但写操作会独占锁。

package main

import (
    "fmt"
    "sync"
)

var counter int
var rwMutex sync.RWMutex

func read() int {
    rwMutex.RLock()
    defer rwMutex.RUnlock()
    return counter
}

func write(val int) {
    rwMutex.Lock()
    counter = val
    rwMutex.Unlock()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            write(i)
            fmt.Println("Read value:", read())
        }(i)
    }
    wg.Wait()
}

基本概念

  • 源码文件的第一行有效代码必须是 package pacakgeName 语句,通过该语句声明自己所在的包
  • 一般包的名称就是其源文件所在目录的名称,虽然Go语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰
  • 包可以定义在很深的目录中,包名的定义是不包括目录路径的,但是包在引用时一般使用全路径引用。比如在GOPATH/src/a/b/ 下定义一个包 c。在包 c 的源码中只需声明为package c,而不是声明为package a/b/c,但是在导入 c 包时,需要带上路径,例如import "a/b/c"
  • 包名一般是小写的,使用一个简短且有意义的名称
  • 包名一般要和所在的目录同名,也可以不同,包名中不能包含 - 等特殊符号
  • 包一般使用域名作为目录名称,这样能保证包名的唯一性,比如 GitHub 项目的包一般会放到 GOPATH/src/github.com/userName/projectName 目录下
  • 包名为 main 的包为应用程序的入口包,编译不包含 main 包的源码文件时不会得到可执行文件
  • 一个文件夹下的所有源码文件只能属于同一个包,同样属于同一个包的源码文件不能放在多个文件夹下

包的导入

  • import 导入语句通常放在源码文件开头包声明语句的下面
  • 导入的包名需要使用双引号包裹起来
  • 包名是从 GOPATH/src/ 后开始计算的,使用 / 进行路径分隔
  • 包的引用路径有两种写法,分别是全路径导入和相对路径导入,相对路径只能用于导入 GOPATH 下的包,标准包的导入只能使用全路径导入

包的引用格式

  1. 标准引用格式
package main
import "fmt"

func main() {
    fmt.Println("Hello world")
}
  1. 自定义别名引用格式
package main
import F "fmt"

func main() {
    // 使用F.来代替标准引用格式的fmt.
    F.Println("Hello world")
}
  1. 省略引用格式
package main
import . "fmt"

func main() {
    // 不用加前缀fmt.
    Println("Hello world")
}
  1. 匿名引用格式
package main
import (
    // 在引用某个包时,如果只是希望执行包初始化的 init 函数,而不使用包内部的数据时,可以使用匿名引用格式
    // 一个包可以有多个 init 函数,包加载时会执行全部的 init 函数,但并不能保证执行顺序
    _ "database/sql"
    "fmt"
)
func main() {
    fmt.Println("Hello world")
}

包加载

包加载

  • 包初始化程序从 main 函数引用的包开始,逐级查找包的引用,直到找到没有引用其他包的包,最终生成一个包引用的有向无环图
  • Go 编译器会将有向无环图转换为一棵树,然后从树的叶子节点开始逐层向上对包进行初始化
  • 单个包的初始化过程如上图所示,先初始化常量,然后是全局变量,最后执行包的 init 函数

拉取非公共module

通过 Go 命令拉取项目依赖的公共 Go Module,只需要在每个开发机上设置环境变量 GOPROXY,配置一个高效且可靠的公共 GOPROXY 服务,如 set GOPROXY=https://goproxy.cn,direct。但是如果同时需要拉取私有的包怎么办呢?如企业自己开发的module依赖。这时我们需要配置set GOPRIVATE=*gitee.com,表示 gitee.com的包不使用代理而是直接拉取。通过go env命令可以看出:

set GONOPROXY=*gitee.com
set GONOSUMDB=*gitee.com
set GOPRIVATE=*gitee.com

开发私有module

  1. 如我们开发一个打印版本的方法,封装在了gitee.com/happywzy/go-module-test包中。
package pkg

func Version() string {
    return "version 1"
}

我们模拟提交了很多个版本,并在不同的commit打上了一些tag,提交到了远程仓库。

# 查看版本号
> git ls-remote -q origin          
9da810c37c774a3775a518d16cfe032a39f862ea        HEAD
9da810c37c774a3775a518d16cfe032a39f862ea        refs/heads/master
9da810c37c774a3775a518d16cfe032a39f862ea        refs/tags/v1.0.0
f4c3585328e0a686ad35537e390473917c4b202b        refs/tags/v1.0.0-test
1e40db9baf18cbb8ed5784858bc9418c738792a7        refs/tags/v2
  1. 我们需要在另一个Module中使用Version()方法
    go.mod配置如下:
module gitee.com/happywzy/go-module

go 1.19

require (
    gitee.com/happywzy/go-module-test v1.0.0-test
)
  • 版本号可以配置成commitId,执行go mod tidy后,会根据commitId自动生成伪版本号,如gitee.com/happywzy/go-module-test v0.0.0-20240524023550-1e40db9baf18,这个版本号不要手敲,防止出错。
  • 版本号可以配置成tag,如v1.0.0v1.0.0-test,但是配置成v2这种好像无法识别。
  • 可以通过go get 命令去安装module,如 go get gitee.com/happywzy/go-module-test@master@后面可以是分支名、tag名、commit ID,非常方便。

如何使用本地的Module

前面介绍的方式需要Module推送到远程仓库后才能使用,这在开发阶段非常不适用,如何直接使用本地的Module呢?有两种方式。

  1. 使用 go.mod 中 replace
replace (
    gitee.com/happywzy/go-module-test v1.0.0-test => ../../happywzy/go-module-test
)

表示gitee.com/happywzy/go-module-test v1.0.0-test这个包直接使用本地路径下的代码。这种方式需要注意在提交代码前需要将replace删除,防止出错。

  1. 使用 go.work

    go.work1.18 版本之后才有。

go 1.19

use (
    ./happywzy/go-module-test
    ./happywzy/go-module
)

go.work文件需要位于两个模块的上层目录,通过go work init初始化生成,使用go work use gitee.com/happywzy/go-module-test添加。

go.work 这个文件无需提交到git,仅用于本地开发即可。