使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 启动
tmux
# 启动并设置 session 名称
tmux new -s {session-name}
# attach
tmux a # or at, or attach
# attach by session name
tmux a -t {session-name}
# 列出 session
tmux ls
# 清理 session
tmux kill-session -t {session-name}

# tmux 内
# 重命名 session
C-B $
# 重命名 window
C-B ,
# 关闭 window
C-B &

快捷键

1
2
# active command mode
ctrl + b

Pane

1
2
3
4
5
6
7
8
9
"   splite vertical
% splite horizon
q show pane numbers
o change focued pane
ctrl+o swap panes
[space] 切换布局

x close panel (or exit)
arrow change panel

Session

1
2
3
$  rename session
s list session
d detach (keep session alive)

Window

1
2
3
4
5
6
7
c		create
& close
n next
p previous
w list windows
f find windows
, name windows

拷贝模式

1
2
3
4
5
6
7
# active command mode
ctrl + b

# command
[ 开启拷贝模式, 可以滚动屏幕
q 退出拷贝模式
[Esc] 退出拷贝模式

调整大小

1
2
3
4
5
# 1. 启用 command mode 
ctrl + b

# 2. 调整大小
{滚轮} 调整上下分屏 Pane 的大小

安装

1
$ sudo pacman -S dpkg

示例

1
2
3
# 添加一个 alt
# 链接创建路径 名称 实际文件路径 优先级
sudo update-alternatives --install /usr/bin/cc cc /usr/bin/gcc-4.9 10
1
sudo update-alternatives --config cc  # 选择并设置默认版本

node startup

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# nvm
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash # curl
$ wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash # nvm

# ls-remote
$ nvm ls-remote
$ nvm ls-remote --lts # 仅列长期支持版本

# install
$ nvm install v12.20.0
$ nvm install 16 # 安装最新的 16 主版本的最新稳定版

# use
$ nvm use v12.20.0

# set default
$ nvm alias default v12.20.0

# check
$ npm -v
$ node -v

常用工具

1
$ npm install nrm -g

初始化项目

1
$ npm init

安装依赖

1
$ npm install <pkg-name> -s

引入依赖

1
const name = require('pkg-name');

spring boot 常见问题

服务停止时Redis连接池断开异常

问题描述

使用Spring Boot的RedisTemplate、RedisLockRegistry,在停止服务时,由于Redis连接早于应用逻辑断开,导致打印 Redis 连接断开异常,具体异常日志如下。

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
org.springframework.data.redis.RedisSystemException: Redis exception; ness
ted exception is io.lettuce.core.RedisException: Connection is closed
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:74)
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:268)
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException(LettuceStringCommands.java:799)
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.incr(LettuceStringCommands.java:332)
at org.springframework.data.redis.connection.DefaultedRedisConnection.incr(DefaultedRedisConnection.java:323)
at org.springframework.data.redis.core.DefaultValueOperations.lambda$increment$0(DefaultValueOperations.java:87)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:224)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:184)
at org.springframework.data.redis.core.AbstractOperations.execute(AbstractOperations.java:95)
at org.springframework.data.redis.core.DefaultValueOperations.increment(DefaultValueOperations.java:87)
...
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)
Caused by: io.lettuce.core.RedisException: Connection is closed
at io.lettuce.core.cluster.ClusterDistributionChannelWriter.write(ClusterDistributionChannelWriter.java:71)
at io.lettuce.core.RedisChannelHandler.dispatch(RedisChannelHandler.java:187)
at io.lettuce.core.cluster.StatefulRedisClusterConnectionImpl.dispatch(StatefulRedisClusterConnectionImpl.java:207)
at io.lettuce.core.AbstractRedisAsyncCommands.dispatch(AbstractRedisAsyncCommands.java:467)
at io.lettuce.core.AbstractRedisAsyncCommands.incr(AbstractRedisAsyncCommands.java:796)
at sun.reflect.GeneratedMethodAccessor284.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at io.lettuce.core.cluster.ClusterFutureSyncInvocationHandler.handleInvocation(ClusterFutureSyncInvocationHandler.java:114)
at io.lettuce.core.internal.AbstractInvocationHandler.invoke(AbstractInvocationHandler.java:80)
at com.sun.proxy.$Proxy188.incr(Unknown Source)
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.incr(LettuceStringCommands.java:330)
... 13 more

解决方案

解决思路是让Redis连接断开时间晚于服务Bean实例销毁时间,这里的解决方案是使用 @DependsOn 注解,DependsOn 既可以保证依赖Bean早于当前Bean创建,同时也会保证依赖Bean晚于当前Bean销毁,在使用redisTemplate或redisLockRegistry,或二者的Bean类,添加注解,示例如下,按需选择其一或两个。

1
@DependsOn({"redisTemplate", "redisLockRegistry"})

接口返回 null 字段

1
2
3
4
# 添加如下配置
spring:
jackson:
default-property-inclusion: non_null

跨域

1
2
3
4
5
// 添加如下注解
@CrossOrigin(maxAge = 1, methods = {RequestMethod.POST, RequestMethod.GET})
@RequestMapping(value = "info", method = {
RequestMethod.POST, RequestMethod.GET
}, produces = MediaType.APPLICATION_JSON_VALUE)

consul 部署

部署Consul可能会有一些坑,每个坑都可能导致非常严重的事故。

配置

配置详情参考官方文档。在配置Consul集群时,重点在初始化集群。初始化一个Consul集群,首先需要部署主节点,并选举出Leader,然后部署代理节点,加入集群。

部署主节点初始化集群,有两种方式。一种是指定Leader,并首先部署(bootstrap设置为true),其他主节点配置加入指定的Leader;另一种是不指定Leader(bootstrap设置为false),在主节点达到期望的数量时(bootstrap_expect),自动选举。第二种相对一第一种,更简单,且不容易出错。

master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"client_addr": "0.0.0.0",
"datacenter": "consul-example",
"data_dir": "/srv/consul/consul.d",
"domain": "dev",
"dns_config": {
"enable_truncate": true,
"only_passing": true
},
"enable_syslog": true,
"encrypt": "MUEfHaiegJAg9oKogQYG9G5xDg3Bp1eBe/wd4OpoWwE=",
"leave_on_terminate": true,
"log_level": "INFO",
"rejoin_after_leave": true,
"server": true,
"bootstrap": false,
"bootstrap_expect": 3,
"retry_join": [
"172.17.0.2",
"172.17.0.3",
"172.17.0.4"
],
"ui": true
}

注意事项

  • 在docker中,enable_syslog 需要设置为false,否则可能会启动失败
  • encrypt 需要使用命令 consul keygen 重新生成并更新

agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"client_addr": "0.0.0.0",
"datacenter": "consul-example",
"data_dir": "/srv/consul/consul.d",
"domain": "dev",
"dns_config": {
"enable_truncate": true,
"only_passing": true
},
"enable_syslog": true,
"encrypt": "MUEfHaiegJAg9oKogQYG9G5xDg3Bp1eBe/wd4OpoWwE=",
"leave_on_terminate": true,
"log_level": "INFO",
"rejoin_after_leave": true,
"server": false,
"bootstrap": false,
"retry_join": [
"172.17.0.2",
"172.17.0.3",
"172.17.0.4"
],
"ui": false
}

部署

下载容器

1
$ docker pull ubuntu:18.04

主节点

创建容器

1
2
3
$ docker run -it -d --restart=always -p 8500:8500 --name=consul-srv1 ubuntu:18.04 # 绑定端口,用于访问界面
$ docker run -it -d --restart=always -p 8501:8501 --name=consul-srv2 ubuntu:18.04 # 可以不绑定端口
$ docker run -it -d --restart=always -p 8502:8502 --name=consul-srv3 ubuntu:18.04 # 可以不绑定端口

获取IP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 登录容器
$ docker exec -it consul-srv1 /bin/bash

# 初始化容器
$ apt update && apt install -y iproute2 wget unzip vim

# 获取IP
## 1. 通过 hosts 文件获取
$ cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 e7cdefc0954c
## 2. 通过 ip 命令获取
$ apt update && apt install iproute2
$ ip address
...
inet 172.17.0.2/16
...
容器 ip
consul-srv1 172.17.0.2
consul-srv2 172.17.0.3
consul-srv3 172.17.0.4

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 创建服务目录
$ mkdir /srv/consul && cd /srv/consul
# 下载 consul
$ wget https://releases.hashicorp.com/consul/1.9.0/consul_1.9.0_linux_amd64.zip
# 解压
$ unzip consul_1.9.0_linux_amd64.zip
# 添加配置文件
$ vim config.json
# 写入配置文件,把 enable_syslog 设为 false

# 添加启动脚本
$ vim run-consul.sh # 输入如下内容
[ ! -d /srv/consul/consul.d ] && mkdir /srv/consul/consul.d
config_dir="/srv/consul/config.json"
host_name="`hostname --fqdn`"
host_ip=`hostname -I | awk '{print $1}'`
consul agent -config-dir="$config_dir" -bind=$host_ip

启动

1
$ export PATH=.:$PATH && bash run-consul.sh

访问UI界面

浏览器打开地址 localhost:8500

代理节点

创建容器

1
$ docker run -it -d --restart=always --name=consul-agt1 ubuntu:18.04

初始化

首先登陆容器。

1
2
3
4
5
# 登录容器
$ docker exec -it consul-agt1 /bin/bash

# 初始化容器
$ apt update && apt install -y iproute2 wget unzip vim

初始化和主节点一致。

启动

1
$ export PATH=.:$PATH && bash run-consul.sh

访问界面,可以看到Agent信息。

更换主节点

如果我们想更换掉所有的主节点,需要如下步骤。

  • 部署新的主节点服务,并加入到集群,同步集群数据
  • 更新所有代理节点的主节点配置文件,并重启
  • 下线旧主节点

说明

  • bootstrap_expect 主要作用于集群初始化阶段,只有发现足够的主节点,才会启动选主程序,并初始化。集群初始化后,停止所有主节点,并启动其中一个, 会根据历史数据,启动集群,此时 bootstrap_expect 配置的值没有意义。

最佳实践

  • 设置 encrypt,不同机房,使用不同 datacenter 名称,避免跨环境注册,服务混合
  • 设置 limits.http_max_conns_per_client ,默认 200,极有可能不够用,出现各种问题

django viewset

自定义 Action

1
2
3
4
5
6
7
8
9
10
11
12
from rest_framework.decorators import action


class SnippetViewSet(viewsets.ModelViewSet):
...

@action(detail=False, methods=['GET'], name='Get Highlight')
def highlight(self, request, *args, **kwargs):
queryset = models.Highlight.objects.all()

serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

跨域

webpack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// config/index.js
module.exports = {
dev: {
...
proxyTable: {
'/api': {
target: "http://localhost:8000",
changeOrigin: true,
pathRewrite: {
'^/api': 'api'
}
},
},
...
}
}