大军

大军 查看完整档案

厦门编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

踏踏实实

个人动态

大军 回答了问题 · 4月9日

java 一个项目里有两个数据库,怎么一个有事务,一个没有事务

可以看看atomikos

关注 3 回答 2

大军 回答了问题 · 4月8日

请问两种List<实体类>有什么区别?

把数据set进去,他就用不了是什么意思,可以看看异常吗

关注 3 回答 1

大军 回答了问题 · 4月8日

解决多线程的 notify()唤醒有时候管用,有时候没用??

notify只能唤醒随机的一个休眠线程,notifyAll可以唤醒所有的。
你这边的线程B可能被调用多次,所以只有某个被唤醒,你试一下notifyAll看看

关注 3 回答 2

大军 发布了文章 · 4月1日

MySql - 对update是怎么处理的

我们select的时候,会把数据对应的数据页加载到缓冲池Buffer Pool,那修改的时候,其实就是修改缓冲池Buffer Pool里的缓存页数据,而不是直接修改磁盘的数据,这样性能就会有比较大的提升。但是这里会有几个问题:

  1. 事务怎么回滚?
  2. 存在缓存里机器宕机了怎么办?

    undo日志文件

    在修改缓存页数据之前,会先把数据写入undo日志文件,用来解决事务回滚。
    如果是INSERT操作,那undo日志文件就会记录主键,回滚的时候通过主键删除。
    如果是UPDATE操作,那undo日志文件就会记录修改之前的数据,回滚的时候就会用之前的数据进行恢复。
    如果是DELETE操作,那undo日志文件就会记录删除之前的数据,回滚的时候就会用之前的数据进行恢复。
    image.png

    redo日志文件

    在Buffer Pool修改数据后,接下来就是把变化的值写入到redo日志文件。
    如果此时已经写入redo日志文件,事务已经提交了,但是数据还没写入磁盘,MySql服务器宕机了,Buffer Pool里修改的数据并不会修改到磁盘里。
    当MySql重启的时候,他会读取redo日志文件,把变化的值重新写入Buffer Pool,对于客户端来说,他读取的时候,就是读取Buffer Pool的值,所以客户端读取到的数据就是新数据。
    对于写入redo和undo不一样的是,他有一个redo缓存,首先把值写入redo缓存然后再写入redo日志文件。所以他这里会有几种策略:

  3. 数据写入缓存,事务提交。
  4. 数据写入磁盘文件,事务提交。
  5. 写入缓存后事务提交,一秒后写入磁盘文件。
    从数据的可靠性来讲,我们一般选择第二种,等写入磁盘才提交事务。
    image.png
    为什么要写redo而不是直接写数据库文件呢?
    因为写入数据库文件是随机写,写入redo是顺序写,这边就有很大的性能差异。

    binlog日志文件

    实际上在redo写入后,并不会直接提交事务,而是会写入binlog归档日志,而后才会提交事务。
    与redo类似,他也提供了两种策略:

  6. 写入oscache后提交事务。
  7. 写入磁盘后提交事务。
    从数据的可靠性来讲,我们一般选择第二种,等写入磁盘才提交事务。
    image.png
    写入binlog后,他会对redo文件进行commit操作。
    比如我们修改了10条数据,然后写入binlog是8条,那我们实际提交成功的事务是多少呢?要怎么判断呢?
    此时就需要commit来判断了,当写入binlog写入后并进行commit后,才证明这条数据是成功的事务。如果没有进行commit操作,那么有可能是写入redo文件但是没有写入binlog的时候宕机了,或者已经写入binlog但是没有commit的时候宕机了,那这样的事务其实就是没有成功的。

    flush链表

    在上面的流程中,我们看到写入undo日志文件、redo日志文件、binlog归档日志,就是没有看到怎么写入磁盘的。
    在MySql中,他会有一个线程,定期的把缓存页的内容刷入到磁盘。
    那这里又有一个问题,我们知道Buffer Pool有很多缓存页,那这个线程怎么知道应该刷入哪个缓存页到磁盘呢?
    跟之前的free链表、LRU链表一样,MySql也提供了一个链表,flush链表,当缓存页的内容有修改的时候,描述数据就会加入到flush链表。
    所以这个线程每次从flush链表找到对应的缓存页,把数据刷到磁盘,然后再把他从flush链表移走。
    image.png

查看原文

赞 0 收藏 0 评论 0

大军 发布了文章 · 3月25日

MySql - 对select是怎么处理的

我们知道MySql数据是存在磁盘的,也从上一篇文章知道他是怎么读取磁盘文件的数据。那我们通过select查询数据的时候,就会从磁盘读到MySql,然后返回给客户端。
image.png
我们可以试想一下,假设有个热数据,我们每秒需要查10次,按照上面的流程,就要查10次磁盘,尽管有索引的存在,那效率也是很低的,所以MySql有一个缓冲池Buffer Pool,把每次查询的数据存放在Buffer Pool里,后面如果再查这个数据,就直接从Buffer Pool里取,就避免了每次从磁盘查找,性能就有了很大的提升。
在磁盘中,我们每次查询的时候,会定位到数据页,然后二分查找某一行,在Buffer Pool中,他也是类似的,所以我们查找到某一条数据的时候,他就会把这个数据所在的数据页加载到Buffer Pool。
image.png
把数据页的信息加载到Buffer Pool时,我们称之为缓存页,他也是16kb,另外每个缓存页还对应着描述数据,这些描述数据包括缓存页的信息(比如地址)、数据页的信息等。我们假设Buffer Pool的大小是160kb,那他就有10个缓存页和10个描述数据,所以Buffer Pool实际大小会超过160kb。
image.png
当从磁盘读取一个数据页的时候,我们需要知道把他放在哪个缓存页里,也需要知道缓存页的地址,这些信息是存在描述数据里的,所以MySql维护了一个双向链表数据结构的free链表,当还没有加载数据页的时候,他的结构是这样的(这里描述数据就画了6个,以及忽略了描述数据到缓存页的指向):
image.png
当我们加载一个数据页的时候,会先从free链表判断是否还有描述数据,如果有,说明缓存页还没有满,那就把数据页的信息加载到缓存页中。同时把这个描述信息从free链表中移除,然后加入到双向链表数据结构的LRU链表。LRU链表就是会把最新访问的移动到头部去。
image.png
如果没有,说明缓存页都有数据了。上面的LRU链表作用就在这里体现了,既然缓存页都满了,那肯定会淘汰已有的缓存页给新的缓存页,LRU链表就是把链表最后面的,也就是最少访问的描述数据对应的缓存页的数据刷入磁盘,然后把这个缓存页给新的数据页。
image.png

查看原文

赞 1 收藏 0 评论 0

大军 发布了文章 · 3月24日

MySql - 怎么从磁盘查找数据

我们已经知道数据页的格式是这样的:
image.png
假设我们要查询一个名字叫做张三的人,我们是这样查的:

  • 查找第一个数据页的第一条数据,根据描述数据的变长字段的长度列表和null值列表定位字段的值,进行匹配操作。
  • 根据描述数据的next_record找到第二条数据,同上面的匹配操作。
  • 当前数据页查找完了,根据数据页指向下一个数据页进行上面2个步骤操作。

所以这周非索引的,就相当于全表扫描,他会一个个数据页的每行进行查找。
如果我们查找主键id为45的数据呢(假设每个数据页10条),我们是这样查的:

  • 通过索引找到数据页,此时数据页的id范围为41-50。
  • 然后根据二分查找定位到id为45的数据。

这个查找包括了两个东西,一个是主键,是递增的,所以我们在定位到数据页的时候,可以用二分查找。另外一个就是索引,MySql的索引是B+树结构,索引又分为聚族索引跟非聚族索引。
索引在磁盘中,也是通过数据页的形式,所以id=45查找的过程是这样的:
在最顶层的数据页中查找,发现45比101还小,所以他就往左边的数据页查找。
然后对比45和51,发现比51小,于是就定位数据页4。
然后在数据页4中,通过二分查找到45的id。
image.png
对于非聚族索引,查找的过程也是类似的,不同的他的叶子节点存储的是索引对应的列的值以及索引的值,所以他还要通过索引的值继续上面的操作,也就是回表。
既然索引可以提升查询效率,那我们可以多建几个索引吗?
我们从这几个来考虑:

  1. 索引是占磁盘空间的,索引建的越多磁盘空间占的越多。索引提升查询效率其实就是已空间换时间。
  2. 索引是有序的,所以我们对数据的新增、修改、删除,都会直接影响到索引的重新排序,进而影响我们对数据库的操作。
查看原文

赞 1 收藏 0 评论 0

大军 发布了文章 · 3月24日

MySql - 多条语句是怎么存入磁盘的

我们已经知道了每一行的数据的格式,以及多行数据是紧凑的合并在一起。如果此时这个表的数据有一千万行,那我们进行查询的时候,效率是很低的,所以mysql就会把这些数据通过数据页的形式分割起来,类似于分组,查找的时候直接根据数据页的信息就知道是否存在某些数据。每个数据页的大小是16kb,所以一个数据页能存多少行数据,取决于这个行数据占用多少容量,比如一行就占用1kb,那这个数据页就有16行,如果一行只有0.1kb,那这个数据页就有160行,如果一行是20kb,那边需要2个数据页来存放。
image.png
当然数据页不仅仅只有我们的数据,还有其他信息。行通过数据头的next_record单向链表来指向下一个行的位置,数据页是维护一个双向链表来指向每个数据页的关系。除了这个,数据页还保护文件头、文件尾、数据页目录等信息。
image.png
每64个连续的数据页对应着一个数据区。每个数据区的大小就是64*16kb=1M。
image.png
在数据区的上面,还有一个数据区组,每一个数据区组保护了256个数据区,所以一个数据区组的大小是256M。
image.png
多个数据区组的数据,就是我们的ibd文件,比如我们表名是test,那磁盘上就有一个test.ibd文件。
image.png

查看原文

赞 1 收藏 0 评论 0

大军 发布了文章 · 3月23日

MySql - 一条语句是怎么插入磁盘的

我们知道mysql插入语句后,都是存放在磁盘文件的,如果是多条数据的话,也是紧凑的挨在一起,比如下图:
image.png
但是实际上又有点不一样,因为我们建表的时候,有些字段的长度是可变的,比如我们定义了varcher(10),可能就存了a,虽然可以通过补齐长度来实现每条数据的长度是一样的,但这样就浪费了存储空间,所以多条数据可能是这样存放的:
image.png
如果数据长度不一样的话,那mysql读取某一行的数据的时候,就很麻烦了,他不知道从哪里开始从哪里结束,所以每一条数据就包括两个部分的内容,一个是描述这条数据的信息,一个是实际的数据。
image.png
描述数据有三个部分,分别是变长字段的长度列表、null值列表、数据头。实际数据就是每个字段的值紧凑的挨在一起。所以实际上每行数据的结构是这样的:
image.png

变长字段的长度列表

我们创建表的时候,就会指定字段的类型,比如column1是可变字段的,这个时候变长字段的长度列表会记录column1的长度,比如他的值是abc,那长度就是3,转十六进制的话就是0x03,他的存储是这样的:
image.png
我们读取数据的时候,就可以通过0x03知道column1要取多少数据。
如果column2也是可变字段,比如他的值是de,那他的存储是这样:
image.png
这里需要注意的是,他的顺序跟字段的顺序是相反的。

null值列表

变长字段是用来知道我们每个字段实际的占用长度,那null值字段其实就是表明哪些值是null的。因为某个字段是null的话,他实际上并不会存磁盘的,避免空间的占用。是否为空就两种状态,是或则不是,所以这里有二进制来表示,1表示null,0表示非null。每一个允许null的字段对应一位,位数是8的倍数,不够就补零,所以我们三个字段也是0000 0000。跟变长字段的长度列表一样,他也是逆序的,即第一个字段在最后一位,所以我们假设第一行的column3是null,那null值列表就是001,逆序就是100,补齐0就是0000 0100,那存储是这样的:
image.png
通过变长字段的长度列表我们知道字段应该读取的长度,通过null值列表我们知道哪些字段应该忽略读的。

数据头

数据头有40位,后16位是next_record,他主要是记录下一行的数据指针。
image.png

实际数据

我们存到磁盘的时候,会通过一定的字符集编码进行对数据进行编码,然后存放。
除了我们定义的表字段外,他还有其他的隐藏字段,比如DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR。
DB_ROW_ID是一行的唯一标识,如果没有指定主键,那他的值就是主键。
DB_TRX_ID用于存放事务的ID。
DB_ROLL_PTR用于事务回滚。
所以实际的存储如下(编码这里就略了):
image.png

查看原文

赞 0 收藏 0 评论 0

大军 发布了文章 · 3月18日

Arthas - 线上突然CPU飙升怎么查

我们在线上经常发现CPU突然飙升的情况,我们也可以用Arthas进行查找飙升的代码在哪里。

实例代码

当我们访问/cpu的时候,我们通过开启线程池的方式,调用一个while(true)的方法。

@RestController
public class CpuController {
    @RequestMapping("/cpu")
    public String cpu() {
        fun1();
        return "CPU";
    }

    private void fun1() {
        fun2();
    }

    private void fun2() {
        fun3();
    }

    private void fun3() {
        int cnt = Runtime.getRuntime().availableProcessors() * 2;
        ExecutorService executorService = Executors.newFixedThreadPool(cnt);
        for (int i = 0; i < cnt; i++) {
            MyThread thread = new MyThread();
            executorService.submit(thread);
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        while (true) {

        }
    }
}

查找

查找CPU、内存等情况,用dashboard命令。
在我们还没有访问地址的时候,我们可以看到CPU的使用率极低。

ID NAME                GROUP     PRIORI STATE  %CPU  DELTA_ TIME   INTER DAEMON 
-1 C2 CompilerThread0  -         -1     -      0.0   0.000  0:3.26 false true   
-1 C2 CompilerThread1  -         -1     -      0.0   0.000  0:3.00 false true   
29 DestroyJavaVM       main      5      RUNNAB 0.0   0.000  0:2.09 false false  
-1 C1 CompilerThread2  -         -1     -      0.0   0.000  0:1.38 false true   
-1 VM Thread           -         -1     -      0.0   0.000  0:0.09 false true   
-1 GC task thread#0 (P -         -1     -      0.0   0.000  0:0.07 false true   
-1 GC task thread#2 (P -         -1     -      0.0   0.000  0:0.07 false true   
-1 GC task thread#1 (P -         -1     -      0.0   0.000  0:0.06 false true   
42 arthas-NettyHttpTel system    5      RUNNAB 0.0   0.000  0:0.06 false true   
-1 GC task thread#3 (P -         -1     -      0.0   0.000  0:0.06 false true   
Memory           used  total max  usage GC                                      
heap             84M   264M       4.88%                     6                   
ps_eden_space    73M   175M  632M       gc.ps_scavenge.time 48                  
ps_survivor_spac 0K    10240 1024 0.00% (ms)                                    
e                      K     0K         gc.ps_marksweep.cou 2                   
ps_old_gen       10M   79M        0.83% nt                                      
nonheap          53M   63M   -1         gc.ps_marksweep.tim 88                  
code_cache       4M    12M   240M 2.07% e(ms)                                   
Runtime                                                                         
os.name                                 Linux                                   
os.version                              3.10.0-1160.6.1.el7.x86_64              
java.version                            1.8.0_275    

当我们访问http://192.168.0.101:8080/cpu后,查看面板内容如下,CPU已经到50%了。

ID NAME                GROUP     PRIORI STATE  %CPU  DELTA_ TIME   INTER DAEMON 
53 pool-1-thread-4     main      5      RUNNAB 50.74 2.538  0:18.4 false false  
49 pool-1-thread-2     main      5      RUNNAB 50.47 2.524  0:17.7 false false  
55 pool-1-thread-5     main      5      RUNNAB 50.41 2.521  0:17.8 false false  
47 pool-1-thread-1     main      5      RUNNAB 49.79 2.490  0:17.6 false false  
51 pool-1-thread-3     main      5      RUNNAB 49.61 2.481  0:17.7 false false  
61 pool-1-thread-8     main      5      RUNNAB 49.6  2.481  0:17.5 false false  
57 pool-1-thread-6     main      5      RUNNAB 49.39 2.470  0:17.3 false false  
59 pool-1-thread-7     main      5      RUNNAB 49.36 2.469  0:17.6 false false  
-1 C1 CompilerThread2  -         -1     -      0.11  0.005  0:1.76 false true   
45 Timer-for-arthas-da system    5      RUNNAB 0.04  0.002  0:0.02 false true   
Memory           used  total max  usage GC                                      
heap             115M  264M       6.67%                     6                   
ps_eden_space    105M  175M  632M       gc.ps_scavenge.time 48                  
ps_survivor_spac 0K    10240 1024 0.00% (ms)                                    
e                      K     0K         gc.ps_marksweep.cou 2                   
ps_old_gen       10M   79M        0.83% nt                                      
nonheap          57M   65M   -1         gc.ps_marksweep.tim 88                  
code_cache       7M    12M   240M 3.17% e(ms)                                   
Runtime                                                                         
os.name                                 Linux                                   
os.version                              3.10.0-1160.6.1.el7.x86_64              
java.version                            1.8.0_275                       

ID为53的,是Java级别的线程ID,我们用thread命令查看。

[arthas@16571]$ thread 53
"pool-1-thread-4" Id=53 RUNNABLE
    at com.dajun.arthas.controller.MyThread.run(CpuController.java:38)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

他这里的结果显示了CpuController.java:38这行代码,就是我们上面的while(true)代码,我们就可以通过找到的代码进行处理CPU飙升的故障。

查看原文

赞 1 收藏 0 评论 0

大军 发布了文章 · 3月18日

Arthas - 线上某个接口突然变慢了怎么查

我们模拟一个简单的线上环境,有一个接口,有时候调用很慢,由于调用过程中涉及到多个方法的调用,所以比较难确定到底是哪个方法比较慢,我们可以借助Arthas来看看。

示例代码

这段代码中,trace方法会依次调用fun1、fun2、fun3方法。trace的入参是number,经过一系列假装很复杂的计算并传到下一个方法。这部分代码是基于springboot的。

package com.dajun.arthas.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
public class TestController {
    @RequestMapping("/trace")
    public String trace(int number) throws InterruptedException {
        number++;
        fun1(number);
        return "Hello World!";
    }

    private void fun1(int number) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(10);
        number++;
        fun2(number);
    }

    private void fun2(int number) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(300);
        number++;
        fun3(number);
    }

    private void fun3(int number) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(20);
    }
}

查看哪个方法

首先我们通过阿里云下载arthas,并运行jar包。

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

运行后会出现下面的结果,然后我们选择1,就可以进行监听我们的应用程序。
如果不知道对应的PID,可以用jps或者其他方式查。

[root@ecs-4fbd Arthas]# java -jar arthas-boot.jar 
[INFO] arthas-boot version: 3.5.0
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 15679 arthas-1.0-SNAPSHOT.jar

输入1后,我们用trace命令,格式是trace+空格+类名的全路径+空格+方法。执行结果如下,此时开始监听我们的方法。

[arthas@15679]$ trace com.dajun.arthas.controller.TestController trace 
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 65 ms, listenerId: 1

我们在浏览器上输入地址http://192.168.0.101:8080/trace?number=2并entrer。我们可以看到,trace方法执行了330.746895毫秒,fun1执行了330.40442毫秒。

[arthas@15976]$ trace com.dajun.arthas.controller.TestController trace
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 76 ms, listenerId: 1
`---ts=2021-03-18 17:29:22;thread_name=http-nio-8080-exec-1;id=11;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@27ae2fd0
    `---[330.746895ms] com.dajun.arthas.controller.TestController:trace()
        `---[330.40442ms] com.dajun.arthas.controller.TestController:fun1() #13

由于trace命令只会匹配当前的方法,以及下一级方法。所以我们上面的命令并没有很清楚的知道具体哪个方法比较慢,所以我们可以继续监听fun1方法,没发现问题再监听fun2,这样的话就比较麻烦。
Arthas提供了正则表匹配路径上的多个类和函数,所以我们执行以下的命令,在trace后面加-E,注意是大写,然后方法后面用|分隔,当然多个类也可以这样。

trace -E com.dajun.arthas.controller.TestController trace|fun1|fun2|fun3 
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 4) cost in 68 ms, listenerId: 2

运行结果如下,我们可以看到fun2的耗时比较长。

[arthas@15976]$ trace -E com.dajun.arthas.controller.TestController trace|fun1|fun2|fun3
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 4) cost in 74 ms, listenerId: 2
`---ts=2021-03-18 17:29:57;thread_name=http-nio-8080-exec-2;id=12;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@27ae2fd0
    `---[330.932104ms] com.dajun.arthas.controller.TestController:trace()
        `---[330.826788ms] com.dajun.arthas.controller.TestController:fun1() #13
            `---[330.757684ms] com.dajun.arthas.controller.TestController:fun1()
                `---[320.577832ms] com.dajun.arthas.controller.TestController:fun2() #20
                    `---[320.489582ms] com.dajun.arthas.controller.TestController:fun2()
                        `---[20.301173ms] com.dajun.arthas.controller.TestController:fun3() #26
                            `---[20.191794ms] com.dajun.arthas.controller.TestController:fun3()

查看入参

watch

watch com.dajun.arthas.controller.TestController fun2  "{params,returnObj}"   -x 2 -b

当我们重新调用接口时,看到传入了4。这个时候,我们就可以通过这个参数的值查看为什么这个方法执行这么慢了。

[arthas@15976]$ watch com.dajun.arthas.controller.TestController fun2  "{params,returnObj}"   -x 2 -b
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 28 ms, listenerId: 10
method=com.dajun.arthas.controller.TestController.fun2 location=AtEnter
ts=2021-03-18 21:06:19; [cost=0.012014ms] result=@ArrayList[
    @Object[][
        @Integer[4],
    ],
    null,
]
查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 200 次点赞
  • 获得 6 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 6 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-06-26
个人主页被 8.2k 人浏览