Redis集群备注

介绍

  1. Redis集群是一个可以在多个Redis节点之间进行数据共享的设施(installation)。
  2. Redis集群不支持那些需要同时处理多个键的Redis命令,因为执行这些命令需要在多个Redis节点之间移动数据,并且在高负载的情况下,这些命令将降低Redis集群的性能,并导致不可预测的行为。
  3. Redis集群通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部份分节点失效或者无法进行通讯,集群也可以继续处理命令请求。

Redis集群提供了以下两个好处:
1、将数据自动切分(split)到多个节点的能力。
2、当集群中的一部份节点失效或者无法进行通讯时,仍然可以继续处理命令请求的能力。

数据节点分配

Redis 集群没有并使用传统的一致性哈希来分配数据,而是采用另外一种叫做哈希槽 (hash slot)的方式来分配的。redis cluster 默认分配了 16384 个slot,当我们set一个key 时,会用CRC16算法来取模得到所属的slot,然后将这个key 分到哈希槽区间的节点上,具体算法就是:CRC16(key) % 16384。集群中的每个节点负责处理一部分哈希槽。

这种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。
当添加集群节点时,需要将已有节点的哈希槽移动到新的节点上进行处理。
当删除集群节点时,需要先将节点上已分配的哈希槽移动到其它的节点上再进行删除。

客户端存储分配

当客户端向集群节点中任一节点发出存储或读取请求时,redis节点先根据KeyHash出来的值判断是否属于当前集群节点能进行处理,如果不能处理则会将能完成这一请求的Redis节点信息返回给客户端。客户端将再次向能处理请求的Redis节点发出请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ redis-cli -c -p 7000
redis 127.0.0.1:7000> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:7002
OK
redis 127.0.0.1:7002> set hello world
-> Redirected to slot [866] located at 127.0.0.1:7000
OK
redis 127.0.0.1:7000> get foo
-> Redirected to slot [12182] located at 127.0.0.1:7002
"bar"
redis 127.0.0.1:7000> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"

集群节点故障

采用数据分片的处理集群数据分配就存在无法避免的问题,当其中一个集群节点挂掉时,分配到此节点上哈希槽的数据将无法处理。
为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作,
Redis 集群对每个集群节点都提供了了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。


观点仅代表自己,期待你的留言。

Redis实现分布式锁和分布式队列

分布式锁

通过Redis做分布式系统的共享内存实现方案中,可以实现分布式锁的功能。实现方法就是用 SET NX 命令设置一个设定了存活周期 TTL 的 Key 来获取锁,通过删除 Key 来释放锁,通过存活周期来保证避免死锁。
如:SET resource_name my_random_value NX PX 30000

注:


SET key value [EX seconds] [PX milliseconds] [NX|XX]
从2.6.12版本开始,redis为SET命令增加了一系列选项:

  • EX seconds – Set the specified expire time, in seconds.
  • PX milliseconds – Set the specified expire time, in milliseconds.
  • NX – Only set the key if it does not already exist.
  • XX – Only set the key if it already exist.
  • EX seconds – 设置键key的过期时间,单位时秒
  • PX milliseconds – 设置键key的过期时间,单位时毫秒
  • NX – 只有键key不存在的时候才会设置key的值
  • XX – 只有键key存在的时候才会设置key的值

但,以上简单方案存在一个单点故障的风险。在master/slave环境下,当:

  1. 客户端A获取到锁.
  2. master节点在将 key 复制到 slave 节点之前崩溃了
  3. 此时,slave节点被提升为master节点.
  4. 客户端 B 从新的 master 节点获得了锁(而这个锁实际上已经由客户端 A 所持有),导致了系统中有两个客户端在同一时间段内持有同一个互斥锁,破坏了互斥锁的安全性。

__解决方法:__需要解决以上问题,需要在进行锁设值时将惟一标识系统的ID做为value一同存储到Redis中,在完成所有的操作后,进行解锁时取出Redis内存储的value进行比对,
如果锁key对应的value还是当前系统的ID,那么表示当前锁目前只被当前系统所持有,反之,则表示锁被其它的系统抢占,那么需要回滚所有的操作。

如:

客户端A(ID为SYSA) Redis 客户端B(ID为SYSB)
获取到锁(SET dist_lock SYSA NX PX 30000)
进行其它的业务操作。。。 master发生故障,slave被提升为NewMaster(未将master中dist_lock复制到NewMaster)
获取到锁(SET dist_lock SYSB NX PX 30000)
解锁 (get dist_lock,对value进行判定,为SYSA则执行解锁,否则回滚业务)
解锁 (get dist_lock,对value进行判定,为SYSB则执行解锁,否则回滚业务)

分布式队列

Redis实现分布式队列主要实现是通过有序集合实现,通过ZADD向集合内添加元素,同时添加多个元素时为事务处理(不存在部分成功部分失败的情况)

注:


ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
从3.0.2版本开始,增加以下选项:

  • XX: Only update elements that already exist. Never add elements.
  • NX: Don’t update already existing elements. Always add new elements.
  • CH: Modify the return value from the number of new elements added, to the total number of elements changed (CH is an abbreviation of changed). Changed elements are new elements added and elements already existing for which the score was updated. So elements specified in the command line having the same score as they had in the past are not counted. Note: normally the return value of ZADD only counts the number of new elements added.
  • INCR: When this option is specified ZADD acts like ZINCRBY. Only one score-element pair can be specified in this mode.

另外的选择

_Java库:_其实也在使用redisson来实现分布式锁。它封装了针对Redis各种操作的分布式实现。

其它语言可参照官网文章: http://redis.io/topics/distlock?cm_mc_uid=77610277652214581184982&cm_mc_sid_50200000=1464593765


观点仅代表自己,期待你的留言。

关于MariaDB事务阻塞等待及锁的实验笔记

实验环境

Server Version: 10.0.24-MariaDB
SET GLOBAL tx_isolation=’REPEATABLE-READ’;
SET SESSION tx_isolation=’REPEATABLE-READ’;
SET @@autocommit=0;

SHOW VARIABLES like ‘autocommit’;
SELECT @@GLOBAL.tx_isolation, @@tx_isolation;

实验结论

幻读的概念是另一事务先完成的情况下判断

事务A 事务B
start TRANSACTION;
select * from t1;(查询出2条数据)
start TRANSACTION;
insert into t1 values(null,’v222’);
select * from t1;(查询出2条数据)
commit;(这一步很关键,它标识了事务B的完成)
select * from t1;(查询出3条数据)

InnoDB存储引擎在REPEATABLE-READ的隔离级别下解决了幻读情况,所以会造成以上的情况。

并发事务修改同一行数据时,后执行更新的事务会__阻塞等待__先执行更新事务先结束(rollback或commit)或者超时

事务A 事务B
start TRANSACTION; start TRANSACTION;
update t1 SET da=’v1111’ where id=1;
update t1 SET da=’v1111’ where id=1;(此时会等待事务A执行完成 OR 等待超时)

并发事务更新时只要被更新的数据存在交集,那么就会存在__阻塞等待__另一事务完成的现象

事务A 事务B
start TRANSACTION;
select * from t1;(查询出2条数据)
start TRANSACTION;
insert into t1 values(null,’v222’);
update t1 set da=’updated’(此时会等事务B的完成)
commit;

并发事务更新时只要存在全表扫描(不管数据存在不存在交集),那么就会存在__阻塞等待__另一事务完成的现象

事务A 事务B
start TRANSACTION;
select * from t1;(查询出2条数据)
start TRANSACTION;
update t1 SET da=’v1113333331’ where id=1
update t1 set da=’updated’ where da like ‘%0000%’(此时会等事务B的完成)
commit;

观点仅代表自己,期待你的留言。

数据库事务隔离级别与并发事务控制(悲观锁与乐观锁)

事务特性

事务具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

  1. 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  2. 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  3. 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  4. 持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

事务的隔离级别

由于事务的隔离性允许多个事务同时处理同一数据,所以,在多个事务并发操作的过程中,如果控制不好隔离级别,就有可能产生_脏读_、_不可重复读_或者_幻读_等现象。因此在操作数据的过程中需要合理利用数据库锁机制或者多版本并发控制机制获取更高的隔离等级,但是,随着数据库隔离级别的提高,数据的并发能力也会有所下降。所以,如何在并发性和隔离性之间做一个很好的权衡就成了一个至关重要的问题。

ANSI/ISO SQL定义的标准隔离级别从高到低依次为:可串行化(Serializable)、可重复读(Repeatable reads)、提交读(Read committed)、未提交读(Read uncommitted)。

隔离级别 脏读(Dirty Read) 不可重复读(NonRepeatable Read) 幻读(Phantom Read)
未提交读(Read uncommitted) 可能 可能 可能
提交读(Read committed) 不可能 可能 可能
可重复读(Repeatable Read) 不可能 不可能 可能
可串行化(Serializable) 不可能 不可能 不可能

===========================================================================================

  • 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。
  • 提交读(Read Committed):被读取的数据可以被其他事务修改。这样就可能导致不可重复读。也就是说,事务的读取数据的时候获取读锁,但是读完之后立即释放(不需要等到事务结束),而写锁则是事务提交之后才释放。释放读锁之后,就可能被其他事物修改数据。Oracle等多数数据库默认都是该级别 (不重复读)
  • 可重复读(Repeated Read):所有被Select获取的数据都不能被修改,这样就可以避免一个事务前后读取数据不一致的情况。但是却没有办法控制幻读,因为这个时候其他事务不能更改所选的数据,但是可以增加数据,因为前一个事务没有范围锁。Mysql的InnoDB引擎默认隔离级别。
  • 可串行化(Serializable):所有事务都一个接一个地串行执行,这样可以避免幻读(phantom reads),每次读都需要获得表级共享锁,读写相互都会阻塞。

数据库并发控制

虽然将事务串形化可以保证数据在多事务并发处理下不存在数据不一致的问题,但串行执行使得数据库的处理性能大幅度地下降,常常是你接受不了的。所以,一般来说,数据库的隔离级别都会设置为read committed(只能读取其他事务已提交的数据),然后由应用程序使用__乐观锁/悲观锁__来弥补数据不一致的问题。

乐观锁

虽然名字中带“锁”,但是乐观锁并不锁住任何东西,而是在提交事务时检查自己上次读取这条记录后,是否有其他事务修改了这条记录,如果没有则提交,如果被修改了则回滚。如果并发的可能性并不大,那么锁定策略带来的性能消耗是非常小的。

常见实现方式:在数据表增加version字段,每次事务开始时将取出version字段值,而后在更新数据的同时version增加1(如:update xxx set data=#{data},version=version+1 where version=#{version}),如没有数据被更新,那么说明数据由其它的事务进行了更新,此时就可以判断当前事务所操作的历史快照数据。

悲观锁

和乐观锁相比,悲观锁则是一把真正的锁了,它通过SQL语句“select for update”锁住select出的那批数据,这时如果其他事务来更新这批数据时会等待。

总的来说,悲观锁相对乐观锁更安全一些,但是开销也更大,甚至可能出现数据库死锁的情况,建议只在乐观锁无法工作时才使用。


观点仅代表自己,期待你的留言。

Maven应用远程部署

简介

在程序开发的过程中对研发环境服务器应用部署将会非常的频繁,而通过tomcat-maven-plugin的deploy很容易实现web应用的远程发布。
而针对jar的发布一般会搭建maven私服,同样在研发阶段也会发布的非常频繁通过maven的deploy也非常容易实现maven私服的jar提交。

远程发布Web应用

以tomcat-maven-plugin为例,具体配置如下:
pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/</path>
<port>8080</port>
<uriEncoding>UTF-8</uriEncoding>
<server>DevServer</server>
<url>http://deploy.dev.com/manager</url>
</configuration>
</plugin>
</plugins>
</build>
</project>

说明:
server: 为settings.xml中配置的server节点ID,用于上传鉴权。
url:发布到的服务器的tomcat/manager工程访问地址。

settings.xml

1
2
3
4
5
6
7
8
9
10
11
<?xml version='1.0' encoding='utf-8'?>
<settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd" xmlns="http://maven.apache.org/SETTINGS/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<servers>
<server>
<id>DevServer</id>
<username>abc</username>
<password>xxx</password>
</server>
</servers>
</settings>

tomcat-users.xml

1
2
3
4
5
6
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="manager-gui" />
<role rolename="manager-script" />
<user username="abc" password="xxx" roles="manager-gui,manager-script"/>
</tomcat-users>

–[更新] 发现更简便配置,详细如下:—
将maven的pom.xml中将server节点直接替换成username与password节点,这样就不需要在setting.xml中进行配置了。
pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<path>/</path>
<port>8080</port>
<uriEncoding>UTF-8</uriEncoding>
<username>abc</username>
<password>xxx</password>
<url>http://deploy.dev.com/manager</url>
</configuration>
</plugin>
</plugins>
</build>
</project>

远程发布依赖库jar

pom.xml

1
2
3
4
5
6
7
8
9
10
11
<?xml version='1.0' encoding='utf-8'?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<distributionManagement>
<snapshotRepository>
<id>snapshots</id>
<name>libs-snapshot</name>
<url>http://mvn.code.com/artifactory/libs-snapshot-local</url>
</snapshotRepository>
</distributionManagement>
</project>

说明:
id: 表示配置的用户名和密码,这个ID在settings.xml里配置
url: 为私服上传地址。

settings.xml

1
2
3
4
5
6
7
8
9
10
11
<?xml version='1.0' encoding='utf-8'?>
<settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd" xmlns="http://maven.apache.org/SETTINGS/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<servers>
<server>
<id>snapshots</id>
<username>abc</username>
<password>xxx</password>
</server>
</servers>
</settings>

配置完成之后执行maven deploy就OK啦。


观点仅代表自己,期待你的留言。

Maven应用发布之不同环境不同配置的实现

简介

一个应用程序从研发到发布一般需要经过三套环境(研发环境、测试环境、生产环境)甚至更多。针对这同的运行环境应用程序也需要一些不同的参数配置值(如果日志输出级别、数据库连接池配置等)。
Maven的Profile配置很好的解决这一问题。在pom.xml中配置多套参数值,在程序打包__编译时期__通过部署环境对配置参数进行替换来完成。
__注意:__当配置项找不到对应的配置值时会保持原样。

pom.xml多套参数配置

以不同部署环境配置日志输出级别为例。

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<properties>
<catalina.base>/app/servers/logs</catalina.base>
</properties>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<catalina.log.priority>debug</catalina.log.priority>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<catalina.log.priority>warn</catalina.log.priority>
</properties>
</profile>
<profile>
<id>pro</id>
<properties>
<catalina.log.priority>error</catalina.log.priority>
</properties>
</profile>
</profiles>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

说明:
以上配置表示在编译时期对src/main/resources目录下配置文件中的${catalina.log.priority}${catalina.base}进行替换。而catalina.base参数值不区分部署环境。
编译开发环境的部署程序包命令如下:

1
Jianjun:~ Jianjun$ mvn package –P dev

编译测试环境的部署程序包命令如下:

1
Jianjun:~ Jianjun$ mvn package –P test

编译生产环境的部署程序包命令如下:

1
Jianjun:~ Jianjun$ mvn package –P pro

如果不指-P参数,默认会使用dev的配置,由于dev节点配置了__activeByDefault__

参数配置项太多

当配置项太多之后我们可以通过外置properties文件来进行配置,而properties文件需要与pom.xml在同一路径下。
properties配置如下:
product-deploy-config-dev.properties

1
catalina.log.priority=debug

product-deploy-config-test.properties

1
catalina.log.priority=warn

product-deploy-config-pro.properties

1
catalina.log.priority=error

pom.xml配置如下:

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<properties>
<catalina.base>/app/servers/logs</catalina.base>
</properties>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<filters>
<filter>product-deploy-config-dev.properties</filter>
</filters>
</build>
</profile>
<profile>
<id>test</id>
<build>
<filters>
<filter>product-deploy-config-test.properties</filter>
</filters>
</build>
</profile>
<profile>
<id>pro</id>
<build>
<filters>
<filter>product-deploy-config-pro.properties</filter>
</filters>
</build>
</profile>
</profiles>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>

观点仅代表自己,期待你的留言。

使用端口转发解决80端口使用问题

简介

当我用在使用linux非root用户进行应用发布时,如遇到应用程序需要占用80端口时总会由于权限不足遇到java.net.BindException: Permission denied:80的错误报出。
Linux为保证系统安全,限制了非root用户对1024以下的端口进行占用。
那么,本文将通过端口转发的方式解决80端口在Linux和MacOSX系统上占用的问题。

linux非Root用户使用80端口

linux操作系统的可以通过iptables来进行端口转发,将流向80端口的数据内部转发到大于1024的端口上。那么非root用户就可以通过占用大于1024的端口进行程序应用的功能处理。
以80转发到8080端口为例:

1
2
3
4
5
6
[root@localhost ~]# sudo iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 8080
[root@localhost ~]# sudo iptables -t nat -A OUTPUT -d localhost -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 8080
[root@localhost ~]# iptables -t filter -F
[root@localhost ~]# iptables-save
[root@localhost ~]# service iptables restart
[root@localhost ~]# chkconfig iptables on

iptables -t filter -F : 为清除所有的过滤规则。
chkconfig iptables on : 设置iptables开机启动。

iptables存储文件:/etc/sysconfig/iptables

MacOSX使用80端口

MacOS操作系统下则使用pf来进行端口转发。
pf启动时会自动装载/etc/pf.conf文件,因此,我们可以通过修改这个文件进行80端口数据的转发。
修改后的文件内容如下:

1
2
3
4
5
6
7
scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
rdr on lo0 inet proto tcp from any to 127.0.0.1 port 80 -> 127.0.0.1 port 8080
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"

使用 pfctl使用重新加载pf.conf配置,并启动pf

1
2
Jianjun:~ Jianjun$ sudo pfctl -f /etc/pf.conf
Jianjun:~ Jianjun$ sudo pfctl -e

另,关闭pf的命令为:sudo pfctl -d


观点仅代表自己,期待你的留言。

docker-dockerfile

基本结构

Dockerfile 由一行行字母大写的指令语句组成,并且支持以 # 开头的注释行。
一般的,Dockerfile 分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。

指令

指令的一般格式为 INSTRUCTION arguments,指令包括 FROM、MAINTAINER、RUN 等。

FROM

格式为 FROM <image>FROM <image>:<tag>
第一条指令必须为 FROM 指令。并且,如果在同一个Dockerfile中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)。

MAINTAINER

格式为 MAINTAINER <name>,指定维护者信息。

RUN

格式为 RUN <command>RUN ["executable", "param1", "param2"]
前者将在 shell 终端中运行命令,即 /bin/sh -c;后者则使用 exec 执行。指定使用其它终端可以通过第二种方式实现,例如 RUN ["/bin/bash", "-c", "echo hello"]
每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用 \ 来换行。

CMD

支持三种格式

  • CMD [“executable”,”param1”,”param2”] 使用 exec 执行,推荐方式;
  • CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
  • CMD [“param1”,”param2”] 提供给 ENTRYPOINT 的默认参数;
    指定启动容器时执行的命令,__每个 Dockerfile 只能有一条 CMD 命令__。如果指定了多条命令,只有最后一条会被执行。
    如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD 指定的命令。

    EXPOSE

格式为 EXPOSE <port> [<port>...]
告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 主机会自动分配一个端口转发到指定的端口。

ENV

格式为 ENV <key> <value>。 指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。
例如

1
2
3
4
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ADD

格式为 ADD <src> <dest>
该命令将复制指定的 <src> 到容器中的 <dest>。 其中 <src> 可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。

COPY

格式为 COPY <src> <dest>
复制本地主机的 <src>(为 Dockerfile 所在目录的相对路径)到容器中的 <dest>
当使用本地目录为源目录时,推荐使用 COPY

ENTRYPOINT

两种格式:

  • ENTRYPOINT [“executable”, “param1”, “param2”]
  • ENTRYPOINT command param1 param2(shell中执行)。
    配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
    每个 Dockerfile 中只能有一个 ENTRYPOINT,__当指定多个时,只有最后一个起效__。

    VOLUME

格式为 VOLUME ["/data"]
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。

USER

格式为 USER daemon
指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:RUN groupadd -r postgres && useradd -r -g postgres postgres。要临时获取管理员权限可以使用 gosu,而不推荐 sudo

WORKDIR

格式为 WORKDIR /path/to/workdir
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。
例如

1
2
3
4
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

则最终路径为 /a/b/c

ONBUILD

格式为 ONBUILD [INSTRUCTION]
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,Dockerfile 使用如下的内容创建了镜像 image-A

1
2
3
4
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]

如果基于 image-A 创建新的镜像时,新的Dockerfile中使用 FROM image-A指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。

1
2
3
4
5
FROM image-A

#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src

使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild

创建镜像

编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像。
基本的格式为 docker build [选项] 路径,该命令将读取指定路径下(包括子目录)的 Dockerfile,并将该路径下所有内容发送给 Docker 服务端,由服务端来创建镜像。因此一般建议放置 Dockerfile 的目录为空目录。也可以通过 .dockerignore文件(每一行添加一条匹配模式)来让 Docker 忽略路径下的目录和文件。
要指定镜像的标签信息,可以通过 -t 选项,
例如:

1
$ sudo docker build -t myrepo/myapp /tmp/test1/

https://yeasy.gitbooks.io/docker_practice/content/dockerfile/index.html

观点仅代表自己,期待你的留言。

docker数据卷与网络配置

数据卷

Docker 挂载数据卷的默认权限是读写,用户也可以通过 :ro 指定为只读。
示例:

1
$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py

创建数据卷

在用 docker run 命令的时候,使用 -v 标记来创建一个数据卷并挂载到容器里。在一次 run 中多次使用可以挂载多个数据卷。
示例:

1
$ sudo docker run -d -P --name web -v /webapp training/webapp python app.py

挂载一个主机目录作为数据卷

加载主机的 /src/webapp 目录到容器的 /opt/webapp 目录.
示例:

1
$ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py

注意:本地目录的路径必须是绝对路径,如果目录不存在 Docker 会自动为你创建它。

挂载一个本地主机文件作为数据卷

-v 标记也可以从主机挂载单个文件到容器中
示例:

1
$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash

删除数据卷

数据卷是被设计用来持久化数据的,它的生命周期独立于容器,Docker不会在容器被删除后自动删除数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v这个命令。无主的数据卷可能会占据很多空间,要清理会很麻烦。

数据卷容器

如果你有一些持续更新的数据需要在容器之间共享,最好创建数据卷容器。
数据卷容器,其实就是一个正常的容器,专门用来提供数据卷供其它容器挂载的。
首先,创建一个名为 dbdata 的数据卷容器:

1
$ sudo docker run -d -v /dbdatas --name dbdata training/postgres echo Data-only container for postgres

然后,在其他容器中使用 --volumes-from 来挂载 dbdata 容器中的数据卷。

1
2
$ sudo docker run -d --volumes-from dbdata --name db1 training/postgres
$ sudo docker run -d --volumes-from dbdata --name db2 training/postgres

可以使用超过一个的 –volumes-from 参数来指定从多个容器挂载不同的数据卷。 也可以从其他已经挂载了数据卷的容器来级联挂载数据卷。

1
$ sudo docker run -d --name db3 --volumes-from db1 training/postgres

*注意:使用 --volumes-from 参数所挂载数据卷的容器自己并不需要保持在运行状态。
如果删除了挂载的容器(包括 dbdata、db1 和 db2),数据卷并不会被自动删除。如果要删除一个数据卷,必须在删除最后一个还挂载着它的容器时使用 docker rm -v 命令来指定同时删除关联的容器。

利用数据卷容器来备份、恢复、迁移数据卷

备份

首先使用 –volumes-from 标记来创建一个加载 dbdata 容器卷的容器,并从主机挂载当前目录到容器的 /backup 目录。
命令如下:

1
$ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

容器启动后,使用了 tar 命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar 的文件。

恢复

如果要恢复数据到一个容器,首先创建一个带有空数据卷的容器 dbdata2。

1
$ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash

然后创建另一个容器,挂载 dbdata2 容器卷中的数据卷,并使用 untar 解压备份文件到挂载的容器卷中。

1
$ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar

为了查看/验证恢复的数据,可以再启动一个容器挂载同样的容器卷来查看

1
$ sudo docker run --volumes-from dbdata2 busybox /bin/ls /dbdata

网络配置

外部访问容器

docker容器一般使用5000做为服务端口(类似于80端口的概念)。

  • docker支持在docker run时增加-P来将主机随机端口(49000~49900)映射到容器内的5000。可通过docker ps查看到对应的占用端口。
  • docker支持在docker run时增加-p来将指定的主机端口映射到容器内的指定端口。
    支持以下格式:
    • ip:hostPort:containerPort: 将主机指定IP下的指定端口绑定到容器内的指定端口。
    • ip::containerPort:绑定主机指定IP下的任意端口到容器的指定端口,本地主机会自动分配一个端口。还可以使用 udp 标记来指定 udp 端口。如$ sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py
    • hostPort:containerPort:指定主机所有IP下的指定端口到容器内的指定端口。

使用 docker port 来查看当前映射的端口配置,
注意:

  • 容器有自己的内部网络和 ip 地址(使用 docker inspect 可以获取所有的变量,Docker 还可以有一个可变的网络配置。)
  • -p 参数可以多次使用。如:$ sudo docker run -d -p 5000:5000 -p 3000:80 training/webapp python app.py

    容器互联

    连接系统依据容器的名称来执行。因此,首先需要通过--name自定义一个好记的容器命名。使用 docker ps 来验证设定的命名。

使用 --link 参数可以让容器之间安全的进行交互。参数的格式为 --link name:alias,其中 name 是要链接的容器的名称,alias 是这个连接的别名。
将web容器与db容器连接,且连接被命名为db, 启动命令如下:

1
$ sudo docker run -d -P --name web --link db:db training/webapp python app.py

实现原理:
Docker 通过 2 种方式为容器公开连接信息:

  • 环境变量。 通过linux命令env可以看到,环境变量通过增加alias做为前缀标识来区别容器的环境变量与连接。
  • 更新 /etc/hosts 文件。在hosts文件中增加alias条目的映射ip。

观点仅代表自己,期待你的留言。

docker常用命令总结

docker pull

说明:获取网络镜像到本地

1
2
3
4
5
6
7
8
9
 [root@localhost ~]# docker pull --help

Usage: docker pull [OPTIONS] NAME[:TAG|@DIGEST]

Pull an image or a repository from a registry

-a, --all-tags Download all tagged images in the repository
--disable-content-trust=true Skip image verification
--help Print usage

当不指定TAG时,默认为:latest
示例:

1
2
[root@localhost ~]# sudo docker pull ubuntu:12.04
[root@localhost ~]# docker pull registry.hub.docker.com/ubuntu:12.04

docker images

说明:显示本地已有的镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost ~]# docker images --help

Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]

List images

-a, --all Show all images (default hides intermediate images)
--digests Show digests
-f, --filter=[] Filter output based on conditions provided
--format Pretty-print images using a Go template
--help Print usage
--no-trunc Don't truncate output
-q, --quiet Only show numeric IDs

镜像的 ID 唯一标识了镜像。

docker run

说明:使用已下载到本地的镜像启动一个新容器

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
[root@localhost ~]# docker run --help

Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

Run a command in a new container

-a, --attach=[] Attach to STDIN, STDOUT or STDERR
--add-host=[] Add a custom host-to-IP mapping (host:ip)
--blkio-weight Block IO (relative weight), between 10 and 1000
--blkio-weight-device=[] Block IO weight (relative device weight)
--cpu-shares CPU shares (relative weight)
--cap-add=[] Add Linux capabilities
--cap-drop=[] Drop Linux capabilities
--cgroup-parent Optional parent cgroup for the container
--cidfile Write the container ID to the file
--cpu-period Limit CPU CFS (Completely Fair Scheduler) period
--cpu-quota Limit CPU CFS (Completely Fair Scheduler) quota
--cpuset-cpus CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems MEMs in which to allow execution (0-3, 0,1)
-d, --detach Run container in background and print container ID
--detach-keys Override the key sequence for detaching a container
--device=[] Add a host device to the container
--device-read-bps=[] Limit read rate (bytes per second) from a device
--device-read-iops=[] Limit read rate (IO per second) from a device
--device-write-bps=[] Limit write rate (bytes per second) to a device
--device-write-iops=[] Limit write rate (IO per second) to a device
--disable-content-trust=true Skip image verification
--dns=[] Set custom DNS servers
--dns-opt=[] Set DNS options
--dns-search=[] Set custom DNS search domains
-e, --env=[] Set environment variables
--entrypoint Overwrite the default ENTRYPOINT of the image
--env-file=[] Read in a file of environment variables
--expose=[] Expose a port or a range of ports
--group-add=[] Add additional groups to join
-h, --hostname Container host name
--help Print usage
-i, --interactive Keep STDIN open even if not attached
--ip Container IPv4 address (e.g. 172.30.100.104)
--ip6 Container IPv6 address (e.g. 2001:db8::33)
--ipc IPC namespace to use
--isolation Container isolation level
--kernel-memory Kernel memory limit
-l, --label=[] Set meta data on a container
--label-file=[] Read in a line delimited file of labels
--link=[] Add link to another container
--log-driver Logging driver for container
--log-opt=[] Log driver options
-m, --memory Memory limit
--mac-address Container MAC address (e.g. 92:d0:c6:0a:29:33)
--memory-reservation Memory soft limit
--memory-swap Swap limit equal to memory plus swap: '-1' to enable unlimited swap
--memory-swappiness=-1 Tune container memory swappiness (0 to 100)
--name Assign a name to the container
--net=default Connect a container to a network
--net-alias=[] Add network-scoped alias for the container
--oom-kill-disable Disable OOM Killer
--oom-score-adj Tune host's OOM preferences (-1000 to 1000)
-P, --publish-all Publish all exposed ports to random ports
-p, --publish=[] Publish a container's port(s) to the host
--pid PID namespace to use
--privileged Give extended privileges to this container
--read-only Mount the container's root filesystem as read only
--restart=no Restart policy to apply when a container exits
--rm Automatically remove the container when it exits
--security-opt=[] Security Options
--shm-size Size of /dev/shm, default value is 64MB
--sig-proxy=true Proxy received signals to the process
--stop-signal=SIGTERM Signal to stop a container, SIGTERM by default
-t, --tty Allocate a pseudo-TTY
--tmpfs=[] Mount a tmpfs directory
-u, --user Username or UID (format: <name|uid>[:<group|gid>])
--ulimit=[] Ulimit options
--uts UTS namespace to use
-v, --volume=[] Bind mount a volume
--volume-driver Optional volume driver for the container
--volumes-from=[] Mount volumes from the specified container(s)
-w, --workdir Working directory inside the container

IMAGE可以为镜像名或者镜像ID。
示例:

1
2
sudo docker run -t -i ouruser/sinatra:v2 /bin/bash
sudo docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" #守护态运行

在执行 docker run 的时候如果添加 –rm 标记,则容器在终止后会立刻删除。注意,–rm 和 -d 参数不能同时使用。

docker logs

说明:获取容器输出的信息,适用于守护态运行docker容器。

1
2
3
4
5
6
7
8
9
10
11
[root@localhost ~]# docker logs --help

Usage: docker logs [OPTIONS] CONTAINER

Fetch the logs of a container

-f, --follow Follow log output
--help Print usage
--since Show logs since timestamp
-t, --timestamps Show timestamps
--tail=all Number of lines to show from the end of the logs

docker stop

说明:停止一个正在启动的容器。

1
2
3
4
5
6
7
8
9
[root@localhost ~]# docker stop --help

Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]

Stop a running container.
Sending SIGTERM and then SIGKILL after a grace period

--help Print usage
-t, --time=10 Seconds to wait for stop before killing it

docker start

说明:启动一个已经停止的容器。

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# docker start --help

Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]

Start one or more stopped containers

-a, --attach Attach STDOUT/STDERR and forward signals
--detach-keys Override the key sequence for detaching a container
--help Print usage
-i, --interactive Attach container's STDIN

docker restart

说明:将一个运行态的容器终止,然后再重新启动它。

1
2
3
4
5
6
7
8
[root@localhost ~]# docker restart --help

Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...]

Restart a container

--help Print usage
-t, --time=10 Seconds to wait for stop before killing the container

docker attach

说明:在使用 -d 参数时,容器启动后会进入后台。 某些时候需要进入容器进行操作就可以用它。

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# docker attach --help

Usage: docker attach [OPTIONS] CONTAINER

Attach to a running container

--detach-keys Override the key sequence for detaching a container
--help Print usage
--no-stdin Do not attach STDIN
--sig-proxy=true Proxy all received signals to the process

docker commit

说明:将修改后的容器提交保存为一个新的镜像。

1
2
3
4
5
6
7
8
9
10
11
[root@localhost ~]# docker commit --help

Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

Create a new image from a container's changes

-a, --author Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
-c, --change=[] Apply Dockerfile instruction to the created image
--help Print usage
-m, --message Commit message
-p, --pause=true Pause container during commit

示例:

1
2
$ sudo docker commit -m "Added json gem" -a "Docker Newbee" 0b2616b0e5a8 ouruser/sinatra:v2
4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c

docker tag

说明:修改镜像标签

1
2
3
4
5
6
7
[root@localhost ~]# docker tag --help

Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]

Tag an image into a repository

--help Print usage

示例:

1
docker tag 5db5f8471261 ouruser/sinatra:devel #将5db5f8471261的镜像tag更改为devel

docker push

说明:自己创建的本地镜像上传到远程仓库中来共享。

1
2
3
4
5
6
7
8
[root@localhost ~]# docker push --help

Usage: docker push [OPTIONS] NAME[:TAG]

Push an image or a repository to a registry

--disable-content-trust=true Skip image signing
--help Print usage

docker save

说明:将镜像导出成本地文件

1
2
3
4
5
6
7
8
[root@localhost ~]# docker save --help

Usage: docker save [OPTIONS] IMAGE [IMAGE...]

Save an image(s) to a tar archive (streamed to STDOUT by default)

--help Print usage
-o, --output Write to a file, instead of STDOUT

示例:

1
[root@localhost ~]# docker save -o ubuntu_14.04.tar ubuntu:14.04

docker load

说明:将镜像本地文件加载成为本地镜像

1
2
3
4
5
6
7
8
[root@localhost ~]# docker load --help

Usage: docker load [OPTIONS]

Load an image from a tar archive or STDIN

--help Print usage
-i, --input Read from a tar archive file, instead of STDIN

示例:

1
[root@localhost ~]# docker load --input ubuntu_14.04.tar

docker ps

说明:列出对应的容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost ~]# docker ps --help

Usage: docker ps [OPTIONS]

List containers

-a, --all Show all containers (default shows just running)
-f, --filter=[] Filter output based on conditions provided
--format Pretty-print containers using a Go template
--help Print usage
-l, --latest Show the latest created container (includes all states)
-n=-1 Show n last created containers (includes all states)
--no-trunc Don't truncate output
-q, --quiet Only display numeric IDs
-s, --size Display total file sizes

示例:

1
[root@localhost ~]# docker ps -a

docker export

说明:导出本地某个容器为快照文件

1
2
3
4
5
6
7
8
[root@localhost ~]# docker export --help

Usage: docker export [OPTIONS] CONTAINER

Export a container's filesystem as a tar archive

--help Print usage
-o, --output Write to a file, instead of STDOUT

docker import

说明:将容器快照文件导入为本地容器或者本地镜像。与docker load的区别在于容器快照文件将丢弃所有的历史记录和元数据信息,而镜像存储文件将保存完整记录,体积也要大。

1
2
3
4
5
6
7
8
9
[root@localhost ~]# docker import --help

Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]

Import the contents from a tarball to create a filesystem image

-c, --change=[] Apply Dockerfile instruction to the created image
--help Print usage
-m, --message Set commit message for imported image

示例:

1
[root@localhost ~]# cat ubuntu.tar | sudo docker import - test/ubuntu:v1.0

docker rm

说明:移除容器

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# docker rm --help

Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]

Remove one or more containers

-f, --force Force the removal of a running container (uses SIGKILL)
--help Print usage
-l, --link Remove the specified link
-v, --volumes Remove the volumes associated with the container

示例:

1
[root@localhost ~]# docker rm $(docker ps -a -q) #清理所有处于终止状态的容器

docker rmi

说明:移除本地镜像,移出前需要先移除到镜像对应的容器。

1
2
3
4
5
6
7
8
9
[root@localhost ~]# docker rmi --help

Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]

Remove one or more images

-f, --force Force removal of the image
--help Print usage
--no-prune Do not delete untagged parents

清理所有未打过标签的本地镜像:docker rmi $(docker images -q -f "dangling=true")

docker inspect

说明:查看容器或者镜像的详情信息。

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# docker inspect --help

Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]

Return low-level information on a container or image

-f, --format Format the output using the given go template
--help Print usage
-s, --size Display total file sizes if the type is container
--type Return JSON for specified type, (e.g image or container)

docker port

说明:来查看当前映射的端口配置,也可以查看到绑定的地址。

1
2
3
4
5
6
7
[root@localhost ~]# docker port --help

Usage: docker port [OPTIONS] CONTAINER [PRIVATE_PORT[/PROTO]]

List port mappings or a specific mapping for the CONTAINER

--help Print usage

观点仅代表自己,期待你的留言。