/images/logo.jpg

[Java] 2. Unit Test Basic Usage

[Java] 2. Unit Test Basic Usage

Mockito Basic Usage

In unit testing, many tests (except Util classes) need to mock some services to ensure only the current logic being tested is actually tested.

Specifically, you need to first mock an object, then mock the methods of this object, and then you can use the mocked methods to test the logic you want to test.

Mock Objects

First, you need to declare the interfaces/implementation classes that need to be mocked in the Test class. For example:

[Spring] 1. Spring Web CompletionStage Overview

[Spring] 1. Spring Web CompletionStage Overview

Introduction

  • Spring-web provides excellent support for asynchronous operations, which can be used for many optimizations through asynchronous return forms:
    • Improve throughput
    • Fine-tune execution thread pools for various business operations

Sample Code

 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
/**
 * async interface controller
 *
 * @author Goody
 * @version 1.0, 2024/9/19
 */
@RestController
@RequestMapping("/goody")
@RequiredArgsConstructor
@Slf4j
public class GoodyAsyncController {

    private static final AtomicInteger COUNT = new AtomicInteger(0);
    private static final Executor EXECUTOR = new ThreadPoolExecutor(
        10,
        10,
        10,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(10),
        r -> new Thread(r, String.format("customer-t-%s", COUNT.addAndGet(1)))
    );

    @GetMapping("async/query1")
    public CompletionStage<String> asyncQuery1() {
        log.info("async query start");
        return CompletableFuture.supplyAsync(() -> {
            log.info("async query sleep start");
            ThreadUtils.sleep(1000);
            log.info("async query sleep done");
            log.info("async query done");
            return "done";
        }, EXECUTOR);
    }

    @GetMapping("sync/query1")
    public String syncQuery1() throws InterruptedException {
        log.info("sync query start");
        final CountDownLatch latch = new CountDownLatch(1);
        EXECUTOR.execute(() -> {
            log.info("sync query sleep start");
            ThreadUtils.sleep(1000);
            log.info("sync query sleep done");
            latch.countDown();
        });
        latch.await();
        log.info("sync query done");
        return "done";
    }
}
  • Defined a custom thread pool for use in asynchronous scenarios
  • Here’s one synchronous and one asynchronous endpoint, let’s look at the specific request scenarios

Single Request

Request Async Interface

curl –location ‘127.0.0.1:50012/goody/async/query1’

[Java] 1. Lombok

[Java] 1. Lombok

Introduction

  • Lombok is a Java library that can automatically generate repetitive code for Java classes, such as getter, setter, equals, and hashCode methods.

Principles

  • Lombok’s working principle is based on Annotation Processors and the Java Compiler API.

  • When Lombok is discovered by the compiler, it uses annotation processors to modify Java code. During this process, Lombok checks for specific annotations in classes and generates corresponding code based on these annotations, such as getter and setter methods.

[MySQL] 1. MySQL Fast Query Insights

[MySQL] 1. MySQL Fast Query Insights

Preface

  • Let’s start with some of the most common optimization measures we use in daily learning and business scenarios

    1. Do more things in the same unit of time

      • QuickSort uses the binary search idea to sort multiple arrays within a single loop

        /images/3.%20%E6%B5%85%E8%B0%88MySQL%E5%BF%AB%E9%80%9F%E6%9F%A5%E8%AF%A2/1.%20%E5%BF%AB%E6%8E%92.jpg

    2. Query less information in total

      • KMP algorithm preprocesses the main string to reduce the number of matches. This is the power of logic

[Network] 1. File Transfer Optimization Sharing

[Network] 1. File Transfer Optimization Sharing

Background (Sensitive Data Masked)

  • Due to various requirements, we need to upload data to overseas OSS for storage. So we developed a proxy service to maintain data and perform encryption operations. During this process, we discovered that data upload and download were very slow. After a series of investigations, we finally located the root cause of the problem and provided a solution. We’re now sharing the troubleshooting process.
  • Of course, one prerequisite is internal network connectivity through dedicated line network access to achieve theoretical physical limits. Using complex and lengthy public networks is neither suitable for file security nor for large file long-term transmission.

Service-Level Issues

Description
  • Initially, we suspected it was due to data writing to disk being too slow. Because uploads must be written to disk to prevent files from being too large. Downloads use direct streaming transmission, which is very reasonable. The only improvement would be to perform streaming encryption and transmission for uploads, but the current issue is not significant.

Phenomenon

  • Using our written script to upload 1M of encrypted data took nearly 2 seconds
1
2
3
4
5
import requests
requests.post(f"{url}/upload/files", files={
    "data": ('', upload_data, "application/json"),
    "file": transfer_data
})
1
2
3
4
5
6
7
$ python oss.py --file_input=./1M.data --region=us --model=3 --range=5
encrypted_upload
upload ./1M.data, encrypt cost 4.714599609375, upload cost 1788.95849609375
upload ./1M.data, encrypt cost 10.140625, upload cost 1945.90087890625
upload ./1M.data, encrypt cost 9.924560546875, upload cost 1756.984130859375
upload ./1M.data, encrypt cost 8.694580078125, upload cost 1930.31201171875
upload ./1M.data, encrypt cost 8.279296875, upload cost 1739.38623046875

Packet Capture

  • After communicating with operations, they suspected it was a network issue and performed packet capture to investigate.

Packet Capture Demonstration

Ping Packets

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ sudo tcpdump -i bond0 | grep x.x.x.x1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on bond0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:21:19.255718 IP public2.alidns.com.domain > domain1.36590: 43190 1/0/1 A x.x.x.x1 (88)
16:21:19.256404 IP domain1 > x.x.x.x1: ICMP echo request, id 32590, seq 1, length 64
16:21:19.456754 IP x.x.x.x1 > domain1: ICMP echo reply, id 32590, seq 1, length 64
16:21:20.257688 IP domain1 > x.x.x.x1: ICMP echo request, id 32590, seq 2, length 64
16:21:20.458076 IP x.x.x.x1 > domain1: ICMP echo reply, id 32590, seq 2, length 64
16:21:21.259088 IP domain1 > x.x.x.x1: ICMP echo request, id 32590, seq 3, length 64
16:21:21.459506 IP x.x.x.x1 > domain1: ICMP echo reply, id 32590, seq 3, length 64
16:21:22.260538 IP domain1 > x.x.x.x1: ICMP echo request, id 32590, seq 4, length 64
16:21:22.460976 IP x.x.x.x1 > domain1: ICMP echo reply, id 32590, seq 4, length 64
1
2
3
4
5
6
7
8
9
$ ping domain1
PING domain1 (x.x.x.x1) 56(84) bytes of data.
64 bytes from x.x.x.x1 (x.x.x.x1): icmp_seq=1 ttl=58 time=200 ms
64 bytes from x.x.x.x1 (x.x.x.x1): icmp_seq=2 ttl=58 time=200 ms
64 bytes from x.x.x.x1 (x.x.x.x1): icmp_seq=3 ttl=58 time=200 ms
^C
--- domain1 ping statistics ---
4 packets transmitted, 3 received, 25% packet loss, time 3004ms
rtt min/avg/max/mdev = 200.395/200.419/200.456/0.517 ms

Three-Way Handshake

1
2
3
16:54:06.286416 IP domain1.33666 > x.x.x.x1.http: Flags [S], seq 2682796272, win 64240, options [mss 1460,sackOK,TS val 2595135963 ecr 0,nop,wscale 7], length 0
16:54:06.486797 IP x.x.x.x1.http > domain1.33666: Flags [S.], seq 2198055866, ack 2682796273, win 62643, options [mss 1460,sackOK,TS val 2062390218 ecr 2595135963,nop,wscale 7], length 0
16:54:06.486840 IP domain1.33666 > x.x.x.x1.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 2595136163 ecr 2062390218], length 0

Four-Way Handshake

1
2
3
16:54:28.356723 IP domain1.54028 > x.x.x.x1.http: Flags [F.], seq 1746, ack 215, win 501, options [nop,nop,TS val 2595158034 ecr 2062412087], length 0
16:54:28.557169 IP x.x.x.x1.http > domain1.54028: Flags [F.], seq 215, ack 1747, win 477, options [nop,nop,TS val 2062412289 ecr 2595158034], length 0
16:54:28.557222 IP domain1.54028 > x.x.x.x1.http: Flags [.], ack 216, win 501, options [nop,nop,TS val 2595158234 ecr 2062412289], length 0

tcpdump Flags

  • Tcpdump flags are flags that indicate TCP connection status or actions. They are usually represented in square brackets in tcpdump output. There are various flags in tcpdump output, and the output may also contain combinations of multiple TCP flags. Some common flags include:
    • S (SYN): This flag is used to establish a connection between two hosts. It is set in the first packet of the three-way handshake.
    • . (No flag): This means no flag is set in the packet. It is usually used for data transmission or acknowledgment packets.
    • P (PUSH): This flag is used to indicate that the sender wants to send data as soon as possible without waiting for the buffer to fill.
    • F (FIN): This flag is used to terminate the connection between two hosts. It is set in the last packet of the four-way handshake.
    • R (RST): This flag is used to reset connections that are in an invalid state or encounter errors. It is also used to reject unwanted connection attempts.
    • W (ECN CWR): This flag is used to indicate that the sender has reduced its congestion window size according to the network’s Explicit Congestion Notification (ECN).
    • E (ECN-Echo): This flag is used to indicate that the receiver has received a packet with the ECN bit, meaning there is congestion in the network.
  • For example, a packet with flags [S.] means it is a SYN packet, the first step in establishing a TCP connection. A packet with flags [P.] means it is a PUSH packet containing data that the sender wants to transmit quickly. A packet with flags [F.] means it is a FIN packet, the last step in closing a TCP connection.

Why tcpdump Four-Way Handshake Only Has Three Packets

  • The reason tcpdump four-way handshake only has three packets may be due to the following:
    • One possibility is that the passive closing party (the one receiving FIN) sends its own FIN while replying with ACK, combining the second and third handshakes into one packet, saving one packet. In this case, the passive closing party has no more data to send, so it can directly enter the LAST_ACK state and wait for the final ACK from the active closing party.
    • Another possibility is that the active closing party (the one sending FIN) doesn’t reply with ACK promptly after receiving the passive closing party’s FIN, but sends ACK after some time with the RST flag set, indicating a forced connection reset. In this case, the active closing party may have encountered an exception or timeout, so it no longer waits for the 2MSL time but directly enters the CLOSE state.
    • Another possibility is that tcpdump didn’t capture all packets due to network delay or packet loss, causing certain handshake packets not to be captured. In this case, you can try re-capturing packets or increasing the capture time range to see if you can see the complete four-way handshake process.

Actual Data

1
2
3
$ python oss.py --file_input=./1K.data --file_output=./download-1M.data --region=us --model=3 --range=5
encrypted_upload
http://domain1 upload ./1K.data, encrypt cost 1.530029296875, upload cost 408.5546875
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ sudo tcpdump -i bond0 | grep x.x.x.x1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on bond0, link-type EN10MB (Ethernet), capture size 262144 bytes

16:54:06.286416 IP domain1.33666 > x.x.x.x1.http: Flags [S], seq 2682796272, win 64240, options [mss 1460,sackOK,TS val 2595135963 ecr 0,nop,wscale 7], length 0
16:54:06.486797 IP x.x.x.x1.http > domain1.33666: Flags [S.], seq 2198055866, ack 2682796273, win 62643, options [mss 1460,sackOK,TS val 2062390218 ecr 2595135963,nop,wscale 7], length 0
16:54:06.486840 IP domain1.33666 > x.x.x.x1.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 2595136163 ecr 2062390218], length 0
16:54:06.486930 IP domain1.33666 > x.x.x.x1.http: Flags [P.], seq 1:292, ack 1, win 502, options [nop,nop,TS val 2595136164 ecr 2062390218], length 291: HTTP: POST /upload/files HTTP/1.1
16:54:06.486960 IP domain1.33666 > x.x.x.x1.http: Flags [P.], seq 292:1746, ack 1, win 502, options [nop,nop,TS val 2595136164 ecr 2062390218], length 1454: HTTP
16:54:06.687234 IP x.x.x.x1.http > domain1.33666: Flags [.], ack 292, win 488, options [nop,nop,TS val 2062390419 ecr 2595136164], length 0
16:54:06.687279 IP x.x.x.x1.http > domain1.33666: Flags [.], ack 1746, win 477, options [nop,nop,TS val 2062390419 ecr 2595136164], length 0
16:54:06.690277 IP x.x.x.x1.http > domain1.33666: Flags [P.], seq 1:215, ack 1746, win 477, options [nop,nop,TS val 2062390422 ecr 2595136164], length 214: HTTP: HTTP/1.1 200 OK
16:54:06.690314 IP domain1.33666 > x.x.x.x1.http: Flags [.], ack 215, win 501, options [nop,nop,TS val 2595136367 ecr 2062390422], length 0
16:54:06.692023 IP domain1.33666 > x.x.x.x1.http: Flags [F.], seq 1746, ack 215, win 501, options [nop,nop,TS val 2595136369 ecr 2062390422], length 0
16:54:06.892401 IP x.x.x.x1.http > domain1.33666: Flags [F.], seq 215, ack 1747, win 477, options [nop,nop,TS val 2062390624 ecr 2595136369], length 0
16:54:06.892448 IP domain1.33666 > x.x.x.x1.http: Flags [.], ack 216, win 501, options [nop,nop,TS val 2595136569 ecr 2062390624], length 0
  • Actually uploading 1M of data for analysis, simplified here.
  • Since all time jumps occur in packets returned from the server side, the problem is now very clear. Due to the actual physical distance between Shenzhen and the US East Coast, the 200ms round trip has reached its limit. So it’s actually reasonable.

Soul-Searching Question

  • At this point, a soul-searching question arises: why was it faster when using the public network before?
  • After communicating with colleagues from sister departments and simulating their code, we tested using AWS SDK
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import boto3
from boto3.s3.transfer import TransferConfig

def download():
    s3_client = client(access_key, access_secret, host)
    GB = 1024 ** 3
    config = TransferConfig(multipart_threshold=2 * GB, max_concurrency=10, use_threads=True)
    s3_client.download_file(Bucket="bucket", Key="name-100.jpg", Filename="name-100.jpg", Config=config)

if __name__ == '__main__':
    download()
    # ...
    download()

Results

[Github] 1. Deploy GitHub Blog Site

[Github] 1. Deploy GitHub Blog Site

intro

  • GitHub Pages is a static site hosting service that takes HTML, CSS, and JavaScript files straight from a repository on GitHub, optionally runs the files through a build process, and publishes a website.

pre-work

create a repository

  • Create a repository named your_github_username.github.io, where your_github_username is your GitHub username. For example, if your GitHub username is octocat, the repository name should be octocat.github.io.

hugo install

create a blog site

hugo init site

1
2
3
4
5
6
7
8
# create directory
mkdir your_github_username.github.io
# cd to directory
cd your_github_username.github.io
# init site
hugo new site .
# git init, make sure it's a git repository
git init

add a theme

1
2
3
4
5
6
7
8
9
# add a theme, here we use LoveIt theme.
git submodule add https://github.com/dillonzq/LoveIt.git themes/LoveIt
# now the git is main branch which is not stable, we need to checkout to the latest stable version.
cd themes/LoveIt
git checkout v0.3.0
cd ../..
# now, there should be a .gitmodules file in your directory. if not, you need to run `git init` first.
# copy the exampleSite config file to the root directory
cp themes/LoveIt/exampleSite/hugo.toml .

modify the config file

  • modify the config file hugo.toml

bashURL

1
baseURL = "https://gooddayday.github.io"

themes directory

1
2
3
# themes directory
# 主题目录
themesDir = "./themes"

website title

1
2
3
# website title
# 网站标题
title = "GoodyHao's Blog"

website images

1
2
3
# website images for Open Graph and Twitter Cards
# 网站图片, 用于 Open Graph 和 Twitter Cards
images = ["/logo.jpg"]

website icon

  • put icon file in the static directory

gitRepo

  • modify the gitRepo to your public git repo url
1
2
3
# public git repo url only then enableGitInfo is true
# 公共 git 仓库路径,仅在 enableGitInfo 设为 true 时有效
gitRepo = "https://github.com/GOODDAYDAY/GOODDAYDAY.github.io"

github deploy

create a workflow file

  • create a file .github/workflows/deploy.yaml, and add the following content:
 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
name: Deploy Hugo to GitHub Pages
on:
  push:  # Trigger condition: push code to master branch
    branches:
      - master
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest  # Use Ubuntu environment
    steps:
      # 1. Check out repository code (recursively pull theme submodule)
      - uses: actions/checkout@v4
        with:
          submodules: true

      # 2. Install Hugo (use extended version, supports SASS)
      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'  # Or specify version (e.g., '0.147.2')
          extended: true

      # 3. Cache dependencies (speed up subsequent builds)
      - uses: actions/cache@v3
        with:
          path: |
            resources/_gen
            public
          key: ${{ runner.os }}-hugo-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-hugo-

      # 4. Build Hugo site (enable compression)
      - name: Build Hugo site
        run: hugo --minify

      # 5. Deploy to GitHub Pages (automatically push public directory to gh-pages branch)
      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v4
        with:
          github_token: ${{ secrets.GITHUB_TOKEN  }}  # GitHub automatically provided Token (no manual creation needed)
          publish_dir: ./public  # Point to Hugo generated static files directory
          force_orphan: true  # Force create new commit (avoid branch history confusion)
  • github repository settings -> pages -> source -> select gh-pages branch and / (root) folder -> save
    • if gh-pages branch not exist, need to push code to github first

/images/1.%20deploy%20github%20blog%20site.md/1.%20github-page-setting.png