0%

/bin 存放基础命令的可执行文件和符号连接,包括cat, cp, ls, touch, rm等

/sbin 存放超级用户使用的基础的管理程序,包括ifup, iptables, mkfs, route, swapon等

/usr Unix Software Resource的缩写, 也就是系统软件资源所放置的目录,全部软件都安装在这里
/usr/bin 存放很多应用程序的可执行文件,包括akw, vim, gcc, go, cmake等
/usr/local/bin 存放安装出来的可执行文件,包括ipython, jupyter-notebook, pip, wheel等
/usr/sbin 存放一些非核心的管理程序和一些安装出来的daemon程序,包括useradd, nginx, netplan, mysqld, sshd等
/usr/include Linux下开发和编译应用程序需要的头文件
/usr/lib 动态链接共享库和静态档案库目录
/usr/src 源代码目录
/usr/doc 文档目录
/usr/man 帮助文档目录

/var variable的缩写,包含可变的数据文件
/var/log/ 各种程序的日志文件
/var/www/ nginx网站目录,相当于nginx的数据文件
/var/lib/ 各种程序运行时会改变的数据文件
/var/lib/docker/ docker的镜像和容器数据
/var/lib/mysql/ mysql数据库的数据
/var/lib/mongodb/ mongodb数据库的数据

/etc 包含所有系统管理和维护方面的配置文件
/etc/hosts 域名解析配置
/etc/resolv.conf 域名解析服务器配置
/etc/init.d/ 自启动脚本,详见 配置Linux开机启动脚本(基于initd)
/etc/rc?.d/ 自启动脚本的软连接,?表示系统的运行级
/etc/profile 所有用户登录Shell时执行
/etc/profile.d/ 所有用户登录Shell时遍历目录下的脚本执行
/etc/issue Linux版本信息
/etc/sudoers 配置允许sudo的用户
/etc/passwd 用户列表 username:原密码占位符x:uid:gid:描述性信息:家目录:默认Shell
/etc/apt/sources.list apt软件源配置,详见 替换Ubuntu 18.04软件源
/etc/network/interfaces 网卡配置文件
/etc/netplan/*.yaml 网卡配置文件
/etc/fstab 系统启动时自动执行的挂载路径,也是mount -a时挂载的路径,详见 Linux NFS共享目录配置及开机自动挂载
/etc/exports 配置通过nfs共享的目录,详见 Linux NFS共享目录配置及开机自动挂载
/etc/samba/smb.conf 配置通过samba共享的目录
/etc/systemd/system/*.service 自启动的服务配置文件的符号连接,详见 配置Linux开机启动脚本(基于systemd)
/etc/docker/daemon.json docker配置文件
/etc/nginx/nginx.conf /etc/nginx/sites-enabled/default nginx配置文件,详见 nginx配置
/etc/mysql/my.cnf /etc/mysql/mysql.conf.d/mysqld.cnf mysql配置文件
/etc/redis/redis.conf redis配置文件

/lib 包含系统引导和在root用户执行命令时候所必需用到的共享库

/home 所有普通用户家目录的父目录

/root root用户的家目录

/opt 用户级的程序目录,自己下载解压的软件可以放在这个目录下,然后将可执行文件的符号连接创建到 /usr/local/bin

/boot 目录存放系统核心文件以及启动时必须读取的文件,包括Linux内核的二进制映像

/tmp 临时文件目录

/mnt 该目录是默认的文件系统临时装载点

/media 挂在多媒体设备的目录,如默认情况下软盘、光盘、U盘设备都挂在在此目录

/dev 目录保存着外部设备代码的文件,这些文件比较特殊,实际上它们都指向所代表的外围设备,如终端、磁盘驱动器、光驱、打印机等。

/proc 进程文件系统proc的根目录,其中的部分文件分别对应正在运行的进程,可用于访问当前进程的地址空间。它是一个非常特殊的虚拟文件系统,其中并不包含“实际的”文件,而是可用以引用当前运行系统的系统信息,如CPU、内存、运行时间、软件配置以及硬件配置的信息,这些信息是在内存中由系统自己产生的。

/sys 是一个类似于proc文件系统的特殊文件系统,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息。其实,就是在用户态可以通过对sys文件系统的访问,来看内核态的一些驱动或者设备等。

/run 目录中存放的是自系统启动以来描述系统信息的文件。比较常见的用途是daemon进程将自己的pid保存到这个目录。标准要求这个文件夹中的文件必须是在系统启动的时候清空,以便建立新的文件。

本文的操作在我的Ubuntu 18.04上并不能正常工作,我觉得这和系统启动是使用initd还是systemd有关

检查根进程是initd还是systemd

通过

1
pstree -p|head -1

1
ps -ef|awk '($2==1)'

可以看到系统的1号进程,也就是启动全部进程的根进程是initd还是systemd。

systemd参考 配置Linux开机启动脚本(基于systemd)


创建开机启动脚本

先将脚本放在 /etc/init.d/ 下,例如脚本名为 new_service.sh

执行如下指令,在这里90表明一个优先级,越高表示执行的越晚

1
2
cd /etc/init.d/
sudo update-rc.d new_service.sh defaults 90

创建完成会在 /etc/rc*.d/ 下生成相应的软连接

rc后面的数字含义

  • 0 Halt the system 停机(千万不要把 initdefault 设置为0),机器关闭
  • 1 Single user mode 单用户模式,与 Win9x 下的安全模式类似
  • 2 Basic multi user mode 基本多用户模式,没有 NFS 支持
  • 3 Multi user mode 完整的多用户模式,是标准的运行级
  • 4 None 一般不用,在一些特殊情况下可以用它来做一些事情。例如在笔记本电脑的电池用尽时,可以切换到这个模式来做一些设置。
  • 5 Multi user mode with GUI 就是X11,进到XWindow系统了
  • 6 Reboot the system 重新启动(千万不要把initdefault 设置为6),运行 init 6 机器就会重启
  • S, s Single user mode

软连接开头为Kxx或Sxx,Kxx表示不启动(似乎和这个文件不存在一样),Sxx表示启动,xx是启动顺序,01~99。

移除开机启动脚本

1
sudo update-rc.d -f new_service.sh remove

检查根进程是initd还是systemd

通过

1
pstree -p|head -1

1
ps -ef|awk '($2==1)'

可以看到系统的1号进程,也就是启动全部进程的根进程是initd还是systemd。

initd参考 配置Linux开机启动脚本(基于initd)


基于systemd的自启动首先需要创建服务,在服务配置文件中配置调用需要自启动的脚本,然后设置服务自启动。

创建服务

/lib/systemd/system/ 下新建.service文件,例如 kafka.service

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=Kafka Server
After=network.target zookeeper.service

[Service]
Type=simple
ExecStart=/opt/kafka_2.12-2.3.1/bin/kafka-server-start.sh /opt/kafka_2.12-2.3.1/config/server.properties

[Install]
WantedBy=multi-user.target

配置自启动脚本

其中最重要的,运行脚本,配置在ExecStart,如果脚本里面启动的程序是常驻内存的,这里不用设置后台运行。

After 用来配置服务的依赖项,表示服务器希望在哪些服务后启动。

WantedBy 类似系统的运行级别,如果只希望在图形界面自启动可以配置成 graphical.target ,默认配置成 multi-user.target 即可。

配置简单服务以上就够了,更详细的说明可以参考 Systemd 入门教程:实战篇 - 阮一峰的网络日志 (ruanyifeng.com)

修改服务配置文件后需要重新载入服务配置文件.

最后不要忘了设置服务自启动 systemctl enable kafka.service ,随便给出服务相关的常用命令。

都以kafka为例,清自行替换成其他服务。

常用服务配置命令

重新载入服务配置文件

1
systemctl daemon-reload

设置服务自启动

1
systemctl enable kafka.service

查询是否自启动服务

1
systemctl is-enabled kafka.service

取消服务自启动

1
systemctl disable kafka.service

常用服务控制命令

查询服务状态

1
service kafka status

启动服务

1
service kafka start

停止服务

1
service kafka stop

重启服务器

1
service kafka status

大学时候,尤其大二大三,因为参加ACM ICPC的关系,沉迷于在各大高校的OJ刷题(主要在 ZJU, PKU, HDU),回想起那时,写DP状态转移方程,编码,提交,AC一气呵成的爽快感至今仍然鲜活,以至于刚毕业那几年还时不时的做几道玩。

后来也许是工作忙了,也许是其他事情多了,也许是兴趣转移了(向投资),……,总之算来已经好久没有做题了。

今天从B站一个UP主那边发现了这个网站
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 https://leetcode.cn

(从我的邀请连接注册学习可以帮我拿点积分)

似乎主要面向在职人群,我看还提供了面试求职之类的功能

新注册会询问你是在职还是学生,工作领域是前端、后端还是移动端等等。

然后就给我推了个新手专属题单

这我就忍不了了啊,水了一把

支持的语言有C++、Java、Python、JavaScript、Go、Rust等等,基本上传统主流和新兴主流语言都支持。

我发现答这种题我竟然还是倾向于用C++大于Python。

代码库地址

https://code.aliyun.com/daimingzhuang/flask-restful.git

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.
├── README.md # 说明文件
├── config.yml # 全局配置文件
├── foo.db # 测试用的SQLite数据库,可以删掉
├── resources # RESTful资源目录
│ ├── BaseResource.py # 定义了几个资源的基类,通用的CRUD接口,并实现了列表的条件查询、排序和分页。
│ ├── IndexResource.py # 示例主页,可以删掉
│ ├── TokenResource.py # Token示例,用来查询登录(GET)、登录(POST)、登出(DELETE),可根据实际情况改写
│ ├── UserResource.py # 用户资源示例
│ └── __init__.py # 统一绑定资源到URI
├── rest_server.py # 服务器主程序
└── utils
├── __init__.py
├── common.py # 定义了一些通用的工具
├── config.py # 读取全局配置文件
├── db.py # 数据库模型定义、关系映射等
├── db_base.py # SQLAlchemy ORM 框架基类
├── logger.py # 日志
├── mutex.py # 基于Redis的跨进程锁,实现了互斥锁和读写锁
├── redis_pool.py # Redis连接池
└── redis_session.py # 基于Redis的HttpSession

环境依赖

python3

pip3 install -r requirements.txt

1
2
3
4
5
flask
flask-restful
sqlalchemy
redis
pyyaml

开发指南

根据项目情况配置 config.yml

修改 utils/db.py ,对数据模型(即数据库的表)进行定义。

resources 下新建资源,继承 BaseResource 中的某个基类,有需要时可重写get/post/put/delete方法实现自定义的业务逻辑。

resources/__init__.py 中添加资源的URI映射。

utils文件夹除了db.py,其他代码通用性都比较强,一般都只需使用无需修改。

resources文件夹除了BaseResource.py,其他代码可以认为都是示例,应该根据需要修改和删除。

httpsession存储在redis服务器中,前端的登录标识通过登录返回的header中的token字段和数据的{“data”: {“token”: “xxx”}}同时返回,前端后续的请求应该在header携带token字段(或token参数)以表明身份,具体可以看后面的请求示例

启动方式

开发环境

1
python3 reset_server.py

Linux上的生产环境

1
gunicorn -b 0.0.0.0:8088 rest_server:app

请求示例

查询当前登录状态

1
GET http://localhost:8088/Token

response text

1
2
3
4
5
{
"result_code": "fail",
"err_code": "LOGIN_FAILED",
"err_code_des": "用户未登录"
}

登录

1
2
POST http://localhost:8088/Token
body {"username":"admin", "password":"123456"}

response text

1
2
3
4
5
6
7
8
9
10
11
{
"result_code": "success",
"data": {
"token": "66007530da1e424ba35006afad746fa9",
"user": {
"id": 1,
"username": "admin",
},
"create_time": 1651989903.466016
}
}

查看登录后的测试主页

1
2
GET http://localhost:8088/
header token: 66007530da1e424ba35006afad746fa9

response text

1
"Hello, admin."

查询列表指定条件、分页、排序

1
2
http://localhost:8088/Users?page=1&page_size=15&sort=username&order=asc&username_like=a%n
header token: 66007530da1e424ba35006afad746fa9

response text

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"result_code": "success",
"data": [
{
"id": 1,
"username": "admin"
}
],
"total": 1,
"page": 1,
"page_size": 15,
"page_num": 1
}

更多的查询条件使用方法,可以阅读 resources/BaseResource.pyROPluralResource 类的 get 方法的源码

有时候和第三方对接,对方只提供了jar包接口,而我们的系统是Python实现的,这时候可以通过jpype库实现Python调用jar包里的代码。

为了完整说明,先写一个简单的Java类,然后打包成jar,再通过Python调用。

Hello.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Hello
{
public static void sayHello() {
System.out.println("Hello World!");
}

public Hello() {

}

public int add(int a, int b) {
return a + b;
}
};

编译得到Hello.class

1
javac Hello.java

创建jar包

1
jar cf Hello.jar Hello.class

安装jpype库

1
pip3 install jpype1

调用示例

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
50
51
52
53
54
55
56
57
58
59
60
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
import jpype
import sys, os


def get_full_path(path):
if os.path.isabs(path):
return path
# 尝试对相对路径进行查找
# 查找当前目录
t = os.path.abspath(path)
if os.path.exists(t):
return t
# 查找启动脚本所在目录
main_path = os.path.abspath(sys.argv[0])
t, _ = os.path.split(main_path)
t = os.path.join(t, path)
if os.path.exists(t):
return t
# 查找当前脚本所在目录
script_path = os.path.realpath(__file__)
t, _ = os.path.split(script_path)
t = os.path.join(t, path)
if os.path.exists(t):
return t
raise FileNotFoundError(f"Can NOT find '{path}'.")


class PyJar:
def __init__(self, path=None):
if not jpype.isJVMStarted():
jvmPath = jpype.getDefaultJVMPath()
jpype.startJVM(jvmPath, "-ea")
self.addClassPath(path)

def addClassPath(self, path):
if path:
if isinstance(path, list) or isinstance(path, tuple):
for e in path:
jpype.addClassPath(get_full_path(e))
else:
jpype.addClassPath(get_full_path(path))

def jClass(self, classpath):
return jpype.JClass(classpath)


def main():
jar = PyJar("./Hello.jar")

Hello = jar.jClass("Hello") # 取得Java类,这里要把类路径写全,例如 com.xxcompany.yy.zz
instance = Hello() # 创建对象
Hello.sayHello() # 调用类静态函数
print(instance.add(1,2)) # 调用对象方法


if __name__ == '__main__':
main()

需要说明的是,一个进程只能启动一个JVM,并且JVM被关闭后不能再次启动,所以虽然jpype提供了关闭JVM的方法jpype.shutdownJVM(),我并没有封装在PyJar中,这样PyJar就可以视作对一个jar包的载入,在不同的.py文件里可以创建多个PyJar对象载入不同的jar包而不会出现JVM重复启动或者JVM已经终止的报错。

关于jar包依赖

.java依赖jar包时的.class编译

1
javac -classpath "/path1/1.jar:/path2/2.jar" abc.java

在使用时需要通过addClassPath把依赖的包都加入进去。

关于包路径

如果.java文件中通过package com.xx.yy;指定了路径,那么打jar包时应该把当前目录cd到com的上层目录

1
jar cf x.jar com/xx/yy/zz.class

Maven项目

pom.xml 可以把很多java文件打在一个包中,我们可以自己编写或者我们得到了一个带有pom.xml的源码项目,可以使用如下命令打包.jar

1
mvn clean package

对于pom.xml依赖的jar包我们需要将其下载下来,通过命令

1
mvn dependency:copy-dependencies

这样我们就可以把打包出来的jar和依赖的jar一并提取出来拷到需要被python加载的路径下,通过上面类的addClassPath方法使用。

XXL-JOB是一个轻量级分布式任务调度框架。
它解决的问题是能在统一的界面上,方便的管理一堆定时执行的、或相互依赖的任务,让他们并行或以正确的顺序串行在一组机器上执行,并且对执行结果进行管理,包括执行日志,失败通知等。

官网

https://www.xuxueli.com/xxl-job/

源码仓库

https://github.com/xuxueli/xxl-job

编译环境

Maven3+
Jdk1.8+

运行环境

Jre1.8+
Mysql5.7+

任务运行模式

任务运行模式是指任务通过何种代码部署(粘合)到任务调度中心,默认的方式支持:Shell、Python、Nodejs、PowerShell等,比如我在下面添加了Python3
com.xxl.job.core.glue.GlueTypeEnum

1
2
3
4
5
6
7
8
BEAN("BEAN", false, null, null),
GLUE_GROOVY("GLUE(Java)", false, null, null),
GLUE_SHELL("GLUE(Shell)", true, "bash", ".sh"),
GLUE_PYTHON("GLUE(Python)", true, "python", ".py"),
GLUE_PYTHON3("GLUE(Python3)", true, "python3", ".py"),
GLUE_PHP("GLUE(PHP)", true, "php", ".php"),
GLUE_NODEJS("GLUE(Nodejs)", true, "node", ".js"),
GLUE_POWERSHELL("GLUE(PowerShell)", true, "powershell", ".ps1");

配置文件

调度中心配置

1
2
./xxl-job-admin/src/main/resources/application.properties
./xxl-job-admin/src/main/resources/logback.xml

主要要确保数据库(xxl-job, datasource)配置正确

执行器配置

1
2
./xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/application.properties
./xxl-job-executor-samples/xxl-job-executor-sample-springboot/src/main/resources/logback.xml

主要注意两个地方:
调度中心地址
xxl.job.admin.addresses
执行器appname,相同appname的执行器构成一个执行器集群,在调度时不区分,不同的appname在配置任务时可选择由哪个appname的执行器执行
xxl.job.executor.appname

数据库初始化

1
./doc/db/tables_xxl_job.sql

构建方法

重新构建
mvn clean package
增量构建
mvn package

运行调度中心

构建完默认会生成

1
./xxl-job-admin/target/xxl-job-admin*.jar

可以重命名并复制到其他路径,启动命令

1
java -jar xxl-job-admin*.jar

默认通过

1
http://localhost:8080/xxl-job-admin/

访问调度中心,端口和路径在配置文件application.properties中可修改
用户名admin,密码123456,可以在登录后修改

运行执行器

构建完默认会生成

1
./xxl-job-executor-samples/xxl-job-executor-sample-springboot/target/xxl-job-executor*.jar

可以重命名并复制到其他主机,启动命令

1
java -jar xxl-job-executor*.jar

启动后会根据配置自动向调度中心注册自己

静态查看一个可执行文件或一个so依赖的动态库

1
ldd <可执行文件名或so文件名>
1
ldd -r <可执行文件名或so文件名>

​ -r 选项除了输出依赖的动态库(缺失会输出 not found)还会输出缺失的符号(undefined symbol)

动态查看一个进程加载的动态库

1
cat /proc/<PID>/maps|awk '{print $6}'|grep '\.so'|sort|uniq
1
lsof -p <PID>|awk '{print $NF}'|grep '\.so'|sort|uniq
1
pmap -p <PID>|awk '{print $4}'|grep '\.so'|sort|uniq

​ 3个命令输出结果应该是一样的

当存在不同版本so的时候可以用来确保使用到的是正确路径下的版本

nohup

1
nohup command params >/dev/null 2>&1 &

nohup 表示终端关闭时不挂断程序,否则会因为SIGHUB信号在关闭终端时自动结束程序

command params 是需要执行的命令和命令参数

>/dev/null 表示将标准输出重定向到空设备即丢弃输出,也可以改成重定向到指定的文件

2>&1 表示将stderr错误输出重定向到标准输出

最后的 &表 示在后台执行

jobs

查看当前终端放入后台的工作,可以使用 -l 参数增加显示PID

对于前台执行的程序,通过ctrl+z,可以挂起程序,再使用

1
bg [%job_num]

可以将程序放到后台继续执行,%job_num是任务编号,可以省略,省略时操作最后一个非后台运行的程序

也可以将程序放回前台执行,通过

1
fg [%job_num]

disown

在后台执行和是否会挂断是两件事。

我们前面通过 ctrl+z,bg 把任务放在后台运行,但是终端退出仍然会被自动结束,使用

1
disown -h [%job_num] 

可以将任务免于挂断,相当于补了一个nohup

-h 选项的意思是只标记不挂断,而不移出任务列表,通过jobs命令仍然可以看到,如果不加-h 则会移出任务列表同时标记不挂断。

screen

前面讲了如何在后台执行程序并免于挂断,但有时候我们还希望和程序交互,在后台执行就并不方便了,这时候我们可以通过screen在前台执行,同时免于挂断,一个screen就相当于一个可以随时断开随时可以恢复的终端窗口。

新建一个名为name的screen

1
screen -S name

重新连接到名为name的screen

1
screen -r name

附到一个连接状态名为name的screen

1
screen -x name

查看当前的screen列表

1
screen -ls

断开连接当前screen

1
ctrl+a d

reptyr

从一个终端(或后台)接收运行的进程,并将其迁移到另一个终端(的前台)。

1
reptyr -T <PID>

这个命令我就没成功执行过。不知为何

移动光标

gg 跳至文首
G 调至文尾
行号G 跳转到指定行
w 移动到后一个单词第一个字符
b 移动到前一个词的第一个字符

up、down、left、right、PageDown、PageUp、Home、End 都是可用的,没必要用vi重定义的按键

删除

dd 删除光标所在行
d+移动光标 从当前光标位置删除到光标移动后的位置
例如删除全文时,可以使用 gg dG

查找/替换

/ pattern 从光标处向后查找
? pattern 从光标处向前查找
n 查找下一个
. 执行前次的命令操作,结合/和n可以实现逐一确认替换
:%s/src_str/dst_str/g 替换全部的src_str为dst_str
:‘<,’>s/src_str/dst_str/g 替换选中区域的src_str为dst_str,在可视模式选中后直接按:会自动出现:‘<,’>
u Undo
Ctrl+r Redo
p 粘贴到光标的后面或当前行的下面(取决于复制时是逐字模式还是逐行模式)
P 粘贴到光标的前面或当前行的上面(取决于复制时是逐字模式还是逐行模式)

可视模式(选择/剪切/复制)
v 进入逐字可视模式
V 进入逐行可视模式
Ctrl+v 进入块(列)选择模式
移动光标 从当前光标位置选择到光标移动后的位置
选中后的操作
U 将选中的目标全部替换为大写
u 将选中的目标全部替换为小写
c 剪切
y 复制
d 删除

输入模式

i 从当前光标处进入输入模式
o 在当前行之下新加一行,并进入输入模式
ESC 退出插入模式

编辑模式

:q 退出
:wq 保存并退出
:q! 退出放弃保存
:wq! 强制保存并退出
:wq! sudo tee % 提权并保存退出
:set number 显示行号
:set ruler 显示光标位置在右下角