liuhao163.github.io

杂七杂八


  • Home

  • Categories

  • Tags

  • Archives

  • Sitemap

mysql数据的完整性--草稿

Posted on 2019-02-03 | Edited on 2022-09-21 | In Mysql , Mysql基础

mysql的数据删除的原理

我的数据删除了为什么表还是这么大?

mysql删除数据的原理

mysql采用标记删除法,即并不是物理删除而是采用标记删除。
delete数据时候:数据标记为可服用,如果插入的数据在这个数据页,可复用这个空间,例如:索引页的范围内可复用,超过了则需要插入新的空间。
delete整个数据页:如果数据页上所有数据被删除,则整个数据页可复用。

数据页的分裂和合并

如果数据页利用率小,数据页会合并。
如果数据页满了,中间插入会导致数据页分裂。

释放表空间

1
ALTER TABLE t ENGINE=innodb;

在mysql5.5会有MDL锁,不能Online执行

在mysql5.6会有online ddl机制,但是会小号CPU和IO(因为扫表),大表请谨慎。

这两种区别
5.5是在server层,copy出tmp_table,这时候不能对原表进行操作,然后copy到老表中。
5.6是在innodb层,copy出tmp_file,改的写row_log,然后重放生成逻辑一直的新表,tmp_file inplace操作到老表中。(tmp_file是个inplace操作)

mysql是怎么保证高可用的

Posted on 2019-02-03 | Edited on 2022-09-21 | In Mysql , Mysql高可用架构

mysql是怎么保证高可用的–如何解决主备延迟

mysql的主从同步会保证主库到从库的最终一致性,但是mysql的高可用,光有最终一致性是远远不够的。下面会讨论影响mysql主备高可用的因素以及解决方法。

主备延迟

  • 我们在备库上可以执行命令:seconds_behind_master来查看备库的状态,会显示备库同步数据和主库的时间差(单位:秒),这个时间差计算方式是:同一事物,备库执行完binlog的时间-主库产生binlog的时间备库执行的binlog的时间,当然这个值越小越好。
  • 在网络正常的情况下,主备同步binlog的时间往往很短,真正造成主备延迟的原因往往是relylog的消费赶不上binlog的生产。那么是什么造成主备延迟的呢

主备延迟的原因

  1. 主备库,备库的性能要不主库所在的的机器性能差;–目前很少见了,因为大多是双M配置,所以备库可能会变为主,所以在部署时候都会选择对称部署;
  2. 备库压力大,备库上很多的查询需求,比如备份数据,运营统计等会放在备库执行。在同步binlog时候往往也会占用查询的资源,导致备库的眼里很大,引起主备延迟
  3. 大事物,由于mysql是在主库上事物提交之后才开始传输binlog给备库,这样如果1个很大的事物在主库执行N长时间后,在同步给备库,备库在执行这个事物时候就会造成主从不一致。
    1. 一次删除很多的数据,如:数据库快满了删除数据;
    2. 大表的DDL
  4. 库的并行复制能力,具体放在mysql是如何降低主备延迟的

主备延迟的解决方案

  1. 首先对于双M架构,主备库建议对称部署,保证切换朱备库时候性能没有打的差别
  2. 建议根据需要一主多从,比如说:一个主备做为切换,一个从库作为数据备份,一个从库作为数据分析以及运营支持;
  3. 根本上杜绝大事物,对于删除可以考虑分批删除数据;DDL可以采用开源的工具进行入:gh-ost方案

主备切换的几种策略

可靠性优先

该策略可能会有一段时间服务不可用,流程如下:

  1. 判断备库的seconds_behind_master的值,当相于一个值的时候进行第二步,否则一直重试;
  2. 主库设置readonly=true,即只读;
  3. 备库等待seconds_behind_master=0为止;
  4. 备库设置readonly=fase,即可写;
  5. 切换业务到备库;

注:第一步的作用实际上是希望主库和从库的延时在小于一个阈值时候开始做切换,否则如果相差30分钟,直接切换,那么服务就会有30分钟不可用,业务方一般是不可忍受的。(当然。。。。某些时候我也作为业务方我也就忍了)

可用性优先

和上面相比,保证业务的可用,但是会牺牲掉数据的一致性

  1. 备库设置readonly=fase,即可写;
  2. 切换业务到备库;
  3. 主库设置eadonly=true

注:在下面的情况会导致数据不一致,原因如下:(老的主库是A,备库是B)

  1. A插入了数据c(4),还没有同步到B时候进行切换
  2. 这时候B变为主,B库插入数据c(5),这条数据是(4,5)
  3. 然后B从A通过relylog重放数据是c(4)那条数据,这时候B库中(5,4)
  4. A库通过重放relylog获取刚才c(5)的数据,这时候数据是(5,5)

avatar

总结,Mysql作为数据的存储,还是应该优先保证数据的一致性,所以建议采用可靠性优先方案保证数据的一致性。否则,对于一些依赖写入的业务,可能因为在切换主备时候数据不一致引起逻辑的错误,而且由于往往写入不是一个单一的操作,这一次的不一致会导致后续的引发更多的问题,最终导致数据无法修复。

其他的好的建议:如果业务不依赖mysql的写入可以针对写入进行降级,比如:让数据先写到临时文件中,然后切换后慢慢的同步到mysql中。

ByteBuf

Posted on 2019-02-03 | Edited on 2022-09-21 | In netty

ByteBuf

介绍Netty中的ByteBuf

byteBuf种类

netty中的byteBuf除了JDK中的heapBuffer和directBuffer还有compositeBuffer类型

传统的Java的nio操作都是ByteBuffer.allocate(int capacity)实际上是神了一个HeapBuffer。当通过Socket传输对象时候我们实际上是申请了一块临时的DirectBuffer(堆外内存),将数据copy到DirectBuffer,在write出去。所以HeapBuffer传输数据会有数据copy,DirectBuffer没有数据copy。但是申请和管理DirectBuffer会更复杂更慢。

Heap Buffer 堆缓冲区

ByteBuf将数据存储在JVM的堆空间,通过将数据存储在数组中实现的。
优点是:由于数据存储在JVM的堆中可以快速创建和快速释放,并且提供了数组的直接快速访问的方法。
缺点是:每次读写数据都要先将数据拷贝到直接缓冲区再进行传递。

Direct Buffer 直接缓冲区

在堆之外直接分配内存,直接缓冲区不会占用堆的容量。
优点是:在使用Socket传递数据时性能很好,由于数据直接在内存中,不存在从JVM拷贝数据到直接缓冲区的过程,性能好。
缺点是:因为Direct Buffer是直接在内存中,所以分配内存空间和释放内存比堆缓冲区更复杂和慢

Mysql是如何保证数据不丢失的

Posted on 2019-02-02 | Edited on 2022-09-21 | In Mysql , Mysql基础

Mysql是如何保证数据不丢失的

binlog

  1. 事务执行过程中binlog写入到binlog cache,事务提交时写入到binlog文件中。
  2. 事务的原子性决定这无论事务有多大,binlogcahe都要一次性完整的写入到binlog文件中,写入方式如下:
    1. 系统为每个binlog_cache开辟了一片内存(每个线程都有一个),参数是blong_cache_size,超过这个阈值的会保存在临时文件中
    2. 事务提交时候,出于性能的考虑回将bionlog_cache都write到文件系统的page_cache中,在通过fsync刷新到磁盘文件中。如下图所示是binlog的同步机制。

如图:
avatar

  • 只有fsnyc刷盘这不操作才会占用IOPS。
  • write和fsync的时机是用binglog_sync参数来控制的
    • =0 每次事务提交时候都只write不sync
    • =1 每次事务提交都sync
    • =N(N>1) 每次事务提交都write,积攒到N时候才会fsync

在IO出现瓶颈时候我们可以设置一个较大的值来提升性能(个人不是很推荐做,如果出现这种情况建议从业务考虑优化数据库)

redolog的写入机制

redolog从写入到最终写入到磁盘中会经历如下的阶段:写入redolog-buffer中–>在事务提交、或者一定时机(见下面)下:redolog-buffer写入到文件系统的pagecache(write)–>文件系统pagecache写入到磁盘中(fsnyc),这样的操作的意义是为了提高mysql的吞吐的,具体的机制见下面:

avatar

  1. 一个事务会产生很多条redolog如果每次直接持久化磁盘会消耗大量的磁盘IO,所以redolog会先写入redolog-buffer中,之后在write文件系统的到pagecache中,这俩步是内存操作很快。
  2. 是否会影响数据的持久化,比如mysql在事务进行中crash了,这时候redolog-buffer中的数据丢失怎么办?答案是由于事物没有提交,所以事物会进行回滚。
  3. redolog-buffer持久化的条件和机制:
    1. 受参数innodb_flush_log_at_tx_commit的控制:
      1. =0:每次提交都只停留在redolog-buffer中;
      2. =1:每次提交都会持久化到磁盘中;
      3. =2:每次提交都只会写入到文件的pagecache中;
    2. innodb后台会有一个线程每秒一次,会把redolog-buffer中的日志,调用write写到pagecahe中,在fsync到磁盘中;
    3. 其他场景触发redolog的持久化
      1. 当redolog-buffer占到了innodb_log_buffer_size的一半时候,会调用write将buffer中的log写入到文件系统的pagecache中
      2. 另一种是并行事物提交时候,如果innodb_flush_log_at_tx_commit设置为1时候会,即时当前事物没有commit,也会将redolog写入到文件系统中。

双1设置

即:binlogsnyc=1,innodb_flush_log_at_tx_commit也设置为1。
由于innodb的事物提交redolog和binlog是2PC。
所以当redolog在prepare时候,为了故障恢复一定会持久化一次,所以这时候需要fsync到磁盘中。
binlog在进行fsync到磁盘中
这时候事物在redolog在commit时候,mysql由于有奔溃恢复机制和后台线程每秒轮训一次刷盘会认为redolog没有必要在fsync一次到磁盘了,只会写入到文件的pagecache中。

gourp commit机制

一个事物的提交由于redolog和binglog都要持久化,磁盘IO还是很大Mysql是如何优化这部分呢。这里mysql采用了组提交(group commit)的方式。
如图所示:
avatar

  • 首先介绍下LSN(log sequence number),一个单调递增的序号,用来对应redolog的一个个写入点。当然也会写入到数据页中,用于flush脏盘时候避免重复执行(不在讨论范围内)
  • 如图,当3个事务同时都写完redolog-buffer 并且处于prepare阶段时候,这时候就构成了一个gourp
    1. 第一个先到达的trx1,成为组里的leader,LSN=50;
    2. 等trx1开始写盘时候,组里已经有其他俩个事物,这时候LSN=160;
    3. trx1开始写盘,所有lsn<160的日志都会被写入到磁盘中;
    4. trx3,trx4就可以直接返回了;

所以在并发事物中,当写完redolog,越晚调用fsync,带的log越多性能也就越好;mysql在这方法采用的是拖时间的策略,即:在双1配置下
redolog-prepare(write)–>binglog(write)–>redolog-prepare(fsync)–>binlog(fsnyc)–>redolog commit(write)

如图:
avatar

  • 如上图所示,由于redolog在write和fsnyc中有一个binlog-write的过程,所以在持久化磁盘时候你可以带上更多的log;
  • 另外:binlog也可以采用组提交,只不过由于这俩个阶段间隔短可能没有redolog那么明显
    • binlog的gruop commit的参数如下:
      • binlog_group_commit_sync_delay:表示延迟多少微妙后就会调用fsnyc
      • binlog_gourp_commit_sync_no_delay_count:表示累计多少次后才调用fsync
      • 俩个参数是or的关系,不过如果binlog_group_commit_sync_delay设置为0,binlog_gourp_commit_sync_no_delay_count就无效了

综上所述:

  • mysql的WAL机制是由于redolog和binlog都是顺序写,保证了高吞吐;
  • 同时采用了组提交的方式,来减少了IOPS;

MySql是如何降低主备延迟的

Posted on 2019-02-02 | Edited on 2022-09-21 | In Mysql , Mysql高可用架构

MySql是如何降低主备延迟的

上一章MySQL是怎么保证高可用的,中提到了主库的并行复制能力会影响主备的延迟。在mysql5.6之前,rely_log被sql_thread重放写入备库,这里的sql_thread只能单线程消费,所以会很大程度降低mysql数据库的吞吐,在TPS高的时候就会有很大的延迟。如果想增强吞吐就要多sql_thread并行处理relay_log,如图:

avatar

但是mysql并行的主从并行复制要遵守如下原则:

  1. 对于不同的事务的binlog如果修改同一行数据,为了避免相互覆盖,不能并行运行只能串行
  2. 对于相同事务的binlog,为了保证事务的完整性,不能并行复制,

5.5之前的主从复制策略

在5.5的时候,为了增强mysql的从库消费relay_log的吞吐,使用者自己实现了主从复制的策略主要有俩种:

按表并行复制

  • 每个work维护一个hash_table,key是binlog的dbname+tablename;
  • 当一个binlog分配给coordainator,coordainator会循环检查这个binlog的key(dbname+tablename),是否在work的hash_table中,如果出现说明这个binlog和这个worker冲突;
    • 如果只有一个冲突,交给这个work处理;
    • 如果大于一个冲突,coordainator进入等待;
    • 如果没有冲突,交给空闲的worker处理;
  • 当worker处理完后会把key从hash_table中删掉;

按行并行复制

和上面类似,只不过这次并行控制的粒度更细,只要保证binlog不是修改的同一行数据即可并行复制,那么hash_table的key就变为binlog的dbname+table+唯一健的值(主键+唯一索引),光有主键不够一定要带上唯一索引的值,否则如下情况会报唯一建冲突。

注:c=原有的值是1,当备库并行复制时候先执行sessionB,由于这时候sessionA还没执行,会报唯一建冲突
| sessionA | sessionB |
| —— | —— |
| update t set c=5 where id=1 ; | - |
| - | update t set c=1 where id=1; |

缺点

  1. 必须要能通过解析binlog获取dbname,tablename,pk,uniqekey,所以binlog要是row类型;
  2. 必须有主键;
  3. 不能有外键;(因为外键的级联更新不在binlog中,所以建冲突就不准确)
  4. 按表的策略:在对热点表的时候coordainator会频繁进入等待,又变成了单线程复制;
  5. 按行的策略:要计算hashtable的key需要额外解析pk和uniquekey所以需要额外的开销;

Mysql5.6的并行复制策略

按照库名并行复制

优点:

  1. 构造hash很快,只需要库名;
  2. binlog即使不是row也能很方便获取库名;

缺点:

  1. 若果服务器上只有一个库,优化效果不明显;

MariaDB的并行复制策略

它的思想是思想是模拟主库运行:利用group commit,每一组事务能同时提交一定是不冲突的;那么主库commit之后就将一组的binlog一起并行执行

  • 为每个group commit分配个递增的commit_id;
  • 将commit_id写入到binlog中
  • 同一个commit_id的binglog分到不同的worker上去。
  • 执行完这个commit_id后在执行下一个commit_id的binlog。

缺点:

  1. 并不是真正的象主库并行,主库当一组事物在commit阶段时候,下一组事物是在运行中的。而从库消费的时候只能是一组一组的消费,所以还是会造成主备的延迟。
  2. 当一组有一个很大的事物在worker中运行时候看,其他的worker先运行完了也只能等在哪里。

MySql5.7的并行复制的策略

采用类似的MariaDb的类似的赋值策略由slave-parallel-type参数控制:

  • DATABASE时候采用类似5.6的并行复制策略
  • LOGICAL_CLOCK时候采用MariaDb的并行复制策略,但是做了优化;

和MariaDb相比,将binlog的执行时期从commit阶段放到了2PC的redolog的prepare阶段,mysql认为事物进入prepare阶段就说明数据是可靠的可以进行主从复制了。

  • 同时处于 prepare 状态的事务,在备库执行时是可以并行的
  • 处于 prepare 状态的事务,与处于 commit 状态之间,在备库执行时也是可以并行的。

我们可以通过binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay参数故意拉长主库的binlog从write到fsync的时间,减少binlog的写盘次数,制造更多同时在prepare时期的binlog,加大并行度。

MySql5.7.22的并行复制优化

增加参数binlog-transaction-dependency-tracking。

  1. COMMIT_ORDER:和上面说的策略一样;但是如果是追历史数据还是会退化成单线程,所以适合线上库。
  2. WRITESET:对于事务更新的每一行计算hash值,组成集合writeset,如果俩个事务没有操作相同的行,也就是writeset没有交集,就可以并行。
  3. WRITESET_SESSION:多了一个约束,即在主库上同一个线程先后执行的事务,在从库上也要保证同样的顺序。

和之前5.5的按行并行复制策略很类似但是有几点优化:

  1. hash值提前算好了,减少了主从同步时候的压力;
  2. 不需要在从库每次都把所有woker遍历一遍找出是否冲突;
  3. 备库不用解析binlog对备库的binlog格式无要求;

思考如下:
假设一个 MySQL 5.7.22 版本的主库,单线程插入了很多数据,3小时候,搭建从库开始同步数据binlog-transaction-dependency-tracking改如何设置?
答:建议采用 WRITESET,因为是单线程插入,如果采用WRITESET_SESSION,那么会退化成单线程同步relaylog。COMMIT_ORDER因为是追历史数据,所以会退化成单线程

Promise 和 Future

Posted on 2019-02-02 | Edited on 2022-09-21 | In netty

Promise 和 Future

netty中的I/O操作大部分为异步,netty在Java的Future的基础上封装了Future和Promise

Future

继承java.util.concurrent.Future接口,并且在Future基础上做了如下的强化。
future是read-only的。我们没有办法在Future中改变状态,只能获取状态。进行后续操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public interface Future<V> extends java.util.concurrent.Future<V>{

/**
* 判断I/O是否成功,和Future的isDone()相比,能得到是否真正完成的结果(有可能成功、失败、取消)
*/
boolean isSuccess();

/**
* 是否已经中断
*/
@Override
boolean cancel(boolean mayInterruptIfRunning);

/**
* 方法表示如果I/O操作失败,返回异常信息
* Returns the cause of the failed I/O operation if the I/O operation has
* failed.
*
* @return the cause of the failure.
* {@code null} if succeeded or this future is not
* completed yet.
*/
Throwable cause();

/**
* 用观察者模式对future操作进行更精准的管理调用,如果get(),需要在代码中显示的调用在完成后续操作,如果用Listerner可以通过noify完成后续操作。
* Adds the specified listener to this future. The
* specified listener is notified when this future is
* {@linkplain #isDone() done}. If this future is already
* completed, the specified listener is notified immediately.
*/
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
}

ChannelFuture

ChannelFuture可以通过添加ChannelFutureListener监听器,当I/O操作完成的时候来通知调用。相比于wait()方式也更推荐这种方式来获取结果状态或者执行后续操作。
此外,不建议在ChannelHandler中调用await(),因为ChannelHandler中事件驱动的方法被一个I/O线程调用,可能一直不回完成,那么await()也可能被I/O线程调用,同样会一直block,因此会产生死锁。
另外在,在Future的基础上增加了获取channle的方法

Promise

对比Future,Promise是writeable的,可以修改状态,调用noify执行Future(Promis)的listerner方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public interface Promise<V> extends Future<V> {

//一下是定义了标记了Future状态的方法,有且只能标记一次,
Promise<V> setSuccess(V result);
boolean trySuccess(V result);
Promise<V> setFailure(Throwable cause);
boolean tryFailure(Throwable cause);
boolean setUncancellable();

@Override
Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);

@Override
Promise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);

@Override
Promise<V> await() throws InterruptedException;

@Override
Promise<V> awaitUninterruptibly();

@Override
Promise<V> sync() throws InterruptedException;

@Override
Promise<V> syncUninterruptibly();
}

DefaultPromise

在DefaultChannelPromise中会,改变状态会通知listerner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

@Override
public Promise<V> setSuccess(V result) {
if (setSuccess0(result)) {
//todo notify
notifyListeners();
return this;
}
throw new IllegalStateException("complete already: " + this);
}

private void notifyListeners() {
EventExecutor executor = executor();
if (executor.inEventLoop()) {
...
notifyListenersNow();
return;
...
}
safeExecute(executor, new Runnable() {
@Override
public void run() {
notifyListenersNow();
}
});
}

private void notifyListenersNow() {
...
for (;;) {
if (listeners instanceof DefaultFutureListeners) {
notifyListeners0((DefaultFutureListeners) listeners);
} else {
notifyListener0(this, (GenericFutureListener<?>) listeners);
}
...
}
}

eg:在register过程中
AbstactServerBootstrap.initAndRegister
–>register()
–>SingleThreadEventLoop.register(channel)
–>SingleThreadEventLoop.register(new DefaultChannelPromise(channel, this))
–>AbstractUnsafe.register(promise,evenlop)–>AbstractUnsafe.resiter0(promise)–>safeSetSuccess(promise)

在safeSetSuccess调用promise.traSuccess()。notifyPromise中的listerner

mysql基础知识--草稿

Posted on 2019-02-01 | Edited on 2022-09-21 | In Mysql , Mysql基础

Mysql的基础知识

Mysql的基础知识

一条sql语句从提交到返回结果经过哪几步

客户端–>连接器–>查询缓存(如果有直接返回,8.0会被废除)–>分析器–>优化器–>执行器–>存储引擎–>server数据。

我们经常用到的查询语句

select * from t where t=1 ordey by date desc limit 0 ,1;

索引

mysql是如何保证主从同步的(binlog)--草稿

Posted on 2019-02-01 | Edited on 2022-09-21 | In Mysql , Mysql高可用架构

mysql是如何保证主从同步的(binlog)–草稿

  1. mysql的主从搭建

  2. mysql的主从数据同步的流程(图)

  3. 查看binlog方式:mysql> show binlog events in ‘master.000001’;

  4. mysql的binlog的几种形式

    1. row。
      1. 优点:可做数据恢复
      2. 缺点:可能会很大,因为是每一条数据一个log,所以如果一个涉及很多行修改的sql语句会有很多row的log。
      3. 恢复的话:相应的事件逆序即可
      4. 查看用:mysqlbinlog -vv data/master.000001 –start-position=8900;
    2. statement。(优点:节省空间只是一条sql语句,缺点:无法做数据恢复)线上不会用
      1. 在某些情况下可能会导致数据不一致比如limit和默认排序根据选择的索引不同数据会不一样。(warn)
    3. mixed(前两种混合 一般用这种)
  5. 数据重放的方法

    1. badcase:mysqlbinlog解析日志,直接copy出statement重发,由于mysql语句是依赖上下文的所以会有风险。
    2. goodcase:mysqlbinlog解析日志,吧解析结果发给mysql执行,如:
      1. mysqlbinlog master.000001 –start-position=2738 –stop-position=2973 | mysql -h127.0.0.1 -P13000 -u$user -p$pwd;
  6. mysql的双M架构(对比M-S)(如何解决主从循环复制的问题)

    1. 生产上使用较多的数据库,好处是主从切换不用修改节点状态,因为都是主
    2. 如何解决循环复制的:因为双M,所以另一个主一般会开启log_slave_updates(on)
      1. 说明情况:server1写一条binlog,同步到server2这时候server2也会产生一条binlog,这时候如果同步到server1就会循环同步,mysql如何解决?
      2. 解决方案:
        1. 俩个server的serverid不一样,一样的话不能成为主备;
        2. 第一次生产binlog的库带着serverid
        3. 备库接到binlog在重放的时候带着主库的serverid
        4. 这条log同步到主库时候,主库发现这个serverid和自己一样说明是自己产生的直接丢弃

源代码说明

Posted on 2019-02-01 | Edited on 2022-09-21 | In netty

netty 源码的学习

netty源码学习的git地址:https://github.com/liuhao163/netty.git

branch:4.1-ericliu

1…2223

Liu hao

励志当好厨子的程序员

229 posts
54 categories
81 tags
RSS
GitHub E-Mail
© 2018 – 2023 Liu hao
Powered by Hexo v3.9.0
|
Theme – NexT.Pisces v7.0.0