MySQL复制

前言

在上一篇文章,我们整理总结MySQL的常用知识点,在这里,我们尝试学习MySQL复制的相关概念。

什么是MySQL的复制

MySQL的复制允许将主服务器的数据复制到一个或多个从服务器中。默认情况下,复制是异步的。从服务器不需要永久连接就可以从主服务器获取最新的数据。根据配置,可以在数据库中复制所有的数据库,选定数据库,甚至是选定的表。

为什么要实现MySQL复制

MySQL的复制有以下优点:

  1. 在多个从服务器中做负载均衡提升性能
  2. 数据安全性,可以在多个从服务器复制数据,而不会影响主服务器。
  3. 离线数据分析,可以在从服务器上做实时数据分析。
  4. 远程数据分发,为网站创建本地的副本。

MySQL复制方式

MySQL支持不同的复制方式。传统的方方式是基于从主二进制日志记录和位置进行同步。新的基于全局事务标示符GTIDs(Global Transaction Identifiers 是事务性的,因此不需要关注日志文件或位置,极大地简化了许多常见的复制任务。

同步 vs 半同步

MySQL的复制支持不同类型的同步。最初的同步是单向异步复制,也就是说一个服务器充当主服务器,一个或多个服务器充当从服务器。

异步复制的机制是主服务器将事件写入其二进制日志,但不知道从服务器是否或合适检索和处理这些事件。使用异步复制是,如果主服务器出现奔溃,它提交的事务可能不会传输到任何从服务器。因此,在这种情况下,把一个从服务器提升为主服务器,可能会缺少奔溃的主服务器丢失的部分事务。

半同步复制,可用做异步复制的升级版:

  1. 从服务器链接到主服务器时会显示是否具有半同步的能力。
  2. 如果主服务器启用了半同步复制,并且至少有一个半同步从服务器,那么在主服务器上执行事务提交的线程,会等待至少一个从服务器确认接受到事务的全部事件,或者直到超时发生。
  3. 只有将事件写入其中继日志并刷新到磁盘,从服务器才会确认收到事务的事件。
  4. 如果在没有任何从服务器确认事务的情况下发生超时,则主服务器将恢复为异步复制。当至少有一个版同步从服务器赶上时,主服务器将恢复版同步复制。
  5. 必须在主服务器和从服务器都启用版同步复制。如果在主服务器上禁用了版同步复制,或者在主服务器上启用了但没有在从服务器上启用半同步复制,则主服务器使用同步复制。

配置参数

server-id

指定服务器ID.当启用二进制日志记录时,需要为每一个服务器设置一个唯一的服务器ID,范围为1到232 − 1。唯一意味着每个ID必须与任何其他复制主或者从服务器使用的ID不同。

如果设置0 ,则意味着主服务器拒绝从服务器连接,从服务器会拒绝连接到主服务器。

binlog-do-db

设置需要记录二进制日志的数据库。多个数据库,需要设置多行记录,由于MySQL的数据库名称可以包含逗号,因此带有逗号分割列表,被认为是一个数据库。

binlog-ignore-db

设置不需要二进制日记记录的数据库。

日志

MySQL自定义较多的日志概念,这里尝试整理总结下。

binary log

二进制日志包含描述数据库更改,如表创建操作或表数据更改的事件。它包含可能进行更改的语句事件,还包含有关每个语句花费更新数据的时间信息。二进制日志有2个重要的目的:

  1. 对于复制,主复制服务器上的二进制日志提供要发送到从服务器的数据更改记录。
  2. 部分数据恢复操作需要使用二进制日志。

relay log

中继日志由从主服务器的二进制日志读取并由I/O线程写入的事件构成。中继日志中的事件作为SQL线程的一部分在从服务器上执行。

undo log

撤销日志,用于数据一致性读取,或者用于事务的回滚和撤销。存在与撤销表空间,保存由当前事务修改的数据副本的存储区域。如果另外一个事务需要作为一致性读取操作的一部分,则返回该区域检索到的未经修改的数据。

redo log

在服务器奔溃恢复期间使用的一种基于磁盘的数据结构,用于更正由不完全事务写入的数据。在意外之前没有完成的数据文件更新的修改将自动重放。

参考链接

  1. MySQL Replication
  2. MySQL Semisynchronous Replication
  3. Server System Variables
  4. The Binary Log

练习 Web 爬虫

问题

在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。

修改 Crawl 函数来并行地抓取 URL,并且保证不重复。

提示:你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!

背景知识

  1. 学习 Go语言的sync.Mutex用法,阅读计数器实现的源代码,这个例子也可以参数计数器来实现。
  2. 在多个协程抓取时可以添加互斥锁,这也是Mutex的应用。
  3. 需要你深刻的理解协程的编程方式,也需要注意信道的数据获取方式。

实现思路

  1. 基本代码算是实现了,但是没有做协程安全,如果多个协程同时处理,就会有变量被覆盖的情况。
  2. 还有一种情况,就是并没有对爬虫进行去重判断,有可能实现的爬虫会一直死循环,并需要对map进行互斥锁判断,避免会有冲突。
  3. URL的抓取实现是一些测试用例,并不是真实世界的抓取,现实中的爬虫会比这个更复杂,简单的来讲,最少要添加一个超时的处理,异常的处理。
  4. 在Crawl中如何实现协程抓取URL?如何同步控制map的URL更新?
  5. 定义SafeCounter类型,类似计数器有两个变量,一个是字符串和整数的映射,另外一个是互斥锁的实现。定义一个全局变量sc,在全部的函数里都可以使用。
  6. 由于map不是协程安全,所有验证函数validUrl和标记函数markUrl函数,都需要加下互斥锁。
  7. 并行的抓取,可以在Crawl实现,对抓取回来的内容里的二级URL进行协程处理,并发抓取URL。
    难点在于下面这两个地方:
//方案1
//开启协程处理二级URL
ch := make(chan int, len(urls))
for _, u := range urls {
    go crawl(u, depth-1, fetcher, ch)
}
for i:=0; i < len(urls); i++ {
    //从信道接受数据
     <- ch
}

func crawl(url string, depth int, fetcher Fetcher, ch chan int) {
    Crawl(url, depth, fetcher)
    ch <- 1
}

第一个是,根据对应urls数量开启配置信道缓冲区大小。
通过for循环urls数量,实现并行协程处理,然后重点来了,从信道缓冲区,接受协程发送过来的数据。
第二个,在私有函数crawl,注意,调用完抓取函数Crawl后,需要关闭信道。

细节

一级URL 标题 内容里的二级URL
https://golang.org/ “The Go Programming Language” "https://golang.org/pkg/","https://golang.org/cmd/"
https://golang.org/pkg/ “Packages” "https://golang.org/","https://golang.org/cmd/","https://golang.org/pkg/fmt/","https://golang.org/pkg/os/",
https://golang.org/pkg/fmt/ “Package fmt” "https://golang.org/","https://golang.org/pkg/",
https://golang.org/pkg/os/ “Package os” "https://golang.org/","https://golang.org/pkg/",

实现代码

GitHub

参考链接

  1. 练习:Web 爬虫
  2. GO指南 练习:Web爬虫
  3. 学习一下golang 练习70 web crawler

练习 等价二叉查找树

问题

不同二叉树的叶节点上可以保存相同的值序列。例如,以下两个二叉树都保存了序列 `1,1,2,3,5,8,13`。


在大多数语言中,检查两个二叉树是否保存了相同序列的函数都相当复杂。 我们将使用 Go 的并发和信道来编写一个简单的解法。

本例使用了 tree 包,它定义了类型:

type Tree struct {
    Left  *Tree
    Value int
    Right *Tree
}
1. 实现 Walk 函数。

2. 测试 Walk 函数。

函数 tree.New(k) 用于构造一个随机结构的已排序二叉查找树,它保存了值 k, 2k, 3k, ..., 10k。

创建一个新的信道 ch 并且对其进行步进:

go Walk(tree.New(1), ch)
然后从信道中读取并打印 10 个值。应当是数字 1, 2, 3, ..., 10。

3. 用 Walk 实现 Same 函数来检测 t1 和 t2 是否存储了相同的值。

4. 测试 Same 函数。

Same(tree.New(1), tree.New(1)) 应当返回 true,而 Same(tree.New(1), tree.New(2)) 应当返回 false。

Tree 的文档可在[这里](https://godoc.org/golang.org/x/tour/tree#Tree)找到。

背景知识

  1. 你需要了解什么是等价二叉树?
  2. 阅读Tree类型的Go实现源代码。代码实现了3个函数,New函数新建二叉树,insert函数插入新元素,实现了打印字符串String接口
  3. 了解协程,信道的实现原理。
  4. 判断等价二叉树相等的话,那么左右节点的值都需要相等。

实现思路

  1. 实现 Walk 函数,循环通过信道发送数据,然后接受并输出。Walk的遍历方式为中序遍历,先遍历根节点,然后左节点,最后右节点。
  2. 注意tree的遍历方式,也就是获得值的方式,实现一个私有的walk函数,签名和Walk一致,因为Tree类型较为简单,并不能直接判断循环的结束。故采用这样的方式,循环遍历到最后,t == nil时,关闭信道,不然会报错的。
  3. 实现 Same 函数,循环判断tree每一个值是否相等。难点在于,创建2个信道,去读取Walk返回的Tree的节点值。

实现代码

GitHub

参考链接

  1. 练习:等价二叉查找树
  2. 二叉查找树
  3. Go指南练习之《等价二叉树》(Equivalent Binary Trees)