牙小木木

牙小木木 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 tuber.github.io 编辑
编辑

iamtb.cn

个人动态

牙小木木 发布了文章 · 9月17日

mysql Explain初探

mysql执行计划

​ 在企业的应用场景中,为了知道优化SQL语句的执行,需要查看SQL语句的具体执行过程,以加快SQL语句的执行效率。

​ 可以使用explain+SQL语句来模拟优化器执行SQL查询语句,从而知道mysql是如何处理sql语句的。

​ 官网地址: https://dev.mysql.com/doc/ref...

1、执行计划中包含的信息

ColumnMeaning
idThe SELECT identifier
select_typeThe SELECT type
tableThe table for the output row
partitionsThe matching partitions
typeThe join type
possible_keysThe possible indexes to choose
keyThe index actually chosen
key_lenThe length of the chosen key
refThe columns compared to the index
rowsEstimate of rows to be examined
filteredPercentage of rows filtered by table condition
extraAdditional information

id

select查询的序列号,包含一组数字,表示查询中执行select子句或者操作表的顺序

id号分为三种情况:

​ 1、如果id相同,那么执行顺序从上到下

explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.losal and sg.hisal;

​ 2、如果id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行

explain select * from emp e where e.deptno in (select d.deptno from dept d where d.dname = 'SALES');

​ 3、id相同和不同的,同时存在:相同的可以认为是一组,从上往下顺序执行,在所有组中,id值越大,优先级越高,越先执行

explain select * from emp e join dept d on e.deptno = d.deptno join salgrade sg on e.sal between sg.losal and sg.hisal where e.deptno in (select d.deptno from dept d where d.dname = 'SALES');

select_type

主要用来分辨查询的类型,是普通查询还是联合查询还是子查询

select_type ValueMeaning
SIMPLESimple SELECT (not using UNION or subqueries)
PRIMARYOutermost SELECT
UNIONSecond or later SELECT statement in a UNION
DEPENDENT UNIONSecond or later SELECT statement in a UNION, dependent on outer query
UNION RESULTResult of a UNION.
SUBQUERYFirst SELECT in subquery
DEPENDENT SUBQUERYFirst SELECT in subquery, dependent on outer query
DERIVEDDerived table
UNCACHEABLE SUBQUERYA subquery for which the result cannot be cached and must be re-evaluated for each row of the outer query
UNCACHEABLE UNIONThe second or later select in a UNION that belongs to an uncacheable subquery (see UNCACHEABLE SUBQUERY)
--sample:简单的查询,不包含子查询和union
explain select * from emp;

--primary:查询中若包含任何复杂的子查询,最外层查询则被标记为Primary
explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ;

--union:若第二个select出现在union之后,则被标记为union
explain select * from emp where deptno = 10 union select * from emp where sal >2000;

--dependent union:跟union类似,此处的depentent表示union或union all联合而成的结果会受外部表影响
explain select * from emp e where e.empno  in ( select empno from emp where deptno = 10 union select empno from emp where sal >2000)

--union result:从union表获取结果的select
explain select * from emp where deptno = 10 union select * from emp where sal >2000;

--subquery:在select或者where列表中包含子查询
explain select * from emp where sal > (select avg(sal) from emp) ;

--dependent subquery:subquery的子查询要受到外部表查询的影响
explain select * from emp e where e.deptno in (select distinct deptno from dept);

--DERIVED: from子句中出现的子查询,也叫做派生类,
explain select staname,ename supname from (select ename staname,mgr from emp) t join emp on t.mgr=emp.empno ;

--UNCACHEABLE SUBQUERY:表示使用子查询的结果不能被缓存
 explain select * from emp where empno = (select empno from emp where deptno=@@sort_buffer_size);
 
--uncacheable union:表示union的查询结果不能被缓存:sql语句未验证

table

对应行正在访问哪一个表,表名或者别名,可能是临时表或者union合并结果集

    1、如果是具体的表名,则表明从实际的物理表中获取数据,当然也可以是表的别名

​ 2、表名是derivedN的形式,表示使用了id为N的查询产生的衍生表

​ 3、当有union result的时候,表名是union n1,n2等的形式,n1,n2表示参与union的id

type

type显示的是访问类型,访问类型表示我是以何种方式去访问我们的数据,最容易想的是全表扫描,直接暴力的遍历一张表去寻找需要的数据,效率非常低下,访问的类型有很多,效率从最好到最坏依次是:

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般情况下,得保证查询至少达到range级别,最好能达到ref

--all:全表扫描,一般情况下出现这样的sql语句而且数据量比较大的话那么就需要进行优化。
explain select * from emp;

--index:全索引扫描这个比all的效率要好,主要有两种情况,一种是当前的查询时覆盖索引,即我们需要的数据在索引中就可以索取,或者是使用了索引进行排序,这样就避免数据的重排序
explain  select empno from emp;

--range:表示利用索引查询的时候限制了范围,在指定范围内进行查询,这样避免了index的全索引扫描,适用的操作符: =, <>, >, >=, <, <=, IS NULL, BETWEEN, LIKE, or IN() 
explain select * from emp where empno between 7000 and 7500;

--index_subquery:利用索引来关联子查询,不再扫描全表
explain select * from emp where emp.job in (select job from t_job);

--unique_subquery:该连接类型类似与index_subquery,使用的是唯一索引
 explain select * from emp e where e.deptno in (select distinct deptno from dept);
 
--index_merge:在查询过程中需要多个索引组合使用,没有模拟出来

--ref_or_null:对于某个字段即需要关联条件,也需要null值的情况下,查询优化器会选择这种访问方式
explain select * from emp e where  e.mgr is null or e.mgr=7369;

--ref:使用了非唯一性索引进行数据的查找
 create index idx_3 on emp(deptno);
 explain select * from emp e,dept d where e.deptno =d.deptno;

--eq_ref :使用唯一性索引进行数据查找
explain select * from emp,emp2 where emp.empno = emp2.empno;

--const:这个表至多有一个匹配行,
explain select * from emp where empno = 7369;
 
--system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现

possible_keys

​ 显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

key

​ 实际使用的索引,如果为null,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的select字段重叠。

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

key_len

表示索引中使用的字节数,可以通过key_len计算查询中使用的索引长度,在不损失精度的情况下长度越短越好。

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

ref

显示索引的哪一列被使用了,如果可能的话,是一个常数

explain select * from emp,dept where emp.deptno = dept.deptno and emp.deptno = 10;

rows

根据表的统计信息及索引使用情况,大致估算出找出所需记录需要读取的行数,此参数很重要,直接反应的sql找了多少数据,在完成目的的情况下越少越好

explain select * from emp;

extra

包含额外的信息。

--using filesort:说明mysql无法利用索引进行排序,只能利用排序算法进行排序,会消耗额外的位置
explain select * from emp order by sal;

--using temporary:建立临时表来保存中间结果,查询完成之后把临时表删除
explain select ename,count(*) from emp where deptno = 10 group by ename;

--using index:这个表示当前的查询时覆盖索引的,直接从索引中读取数据,而不用访问数据表。如果同时出现using where 表名索引被用来执行索引键值的查找,如果没有,表面索引被用来读取数据,而不是真的查找
explain select deptno,count(*) from emp group by deptno limit 10;

--using where:使用where进行条件过滤
explain select * from t_user where id = 1;

--using join buffer:使用连接缓存,情况没有模拟出来

--impossible where:where语句的结果总是false
explain select * from emp where empno = 7469;

Thanks 马士兵教育

查看原文

赞 0 收藏 0 评论 0

牙小木木 发布了文章 · 9月1日

简单谈谈单体服务和微服务的理解

公司业务从单体逐渐转移到了微服务,一些总结。另外也可以当做一道面试题,题目如文章标题^_^

单体服务:

1. 初期优势:业务模块耦合度高,IPC通信简单(方法、函数直接调用,本地进程直接调用或者通过mq来异步通信)、(统一)部署快,测试快(依赖度相对较低)
2. 长期发展:代码量变大,越来越多类似的轮子被造出来,一个需求改变可能涉及到多个地方同步修改(迁一发能动全身),业务逻辑冗余复杂,老员工成了吉祥物。

微服务

3. 小试牛刀:停止单体服务下的无限制扩张。按业务线(线索、订单、商品、用户)分开,再按业务线细分(比如按表)
4. 小有欢喜:应用小(模版一键生成,干活少),独立部署和扩展;逻辑单一,维护性好;。
5. 渐入坑境:随着微服务的规模变大一大堆问题呈线性增加:部署频率增加,链路调用增加,对服务高可用受到挑战,监控(服务发现)治理,测试周期变长,问题定位困难,各个端业务规范不一致,单一服务上下游影响甚至拖垮整条链路(雪崩),数据库事务难以控制,粒度划分难(甩锅开始)
6. 善假于物:配置中心、再抽离基础库组件化与定时更新、统一业务方接口/RPC协议/服务规范,配合服务注册与发现,监控体系,业务网关、流量网关、权限网关、日志系统,分布式链路追踪,调度中心,持续集成,持续部署,负载均衡、熔断、限流,降级机制,server mesh。

微服务:从设计到部署.pdf(提取码: i6as)

查看原文

赞 0 收藏 0 评论 0

牙小木木 关注了专栏 · 8月27日

【前端有的玩】公众号

我是子君,我的公众号是【前端有的玩】,欢迎关注。每周我会至少分享一篇前端相关文章,希望可以帮助到致力于前端开发的你。

关注 1409

牙小木木 赞了文章 · 8月27日

浅析MySQL二段锁

背景

虽然两阶段加锁(2PL)听起来和两阶段提交(two-phase commit, 2PC)很相似,但它们是完全不同的东西。

在介绍MySQL二段锁之前,我需要理清一下概念,即MySQL二阶段加锁与二阶段提交的区别:

二阶段加锁:用于单机事务中的一致性和隔离性
二阶段提交:用于分布式事务

何为二段锁

在一个事务操作中,分为加锁阶段解锁阶段,且所有的加锁操作在解锁操作之前,具体如下图所示:

alt text

加锁时机

当对记录进行更新操作或者select for update(X锁)、lock in share mode(S锁)时,会对记录进行加锁,锁的种类很多,不在此赘述。

何时解锁

在一个事务中,只有在commit或者rollback时,才是解锁阶段。

二阶段加锁最佳实践

下面举个具体的例子,来讲述二段锁对应用性能的影响,我们举个库存扣减的例子:

方案一:
start transaction;
// 锁定用户账户表
select * from t_accout where acount_id=234 for update
//生成订单
insert into t_trans;
// 减库存
update t_inventory set num=num-3 where id=${id} and num>=3;
commit;
方案二:
start transaction;
// 减库存
update t_inventory set num=num-3 where id=${id} and num>=3;
// 锁定用户账户表
select * from t_accout where acount_id=234 for update
//生成订单
insert into t_trans;
commit;

我们的应用通过JDBC操作数据库时,底层本质上还是走TCP进行通信,MySQL协议是一种停-等式协议(和http协议类似,每发送完一个分组就停止发送,等待对方的确认,在收到确认后再发送下一个分组),既然通过网络进行通信,就必然会有延迟,两种方案的网络通信时序图如下:

alt text

由于商品库存往往是最致命的热点,是整个服务的热点。如果采用第一种方案的话,TPS理论上可以提升3rt/rt=3倍。而这是在一个事务中只有3条SQL的情况,理论上多一条SQL就多一个rt时间。

另外,当更新操作到达数据库的那个点,才算加锁成功。commit到达数据库的时候才算解锁成功。所以,更新操作的前半个rtcommit操作的后半个rt都不计算在整个锁库存的时间内。

性能优化

从上面的例子可以看出,在一个事务操作中,将对最热点记录的操作放到事务的最后面,这样可以显著地提高服务的吞吐量

select for update 和 update where的最优选择

我们可以将一些简单的判断逻辑写到update操作的谓词里面,这样可以减少加锁的时间,如下:

方案一:
start transaction
num = select count from t_inventory where id=234 for update
if count >= 3:
    update t_inventory set num=num-3 where id=234
    commit 
else:
    rollback

方案二:

start transaction:
    int affectedRows = update t_inventory set num=num-3 where id=234 and num>=3
    if affectedRows > 0:
        commit
    else:
        rollback

延时图如下:
alt text

从上图可以看出,加了update谓词以后,一个事务少了1rt的锁记录时间(update谓词和select for update对记录加的都是X锁,所以效果是一样的)。

死锁

加锁SQL都或多或少会遇到这个问题。上面的最佳实践中,笔者建议在一个事务中,对记录的加锁按照记录的热点程度升序排列,对与任何会并发的SQL都必须按照相同的顺序来处理,否则会导致死锁,如下图:

alt text

总结

合理地写好SQL,对于我们提高系统的吞吐量至关重要。

原文链接

https://segmentfault.com/a/11...

查看原文

赞 8 收藏 28 评论 6

牙小木木 发布了文章 · 7月30日

mysql 的索引下推(IndexCondition)简单理解

前置版本

mysql> select version();
+------------+
| version()  |
+------------+
| 5.7.26-log |
+------------+

先来建个表加个索引

DROP TABLE IF EXISTS `indexconditionpushdown`;
CREATE TABLE `indexconditionpushdown` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `age` int(11) NOT NULL,
  `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `multi_name_age` (`name`,`age`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

-- ----------------------------
-- Records of indexconditionpushdown
-- ----------------------------
INSERT INTO `indexconditionpushdown` VALUES ('1', '20', '陈某');
INSERT INTO `indexconditionpushdown` VALUES ('2', '30', '陈某某');
INSERT INTO `indexconditionpushdown` VALUES ('3', '30', '李某');

alter table indexConditionPushDown add index multi_name_age(name,age);

ICP版本

mysql> explain select * from indexConditionPushDown where name like "陈%" and age =20;
+----+-------------+------------------------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
| id | select_type | table                  | partitions | type  | possible_keys  | key            | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+------------------------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | indexConditionPushDown | NULL       | range | multi_name_age | multi_name_age | 771     | NULL |    1 |    33.33 | Using index condition |
+----+-------------+------------------------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+
1 row in set

禁用ICP

mysql> set optimizer_switch='index_condition_pushdown=off';
Query OK, 0 rows affected

mysql> explain select * from indexConditionPushDown where name like "陈%" and age =20;
+----+-------------+------------------------+------------+-------+----------------+----------------+---------+------+------+----------+-------------+
| id | select_type | table                  | partitions | type  | possible_keys  | key            | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------------------+------------+-------+----------------+----------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | indexConditionPushDown | NULL       | range | multi_name_age | multi_name_age | 771     | NULL |    1 |    33.33 | Using where |
+----+-------------+------------------------+------------+-------+----------------+----------------+---------+------+------+----------+-------------+
1 row in set

结论 【Extra】

  1. ICP版本为 Using index condition
  2. 禁用后未 Using where
查看原文

赞 0 收藏 0 评论 0

牙小木木 关注了用户 · 7月29日

Kevin @kevinyan

公众号:网管叨bi叨 | 每周分享原创技术文章

关注 571

牙小木木 赞了文章 · 7月10日

MySQL数据以全量和增量方式,向ES搜索引擎同步流程

本文源码:GitHub·点这里 || GitEE·点这里

一、配置详解

场景描述:MySQL数据表以全量和增量的方式向ElasticSearch搜索引擎同步。

1、下载内容

  • elasticsearch 版本 6.3.2
  • logstash 版本 6.3.2
  • mysql-connector-java-5.1.13.jar

2、核心配置

  • 路径:/usr/local/logstash
  • 新建配置目录:sync-config

1)、配置全文

/usr/local/logstash/sync-config/cicadaes.conf

input {
    stdin {}
    jdbc {
        jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/cicada?characterEncoding=utf8"
        jdbc_user => "root"
        jdbc_password => "root123"
        jdbc_driver_library => "/usr/local/logstash/sync-config/mysql-connector-java-5.1.13.jar"
        jdbc_driver_class => "com.mysql.jdbc.Driver"
        jdbc_paging_enabled => "true"
        jdbc_page_size => "50000"
        jdbc_default_timezone => "Asia/Shanghai"
        statement_filepath => "/usr/local/logstash/sync-config/user_sql.sql"
        schedule => "* * * * *"
        type => "User"
        lowercase_column_names => false
        record_last_run => true
        use_column_value => true
        tracking_column => "updateTime"
        tracking_column_type => "timestamp"
        last_run_metadata_path => "/usr/local/logstash/sync-config/user_last_time"
        clean_run => false
    }
    jdbc {
        jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/cicada?characterEncoding=utf8"
        jdbc_user => "root"
        jdbc_password => "root123"
        jdbc_driver_library => "/usr/local/logstash/sync-config/mysql-connector-java-5.1.13.jar"
        jdbc_driver_class => "com.mysql.jdbc.Driver"
        jdbc_paging_enabled => "true"
        jdbc_page_size => "50000"
        jdbc_default_timezone => "Asia/Shanghai"
        statement_filepath => "/usr/local/logstash/sync-config/log_sql.sql"
        schedule => "* * * * *"
        type => "Log"
        lowercase_column_names => false
        record_last_run => true
        use_column_value => true
        tracking_column => "updateTime"
        tracking_column_type => "timestamp"
        last_run_metadata_path => "/usr/local/logstash/sync-config/log_last_time"
        clean_run => false
    }
}
filter {
    json {
        source => "message"
        remove_field => ["message"]
    }
}
output {
    if [type] == "User" {
        elasticsearch {
            hosts => ["127.0.0.1:9200"]
            index => "cicada_user_search"
            document_type => "user_search_index"
        }
    }
    if [type] == "Log" {
        elasticsearch {
            hosts => ["127.0.0.1:9200"]
            index => "cicada_log_search"
            document_type => "log_search_index"
        }
    }
}

2)、SQL文件

  • user_sql.sql
SELECT
    id,
    user_name userName,
    user_phone userPhone,
    create_time createTime,
    update_time updateTime
FROM c_user
WHERE update_time > : sql_last_value
  • log_sql.sql
SELECT
    id,
    param_value paramValue,
    request_ip requestIp,
    create_time createTime,
    update_time updateTime
FROM c_log
WHERE update_time > : sql_last_value

3)、配置参数说明

  • input参数
statement_filepath:读取SQL语句位置
schedule :这里配置每分钟执行一次
type :类型,写入ES的标识
lowercase_column_names :字段是否转小写
record_last_run :记录上次执行时间
use_column_value :使用列的值
tracking_column :根据写入ES的updateTime字段区分增量数据
tracking_column_type :区分的字段类型
  • output参数
hosts :ES服务地址
index :Index名称,类比理解数据库名称
document_type :Type名称,类比理解表名称

3、启动进程

/usr/local/logstash/bin/logstash  
-f  
/usr/local/logstash/sync-config/cicadaes.conf

二、ES客户端工具

1、下载软件

kibana-6.3.2-windows-x86_64

2、修改配置

kibana-6.3.2-windows-x86_64configkibana.yml

添加配置:

elasticsearch.url: "http://127.0.0.1:9200"

3、双击启动

kibana-6.3.2-windows-x86_64binkibana.bat

4、访问地址

http://localhost:5601

图片描述

三、源代码地址

GitHub·地址
https://github.com/cicadasmile/linux-system-base
GitEE·地址
https://gitee.com/cicadasmile/linux-system-base

查看原文

赞 6 收藏 4 评论 0

牙小木木 发布了文章 · 7月2日

MySql Binlog statement row mixed 三种模式初探

一句话介绍

以下主要演示了MySql Binlog的row格式和statement内容和相关的知识点,

  • ROW记录包括了是EVENT TYPE,且是基于每行的,即你执行了一个DML操作,binlog中记录的并不是具体的这个sql,而是针对该语句的每一行或者多行记录各自生成记录,这样能有效避免主从下针对同一条sql而产生不同的结果(参考文中force indx的例子),这种方式无疑是最安全的,但是效率和空间上消耗是最大的。
  • STATAMENT 是基于sql执行语句的(显示记录),相对于row占用的存储空间要少。用于数据同步的话还是要谨慎,需要保证主从机器之间的一致性(variables参数,Binlog日志格式参数,表引擎,数据,索引等等),如果不能保证,用于恢复数据的情景还是要慎用(可以参考下面update where limit语句的例子)
  • MIXED格式是自动判断并自动切换行和语句的策略,既然是自动,就不能保证完全符合每个业务场景,除非Server层面能做到绝对安全。。

环境及参数说明

环境参数

mysql> select version();
+-----------------------------+
| version()                   |
+-----------------------------+
| 5.7.30-0ubuntu0.16.04.1-log |
+-----------------------------+
mysql> show variables like "%binlog%";
+--------------------------------------------+----------------------+
| Variable_name                              | Value                |
+--------------------------------------------+----------------------+
| binlog_cache_size                          | 32768                |
| binlog_checksum                            | CRC32                |
| binlog_direct_non_transactional_updates    | OFF                  |
| binlog_error_action                        | ABORT_SERVER         |
| binlog_format                              | ROW                  |
| binlog_group_commit_sync_delay             | 0                    |
| binlog_group_commit_sync_no_delay_count    | 0                    |
| binlog_gtid_simple_recovery                | ON                   |
| binlog_max_flush_queue_time                | 0                    |
| binlog_order_commits                       | ON                   |
| binlog_row_image                           | FULL                 |
| binlog_rows_query_log_events               | OFF                  |
| binlog_stmt_cache_size                     | 32768                |
| binlog_transaction_dependency_history_size | 25000                |
| binlog_transaction_dependency_tracking     | COMMIT_ORDER         |
| innodb_api_enable_binlog                   | OFF                  |
| innodb_locks_unsafe_for_binlog             | OFF                  |
| log_statements_unsafe_for_binlog           | ON                   |
| max_binlog_cache_size                      | 18446744073709547520 |
| max_binlog_size                            | 104857600            |
| max_binlog_stmt_cache_size                 | 18446744073709547520 |
| sync_binlog                                | 1                    |
+--------------------------------------------+----------------------+
mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000003 |     18117 |
| mysql-bin.000004 |       523 |
| mysql-bin.000005 |       523 |
| mysql-bin.000006 |       177 |
| mysql-bin.000007 |      4232 |
+------------------+-----------+

说明

在具体之前我们先看和本文操作不强相关但是已经存在的binlog,d当前最新的文件名为mysql-bin.000007 。主要目的是做一个上下文的分界线(id标识)

root@base2018:/var/log/mysql# ls -lt|grep mysql-bin
-rw-r----- 1 mysql mysql  4232 Jul  2 10:32 mysql-bin.000007
-rw-r----- 1 mysql mysql   160 Jul  2 10:24 mysql-bin.index
-rw-r----- 1 mysql mysql   177 Jul  2 10:23 mysql-bin.000006
-rw-r----- 1 mysql mysql   523 Jun 25 06:25 mysql-bin.000005
-rw-r----- 1 mysql mysql   523 Jun 24 06:25 mysql-bin.000004
-rw-r----- 1 mysql mysql 18117 Jun 23 06:25 mysql-bin.000003

show binlog events in 'mysql-bin.000007'\G

.## 省略..
*************************** 32. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2318
 Event_type: Write_rows
  Server_id: 1
End_log_pos: 2366
       Info: table_id: 120 flags: STMT_END_F
*************************** 33. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2366
 Event_type: Xid
  Server_id: 1
End_log_pos: 2397
       Info: COMMIT /* xid=62 */
33 rows in set (0.00 sec)

我们可以看一下最后一条 第row id为的详细信息,因为其开始Pos2366,记住这个33,^_^

开始造作

建表插入测试数据

CREATE TABLE `binlog_demo` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `t_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `a` (`a`),
  KEY `t_modified`(`t_modified`)
) ENGINE=InnoDB;

insert into binlog_demo values(1,1,'2020-07-01');
insert into binlog_demo values(2,2,'2020-07-02');
insert into binlog_demo values(3,3,'2020-07-02');
insert into binlog_demo values(4,4,'2020-07-04');
insert into binlog_demo values(5,5,'2020-07-05');

执行一条sql(row格式)

mysql> delete from binlog_demo /*comment*/  where a>=4 and t_modified<='2020-07-10' limit 1;
Query OK, 1 row affected (0.01 sec)

接上面的第33条之后看

*************************** 34. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2397
 Event_type: Anonymous_Gtid
  Server_id: 1
End_log_pos: 2462
       Info: SET @@SESSION.GTID_NEXT= 'ANONYMOUS'
*************************** 35. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2462
 Event_type: Query
  Server_id: 1
End_log_pos: 2542
       Info: BEGIN
*************************** 36. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2542
 Event_type: Table_map
  Server_id: 1
End_log_pos: 2599
       Info: table_id: 120 (test.binlog_demo)
*************************** 37. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2599
 Event_type: Delete_rows
  Server_id: 1
End_log_pos: 2647
       Info: table_id: 120 flags: STMT_END_F
*************************** 38. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2647
 Event_type: Xid
  Server_id: 1
End_log_pos: 2678
       Info: COMMIT /* xid=86 */

去掉\G后展开对应下面的5行,注意2397是上一个binlogEnd_log_pos,也就是那个33的end pos。

| mysql-bin.000007 | 2397 | Anonymous_Gtid |         1 |        2462 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                                                                                                                                                                                      |
| mysql-bin.000007 | 2462 | Query          |         1 |        2542 | BEGIN                                                                                                                                                                                                                                                     |
| mysql-bin.000007 | 2542 | Table_map      |         1 |        2599 | table_id: 120 (test.binlog_demo)                                                                                                                                                                                                                          |
| mysql-bin.000007 | 2599 | Delete_rows    |         1 |        2647 | table_id: 120 flags: STMT_END_F                                                                                                                                                                                                                           |
| mysql-bin.000007 | 2647 | Xid            |         1 |        2678 | COMMIT /* xid=86 */                                                                                                                                                                                                                                       |
+------------------+------+----------------+-----------+-------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
38 rows in set (0.00 sec)

我们从2462
mysqlbinlog -vv mysql-bin.000007 --start-position=2462

/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#200702 10:24:14 server id 1  end_log_pos 123 CRC32 0xa97adc3a     Start: binlog v 4, server v 5.7.30-0ubuntu0.16.04.1-log created 200702 10:24:14 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
TkX9Xg8BAAAAdwAAAHsAAAABAAQANS43LjMwLTB1YnVudHUwLjE2LjA0LjEtbG9nAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABORf1eEzgNAAgAEgAEBAQEEgAAXwAEGggAAAAICAgCAAAACgoKKioAEjQA
ATrceqk=
'/*!*/;
# at 2462
#200702 11:26:00 server id 1  end_log_pos 2542 CRC32 0x2e66d2a1     Query    thread_id=14    exec_time=0    error_code=0
SET TIMESTAMP=1593660360/*!*/;
SET @@session.pseudo_thread_id=14/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1436549152/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.time_zone='SYSTEM'/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 2542
#200702 11:26:00 server id 1  end_log_pos 2599 CRC32 0x8d66ca1a     Table_map: `test`.`binlog_demo` mapped to number 120
# at 2599
#200702 11:26:00 server id 1  end_log_pos 2647 CRC32 0x59777bf5     Delete_rows: table id 120 flags: STMT_END_F

BINLOG '
yFP9XhMBAAAAOQAAACcKAAAAAHgAAAAAAAEABHRlc3QAC2JpbmxvZ19kZW1vAAMDAxEBAAIaymaN
yFP9XiABAAAAMAAAAFcKAAAAAHgAAAAAAAEAAgAD//gEAAAABAAAAF7/VgD1e3dZ
'/*!*/;
### DELETE FROM `test`.`binlog_demo`
### WHERE
###   @1=4 /* INT meta=0 nullable=0 is_null=0 */
###   @2=4 /* INT meta=0 nullable=1 is_null=0 */
###   @3=1593792000 /* TIMESTAMP(0) meta=0 nullable=0 is_null=0 */
# at 2647
#200702 11:26:00 server id 1  end_log_pos 2678 CRC32 0xba0bb150     Xid = 86
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

以上内容中字段值简单介绍

  • server_id可以保证服务标识和做互主的时候避免循环导入binlog
  • CRC32 ,即show variables like "%binlog%"里设置binlog_checksum的校验方式
  • Table_map 也是一个事件,但只支持row格式的(statement本身就是一条sql)就是一个表与id的映射,操作多个就会对应多个
  • Delete_rows 是ROWS_EVENT之一,常见的还有UPDATE_ROWS_EVENT, WRITE_ROWS_EVENT ,其他事件.
  • where 就是sql语句里的where条件的具体值
  • Xid和commit就代表最终ok

切换到statement

mysql>  SET SESSION binlog_format = 'STATEMENT';
Query OK, 0 rows affected (0.00 sec)

insert into binlog_demo values(6,66,'2020-07-06');

接上面的39行

*************************** 40. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2743
 Event_type: Query
  Server_id: 1
End_log_pos: 2830
       Info: BEGIN
*************************** 41. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2830
 Event_type: Query
  Server_id: 1
End_log_pos: 2961
       Info: use `test`; insert into binlog_demo values(6,66,'2020-07-06')
*************************** 42. row ***************************
   Log_name: mysql-bin.000007
        Pos: 2961
 Event_type: Xid
  Server_id: 1
End_log_pos: 2992
       Info: COMMIT /* xid=114 */
42 rows in set (0.00 sec)
PS:End_log_posshow master status里的数据pos大小是保持一致的

从2743行开始看 mysqlbinlog -vv mysql-bin.000007 --start-position=2743

PS: mysqlbinlog一些常用参数:
 --base64-output=decode-rows –v 
 --start-position --stop-position
 --start-time= --stop-time
 --read-from-remote-server
root@base2018:/var/log/mysql# mysqlbinlog -vv mysql-bin.000007 --start-position=2743
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#200702 10:24:14 server id 1  end_log_pos 123 CRC32 0xa97adc3a     Start: binlog v 4, server v 5.7.30-0ubuntu0.16.04.1-log created 200702 10:24:14 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
TkX9Xg8BAAAAdwAAAHsAAAABAAQANS43LjMwLTB1YnVudHUwLjE2LjA0LjEtbG9nAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABORf1eEzgNAAgAEgAEBAQEEgAAXwAEGggAAAAICAgCAAAACgoKKioAEjQA
ATrceqk=
'/*!*/;
# at 2743
#200702 11:58:33 server id 1  end_log_pos 2830 CRC32 0xa47b4e54     Query    thread_id=10    exec_time=0    error_code=0
SET TIMESTAMP=1593662313/*!*/;
SET @@session.pseudo_thread_id=10/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1436549152/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.time_zone='SYSTEM'/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 2830
#200702 11:58:33 server id 1  end_log_pos 2961 CRC32 0x48d934c7     Query    thread_id=10    exec_time=0    error_code=0
use `test`/*!*/;
SET TIMESTAMP=1593662313/*!*/;
insert into binlog_demo values(6,66,'2020-07-06')
/*!*/;
# at 2961
#200702 11:58:33 server id 1  end_log_pos 2992 CRC32 0x39d367e0     Xid = 114
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

row 和statement不同

row格式的BEGINCOMMIT之间的内容不同,statement只是存了insert语句(包括了我们没有 显示执行的use test;),并且带有一个 SET TIMESTAMP(这个时间可以理解为当前操作上下文的时间,避免在主从同步时发生时间不一致的问题比如一个CURRENT_TIME,NOW(),我理解就类似于linuxntp)

delete语句肯能导致数据不一致(statement模式)

上面是一个插入语句,比较简单,我们看一下特殊的删除情况 。再呈现一下上下文关系。

表中一共6条数据,且两个索引

mysql> select * from binlog_demo;
+----+----+---------------------+
| id | a  | t_modified          |
+----+----+---------------------+
|  1 |  1 | 2020-07-01 00:00:00 |
|  2 |  2 | 2020-07-02 00:00:00 |
|  3 |  3 | 2020-07-02 00:00:00 |
|  5 |  5 | 2020-07-05 00:00:00 |
|  6 | 66 | 2020-06-01 00:00:00 |
+----+----+---------------------+
5 rows in set
show index from binlog_demo;
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table       | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| binlog_demo |          0 | PRIMARY    |            1 | id          | A         |           5 | NULL     | NULL   |      | BTREE      |         |               |
| binlog_demo |          1 | a          |            1 | a           | A         |           5 | NULL     | NULL   | YES  | BTREE      |         |               |
| binlog_demo |          1 | t_modified |            1 | t_modified  | A         |           4 | NULL     | NULL   |      | BTREE      |         |               |
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

默认走的索引是a

mysql> select * from binlog_demo   where a>=4 and t_modified<='2020-07-10' limit 1;
+----+---+---------------------+
| id | a | t_modified          |
+----+---+---------------------+
|  5 | 5 | 2020-07-05 00:00:00 |
+----+---+---------------------+
1 row in set

mysql> explain select * from binlog_demo   where a>=4 and t_modified<='2020-07-10' limit 1;
+----+-------------+-------------+------------+-------+---------------+-----+---------+------+------+----------+------------------------------------+
| id | select_type | table       | partitions | type  | possible_keys | key | key_len | ref  | rows | filtered | Extra                              |
+----+-------------+-------------+------------+-------+---------------+-----+---------+------+------+----------+------------------------------------+
|  1 | SIMPLE      | binlog_demo | NULL       | range | a,t_modified  | a   | 5       | NULL |    2 |      100 | Using index condition; Using where |
+----+-------------+-------------+------------+-------+---------------+-----+---------+------+------+----------+------------------------------------+

强行指定使用索引t_modified

mysql> select * from binlog_demo use index(t_modified)  where a>=4 and t_modified<='2020-07-10' limit 1;
+----+----+---------------------+
| id | a  | t_modified          |
+----+----+---------------------+
|  6 | 66 | 2020-06-01 00:00:00 |
+----+----+---------------------+
1 row in set

mysql> explain select * from binlog_demo use index(t_modified)  where a>=4 and t_modified<='2020-07-10' limit 1;
+----+-------------+-------------+------------+-------+---------------+------------+---------+------+------+----------+------------------------------------+
| id | select_type | table       | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra                              |
+----+-------------+-------------+------------+-------+---------------+------------+---------+------+------+----------+------------------------------------+
|  1 | SIMPLE      | binlog_demo | NULL       | range | t_modified    | t_modified | 4       | NULL |    5 |    33.33 | Using index condition; Using where |
+----+-------------+-------------+------------+-------+---------------+------------+---------+------+------+----------+------------------------------------+
1 row in set

可以看出用默认索引查到的数据id为5,force index后查到的id为6.
那我们执行
delete from binlog_demo where a>=4 and t_modified<='2020-07-10' limit 1;的话,可以想到从库上执行的时候不一定也是按照同样的索引选择策略,这样可能导致不一致。所以row格式的就可以避免这个问题,row会将操作每行的信息都记录下来。这样能保证完全一致。
row 格式的

root@base2018:/var/log/mysql# mysqlbinlog -vv mysql-bin.000007 --start-position=2992;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#200702 10:24:14 server id 1  end_log_pos 123 CRC32 0xa97adc3a     Start: binlog v 4, server v 5.7.30-0ubuntu0.16.04.1-log created 200702 10:24:14 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
TkX9Xg8BAAAAdwAAAHsAAAABAAQANS43LjMwLTB1YnVudHUwLjE2LjA0LjEtbG9nAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABORf1eEzgNAAgAEgAEBAQEEgAAXwAEGggAAAAICAgCAAAACgoKKioAEjQA
ATrceqk=
'/*!*/;
# at 2992
#200702 12:19:31 server id 1  end_log_pos 3057 CRC32 0x10b875ba     Anonymous_GTID    last_committed=10    sequence_number=11    rbr_only=no
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 3057
#200702 12:19:31 server id 1  end_log_pos 3144 CRC32 0x8f84f3a7     Query    thread_id=15    exec_time=0    error_code=0
SET TIMESTAMP=1593663571/*!*/;
SET @@session.pseudo_thread_id=15/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1436549152/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.time_zone='SYSTEM'/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 3144
#200702 12:19:31 server id 1  end_log_pos 3302 CRC32 0x450cca73     Query    thread_id=15    exec_time=0    error_code=0
use `test`/*!*/;
SET TIMESTAMP=1593663571/*!*/;
UPDATE `binlog_demo` SET `t_modified`='2020-06-01 00:00:00' WHERE (`id`='6')
/*!*/;
# at 3302
#200702 12:19:31 server id 1  end_log_pos 3333 CRC32 0x73ca56b1     Xid = 132
COMMIT/*!*/;
# at 3333
#200702 12:27:03 server id 1  end_log_pos 3398 CRC32 0xad1d9f72     Anonymous_GTID    last_committed=11    sequence_number=12    rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 3398
#200702 12:27:03 server id 1  end_log_pos 3478 CRC32 0xccf08199     Query    thread_id=14    exec_time=0    error_code=0
SET TIMESTAMP=1593664023/*!*/;
BEGIN
/*!*/;
# at 3478
#200702 12:27:03 server id 1  end_log_pos 3535 CRC32 0x9946e0d7     Table_map: `test`.`binlog_demo` mapped to number 120
# at 3535
#200702 12:27:03 server id 1  end_log_pos 3583 CRC32 0x86c26c2a     Delete_rows: table id 120 flags: STMT_END_F

BINLOG '
F2L9XhMBAAAAOQAAAM8NAAAAAHgAAAAAAAEABHRlc3QAC2JpbmxvZ19kZW1vAAMDAxEBAALX4EaZ
F2L9XiABAAAAMAAAAP8NAAAAAHgAAAAAAAEAAgAD//gFAAAABQAAAF8Ap4AqbMKG
'/*!*/;
### DELETE FROM `test`.`binlog_demo`
### WHERE
###   @1=5 /* INT meta=0 nullable=0 is_null=0 */
###   @2=5 /* INT meta=0 nullable=1 is_null=0 */
###   @3=1593878400 /* TIMESTAMP(0) meta=0 nullable=0 is_null=0 */
# at 3583
#200702 12:27:03 server id 1  end_log_pos 3614 CRC32 0xe2c7ad74     Xid = 140
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

statement下delete 的warning

Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. The statement is unsafe because it uses a LIMIT clause. This is unsafe because the set of rows included cannot be predicted.

mysql> SET SESSION binlog_format = 'STATEMENT';
Query OK, 0 rows affected (0.00 sec)

mysql> delete from binlog_demo  where a>=2 and t_modified<='2020-07-10' limit 1;
Query OK, 1 row affected, 1 warning (0.01 sec)

mysql> show warnings\G
*************************** 1. row ***************************
  Level: Note
   Code: 1592
Message: Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. The statement is unsafe because it uses a LIMIT clause. This is unsafe because the set of rows included cannot be predicted.
1 row in set (0.00 sec)

ROW格式下没有warning

mysql> SET SESSION binlog_format = 'ROW';
Query OK, 0 rows affected (0.00 sec)

mysql> delete from binlog_demo  where a>=2 and t_modified<='2020-07-10' limit 1;
Query OK, 1 row affected (0.05 sec)

*************************** 52. row ***************************
   Log_name: mysql-bin.000007
        Pos: 3614
 Event_type: Anonymous_Gtid
  Server_id: 1
End_log_pos: 3679
       Info: SET @@SESSION.GTID_NEXT= 'ANONYMOUS'
*************************** 53. row ***************************
   Log_name: mysql-bin.000007
        Pos: 3679
 Event_type: Query
  Server_id: 1
End_log_pos: 3766
       Info: BEGIN
*************************** 54. row ***************************
   Log_name: mysql-bin.000007
        Pos: 3766
 Event_type: Query
  Server_id: 1
End_log_pos: 3920
       Info: use `test`; delete from binlog_demo  where a>=2 and t_modified<='2020-07-10' limit 1
*************************** 55. row ***************************
   Log_name: mysql-bin.000007
        Pos: 3920
 Event_type: Xid
  Server_id: 1
End_log_pos: 3951
       Info: COMMIT /* xid=159 */
*************************** 56. row ***************************
   Log_name: mysql-bin.000007
        Pos: 3951
 Event_type: Anonymous_Gtid
  Server_id: 1
End_log_pos: 4016
       Info: SET @@SESSION.GTID_NEXT= 'ANONYMOUS'
*************************** 57. row ***************************
   Log_name: mysql-bin.000007
        Pos: 4016
 Event_type: Query
  Server_id: 1
End_log_pos: 4096
       Info: BEGIN
*************************** 58. row ***************************
   Log_name: mysql-bin.000007
        Pos: 4096
 Event_type: Table_map
  Server_id: 1
End_log_pos: 4153
       Info: table_id: 120 (test.binlog_demo)
*************************** 59. row ***************************
   Log_name: mysql-bin.000007
        Pos: 4153
 Event_type: Delete_rows
  Server_id: 1
End_log_pos: 4201
       Info: table_id: 120 flags: STMT_END_F
*************************** 60. row ***************************
   Log_name: mysql-bin.000007
        Pos: 4201
 Event_type: Xid
  Server_id: 1
End_log_pos: 4232
       Info: COMMIT /* xid=165 */
60 rows in set (0.00 sec)



root@base2018:/var/log/mysql# mysqlbinlog -vv mysql-bin.000007 --start-position=3614
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#200702 10:24:14 server id 1  end_log_pos 123 CRC32 0xa97adc3a     Start: binlog v 4, server v 5.7.30-0ubuntu0.16.04.1-log created 200702 10:24:14 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
TkX9Xg8BAAAAdwAAAHsAAAABAAQANS43LjMwLTB1YnVudHUwLjE2LjA0LjEtbG9nAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABORf1eEzgNAAgAEgAEBAQEEgAAXwAEGggAAAAICAgCAAAACgoKKioAEjQA
ATrceqk=
'/*!*/;
# at 3614
#200702 12:38:24 server id 1  end_log_pos 3679 CRC32 0x0937d47a     Anonymous_GTID    last_committed=12    sequence_number=13    rbr_only=no
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 3679
#200702 12:38:24 server id 1  end_log_pos 3766 CRC32 0x92b2ee24     Query    thread_id=14    exec_time=0    error_code=0
SET TIMESTAMP=1593664704/*!*/;
SET @@session.pseudo_thread_id=14/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1436549152/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.time_zone='SYSTEM'/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
BEGIN
/*!*/;
# at 3766
#200702 12:38:24 server id 1  end_log_pos 3920 CRC32 0x22d50c86     Query    thread_id=14    exec_time=0    error_code=0
use `test`/*!*/;
SET TIMESTAMP=1593664704/*!*/;
delete from binlog_demo  where a>=2 and t_modified<='2020-07-10' limit 1
/*!*/;
# at 3920
#200702 12:38:24 server id 1  end_log_pos 3951 CRC32 0x35efdb16     Xid = 159
COMMIT/*!*/;
# at 3951
#200702 12:42:08 server id 1  end_log_pos 4016 CRC32 0xd357905c     Anonymous_GTID    last_committed=13    sequence_number=14    rbr_only=yes
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
SET @@SESSION.GTID_NEXT= 'ANONYMOUS'/*!*/;
# at 4016
#200702 12:42:08 server id 1  end_log_pos 4096 CRC32 0xfbb008a4     Query    thread_id=14    exec_time=0    error_code=0
SET TIMESTAMP=1593664928/*!*/;
BEGIN
/*!*/;
# at 4096
#200702 12:42:08 server id 1  end_log_pos 4153 CRC32 0xc7c76464     Table_map: `test`.`binlog_demo` mapped to number 120
# at 4153
#200702 12:42:08 server id 1  end_log_pos 4201 CRC32 0xa7203e07     Delete_rows: table id 120 flags: STMT_END_F

BINLOG '
oGX9XhMBAAAAOQAAADkQAAAAAHgAAAAAAAEABHRlc3QAC2JpbmxvZ19kZW1vAAMDAxEBAAJkZMfH
oGX9XiABAAAAMAAAAGkQAAAAAHgAAAAAAAEAAgAD//gDAAAAAwAAAF78swAHPiCn
'/*!*/;
### DELETE FROM `test`.`binlog_demo`
### WHERE
###   @1=3 /* INT meta=0 nullable=0 is_null=0 */
###   @2=3 /* INT meta=0 nullable=1 is_null=0 */
###   @3=1593619200 /* TIMESTAMP(0) meta=0 nullable=0 is_null=0 */
# at 4201
#200702 12:42:08 server id 1  end_log_pos 4232 CRC32 0x21a1766f     Xid = 165
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

写在最后

  1. 关于Binlog:
    Binlog是MySql Server层的实现,虽然也有所谓的commit,但是这个commit与具体实现引擎无关(与innodb的 事务日志redo/undo不冲突)。主要是做主从,备份/恢复用的。
  2. 关于mixed,顾名思义。两者结合是最好的,是可以自动判断是否会影响主备不一致。
  3. row 模式会针对每行都会记录,delete一行就会有一行记录, 其中binlog_row_image 决定了记录的多少

    • full 记录所有的列
    • minimal 记录变化的列
    • noblob 记录除去blob和text之外的列
      这样就直接导致row格式的binlog就会很大。
查看原文

赞 0 收藏 0 评论 0

牙小木木 发布了文章 · 6月19日

msyql 开启 replication semi synchronous

背景

通过用 go-mysql 同步到es过程中,提示如下错误

master does not support semi synchronous replication, use no semi-sync

解决

1.安装插件

mysql> show variables like '%semi%';
Empty set

mysql> select version();
+-------------------------+
| version()               |
+-------------------------+
| 5.7.30-0ubuntu0.16.04.1 |
+-------------------------+
1 row in set

mysql> INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
Query OK, 0 rows affected

mysql> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
Query OK, 0 rows affected

mysql> select version();
+-------------------------+
| version()               |
+-------------------------+
| 5.7.30-0ubuntu0.16.04.1 |
+-------------------------+
1 row in set

mysql> show variables like '%semi%';
+-------------------------------------------+------------+
| Variable_name                             | Value      |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled              | OFF        |
| rpl_semi_sync_master_timeout              | 10000      |
| rpl_semi_sync_master_trace_level          | 32         |
| rpl_semi_sync_master_wait_for_slave_count | 1          |
| rpl_semi_sync_master_wait_no_slave        | ON         |
| rpl_semi_sync_master_wait_point           | AFTER_SYNC |
| rpl_semi_sync_slave_enabled               | OFF        |
| rpl_semi_sync_slave_trace_level           | 32         |
+-------------------------------------------+------------+
8 rows in set

mysql>  set global rpl_semi_sync_master_enabled=1;
Query OK, 0 rows affected

mysql> set global rpl_semi_sync_master_timeout=30000;
Query OK, 0 rows affected

mysql> show variables like '%semi%';
+-------------------------------------------+------------+
| Variable_name                             | Value      |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled              | ON         |
| rpl_semi_sync_master_timeout              | 30000      |
| rpl_semi_sync_master_trace_level          | 32         |
| rpl_semi_sync_master_wait_for_slave_count | 1          |
| rpl_semi_sync_master_wait_no_slave        | ON         |
| rpl_semi_sync_master_wait_point           | AFTER_SYNC |
| rpl_semi_sync_slave_enabled               | OFF        |
| rpl_semi_sync_slave_trace_level           | 32         |
+-------------------------------------------+------------+
8 rows in set

mysql> 

2. 修改配置文件并重启


root@base2018:/etc/mysql# vim mysql.conf.d/mysqld.cnf 

# 开启半同步
rpl_semi_sync_slave_enabled =1
rpl_semi_sync_master_enabled =1
#开启binlog
server_id=1
log_bin = mysql-bin
binlog_format = ROW
expire_logs_days = 30
log-bin = /var/log/mysql/mysql-bin.log

root@base2018:/etc/mysql# service mysql start

其他关于binlog

  1. 通过 show binlog events\G;可以可视化bing log内容.(可以加很多查询参数)
查看原文

赞 0 收藏 0 评论 0

牙小木木 赞了文章 · 6月15日

《Dubbo迈出云原生重要一步-应用级服务发现解析》

1.png

作者 | 刘军(陆龟)  Apache Dubbo PMC

概述

社区版本 Dubbo 从 2.7.5 版本开始,新引入了一种基于实例(应用)粒度的服务发现机制,这是我们为 Dubbo 适配云原生基础设施的一步重要探索。版本发布到现在已有近半年时间,经过这段时间的探索与总结,我们对这套机制的可行性与稳定性有了更全面、深入的认识;同时在 Dubbo 3.0 的规划也在全面进行中,如何让应用级服务发现成为未来下一代服务框架 Dubbo 3.0 的基础服务模型,解决云原生、规模化微服务集群扩容与可伸缩性问题,也已经成为我们当前工作的重点。

既然这套新机制如此重要,那它到底是怎么工作的呢?今天我们就来详细解读一下。在最开始的社区版本,我们给这个机制取了一个神秘的名字 - 服务自省,下文将进一步解释这个名字的由来,并引用服务自省代指这套应用级服务发现机制。

熟悉 Dubbo 开发者应该都知道,一直以来都是面向 RPC 方法去定义服务的,并且这也是 Dubbo 开发友好性、治理功能强的基础。既然如此,那我们为什么还要定义个应用粒度的服务发现机制呢?这个机制到底是怎么工作的?它与当前机制的区别是什么?它能给我们带来哪些好处那?对适配云原生、性能提升又有哪些帮助?

带着所有的这些问题,我们开始本文的讲解。

服务自省是什么?

首先,我们先来解释文章开篇提到的问题:

  1. 应用粒度服务发现是到底是一种怎样的模型,它与当前的 Dubbo 服务发现模型的区别是什么?
  2. 我们为什么叫它服务自省?

所谓“应用/实例粒度” 或者“RPC 服务粒度”强调的是一种地址发现的数据组织格式。

2.png

以 Dubbo 当前的地址发现数据格式为例,它是“RPC 服务粒度”的,它是以 RPC 服务作为 key,以实例列表作为 value 来组织数据的:

"RPC Service1": [
  {"name":"instance1", "ip":"127.0.0.1", "metadata":{"timeout":1000}},
  {"name":"instance2", "ip":"127.0.0.1", "metadata":{"timeout":2000}},
  {"name":"instance3", "ip":"127.0.0.1", "metadata":{"timeout":3000}},
]
"RPC Service2": [Instance list of RPC Service2],
"RPC ServiceN": [Instance list of RPC ServiceN]

而我们新引入的“应用粒度的服务发现”,它以应用名(Application)作为 key,以这个应用部署的一组实例(Instance)列表作为 value。这带来两点不同:

  1. 数据映射关系变了,从 RPC Service -> Instance 变为 Application -> Instance;
  2. 数据变少了,注册中心没有了 RPC Service 及其相关配置信息。
"application1": [
  {"name":"instance1", "ip":"127.0.0.1", "metadata":{}},
  {"name":"instance2", "ip":"127.0.0.1", "metadata":{}},
  {"name":"instanceN", "ip":"127.0.0.1", "metadata":{}}
]

要进一步理解新模型带来的变化,我们看一下应用与 RPC 服务间的关系,显而易见的,1 个应用内可能会定义 n 个 RPC Service。因此 Dubbo 之前的服务发现粒度更细,在注册中心产生的数据条目也会更多(与 RPC 服务成正比),同时也存在一定的数据冗余。

简单理解了应用级服务发现的基本机制,接着解释它为什么会被叫做“服务自省”?

其实这还是得从它的工作原理说起,上面我们提到,应用粒度服务发现的数据模型有几个以下明显变化:数据中心的数据量少了,RPC 服务相关的数据在注册中心没有了,现在只有 application - instance 这两个层级的数据。为了保证这部分缺少的 RPC 服务数据仍然能被 Consumer 端正确的感知,我们在 Consumer 和 Provider 间建立了一条单独的通信通道:Consumer 和 Provider 两两之间通过特定端口交换信息,我们把这种 Provider 自己主动暴露自身信息的行为认为是一种内省机制,因此从这个角度出发,我们把整个机制命名为:服务自省

为什么需要服务自省?

上面讲服务自省的大概原理的时候也提到了它给注册中心带来的几点不同,这几点不同体现在 Dubbo 框架侧(甚至整个微服务体系中),有以下优势:

  • 与业界主流微服务模型对齐,比如 SpringCloud、Kubernetes Native Service 等;
  • 提升性能与可伸缩性。注册中心数据的重新组织(减少),能最大幅度的减轻注册中心的存储、推送压力,进而减少 Dubbo Consumer 侧的地址计算压力;集群规模也开始变得可预测、可评估(与 RPC 接口数量无关,只与实例部署规模相关)。

1. 对齐主流微服务模型

自动、透明的实例地址发现(负载均衡)是所有微服务框架需要解决的事情,这能让后端的部署结构对上游微服务透明,上游服务只需要从收到的地址列表中选取一个,发起调用就可以了。要实现以上目标,涉及两个关键点的自动同步:

  • 实例地址,服务消费方需要知道地址以建立链接;
  • RPC 方法定义,服务消费方需要知道 RPC 服务的具体定义,不论服务类型是 rest 或 rmi 等。

3.jpg

对于 RPC 实例间借助注册中心的数据同步,REST 定义了一套非常有意思的成熟度模型,感兴趣的朋友可以参考这里的链接 :
https://www.martinfowler.com/articles/richardsonMaturityModel.html

按照文章中的 4 级成熟度定义,Dubbo 当前基于接口粒度的模型可以对应到 L4 级别。

接下来,我们看看 Dubbo、SpringCloud 以及 Kubernetes 分别是怎么围绕自动化的实例地址发现这个目标设计的。

2. Spring Cloud

Spring Cloud 通过注册中心只同步了应用与实例地址,消费方可以基于实例地址与服务提供方建立链接,但是消费方对于如何发起 HTTP 调用(SpringCloud 基于 rest 通信)一无所知,比如对方有哪些 HTTP endpoint,需要传入哪些参数等。

RPC 服务这部分信息目前都是通过线下约定或离线的管理系统来协商的。这种架构的优缺点总结如下:

  • 优势:部署结构清晰、地址推送量小;
  • 缺点:地址订阅需要指定应用名, provider 应用变更(拆分)需消费端感知;RPC 调用无法全自动同步。

4.jpg

3. Dubbo

Dubbo 通过注册中心同时同步了实例地址和 RPC 方法,因此其能实现 RPC 过程的自动同步,面向 RPC 编程、面向 RPC 治理,对后端应用的拆分消费端无感知,其缺点则是地址推送数量变大,和 RPC 方法成正比。

5.jpg

4. Dubbo + Kubernetes

Dubbo 要支持 Kubernetes native service,相比之前自建注册中心的服务发现体系来说,在工作机制上主要有两点变化:

  • 服务注册由平台接管,provider 不再需要关心服务注册;
  • consumer 端服务发现将是 Dubbo 关注的重点,通过对接平台层的 API-Server、DNS 等,Dubbo client 可以通过一个 Service Name(通常对应到 Application Name)查询到一组 Endpoints(一组运行 provider 的 pod),通过将 Endpoints 映射到 Dubbo 内部地址列表,以驱动 Dubbo 内置的负载均衡机制工作。

Kubernetes Service 作为一个抽象概念,怎么映射到 Dubbo 是一个值得讨论的点

Service Name - > Application Name,Dubbo 应用和 Kubernetes 服务一一对应,对于微服务运维和建设环节透明,与开发阶段解耦。

apiVersion: v1
kind: Service
metadata:
  name: provider-app-name
spec:
  selector:
    app: provider-app-name
  ports:
    - protocol: TCP
      port:
      targetPort: 9376

Service Name - > Dubbo RPC Service,Kubernetes 要维护调度的服务与应用内建 RPC 服务绑定,维护的服务数量变多。

---
apiVersion: v1
kind: Service
metadata:
  name: rpc-service-1
spec:
  selector:
    app: provider-app-name
  ports: ##
...
---
apiVersion: v1
kind: Service
metadata:
  name: rpc-service-2
spec:
  selector:
    app: provider-app-name
  ports: ##
...
---
apiVersion: v1
kind: Service
metadata:
  name: rpc-service-N
spec:
  selector:
    app: provider-app-name
  ports: ##
...

6.png

结合以上几种不同微服务框架模型的分析,我们可以发现,Dubbo 与 SpringCloud、Kubernetes 等不同产品在微服务的抽象定义上还是存在很大不同的。SpringCloud 和 Kubernetes 在微服务的模型抽象上还是比较接近的,两者基本都只关心实例地址的同步,如果我们去关心其他的一些服务框架产品,会发现它们绝大多数也是这么设计的;

即 REST 成熟度模型中的 L3 级别。

对比起来 Dubbo 则相对是比较特殊的存在,更多的是从 RPC 服务的粒度去设计的。

对应 REST 成熟度模型中的 L4 级别。

如我们上面针对每种模型做了详细的分析,每种模型都有其优势和不足。而我们最初决定 Dubbo 要做出改变,往其他的微服务发现模型上的对齐,是我们最早在确定 Dubbo 的云原生方案时,我们发现要让 Dubbo 去支持 Kubernetes Native Service,模型对齐是一个基础条件;另一点是来自用户侧对 Dubbo 场景化的一些工程实践的需求,得益于 Dubbo 对多注册、多协议能力的支持,使得 Dubbo 联通不同的微服务体系成为可能,而服务发现模型的不一致成为其中的一个障碍,这部分的场景描述请参见这里

5. 更大规模的微服务集群 - 解决性能瓶颈

这部分涉及到和注册中心、配置中心的交互,关于不同模型下注册中心数据的变化,之前原理部分我们简单分析过。为更直观的对比服务模型变更带来的推送效率提升,我们来通过一个示例看一下不同模型注册中心的对比:

7.png

图中左边是微服务框架的一个典型工作流程,Provider 和  Consumer 通过注册中心实现自动化的地址通知。其中,Provider 实例的信息如图中表格所示:

应用 DEMO 包含三个接口 DemoService 1 2 3,当前实例的 ip 地址为 10.210.134.30。

  • 对于 Spring Cloud 和 Kubernetes 模型,注册中心只会存储一条 DEMO - 10.210.134.30+metadata 的数据;
  • 对于老的 Dubbo 模型,注册中心存储了三条接口粒度的数据,分别对应三个接口 DemoService 1 2 3,并且很多的址数据都是重复的。

可以总结出,基于应用粒度的模型所存储和推送的数据量是和应用、实例数成正比的,只有当我们的应用数增多或应用的实例数增长时,地址推送压力才会上涨。

而对于基于接口粒度的模型,数据量是和接口数量正相关的,鉴于一个应用通常发布多个接口的现状,这个数量级本身比应用粒度是要乘以倍数的;另外一个关键点在于,接口粒度导致的集群规模评估的不透明,相对于实i例、应用增长都通常是在运维侧的规划之中,接口的定义更多的是业务侧的内部行为,往往可以绕过评估给集群带来压力。

以 Consumer 端服务订阅举例,根据我对社区部分 Dubbo 中大规模头部用户的粗略统计,根据受统计公司的实际场景,一个 Consumer 应用要消费(订阅)的 Provier 应用数量往往要超过 10 个,而具体到其要消费(订阅)的的接口数量则通常要达到 30 个,平均情况下 Consumer 订阅的 3 个接口来自同一个 Provider 应用,如此计算下来,如果以应用粒度为地址通知和选址基本单位,则平均地址推送和计算量将下降 60% 还要多。

而在极端情况下,也就是当 Consumer 端消费的接口更多的来自同一个应用时,这个地址推送与内存消耗的占用将会进一步得到降低,甚至可以超过 80% 以上。

一个典型的几段场景即是 Dubbo 体系中的网关型应用,有些网关应用消费(订阅)达 100+ 应用,而消费(订阅)的服务有 1000+ ,平均有 10 个接口来自同一个应用,如果我们把地址推送和计算的粒度改为应用,则地址推送量从原来的 n  1000 变为 n  100,地址数量降低可达近 90%。

工作原理

1. 设计原则

上面一节我们从服务模型及支撑大规模集群的角度分别给出了 Dubbo 往应用级服务发现靠拢的好处或原因,但这么做的同时接口粒度的服务治理能力还是要继续保留,这是 Dubbo 框架编程模型易用性、服务治理能力优势的基础。

以下是我认为我们做服务模型迁移仍要坚持的设计原则:

  • 新的服务发现模型要实现对原有 Dubbo 消费端开发者的无感知迁移,即 Dubbo 继续面向 RPC 服务编程、面向 RPC 服务治理,做到对用户侧完全无感知;
  • 建立 Consumer 与 Provider 间的自动化 RPC 服务元数据协调机制,解决传统微服务模型无法同步 RPC 级接口配置的缺点。

2. 基本原理详解

应用级服务发现作为一种新的服务发现机制,和以前 Dubbo 基于 RPC 服务粒度的服务发现在核心流程上基本上是一致的:即服务提供者往注册中心注册地址信息,服务消费者从注册中心拉取&订阅地址信息。

这里主要的不同有以下两点:

  • 注册中心数据以“应用 - 实例列表”格式组织,不再包含 RPC 服务信息;

8.png

以下是每个 Instance metadata 的示例数据,总的原则是 metadata 只包含当前 instance 节点相关的信息,不涉及 RPC 服务粒度的信息。

总体信息概括如下:实例地址、实例各种环境标、metadata service 元数据、其他少量必要属性。

{
  "name": "provider-app-name",
  "id": "192.168.0.102:20880",
  "address": "192.168.0.102",
  "port": 20880,
  "sslPort": null,
  "payload": {
    "id": null,
    "name": "provider-app-name",
    "metadata": {
      "metadataService": "{\"dubbo\":{\"version\":\"1.0.0\",\"dubbo\":\"2.0.2\",\"release\":\"2.7.5\",\"port\":\"20881\"}}",
      "endpoints": "[{\"port\":20880,\"protocol\":\"dubbo\"}]",
      "storage-type": "local",
      "revision": "6785535733750099598",
    }
  },
  "registrationTimeUTC": 1583461240877,
  "serviceType": "DYNAMIC",
  "uriSpec": null
}
  • Client – Server 自行协商 RPC 方法信息。

在注册中心不再同步 RPC 服务信息后,服务自省在服务消费端和提供端之间建立了一条内置的 RPC 服务信息协商机制,这也是“服务自省”这个名字的由来。服务端实例会暴露一个预定义的 MetadataService RPC 服务,消费端通过调用 MetadataService 获取每个实例 RPC 方法相关的配置信息。

9.jpg

当前 MetadataService 返回的数据格式如下:

[
  "dubbo://192.168.0.102:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=9585&release=2.7.5&side=provider&timestamp=1583469714314", 
 "dubbo://192.168.0.102:20880/org.apache.dubbo.demo.HelloService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=9585&release=2.7.5&side=provider&timestamp=1583469714314",
  "dubbo://192.168.0.102:20880/org.apache.dubbo.demo.WorldService?anyhost=true&application=demo-provider&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&pid=9585&release=2.7.5&side=provider&timestamp=1583469714314"
]

熟悉 Dubbo 基于 RPC 服务粒度的服务发现模型的开发者应该能看出来,服务自省机制机制将以前注册中心传递的 URL 一拆为二:

  • 一部分和实例相关的数据继续保留在注册中心,如 ip、port、机器标识等;
  • 另一部分和 RPC 方法相关的数据从注册中心移除,转而通过 MetadataService 暴露给消费端。

理想情况下是能达到数据按照实例、RPC 服务严格区分开来,但明显可以看到以上实现版本还存在一些数据冗余,有些也数据还未合理划分。尤其是 MetadataService 部分,其返回的数据还只是简单的 URL 列表组装,这些 URL其实是包含了全量的数据。

以下是服务自省的一个完整工作流程图,详细描述了服务注册、服务发现、MetadataService、RPC 调用间的协作流程。

10.png

  1. 服务提供者启动,首先解析应用定义的“普通服务”并依次注册为 RPC 服务,紧接着注册内建的 MetadataService 服务,最后打开 TCP 监听端口;
  2. 启动完成后,将实例信息注册到注册中心(仅限 ip、port 等实例相关数据),提供者启动完成;
  3. 服务消费者启动,首先依据其要“消费的 provider 应用名”到注册中心查询地址列表,并完成订阅(以实现后续地址变更自动通知);
  4. 消费端拿到地址列表后,紧接着对 MetadataService 发起调用,返回结果中包含了所有应用定义的“普通服务”及其相关配置信息;
  5. 至此,消费者可以接收外部流量,并对提供者发起 Dubbo RPC 调用。
在以上流程中,我们只考虑了一切顺利的情况,但在更详细的设计或编码实现中,我们还需要严格约定一些异常场景下的框架行为。比如,如果消费者 MetadataService 调用失败,则在重试知道成功之前,消费者将不可以接收外部流量。

服务自省中的关键机制

1. 元数据同步机制

Client 与 Server 间在收到地址推送后的配置同步是服务自省的关键环节,目前针对元数据同步有两种具体的可选方案,分别是:内建 MetadataService;独立的元数据中心,通过中细化的元数据集群协调数据。

  • 内建 MetadataService:MetadataService 通过标准的 Dubbo 协议暴露,根据查询条件,会将内存中符合条件的“普通服务”配置返回给消费者。这一步发生在消费端选址和调用前;
  • 元数据中心:复用 2.7 版本中引入的元数据中心,provider 实例启动后,会尝试将内部的 RPC 服务组织成元数据的格式到元数据中心,而 consumer 则在每次收到注册中心推送更新后,主动查询元数据中心。
注意 consumer 端查询元数据中心的时机,是等到注册中心的地址更新通知之后。也就是通过注册中心下发的数据,我们能明确的知道何时某个实例的元数据被更新了,此时才需要去查元数据中心。

11.jpg

2. RPC 服务 < - > 应用映射关系

回顾上文讲到的注册中心关于“应用 - 实例列表”结构的数据组织形式,这个变动目前对开发者并不是完全透明的,业务开发侧会感知到查询/订阅地址列表的机制的变化。具体来说,相比以往我们基于 RPC 服务来检索地址,现在 consumer 需要通过指定 provider 应用名才能实现地址查询或订阅。

老的 Consumer 开发与配置示例:

<!-- 框架直接通过 RPC Service 1/2/N 去注册中心查询或订阅地址列表 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<dubbo:reference interface="RPC Service 1" />
<dubbo:reference interface="RPC Service 2" />
<dubbo:reference interface="RPC Service N" />

新的 Consumer 开发与配置示例:

<!-- 框架需要通过额外的 provided-by="provider-app-x" 才能在注册中心查询或订阅到地址列表 -->
<dubbo:registry address="zookeeper://127.0.0.1:2181?registry-type=service"/>
<dubbo:reference interface="RPC Service 1" provided-by="provider-app-x"/>
<dubbo:reference interface="RPC Service 2" provided-by="provider-app-x" />
<dubbo:reference interface="RPC Service N" provided-by="provider-app-y" />

以上指定 provider 应用名的方式是 Spring Cloud 当前的做法,需要 consumer 端的开发者显示指定其要消费的 provider 应用。

以上问题的根源在于注册中心不知道任何 RPC 服务相关的信息,因此只能通过应用名来查询。

为了使整个开发流程对老的 Dubbo 用户更透明,同时避免指定 provider 对可扩展性带来的影响(参见下方说明),我们设计了一套 RPC 服务到应用名的映射关系,以尝试在 consumer 自动完成 RPC 服务到 provider 应用名的转换。

12.jpg

Dubbo 之所以选择建立一套“接口-应用”的映射关系,主要是考虑到 service - app 映射关系的不确定性。一个典型的场景即是应用/服务拆分,如上面提到的配置 ,PC Service 2 是定义于 provider-app-x 中的一个服务,未来它随时可能会被开发者分拆到另外一个新的应用如 provider-app-x-1 中,这个拆分要被所有的 PC Service 2 消费方感知到,并对应用进行修改升级,如改为 ,这样的升级成本不可否认还是挺高的。
到底是 Dubbo 框架帮助开发者透明的解决这个问题,还是交由开发者自己去解决,当然这只是个策略选择问题,并且 Dubbo 2.7.5+ 版本目前是都提供了的。其实我个人更倾向于交由业务开发者通过组织上的约束来做,这样也可进一步降低 Dubbo 框架的复杂度,提升运行态的稳定性。

总结与展望

应用级服务发现机制是 Dubbo 面向云原生走出的重要一步,它帮 Dubbo 打通了与其他微服务体系之间在地址发现层面的鸿沟,也成为 Dubbo 适配 Kubernetes Native Service 等基础设施的基础。

我们期望 Dubbo 在新模型基础上,能继续保留在编程易用性、服务治理能力等方面强大的优势。但是我们也应该看到应用粒度的模型一方面带来了新的复杂性,需要我们继续去优化与增强;另一方面,除了地址存储与推送之外,应用粒度在帮助 Dubbo 选址层面也有进一步挖掘的潜力。

作者简介

刘军,Github 账号 Chickenlj,Apache Dubbo PMC,项目核心开发,见证了Dubbo从重启开源到Apache毕业的整个流程。现任职阿里云云原生应用平台团队,参与服务框架、微服务相关工作,目前主要在推动 Dubbo 3.0 - Dubbo 云原生。

课程推荐

为了更多开发者能够享受到 Serverless 带来的红利,这一次,我们集结了 10+ 位阿里巴巴 Serverless 领域技术专家,打造出最适合开发者入门的 Serverless 公开课,让你即学即用,轻松拥抱云计算的新范式——Serverless。

点击即可免费观看课程:https://developer.aliyun.com/learning/roadmap/serverless

阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”
查看原文

赞 2 收藏 2 评论 0

认证与成就

  • 获得 210 次点赞
  • 获得 36 枚徽章 获得 1 枚金徽章, 获得 9 枚银徽章, 获得 26 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-07-29
个人主页被 1.6k 人浏览