Jason Lee Essay

Only dead fish go with the flow!

Jason Lee Essay

SpringCloud配置信息部分解释如下:

eureka.instance.prefer-ip-address

1
2
3
4
5
/**
* Flag to say that, when guessing a hostname, the IP address of the server should be
* used in prference to the hostname reported by the OS.
*/
private boolean preferIpAddress = false;

这段的意思是是否需要将serverName 转换为IP地址注册到eureka-server中。
两个例子:
1115 端口

1
2
3
4
5
server:
port: 1115
eureka:
instance:
prefer-ip-address: true

1116端口

1
2
3
4
5
server:
port: 1116
eureka:
instance:
prefer-ip-address: false

结果:1115端口转换为了IP地址注册,1116端口没有注册转换名称给服务器。

1
2
3
4
[
"URI:http://192.168.1.26:1115,service_id:CLIENT-CONSUMER",
"URI:http://peer1:1116,service_id:CLIENT-CONSUMER",
]

在各种的网络封锁下,访问google必须需要proxy代理才能访问到,而代理有时和各种的缓存有相似之处,本质上都是一个服务无法访问到或者响应慢,通过一个或多个中间服务器形成一个较快的链接。这里记录下各种代理的使用方式。

  1. git
  2. docker
  3. npm
  4. apt

git

1
git config --global https.proxy socks5://localhost:1080

验证:

1
2
root@localhost: # git config --global --get-regexp http.*
https.proxy socks5://127.0.0.1:1080

docker

npm

apt

Sharding-jdbc现在是Apache基金会顶级项目ShardingSphere下的一个产品,是以jdbc的形式支持读写分离、分库分表等类似数据库中间件功能,优点是入侵性小、配置方便,而且背后有个好爹。

Sharding-jdbc支持读写分离和分库分表两个核心功能,并且可以在读写分离基础上叠加分库分表。

Maven坐标

Maven中央仓库中,有两个artifactId为sharding-jdbc的包,一个groupId是io.shardingjdbc,一个是org.apache.shardingsphere,注意不要选错了,apache的发布版本都是4.0.0之后的,之前的是老版本应该也不会继续更新了。

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/sharding-jdbc-core -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>4.0.0-RC1</version>
</dependency>

读写分离

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

spring.shardingsphere.datasource.names=master,slave0

spring.shardingsphere.datasource.master.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.master.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.shardingsphere.datasource.master.url=jdbc:log4jdbc:mysql://ip:port/db?useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.master.username=root
spring.shardingsphere.datasource.master.password=123
spring.shardingsphere.datasource.master.max-active=10
spring.shardingsphere.datasource.master.druid.filters=stat,wall,config

spring.shardingsphere.datasource.slave0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.slave0.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.shardingsphere.datasource.slave0.url=jdbc:log4jdbc:mysql://ip:port/db?useUnicode=true&characterEncoding=utf-8
spring.shardingsphere.datasource.slave0.username=root
spring.shardingsphere.datasource.slave0.password=123
spring.shardingsphere.datasource.slave0.max-active=10
spring.shardingsphere.datasource.slave0.druid.filters=stat,wall,config


spring.shardingsphere.masterslave.load-balance-algorithm-type=round_robin
spring.shardingsphere.masterslave.name=ms
spring.shardingsphere.masterslave.master-data-source-name=master
spring.shardingsphere.masterslave.slave-data-source-names=slave0

spring.shardingsphere.props.sql.show=true

忽略druid的配置,使用读写分离的这版,druid的sql监控功能一直打不开。

之前线上系统并发量小,用单数据库实例节点就可以解决问题,最近压力有所上升,单节点经常遇到锁表的问题,数据库做过几次相关的优化还是老样子,就把数据库中间件的使用提上了日程。于是就开始了测试mycat的性能,做了几轮之后,性能还可以。但是忽略了一个重要的问题,测试都在单个schema下进行,今天偶然用到了跨库查询,结果就挂了。

问题还原

mycat配置多个schema,只能在单个schema下进行查询,即便按照标准sql写出跨库查询sql,还是不会识别schema,只能在当前schenma下查询,即mysql的1064错误。

1
2
3
4
5
6
7
8
9
10
11
<schema name="testdb1">
<table name="t1" datanode="dn1" />
</schema>
<schema name="testdb2">
<table name="t2" datanode="dn2" />
</schema>

select * from testdb1.t1 join testdb2.t2;

Error 1064 talbe not defined in testdb2.

官网方案

两个表都是不分片的表,但是位于不同的物理数据库上。也无法将某一批次的表做成全局表。即便做成全局表,也无法join成功。

支持单库内部任意join,支持跨库2表join,甚至基于caltlet的多表join。

官网给出的解释是支持跨库2表join,在应用已基本完成的情况下,不想再重新整理sql了。

替换方案是换成了apache的shardingsphere试试,看着介绍还不错

Nmap,也就是Network Mapper,最早是Linux下的网络扫描和嗅探工具包。

其基本功能有三个:

  1. 是扫描主机端口,嗅探所提供的网络服务
  2. 是探测一组主机是否在线
  3. 还可以推断主机所用的操作系统,到达主机经过的路由,系统已开放端口的软件版本

安装

windows安装

zennmap下载

下载后点击安装

linux安装
1
2
3
4
5
#ubuntu
apt-get install nmap
#centos
rpm -vhU https://nmap.org/dist/nmap-7.70-1.x86_64.rpm

扫描单个主机的端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost ~]# nmap -T4 -A -v 192.168.1.25
Starting Nmap 7.70 ( https://nmap.org ) at 2019-04-22 11:14 ?D1ú±ê×?ê±??
NSE: Loaded 148 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 11:14
Completed NSE at 11:14, 0.00s elapsed
Initiating NSE at 11:14
Completed NSE at 11:14, 0.01s elapsed
Initiating Ping Scan at 11:14
Scanning 192.168.1.25 [4 ports]
Completed Ping Scan at 11:14, 1.36s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 11:14
Completed Parallel DNS resolution of 1 host. at 11:14, 0.05s elapsed
Initiating SYN Stealth Scan at 11:14
Scanning 192.168.1.25 [1000 ports]
Discovered open port 8080/tcp on 192.168.1.25
Discovered open port 1080/tcp on 192.168.1.25
Discovered open port 8100/tcp on 192.168.1.25
Discovered open port 4000/tcp on 192.168.1.25
Discovered open port 2000/tcp on 192.168.1.25
Discovered open port 8088/tcp on 192.168.1.25
Discovered open port 6881/tcp on 192.168.1.25
Completed SYN Stealth Scan at 11:14, 1.11s elapsed (1000 total ports)
Initiating Service scan at 11:14

扫描局域网内的网段

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost ~]# nmap -sn -PE -PA 192.168.1.0/24
Starting Nmap 7.70 ( https://nmap.org ) at 2019-04-22 11:21 CST
Nmap scan report for 192.168.1.1
Host is up (0.0011s latency).
MAC Address: 74:EA:CB:F0:7B:2C (Unknown)
Nmap scan report for 192.168.1.13
Host is up (0.00043s latency).
MAC Address: F4:8E:38:76:DE:D7 (Dell)
Nmap scan report for 192.168.1.14
Host is up (0.00058s latency).
MAC Address: 8C:EC:4B:70:C3:2E (Unknown)
Nmap done: 256 IP addresses (3 hosts up) scanned in 13.94 seconds

基础的嗅探工作还挺好用

Druid作为阿里巴巴出品的数据库连接池产品,虽然从坊间传闻hikaricp性能更好,奈何Druid还有监控功能啊,线上的SQL问题统计方便很多,所以必须要了解下Druid的配置

以下对Druid的依赖管理略过。

JavaEE版本

StatViewServlet是一个标准的javax.servlet.http.HttpServlet,需要配置在你web应用中的WEB-INF/web.xml中。

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>DruidStatView</servlet-name>
<servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DruidStatView</servlet-name>
<url-pattern>/druid/*</url-pattern>
</servlet-mapping>

根据配置中的url-pattern来访问内置监控页面,如果是上面的配置,内置监控页面的首页是/druid/index.html

打开上面的配置能够打开页面,相关的sql统计功能还需要打开统计功能:

1
2
3
4
5
6
7
8
9
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<!-- Connection Info -->
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="filters" value="stat,wall,config" />
</bean>

这样就可以查看到统计功能了。

一般JavaEE的工程具备一个contextPath,访问路径就是:http://ip:port/${contextPath}/druid/index.html

SpringBoot版本

Spring Boot的配置更简单了,没有web.xml文件了,properties/yml的文件配置就够了。

1
2
3
4
5
6
7
spring:
datasource:
druid:
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true

就开启了druid的监控,但是这个有个问题,就是不认__server.serverlet.path__配置项。

补充

如果用了shiro或者Spring-Security等权限管理,一定要关闭对druid的权限验证,而且也不推荐将druid合并到业务帐号里面。

有一台机器,已经建立了一个vg,容量是20T,现在准备新建一个lv,作为数据盘根目录,创建此LV过程中出现了各种的问题。

环境:

1
2
3
centos 6.5
e2fsprogs 1.41
磁盘分区:gpt

问题1:New size too large to be expressed in 32 bits

ext4分区理论可支持最大1EB,单文件体积16TB。但格式化的过程出现了:New size too large to be expressed in 32 bits 这个异常,晚上检索查找到,老版本的e2fsprogs命令存在16T的限制,此BUG修复要在E2fsprogs 1.42.12 (August 29, 2014)版本。
centos6.5默认是1.41,刚好错过一个版本,因为是甲方远程主机,更新出问题还要出差去修复,没敢做。

解决方案就是分成了两个10T的lv使用。

问题2:mkfs.ext4格式化极慢

以下引自:理解inode

inode是什么?

理解inode,要从文件储存说起。

文件储存在硬盘上,硬盘的最小存储单位叫做”扇区”(Sector)。每个扇区储存512字节(相当于0.5KB)。

操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个”块”(block)。这种由多个扇区组成的”块”,是文件存取的最小单位。”块”的大小,最常见的是4KB,即连续八个 sector组成一个 block。

文件数据都储存在”块”中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为”索引节点”。

每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。

inode也需要占用一些存储空间,因为inode是预分配的,所以格式化的时候就已经确定inode的数量了。

每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。

格式化时,每bytes-per-inode(或inode_ratio)字节大小的空间就创建一个inode,在分区大小固定前提下,该值越大,inode个数越少,data block就越多,该值越小,inode个数越多,data block就越少;以默认值16384(即16KiB)为例,如果文件系统上所有文件大小均为16KiB(或平均值),则data block耗尽的同时inode也将耗尽,二者占文件系统比例处于最理想状态,对于大量小文件的业务,通常将该值调小以增加inode数量;

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
[defaults]
base_features = sparse_super,filetype,resize_inode,dir_index,ext_attr
default_mntopts = acl,user_xattr
enable_periodic_fsck = 0
blocksize = 4096
inode_size = 256
inode_ratio = 16384

[fs_types]
ext3 = {
features = has_journal
}
ext4 = {
features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize,64bit
inode_size = 256
}
big = {
inode_ratio = 32768
}
huge = {
inode_ratio = 65536
}
news = {
inode_ratio = 4096
}
largefile = {
inode_ratio = 1048576
blocksize = -1
}
largefile4 = {
inode_ratio = 4194304
blocksize = -1
}

通过更改inode_ratio的数值来控制inode的数量,用来控制inode的数量及空间,这部分影响格式化的速度。

如果是和上述相反的大文件存储业务,可以将inode_ratio值调大,以增加data block数量,如使用“-T largefile”选项对应的inode_ratio值为1MiB,在1.8TiB大小的分区创建ext4文件系统时,可增加20~30GiB左右的数据空间。

对于不能准备评估或需求特殊(如海量小文件)的存储业务,可考虑使用ReiserFS、XFS、JFS等,以避免inode耗尽的风险

第一份copy中git删除本地代码,完全重新初始化了代码,提交后,第二个copy使用正常的git pull 无法更新代码,需要进行强制删除操作。

1
2
3
git fetch --all  
git reset --hard origin/master
git pull

Mysql搭建主从其实配置特别简单,核心配置项目就那么几个就够了。

数据库配置

Master的配置项目

1
2
3
4
[mysqld]
server-id=1
log-bin=log
binlog-ignore-db=mysql,performance_schema,information_schema

其中只有server-id和log-bin这两项是最重要的,当然一般情况下会显示的指定binlog的格式,binlog日志文件的大小等配置。

Slave的配置:

1
2
3
[mysqld]
server-id=2
replicate-ignore-db=mysql,performance_schema,information_schema

其中binlog-ignore-db和replicate-ignore-db分别在master和slave上面配置的不进行同步的数据库,建议两边完全对等,不要出现差异。

Slave启动同步

prepare 同步

开启同步前要么保证主从状态一致,要么保证master的binlog足够slave同步到master一致的状态,不然Slave_sql_ruuning这个标识位会在某些时段变为NO,即便通过__set global sql_slave_skip_counter=1__设置将状态同步到一致,但数据极大可能仍然不是一致的,因为这个操作是跳过了master的部分操作。所以启动同步前一定要保证binlog足够,不然就手工同步一遍master到slave,再开启操作。

获取master状态
1
2
3
4
5
6
7
mysql> show master status\G;
File: log.000004
Position: 154
Binlog_Do_DB:
Binlog_Ignore_DB: mysql,performance_schema,information_schema
Executed_Gtid_Set:
1 row in set (0.00 sec)

从中提前两个结果 File和Position作为slave启动的脚本。

启动slave

将master的file和position填入slave的启动语句中。

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
stop slave;
change master to
master_host="******",
MASTER_USER = 'root',
MASTER_PASSWORD = '*****',
MASTER_LOG_FILE = 'log.000004' ,
MASTER_LOG_POS = 154
;
start slave;

show slave status\G;
Slave_IO_State: Waiting for master to send event
Master_Host: ******
Master_User: root
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: log.000004
Read_Master_Log_Pos: 154
Relay_Log_File: 8d5dd16836bd-relay-bin.000005
Relay_Log_Pos: 355
Relay_Master_Log_File: log.000004
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Exec_Master_Log_Pos: 154
Seconds_Behind_Master: 0

1 row in set (0.00 sec)

最后一个show slave status是为了确认是不是启动了额。监测指标核心是三个:Relay_Master_Log_File、Exec_Master_Log_Pos、Seconds_Behind_Master。前两个是核心指标,即slave读取到了master的binlog文件和位置,如果这两个参数的结果和master的binlog文件名称和postion一致,即可认为master和slave状态一致。SBM一定意义上表示了slave和master的时延是多少,这个时延是以slave的时间减去master的binlog时间戳,有作弊的可能,因此主要查看前两个参数。

环境

  • Java 8
  • mysql 5.7 1主1从
  • mycat 1.6
  • druid 1.4

错误情况

启动应用,使用jmeter做压力测试,先将所有url都执行一遍,所有的select和insert类型的请求通过,包含update或者delete的请求失败,查看日志,报错信息:Connection is read-only. Queries leading to data modification are not allowed

select insert update delete
o o x x

更改连接池配置

添加spring-boot的数据库链接池为非只读

1
spring.datasource.druid.default-read-only=false
select insert update delete
o o x x

添加事务,网上给出最多的方法

给所有涉及update或者delete的service类添加事务声明

1
2
import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = false)
select insert update delete
o o x x

切换为直连数据库,意外收获。

select insert update delete
o o o o

没有写入的问题,可以证明是mycat的连接的问题。

排查mycat的配置信息

1
2
3
4
<schema name="testdb" checkSQLschema="false" sqlMaxLimit="100">
<table name="t1" datanode = "dn1" rule=""/>
....
</schema>
1
2
3
4
5
<user name="root" defaultAccount="true">
<property name="password">123456</property>
<property name="schemas">testdb</property>
<property name="readOnly">false</property>
</user>

这部分生命信息看上去没有任何问题。

spring 事务的源代码

org.springframework.orm.jpa.vendor.HibernateJpaDialect.java beginTransaction方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public Object beginTransaction(EntityManager entityManager, TransactionDefinition definition)throws PersistenceException, SQLException, TransactionException {
Session session = getSession(entityManager);
if (definition.getTimeout() != TransactionDefinition.TIMEOUT_DEFAULT) {
session.getTransaction().setTimeout(definition.getTimeout());
}
boolean isolationLevelNeeded = (definition.getIsolationLevel() !=TransactionDefinition.ISOLATION_DEFAULT);
Integer previousIsolationLevel = null;
Connection preparedCon = null;
if (isolationLevelNeeded || definition.isReadOnly()) {
if (this.prepareConnection) {
preparedCon = HibernateConnectionHandle.doGetConnection(session);
//下面一行的调用起到了关键作用
previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(preparedCon, definition);
}else if (isolationLevelNeeded) {
throw new InvalidIsolationLevelException(getClass().getSimpleName() +
" does not support custom isolation levels since the 'prepareConnection' flag is off.");
}
}
entityManager.getTransaction().begin();
FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly());
return new SessionTransactionData(session, previousFlushMode, preparedCon, previousIsolationLevel);
}

org.springframework.jdbc.datasource.DataSourceUtils.java Line:91

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

@Nullable
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition) throws SQLException {
Assert.notNull(con, "No Connection specified");
if (definition != null && definition.isReadOnly()) {
try {
if (logger.isDebugEnabled()) {
logger.debug("Setting JDBC Connection [" + con + "] read-only");
}
//直接写死为readOnly=true。这个原因需要调查。
con.setReadOnly(true);
} catch (RuntimeException | SQLException var4) {
for(Object exToCheck = var4; exToCheck != null; exToCheck = ((Throwable)exToCheck).getCause()) {
if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
throw var4;
}
}
logger.debug("Could not set JDBC Connection read-only", var4);
}
}
Integer previousIsolationLevel = null;
if (definition != null && definition.getIsolationLevel() != -1) {
if (logger.isDebugEnabled()) {
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to" + definition.getIsolationLevel());
}
int currentIsolation = con.getTransactionIsolation();
if (currentIsolation != definition.getIsolationLevel()) {
previousIsolationLevel = currentIsolation;
con.setTransactionIsolation(definition.getIsolationLevel());
}
}
return previousIsolationLevel;
}

这个方法的解释是针对select方法,hibernate会强制设为只读请求,但为啥update方法会来这里,没有找到原因。

添加参数useLocalSessionState=true

1
spring.datasource.url=jdbc:log4jdbc:mysql://ip:8066/testdb?useUnicode=true&characterEncoding=utf-8&useLocalSessionState=true

解决了这个问题,关于useLocalSessionState的解释是:__
默认情况下,我们的连接串信息没有包含useLocalSessionState参数的设置,这个值默认为false。
这个值的作用是驱动程序是否使用autocommit,read_only和transaction isolation的内部值(jdbc端的本地值)。
如果设置为false,则需要这个判断这三个参数的场景,都需要发语句到远端请求,比如更新语句前,
需要发语句select @@session.tx_read_only确认会话是否只读。
如果设置为true,则只需要取本地值即可。__

似乎是本地和远程服务器的配置有关,但远程服务器是mycat,莫非mycat的链接不是只读的,但没有找到mycat是只读的配置选项。

参数可以解决readOnly的问题,但只读链接的产生过程和原因不明。

0%