[干货] Flume综述与实例

Flume是一个分布式的、可靠的数据收集、集合和移动的组件。基于流式数据模型,非常健壮、支持容错、故障转移等特性。本用实例辅助说明Flume的大部分核心概念。

这里写图片描述

版本记录:
2016-07-23 初稿


安装FLume

Flume的安装非常简单,其核心就是agent。

从官网下载稳定版本:

wget http://apache.fayea.com/flume/1.6.0/apache-flume-1.6.0-bin.tar.gz

tar zxvf apache-flume-1.6.0-bin.tar.gz

mv apache-flume-1.6.0-bin apache-flume-1.6.0

为了运行方便,我们把bin目录加到Path中:

vim /etc/profile
export FLUME_HOME=/opt/flume/apache-flume-1.6.0
export PATH=$PATH:$FLUME_HOME/bin
source /etc/profile

测试一下是否安装成功:

flume-ng help

这里写图片描述

入门例子

Flume的核心工作都是通过Flume Agent来完成的,Flume agent是一个长期运行的Java进程,其中运行着source和sink,source和sink之间通过channel连接。source作为生产者,产生数据,输送到channel,sink从则从channel中读取数据,并保存到HDFS之类的数据目的地。Flume中自带了非常丰富的各个组件实现。例如从目录中拉取新文件数据的spool source,收集运行命令输出结果的exec source。channel的实现由内存memory,文件file channel等。sink有简单的logger输出,HDFS sink,avro sink等。

数据在Flume中用事件event来表示,因此一个运行中的Flume agent就是一个event的流动系统,由source产生,传送到channel,再传送到sink用于存储或者下一步处理。

多个Flume Agent可以相互连接构成一个拓扑图,从而提供稳定的、高吞吐的数据收集系统。因此Flume系统中,核心是配置。使用Flume提供的丰富组件,构成满足自己需求的系统。如果原生组件不足以满足需求,完全可以扩展自己需要的组件,Flume提供了非常好的拓展点。

Agent的示意图如下:

这里写图片描述

假设我们现在想监控/tmp/spooldir目录下的文件变动,一旦有新增文件后,读取每一行,输出到控制台。我们首先配置一个名为test-agent.properties的文件,内容如下:

agent1.sources = source1
agent1.sinks = sink1
agent1.channels = channel1

agent1.sources.source1.channels=channel1
agent1.sinks.sink1.channel = channel1

agent1.sources.source1.type=spooldir
agent1.sources.source1.spoolDir=/tmp/spooldir

agent1.sinks.sink1.type=logger

agent1.channels.channel1.type=file

配置文件的属性名称采用级联的层次结构来设置各种属性。我们采用Flume自带的spooldir作为source,使用具有持久化特性的file channel以及简单的logger sink。

接下来先创建监控的目录:

mkdir /tmp/spooldir
````

使用flume-ng启动agent:




<div class="se-preview-section-delimiter"></div>

“`

flume-ng agent --conf-file /opt/flume/conf/spool-to-logger.properties --name agent1 --conf $FLUME_HOME/conf -Dflume.root.logger=INFO,console

这里写图片描述

日志中主要是一些source sink的启动信息。

接着我们在在一个终端中,往/tmp/spooldir中新增一个文件,为了保存新增文件的原子操作,我们先创建一个隐藏文件:

echo "Hello Flume" > /tmp/spooldir/.file1.txt

然后使用原子性操作mv改变为可见文件:

 mv /tmp/spooldir/.file1.txt /tmp/spooldir/file1.txt 

此时日志中可以看到如下输出:

这里写图片描述

成功。一行会被当做一个事件,我们写入一行,所以控制台收到一个事件的日志。多行文件的输出类似:

这里写图片描述

body的输出中是UTF-8编码格式。成功处理完之后,file channel中的文件名添加了COMPLETE的后缀,表示该文件已经处理过。

事务与可靠性

Flume使用独立的事务在source-channel和channel-sink之间传送事件。在上面的例子中,只有当事件被成功提交到file channel之后,原始文件才会被标记为完成(COMPLETE后缀)。类似的,从channel到logger输出控制台也封装在事务中,如果某种原因导致无法输出到控制台,则事务回滚,事件数据依然保存在file channel中。

file channel虽然提供了持久化,但是其性能较差,吞吐量会受到一定的限制。相反,memory channel则牺牲可靠性换取吞吐量,如果内存中的事件因为机器故障重启而丢失,则这些事件无法恢复。

Flume在传送事件时,保证至少一次到达(at-least-once),也就是说可能出现重复。例如,上述的spool source中,当重启Agent之后,如果文件在上次还没有被标识为完成,可能提交了部分数据到channel,因此重启后会重新处理这些未完成的文件。如果上次处理过程中,有些数据已经输出到控制台,但是事务还没有提交(在输出之后以提交之间发生故障),则这些事件会被重试,重现重复。

如果需要精确的一次到达,可以使用exactly onece,要达到精确一次到达,需要使用两阶段提交协议,这样的协议开销非常昂贵。因此Flume区别于传统的消息中间件的一点在于其使用至少一次到达来达到高容量的并发。而传统的消息中间件一般采用精确的一次到达。很多场合中,完全可以在数据的其他处理环节中对重复的数据进行去重,通常是采用MapReduce或者Hive作业。

批量处理

为了效率,Flume在一次事务中会尝试批量读取事件。这对于file channel的性能提升尤为明显,因此一次在一次file channel的事务中,需要产生一次昂贵的fsync调用。例如,在上面提到的spool source中,可以使用batchSize属性来配置一次读取多少行。在Avro sink中,在调用RPC发送事件给Avro source之前,也会尝试读取100个事件,然后再批量发送。当然,如果没有达到100个事件,不会阻塞。

HDFS Sink

Flume最初的出发点就是用于收集大量数据到Hadoop存储中。下面是一个HDFS sink的配置例如:

agent1.sources = source1
agent1.sinks = sink1
agent1.channels = channel1

agent1.sources.source1.channels = channel1
agent1.sinks.sink1.channel = channel1

agent1.sources.source1.type = spooldir
agent1.sources.source1.spoolDir = /tmp/spooldir

agent1.sinks.sink1.type = hdfs
agent1.sinks.sink1.hdfs.path = /tmp/flume
agent1.sinks.sink1.hdfs.filePrefix = events
agent1.sinks.sink1.hdfs.fileSuffix = .log
agent1.sinks.sink1.hdfs.inUsePrefix = _
agent1.sinks.sink1.hdfs.fileType = DataStream

agent1.channels.channel1.type = file

HDFS sink的sink 类型为hdfs,配置项包括文件路径,文件前缀后缀,文件格式等。

正在处理但是还未完成的文件会有一个.tmp后缀,正在处理的前缀使用inUsePredix属性设置,我们这里设置为下划线,在MapReduce作业中,会自动忽略下划线开头的文件。一个典型的临时文件(未完成)名称如_events.1386734789.log.tmp,其中的数字是HDFS Sink生成的时间戳。

文件会一直处于打开状态,直到下面的任意条件满足:

  • 时间达到30秒(通过hdfs.rollInterval属性配置)
  • 达到hdfs.rollSize配置的大小(默认为1024字节)
  • 事件数量达到hdfs.rollCount配置的数量(默认为10)

当上述任意条件满足之后,文件被关闭,前缀后缀被移除。新到来的时间写入新的文件,然后继续上述过程。

HDFS sink使用运行agent的用户名写入HDFS,可以通过hdfs.proxyUser配置。

分区与拦截器

大量的数据集经常会进行分区,使用时间来分区是很常见的一种形式,例如每天一个分区,然后MapReduce作业定期处理分区。通过设置HDFS Sink的Path,很容易对数据进行分区:

agent1.sinks.sink1.hdfs.path = /tmp/flume/year=%Y/month=%m/day=%d

上述配置按照天对数据进行分区。如果使用Hive,一样可以映射到Hive中的分区和Buckets。

事件被写入哪一个分区,根据事件头部中的timestamp来判断。默认情况下,事件头部中没有时间,但是可以配置一个时间拦截器来添加。Flume的拦截器(Interceptor)机制用于修改或者删除事件数据,拦截器被绑定到source中,并在事件到达channel之前运行。下面的配置为source1添加了一个拦截器:

agent1.sources.source1.interceptors=interceptor1
agent1.sources.source1.interceptors.interceptor1.type=timestamp

这个时间戳是在agent机器上产生事件的时间戳,如果agent运行在大规模的agent拓扑中,数据HDFS的时间可能与时间产生的时间有较大差别。HDFS Sink提供了一个属性用于配置使用数据到达HDFS的时间作为时间戳,这个属性是hdfs.userLocalTimeStamp.

文件格式

通常情况下,使用二进制存储数据所需要的空间会比存储为文本所需空间小。HDFS sink提供了hdfs.fileType用于控制文件类型。
这个属性默认为SequenceFile,这种文件格式使用时间的时间戳作为LongWritable Key,然后没有时间戳头部,则使用当前时间。事件的body作为BytesWritable写入到value。如果value要保存为Text,可以将hdfs.writeFormar设置为Text。

如果要以Avro的文件格式写入数据,hdfs.fileType设置为DateStream。另外需要设置一个值为avro_event的serializer属性(没有hdfs.前缀)。serializer.compressionCodec属性用于设置压缩。

agent1.sinks.sink1.type=hdfs
agent1.sinks.sink1.hdfs.path=/tmp/flume
agent1.sinks.sink1.hdfs.filePrefix=events
agent1.sinks.sink1.hdfs.fileSuffix=.avro
agent1.sinks.sink1.hdfs.fileType=DataStream
agent1.sinks.sink1.hdfs.serializer=avro_event
agent1.sinks.sink1.hdfs.serializer.compressionCodec=snappy

一个事件将被转化为一条Avro记录,这个记录中有2个filed:headers和body。headers是一个字符串Avro Map,body则为Avro Bytes。avro记录的schema也可以自定义。

如果要将内存中的Avro 对象发送到Flume,可以选择使用Log4jAppender,这个Appender允许我们将Avro的通用对象或者特定对象(generic,specifix)写入到日志中,然后发送到Avro Source(充当Avro RPC Server)。此时serializer的值要设为:

org.apache.flume.sink.hdfs.AvroEventSerializer$Builder

Avro schema在header中设置。如果要将其他对象转化为Avro对象(进而发送给Avro Source),可以通过实现AbstractAvroEventSerializer来完成。

Fan Out

Fan out(扇出)是指一个source的数据被传送给多个channel。事件发生时,同时发送给多个channel,进而到达多个sink(一个sink只能对应一个channel)。例如我们想把数据传到HDFS进行存档,同时想把数据放到Solr或者ElasticSearch进行搜索,此时可以给source配置两个channel:

agent.sources.source1.channels=filechannel solrchannel

下图是同时传送到file channel和memory的示意图:

这里写图片描述

当一个source配置有多个channel时,Flume针对每个channel使用单独的事务进行数据传送。在上面的例子中,Flume将使用一个事务把数据从spool目录传送到file channel。另一个memory channel则使用另一个事务。如果两个事务都失败(例如因为空间不足),则事件不会从source中移除,而是继续保留在source,稍后再重试。

如果我们对某个channel的数据完整性比较不在乎,例如上述的logger sink,我们不太关心其数据可靠性,因此我们可以把这个channel配置为可选的:

agent1.sources.source1.selector.optional = memorychannel

此时,如果memory channel对应的事务失败了,则事件不会被留在source被在稍后重试,而是忽略掉optional channel的事务失败,把事件从source中移除。

在正常的fan-out模式中,事件被复制到所有的channel,但是Flume还提供了更灵活的选择。所以我们可以把某些事件发送到channela,把另一个类型的事件发送到channelb。要达到这种多路复用的效果,可以使用multiplexing selector设置source,使得我们可以精确定义事件到channel的路由或者映射关系。

分布式Agent拓扑

当我们有多个数据源时,在把数据送到类似HDFS的存储之前,我们可能想先做一次聚合或者预处理。例如,如果数据要存到HDFS中供MapReduce作业处理,如果数据以无数的小文件存在,对MapReduce的性能是极不利的。因此我们可以在数据原始来源处部署Agent,然后多个来源的数据先做一个去重和聚合,组成比较大的文件,然后再汇总到HDFS。此时Agent之间是分层的:

这里写图片描述

上图中,tier2对tier1的数据进行聚合,他们之间的连接通过tier1一个特殊的sink和tier2一个特殊的source完成。tier1使用Avro sink通过RPC将事件远程发送给位于tier2的Avro source。此时tier1的sink充当的是RPC的客户端调用,tier2Avro source则充当RPC Server。
注意这里的Avro sink和source并没有读写Avro文件的能力,它们之间只是使用AVRO RPC作为通信协议,传输事件。如果需要使用sink把数据写入到Avro文件中,则需要使用类似HDFS的sink来完成。

第一层的agent配置如下:

agent1.sources = source1
agent1.sinks = sink1
agent1.channels = channel1
agent1.sources.source1.channels = channel1
agent1.sinks.sink1.channel = channel1
agent1.sources.source1.type = spooldir
agent1.sources.source1.spoolDir = /tmp/spooldir
agent1.sinks.sink1.type = avro
agent1.sinks.sink1.hostname = localhost
agent1.sinks.sink1.port = 10000
agent1.channels.channel1.type = file
agent1.channels.channel1.checkpointDir=/tmp/agent1/file- channel/checkpoint
agent1.channels.channel1.dataDirs=/tmp/agent1/file- channel/data

第二层agent配置如下:

agent2.sources = source2
agent2.sinks = sink2
agent2.channels = channel2
agent2.sources.source2.channels = channel2
agent2.sinks.sink2.channel = channel2
agent2.sources.source2.type = avro
agent2.sources.source2.bind = localhost
agent2.sources.source2.port = 10000
agent2.sinks.sink2.type = hdfs
agent2.sinks.sink2.hdfs.path = /tmp/flume
agent2.sinks.sink2.hdfs.filePrefix = events
agent2.sinks.sink2.hdfs.fileSuffix = .log
agent2.sinks.sink2.hdfs.fileType = DataStream
agent2.channels.channel2.type = file
agent2.channels.channel2.checkpointDir=/tmp/agent2/file- channel/checkpoint
agent2.channels.channel2.dataDirs=/tmp/agent2/file- channel/data

最后的结构图如下:

这里写图片描述

在Flume Agent中,分别使用不同的事务在source-channel和channel-sink之间传递事件,保证事件的可靠传递。在Avro Source-Sink的情况下,Flume也使用事务能确保事件可靠地从Avro sink传输到Avto source并且写入channel。

在agent1中,Avro Sink从channel中读取事件(批次),并且发送给agent1的source,这一整个逻辑被包含在一个事务中,只有当sink收到来自agent2 Avro Source的确认消息之后,才会提交事务。消息确认采用异步机制。
在agent2中,Avro source读取来自agent1的事件,并写入到channel的逻辑也封装在一个事务中,只有当数据确保写入channel之后,才会向agent1发送确认消息。从而确保事件从一个agent的channel可靠传送到另一个agent的channel。

在这样的架构中,agent1和agent2只要其中一个出现故障,数据就无法被传送到HDFS。为了解决这个问题,可以使用下一节中的Sink group来达到故障转移。

Sink Group

Sink group在逻辑上封装一组sink,使用时(与Channel配合时)当做一个sink使用。利用sink group,可以达到负载均衡、故障转移等目的。例如,下图中的两个tier2 agent被封装成成一个sink group,当其中一个不可用时,数据被发往另一个,避免全部中断。

这里写图片描述

看一个sink group的配置:

agent1.sources = source1
agent1.sinks =sink1a sink1b
#group
agent1.sinkgroups= sinkgroup1
agent1.channels = channel1

#link
agent1.sources.source1.channels= channel1
agent1.sinks.sink1a.channel=channel1
agent1.sinks.sink1b.channel=channel1

#sink group
agent1.sinkgroups.sinkgroup1.sinks=sink1a sink1b
agent1.sinkgroups.sinkgroup1.processor.type=load_balance
agent1.sinkgroups.sinkgroup1.processor.backoff=true

#source1
agent1.sources.source1.type=spooldir
agent1.sources.source1.spoolDir=/tmp/spooldir

#sink1a
agent1.sinks.sink1a.type=avro
agent1.sinks.sink1a.hostname=slave1
agent1.sinks.sink1a.port=10000

#sink1b
agent1.sinks.sink1b.type=avro
agent1.sinks.sink1b.hostname=slave1
agent1.sinks.sink1b.port=10001

#channel1
agent1.channels.channel1.type=file

agent1.channels.chennel1.checkpointDir=/opt/flume/checkpoint/agent1/file-channel/checkpoint
agent1.channels.channel1.dataDir=/opt/flume/data/agent1/file-channel/data

上述配置中,我们配置了2个sink共享一个file channel,并使用avro将数据传递到下一级agent。这些都是常规配置,除此之外,我们为agent定义了sink group:

agent1.sinkgroups= sinkgroup1

以及相应的sink group属性。这个group包含sink1a和sink1b,type配置为load_balance,Flume事件将被负载均衡到这两个sink,默认采用round-robin决定事件应该发送到哪一个sink,如果sink1a不可用,则发往sink1b,如果两个都不用,则事件保留在file channel。负载均衡的策略可以通过processor.selector来修改。

默认情况下,sink的不可用是不会被processor记住的,如果这一次sink1a不可用,下一批事件的时候,还会再次尝试sink1a,这可能导致效率很低。因此,我们配置了processor.backoff=true,当某个sink不可用时,就会被加入黑名单列表中,一定时间之后再从黑名单中移除,继续被尝试。黑名单的最长有效期通过processor.selector.maxTimeOut配置。

另一种processor的类型是failover,这种类型的sink group,事件被发送到一个优先的sink,如果这个优先的sink不可用,则切换到备用的sink。failover sink processor在组内维护一个优先级顺序,分发事件时,按照优先级从高到低依次分发直到有可用的sink。不可用的sink将被暂时加入黑名单,时间通过processor.maxpenalty配置,最长30秒。

在下一级agent中,我们再10000和10001端口分配配置两个avro source。agent2a的配置如下:

agent2a.sources=source2a
agent2a.sinks = sink2a
agent2a.channels = channel2a

agent2a.sources.source2a.channels =channel2a
agent2a.sinks.sink2a.channel=channel2a

agent2a.sources.source2a.type=avro
agent2a.sources.source2a.bind=slave1
agent2a.sources.source2a.port=10000

agent2a.sinks.sink2a.type=hdfs
agent2a.sinks.sink2a.hdfs.path=/tmp/flume
#避免冲突
agent2a.sinks.sink2a.hdfs.filePrefix = events-a
agent2a.sinks.sink2a.hdfs.fileSuffix = .log
agent2a.sinks.sink2a.hdfs.fileType = DataStream

agent2a.channels.channel2a.type=file

agent2a.channels.chennel2a.checkpointDir=/opt/flume/checkpoint/agent2a/file-channel/checkpoint
agent2a.channels.channel2a.dataDir=/opt/flume/data/agent2a/file-channel/data

agent2b的配置完全类似:

agent2b.sources=source2b
agent2b.sinks = sink2b
agent2b.channels = channel2b

agent2b.sources.source2b.channels =channel2b
agent2b.sinks.sink2b.channel=channel2b

agent2b.sources.source2b.type=avro
agent2b.sources.source2b.bind=slave1
agent2b.sources.source2b.port=10001

agent2b.sinks.sink2b.type=hdfs
agent2b.sinks.sink2b.hdfs.path=/tmp/flume
#避免冲突
agent2b.sinks.sink2b.hdfs.filePrefix = events-b
agent2b.sinks.sink2b.hdfs.fileSuffix = .log
agent2b.sinks.sink2b.hdfs.fileType = DataStream

agent2b.channels.channel2b.type=file

agent2b.channels.chennel2b.checkpointDir=/opt/flume/checkpoint/agent2b/file-channel/checkpoint
agent2b.channels.channel2b.dataDir=/opt/flume/dataagent2b/file-channel/data

配置了hdfs的文件前缀,是为了避免两个agent同时写入的时候出现冲突。如果二级的agent部署在不同的机器上,可以配置一个hostname拦截器,然后使用hostname作为文件前缀:

agent2a.sinks.sink2a.hdfs.filePrefix=event-%{host}

最终整个示意图如下:

这里写图片描述

OK,我们在真实环境中运行一下这个系统:
在/opt/fluem/conf目录下放着我们刚才的三个属性文件:

spool-tier1.properties
spool-tier2-a.properties
spool-tier2-b.properties

启动HDFS:

start-dfs.sh

使用jps确保正常启动。

启动tier2的agent:

flume-ng agent --conf-file /opt/flume/conf/spool-tier2-a.properties --name agent2a --conf $FLUME_HOME/conf -Dflume.root.logger=INFO,console

flume-ng agent --conf-file /opt/flume/conf/spool-tier2-a.properties --name agent2a --conf $FLUME_HOME/conf -Dflume.root.logger=INFO,console

这里写图片描述

启动tier1的agent:

flume-ng agent --conf-file /opt/flume/conf/spool-tier1.properties --name agent1 --conf $FLUME_HOME/conf -Dflume.root.logger=INFO,console

往/tmp/spooldir目录新增文件。可以在日志中看到,正常情况下,agent2a和agent2b采用round-robin方式轮流,如果我们退出其中的agent2a,则此时负载全部落到agent2b上。

在应用中基础Flume

Avro Source作为一个RPC Server,可以接受RPC请求,所以可以写客户端程序往Avro Source发送事件。

Flume SDK提供了Avro和Thrift Source的客户端接口,通过这个SDK,很容易将事件发送到Avro Source或者Thrift Source。同时支持负载均衡、故障转移等。

Flume嵌入式的Agent提供了类似的功能,它运行在一个Java引用程序中,可以通过其暴露的EmbeddedAgent对象发送时间,事件的接受端目前只支持Avro。

Flume组件一览

上文中只提到了Flume的部分组件,除此外还有很多组件可供使用和扩展,下表列出一些常见的组件:

类别 组件 描述
Source Avro 监听Avro RPC调用(来自Avro sink或者Flume SDK)
Source Exec 运行Unix命令,例如tail -f,并将标准输出中的每一行转化成一个事件,注意该source不保证传送事件到channel
HTTP 在端口上监听,并通过可插拔的Handler将请求转化为事件
JMS 从JMS队列或者topic读取消息,转化为事件
Netcat 监听端口,每行文本转化为事件
Sequencegenerator 递增生成序列号,用于测试
Spooling directory 检测目录中新增文件,把文件中的每一行转化为事件
Syslog 从系统日志中读取每一行并转化为事件
Thrift 监听端口,接受Thrift RPC调用(来自Thrift Sink或者FLume SDK)
Twitter 对接Twitter的Straming API
Sink Avro 通过Avro RPC发送事件到Avro Source
Elasticsearch 按照Logstash的格式将事件写入ES集群
File roll 将事件写入本地文件系统
HBase 使用指定的serializer将事件写入HBase
HDFS 以文本、序列文件、Avro或者其他格式写入事件到HDFS
IRC 发送事件到IRC channel
Logger 使用SLF4J将事件输出到日志,测试用
Morphline(Solr) 常用与加载数据到Solr,运行一系列Marphline命令
Null 忽略事件
Thrift 使用Thrift RPC发送事件到Thrift Source
Channel File 事件保存在本地文件
JDBC 事件写入到嵌入式的Derby数据库
Memory 在内存队列中保存事件
Interceptor Host 添加agent所在的机器的host或者ip到事件头部
Morphline 基于Morphline配置文件过滤事件或者修改头部
Regex extractor 从事件body中提取匹配的表达式
Static 设置固定的Header到事件
TimeStamp 设置时间戳头部
UUID 设置id头部

参考资料

  • 《Hadoop权威指南》

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
本文由EasyDarwin开源团队成员Alex贡献: http://blog.csdn.net/cai6811376/article/details/52006958 EasyDarwin云平台一直在稳步的升级迭代中,近日,EasyDarwin云平台实现了语音对讲的功能。对讲功能的加入,标志着EasyDarwin云平台进一步的完善。 流程设计 客户端使用POST的方式在body中携带协议报文向云平台发送开始对讲命令; 云平台组织协议报文向指定的设备发送; 设备执行开始对讲命令后向云平台返回相应报文; 云平

Spring事务管理(5)-开启事务 - 2016-07-25 18:07:44

在前几篇文章中,我们分析了Spring的AOP实现,AOP的实现原理即JdkDynamicAop或Cglib。目标方法执行的时候,进入invoke方法中先执行Advisors链。Spring的事务开启需要在目标方法执行前进行,因此可以作为一个前置增强添加到Advisors链中。 Spring的声明式事务配置如下: bean id = "transactionManager" class = "org.springframework.orm.hibernate3.HibernateTransactionMa

Linux之vim学习 - 2016-07-25 18:07:26

vim 分三种模式:一般模式、编辑模式、命令模式 1.一般模式 一般模式下可以进行移动光标、删除、粘贴复制等操作 移动光标操作 h或向左箭头:光标左移动一个字符j或向下箭头:光标下移动一个字符k或向上箭头:光标上移动一个字符l或向右箭头:光标右移动一个字符 30 j:光标下移动 30 个字符ctrl+f:屏幕向下移动一页,相当于Page Down (常用)ctrl+b:屏幕向上移动一页,相当于Page Down (常用)ctrl+d:屏幕向下移动半页ctrl+u:屏幕向上移动半页nspace右移动n个字符
1.DispatchAction-分派Action 1.1 为什么需要DispatchAction 如果每个请求都对应一个Action,就会造成action过多,程序显得比较臃肿,所以可以把一类请求写到一个action中处理,即DispatchAction 在没有使用框架之前,当我们通过一个控制器处理多个请求的时候,我们是通过在URL后面跟上参数来区别不同的操作,比如,下述链接: http://localhost:8080/UserManager/main?operateId=adduid=123 htt

[置顶] 逐步深入TCP/IP协议栈 - 2016-07-25 18:07:17

一、关于应用层用户数据的历程,见下图:                                                                             TCP/IP数据包的封装 过程: 应用层将数据通过协议栈逐层向下传递,其下的每层接到来自上层的数据时,根据每层的协议都要在其数据 的前端添加首部信息进行封装。不同的协议层对数据包有不同 的称谓,在传输层叫做数据段,在网络层叫做数据报, 在链路层叫做数据帧。在经过链路层时,数据已经封装成帧并递交给物理层的传输介质上,到

Linux命令应用大词典 目录 - 2016-07-25 18:07:40

【作者】 於岳 【编辑】 李永涛 【ISBN】 978-7-115-40151-9 【日期】 2015-12 【页数】 703页 【字数】 1123千字 【开本】 16 【定价】 89元 【总数】 本书包含46大类729个命令 按章节分 第1章 登录、退出、关机和重启 章节-页码 命令 介绍 备注 1.1-P1 login 用户登录系统 1.2-P1 logout 退出登录Shell 1.3-P1 nologin 限制用户登录 1.4-P2 exit 退出Shell 1.5-P2 sulogin 单用户登

Dubbo--生态系统安装 - 2016-07-25 18:07:33

dubbo的整套环境主要有几个系统: Zookeeper注册中心安装 管理控制台安装 简易监控中心安装       在 Dubbo官网 中已经讲的很详细。本文主要是为了记录一下自己在安装过程遇到的问题,同时也把安装完成之后的文件包记录 下。       运行环境: tate@ubuntu:~$ uname -aLinux ubuntu 3.19.0-65-generic #73~14.04.1-Ubuntu SMP Wed Jun 29 21:05:22 UTC 2016 x86_64 x86_64 x8
1.使用者与群组以及其他人的概念 假设有一家人,家裡只有三兄弟,分别是王大毛、王二毛与王三毛三个人, 而这个家庭是登记在王大毛的名下的!所以,『王大毛家有三个人,分别是王大毛、王二毛与王三毛』, 而且这三个人都有自己的房间,并且共同拥有一个客厅! 使用者的意义:由于王家三人各自拥有自己的房间,所以, 王二毛虽然可以进入王三毛的房间,但是二毛不能翻三毛的抽屉! 因为抽屉里面面可能有三毛自己私人的东西,例如日记等等的,这是『私人的空间』,所以当然不能让二毛拿!  群组的概念:由于共同拥有客厅,所以王家三兄弟可

理解 glibc malloc - 2016-07-25 17:07:29

本篇文章主要完成了对 《Understanding glibc malloc》 的翻译工作。限于本人翻译水平与专业技术水平(纯粹为了了解内存分配而翻),本文章必定会有很多不足之处,请大家见谅,也欢迎大家的指正! 联系邮箱: 974985526@qq.com 。 堆内存是一个很有意思的领域,这样的问题: 堆内存是如何从内核中分配的? 内存管理效率怎样? 它是由内核、库函数还是应用本身管理的? 堆内存可以开发吗? 我也困惑了很久,但是直到最近我才有时间去了解它。下面就让我来谈谈我的研究成果。开源社区提供了很多
一、别人记得流水账 MAC上装东西总是遇到些问题,这里做个流水帐。希望能对别人有点帮助哈   1、先下载需要的软件包        OCR工具:   Tesseract-OCR3.0.1  source code      tesseract-ocr-3.01.eng.tar.gz  破验证码用英文就够了。        图像处理工具:   Leptonica   1.68        png识别工具:   libpng         jpeg 识别工具 : libjpeg         tif 识