0%

说明

在三台机器上搭建一个集群

服务器 IP
node152 172.16.20.152
node153 172.16.20.153
node154 172.16.20.154

部署

  • 安装etcd
yum -y install etcd
  • 修改默认配置: /etc/etcd/etcd.conf
ETCD_DATA_DIR="/home/etcd/node152.etcd"
ETCD_LISTEN_PEER_URLS="http://172.16.20.152:2380"
ETCD_LISTEN_CLIENT_URLS="http://172.16.20.152:2379,http://localhost:2379"
ETCD_NAME="node152"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://172.16.20.152:2380"
ETCD_ADVERTISE_CLIENT_URLS="http://172.16.20.152:2379"
ETCD_INITIAL_CLUSTER="node152=http://172.16.20.152:2380,node153=http://172.16.20.153:2380,node154=http://172.16.20.154:2380"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster"
ETCD_INITIAL_CLUSTER_STATE="new"

注意:数据目录etcd用户有权限创建,或者提前创建好并分配权限;
其他节点根据IP修改.

  • 修改服务配置: /usr/lib/systemd/system/etcd.service
[Unit]
Description=Etcd Server
After=network.target
After=network-online.target
Wants=network-online.target

[Service]
Type=notify
WorkingDirectory=/var/lib/etcd/
EnvironmentFile=-/etc/etcd/etcd.conf
User=etcd
# set GOMAXPROCS to number of processors
ExecStart=/bin/bash -c "GOMAXPROCS=$(nproc) /usr/bin/etcd --name=\"${ETCD_NAME}\" --data-dir=\"${ETCD_DATA_DIR}\" --listen-client-urls=\"${ETCD_LISTEN_CLIENT_URLS}\" \
--listen-peer-urls=\"${ETCD_LISTEN_PEER_URLS}\" \
--initial-advertise-peer-urls=\"${ETCD_INITIAL_ADVERTISE_PEER_URLS}\" \
--advertise-client-urls=\"${ETCD_ADVERTISE_CLIENT_URLS}\" \
--initial-cluster=\"${ETCD_INITIAL_CLUSTER}\"  \
--initial-cluster-token=\"${ETCD_INITIAL_CLUSTER_TOKEN}\" \
--initial-cluster-state=\"${ETCD_INITIAL_CLUSTER_STATE}\""

Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
  • 启动并设置开机启动
systemctl start etcd
systemctl enable etcd
  • 检查集群状态
[root@data7 home]# etcdctl cluster-health
member 1031e29bb8572c51 is healthy: got healthy result from http://172.16.20.152:2379
member 6937e3968a3fec18 is healthy: got healthy result from http://172.16.20.154:2379
member 8b79f77c6775414b is healthy: got healthy result from http://172.16.20.153:2379
cluster is healthy

[root@data7 home]# etcdctl member list
1031e29bb8572c51: name=node152 peerURLs=http://172.16.20.152:2380 clientURLs=http://172.16.20.152:2379 isLeader=true
6937e3968a3fec18: name=node154 peerURLs=http://172.16.20.154:2380 clientURLs=http://172.16.20.154:2379 isLeader=false
8b79f77c6775414b: name=node153 peerURLs=http://172.16.20.153:2380 clientURLs=http://172.16.20.153:2379 isLeader=false

常见问题

  1. 通过v3版本写入,etcdctl 命令查不到.
    ETCD v2 v3版本数据不互通,需要指定版本:export ETCDCTL_API=3

比如有一个请求,需要先解密,返回的内容需要先加密

可以用gin.HandlerFunc统一处理

// Data是加密的,需要统一解密传给后端业务处理
type Request struct {
    AggEntityID string `json:"AggEntityId"`
    Data        string `json:"Data"`
    TimeStamp   string `json:"TimeStamp"` //yyyyMMddHHmmss
    Seq         string `json:"Seq"`       //0001
    Sig         string `json:"Sig"`       // 参数签名
}
// Data是明文的,需要统一加密返回给请求端
type Response struct {
    Ret  int32  `json:"Ret"`
    Msg  string `json:"Msg"`
    Data string `json:"Data"`
    Sig  string `json:"Sig"`
}

统一处理

import (
    "bytes"
    "encoding/json"
    "gitee.com/njderi/public-plat/internal/vpp/front-gateway/model"
    "github.com/gin-gonic/gin"
    "net/http"
)

// EncryptResponse 返回Response时先加密Data字段
func EncryptResponse() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 创建一个缓冲区来捕获响应
        w := &responseWriter{body: &bytes.Buffer{}, ResponseWriter: c.Writer}
        c.Writer = w

        c.Next()

        if c.Writer.Status() != http.StatusOK {
            return
        }
        // 获取response
        var res model.Response
        if err := json.Unmarshal(w.body.Bytes(), &res); err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read response body"})
            return
        }
        // 修改response
        res.Ret = 1
        res.Msg = "Encrypted Response"
        encryptedBody, err := json.Marshal(res)
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to marshal encrypted response"})
            return
        }
        // 修改response body
        c.Writer = w.ResponseWriter
        c.Writer.Header().Set("Content-Type", "application/json")
        c.Writer.WriteHeader(http.StatusOK)
        c.Writer.Write(encryptedBody)
    }
}

type responseWriter struct {
    gin.ResponseWriter
    body *bytes.Buffer
}

func (w *responseWriter) Write(b []byte) (int, error) {
    return w.body.Write(b)
}
import (
    "bytes"
    "encoding/json"
    "gitee.com/njderi/public-plat/internal/vpp/front-gateway/model"
    "github.com/gin-gonic/gin"
    "io"
    "io/ioutil"
    "net/http"
)

// DecryptRequest 接收到Request后解密Data字段
func DecryptRequest() gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.Request.Method == http.MethodPost || c.Request.Method == http.MethodPut {
            body, err := io.ReadAll(c.Request.Body)
            if err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
                c.Abort()
                return
            }

            var req model.Request
            if err := json.Unmarshal(body, &req); err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format"})
                c.Abort()
                return
            }

            req.Data = "decrypt(req.Data, key)"

            decryptedBody, err := json.Marshal(req)
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to marshal decrypted request"})
                c.Abort()
                return
            }

            c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(decryptedBody))
        }
        c.Next()
    }
}
import (
    "github.com/gin-gonic/gin"
)

func SetupAuthGroup(c *gin.Engine) {
    authGroup := c.Group("/vpp/fg/v1")
    // 这个group下所有的接口都会先解密,返回先加密
    authGroup.Use(controller.DecryptRequest())
    authGroup.Use(controller.EncryptResponse())
    {
        authGroup.POST("/query_token", controller.GetToken)
    }
}

事务隔离级别

MySQL 8.4支持的事务隔离级别包括 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

临时修改

-- 查看当前隔离级别的命令
select @@global.transaction_isolation,@@transaction_isolation;
-- 修改全局隔离级别为读提交
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 修改会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

永久生效

# my.cnf
transaction-isolation = READ-COMMITTED

三个工具

  • protoc
  • protoc-gen-go
  • protoc-gen-go-grpc

protoc

Gitehub下载页下载最新的 protoc ,window 环境下载 protoc-27.0-win64.zip,解压后将 bin/protoc.exe 移动到 $GOPATH/bin 目录下。

注意:$GOPATH/bin 需要配置在系统环境变量 PATH 中.

protoc-gen-go

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

protoc-gen-go-grpc

go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

三个工具安装好后,在 $GOPATH/bin 下有:protoc.exeprotoc-gen-go.exeprotoc-gen-go-grpc.exe 就算安装好了。

测试

准备一个 hello.proto :

syntax = "proto3";

option go_package = "./;proto";

package proto;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloResponse) {}
  rpc SayHi (HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string message = 1;
}

生成 hello.pb.gohello_grpc.pb.go

protoc --go_out=. --go-grpc_out=. hello.proto

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