我们提供安全,免费的手游软件下载!

安卓手机游戏下载_安卓手机软件下载_安卓手机应用免费下载-先锋下载

当前位置: 主页 > 软件教程 > 软件教程

主从复制原理

来源:网络 更新时间:2024-05-13 15:30:43

主从复制是数据库系统中常见的原理之一,其核心在于实现数据在主节点和从节点之间的同步。主节点负责接收写操作,从节点负责读取数据和备份。下面将介绍主从复制的步骤和网络模型。

建立连接

主从复制的第一步是建立连接。从节点需要配置主节点的IP和端口,通过执行replicaof命令并发送psync命令来与主节点建立连接。

同步数据到从库

主节点会生成RDB文件并发送给从库,同时为每一个从节点开辟一块replication buffer缓冲区来记录从生成RDB文件开始收到的所有写命令。从库接收RDB文件后清空数据并加载RDB文件。

发送新写的命令给从库

主节点会将replication buffer缓冲区中的数据(增量写命令)发送到从节点,从节点接收并执行,使从节点与主节点保持同步。

基于长连接传播

主从复制还维持着心跳机制,方便后续命令传输。网络模型主要包括阻塞IO、非阻塞IO、IO多路复用、信号驱动IO和异步IO。

IO多路复用

IO多路复用中使用的文件描述符简称FD,可以提高IO效率。在IO多路复用中,有select、poll和epoll等多种方式。

select

select的流程是首先创建fd_set rfds,然后执行select指令,内核会遍历fd_set,最后遍历fd_set找到就绪的数据。然而,select存在一些问题,比如需要将整个fd_set从用户空间拷贝到内核空间,还需要遍历一次,并且最大监听数为1024。

poll

poll将数据改为了链表,但仍需遍历。

epoll

epoll包括创建epoll实例、添加需要监听的fd以及等待fd就绪等步骤。epoll有两种通知模式:levelTriggered模式和EdgeTriggered模式。

淘汰策略

在淘汰策略方面,有全体和ttl两种分类,其中LRU、抽样LRU和LFU是常见的淘汰策略。在RedisObject中使用逻辑访问次数来记录访问频率,并采取相应的淘汰策略。

总结

文章总结了主从复制的原理和网络模型,以及IO多路复用的几种方式。同时提到了淘汰策略中的LRU、抽样LRU和LFU,对于理解数据库系统和网络模型具有一定的参考意义。


附带的TCP连接Redis客户端是使用Go语言编写的。

下面是客户端的Go语言代码。

package main

import (
    "bufio"
    "errors"
    "fmt"
    "io"
    "net"
    "strconv"
)

// RedisClient 封装用于连接
type RedisClient struct {
    conn net.Conn
    //包装一层 方便读写
    writer *bufio.Writer
    reader *bufio.Reader
}

func main() {
    //连接redis
    conn, err := net.Dial("tcp", "127.0.0.1:6379")
    if err != nil {
       fmt.Println("连接redis失败", err)
    }
    client := RedisClient{conn: conn, writer: bufio.NewWriter(conn), reader: bufio.NewReader(conn)}
    defer client.conn.Close()
    //发送命令
    client.sendRequest([]string{"set", "name", "方块"})
    //zrange boards:2024-4 0 -1
    //client.sendRequest([]string{"zrange", "boards:2024-4", "0", "-1"})
    //LRANGE tlist 0 -1
    client.sendRequest([]string{"LRANGE", "tlist", "0", "-1"})

}

func (client *RedisClient) sendRequest(args []string) interface{} {
    length := len(args)
    firstCommand := fmt.Sprintf("%s%d", "*", length)
    client.writeCommand(firstCommand)
    for _, s := range args {
       n := len(s)
       client.writeCommand("$" + strconv.Itoa(n))
       client.writeCommand(s)
       println(n, s)
    }
    response := client.handleResponse()
    return response
}

// 写命令
func (client *RedisClient) writeCommand(s string) {
    client.conn.Write([]byte(s + "\r\n"))
}

// 解析返回结果
func (client *RedisClient) handleResponse() interface{} {
    r, _, _ := client.reader.ReadRune()
    flag := string(r)
    switch flag {
    case "+":
       return client.ReadLine()
    case "-":
       return client.ReadLine()
    case ":":
       line := client.ReadLine()
       res, _ := strconv.Atoi(line)
       return res
    case "$":
       readRune := client.ReadLine()
       length := string(readRune)
       if length == "-1" {
          return nil
       } else if length == "0" {
          return ""
       }
       lll, _ := strconv.Atoi(length)
       bytes := make([]byte, lll+2)
       n, _ := client.reader.Read(bytes)
       return string(bytes[:n])
    case "*":
       return client.readBulkString()
    default:
       return errors.New("错误")
    }
}

// 读一行
func (client *RedisClient) ReadLine() string {
    bytes, _, _ := client.reader.ReadLine()
    return string(bytes)
}

// 读到末尾
func (client *RedisClient) ReadToEnd() string {
    var size = 1024
    bytes := make([]byte, size)
    var temp = ""
    for {
       n, err := client.reader.Read(bytes)
       temp += string(bytes[:n])
       if err == io.EOF || n == 0 || n < size {
          break
       }
    }
    return temp
}

func (client *RedisClient) readBulkString() interface{} {
    counts, _ := strconv.Atoi(client.ReadLine())
    if counts <= 0 {
       return nil
    }
    var lists []interface{}
    for i := 0; i < counts; i++ {
       res := client.handleResponse()
       lists = append(lists, res)
    }
    return lists
}