docker_mysql

拉取镜像

1
docker pull mysql

启动指令

1
2
3
4
5
6
7
8
9
# 简单启动
docker run --name mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag

docker run --name mysql -v /tmp/mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=pass -d mysql:tag

# 端口及目录映射
docker run --name mysql-3306 -v /tmp/mysql:/var/lib/mysql -p 33060:3306 -e MYSQL_ROOT_PASSWORD=pass -d mysql:tag


1
2
3
4
5
6
# 常用
docker run --name mysql-3306 -v /tmp/mysql:/var/lib/mysql -p 33060:3306 -e MYSQL_ROOT_PASSWORD=pass -d mysql --default-authentication-plugin=mysql_native_password

CREATE USER 'root'@'%' IDENTIFIED BY 'pass';
GRANT ALL ON *.* TO 'root'@'%';
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'pass';

big_endian

Big Endian and Little Endian

A load word or store word instruction uses only one memory address. The lowest address of the four bytes is used for the address of a block of four contiguous bytes.

How is a 32-bit pattern held in the four bytes of memory? There are 32 bits in the four bytes and 32 bits in the pattern, but a choice has to be made about which byte of memory gets what part of the pattern. There are two ways that computers commonly do this:

Big Endian Byte Order: The most significant byte (the “big end”) of the data is placed at the byte with the lowest address. The rest of the data is placed in order in the next three bytes in memory.

Little Endian Byte Order: The least significant byte (the “little end”) of the data is placed at the byte with the lowest address. The rest of the data is placed in order in the next three bytes in memory.

In these definitions, the data, a 32-bit pattern, is regarded as a 32-bit unsigned integer. The “most significant” byte is the one for the largest powers of two: 231, …, 224. The “least significant” byte is the one for the smallest powers of two: 27, …, 20.

For example, say that the 32-bit pattern 0x12345678 is stored at address 0x00400000. The most significant byte is 0x12; the least significant is 0x78.

Within a byte the order of the bits is the same for all computers (no matter how the bytes themselves are arranged).

一、字节序

字节序,也就是字节的顺序,指的是多字节的数据在内存中的存放顺序。

在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如:如果C/C++中的一个int型变量 a 的起始地址是&a = 0x100,那么 a 的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103位置。

根据整数 a 在连续的 4 byte 内存中的存储顺序,字节序被分为大端序(Big Endian)小端序(Little Endian)两类。 然后就牵涉出两大CPU派系:

  • Motorola 6800,PowerPC 970,SPARC(除V9外)等处理器采用 Big Endian方式存储数据;
  • x86系列,VAX,PDP-11等处理器采用Little Endian方式存储数据。

另外,还有一些处理器像ARM, DEC Alpha的字节序是可配置的。

二、大端与小端

那么,到底什么是大端,什么是小端? 如下图:

img

我相信上面的图已经够直观了。也就是说:

  • Big Endian 是指低地址端 存放 高位字节。
  • Little Endian 是指低地址端 存放 低位字节。

各自的优势:

  1. Big Endian:符号位的判定固定为第一个字节,容易判断正负。
  2. Little Endian:长度为1,2,4字节的数,排列方式都是一样的,数据类型转换非常方便。

三、为什么要注意字节序

如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。

但是,如果你的程序要跟别人的程序产生交互呢? 比如,当一个 C/C++ 的程序要与一个 Java 程序交互时:

  • C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而现在比较普遍的 x86 处理器是 Little Endian
  • JAVA编写的程序则唯一采用 Big Endian 方式来存储数据

试想,如果你的C/C++程序将变量 a = 0x12345678 的首地址传递给了Java程序,由于Java采取 Big Endian 方式存储数据,很自然的它会将你的数据翻译为 0x78563412。显然,问题就出现了!!!

另外,网络传输一般采用 Big Endian,也被称之为网络字节序,或网络序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。

四、判断机器的字节序

由于 C/C++ 存储数据时的字节序依赖所在平台的CPU,所以我们可以通过C/C++程序判定机器的端序:

1
2
3
4
5
6
7
8
void Endianness()
{
int a = 0x12345678;
if( *((char*)&a) == 0x12)
cout << "Big Endian" << endl;
else
cout << "Little Endian" << endl;
}

五、网络序和主机序

网络字节序:TCP/IP各层协议将字节序定义为 Big Endian,因此TCP/IP协议中使用的字节序是大端序。

主机字节序:整数在内存中存储的顺序,现在 Little Endian 比较普遍。(不同的 CPU 有不同的字节序)

在进行网络通信时 通常需要调用相应的函数进行主机序和网络序的转换。Berkeley socket API 定义了一组转换函数,用于16和32bit整数在网络序和本机字节序之间的转换。htonl,htons用于本机序转换到网络序;ntohl,ntohs用于网络序转换到本机序。

iptables

## netstat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-a或--all:显示所有连线中的Socket;
-A<网络类型>或--<网络类型>:列出该网络类型连线中的相关地址;
-c或--continuous:持续列出网络状态;
-C或--cache:显示路由器配置的快取信息;
-e或--extend:显示网络其他相关信息;
-F或--fib:显示FIB;
-g或--groups:显示多重广播功能群组组员名单;
-h或--help:在线帮助;
-i或--interfaces:显示网络界面信息表单;
-l或--listening:显示监控中的服务器的Socket;
-M或--masquerade:显示伪装的网络连线;
-n或--numeric:直接使用ip地址,而不通过域名服务器;
-N或--netlink或--symbolic:显示网络硬件外围设备的符号连接名称;
-o或--timers:显示计时器;
-p或--programs:显示正在使用Socket的程序识别码和程序名称;
-r或--route:显示Routing Table;
-s或--statistice:显示网络工作信息统计表;
-t或--tcp:显示TCP传输协议的连线状况;
-u或--udp:显示UDP传输协议的连线状况;
-v或--verbose:显示指令执行过程;
-V或--version:显示版本信息;
-w或--raw:显示RAW传输协议的连线状况;
-x或--unix:此参数的效果和指定"-A unix"参数相同;
--ip或--inet:此参数的效果和指定"-A inet"参数相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
netstat -a     #列出所有端口
netstat -at #列出所有tcp端口
netstat -au #列出所有udp端口

netstat -l #只显示监听端口
netstat -lt #只列出所有监听 tcp 端口
netstat -lu #只列出所有监听 udp 端口
netstat -lx #只列出所有监听 UNIX 端口

netstat -s #显示所有端口的统计信息
netstat -st #显示TCP端口的统计信息
netstat -su #显示UDP端口的统计信息

netstat -pt #在netstat输出中显示 PID 和进程名称

netstat -ntu | grep :80 | awk '{print $5}' | cut -d: -f1 | awk '{++ip[$1]} END {for(i in ip) print ip[i],"\t",i}' | sort -nr #查看连接某服务端口最多的的IP地址

netstat -nt | grep -e 127.0.0.1 -e 0.0.0.0 -e ::: -v | awk '/^tcp/ {++state[$NF]} END {for(i in state) print i,"\t",state[i]}' #TCP各种状态列表

iptables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-t<表>:指定要操纵的表;
-A:向规则链中添加条目;
-D:从规则链中删除条目;
-i:向规则链中插入条目;
-R:替换规则链中的条目;
-L:显示规则链中已有的条目;
-F:清楚规则链中已有的条目;
-Z:清空规则链中的数据包计算器和字节计数器;
-N:创建新的用户自定义规则链;
-P:定义规则链中的默认目标;
-h:显示帮助信息;
-p:指定要匹配的数据包协议类型;
-s:指定要匹配的数据包源ip地址;
-j<目标>:指定要跳转的目标;
-i<网络接口>:指定数据包进入本机的网络接口;
-o<网络接口>:指定数据包要离开本机所使用的网络接口。
1
iptables -t 表名 <-A/I/D/R> 规则链名 [规则号] <-i/o 网卡名> -p 协议名 <-s 源IP/源子网> --sport 源端口 <-d 目标IP/目标子网> --dport 目标端口 -j 动作

清除已有规则

1
2
3
4
5
iptables -F
iptables -X
iptables -Z
iptables -L -n --line-number #显示编号
iptables -D INPUT 2 #删除INPUT链编号为2的规则
1
2
3
4
5
6
7
8
9
iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT               #允许本地回环接口(即运行本机访问本机)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT #允许已建立的或相关连的通行
iptables -A OUTPUT -j ACCEPT #允许所有本机向外的访问
iptables -A INPUT -p tcp --dport 22 -j ACCEPT #允许访问22端口
iptables -A INPUT -p tcp --dport 80 -j ACCEPT #允许访问80端口
iptables -A INPUT -p tcp --dport 21 -j ACCEPT #允许ftp服务的21端口
iptables -A INPUT -p tcp --dport 20 -j ACCEPT #允许FTP服务的20端口
iptables -A INPUT -j REJECT #禁止其他未允许的规则访问
iptables -A FORWARD -j REJECT #禁止其他未允许的规则访问

样例

1
2
sudo iptables -I FORWARD -p tcp --dport 8080 -j DROP  # 不响应对8080的请求,等待超时(效果:Operation timed out)
sudo iptables -I INPUT -p tcp --dport 8080 -j REJECT # 直接拒绝其他机器连接本机8080 (效果:Connection refused)

nginx-gray

Nginx 灰度测试

灰度测试在系统重构过程中是必不可少的,很多公司有自己的灰度解决方案。对于初创型或者小型团队,缺少必要的工具,因此这里借用nginx的转发(反向代理)能力做一下灰度测试。

Nginx配置

Nginx配置文件主要分4部分

  • main(全局设置): main部分的指令将影响其他所有的设置
  • server(主机设置): server部分的指令主要作用于指定的主机和端口
  • upstream(负载均衡服务器设置): upstream指令主要作用于负载均衡的设置
  • location(指定网页的设置): 主要用于匹配上的网页的设置。首先匹配 =,其次匹配^~, 其次是按文件中顺序的正则匹配,最后是交给 / 通用匹配。当有匹配成功时候,停止匹配,按当前匹配规则处理请求。

proxy_pass

在nginx中配置proxy_pass代理转发时,如果在proxy_pass后面的url加/,表示绝对根路径;如果没有/,表示相对路径,把匹配的路径部分也给代理走。

假设下面四种情况分别用 http://192.168.1.1/proxy/test.html 进行访问。

CASE 1:

1
2
3
location /proxy/ {
proxy_pass http://127.0.0.1/;
}

代理到URL:http://127.0.0.1/test.html

CASE 2:

1
2
3
location /proxy/ {
proxy_pass http://127.0.0.1;#少了一个/
}

代理到URL:http://127.0.0.1/proxy/test.html

CASE 3:

1
2
3
location /proxy/ {
proxy_pass http://127.0.0.1/v1/;#加了一个子路径
}

代理到URL:http://127.0.0.1/v1/test.html

CASE 4:

1
2
3
location /proxy/ {
proxy_pass http://127.0.0.1/v1;#加了一个前缀
}

代理到URL:http://127.0.0.1/v1test.html

灰度粒度

URL级别

1
2
3
location  /api_to_gray {
proxy_pass http://target_endpoint/api_to_gray;
}

参数级别

1
2
3
4
5
6
7
8
location  /api_to_gray {
//$arg_param 是url里面的参数,$http_param 是header里面的参数
if ($arg_param = "value"){
add_header Version 'V2';
proxy_pass http://target_endpoint/api_to_gray;
}
proxy_pass http://original_endpoint;
}

参考文档

Nginx 内置变量

Linux 文件对比命令

comm 指令

1
comm [-123][--help][--version][第1个文件][第2个文件]

参数

  • -1 不显示只在第1个文件里出现过的列。
  • -2 不显示只在第2个文件里出现过的列。
  • -3 不显示只在第1和第2个文件里出现过的列。
  • –help 在线帮助。
  • –version 显示版本信息。

需要排序,即便排序也不一定正确,所以不建议使用,适合对比极少数同行差异的文件。

diff 指令

同comm指令,也是逐行对比,如果同样的内容处于不同行中,对比结果也不是期望的

1
diff [-abBcdefHilnNpPqrstTuvwy][-<行数>][-C <行数>][-D <巨集名称>][-I <字符或字符串>][-S <文件>][-W <宽度>][-x <文件或目录>][-X <文件>][--help][--left-column][--suppress-common-line][文件或目录1][文件或目录2]

grep 指令

1
grep [-abcEFGhHilLnqrsvVwxy][-A<显示列数>][-B<显示列数>][-C<显示列数>][-d<进行动作>][-e<范本样式>][-f<范本文件>][--help][范本样式][文件或目录...]
1
2
3
4
5
6
# 查看file2比file1多出的内容
grep -v -x -f file1 file2
# 查看file1和file2中单独存在的内容(差集1+差集2)
grep -v -x -f file1 file2 && grep -v -x -f file2 file1
# 查看file1和file2的交集
grep -x -f file1 file2

-x –line-regexp : 只显示全列符合的列。

-f <规则文件> 或 –file=<规则文件>: 指定规则文件,其内容含有一个或多个规则样式,让grep查找符合规则条件的文件内容,格式为每行一个规则样式。

nginx-variables

Nginx 内置变量大全

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
$args //请求中的的参数名,即“?”后面的arg_name=arg_value形式的arg_name
$arg_PARAMETER //这是参数的一个匹配模式,PARAMETER为具体的参数名,$arg_PARAMETER就表示获取具体的参数值,例如上面的$arg_name就是获取url中name的值
$is_args //判断url是否带参数,如果带,则返回一个?,否则返回一个空字符串

$http_user_agent //获取的是客户端访问代理的类型,请求头中的信息
$sent_http_content_type //获取的是http响应头中content_type的值
$sent_http_content_length //获取的是http响应头重的content_length的值

$request_filename //该变量获取的是请求的文件在linux服务器上的完整的绝对路径
$request_method //该表示获取的是http请求的方法
$request_uri //该变量表示的原始请求的uri,包括参数。所谓原始请求就是即使在内部做了重定向之后也不会变化
$uri //获取的是当前请求的uri,不包括参数
$content_length //获取的是http请求头中Content-Length的值
$content_type //获取的是http请求头中的Content-Type字段,不过这里也没显示。。。
$document_root //获取的是请求url的文件所在的目录路径
$document_uri //当前请求的uri,从上面的信息来看,和uri的效果是一样的
$remote_addr //获取的是客户端的ip地址,这里为什么是10.0.10.11呢,因为我是在本机上用curl测试的,即使客户端也是服务器
$remote_port //获取客户端的访问端口,这个端口是随机的
$remote_user //获取客户端的认证用户信息,这里因为没有用认证,所谓显示为空
$server_protocol //表示服务器端想客户端发送响应的协议
$server_addr //服务器的地址
$server_name //客户端访问服务端的域名,即url中的域名(通配符不自动解析)
$server_port //服务器端做出响应的端口号
$binary_remote_addr //显示二进制的客户端地址
$host //和server_name一样,表示的是域名
$hostname //表示服务器端的主机名

$proxy_add_x_forwarded_for //获取的是客户端的真实ip地址
$proxy_host //该变量获取的是upstream的上游代理名称,例如upstream backend
$proxy_port //该变量表示的是要代理到的端口
$proxy_protocol_addr //代理头部中客户端的ip地址,或者是一个空的字符串
$upstream_addr //代理到上游的服务器地址信息
$upstream_cache_status //proxy的缓存状态,例如这里第一次访问为MISS,第二次访问时为HIT
$upstream_response_length //上游服务器响应报文的长度
$upstream_response_time //上游服务器响应的时间
$upstream_status //上游服务器响应的状态码

$scheme //表示的是使用http的访问协议 http or https
$limit_rate //表示当前连接的限速是多少,0表示无限制
$query_string //表示的是查询字符串,也就是url中的参数,和$args一样
$realpath_root //表示的是请求页面的真实所在目录的路径 和$document_root是一样的

proxy-in-mybatis

代理在Mybatis里面的使用

主要位于org.apache.ibatis.binding包里面

1
2
MapperRegistry  负责注册获取Mapper
MapperProxy Mapper接口的代理

binding

代理实现

org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper

1
2
3
4
5
SqlSession.getMapper
---> Configuration.getMapper
---> MapperRegistry.getMapper
---> mapperProxyFactory.newInstance
---> Proxy.newProxyInstance

command 来自 mapper的注解或者xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

Mybatis源码解读

整体代码结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
└── org
└── apache
└── ibatis
├── annotations
├── binding
├── builder
├── cache
├── cursor
├── datasource
├── exceptions
├── executor
├── io
├── jdbc
├── lang
├── logging
├── mapping
├── package-info.java
├── parsing
├── plugin
├── reflection
├── scripting
├── session
├── transaction
├── type
└── util

几个核心包

Executor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
├── BaseExecutor.java
├── BatchExecutor.java
├── BatchExecutorException.java
├── BatchResult.java
├── CachingExecutor.java
├── ErrorContext.java
├── ExecutionPlaceholder.java
├── Executor.java
├── ExecutorException.java
├── ResultExtractor.java
├── ReuseExecutor.java
├── SimpleExecutor.java
├── keygen
│   ├── Jdbc3KeyGenerator.java
│   ├── KeyGenerator.java
│   ├── NoKeyGenerator.java
│   ├── SelectKeyGenerator.java
│   └── package-info.java
├── loader
│   ├── AbstractEnhancedDeserializationProxy.java
│   ├── AbstractSerialStateHolder.java
│   ├── CglibProxyFactory.java
│   ├── JavassistProxyFactory.java
│   ├── ProxyFactory.java
│   ├── ResultLoader.java
│   ├── ResultLoaderMap.java
│   ├── WriteReplaceInterface.java
│   ├── cglib
│   ├── javassist
│   └── package-info.java
├── package-info.java
├── parameter
│   ├── ParameterHandler.java
│   └── package-info.java
├── result
│   ├── DefaultMapResultHandler.java
│   ├── DefaultResultContext.java
│   ├── DefaultResultHandler.java
│   ├── ResultMapException.java
│   └── package-info.java
├── resultset
│   ├── DefaultResultSetHandler.java
│   ├── ResultSetHandler.java
│   ├── ResultSetWrapper.java
│   └── package-info.java
└── statement
├── BaseStatementHandler.java
├── CallableStatementHandler.java
├── PreparedStatementHandler.java
├── RoutingStatementHandler.java
├── SimpleStatementHandler.java
├── StatementHandler.java
├── StatementUtil.java
└── package-info.java

Session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
├── AutoMappingBehavior.java
├── AutoMappingUnknownColumnBehavior.java
├── Configuration.java
├── ExecutorType.java
├── LocalCacheScope.java
├── ResultContext.java
├── ResultHandler.java
├── RowBounds.java
├── SqlSession.java
├── SqlSessionException.java
├── SqlSessionFactory.java
├── SqlSessionFactoryBuilder.java
├── SqlSessionManager.java
├── TransactionIsolationLevel.java
├── defaults
└── package-info.java

数据库连接打开

org.apache.ibatis.transaction.jdbc.JdbcTransaction#openConnection

Mapper 生成

1
2
3
4
5
T org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
T org.apache.ibatis.session.Configuration#getMapper
T org.apache.ibatis.binding.MapperRegistry#getMapper
T org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
T java.lang.reflect.Proxy#newProxyInstance

SQL流图

Select 流程

建连

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
at org.apache.ibatis.transaction.jdbc.JdbcTransaction.getConnection(JdbcTransaction.java:60)
at org.apache.ibatis.executor.BaseExecutor.getConnection(BaseExecutor.java:337)
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:86)
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:62)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:325)
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:103)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:89)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:76)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:87)
at org.apache.ibatis.binding.MapperProxy$PlainMethodInvoker.invoke(MapperProxy.java:145)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86)
at com.sun.proxy.$Proxy.select(Unknown Source:-1)

执行

1
2
3
4
5
6
7
8
9
org.apache.ibatis.executor.CachingExecutor#query(ms,parameterObject,rowBounds,resultHandler)
org.apache.ibatis.executor.BaseExecutor#query(ms,parameter,rowBounds,resultHandler,key,boundSql)
org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
org.apache.ibatis.executor.SimpleExecutor#doQuery
org.apache.ibatis.executor.statement.RoutingStatementHandler#parameterize
org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize
org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
org.apache.ibatis.executor.statement.RoutingStatementHandler#query
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets

Executor ==> Statement ==> Parameter ==> ResultSet

绿色 几个组件都是可以通过mybatis插件改写的,参考 mybatis plugin

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

  • StatementHandler (prepare, parameterize, batch, update, query)

  • ParameterHandler (getParameterObject, setParameters)

  • ResultSetHandler (handleResultSets, handleOutputParameters)

tcpdump及tshark 使用

通用抓包工具

  1. tcpdump
  2. tshark (wireshark 命令行版)

tshark 安装

yum install -y wireshark

http请求抓包

  • tcpdump方式:
1
sudo tcpdump -A -s 0 'tcp port 8080 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'
  • tshark方式:
1
2
# 加 -V 显示包体内容,否则仅url,response_code等
sudo tshark -i eth0 -R 'ip.addr ==100.100.17.97 and http'

mysql 抓包

  • tcpdump方式
1
2
3
4
5
6
7
8
9
10
sudo tcpdump -i any -s 0 -l -w - dst port 3306 | strings | perl -e '
while(<>) { chomp; next if /^[^ ]+[ ]*$/;
if(/^(SELECT|UPDATE|DELETE|INSERT|SET|COMMIT|ROLLBACK|CREATE|DROP|ALTER|CALL)/i)
{
if (defined $q) { print "$q\n"; }
$q=$_;
} else {
$_ =~ s/^[ \t]+//; $q.=" $_";
}
}'
  • tshark方式
1
2
# time_delta 本次回包耗时(RTT)
sudo tshark -i eth0 -R "ip.addr==ip" -d tcp.port==3306,mysql -o tcp.calculate_timestamps:true -T fields -e frame.number -e frame.time_epoch -e frame.time_delta_displayed -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -e tcp.time_delta -e tcp.stream -e tcp.len -e mysql.query

tcpdump及tshark 使用

译自 A tcpdump Tutorial and Primer with Examples 有所增删,可以通过 man tcpdump 获取更详细信息

只要和网络沾边,都可以使用tcpdump来排查问题。

1 基础

1.1 常用选项

  • -i any :监听任意网络设备
  • -i eth0 :监听eth0
  • -D :列出所有可监听设备
  • -n :不要把IP解析为主机名(显示IP)
  • -nn :不要把IP和PORT解析为主机名和服务(显示IP和端口)
  • -q :显示更少信息
  • -tttt :更具有可读性的时间格式
  • -X :以hex和ascii格式显示包内容
  • -XX :同 -X , 同时包含以太网协议头
  • -v, -vv, -vvv :显示更多信息
  • -c x :最多抓取x个包,然后退出
  • -A :以ASCII格式打印每个包(最小化link level的头),适合抓web页面
  • -s :Define the snaplength (size) of the capture in bytes. 使用 -s0 获取全部
  • -w file :把结果输出到文件file
  • -S :Print absolute sequence numbers.

1.2 表达式

表达式用于过滤抓到的包,有三类表达式

  • 类型 host net port
  • 数据流向 src dst
  • 协议 tcp udp icmp

2 例子

2.1 获取所有流量

tcpdump -i any 当机器有多个网卡,不确定流量走哪个时,使用这个选项

2.2 获取eth0上的流量

1
tcpdump -i eth0

2.3 查看原始流量

1
tcpdump -nn -tttt -vv -S -c 10

2.4 查看指定host和port

1
tcpdump port 80 and host 10.129.204.105

2.5 以hex和ascii查看包内容

1
tcpdump -nn -v -X -s0 -c 1 tcp

2.6 指定数据的方向

获取所有目标端口是80的流量 tcpdump -i any -nn -s0 -c 10 -A tcp and dst port 80

获取所有80端口的流量 tcpdump -i any -nn -s0 -c 10 -A tcp and port 80

2.7 获取指定协议的流量

获取vrrp协议的流量,用于调试keepalive tcpdump vrrp

2.8 获取ipv6的流量

1
tcpdump ip6

2.9 获取某个端口范围的流量

1
tcpdump -nn portrange 21-23 and src host 10.136.110.179

2.10 基于包大小过滤

1
2
tcpdump -i any -nn port 80 and less 80
tcpdump -i any -nn port 80 and greater 80

3 进阶

3.1 表达式组合

通过 and 或 && or 或 || not 或 ! 可以组合表达式

表达式的结合律有点奇怪,需要自己体会,例如: 监听80或8080并且来自10.129.204.105的请求 tcpdump -i any -nn -vv port 80 or 8080 and src host 10.129.204.105 tcpdump -i any -nn -vv port 80 or port 8080 and src host 10.129.204.105

监听80并且来自10.129.204.105的请求或监听8080的请求(没有来源IP限制) tcpdump -i any -nn -vv port 80 and src host 10.129.204.105 or port 8080

为了避免这些微妙的差别,可以使用 () ,注意这里的单引号 tcpdump -i any -nn -vv '(port 80 or port 8080)' and src host 10.129.204.105

3.2 指定源host和目标端口

1
tcpdump -i any -nn -vv port 80 and src host 10.129.204.105

3.3 目标是某台机器的所有非tcp请求

1
tcpdump -i any -nn dst host 10.136.110.179 and not tcp

3.4 来自某台机器的所有非22请求

1
tcpdump -i any -nn src host 10.129.204.105 and not dst port 22

3.5 获取tcp flag

先了解下TCP和IP协议头,虽然最后有0~40个可选bytes,这里没用到,可以先忽略它们。TCP头20bytes,其中tcp flags在第13个byte(下标为0) |C|E|U|A|P|R|S|F|

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
TCP
0 15 31
-----------------------------------------------------------------
| source port | destination port |
-----------------------------------------------------------------
| sequence number |
-----------------------------------------------------------------
| acknowledgment number |
-----------------------------------------------------------------
| HL | rsvd |C|E|U|A|P|R|S|F| window size |
-----------------------------------------------------------------
| TCP checksum | urgent pointer |
-----------------------------------------------------------------
| optional data 0~40 bytes |
-----------------------------------------------------------------

|---------------|
|C|E|U|A|P|R|S|F|
|---------------|
|7 5 3 0|

IP
0| 1| 2| 3| 4| 5| 6| 7| 8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31
-----------------------------------------------------------------------------------------------
| version | IHL | DSCP | ECN | total length
-----------------------------------------------------------------------------------------------
| Identification | Flags | Fragment Offset
-----------------------------------------------------------------------------------------------
| TTL | Protocol | Header Checksum
-----------------------------------------------------------------------------------------------
| Source IP
-----------------------------------------------------------------------------------------------
| Destination IP
-----------------------------------------------------------------------------------------------

3.5.1 flag列表

  • URGENT (URG) tcp[13] & 32 != 0tcp[tcpflags] == tcp-urg
  • ACKNOWLEDGE (ACK) tcp[13] & 16 != 0tcp[tcpflags] == tcp-ack
  • PUSH (PSH) tcp[13] & 8 != 0tcp[tcpflags] == tcp-psh
  • RESET (RST) tcp[13] & 4 != 0tcp[tcpflags] == tcp-rst
  • SYNCHRONIZE (SYN) tcp[13] & 2 != 0tcp[tcpflags] == tcp-syn
  • FINISH (FIN) tcp[13] & 1 != 0tcp[tcpflags] == tcp-fin

例如:获取所有SYN请求 tcpdump -i any -nn -A -s0 'tcp[13] & 2 != 0'

3.5.2 获取tcp连接的开始和结束

1
tcpdump -i any -nn -s0 -X 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0'

3.5.3 获取所有HTTP GET请求

tcp[12] 的高4位是TCP HEADER的长度(通常是20 bytes),TCP之后是应用协议 0x47455420 是 GET 外加一个空格 tcpdump -i any -nn -s0 -X 'tcp[((tcp[12] & 0xf0) >> 2):4] = 0x47455420' 通常 tcpdump -i any -nn -s0 -X 'tcp[20:4] = 0x47455420' 也是可以的

3.5.4 获取所有SSH连接

1
0x5353482D` 是 `SSH-` `tcpdump -i any -nn -s0 -X 'tcp[20:4] = 0x5353482D'

3.5.5 获取所有带数据的包

1
ip[2:2]` 整个IP包的长度 `(ip[0] & 0xf) <<2` 是IP头的长度 `tcpdump -i any -nn -s0 -X port 80 and '(((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)'