Nicksxs's Blog

What hurts more, the pain of hard work or the pain of regret?

0%

上周在处理一个 nginx 配置的时候,发现了一个之前不理解的小点,说一个场景,就是我们一般的处理方式就是一个 ip 端口只能配置一个域名的服务,比如 https://nicksxs.me 对应配置到 127.0.0.1:443,如果我想要把 https://nicksxs.com 也解析到这个服务器,并转发到不同的下游,这里就需要借助所谓的 SNI 的功能

Server Name Indication

A more generic solution for running several HTTPS servers on a single IP address is TLS Server Name Indication extension (SNI, RFC 6066), which allows a browser to pass a requested server name during the SSL handshake and, therefore, the server will know which certificate it should use for the connection. SNI is currently supported by most modern browsers, though may not be used by some old or special clients.
来源
机翻一下:在单个 IP 地址上运行多个 HTTPS 服务器的更通用的解决方案是 TLS 服务器名称指示扩展(SNI,RFC 6066),它允许浏览器在 SSL 握手期间传递请求的服务器名称,因此,服务器将知道哪个 它应该用于连接的证书。 目前大多数现代浏览器都支持 SNI,但某些旧的或特殊的客户端可能不使用 SNI。

首先我们需要确认 sni 已被支持

在实际的配置中就可以这样

stream {
  map $ssl_preread_server_name $stream_map {
    nicksxs.me nme;
    nicksxs.com ncom;
  }

  upstream nme {
    server 127.0.0.1:8000;
  }
  upstream ncom {
    server 127.0.0.1:8001;
  }

  server {
    listen 443 reuseport;
    proxy_pass $stream_map;
    ssl_preread on;
  }
}

类似这样,但是这个理解是非常肤浅和不完善的,只是简单记忆下,后续再进行补充完整

还有一点就是我们在配置的时候经常配置就是 server_name,但是会看到直接在使用 ssl_server_name,
其实在listen 标识了 ssl, 对应的 ssl_server_name 就等于 server_name,不需要额外处理了。

题目介绍

You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad.

Suppose you have n versions [1, 2, ..., n] and you want to find out the first bad one, which causes all the following ones to be bad.

You are given an API bool isBadVersion(version) which returns whether version is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API.

示例

Example 1:

Input: n = 5, bad = 4
Output: 4
Explanation:
call isBadVersion(3) -> false
call isBadVersion(5) -> true
call isBadVersion(4) -> true
Then 4 is the first bad version.

Example 2:

Input: n = 1, bad = 1
Output: 1

简析

简单来说就是一个二分查找,但是这个问题其实处理起来还是需要搞清楚一些边界问题

代码

public int firstBadVersion(int n) {
    // 类似于双指针法
    int left = 1, right = n, mid;
    while (left < right) {
        // 取中点
        mid = left + (right - left) / 2;
        // 如果不是错误版本,就往右找
        if (!isBadVersion(mid)) {
            left = mid + 1;
        } else {
        // 如果是的话就往左查找
            right = mid;
        }
    }
    // 这里考虑交界情况是,在上面循环中如果 left 是好的,right 是坏的,那进入循环的时候 mid == left
    // 然后 left = mid + 1 就会等于 right,循环条件就跳出了,此时 left 就是那个起始的错误点了
    // 其实这两个是同一个值
    return left;
}

往右移动示例

往左移动示例

结果

题目介绍

Given an integer array nums of length n and an integer target, find three integers in nums such that the sum is closest to target.

Return the sum of the three integers.

You may assume that each input would have exactly one solution.

简单解释下就是之前是要三数之和等于目标值,现在是找到最接近的三数之和。

示例

Example 1:

Input: nums = [-1,2,1,-4], target = 1
Output: 2
Explanation: The sum that is closest to the target is 2. (-1 + 2 + 1 = 2).

Example 2:

Input: nums = [0,0,0], target = 1
Output: 0

Constraints:

  • 3 <= nums.length <= 1000
  • -1000 <= nums[i] <= 1000
  • -10^4 <= target <= 10^4

简单解析

这个题思路上来讲不难,也是用原来三数之和的方式去做,利用”双指针法”或者其它描述法,但是需要简化逻辑

code

public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        // 当前最近的和
        int closestSum = nums[0] + nums[1] + nums[nums.length - 1];
        for (int i = 0; i < nums.length - 2; i++) {
            if (i == 0 || nums[i] != nums[i - 1]) {
                // 左指针
                int left = i + 1;
                // 右指针
                int right = nums.length - 1;
                // 判断是否遍历完了
                while (left < right) {
                    // 当前的和
                    int sum = nums[i] + nums[left] + nums[right];
                    // 小优化,相等就略过了
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    // 这里判断,其实也还是希望趋近目标值
                    if (sum < target) {
                        left++;
                    } else {
                        right--;
                    }
                    // 判断是否需要替换
                    if (Math.abs(sum - target) < Math.abs(closestSum - target)) {
                        closestSum = sum;
                    }
                }
            }
        }
        return closestSum;
    }

结果

寻找原因

这次碰到一个比较奇怪的问题,应该统一发布脚本统一给应用启动参数传了个 -Dserver.port=xxxx,其实这个端口会作为 dubbo 的服务端口,并且应用也不提供 web 服务,但是在启动的时候会报embedded servlet container failed to start. port xxxx was already in use就觉得有点奇怪,仔细看了启动参数猜测可能是这个问题,有可能是依赖的二方三方包带了 spring-web 的包,然后基于 springboot 的 auto configuration 会把这个自己加载,就在本地复现了下这个问题,结果的确是这个问题。

解决方案

老版本 设置 spring 不带 web 功能

比较老的 springboot 版本,可以使用

SpringApplication app = new SpringApplication(XXXXXApplication.class);
app.setWebEnvironment(false);
app.run(args);

新版本

新版本的 springboot (>= 2.0.0)可以在 properties 里配置

spring.main.web-application-type=none

或者

SpringApplication app = new SpringApplication(XXXXXApplication.class);
app.setWebApplicationType(WebApplicationType.NONE);

这个枚举里还有其他两种配置

public enum WebApplicationType {

	/**
	 * The application should not run as a web application and should not start an
	 * embedded web server.
	 */
	NONE,

	/**
	 * The application should run as a servlet-based web application and should start an
	 * embedded servlet web server.
	 */
	SERVLET,

	/**
	 * The application should run as a reactive web application and should start an
	 * embedded reactive web server.
	 */
	REACTIVE

}

相当于是把none 的类型和包括 servlet 和 reactive 放进了枚举类进行控制。

题目介绍

Given a 2D grid of size m x n and an integer k. You need to shift the grid k times.

In one shift operation:

Element at grid[i][j] moves to grid[i][j + 1].
Element at grid[i][n - 1] moves to grid[i + 1][0].
Element at grid[m - 1][n - 1] moves to grid[0][0].
Return the 2D grid after applying shift operation k times.

示例

Example 1:

Input: grid = [[1,2,3],[4,5,6],[7,8,9]], k = 1
Output: [[9,1,2],[3,4,5],[6,7,8]]

Example 2:

Input: grid = [[3,8,1,9],[19,7,2,5],[4,6,11,10],[12,0,21,13]], k = 4
Output: [[12,0,21,13],[3,8,1,9],[19,7,2,5],[4,6,11,10]]

Example 3:

Input: grid = [[1,2,3],[4,5,6],[7,8,9]], k = 9
Output: [[1,2,3],[4,5,6],[7,8,9]]

提示

  • m == grid.length
  • n == grid[i].length
  • 1 <= m <= 50
  • 1 <= n <= 50
  • -1000 <= grid[i][j] <= 1000
  • 0 <= k <= 100

解析

这个题主要是矩阵或者说数组的操作,并且题目要返回的是个 List,所以也不用原地操作,只需要找对位置就可以了,k 是多少就相当于让这个二维数组头尾衔接移动 k 个元素

代码

public List<List<Integer>> shiftGrid(int[][] grid, int k) {
        // 行数
        int m = grid.length;
        // 列数
        int n = grid[0].length;
        // 偏移值,取下模
        k = k % (m * n);
        // 反向取下数量,因为我打算直接从头填充新的矩阵
        /*
         *    比如
         *    1 2 3
         *    4 5 6
         *    7 8 9
         *    需要变成
         *    9 1 2
         *    3 4 5
         *    6 7 8
         *    就要从 9 开始填充
         */
        int reverseK = m * n - k;
        List<List<Integer>> matrix = new ArrayList<>();
        // 这类就是两层循环
        for (int i = 0; i < m; i++) {
            List<Integer> line = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                // 数量会随着循环迭代增长, 确认是第几个
                int currentNum = reverseK + i * n +  (j + 1);
                // 这里处理下到达矩阵末尾后减掉 m * n
                if (currentNum > m * n) {
                    currentNum -= m * n;
                }
                // 根据矩阵列数 n 算出在原来矩阵的位置
                int last = (currentNum - 1) % n;
                int passLine = (currentNum - 1) / n;

                line.add(grid[passLine][last]);
            }
            matrix.add(line);
        }
        return matrix;
    }

结果数据


比较慢