北京租房所思所想

毕业面临人生第一次租房,而到现在从学校搬出来也已经有近一个月时间,期间工作有些繁忙,而有一些租房相关的笔记一直躺在WizNote中,直到今天回到家,才想到应该把我的经历记下来,提醒自己以后租房过程中避坑。

信息来源

现在的社会是一个信息为主的社会,我们经常挂在嘴边说的一句话就是信息不对称能够产生利益,现在社会很多的金钱交易都是基于信息的不对称。而租房信息获取的途径也会直接导致以后一年的住房条件和心情。下面就讲讲我参考的几个主要站点和App。

自如

首先就是自如,也是我周围同学使用最多的,大多数周围的同学都通过自如找到了房子,我也看过一些自如的房子,自如有如下特点

  • 自如的房子装修一般还不错,该有的家具设施都有,一般也比较干净
  • 自如需要多交一个月的服务费,和中介多交一个月也无太大差别,另水电自理
  • 双周的保洁还是让公共区域非常的干净,至少我看过的两个房子进门都非常干净也没有异味

缺点:

  • 但是自如的房子一般都比较”贵”,相对来讲
  • 住自如的房子就要抱着和室友相处的”危险”,当然我相信这个世界是美好的,同在一个屋檐下,在同一个城市打拼,本就应该相互尊重相互谦让一下,然而事实就是我身边确实发生了非常不愉快的事情,甚至严重到了报警,所以如果租自如的房子,请做好合租的思想准备。

107间

在同学QQ群中第一次听说,立马下载安装注册之后,需要缴纳100元作为认证费用,只有缴纳费用之后才能看到发布者联系方式。可是缴纳认证费用之后连续联系的两家都已经将房屋出租出去。不及时更新房屋信息,并且在缴纳认证费用的界面没有任何提示。如果30天内没有签约,费用不退。

总结下:

  • 房源相对较少
  • 房屋信息更新不及时,打电话联系客服竟然还是人工和房东进行确认,这都什么时候还用人工确认
  • 签约流程不明确,107间确实过滤了大部分中介,可带来的却是中间确认流程的缺失

强烈建议将该App 拉入黑名单。白白让我交了100块的认证。

链家

房源多,整租单间价格和市场几乎就是市场均价,但是环境看机会。

我爱我家

类似链家。

安居客

同样也是一个找租房的应用,不过看官网和他应用的主要内容似乎是一家经营房产,买卖房屋为主业的公司,租房只是他们其中的一个副业,当然也可以在上面找找周围合适的房屋信息,个人感觉比那个鬼 107 间要靠谱一些,和自如相同也是有“房管”来管理租赁一个范围内的房屋,和自如类似。可以参考,毕竟最后没有在安居客租房因此也不能下结论。

内网

如果你公司内网有租房信息,请一定要利用好,租房那天在外面跑了很多地方,顶着大太阳看了一个有一个房子,却只有在公司内网同事分享的一些房子靠谱一些。个人最后也在同学的推荐下找到了相对比较合适的房子。

58

其他 58,链家 ,豆瓣 之类,只是寥寥看了一下,并没有真正却联系,在看房的时候也了解到不乏有之前的房东在 58 上找到很好的房子,进而想要帮房东更快的租出房子而内网发布信息的。当然如果个人贸然出租请还是注意防骗。

关于合同的一切

这个社会的复杂性都在一直合同中变的扁平和简洁,而着又同样带来了合同的复杂性,合同越复杂带来的后续处理细节就越方便。我们找到房东直租,但房东是一位上了年龄的阿姨,合同也让我们自己带,网上,同学问了一圈,最后在住房和城市建设委员会的网站上找了一份租房合同模板 。但网站上给出的版本还是 08 年的版本,我们又找了一份 14 年的修订版 —- 北京市房屋租赁合同(自行成交版2014)

有了合同模板就好很多,按照上面的填空,填完也能够整个合同流程有一个大概的印象,应该注意的地方合同中大多都有提到,当然除了合同中提到的内容,涉及到金钱的地方要额外的注意。

  • 押一付三,每月金额,押金金额,开始日期结束日期,交房租日期。
  • 家具、空调、洗衣机等等电器的使用情况和损坏状况。
  • 关键的一点,水电费的读数,取暖费谁交。

2017-07-25 租房 , 合租 , 生活 , living

MySQL 中 KEY vs PRIMARY KEY vs UNIQUE KEY vs INDEX 的区别

对于标题中提出的问题,可以拆分来一步步解决。先来区分 MySQL 中的 KEYINDEX ,之后问题就可以简化为 PRIMARY KEY,UNIQUE KEY 和 INDEX 的区别。而这三者也正好是索引的划分,主键索引,唯一索引和普通索引(INDEX)。

INDEX 可以用来提高数据库中查询数据的速度。INDEX 通常加在那些 JOIN, WHERE,和 ORDER BY 子句的列上。创建索引时,需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。

索引也有它的缺点:虽然索引提高了查询速度,却会降低更新表的速度,如对表进行 INSERT、UPDATE 和 DELETE。因为更新表时,MySQL 不仅要保存数据,还要保存一下索引文件。

MySQL 中 KEY 与 INDEX 区别

KEY 通常是 INDEX 同义词,但实际上还是有区别的。如果关键字属性 PRIMARY KEY 在列定义中已给定,则 PRIMARY KEY 也可以只指定为 KEY。这么做的目的是与其它数据库系统兼容。PRIMARY KEY 是一个唯一 KEY,此时,所有的关键字列必须定义为 NOT NULL。如果这些列没有被明确地定义为 NOT NULL,MySQL 应隐含地定义这些列。

KEY 即键值,是关系模型理论中的一部份,比如有主键(PRIMARY KEY),外键(Foreign KEY)等,用于数据完整性检验和唯一性约束等。而 INDEX 则处于实现层面,可以对表的任意列建立索引,那么当建立索引的列处于 SQL 语句中的 Where 条件中时,就可以得到快速的数据定位,从而提高检索效率。至于 UNIQUE INDEX,则只是属于 INDEX 中的一种而已,建立了 UNIQUE INDEX 表示此列数据不可重复。

于是,在设计表的时候,KEY 只是要处于模型层面的,而当需要进行查询优化,则对相关列建立索引即可。

KEY

KEY 是数据库的物理结构,一般理解为,包含两层含义,一是约束,偏重于约束和规范数据库的结构完整性,二是索引,辅助查询。

  • primary key 有两个作用,一是约束作用(constraint),用来规范一个存储主键和唯一性,但同时也在此 key 上建立了一个 index;
  • unique key 也有两个作用,一是约束作用(constraint),规范数据的唯一性,但同时也在这个 key 上建立了一个 index;
  • foreign key 也有两个作用,一是约束作用(constraint),规范数据的引用完整性,但同时也在这个 key 上建立了一个 index;

可见,key 是同时具有 constraint 和 index 的意义。

INDEX

INDEX 是数据库的物理结构,一般翻译为索引,但只辅助查询,会在创建时占用另外的空间。索引分为前缀索引、全文索引等。索引只是索引,不会去约束索引字段的行为。

PRIMARY KEY 和 UNIQUE KEY 的区别

PRIMARY KEYs(主键) 和 UNIQUE KEYs(唯一键约束) 是类似的, PRIMARY KEY 通常是一列,也有可能多列,通常由他来决定一行数据 (row)。 一张表只能有一个 PRIMARY KEY,但可以有很多 UNIQUE KEY。 当给一列设置为 UNIQUE KEY 之后,不能有两行在该列上有相同的数据。 PRIMARY KEY 不允许有 NULL 值,但是 UNIQUE KEY 可以。

修改表 ALTER TABLE table_name ADD PRIMARY KEY(column_name, ...)

总结,相同点:

  • PRIMARY KEY 和 UNIQUE KEY 都是用来保证列上数据的为原型
  • 都可以在一列或者多列上加

差异点:

  • 同一张表 PRIMARY KEY 只能有一个, UNIQUE KEY 可以有多个
  • PRIMARY KEY 不能有空值, UNIQUE KEY 可以有。如果 PRIMARY KEY 的 1 个或多个列为 NULL,在增加 PRIMARY KEY 时,列自动更改为 NOT NULL 。而 UNIQUE KEY 对列没有要求是通过参考索引实施的,如果插入的值均为 NULL,则根据索引的原理,全 NULL 值不被记录在索引上,所以插入全 NULL 值时,可以有重复的,而其他的则不能插入重复值。

    alter table t add constraint uk_t_1 UNIQUE (a,b); insert into t (a ,b ) values (null,1); # 不能重复 insert into t (a ,b ) values (null,null);#可以重复

在 MySQL 中,对于一个 PRIMARY KEY 的列,MySQL 已经自动对其建立了 UNIQUE INDEX,无需重复再在上面建立索引了。

网上关于 PRIMARY KEY 和 UNIQUE INDEX 的一段解释:

Note that “PRIMARY” is called PRIMARY KEY not INDEX.
KEY is something on the logical level, describes your table and database design (i.e. enforces referential integrity …)
INDEX is something on the physical level, helps improve access time for table operations.
Behind every PK there is (usually) UNIQUE INDEX created (automatically).

操作索引 SQL

建立索引会占用磁盘空间的索引文件。

CREATE INDEX IndexName ON mytable(username(length));

如果是 CHAR,VARCHAR 类型,length 可以小于字段实际长度;如果是 BLOB 和 TEXT 类型,必须指定 length。

在创建表时创建索引:

CREATE TABLE mytable(
	ID INT NOT NULL,
	username VARCHAR(15) NOT NULL,
	INDEX [INDEXName] (username(length))
);

删除索引

DROP INDEX [INDEXName] ON mytable;

2017-07-24 mysql , linux , key , index

树莓派中安装MySQL 5.7

最近用到 MySQL 5.7 把所有设备上的 MySQL 版本都升级到了最新,在 Ubuntu/Debian 上升级MySQL 5.7 的内容可以在之前的文章看到。现在记录一下树莓派中升级 MySQL 的步骤。使用到 MySQL 5.7 主要也是因为其支持的新数据类型,之前项目用到了,迁移的时候会遇到问题。在网上寻找解决方案的时候遇到了一个和我遭遇差不多的,需要使用到 MySQL 5.7+ 才支持的 JSON data-type。

在 respberry pi 官方的源中,只有稳定版的 5.5 MySQL,如果要用到最新的版本只能够自己手动编译更新安装,幸而 Debian 提供了编译好的安装包 ,根据以下步骤就能够安装上。

下载依赖

apt-get 安装

sudo apt-get install libaio1 libaio-dev libhtml-template-perl libevent-core-2.0-5

需要安装新版本的 gcc,所以要从 stretch 源中拉取

sudo vim /etc/apt/sources.list
# change jessie to stretch

sudo apt-get update
sudo apt-get install gcc-6 g++-6

# change stretch back to jessie 
sudo apt-get update

然后安装两个依赖

wget http://ftp.debian.org/debian/pool/main/m/mecab/libmecab2_0.996-3.1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/l/lz4/liblz4-1_0.0~r131-2+b1_armhf.deb
sudo dpkg -i libmecab2_0.996-3.1_armhf.deb
sudo dpkg -i liblz4-1_0.0~r131-2+b1_armhf.deb

移除原来的版本

sudo apt-get --purge remove mysql-server # and a lot of like client, common etc

安装最新版

下载 deb 并安装

wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/libmysqlclient-dev_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/libmysqlclient20_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/libmysqld-dev_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-client-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-client-core-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-server-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-5.7/mysql-server-core-5.7_5.7.18-1_armhf.deb
wget http://ftp.debian.org/debian/pool/main/m/mysql-defaults/mysql-common_5.8+1.0.2_all.deb

sudo dpkg -i mysql-common_5.8+1.0.2_all.deb
sudo dpkg -i mysql-client-core-5.7_5.7.18-1_armhf.deb
sudo dpkg -i mysql-client-5.7_5.7.18-1_armhf.deb
sudo dpkg -i mysql-server-core-5.7_5.7.18-1_armhf.deb
sudo dpkg -i mysql-server-5.7_5.7.18-1_armhf.deb

注意这里的版本号,可能之后会更新,去 ftp 中获取最新的版本在这里替换即可。

安装完成之后 sudo reboot

在启动之后

mysql_upgrade -u root -p --force
# 然后输入密码
sudo service mysql restart

安装完毕。

reference


2017-07-23 MySQL , Linux , RespberryPi

每天学习一个命令:使用 rsync 增量同步备份文件

rsync 全名 Remote Sync,是类 UNIX 系统下的数据镜像备份工具。可以方便的实现本地,远程备份,rsync 提供了丰富的选项来控制其行为。rsync 优于其他工具的重要一点就是支持增量备份。

rsync - a fast, versatile, remote (and local) file-copying tool

rsync 是一个功能非常强大的工具,其命令也有很多功能选项,它的特性如下:

  • 可以保持文件原来的权限、时间、所有者、组信息、软硬链接等等
  • 可以从远程或者本地镜像保存整个目录树和文件系统
  • 无须特殊权限 super-user 即可安装使用
  • 快速:要比 scp (Secure Copy) 要快;第一次同步时 rsync 会复制全部内容,但在下一次只传输修改过的文件。rsync 在传输数据的过程中可以实行压缩及解压缩操作,可以使用更少的带宽
  • 安全:可以使用 scp、ssh 等方式来传输文件,当然也可以通过直接的 socket 连接
  • 支持匿名传输,以方便进行网站镜像

rsync 的官方网站:http://rsync.samba.org/,可以从上面得到最新的版本。

rsync 的使用

Rsync 的命令格式可以为以下六种:

# 本地模式
rsync [OPTION...] SRC DEST
# 远程 Push
rsync [OPTION...] SRC [USER@]HOST:DEST
# 远程 Pull
rsync [OPTION...] [USER@]HOST:SRC DEST
# 通过 Rsync daemon Pull
rsync [OPTION...] [USER@]HOST::SRC DEST
rsync [OPTION...] rsync://[USER@]HOST[:PORT]/SRC... [DEST]
# 通过 Rsync daemon Push
rsync [OPTION...] SRC [USER@]HOST::DEST
rsync [OPTION...] SRC... rsync://[USER@]HOST[:PORT]/DEST

上述命令中,SRC 表示源地址,而 DEST 表示目标地址,这二者可以是本地目录,也可以是远程服务器地址。当只有 SRC 地址没有 DEST 时会列出所有的文件列表,而不会执行拷贝。

rsync 有两种方式来连接远程服务器

  • 使用 remote shell 程序,比如 ssh 或者 rsh
  • 或者直接通过 TCP 来连接 daemon

这两种方式的直接区别体现在路径中的冒号 (:) ,当只有一个冒号时使用 remote shell,当有两个冒号时使用 daemon 连接。

rsync 有六种不同的工作模式:

  1. 拷贝本地文件;当 SRC 和 DEST 路径信息都不包含有单个冒号”:”分隔符时就启动这种工作模式。
  2. 使用一个远程 shell 程序(如 rsh、ssh)来实现将本地机器的内容拷贝到远程机器。当 DEST 路径地址包含单个冒号”:”分隔符时启动该模式。
  3. 使用一个远程 shell 程序(如 rsh、ssh)来实现将远程机器的内容拷贝到本地机器。当 SRC 地址路径包含单个冒号”:”分隔符时启动该模式。
  4. 从远程 rsync 服务器中拷贝文件到本地机。当 SRC 路径信息包含”::”分隔符时启动该模式。
  5. 从本地机器拷贝文件到远程 rsync 服务器中。当 DEST 路径信息包含”::”分隔符时启动该模式。
  6. 列远程机的文件列表。这类似于 rsync 传输,不过只要在命令中省略掉本地机信息即可。

这 6 种方式看似复杂,其实只要记住一些常用参数,然后记住一些常用方法就能够将 rsync 利用起来。

可以 man rsync 参考 rsync 文档,了解详细的使用方法,下面解析一些参数的使用

常用的几个参数

-v  verbose 详细输出
-a 	归档模式,递归方式传输文件,并保持连接,权限,用户和组,时间信息
-z  压缩文件传输
-h  human-readable, 输出友好
-u  跳过已经存在的文件,备份更新

rsync 参数的具体解释如下:

-v, --verbose 详细模式输出
-q, --quiet 精简输出模式
-c, --checksum 打开校验开关,强制对文件传输进行校验
-a, --archive 归档模式,表示以递归方式传输文件,并保持所有文件属性,等于 -rlptgoD
-r, --recursive 对子目录以递归模式处理
-R, --relative 使用相对路径信息
-b, --backup 创建备份,也就是对于目的已经存在有同样的文件名时,将老的文件重新命名为~filename。可以使用 --suffix 选项来指定不同的备份文件前缀。
--backup-dir 将备份文件(如~filename) 存放在在目录下。
-suffix=SUFFIX 定义备份文件前缀
-u, --update 仅仅进行更新,也就是跳过所有已经存在于 DST,并且文件时间晚于要备份的文件。(不覆盖更新的文件)
-l, --links 保留软链结
-L, --copy-links 想对待常规文件一样处理软链结
--copy-unsafe-links 仅仅拷贝指向 SRC 路径目录树以外的链结
--safe-links 忽略指向 SRC 路径目录树以外的链结
-H, --hard-links 保留硬链结
-p, --perms 保持文件权限
-o, --owner 保持文件属主信息
-g, --group 保持文件属组信息
-D, --devices 保持设备文件信息
-t, --times 保持文件时间信息
-S, --sparse 对稀疏文件进行特殊处理以节省 DST 的空间
-n, --dry-run 现实哪些文件将被传输
-W, --whole-file 拷贝文件,不进行增量检测
-x, --one-file-system 不要跨越文件系统边界
-B, --block-size=SIZE 检验算法使用的块尺寸,默认是 700 字节
-e, --rsh=COMMAND 指定使用 rsh、ssh 方式进行数据同步
--rsync-path=PATH 指定远程服务器上的 rsync 命令所在路径信息
-C, --cvs-exclude 使用和 CVS 一样的方法自动忽略文件,用来排除那些不希望传输的文件
--existing 仅仅更新那些已经存在于 DST 的文件,而不备份那些新创建的文件
--delete 删除那些 DST 中 SRC 没有的文件
--delete-excluded 同样删除接收端那些被该选项指定排除的文件
--delete-after 传输结束以后再删除
--ignore-errors 及时出现 IO 错误也进行删除
--max-delete=NUM 最多删除 NUM 个文件
--partial 保留那些因故没有完全传输的文件,以是加快随后的再次传输
--force 强制删除目录,即使不为空
--numeric-ids 不将数字的用户和组 ID 匹配为用户名和组名
--timeout=TIME IP 超时时间,单位为秒
-I, --ignore-times 不跳过那些有同样的时间和长度的文件
--size-only 当决定是否要备份文件时,仅仅察看文件大小而不考虑文件时间
--modify-window=NUM 决定文件是否时间相同时使用的时间戳窗口,默认为 0
-T --temp-dir=DIR 在 DIR 中创建临时文件
--compare-dest=DIR 同样比较 DIR 中的文件来决定是否需要备份
-P 等同于 --partial
--progress 显示备份过程
-z, --compress 对备份的文件在传输时进行压缩处理
--exclude=PATTERN 指定排除不需要传输的文件模式
--include=PATTERN 指定不排除而需要传输的文件模式
--exclude-from=FILE 排除 FILE 中指定模式的文件
--include-from=FILE 不排除 FILE 指定模式匹配的文件
--version 打印版本信息

Example

下面举例说明 rsync 的六种不同工作模式:

拷贝本地文件

当 SRC 和 DES 路径信息都不包含有单个冒号 “:” 分隔符时就启动这种工作模式。

同步文件

rsync -ahvz backup.tar.gz  /backups/  # DESC 不存在时自动创建

将备份文件同步到 /backups/ 目录下。

同步目录

rsync -avzh /home/src /backups/files/

/home/src 目录下的文件同步发送到 /backups/files 目录下。记住如果目标地址没有 src 目录,rsync 会自动创建该文件夹。

rsync -avz /home/src/ /backups/files/

SRC 路径末尾的 / 表示不自动创建 DEST 文件夹,在 man rsync 中的解释就是末尾的 / 表示”拷贝当前目录下的文件” ,而不是”拷贝当前的目录”.

远程 shell 拷贝到远程

使用一个远程 shell 程序(如 rsh、ssh) 来实现将本地机器的内容拷贝到远程机器。当 DES 路径地址包含单个冒号”:”分隔符时启动该模式。

rsync -avz /local/path/  user@remoteip:/path/to/files/

将本地 /local/path/ 中的文件同步备份到远程 /path/to/files/ 目录。

远程 shell 拷贝到本地

使用一个远程 shell 程序(如 rsh、ssh) 来实现将远程机器的内容拷贝到本地机器。当 SRC 地址路径包含单个冒号”:”分隔符时启动该模式。

rsync -avz user@remoteip:/home/user/src  ./src

远程 rsync 服务器拷贝到本地

从远程 rsync 服务器中拷贝文件到本地机。当 SRC 路径信息包含”::”分隔符时启动该模式。

rsync -av user@remoteip::www  /databack

拷贝本地文件到远程

从本地机器拷贝文件到远程 rsync 服务器中。当 DES 路径信息包含”::”分隔符时启动该模式。

rsync -av /databack user@remoteip::www

文件列表

列远程机的文件列表。这类似于 rsync 传输,不过只要在命令中省略掉本地机信息即可。

rsync -v rsync://remoteip /www

rsync 使用非标准端口

经常遇见的一种情况就是 ssh 更改了默认 22 端口,这个时候就需要使用 -e 参数。

rsync 有两种常用的认证方式,一种为 rsync-daemon 方式,另外一种则是 ssh。

ssh 一般为首选,但当远端服务器的 ssh 默认端口被修改后,rsync 找不到一个合适的方法来输入对方 ssh 服务端口号。

比如现在向机器 remoteip 传送文件,但此时 remoteip 的 ssh 端口已经不是默认的 22 端口。

键入命令

rsync /local/path user@remoteip:/path/to/files/ # 出现错误

rsync 中的命令 参数 -e, --rsh=COMMAND 指定使用 rsh、ssh 方式进行数据同步。

参数的作用是可以使用户自由选择想要使用的 shell 程序来连接远端服务器,当然也可以设置成使用默认的 ssh 来连接,但是这样我们就可以加入 ssh 的参数了。

现在命令可以这样写了:

rsync -avz -e "ssh -p $port" /local/path/ user@remoteip:/path/to/files/

显示备份进度

可以使用 --progress 选项来显示进度

rsync -avzhe ssh --progress /home/files/ root@remoteip:/path/to/files/

限制备份文件最大值

设置 Max size 备份文件

rsync -avzhe ssh --max-size='2000k' /var/lib/rpm/ root@remoteip:/root/tmprpm

备份结束后自动删除本地文件

rsync --remove-source-files -zvh backup.tar /tmp/backups/

需要注意的是 rsync 使用 --remove-source-files 之后源文件同步之后会被删除,但是源文件所在的文件夹是不会被删除的,可以通过如下命令删除空文件夹:

find . -depth -type d -empty -delete

同步过程中删除远程中已经在本地删除的文件

使用 --delete 选项。

rsync -avh --delete /path/to/local root@remote:/path/to/remote

设置备份带宽

--bwlimit=RATE 选项允许用户指定最大传输速率,RATE 值可以是字符串也可以是数值,如果是字符串,比如 --bwlimit=1.5m 表示每秒最高传输速率 1.5m,如果没有后缀那么单位是 1024 bytes。

rsync --bwlimit=100 -avzhe ssh /var/lib/rpm/ root@remoteip:/root/tmprpm/

参考


2017-07-22 linux , rsync , scp , sync , command

终端复用工具 Tmux 使用介绍

Tmux 是一个很棒的终端复用工具,和 screen 命令类似,但是 Tmux 终极的分屏功能要比 screen 强大很多,当然入门也比 screen 要高很多。如果你长时间在终端进行编程或者操作,或者你陷入无数的 Tab 而无法自拔,那么你应该需要开始了解一些 Tmux 的基本使用。

本文会从如下几个方面对 Tmux 进行介绍:

  1. Tmux 基本使用
  2. Tmux 的基本模块
    • Windows
    • Panes
    • Sessions

Tmux 主要包括以下几个模块:

  • session 会话:一个服务器连接可以包含多个会话
  • window 窗口:一个会话可以包含多个窗口
  • pane 面板:一个窗口可以包含多个面板,如果桌面足够大可以充分利用面板达到非常强大的分屏

安装

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 的前缀快捷键

Tmux 的快捷键前缀(Prefix), 为了使自身的快捷键和其他软件的快捷键互不干扰,Tmux 提供了一个快捷键前缀,和 screen 默认激活控制台的 Ctrl+a 不同,Tmux 默认的是 Ctrl+b。当想要使用 Tmux 的快捷键时,需要先按下快捷键前缀,然后再按下快捷键。Tmux 所使用的快捷键前缀默认是组合键 Ctrl-b(同时按下 Ctrl 键和 b 键)。假如你想通过快捷键显示当前 Tmux 中的 session 列表(对应的快捷键是 s),那么你只需要做以下几步:

  1. 按下组合键 Ctrl-b (Tmux 快捷键前缀)
  2. 放开组合键 Ctrl-b
  3. 然后按下 s
  4. 在显示的列表中选择

使用快捷键之后就可以执行一些相应的指令了。当然如果你不习惯使用 Ctrl+b,也可以在 ~/.tmux.conf 文件中加入以下内容把快捷键变为 Ctrl+a, 或者其他快捷键:

# Set prefix key to Ctrl-a
unbind-key C-b
set-option -g prefix C-a

在下文中就使用 <prefix> 来代替 Tmux 的前缀快捷键了。

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 来查看。

session 相关

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 窗口相关

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 相关

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

TIPS

让你的 Tmux 更加高效,一下内容都可以编辑进 ~/.tmux.conf 用来进一步自定义 Tmux 的行为。 默认的 Tmux 有很多操作方式可能比较 awkward, 只有自己配置让自己熟悉他的行为之后才能让 Tmux 展现出最高效的部分。

自定义 Prefix

在前面也说过, Tmux 默认的 Prefix 是 Ctrl + b,可以按照自己的习惯设定 Prefix 快捷键,很多人将 Caps 和 Ctrl 互换,并且将 Prefix 定义为 Ctrl + a, 我自己使用了以下还是不怎么习惯,所以我将 Prefix 定义成了 Ctrl + \

定义分割 pane 的快捷键

默认情况下 Tmux 使用 " 来垂直分割成上下两部分,使用 % 来水平分割成左右两部分,但是这两个键需要 Shift+',以及很难按到的%,不如直观上使用 |- 来分割 pane。

# split panes using | and -
bind | split-window -h
bind - split-window -v
unbind '"'
unbind %

在 pane 中移动

在 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 中移动。

设置 Colorscheme

在设置完 Tmux 在 Tmux 中使用 Vim 的时候会发现,Vim 的 colorscheme 变的有些奇怪,需要在 .bashrc 或者 .zshrc 中设置:

export TERM="xterm-256color"

复制模式

如果要在 Tmux 中进行复制可以使用 Tmux 的复制模式。 Tmux 中的复制需要使用 <prefix> [ 来进入,具体分为如下几步:

  • <prefix> [ 进入复制模式
  • space 开始复制,移动光标进行选择复制
  • Enter 复制并退出复制模式
  • 在将光标移动到指定位置,按 <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 来将内容拷贝到系统粘贴板。

Tmux 和 iTerm,Guake,Terminator 等终端的区别

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 配置中有我过去多年使用的配置。

reference


2017-07-21 tmux , linux , command , screen , terminal

将 MySQL 升级到 5.7

这些天折腾 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

reference


2017-07-20 MySQL , Linux

Java 查漏补缺之 class

Java 程序在运行时,Java 运行时系统一直对所有的对象进行所谓的运行时类型标识。Class 类封装一个对象和接口运行时的状态,当装载类时,Class 类型的对象自动创建。

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个 Class 对象。

虚拟机为每种类型管理一个独一无二的 Class 对象。也就是说,每个类(型)都有一个 Class 对象。运行程序时,Java 虚拟机 (JVM) 首先检查是否所要加载的类对应的 Class 对象是否已经加载。如果没有加载,JVM 就会根据类名查找.class 文件,并将其 Class 对象载入。Class 对象包含着类的元数据(metadata)包括类的

  • name
  • package
  • methods
  • fields
  • constructors
  • annotations

基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。枚举是 class,而 注解 (annotation) 是 interface。

获取 Class 对象

有如下方法可以获取 Class 对象

  • 直接使用 MyClass.class 来获取,一般当类没有实例时可以直接通过这种方式来获取
  • 如果类已经申明了实例 myClass,则可以通过实例方法 myClass.getClass() 方法来获取
  • 通过 Class 类的静态方法 forName() 来获取,比如 Class.forName("MyClass"),传入的参数必须是接口或者类的名字

Class 类常用方法

getName()

getName() 方法返回 String,Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。

getConstructors()

获取类所有公开的构造函数数组,返回 Constructor<?>[]

newInstance()

通常在反射中为类创建实例。newInstance 调用默认构造函数(无参)来初始化新建对象。

getSuperclass()

返回表示此 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)。

reference

  • Java doc

2017-07-17 java , linux , class , reflection

Docker 入门

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。

安装

Ubuntu

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

Raspberry Pi 树莓派

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

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

列出的信息中,可以看到几个字段

  • REPOSITORY 镜像名字,比如 nginx
  • TAG 镜像的标记, latest 或者特定版本号
  • IMAGE ID 镜像的唯一标示
  • CREATED
  • SIZE

默认 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

其他参考链接


2017-07-16 docker , linux , 容器 , 虚拟化

Google Guava 库学习笔记

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

Optional 存在的意义就是为了替换 null,null 存在的大部分情况用户是无法知道方法想要返回的什么。null 多数情况下并不意味着返回值没有值,一些情况下可能表示 error,甚至也有人用 null 来表示成功,或者表示成功了但是没有返回值。因此 Optional 存在的意义并不是可读性,而是强迫使用他的人思考返回值应该是什么。

举个例子,当调用别人的 other.method(a, b) 方法时,可能经常忘记 method 返回值可能是 null,而当 method 返回值为 Optional 时,调用者几乎不可能忘记 null 的存在,因为调用者需要自己 unwrap 来获取结果。基于这个原因,Guava 通常建议将 Optional 作为返回值。1

集合类

Guava 的集合类是对 JDK 集合类的扩展。

Immutable Collections

不可变对象有很多好处:

  • 不受信任的库可以安全使用
  • 线程安全
  • Doesn’t need to support mutation, and can make time and space savings with that assumption. All immutable collection implementations are more memory-efficient than their mutable siblings. (analysis)
  • 可以当做常量使用

Guava 提供了简单,易于使用的不可变 Collection 类型的版本, JDK 提供了 Collections.unmodifiableXXX 方法,但有些缺陷

  • unwieldy and verbose; unpleasant to use everywhere you want to make defensive copies
  • unsafe: the returned collections are only truly immutable if nobody holds a reference to the original collection
  • inefficient: the data structures still have all the overhead of mutable collections, including concurrent modification checks, extra space in hash tables, etc.

创建 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

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 的内存消耗在不同元素上是线性的

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

Multimap

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

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);

Cache

通过例子一目了然

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);
             }
           });

Functional idioms

Files 文件操作

Copy

复制文件,复制文件要求源地址和目的地址不一致。如果目的地址文件存在则会被直接覆盖

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) {}

Move/Rename

移动或者重命名文件

File original = new File("src/main/resources/copy.txt");
File newFile = new File("src/main/resources/newFile.txt");
Files.move(original, newFile); // 移动或重命名文件,类似 Unix 中的 mv

Read

将文件读取为字符串列表

List<String> readLines = Files.readLines(file, Charsets.UTF_8);

Write

写文件,或者附加内容到文件

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); // 重写文件

File Hash

为文件生成 Hash 值

File file = new File("src/main/resources/sampleTextFileOne.txt");
HashCode hashCode = Files.hash(file, Hashing.md5());
System.out.println(hashCode);

reference


2017-07-13 google , guava , java , java-lib , lib , utils

Spring MVC 处理文件上传

Spring MVC 处理文件上传

添加Maven依赖

<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

reference


2017-07-11 SpringMVC , Java , Web , File

电子书

本站提供服务

最近文章

  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。
  • Go 语言编写的网络穿透工具 chisel chisel 是一个在 HTTP 协议上的 TCP/UDP 隧道,使用 Go 语言编写,10.9 K 星星。