Tmux 是一个很棒的终端复用工具,和 screen 命令类似,但是 Tmux 终极的分屏功能要比 screen 强大很多,当然入门也比 screen 要高很多。如果你长时间在终端进行编程或者操作,或者你陷入无数的 Tab 而无法自拔,那么你应该需要开始了解一些 Tmux 的基本使用。
本文会从如下几个方面对 Tmux 进行介绍:
Tmux 主要包括以下几个模块:
Ubuntu/Debian 下:
sudo apt-get install tmux
如果是在 macOS 下:
brew install tmux
Tmux 和其他系统的命令一样拥有很多的启动选项,在 man tmux
里面能看到很多。比如 -2
就是启动 256 colours 支持。
默认情况下,tmux 会加载系统配置 /etc/tmux.conf
然后是用户配置 ~/.tmux.conf
。如果配置了该选项,tmux 会在启动时加载,如果配置发生错误,那么会在第一个 session 创建时报错,然后继续处理下面的配置文件。
如果不想要加载自己的配置文件可以在启动的时候使用 -f file
来指定。
Tmux is a tool that allows running multiple terminal sessions through a single terminal window. It allows you to have terminal sessions running in the background and attach and detach from them as needed, which is very useful.
Tmux 的快捷键前缀(Prefix), 为了使自身的快捷键和其他软件的快捷键互不干扰,Tmux 提供了一个快捷键前缀,和 screen 默认激活控制台的 Ctrl+a 不同,Tmux 默认的是 Ctrl+b。当想要使用 Tmux 的快捷键时,需要先按下快捷键前缀,然后再按下快捷键。Tmux 所使用的快捷键前缀默认是组合键 Ctrl-b(同时按下 Ctrl 键和 b 键)。假如你想通过快捷键显示当前 Tmux 中的 session 列表(对应的快捷键是 s),那么你只需要做以下几步:
Ctrl-b
(Tmux 快捷键前缀)Ctrl-b
s
键使用快捷键之后就可以执行一些相应的指令了。当然如果你不习惯使用 Ctrl+b
,也可以在 ~/.tmux.conf
文件中加入以下内容把快捷键变为 Ctrl+a, 或者其他快捷键:
# Set prefix key to Ctrl-a
unbind-key C-b
set-option -g prefix C-a
在下文中就使用 <prefix>
来代替 Tmux 的前缀快捷键了。
每当开启一个新的会话 session 时,Tmux 都会先读取 ~/.tmux.conf
这个文件。该文件中存放的就是对 Tmux 的配置。
如果你希望新的配置项能够立即生效,那么你可以将下面这一行配置加入到文件 ~/.tmux.conf
中。
bind r source-file ~/.tmux.conf \; display-message "tmux config reloaded" # create new short cut to reload tmux.conf
这样配置了之后,每当向 ~/.tmux.conf 文件中添加了新的配置,只需要按下 <prefix> r
就可以重新加载配置并使新的配置生效,从而免去了开启一个新的会话使之生效的步骤。
以下所有的操作都是激活控制台之后,即键入 <prefix>
前提下才可以使用的命令
基本操作
<prefix> ? 列出所有快捷键;按 q 返回
<prefix> d Detach当前会话,可暂时返回 Shell 界面,输入`tmux attach`能够重新进入之前会话
<prefix> s 切换会话 session;在同时开启了多个会话时使用
<prefix> D 选择要脱离的会话;在同时开启了多个会话时使用
<prefix> : 进入命令行模式;此时可输入支持的命令,例如 kill-server 所有 Tmux 会话
<prefix> [ 复制模式,光标移动到复制内容位置,空格键开始,方向键选择复制,回车确认,q/Esc 退出
<prefix> ] 进入粘贴模式,粘贴之前复制的内容,按 q/Esc 退出
<prefix> = 选择性粘贴缓冲区中内容
<prefix> ~ 列出提示信息缓存;其中包含了之前 Tmux 返回的各种提示信息
<prefix> t 显示当前的时间
<prefix> Ctrl+z 挂起当前会话
如果要查看当前 Tmux 的配置,可以通过 tmux show -g
来查看。
Tmux 的一个 Session 可以包含多个 Windows.
在 Tmux 外部 Shell 中可以使用如下方式来管理 Tmux 的 Session:
tmux 创建 session
tmux new -s $session_name 创建并指定 session 名字
tmux ls 列出存在的 session,包括 session 中存在的 windows
tmux attach -t session_name 进入指定会话 session_name
tmux a -t $session_name 上面的缩写形式,进入已存在的 session
tmux kill-session -t $session_name 删除指定 session
在 Tmux 内:
<prefix> :kill-session 删除退出当前 session
<prefix> d 临时退出 session,会话在后台运行,可以通过 attach 进入指定的会话
<prefix> :kill-server 删除所有活跃 session
<prefix> :new -s session_name 在 Tmux 中新建 session
<prefix> s 列出所有活跃 session,并可以从列表中选择 session
<prefix> $ 重命名 session
window(窗口)在 session 里,一个 session 可以有 N 个 window,并且 window 可以在不同的 session 里移动。 window 可以看成是一个 session 的不同 tab。
<prefix> c 创建 window
<prefix> & 删除或者关闭 window
<prefix> n 下一个 window
<prefix> p 上一个 window
<prefix> w 列出现在开启的 window
<prefix> , 重命名 window
<prefix> f 在多个 window 里搜索关键字
<prefix> l last window 在相邻的两个 window 里切换
<prefix> 0,1,2 在 window 之间切换,如果窗口数超过 10 个,可以使用 `<prefix> 'num` 来切换
pane 在 window 里,可以有 N 个 pane,并且 pane 可以在不同的 window 里移动、合并、拆分。
创建 pane:
<prefix> " 横切 split pane horizontal,后面会 remap 这个键
<prefix> % 竖切 split pane vertical,后面 remap 这个键
<prefix> o 按顺序在 pane 之间移动
<prefix> x 关闭 pane
<prefix> z 最大化 pane 和恢复原状 toggle pane zoom
<prefix> ! 移动 pane 至 window
<prefix> "空格" 更换 pane 排版
<prefix> { 移动 pane 往左边,往上面
<prefix> } 移动 pane 往右边,往下面
<prefix> q 显示 pane 编号,在显示编号的时候按对应数字可以切换到该 pane,这个操作太复杂,后面 remap
<prefix> 方向键上下左右 上下左右选择 pane
调整 pane 的大小
<prefix> :resize-pane -U #向上
<prefix> :resize-pane -D #向下
<prefix> :resize-pane -L #向左
<prefix> :resize-pane -R #向右
<prefix> :resize-pane -D 20 (Resizes the current pane down by 20 cells)
<prefix> :resize-pane -U 20 (Resizes the current pane upward by 20 cells)
<prefix> :resize-pane -L 20 (Resizes the current pane left by 20 cells)
<prefix> :resize-pane -R 20 (Resizes the current pane right by 20 cells)
<prefix> :resize-pane -t 2 20 (Resizes the pane with the id of 2 down by 20 cells)
<prefix> :resize-pane -t -L 20 (Resizes the pane with the id of 2 left by 20 cells)
在上下左右的调整里,最后的参数可以加数字 用以控制移动的大小,例如:
<prefix> :resize-pane -D 50
移动 pane 合并至某个 window
<prefix> :join-pane -t $window_name
列出缓冲区目标
<prefix> :list-buffer
查看缓冲区内容
<prefix> :show-buffer
vi 模式
<prefix> :set mode-keys vi
快捷键帮助
<prefix> ? (<prefix> :list-keys)
Tmux 内置命令帮助
<prefix> :list-commands
让你的 Tmux 更加高效,一下内容都可以编辑进 ~/.tmux.conf
用来进一步自定义 Tmux 的行为。 默认的 Tmux 有很多操作方式可能比较 awkward, 只有自己配置让自己熟悉他的行为之后才能让 Tmux 展现出最高效的部分。
在前面也说过, Tmux 默认的 Prefix 是 Ctrl + b,可以按照自己的习惯设定 Prefix 快捷键,很多人将 Caps 和 Ctrl 互换,并且将 Prefix 定义为 Ctrl + a, 我自己使用了以下还是不怎么习惯,所以我将 Prefix 定义成了 Ctrl + \
默认情况下 Tmux 使用 "
来垂直分割成上下两部分,使用 %
来水平分割成左右两部分,但是这两个键需要 Shift+',以及很难按到的%,不如直观上使用 |
和 -
来分割 pane。
# split panes using | and -
bind | split-window -h
bind - split-window -v
unbind '"'
unbind %
在 pane 中移动是最高频的操作了,默认 Tmux 的行为需要每一次都按 <prefix>
, 这样导致每一次都非常麻烦,所以在配置中定义 M-<direction>
(M 代表 Meta,也就是键盘上的 Alt 键), 这样每一次只需要按 Alt+h
, 就能够移动到左边的 pane。
# switch panes using Alt-arrow without prefix
bind -n M-h select-pane -L
bind -n M-l select-pane -R
bind -n M-k select-pane -U
bind -n M-j select-pane -D
这里另外推荐一个 Plugin,如果不想自己配置,可以使用这个插件 vim-tmux-navigator ,这个插件做到了在 pane 中移动就像在 vim 中一样,并且可以和 vim 无缝衔接。使用 Ctrl + hjkl 来在不同的 Pane 中移动。
在设置完 Tmux 在 Tmux 中使用 Vim 的时候会发现,Vim 的 colorscheme 变的有些奇怪,需要在 .bashrc
或者 .zshrc
中设置:
export TERM="xterm-256color"
如果要在 Tmux 中进行复制可以使用 Tmux 的复制模式。
Tmux 中的复制需要使用 <prefix> [
来进入,具体分为如下几步:
<prefix> [
进入复制模式<prefix> ]
进行粘贴注意这种方式只能在 Tmux 中粘贴,而不会拷贝到系统粘贴板。所以我习惯重新 map 几个快捷键,沿用 Vim 中的方式
# in version 2.3 and below https://github.com/tmux/tmux/commit/76d6d3641f271be1756e41494960d96714e7ee58
setw -g mode-keys vi
bind-key -T copy-mode-vi 'v' send -X begin-selection # Begin selection in copy mode.
bind-key -T copy-mode-vi 'C-v' send -X rectangle-toggle # Begin selection in copy mode.
bind-key -T copy-mode-vi 'y' send -X copy-selection # Yank selection in copy mode.
# https://superuser.com/a/693990/298782
这样以后我就能够在复制模式中按下 y
来将内容拷贝到系统粘贴板。
X Windows 系统中常用的 Xterm,GNU Screen,SSH, GNome 中的 Terminal,KDE 中的 Konsole,Mac 下常用的 iTerm2 等,这些软件都属于 Terminal Emulator。 iTerm 等只是一个 GUI 软件,它的窗格只是窗格。而 Tmux 是终端复用,在一个命令行窗口中不仅可以显示多个 Shell 的内容,而且可以保持多个会话。 最重要的是:Tmux 和 Vim 一样属于字符终端软件,不需要任何 GUI 的支持,在远程登录时尤其有用。
升级了 tmux 之后,在启动 tmux 时如果遇到如下问题:
lost server
no server running on /tmp/tmux-1000/default
no server running on /tmp/tmux-1000/default
no sessions
检查一下 ~/.tmux.conf
配置,删除掉
set -g status-utf8 on
该配置已经被废弃。
最后我的 dotfiles 配置中有我过去多年使用的配置。
这些天折腾 Django 的时候用到了 MySQL,然而本地和VPS 上使用的版本不一致,本地使用了 5.7 版本,而 VPS 上使用了 5.5 的老版本,在数据迁移的时候遇到了 5.5 版本下不支持 DATETIME(6) 这样的数据类型。 DATETIME(6) 用来保存精确到微秒的时间。
环境:
系统:Debian 7, 按道理 Ubuntu/Debian 系应该都可以
无奈只能升级 MySQL 到 5.7 , 结果也比较顺利,官方有很详细 的升级说明:
wget http://dev.mysql.com/get/mysql-apt-config_x.y.z-1_all.deb # 从官网找到最新的版本,上面的链接中有
sudo dpkg -i mysql-apt-config_x.y.z-1_all.deb
sudo apt-get update
sudo apt-get install mysql-server
在中间弹出配置框时选择 5.7 ,应用,即可
mysql --version
最后记得运行
sudo mysql_upgrade -u root -p
Java 程序在运行时,Java 运行时系统一直对所有的对象进行所谓的运行时类型标识。Class 类封装一个对象和接口运行时的状态,当装载类时,Class 类型的对象自动创建。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个 Class 对象。
虚拟机为每种类型管理一个独一无二的 Class 对象。也就是说,每个类(型)都有一个 Class 对象。运行程序时,Java 虚拟机 (JVM) 首先检查是否所要加载的类对应的 Class 对象是否已经加载。如果没有加载,JVM 就会根据类名查找.class 文件,并将其 Class 对象载入。Class 对象包含着类的元数据(metadata)包括类的
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。枚举是 class,而 注解 (annotation) 是 interface。
有如下方法可以获取 Class 对象
myClass.getClass()
方法来获取forName()
来获取,比如 Class.forName("MyClass")
,传入的参数必须是接口或者类的名字getName()
方法返回 String,Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
获取类所有公开的构造函数数组,返回 Constructor<?>[]
通常在反射中为类创建实例。newInstance
调用默认构造函数(无参)来初始化新建对象。
返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。
Class API | 列表 | 私有构造函数 |
---|---|---|
getDeclaredConstructor() | no | yes |
getConstructor() | no | no |
getDeclaredConstructors() | yes | yes |
getConstructors() | yes | no |
Class API | 成员变量列表 | 继承的变量 | 私有变量 |
---|---|---|---|
getDeclaredField() | no | no | yes |
getField() | no | yes | no |
getDeclaredFields() | yes | no | yes |
getFields() | yes | yes | no |
Class API | 方法列表 | 继承的方法 | 私有方法 |
---|---|---|---|
getDeclaredMethod() | no | no | yes |
getMethod() | no | yes | no |
getDeclaredMethods() | yes | no | yes |
getMethods() | yes | yes | no |
综上基本看到出来:不带 Declared
的方法都是返回的类公开的 public 可访问的,而带 Declared
的方法能够访问私有。
而对于继承的方法或者变量,通过不带 Declared
的方法能够访问到。
public static Class<?> forName(String className)// 传入完整的“包,类”名称实例化 Class 对象
public Constructor[] getContructors() // 得到一个类的全部的构造方法
public Field[] getDeclaredFields()// 得到本类中单独定义的全部属性
public Field[] getFields()// 得到本类继承而来的全部属性
public Method[] getMethods()// 得到一个类的全部方法
public Method getMethod(String name,Class..parameterType)// 返回一个 Method 对象,并设置一个方法中的所有参数类型
public Class[] getInterfaces() // 得到一个类中锁实现的全部接口
public String getName() // 得到一个类完整的“包。类”名称
public Package getPackage() // 得到一个类的包
public Class getSuperclass() // 得到一个类的父类
public Object newInstance() // 根据 Class 定义的类实例化对象
public Class<?> getComponentType() // 返回表示数组类型的 Class
public boolean isArray() // 判断此 class 是否是一个数组
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。一直以来反射技术都是 Java 中的闪亮点,这也是目前大部分框架(如 Spring/Mybatis 等)得以实现的支柱。在 Java 中,Class 类与 java.lang.reflect 类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有 Constructor 类表示的是 Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field 表示 Class 对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含 private)、Method 表示 Class 对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含 private)。
Docker 是一个能够把开发环境的应用程序自动部署到容器的开源引擎。该引擎的目标是提供一个轻量、快速的环境,能够运行开发者的程序,并方便高效地将程序从开发者的笔记本部署到测试环境,然后再部署到生产环境。
Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。
Docker 使用客户端 - 服务端 C/S 架构,使用远程 API 来管理和创建 Docker 容器。
Docker 容器通过 Docker 镜像来创建。容器与镜像的关系类似于面向对象编程中的对象与类。
Docker 官网:http://www.docker.com
Github Docker 源码:https://github.com/docker/docker
名词 | 解释 |
---|---|
Docker 镜像 (Images) | Docker 镜像是用于创建 Docker 容器的模板。可以使用 docker images 来查看镜像 |
Docker 容器 (Container) | 容器是独立运行的一个或一组应用。可以使用 docker ps -a 来查看 container |
Docker 客户端 (Client) | Docker 客户端通过命令行或者其他工具使用 Docker API (https://docs.docker.com/reference/api/docker_remote_api) 与 Docker 的守护进程通信。 |
Docker 主机 (Host) | 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。 |
Docker 仓库 (Registry) | Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。 Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用 |
Docker Machine | Docker Machine 是一个简化 Docker 安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装 Docker,比如 VirtualBox、 Digital Ocean、Microsoft Azure。 |
Docker 要求 Ubuntu 系统的内核版本高于 3.10 ,查看本页面的前提条件来验证你的 Ubuntu 版本是否支持 Docker。 通过 uname -r 命令查看你当前的内核版本。通过如下命令安装
wget -qO- https://get.docker.com/ | sh
启动
sudo service docker start
启动测试运行 hello-world
docker run hello-world
Hypriot team 提供了一个可安装的 Package,可以不必自己编译安装:
$ curl -ks https://packagecloud.io/install/repositories/hypriot/schatzkiste/script.deb.sh | sudo bash
$ sudo apt-get install docker-hypriot=1.10.3-1
$ sudo sh -c 'usermod -ag docker $sudo_user'
$ sudo systemctl enable docker.service
安装完后
pi@raspberrypi ~ $ sudo docker info
Containers: 0
Running: 0
Paused: 0
Stopped: 0
Images: 0
Server Version: 1.10.3
Storage Driver: overlay
Backing Filesystem: extfs
Execution Driver: native-0.2
Logging Driver: json-file
Plugins:
Volume: local
Network: bridge null host
Kernel Version: 4.1.19-v7+
Operating System: Raspbian GNU/Linux 8 (jessie)
OSType: linux
Architecture: armv7l
CPUs: 4
Total Memory: 925.8 MiB
Name: raspberrypi
ID: UMZZ:ZYZY:TLB2:DKK7:GY6V:SYW3:JZZD:7L4X:JNXY:HNRQ:PFFO:K4X5
Debug mode (server): true
File Descriptors: 11
Goroutines: 20
System Time: 2017-07-16T14:07:39.946162928Z
EventsListeners: 0
Init SHA1: 0db326fc09273474242804e87e11e1d9930fb95b
Init Path: /usr/lib/docker/dockerinit
Docker Root Dir: /var/lib/docker
WARNING: No memory limit support
WARNING: No swap limit support
WARNING: No oom kill disable support
WARNING: No cpu cfs quota support
WARNING: No cpu cfs period support
查看版本
pi@raspberrypi ~ $ sudo docker version
Client:
Version: 1.10.3
API version: 1.22
Go version: go1.4.3
Git commit: 20f81dd
Built: Thu Mar 10 22:23:48 2016
OS/Arch: linux/arm
Server:
Version: 1.10.3
API version: 1.22
Go version: go1.4.3
Git commit: 20f81dd
Built: Thu Mar 10 22:23:48 2016
OS/Arch: linux/arm
参考文档, 有关树莓派从 wheezy 升级到 Debian 8 Jessie 的内容也可以参考该链接
$ sudo sed -i 's/wheezy/jessie/' /etc/apt/sources.list
$ sudo sed -i 's/wheezy/jessie/' /etc/apt/sources.list.d/raspi.list
$ sudo apt-get update && sudo apt-get -y upgrade # answer 'y' to upcoming questions
$ sudo apt-get -y dist-upgrade # answer 'y' to upcoming questions
$ sudo init 6
$ sudo apt-get -y autoremove
$ sudo apt-get -y purge $(dpkg -l | awk '/^rc/ { print $2 }')
$ sudo init 6
Linux Mint 下安装的时候使用 Ubuntu 下那种方式的时候没有安装成功,网上查说源中的内容有些问题,使用自己的添加的 Repository 才可以:
# First import the GPG key
sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 \
--recv-keys 58118E89F3A912897C070ADBF76221572C52609D
# Next, point the package manager to the official Docker repository
sudo apt-add-repository 'deb https://apt.dockerproject.org/repo ubuntu-xenial main'
# Update the package database
sudo apt update
# 安装必要的包
sudo apt install linux-image-generic linux-image-extra-virtual
# 安装 docker
sudo apt install docker-engine
最后安装成功
sudo docker version
[sudo] password for einverne:
Client:
Version: 17.05.0-ce
API version: 1.29
Go version: go1.7.5
Git commit: 89658be
Built: Thu May 4 22:10:54 2017
OS/Arch: linux/amd64
Server:
Version: 17.05.0-ce
API version: 1.29 (minimum version 1.12)
Go version: go1.7.5
Git commit: 89658be
Built: Thu May 4 22:10:54 2017
OS/Arch: linux/amd64
Experimental: false
镜像是 Docker 的三大组件之一,Docker 官方托管了一个镜像的 Hub,可以从上面获取镜像。
可以使用 sudo docker search []
来搜索镜像
可以使用 docker pull
从仓库中获取所需要的镜像,比如从官方仓库下载 ubuntu 12.04 的镜像:
sudo docker pull ubuntu:12.04
实际该命令相当与 sudo docker pull registry.hub.docker.com/ubuntu:12.04
,即从注册的服务器的 Ubuntu Repository 中拉取 12.04 的镜像。
在国内拉去镜像的速度一般不是很快,可以使用官方的镜像加速,其他网易,阿里都有提供,但是建议还是使用官方镜像。
docker pull registry.docker-cn.com/myname/myrepo:mytag
比如说:
docker pull registry.docker-cn.com/library/ubuntu:16.04
Docker Store 是发现 Docker 镜像的新地方。
当拉取完成之后可以使用 sudo docker images
来查看本地的镜像列表
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest 1e5ab59102ce 6 days ago 108MB
redis latest 4e482b286430 3 months ago 99MB
列出的信息中,可以看到几个字段
默认 docker images
只会显示顶层镜像,如果希望显示包含中间层镜像在内的所有镜像,需要添加 -a
参数
docker images -a
docker images
命令有 -f
参数用来过滤,比如列出 虚悬镜像 dangling image 可以使用
$ sudo docker images -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 8e7492c7cb6a 28 minutes ago 122MB
虚悬镜像是指因为官方镜像维护,发布新版本之后有些本地镜像名被取消,虚悬镜像已经没有价值,可以随意删除
sudo docker rmi $(sudo docker images -q -f dangling=true)
docker images
支持更多的过滤器语法,比如,希望看到 ubuntu:14.04
之后建立的镜像,可以
sudo docker images -f since=ubuntu:14.04
想看某个时间之前的镜像,可以把 since
改成 before
也可以自定输出格式
sudo docker images --format "\{\{.ID\}\}: \{\{.Repository\}\}"
sudo docker images --format "table \{\{.ID\}\}\t\{\{.Repository\}\}\t\{\{.Tag\}\}"
本地镜像保存在 Docker 宿主机 /var/lib/docker
目录下,每个镜像都保存在 Docker 所采用的存储驱动目录下,比如 aufs 或者 devicemapper。 可以在 /var/lib/docker/containers
目录下看到所有的容器。
可以运行本地镜像
sudo docker run -t -i ubuntu:12.04 /bin/bash
sudo docker run --name=webserver –p 8080:80 –d nginx
run
命令用来创建一个 docker container-p
参数,用来将 -p local-machine-port:internal-container-port
暴露出来,比如 8080:80 将内部的 80 端口映射到 8080 端口-d
用来使用 daemon 后台运行当运行某一个镜像时,docker 会自动创建一个 container 容器在,该容器中运行该镜像,可以使用 sudo docker ps -a
来查看当前正在运行的容器。可以使用 docker stop [ContainerId]
来终止一个容器的运行。
使用 docker stop
来终止一个运行中的容器,当前正在运行或者终止的容器可以使用 sudo docker ps -a
来查看。
处于终止状态的容器,可以通过 docker start
命令来重新启动。
此外,docker restart
命令会将一个运行态的容器终止,然后再重新启动它。
docker kill <ID>
可以用来强制停止一个容器
但在 docker run
时使用 -d
参数时,容器会进入后台,可以使用 attach
来进入容器
sudo docker attach <containerid>
只有当容器停止运行时才可以将其删除,确认容器已经停止,使用 sudo docker rm [Container Id]
来删除一个容器, 使用 sudo docker rmi [ImageId]
来删除一个本地的镜像。
删除所有容器,可以使用 docker rm $(docker ps -a -q)
, -q
参数表示只列出容器 ID。
可以使用 docker cp
命令来在容器和宿主机之间拷贝文件。
docker cp foo.txt mycontainer:/foo.txt
docker cp mycontainer:/foo.txt foo.txt
Guava 是 Google 开源的 Java 核心类库,包含了 Java 开发中众多的核心功能,其中最著名最好用的也就是合集和缓存相关的工具了。
Guava 有很多学习材料,比如官方 Wiki
源码包的简单说明:
com.google.common.annotations:普通注解类型。 com.google.common.base:基本工具类库和接口。 com.google.common.cache:缓存工具包,非常简单易用且功能强大的 JVM 内缓存。 com.google.common.collect:带泛型的集合接口扩展和实现,以及工具类,这里有很多好用的集合。 com.google.common.eventbus:发布订阅风格的事件总线 com.google.common.hash: 哈希工具包 com.google.common.io:I/O 工具包 com.google.common.math:原始算术类型和超大数的运算工具包 com.google.common.net:网络工具包 com.google.common.primitives:八种原始类型和无符号类型的静态工具包 com.google.common.reflect:反射工具包 com.google.common.util.concurrent:多线程工具包
Optional 存在的意义就是为了替换 null
,null 存在的大部分情况用户是无法知道方法想要返回的什么。null 多数情况下并不意味着返回值没有值,一些情况下可能表示 error,甚至也有人用 null 来表示成功,或者表示成功了但是没有返回值。因此 Optional 存在的意义并不是可读性,而是强迫使用他的人思考返回值应该是什么。
举个例子,当调用别人的 other.method(a, b)
方法时,可能经常忘记 method 返回值可能是 null,而当 method 返回值为 Optional 时,调用者几乎不可能忘记 null 的存在,因为调用者需要自己 unwrap 来获取结果。基于这个原因,Guava 通常建议将 Optional 作为返回值。1
Guava 的集合类是对 JDK 集合类的扩展。
不可变对象有很多好处:
Guava 提供了简单,易于使用的不可变 Collection 类型的版本, JDK 提供了 Collections.unmodifiableXXX
方法,但有些缺陷
创建 ImmutableXXX 可以有以下方式:
copyOf
方法,比如 ImmutableSet.copyOf(set)
of
方法,ImmutableSet.of("a", "b", "c")
使用 Builder
方式
public static final ImmutableSet<Color> GOOGLE_COLORS =
ImmutableSet.<Color>builder()
.addAll(WEBSAFE_COLORS)
.add(new Color(0, 191, 255))
.build();
Multiset 和 set 的区别就在于可以存放相同的元素超过一次,multisets {a, a, b} 和 {a, b, a} 是相同的。官方文档上说可以用两种眼光看 Multiset
ArrayList<E>
没有排序的约束,顺序是不关心的Map<E, Integer>
包含元素和计数Multiset API 在设计的时候也充分的包含了着两种想法:
当看成通常的 Collection 时, Multiset 表现和无序的 ArrayList 相似
add(E)
添加元素iterator()
遍历size()
是集合中所有元素的数量而其他查询操作,性能表现,和 Map<E, Integer>
类似
count(Object)
返回元素的计数,HashMultiset 的时间复杂度 O(1),而 TreeMultiset
是 O(log n)entrySet()
返回 Set<Multiset.Entry<E>>
表现和 keySet()
类似Multiset 接口定义的接口主要有:
Method | 解释 |
---|---|
add(E element) | 向其中添加单个元素 |
add(E element,int occurrences) | 向其中添加指定个数的元素 |
count(Object element) | 返回给定参数元素的个数 |
remove(E element) | 移除一个元素,其 count 值 会响应减少 |
remove(E element,int occurrences) | 移除相应个数的元素 |
elementSet() | 将不同的元素放入一个 Set 中 |
entrySet() | 类似与 Map.entrySet 返回 Set<Multiset.Entry> 。包含的 Entry 支持使用 getElement() 和 getCount() |
setCount(E element ,int count) | 设定某一个元素的重复次数 |
setCount(E element,int oldCount,int newCount) | 将符合原有重复个数的元素修改为新的重复次数 |
retainAll(Collection c) | 保留出现在给定集合参数的所有的元素 |
removeAll(Collectionc) | 去除出现给给定集合参数的所有的元素 |
Multiset 的常用实现
Guava 提供了 Multiset 的多种实现,这些实现基本对应了 JDK 中 Map 的实现: Map | Corresponding Multiset | Supports null elements ——————|————————-|————————— HashMap | HashMultiset | Yes TreeMap | TreeMultiset | Yes (if the comparator does) LinkedHashMap | LinkedHashMultiset | Yes ConcurrentHashMap | ConcurrentHashMultiset | No ImmutableMap | ImmutableMultiset | No
Java 开发中可能经常会需要实现 Map<K, List<V>>
or Map<K, Set<V>>
类似的数据结构,Multimap 就是为了解决此类时间而生。
有两种看待 Multimap 的方式,单独的 key 对应 value
a -> 1
a -> 2
a -> 4
b -> 3
c -> 5
或者是,唯一的 key 对应一个集合
a -> [1, 2, 4]
b -> [3]
c -> [5]
通常来说,以第一种方式思考即可,但是 Multimap 也提供了 asMap()
方式来返回一个 Map<K, Collection<V>>
。重要的是,没有 key map 到一个空集合这样的方式。
通常来说不会直接使用 Multimap 接口,而是使用 ListMultimap
或者 SetMultimap
,对应将 key 映射到 List 或者 Set 。
最直接构建 Multimap 的方式是使用 MultimapBuilder,这种方式允许用户自定义 key 和 value 应该有的样式。
// creates a ListMultimap with tree keys and array list values
ListMultimap<String, Integer> treeListMultimap =
MultimapBuilder.treeKeys().arrayListValues().build();
// creates a SetMultimap with hash keys and enum set values
SetMultimap<Integer, MyEnum> hashEnumMultimap =
MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build();
当然也可以直接使用 create()
方法。
BiMap 提供了一种新的集合类型,它提供了 key 和 value 的双向关联的数据结构。在使用 BiMap 时,会要求 Value 的唯一性。如果 value 重复了则会抛出错误:java.lang.IllegalArgumentException. 如果想要强制赋值,可以使用 BiMap.forcePut(key, value)
这样之前的可能存在的值会被覆盖。
BiMap<String, Integer> userId = HashBiMap.create();
...
String userForId = userId.inverse().get(id);
通过例子一目了然
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
复制文件,复制文件要求源地址和目的地址不一致。如果目的地址文件存在则会被直接覆盖。
File original = new File("path/to/original");
File copy = new File("path/to/copy");
Files.copy(original, copy);
查看方法签名,还支持
public static void copy(File from, OutputStream to) {}
public static void write(byte[] from, File to) {}
移动或者重命名文件
File original = new File("src/main/resources/copy.txt");
File newFile = new File("src/main/resources/newFile.txt");
Files.move(original, newFile); // 移动或重命名文件,类似 Unix 中的 mv
将文件读取为字符串列表
List<String> readLines = Files.readLines(file, Charsets.UTF_8);
写文件,或者附加内容到文件
File file = new File("quote1.txt");
String hamletQuoteStart = "To be, or not to be";
Files.write(hamletQuoteStart,file, Charsets.UTF_8);// 写文件
String hamletQuoteEnd = ",that is the question";
Files.append(hamletQuoteEnd,file,Charsets.UTF_8); // 追加文件
String overwrite = "Overwriting the file";
Files.write(overwrite, file, Charsets.UTF_8); // 重写文件
为文件生成 Hash 值
File file = new File("src/main/resources/sampleTextFileOne.txt");
HashCode hashCode = Files.hash(file, Hashing.md5());
System.out.println(hashCode);
Spring MVC 处理文件上传
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<form method="POST" action="uploadFile" enctype="multipart/form-data">
File to upload: <input type="file" name="file">
Name: <input type="text" name="name">
<input type="submit" value="Upload"> Press here to upload the file!
</form>
form 的 enctype 应该是 multipart/form-data
在 WEB-INF 目录下 servlet-context.xml 中添加配置
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="104857600"/>
</bean>
注意这里的 size 单位是 B,所以上面的大小限制是 100MB = 1024 * 1024 * 100
。
Controller 中代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
@RequestMapping("/upload")
public class FileController {
private static Logger logger = LoggerFactory.getLogger(FileController.class);
@RequestMapping(value = "/zip", method = RequestMethod.POST)
@ResponseBody
public CommonResponse<ZipResponse> upload(@RequestParam("file") MultipartFile file,
HttpServletRequest req,
HttpServletResponse resp) throws BaseApiException {
// deal with CSRF
CommonResponse<ZipResponse> commonResponse = new CommonResponse<>();
ZipResponse response = new ZipResponse();
try {
File cFile = new File("/tmp/" + file.getOriginalFilename());
file.transferTo(cFile);
FdsFileService fdsFileService = new FdsFileService();
// store some where
String url = "";
response.setUrl(url);
commonResponse.setData(response);
cFile.delete();
} catch (Exception e) {
commonResponse.fail("fail");
logger.error("上传ZIP失败", e);
}
return commonResponse;
}
}
Spring 在处理文件上传的时候报错:
the request was rejected because no multipart boundary was found
解决方法:不要手动设置 Content-Type ,让 Chrome 或者其他浏览器处理, Postman 同理不需要设置 Content-Type。 Refer
Versions Plugin 该插件用于需要管理项目
用来设置版本号
mvn versions:set -DnewVersion=1.0.1
如果需要回滚
mvn versions:revert
确认则使用
mvn versions:commit
在 Linux 上查看系统流量有很多命令,平时一直使用 iftop 来查看单块网卡或者系统整体的流量,iftop 可以查看 TCP 链接的流量情况,分析出流量连往的 IP 地址。但是 iftop 无法做到查看系统中单个进程的网络流量情况。所以 Google 一下之后发现了 nethogs 。
nethogs 自己的介绍
NetHogs is a small ‘net top’ tool. Instead of breaking the traffic down per protocol or per subnet, like most such tools do, it groups bandwidth by process - and does not rely on a special kernel module to be loaded. So if there’s suddenly a lot of network traffic, you can fire up NetHogs and immediately see which PID is causing this, and if it’s some kind of spinning process, kill it.
看介绍就能很快速的知道 nethogs 的设计用途,他就是为了查看单独进程流量情况而被创造出来的。nethogs 是一个小型的 net top 工具,不和大多数工具那样按照每个协议或者子网的速度,而是按照进程进行带宽分组。 nethogs 不需要依赖某个特殊的内核模块,如果发生了网络阻塞,你可以启动 nethogs 立即看到哪个 PID 造成的,这样就可以轻松的找到占用带宽的程序,然后进行相应的内容控制。
在 Debian/Ubuntu 下,sudo apt-get install nethogs
官方源的中 nethogs 因为版本过老,可能会有一些问题,不过可以先尝试安装一下。如果启动 sudo nethogs
之后发现有
creating socket failed while establishing local IP - are you root?
这样的错误。(PS: 这个错误已经在 0.8.1 中被解决)请使用编译安装。
wget -c https://github.com/raboof/nethogs/archive/v0.8.5.tar.gz
tar xf v0.8.5.tar.gz
cd ./nethogs-0.8.5/
安装必要的依赖,编译安装
sudo apt-get install libncurses5-dev libpcap-dev
make && sudo make install
检查版本并启动
nethogs -V
sudo nethogs
使用就非常简单了,直接运行就能查看结果.
~# nethogs
NetHogs version 0.8.5
PID USER PROGRAM DEV SENT RECEIVED
2214 root /usr/lib/apt/methods/http eth0 4.693 238.631 KB/sec
2051 ubuntu sshd: ubuntu@pts/1 eth0 3.442 0.310 KB/sec
1120 ubuntu sshd: ubuntu@pts/0 eth0 0.416 0.035 KB/sec
2213 root /usr/lib/apt/methods/http eth0 0.021 0.023 KB/sec
? root unknown TCP 0.000 0.000 KB/sec
TOTAL 8.572 239.000 KB/sec
指定某块网卡
nethogs eth0
在启动 nethogs 时使用 -d seconds
参数定义刷新频率
nethogs -d 1 # 每秒钟刷新
在进入 nethogs 之后,可以使用如下的交互命令:
m: 修改网速单位
r: 按照流量排序
s: 按照发送流量排序
q: 退出
Supervisor (http://supervisord.org) 是一个进程管理工具,可以很方便的用来启动、重启、关闭,监控进程。更多的介绍可以参考官网。Supervisor 设计的目的只要是为了:
rc.d
脚本是不方便的,rc.d
脚本是管理,初始化,自启动进程的工具,但是编写这些脚本非常困难,并且也难以维护。另外,rc.d
脚本在进程崩溃之后也不会自动重启,而且很多应用在崩溃之后也不会自动重启,这就可能导致程序在崩溃后进程丢失。Supervisord 将管理的程序作为子进程启动,并且可以配置在崩溃后自动重启。因为 Supervisord 使用 Python 编写,所以安装:
sudo pip install supervisor
如果是 Ubuntu 系统,还可以使用 apt-get 安装。命令如下
sudo apt-get install supervisor
如果在系统中只想要启动一个 supervisord,并且希望有开机启动,推荐使用 apt 方式安装。
Supervisor 有两个主要的组成部分:
supervisord,运行 Supervisor 时会启动一个进程 supervisord,它负责
supervisorctl,是命令行管理工具,可以用来执行 stop、start、restart 等命令,对这些子进程进行管理。
supervisord 的配置同样也分为两部分:supervisord(server 端)和应用程序。
先来看 supervisord 的配置文件。安装完 supervisor 之后,可以运行 echo_supervisord_conf
命令输出默认的配置项,也可以重定向到一个配置文件里(如果是 apt 安装,则默认配置在 /etc/supervisor/supervisord.conf 下):
echo_supervisord_conf > /etc/supervisord.conf
其中重要的部分配置:
[unix_http_server]
file=/tmp/supervisor.sock ; UNIX socket 文件路径,supervisorctl 会使用
;chmod=0700 ; socket 文件的 mode,默认是 0700
;chown=nobody:nogroup ; socket 文件的 owner,格式: uid:gid
;[inet_http_server] ; HTTP 服务器,提供 web 管理界面
;port=127.0.0.1:9001 ; Web 管理后台运行的 IP 和端口
;username=user ; WEB 管理后台的用户名
;password=123 ; WEB 管理后台的密码
[supervisord]
logfile=/tmp/supervisord.log ; 日志文件,默认是 $CWD/supervisord.log
logfile_maxbytes=50MB ; 日志文件大小,超出会 rotate (分割),默认 50MB
logfile_backups=10 ; 日志文件保留备份数量默认 10
loglevel=info ; 日志级别,默认 info,其它:debug,warn,trace
pidfile=/tmp/supervisord.pid ; pid 文件
nodaemon=false ; 是否在前台启动,默认是 false,即以 daemon 的方式启动
minfds=1024 ; 可以打开的文件描述符的最小值,默认 1024
minprocs=200 ; 可以打开的进程数的最小值,默认 200
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; 通过 UNIX socket 连接 supervisord,路径与 unix_http_server 部分的 file 一致
;serverurl=http://127.0.0.1:9001 ; 通过 HTTP 的方式连接 supervisord
; 包含其他的配置文件
[include]
files = relative/directory/*.ini ; 可以是 *.conf 或 *.ini
我们把上面这部分配置保存到 /etc/supervisord.conf(或其他任意有权限访问的文件),然后启动 supervisord(通过 -c 选项指定配置文件路径,如果不指定会按照这个顺序查找配置文件:$CWD/supervisord.conf
, $CWD/etc/supervisord.conf
, /etc/supervisord.conf
)
supervisord -c /etc/supervisord.conf
查看 supervisord 是否在运行:
ps aux | grep supervisord
supervisord
是 supervisor 的守护进程,但是他自身并没有 reload 选项,因此需要使用
sudo service supervisor restart # 来重启 supervisord
sudo /etc/init.d/supervisor restart
如果需要使用其他 conf 文件,在 stop supervisord 之后在使用 -c
参数后接配置文件。
可以使用 sudo service supervisor status
来查看 supervisord 的服务状态
上面我们已经把 supervisrod 运行起来了,现在可以添加我们要管理的进程的配置文件。可以把所有配置项都写到 supervisord.conf 文件里,但并不推荐这样做,而是通过 include 的方式把不同的程序(组)写到不同的配置文件里。
为了举例,我们新建一个目录 /etc/supervisor/ 用于存放这些配置文件,相应的,把 /etc/supervisord.conf 里 include 部分的的配置修改一下:
[include]
files = /etc/supervisor/*.conf
假设有个用 Python 和 Flask 框架编写的用户中心系统,取名 program_name,用 gunicorn (http://gunicorn.org/) 做 web 服务器。项目代码位于 /home/einverne/projects/program_name,gunicorn 配置文件为 gunicorn.py,WSGI callable 是 wsgi.py 里的 app 属性。所以直接在命令行启动的方式可能是这样的:
cd /home/einverne/projects/program_name
gunicorn -c gunicorn.py wsgi:app
现在编写一份配置文件来管理这个进程(需要注意:用 supervisord 管理时,gunicorn 的 daemon 选项需要设置为 False):
; 设置进程的名称, 使用 supervisorctl 来管理进程需要使用该进程名
[program:your_program_name]
directory = /home/einverne/projects/name; 程序的启动目录
command = gunicorn -c gunicorn.py wsgi:app ; 启动命令,与手动在命令行启动的命令是一样的
autostart = true ; 在 supervisord 启动的时候也自动启动
startsecs = 5 ; 启动 5 秒后没有异常退出,就当作已经正常启动了
autorestart = true ; 程序异常退出后自动重启
startretries = 3 ; 启动失败自动重试次数,默认是 3
user = root ; 用哪个用户启动
redirect_stderr = true ; 把 stderr 重定向到 stdout,默认 false
stdout_logfile_maxbytes = 20MB ; stdout 日志文件大小,默认 50MB
stdout_logfile_backups = 20 ; stdout 日志文件备份数
; stdout 日志文件,需要注意当指定目录不存在时无法正常启动,所以需要手动创建目录(supervisord 会自动创建日志文件)
stdout_logfile = /data/logs/program_name_stdout.log
loglevel = info ; loglevel 指定了日志级别, python 的 print 语句输出的日志是不会被记录到日志文件的,需要搭配 Python 的 logging 模块来输出指定级别的日志
; 可以通过 environment 来添加需要的环境变量,一种常见的用法是修改 PYTHONPATH
; environment=PYTHONPATH=$PYTHONPATH:/path/to/somewhere
一份配置文件至少需要一个 [program:x]
部分的配置,来告诉 supervisord 需要管理那个进程。[program:x]
语法中的 x 表示 program name,会在客户端(supervisorctl 或 web 界面)显示,在 supervisorctl 中通过这个值来对程序进行 start、restart、stop 等操作。更加详细的配置可以参考官网。
使用 group 开启或者关闭一组程序,在配置目录下加上额外的配置文件
[group:group1]
programs=group-member-1,group-member-2 ; each refers to 'x' in [program:x] definitions
priority=999 ; the relative start priority (default 999)
[program:group-member-1]
command=xxx
autostart=true
autorestart=true
user=redis
stdout_logfile=xxx
stderr_logfile=xxx
[program:group-member-2]
command=xxx
autostart=true
autorestart=true
user=redis
stdout_logfile=xxx
stderr_logfile=xxx
添加了 group 配置之后, 进程管理名就变成了 group1:group-member-1 这样的形式,可以使用如下方法启动一组程序
supervisor> start group1:*
Supervisorctl 是 supervisord 的一个命令行客户端工具,启动时需要指定与 supervisord 使用同一份配置文件,否则与 supervisord 一样按照顺序查找配置文件。
supervisorctl -c /etc/supervisord.conf
上面这个命令会进入 supervisorctl 的 shell 界面,然后可以执行不同的命令了:
> status # 查看程序状态
> stop program_name # 关闭 program_name 程序
> start program_name # 启动 program_name 程序
> restart program_name # 重启 program_name 程序
> reread # 读取有更新(增加)的配置文件,不会启动新添加的程序,也不会重启任何程序
> reload # 载入最新的配置文件,停止原有的进程并按照新的配置启动
> update # 重启配置文件修改过的程序,配置没有改动的进程不会收到影响而重启
上面这些命令都有相应的输出,除了进入 supervisorctl 的 shell 界面,也可以直接在 bash 终端运行:
$ supervisorctl status
$ supervisorctl stop program_name
$ supervisorctl start program_name
$ supervisorctl restart program_name
$ supervisorctl reread
$ supervisorctl reload
$ supervisorctl update
当 supervisor 的日志文件大小超过 stdout_logfile_maxbytes
时,之前的日志文件会被放到 logfile.log.1 文件中备份。可以在相应 program 配置中配置如下两项改变日志的行为:
stdout_logfile_maxbytes
为 0 时,所有的日志文件都会被放到一个文件中stdout_logfile_backups
为 0 时,当日志文件太大时,旧文件就会被删除而不是移动到单独的文件中。配置 stderr_logfile_maxbytes
和 stderr_logfile_backups
类似。
这样的日志方式叫做 log file rotation
除了 supervisorctl 之外,还可以配置 supervisrod 启动 web 管理界面,这个 web 后台使用 Basic Auth 的方式进行身份认证。
除了单个进程的控制,还可以配置 group,进行分组管理。
经常查看日志文件,包括 supervisord 的日志和各个 pragram 的日志文件,程序 crash 或抛出异常的信息一半会输出到 stderr,可以查看相应的日志文件来查找问题。
Supervisor 有很丰富的功能,还有其他很多项配置,可以在官方文档获取更多信息:http://supervisord.org/index.html
在使用 pip 安装的时候默认并没有安装成服务,因此如果想要使用开机启动可以使用 APT 安装。 而如果已经安装了 supervisor 想要自己配置开机启动脚本,可以使用这个 link 中的方法来添加。
supervisor 官方提供 的开机脚本似乎对于 Ubuntu 有些问题,可以使用上方 Serverfault 中提到的。
sudo curl https://gist.github.com/howthebodyworks/176149/raw/88d0d68c4af22a7474ad1d011659ea2d27e35b8d/supervisord.sh > /etc/init.d/supervisord
sudo chmod +x /etc/init.d/supervisord
sudo update-rc.d supervisord defaults
确保在 /etc/supervisord.conf
中配置了正确的 pid, 并且和 /etc/init.d/supervisord
相对应:
pidfile=/var/run/supervisord.pid
测试:
service supervisord stop
service supervisord start
错误关键字:supervisor can’t find command
这时候可以手动 开启一个 bash ,或者 sh
commmand=sh -c 'your command'
问题关键字:error: <class ‘socket.error’>, [Errno 13] Permission denied: file: /usr/lib/python2.7/socket.py line
权限问题,绝大部分情况下使用 sudo supervisorctl
即可解决。当然如果你愿意配置一个 supervisor 用户组,然后在 配置文件中配置相应的权限也可以解决。
具体参考:https://github.com/Supervisor/supervisor/issues/173
了解并熟悉 MySQL 中的数据类型,对建表和数据库优化都非常重要。 MySQL 实现了 SQL 定义的类型,也相应的增加了 tiny, small, big 的类型。 MySQL 的数据类型主要分成三个部分:
更多的内容可以在官网 查到。
MySQL 数据类型 | 大小 | 范围(有符号) |
---|---|---|
TINYINT(m) | 1 字节 | 范围 (-128~127) |
SMALLINT(m) | 2 个字节 | 范围 (-32768~32767) |
MEDIUMINT(m) | 3 个字节 | 范围 (-8388608~8388607) |
INT(m) | 4 个字节 | 范围 2^31-1(-2147483648~2147483647) |
BIGINT(m) | 8 个字节 | 范围 2^63-1(+-9.22*10 的 18 次方) |
取值范围如果加了 unsigned (无符号),则最大值翻倍,如 TINYINT unsigned 的取值范围为 (0~256)。 INT(m) 里的 m 是表示 SELECT 查询结果集中的显示宽度,并不影响实际的取值范围,没有影响到显示的宽度,不知道这个 m 有什么用。
MySQL 数据类型 | 大小 | 含义 |
---|---|---|
float(m,d) | 4 字节 | 单精度浮点型 8 位精度 (4 字节) m 总个数,d 小数位 |
double(m,d) | 8 字节 | 双精度浮点型 16 位精度 (8 字节) m 总个数,d 小数位 |
设一个字段定义为 float(5,3),如果插入一个数 123.45678, 实际数据库里存的是 123.457,但总个数还以实际为准,即 6 位。
浮点型在数据库中存放的是近似值,而定点类型在数据库中存放的是精确值。
decimal(m,d) 参数 m<65
是总个数,d<30
且 d<m
是小数位。
MySQL 数据类型 | 大小 | 含义 |
---|---|---|
char(n) | 0-255 字节 | 固定长度,最多 255 个字符 |
varchar(n) | 0-65535 字节 | 固定长度,最多 65535 个字符 |
tinytext | 0-255 字节 | 可变长度,最多 255 个字符, 255B |
text | 0-65535 字节 | 可变长度,最多 65535 个字符,64 KB |
mediumtext | 0-16777 215 字节 | 可变长度,最多 2 的 24 次方 -1 个字符,最大16MB |
longtext | 0-4294967295 字节 | 可变长度,最多 2 的 32 次方 -1 个字符,最大 4G |
char 和 varchar 比较:
n<=255
)或 2 个字节 (n>255
),所以 varchar(4), 存入 3 个字符将占用 4 个字节。varchar 和 text 比较:
n<=255
)或 2 个字节 (n>255
),text 是实际字符数 +2 个字节。char(n) 和 varchar (n)中括号中 n 代表字符的个数,并不代表字节个数,所以当使用中文的时候 (UTF8) 意味着可以插入 m 个中文,但是实际会占用 m*3
个字节。
同时 char 和 varchar 最大的区别就在于 char 不管实际 value 都会占用 n 个字符的空间,而 varchar 只会占用实际字符应该占用的空间 +1,并且实际空间 +1<=n
Value | CHAR(4) | Storage Required | VARCHAR(4) | Storage Required |
---|---|---|---|---|
’’ | ’ ‘ | 4 bytes | ’’ | 1 byte |
‘ab’ | ‘ab ‘ | 4 bytes | ‘ab’ | 3 bytes |
‘abcd’ | ‘abcd’ | 4 bytes | ‘abcd’ | 5 bytes |
‘abcdefg’ | ‘abcd’ | 4 bytes | ‘abcd’ | 5 bytes |
在使用 MySQL 存储字符串时经常会疑惑选择哪一种数据类型。
首先从空间方面,当 varchar 大于某些值时,会自动转换成 text,大概为
tinytext
text
mediumtext
所以对于大内容 varchar 和 text 并没有太多区别。
其次从性能方面,索引是影响性能最关键的因素,对于 text 来说,只能添加前缀索引,并且索引最大只能 1000 字节。而 varchar 即使超过 1000 字节长,也会被截断。
数据类型 | 大小 | 用途 |
---|---|---|
TINYBLOB | 0~255 字节 | 不超过 255 个字符二进制字符串 |
BLOB | 0~65535 字节 | 二进制 |
MEDIUMBLOB | 0-16 777 215 字节 | 二进制形式的中等长度文本数据 |
LONGBLOB | 0-4 294 967 295 字节 | 二进制形式的极大文本数据 |
MySQL 数据类型 | 大小 | 范围 | 含义 |
---|---|---|---|
date | 3 字节 | 1000-01-01/9999-12-31 | 日期 ‘2008-12-2’ |
time | 3 字节 | ‘-838:59:59’/’838:59:59’ | 时间 ‘12:25:36’ |
datetime | 8 字节 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | 日期时间 ‘2008-12-2 22:06:44’ |
timestamp | 4 字节 | 1970-01-01 00:00:00/2037 年某时 | 自动存储记录修改时间 |
每个时间类型有一个有效值范围和一个”零”值,当指定不合法的 MySQL 不能表示的值时使用”零”值。若定义一个字段为 timestamp,这个字段里的时间数据会随其他字段修改的时候自动刷新,所以这个数据类型的字段可以存放这条记录最后被修改的时间。
Datetime 和 timestamp 的区别
区别 | datetime | timestamp |
---|---|---|
空间 | 8 字节 | 4 字节 |
是否允许空值 | 允许 | 允许 |
是否可以自定义值 | 可以 | 不可以 |
支持时间范围 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | 不能早于 1970 或者晚于 2037 年 |
是否与时区相关 | 无关 | 值以 UTC 格式保存,存储 milliseconds,需要存储或者取出时手动转换时区 |
默认值 | 可以在指定 datetime 字段的值的时候使用 now() 变量来自动插入系统的当前时间 | 默认值为 CURRENT_TIMESTAMP() ,当前系统时间 |
结论 | 类型适合用来记录数据的原始创建时间,无论如何更改记录中其他字段, datetime 都不会改变,除非手动改变 | 数据库会自动修改其值,任何修改记录都会被更新,如果需要不设置自动更新,通过设置 DEFAULT CURRENT_TIMESTAMP 可实现。timestamp 类型适合用来记录数据最后修改时间。 |
MySQL 关键字 | 含义 |
---|---|
NULL | 数据列可包含 NULL 值 |
NOT NULL | 数据列不允许包含 NULL 值 |
DEFAULT | 默认值 |
PRIMARY KEY | 主键 |
AUTO_INCREMENT | 自动递增,适用于整数类型 |
UNSIGNED | 无符号 |
CHARACTER SET name | 指定一个字符集 |