数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

「深度学习福利」大神带你进阶工程师,立即查看>>>
https://blog.csdn.net/w2064004678/article/details/83012387
MVCC,Multi-Version Concurrency Control,多版本并发控制。 MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;
如果有人从数据库中读数据的同时,有另外的人写入数据,有可能读数据的人会看到『半写』或者不一致的数据。 有很多种方法来解决这个问题,叫做并发控制方法 最简单的方法,通过加锁,让所有的读者等待写者工作完成,但是这样效率会很差。 MVCC 使用了一种不同的手段, 每个连接到数据库的读者, 在某个瞬间看到的是数据库的一个快照 , 写者写操作造成的变化在写操作完成之前(或者数据库事务提交之前)对于其他的读者来说是不可见的。 当一个 MVCC 数据库需要更一个一条数据记录的时候, 它不会直接用新数据覆盖旧数据,而是将旧数据标记为过时(obsolete)并在别处增加新版本的数据。 这样就会有存储多个版本的数据,但是只有一个是最新的。 需要系统周期性整理(sweep through)以真实删除老的、过时的数据。
数据库
2019-06-19 16:49:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
举一反三:跨平台版本迁移之 XTTS 方案操作指南 2018年04月22日 00:00:00 Enmotech 阅读数 3409


作者 | 罗贵林: 云和恩墨技术工程师,具有8年以上的 Oracle 数据库工作经验,曾任职于大型的国家电信、省级财政、省级公安的维护,性能调优等。精通 Oracle 数据库管理,调优,问题诊断。擅长 SQL 调优,Oracle Rac 等维护,管理。

本文由恩墨大讲堂155期线上分享整理而成 。课程回看可点击文末 “阅读原文” 。


1 跨平台跨版本迁移方案对比

针对跨平台跨版本的迁移,主要有以下三种方式:数据泵、GoldenGate / DSG、XTTS,针对停机时间、复杂度、实施准备时间,做了以下列表比对:


客户的需求都是最短停机时间,最少数据丢失。对于 GoldenGate / DSG 来说它的停机时间是最短的,但实施准备时间最长,复杂度最高;数据泵是停机时间最长,实施准备时间最短;XTTS 是介于这两者之间的,同时满足客户提出的短停机时间、低实施成本需求。

数据泵比较适用的场景就是数据量比较小、数据大概在 5T 以下,使用数据泵会方便很多。

GoldenGate / DSG 比较适用大数据量的数据分发,灾备库建设。

XTTS 是单次数据库跨平台、跨版本迁移利器,相同平台,相同版本迁移首选 rman。

在 Oracle11g 中的 RMAN 支持不同操作系统和不同 DB 版本之间的使用,关于 RMAN 的兼容性。如下图示:


注意以下操作系统的组合,这里假设 DB version 相同:
(1)For Oracle Database 10g Release 2 and above releases:
--在 Oracle 10gR2 之后的版本,支持如下操作系统之间的 RMAN 操作:
Solaris x86-64 <-> Linux x86-64
HP-PA <-> HP-IA
Windows IA (64-bit) / Windows (64-bitItanium) <-> Windows 64-bit for AMD / Windows (x86-64)
(2)For Oracle Database 11g Release 1 and above releases (requires minimum 11.1 compatible setting):
--在 Oracle 11gR1 之后的版本,支持如下操作系统之间的 RMAN 操作,当然这里也包含第一条里提到的 10gR2 后的组合。
Linux <-> Windows
(3)For Oracle Database 11g Release 2(11.2.0.2) and above releases:
Solaris SPARC (64-bit) <-> AIX(64-bit) - Note: this platform combination is currently not supported due to Bug 12702521
--在 11gR2 中,因为 Bug 12702521 的存在,Solaris SPARC (64-bit) <-> AIX (64-bit) 这2个版本之间不能进行 RMAN 操作。

XTTS 同样须遵循 Oracle 升级路线:

Oracle 9i/10g/11g 数据库升级路线图(upgrade roadmap)


2 XTTS 各版本功能对比

XTTS (Cross Platform Transportable Tablespaces) 跨平台迁移表空间,是 Oracle 自10g 推出的一个用来移动单个表空间数据以及创建一个完整的数据库从一个平台移动到另一个平台的迁移备份方法。它是 Oracle 8i 开始就引入的一种基于表空间传输的物理迁移方法,命名为 TTS,不过 8i 的表空间迁移仅支持相同平台、相同块大小之间的表空间传输,从 Oracle 9i 开始,TTS 开始支持同平台中,不同块大小的表空间传输,这个时候很多数据库管理员就注意到了 TTS 在实际工作中的应用,不过由于每次移动表空间都需要停机、停业务,而 9i 的 TTS 只能在相同平台之间进行数据移动,相比 Oracle RMAN 本身的快捷方便,更多人更愿意选择使用 RMAN 进行数据备份、数据移动,基于 TTS 的这些缺点,Oracle 10g 时代引入了跨平台的表空间传输方案 XTTS,标志着第一代 XTTS 的诞生。

可以理解为 TTS 就是传输表空间,把表空间传输出去,数据从一个库传输到另外一个库,不支持增量备份,而 XTTS 是在 TTS 基础上做了一些更新,支持了跨平台,支持增量备份。

XTTS 各版本的功能比对如下,表一:XTTS 各版本功能比对表


在 Oracle11gR2(推荐使用 11.2.0.4 及之后版本)以后,Oracle 推出了通过前滚数据文件,拷贝数据后再进行多次增量备份的 XTTS 来完成迁移过程,在这个过程中通过开启块跟踪特性,根据 SCN 号来执行一系列的增量备份,并且通过对块跟踪文件的扫描,来完成增量数据的增量备份应用,最后在通过一定的停机时间,在源库 read only 的状态下进行最后一次增量备份转换应用,使得整个迁移过程的停机时间同源库数据块的变化率成正比。这样大大的缩短了停机时间。


3 XTTS 前置条件检查

使用 XTTS 进行数据迁移需要具备的哪些前置条件?

可列出如下表格进行详细对比:

说明: 如果源端空间不够可以采用 NFS 磁盘挂载的方式,即将 Linux 的 NFS 文件系统挂载到中间环境(AIX 小机)的方式。 XTTS 基于 RMAN 备份的方法,对于空间需求要求较高。 目标端新环境,提前安装并部署好 Oracle+ASM 环境,同时创建与现有生产库字符集一致的数据库。


4 XTTS 三种迁移方式

采用 XTTS 迁移方式,具备跨平台字序转换和全量初始化加增量 merge 的功能,非常适用于异构 OS 跨平台迁移,成为数据库实施人员中公认的大数据量跨平台迁移的最佳选择。

传统的 TTS 传输表空间要求数据由源端到目标端传输的整个过程中,表空间必须置于 read only 模式,严重影响业务可用性。XTTS 方式可以在业务正常运行的情况下,进行物理全量初始化,增量 block 备份,数据高低字节序转码,增量 block 应用,保持目标端与源端数据的同步,整个过程不影响源端数据库使用。在最后的增量 block 应用完毕后,利用停机窗口进行数据库切换,显著地减少了停机时间。

XTTS 技术主要通过 DBMS_FILE_TRANSFER、RMAN 备份、手工 XTTS 迁移三种方式来进行数据库迁移:

4.1 方式一:dbms_file_transfer

DBMS_FILE_TRANSFER 包是 Oracle 提供的一个用于复制二进制数据库文件或在数据库之间传输二进制文件的程序包,在 XTTS 迁移中,利用不同的参数进行数据文件传输转换完成迁移。

DBMS_FILE_TRANSFER 方式主要使用了 xttdriver.pl 脚本的以下几个参数:


4.2 方式二:RMAN Backup

RMAN Backup 方式是基于 RMAN 备份原理,通过使用 rman-xttconvert_2.0 包提供的参数,对数据库进行基于表空间的备份,将备份生产的备份集写到本地或者 NFS 盘上,然后在通过 rman-xttconvert_2.0 包中包含的不同平台之间数据文件格式转换的包对进行数据文件格式转换,最后通过记录的表空间 的FILE_ID 号与生产元数据的导入来完成。

RMAN Backup 方式主要使用了 xttdriver.pl 脚本的以下几个参数:


4.3 方式三:手工 XTTS 迁移

Oracle 提供的封装 perl 脚本仅支持目标系统 LINUX,而通过手工 XTTS 迁移的方式可以支持目标系统是 AIX、HP、SOLARIS 等 UNIX 系统,主要有如下几个阶段:

1)rman copy rman target / <run{
allocate channel c1 type disk;
allocate channel c2 type disk;
backup as copy datafile 18,19,20,21,22........ format '/dump1/enmo/copy/enmo_%U';
release channel c1;
release channel c2;
}
EOF

2) 数据文件格式转换 convert from platform 'HP-UX IA (64-bit)' datafile '/dump1/ccm/vvstart_tabs.dbf' format '+FLASHDATA/ORCL/DATAFILE/vvstart_new_01.dbf';

3) 增量备份 set until scn=1850
backup incremental from scn 1000 datafile 18,19,20,21,22...... format '/dump1/enmo/incr/copy_%d_%T_%U';3;

4) 增量转换和应用 增量转换:
sys.dbms_backup_restore.backupBackupPiece(bpname => '/dump1/enmo/incr/copy_ORCL_20160707_78ra40o7_1_1',
fname => '/dump1/enmo/incr/copy_ORCL_20160707_78ra40o7_1_1_conv',handle => handle,media=> media,
comment=> comment, concur=> concur,recid=> recid,stamp => stamp, check_logical => FALSE,copyno=> 1,
deffmt=> 0, copy_recid=> 0,copy_stamp => 0,npieces=> 1,dest=> 0,pltfrmfr=> 4);
增量应用:
sys.dbms_backup_restore.restoreBackupPiece(done => done, params => null, outhandle => outhandle,outtag => outtag, failover => failover);

三种方式的目标端数据库版本均需要为 11.2.0.4 版本或者以上,如果在使用过程中,目标库的版本是 11.2.0.3 或者更低,那么需要创建一个单独的 11.2.0.4 版本数据库作为中间库来在目标端进行数据文件的格式转换,而使用 DBMS_FILE_TRANSFER 包目标端的数据库版本必须是 11.2.0.4。

5 XTTS 初始参数说明

XTTS 是基于一组 rman-xttconvert_2.0 的脚本文件包来实现跨平台的数据迁移,主要包含 Perl script xttdriver 和 xttdriver Perl 脚本。Perl script xttdriver.pl 是备份、转换、应用的执行脚本,xtt.properties 是属性文件,其中包含 XTTS 配置的路径、参数。

rman-xttconvert_2.0 包参数说明如下表:


6 XTTS 迁移步骤(使用 RMAN 备份方法)

主要有以下步骤:
1)初始化参数设置;
2)将源端数据文件传输到目标系统;
3)转换数据文件为目标系统的字节序;
4)在源端创建增量备份,并传输到目标端;
5)在目标端恢复增量备份;
6)重复多次操作4和5步骤;
7)将源端数据库表空间设置为 READ ONLY 模式;
8)最后一次执行4和5步骤;
9)在源端导出元数据,并在目标端导入;
10)将目标端的数据库表空间设置为 READ WRITE;
11)数据验证。

6.1 XTTS 迁移准备阶段

6.1.1 生产库打开块跟踪特性

首先在生产库上打开块跟踪功能。
如果源库是 11g,延时段特性需要先禁用 alter system set deferred_segment_creation=false sid='*' scope=spfile;
不然 xtts 不会将空表导入目标库。
alter database enable block change tracking using file '/home/oracle/xtts/block_change_tracking.log';

6.1.2 传输表空间前自包含检查

首先对表空间做自包含检查,检查出 Index 存在自包含问题,需要重建或者最后创建: SQL> execute dbms_tts.transport_set_check(‘DATATBS ’,true);

PL/SQL procedure successfully completed.

SQL> select * from transport_set_violations;

XXXX 创建在 USERS 表空间,需要提前迁移至 DATATBS 表空间。 Drop index XXXX;
CREATE INDEX XXXX ON "LUOKLE"."BI_LUOKLEINSTRUCTION" ("SENDTIME", "STATUS", "RECLUOKLE", "CREATORORGID", "CREATETIME") TABLESPACE DATATBS parallel 8;
Alter index XXXX noparallel;

由于 XTTS 最后导入元数据时候不支持临时表,所以需要提前查出系统临时表信息。 select dbms_metadata.get_ddl('TABLE',TABLE_NAME,owner) from dba_tables where TEMPORARY='Y' and owner=XXX;

需要手工创建的临时表有X个,以下脚本导入元数据之后手工执行。 CREATE GLOBAL TEMPORARY TABLE XXX
( "_ID" NUMBER,
"STATUS" CHAR(1)
) ON COMMIT PRESERVE ROWS ;

在表空间传输的中,要求表空间集为自包含的,自包含表示用于传输的内部表空间集没有引用指向外部表空间集。自包含分为两种:一般自包含表空间集和完全(严格)自包含表空间集。

常见的以下情况是违反自包含原则的: 索引在内部表空间集,而表在外部表空间集(相反地,如果表在内部表空间集,而索引在外部表空间集,则不违反自包含原则); 分区表一部分区在内部表空间集,一部分在外部表空间集(对于分区表,要么全部包含在内部表空间集中,要么全不包含); 如果在传输表空间时同时传输约束,则对于引用完整性约束,约束指向的表在外部表空间集,则违反自包含约束;如果不传输约束,则与约束指向无关; 表在内部表空间集,而 lob 列在外部表空间集,则违反自包含约束。

通常可以通过系统包 DBMS_TTS 来检查表空间是否自包含,验证可以以两种方式执行:非严格方式和严格方式。

以下是一个简单的 验证过程 ,假定在 eygle 表空间存在一个表 eygle,其上存在索引存储在 USERS 表空间: SQL> create table eygle as select rownum id ,username from dba_users;
SQL> create index ind_id on eygle(id) tablespace users;

以SYS用户执行非严格自包含检查(full_check=false):

执行严格自包含检查(full_check=true):

反过来对于 USERS 表空间来说,非严格检查也是无法通过的:

但是可以对多个表空间同时传输,则一些自包含问题就可以得到解决:

6.1.3 目标端创建数据库并修改部分参数

在目标环境需要提前安装好 GI 和 Oracle 软件,并创建监听、拷贝生产环境的 TNS 和新数据库,并修改部分数据库参数: create directory xtts_dir as 'home/oracle/xtts/';
grant read,write on directory xtts3 to public;

调整以下参数: alter system set "_optimizer_adaptive_cursor_sharing"=false sid='*' scope=spfile;
alter system set "_optimizer_extended_cursor_sharing"=none sid='*' scope=spfile;
alter system set "_optimizer_extended_cursor_sharing_rel"=none sid='*' scope=spfile;
alter system set "_optimizer_use_feedback"=false sid ='*' scope=spfile;
alter system set deferred_segment_creation=false sid='*' scope=spfile;
alter system set event='28401 trace name context forever,level 1' sid='*' scope=spfile;
alter system set resource_limit=true sid='*' scope=spfile;
alter system set resource_manager_plan='force:' sid='*' scope=spfile;
alter system set "_undo_autotune"=false sid='*' scope=spfile;
alter system set "_optimizer_null_aware_antijoin"=false sid ='*' scope=spfile;
alter system set "_px_use_large_pool"=true sid ='*' scope=spfile;
alter system set audit_trail=none sid ='*' scope=spfile;
alter system set "_partition_large_extents"=false sid='*' scope=spfile;
alter system set "_index_partition_large_extents"= false sid='*' scope=spfile;
alter system set "_use_adaptive_log_file_sync"=false sid ='*' scope=spfile;
alter system set disk_asynch_io=true sid ='*' scope=spfile;
alter system set db_files=2000 scope=spfile;

alter profile "DEFAULT" limit PASSWORD_GRACE_TIME UNLIMITED;
alter profile "DEFAULT" limit PASSWORD_LIFE_TIME UNLIMITED;
alter profile "DEFAULT" limit PASSWORD_LOCK_TIME UNLIMITED;
alter profile "DEFAULT" limit FAILED_LOGIN_ATTEMPTS UNLIMITED;

exec dbms_scheduler.disable( 'ORACLE_OCM.MGMT_CONFIG_JOB' );
exec dbms_scheduler.disable( 'ORACLE_OCM.MGMT_STATS_CONFIG_JOB' );

BEGIN
DBMS_AUTO_TASK_ADMIN.DISABLE(
client_name => 'auto space advisor',
operation => NULL,
window_name => NULL);
END;
/

BEGIN
DBMS_AUTO_TASK_ADMIN.DISABLE(
client_name => 'sql tuning advisor',
operation => NULL,
window_name => NULL);
END;
/
6.1.4 源端保留用户信息和权限

源端保留用户信息和权限: spool create_user_LUOKLE.sql
select 'create user '||username||' identified by values '||''''||password||''''||';' from dba_users where default_tablespace in('TEST');
spool off

角色权限的语句: spool grant_role_priv_LUOKLE.sql
select 'grant '||GRANTED_ROLE||' to '||grantee||';' from dba_role_privs where grantee in(select username from dba_users where default_tablespace in('TEST'));
spool off

sys 权限的赋权语句: spool grant_sys_priv_LUOKLE.sql
select 'grant '||privilege||' to '||grantee||';' from dba_sys_privs where grantee in(select username from dba_users where default_tablespace in('TEST'));
spool off

对表空间的配额权限语句: spool unlimited_tablespace_LUOKLE.sql
select 'alter user '||username||' quota unlimited on DATATBS ||';' from dba_users where default_tablespace in('TEST');
spool off

附:若后期存在用户与其他非本用户的对象权限问题,如 Schema A 对 Schema B 上表的访问和操作等权限,可以使用以下语句在源库检索出权限,并在目标端数据库进行赋权即可: set line 200
set pages 0
spool grant_tab_priv.sql
select 'grant ' || privilege || ' on ' || owner || '.' || table_name || ' to ' || grantee || ';'
from dba_tab_privs
where owner in (select username from dba_users where default_tablespace in('TEST'))
or grantee in (select username from dba_users where default_tablespace in('TEST''))
and privilege in('SELECT','DELETE','UPDATE','INSERT') and grantable='NO'
union
select 'grant ' || privilege || ' on ' || owner || '.' || table_name || ' to ' || grantee || ' with grant option;'
from dba_tab_privs
where owner in (select username from dba_users where default_tablespace in('TEST'))
or grantee in (select username from dba_users where default_tablespace in('TEST'))
and privilege in('SELECT','DELETE','UPDATE','INSERT') and grantable='YES';
spool off

6.2 XTTS 迁移初始化阶段
6.2.1 源端更改配置文件 xtt.properties

更改以下参数: tablespaces=TEST,TEST_INDEX
platformid=13
dfcopydir=/home/oracle/xtts/bak
backupformat=/home/oracle/xtts/bakincr
stageondest=/home/oracle/xtts/bak
storageondest=+DATA/oracle11gasm/datafile
backupondest=+DATA/oracle11gasm/datafile
asm_home=/u01/app/grid/product/11.2.0/grid
asm_sid=+ASM
parallel=2
rollparallel=2
getfileparallel=2

更改配置之后,将整个 rman-xttconvert 目录传输至目标端。

6.2.2 源端进行迁移初始化

在源端进行初始化,即 backup as copy 的形式备份数据文件到 /aix_xtts/bak 下。 more full_backup.sh
export TMPDIR=/aix_xtts
perl xttdriver.pl -p

执行脚本进行全备: nohup sh ./full_back.sh >full_back.log &

初始化之后产生 xttplan.txt rmanconvert.cmd;
xttplan.txt 记录了当前 SCN,也就是下次需要增量的开始 SCN;
rmanconvert.cmd 记录了文件转换的名字。
[oracle@oracle11gasm xtts]$ cat rmanconvert.cmd
host 'echo ts::TEST';
convert from platform 'Linux x86 64-bit'
datafile
'/home/oracle/xtts/bak/TEST_5.tf'
format '+DATA/oracle11gasm/datafile/%N_%f.xtf'
parallelism 2;
host 'echo ts::TEST_INDEX';
convert from platform 'Linux x86 64-bit'
datafile
'/home/oracle/xtts/bak/TEST_INDEX_6.tf'
format '+DATA/oracle11gasm/datafile/%N_%f.xtf'
parallelism 2;
6.2.3 转换初始化文件至 ASM 中

由于使用了 NFS 不需要再次传输 /aix_xtts/bak,修改 xtt.properties 文件:
修改备库 xtt.properties 文件:

增加: asm_home=/oracle/app/grid/11.2.0.4
asm_sid=+ASM

该步骤中,我们需要在 Linux 目标端主机上完成,进行全库的数据文件转换,通过脚本直接将数据文件转换到 ASM DISKGROUP 中。

注意: 该转换步骤中,我们只需要转换我们需要传输的业务表空间即可,也就是 DATATBS 。

如下是全库的转换脚本: more convert.sh
export XTTDEBUG=1
export TMPDIR=/aix_xtts
perl xttdriver.pl -c

执行脚本进行转换: nohup sh ./convert.sh >convert.log &

并且在目标的 storageondest 目录下会生成经转换后的数据文件拷贝。

日志如下: --------------------------------------------------------------------
Parsing properties
--------------------------------------------------------------------
--------------------------------------------------------------------
Done parsing properties
--------------------------------------------------------------------
--------------------------------------------------------------------
Checking properties
--------------------------------------------------------------------
--------------------------------------------------------------------
Done checking properties
--------------------------------------------------------------------
--------------------------------------------------------------------
Performing convert
--------------------------------------------------------------------
--------------------------------------------------------------------
Converted datafiles listed in: /xtts/xttnewdatafiles.txt

转换成功之后会生成 xttnewdatafiles.txt,该文件为数据文件在 ASM MAP 关系表,即 file_id 和数据文件名对应表,增量恢复需要。

如果没有产生需要手工创建,命令格式如下: [oracle@oracle11gasm xtts]$ cat xttnewdatafiles.txt
::TEST
5,+DATA/oracle11gasm/datafile/test_5.xtf
::TEST_INDEX
6,+DATA/oracle11gasm/datafile/test_index_6.xtf

6.3 XTTS 迁移增量备份恢复
6.3.1 生产库进行第一次增量备份

由于生产库每天的归档极大,因此需要进行多次增量备份,并将增量备份传输到目标端 Linux 的新环境,并应用增量备份,其中初始化产生的 xttplan.txt 文件记录了增量 SCN 起始位置,这里我们只需要对需要传输的表空间进行增量备份即可: more do_incr.sh
export TMPDIR=/aix_xtts
perl xttdriver.pl –i

执行脚本进行增量备份: nohup sh ./do_incr.sh >do_incr_1.log &

backup incremental from scn 13827379581 第一次增量备份开始的 SCN,由 -p 初始化产生;
增量8天的数据时间花费100分钟,产生增量文件40G。
[root@ecmsdb01plk xtts]# cat xttplan.txt.new --增量备份生成新的 xttplan.txt.new 文件
DATATBS ::::14000344337 --下一次增量备份开始的 SCN
[oracle@oracle11g bakincr]$ pwd
/home/oracle/xtts/bakincr
[oracle@oracle11g bakincr]$ ls -lrt
-rw-r----- 1 oracle oinstall 49152 Apr 1 08:00 0gsv7s0v_1_1
-rw-r----- 1 oracle oinstall 49152 Apr 1 08:00 0hsv7s10_1_1

第一次增量备份之后产生的2个配置文件为 tsbkupmap.txt 和 incrbackups.txt,这两个为增量与数据文件对应关系配置,在做增量恢复时候需要用到。
[oracle@oracle11gasm xtts]$ cat tsbkupmap.txt
TEST_INDEX::6:::1=0hsv7s10_1_1
TEST::5:::1=0gsv7s0v_1_1
[oracle@oracle11gasm xtts]$ cat incrbackups.txt
/home/oracle/xtts/bakincr/0hsv7s10_1_1
/home/oracle/xtts/bakincr/0gsv7s0v_1_1

通过 FTP 传输增量文件至目标 LINUX 环境。
ftp> put a0rj69fq_1_1
200 PORT command successful.
150 Opening data connection for a0rj69fq_1_1.
226 Transfer complete.
23119626240 bytes sent in 1395 seconds (1.618e+04 Kbytes/s)
local: a0rj69fq_1_1 remote: a0rj69fq_1_1
ftp>
ftp>

ftp>put 9vrj66jc_1_1
200 PORT command successful.
150 Opening data connection for 9vrj66jc_1_1.
226 Transfer complete.
22823591936 bytes sent in 1381 seconds (1.614e+04 Kbytes/s)
local: 9vrj66jc_1_1 remote: 9vrj66jc_1_1

两个窗口并行传输,花费时间25分钟,由于第一次增量数据较大整体消耗2个小时。
6.3.2 目标端进行第一次增量恢复

增量恢复前需要检查 xttnewdatafiles.txt(数据文件在 ASM 中 MAP 关系表)、tsbkupmap.txt 和 incrbackups.txt(增量与数据文件对应关系配置)、xttplan.txt(下次需要增量的开始 SCN)这些配置文件是否存在,如不存在会出现报错。

将增量备份集放置 /home/oracle/xtts/bak下,做增量恢复:
more restore_incr.sh
export TMPDIR=/home/oracle/xtts
perl xttdriver.pl -r

执行脚本进行增量恢复: nohup sh ./restore_incr.sh >restore_incr.log &

第一次增量转换加恢复40G数据耗时30分钟。



如果在-r应用报 ORA-19638 错误,则需要把 xttplan.txt 使用前一次或前二次的。(因为多次的-i和-s会对 xttplan.txt 进行修改)
6.3.3 生成下次增量所需 SCN 配置文件
$ perl xttdriver.pl -s
--------------------------------------------------------------------
Parsing properties
--------------------------------------------------------------------
--------------------------------------------------------------------
Done parsing properties
--------------------------------------------------------------------
--------------------------------------------------------------------
Checking properties
--------------------------------------------------------------------
--------------------------------------------------------------------
Done checking properties
--------------------------------------------------------------------
Prepare newscn for Tablespaces: 'DATATBS '
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
Prepare newscn for Tablespaces: ''
New /oraexport/xtts/xttplan.txt with FROM SCN's generated

[root@ecmsdb01plk xtts]# cat xttplan.txt
DATATBS ::::14000344337

生产环境执行如上命令,产生最新 xttplan.txt 文件,下次增量备份开始 SCN 为 14000344337。
6.3.4 生产库进行第二次增量备份

生产环境进行第二次增量备份,此次增量备份一天数据: more do_incr.sh
export TMPDIR=/home/oracle/xtts
perl xttdriver.pl –i

执行脚本进行增量备份: nohup sh ./do_incr.sh >do_incr_1.log &
backup incremental from scn 14000344337 第二次增量备份开始的 SCN。

第二次增量备份一天数据耗时5分钟。产生文件12G,通过 FTP 并行传输花费10分钟,总体二次增量备份一天数据耗时20分钟。
6.3.5 目标端进行第二次增量恢复

xttplan.txt 使用前一次的。
二次增量恢复一天数据,耗时15分钟: [oracle@ecmsdb01plk scripts_linux]$ perl xttdriver.pl -r -d

6.4 XTTS 正式迁移


切割准备工作示意图
6.4.1 停止业务

业务部门停止应用程序。数据库检查当前会话,需要杀掉已经存在的会话。 set lines 132 pages 1111 trim on trims on
spo machine_before_upgrade.txt
select inst_id, machine, count(*) from gv$session where username!='SYS' group by inst_id, machine;
spo off

Kill 掉任然连接到数据库的会话: SELECT 'kill -9 '||SPID FROM V$PROCESS
WHERE ADDR IN (SELECT PADDR FROM V$SESSION WHERE USERNAME != 'SYS');

select 'alter system kill session '''||sid||','||serial#||''' immediate;'
from v$session where USERNAME != 'SYS';
6.4.2 生成最后一次增量备份 SCN 配置文件
$ perl xttdriver.pl -s

使用 xttdriver.pl –s生成最后一个 SCN 增量配置文件(即最后一次增量备份开始的SCN),也可以手工修改 xttplan.txt。
6.4.3 生产库将表空间设置为只读

将业务表空间设置为只读模式,并开始最后一次的增量备份。
sqlplus / as sysdba
alter tablespace DATAtBS read only;
6.4.4 最后一次增量备份

在源端中间环境进行最后一次增量备份: more do_incr.sh
export TMPDIR=/home/oracle/xtts
perl xttdriver.pl –i
执行脚本进行备份:
nohup sh ./do_incr.sh >do_incr_1.log &

按照之前每天增量备份加传输大概耗时30分钟,在增量备份同时可以进行元数据的导出。
6.4.5 导出元数据(XTTS 元数据以及其他对象元数据)

在这个步骤中,我们可以并行同时导出 XTTS 的元数据以及其他的元数据,例如数据库存储过程,函数。触发器等等。

导出传输表空间元数据命令如下: create or replace directory xtts3 as '/oraexport/xtts/ '
exp \'/ as sysdba \' transport_tablespace=y tablespaces='DATATBS ' file=/oraexport/xtts/exp_DATATBS_xtts.dmp log=/oraexport/xtts/exp_DATATBS_xtts.log STATISTICS=none parallel=8
expdp \'/ as sysdba\' dumpfile=tts.dmp directory=xtts_dir logfile=expdp_xtts.log transport_tablespaces=TEST,TEST_INDEX exclude=STATISTICS;

导出元数据耗时2分钟。

导出其他对象数据如下: expdp \'/ as sysdba\' directory=xtts_dir dumpfile=expdp_LUOKLE_meta.dmp logfile=expdp_LUOKLE_meta.log CONTENT=metadata_only SCHEMAS=LUOKLE parallel=2;

导出其他数据耗时15分钟。

导出完成之后,将 dmp 文件传输到 Linux。
6.4.6 最后一次应用增量备份

将备份集放置 /oradata2 下,做增量恢复: more restore_incr.sh
export TMPDIR=/home/oracle/xtts
perl xttdriver.pl -r -d
执行脚本进行增量恢复:
nohup sh ./restore_incr.sh >restore_incr.log &
根据之前增量恢复一天数据大概耗时15分钟。
6.4.7 导入 XTTS 元数据

通过如下命令将 xtts 表空间元数据导入到目标新库中: create or replace directory xtts_dir as '/home/oracle/xtts/';
impdp \'/ as sysdba\' dumpfile=expdp_tts.dmp directory=xtts_dir transport_tablespace=y datafiles='+DATA/ORACLE11GASM/DATAFILE/test_5.xtf,+DATA/ORACLE11GASM/DATAFILE/test_index_6.xtf';
根据之前测试结果导入时间为10分钟左右。
6.4.8 目标端新库将表空间设置为读写模式

将业务表空间设置为可读写模式: sqlplus / as sysdba
alter tablespace DATATBS read write;
6.4.9 目标端新库导入其他对象元数据
impdp \'/ as sysdba\' dumpfile=expdp_LUOKLE_meta.dmp directory=xtts_dir

STATISTICS 如果不使用之前统计信息可用排除,最后收集。

根据之前测试耗时15分钟左右,XTTS 已完成表空间迁移。
数据库
2019-06-19 14:55:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
MySQL作为蓝鲸平台存取数据的主要数据库,其稳定性关系到蓝鲸平台的使用体验,而其数据安全性则可能关系到企业IT资产相关信息,在安装和维护蓝鲸平台的过程中应引起足够重视。本文将对如下几个出现过的问题进行分析和提供处理建议。这几个问题分别为:升级,配置日志自动清理,binlog手动清理维护,表清空,备份。
  蓝鲸平台的MySQL数据库会在安装平台时自动安装,其版本为5.5.24,架构为主从复制(5台服务器情况下),下文分别对提到的几个问题进行描述。

MySQL升级
  
1.问题分析
  蓝鲸平台默认安装的MySQL版本为5.5.24,一般不符合安全扫描的版本要求,为了避免在使用过程中由于安全问题需要重新升级数据库,建议在完成平台安装后及时进行数据库升级,此时数据库还没存入业务数据,升级无风险且升级效率高,基于已经在生成环境验证过的升级经验,为防止版本跨度过大导致的兼容问题,建议升级到MySQL5.5的最高版本即5.5.62,采用逻辑升级的方法,使用mysqldump将低版本的据库全库导出,再导入到安装好的新版本。
 
2. 逻辑升级过程
1、查当前MySQL进程
  检查当前运行的MySQL的进程详情,主要是查看启动参数,用于升级后对比   [root@paas-1 install]# ps -ef |grep mysql   root 20407 8526 0 15:10 pts/3 00:00:00 grep --color=auto mysql   root 29942 1 0 Dec17 ? 00:00:00 /bin/sh /data/bkce/service/mysql/bin/mysqld_safe --datadir=/data/bkce/public/mysql/ --pid-file=/data/bkce/public/mysql/mysql.pid   mysql 30344 29942 5 Dec17 ? 13:17:37 /data/bkce/service/mysql/bin/mysqld --basedir=/data/bkce/service/mysql --datadir=/data/bkce/public/mysql/ --plugin-dir=/data/bkce/service/mysql/lib/plugin --user=mysql --log-error=/data/bkce/public/mysql//paas-1.err --pid-file=/data/bkce/public/mysql/mysql.pid --socket=/data/bkce/logs/mysql/mysql.sock --port=3306
2、全库备份
  使用如下命令进行全库备份,备份路径可根据数据量,磁盘性能等进行调整 [root@paas-1 service]# mysqldump -uroot -x -A -E -R >/tmp/alldbback.sql
3、备份安装目录,数据目录
  停止数据库服务,并对basedir,datadir目录进行备份,升级前一定要进行备份,避免出现意外进行回退   [root@paas-1 service]# mkdir /data/backup   [root@paas-1 service]# mv mysql/ /data/backup/   [root@paas-1 service]# cd /data/bkce/public/   [root@paas-1 public]# mv mysql /data/backup/mysql_data
4、新版本解压安装
  使用二进制的包进行解压安装,下载5.5.62版本的压缩包,上传至安装目录进行解压,创建软连接为mysql   [root@paas-1 service]# tar zxf /tmp/mysql-5.5.62-linux-glibc2.12-x86_64.tar.gz   [root@paas-1 service]# ln -s mysql-5.5.62-linux-glibc2.12-x86_64/ mysql
   5、初始化数据库
  对数据目录进行初始化   [root@paas-1 public]# mkdir mysql   [root@paas-1 mysql]# cd /data/bkce/service/mysql   [root@paas-1 mysql]# ./scripts/mysql_install_db --user=mysql --basedir=/data/bkce/service/mysql --datadir=/data/bkce/public/mysql/   Installing MySQL system tables...   181227 15:39:10 [Note] Ignoring --secure-file-priv value as server is running with --bootstrap.   181227 15:39:10 [Note] /data/bkce/service/mysql/bin/mysqld (mysqld 5.5.62-log) starting as process 24766 ...   OK   Filling help tables...   181227 15:39:11 [Note] /data/bkce/service/mysql/bin/mysqld (mysqld 5.5.62-log) starting as process 24790 ...   OK   To start mysqld at boot time you have to copy   support-files/mysql.server to the right place for your system   PLEASE REMEMBER TO SET A PASSWORD FOR THE MySQL root USER !   To do so, start the server, then issue the following commands:   /data/bkce/service/mysql/bin/mysqladmin -u root password 'new-password'   /data/bkce/service/mysql/bin/mysqladmin -u root -h 132.108.252.43 password 'new-password'   Alternatively you can run:   /data/bkce/service/mysql/bin/mysql_secure_installation   which will also give you the option of removing the test   databases and anonymous user created by default. This is   strongly recommended for production servers.   See the manual for more instructions.   You can start the MySQL daemon with:   cd /data/bkce/service/mysql ; /data/bkce/service/mysql/bin/mysqld_safe &   You can test the MySQL daemon with mysql-test-run.pl   cd /data/bkce/service/mysql/mysql-test ; perl mysql-test-run.pl   Please report any problems at http://bugs.mysql.com/
6、修改配置并启动
  因原my.cnf配置文件未设置pid file,新版启动时会根据主机名自动生成,跟原进程下的pid文件不符,需要在配置文件里添加如下配置:   pid-files=   vim /etc/my.cnf   [mysqld]   /data/bkce/public/mysql/mysql.pid
  启动数据库:  /data/bkce/service/mysql/bin/mysqld_safe &
7、导入数据
  登录mysql,确认版本已经升级至5.5.62,接下来导入备份的数据 mysql -uroot   等待其导入完成即可。
8、恢复mysql.sh文件
  在蓝鲸平台中控机上控制mysql服务的启停,是通过调用安装目录下的mysql.sh文件实现的,所以需要将该文件恢复到新版安装后的目录下   [root@paas-1 mysql]# cd /data/backup/mysql/bin/   [root@paas-1 bin]# cp mysql.sh /data/bkce/service/mysql/bin/
9、升级确认
  通过中控机查看及启停mysql,检查是否正常   [root@paas-1 mysql]# cd /data/install/   [root@paas-1 install]# ./bkcec status mysql   [root@paas-1 install]#./bkcec stop mysql   [root@paas-1 install]#./bkcec start mysql
  检查蓝鲸平台,查看数据库调用是否正常。到此,整个升级过程完成。

MySQL配置
  1.问题分析
我处理过的蓝鲸平台MySQL问题,很多是因为磁盘空间不足导致,而磁盘空间不足多是由于binlog日志过多而未及时清理造成,蓝鲸在客户处使用过程中由于没有专门的运维团队,通常很少去关注后台服务器的情况,所以binlog自动清理的机制强烈建议开启。

  2.测试过程
   1、设置binlog自动清理
  参数expire_logs_days表示超过该参数值的binlog日志会自动清理,可以根据服务器磁盘空间来确定该值的设置,前提是保证日志保留时间大于数据库备份频率,比如备份是7天一次全备,则expire_logs_days的值应大于7,空间充足情况下一般建议设置为7天或者15天。
  设置方法,在参数文件中配置expire_logs_days=7即可在重启MySQL后生效。也可以在MySQL命令行直接设置:
   mysql> set global expire_logs_days=7;
  在命令行设置不会立即对日志进行清理,需要达到如下几种触发条件之一:
  1、binlog大小超过max_binlog_size
  2、手动执行flush logs
  3、MySQL服务重新启动时
  所以如果是MySQL使用过程中命令行设置了该参数,可以使用flush logs切换日志触发清理,此时需要注意清理需要占用服务器I/O资源,应在不影响使用情况下执行。

binlog手动清理维护
1.问题分析
  当磁盘空间由于binlog占用空间不足,MySQL服务已经挂起时,需要手动清理binlog日志。处理过的几次问题是管理员在清理binlog时删除了所有的binlog日志,磁盘空间释放后,重启MySQL服务发现启动失败,其原因就是binlog被全部清理后,MySQL服务启动需要写入日志到最新的一个binlog,而其记录的最后一个binlog找不到,所以会报错启动失败。
   1、手动清理的正确方法
  手动清理binlog日志时,需要保留最新一个日志,保证数据库重启后可以检测到最新的写入日志,确认最后一个日志,可以根据日志名称(序号递增)或者写入时间来确定。
   2、删除所有日志后的处理方法
  如果已经删除了所有的日志而无法启动数据库,此时可以按照如下方法处理:
  在datadir目录下找到mysql-bin.index文件,该文件是记录数据库里的binlog信息,清空该文件内容,重启数据库,此时日志会重置为1号,数据库恢复正常。

表清空操作
 1.问题分析
  由于数据库里某些日志表太大,影响查询和插入表的效率,有时会做清空表的操作,而管理员维护MySQL数据库时习惯使用图形工具如Navicat,当表的数据比较大时,从图形工具点击清空表,系统会卡住,原因是工具里的情况表是delete的操作,数据库执行时会每条数据进行删除并记录redo,undo日志,占用数据库资源较大导致的数据库hang住。
   1、表清空的正确方法
  如果确认表数据是不需要的,请使用truncate的方式情况表,效率非常高且占用资源少,SQL语法如下: mysql> truncate table_name;
  
MySQL备份
  1.问题分析
  数据库最重要的就是数据,数据的安全高于一切,而完善的备份是数据安全的最后一道防线,蓝鲸平台是一个企业级的平台,其存储的数据也是至关重要的,所以备份策略必须合理制定。MySQL 最常用的备份方式分为逻辑备份mysqldump,物理备份xtrabackup,当数据量不大,备份时间在1小时内可以完成的,使用mysqldump即可,更大数据量则需要考虑使用xtrabackup,下面主要介绍mysqldump。

   2.备份通用性命令
  mysqldump是个很灵活的工具,有很多参数可以在备份时使用,不过对于蓝鲸平台的mysql备份,建议使用如下通用的备份命令即可: mysqldump -uroot -p --all-databases --master-data=2 --flush-logs --single-transaction > /backup/dbfull-`date +%F`.sql
  如果需要压缩备份文件,可以用如下命令: mysqldump -uroot -p --all-databases --master-data=2 --flush-logs --single-transaction |gzip > dbfull-`date +%F`.tar.gz
  该命令备份所有的数据库,记录备份时binlog的位置(用于建立主从关系的复制起始点或是恢复到指定时间点),以事务的方式备份,不会影响主库运行。
  备份如果是在本地磁盘,还需要定期清理备份文件,比如清理30天前的备份文件,如下清理脚本可以参考:  find /backup -mtime +30 -name "dbfull-*.sql" -exec rm -rf {} \;
 
随着蓝鲸平台在企业级的广泛使用,其存储数据的MySQL数据库应确保数据安全和MySQL服务平稳运行,本文所列举的几个问题都是常见的导致平台无法使用的问题,其处理方法也都是验证过并在生成环境使用的方式,可以作为处理该类问题的参考。
数据库
2019-06-19 14:11:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
Oracle中如何给字段设置自增长

新建序列

--创建序列
create sequence assets_autoinc--assets_autoinc序列名
minvalue 1 --最小值
maxvalue 99999999999999999999999999 --最大值
start with 1--从1开始
increment by 1--每次加1
nocache;--不建缓冲区
选中这一部分,然后运行即可。打开左边的列表,就会出现刚刚新增的序列。
创建完序列之后在SQL Window中输入插入语句
可以实现字段的自增长
例如:
insert into 表名(字段1,字段2,……) values(值1,值2,……)

但是如果在表上直接操作

需要手动添加该需要自增的字段。

解决办法:
新建触发器

create or replace trigger insert_assets_autoinc --insert_assets_autoinc触发器名称
before insert
on assets --assets 是表名
for each row
declare
-- local variables here(这里是局部变量)
nextid number;
begin
IF :new.ASSETSID IS NULL or :new.ASSETSID=0 THEN --ASSETSID是列名
select assets_autoinc.nextval --assets_autoinc正是刚才创建的序列名
into nextid
from sys.dual;
:new.ASSETSID:=nextid;
end if;
end insert_assets_autoinc;

选中运行:

成功之后刷新左侧列表,然后即可看到刚刚建的触发器:

这样不管是利用sql语句添加
还是直接操作表都可以实现字段的自增长了。
数据库
2019-06-19 14:01:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
概述
本文主要分享针对想压测OceanBase时需要了解的一些技术原理。这些建议可以帮助用户对OceanBase做一些调优,再结合测试程序快速找到适合业务的最佳性能。由于OceanBase自身参数很多、部署形态也比较灵活,这里并没有给出具体步骤。
数据库读写特点
压测的本质就是对一个会话的逻辑设计很高的并发。首先需要了解单个会话在数据库内部的读写逻辑。比如说,业务会话1对数据库发起一个DML SQL,第一次修改某笔记录,数据库会怎么做呢?
为了便于理解OB的行为,我们先看看ORACLE是怎么做的。后面有对比才可以加深理解。
ORACLE 读写特点
ORACLE会话第一次修改一行记录,如果该记录所在块(8K大小)不在内存(Buffer Cache)里时会先从磁盘文件里读入到内存里。这个称为一次物理读,为了性能考虑,ORACLE一次会连续读取相邻的多个块。然后就直接在该块上修改,修改之前会先记录REDO和UNDO(包括UNDO的REDO)。然后这个数据块就是脏块(Dirty Block)。假设事务没有提交,其他会话又来读取这个记录,由于隔离级别是读已提交(READ COMMITTED),ORACLE会在内存里克隆当前数据块到新的位置,新块包含了最新的未提交数据。然后ORACLE在新块上逆向应用UNDO链表中的记录,将数据块回滚到读需要的那个版本(SCN),然后才能读。这个也称为一次一致性读(Consistency Read),这个新块也称为CR块。
即使是修改一条记录一个字段的几个字节,整个块(8K大小)都会是脏块。随着业务持续写入,大量脏块会消耗数据库内存。所以ORACLE会有多重机制刷脏块到磁盘数据文件上。在事务日志切换的时候也会触发刷脏块操作。如果业务压力测试ORACLE,大量的写导致事务日志切换很频繁,对应的刷脏操作可能相对慢了,就会阻塞日志切换,也就阻塞了业务写入。这就是ORACLE的特点。解决办法就是加大事务日志文件,增加事务日志成员或者用更快的磁盘存放事务日志和数据文件。
ORACLE里一个表就是一个Segment(如果有大对象列还会有独立的Segment,这个先忽略),Segment由多个不一定连续的extent组成,extent由连续的Block(每个大小默认8K)组成,extent缺点是可能会在后期由于频繁删除和插入产生空间碎片。
OceanBase 读写特点
OceanBase会话第一次修改一行记录,如果该记录所在块(64K大小)不在内存(Block Cache)里时也会先从磁盘文件里读入到内存里。这个称为一次物理读。然后要修改时跟ORACLE做法不同的是,OceanBase会新申请一小块内存用于存放修改的内容,并且链接到前面Block Cache里该行记录所在块的那笔记录下。如果修改多次,每次修改都跟前面修改以链表形式关联。同样在修改之前也要先在内存里记录REDO。每次修改都会记录一个内部版本号,记录的每个版本就是一个增量。其他会话读取的时候会先从Block Cache中该记录最早读入的那个版本(称为基线版本)开始读,然后叠加应用后面的增量版本直到合适的版本(类似ORACLE中SCN概念)。(随着版本演进,这里细节逻辑可能会有变化。)
OB的这个读方式简单说就是从最早的版本读起,逐步应用增量(类似REDO,但跟REDO日志无关)。而ORACLE一致性读是从最新的版本读起,逐步回滚(应用UNDO)。在OB里,没有UNDO。当版本链路很长时,OB的读性能会略下降,所以OB也有个checkpoint线程定时将记录的多个版本合并为少数几个版本。这个合并称为小合并(minor compaction)。此外,OB在内存里针对行记录还有缓存,
从上面过程还可以看出,每次修改几个字节,在内存里的变脏的块只有增量版本所在的块(默认写满才会重新申请内存),基线数据块是一直不变化。所以OB里脏块产生的速度非常小,脏块就可以在内存里保存更久的时间。实际上OB的设计就是脏块默认不刷盘。那如果机器挂了,会不会丢数据呢?
OB跟ORACLE一样,修改数据块之前会先记录REDO,在事务提交的时候,REDO要先写到磁盘上(REDO同时还会发送往其他两个副本节点,这个先忽略)。有REDO在,就不怕丢数据。此外,增量部分每天还是会落盘一次。在落盘之前,内存中的基线数据和相关的增量数据会在内存里进行一次合并(称Merge),最终以SSTable的格式写回到磁盘。如果说内存里块内部产生碎片,在合并的那一刻,这个碎片空间基本被消弭掉了。所以说OB的数据文件空间碎片很小,不需要做碎片整理。同时OB的这个设计也极大降低了LSM的写放大问题。
当业务压测写OB时,脏块的量也会增长,最终达到增量内存限制,这时候业务就无法写入,需要OB做合并释放内存。OB的合并比较耗IO、CPU(有参数可以控制合并力度),并且也不会等到内存用尽才合并,实际会设置一个阈值。同时为了规避合并,设计了一个转储机制。当增量内存使用率超过阈值后,就开启转储。转储就是直接把增量内存写到磁盘上(不合并)。转储对性能的影响很小,可以高峰期发生,并且可以转储多次(参数配置)。
OB增量内存就类似一个水池,业务写是进水管在放水, 转储和大合并是出水管。水位就是当前增量内存使用率。当进水的速度快于出水,池子可能就会满。这时候业务写入就会报内存不足的错误。
这就是OB读写的特点,解决方法就是加大OB内存、或者允许OB自动对业务写入速度限流。
OceanBase部署建议
OB 在commit的时候redo落盘会写磁盘。读数据的时候内存未命中的时候会有物理读,转储和大合并的时候落盘会有密集型写IO。这些都依赖磁盘读写性能。所以建议磁盘都是SSD盘,并且建议日志盘和数据盘使用独立的文件系统。如果是NVME接口的闪存卡或者大容量SSD盘,那日志盘和数据盘放在一起也可以。不要使用LVM对NVME接口的大容量SSD做划分,那样瓶颈可能会在LVM自身。
OB的增量通常都在内存里,内存不足的时候会有转储,可以转储多次。尽管如此,建议测试机器的内存不要太小,防止频繁的增量转储。通常建议192G内存以上。
OB集群的节点数至少要有三个。如果是功能了解,在单机上起3个OB进程模拟三节点是可以的,但是如果是性能测试,那建议还是使用三台同等规格的物理机比较合适。机器规格不一致时,最小能力的机器可能会制约整个集群的性能。 OceanBase集群的手动部署请参考《OceanBase数据库实践入门——手动搭建OceanBase集群》。在部署好OceanBase之后,建议先简单了解一下OceanBase的使用方法,详情请参考文章《OceanBase数据库实践入门——常用操作SQL》。
如果要验证OB的弹性缩容、水平扩展能力,建议至少要6节点(部署形态2-2-2)。并且测试租户(实例)的每个Zone里的资源单元数量至少也要为2个,才可以发挥多机能力。这是因为OB是多租户设计,对资源的管理比较类似云数据库思想,所以里面设计有点精妙,详情请参见《揭秘OceanBase的弹性伸缩和负载均衡原理》。
下面是一个租户的测试租户资源初始化建议 登录sys租户 create resource unit S1, max_cpu=2, max_memory='10G', min_memory='10G', max_iops=10000, min_iops=1000, max_session_num=1000000, max_disk_size=536870912; create resource unit S2, max_cpu=4, max_memory='20G', min_memory='20G', max_iops=20000, min_iops=5000, max_session_num=1000000, max_disk_size=1073741824; create resource unit S3, max_cpu=8, max_memory='40G', min_memory='40G', max_iops=50000, min_iops=10000, max_session_num=1000000, max_disk_size=2147483648; select * from __all_unit_config; create resource pool pool_demo unit = 'S2', unit_num = 2; select * from __all_resource_pool order by resource_pool_id desc ; create tenant t_obdemo resource_pool_list=('pool_demo'); alter tenant t_obdemo set variables ob_tcp_invited_nodes='%';
请注意上面的unit_num=2这个很关键。如果unit_num=1,OB会认为这个租户是个小租户,后面负载均衡处理时会有个默认规则。 登录业务租户 create database sbtest; grant all privileges on sbtest.* to sbuser@'%' identified by 'sbtest';
sysbench压测建议
因为测试场景跟业务有关,这里就以常见的sysbench场景举例分析
sysbench工具可以建几个结构相同的表,然后执行纯读、纯写、读写混合。其中读又分根据主键或者二级索引查询,等值查询、IN查询或范围查询几种。详细的可以查看官方介绍。
如果用sysbench压测OB,创建很多表是一种方法。另外一种方法就是创建分区表。OceanBase是分布式数据库,数据迁移和高可用的最小粒度是分区,分区是数据表的子集。分区表有多个分区,非分区表只有一个分区。分区表的拆分细节是业务可以定义的。OceanBase可以将多个分区分布到不同节点,也可能不会分布到多个节点。这个取决于OB集群和租户的规划设计。所以对sysbench创建的表(分区),在性能分析时要分析分区具体的位置。
建表准备
下面是sysbench里分区表示例,修改 oltp_common.lua: query = string.format([[ CREATE TABLE sbtest%d( id %s, k INTEGER DEFAULT '0' NOT NULL, c CHAR(120) DEFAULT '' NOT NULL, pad CHAR(60) DEFAULT '' NOT NULL, %s (id,k) ) partition by hash(k) partitions %s %s %s]], table_num, id_def, id_index_def, part_num, engine_def, extra_table_options)
需要注意用分区表后,主键列和唯一索引列需要包含分区键。分区表的索引有本地(LOCAL)索引和全局索引两种。本地索引的存储跟数据是在一起的,全局索引的存储是独立的。全局索引是为了应对查询条件不是分区键的场景,没有全局索引时,会扫描所有分区的本地索引。这两种索引的性能优劣没有定论,以实际业务场景测试为准。此外,传统数据库索引对修改操作会有负面影响,分布式数据库的全局索引对修改操作的影响可能更大,因为一个简单的DML语句都会因为要同步修改全局索引而产生分布式事务。而本地索引就没有分布式事务问题。所以对全局索引的使用场景要谨慎评估。这个特性不是OB特有,只要是分布数据库都会面临这个问题。
数据分布均衡
本节是说明OB数据分布背后的原理和方法。
OceanBase是分布式数据库,它通过每个机器上的observer进程将多个机器的资源能力聚合成一个大的资源池,然后再为每个业务分配不同资源规格的租户(实例)。所以每个业务租户(实例)的资源能力都只是整个集群能力的子集。业务并不一定能使用到全部机器资源。
OceanBase里的数据都有三份,细到每个分区有三个副本,角色上有1个leader副本2个follower副本。默认只有leader副本提供读写服务,leader副本所在的节点才有可能提供服务,有负载。OceanBase调整各个节点负载的方法是通过调整内部leader副本的位置实现的。这个调整可以让OceanBase自动做,也可以手动控制。
OceanBase自动负载均衡是参数enable_rebalance控制,默认值为True。可以查看确认。修改用alter system语句。 alter system set enable_rebalance=True; show parameters like 'enable_rebalance';
查看实际表的分区leader副本位置 ##查看分区分布 SELECT t5.tenant_id,t5.tenant_name,t3.database_name, t4.tablegroup_name, t2.table_name, t1.partition_id, concat(t1.svr_ip,':',t1.svr_port) observer, t1.role, t1.data_size,t1.row_count from `gv$partition` t1 join `__all_table` t2 on (t1.tenant_id=t2.tenant_id and t1.table_id=t2.table_id) join `__all_database` t3 on (t2.tenant_id=t3.tenant_id and t2.database_id=t3.database_id) left join `__all_tablegroup` t4 on (t1.tenant_id=t4.tenant_id and t1.tablegroup_id=t4.tablegroup_id) join `__all_tenant` t5 on (t1.tenant_id=t5.tenant_id) where t5.tenant_id = 1020 and role=1 and database_name in ('sysbenchtest') order by t5.tenant_id,t3.database_id,t1.tablegroup_id,t1.partition_id, t1.role;
sysbench命令参数
sysbench的机制是压测过程中如果有错误就会报错退出,所以需要针对一些常见的错误进行忽略处理,这样sysbench会话可以重试继续运行。比如说主键或者唯一键冲突、事务被杀等等。
下面的运行命令供参考 初始化 ./sysbench --test=./oltp_read_only.lua --mysql-host=***.***.82.173 --mysql-port=4001 --mysql-db=test --mysql-user="sbuser" --mysql-password=sbtest --tables=16 --table_size=100000000 --threads=32 --time=300 --report-interval=5 --db-driver=mysql --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016 prepare 纯读 ./sysbench --test=./oltp_read_only.lua --mysql-host=***.***.82.173 --mysql-port=4001 --mysql-db=test --mysql-user="sbuser" --mysql-password=sbtest --tables=16 --table_size=100000000 --threads=96 --time=600 --db-driver=mysql --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016 --secondary=on run 纯写 ./sysbench --test=./oltp_write_only.lua --mysql-host=***.***.82.173 --mysql-port=4001 --mysql-db=test --mysql-user="sbuser" --mysql-password=sbtest --tables=16 --table_size=100000000 --threads=32 --time=600 --report-interval=5 --db-driver=mysql --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016,1062 run 读写混合 ./sysbench --test=./oltp_read_write.lua --mysql-host=***.***.82.173 --mysql-port=4001 --mysql-db=test --mysql-user="sbuser" --mysql-password=sbtest --tables=16 --table_size=100000000 --threads=32 --time=600 --report-interval=5 --db-driver=mysql --db-ps-mode=disable --skip-trx=on --mysql-ignore-errors=6002,6004,4012,2013,4016,1062 run
测试观察
在纯写或者读写测试中,注意观察增量增量内存使用进度。如果写入速度比转储和合并速度还快,那会碰到内存不足写入失败错误。这就是OB租户资源相对不足了。观察这个内存使用进度可以通过 dooba脚本。dooba脚本默认在/home/admin/oceanbase/bin/目录下。
使用示例如下:
python dooba -h11. .84.84 -uroot@sys#obdemo -P2883 -p * *
OB有个内部视图gv$sql_audit可以查看执行过所有成功或失败的SQL,用来分析具体的SQL性能。用法详情参见官网(oceanbase.alipay.com) 或 文章《阿里数据库性能诊断的利器——SQL全量日志》。 select /*+ read_consistency(weak) query_timeout(1000000000) */ usec_to_time(request_time) req_time, svr_Ip, trace_id, sid, client_ip, tenant_id,tenant_name,user_name,db_name, query_sql, affected_rows,ret_code, event, state, elapsed_time, execute_time, queue_time, decode_time, get_plan_time, block_cache_hit, bloom_filter_cache_Hit, block_index_cache_hit, disk_reads,retry_cnt,table_scan, memstore_read_row_count, ssstore_read_row_count, round(request_memory_used/1024/1024) req_mem_mb from gv$sql_audit where tenant_id=1012 and user_name in ('demouser') order by request_time desc limit 100;
经验总结
本节是一些测试场景的经验总结,需要提前了解一些OceanBase的原理特性介绍。
实际测试情形可能会出现由于是三节点部署,所以测试时压力都打到一台服务器上,这个建议用六节点测试。还有个办法就是禁用自动负载均衡手动调整分区位置。调整是OB给业务的手段,业务上有些表会有JOIN,为了性能(避免跨节点请求),业务需要这种干预能力。
还有一种情形是写入压力非常大,跑了一段时间后报内存不足的提示,这个就是租户内存资源相对写入速度和量不足了(OB的转储和合并对内存的回收赶不上写入对内存的消耗),此时需要扩容或者调整测试需求。OB 2.x版本还有自动对应用写入限速功能(自我保护),这个会影响测试报告里性能结果。如果数据库分析sql性能以及分布式调优都做了,那可以认为当前写入的TPS就是数据库的写入峰值了。需要注意的是不同的硬件,不同的租户规格,不同的测试场景,这个TPS能力都表现不同。
sysbench有个batch insert功能,当表是分区表的时候,默认这个batch insert 很可能会产生分布式事务,性能比单表写入要慢。需要靠提高客户端并发数来提升总的吞吐量。此外这个批量的大小不宜太大。由于OB支持SQL执行计划缓存,SQL文本过大且并发很高时,会在SQL解析环节面临内存不足问题。数据库内存的大部分还是主要用于存取数据。JAVA的addBatch方法也同理,建议批量大小设置为100以内。
在查询验证数据的时候,可能会碰到超时类错误。OB里超时的可能场景有多个: SQL语句超时,由租户变量 ob_query_timeout控制。单位是微秒,默认是10秒。 事务空闲超时,由租户变量 ob_trx_idle_timeout控制。单位是微秒,默认120秒。 事务超时,由租户变量 ob_trx_timeout控制。单位是微秒,默认100秒。
以上超时时间都可以在租户里根据实际情况修改。
关于OB的错误号解释可以查看官网系统错误码 ( https://oceanbase.alipay.com/docs/oceanbase/%E5%8F%82%E8%80%83%E7%B1%BB/%E7%B3%BB%E7%BB%9F%E9%94%99%E8%AF%AF%E7%A0%81/tslkmg)
其他
关于使用分布式数据库测试,不同的做法可以得到不同的结果。即使是同一个OB租户(实例),不同的人设计的表结构不同,或者SQL不同都有可能取得不同的性能数据。这些不同都是可以从原理上给出解释。熟知这些原理的更容易发挥OB的分布式数据库特点。使用越深入会发现这个原理越多且更加有趣。当然环境、场景的不同,还是会遇到一些问题。如果有碰到问题,欢迎公众号留言讨论。
推荐阅读
OceanBase数据库实践入门——常用操作SQL
OceanBase分区表有什么不同?
阿里数据库性能诊断的利器——SQL全量日志
揭秘OceanBase的弹性伸缩和负载均衡原理
作者:mq4096
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-06-19 12:56:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
执行这个语句解决,execute dbms_stats.delete_schema_stats('crpower');
数据库
2019-06-19 09:10:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
MySQL性能管理及架构设计(一):什么影响了数据库查询速度、什么影响了MySQL性能

MySQL性能管理及架构设计(二):数据库结构优化、高可用架构设计、数据库索引优化

MySQL性能管理及架构设计(三):SQL查询优化、分库分表 - 完结篇
数据库
2019-06-18 17:31:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
生产环境Oracle RAC扩表空间全记录
loong576 关注0人评论 16863人阅读 2018-05-22 09:40:33

最近zabbix告警rac库表空间使用率超过75%需要扩容,本文记录了变更操作。

1.表空间查看 set pages 999 set linesize 999 SELECT a.tablespace_name "表空间名称" , 100-ROUND (( NVL ( b.bytes_free,0 ) /a.bytes_alloc ) *100,2 ) "占用率(%)" , ROUND ( a.bytes_alloc/1024/1024,2 ) "容量(M)" , ROUND ( NVL ( b.bytes_free,0 ) /1024/1024,2 ) "空闲(M)" , ROUND (( a.bytes_alloc - NVL ( b.bytes_free , 0 )) /1024/1024,2 ) "使用(M)" , TO_CHAR ( SYSDATE, 'yyyy-mm-dd hh24:mi:ss' ) "采样时间" FROM ( SELECT f.tablespace_name, SUM ( f.bytes ) bytes_alloc, SUM ( DECODE ( f.autoextensible, 'YES' ,f.maxbytes, 'NO' ,f.bytes )) maxbytes FROM dba_data_files f GROUP BY tablespace_name ) a, ( SELECT f.tablespace_name, SUM ( f.bytes ) bytes_free FROM dba_free_space f GROUP BY tablespace_name ) b WHERE a.tablespace_name = b.tablespace_name ;

发现表空间BMSBAK使用率为75.67%,超阀值

2.asm查看 SQL > select name,total_mb, free_mb from v $asm_diskgroup ; NAME TOTAL_MB FREE_MB ------------------------------ ---------- ---------- ORADATA 1988096 1154582

rac的库在asm上。
查看剩余可扩空间,发现剩余空间有1T多。

3.查看数据文件 SQL > select a.tablespace_name,a.FILE_NAME,bytes/1024/1024 || 'M' "size" ,a.AUTOEXTENSIBLE,a.MAXBYTES,a.INCREMENT_BY from dba_data_files a order by a.FILE_NAME ;

发现BMSBAK表空间数据文件有8个,全部为自动扩展,大小由5120M——16384M不等,自动扩展的值由1——16384不等(这两处显得很不专业,数据文件大小和扩展值最好保持一致,不要太随意)。
加一个数据文件(16384M)后使用率预期为64%,符合要求。

4.确定扩展大小 SQL > show parameter db_block ; NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ db_block_buffers integer 0 db_block_checking string FALSE db_block_checksum string TRUE db_block_size integer 8192

扩展大小:8192*16384/1024/1024M=128M(db_block_size*INCREMENT_BY,块大小*块数=自动扩展的大小)

5.扩展表空间 SQL > alter tablespace BMSBAK add datafile '+ORADATA/callcent/datafile/bmsbak09.dbf' SIZE 16384M AUTOEXTEND ON NEXT 128M ;


6.确认扩展后表空间使用率
发现使用率下降至64.62%。

至此扩表空间变更完成。
数据库
2019-06-18 15:01:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
SELECT * FROM mysql.user;
SELECT USER();
SELECT CURRENT_USER();
一、创建登录用户
语法:
CREATE USER '登录用户名'@'客户端的IP地址' IDENTIFIED BY '密码';
1、创建一个登录账户,没有任何授权
CREATE USER 'ksfhaha'@'127.0.0.1' IDENTIFIED BY '123456'; #此用户只有查看test数据库的权限
DROP USER 'ksfhaha'@'%'; #删除一个用户
DROP USER 'ksfroot'@'192.168.23.%'; #删除一个用户
二、授权
创建一个用户,同时授予在wlw数据库里所有表的select权限。即授权的同时生成登录账户
注意:ksfxixi账号登录到系统中,只能使用select语句,其他的语句都是没有权限的
GRANT SELECT ON wlw.* TO 'ksfxixi'@'客户端的IP地址' IDENTIFIED BY '123456';
注意:ksfdidi登录到系统中,只能使用wlw数据库,只能使用 SELECT CREATE INSERT DELETE 这几个指令,其他语句是没有权限的。
GRANT SELECT,CREATE,INSERT,DELETE ON wlw.* TO 'ksfdidi'@'客户端的IP地址' IDENTIFIED BY '123456';
授予全部权限给ksfdidi
GRANT ALL ON *.* TO 'ksfdidi'@'客户端的IP地址' IDENTIFIED BY '123456';
授予ksfdidi所有权限,同时可以用ksfdidi创建用户,给新创建的用户授权,这个账户相当于root
GRANT ALL WITH GRANT OPTION ON *.* TO 'ksfdidi'@'客户端的IP地址' IDENTIFIED BY '123456';
授予一个网段的计算机都可以登录到服务器上来
GRANT ALL ON *.* TO 'ksfroot'@'192.168.23.%' IDENTIFIED BY '123456';
FLUSH PRIVILEGES; 每次授权完毕,都要进行一次权限刷新
取消权限
REVOKE delete,insert,update ON wlw.* FROM 'ksfdidi'@'客户端IP地址';
REVOKE ALL PRIVILEGES,GRANT OPTION FROM 'ksfdidi'@'%';
REVOKE ALL PRIVILEGES,GRANT OPTION FROM 'ksfdidi'@'192.168.23.%';
SELECT * FROM mysql.user; #查看用户表
#显示授权情况
SHOW GRANTS;
#显示各种权限情况
SHOW PRIVILEGES;
康庄的教师机的IP地址:
192.168.23.196
康庄的教师机的MySQL用户账号、密码
ksfroot 123456
给一台数据库客户端授权
GRANT ALL ON *.* TO 'ksfroot'@'192.168.23.90' IDENTIFIED BY '123456';
给一个网段的所有计算机授权
GRANT ALL ON *.* TO 'ksfroot'@'192.168.23.%' IDENTIFIED BY '123456';
数据库
2019-06-18 13:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
在MySQL或MariaDB中,任意时间对数据库所做的修改,都会被记录到日志文件中。例如,当你添加了一个新的表,或者更新了一条数据,这些事件都会被存储到二进制日志文件中。二进制日志文件在MySQL主从复合中是非常有用的,主服务器会发送其数据到远程服务器中。
当你需要恢复MySQL时,也会需要使用到二进制日志文件。
mysqlbinlog 命令,以用户可视的方式展示出二进制日志中的内容。同时,也可以将其中的内容读取出来,供其他MySQL实用程序使用。
在此示例中,我们将会涉及以下内容: 获取当前二进制日志列表 mysqlbinlog默认行为 获取特定数据库条目 禁止恢复过程产生日志 在输出中控制base-64 BINLOG mysqlbinlog输出调试信息 跳过前N个条目 保存输出到文件 从一个特定位置提取条目 将条目截止到一个特定的位置 刷新日志以清除Binlog输出 在输出中只显示语句 查看特定开始时间的条目 查看特定结束时间的条目 从远程服务器获取二进制日志
1 获取当前二进制日志列表
在mysql中执行以下命令,即可查看二进制日志文件的列表。

1
2
3
4
5
6
7
8
mysql > SHOW BINARY LOGS ; + -- -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- + | Log_name | File_size | + -- -- -- -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- + | mysqld - bin . 000001 | 15740 | | mysqld - bin . 000002 | 3319 | . . . .

如果熊没有开启此功能,则会显示:

1
2
mysql > SHOW BINARY LOGS ; ERROR 1381 ( HY000 ) : You are not using binary logging

二进制日志文件默认会存放在 /var/lib/mysql 目录下

1
2
3
4
5
$ ls - l / var / lib / mysql / - rw - rw -- -- . 1 mysql mysql 15740 Aug 28 14 : 57 mysqld - bin . 000001 - rw - rw -- -- . 1 mysql mysql 3319 Aug 28 14 : 57 mysqld - bin . 000002 . . . .

2 mysqlbinlog 默认行为
下面将以一种用户友好的格式显示指定的二进制日志文件(例如:mysqld.000001)的内容。

1
$ mysqlbinlog mysqld - bin . 000001

mysqlbinlog默认会显示为以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*!40019 SET @@session.max_insert_delayed_threads=0*/ ; /*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/ ; DELIMITER /*!*/ ; # at 4#170726 14:57:37 server id 1 end_log_pos 106 Start: binlog v 4, server v 5.1.73-log created 170726 14:57:37 at startup# Warning: this binlog is either in use or was not closed properly. ROLLBACK /*!*/ ; BINLOG ' IeZ4WQ8BAAAAZgAAAGoAAAABAAQANS4xLjczLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAh5nhZEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC ' /*!*/ ; # at 106 #170726 14:59:31 server id 1 end_log_pos 182 Query thread_id=2 exec_time=0 error_code=0 SET TIMESTAMP = 1501095571 /*!*/ ; SET @ @ session . pseudo_thread_id = 2 /*!*/ ; SET @ @ session . foreign_key_checks = 1 , @ @ session . sql_auto_is_null = 1 , @ @ session . unique_checks = 1 , @ @ session . autocommit = 1 /*!*/ ; SET @ @ session . sql_mode = 0 /*!*/ ; SET @ @ session . auto_increment_increment = 1 , @ @ session . auto_increment_offset = 1 /*!*/ ; /*!\C latin1 */ /*!*/ ; SET @ @ session . character_set_client = 8 , @ @ session . collation_connection = 8 , @ @ session . collation_server = 8 /*!*/ ; . . . . . . # at 14191 #170726 15:20:38 server id 1 end_log_pos 14311 Query thread_id=4 exec_time=0 error_code=0SET TIMESTAMP=1501096838/*!*/; insert into salary ( name , dept ) values ( 'Ritu' , 'Accounting' ) /*!*/ ; DELIMITER ; # End of log file ROLLBACK /* added by mysqlbinlog */ ; / * ! 50003 SET COMPLETION_TYPE = @ OLD_COMPLETION_TYPE*

上面的命令将会显示出,在该系统上数据库发生的所有改变事件。
3 获取特定数据库条目
默认情况下,mysqlbinlog会显示所有的内容,太过于杂乱。使用 -d 选项,可以指定一个数据库名称,将只显示在该数据库上所发生的事件。


1
$ mysqlbinlog - d crm mysqld - bin . 000001 > crm - events . txt

也可以使用 --database 命令,效果相同。

1
$ mysqlbinlog - database crm mysqld - bin . 000001 > crm - events . txt

4 禁止恢复过程产生日志
在使用二进制日志文件进行数据库恢复时,该过程中也会产生日志文件,就会进入一个循环状态,继续恢复该过程中的数据。因此,当使用mysqlbinlog命令时,要禁用二进制日志,请使用下面所示的-D选项:

1
$ mysqlbinlog - D mysqld - bin . 000001

也可以使用 --disable-log-bin 命令,效果相同。

1
$ mysqlbinlog -- disable - log - bin mysqld - bin . 000001

备注:在输出中,当指定-D选项时,将看到输出中的第二行。也就是SQL_LOG_BIN=0

1
2
3
/*!40019 SET @@session.max_insert_delayed_threads=0*/ ; /*!32316 SET @OLD_SQL_LOG_BIN=@@SQL_LOG_BIN, SQL_LOG_BIN=0*/ ; /*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/ ;

当使用-to-last-log选项时,这个选项也会有所帮助。另外,请记住,该命令需要root权限来执行。
5 在输出中控制base-64 BINLOG
使用base64-output选项,可以控制输出语句何时是输出base64编码的BINLOG语句。以下是base64输出设置的可能值:
never always decode-rows auto(默认)
never:当指定如下所示的“never”时,它将在输出中显示base64编码的BINLOG语句。

1
$ mysqlbinlog -- base64 - output = never mysqld - bin . 000001

将不会有任何与下面类似的行,它具有base64编码的BINLOG。

1
BINLOG ' IeZ4WQ8BAAAAZgAAABAAQANS4xLjczLWxvZwAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAh5nhZEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC

always:当指定“always”选项时,只要有可能,它将只显示BINLOG项。因此,只有在专门调试一些问题时才使用它。

1
$ mysqlbinlog -- base64 - output = always mysqld - bin . 000001

下面是“always”的输出,它只显示了BINLOG项。

1
2
3
4
5
6
7
8
9
10
BINLOG ' IeZ4WQ8BAAAAZgAAAGoAAAABAAQANS4xLjczLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAh5nhZEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC ' /*!*/ ; # at 106 #170726 14:59:31 server id 1 end_log_pos 182 BINLOG ' k+Z4WQIBAAAATAAAALYAAAAIAAIAAAAAAAAADAAAGgAAAEAAAAEAAAAAAAAAAAYDc3RkBAgACAAI AHRoZWdlZWtzdHVmZgBCRUdJTg== ' /*!*/ ; # at 182 #170726 14:59:30 server id 1 end_log_pos 291 BINLOG ' kuZ4WQIBAAAAbQAAACMBAAAAAAIAAAAAAAAADAAAGgAAAEAAAAEAAAAAAAAAAAYDc3RkBAgACAAI AHRoZWdlZWtzdHVmZgBJTlNFUlQgSU5UTyB0IFZBTFVFUygxLCAnYXBwbGUnLCBOVUxMKQ== ' /*!*/ ; # at 291 #170726 14:59:30 server id 1 end_log_pos 422 BINLOG ' kuZ4WQIBAAAAgwAAAKYBAAAAAAIAAAAAAAAADAAAGgAAAEAAAAEAAAAAAAAAAAYDc3RkBAgACAAI AHRoZWdlZWtzdHVmZgBVUERBVEUgdCBTRVQgbmFtZSA9ICdwZWFyJywgZGF0ZSA9ICcyMDA5LTAx LTAxJyBXSEVSRSBpZCA9IDE =

decode-rows:这个选项将把基于行的事件解码成一个SQL语句,特别是当指定-verbose选项时,如下所示。

1
$ mysqlbinlog -- base64 - output = decode - rows -- verbose mysqld - bin . 000001

auto:这是默认选项。当没有指定任何base64解码选项时,它将使用auto。在这种情况下,mysqlbinlog将仅为某些事件类型打印BINLOG项,例如基于行的事件和格式描述事件。

1
2
$ mysqlbinlog -- base64 - output = auto mysqld - bin . 000001 $ mysqlbinlog mysqld - bin . 000001

6 mysqlbinlog输出调试信息
下面的调试选项,在完成处理给定的二进制日志文件之后,将检查文件打开和内存使用。

1
$ mysqlbinlog -- debug - check mysqld - bin . 000001

如下所示,在完成处理给定的二进制日志文件之后,下面的调试信息选项将显示额外的调试信息。

1
2
3
4
5
6
$ mysqlbinlog -- debug - info mysqld - bin . 000001 > / tmp / m . di User time 0.00 , System time 0.00 Maximum resident set size 2848 , Integral resident set size 0 Non - physical pagefaults 863 , Physical pagefaults 0 , Swaps 0 Blocks in 0 out 48 , Messages in 0 out 0 , Signals 0 Voluntary context switches 1 , Involuntary context switches 2

7 跳过前N个条目
除了读取整个mysql二进制日志文件外,也可以通过指定偏移量来读取它的特定部分。可以使用 -o 选项。o代表偏移。

下面将跳过指定的mysql bin日志中的前10个条目。

1
$ mysqlbinlog - o 10 mysqld - bin . 000001

为了确保它正常工作,给偏移量提供一个巨大的数字,将看不到任何条目。下面的内容将从日志中跳过10,000个条目(事件)。

1
2
3
4
5
6
$ mysqlbinlog - o 10000 mysqld - bin . 000001 /*!40019 SET @@session.max_insert_delayed_threads=0*/ ; /*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/ ; . . . . # End of log file ROLLBACK /* added by mysqlbinlog */ ; /*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/ ;

在本例中,由于这个特定的日志文件没有10,000个条目,所以在输出中没有显示任何数据库事件。
8 保存输出到文件
也可以使用简单的 Linux 重定向命令,将输出存储到一个文件中,如下所示。

1
$ mysqlbinlog mysqld - bin . 000001 > output . log

或者也可以使用 -r (结果文件)选项,如下所示,将输出存储到一个文件中。

1
$ mysqlbinlog - r output . log mysqld - bin . 000001

备注:还可以使用 -server-id 指定mysql服务器,确保是由给定服务器id的mysql服务器所生成的日志。

1
$ mysqlbinlog -- server - id = 1 - r output . log mysqld - bin . 000001

9 从一个特定位置提取条目
通常在mysql二进制日志文件中,你将看到如下所示的位置号。下面是mysqlbinlog的部分输出,你可以看到“15028”是一个位置编号。

1
2
3
4
5
6
7
8
#170726 15:38:14 server id 1 end_log_pos 15028 Query thread_id=5 exec_time=0 error_code=0 SET TIMESTAMP = 1501097894 /*!*/ ; insert into salary values ( 400 , 'Nisha' , 'Marketing' , 9500 ) /*!*/ ; # at 15028 #170726 15:38:14 server id 1 end_log_pos 15146 Query thread_id=5 exec_time=0 error_code=0 SET TIMESTAMP = 1501097894 /*!*/ ; insert into salary values ( 500 , 'Randy' , 'Technology' , 6000 )

下面的命令将从位置编号为15028的二进制日志条目处开始读取。

1
$ mysqlbinlog - j 15028 mysqld - bin . 000001 > from - 15028.out

当在命令行中指定多个二进制日志文件时,开始位置选项将仅应用于给定列表中的第一个二进制日志文件。还可以使用 -H 选项来获得给定的二进制日志文件的十六进制转储,如下所示。


1
$ mysqlbinlog - H mysqld - bin . 000001 > binlog - hex - dump . out

10 将条目截止到一个特定的位置
就像前面的例子一样,你也可以从mysql二进制日志中截止到一个特定位置的条目,如下所示。

1
$ mysqlbinlog -- stop - position = 15028 mysqld - bin . 000001 > upto - 15028.out

上面的示例将在15028的位置上停止binlog。当在命令行中指定多个二进制日志文件时,停止位置将仅应用于给定列表中的最后一个二进制日志文件。
11 刷新日志以清除Binlog输出
当二进制日志文件没有被正确地关闭时,将在输出中看到一个警告消息,如下所示。

1
$ mysqlbinlog mysqld - bin . 000001 > output . out

如下所示,报告中提示binlog文件没有正确地关闭。

1
2
3
4
5
6
7
# head output.log /*!40019 SET @@session.max_insert_delayed_threads=0*/ ; . . . . # Warning: this binlog is either in use or was not closed properly. . . . . . . BINLOG ' IeZ4WQ8BAAAAZgAAAGoAAAABAAQANS4xLjczLWxvZwAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAh5nhZEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC

当看到这个提示时,需要连接到mysql并刷新日志,如下所示。

1
mysql > flush logs ;

刷新日志之后,再次执行mysqlbinlog命令,将不会看到在mysqlbinlog输出中binlog未正确关闭的警告消息。
12 在输出中只显示语句
默认情况下,正如在前面的示例输出中看到的一样,除了SQL语句之外,在mysqlbinlog输出中还会有一些附加信息。如果只想查看常规的SQL语句,而不需要其他内容,那么可以使用 -s 选项,如下所示。

也可以使用 --short-form 选项,效果相同。

1
2
3
$ mysqlbinlog - s mysqld - bin . 000001
$ mysqlbinlog -- short - form mysqld - bin . 000001

下面是上述命令的部分输出。在这里,它将只显示来自给定二进制日志文件的SQL语句。


1
2
3
4
5
6
SET TIMESTAMP = 1501096106 /*!*/ ; insert into employee values ( 400 , 'Nisha' , 'Marketing' , 9500 ) /*!*/ ; SET TIMESTAMP = 1501096106 /*!*/ ; insert into employee values ( 500 , 'Randy' , 'Technology' , 6000 ) . . . .

不会显示像下面这样的条目:


1
2
# at 1201 #170726 15:08:26 server id 1 end_log_pos 1329 Query thread_id=3 exec_time=0 error_code=0

13 查看特定开始时间的条目
下面将只提取从指定时间开始的条目。在此之前的任何条目都将被忽略。


1
$ mysqlbinlog -- start - datetime = "2017-08-16 10:00:00" mysqld - bin . 000001

当你想要从一个二进制文件中提取数据时,这是非常有用的,因为你希望使用它来恢复或重构在某个时间段内发生的某些数据库活动。时间戳的格式可以是MySQL服务器所理解的DATETIME和timestamp中的任何类型。

14 查看特定结束时间的条目
与前面的开始时间示例一样,这里也可以指定结束时间,如下所示。

1
$ mysqlbinlog -- stop - datetime = "2017-08-16 15:00:00" mysqld - bin . 000001

上面的命令将读取到给定结束时间的条目。任何来自于超过给定结束时间的mysql二进制日志文件的条目都不会被处理。
15 从远程服务器获取二进制日志
在本地机器上,还可以读取位于远程服务器上的mysql二进制日志文件。为此,需要指定远程服务器的ip地址、用户名和密码,如下所示。

此处使用-R选项。-R选项与-read-from-remote-server相同。

1
$ mysqlbinlog - R - h 192.168.101.2 - p mysqld - bin . 000001

在上面命令中:
-R 选项指示mysqlbinlog命令从远程服务器读取日志文件 -h 指定远程服务器的ip地址 -p 将提示输入密码。默认情况下,它将使用“root”作为用户名。也可以使用 -u 选项指定用户名。 mysqld-bin.000001 这是在这里读到的远程服务器的二进制日志文件的名称。
下面命令与上面的命令完全相同:

1
$ mysqlbinlog -- read - from - remote - server -- host = 192.168.101.2 - p mysqld - bin . 000001

如果只指定 -h 选项,将会得到下面的错误消息。

1
2
$ mysqlbinlog - h 192.168.101.2 mysqld - bin . 000001 mysqlbinlog : File 'mysqld-bin.000001' not found ( Errcode : 2 )

当你在远程数据库上没有足够的特权时,将得到以下“不允许连接”错误消息。在这种情况下,确保在远程数据库上为本地客户机授予适当的特权。

1
2
3
$ mysqlbinlog - R -- host = 192.168.101.2 mysqld - bin . 000001 ERROR : Failed on connect : Host '216.172.166.27' is not allowed to connect to this MySQL server

如果没有使用 -p 选项指定正确的密码,那么将得到以下“访问拒绝”错误消息。

1
2
$ mysqlbinlog - R -- host = 192.168.101.2 mysqld - bin . 000001 ERROR : Failed on connect : Access denied for user 'root' @ '216.172.166.27' ( using password : YES )

下面的示例显示,还可以使用-u选项指定mysqlbinlog应该用于连接到远程MySQL数据库的用户名。请注意,这个用户是mysql用户(不是Linux服务器用户)。


1
$ mysqlbinlog - R -- host = 192.168.101.2 - u root - p mysqld - bin . 000001 < span style = "text-indent: 2em;
数据库
2019-06-18 11:51:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果我们要去一本书中翻看某部分的内容,最简单的方法就是先翻到目录也就是“索引”部分,找到对应的页码,数据库也是如此。
本文以mysql为例来表述InnoDB

索引基础
在mysql中存储引擎先使用索引找到对应的位置,然后根据匹配到的索引记录找到对应的行数。
索引可以包含一个或者多个列的值。对于包含多个列的索引,索引的顺序十分重要,mysql只能高效的使用索引最左前缀的列。

Mysql支持的索引类型
mysql中普遍使用B+Tree做索引,但在实现上又根据聚簇索引和非聚簇索引而不同。
聚簇索引
所谓聚簇索引,就是指主索引文件和数据文件为同一份文件,聚簇索引主要用在Innodb存储引擎中。在该索引实现方式中B+Tree的叶子节点上的data就是数据本身,key为主键,如果是一般索引的话,data便会指向对应的主索引:
在B+Tree的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的B+Tree。做这个优化的目的是为了提高区间访问的性能,当找到起始节点后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。
非聚簇索
非聚簇索引就是指B+Tree的叶子节点上的data,并不是数据本身,而是数据存放的地址。主索引和辅助索引没啥区别,只是主索引中的key一定得是唯一的。主要用在MyISAM存储引擎中。 B-Tree:如果一次检索需要访问4个节点,数据库系统设计者利用磁盘预读原理,把节点的大小设计为一个页,那读取一个节点只需要一次I/O操作,完成这次检索操作,最多需要3次I/O(根节点常驻内存)。数据记录越小,每个节点存放的数据就越多,树的高度也就越小,I/O操作就少了,检索效率也就上去了。 B+Tree:非叶子节点只存key,大大滴减少了非叶子节点的大小,那么每个节点就可以存放更多的记录,树更矮了,I/O操作更少了。所以B+Tree拥有更好的性能。

创建一个包含两个列的索引和创建两个只包含一个列的索引的区别

数据库
2019-06-18 10:54:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
作者:黄佳豪
TiDB Binlog 组件用于收集 TiDB 的 binlog,并准实时同步给下游,如 TiDB、MySQL 等。该组件在功能上类似于 MySQL 的主从复制,会收集各个 TiDB 实例产生的 binlog,并按事务提交的时间排序,全局有序的将数据同步至下游。利用 TiDB Binlog 可以实现数据准实时同步到其他数据库,以及 TiDB 数据准实时的备份与恢复。随着大家使用的广泛和深入,我们遇到了不少由于对 TiDB Binlog 原理不理解而错误使用的情况,也发现了一些 TiDB Binlog 支持并不完善的场景和可以改进的设计。
在这样的背景下,我们开展 TiDB Binlog 源码阅读分享活动,通过对 TiDB Binlog 代码的分析和设计原理的解读,帮助大家理解 TiDB Binlog 的实现原理,和大家进行更深入的交流,同时也有助于社区参与 TiDB Binlog 的设计、开发和测试。
背景知识
本系列文章会聚焦 TiDB Binlog 本身,读者需要有一些基本的知识,包括但不限于: Go 语言,TiDB Binlog 由 Go 语言实现,有一定的 Go 语言基础有助于快速理解代码。 数据库基础知识,包括 MySQL、TiDB 的功能、配置和使用等;了解基本的 DDL、DML 语句和事务的基本常识。 了解 Kafka 的基本原理。 基本的后端服务知识,比如后台服务进程管理、RPC 工作原理等。
总体而言,读者需要有一定 MySQL/TiDB/Kafka 的使用经验,以及可以读懂 Go 语言程序。在阅读 TiDB Binlog 源码之前,可以先从阅读 《TiDB Binlog 架构演进与实现原理》 入手。
内容概要
本篇作为《TiDB Binlog 源码阅读系列文章》的序篇,会简单的给大家讲一下后续会讲哪些部分以及逻辑顺序,方便大家对本系列文章有整体的了解。 初识 TiDB Binlog 源码:整体介绍一下 TiDB Binlog 以及源码,包括 TiDB Binlog 主要有哪些组件与模块,以及如何在本地利用集成测试框架快速启动一个集群,方便大家体验 Binlog 同步功能与后续可能修改代码的测试。 pump client 介绍:介绍 pump client 同时让大家了解 TiDB 是如何生成 binlog 的。 pump server 介绍:介绍 pump 启动的主要流程,包括状态维护,定时触发 gc 与生成 fake binlog 驱动下游。 pump storage 模块:storage 是 pump 的主要模块,主要负载 binlog 的存储,读取与排序, 可能分多篇讲解。 drainer server 介绍:drainer 启动的主要流程,包括状态维护,如何获取全局 binlog 数据以及 Schema 信息。 drainer loader package 介绍:loader packge 是负责实时同步数据到 mysql 的模块,在 TiDB Binlog 里多处用到。 drainer sync 模块介绍:以同步 mysql 为例介绍 drainer 是如何同步到不同下游系统。 slave binlog 介绍:介绍 drainer 如何转换与输出 binlog 数据到 Kafka。 arbiter 介绍:同步 Kafka 中的数据到下游,通过了解 arbiter,大家可以了解如何同步数据到其他下游系统,比如更新 Cache,全文索引系统等。 reparo 介绍:通过了解 reparo,大家可以将 drainer 的增量备份文件恢复到 TiDB 中。
小结
本篇文章主要介绍了 TiDB Binlog 源码阅读系列文章的目的和规划。下一篇文章我们会从 TiDB Binlog 的整体架构切入,然后分别讲解各个组件和关键设计点。更多的源码内容会在后续文章中逐步展开,敬请期待。
最后欢迎大家参与 TiDB Binlog 的开发。
原文阅读:
TiDB Binlog 组件用于收集 TiDB 的 binlog,并准实时同步给下游,如 TiDB、MySQL 等。该组件在功能上类似于 MySQL 的主从复制,会收集各个 TiDB 实例产生的 binlog,并按事务提交的时间排序,全局有序的将数据同步至下游。利用 TiDB Binlog 可以实现数据准实时同步到其他数据库,以及 TiDB 数据准实时的备份与恢复。随着大家使用的广泛和深入,我们遇到了不少由于对 TiDB Binlog 原理不理解而错误使用的情况,也发现了一些 TiDB Binlog 支持并不完善的场景和可以改进的设计。
在这样的背景下,我们开展 TiDB Binlog 源码阅读分享活动,通过对 TiDB Binlog 代码的分析和设计原理的解读,帮助大家理解 TiDB Binlog 的实现原理,和大家进行更深入的交流,同时也有助于社区参与 TiDB Binlog 的设计、开发和测试。
背景知识
本系列文章会聚焦 TiDB Binlog 本身,读者需要有一些基本的知识,包括但不限于: Go 语言,TiDB Binlog 由 Go 语言实现,有一定的 Go 语言基础有助于快速理解代码。 数据库基础知识,包括 MySQL、TiDB 的功能、配置和使用等;了解基本的 DDL、DML 语句和事务的基本常识。 了解 Kafka 的基本原理。 基本的后端服务知识,比如后台服务进程管理、RPC 工作原理等。
总体而言,读者需要有一定 MySQL/TiDB/Kafka 的使用经验,以及可以读懂 Go 语言程序。在阅读 TiDB Binlog 源码之前,可以先从阅读 《TiDB Binlog 架构演进与实现原理》 入手。
内容概要
本篇作为《TiDB Binlog 源码阅读系列文章》的序篇,会简单的给大家讲一下后续会讲哪些部分以及逻辑顺序,方便大家对本系列文章有整体的了解。 初识 TiDB Binlog 源码:整体介绍一下 TiDB Binlog 以及源码,包括 TiDB Binlog 主要有哪些组件与模块,以及如何在本地利用集成测试框架快速启动一个集群,方便大家体验 Binlog 同步功能与后续可能修改代码的测试。 pump client 介绍:介绍 pump client 同时让大家了解 TiDB 是如何生成 binlog 的。 pump server 介绍:介绍 pump 启动的主要流程,包括状态维护,定时触发 gc 与生成 fake binlog 驱动下游。 pump storage 模块:storage 是 pump 的主要模块,主要负载 binlog 的存储,读取与排序, 可能分多篇讲解。 drainer server 介绍:drainer 启动的主要流程,包括状态维护,如何获取全局 binlog 数据以及 Schema 信息。 drainer loader package 介绍:loader packge 是负责实时同步数据到 mysql 的模块,在 TiDB Binlog 里多处用到。 drainer sync 模块介绍:以同步 mysql 为例介绍 drainer 是如何同步到不同下游系统。 slave binlog 介绍:介绍 drainer 如何转换与输出 binlog 数据到 Kafka。 arbiter 介绍:同步 Kafka 中的数据到下游,通过了解 arbiter,大家可以了解如何同步数据到其他下游系统,比如更新 Cache,全文索引系统等。 reparo 介绍:通过了解 reparo,大家可以将 drainer 的增量备份文件恢复到 TiDB 中。
小结
本篇文章主要介绍了 TiDB Binlog 源码阅读系列文章的目的和规划。下一篇文章我们会从 TiDB Binlog 的整体架构切入,然后分别讲解各个组件和关键设计点。更多的源码内容会在后续文章中逐步展开,敬请期待。
最后欢迎大家参与 TiDB Binlog 的开发。
原文阅读 : https://www.pingcap.com/blog-cn/tidb-binlog-source-code-reading-1/
数据库
2019-06-18 10:06:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
查看mysql当前使用时区
show variables like '%time_zone%';
解决办法
serverTimezone=CTT spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://xxx.xxx.xxx.xxx:3306/shys?serverTimezone=CTT&useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false spring.datasource.username=xxxxxx spring.datasource.password=xxxxxx spring.datasource.initialize=false
排错过程 mysql-connector-java-8.0.13.jar
当 JDBC 与 MySQL 开始建立连接时 com.mysql.cj.jdbc.ConnectionImpl.initializePropsFromServer() throws SQLException { ... this.session.getProtocol().initServerSession(); ... } com.mysql.cj.protocol.a.NativeProtocol.initServerSession() { configureTimezone(); ... } com.mysql.cj.protocol.a.NativeProtocol.configureTimezone() { String configuredTimeZoneOnServer = this.serverSession.getServerVariable("time_zone"); if ("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)) { configuredTimeZoneOnServer = this.serverSession.getServerVariable("system_time_zone"); } String canonicalTimezone = getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue(); if (configuredTimeZoneOnServer != null) { // user can override this with driver properties, so don't detect if that's the case if (canonicalTimezone == null || StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone)) { try { canonicalTimezone = TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer, getExceptionInterceptor()); } catch (IllegalArgumentException iae) { throw ExceptionFactory.createException(WrongArgumentException.class, iae.getMessage(), getExceptionInterceptor()); } } } if (canonicalTimezone != null && canonicalTimezone.length() > 0) { this.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone)); // // The Calendar class has the behavior of mapping unknown timezones to 'GMT' instead of throwing an exception, so we must check for this... // if (!canonicalTimezone.equalsIgnoreCase("GMT") && this.serverSession.getServerTimeZone().getID().equals("GMT")) { throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("Connection.9", new Object[] { canonicalTimezone }), getExceptionInterceptor()); } } this.serverSession.setDefaultTimeZone(this.serverSession.getServerTimeZone()); }
追踪代码可知,当 MySQL 的 time_zone 值为 SYSTEM 时,会取 system_time_zone 值作为协调时区。
重点在这里!若 String configuredTimeZoneOnServer 得到的是 CST 那么 Java 会误以为这是 CST -0500 ,因此 TimeZone.getTimeZone(canonicalTimezone) 会给出错误的时区信息。
如图所示,本机默认时区是 Asia/Shanghai +0800 ,误认为服务器时区为 CST -0500 ,实际上服务器是 CST +0800 。 com.mysql.cj.ClientPreparedQueryBindings.setTimestamp(int parameterIndex, Timestamp x, Calendar targetCalendar, int fractionalLength) { if (x == null) { setNull(parameterIndex); } else { x = (Timestamp) x.clone(); if (!this.session.getServerSession().getCapabilities().serverSupportsFracSecs() || !this.sendFractionalSeconds.getValue() && fractionalLength == 0) { x = TimeUtil.truncateFractionalSeconds(x); } if (fractionalLength < 0) { // default to 6 fractional positions fractionalLength = 6; } x = TimeUtil.adjustTimestampNanosPrecision(x, fractionalLength, !this.session.getServerSession().isServerTruncatesFracSecs()); this.tsdf = TimeUtil.getSimpleDateFormat(this.tsdf, "''yyyy-MM-dd HH:mm:ss", targetCalendar, targetCalendar != null ? null : this.session.getServerSession().getDefaultTimeZone()); StringBuffer buf = new StringBuffer(); buf.append(this.tsdf.format(x)); if (this.session.getServerSession().getCapabilities().serverSupportsFracSecs()) { buf.append('.'); buf.append(TimeUtil.formatNanos(x.getNanos(), 6)); } buf.append('\''); setValue(parameterIndex, buf.toString(), MysqlType.TIMESTAMP); } }
原因
Timestamp 被转换为会话时区的时间字符串了。问题到此已然明晰: JDBC 误认为会话时区在 CST-5 JBDC 把 Timestamp+0 转为 CST-5 的 String-5 MySQL 认为会话时区在 CST+8,将 String-5 转为 Timestamp-13
最终结果相差 13 个小时!如果处在冬令时还会相差 14 个小时!
数据库
2019-06-19 19:38:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
sql:
update T_CONFIG
SET CONFIG_CONTENT = REPLACE(CONFIG_CONTENT,'搜索的字符串','替换后的字符串')
WHERE CONFIG_NAME = 'aa'

参考连接:https://baijiahao.baidu.com/s?id=1632772479127837460&wfr=spider&for=pc
数据库
2019-06-19 19:20:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
备份: mongodump -h 127.0.0.1:27017 --collection log_aliyun_operation --db log --gzip --archive=/home/20190619.archive
恢复: mongorestore -h 127.0.0.1:27017 --gzip --archive=/***/***/Desktop/test.archive
注意:--archive= 你的.archive文件存放位置
数据库
2019-06-19 17:43:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
没有返回值,说明处于专用模式:
SQL> select * from v$dispatcher;
未选定行
SQL> show parameter shared_ser
NAME TYPE VALUE

max_shared_servers integer
shared_server_sessions integer
shared_servers integer 0
在线修改共享服务器进程的数量
SQL> alter system set shared_servers=5 scope=both;
系统已更改。
SQL> show parameter shared_servers
NAME TYPE VALUE

max_shared_servers integer
shared_servers integer 5
SQL>
配置dispatchersDispatchers参数用于配置共享模式架构中dispatcher进程,共享模式只少需要一个dispatcher进程。
SQL> alter system set dispatchers='(PROTOCOL=TCP) (dispatchers=3)' scope=both;
系统已更改。
再次查看
SQL> select * from v$dispatcher;
NAME
----
NETWORK

PADDR STATUS ACC MESSAGES BYTES BREAKS
---
OWNED CREATED IDLE BUSY LISTENER CONF_INDX

D000
(ADDRESS=(PROTOCOL=tcp)(HOST=WIN-G7Q6ORP7VGQ)(PORT=49432))
000007FF22CC3238 WAIT YES 0 0 0
0 0 20107 0 0 0
NAME
----
NETWORK

PADDR STATUS ACC MESSAGES BYTES BREAKS
---
OWNED CREATED IDLE BUSY LISTENER CONF_INDX

D001
(ADDRESS=(PROTOCOL=tcp)(HOST=WIN-G7Q6ORP7VGQ)(PORT=49437))
000007FF22CC97A8 WAIT YES 0 0 0
0 0 532 0 0 0
NAME
----
NETWORK

PADDR STATUS ACC MESSAGES BYTES BREAKS
---
OWNED CREATED IDLE BUSY LISTENER CONF_INDX

D002
(ADDRESS=(PROTOCOL=tcp)(HOST=WIN-G7Q6ORP7VGQ)(PORT=49438))
000007FF22CCA890 WAIT YES 0 0 0
0 0 526 0 0 0
SQL>
数据库
2019-06-13 17:12:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
作者: 李建俊
上一篇《 gRPC Server 的初始化和启动流程 》为大家介绍了 gRPC Server 的初始化和启动流程,本篇将带大家深入到 grpc-rs 这个库里,查看 RPC 请求是如何被封装和派发的,以及它是怎么和 Rust Future 进行结合的。
gRPC C Core
gRPC 包括了一系列复杂的协议和流控机制,如果要为每个语言都实现一遍这些机制和协议,将会是一个很繁重的工作。因此 gRPC 提供了一个统一的库来提供基本的实现,其他语言再基于这个实现进行封装和适配,提供更符合相应语言习惯或生态的接口。这个库就是 gRPC C Core,grpc-rs 就是基于 gRPC C Core 进行封装的。
要说明 grpc-rs 的实现,需要先介绍 gRPC C Core 的运行方式。gRPC C Core 有三个很关键的概念 grpc_channel 、 grpc_completion_queue 、 grpc_call 。 grpc_channel 在 RPC 里就是底层的连接, grpc_completion_queue 就是一个处理完成事件的队列。 grpc_call 代表的是一个 RPC。要进行一次 RPC,首先从 grpc_channel 创建一个 grpc_call,然后再给这个 grpc_call 发送请求,收取响应。而这个过程都是异步,所以需要调用 grpc_completion_queue 的接口去驱动消息处理。整个过程可以通过以下代码来解释(为了让代码更可读一些,以下代码和实际可编译运行的代码有一些出入)。 grpc_completion_queue* queue = grpc_completion_queue_create_for_next(NULL); grpc_channel* ch = grpc_insecure_channel_create("example.com", NULL); grpc_call* call = grpc_channel_create_call(ch, NULL, 0, queue, "say_hello"); grpc_op ops[6]; memset(ops, 0, sizeof(ops)); char* buffer = (char*) malloc(100); ops[0].op = GRPC_OP_SEND_INITIAL_METADATA; ops[1].op = GRPC_OP_SEND_MESSAGE; ops[1].data.send_message.send_message = "gRPC"; ops[2].op = GRPC_OP_SEND_CLOSE_FROM_CLIENT; ops[3].op = GRPC_OP_RECV_INITIAL_METADATA; ops[4].op = GRPC_OP_RECV_MESSAGE; ops[4].data.recv_message.recv_message = buffer; ops[5].op = GRPC_OP_RECV_STATUS_ON_CLIENT; void* tag = malloc(1); grpc_call_start_batch(call, ops, 6, tag); grpc_event ev = grpc_completion_queue_next(queue); ASSERT_EQ(ev.tag, tag); ASSERT(strcmp(buffer, "Hello gRPC"));
可以看到,对 grpc_call 的操作是通过一次 grpc_call_start_batch 来指定的。这个 start batch 会将指定的操作放在内存 buffer 当中,然后通过 grpc_completion_queue_next 来实际执行相关操作,如收发消息。这里需要注意的是 tag 这个变量。当这些操作都完成以后, grpc_completion_queue_next 会返回一个包含 tag 的消息来通知这个操作完成了。所以在代码的末尾就可以在先前指定的 buffer 读出预期的字符串。
由于篇幅有限,对于 gRPC C Core 的解析就不再深入了,对这部分很感兴趣的朋友也可以在 github.com/grpc/grpc 阅读相关文档和源码。
封装与实现细节
通过上文的分析可以明显看到,gRPC C Core 的通知机制其实和 Rust Future 的通知机制非常类似。Rust Future 提供一个 poll 方法来检验当前 Future 是否已经 ready。如果尚未 ready,poll 方法会注册一个通知钩子 task 。等到 ready 时, task 会被调用,从而触发对这个 Future 的再次 poll,获取结果。 task 其实和上文中的 tag 正好对应起来了,而在 grpc-rs 中, tag 就是一个储存了 task 的 enum。 pub enum CallTag { Batch(BatchPromise), Request(RequestCallback), UnaryRequest(UnaryRequestCallback), Abort(Abort), Shutdown(ShutdownPromise), Spawn(SpawnNotify), }
tag 之所以是一个 enum 是因为不同的 call 会对应不同的行为,如对于服务器端接受请求的处理和客户端发起请求的处理就不太一样。
grpc-rs 在初始化时会创建多个线程来不断调用 grpc_completion_queue_next 来获取已经完成的 tag ,然后根据 tag 的类型,将数据存放在结构体中并通知 task 来获取。下面是这个流程的代码。 // event loop fn poll_queue(cq: Arc) { let id = thread::current().id(); let cq = CompletionQueue::new(cq, id); loop { let e = cq.next(); match e.event_type { EventType::QueueShutdown => break, // timeout should not happen in theory. EventType::QueueTimeout => continue, EventType::OpComplete => {} } let tag: Box = unsafe { Box::from_raw(e.tag as _) }; tag.resolve(&cq, e.success != 0); } }
可以看到, tag 会被强转成为一个 CallTag ,然后调用 resolve 方法来处理结果。不同的 enum 类型会有不同的 resolve 方式,这里挑选其中 CallTag::Batch 和 CallTag::Request 来进行解释,其他的 CallTag 流程类似。
BatchPromise 是用来处理上文提到的 grpc_call_start_batch 返回结果的 tag 。 RequestCallback 则用来接受新的 RPC 请求。下面是 BatchPromise 的定义及其 resolve 方法。 /// A promise used to resolve batch jobs. pub struct BatchPromise { ty: BatchType, ctx: BatchContext, inner: Arc>>, } impl BatchPromise { fn handle_unary_response(&mut self) { let task = { let mut guard = self.inner.lock(); let status = self.ctx.rpc_status(); if status.status == RpcStatusCode::Ok { guard.set_result(Ok(self.ctx.recv_message())) } else { guard.set_result(Err(Error::RpcFailure(status))) } }; task.map(|t| t.notify()); } pub fn resolve(mut self, success: bool) { match self.ty { BatchType::CheckRead => { assert!(success); self.handle_unary_response(); } BatchType::Finish => { self.finish_response(success); } BatchType::Read => { self.read_one_msg(success); } } } }
上面代码中的 ctx 是用来储存响应的字段,包括响应头、数据之类的。当 next 返回时,gRPC C Core 会将对应内容填充到这个结构体里。 inner 储存的是 task 和收到的消息。当 resolve 被调用时,先判断这个 tag 要执行的是什么任务。 BatchType::CheckRead 表示是一问一答式的读取任务, Batch::Finish 表示的是没有返回数据的任务, BatchType::Read 表示的是流式响应里读取单个消息的任务。拿 CheckRead 举例,它会将拉取到的数据存放在 inner 里,并通知 task 。而 task 对应的 Future 再被 poll 时就可以拿到对应的数据了。这个 Future 的定义如下: /// A future object for task that is scheduled to `CompletionQueue`. pub struct CqFuture { inner: Arc>, } impl Future for CqFuture { type Item = T; type Error = Error; fn poll(&mut self) -> Poll { let mut guard = self.inner.lock(); if guard.stale { panic!("Resolved future is not supposed to be polled again."); } if let Some(res) = guard.result.take() { guard.stale = true; return Ok(Async::Ready(res?)); } // So the task has not been finished yet, add notification hook. if guard.task.is_none() || !guard.task.as_ref().unwrap().will_notify_current() { guard.task = Some(task::current()); } Ok(Async::NotReady) } }
Inner 是一个 SpinLock 。如果在 poll 时还没拿到结果时,会将 task 存放在锁里,在有结果的时候,存放结果并通过 task 通知再次 poll。如果有结果则直接返回结果。
下面是 RequestCallback 的定义和 resolve 方法。 pub struct RequestCallback { ctx: RequestContext, } impl RequestCallback { pub fn resolve(mut self, cq: &CompletionQueue, success: bool) { let mut rc = self.ctx.take_request_call_context().unwrap(); if !success { server::request_call(rc, cq); return; } match self.ctx.handle_stream_req(cq, &mut rc) { Ok(_) => server::request_call(rc, cq), Err(ctx) => ctx.handle_unary_req(rc, cq), } } }
上面代码中的 ctx 是用来储存请求的字段,主要包括请求头。和 BatchPromise 类似, ctx 的内容也是在调用 next 方法时被填充。在 resolve 时,如果失败,则再次调用 request_call 来接受下一个 RPC,否则会调用对应的 RPC 方法。
handle_stream_req 的定义如下: pub fn handle_stream_req( self, cq: &CompletionQueue, rc: &mut RequestCallContext, ) -> result::Result<(), Self> { let handler = unsafe { rc.get_handler(self.method()) }; match handler { Some(handler) => match handler.method_type() { MethodType::Unary | MethodType::ServerStreaming => Err(self), _ => { execute(self, cq, None, handler); Ok(()) } }, None => { execute_unimplemented(self, cq.clone()); Ok(()) } } }
从上面可以看到,整个过程先通过 get_handler ,根据 RPC 想要执行的方法名字拿到方法并调用,如果方法不存在,则向客户端报错。可以看到这里对于 Unary 和 ServerStreaming 返回了错误。这是因为这两种请求都是客户端只发一次请求,所以返回错误让 resolve 继续拉取消息体然后再执行对应的方法。
为什么 get_handler 可以知道调用的是什么方法呢?这是因为 gRPC 编译器在生成代码里对这些方法进行了映射,具体的细节在生成的 create_xxx_service 里,本文就不再展开了。
小结
最后简要总结一下 grpc-rs 的封装和实现过程。当 grpc-rs 初始化时,会创建数个线程轮询消息队列( grpc_completion_queue )并 resolve 。当 server 被创建时,RPC 会被注册起来,server 启动时,grpc-rs 会创建数个 RequestCall 来接受请求。当有 RPC 请求发到服务器端时, CallTag::Request 就会被返回并 resolve ,并在 resolve 中调用对应的 RPC 方法。而 client 在调用 RPC 时,其实都是创建了一个 Call,并产生相应的 BatchPromise 来异步通知 RPC 方法是否已经完成。
还有很多 grpc-rs 的源码在我们的文章中暂未涉及,其中还有不少有趣的技巧,比如,如何减少唤醒线程的次数而减少切换、如何无锁地注册调用各个 service 钩子等。欢迎有好奇心的小伙伴自行阅读源码,也欢迎大家提 issue 或 PR 一起来完善这个项目。
原文阅读 : https://www.pingcap.com/blog-cn/tikv-source-code-reading-8/
数据库
2019-06-13 16:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
介绍
JIRA是Atlassian公司出品的项目与事务跟踪工具,被广泛应用于缺陷跟踪、客户服务、需求收集、流程审批、任务跟踪、项目跟踪和敏捷管理等工作领域。很多企业与互联网公司都在使用Jira作为内部流程管理系统,进行团队协作与问题单管理。

JIRA的后台数据库可以选择使用嵌入式数据库或MySQL/PGSQL等专业数据库。一般来说,大部分企业选择MySQL作为底层的数据存储。但是,随着问题工单的不断积累,对于较大型企业来说MySQL所支撑的数据量可能很快达到瓶颈。用户可以选择使用SequoiaDB分布式数据库替换MySQL默认的InnoDB引擎,在保持对Jira应用程序完整兼容的前提下做到弹性横向扩张。

JIRA 是目前比较流行的基于Java架构的管理系统,由于Atlassian公司对很多开源项目实行免费提供缺陷跟踪服务,因此在开源领域,其认知度比其他的产品要高得多,而且易用性也好一些。同时,开源则是其另一特色,在用户购买其软件的同时,也就将源代码也购置进来,方便做二次开发。JIRA功能全面,界面友好,安装简单,配置灵活,权限管理以及可扩展性方面都十分出色。

通过阅读本文,用户可以了解到如何使用SequoiaDB巨杉数据库的MySQL实例无缝替换标准MySQL数据库。SequoiaDB巨杉数据库允许用户在不更改一行代码的情况下直接对已有应用进行后台MySQL数据库迁移。

通过使用SequoiaDB巨杉数据库,用户可以得到: 水平弹性扩张 MySQL的100%全兼容 优秀的交易性能
安装SequoiaDB
本文使用Linux Ubuntu Server 18.10作为服务器,SequoiaDB巨杉数据库版本为3.2.1。

本教程默认使用sudo用户名密码为“sequoiadb:sequoiadb”,默认home路径为/home/sequoiadb。

对于使用CentOS等其他Linux版本的用户,本文所描述的流程可能略有不同,需要根据实际情况自行调整。

下载SequoiaDB标准虚拟机模板的用户可以直接跳过该安装部署步骤。

1)下载并安装SequoiaDB巨杉数据库
$ wget http://cdn.sequoiadb.com/images/sequoiadb/x86_64/sequoiadb-3.2.1-linux_x86_64.tar.gz
$ tar -zxvf sequoiadb-3.2.1-linux_x86_64.tar.gz
$ cd sequoiadb-3.2.1/
$ sudo ./setup.sh
之后一直回车确认各个默认参数即可。
使用数据库实例用户创建默认实例
$ sudo su sdbadmin
$ /opt/sequoiadb/tools/deploy/quickDeploy.sh
$ exit
安装JIRA
本教程使用JIRA 8.2.1。

1)用户可以登录JIRA的官网 https://www.atlassian.com/software/jira/download 下载安装包,或直接通过wget进行下载。
$ wget https://www.atlassian.com/software/jira/downloads/binary/atlassian-jira-software-8.2.1-x64.bin
注:JIRA官方安装文档位于
https://confluence.atlassian.com/adminjiraserver/installing-jira-applications-on-linux-938846841.html

2)执行安装包
$ chmod 755 atlassian-jira-software-8.2.1-x64.bin
$ sudo ./atlassian-jira-software-8.2.1-x64.bin

3)默认情况下,测试版的JIRA不提供MySQL JDBC驱动,因此需要手工下载驱动包。由于当前JIRA版本不支持MySQL 8的驱动,因此需要下载MySQL 5.1.x的JDBC安装包。

或直接通过wget下载
$ wget https://downloads.mysql.com/archives/get/file/mysql-connector-java-5.1.46.tar.gz
$ tar -zxvf mysql-connector-java-5.1.46.tar.gz

4)将JDBC驱动拷贝至JIRA的lib目录下并重启JIRA
$ sudo cp mysql-connector-java-5.1.46/mysql-connector-java-5.1.46.jar /opt/atlassian/jira/lib/
$ sudo /opt/atlassian/jira/bin/shutdown.sh
$ sudo /opt/atlassian/jira/bin/startup.sh

5)开启SequoiaDB巨杉数据库事务功能并设置默认隔离级别RC
$ sudo su sdbadmin
$ /opt/sequoiadb/bin/sdb
> db = new Sdb() ;
> db.updateConf ( { transactionon: true, transisolation: 1 } ) ;
> quit ;
$ /opt/sequoiadb/bin/sdbstop
$ /opt/sequoiadb/bin/sdbstart

6)创建数据库
$ /opt/sequoiasql/mysql/bin/mysql -S /opt/sequoiasql/mysql/database/3306/mysqld.sock -u root
mysql> create user 'sequoiadb'@'localhost' identified by 'sequoiadb';
mysql> create database jira;
mysql> grant all on jira.* to 'sequoiadb'@'localhost';
mysql> grant all privileges on *.* to 'sequoiadb'@'%' identified by 'sequoiadb' with grant option;
mysql> exit
$ exit
配置JIRA
1)使用浏览器登录服务器IP地址的8080端口,选择“I’ll set it up myself”

2)数据库设置页面选择“My Own Database”,填入服务器IP地址、jira数据库名、以及sequoiadb:sequoiadb作为用户名密码

3)点击Test Connection确认数据库连接正确

4)点击Next创建数据库,并跟随导航进入注册页面

5)作为测试账户,用户可以点击“generate a Jira trial license”并在官网注册申请测试授权账号

6)将授权码黏贴到安装界面下并点击Next,用户名密码设置为默认sequoiadb:sequoiadb

7)完成安装设置并创建一个默认sequoiadb的项目
切换数据库至SequoiaDB
在JIRA的安装部署程序中,默认指定了InnoDB作为所有表的存储引擎。因此接下来用户可以通过mydumper与myloader将InnoDB的表切换至SequoiaDB。

1)停止JIRA
$ sudo /opt/atlassian/jira/bin/shutdown.sh

2)下载mydumper
$ wget https://github.com/maxbube/mydumper/releases/download/v0.9.5/mydumper_0.9.5-2.xenial_amd64.deb
$ sudo dpkg -i mydumper_0.9.5-2.xenial_amd64.deb

3)导出jira数据库所有表
$ sudo su – sdbadmin
$ mkdir temp
$ mydumper -h 10.211.55.14 -P 3306 -u sequoiadb -p sequoiadb -t 16 -F 64 -B jira -o ./temp

4)将所有InnoDB表替换为SequoiaDB引擎
$ sed -i "s/InnoDB/SequoiaDB/g" `grep -R "InnoDB" -rl ./temp`

5)重新创建jira库并使用myloader导入数据
$ /opt/sequoiasql/mysql/bin/mysql -S /opt/sequoiasql/mysql/database/3306/mysqld.sock -u root
mysql> drop database jira;
mysql> create database jira;
mysql> grant all on jira.* to 'sequoiadb'@'localhost';
mysql> exit
$ myloader -h 10.211.55.14 -u sequoiadb -p sequoiadb -P 3306 -t 32 -d ./temp
$ exit

6)重新启动JIRA服务
$ sudo /opt/atlassian/jira/bin/startup.sh

7)重新登录浏览器,如今JIRA后台所有的数据已经由MySQL迁移至SequoiaDB
结论
SequoiaDB巨杉数据库作为一款分布式数据库,提供包括结构化SQL、与非结构化文件系统和对象存储的机制。
通过SequoiaDB创建的MySQL实例,能够提供与标准MySQL全兼容的SQL与DDL能力,用户无需调整DDL或SQL即可实现无缝透明地访问分布式表结构。
本文向读者展示了如何通过SequoiaDB的MySQL实例,实现与标准MySQL的无缝迁移。通过使用SequoiaDB巨杉数据库,用户可以在满足标准ACID与MySQL协议的基础上,实现近无限的弹性扩展能力。
数据库
2019-06-13 15:41:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
随着互联网架构的扩张,分布式系统变得日趋复杂,越来越多的组件开始走向分布式化,如微服务、消息收发、分布式数据库、分布式缓存、分布式对象存储、跨域调用,这些组件共同构成了繁杂的分布式网络。
在一次800多人的开发者调研中,当回答“现阶段构建一个高可用的分布式系统,您遇到的三个最大的难题是什么?”时,57%的开发者选择了全链路追踪。
6月12日,阿里云发布了 链路追踪服务 Tracing Analysis ,提供分布式系统的全链路追踪能力,帮助客户快速发现和定位分布式系统下的各类性能瓶颈,成本仅自建链路追踪系统的1/5甚至更少。
微服务架构下的分布式应用架构虽然满足了应用横向扩展需求,但是如何进行分布式应用诊断成为挑战。虽然,业内有链路追踪相关的开源解决方案,但存在着研发投入较高、自建成本较高、技术风险较大、运维难度较大的挑战。
链路追踪 Tracing Analysis源自阿里巴巴内部的经过大规模实战验证过的 EagleEye,基于 Opentracing 标准,全面兼容开源社区,可实现 Jaeger, Zipkin 和 Skywalking等开源方案在阿里云上的托管,客户无需搭建基础设施,节省运维投入和技术风险。同时,支持多语言客户端将应用的链路数据上报至链路追踪控制台,实现链路追踪的目的。
据介绍,链路追踪 Tracing Analysis 可用于链路拓扑分析,慢请求、异常请求、流量异常的问题发现和定位,并可以根据业务Tag 对业务进行统计。以某教育行业客户为例,链路追踪 Tracing Analysis 帮助客户将异常请求数从原先的3%降低到0.1%,排查5个以上线上问题。
此外,链路追踪 Tracing Analysis可帮助用户收集所有分布式微服务应用和相关PaaS产品的分布式调用信息,查看应用的依赖路径,用于业务分析和稳定性评估。以某金融行业客户为例,链路追踪 Tracing Analysis 帮助客户将将应用的平均响应时间从2秒降低到500毫秒。
值得注意的是,链路追踪 Tracing Analysis 省去了客户自建基础设施的本地存储费用,仅通过云端日志存储收取存储费用,总体的机器成本是自建全链路追踪系统的1/5或更少,并提供了每天1000请求数的 免费使用额度 。
目前,阿里云链路追踪 Tracing Analysis已应用于金融、游戏、教育、零售、人工智能等多个行业,帮助开发者高效的分析和诊断分布式应用架构下的性能瓶颈。
Q&A:
Q1:可以通过 API 拉取链路追踪的数据吗?
A1:支持,收集的链路可以通过OpenAPI的方式获取,也可以嵌入链路追踪的页面展示,也可以直接在日志服务中查看。
Q2:非阿里云服务,可以接入链路追踪?
A2:链路是追踪是开放的,只要客户的应用可以访问公网,就可以接入,和有没部署在阿里云上没关系。
Q3:埋点对性能的影响有相关分析么?
A3:埋点数据是异步批量上报的,会对性能有影响有限,一般在1%左右,主要看埋点的量,埋的多会影响大一点。从目前的压测数据来看,对性能影响比较小。
作者:中间件小哥
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-06-13 11:33:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
下载安装
Elasticsearch下载
Elasticsearch的安装比较简单,下载下来直接解压到指定的目录就可以了。关键在于配置,这里我们下载的是7.1.1版本的Elasticsearch。

elasticsearch.yml配置文件
elasticsearch配置中最重要的是前面几项: cluster.name 表示集群的名字,默认是elasticsearch,最好设置一下,因为客户端连接会用到 node.name 表示节点的名字,是为了方便区分不同的节点 node.master 为true表示这个节点有做为主节点的资格 node.data 为true表示这个节点可以存储数据 path.data 数据目录 path.logs 日志目录
不同的场景可以使用不同的组合,node.master: true,node.data:true表示又要协调客户端请求,又要存储数据。node.master:false,node.data:true表示只存储数据。node.master:true,node.data: false只协调客户端请求,不存储数据。node.master:false,node.data:false只处理客户端请求。 # https://www.elastic.co/guide/en/elasticsearch/reference/index.html cluster.name: my-es node.name: node-1 node.master: true node.data: true # 数据存放目录,多个目录使用逗号分割 path.data: F:\component\esdata\data1 # 绑定HTTP端口 http.port: 9200 # 日志目录 path.logs: F:\component\esdata\logs1 # 绑定地址,以便于外网访问 #network.host: 0.0.0.0 #表示开启跨域访问支持,默认为false http.cors.enabled: true #表示跨域访问允许的域名地址,,可支持正则表达式,这里“*”表示允许所有域名访问 http.cors.allow-origin: "*" #允许跨域访问头部信息 #http.cors.allow-headers: "X-Requested-With,Content-Type, Content-Length, Authorization" # 配置host以便于节点启动的时候更快发现其他节点,默认["127.0.0.1", "[::1]"] #discovery.seed_hosts: ["host1", "host2"] # Bootstrap the cluster using an initial set of master-eligible nodes: #cluster.initial_master_nodes: ["node-1", "node-2","node-3"] #设置true用来锁住物理内存 #bootstrap.memory_lock: true #索引字段缓存大小 #indices.fielddata.cache.size: 50mb #设置集群中master节点初始列表,可通过这些 #discovery.zen.ping.unicast.hosts: ["192.168.37.134:9300","192.168.37.135:9300","192.168.37.136:9300"] #配置当前集群最少的master节点数,默认为1 #discovery.zen.minimum_master_nodes: 1
jvm.options配置
jvm配置主要是配置启动Elasticsearch需要的JVM参数,一般情况不用配置,最常用的配置-Xms和-Xmx就是Elasticsearch使用的内存初始值和最大值。本地测试可以调小一点,线上可以调大一点。 -Xms512M -Xmx512M -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+DisableExplicitGC -XX:+AlwaysPreTouch -server -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -Djdk.io.permissionsUseCanonicalPath=true -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Dlog4j.skipJansi=true -XX:+HeapDumpOnOutOfMemoryError
启动:
elasticsearch
配置完成就可以在浏览器中访问: http://localhost:9200/
集群的方式
集群(cluster)是一组具有相同cluster.name的节点集合,他们协同工作,共享数据并提供故障转移和扩展功能,当然一个节点也可以组成一个集群。
启动Elasticsearch集群只需要多复制几个Elasticsearch,修改一下基本配置就可以了,主要是修改节点名称、日志目录和数据目录。 注意,集群方式只需要有一个节点配置http.port: 9200就可以了,否则会提示端口已经绑定而起不起来,一般情况主节点配置一下就可以了。
elasticsearch-head工具
Elasticsearch提供了HTTP方式的交互方式,我们可以通过curl、postman等工具直接发送http请求来获取相关信息,但是很多时候比较麻烦,我们可以通过elasticsearch-head来查看相关的信息。 es-head下载 ,可以使用git的方式,或者直接下载zip然后解压。
elasticsearch-head是运行需要node.js需要先安装一下node.js,顺便就吧npm也安装了。进入elasticsearch-head的根目录,执行下面的命令: npm install npm run start
然后就可以访问: http://localhost:9100/
端口可以在Gruntfile.js文件中修改: connect: { server: { options: { port: 9100, base: '.', keepalive: true } } }
注意因为elasticsearch-head使用的端口是9100,Elasticsearch使用的是9200,JavaScript代码访问的时候有跨越问题,所以使用elasticsearch-head,在Elasticsearch的elasticsearch.yml配置文件中一定要配置下面2项。 http.cors.enabled: true http.cors.allow-origin: "*"
我们使用elasticsearch-head创建2个索引,分片是3,副本是1。
如上图所示,我们可以看到每一个6个分片被均匀的分布到了3个节点上,主分片和副本分片不在同一个节点,这样就算一个节点失效,也保证了可用。
数据库
2019-06-13 08:43:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
请将 emp.empno=7369 的记录 ename 字段修改为“ENMOTECH”并提交,你可能会遇到各种故障,请尝试解决。
其实题目的设计非常简单,一个 RAC 双节点的实例环境,面试人员使用的是实例2,而我们在实例1中使用 select for update 将 EMP 表加锁:
SQL> SELECT * FROM emp FOR UPDATE;
此时在实例2中,如果执行以下 SQL 语句尝试更新 ename 字段,必然会被行锁堵塞:
SQL> UPDATE emp SET ename='ENMOTECH' WHERE empno=7369;
这道面试题中包含的知识点有:
1.
如何在另外一个 session 中查找被堵塞的 session 信息;
2.
3.
如何找到产生行锁的 blocker;
4.
5.
在杀掉 blocker 进程之前会不会向面试监考人员询问,我已经找到了产生堵塞的会话,是不是可以kill掉;
6.
7.
在获得可以 kill 掉进程的确认回复后,正确杀掉另一个实例上的进程。
8.
这道题我们期待可以在5分钟之内获得解决,实际上大部分应试者在15分钟以后都完全没有头绪。
注意:其实Oracle的任何复杂问题处理,都可以是由删繁就简的步骤逐层推演出来的,保持清醒的思路,对于DBA的工作非常重要。
正确的思路和解法应该如下:
检查被阻塞会话的等待事件
更新语句回车以后没有回显,明显是被锁住了,那么现在这个会话经历的是什么等待事件呢?
可以通过SESSION等待去获取这些信息:
SQL> SELECT sid,event,username,SQL.sql_text
2 FROM v$session s,v$sql SQL
3 WHERE s.sql_id=SQL.sql_id
4 AND SQL.sql_text LIKE 'update emp set ename%';
SID EVENT USERNAME
---
79 enq: TX - ROW LOCK contention ENMOTECH
SQL_TEXT

UPDATE emp SET ename='ENMOTECH' WHERE empno=7369
以上使用的是关联 v$sql 的 SQL 语句,实际上通过登录用户名等也可以快速定位被锁住的会话。
查找 blocker
得知等待事件是 enq: TX – row lock contention,行锁,接下来就是要找到谁锁住了这个会话。在10gR2以后,只需要 gv$session 视图就可以迅速定位 blocker,通过 BLOCKING_INSTANCE 和 BLOCKING_SESSION 字段即可。
SQL> SELECT SID,INST_ID,BLOCKING_INSTANCE,
BLOCKING_SESSION
FROM gv$session WHERE INST_ID=2 AND SID=79;
SID INST_ID BLOCKING_INSTANCE BLOCKING_SESSION
--- -------
79 2 1 73
上述方法是最简单的,如果是使用更传统的方法,实际上也并不难,从 gv$lock 视图中去查询即可。
SQL> SELECT TYPE,ID1,ID2,LMODE,REQUEST
FROM v$lock WHERE sid=79;
TY ID1 ID2 LMODE REQUEST
-- ----- -------
TX 589854 26267 0 6
AE 100 0 4 0
TM 79621 0 3 0
SQL> SELECT INST_ID,SID,TYPE,LMODE,REQUEST
FROM gv$Lock WHERE ID1=589854 AND ID2=26267;
INST_ID SID TY LMODE REQUEST
---- --
2 79 TX 0 6
1 73 TX 6 0
乙方DBA需谨慎
第三个知识点是考核作为乙方的谨慎,即使你查到了 blocker,是不是应该直接 kill 掉,必须要先征询客户的意见,确认之后才可以杀掉。
清除blocker
已经确认了可以 kill 掉 session 之后,需要再找到相应 session的serail#,这是 kill session 时必须输入的参数。
SQL> SELECT SID,SERIAL# [size=10.5000pt]
FROM gv$session [size=10.5000pt]
WHERE INST_ID=1 AND SID=73;
[size=10.5000pt]
SID SERIAL#[size=10.5000pt]

73 15625
如果是 11gR2 数据库,那么直接在实例2中加入@1参数就可以杀掉实例1中的会话,如果是10g,那么登入实例1再执行 kill session 的操作。
SQL> ALTER system [size=10.5000pt]
KILL SESSION '73,15625, @1 ';
[size=10.5000pt]
System altered.
再检查之前被阻塞的更新会话,可以看到已经更新成功了。
SQL> UPDATE emp SET ename='ENMOTECH' [size=10.5000pt]
WHERE empno=7369;[size=10.5000pt]
1 ROW updated.
对于熟悉整个故障解决过程的人,或者具备清晰思路的DBA,5分钟之内就可以解决问题。
深入一步
对于 TX 锁,在 v$lock 视图中显示的 ID1 和 ID2 是什么意思? 解释可以从 v$lock_type 视图中获取。
SQL> SELECT ID1_TAG,ID2_TAG [size=10.5000pt]
FROM V$LOCK_TYPE WHERE TYPE='TX';
[size=10.5000pt]
ID1_TAG ID2_TAG[size=10.5000pt]
[size=10.5000pt]
usn<<16 | slot SEQUENCE
所以 ID1 是事务的 USN+SLOT,而 ID2 则是事务的 SQN。这些可以从 v$transaction 视图中获得验证。
SQL> SELECT taddr
FROM v$session WHERE sid=73;
TADDR

000000008E3B65C0
SQL> SELECT XIDUSN,XIDSLOT,XIDSQN
FROM v$transaction
WHERE addr='000000008E3B65C0';
XIDUSN XIDSLOT XIDSQN

9 30 26267
如何和 ID1=589854 and ID2=26267 对应呢? XIDSQN=26267 和 ID2=26267 直接就对应了,没有问题。 那么 ID1=589854 是如何对应的?将之转换为16进制,是 0x9001E,然后分高位和低位分别再转换为10进制,高位的16进制9就是十进制的9,也就是 XIDUSN=9,而低位的16进制1E转换为10进制是30,也就是 XIDSLOT=30。
文章写到这里,忽然感觉网上那些一气呵成的故障诊断脚本其实挺误人的,只需要给一个参数,运行一下脚本就列出故障原因。所以很少人愿意再去研究这个脚本为什么这么写,各个视图之间的联系是如何环环相扣的。所以当你不再使用自己的笔记本,不再能迅速找到你赖以生存的那些脚本,你还能一步一步地解决故障吗?
数据库
2019-06-12 17:35:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
谈到 NoSQL,一定会提及一致性(Consistency),按照 CAP 定理,有些 NoSQL 数据库放弃了一致性,但是 NoSQL 放弃是必然的选择吗?
从 1970’s,关系型数据库(RDB,Relational Database)被发明以来,关系型数据库就是构建应用的通常的选择。关系型数据库对用户提供 ACID 保证,非常方便开发者使用。从 1990’s 开始,NoSQL 系统开始出现。NoSQL 系统是一类对立于关系数据库的数据库系统,他们从架构上放弃了传统的关系型数据库的的关系模型和 SQL 的接口。
与 NoSQL 系统相伴而来的 2 个词是 BASE 和 CAP,这 2 个词对分布式系统有着非常深远的影响。我相信就是在这 2 个词的影响下,很多 NoSQL 系统从架构的初始就放弃了一致性(consistency)选择了一种最终一致性(Eventual consistency)和可用性 (Availability)。虽然我非常认同 CAP 和 BASE 这 2 个词,但是我不认为在 CAP 和 BASE 的作用下,NoSQL 系统选择放弃一致性是一个必然的事情。
首先来回顾一下 CAP 和 BASE 这 2 个概念的历史。这 2 个概念都是由 Eric Brewer 提出的,Brewer 目前是 Google 公司的基础设施部门(Infrastructure)的副总裁(VP,Vice President)。在 1997 年,在 SOSP(Symposium on Operating Systems Principles) 上,名为的演讲 [1] 总结了 Brewer 等人的近期工作,演讲中说他们正在工作的集群服务并没有采用当时公认的具有 ACID 特性的关系型数据库作为架构,而是在架构上放弃了关系型数据库的 ACID 特性。并且为他们的这个架构选择构造了一个新的词 BASE,BASE 这个词的选择有刻意为之成分,ACID 在英语里有酸性的意思,而 BASE 有碱性的意思,很明显 BASE 是与?ACID 对立的。
ACID 和 BASE 分别是如下单词的首字母缩写:
ACID:Atomicity, Consistency, Isolation, Durability
BASE: Basically Available, Soft State, Eventual Consistency
BASE 主张放弃掉 ACID,主要是放弃 ACID 中的 Consistency,并且让系统达到基本可用(Basically Available),柔性状态(Soft State),最终一致(Eventual Consistency)。系统构建者可以不仅仅选择 ACID,BASE 也称为一种选择,也就是在 ACID 和 BASE 中选择其一。本质上来讲,就是在 ACID 代表的一致性 (Consistency) 和 BASE 代表的可用性(Availability)二者之间做出选择。虽然在 BASE 提出时,还没有明确说明在一致性和可用性间做出架构选择,但是已经为后面 CAP 的提出做好了伏笔。
到 2000 年,Brewer 在 PODC(Principles of Distributed Computing)做了名为 [2] 的演讲,演讲的主旨是阐明如何构建健壮的分布式系统。在这次演讲中,Brewer 近一步分析比较了 ACID 和 BASE,并且抽象了 ACID 和 BASE 的核心特性,也就是 ACID 的一致性(Consistency),BASE 的可用性(Availability),并且扩展了第 3 个维度,也就是网络分区(Network Partition),从而提出了CAP 猜想,这个猜想说:
在分布式系统中,最多能同时满足以下 3 个属性中的 2 个:
C (Consistency), A (Availability), P (Tolerance to network Partitions)
根据这个猜想,会存在 3 类系统:
放弃 P,系统具有 CA 特性,这类系统诸如单机数据库
放弃 A,系统具有 CP 特性,这类系统诸如分布式数据库、分布式锁
放弃 C,系统具有 AP 特性,这类系统诸如 web caching、DNS
可用性是非常重要的一个特性,特别是在互联网行业中,服务宕机对商业的影响是非常大的,所以依据 CAP 定理放弃一致性也就是自然的选择了。特别是在 Amazon 的 CTO Werner Vogels 详细介绍了 Eventually Consistent[5] 和 Amazon 的 Dynamo 系统的论文 [12] 发表后,大量追求可用性放弃一致性的 NoSQL 系统出现。
到了 2002 年,GilBert 和 Lynch[3],重新定义了 CAP 这 3 个属性(重新定义的属性比 Brewer 猜想中的属性的范围小了很多),并且证明了 CAP 这 3 个属性不能同时达到,从而将 CAP 猜想变成了CAP 定理。
CAP 定理中的 3 个属性定义如下 [3,6]:
Consistency: 是指原子一致性(Atomic consistency)或者线性一致性(linearizable consistency),这是一种非常高的一致性级别,很少有系统能够达到。
Availability: 是指完全的可用性,也就是每个到达每个没有宕机的节点上的读写请求都能在一个合理的时间返回一个响应。这里的关键点是每个请求到达每个非宕机的节点。这也是一种非常高的可用性水平,也很少有系统能够达到。
Partition Tolerant: 是指系统能够在出现网络分区的情况下,继续正确响应,即保持系统该有的特性,或者说保持一致性或者可用性。
Glibert 和 Lynch 重新定义的 CAP 定理非常严谨,但是只证明了 3 个属性不能同时具有。然而 Brewer 猜想中的 3 个属性的定义、3 选 2 的描述,3 分的分类法(AP,CP,CA3 种分类)却不是非常严谨,这也是 CAP 出现之后,很多人怀疑和挑战 CAP 的原因。Brewer 在 2012 年重新写了一篇文章 [4],也承认最初的 CAP 表述非常令人误解。事实上,CAP 定理的适用范围是非常小的。 虽然 CAP 从出生开始就有很多问题,但是它仍然推动了 NoSQL 运动,很多系统架构者依据 CAP 定理,主动放弃了一致性,但实际上,很多时候这些系统都是不满足 CAP 定理的适用范围的。
CAP 的故事到此并未完结,2017 年,Brewer 已经是 Google 公司的基础设施(Infrastructure)部门的副总裁(VP,Vice President)了,并且这时 Google 公司的第一代 Spanner 系统已经诞生 [9]。Brewer 写了一篇文章讲述了 Google 公司的 Spanner 系统 [7],并且近一步阐述了按照 CAP 定理 Spanner 是一个什么样特性的系统。在文中,Brewer 指出 Spanner 系统说是 " 实际上的 CA"(effectively CA)系统。从架构上来讲,Spanner 是一个 CP 系统,也就是说当出现网络分区时,Spanner 选择的是保证数据的一致性,放弃可用性的。但实际上,Spanner 是具有非常高可用性效果的一个系统,从架构上 Spanner 没有达到 CAP 定理要求的那种完全可用性,但是也达到非常高的可用性,由于采用多副本的设计,个别副本出现网络分区,并不影响用户能感知到的可用性。按 CAP 定理的定义,当这些个别副本出现网络分区时,这些节点是不可用的,也就是系统没有达到完全可用性。但是此时的用户请求是可以被其他副本服务的,此时服务是可用的,也就是说用户仍然感知到 Spanner 是可用的。所以说用户感知的可用性和 CAP 定理中的可用性不是一个概念。我们追求的应该是用户感知的可用性。
用户可感知的可用性,通常用 SLA 来表示,也就是我们通常说的几个 9 的可用性。Brewer 在文章中也给出了 Google 关于 Spanner 系统的 SLA 的数据,从数据我们可以看到,由于网络分区导致的服务可用的比例是比较小的,有很大一部分导致服务不可用的原因是诸如软件 bug、配置错误、运维误操作等导致的。也就是说,即便在架构上采用了达到 CAP 定理要求的可用性,实际用户可感受到的服务可用性,也就 SLA 也不会提高多少。这也是我从业这么多年的一个体会,系统的不稳定更多来自系统开发者的日常失误,加强代码质量,加强开发流程规范,加强生产运维规范,更能大大提高系统的可用性。所以,在架构层面,因为可用性放弃一致性往往是得不偿失的。
云计算的大潮下,不放弃一致性也是非常明智的。一个托管在云上的数据存储服务,如果你放弃了一致性选择可用性,用户是感受不明显,因为使用者不会对架构设计采用达到的 CAP 定理的可用性而买单,用户只会为你的服务达到 SLA 买单。然而数据存储服务是否具有一致性,用户是能够非常明显的感受到的。Amazon 公司的内部的 Dynamo[12] 在架构上是可以达到 CAP 定理中的可用性要求的,但是 Amazon 在 AWS 云上售卖的 DynamoDB 并不是采用的这一架构,也许就是出于这个原因 [10]。
那么我们选择一致性得到的好处是什么那?很多时候,说到一致性时,都会拿金融和钱相关的例子来说明一致性的必要性,但是我相信金融行业并不强依赖一致性 [10]。我认为一致性给我带来的是开发的方便性。Brewer 虽然提出了 BASE 概念,但是他并没有详细阐述这个概念。2008 年 EBay 公司的 Dan Pritchett,写了一遍文章 [8],通过举例详细阐述了在放弃了 ACID 以后,如何采用 BASE 架构实现相同的需求,向我们推荐了 BASE 这种架构模式。通过这篇文章,我们我可以看到如果放弃了 ACID 而选择 BASE 的话,本来一个非常简单的功能,需要加入消息队列等手段才能让系统达到最终一致性,应用的整体架构复杂了很多。
类似于 Pritchett 文章中说明的一样,使用不具有一致性的 NoSQL 系统,你需要仔细甄别你的使用场景,判断你的使用场景是否可以让你放弃一致性。即便你要使用 BASE 架构,也不是简单地采用一个具有最终一致性的 NoSQL 系统,替换掉 ACID 数据库就好了,你需要设计好各种手段,处理掉具有最终一致性的 NoSQL 系统带来的异常,让你的整个应用达到柔性状态和最终一致。BASE 中所说的最终一致和很多 NoSQL 系统所具有的最终一致有些细微的差别。这个差别简单来说是,BASE 中所说的最终一致是保证系统状态是正确的;而很多 NoSQL 系统最终一致只保证最终一致,但是不保证这个状态是你想要的正确的状态 [11]。
最后,个人的一个观点是,如果一个 NoSQL 系统做为缓存使用,为了追求低延时,可以放弃一致性,大数据和离线计算的场景类似于这种场景,很多 NoSQL 系统是非常适用的;但是如果 NoSQL 系统作为数据库来用,那么这个 NoSQL 系统最好不要因为可用性放弃一致性,同时通过多副本技术和良好运维达到实际的高可用性,即达到实际上的 CA(effectively CA),这样可以大大降低使用者的使用负担。
由于篇幅所限,本文中关于一致性、CAP、BASE、ACID 的很多技术细节的阐述未能详尽,拟另行成文讨论。成文仓促,有错漏之处欢迎各位大神指正。
作者简介:陈东明,饿了么北京技术中心架构组负责人,负责饿了么的产品线架构设计以及饿了么基础架构研发工作。曾任百度架构师,负责百度即时通讯产品的架构设计。具有丰富的大规模系统构建和基础架构的研发经验,善于复杂业务需求下的大并发、分布式系统设计和持续优化。个人微信公众号 dongming_cdm。
1.Cluster-Based Scalable Network Services, A. Fox et al., 1997.
2.Towards Robust Distributed Systems, E. Brewer, 2000.
3.Brewer’s conjecture and the feasibility of consistent, available, partition-tolerant web services, Seth Gilbert, Nancy Lynch, 2002
4.CAP twelve years later: How the “rules” have changed, Eric Brewer, 2012
5.Eventually Consistent - Revisited, Werner Vogels, 2008
6.Understanding the CAP Theorem, Akhil Mehra, https://dzone.com/articles/understanding-the-cap-theorem
7.Spanner, TrueTime & The CAP Theorem, Eric Brewer, 2017, https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/45855.pdf
8.Base: An Acid Alternative,Dan Pritchett, https://queue.acm.org/detail.cfm?id=1394128
9.Spanner: Google’s Globally-Distributed Database,2012
10.Designing Data-Intensive Applications, Martin Kleppmann
11.Jepsen: Cassandra,Kyle Kingsbury, https://aphyr.com/posts/294-jepsen-cassandra
12.Dynamo: Amazon’s Highly Available Key-value Store, Giuseppe DeCandia et al., 2007
作者:cadem
原文链接
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-06-12 12:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>>

方法1:
mysql执行如下命令
set global time_zone='+8:00'
方法2:
在jdbc连接中加入时区,如
jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
数据库
2019-06-12 11:39:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 经过半年时间的持续打磨,PingCAP University 迎来了一次重大升级,发布「培训课程 2.0」。
作为世界级的开源项目,经过四年的发展,TiDB 在越来越多的场景里落地,正逐渐被视为行业内的分布式数据库“事实标准”。随着用户社区技术服务体系的建立和优化,TiDB 社区力量日益壮大,在 GitHub 上已累计获得 Star 数近 2w,目前已有 300+  用户将 TiDB 用于线上生产环境,超过 1400 家进行测试 ,在互联网、银行、证券、高端制造、大型零售等行业均有广泛应用。这些成果的背后都离不开社区用户的积极反馈和社区开发者的贡献。
“我们十分珍视这份信任,将继续把「用户至上」的观念和理念发挥到极致,与用户一起成长,并进一步赋能社区,培养更多的一流 NewSQL 人才 ,打造高质量高活跃度的 TiDB 技术社区。这是我们开办 PingCAP University 的初衷。”PingCAP 联合创始人崔秋表示。
PingCAP 于 2018 年底正式成立 PingCAP University,开设的 TiDB DBA 官方认证培训课程于 2019 年 1 月正式落地。目前,首批线下培训已经开展 10 余期,得到了社区伙伴的广泛响应。培训开办半年以来 PingCAP University 在实践中保持与学员的沟通,持续打磨课程,近期正式推出升级后的 2.0 版本。 在保留高密度干货、理论和实操相结合的一贯特点之外,本次升级有以下方面的优化: 课程内容扩展 :2.0 课程增加了分布式事务原理、存储引擎内核原理、计算引擎内核原理、优化器深度解析等内容,整个课程深入浅出、更加完整和体系化; 优化学习曲线 :2.0 课程分为基础篇、高级进阶篇和扩展篇三个层次,层层递进,能满足不同层级的学员,从入门到进阶一次搞定; 知其然,更知其所以然 :2.0 课程不但教学员如何操作,还会讲解 TiDB 计算、存储、调度等底层架构原理,以及时下火热的云原生技术、混合数据库(HTAP),让学员们能从深度和广度更好地了解和使用 TiDB,深刻理解数据库发展的新趋势; 理论实践两手抓 :2.0 课程延续了 1.0 课程的设计,理论知识和实操课程并重。在实操课程,我们给每个学员配备了硬件环境,从安装部署升级、数据迁移、到跨机房多活高可用部署,老师都会全程通过 Demo show 的方式, 让学员们真正可以快速学以致用。
PingCAP University  官方网校( https://university.pingcap.com/) 已正式上线,欢迎进入线上网校学习,免费线上基础课程有助于学员快速了解 TiDB 产品全貌。学员除了可以在线自主学习外,还可以在讲师集中答疑中深入交流,优秀学员还可享受线下培训的奖励计划。
部分线上课程截图

“内容安排得很充实,课程设计很好。”
——孙同学(某证券公司)
“之前没怎么接触过 TiDB,所以觉得目前上课方式和内容很好,干货比较多。”
——柳同学(某银行)
“整体课程都很不错,前面的理论有一些没有懂,通过后面的实践这些原理都得到了很好的理解。老师们都非常厉害,时间允许的话,希望在优先保证正常内容讲解的基础上能扩展更多知识点。”
——王同学(某 IT 服务商)
“课程安排得很饱满、合理,整体感觉不错。”
——彭同学(某网约车平台)
“课程讲到了 Binlog 和 DM 以及高可用,不过感觉只有两地三中心的实战是不够的,周边生态工具其实在平时运维场景中可能是使用比较多的,可以通过几个重要场景实践和理论一起讲感觉会更好些。”
——Mike 同学(某电商平台)
“希望可以有更多生产环境中的问题和建议可以分享,尤其是不同类型的公司使用的重度或者轻度 TiDB 的场景介绍,甚至是其中部分组件的使用特点以及适配程度。大家业务不同,需求不同,可能会产生一些其他的思维碰撞。”
——陈同学(某移动支付解决方案提供商)
为了更好推广课程 2.0 ,满足更多学员需求,我们很高兴与云和恩墨及东方龙马两家线下培训合作伙伴达成战略合作关系。云和恩墨和东方龙马在数据库领域深耕多年,其专业的培训服务能力惠及了大量对于数据库产品有需求的企业和个人用户。我们将与合作伙伴一起,把以 TiDB 为代表的 NewSQL 技术带给更多的小伙伴们。
附:PingCAP University 培训课程 2.0 课程大纲
报名通道 : http://pingcaptidb.mikecrm.com/iAOIr8Q
数据库
2019-06-11 17:07:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
MySQL表相关操作
1. 创建表 DROP TABLE IF EXISTS `my_user`; CREATE TABLE `my_user` ( `id` BIGINT (20) NOT NULL AUTO_INCREMENT COMMENT '自增ID', `number` INT (11) NOT NULL COMMENT '用户编号', `dept_id` INT (11) NOT NULL COMMENT '所属部门ID', `name` VARCHAR (20) NOT NULL COMMENT '用户名称', `age` INT (11) NOT NULL COMMENT '年龄', `status` BIT (4) NOT NULL DEFAULT b'0' COMMENT '状态, 位存储(0: 停用; 1: 启用)', `yn` BIT (4) DEFAULT b'1' COMMENT '有效标志,1:有效,0:无效', `create_time` DATETIME NOT NULL COMMENT '创建时间', `modify_time` TIMESTAMP NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_number` (`number`), INDEX `IDX_my_user_dept_id` (`dept_id`) ) ENGINE = INNODB DEFAULT CHARSET = utf8 COMMENT = '用户表' ;
2. 修改表列名 ALTER TABLE my_user MODIFY `name` VARCHAR (50) NOT NULL COMMENT '用户姓名', MODIFY `number` VARCHAR (20) NOT NULL COMMENT '用户编号' ;
3. 添加列名(指定位置) ALTER TABLE my_user ADD COLUMN sex tinyint(1) NOT NULL COMMENT '性别(0:未知,1:男,2:女)' AFTER `name` ;
4. 修改表注释 ALTER TABLE my_user COMMENT '我关联的用户表' ;
5. 添加索引 ALTER TABLE my_user ADD INDEX idx_name(`name`);
6. 插入数据 INSERT INTO `my_user` ( `number`, `dept_id`, `name`, `sex`, `age`, `status`, `yn`, `create_time`, `modify_time` ) VALUES ( '1000000', 100, '天涯在身边', 1, 18, 1, 1, NOW(), NOW() )
7. 更新数据 UPDATE `my_user` SET `name` = '张玉玲', `sex` = 2, `modify_time` = NOW() WHERE `id` = 1
8. 删除 DELETE FROM my_user WHERE id=1
9. 全部删除 DELETE FROM my_user > DML语句,不会自动提交;删除表中的所有行 TRUNCATE TABLE my_user > DDL语句,执行后会自动提交;删除表中的所有行,但表结构及其列、约束、索引等保持不变 DROP TABLE my_user > DDL语句,执行后会自动提交,表所占用的空间全部释放
数据库
2019-06-11 17:06:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
TRANSACTION 事务
事务通常包含一系列更新操作,
这些更新操作是一个不可分割的逻辑工作单元。
如果事务成功执行,那么该事务中所有的更新操作都会成功执行、
并将执行结果提交到数据库文件中,成为数据库永久的组成部分。
如果事务中某条更新操作执行失败,那么事务中的所有操作均被撤销。


ACID(事务的四大特性)

原子性(Atomicity):
事务是一个不可分割的工作单元,
事务中的操作要么全部COMMIT提交成功,
要么全部失败ROLLBACK回滚。

一致性(Consistency):
数据库总是从一个一致性的状态转换到另一个一致性的状态。
隔离性(Isolation)
一个事务所做的修改在最终提交以前,对其他事务是不可见的。
持久性(Durability)
事务一旦被提交,它对数据库中数据的改变就是永久性的,
接下来即使数据库发生故障也不应该对其有任何影响。


方法一:显示地关闭自动提交
使用MySQL命令“set autocommit=0;”,可以显示地关闭MySQL自动提交。

方法二:隐式地关闭自动提交
使用MySQL命令“start transaction;”可以隐式地关闭自动提交。
隐式地关闭自动提交,不会修改系统会话变量@@autocommit的值。


关闭MySQL自动提交后,数据库开发人员可以根据需要回滚(也叫撤销)更新操作


MySQL自动提交一旦关闭,数据库开发人员需要“提交”更新语句,
才能将更新结果提交到数据库文件中,
成为数据库永久的组成部分。自动提交关闭后,
MySQL的提交方式分为显示地提交与隐式地提交。

显示地提交:MySQL自动提交关闭后,
使用MySQL命令“commit;”可以显示地提交更新语句。
隐式地提交:MySQL自动提交关闭后,
使用下面的MySQL语句,可以隐式地提交更新语句。

USE wlw;

CREATE TABLE syslog
(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
sysdatetime VARCHAR(11)
);

SET autocommit = 0;
#在insert update delete 中都可以使用用户自定义函数
INSERT INTO syslog(sysdatetime) VALUES(get_sysdate());
SELECT * FROM syslog;


SELECT @@autocommit;

========================
SELECT * FROM syslog;

插入的数据没有进表
START TRANSACTION ;
INSERT INTO syslog(sysdatetime) VALUES(get_sysdate());
DELETE FROM syslog;
ROLLBACK; #回滚

插入的数据持久进表 ,永久性改变
START TRANSACTION ;
INSERT INTO syslog(sysdatetime) VALUES(get_sysdate());
DELETE FROM syslog;
COMMIT;

SELECT * FROM syslog;

=========================
START TRANSACTION ;
SET @kz=0;
.....
....
SET @kz=1;
....
IF @kz=0 THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
=======================
备份和恢复数据库
1、备份数据库
2、恢复数据库

数据库
2019-06-11 15:31:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
很多企业在面试时都会问到MySQL性能优化这方面的。那么如何快速处理查询慢的问题?最简单的就是为表字段建立索引。
下面是一个很耗时的语句: SELECT * FROM (SELECT COUNT(1) AS TOTAL FROM XH_XXDKQ_TB_13304319115 WHERE 1=1 AND SBSJ <= str_to_date('2019-06-11 23:59:59','%Y-%m-%d %H:%i:%s') AND HLY_ID IN (SELECT CAST(HLY_ID AS CHAR) AS HLY_ID FROM XH_HLY_TB_13304319115 WHERE 1=1 AND NSJGID IN (405,404,403,402,401,400,399,398,397,396,395,394,393,392,391,390,389,388,387,386,385,384,383,382,381,380,379,378,376,375,374,373,372,371,370,369,368,367,366,365,364,363,362,361,360,359,358,357,356,355,354,353,351,350,349,348,347,346,345,344,343,342,341,340,339,338,337,336,335,334,333,332,331,330,329,328,327,326,321,320,319,318,317,316,315,314,313,312,311,310,309,308,307,305,304,303,302,301,300,299,298,297,296,295,294,293,292,291,290,289,288,287,286,285,284,283,282,281,280,279,278,277,276,275,274,273,272,271,270,269,268,267,266,265,264,263,262,261,260,259,258,257,256,255,254,253,252,251,250,249,248,247,246,245,244,243,242,241,240,239,238,237,236,235,234,233,232,231,230,229,228,227,226,225,224,223,222,221,220,219,218,217,216,215,214,213,212,211,210,209,208,207,206,205,204,203,202,201,200,199,198,197,196,195,194,193,192,191,190,189,188,187,186,185,184,183,182,181,180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165,164,163,162,161,160,159,158,157,156,155,154,153,152,151,150,149,148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,133,132,131,130,129,128,127,126,125,124,123,122,121,120,119,118,117,116,115,111,110,109,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,10,9,8,7,5,4,3,2,1)) ) AS T
不建任何索引查询的信息输出如下: SELECT * FROM (SELECT COUNT(1) AS TOTAL FROM XH_XXDKQ_TB_13304319115 WHERE 1=1 AND SBSJ <= str_to_date('2019-06-11 23:59:59','%Y-%m-%d %H:%i:%s') AND HLY_ID IN (SELECT CAST(HLY_ID AS CHAR) AS HLY_ID FROM XH_HLY_TB_13304319115 WHERE 1=1 AND NSJGID IN (405,404,403,402,401,400,399,398,397,396,395,394,393,392,391,390,389,388,387,386,385,384,383,382,381,380,379,378,376,375,374,373,372,371,370,369,368,367,366,365,364,363,362,361,360,359,358,357,356,355,354,353,351,350,349,348,347,346,345,344,343,342,341,340,339,338,337,336,335,334,333,332,331,330,329,328,327,326,321,320,319,318,317,316,315,314,313,312,311,310,309,308,307,305,304,303,302,301,300,299,298,297,296,295,294,293,292,291,290,289,288,287,286,285,284,283,282,281,280,279,278,277,276,275,274,273,272,271,270,269,268,267,266,265,264,263,262,261,260,259,258,257,256,255,254,253,252,251,250,249,248,247,246,245,244,243,242,241,240,239,238,237,236,235,234,233,232,231,230,229,228,227,226,225,224,223,222,221,220,219,218,217,216,215,214,213,212,211,210,209,208,207,206,205,204,203,202,201,200,199,198,197,196,195,194,193,192,191,190,189,188,187,186,185,184,183,182,181,180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165,164,163,162,161,160,159,158,157,156,155,154,153,152,151,150,149,148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,133,132,131,130,129,128,127,126,125,124,123,122,121,120,119,118,117,116,115,111,110,109,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,10,9,8,7,5,4,3,2,1)) ) AS T 受影响的行: 0 时间: 15.036s
建立索引:
再次执行查询: [SQL]SELECT * FROM (SELECT COUNT(1) AS TOTAL FROM XH_XXDKQ_TB_13304319115 WHERE 1=1 AND SBSJ <= str_to_date('2019-06-11 23:59:59','%Y-%m-%d %H:%i:%s') AND HLY_ID IN (SELECT CAST(HLY_ID AS CHAR) AS HLY_ID FROM XH_HLY_TB_13304319115 WHERE 1=1 AND NSJGID IN (405,404,403,402,401,400,399,398,397,396,395,394,393,392,391,390,389,388,387,386,385,384,383,382,381,380,379,378,376,375,374,373,372,371,370,369,368,367,366,365,364,363,362,361,360,359,358,357,356,355,354,353,351,350,349,348,347,346,345,344,343,342,341,340,339,338,337,336,335,334,333,332,331,330,329,328,327,326,321,320,319,318,317,316,315,314,313,312,311,310,309,308,307,305,304,303,302,301,300,299,298,297,296,295,294,293,292,291,290,289,288,287,286,285,284,283,282,281,280,279,278,277,276,275,274,273,272,271,270,269,268,267,266,265,264,263,262,261,260,259,258,257,256,255,254,253,252,251,250,249,248,247,246,245,244,243,242,241,240,239,238,237,236,235,234,233,232,231,230,229,228,227,226,225,224,223,222,221,220,219,218,217,216,215,214,213,212,211,210,209,208,207,206,205,204,203,202,201,200,199,198,197,196,195,194,193,192,191,190,189,188,187,186,185,184,183,182,181,180,179,178,177,176,175,174,173,172,171,170,169,168,167,166,165,164,163,162,161,160,159,158,157,156,155,154,153,152,151,150,149,148,147,146,145,144,143,142,141,140,139,138,137,136,135,134,133,132,131,130,129,128,127,126,125,124,123,122,121,120,119,118,117,116,115,111,110,109,107,106,105,104,103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82,81,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,10,9,8,7,5,4,3,2,1)) ) AS T 受影响的行: 0 时间: 0.180s
很多时候我们都知道要做数据库级别的优化,但在开发过程中往往被忽略掉对这部分的优化,这点很值得注意。
索引查询范围标识:
1.const
如果是根据主键或唯一索引 只取出确定的一行数据。是最快的一种。
2.range
索引或主键,在某个范围内时
3.index
仅仅只有索引被扫描
4.all
全表扫描,最令人心痛
数据库
2019-06-11 12:40:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
前言
上篇文章 Redis闲谈(1):构建知识图谱 介绍了redis的基本概念、优缺点以及它的内存淘汰机制,相信大家对redis有了初步的认识。互联网的很多应用场景都有着Redis的身影,它能做的事情远远超出了我们的想像。Redis的底层数据结构到底是什么样的呢,为什么它能做这么多的事情?本文将探秘Redis的底层数据结构以及常用的命令。
本文知识脑图如下:
一、Redis的数据模型
用 键值对 name:"小明" 来展示Redis的数据模型如下:
dictEntry: 在一些编程语言中,键值对的数据结构被称为字典,而在Redis中,会给每一个key-value键值对分配一个字典实体,就是“dicEntry”。dicEntry包含三部分: key的指针、val的指针、next指针 ,next指针指向下一个dicteEntry形成链表,这个next指针可以将多个哈希值相同的键值对链接在一起, 通过链地址法来解决哈希冲突的问题 sds : Simple Dynamic String ,简单动态字符串,存储字符串数据。 redisObject :Redis的5种常用类型都是以RedisObject来存储的,redisObject中的 type 字段指明了值的数据类型(也就是5种基本类型)。 ptr 字段指向对象所在的地址。
RedisObject对象很重要,Redis 对象的类型 、 内部编码 、 内存回收 、 共享对象 等功能,都是基于RedisObject对象来实现的。
这样设计的好处是:可以针对不同的使用场景,对5种常用类型设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。
Redis将jemalloc作为默认内存分配器,减小内存碎片。jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。
二、Redis支持的数据结构
Redis支持的数据结构有哪些?
如果回答是String、List、Hash、Set、Zset就不对了,这5种是redis的常用基本数据类型,每一种数据类型内部还包含着多种数据结构。
用encoding指令来看一个值的数据结构。比如: 127.0.0.1:6379> set name tom OK 127.0.0.1:6379> object encoding name "embstr"
此处设置了name值是tom,它的数据结构是embstr,下文介绍字符串时会详解说明。 127.0.0.1:6379> set age 18 OK 127.0.0.1:6379> object encoding age "int"
如下表格总结Redis中所有的数据结构类型:
底层数据结构 编码常量 object encoding指令输出 整数类型 REDIS_ENCODING_INT "int"
embstr字符串类型 REDIS_ENCODING_EMBSTR "embstr"
简单动态字符串 REDIS_ENCODING_RAW "raw"
字典类型 REDIS_ENCODING_HT "hashtable"
双端链表 REDIS_ENCODING_LINKEDLIST "linkedlist"
压缩列表 REDIS_ENCODING_ZIPLIST "ziplist"
整数集合
跳表和字典
REDIS_ENCODING_INTSET
REDIS_ENCODING_SKIPLIST
"intset"
"skiplist"
补充说明 假如面试官问:redis的数据类型有哪些?
回答:String、list、hash、set、zet
一般情况下这样回答是正确的,前文也提到redis的数据类型确实是包含这5种,但细心的同学肯定发现了之前说的是**“常用”**的5种数据类型。其实,随着Redis的不断更新和完善,Redis的数据类型早已不止5种了。
登录redis的官方网站打开官方的数据类型介绍:
https://redis.io/topics/data-types-intro
发现Redis支持的数据结构不止5种,而是8种,后三种类型分别是: 位数组(或简称位图):使用特殊命令可以处理字符串值,如位数组:您可以设置和清除各个位,将所有位设置为1,查找第一个位或未设置位,等等。 HyperLogLogs:这是一个概率数据结构,用于估计集合的基数。不要害怕,它比看起来更简单。 Streams:仅附加的类似于地图的条目集合,提供抽象日志数据类型。
本文主要介绍5种常用的数据类型,上述三种以后再共同探索。
2.1 string字符串
字符串类型是redis最常用的数据类型,在Redis中,字符串是可以修改的,在底层它是以字节数组的形式存在的。
Redis中的字符串被称为简单动态字符串「SDS」,这种结构很像Java中的ArrayList,其长度是动态可变的. struct SDS { T capacity; // 数组容量 T len; // 数组长度 byte[] content; // 数组内容 }
content[] 存储的是字符串的内容, capacity 表示数组分配的长度, len 表示字符串的实际长度。
字符串的编码类型有int、embstr和raw三种,如上表所示,那么这三种编码类型有什么不同呢? int 编码 :保存的是可以用 long 类型表示的整数值。 raw 编码 :保存长度大于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。 embstr 编码 :保存长度小于44字节的字符串(redis3.2版本之前是39字节,之后是44字节)。
设置一个值测试一下: 127.0.0.1:6379> set num 300 127.0.0.1:6379> object encoding num "int" 127.0.0.1:6379> set key1 wealwaysbyhappyhahaha OK 127.0.0.1:6379> object encoding key1 "embstr" 127.0.0.1:6379> set key2 hahahahahahahaahahahahahahahahahahahaha OK 127.0.0.1:6379> strlen key2 (integer) 39 127.0.0.1:6379> object encoding key2 "embstr" 127.0.0.1:6379> set key2 hahahahahahahaahahahahahahahahahahahahahahaha OK 127.0.0.1:6379> object encoding key2 "raw" 127.0.0.1:6379> strlen key2 (integer) 45
raw类型和embstr类型对比
embstr编码的结构:
raw编码的结构:
embstr和raw都是由redisObject和sds组成的。不同的是:embstr的redisObject和sds是连续的,只需要使用 malloc 分配一次内存;而raw需要为redisObject和sds分别分配内存,即需要分配两次内存。
所有相比较而言,embstr少分配一次内存,更方便。但embstr也有明显的缺点:如要增加长度,redisObject和sds都需要重新分配内存。
上文介绍了embstr和raw结构上的不同。重点来了~ 为什么会选择44作为两种编码的分界点?在3.2版本之前为什么是39?这两个值是怎么得出来的呢?
1) 计算RedisObject占用的字节大小 struct RedisObject { int4 type; // 4bits int4 encoding; // 4bits int24 lru; // 24bits int32 refcount; // 4bytes = 32bits void *ptr; // 8bytes,64-bit system } type: 不同的redis对象会有不同的数据类型(string、list、hash等),type记录类型,会用到 4bits 。 encoding:存储编码形式,用 4bits 。 lru:用 24bits 记录对象的LRU信息。 refcount:引用计数器,用到 32bits 。 *ptr:指针指向对象的具体内容,需要 64bits 。
计算: 4 + 4 + 24 + 32 + 64 = 128bits = 16bytes
第一步就完成了,RedisObject对象头信息会占用 16字节 的大小,这个大小通常是固定不变的.
2) sds占用字节大小计算
旧版本: struct SDS { unsigned int capacity; // 4byte unsigned int len; // 4byte byte[] content; // 内联数组,长度为 capacity }
这里的 unsigned int 一个4字节,加起来是8字节.
内存分配器jemalloc分配的内存如果超出了64个字节就认为是一个大字符串,就会用到raw编码。
前面提到 SDS 结构体中的 content 的字符串是以字节\0结尾的字符串,之所以多出这样一个字节,是为了便于直接使用 glibc 的字符串处理函数,以及为了便于字符串的调试打印输出。所以我们还要减去1字节 64byte - 16byte - 8byte - 1byte = 39byte
新版本: struct SDS { int8 capacity; // 1byte int8 len; // 1byte int8 flags; // 1byte byte[] content; // 内联数组,长度为 capacity }
这里unsigned int 变成了uint8_t、uint16_t.的形式,还加了一个char flags标识,总共只用了3个字节的大小。相当于优化了sds的内存使用,相应的用于存储字符串的内存就会变大。
然后进行计算:
64byte - 16byte -3byte -1byte = 44byte 。
总结:
所以,redis 3.2版本之后embstr最大能容纳的字符串长度是44,之前是39。长度变化的原因是SDS中内存的优化。
2.2 List
Redis中List对象的底层是由quicklist(快速列表)实现的,快速列表支持从链表头和尾添加元素,并且可以获取指定位置的元素内容。
那么,快速列表的底层是如何实现的呢?为什么能够达到如此快的性能?
罗马不是一日建成的,quicklist也不是一日实现的,起初redis的list的底层是ziplist(压缩列表)或者是 linkedlist(双端列表)。先分别介绍这两种数据结构。
ziplist 压缩列表
当一个列表中只包含少量列表项,且是小整数值或长度比较短的字符串时,redis就使用ziplist(压缩列表)来做列表键的底层实现。
测试: 127.0.0.1:6379> rpush dotahero sf qop doom (integer) 3 127.0.0.1:6379> object encoding dotahero "ziplist"
此处使用老版本redis进行测试,向dota英雄列表中加入了qop痛苦女王、sf影魔、doom末日使者三个英雄,数据结构编码使用的是ziplist。
压缩列表顾名思义是进行了压缩,每一个节点之间没有指针的指向,而是多个元素相邻,没有缝隙。 所以 ziplist是Redis为了节约内存而开发的 ,是由一系列特殊编码的连续内存块组成的顺序型数据结构。具体结构相对比较复杂,大家有兴趣地话可以深入了解。 struct ziplist { int32 zlbytes; // 整个压缩列表占用字节数 int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点 int16 zllength; // 元素个数 T[] entries; // 元素内容列表,挨个挨个紧凑存储 int8 zlend; // 标志压缩列表的结束,值恒为 0xFF }
双端列表(linkedlist)
双端列表大家都很熟悉,这里的双端列表和java中的linkedlist很类似。
从图中可以看出Redis的linkedlist双端链表有以下特性:节点带有prev、next指针、head指针和tail指针,获取前置节点、后置节点、表头节点和表尾节点、获取长度的复杂度都是O(1)。
压缩列表占用内存少,但是是顺序型的数据结构,插入删除元素的操作比较复杂,所以压缩列表适合数据比较小的情况,当数据比较多的时候,双端列表的高效插入删除还是更好的选择
在Redis开发者的眼中,数据结构的选择,时间上、空间上都要达到极致,所以,他们将压缩列表和双端列表合二为一,创建了 快速列表(quicklist) 。和java中的hashmap一样,结合了数组和链表的优点。
快速列表(quicklist) rpush: listAddNodeHead ---O(1) lpush: listAddNodeTail ---O(1) push:listInsertNode ---O(1) index : listIndex ---O(N) pop:ListFirst/listLast ---O(1) llen:listLength ---O(N)
struct ziplist { ... } struct ziplist_compressed { int32 size; byte[] compressed_data; } struct quicklistNode { quicklistNode* prev; quicklistNode* next; ziplist* zl; // 指向压缩列表 int32 size; // ziplist 的字节总数 int16 count; // ziplist 中的元素数量 int2 encoding; // 存储形式 2bit,原生字节数组还是 LZF 压缩存储 ... } struct quicklist { quicklistNode* head; quicklistNode* tail; long count; // 元素总数 int nodes; // ziplist 节点的个数 int compressDepth; // LZF 算法压缩深度 ... }
quicklist 默认的压缩深度是 0,也就是不压缩。压缩的实际深度由配置参数list-compress-depth决定。为了支持快速的 push/pop 操作,quicklist 的首尾两个 ziplist 不压缩,此时深度就是 1。如果深度为 2,表示 quicklist 的首尾第一个 ziplist 以及首尾第二个 ziplist 都不压缩。
2.3 Hash
Hash数据类型的底层实现是ziplist(压缩列表)或字典(也称为hashtable或散列表)。这里压缩列表或者字典的选择,也是根据元素的数量大小决定的。
如图hset了三个键值对,每个值的字节数不超过64的时候,默认使用的数据结构是 ziplist 。
当我们加入了字节数超过64的值的数据时,默认的数据结构已经成为了hashtable。
Hash对象只有同时满足下面两个条件时,才会使用ziplist(压缩列表): 哈希中元素数量小于512个; 哈希中所有键值对的键和值字符串长度都小于64字节。
压缩列表刚才已经了解了,hashtables类似于jdk1.7以前的hashmap。hashmap采用了链地址法的方法解决了哈希冲突的问题。想要深入了解的话可以参考之前写的一篇博客: hashmap你真的了解吗
Redis中的字典
redis中的dict 结构内部包含两个 hashtable,通常情况下只有一个 hashtable 是有值的。但是在 dict 扩容缩容时,需要分配新的 hashtable,然后进行渐进式搬迁,这时两个 hashtable 存储的分别是旧的 hashtable 和新的 hashtable。待搬迁结束后,旧的 hashtable 被删除,新的 hashtable 取而代之。
2.4 Set
Set数据类型的底层可以是 intset (整数集)或者是 hashtable (散列表也叫哈希表)。
当数据都是整数并且数量不多时,使用intset作为底层数据结构;当有除整数以外的数据或者数据量增多时,使用hashtable作为底层数据结构。 127.0.0.1:6379> sadd myset 111 222 333 (integer) 3 127.0.0.1:6379> object encoding myset "intset" 127.0.0.1:6379> sadd myset hahaha (integer) 1 127.0.0.1:6379> object encoding myset "hashtable"
inset的数据结构为: typedef struct intset { // 编码方式 uint32_t encoding; // 集合包含的元素数量 uint32_t length; // 保存元素的数组 int8_t contents[]; } intset; intset底层实现为有序、无重复数的数组。 intset的整数类型可以是16位的、32位的、64位的。如果数组里所有的整数都是16位长度的,新加入一个32位的整数,那么整个16的数组将升级成一个32位的数组。升级可以提升intset的灵活性,又可以节约内存,但不可逆。
2.5 Zset
Redis中的Zset,也叫做 有序集合 。它的底层是ziplist(压缩列表)或 skiplist (跳跃表)。
压缩列表前文已经介绍过了,同理是在元素数量比较少的时候使用。此处主要介绍跳跃列表。
跳表
跳跃列表,顾名思义是可以跳的,跳着查询自己想要查到的元素。大家可能对这种数据结构比较陌生,虽然平时接触的少,但它确实 是一个各方面性能都很好的数据结构,可以支持快速的查询、插入、删除操作,开发难度也比红黑树要容易的多 。
为什么跳表有如此高的性能呢?它究竟是如何“跳”的呢?跳表利用了二分的思想,在数组中可以用二分法来快速进行查找,在链表中也是可以的。
举个例子,链表如下:
假设要找到10这个节点,需要一个一个去遍历,判断是不是要找的节点。那如何提高效率呢?mysql索引相信大家都很熟悉,可以提高效率,这里也可以使用索引。抽出一个索引层来:
这样只需要找到9然后再找10就可以了,大大节省了查找的时间。
还可以再抽出来一层索引,可以更好地节约时间:
这样基于链表的“二分查找”支持快速的插入、删除,时间复杂度都是O(logn)。
由于跳表的快速查找效率,以及实现的简单、易读。所以Redis放弃了红黑树而选择了更为简单的跳表。
Redis中的跳跃表: typedef struct zskiplist { // 表头节点和表尾节点 struct zskiplistNode *header, *tail; // 表中节点的数量 unsigned long length; // 表中层数最大的节点的层数 int level; } zskiplist; typedef struct zskiplistNode { // 成员对象 robj *obj; // 分值 double score; // 后退指针 struct zskiplistNode *backward; // 层 struct zskiplistLevel { // 前进指针 struct zskiplistNode *forward; // 跨度---前进指针所指向节点与当前节点的距离 unsigned int span; } level[]; } zskiplistNode;
zadd---zslinsert---平均O(logN), 最坏O(N)
zrem---zsldelete---平均O(logN), 最坏O(N)
zrank--zslGetRank---平均O(logN), 最坏O(N)
总结
本文大概介绍了Redis的5种常用数据类型的底层实现,希望大家结合源码和资料更深入地了解。
数据结构之美在Redis中体现得淋漓尽致,从String到压缩列表、快速列表、散列表、跳表,这些数据结构都适用在了不同的地方,各司其职。
不仅如此,Redis将这些数据结构加以升级、结合,将内存存储的效率性能达到了极致,正因为如此,Redis才能成为众多互联网公司不可缺少的高性能、秒级的key-value内存数据库。 作者:杨亨
拓展阅读: Redis闲谈(1):构建知识图谱
来源:宜信技术学院
数据库
2019-06-11 10:28:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
项目需要使用Redis来做缓存,研究了一下如何将其与Spring Boot整合。网上的demo要么就是太过于庞大,要么就是版本过于陈旧,配置时候会有各种坑。因此自己在踩过了各种坑之后,写一个小demo来记录下:
1.项目结构:
2.pom的依赖配置:
本人使用的Spring Boot是2.0.4.RELEASE版本的: org.springframework.boot spring-boot-starter-parent 2.0.4.RELEASE
添加redis的依赖:
org.springframework.boot spring-boot-starter-data-redis

添加common-pool2的依赖: org.apache.commons commons-pool2 2.0

我在这里尝试过不加version,让springboot自己选择最合适的版本,结果会报错。参考别人的例子,选择2.0版本,没有问题。
3.修改application.yml:
这里的host填写自己redis的IP, timeout别设置为0,否则运行会报错 。
redis 单机配置 spring: redis: host: port: 6379 lettuce: pool: max-wait: 100000 max-idle: 10 max-active: 100 timeout: 5000

redis 集群配置 redis: database: 0 # 集群设置 begin cluster: nodes: - 10.217.17.70:7000 - 10.217.17.74:7000 - 10.217.17.75:7000 max-redirects: 3 # 获取失败 最大重定向次数 #集群设置 end #单节点 begin # host: 10.217.17.74 # port: 7000 #单节点 end lettuce: pool: max-wait: 100000 max-idle: 10 max-active: 100 timeout: 5000


3.编写dao层 : package com.hg.redis; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Repository; import java.util.concurrent.TimeUnit; /** * @Description: * @Author: jiangfan * @CreateTime 2018/9/27 上午10:14 */ @Repository public class RedisDao { @Autowired private StringRedisTemplate template; public void setKey(String key, String value) { ValueOperations ops = template.opsForValue(); ops.set(key, value); } public String getValue(String key) { ValueOperations ops = this.template.opsForValue(); return ops.get(key); } }


4.修改测试启动类: package com.hg.redis; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) @SpringBootTest public class RedisApplicationTests { @Test public void contextLoads() { } }

5.编写一个测试类: package com.hg.redis; import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; /** * @Description: * @Author: jiangfan * @CreateTime 2018/9/27 上午10:26 */ @RunWith(SpringRunner.class) @SpringBootTest public class SpringbootRedisApplicationTests { public static Logger logger = LoggerFactory.getLogger(SpringbootRedisApplicationTests.class); @Test public void contextLoads(){} @Autowired RedisDao redisDao; @Test public void testRedis() { redisDao.setKey("name","jerry"); redisDao.setKey("age","11"); logger.info(redisDao.getValue("name")); logger.info(redisDao.getValue("age")); } }

6.运行测试类:



解决 key 和 value 序列号的问题
package com.jxlgzwh.brdp.sso.server.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public class RedisConfig { /** * 配置自定义redisTemplate * @return */ @Bean RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值 Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); template.setValueSerializer(serializer); //使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } }


数据库
2019-06-10 20:59:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
触发器 TRIGGER
1、触发器的定义
2、使用的场景
3、掌握触发器的创建语法
4、理解触发器的触发机制
什么叫做触发器?
当一个表中的数据发生改变的时候,会引起其他表中相关数据改变,
编制一个小程序附着在表上,把这种改变自动化执行,成为触发器。
触发器的类型?
在进行insert、update、delete操作时,触发相关的insert、update、delete
触发器触发。分为:insert、update、delete触发器。
==============================
USE bigdata2;
CREATE TABLE goods
(
gid INT NOT NULL PRIMARY KEY,
gname VARCHAR(20),
gnum INT
);
INSERT INTO goods VALUES (119,'香猪',80);
INSERT INTO goods VALUES (120,'臭狗',25);
INSERT INTO goods VALUES (121,'贼猫',34);
SELECT * FROM goods;
CREATE TABLE ords
(oid INT NOT NULL PRIMARY KEY,
gid INT,
gname VARCHAR(20),
onum INT
);
SELECT * FROM ords;
#定义一个insert触发器
DELIMITER $$
CREATE TRIGGER tri_ords_insert
AFTER INSERT
ON ords
FOR EACH ROW
BEGIN
UPDATE goods SET gnum=gnum-2 WHERE gid=120;
END$$
DELIMITER ;
SELECT * FROM goods;
SELECT * FROM ords;
INSERT INTO ords VALUES(1,120,'臭狗',2);
SELECT * FROM goods;
SELECT * FROM ords;
INSERT INTO ords VALUES(2,119,'香猪',30);
====================================
DROP TRIGGER tri_ords_insert;
#优化insert触发器
DELIMITER $$
CREATE TRIGGER tri_kz
AFTER INSERT
ON ords
FOR EACH ROW
BEGIN
UPDATE goods SET gnum=gnum - new.onum WHERE gid=new.gid;
END$$
DELIMITER ;
SELECT * FROM ords;
SELECT * FROM goods;
INSERT INTO ords VALUES(5,121,'贼猫',12);
===================
#定义一个delete触发器
DELIMITER $$
CREATE TRIGGER tri_kz_delete
AFTER DELETE
ON ords
FOR EACH ROW
BEGIN
UPDATE goods SET gnum=gnum+old.onum WHERE gid=old.gid;
END$$
DELIMITER ;
SELECT * FROM goods;
SELECT * FROM ords;
DELETE FROM ords WHERE oid=5;
======================================
#定义一个update触发器
#注意使用new old
DELIMITER $$
CREATE TRIGGER tri_kz_updatetrigger
AFTER UPDATE
ON ords
FOR EACH ROW
BEGIN
IF new.onum>old.onum THEN
UPDATE goods SET gnum=gnum - (new.onum-old.onum)
WHERE gid=new.gid;
ELSE
UPDATE goods SET gnum=gnum + (new.onum-old.onum)
WHERE gid=new.gid;
END IF;
END$$
DELIMITER ;
SELECT * FROM goods;
SELECT * FROM ords;
UPDATE ords SET onum=35 WHERE gid=119;
DROP TRIGGER tri_kz_updatetrigger;
=====================================
#定义一个日志记录表
CREATE TABLE klog
(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
kuser VARCHAR(20),
kdate DATETIME,
kdolist VARCHAR(50)
);
#定义一个insert 触发器在goods表上
DELIMITER $$
CREATE TRIGGER tri_goods_insert
AFTER INSERT
ON goods
FOR EACH ROW
BEGIN
INSERT INTO klog(kuser,kdate,kdolist)
VALUES(USER(),NOW(),CONCAT('插入:',new.gid,new.gname,new.gnum));
END$$
DELIMITER ;
#定义一个delete 触发器在goods表上
DELIMITER $$
CREATE TRIGGER tri_goods_delete
AFTER DELETE
ON goods
FOR EACH ROW
BEGIN
INSERT INTO klog(kuser,kdate,kdolist)
VALUES(USER(),NOW(),'删除一条记录....');
END$$
DELIMITER ;
#定义一个udpate触发器在goods表上
DELIMITER $$
CREATE TRIGGER tri_goods_udpate
AFTER UPDATE
ON goods
FOR EACH ROW
BEGIN
INSERT INTO klog(kuser,kdate,kdolist)
VALUES(USER(),NOW(),'update一条记录....');
END$$
DELIMITER ;
SELECT * FROM klog;
SELECT * FROM goods;
#插入一条数据,触发插入触发器
INSERT INTO goods VALUES(130,'土贼',130);
SELECT * FROM goods;
SELECT * FROM klog;
#更改一条数据,触发update触发器
UPDATE goods SET gname='土狗土狗' WHERE gid=130;
SELECT * FROM goods;
SELECT * FROM klog;
#删除一条数据,触发delete触发器
DELETE FROM goods WHERE gid=130;
SELECT * FROM goods;
SELECT * FROM klog;
INSERT INTO goods VALUES(139,'丧尸',23);
SELECT * FROM goods;
SELECT * FROM klog;
#总结:
#1、触发器是定义在表上,被数据操作语句触发执行
#2、在insert 触发器上有new 对象的使用
#在delete 触发器上有old 对象的使用
#在update 触发器上有new 、 old 对象的使用
#3、每个表上,只有一个insert的触发器,不可能有两个insert 的触发器,
#对应的delete 、update 触发器也是同样的道理
数据库
2019-06-10 17:25:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
拔掉数据库的电源会怎样?
假设我们拔掉数据库的电源会怎样?
在日前举行的阿里云“企业级”云灾备解决方案发布会上,阿里云智能技术战略总监陈绪就来了一场现场 “断电”演示 ,拔掉了数据库的电源。
( 直播回放: https://yq.aliyun.com/live/1104/event )
猜猜现场发生了什么?
数据丢失,业务瘫痪,企业资金受损?
企业级云灾备解决方案 实时大屏
上述情况统统没有出现!没有出现任何数据丢失,也没有业务瘫痪,10秒后,上云企业的业务就完全恢复了。
那么这是如何实现的呢?
在会上,阿里云智能数据库产品事业部技术总监天羽为大家全面解析 《云时代,数据库新型灾备架构》 ,详细介绍了在混合云架构下,从异地备份、容灾、双活到统一管理的数据库一站式灾备解决方案。
有了云数据库新型灾备架构,即使断电又何妨?通过阿里云提供的 DBS 、 DTS 、 HDM 等服务,您的企业也可轻松构建灾备体系,做到“有备无患”。
墨菲定律 鸡蛋不能放在一个篮子里
对于每个企业而言,数据库都是其最为核心的资产。但是单点故障是不可避免的,因此为了提升数据安全,需要做的就是数据冗余。
国家对于数据库灾难恢复能力也定义了相应的标准。对于位于等级2~3的一般业务而言,需要每天进行备份;对于位于等级4的重要业务而言,需要每天全量+增量备份;对于等级5的关键业务而言,要求数据丢失不能超过半个小时,并且要求在分钟级别恢复业务;对于位于等级6的核心业务而言,则需要做到数据零丢失。
阿里巴巴数据库从备份到多活的发展经过了以下历程:
2012年 之前,阿里巴巴采用的是异地冷备+热备方案,提供只读副本,当时异地冷备和热备可能出现异地延时比较长的问题,导致出现灾难之后敢不敢进行数据库切换成为一个问题,可能现在很多传统企业还在使用该方案。
2013年 ,阿里巴巴通过数据库实时日志的解析能力实现了同城双活。
2014年 ,阿里巴巴实现了异地双活。
2015年 ,阿里巴巴就实现了中美同步以及多个地域、多点写入的数据同步策略。
2016年 ,阿里巴巴实现了分布式数据强一致的能力以及异地多活能力。
在不断提升阿里巴巴灾备能力的过程中,我们也在阿里云上孵化了 数据库备份(DBS)、数据传输(DTS)、混合云数据库管理(HDM) ,搭建从备份、容灾、双活及混合云统一管理的一站式云灾备解决方案。
对于等级1到等级4的业务而言,可以通过DBS将数据实时备份到阿里云OSS上,该方案具有低成本、秒级RPO的优势;
对于等级5的业务而言,可以通过DTS数据传输服务将本地IDC或者其他云产商的数据库备份到阿里云上去,实现热备或者双活解决方案,实现秒级RPO和秒级RTO。
阿里云数据库新型灾备方案
众所周知,传统灾备解决方案存在成本高昂、实施困难、运维复杂、RTO和RPO无法保障等问题。
阿里云拥有遍布全球安全可靠的数据中心,是企业用户天然的异地灾备中心。阿里云的新型灾备方案可以为您提供低成本、高质量、开箱即用的数据库灾备服务。
1、 数据库备份服务DBS
数据库备份服务DBS结合阿里云对象存储服务OSS,能够为用户提供秒级RPO以及低成本的特性,并且实现了国家灾备等级4的相应能力。
用户自建的IDC或者来自其他云厂商的数据库可以通过DBS备份到阿里云OSS之上,而且整个备份的实现过程非常简单,只需要打通网络就可以通过DBS实现数据备份到云上,当出现灾难的时候就能够完成云上数据库快速恢复。
除了和云上数据库进行打通之外,对于数据的备份集而言,也可以通过数据湖服务直接进行查询和验证(无需恢复),这也是阿里云特有的能力之一。
阿里云数据库备份服务DBS主要有如下优势:
秒级RPO:因为数据库发生变更的时候,首先会记录日志,再刷新数据。而阿里巴巴沉淀了一整套数据库解析技术,通过这个技术能够实现秒级冷备到阿里云上的能力,并且其冷备数据和在线数据之间仅存在秒级延时。 低成本:借助OSS的能力可以实现对于数据的周期性归档,并且允许数据库只备份核心关注的数据业务表,仅备份有效数据,同时进行加密和压缩。 备份数据可在线读,验证有效性:基于DLA的数据湖能力,备份逻辑数据集允许用户直接进行备份集查询,查询里面的数据内容并且校验其中的数据。基于RDS的能力能够帮助用户在出现灾难时实现数据库的快速恢复。 丰富的备份数据源:阿里云数据库备份服务DBS能够支持非常丰富的数据源,包括Oracle、MySQL、SQLServer、MongoDB以及Redis等。
2、数据库热备以及双活架构DTS
结合DTS和RDS就能够实现云上数据库热备,可以实现国标等级5的灾备能力。无论是将业务中心建立在自建IDC还是其他云厂商上,通过DTS热备到阿里云上,当出现本地IDC出现数据库故障或者误操作的时候,用户就可以一键切换到云热备之上,实现秒级RPO和秒级RTO。
您还可以更进一步,借助DTS和RDS实现多活,除了将业务切换到阿里云上之外,还可以反向建立阿里云到本地IDC数据库的同步链路,从而建立双向同步通道,这样就能够提供异地双活能力,两端都可以进行写入和切换。业务也可以在云上和本地IDC之间进行分流,从而实现就近写入和就近服务的查询能力,同时能够支持实现容灾。
如果采用传统热备方案,将数据热备到云上之后可以支持实现秒级RPO的数据库切换,但是当切换完成之后如果想要去恢复灾备系统,则需要一定的恢复过程,但是当建立了双向同步通道之后,可以很快地切换到阿里云,同时很快地切换回来,因此能够支持企业实现在线的容灾演练。
关于阿里云数据库传输服务DTS:
阿里巴巴在2011年左右开始投入做数据库的日志解析,而DTS除了能够实现日志解析之外,还能够实现高效的数据同步,是阿里巴巴内部实现异地多活的基础设施,也是阿里巴巴的数据从生产到消费的数据流基础设施。
DTS也支持了非常丰富的数据源,包括关系型数据库、NoSQL及大数据等17种数据源,承担了阿里云上的40多万的数据传输任务。
3、基于DMS+HDM的数据库统一管理方案
除了上述的DBS和DTS两款灾备产品之外,当用户使用线下到线上的数据同步或者线下到线上数据热备之后,就会形成一个混合云数据库架构。
阿里云为此提供了一整套 数据库混合云统一管理解决方案 ,该方案沉淀了阿里在脱敏审计、变更管控以及研发协同等多方面的能力。
在混合云上,如果数据库分布在自建的IDC、其他云厂商以及阿里云上,就可以通过阿里云的混合云数据库管理(HDM)进行统一管理,通过One Console实现统一监控、告警、性能优化和风险识别。
了解 企业级云灾备解决方案——“十万先行者计划” ,请点击:
https://promotion.aliyun.com/ntms/act/hclouddr/index.html
相关阅读
阿里云发布企业级云灾备解决方案,十万先行者计划开启普惠灾备
专访阿里数据库备份专家 教你Pick最有效的备份系统
作者:七幕
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-06-10 13:06:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
一、LVM简介
  LVM是逻辑盘卷管理(Logical Volume Manager)的简称,它是Linux环境下对磁盘分区进行管理的一种机制,LVM是建立在硬盘和分区之上的一个逻辑层,来提高磁盘分区管理的灵活性。
  LVM最大的特点就是可以对磁盘进行动态管理。因为逻辑卷的大小是可以动态调整的,而且不会丢失现有的数据。如果我们新增加了硬盘,其也不会改变现有上层的逻辑卷。作为一个动态磁盘管理机制,逻辑卷技术大大提高了磁盘管理的灵活性。如果期望扩容云盘的IO能力,则可以通过将多块容量相同的云盘做RAID0。
图1:LVM逻辑示意图(图片来自于互联网)
二、创建LVM卷
2.1步骤一 创建物理卷PV
  如下以5块云盘通过LVM创建弹性可扩展逻辑卷为例。 root@lvs06:~# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT vda 252:0 0 40G 0 disk └─vda1 252:1 0 40G 0 part / vdb 252:16 0 1T 0 disk vdc 252:32 0 1T 0 disk vdd 252:48 0 1T 0 disk vde 252:64 0 1T 0 disk vdf 252:80 0 1T 0 disk step1 : 以root账号登录云服务器 step2 :执行以下命令,为云盘创建PV卷 pvcreate <磁盘路径1> ... <磁盘路径N>
说明 :此处需要填写云盘的设备名称,如果需要添加多个云盘,则可以添加多云盘设备名称,中间以空格间隔。如下以/dev/vdb, /dev/vdc,/dev/vdd,/dev/vde,/dev/vdf为例,执行结果如下: root@lvs06:~# pvcreate /dev/vdb /dev/vdc /dev/vdd /dev/vde /dev/vdf Physical volume "/dev/vdb" successfully created. Physical volume "/dev/vdc" successfully created. Physical volume "/dev/vdd" successfully created. Physical volume "/dev/vde" successfully created. Physical volume "/dev/vdf" successfully created. step3 :执行以下命令,查看该服务器上物理卷(PV)信息: lvmdiskscan | grep LVM
执行结果如下: root@lvs06:~# lvmdiskscan | grep LVM /dev/vdb [ 1.00 TiB] LVM physical volume /dev/vdc [ 1.00 TiB] LVM physical volume /dev/vdd [ 1.00 TiB] LVM physical volume /dev/vde [ 1.00 TiB] LVM physical volume /dev/vdf [ 1.00 TiB] LVM physical volume 5 LVM physical volume whole disks 0 LVM physical volumes
2.2步骤二 创建卷组(VG) step1 :执行以下命令,创建卷组(VG) vgcreate <卷组名> <物理卷路径1>……<物理卷路径N>
执行结果如下: root@lvs06:~# vgcreate lvm_01 /dev/vdb /dev/vdc /dev/vdd /dev/vde /dev/vdf Volume group "lvm_01" successfully created
说明 : 1.卷组名:该参数可自定义 2.物理卷路径:此处填写云盘的物理卷名称,多个物理卷直接以空格间隔 3.当提示 “Volume group XXX successfully created”标识卷组创建成功;
- step2 :执行以下命令,可以向卷组(VG)中添加物理卷(PV) vgextend 卷组名称 <物理卷路径1>……<物理卷路径N>
如下,向卷组(VG)lvm_01中添加一块新的物理卷: root@lvs06:~# pvcreate /dev/vdg Physical volume "/dev/vdg" successfully created. root@lvs06:~# vgextend lvm_01 /dev/vdg Volume group "lvm_01" successfully extended step3 :创建卷组(VG)成功后,可通过vgs,vgdisplay命令查看卷组信息 root@lvs06:~# vgs VG #PV #LV #SN Attr VSize VFree lvm_01 6 0 0 wz--n- <6.00t <6.00t
2.3步骤三 创建逻辑卷(LV) step1 :执行以下命令创建逻辑卷(LV) lvcreate [-L <逻辑卷大小>][ -n <逻辑卷名称>] <卷组名称>
参数说明 : 1.逻辑卷大小:逻辑卷的大小应小于卷组(VG)剩余可用空间,单位可以选择MB、GB或者TB 2.逻辑卷名称:可自定义 3.卷组名称:此处填写逻辑卷所在的卷组名称
本文以创建1个4TB的逻辑卷(LV)为例,执行结果如下: root@lvs06:~# lvcreate -L 5T -n lv01 lvm_01 Logical volume "lv01" created. step2 :执行lvdisplay命令查看,逻辑卷详细信息: root@lvs06:~# lvdisplay --- Logical volume --- LV Path /dev/lvm_01/lv01 LV Name lv01 VG Name lvm_01 LV UUID svB00x-l6Ke-ES6M-ctsE-9P6d-dVj2-o0h3Kz LV Write Access read/write LV Creation host, time lvs06, 2019-06-06 15:27:19 +0800 LV Status available # open 0 LV Size 5.00 TiB Current LE 1310720 Segments 6 Allocation inherit Read ahead sectors auto - currently set to 256 Block device 253:0
2.4步骤四 创建并挂载文件系统 step1 :执行以下命令,在创建好的逻辑卷(LV)上创建文件系统 mkfs.文件系统格式 逻辑卷路径
针对上一步中的逻辑卷创建ext4文件系统,执行结果如下: root@lvs06:~# mkfs.ext4 /dev/lvm_01/lv01 mke2fs 1.44.1 (24-Mar-2018) Creating filesystem with 1342177280 4k blocks and 167772160 inodes Filesystem UUID: 2529002f-9209-4b6a-9501-106c1145c77f Superblock backups stored on blocks: 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208, 4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968, 102400000, 214990848, 512000000, 550731776, 644972544 Allocating group tables: done Writing inode tables: done Creating journal (262144 blocks): done Writing superblocks and filesystem accounting information: done step2 :执行以下命令挂载文件系统: mount 逻辑卷路径 挂载点
执行结果如下: root@lvs06:~# mount /dev/lvm_01/lv01 /media/lv01 root@lvs06:~# df -h Filesystem Size Used Avail Use% Mounted on udev 12G 0 12G 0% /dev tmpfs 2.4G 3.7M 2.4G 1% /run /dev/vda1 40G 3.6G 34G 10% / tmpfs 12G 0 12G 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock tmpfs 12G 0 12G 0% /sys/fs/cgroup tmpfs 2.4G 0 2.4G 0% /run/user/0 /dev/mapper/lvm_01-lv01 5.0T 89M 4.8T 1% /media/lv01
三、进阶场景
3.1扩展逻辑卷以及系统容量 Step1 :执行以下命令,可扩展逻辑卷的容量 lvextend [-L +/- <增减容量>] <逻辑卷路径>
参数说明 : 1.增减容量:当卷组中可剩余容量时 ,可以执行扩容逻辑卷操作。扩容逻辑卷之后还需要扩容对应的文件系统才能生效; 2.逻辑卷路径:此处填写带扩容的逻辑卷路径
如下针对/dev/lvm_01/lv01 卷再扩容500GB物理空间,执行结果如下: root@lvs06:~# lvextend -L +500GB /dev/lvm_01/lv01 Size of logical volume lvm_01/lv01 changed from 5.00 TiB (1310720 extents) to <5.49 TiB (1438720 extents). Logical volume lvm_01/lv01 successfully resized. step2 :执行pvs命令,查看物理卷(pv)使用情况: root@lvs06:~# pvs PV VG Fmt Attr PSize PFree /dev/vdb lvm_01 lvm2 a-- <1024.00g 0 /dev/vdc lvm_01 lvm2 a-- <1024.00g 0 /dev/vdd lvm_01 lvm2 a-- <1024.00g 0 /dev/vde lvm_01 lvm2 a-- <1024.00g 0 /dev/vdf lvm_01 lvm2 a-- <1024.00g 0 /dev/vdg lvm_01 lvm2 a-- <1024.00g <523.98g step3 :执行以下resize2fs命令扩容文件系统: resize2fs 逻辑卷路径
执行结果如下: root@lvs06:~# resize2fs /dev/lvm_01/lv01 resize2fs 1.44.1 (24-Mar-2018) Filesystem at /dev/lvm_01/lv01 is mounted on /media/lv01; on-line resizing required old_desc_blocks = 640, new_desc_blocks = 703 The filesystem on /dev/lvm_01/lv01 is now 1473249280 (4k) blocks long. step4 :执行df-h名称,查看文件系统扩容情况 root@lvs06:~# df -h Filesystem Size Used Avail Use% Mounted on udev 12G 0 12G 0% /dev tmpfs 2.4G 3.7M 2.4G 1% /run /dev/vda1 40G 3.6G 34G 10% / tmpfs 12G 0 12G 0% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock tmpfs 12G 0 12G 0% /sys/fs/cgroup tmpfs 2.4G 0 2.4G 0% /run/user/0 /dev/mapper/lvm_01-lv01 5.5T 83M 5.2T 1% /media/lv01
作者:小盆友开飞机
原文链接 ​
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-06-10 12:36:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
用AdoQuery从脚本文件读入内容,执行时提示“不正常地定义参数对象”,“提供了不一致或不完整的信息”之类的错误,本以为是SQL语句有问题,用查询分析器执行时没有发现错误,一切OK。但一到Delphi中执行就提示“不正常地定义参数对象”,“提供了不一致或不完整的信息”。用Google对关键字进行检索,发现有提示,说是由于TADOQuery对象把":"后的字符当作变量来使用,导致数据识别错误。只要把 TADOQuery.ParamCheck设置为False即可。一试,果然如此。
数据库
2019-06-10 09:37:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式(不是数据结构,而是存储结构),具体细节依赖于其实现方式,但innodb的聚簇索引实际上是在同一个结构中保存了BTree(B-Tree ,B+Tree)索引和数据行。
https://blog.csdn.net/u013235478/article/details/50625677 这个链接里介绍了BTree索引
聚簇索引(clustered index)也称为聚集索引,聚类索引,簇集索引,聚簇索引确定表中数据的物理顺序。
聚集索引:类似字典的拼音目录。表中的数据按照聚集索引的规则来存储的。就像新华字典。整本字典是按照A-Z的顺序来排列。这也是一个表只能有一个聚集索引的原因。因为这个特点,具体索引应该建在那些经常需要order by,group by,按范围取值的列上。因为数据本身就是按照聚集索引的顺序存储的。不应该建在需要频繁修改的列上,因为聚集索引的每次改动都以为这表中数据的物理数据的一次重新排序。

聚集索引默认是按照每张表的主键构造一棵B+Tree(平衡查询树),树中的叶子节点存放着表中的行记录数据,因此,也将聚集索引的叶子节点称为数据页;非叶子节点中存放着仅仅是键值和指向叶子节点的偏移量。每个叶子节点(数据页)都通过一个双向链表进行连接。
由于实际的数据页只能按照一棵B+树进行排序,因此数据库中每张表只能有一个聚集索引。
聚集索引能过特别快的访问针对范围值的查询。

innodb将通过主键聚集数据,如果没有定义主键,Innodb会选择第一个非空的唯一索引代替,如果没有非空唯一索引,Innodb会隐式定义一个6字节的rowid主键来作为聚集索引。innodb只聚集在同一个页面中的记录,包含相邻键值的页面可能会相距甚远。
要注意:聚簇主键可能对性能有帮助,但也可能导致严重的性能问题,尤其是将表的存储引擎从innodb转换成其他引擎的时候。
数据库
2019-06-10 00:17:00
「深度学习福利」大神带你进阶工程师,立即查看>>> 通过上一篇基本锁的介绍,基本上Mysql的基础加锁步奏,有了一个大概的了解。接下来我们进入最后一个锁的议题:间隙锁。间隙锁,与行锁、表锁这些要复杂的多,是用来解决幻读的一大手段。通过这种手段,我们没必要将事务隔离级别调整到序列化这个最严格的级别,而导致并发量大大下降。读取这篇文章之前,我想,我们要首先还是读一下我先前的两篇文章,否则一些理念还真的透彻不了: Mysql心路历程:两个"log"引发的"血案" Mysql心路历程:Mysql各种锁机制(入门篇)
一、基础测试表与数据
为了进行整个间隙锁的深入,我们要构建一些基础的数据,本章我们都会用构建的基础数据来进行,下面是数据表与索引的建立: create table `t` ( `id` int(11) not null, `c` int(11) DEFAULT null, `d` int(11) DEFAULT null, primary key (`id`), key `c` (`c`) ) ENGINE=InnoDB;
然后我们插入一些基础的数据: insert into t values (0,0,0), (5,5,5), (10,10,10), (15,15,15), (20,20,20), (25,25,25);
另外,我们本次讲解,都是使用默认的数据库隔离级别: 可重复读
二、什么叫幻读
好,这个问题,就很关键了!我们来细说,幻读的具体出现的经典场景。其实很简单,先看下面的具体的复现sql语句:
sessionA sessionB sessionC t1 begin;
select * from t where d = 5 for update;
t2 update t set d = 5 where id = 0;
t3 select * from t where d = 5 for update;
t4
t5 t6
select * from t where d = 5 for update; commit;

insert into t values(1,1,5)
针对这一系列的操作,我们来一个个分析: sessionA在t1时刻,可见读的结果是:(5,5,5),d没有索引,所以是全表扫描,对id为5的那一行,加行锁的写锁 由于sessionB再t2时刻,将id为0的数据改了下,所以t3时刻,sessionA的可见读的结果是:(0,0,5),(5,5,5) 由于sessionC再t4时刻,插入了条不存在的数据,所以t6时刻,sessionA的可见读结果是:(0,0,0)(1,1,5)(5,5,5) 如果,我们不添加for update进行可见读,普通的一致性读的情况下,由于mvcc的创建快照机制的影响,sessionA一直都会只看到(5,5,5)这一条数据 update之后,可见读查出来的多一条数据,并不是幻读, 只有插入之后的可见读,多读出来的数据,才叫幻读 。就好比我们本来有两条原始数据,可是在事务的没结束之前的前后去读,分别读出来2条和3条,多出一条,就好像我在之后读出的3条数据,是幻影一样,突然出现了,所以叫幻读。 虽然我们平时几乎不会使用select for update进行查询,但是,要记住,update语句之前就是要进行一次for update的select查询的!
三、幻读会有什么影响
大概上,有两个影响,如下。
1、语义冲突 select * from t where d = 5 for update;
类似的,我们这条语句,其实语义上面是想锁住所有d等于5的行数据,让其不能update和insert。然而,我们接下来的sessionB和sessionC里面,如果没有相关的解决幻读的机制存在,那么都会有问题: -- sessionB增加点操作 update t set d = 5 where id = 0; update t set c = 5 where id = 0;
可见第二条sql已经操作了id等于0,d等于5这一行的数据,与之前的锁所有等于5的行语义上面冲突。
2、数据一致性问题
这个很关键,涉及到binglog问题。下面是我们具体操作的sql表格:
sessionA sessionB sessionC t1 begin;
select * from t where d = 5 for update;
update t set d = 100 where d = 5;
t2 update t set d = 5 where id = 0;
update t set c = 5 where id = 0;
t3 select * from t where d = 5 for update;
t4 insert into t values(1,1,5);
t5 t6
select * from t where d = 5 for update; commit;

update t set c = 5 where id = 1;
由于,binglog是要等commit之后,才会记录的(后面文章会有细节的讲解),所以,上面这一系列的sql操作,到了binglog里面会变成下面的样子: update t set d=5 where id=0; /*(0,0,5)*/ update t set c=5 where id=0; /*(0,5,5)*/ insert into t values(1,1,5); /*(1,1,5)*/ update t set c=5 where id=1; /*(1,5,5)*/ update t set d=100 where d=5;/* 所有 d=5 的行,d 改成 100*/
可以看到,由于我们前面说,只对id等于5这一行,加了行锁,所以sessionB的操作是可以进行的,所以,最终会发现,我们sessionA里面的update操作,是最后执行的,如果拿着这个binglog同步从库的话,必然会导致,(0,5,100)、(1,5,100) 和 (5,5,100)这种数据出现,和主库完全不一致!(主库里面,只有id为5的数据,d才为100)。
那么我们将所有扫秒到的数据行都加了锁,会如何呢?那么,sessionB里面的第一条update语句将被阻塞,binglog里面的数据如下: insert into t values(1,1,5); /*(1,1,5)*/ update t set c=5 where id=1; /*(1,5,5)*/ update t set d=100 where d=5;/* 所有 d=5 的行,d 改成 100*/ update t set d=5 where id=0; /*(0,0,5)*/ update t set c=5 where id=0; /*(0,5,5)*/
这样的结果,id为0的这一行的数据,的确能保证数据的一直性,但是,会发现,刚刚插进去的id为1的这一样,在主库里面,d的值为5,但是在从库里面执行了binglog之后,会变成100,又会有不一致的情况出现了!
四、初入"间隙锁"
针对幻读问题,我们日常理论中经常"背诵"的,是:第三事务隔离级别会出现幻读情况,只有通过提高隔离级别,到最高级别的串行化,能解决幻读这样的问题。但是这样,每一个时刻只能有一个线程操作同一个表,并发性大大的降低,根本无法满足,高并发的需求,要知道,Mysql这东西,可是各大顶级互联网公司趋之若鹜的基础数据库,怎么能效率这么差呢?在这里,Mysql就引入了间隙锁的概念。下面我们来看看,间隙锁如何加锁。
1、间隙锁的产生
首先,如果我们使用下面语句进行查询: select * from t where d = 5 for update;
这样,由于d是没有索引的,那么会走全表查询,默认走的是id的主键索引,按照id的主键值,会产生如下的区间:
2、如何加间隙锁
例如上面的select语句中,d是没有索引的,所以通过id索引进行全表扫面,又因为是for update,那么,会将表中仅有的六条数据,都加上行锁,然后,针对上面的六个区间,也会加上 间隙锁 。行锁+间隙锁就是我们喜闻乐见的:next-key lock了!所以,整体上看也就是7个next-key lock:
这个+∞是可以进行配置的,给每个索引分配一个不存在的值
3、间隙锁的特性
前面的文章,我们似乎聊过行锁之间的互斥形式:
读锁 写锁
读锁 写锁
兼容 冲突
冲突 冲突
但是间隙锁不是。 和间隙锁冲突的,是往这个间隙里面插入一条数据 !这一点也是很好的保持并发性的一个挽回。下面看一个操作:
sessionA sessionB begin;
select * from t where c = 7 lock in share model;

begin;
select * from t where c = 7 for update;
虽然,两个事务,都是真对同一条数据,进行可见读的查询,但是并不会阻塞!因为c没有7的这个值,那结果就是,只会在数据库里面加上了(5,10)这个间隙锁,两个可见读并不会因为间隙锁和互斥冲突!
如果这样,加上间隙锁的特性,和行锁的特性,针对上面章节的sql操作:
sessionA sessionB sessionC t1 begin;
select * from t where d = 5 for update;
update t set d = 100 where d = 5;
t2 update t set d = 5 where id = 0; (阻塞)
update t set c = 5 where id = 0;
t3 select * from t where d = 5 for update;
t4 insert into t values(1,1,5); (阻塞)
t5 t6
select * from t where d = 5 for update; commit;

update t set c = 5 where id = 1;
最终生成的binglog就会是: update t set d=100 where d=5;/* d 改成 100*/ insert into t values(1,1,5); /*(1,1,5)*/ update t set c=5 where id=1; /*(1,5,5)*/ update t set d=5 where id=0; /*(0,0,5)*/ update t set c=5 where id=0; /*(0,5,5)*/
这样,就解决了数据一致性的问题了,主从库里面都能保持一致。
4、间隙锁带来的问题
虽然,间隙锁能比较好的解决上诉我们探讨的问题,但是同时也会带来些麻烦,要我们特别的注意。例如下面的操作,是一段业务上面的伪代码: tx.beginTransaction(); var t = select * from t where id = 9 for update; if(t){ update t set d = 45 where id = 9; }else{ insert into t values(9,5,45); } tx.commit();
(假设id等于9这一行不存在)这段业务逻辑代码,普通情况下,我也经常看到,问题不太会出现,一旦并发量上去了,就会出问题,会造成死锁,下面我们看看造成死锁的sql执行序列:
sessionA sessionB t1 begin;
select * from t where id = 9 for update;
begin;
t2 select * from t where id = 9 for update;
t3
insert into t values(9,5,45); (阻塞,等待sessionB的(5,10)的间隙锁释放,死锁!)
insert into t values(9,5,45);(阻塞,等待sessionA的(5,10)的间隙锁释放)
当然,InnoDB的自动死锁检测,会发现这一点,主动将sessionA回滚,报错!
五、晋级"间隙锁"
有关于间隙锁,是最后一层级的细节所在,所以在判断是否加、怎么加、是否会阻塞方面,有非常多的考量。接下来我们来分别来说一下4个细节,分别对应4个例子,来讲讲,首先我们列出五条规则: 加锁的基本单位是next-key lock,就是针对扫描过的数据进行加间隙锁 索引上进行等值查询时,给唯一索引加锁的时候,next-key lock退化为行锁 索引上进行等值查询时,向右遍历,最后一个数值不满足等值的条件的时候,next-key lock退化为间隙锁,就是前后都是开区间 唯一索引的范围查询,会访问到第一个不满足的条件为止
1、第一条规则 加锁的基本单位是next-key lock,就是针对扫描过的数据进行加间隙锁
先来看看几个sql语句: select * from t where id = 5 for update; select * from t where id = 10 lock in share model;
两个分别对5和10这两行加了写锁与读锁,但是最开始,再索引树上面,首先加载id为5和10的这两行的时候,加锁步骤如下: 加(0,5)和(5,10)这两个间隙锁 加5的这一行的行锁(写锁),加10这一行的行锁(读锁) 所以目前为止,基础加锁的单位为next-key lock
2、第二条规则 索引上进行等值查询时,给唯一索引加锁的时候,next-key lock退化为行锁
还是第一条规则的两天语句,发现,id是主键索引(唯一索引),所以去掉了(0,5)(5,10)的这两个间隙锁,所以整个next-key lock变成了单纯的行锁
3、第三条规则 索引上进行等值查询时,向右遍历,最后一个数值不满足等值的条件的时候,next-key lock退化为间隙锁,就是前后都是开区间
先来看看下面的操作过程:
sessionA sessionB sessionC begin;
update t set d = d+1 where id = 7;

insert into t values (8,8,8);(阻塞!)
update t set d = d+1 where id = 10;(成功)
我们来分析: update之前会进行select for update操作,所以就是对id为7的这一行进行可见读 由于7这行记录不存在,但是7落在了(5,10)这个区间,而根据第一条原则,加锁基本单位是next-key lock,所以加锁会加上(5,10)的间隙锁,和10这一行的行锁(写锁),就是(5,10] 由于最后一条记录10和等值查询中的7并不相等,所以退化成了间隙锁,10这个的行锁解除。
所以根据这个规则,(5,10)这个区间是被锁住额,所以insert会被阻塞,另外10这一行的行锁解除,所以sessionC中的update会成功。
4、第四条规则 唯一索引的范围查询,会访问到第一个不满足的条件为止
看看下面的操作序列:
sessionA sessionB sessionC begin;
select * from t where id > 10 and id <=15 for update;

update t set d = d+1 where id = 20;(阻塞)
insert into t values(16,16,16);(阻塞)
分析: 由于10不是等值,15是等值,所以10这一条不会加next-key lock,15会,所以首先加上了(10,15] 虽然是唯一索引,但是是区间查询,并不会停止加锁的脚步,会继续向右 找到20这条记录,加上了next-key lock的(15,20] 由于不是等值查询,是范围查询,所以应用不了规则三,所以最终形成的锁是:(10,15],(15,20]
这么一看,20这一行被行锁锁住,而且15,20的区间还有间隙锁,所以sessionB和sessionC的操作才会阻塞。
5、其他方面的细节
每次加锁,其实都是锁索引树。众所周知,InnoDB的非主键索引的最终叶子节点,都只存储id主键值,然后还要遍历id主键索引,才能搜索出整条的数据,我们通常将这个过程称之为:回表。当然,如果select的是一个字段,这个字段刚好是id,那么Mysql就不用进行回表查询,因为直接在索引树上就能读取到值,MySQL会进行这种优化,通常我们称之为:索引下推。根据这个特性,我们来看看下面的操作序列:
sessionA sessionB sessionC begin;
select id from t where c = 5 lock in share model;

update t set d = d+1 where id = 5;(成功)
insert into t values(3,3,3);(阻塞)
只在c这个非唯一索引上,加了行读锁,基础的加锁单位是(0,5],由于是非唯一索引的查询,并不能退化为行锁 由于非唯一索引,要继续往下,加上了(5,10]这一个的next-key lock,由于最右边的最后一个值,和等值查询并不相等,所以退化成间隙锁(5,10),所以sessionC会被阻塞 由于sessionA中的可见读是读锁,并且只查询id的值,所以启动了索引下推优化,只会加c这个索引上面的行锁。如果换成for update,那就会顺便将主键索引上面也加上锁。所以这里要分清两种行锁的粒度。 所以,最后,sessionB能成功的愿意是:主键索引上并没有加锁
六、结束
锁,在我的能力范围能,能说的就这么多,具体还是要用于实践。接下来,打算写很重要的两个日志文件的介绍:binglog和redolog
数据库
2019-06-09 22:16:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
通常在训练机器学习模型的时候,我们会将数据划分为训练集、验证集和测试集。一般来说,训练集:验证集:测试集的划分比例为6:2:2。对原始数据进行三个集合的划分,是为了能够选出效果(可以理解为准确率)最好的、泛化能力最佳的模型。
训练集(Training Set)
作用是用来拟合模型,通过设置分类器的参数,训练分类模型。后续结合验证集作用时,会选出同一参数的不同取值,拟合出多个分类器。
验证集(Cross ValidaDon Set)
作用是当通过训练集训练出多个模型后,为了能找出效果最佳的模型,使用各个模型对验证集数据进行预测,并记录模型准确率。
选出效果最佳的模型所对应的参数,即用来调整模型参数,如svn中的参数c和核函数等。在交叉验证过程中也需要设置验证集,如k-折交叉验证(k-fold crossValidation)。
测试集(Test Set)
通过训练集和验证集得出最优模型后,使用测试集进行模型预测,用来衡量该最优模型的性能和分类能力。即可以把测试集当做从来不存在的数据集,当已经确定模型参数后,使用测试集进行模型性能评价。
所以说,训练集(Training Set)是用来训练模型或确定模型参数的,如ANN中权值等; 验证集(Validation Set)是用来做模型选择(model selection),即做模型的最终优化及确定的,如ANN的结构;而测试集(Test Set)则纯粹是为了测试已经训练好的模型的泛化能力。
数据库
2019-06-09 11:13:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
做一次日志切换
ALTER SYSTEM SWITCH LOGFILE;
-- 然后看看alert里面的记录
Mon Aug 4 22:31:39 2008
Beginning log switch checkpoint up to RBA [0x9.2.10], SCN: 534450
Thread 1 advanced to log sequence 9
Current log# 2 seq# 9 mem# 0: /u/app/oracle/oradata/orcl/redo02.log
Mon Aug 4 22:35:58 2008
Completed checkpoint up to RBA [0x9.2.10], SCN: 534450
-- 我们能看到checkpoint是在过了一段时间(这里是4分钟)之后才完成的
-- 接着我们来看下V$DATAFILE_HEADER中的结果
NO STATUS TABLESPACE_NAME CUR_SCN RST_DT RST_SCN CKPT_DT CKPT_SCN CKPT_CNT
--- ------- -------- -------- --------- ---------
1 ONLINE SYSTEM 534770 2008-01-12 16:51:53 446075 2008-08-04 22:31:44 534450 67
2 ONLINE UNDOTBS1 534770 2008-01-12 16:51:53 446075 2008-08-04 22:31:44 534450 30
3 ONLINE SYSAUX 534770 2008-01-12 16:51:53 446075 2008-08-04 22:31:44 534450 67
4 ONLINE USERS 534770 2008-01-12 16:51:53 446075 2008-08-04 22:31:44 534450 66
5 ONLINE EXAMPLE 534770 2008-01-12 16:51:53 446075 2008-08-04 22:31:44 534450 26
-- 在这里我们能发现下V$DATAFILE_HEADER里面记录的SCN和日志切换发生的checkpoint的SCN是一样的,
-- 这就证明了日志切换是会更新数据文件头的,同时日志切换的checkpoint是一个级别比较低的操作,
-- 它不会立即完成,这也是出于性能上考虑的。
增量checkpoint查看
当前所知只有在LOG_checkpoint_TIMEOUT设置了非0值之后触发的增量checkpoint会在alert文件中有记录,其他条件触发的增量checkpoint都不会记录在alert文件中。
-- 下面是当LOG_checkpoint_TIMEOUT设置为1800s的时候所产生的增量checkpoint记录
Sun Aug 3 19:08:56 2008
Incremental checkpoint up to RBA [0x8.e17.0], current log tail at RBA [0x8.1056.0]
Sun Aug 3 19:39:00 2008
Incremental checkpoint up to RBA [0x8.1be0.0], current log tail at RBA [0x8.1c6e.0]
Sun Aug 3 20:09:04 2008
Incremental checkpoint up to RBA [0x8.2af5.0], current log tail at RBA [0x8.2b6a.0]
Sun Aug 3 20:39:07 2008
Incremental checkpoint up to RBA [0x8.3798.0], current log tail at RBA [0x8.3851.0]
Sun Aug 3 21:09:10 2008
Incremental checkpoint up to RBA [0x8.47b9.0], current log tail at RBA [0x8.48bb.0]
Sun Aug 3 21:39:14 2008
Incremental checkpoint up to RBA [0x8.548d.0], current log tail at RBA [0x8.5522.0]
Mon Aug 4 21:05:18 2008
top查看fast_start_mttr_target
通过查看V$INSTANCE_RECOVERY动态性能视图可以查看一些MTTR相关的信息。
SELECT TARGET_MTTR,ESTIMATED_MTTR,CKPT_BLOCK_WRITES,CKPT_BLOCK_WRITES FROM V$INSTANCE_RECOVERY
TARGET_MTTR
用户设置的参数FAST_START_MTTR_TARGET的值.
ESTIMATED_MTTR
根据目前脏块数目和日志块数目,评估的现在进行恢复所需要的时间.
CKPT_BLOCK_WRITES
检查点写完的块数目.
CKPT_BLOCK_WRITES
额外的因为检查点引起的数据库写入操作 (因为不必要的检查点的产生,设置一个非常小的系统恢复时间将会对性能产生负面影响,为了帮助管理员监测这个参数设置较小时对数据库的影响,这个视图显示了这个列)
数据库
2019-06-08 17:31:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果我们在系统中配置下面的连接参数: spring.datasource.url=jdbc:hsqldb:file:~/db/cwiki-us-jpetstore
我们怎么知道 hsqldb 数据库的存储路径在哪里?

请参考下面的解答:
在 Windows 系统中,如果你登录的用户名为 yhu 的话。
那么这个数据库文件在 :C:
数据库
2019-06-07 04:00:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
我们定义数据库的字段时,可能会在不经意间定义了和SQL语句关键字名相同的字段,这时我们在对该字段进行操作时,常会发生错误。
例如我在表中定义了一个from字段,我们知道 from值mysql的关键字,所以我们在执行查询时会出错
例如:
执行:SELECT * FROM WHERE from = 'xxx';
报错:execute error:SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' SELECT * FROM WHERE from = 'xxx'' at line 1
解决方法:
1.重新定义字段。
2.用反撇号(`)包住待操作的关键字,换成一下语句则不会报错(注意不是单引号)。
SELECT * FROM WHERE `from` = 'xxx';
数据库
2019-06-06 14:44:00
「深度学习福利」大神带你进阶工程师,立即查看>>>
如果问 mapreduce 和 spark 什么关系,或者说有什么共同属性,你可能会回答他们都是大数据处理引擎。如果问 spark 与 tensorflow 呢,就可能有点迷糊,这俩关注的领域不太一样啊。但是再问 spark 与 MPI 呢?这个就更远了。虽然这样问多少有些不严谨,但是它们都有共同的一部分,这就是我们今天谈论的一个话题,一个比较大的话题:分布式计算框架。
不管是 mapreduce,还是 spark 亦或 tensorflow,它们都是利用分布式的能力,运行某些计算,解决一些特定的问题。从这个 level 讲,它们都定义了一种“分布式计算模型”,即提出了一种计算的方法,通过这种计算方法,就能够解决大量数据的分布式计算问题。它们的区别在于提出的分布式计算模型不同。Mapreduce 正如其名,是一个很基本的 map-reduce 式的计算模型(好像没说一样)。Spark 定义了一套 RDD 模型,本质上是一系列的 map/reduce 组成的一个 DAG 图。Tensorflow 的计算模型也是一张图,但是 tensorflow 的图比起 spark 来,显得更“复杂”一点。你需要为图中的每个节点和边作出定义。根据这些定义,可以指导 tensorflow 如何计算这张图。Tensorflow 的这种具体化的定义使它比较适合处理特定类型的的计算,对 tensorflow 来讲就是神经网络。而 spark 的 RDD 模型使它比较适合那种没有相互关联的的数据并行任务。那么有没有一种通用的、简单的、性能还高的分布式计算模型?我觉着挺难。通用往往意味着性能不能针对具体情形作出优化。而为专门任务写的分布式任务又做不到通用,当然也做不到简单。
插一句题外话,分布式计算模型有一块伴随的内容,就是调度。虽然不怎么受关注,但这是分布式计算引擎必备的东西。mapreduce 的调度是 yarn,spark 的调度有自己内嵌的调度器,tensorflow 也一样。MPI 呢?它的调度就是几乎没有调度,一切假设集群有资源,靠 ssh 把所有任务拉起来。调度实际上应当分为资源调度器和任务调度器。前者用于向一些资源管理者申请一些硬件资源,后者用于将计算图中的任务下发到这些远程资源进行计算,其实也就是所谓的两阶段调度。近年来有一些 TensorflowOnSpark 之类的项目。这类项目的本质实际上是用 spark 的资源调度,加上 tensorflow 的计算模型。
当我们写完一个单机程序,而面临数据量上的问题的时候,一个自然的想法就是,我能不能让它运行在分布式的环境中?如果能够不加改动或稍加改动就能让它分布式化,那就太好了。当然现实是比较残酷的。通常情况下,对于一个一般性的程序,用户需要自己手动编写它的分布式版本,利用比如 MPI 之类的框架,自己控制数据的分发、汇总,自己对任务的失败做容灾(通常没有容灾)。如果要处理的目标是恰好是对一批数据进行批量化处理,那么 可以用 mapreduce 或者 spark 预定义的 api。对于这一类任务,计算框架已经帮我们把业务之外的部分(脚手架代码)做好了。同样的,如果我们的任务是训练一个神经网络,那么用 tensorflow pytorch 之类的框架就好了。这段话的意思是,如果你要处理的问题已经有了对应框架,那么拿来用就好了。但是如果没有呢?除了自己实现之外有没有什么别的办法呢?
今天注意到一个项目,Ray,声称你只需要稍微修改一下你的代码,就能让它变为分布式的(实际上这个项目早就发布了,只是一直没有刻意关注它)。当然这个代码仅局限于 python,比如下面这个例子, | **Basic Python** | **Distributed with Ray** | +------------------------------------------------+----------------------------------------------------+ | | | | # Execute f serially. | # Execute f in parallel. | | | | | | @ray.remote | | def f(): | def f(): | | time.sleep(1) | time.sleep(1) | | return 1 | return 1 | | | | | | | | | ray.init() | | results = [f() for i in range(4)] | results = ray.get([f.remote() for i in range(4)]) | +------------------------------------------------+----------------------------------------------------+
这么简单?这样笔者想到了 openmp(注意不是 openmpi)。来看看, #include #include"omp.h" using namespace std; void main() { #pragma omp parallel for for(int i = 0; i < 10; ++i) { cout << "Test" << endl; } system("pause"); }
把头文件导入,添加一行预处理指令就可以了,这段代码立马变为并行执行。当然 openmp 不是分布式,只是借助编译器将代码中需要并行化的部分编译为多线程运行,本身还是一个进程,因此其并行度收到 CPU 线程数量所限。如果 CPU 是双线程,那只能 2 倍加速。在一些服务器上,CPU 可以是单核 32 线程,自然能够享受到 32 倍加速(被并行化的部分)。不过这些都不重要,在用户看来,Ray 的这个做法和 openmp 是不是有几分相似之处?你不需要做过多的代码改动,就能将代码变为分布式执行(当然 openmp 要更绝一点,因为对于不支持 openmp 的编译器它就是一行注释而已)。
那么 Ray 是怎么做到这一点的呢?其实 Ray 的做法说起来也比较简单,就是定义了一些 API,类似于 MPI 中的定义的通信原语。使用的时候,将这些 API “注入”到代码合适的位置,那么代码就变成了用户代码夹杂着一些 Ray 框架层的 API 调用,整个代码实际上就形成了一张计算图。接下来的事情就是等待 Ray 把这张计算图完成返回就好了。Ray 的论文给了个例子: @ray.remote def create_policy(): # Initialize the policy randomly. return policy @ray.remote(num_gpus=1) class Simulator(object): def __init__(self): # Initialize the environment. self.env = Environment() def rollout(self, policy, num_steps): observations = [] observation = self.env.current_state() for _ in range(num_steps): action = policy(observation) observation = self.env.step(action) observations.append(observation) return observations @ray.remote(num_gpus=2) def update_policy(policy, *rollouts): # Update the policy. return policy @ray.remote def train_policy(): # Create a policy. policy_id = create_policy.remote() # Create 10 actors. simulators = [Simulator.remote() for _ in range(10)] # Do 100 steps of training. for _ in range(100): # Perform one rollout on each actor. rollout_ids = [s.rollout.remote(policy_id) for s in simulators] # Update the policy with the rollouts. policy_id = update_policy.remote(policy_id, *rollout_ids) return ray.get(policy_id)
生成的计算图为
所以,用户要做的事情,就是在自己的代码里加入适当的 Ray API 调用,然后自己的代码就实际上变成了一张分布式计算图了。作为对比,我们再来看看 tensorflow 对图的定义, import tensorflow as tf # 创建数据流图:y = W * x + b,其中W和b为存储节点,x为数据节点。 x = tf.placeholder(tf.float32) W = tf.Variable(1.0) b = tf.Variable(1.0) y = W * x + b with tf.Session() as sess: tf.global_variables_initializer().run() # Operation.run fetch = y.eval(feed_dict={x: 3.0}) # Tensor.eval print(fetch) # fetch = 1.0 * 3.0 + 1.0 ''' 输出: 4.0 '''
可以看出,tensorflow 中是自己需要自己显式的、明确的定义出图的节点,placeholder Variable 等等(这些都是图节点的具体类型),而 Ray 中图是以一种隐式的方式定义的。我认为后者是一种更自然的方式,站在开发者的角度看问题,而前者更像是为了使用 tensorflow 把自己代码逻辑去适配这个轮子。
那么 ray 是不是就我们要寻找的那个即通用、又简单、还灵活的分布式计算框架呢?由于笔者没有太多的 ray 的使用经验,这个问题不太好说。从官方介绍来看,有限的几个 API 确实是足够简单的。仅靠这几个 API 能不能达成通用且灵活的目的还不好讲。本质上来说,Tensorflow 对图的定义也足够 General,但是它并不是一个通用的分布式计算框架。由于某些问题不在于框架,而在于问题本身的分布式化就存在困难,所以试图寻求一种通用分布式计算框架解决单机问题可能是个伪命题。
话扯远了。假设 ray 能够让我们以一种比较容易的方式分布式地执行程序,那么会怎么样呢?前不久 Databricks 开源了一个新项目,Koalas,试图以 RDD 的框架并行化 pandas。由于 pandas 的场景是数据分析,和 spark 面对的场景类似,两者的底层存储结构、概念也是很相似的,因此用 RDD 来分布式化 pandas 也是可行的。我想,如果 ray 足够简单好用,在 pandas 里加一些 ray 的 api 调用花费的时间精力可能会远远小于开发一套 koalas。但是在 pandas 里加 ray 就把 pandas 绑定到了 ray 上,即便单机也是这样,因为 ray 做不到像 openmp 那样如果支持,很好,不支持也不影响代码运行。
啰嗦这么多,其实就想从这么多引擎的细节中跳出来,思考一下到底什么是分布式计算框架,每种框架又是设计的,解决什么问题,有什么优缺点。最后拿大佬的一个观点结束本文。David Patterson 在演讲 “New Golden Age For Computer Architecture” 中提到,通用硬件越来越逼近极限,要想要达到更高的效率,我们需要设计面向领域的架构(Domain Specific Architectures)。这是一个计算架构层出不穷的时代,每种架构都是为了解决其面对的领域问题出现的,必然包含对其问题的特殊优化。通用性不是用户解决问题的出发点,而更多的是框架设计者的“一厢情愿”,用户关注的永远是领域问题。从这个意义上讲,面向领域的计算架构应该才是正确的方向。
声明:限于本人水平有限,文中陈述内容可能有误。欢迎批评指正。
作者:EMR
原文链接
本文为云栖社区原创内容,未经允许不得转载。
数据库
2019-06-06 12:57:00