Cass-总结-溯源码二期

架构

业务状态应该持久化保存, 而不是根据持久化数据动态判断

如果实在需要动态判断, 则将状态获取封装成共用方法

接口

设计时定义接口应多花时间思考, 定下来后不要轻易改动

定义好后改起来很麻烦, 涉及到其他很多人

编写修改原有接口的 Story 时, 只需要列出新增或修改的字段

在列表中标出是该字段新增还是修改. 这样可以节省 Story 的设计时间, 并且入参也同样清晰

对外接口即使很简单也需要写 Story, 提供 Url/入参/出参 给其他组

方便其他组第一时间进行开发

数据库

生产环境大数据量表结构新增字段的方法

  1. 新建子表关联原表
    • 缺点: 数据库结构增熵
  2. 使用临时表保存原表数据(分批插入), 清空原表数据, 增加字段后重新导入
    • 缺点: 需锁表, 阻塞业务
  3. 使用工具增加
    • 缺点: 操作麻烦, 需要数据库权限
  4. 使用云数据库无感知新增

    阿里云可以像普通增加字段一样操作. 不锁表, 不会阻塞业务

    • 缺点: 需要上云

该问题是业界遇到的通用问题, 云上已有成熟的解决方案

大数据量的表设计应该把常用的数据冗余成字段保存

这样可以很方便的根据常用的数据查询或者查询出常用的数据, 而不需要对入参或出参进行额外处理

单元测试

单元测试实际修改或插入数据库的操作必须回滚

这样会导致数据库的数据非正常变更, 从而产生难以定位的 Bug

MySQL-一次其它慢SQL导致的慢SQL的定位过程

分析过程

  1. 使用 StopWatch 统计接口各子方法耗时:

    发现 dao 层方法 storeCustomerMapper.getStoreCustomerInfoList 耗时达 27 秒

  2. 检查慢 sql 日志该方法用到的 sql, 发现其耗时 27 秒

  3. 该 sql 单独执行很快, 因此猜测是该 sql 执行时, 有其它大 sql 正在执行, 导致该 sql 执行缓慢

  4. 检查其它慢 sql, 发现如下 sql, 其结束执行时间为 3:28:00, 执行持续时间为 387s, 即其开始执行时间为 3:21:33. 而我们的 sql 执行开始时间为 3:22:14, 正好在其执行之后执行

  5. 检查慢 sql 日志中其它天的日志, 情况相同, 因此判断是该 sql 导致我们的 sql 执行缓慢导致 Service 层接口返回超时, 从而导致 Controller 层接口返回空

问题

  1. 为什么一个慢 SQL 会导致并发的另一个慢 SQL 同时变慢?

    因为磁盘只有一个磁头, 所以磁盘 I/O 是串行的, 两个 SQL 并发查询时. CPU 会在两个线程间来回切换. 从而导致磁头在两个 SQL 所需的扇区中来回切换, 此时磁头是随机读取, 其速度比一个 SQL 查询时的顺序读取慢很多

Spring-Boot-Note-查询中分页和排序的通用解决方案

Java-Note-spring-jdbc学习笔记

spring-jdbc 学习笔记

总结-开思-品牌库

收获

技术

架构设计

  1. 业务字段和系统字段区分开, 不要混在一起用. 如申请人和创建人. 因为以后业务变化, 创建人可能不再是申请人, 而是其他角色
  2. 设计单据相关表时注意表的拆分, 单据的通用数据(单据状态, 类型等)和对应业务的数据(品牌相关数据)应分开保存. 这样结构更清晰, 代码编写起来更简单
  3. 两个不同概念的东西不要用同一个名称, 比如品牌名称和候选品牌名称不能同时使用品牌名称这个称呼. 以避免对功能的定义产生误会, 从而导致代码返工或者线上事故
  4. 对于执行耗时较长的功能应在story拆分阶段识别出来, 以避免上线后造成的超时异常
  5. 分析可能存在的性能瓶颈, 以避免开发中再识别出来影响迭代时间节点或者线上发布后产生问题
  6. 抽出项目共性
  7. 查询时统一用主键id而不是业务id。因为数据集关联用的主键id

数据库

  1. mybatis 之后一定要加, 否则很可能在和其它标签组合时产生语法错误

不足

  1. 对高性能系统设计的经验不足
  2. 在受到情绪刺激时应该冷静的应对

Java-Note-记一次log4j配置文件无法加载问题的解决过程

起因

在一次由于其他原因更改了POM文件配置的操作后发现: spring boot项目中配置的log4j2.yml文件无法正常加载, 显示错误如下:

ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.

分析过程

发现在更改POM文件过程中删除了几个依赖. 其中有一个是:

1
2
3
4
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

进入com.fasterxml.jackson.dataformat.yaml包中, 发现与解析yml文件相关的类YAMLParser, debug该类的构造器, 发现org.apache.logging.log4j.core包中的JsonConfigurationFactory对象初始化时会加载com.fasterxml.jackson.dataformat.yaml包中的类. 具体代码如下:

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
55
56
57
58
public class JsonConfigurationFactory extends ConfigurationFactory {
...
private static final String[] dependencies = new String[] {
"com.fasterxml.jackson.databind.ObjectMapper",
"com.fasterxml.jackson.databind.JsonNode",
"com.fasterxml.jackson.core.JsonParser"
};

private final boolean isActive;

// 尝试加载com.fasterxml.jackson.dataformat.yaml包中的dependencies类, 如果失败, 将isActive置false
public JsonConfigurationFactory() {
for (final String dependency : dependencies) {
if (!Loader.isClassAvailable(dependency)) {
LOGGER.debug("Missing dependencies for Json support");
isActive = false;
return;
}
}
isActive = true;
}
...
// 如果isActive置false, 则返回null, 使用default configuration
public Configuration getConfiguration(final ConfigurationSource source) {
if (!isActive) {
return null;
}
return new JsonConfiguration(source);
}
...
}

public abstract class ConfigurationFactory extends ConfigurationBuilderFactory {
...
private static class Factory extends ConfigurationFactory {
...
public Configuration getConfiguration(final String name, final URI configLocation) {
...
Configuration config = getConfiguration(true, name);
if (config == null) {
config = getConfiguration(true, null);
if (config == null) {
config = getConfiguration(false, name);
if (config == null) {
// 从这里进入上面的getConfiguration方法
config = getConfiguration(false, null);
}
}
}
if (config != null) {
return config;
}
// 此处为起因中的语句
LOGGER.error("No log4j2 configuration file found. Using default configuration: logging only errors to the console.");
return new DefaultConfiguration();
}
}
}

由上面的代码可知, 当无法加载com.fasterxml.jackson.dataformat.yaml包中的类时, 会弹出起因中的错误

解决方案

POM文件中增加com.fasterxml.jackson.dataformat.yaml依赖

总结

遇到这种框架问题, 可以尝试初步定位问题原因, 再通过debug框架源码来精确定位问题所在位置从而找到解决方案

Untitled

起因

今天公司测试人员在测试环境中测试网站新功能时, 突然在浏览器中弹出警告框, 如下图所示:

排查过程

问题非常诡异, 因为在这个问题出现前后, 没有人更新过应用. 在应用源码中全文搜索alertaa字符串, 没有发现能够弹出该警告框的代码块. 在仔细排查了源码没有找到疑点后, 重新对问题的出现过程进行分析, 发现有时弹出一次, 有时弹出两次, 并且全都是在按条件查询后弹出, 于是尝试确定警告框出现次数, 使用筛选功能进行条件查询, 发现弹窗的次数与后台返回的数据数量相关, 于是把怀疑聚焦在后端传入的数据中. 发现后端传入的数据中某个字段的值中包含<script>alert('aa')</script字符串, 于是确定问题起源.

根本原因

jquery-1.8.2中的html: function( value ) {...}方法的参数value就是经过处理后的包含微博正文字段等所有表格展示内容的html字符串, 然后在该方法里面对该字符串进行解析, 此时会执行其中的<script>代码片段
例如

1
value = "<table class="datagrid-btable" cellspacing="0" cellpadding="0" border="0"><tbody><tr id="datagrid-row-r1-2-0" datagrid-row-index="0" class="datagrid-row  " ><td field="uid_nick"  ><div style="text-align:center;white-space:normal;height:auto;" class="datagrid-cell datagrid-cell-c1-uid_nick">_笺罗</div></td><div style="text-align:center;white-space:normal;height:auto;" class="datagrid-cell datagrid-cell-c1-weibo_content">CMS原发-博文-维系类-带图0710,ADD,sum,000,;‘’“”:?,;''"":?...(@!!#2333#*/^&~+-(2333二三)<br /> &nbsp; <script>alert('aa');</script>,2-0,1.[偷乐]</div></td><td field="exposure_rate"  ><div style="text-align:center;white-space:normal;height:auto;" class="datagrid-cell datagrid-cell-c1-exposure_rate">6</div></td>></tr></tbody></table>"

此时会执行datagrid-cell datagrid-cell-c1-weibo_content类中的<script>字符串

事后总结

  1. 代码编写方面
    1. 在后台对需要插入数据库的字符串进行检查或转义. 虽然不是本人写的插入功能, 但也要引以为戒
    2. 在前端对后台传入的字符串进行转义
  2. bug定位方面
    1. 定位每次出现的具体状况有细微差别的bug时, 应找到bug的出现规律, 有助于发现bug原因

Java-Spring-使用构造器自动装配时的循环依赖问题

Spring中不推荐使用字段注入的方式, 而是推荐使用构造器注入的方式. 构造器注入的方式有很多优点, 但有一点要注意, 构造器注入可能会产生循环依赖的问题.

字段注入会在对象构建之后进行, 因此不会产生构造器构建时的循环依赖问题, 但构造器注入时会有这个问题, 比如两个Serivce互相调用, 此时使用构造器注入会发生循环依赖

暂时的解决方法是将互相调用的Service使用字段注入, 最终的解决方案是重新设计类的划分, 使Service无法互相调用

Java-Note-JVM-一次OutOfMemoryError的处理过程的记录

在启动公司Websophic容器时出现报错, 如下:

1
2
3
4
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (malloc) failed to allocate 160088 bytes for AllocateHeap
# An error report file with more information is saved as:
# /users/xxx/hs_err_pidxxxx.log

然后查看/users/xxx/hs_err_pidxxxx.log内容:

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
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 357564416 bytes for committing reserved memory.
# Possible reasons:
# The system is out of physical RAM or swap space
# The process is running with CompressedOops enabled, and the Java Heap may be blocking the growth of the native heap
# Possible solutions:
# Reduce memory load on the system
# Increase physical memory or swap space
# Check if swap backing store is full
# Decrease Java heap size (-Xmx/-Xms)
# Decrease number of Java threads
# Decrease Java thread stack sizes (-Xss)
# Set larger code cache with -XX:ReservedCodeCacheSize=
# JVM is running with Unscaled Compressed Oops mode in which the Java heap is
# placed in the first 4GB address space. The Java Heap base address is the
# maximum limit for the native heap growth. Please use -XX:HeapBaseMinAddress
# to set the Java Heap base and to place the Java Heap above 4GB virtual address.
# This output file may be truncated or incomplete.
#
# Out of Memory Error (os_linux.cpp:2749), pid=4252, tid=0x00007f3f38bb5700
#
# JRE version: (8.0_201-b09) (build )
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.201-b09 mixed mode linux-amd64 compressed oops)
# Core dump written. Default location: /users/ems/core or core.4252 (max size 521000 kB). To ensure a full core dump, try "ulimit -c unlimited" before starting Java again
#
...

翻译过来就是本地内存分配失败, 可能的原因有两种

  1. 系统物理内存或虚拟内存不足
  2. 程序在压缩指针模式下运行, Java堆会阻塞本地堆的增长

然后使用free -m命令查询, 发现内存足够:

1
2
3
4
             total       used       free     shared    buffers     cached
Mem: 7983 5415 2568 0 170 1460
-/+ buffers/cache: 3784 4199
Swap: 15257 71 15186

那么尝试按第二个问题进行解决, 可能的方案有两种:

  1. 禁止使用压缩指针模式
    1. 方法: 在catalina.sh中JAVA_OPTS的值后面添加-XX:-UseCompressedOops, 再重启tomcat
  2. 将Java堆的起始地址设置成使Java堆大小+起始地址大于4G,
    1. 原因: 请参考 https://blogs.oracle.com/poonam/running-on-a-64bit-platform-and-still-running-out-of-memory,
    2. 方法: 在这里我将起始地址简单直接的设为4G即4294967296

在尝试过这两种方法后发现依然报同样的错误, 这时我在想会不会是堆内存过大, 导致系统无法分配内存, 于是进行尝试: 把堆内存减少一半, 看看效果.

  1. 方法: 在在catalina.sh中JAVA_OPTS的值中把原来的-Xms1024m -Xmx2048m改为-Xms512m -Xmx1024m, 再重启tomcat

结果JVM启动成功, 问题解决.

后续思考: 为什么在可用内存充足的情况下系统无法分配给JVM更多内存? 一直没有想到完美的解释, 如果有明白的兄弟可以指教一下.

尝试对后续思考进行解答: 原因应该还是内存不足, 可能操作系统会预留一些内存, 而我的机器上默认的启动参数是-Xms1024m -Xmx2048m, 可能已经超过了系统允许分配的最高值, 因此无法分配内存. 当我使用java -Xms10m -Xmx20m可以启动成功, java -Xms500m -Xmx2000m会失败, 因此, 应该还是内存不足的问题

对后续思考的最终解答及该问题的完美解决方案:

这个问题是由于/proc/meminfo下的vm.overcommit_memory被设置成不允许overcommit造成的

首先了解一下overcommit的意思: 用户进程申请的是虚拟地址, 而这个虚拟地址是不允许任意申请的, 因为虚拟内存需要物理内存做支撑, 如果分配太多虚拟内存, 会对性能参数影响. overcommit就是对虚拟内存的过量分配

vm.overcommit_memory的用处: 控制过量分配的策略. 这个参数一共有3个可选值:

  1. 0: Heuristic overcommit handling. 就是由操作系统自己决定过量分配策略
  2. 1: Always overcommit. 一直允许过量分配
  3. 2: Don’t overcommit. 不允许过量分配

在这个案例里面, 使用sysctl vm.overcommit_memory来查看, 发现vm.overcommit_memory = 2, 即采用的是不允许过量分配的设置. 而在错误日志中也证明了这一点:

1
2
CommitLimit:    15951192 kB
Committed_AS: 15837036 kB

解决方案是sudo sysctl vm.overcommit_memory=0, 即vm.overcommit_memory = 0, 允许系统自己决定过量分配策略

Linux-Commond-UsefullCommond

  1. top

    1. 显示包含指定 command string 的线程信息并定时刷新(使用 bash)
      1
      __keyword=name_of_process; (while :; do __arg=$(pgrep -d',' -f $__keyword); if [ -z "$__arg" ]; then top -u 65536 -n 1; else top -c -n 1 -p $__arg; fi; uptime; sleep 1; done;)
  2. strace

    1. 追踪目标命令并显示时间和地址及将输出结果放入指定文件
      1
      $ strace -tt -i -o outputfile java -version
  3. scp: 远程文件传输

    1. 将文件传送给指定的远程机器
      1
      $ scp websophic.20190313.v1.3.2.zip ems@198.87.104.101:/users/ems/
  4. tar

    1. tar 解压 tar.gz 文件
      1
      $ tar -xvzf target.tar.gz
  5. tcpdump

    1. 获取指定网卡指定主机的包数据并存入指定文件中
      1
      tcpdump -i ens192 host 192.168.254.200 -w cap190715_2009.pcap
  6. grep

    1. 在文件夹中查找字符串
      1
      grep -rnw '/path/to/somewhere/' -e 'pattern'
  7. sed

    1. 获取文本中指定行范围
      1
      sed -n 5,8p file
  8. dd

    1. 检测磁盘速度
      1
      dd if=/dev/zero of=/tmp/output conv=fdatasync bs=384k count=1k; rm -f /tmp/output
  9. java remote debug

    1
    /usr/local/jdk1.8.0_144/bin/java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=12543 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=20250 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=192.168.255.205 -Dspring.profiles.active=test cms_web-0.0.1-SNAPSHOT.war
  10. tail 高亮

    1
    tail -f 日志文件 |grep -E “高亮的关键字1|高亮的关键字2|…|高亮的关键字N” -A10 -B10 --color=auto
  11. docker 命令

    1. docker exec: 在 docker 中执行 bash 命令
      1. docker exec -it CONTAINER_NAME bash: 在 docker 中新建一个 bash
      2. docker exec -it CONTAINER_NAME jstack PID > threadDump.log: 将 thread dump 保存到指定文件中
      3. sudo docker cp e4e2ffe62ca0:arthas-boot.jar /tmp/: 获取容器中的文件
  12. top 命令
    1. top -Hp pid: 显示一个进程的线程运行信息列表
  13. npm react 后台运行

    (npm run start&)

  14. 切换 java version

    alternatives --config java

  15. CentOS 7 开启端口

    firewall-cmd --zone=public --permanent --add-port=8081/tcp
    firewall-cmd --reload

  16. 后台执行程序

    1
    > nohup APP_NAME
  17. 启动基于 Python Flask 框架开发的应用

    1
    > python3 APP_PATH/app.py
  18. 使用 pm2 后台启动和管理基于 React 框架开发的应用

    1
    2
    3
    4
    # 安装 pm2 模块
    > npm install pm2 -g
    # 使用 pm2 后台启动应用
    > pm2 start npm -- start
  19. 后台启动基于 React 框架开发的应用

    1
    (npm run start&)