All posts by dotte

系统负载能力浅析

一. 衡量指标

用什么来衡量一个系统的负载能力呢?有一个概念叫做每秒请求数(Requests per second),指的是每秒能够成功处理请求的数目。比如说,你可以配置tomcat服务器的maxConnection为无限大,但是受限于服务器系统或者硬件限制,很多请求是不会在一定的时间内得到响应的,这并不作为一个成功的请求,其中成功得到响应的请求数即为每秒请求数,反应出系统的负载能力。

通常的,对于一个系统,增加并发用户数量时每秒请求数量也会增加。然而,我们最终会达到这样一个点,此时并发用户数量开始“压倒”服务器。如果继续增加并发用户数量,每秒请求数量开始下降,而反应时间则会增加。这个并发用户数量开始“压倒”服务器的临界点非常重要,此时的并发用户数量可以认为是当前系统的最大负载能力。

二. 相关因素

一般的,和系统并发访问量相关的几个因素如下:

  • 带宽
  • 硬件配置
  • 系统配置
  • 应用服务器配置
  • 程序逻辑
  • 系统架构

其中,带宽和硬件配置是决定系统负载能力的决定性因素。这些只能依靠扩展和升级提高。我们需要重点关注的是在一定带宽和硬件配置的基础上,怎么使系统的负载能力达到最大。

2.1 带宽

毋庸置疑,带宽是决定系统负载能力的一个至关重要的因素,就好比水管一样,细的水管同一时间通过的水量自然就少(这个比喻解释带宽可能不是特别合适)。一个系统的带宽首先就决定了这个系统的负载能力,其单位为Mbps,表示数据的发送速度。

2.2 硬件配置

系统部署所在的服务器的硬件决定了一个系统的最大负载能力,也是上限。一般说来,以下几个配置起着关键作用:

  • cpu频率/核数:cpu频率关系着cpu的运算速度,核数则影响线程调度、资源分配的效率。
  • 内存大小以及速度:内存越大,那么可以在内存中运行的数据也就越大,速度自然而然就快;内存的速度从原来的几百hz到现在几千hz,决定了数据读取存储的速度。
  • 硬盘速度:传统的硬盘是使用磁头进行寻址的,io速度比较慢,使用了SSD的硬盘,其寻址速度大大较快。

很多系统的架构设计、系统优化,最终都会加上这么一句:使用ssd存储解决了这些问题。

可见,硬件配置是决定一个系统的负载能力的最关键因素。

2.3 系统配置

一般来说,目前后端系统都是部署在Linux主机上的。所以抛开win系列不谈,对于Linux系统来说一般有以下配置关系着系统的负载能力。

  • 文件描述符数限制:Linux中所有东西都是文件,一个socket就对应着一个文件描述符,因此系统配置的最大打开文件数以及单个进程能够打开的最大文件数就决定了socket的数目上限。
  • 进程/线程数限制: 对于apache使用的prefork等多进程模式,其负载能力由进程数目所限制。对tomcat多线程模式则由线程数所限制。
  • tcp内核参数:网络应用的底层自然离不开tcp/ip,Linux内核有一些与此相关的配置也决定了系统的负载能力。

2.3.1 文件描述符数限制

  • 系统最大打开文件描述符数:/proc/sys/fs/file-max中保存了这个数目,修改此值
    1
    2
    3
    4
    临时性:
     echo 1000000 > /proc/sys/fs/file-max
    永久性:
    在/etc/sysctl.conf中设置 fs.file-max = 1000000
  • 进程最大打开文件描述符数:这个是配单个进程能够打开的最大文件数目。可以通过ulimit -n查看/修改。如果想要永久修改,则需要修改/etc/security/limits.conf中的nofile。

通过读取/proc/sys/fs/file-nr可以看到当前使用的文件描述符总数。另外,对于文件描述符的配置,需要注意以下几点:

  • 所有进程打开的文件描述符数不能超过/proc/sys/fs/file-max
  • 单个进程打开的文件描述符数不能超过user limit中nofile的soft limit
  • nofile的soft limit不能超过其hard limit
  • nofile的hard limit不能超过/proc/sys/fs/nr_open

2.3.2 进程/线程数限制

  • 进程数限制:ulimit -u可以查看/修改单个用户能够打开的最大进程数。/etc/security/limits.conf中的noproc则是系统的最大进程数。
  • 线程数限制
    • 可以通过/proc/sys/kernel/threads-max查看系统总共可以打开的最大线程数。
    • 单个进程的最大线程数和PTHREAD_THREADS_MAX有关,此限制可以在/usr/include/bits/local_lim.h中查看,但是如果想要修改的话,需要重新编译。
    • 这里需要提到一点的是,Linux内核2.4的线程实现方式为linux threads,是轻量级进程,都会首先创建一个管理线程,线程数目的大小是受PTHREAD_THREADS_MAX影响的。但Linux2.6内核的线程实现方式为NPTL,是一个改进的LWP实现,最大一个区别就是,线程公用进程的pid(tgid),线程数目大小只受制于资源。
    • 线程数的大小还受线程栈大小的制约:使用ulimit -s可以查看/修改线程栈的大小,即每开启一个新的线程需要分配给此线程的一部分内存。减小此值可以增加可以打开的线程数目。

2.3.3 tcp内核参数

在一台服务器CPU和内存资源额定有限的情况下,最大的压榨服务器的性能,是最终的目的。在节省成本的情况下,可以考虑修改Linux的内核TCP/IP参数,来最大的压榨服务器的性能。如果通过修改内核参数也无法解决的负载问题,也只能考虑升级服务器了,这是硬件所限,没有办法的事。

1
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

使用上面的命令,可以得到当前系统的各个状态的网络连接的数目。如下:

1
2
3
4
5
6
7
LAST_ACK 13
SYN_RECV 468
ESTABLISHED 90
FIN_WAIT1 259
FIN_WAIT2 40
CLOSING 34
TIME_WAIT 28322

这里,TIME_WAIT的连接数是需要注意的一点。此值过高会占用大量连接,影响系统的负载能力。需要调整参数,以尽快的释放time_wait连接。

一般tcp相关的内核参数在/etc/sysctl.conf文件中。为了能够尽快释放time_wait状态的连接,可以做以下配置:

  • net.ipv4.tcp_syncookies = 1 //表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
  • net.ipv4.tcp_tw_reuse = 1 //表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
  • net.ipv4.tcp_tw_recycle = 1 //表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭;
  • net.ipv4.tcp_fin_timeout = 30 //修改系統默认的 TIMEOUT 时间。

这里需要注意的一点就是当打开了tcp_tw_recycle,就会检查时间戳,移动环境下的发来的包的时间戳有些时候是乱跳的,会把带了“倒退”的时间戳的包当作是“recycle的tw连接的重传数据,不是新的请求”,于是丢掉不回包,造成大量丢包。另外,当前面有LVS,并且采用的是NAT机制时,开启tcp_tw_recycle会造成一些异常,可见:http://www.pagefault.info/?p=416。如果这种情况下仍然需要开启此选项,那么可以考虑设置net.ipv4.tcp_timestamps=0,忽略掉报文的时间戳即可。

此外,还可以通过优化tcp/ip的可使用端口的范围,进一步提升负载能力。,如下:

  • net.ipv4.tcp_keepalive_time = 1200 //表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
  • net.ipv4.ip_local_port_range = 10000 65000 //表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为10000到65000。(注意:这里不要将最低值设的太低,否则可能会占用掉正常的端口!)
  • net.ipv4.tcp_max_syn_backlog = 8192 //表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
  • net.ipv4.tcp_max_tw_buckets = 5000 //表示系统同时保持TIME_WAIT的最大数量,如果超过这个数字,TIME_WAIT将立刻被清除并打印警告信息。默认为180000,改为5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果却不大。此项参数可以控制TIME_WAIT的最大数量,避免Squid服务器被大量的TIME_WAIT拖死。

2.4 应用服务器配置

说到应用服务器配置,这里需要提到应用服务器的几种工作模式,也叫并发策略。

  • multi process:多进程方式,一个进程处理一个请求。
  • prefork:类似于多进程的方式,但是会预先fork出一些进程供后续使用,是一种进程池的理念。
  • worker:一个线程对应一个请求,相比多进程的方式,消耗资源变少,但同时一个线程的崩溃会引起整个进程的崩溃,稳定性不如多进程。
  • master/worker:采用的是非阻塞IO的方式,只有两种进程:worker和master,master负责worker进程的创建、管理等,worker进程采用基于事件驱动的多路复用IO处理请求。mater进程只需要一个,woker进程根据cpu核数设置数目。

前三者是传统应用服务器apache和tomcat采用的方式,最后一种是nginx采用的方式。当然这里需要注意的是应用服务器和nginx这种做反向代理服务器(暂且忽略nginx+cgi做应用服务器的功能)的区别。应用服务器是需要处理应用逻辑的,有时候是耗cup资源的;而反向代理主要用作IO,是IO密集型的应用。使用事件驱动的这种网络模型,比较适合IO密集型应用,而并不适合CPU密集型应用。对于后者,多进程/线程则是一个更好地选择。

当然,由于nginx采用的基于事件驱动的多路IO复用的模型,其作为反向代理服务器时,可支持的并发是非常大的。淘宝tengine团队曾有一个测试结果是“24G内存机器上,处理并发请求可达200万”。

2.4.1 nginx/tengine

ngixn是目前使用最广泛的反向代理软件,而tengine是阿里开源的一个加强版nginx,其基本实现了nginx收费版本的一些功能,如:主动健康检查、session sticky等。对于nginx的配置,需要注意的有这么几点:

  • worker数目要和cpu(核)的数目相适应
  • keepalive timout要设置适当
  • worker_rlimit_nofile最大文件描述符要增大
  • upstream可以使用http 1.1的keepalive

典型配置可见:https://github.com/superhj1987/awesome-config/blob/master/nginx/nginx.conf

2.4.2 tomcat

tomcat的关键配置总体上有两大块:jvm参数配置和connector参数配置。

  • jvm参数配置:
    • 堆的最小值:Xms
    • 堆的最大值:Xmx
    • 新生代大小: Xmn
    • 永久代大小: XX:PermSize:
    • 永久代最大大小: XX:MaxPermSize:
    • 栈大小:-Xss或-XX:ThreadStackSize

    这里对于栈大小有一点需要注意的是:在Linux x64上ThreadStackSize的默认值就是1024KB,给Java线程创建栈会用这个参数指定的大小。如果把-Xss或者-XX:ThreadStackSize设为0,就是使用“系统默认值”。而在Linux x64上HotSpot VM给Java栈定义的“系统默认”大小也是1MB。所以普通Java线程的默认栈大小怎样都是1MB。这里有一个需要注意的地方就是java的栈大小和之前提到过的操作系统的操作系统栈大小(ulimit -s):这个配置只影响进程的初始线程;后续用pthread_create创建的线程都可以指定栈大小。HotSpot VM为了能精确控制Java线程的栈大小,特意不使用进程的初始线程(primordial thread)作为Java线程。

    其他还要根据业务场景,选择使用那种垃圾回收器,回收的策略。另外,当需要保留GC信息时,也需要做一些设置。

    典型配置可见:https://github.com/superhj1987/awesome-config/blob/master/tomcat/java_opts.conf

  • connector参数配置
    • protocol: 有三个选项:bio;nio;apr。建议使用apr选项,性能为最高。
    • connectionTimeout:连接的超时时间
    • maxThreads:最大线程数,此值限制了bio的最大连接数
    • minSpareThreads: 最大空闲线程数
    • acceptCount:可以接受的最大请求数目(未能得到处理的请求排队)
    • maxConnection: 使用nio或者apr时,最大连接数受此值影响。

    典型配置可见:https://github.com/superhj1987/awesome-config/blob/master/tomcat/connector.conf

    一般的当一个进程有500个线程在跑的话,那性能已经是很低很低了。Tomcat默认配置的最大请求数是150。当某个应用拥有250个以上并发的时候,应考虑应用服务器的集群。

    另外,并非是无限调大maxTreads和maxConnection就能无限调高并发能力的。线程越多,那么cpu花费在线程调度上的时间越多,同时,内存消耗也就越大,那么就极大影响处理用户的请求。受限于硬件资源,并发值是需要设置合适的值的。

对于tomcat这里有一个争论就是:使用大内存tomcat好还是多个小的tomcat集群好?(针对64位服务器以及tomcat来说)

其实,这个要根据业务场景区别对待的。通常,大内存tomcat有以下问题:

  • 一旦发生full gc,那么会非常耗时
  • 一旦gc,dump出的堆快照太大,无法分析

因此,如果可以保证一定程度上程序的对象大部分都是朝生夕死的,老年代不会发生gc,那么使用大内存tomcat也是可以的。但是在伸缩性和高可用却比不上使用小内存(相对来说)tomcat集群。

使用小内存tomcat集群则有以下优势:

  • 可以根据系统的负载调整tc的数量,以达到资源的最大利用率,
  • 可以防止单点故障。

2.4.3 数据库

mysql

mysql是目前最常用的关系型数据库,支持复杂的查询。但是其负载能力一般,很多时候一个系统的瓶颈就发生在mysql这一点,当然有时候也和sql语句的效率有关。比如,牵扯到联表的查询一般说来效率是不会太高的。

影响数据库性能的因素一般有以下几点:

  • 硬件配置:这个无需多说
  • 数据库设置:max_connection的一些配置会影响数据库的连接数
  • 数据表的设计:使用冗余字段避免联表查询;使用索引提高查询效率
  • 查询语句是否合理:这个牵扯到的是个人的编码素质。比如,查询符合某个条件的记录,我见过有人把记录全部查出来,再去逐条对比
  • 引擎的选择:myisam和innodb两者的适用场景不同,不存在绝对的优劣

抛开以上因素,当数据量单表突破千万甚至百万时(和具体的数据有关),需要对mysql数据库进行优化,一种常见的方案就是分表:

  • 垂直分表:在列维度的拆分
  • 水平分表:行维度的拆分

此外,对于数据库,可以使用读写分离的方式提高性能,尤其是对那种读频率远大于写频率的业务场景。这里一般采用master/slave的方式实现读写分离,前面用程序控制或者加一个proxy层。可以选择使用MySQL Proxy,编写lua脚本来实现基于proxy的mysql读写分离;也可以通过程序来控制,根据不同的sql语句选择相应的数据库来操作,这个也是笔者公司目前在用的方案。由于此方案和业务强绑定,是很难有一个通用的方案的,其中比较成熟的是阿里的TDDL,但是由于未全部开源且对其他组件有依赖性,不推荐使用。

现在很多大的公司对这些分表、主从分离、分布式都基于mysql做了自己的二次开发,形成了自己公司的一套分布式数据库系统。比如阿里的Cobar、网易的DDB、360的Atlas等。当然,很多大公司也研发了自己的mysql分支,比较出名的就是姜承尧带领研发的InNoSQL

redis

当然,对于系统中并发很高并且访问很频繁的数据,关系型数据库还是不能妥妥应对。这时候就需要缓存数据库出马以隔离对mysql的访问,防止mysql崩溃。

其中,redis是目前用的比较多的缓存数据库(当然,也有直接把redis当做数据库使用的)。redis是单线程基于内存的数据库,读写性能远远超过mysql。一般情况下,对redis做读写分离主从同步就可以应对大部分场景的应用。但是这样的方案缺少ha,尤其对于分布式应用,是不可接受的。目前,redis集群的实现方案有以下几个:

  • redis cluster:这是一种去中心化的方案,是redis的官方实现。是一种非常“重”的方案,已经不是Redis单实例的“简单、可依赖”了。目前应用案例还很少,貌似国内的芒果台用了,结局不知道如何。
  • twemproxy:这是twitter开源的redis和memcached的proxy方案。比较成熟,目前的应用案例比较多,但也有一些缺陷,尤其在运维方面。比如无法平滑的扩容/缩容,运维不友好等。
  • codis: 这个是豌豆荚开源的redis proxy方案,能够兼容twemproxy,并且对其做了很多改进。由豌豆荚于2014年11月开源,基于Go和C开发。现已广泛用于豌豆荚的各种Redis业务场景。现在比Twemproxy快近100%。目前据我所知除了豌豆荚之外,hulu也在使用这套方案。当然,其升级项目reborndb号称比codis还要厉害。

2.5 系统架构

影响性能的系统架构一般会有这几方面:

  • 负载均衡
  • 同步 or 异步
  • 28原则

2.5.1 负载均衡

负载均衡在服务端领域中是一个很关键的技术。可以分为以下两种:

  • 硬件负载均衡
  • 软件负载均衡

其中,硬件负载均衡的性能无疑是最优的,其中以F5为代表。但是,与高性能并存的是其成本的昂贵。所以对于很多初创公司来说,一般是选用软件负载均衡的方案。

软件负载均衡中又可以分为四层负载均衡和七层负载均衡。 上文在应用服务器配置部分讲了nginx的反向代理功能即七层的一种成熟解决方案,主要针对的是七层http协议(虽然最新的发布版本已经支持四层负载均衡)。对于四层负载均衡,目前应用最广泛的是lvs。其是阿里的章文嵩博士带领的团队所研发的一款linux下的负载均衡软件,本质上是基于iptables实现的。分为三种工作模式:

  • NAT: 修改数据包destination ip,in和out都要经过lvs。
  • DR:修改数据包mac地址,lvs和realserver需要在一个vlan。
  • IP TUUNEL:修改数据包destination ip和源ip,realserver需要支持ip tunnel协议。lvs和realserver不需要在一个vlan。

三种模式各有优缺点,目前还有阿里开源的一个FULL NAT是在NAT原来的DNAT上加入了SNAT的功能。

此外,haproxy也是一款常用的负载均衡软件。但限于对此使用较少,在此不做讲述。

2.5.2 同步 or 异步

对于一个系统,很多业务需要面对使用同步机制或者是异步机制的选择。比如,对于一篇帖子,一个用户对其分享后,需要记录用户的分享记录。如果你使用同步模式(分享的同时记录此行为),那么响应速度肯定会受到影响。而如果你考虑到分享过后,用户并不会立刻去查看自己的分享记录,牺牲这一点时效性,可以先完成分享的动作,然后异步记录此行为,会提高分享请求的响应速度(当然,这里可能会有事务准确性的问题)。有时候在某些业务逻辑上,在充分理解用户诉求的基础上,是可以牺牲某些特性来满足用户需求的。

这里值得一提的是,很多时候对于一个业务流程,是可以拆开划分为几个步骤的,然后有些步骤完全可以异步并发执行,能够极大提高处理速度。

2.5.3 28原则

对于一个系统,20%的功能会带来80%的流量。这就是28原则的意思,当然也是我自己的一种表述。因此在设计系统的时候,对于80%的功能,其面对的请求压力是很小的,是没有必要进行过度设计的。但是对于另外20%的功能则是需要设计再设计、reivew再review,能够做负载均衡就做负载均衡,能够缓存就缓存,能够做分布式就分布式,能够把流程拆开异步化就异步化。

当然,这个原则适用于生活中很多事物。

三. 一般架构

一般的Java后端系统应用架构如下图所示:LVS+Nginx+Tomcat+MySql/DDB+Redis/Codis

web-arch

其中,虚线部分是数据库层,采用的是主从模式。也可以使用redis cluster(codis等)以及mysql cluster(Cobar等)来替换。

from:http://www.rowkey.me/blog/2015/09/09/load-analysis/

python:No module named xxx

OSX/Linux

Use $ sudo pip install requests if you have pip installed

On OSX you can also use sudo easy_install -U requests if you have easy_install installed.

Windows

Use > Path\easy_install.exe requests if you have a windows machine, where easy_install can be found in your Python*\Scripts folder, if it was installed. (Note Path\easy_install.exe is an example, mine is C:\Python32\Scripts\easy_install.exe)

If you don’t have easy install and are running on a windows machine, you can get it here: http://www.lfd.uci.edu/~gohlke/pythonlibs/#distribute

If you manually want to add a library to a windows machine, you can download the compressed library, uncompress it, and then place it into the Lib folder of your python path.

From Source (Universal)

For any missing library, the source is usually available at https://pypi.python.org/pypi/. Then:

On mac osx and windows, after downloading the source zip, uncompress it and from the termiminal/cmd run python setup.py install from the uncompressed dir.

from:http://stackoverflow.com/questions/17309288/importerror-no-module-named-requests

Python Resource

 Python语言:

1、Python Language Essentials  《Python for Data Analysis》

2、廖雪峰python教程

Python函数式编程:从入门到走火入魔

Python工具及环境:

IPython Notebook: 交互计算新时代

Anaconda Scientific Python Distribution

WinPython

库与框架:

Beautiful Soup 4.2.0 文档

10 Minutes to pandas

数据分析:

零基础学习Python数据分析

Book:

Python基础教程

利用Python进行数据分析

集体智慧编程 (豆瓣)

这是一本非常好的入门书,书中的例子源码都是Python实现的,并且能帮你迅速熟悉Python相关的各种计算库

统计学习方法 (豆瓣)

这本书深入浅出地讲了和机器学习有关的一切数学基础知识,一整本的干货,没有废话,非常值得一读

Some Online Resources:

http://docs.python.org/tut/tut.html – Beginners

http://diveintopython3.ep.io/ – Intermediate

http://www.pythonchallenge.com/ – Expert Skills

http://docs.python.org/ – collection of all knowledge

Some more:

A Byte of Python.

Python 2.5 Quick Reference

Python Side bar

A Nice blog for beginners

Think Python: An Introduction to Software Design

Python Resource

优秀Python学习资源收集汇总(强烈推荐)

学习Python编程的11个资源

Hidden features of Python

怎么用最短时间高效而踏实地学习 Python

refer:http://stackoverflow.com/questions/70577/best-online-resource-to-learn-python

Python正则表达式指南

1. 正则表达式基础

1.1. 简单介绍

正则表达式并不是Python的一部分。正则表达式是用于处理字符串的强大工具,拥有自己独特的语法以及一个独立的处理引擎,效率上可能不如str自带的方法,但功能十分强大。得益于这一点,在提供了正则表达式的语言里,正则表达式的语法都是一样的,区别只在于不同的编程语言实现支持的语法数量不同;但不用担心,不被支持的语法通常是不常用的部分。如果已经在其他语言里使用过正则表达式,只需要简单看一看就可以上手了。

下图展示了使用正则表达式进行匹配的流程:
re_simple_38246a58-83be-4adf-9f30-6d735e9b9b47

正则表达式的大致匹配过程是:依次拿出表达式和文本中的字符比较,如果每一个字符都能匹配,则匹配成功;一旦有匹配不成功的字符则匹配失败。如果表达式中有量词或边界,这个过程会稍微有一些不同,但也是很好理解的,看下图中的示例以及自己多使用几次就能明白。

下图列出了Python支持的正则表达式元字符和语法:
pyre_ebb9ce1c-e5e8-4219-a8ae-7ee620d5f9f1

1.2. 数量词的贪婪模式与非贪婪模式

正则表达式通常用于在文本中查找匹配的字符串。Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪),总是尝试匹配尽可能多的字符;非贪婪的则相反,总是尝试匹配尽可能少的字符。例如:正则表达式”ab*”如果用于查找”abbbc”,将找到”abbb”。而如果使用非贪婪的数量词”ab*?”,将找到”a”。

1.3. 反斜杠的困扰

与大多数编程语言相同,正则表达式里使用”\”作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符”\”,那么使用编程语言表示的正则表达式里将需要4个反斜杠”\\\\”:前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。Python里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用r”\\”表示。同样,匹配一个数字的”\\d”可以写成r”\d”。有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。

1.4. 匹配模式

正则表达式提供了一些可用的匹配模式,比如忽略大小写、多行匹配等,这部分内容将在Pattern类的工厂方法re.compile(pattern[, flags])中一起介绍。

2. re模块

2.1. 开始使用re

Python通过re模块提供对正则表达式的支持。使用re的一般步骤是先将正则表达式的字符串形式编译为Pattern实例,然后使用Pattern实例处理文本并获得匹配结果(一个Match实例),最后使用Match实例获得信息,进行其他的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# encoding: UTF-8
import re
# 将正则表达式编译成Pattern对象
pattern = re.compile(r'hello')
# 使用Pattern匹配文本,获得匹配结果,无法匹配时将返回None
match = pattern.match('hello world!')
if match:
    # 使用Match获得分组信息
    print match.group()
### 输出 ###
# hello

re.compile(strPattern[, flag]):

这个方法是Pattern类的工厂方法,用于将字符串形式的正则表达式编译为Pattern对象。 第二个参数flag是匹配模式,取值可以使用按位或运算符’|’表示同时生效,比如re.I | re.M。另外,你也可以在regex字符串中指定模式,比如re.compile(‘pattern’, re.I | re.M)与re.compile(‘(?im)pattern’)是等价的。
可选值有:

  • re.I(re.IGNORECASE): 忽略大小写(括号内是完整写法,下同)
  • M(MULTILINE): 多行模式,改变’^’和’$’的行为(参见上图)
  • S(DOTALL): 点任意匹配模式,改变’.’的行为
  • L(LOCALE): 使预定字符类 \w \W \b \B \s \S 取决于当前区域设定
  • U(UNICODE): 使预定字符类 \w \W \b \B \s \S \d \D 取决于unicode定义的字符属性
  • X(VERBOSE): 详细模式。这个模式下正则表达式可以是多行,忽略空白字符,并可以加入注释。以下两个正则表达式是等价的:
1
2
3
4
a = re.compile(r"""\d +  # the integral part
                   \.    # the decimal point
                   \d *  # some fractional digits""", re.X)
b = re.compile(r"\d+\.\d*")

re提供了众多模块方法用于完成正则表达式的功能。这些方法可以使用Pattern实例的相应方法替代,唯一的好处是少写一行re.compile()代码,但同时也无法复用编译后的Pattern对象。这些方法将在Pattern类的实例方法部分一起介绍。如上面这个例子可以简写为:

1
2
m = re.match(r'hello', 'hello world!')
print m.group()

re模块还提供了一个方法escape(string),用于将string中的正则表达式元字符如*/+/?等之前加上转义符再返回,在需要大量匹配元字符时有那么一点用。

2.2. Match

Match对象是一次匹配的结果,包含了很多关于此次匹配的信息,可以使用Match提供的可读属性或方法来获取这些信息。

属性:

  1. string: 匹配时使用的文本。
  2. re: 匹配时使用的Pattern对象。
  3. pos: 文本中正则表达式开始搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
  4. endpos: 文本中正则表达式结束搜索的索引。值与Pattern.match()和Pattern.seach()方法的同名参数相同。
  5. lastindex: 最后一个被捕获的分组在文本中的索引。如果没有被捕获的分组,将为None。
  6. lastgroup: 最后一个被捕获的分组的别名。如果这个分组没有别名或者没有被捕获的分组,将为None。

方法:

  1. group([group1, …]):
    获得一个或多个分组截获的字符串;指定多个参数时将以元组形式返回。group1可以使用编号也可以使用别名;编号0代表整个匹配的子串;不填写参数时,返回group(0);没有截获字符串的组返回None;截获了多次的组返回最后一次截获的子串。
  2. groups([default]):
    以元组形式返回全部分组截获的字符串。相当于调用group(1,2,…last)。default表示没有截获字符串的组以这个值替代,默认为None。
  3. groupdict([default]):
    返回以有别名的组的别名为键、以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上。
  4. start([group]):
    返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认值为0。
  5. end([group]):
    返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1)。group默认值为0。
  6. span([group]):
    返回(start(group), end(group))。
  7. expand(template):
    将匹配到的分组代入template中然后返回。template中可以使用\id或\g<id>、\g<name>引用分组,但不能使用编号0。\id与\g<id>是等价的;但\10将被认为是第10个分组,如果你想表达\1之后是字符’0’,只能使用\g<1>0。
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
import re
m = re.match(r'(\w+) (\w+)(?P<sign>.*)', 'hello world!')
print "m.string:", m.string
print "m.re:", m.re
print "m.pos:", m.pos
print "m.endpos:", m.endpos
print "m.lastindex:", m.lastindex
print "m.lastgroup:", m.lastgroup
print "m.group(1,2):", m.group(1, 2)
print "m.groups():", m.groups()
print "m.groupdict():", m.groupdict()
print "m.start(2):", m.start(2)
print "m.end(2):", m.end(2)
print "m.span(2):", m.span(2)
print r"m.expand(r'\2 \1\3'):", m.expand(r'\2 \1\3')
### output ###
# m.string: hello world!
# m.re: <_sre.SRE_Pattern object at 0x016E1A38>
# m.pos: 0
# m.endpos: 12
# m.lastindex: 3
# m.lastgroup: sign
# m.group(1,2): ('hello', 'world')
# m.groups(): ('hello', 'world', '!')
# m.groupdict(): {'sign': '!'}
# m.start(2): 6
# m.end(2): 11
# m.span(2): (6, 11)
# m.expand(r'\2 \1\3'): world hello!

2.3. Pattern

Pattern对象是一个编译好的正则表达式,通过Pattern提供的一系列方法可以对文本进行匹配查找。

Pattern不能直接实例化,必须使用re.compile()进行构造。

Pattern提供了几个可读属性用于获取表达式的相关信息:

  1. pattern: 编译时用的表达式字符串。
  2. flags: 编译时用的匹配模式。数字形式。
  3. groups: 表达式中分组的数量。
  4. groupindex: 以表达式中有别名的组的别名为键、以该组对应的编号为值的字典,没有别名的组不包含在内。
1
2
3
4
5
6
7
8
9
10
11
12
13
import re
p = re.compile(r'(\w+) (\w+)(?P<sign>.*)', re.DOTALL)
print "p.pattern:", p.pattern
print "p.flags:", p.flags
print "p.groups:", p.groups
print "p.groupindex:", p.groupindex
### output ###
# p.pattern: (\w+) (\w+)(?P<sign>.*)
# p.flags: 16
# p.groups: 3
# p.groupindex: {'sign': 3}

实例方法[ | re模块方法]:

  1. match(string[, pos[, endpos]]) | re.match(pattern, string[, flags]):
    这个方法将从string的pos下标处起尝试匹配pattern;如果pattern结束时仍可匹配,则返回一个Match对象;如果匹配过程中pattern无法匹配,或者匹配未结束就已到达endpos,则返回None。
    pos和endpos的默认值分别为0和len(string);re.match()无法指定这两个参数,参数flags用于编译pattern时指定匹配模式。
    注意:这个方法并不是完全匹配。当pattern结束时若string还有剩余字符,仍然视为成功。想要完全匹配,可以在表达式末尾加上边界匹配符’$’。
    示例参见2.1小节。
  2. search(string[, pos[, endpos]]) | re.search(pattern, string[, flags]):
    这个方法用于查找字符串中可以匹配成功的子串。从string的pos下标处起尝试匹配pattern,如果pattern结束时仍可匹配,则返回一个Match对象;若无法匹配,则将pos加1后重新尝试匹配;直到pos=endpos时仍无法匹配则返回None。
    pos和endpos的默认值分别为0和len(string));re.search()无法指定这两个参数,参数flags用于编译pattern时指定匹配模式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # encoding: UTF-8
    import re
    # 将正则表达式编译成Pattern对象
    pattern = re.compile(r'world')
    # 使用search()查找匹配的子串,不存在能匹配的子串时将返回None
    # 这个例子中使用match()无法成功匹配
    match = pattern.search('hello world!')
    if match:
        # 使用Match获得分组信息
        print match.group()
    ### 输出 ###
    # world
  3. split(string[, maxsplit]) | re.split(pattern, string[, maxsplit]):
    按照能够匹配的子串将string分割后返回列表。maxsplit用于指定最大分割次数,不指定将全部分割。

    1
    2
    3
    4
    5
    6
    7
    import re
    p = re.compile(r'\d+')
    print p.split('one1two2three3four4')
    ### output ###
    # ['one', 'two', 'three', 'four', '']
  4. findall(string[, pos[, endpos]]) | re.findall(pattern, string[, flags]):
    搜索string,以列表形式返回全部能匹配的子串。

    1
    2
    3
    4
    5
    6
    7
    import re
    p = re.compile(r'\d+')
    print p.findall('one1two2three3four4')
    ### output ###
    # ['1', '2', '3', '4']
  5. finditer(string[, pos[, endpos]]) | re.finditer(pattern, string[, flags]):
    搜索string,返回一个顺序访问每一个匹配结果(Match对象)的迭代器。

    1
    2
    3
    4
    5
    6
    7
    8
    import re
    p = re.compile(r'\d+')
    for m in p.finditer('one1two2three3four4'):
        print m.group(),
    ### output ###
    # 1 2 3 4
  6. sub(repl, string[, count]) | re.sub(pattern, repl, string[, count]):
    使用repl替换string中每一个匹配的子串后返回替换后的字符串。
    当repl是一个字符串时,可以使用\id或\g<id>、\g<name>引用分组,但不能使用编号0。
    当repl是一个方法时,这个方法应当只接受一个参数(Match对象),并返回一个字符串用于替换(返回的字符串中不能再引用分组)。
    count用于指定最多替换次数,不指定时全部替换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import re
    p = re.compile(r'(\w+) (\w+)')
    s = 'i say, hello world!'
    print p.sub(r'\2 \1', s)
    def func(m):
        return m.group(1).title() + ' ' + m.group(2).title()
    print p.sub(func, s)
    ### output ###
    # say i, world hello!
    # I Say, Hello World!
  7. subn(repl, string[, count]) |re.sub(pattern, repl, string[, count]):
    返回 (sub(repl, string[, count]), 替换次数)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import re
    p = re.compile(r'(\w+) (\w+)')
    s = 'i say, hello world!'
    print p.subn(r'\2 \1', s)
    def func(m):
        return m.group(1).title() + ' ' + m.group(2).title()
    print p.subn(func, s)
    ### output ###
    # ('say i, world hello!', 2)
    # ('I Say, Hello World!', 2)

from:http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html

Docker:带给现代开发人员的福利

Docker 就像是用于 DevOps 的一把瑞士军刀,这一点已得到充分证明。但 Docker 托管的应用程序容器的用途不仅是在云中部署服务器。Docker 容器还可以在许多常见的开发场景中帮助开发和显著提高生产力。本教程将重点介绍 Docker,了解从开发人员角度讲它有何用途。我将介绍 Docker,解释基本概念和术语,并提供一系列实际的开发示例。

您会看到:

  • Docker 可以可简化 Node.js Web 应用程序编码。
  • 您可以使用 Docker 在 Apache Tomcat 服务器上快速创建和测试 Java™ Enterprise Edition (Java EE) 应用程序。
  • Docker 可以通过 Bottle 框架加快 Python Web 应用程序的编码、测试和开发。
  • 您可以通过使用一个 Docker 容器,在几分钟内创建并测试一个具有 NoSQL 数据库后端的完整的三层 Node.js 应用程序。

Docker、golang 和 Bluemix

阅读配套教程 “使用 Docker 和 Bluemix 容器化 golang 应用程序”,将您的多语言探索扩展到 Go 编程语言。另外,了解通过 IBM Containers for Bluemix 将 Docker 化的三层应用程序部署到 Bluemix™ 有都没容易。

您可以在单个 Linux 系统上获得所有这些收益,而不会妨碍任何预设的配置。在了解一些示例后,您就能够轻松地运行各种 Docker 容器来提高日常开发工作流中的生产力。

Docker 基础

Docker 是一个容器管理器。容器将应用程序和它的依赖项捆绑在一起。您从一个存储在存储库中的镜像实例化每个容器,在主机操作系统内隔离的虚拟化环境中运行容器。因为虚拟化技术(通常)是轻量型的,所以您可以同时运行多个容器:

该图演示了 Docker 容器管理器

理解基本术语

了解镜像与容器之间的区别很重要。这两个概念紧密相关,但初学者常常难以了解它们之间的区别。

镜像 是一个静态的层集合。没有任何运行时行为与镜像关联。镜像存储在存储库中(例如 Docker Hub)。

容器 是一个从镜像启动的运行实例。尽管容器可以停止(并在此盘上临时冻结其状态),但它仍是一个容器。

潜在的混淆源于一个事实,那就是通过提交和保存容器,您可以将容器转换为镜像。在这种情况下,您可以将得到的镜像想作是运行的容器的 “冻干 (freeze-dried)” 版本。

您可以使用 Docker 容器管理器执行以下操作:

  • 从镜像运行容器
  • 检查和操作运行的容器的属性
  • 停止运行的容器
  • 在运行的容器内执行额外的任务
  • 启动或重新启动运行的容器
  • 执行其他许多管理任务

容器化的应用程序几乎可以在任何地方运行:桌面 PC 上、服务器上、公共云中,甚至是在一些移动电话上。

学习更多知识。开发更多项目。联系更多同行。

全新的 developerWorks Premium 会员计划提供了强大的开发工具和资源,包括 500 篇通过 Safari Books Online 提供的顶级技术文章(其中超过 100 篇文章是专门面向 Web 开发人员的)、最重要开发人员活动的大幅折扣、最新的 O’Reilly 大会的视频回放,等等。立即注册

Linux 目前是得到最广泛使用和支持的 Docker 平台。Docker 所管理的容器化(轻量型虚拟化)技术在 Linux 平台上最成熟,使用了最新的 Linux 特性,比如 控制组命名空间

如何运行 Docker

您可以通过 docker 命令行客户端来运行 Docker 容器管理器,该客户端有许多可用的开关和选项。这是运行容器化应用程序镜像(在这个例子中是 MySQL 数据库)的一个简化的命令:

docker run -d mysql:5.5

Docker 项目运行着可公开访问的 Docker Hub。用户可以注册并创建自己的容器化应用程序镜像存储库,然后推送镜像来供大家共同使用。例如,您可以找到 Tomcat 服务器、Node.js 和大多数流行的开源数据库的镜像。Docker Hub 的操作理念与 GitHub 相同,因为应用程序镜像是全球 DevOps 和开发人员社区共享和协同创建的。

举例而言,运行 docker run -d mysql:5.5 命令时,如果还没有将 mysql(5.5 版)容器化应用程序镜像存储在本地,就会自动从 Docker Hub 拉取(下载)它。

构建容器化应用程序镜像

还可以使用 docker 命令行客户端来构建容器化应用程序镜像。一种方式是从 Dockerfile 构建镜像 — 一个包含如何安装、设置和配置一个应用程序和它的依赖项的说明的文本文件。

使用 docker 命令行客户端构建容器化镜像的另一种方法是以交互方式设计它们。您在一个运行的容器内手动安装和配置一个应用程序和它的依赖项,然后提交容器并保存为镜像。

一个 Docker 镜像包含一些层,每层大体相当于在安装一组应用程序期间写入磁盘的更改。Docker 管理这些层,并帮助在添加或删除这些镜像时高效地存储和重用。举例而言,一个 mysql 镜像中的层可能包含 Linux 操作系统、Perl、共享库、MySQL 安装和一种基本的 MySQL 配置:

该图演示了一个容器化镜像中的层

Dockerfile 构建容器化镜像或设计自定义容器时,通常不会从头开始。相反,您的工作以现有的 Dockerfile 或一个来自 Docker Hub 的镜像为基础。这样,开发人员和操作人员就能够以彼此的工作为基础,协同创建和管理一组有用的镜像。

将镜像发送到镜像存储库(比如 Docker Hub)的命令是 docker push。下载镜像以供本地使用的命令是 docker pull


开始之前

即使使用 Java、Tomcat、Node.js、Python 和 CouchDB 进行开发,也不需要安装它们中的任何一个。这是在开发工作流中使用 Docker 的美妙之处。

在开始之前,需要一个安装并运行 Docker 的 Linux 系统。要安装 Docker,可以按照适合您的 Linux 发行版的 安装说明 进行操作。

如果无法直接访问 Linux 系统,可以依靠云。大多数云主机都提供了随时可运行的虚拟服务器或虚拟机,只需几分钟就可以为它们配备 Linux 并(通过 SSH 或基于 Web 的终端)连接到它们。这些主机通常与 Docker 兼容。

本教程中的示例假设您在 Linux 系统上运行 Docker。如果您已经在 Mac OS X 或另一个非 Linux 系统上运行 Docker,您的容器可能会在一个由虚拟机管理程序虚拟化的额外虚拟机(常常为 VirtualBox)内运行。在这种情况下,您需要相应地修改示例中的命令才能继续操作。

检查您的 Docker 版本

运行以下命令来检查您的 Docker 版本:

docker --version

高效的层管理

拉取镜像后,请注意指明已存在一个层的消息。Docker 通过层来管理在镜像创建期间写入磁盘的更改,这有助于高效地管理镜像更改集。例如,基础 Ubuntu 镜像或核心实用程序的安装可被多个镜像共享 — 节省了宝贵的磁盘空间和下载时间。

如果您运行 1.8.3 或更高版本,您的系统现在已经设置好。即使使用 Java、Tomcat、Node.js、Python 和 Apache CouchDB 进行开发,也不需要安装或卸载它们中的任何一个。这是在开发工作流中使用 Docker 的美妙之处。

拉取镜像

第一次从 Docker Hub pull(拉取)一个镜像时,必须将它下载到本地 PC 的存储库。根据您的网络访问速度,下载可能很耗时,但对镜像的后续使用将会很快。

运行以下命令来拉取您将在示例中使用的所有镜像:

docker pull node:0.10.40

docker pull java

docker pull tomcat:8

docker pull webratio/ant

docker pull python:3.5

docker pull frodenas/couchdb

有关每个镜像的更多信息,请访问 Docker Hub 的 nodejavatomcatwebratio/antpythonfrodenas/couchdb 页面。

现在,在设置完成后,就可以使用 Docker 处理一个 Node.js 应用程序来进行我们的探索了。


快速设置和开发服务器端 JavaScript 代码

代码发行版中的 javascript 目录(参见 下载)包含一个使用 Node.js 编写的网络商店示例应用程序的源代码。该应用程序(最初由 Lauren Schaefer 设计和编写)使用了 Express Web 框架和 Jade Node 模板引擎。(有关这个应用程序的更多信息,请参阅 “Bluemix 基础:将一个示例 Node.js 应用程序部署到云中。”)通过使用 Docker,您可以在您的计算机上使用这个应用程序,无需安装 Node.js 或它的任何依赖项— 或者排除包安装冲突。

主要程序在 app.js 中。CSS 和关联的 JavaScript 文件在 javascript/public/static 中。Jade 模板在 javascript/view 中。

运行以下命令来安装此应用程序的 Node.js 依赖项:

docker run -it --rm --name lllnode -v "$PWD":/usr/src/myapp -w /usr/src/myapp node:0.10.40 npm install

此命令将会启动 Node 容器,将当前应用程序目录挂载到容器中,然后在应用程序上运行 npm install

  • 基础命令是 docker run � node:0.10.40,它创建一个容器实例并运行您之前拉取的 node:0.10.40 镜像。
  • -it 开关指定您想要一个前台交互式终端。(备用模式是分离的后台进程,可以使用 -d 指定它。)
  • --rm 开关指定执行清理,表示在您退出容器后,Docker 会立即删除它。如果没有指定此开关,该容器会以停止状态持久保存在磁盘上,而且您可以从中断点开始重新启动它。但是,对于忘记删除容器实例的用户而言,重新启动容器是磁盘耗尽的常见来源。如果不使用 --rm,那么任何时候都能运行以下命令来查看您遗留下了多少陈旧的停止容器(数量可能会让您吃惊):
    docker ps -a
  • --name lllnode 选项显式命名容器,这对在其他 Docker 命令中引用该容器很有用。如果没有显式命名容器,Docker 会为它分配一个生成的文本名称 — 这个名称通常没什么意义。您还可以通过内部 ID(一个很长、对人类不友好的十六进制字符串)来引用该容器。
  • -v "$PWD":/usr/src/myapp 选项创建一个卷挂载点。将您当前的工作目录 ($PWD) 挂载为容器内的 /usr/src/myapp。然后可从该容器内访问应用程序的 Node.js 源代码(或您当前的工作目录中可能拥有的其他任何 Node.js 源代码)。
  • -w /usr/src/myapp 选项设置您运行的命令的工作目录。在这个示例中,工作目录更改为挂载的卷。
  • docker run 命令末尾的 npm install 命令在工作目录上的容器内运行。最终结果是您通过容器在当前目录上运行 npm install— 而不安装 Node.js 或它的任何前提软件。

在命令输出中,您可以看到 npm 正在运行的所有依赖项:

安装 Node.js 应用程序依赖项的屏幕截图

在安装依赖项后,可以通过容器运行该应用程序。使用此命令(将它键入为一行):

docker run -it --rm --name lllnode --env PORT=8011 --net="host" -v 
"$PWD":/usr/src/myapp -w /usr/src/myapp node:0.10.40 node app.js

这个命令类似于您之前运行的命令。但请注意,要在容器内运行的命令现在是 node app.js,它将启动应用程序。

查看 app.js 的内容。您会找到以下行:

var appEnv = cfenv.getAppEnv();
app.listen(appEnv.port, appEnv.bind, function() {
   console.log("server starting on " + appEnv.url);
});

这些行导致 Express Web 服务器在 PORT 环境变量指定的端口上监听。为了在容器中设置此环境变量,您刚运行的 docker 命令使用 --env PORT=8011 选项。

此外,该命令的 --net="host" 选项导致容器使用主机操作系统内部的网络堆栈(仅用于开发用途)。然后该应用程序在主机上的端口 8011 上进行监听。

在浏览器中访问 http://localhost:8011/,您可以在其中看到基于运行的容器的 Lauren’s Lovely Landscapes 印刷品商店:

Lauren's Lovely Landscapes 的 Node.js 的屏幕截图

您随时可以退出容器来修改代码或修复错误,然后再次运行容器— 所有操作在几秒内即可完成,不会让开发环境变得杂乱。


在几秒内从 Node.js 转换为 Java EE

现在您已确信 Docker 只需几秒即可启动并运行一个 Node.js 应用程序,您可能想知道它如何更轻量型地处理一些工作 — 比如 Java EE 开发。很高兴为您解答。

假设您刚刚阅读了我的 “使用 Vaadin 实现全堆栈 Java Web 开发” 教程,并渴望试用我的 Java EE Vaadin 示例。但您没有安装 JDK,而且不想因为花一下午时间笨拙地安装一个 JDK 而将系统弄得一团糟。Docker 可以拯救您!

代码下载 的 java 目录中的 ArticleViewer.war 文件是一个包含来自 Vaadin 教程的多个 Vaadin UI 组件的应用程序。您可以通过 Docker 在 Tomcat 8 服务器上运行 ArticleViewer.war。第一步是从您之前拉取的镜像启动一个 Tomcat 8 容器实例:

docker run -it --rm -p 8888:8080 --name=tomcat_server -v $PWD:/mnt tomcat:8

这个命令现在看起来可能很熟悉。为了更方便引用,容器名为 tomcat_server。您目前的工作目录在容器内挂载为 /mnt。

-p 8888:8080 选项告诉 Docker 将主机系统的端口 8888 映射到容器的(内部)端口 8080。在 tomcat 镜像的 Dockerfile 中,您可以看到 Tomcat 在容器内的端口 8080 上执行监听。

通过浏览器访问 http://localhost:8888/,您可以在其中看到熟悉的 Tomcat 8 服务器欢迎页面:

在 Docker 容器内运行的 Tomcat 的屏幕截图

请注意您保持容器在交互式模式下运行(通过 -it 开关)。在该模式下,会在标准输出中连续显示日志,以方便开发。

附加到一个运行的 Docker 容器

现在,容器内挂载的目录 /mnt 是您当前的工作目录,ActiveViewer.war 位于该目录中。

将 ActiveViewer.war 从 /mnt 复制到 Tomcat 8 的 webapps 目录,它会在这里自动部署:

docker exec tomcat_server cp /mnt/ArticleViewer.war /usr/local/tomcat/webapps

这一次不需要使用 docker run 命令,因为 tomcat_server 容器已在运行。相反,您连接到运行的容器并运行一个命令。所以您使用了 docker exec,提供 cp /mnt/ArticleViewer.war /usr/local/tomcat/webapps 命令作为整个命令的最后一部分。

观看 tomcat_server 窗口中的日志,查看来自 ArticleViewer 应用程序部署的日志轨迹。

现在,在浏览器中访问应用程序的 URL(http://localhost:8888/ArticleViewer/)来打开正在运行的应用程序。单击左侧列表中的一篇文章,在右侧查看文章内容:

在 Docker 所启用的 Tomcat 8 上运行的 Vaadin article-viewer 应用程序

在继续后面的操作之前,停下来思考片刻。您没有在系统上下载或安装 JDK 或 Tomcat 8。只需几秒即可在一个功能全面的 Tomcat 8 服务器上运行 Vaadin 应用程序的代码 — 这得益于 Docker。

为 Tomcat 8 开发和构建 Java Web 应用程序

您刚才已经看到,可以使用 Docker 轻松地部署用于测试的 Web 应用程序。也可以通过 Docker 同样轻松地从 Java 源代码编译和构建 Web 应用程序 — 同上面一样,无需让您的开发系统变得一团糟或花很长时间安装前提软件。

将目录更改为 代码发行版 中的 java/LaurenLandscapesJava。这个目录中包含您在 Node.js 示例中看到的 Lauren’s Lovely Landscapes 应用程序的一个 Java 版本。(如果想了解该代码的更详细描述,请阅读 “Bluemix 基础:将一个示例 Java 应用程序部署到云中”。)

build.xml 文件是一个标准的 Apache Ant 构建文件,它包含编译代码和构建 WAR 文件的指令。通过使用 webratio Apache Ant 镜像,可以在更改代码后(或其他任何时刻)快速编译和构建 WAR:

docker run -it --rm -v "$PWD":/mnt webratio/ant bash -c 'cd /mnt; ant'

您现在已经熟悉了这个 docker run 命令中的所有内容。该命令在作为 /mnt 挂载在容器内的当前工作目录内运行 Ant 构建工具。Ant 编译 src 子目录中的所有 Java 源代码,然后构建并绑定 WAR 文件,将它作为 lauren.war 放在 dist 子目录中。

将 lauren.war 从 java/LaurenLandscapesJava/dist 目录复制到 java 目录:

cp dist/lauren.war  ..

您可以使用此命令将 lauren.war 应用程序部署到 Tomcat 8:

docker exec tomcat_server cp /mnt/lauren.war /usr/local/tomcat/webapps

此命令附加到运行的 tomcat_server 容器,并将 lauren.war 文件复制到 Tomcat 8 的 webapps 子目录中用于自动部署。

现在,在浏览器中访问 http://localhost:8888/lauren/,您可以在其中看到在您的 Docker 化 Tomcat 8 上运行的 Lauren’s Lovely Landscape 的 Java 版本。


从 Java 切换到 Python

假设作为一位多语言开发人员的您希望暂时不执行 Java 编码,探索 Python Bottle Web Framework 提供的可能性。为了帮助您快速探索 Python Web 应用程序开发,避免麻烦的安装和依赖项带来的痛苦,Docker 再次挽救了您。

更改到代码发行版的 python/Laurens.Lovely.Landscapes 目录。该目录中包含 Lauren’s Lovely Landscape 应用程序的 Python 版本 — 为 Bottle Web 框架而编写。模板(.tpl 文件)位于 views 子目录中。(有关该代码的更多信息,请查阅 “Bluemix 和 DevOps Services 简介,第 1 部分:部署和更新一个简单应用程序。”)还可以在关联的 Bluemix DevOps Services 存储库 中找到此代码的一个版本。

运行这个 Python 应用程序 — 而不将 Python 安装在您的系统上— 通过以下 Docker 命令(将它们键入为一行):

docker run -it --rm --name lllpython -p 8000:8000 -v "$PWD":/usr/src/myapp -w 
/usr/src/myapp python:3.5 python wsgi.py

现在在浏览器中访问 http://localhost:8000/,您可以在其中看到正在运行的 Lauren’s Lovely Landscapes 应用程序的 Python 版本。

在 Python 中开发时,您可以更改代码并重新运行之前的命令来进行测试。运行 Docker 容器就像运行安装在本地的开发工具一样快。


添加和启动一个分离的数据库

大多数现代 Web 应用程序都涉及 3 层:浏览器作为客户端;一个中间层应用服务器,比如 Express、Tomcat 8 或 Bottle;以及一个后端数据库。通过使用 Docker,您可以快速添加一个数据库,无需将它安装在您的开发系统上。

在这个最后的示例中,您将一个 Apache CouchDB 数据库服务器添加到您的开发环境中。您可以使用 CouchDB API(它与 IBM Cloudant NoSQL 数据库完全兼容)在本地测试 CouchDB 或 Cloudant 代码。

更改到源代码发行版中的数据库目录。这个目录中包含 Lauren’s Lovely Landscapes 应用程序的高级 Node.js 版本。此版本从 CouchDB(或 Cloudant)抓取印刷品商店的库存信息。该网络商店依据可用印刷品的库存来动态更改外观。(您可以在 “Bluemix 基础:将 Cloudant NoSQL 数据库添加到您的 Node.js 应用程序中” 中找到该代码的更多信息。)

要启动 Apache CouchDB 的一个实例,可以使用这个 Docker 命令(将它键入为一行):

docker run -d --name couchdb -p 5984:5984 -e COUCHDB_USERNAME=user -e 
COUCHDB_PASSWORD=abc123 -e COUCHDB_DBNAME=prints -v $PWD/data:/data frodenas/couchdb

该命令使用 COUCHDB_USERNAMECOUCHDB_PASSWORDCOUCHDB_DBNAME 环境变量来配置实例,以便与代码中使用的值匹配。当前的工作目录在容器内挂载为 /data。CouchDB 将数据写入此目录中,使您可以重新启动容器而不丢失数据。

请注意,在这个示例中,您使用了 -d 选项运行该容器,而不是使用 -it --rm 选项。-d 选项启动分离的 CouchDB 实例。您可以使用 docker ps 命令查看所有正在运行的分离的 docket 容器。

接下来安装应用程序的 Node.js 依赖项:

docker run -it --rm --name llldepends -v "$PWD":/usr/src/myapp -w /usr/src/myapp node:0.10.40 npm install

Apache CouchDB 实例还没有任何数据。使用以下命令(将它键入为一行)来运行 dataseeder.js 代码,该代码在 Apache CouchDB 实例中创建一个文档,以便在商店中填充库存:

docker run -it --rm --name dataseeder --net="host" -v "$PWD":/usr/src/myapp -w 
/usr/src/myapp node:0.10.40 node dataseeder.js

在端口 8011 上运行 lllnode 应用程序:

docker run -it --rm --name lllnode --env PORT=8011 --net="host" -v "$PWD":/usr/src/myapp
-w /usr/src/myapp node:0.10.40 node app.js

在浏览器中访问 http://localhost:8011/ 来打开 Lauren’s Lovely Landscapes 商店:

该屏幕截图表明澳大利亚印刷品已脱销

您可以看到澳大利亚的印刷品已脱销。该产品类别突出显示为红色,无法选中。

为了模拟对澳大利亚印刷品补货的过程,您将访问 Apache CouchDB 文档并直接修改库存水平。

Apache CouchDB 提供了一个简单的 GUI(名为 Futon)来访问存储的文档。在浏览器中访问 http://127.0.0.1:5984/_utils/ 来打开 Futon GUI:

CouchDB Futon GUI 的屏幕截图

在 Futon 中,选择 prints。打开文档并展开编号为 3 的库存项。双击澳大利亚印刷品以打开文档进行编辑。将 quan 字段从 0 更改到 3。单击右侧的小型绿色勾号图标,然后单击左上角的 Save Document

使用 Futon 在 Apache CouchDB 中修改并保存一个 NoSQL 文档的屏幕截图

数据库中的数量更新后,在浏览器中重新加载页面 http://localhost:8011/。您可以看到商店现在已屯满货。澳大利亚印刷品不再标记为红色,而且可以选中。

使用 3 层 Web 应用程序时,Docker 简化了中间层应用程序编码的开发工作流,方便了即时本地部署后端数据库来实现测试。


结束语

在这个竞争激烈的时代,您承受不起没有将 Docker 整合进开发工作流中的生活。通过消除反复安装和卸载相互依赖的复杂开发人员包和工具的需求,Docker 可以缩短您的开发时间和探索时间。利用 Docker 来提高您的多语言开发智商,显著提高您的开发生产力。


下载

描述 名字 大小
代码示例 code_docker.zip 22MB

参考资料

from:http://www.ibm.com/developerworks/cn/web/wa-docker-polyglot-programmers/index.html?ca=drs-