openGauss鉴权配置文件pg_hba.conf

准确来说是 Postgres 的鉴权配置文件,因为 openGauss 源于 PostgresSQL 数据库,主要对内核进行和很大的改动,客户端和服务端的驱动协议修改比较少,但支持了 SHA256 等新的密码存储鉴权方式。

首先进入 docker 容器,

1
2
docker ps # 查看 openGauss 容器的 id
docker exec -it <container-id> /bin/bash; exit # 进入 docker 容器进行操作

切换到 omm 用户,启动 gsql:

1
2
su - omm # 切换到 omm 用户
gsql # 启动 gsql

在 gsql 交互式终端中输入以下命令获取 pg_hba.conf 鉴权配置文件的路径:

1
2
3
4
5
6
7
8
9
10
[omm@4ae1997a3f9a opengauss]$ gsql
gsql ((openGauss 1.1.0 build 392c0438) compiled at 2020-12-31 20:07:42 commit 0 last mr )
Non-SSL connection (SSL connection is recommended when requiring high-security)
Type "help" for help.

omm=# show hba_file;
hba_file
-------------------------------------
/var/lib/opengauss/data/pg_hba.conf
(1 row)

此外,对于 enmotech/opengauss 镜像,openGauss 的安装路径在:

1
/usr/local/opengauss

数据路径在:

1
/var/lib/opengauss

pg_hba.conf

  • hba.conf 为 trust,跳过认证;
  • 配置 hba.conf 为 MD5 方式,修改 postgresq.conf 配置文件中的 GUC 参数 password_encryption_type 为 0,表示 MD5 密码存储方式。在这种方式下,与原来PG基本是一样,方便调通流程。可以看一下MD5 认证的基本流程;
  • 配置 hba.conf 为 SHA256 方式,修改 postgresq 配置文件中的 GUC 参数 pssword_encryption_type 为 2 表示 SHA256 密码存储方式(这是 openGauss 新的鉴权方式)调整 SHA256 认证方式;

对于 authmethod-options,其支持以下选项 (cert 和 gss 不要求,但如果有时间的话可以试试):

  • trust: 不验密,禁止远程主机使用trust 方式访问集群
  • reject: 拒绝访问
  • md5: md5认证,默认不支持
  • sha256: sha256认证(推荐使用)
  • cert: 客户端证书认证
  • gss: kerberos认证

password_encryption_type 参数说明

该字段决定采用何种加密方式对用户密码进行加密存储。修改此参数的配置不会自动触发已有用户密码加密方式的修改,只会影响新创建用户或修改用户密码操作。该参数属于 SIGHUP 类型参数,请参考表4-134中对应设置方法进行设置。

取值范围 0、1、2

  • 0 表示使用md5 方式对密码加密
  • 1表示采用sha256和md5两种方式分别对密码加密
  • 2表示采用sha256方式对密码加密

将 trust 方式改为 sha256 认证后,通过 gsql 登录数据库需要指定数据库名,用户名及密码,如:

1
2
3
4
5
6
7
8
9
[omm@4ae1997a3f9a ~]$ gsql -d postgres -U gaussdb -W Enmo@123
gsql: invalid option -- 'D'
Try "gsql --help" for more information.
[omm@4ae1997a3f9a ~]$ gsql -d postgres -U gaussdb -W Enmo@123
gsql ((openGauss 1.1.0 build 392c0438) compiled at 2020-12-31 20:07:42 commit 0 last mr )
Non-SSL connection (SSL connection is recommended when requiring high-security)
Type "help" for help.

postgres=>

其中,postgres 是数据库名,gaussdb 是用户名,Enmo@123 是密码。

更多详细信息查阅参考链接中的官方文档 The pg_hba.conf - Postgres Documentation

参考链接

  1. How do I find the path to pg_hba.conf from the shell?
  2. The pg_hba.conf - Postgres Documentation

借助docker搭建openGauss测试环境

华为的 openGauss 数据库是一款基于 Postgres 数据库内核开发的一款高性能、高安全、高可靠的企业级关系型数据库。

本来是想跟着官方文档手动搭建环境将 openGauss 的数据库后端给跑起来的,但是无奈官方要求的安装环境过于严苛,我尝试了好几天终究还是失败了。最终选择 docker 方案,幸好已经有前辈做好相关的镜像了,我们只需拉取下来,直接运行就好:

1
2
docker pull enmotech/opengauss # 拉取镜像
docker run --name opengauss --privileged=true -d -e GS_PASSWORD=Enmo@123 -p 5432:5432 enmotech/opengauss:latest # 启动容器

运行容器后,openGauss 的数据库后端将监听在 5432 端口,有以下两种方式可以连接到 openGauss 数据库:

  1. 通过 gsql 命令连接到 openGauss 数据库

首先进入容器

1
2
docker ps # 查看所有正在运行的 docker 容器,确定当前 openGauss 容器的 id
docker exec -it <container-id> /bin/bash; exit # 进入该 docker 容器进行操作

进入容器后,切换到 omm 用户后即可使用 gsql 命令来对数据库执行相应操作

1
2
su - omm # 切换到 omm 用户 
gsql # 连接到 openGauss 数据库
  1. 使用 JetBrains 的 DataGrip 来连接

下载好 DataGrip 后,新建一个 postgres 的 data source,配置信息如下:

默认的用户名是 gaussdb,默认的密码是 Enmo@123,默认的数据库名称是 postgres,默认端口是 5432。

连接成功后,如果只是编写应用项目的话,只需要当作是一般的关系型数据库一样用就可以了,openGauss 支持绝大部分 SQL 特性,基本可以满足日常开发需求。

参考链接

  1. openGauss 官方文档
  2. enmotech/opengauss README - docker hub
  3. 怎样使用 DataGrip 连接数据库

关闭mac合盖休眠

1
2
sudo pmset -a disablesleep 1 # 禁用系统休眠
sudo pmset -a disablesleep 0 # 启用系统休眠

服务器Ping不通的排查方案

linux系统是否允许 ping是由好几个因素决定的,如内核参数和防火墙, 几个因素同时允许时才能ping通:

  1. 内核参数

    1
    2
    3
    4
    5
    # 如果是1则从内核层面忽略所有ICMP ECHO请求
    # 也就是Ping这台服务器收不到任何回应
    cat /proc/sys/net/ipv4/icmp_echo_ignore_all
    # 执行下面这个命令修改这项配置
    echo "0" >/proc/sys/net/ipv4/icmp_echo_ignore_all

  2. 防火墙:iptables

    看这篇教程 Linux系统的ECS中没有禁ping却ping不通的解决方法 - 阿里云

  3. 云防火墙

    云厂商一般会给ECS提供云防火墙,这时候需要检查一下安全组规则有没有拒绝ICMP请求

服务器磁盘叕满了

发现一起听歌的后台监控不能正常运行了,吓得我赶紧打开了阿里云看了一眼服务器状态,好家伙磁盘直接100%了。

清理工作启动!

  1. 查找大文件实用命令

    1
    2
    sudo du -ah --max-depth=1 | sort -h # 按文件(夹)大小排序显示列表
    df -h # 用人类可读的方式展示磁盘剩余空间

  2. 实用清理命令

  • 清理系统日志

    1
    2
    sudo journalctl --vacuum-time=2d # 仅保留最近两天的系统日志,多余的都自动删掉
    sudo journalctl --vacuum-size=50M # 仅保留50M大小的系统日志,多余的都自动删掉

  • 清理 PM2 日志

    1
    pm2 flush app_name

  • 清理 nginx 日志

    1
    rm /www/wwwlogs/*

  1. 恢复故障应用

    MongoDB 在磁盘满的情况下,拒绝执行,并且自动停止服务。可以通过以下命令重启:

    1
    2
    3
    sudo service mongod start # 启动 mongod
    sudo service mongod status # 查看 mongod 的状态
    pm2 restart app_name # 重启应用以便冲洗连接数据库

DOM生命周期事件

用框架的你,可能早已忽略了这些事件API

时间先后关系:

  1. DOMContentLoaded ——通过 document.addEventListener('DOMContentLoaded', handler) 监听。当浏览器已完全加载 HTML,并构建了 DOM 树时触发,但像 <img /> 和 css 样式表这些外部资源可能还未加载完成。 注意:当文档中遇到 <script /> 标签时,DOMContentLoaded 事件会等待脚本完全执行结束后再触发。除非带有是 async/defer 属性的脚本或者通过 document.createElement('script') 创建的脚本(这些脚本会异步加载并执行,不会阻塞 DOMContentLoaded 事件)。
  2. load ——通过 window.onload 监听。当浏览器不仅完全加载 HTML,图片样式表这些外部资源也加载完成后触发。
  3. beforeunload ——通过 window.onbeforeunload 监听。表示用户正在离开,我们可以检查用户是否保存了更改,并询问他是否真的要离开。
  4. unload ——通过 window.onunload 监听。表示用户已经离开,我们可以做一些善后工作。

防抖与节流

防抖和节流都可以用来控制函数执行的频率。

防抖 (debounce)

效果

  • 给定一个时间间隔,比如 200ms
  • 在第一次调用函数时,并不立即执行函数,而是延迟 200ms 再看情况执行
  • 如果在这 200ms 延迟内,函数又被调用了,取消之前的定时器,继续延迟 200ms 毫秒,直到一次 200ms 延迟正常结束且这段延迟内函数没有被调用,这时才真正执行这个函数。

例子

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
const debounce = require('lodash/debounce');

(async () => {
const sayHello = debounce(() => {
console.log(new Date(), 'Hello World');
}, 2000);

for await (_ of Array(10).fill()) {
await new Promise(resume => setTimeout(resume, 1000));
console.log(new Date());
sayHello();
}
})();

// 运行结果:
2021-01-19T01:23:42.802Z
2021-01-19T01:23:43.814Z
2021-01-19T01:23:44.815Z
2021-01-19T01:23:45.819Z
2021-01-19T01:23:46.820Z
2021-01-19T01:23:47.823Z
2021-01-19T01:23:48.825Z
2021-01-19T01:23:49.828Z
2021-01-19T01:23:50.829Z
2021-01-19T01:23:51.829Z
2021-01-19T01:23:53.831Z Hello World

因为这里设置防抖的间隔是 2000ms,而调用 sayHello 是每隔一秒调用一次,所以每当延时一秒的时候,函数又被调用,重新设置了一个新的 2000ms 延时,直到第 10 次调用结束后,这次延时 2000 ms 的间隔内不再有新的函数调用,等延时结束后才真正执行我们所需要防抖的函数:

1
2
3
() => {
console.log(new Date(), 'Hello World');
}

举个极端点的例子,如果在程序运行时,始终保持 1000 ms 的间隔调用函数,而防抖的间隔设置为 2000 ms,那么被防抖的函数永远也不会被执行,因为永远满足不了延时 2000 ms 的时间间隔内这个函数没有被调用的条件。我们把上面那个例子改写一下:

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
const debounce = require('lodash/debounce');

const sayHello = debounce(() => {
console.log(new Date(), 'Hello World');
}, 2000);

setInterval(async () => {
await new Promise(resume => setTimeout(resume, 1000));
console.log(new Date());
sayHello();
}, 1000);

// 运行结果:
2021-01-19T01:36:35.478Z
2021-01-19T01:36:36.490Z
2021-01-19T01:36:37.492Z
2021-01-19T01:36:38.494Z
2021-01-19T01:36:39.496Z
2021-01-19T01:36:40.498Z
2021-01-19T01:36:41.498Z
2021-01-19T01:36:42.500Z
2021-01-19T01:36:43.503Z
2021-01-19T01:36:44.503Z
2021-01-19T01:36:45.506Z
2021-01-19T01:36:46.508Z
... // 永远也不会打印 Hello World

实现

1
2
3
4
5
6
7
8
9
10
const debounce = (fn, interval) => {
let timer = null;

return () => {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(fn, interval);
};
};

毫无疑问,debounce 是一个高阶函数,调用它的返回结果是一个函数。在 debounce 中我们还需要一个闭包变量来 timer 来记录定时器,返回的函数是对被防抖函数的封装,主要思想是每次调用函数时,清除之前的定时器(如果有的话),重新设置一个新的定时器,这样就实现了一个简单的防抖函数。

节流 (throttle)

效果

  • 给定一个时间间隔,比如 200ms
  • 在每个 200ms 节流间隔内,哪怕函数被重复调用多次,也只执行函数一次

例子

改写一下上面那个例子

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
const throttle = require('lodash/throttle');

(async () => {
const sayHello = throttle(() => {
console.log(new Date(), 'Hello World');
}, 5000);

for await (_ of Array(10).fill()) {
console.log(new Date());
sayHello();
await new Promise(resume => setTimeout(resume, 1000));
}
})();

// 执行结果:
2021-01-19T02:18:46.864Z
2021-01-19T02:18:46.872Z Hello World
2021-01-19T02:18:47.878Z
2021-01-19T02:18:48.879Z
2021-01-19T02:18:49.880Z
2021-01-19T02:18:50.882Z
2021-01-19T02:18:51.875Z Hello World
2021-01-19T02:18:51.884Z
2021-01-19T02:18:52.887Z
2021-01-19T02:18:53.890Z
2021-01-19T02:18:54.893Z
2021-01-19T02:18:55.896Z
2021-01-19T02:18:56.886Z Hello World

这里我们设置了节流的间隔为 5000ms ,函数调用的间隔仍然是 1000ms。注意到在 18:46:864 ~ 18:51:864 这段时间间隔内函数被调用了5次,但实际只被执行了1次,这就是节流的作用。在一个节流间隔内,无论调用函数多少次,在这个间隔内只真正执行函数一次。

实现

节流函数的实现有很多种,这里用时间戳的方式实现。

1
2
3
4
5
6
7
8
9
10
const throttle = (fn, interval) => {
let timestamp = 0;

return () => {
if (Date.now() - timestamp > interval) {
timestamp = Date.now();
return fn();
}
};
}

即每次成功执行一次函数后,接下来 5000ms 之内的调用都会被忽略,这就是节流的作用。

生成指定范围内的随机整数

首先来看一下 Math.random 吧,它随机生成一个 0 (包含) ~ 1 (不包含) 之间的浮点数。

基于此,我们可以实现一个函数来生成指定范围内的随机整数(MDN上的例子):

1
2
3
4
5
6
7
8
9
10
11
12
// 用于理解版
const getRandomInt = (min, max) => {
const unit = Math.random();
const range = max - min + 1;
const offset = min;

return Math.floor(unit * range + offset);
}

// 实用精简版
const getRandomInt = (min, max) =>
Math.floor(Math.random() * (max - min + 1) + min);

技巧是 range 多加个1,然后再用 Math.floor 给向下取整。unit * range + offset 随机生成的是一个包含最小值但不包含最大值+1的浮点数,再向下取整就是一个既包含最小值又包含最大值的整数了,可谓是非常巧妙了。

这样生成的随机整数各整数被生成到的概率也是相等的,我们做个测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
const probabilities = Array(11).fill(0);

// 随机生成一百万个1-10之间的整数
Array(1_000_000).fill().forEach(() => probabilities[getRandomInt(1, 10)]++);

console.log(probabilities);
// 结果:
// [
// 0, 99883, 99720,
// 99900, 100143, 99933,
// 100150, 99721, 100453,
// 99823, 100274
// ]

每个整数被生成到的概率都是10万左右,所以通过这种方式生成的随机整数也是等概率随机生成。

谈谈你对前端路由的理解

为什么需要前端路由?

以前传统的前端都是一个URL对应一个页面,所以不存在这个问题。随着SPA(single-page application)即单页应用的发展,组件的变化和更新不再对应着URL变化了。但是我们又需要这种对应关系(比如通过一个URL直接访问一个 SPA 应用的子视图),我们急需一个工具专门负责维护组件状态与页面URL之间的对应关系,这就是前端路由的作用所在,如 react 的 react-router 和 vue 的 vue-router。

实现原理?

  1. 哈希模式 #hash

这个模式主要关注 location.hash 值,可以通过事件 hashchange 监听 URL 的变化,从而去跳转到对应子页面。

  1. 历史模式

这个模式主要关注 location.pathname 的值,可以通过事件 popstate 监听 URL,但是它有很多局限性,只有做出浏览器动作如用户点击了回退按钮或在JavaScript中调用了history.back()/history.forwad()才会触发事件,下面这些情况不会触发 popstate 事件:

  • pushState, replaceState (HTML5 的新 API 改变地址栏而不引起页面变化)
  • a 标签的点击事件

解决思路:

遍历 DOM 中所有的 a 标签,监听 click 事件,手动 pushState 改变浏览器地址,并手动调用 popState 中的回调函数。

rust中怎么判断一个变量的类型

在 rust 中没有类似于 typeof 的操作符来判断一个变量的类型,但是我们可以自己写一个函数来打印变量的类型。

1
2
3
4
5
use std::any::type_name;

fn test_type<T>(_: T) {
println!("{:?}", { type_name::<T>() });
}

用法实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
let tup: (i32, f64, u8, bool, &str) = (500, 3.2, 1, false, "Hello World");
println!("{:?}", tup);
println!("{} {}", tup.0, tup.4);

// typeof
test_type(tup); // "(i32, f64, u8, bool, &str)"
test_type(tup.4); // "&str"

let arr: [i32; 5] = [1, 2, 3, 4, 5];
let months: [&str; 3] = ["Jan", "Feb", "Mar"];
test_type(arr); // "[i32; 5]"
test_type(months); // "[&str; 3]"