调试内存泄漏问题的一些经验

内存泄漏(memory leak)是软件中经常遇到的一类问题,这类问题又是比较难以检测的,通常我们在程序遇到Out Of Memory的异常时才会注意到。拿到Out Of Memory的dump文件后,如何分析dump文件找到内存泄漏的线索又是一个难点。这篇文章分享了一些在Windows平台如何调试,检测C++和C#的内存泄漏的一些经验。

一、内存泄漏的Dump分析

通常拿到Out Of Memory的dump之后,用windbg打开,抛出异常的call stack一般是在分配内存时,这个call stack其实意义不大,我们需要知道的是内存为什么被用完了。

一般第一个要运行的命令是!address -summary,它会给出一个内存情况的总结,如下所示。

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
0:000> !address -summary
— Usage Summary —————- RgnCount ———– Total Size ——– %ofBusy %ofTotal
Heap 340 3c876000 ( 968.461 Mb) 48.38% 47.29%
<unknown> 2001 2c3b5000 ( 707.707 Mb) 35.36% 34.56%
Image 1115 119cf000 ( 281.809 Mb) 14.08% 13.76%
Free 544 2e40000 ( 46.250 Mb) 2.26%
Stack 139 2b00000 ( 43.000 Mb) 2.15% 2.10%
Other 95 8a000 ( 552.000 kb) 0.03% 0.03%
TEB 43 2b000 ( 172.000 kb) 0.01% 0.01%
PEB 1 1000 ( 4.000 kb) 0.00% 0.00%
— Type Summary (for busy) —— RgnCount ———– Total Size ——– %ofBusy %ofTotal
MEM_PRIVATE 889 46725000 ( 1.101 Gb) 56.31% 55.04%
MEM_MAPPED 1686 24707000 ( 583.027 Mb) 29.13% 28.47%
MEM_IMAGE 1159 12384000 ( 291.516 Mb) 14.56% 14.23%
— State Summary —————- RgnCount ———– Total Size ——– %ofBusy %ofTotal
MEM_COMMIT 3425 717c1000 ( 1.773 Gb) 90.71% 88.66%
MEM_RESERVE 309 b9ef000 ( 185.934 Mb) 9.29% 9.08%
MEM_FREE 544 2e40000 ( 46.250 Mb) 2.26%
— Protect Summary (for commit) – RgnCount ———– Total Size ——– %ofBusy %ofTotal
PAGE_READWRITE 2300 5e7fd000 ( 1.477 Gb) 75.54% 73.83%
PAGE_EXECUTE_READ 201 abb4000 ( 171.703 Mb) 8.58% 8.38%
PAGE_READONLY 656 616f000 ( 97.434 Mb) 4.87% 4.76%
PAGE_WRITECOPY 125 20d3000 ( 32.824 Mb) 1.64% 1.60%
PAGE_READWRITE|PAGE_GUARD 86 bb000 ( 748.000 kb) 0.04% 0.04%
PAGE_EXECUTE_READWRITE 44 a4000 ( 656.000 kb) 0.03% 0.03%
PAGE_EXECUTE_WRITECOPY 13 6f000 ( 444.000 kb) 0.02% 0.02%
— Largest Region by Usage ———– Base Address ——– Region Size ———-
Heap 7f30000 fd0000 ( 15.813 Mb)
<unknown> 12670000 4020000 ( 64.125 Mb)
Image 68f27000 19cb000 ( 25.793 Mb)
Free 717f2000 7e000 ( 504.000 kb)
Stack 2cf0000 fd000 (1012.000 kb)
Other 7efb0000 23000 ( 140.000 kb)
TEB 7eeb0000 1000 ( 4.000 kb)
PEB 7efde000 1000 ( 4.000 kb)

第一个部分是使用情况,按照大小排序。通常排第一的不是Heap就是<unkown>Heap是C++的非托管内存,<unkown>是C#的托管内存。

第二个部分是类型情况,分了3类,分别是:

  1. MEM_PRIVATE:当前进程独占的内存。
  2. MEM_MAPPED:映射到文件的内存,这些文件不属于进程程序本身,比如Memory Mapping File。
  3. MEM_IMAGE:映射到进程程序的内存,比如程序加载的dll。

最后一个部分是最大连续内存,比如上图中我们可以看到现在最大的连续可用内存只有500k了。

非托管(C++)内存泄漏分析

如果!address -summary的输出中发现Heap被用掉了很多,那很有可能有C++的内存泄漏,我们需要检查堆(heap)来找到可疑的对象。

1.通过!heap -s来看堆的使用情况,会把堆按照大小列出来,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
0:000> !heap -s
LFH Key : 0x352f041c
Termination on corruption : DISABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
—————————————————————————–
Virtual block: 39ee0000 – 39ee0000 (size 00000000)
00520000 00000002 619112 617928 619112 1712 2165 95 1 0 LFH
00ef0000 00001002 1088 324 1088 6 5 2 0 0 LFH
02590000 00001002 1088 316 1088 12 7 2 0 0 LFH
02aa0000 00001002 1088 256 1088 4 4 2 0 0 LFH
00340000 00001002 64 12 64 1 2 1 0 0

2.我们用!heap -stat -h <HeapEntry>来看最大的那个堆,它会列出这个堆上分配的所有对象的统计情况。如果幸运的话我们会看到某个大小的对象数目非常大,占用了很多内存。比如下面的例子中大小为1f64的对象有0x76c6个,占了99%的内存。

1
2
3
4
5
6
7
0:000> !heap -stat -h 00520000
heap @ 00520000
group-by: TOTSIZE max-display: 20
size #blocks total ( %) (percent of total busy bytes)
1f64 76c6 – e905f58 (99.99)
1800 1 – 1800 (0.00)
824 2 – 1048 (0.00)

3.我们可以用!heap -flt s <ObjSize>来列出所有大小是制定大小的对象。输出结果中的UserPtr就是对象的地址,然后可以用d命令来显示这个地址的内容。如果幸运的话,比如这个地址直接存了个字符串,那就好办多了。
4.还有一些情况我们可能能猜到是某些对象泄漏了。比如如果在!address -summary的输出里我们看到MEM_MAPPED大的离谱,而我们程序里所有的MemoryMappingFile都继承自某个基类,那么我们就可以直接看看内存中有多少个这类对象。
5.用命令x modulename!*classname*table*来找内存中虚表的地址。
6.用命令!heap -srch vtableaddress来找到所有的对象。
7.用命令dt modulename!classname objectaddress来看对象的内容是什么,接着就能分析出为什么这些对象有这么多。

托管(C#)内存泄漏分析

如果!address -summary的输出中发现<unkown>被用掉了很多,那很有可能有C#的内存泄漏,调试相对简单。

1.运行loadby sos mscorwks(.net4之前)或者loadby sos clr(.net4及以后)来加载SOS扩展。
2.运行!dumpheap -stat来看托管堆的统计信息,输出如下:

total 976456 objects Statistics: MT Count TotalSize Class Name 71497de4 1 12 System.Runtime.Remoting.Channels.Tcp.TcpClientTransportSinkProvider ...输出是按照TotalSize的递增顺序显示的,直接翻到最后一行,看看是哪个对象占用了最大的TotalSize。
3.运行!dumpheap -mt <mt>来把内存中这个Method Table的所有对象都列出来。结果的第一列就是对象的地址。
4.运行!do <address>来看这个对象的内容是什么。
5.运行!gcroot <address>来看这些对象是被谁引用的,这样多半就能找到发生内存泄漏的原因了。

GDI句柄超过限制

还有一种发生Out Of Memory异常的情况是GDI句柄超过限制了,可以看到dump中crash的call stack中是有关句柄操作的。默认情况下Windows的每个进程的GDI句柄额度是10000,可以通过注册表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\Windows\GDIProcessHandleQuota来修改这个值。

这种情况相对比较好处理,windows上的GDI对象就这些:Bitmap,Brush,DC,Enhanced metafile,Enhanced-metafile DC,Font,Memory DC,Metafile,Metafile DC,Palette,Pen and extended pen,Region。

二、内存泄漏的实时调试

如果可以非常容易重现的话,可以实时调试内存泄漏,这样就会容易很多了。

非托管内存泄漏检测

使用VLD来检测内存泄漏。

VLD是一个VC++的开源内存泄漏检测工具,非常易于使用。在调试器中运行程序,会在结束时生成一个内存泄漏的报告,包含内存分配的call stack。

打开“Create user mode stack trace database”,分析dump

可以用gflags打开“Create user mode stack trace database”,如下所示,这样就会记录下来每个对象创建的call stack,可以就可以很容易的查到泄漏对象是怎么创建出来的了。

使用Windbg的!heap -l命令。

  1. 收集dump,用Windbg打开,然后运行命令.logopen d:\leak.txt打开log。
  2. 运行!heap -l命令,会把所有泄漏的对象列出来,附带创建的call stack。可以很容易的写个程序来分析这个输出,合并重复的对象,计算总大小。

使用Windbg的!heap -p -a <address>命令

按照上面提到的非托管(C++)内存泄漏分析方法来分析dump,最后找到可以的对象时可以直接运行!heap -p -a <address>命令来看到这个地址的对象的创建call stack。

使用UMDH

UMDH是Windows Debugging Tools里的,和Windbg在同一个目录里,可以用UMDH收集多个内存的log,然后比较,找出泄漏的对象。

托管(C#)内存泄漏检测

Visual Studio 2013加入了调试托管内存的功能,在打开dump文件后可以选择”Debug Managed Memory”,可以看到托管对象的大小,数目,root等信息。

Debug Managed MemoryDebug Managed Memory

Managed Object ListManaged Object List

也可打开多个dump通过选择“Select baseline”进行内存比较,可以看到内存的变化。

Compare Managed MemoryCompare Managed Memory

GDI句柄监测

GDIView是一个免费的小工具,可以监测GDI使用情况。

kafka学习笔记:知识点整理

一、为什么需要消息系统

复制代码
复制代码
1.解耦:
  允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。
2.冗余:
  消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的"插入-获取-删除"范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。
3.扩展性:
  因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。
4.灵活性 & 峰值处理能力:
  在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。
5.可恢复性:
  系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。
6.顺序保证:
  在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。(Kafka 保证一个 Partition 内的消息的有序性)
7.缓冲:
  有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。
8.异步通信:
  很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。
复制代码
复制代码

 

二、kafka 架构

2.1 拓扑结构

如下图:

图.1

2.2 相关概念

如图.1中,kafka 相关名词解释如下:

复制代码
复制代码
1.producer:
  消息生产者,发布消息到 kafka 集群的终端或服务。
2.broker:
  kafka 集群中包含的服务器。
3.topic:
  每条发布到 kafka 集群的消息属于的类别,即 kafka 是面向 topic 的。
4.partition:
  partition 是物理上的概念,每个 topic 包含一个或多个 partition。kafka 分配的单位是 partition。
5.consumer:
  从 kafka 集群中消费消息的终端或服务。
6.Consumer group:
  high-level consumer API 中,每个 consumer 都属于一个 consumer group,每条消息只能被 consumer group 中的一个 Consumer 消费,但可以被多个 consumer group 消费。
7.replica:
  partition 的副本,保障 partition 的高可用。
8.leader:
  replica 中的一个角色, producer 和 consumer 只跟 leader 交互。
9.follower:
  replica 中的一个角色,从 leader 中复制数据。
10.controller:
  kafka 集群中的其中一个服务器,用来进行 leader election 以及 各种 failover。
12.zookeeper:
  kafka 通过 zookeeper 来存储集群的 meta 信息。
复制代码
复制代码

2.3 zookeeper 节点

kafka 在 zookeeper 中的存储结构如下图所示:

图.2

 

三、producer 发布消息

3.1 写入方式

producer 采用 push 模式将消息发布到 broker,每条消息都被 append 到 patition 中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保障 kafka 吞吐率)。

3.2 消息路由

producer 发送消息到 broker 时,会根据分区算法选择将其存储到哪一个 partition。其路由机制为:

1. 指定了 patition,则直接使用;
2. 未指定 patition 但指定 key,通过对 key 的 value 进行hash 选出一个 patition
3. patition 和 key 都未指定,使用轮询选出一个 patition。

附上 java 客户端分区源码,一目了然:

复制代码
复制代码
//创建消息实例
public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value) {
     if (topic == null)
          throw new IllegalArgumentException("Topic cannot be null");
     if (timestamp != null && timestamp < 0)
          throw new IllegalArgumentException("Invalid timestamp " + timestamp);
     this.topic = topic;
     this.partition = partition;
     this.key = key;
     this.value = value;
     this.timestamp = timestamp;
}

//计算 patition,如果指定了 patition 则直接使用,否则使用 key 计算
private int partition(ProducerRecord<K, V> record, byte[] serializedKey , byte[] serializedValue, Cluster cluster) {
     Integer partition = record.partition();
     if (partition != null) {
          List<PartitionInfo> partitions = cluster.partitionsForTopic(record.topic());
          int lastPartition = partitions.size() - 1;
          if (partition < 0 || partition > lastPartition) {
               throw new IllegalArgumentException(String.format("Invalid partition given with record: %d is not in the range [0...%d].", partition, lastPartition));
          }
          return partition;
     }
     return this.partitioner.partition(record.topic(), record.key(), serializedKey, record.value(), serializedValue, cluster);
}

// 使用 key 选取 patition
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
     List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
     int numPartitions = partitions.size();
     if (keyBytes == null) {
          int nextValue = counter.getAndIncrement();
          List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
          if (availablePartitions.size() > 0) {
               int part = DefaultPartitioner.toPositive(nextValue) % availablePartitions.size();
               return availablePartitions.get(part).partition();
          } else {
               return DefaultPartitioner.toPositive(nextValue) % numPartitions;
          }
     } else {
          //对 keyBytes 进行 hash 选出一个 patition
          return DefaultPartitioner.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
     }
}
复制代码
复制代码

3.3 写入流程

producer 写入消息序列图如下所示:

图.3

流程说明:

复制代码
复制代码
1. producer 先从 zookeeper 的 "/brokers/.../state" 节点找到该 partition 的 leader
2. producer 将消息发送给该 leader
3. leader 将消息写入本地 log
4. followers 从 leader pull 消息,写入本地 log 后 leader 发送 ACK
5. leader 收到所有 ISR 中的 replica 的 ACK 后,增加 HW(high watermark,最后 commit 的 offset) 并向 producer 发送 ACK
复制代码
复制代码

3.4 producer delivery guarantee

一般情况下存在三种情况:

1. At most once 消息可能会丢,但绝不会重复传输
2. At least one 消息绝不会丢,但可能会重复传输
3. Exactly once 每条消息肯定会被传输一次且仅传输一次

当 producer 向 broker 发送消息时,一旦这条消息被 commit,由于 replication 的存在,它就不会丢。但是如果 producer 发送数据给 broker 后,遇到网络问题而造成通信中断,那 Producer 就无法判断该条消息是否已经 commit。虽然 Kafka 无法确定网络故障期间发生了什么,但是 producer 可以生成一种类似于主键的东西,发生故障时幂等性的重试多次,这样就做到了 Exactly once,但目前还并未实现。所以目前默认情况下一条消息从 producer 到 broker 是确保了 At least once,可通过设置 producer 异步发送实现At most once。

 

四、broker 保存消息

4.1 存储方式

物理上把 topic 分成一个或多个 patition(对应 server.properties 中的 num.partitions=3 配置),每个 patition 物理上对应一个文件夹(该文件夹存储该 patition 的所有消息和索引文件),如下:

图.4

4.2 存储策略

无论消息是否被消费,kafka 都会保留所有消息。有两种策略可以删除旧数据:

1. 基于时间:log.retention.hours=168
2. 基于大小:log.retention.bytes=1073741824

需要注意的是,因为Kafka读取特定消息的时间复杂度为O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关。

4.3 topic 创建与删除

4.3.1 创建 topic

创建 topic 的序列图如下所示:

图.5

流程说明:

复制代码
复制代码
1. controller 在 ZooKeeper 的 /brokers/topics 节点上注册 watcher,当 topic 被创建,则 controller 会通过 watch 得到该 topic 的 partition/replica 分配。
2. controller从 /brokers/ids 读取当前所有可用的 broker 列表,对于 set_p 中的每一个 partition:
	2.1 从分配给该 partition 的所有 replica(称为AR)中任选一个可用的 broker 作为新的 leader,并将AR设置为新的 ISR
	2.2 将新的 leader 和 ISR 写入 /brokers/topics/[topic]/partitions/[partition]/state
3. controller 通过 RPC 向相关的 broker 发送 LeaderAndISRRequest。
复制代码
复制代码

4.3.2 删除 topic

删除 topic 的序列图如下所示:

图.6

流程说明:

1. controller 在 zooKeeper 的 /brokers/topics 节点上注册 watcher,当 topic 被删除,则 controller 会通过 watch 得到该 topic 的 partition/replica 分配。
2. 若 delete.topic.enable=false,结束;否则 controller 注册在 /admin/delete_topics 上的 watch 被 fire,controller 通过回调向对应的 broker 发送 StopReplicaRequest。

 

五、kafka HA

5.1 replication

如图.1所示,同一个 partition 可能会有多个 replica(对应 server.properties 配置中的 default.replication.factor=N)。没有 replica 的情况下,一旦 broker 宕机,其上所有 patition 的数据都不可被消费,同时 producer 也不能再将数据存于其上的 patition。引入replication 之后,同一个 partition 可能会有多个 replica,而这时需要在这些 replica 之间选出一个 leader,producer 和 consumer 只与这个 leader 交互,其它 replica 作为 follower 从 leader 中复制数据。

Kafka 分配 Replica 的算法如下:

1. 将所有 broker(假设共 n 个 broker)和待分配的 partition 排序
2. 将第 i 个 partition 分配到第(i mod n)个 broker 上
3. 将第 i 个 partition 的第 j 个 replica 分配到第((i + j) mode n)个 broker上

5.2 leader failover

当 partition 对应的 leader 宕机时,需要从 follower 中选举出新 leader。在选举新leader时,一个基本的原则是,新的 leader 必须拥有旧 leader commit 过的所有消息。

kafka 在 zookeeper 中(/brokers/…/state)动态维护了一个 ISR(in-sync replicas),由3.3节的写入流程可知 ISR 里面的所有 replica 都跟上了 leader,只有 ISR 里面的成员才能选为 leader。对于 f+1 个 replica,一个 partition 可以在容忍 f 个 replica 失效的情况下保证消息不丢失。

当所有 replica 都不工作时,有两种可行的方案:

1. 等待 ISR 中的任一个 replica 活过来,并选它作为 leader。可保障数据不丢失,但时间可能相对较长。
2. 选择第一个活过来的 replica(不一定是 ISR 成员)作为 leader。无法保障数据不丢失,但相对不可用时间较短。

kafka 0.8.* 使用第二种方式。

kafka 通过 Controller 来选举 leader,流程请参考5.3节。

5.3 broker failover

kafka broker failover 序列图如下所示:

图.7

流程说明:

复制代码
复制代码
1. controller 在 zookeeper 的 /brokers/ids/[brokerId] 节点注册 Watcher,当 broker 宕机时 zookeeper 会 fire watch
2. controller 从 /brokers/ids 节点读取可用broker
3. controller决定set_p,该集合包含宕机 broker 上的所有 partition
4. 对 set_p 中的每一个 partition
    4.1 从/brokers/topics/[topic]/partitions/[partition]/state 节点读取 ISR
    4.2 决定新 leader(如4.3节所描述)
    4.3 将新 leader、ISR、controller_epoch 和 leader_epoch 等信息写入 state 节点
5. 通过 RPC 向相关 broker 发送 leaderAndISRRequest 命令
复制代码
复制代码

5.4 controller failover

当 controller 宕机时会触发 controller failover。每个 broker 都会在 zookeeper 的 “/controller” 节点注册 watcher,当 controller 宕机时 zookeeper 中的临时节点消失,所有存活的 broker 收到 fire 的通知,每个 broker 都尝试创建新的 controller path,只有一个竞选成功并当选为 controller。

当新的 controller 当选时,会触发 KafkaController.onControllerFailover 方法,在该方法中完成如下操作:

复制代码
复制代码
1. 读取并增加 Controller Epoch。
2. 在 reassignedPartitions Patch(/admin/reassign_partitions) 上注册 watcher。
3. 在 preferredReplicaElection Path(/admin/preferred_replica_election) 上注册 watcher。
4. 通过 partitionStateMachine 在 broker Topics Patch(/brokers/topics) 上注册 watcher。
5. 若 delete.topic.enable=true(默认值是 false),则 partitionStateMachine 在 Delete Topic Patch(/admin/delete_topics) 上注册 watcher。
6. 通过 replicaStateMachine在 Broker Ids Patch(/brokers/ids)上注册Watch。
7. 初始化 ControllerContext 对象,设置当前所有 topic,“活”着的 broker 列表,所有 partition 的 leader 及 ISR等。
8. 启动 replicaStateMachine 和 partitionStateMachine。
9. 将 brokerState 状态设置为 RunningAsController。
10. 将每个 partition 的 Leadership 信息发送给所有“活”着的 broker。
11. 若 auto.leader.rebalance.enable=true(默认值是true),则启动 partition-rebalance 线程。
12. 若 delete.topic.enable=true 且Delete Topic Patch(/admin/delete_topics)中有值,则删除相应的Topic。
复制代码
复制代码

 

6. consumer 消费消息

6.1 consumer API

kafka 提供了两套 consumer API:

1. The high-level Consumer API
2. The SimpleConsumer API

其中 high-level consumer API 提供了一个从 kafka 消费数据的高层抽象,而 SimpleConsumer API 则需要开发人员更多地关注细节。

6.1.1 The high-level consumer API

high-level consumer API 提供了 consumer group 的语义,一个消息只能被 group 内的一个 consumer 所消费,且 consumer 消费消息时不关注 offset,最后一个 offset 由 zookeeper 保存。

使用 high-level consumer API 可以是多线程的应用,应当注意:

1. 如果消费线程大于 patition 数量,则有些线程将收不到消息
2. 如果 patition 数量大于线程数,则有些线程多收到多个 patition 的消息
3. 如果一个线程消费多个 patition,则无法保证你收到的消息的顺序,而一个 patition 内的消息是有序的

6.1.2 The SimpleConsumer API

如果你想要对 patition 有更多的控制权,那就应该使用 SimpleConsumer API,比如:

1. 多次读取一个消息
2. 只消费一个 patition 中的部分消息
3. 使用事务来保证一个消息仅被消费一次

但是使用此 API 时,partition、offset、broker、leader 等对你不再透明,需要自己去管理。你需要做大量的额外工作:

1. 必须在应用程序中跟踪 offset,从而确定下一条应该消费哪条消息
2. 应用程序需要通过程序获知每个 Partition 的 leader 是谁
3. 需要处理 leader 的变更

使用 SimpleConsumer API 的一般流程如下:

复制代码
复制代码
1. 查找到一个“活着”的 broker,并且找出每个 partition 的 leader
2. 找出每个 partition 的 follower
3. 定义好请求,该请求应该能描述应用程序需要哪些数据
4. fetch 数据
5. 识别 leader 的变化,并对之作出必要的响应
复制代码
复制代码

以下针对 high-level Consumer API 进行说明。

6.2 consumer group

如 2.2 节所说, kafka 的分配单位是 patition。每个 consumer 都属于一个 group,一个 partition 只能被同一个 group 内的一个 consumer 所消费(也就保障了一个消息只能被 group 内的一个 consuemr 所消费),但是多个 group 可以同时消费这个 partition。

kafka 的设计目标之一就是同时实现离线处理和实时处理,根据这一特性,可以使用 spark/Storm 这些实时处理系统对消息在线处理,同时使用 Hadoop 批处理系统进行离线处理,还可以将数据备份到另一个数据中心,只需要保证这三者属于不同的 consumer group。如下图所示:

图.8

6.3 消费方式

consumer 采用 pull 模式从 broker 中读取数据。

push 模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。

对于 Kafka 而言,pull 模式更合适,它可简化 broker 的设计,consumer 可自主控制消费消息的速率,同时 consumer 可以自己控制消费方式——即可批量消费也可逐条消费,同时还能选择不同的提交方式从而实现不同的传输语义。

6.4 consumer delivery guarantee

如果将 consumer 设置为 autocommit,consumer 一旦读到数据立即自动 commit。如果只讨论这一读取消息的过程,那 Kafka 确保了 Exactly once。

但实际使用中应用程序并非在 consumer 读取完数据就结束了,而是要进行进一步处理,而数据处理与 commit 的顺序在很大程度上决定了consumer delivery guarantee:

复制代码
复制代码
1.读完消息先 commit 再处理消息。
    这种模式下,如果 consumer 在 commit 后还没来得及处理消息就 crash 了,下次重新开始工作后就无法读到刚刚已提交而未处理的消息,这就对应于 At most once
2.读完消息先处理再 commit。
    这种模式下,如果在处理完消息之后 commit 之前 consumer crash 了,下次重新开始工作时还会处理刚刚未 commit 的消息,实际上该消息已经被处理过了。这就对应于 At least once。
3.如果一定要做到 Exactly once,就需要协调 offset 和实际操作的输出。
    精典的做法是引入两阶段提交。如果能让 offset 和操作输入存在同一个地方,会更简洁和通用。这种方式可能更好,因为许多输出系统可能不支持两阶段提交。比如,consumer 拿到数据后可能把数据放到 HDFS,如果把最新的 offset 和数据本身一起写到 HDFS,那就可以保证数据的输出和 offset 的更新要么都完成,要么都不完成,间接实现 Exactly once。(目前就 high-level API而言,offset 是存于Zookeeper 中的,无法存于HDFS,而SimpleConsuemr API的 offset 是由自己去维护的,可以将之存于 HDFS 中)
复制代码
复制代码

总之,Kafka 默认保证 At least once,并且允许通过设置 producer 异步提交来实现 At most once(见文章《kafka consumer防止数据丢失》)。而 Exactly once 要求与外部存储系统协作,幸运的是 kafka 提供的 offset 可以非常直接非常容易得使用这种方式。

更多关于 kafka 传输语义的信息请参考《Message Delivery Semantics》。

6.5 consumer rebalance

当有 consumer 加入或退出、以及 partition 的改变(如 broker 加入或退出)时会触发 rebalance。consumer rebalance算法如下:

复制代码
复制代码
1. 将目标 topic 下的所有 partirtion 排序,存于PT
2. 对某 consumer group 下所有 consumer 排序,存于 CG,第 i 个consumer 记为 Ci
3. N=size(PT)/size(CG),向上取整
4. 解除 Ci 对原来分配的 partition 的消费权(i从0开始)
5. 将第i*N到(i+1)*N-1个 partition 分配给 Ci
复制代码
复制代码

在 0.8.*版本,每个 consumer 都只负责调整自己所消费的 partition,为了保证整个consumer group 的一致性,当一个 consumer 触发了 rebalance 时,该 consumer group 内的其它所有其它 consumer 也应该同时触发 rebalance。这会导致以下几个问题:

复制代码
复制代码
1.Herd effect
  任何 broker 或者 consumer 的增减都会触发所有的 consumer 的 rebalance
2.Split Brain
  每个 consumer 分别单独通过 zookeeper 判断哪些 broker 和 consumer 宕机了,那么不同 consumer 在同一时刻从 zookeeper 看到的 view 就可能不一样,这是由 zookeeper 的特性决定的,这就会造成不正确的 reblance 尝试。
3. 调整结果不可控
  所有的 consumer 都并不知道其它 consumer 的 rebalance 是否成功,这可能会导致 kafka 工作在一个不正确的状态。
复制代码
复制代码

基于以上问题,kafka 设计者考虑在0.9.*版本开始使用中心 coordinator 来控制 consumer rebalance,然后又从简便性和验证要求两方面考虑,计划在 consumer 客户端实现分配方案。(见文章《Kafka Detailed Consumer Coordinator Design》和《Kafka Client-side Assignment Proposal》),此处不再赘述。

 

七、注意事项

7.1 producer 无法发送消息的问题

最开始在本机搭建了kafka伪集群,本地 producer 客户端成功发布消息至 broker。随后在服务器上搭建了 kafka 集群,在本机连接该集群,producer 却无法发布消息到 broker(奇怪也没有抛错)。最开始怀疑是 iptables 没开放,于是开放端口,结果还不行(又开始是代码问题、版本问题等等,倒腾了很久)。最后没办法,一项一项查看 server.properties 配置,发现以下两个配置:

复制代码
复制代码
# The address the socket server listens on. It will get the value returned from 
# java.net.InetAddress.getCanonicalHostName() if not configured.
#   FORMAT:
#     listeners = security_protocol://host_name:port
#   EXAMPLE:
#     listeners = PLAINTEXT://your.host.name:9092
listeners=PLAINTEXT://:9092

# Hostname and port the broker will advertise to producers and consumers. If not set,
# it uses the value for “listeners” if configured. Otherwise, it will use the value
# returned from java.net.InetAddress.getCanonicalHostName().
#advertised.listeners=PLAINTEXT://your.host.name:9092

复制代码
复制代码

以上说的就是 advertised.listeners 是 broker 给 producer 和 consumer 连接使用的,如果没有设置,就使用 listeners,而如果 host_name 没有设置的话,就使用 java.net.InetAddress.getCanonicalHostName() 方法返回的主机名。

修改方法:

1. listeners=PLAINTEXT://121.10.26.XXX:9092
2. advertised.listeners=PLAINTEXT://121.10.26.XXX:9092

修改后重启服务,正常工作。关于更多 kafka 配置说明,见文章《Kafka学习整理三(borker(0.9.0及0.10.0)配置)》。

 

八、参考文章

1. 《Kafka剖析(一):Kafka背景及架构介绍

2. 《Kafka设计解析(二):Kafka High Availability (上)

3. 《Kafka设计解析(二):Kafka High Availability (下)

4. 《Kafka设计解析(四):Kafka Consumer解析

5. 《Kafka设计解析(五):Kafka Benchmark

6. 《Kafka学习整理三(borker(0.9.0及0.10.0)配置)

7. 《Using the High Level Consumer

8. 《Using SimpleConsumer

9. 《Consumer Client Re-Design

10. 《Message Delivery Semantics

11. 《Kafka Detailed Consumer Coordinator Design

12. 《Kafka Client-side Assignment Proposal

13. 《Kafka和DistributedLog技术对比

14. 《kafka安装和启动

15. 《kafka consumer防止数据丢失

 

 

我喜欢程序员,他们单纯、固执、容易体会到成就感;面对压力,能够挑灯夜战不眠不休;面对困难,能够迎难而上挑战自我。他 们也会感到困惑与傍徨,但每个程序员的心中都有一个比尔盖茨或是乔布斯的梦想“用智慧开创属于自己的事业”。我想说的是,其 实我是一个程序员
http://www.cnblogs.com/kms1989/p/6943532.html

Kafka介绍

在流式计算中,Kafka一般用来缓存数据,Storm通过消费Kafka的数据进行计算。

KAFKA + STORM +REDIS

1、Apache Kafka是一个开源消息系统,用Scala写成。

2、Kafka是一个分布式消息队列:生产者、消费者的功能。它提供了类似于JMS的特性,但是在设计实现上完全不同,此外它并不是JMS规范的实现。

3、Kafka对消息保存时根据Topic进行归类,发送消息者称为Producer,消息接收者成为Consumer,此外Kafka集群由多个Kafka实例组成,每个实例(server)称为broker。

4、无论是kafka集群,还是Producer和Consumer都依赖于zookeeper集群保存一些meta信息,来保证系统可用性。

 

Kafka核心组件

Topic :消息根据Topic进行归类

Producer:发送消息者

Consumer:消息接受者

broker:每个kafka实例(server)

Zookeeper:依赖集群保存meta信息。

 

Kafka集群部署(前提:先部署好zookeeper集群)

1、下载安装包

http://kafka.apache.org/

2、上传并解压安装包

tar  -zxvf  /root/kafka_2.11-0.8.2.2.tgz  -C  /export/servers/

cd  /export/servers/

ln -s  kafka_2.11-0.8.2.2  kafka

3、修改配置文件

vi  /export/servers/kafka/config/server.properties

配置一下内容:

4、分发安装包

scp -r /export/servers/kafka_2.11-0.8.2.2  storm2:/export/servers

scp -r /export/servers/kafka_2.11-0.8.2.2  storm3:/export/servers

然后分别在各机器上创建软连

cd /export/servers/

ln -s kafka_2.11-0.8.2.2 kafka

5、再次修改配置文件(重要)

依次修改各服务器上配置文件的broker.id,分别是0,1,2,不得重复!

6、启动集群(先启动zookeeper集群)

依次在每个节点上的/kafka/路径下启动Kafka:

bin/kafka-server-start.sh  config/server.properties

 

Kafka常用操作命令

  • 查看当前服务器中的所有topic

bin/kafka-topics.sh –list –zookeeper  zk01:2181

  • 创建topic

./kafka-topics.sh –create –zookeeper mini1:2181 –replication-factor 1 –partitions 3 –topic first

  • 删除topic

sh bin/kafka-topics.sh –delete –zookeeper zk01:2181 –topic test

需要server.properties中设置delete.topic.enable=true否则只是标记删除或者直接重启。

  • 通过shell命令发送消息

kafka-console-producer.sh –broker-list kafka01:9092 –topic itheima

  • 通过shell消费消息

sh bin/kafka-console-consumer.sh –zookeeper zk01:2181 –from-beginning –topic test1

  • 查看消费位置

sh kafka-run-class.sh kafka.tools.ConsumerOffsetChecker –zookeeper zk01:2181 –group testGroup

  • 查看某个Topic的详情

sh kafka-topics.sh –topic test –describe –zookeeper zk01:2181

 

http://www.cnblogs.com/ahu-lichang/p/6906378.html

.NET全栈开发工程师

1.职位描述

独立负责至少一个产品的前后端开发工作
//注0:今年是博客园开发团队发展的关键一年,我们有两个重要目标——实践领域驱动设计与实现.NET应用的跨平台,我们期待有志开发者的加盟!
//注1:不要求立即全栈,但要在一定时间内成长为全栈。
//注2:投递简历时需要谈一下:你为什么想加入博客园团队。
//注3:除了面试,你还需要完成一份代码作业,我们要看你写的代码。漂亮代码是吸引我们的重要地方。

2.职位要求

* 喜欢编写代码。{
是发自内心的真爱——真正的爱好。在日复一日年复一年,看似枯燥的编码工作中,能够享受取之不尽的用代码进行创作的乐趣。
}

* 乐于解决问题。{
作为程序员,你有一个永远无法回避的问题——每天要面对各种各样的问题,而唯一的解决之道就是喜欢上解决问题。不仅要喜欢上解决问题后的兴奋感觉,更要喜欢上解决问题过程中的煎熬。
}

* 讨厌重复代码。{
将重复代码当作一种垃圾,自己不乱扔垃圾,也不能容忍别人乱扔垃圾。
}

* 讲究代码命名。{
代码如诗,命名如诗句。在代码的创作过程中,追求命名的优雅,不仅让自己也让别人读起代码来赏心悦目。

* 写过三年代码。{
编程语言不限。但从事这个职位,需要用C#,因为团队协作开发要使用统一的工具。

* 基础知识牢靠。{
比如知道三大原理(计算机原理、操作系统原理、编译原理)两个协议(TCP与HTTP协议)一种结构(数据结构)。不牢靠的地方,能及时补课,夯实基础。

* 基本功够扎实。{
比如正则表达式不在话下,Windows/Linux服务器玩得转,Git轻车熟路。如有基本功不扎实,能积极补练基本功。

* 能够面向对象。{
至少熟悉一门面向对象编程语言,有一定的面向对象设计基础,能够将业务逻辑转变为对象之间的交互。

* 英语水平不赖。{
能够流畅地阅读英文技术资料,能够顺畅地与国外开发者用英文进行文字交流,能够准确地用英文进行代码中的命名,能够用英文写代码注释与git提交说明。

* 自学能力很强。{
能够根据面临的实际问题,根据自己的不足,根据自己的兴趣,根据未来发展的需要,持续地进行学习。更要能在自己所犯的错误中学习。一边写代码,一边学习;一边学习,一边写代码,这就是你的程序人生。

* 自我驱动开发。{
在我们这里没有产品经理,没有项目经理,一切由程序员说了算。你要能直接面对用户,主动挖掘用户需求,将之变为自己的想法,然后用漂亮的代码实现。

扎实的基础知识之路

纵观博客园的招聘启事条目中,基础知识的牢靠一条中提出了“三大原理、两个协议和一种结构”,因此我开始补习我的计算机专业基础知识,下面列出我的补课成果:

1.一种结构:数据结构

参考书目:《大话数据结构》、《数据结构(C#语言描述)》、《剑指Offer》

  ①线性表部分:

线性表(上){ 数组、ArrayList }

线性表(中){ 单链表、双链表、LinkedList }

线性表(下){ 循环链表、约瑟夫问题 }

②栈与队列部分:

栈 { LIFO、Stack<T> }

队列 { FIFO、Queue<T> }

③树与二叉树部分:

树与二叉树(上){ 二叉树的创建与递归遍历 }

树与二叉树(中){ 二叉树的非递归遍历与二叉查找树 }

树与二叉树(下){ 二叉树的应用:求解四则运算 }

④图部分:

图(上){ 图的基本概念、存储结构与模拟实现 }

图(中){ 图的深度与广度优先遍历算法与实现 }

图(中){ 最小生成树算法介绍与实现 }

图(下){ 最短路径算法介绍与实现 }

⑤查找部分:

查找(上){ 二分查找、二叉查找树、平衡二叉树、SortedDictionary<TKey,TValue> }

查找(下){ 哈希表、Hashtable、Dictionary、三种查找表的对比测试 }

⑥排序部分:

   排序:{ 插入排序、交换排序、选择排序、归并排序 }

⑦剑指Offer算法题部分:

面试题1:{ 实现Singleton模式 }  面试题2:{ 二维数组中的查找 }

面试题3:{ 替换空格 }  面试题4:{ 从尾到头打印链表 }

面试题5:{ 重建二叉树 }   面试题6:{ 用两个栈实现队列 }

面试题7:{ 旋转数组的最小数字 }  面试题8:{ 斐波那契数列 }

面试题9:{ 二进制中1的个数 }  面试题10:{ 数值的整数次方 }

面试题11:{ 打印1到最大的n位数 }  面试题12:{ 在O(1)时间删除链表 }

面试题13:{ 调整整数数组使得奇数在偶数之前 }  面试题14:{ 链表的倒数第k个节点 }

面试题15:{ 反转链表 }  面试题16:{ 合并两个排序的链表 }

面试题17:{ 二叉树的子结构 }  面试题18:{ 二叉树的镜像 }

面试题19:{ 包含Min函数的栈 }  面试题20:{ 栈的压入、弹出序列 }

面试题21:{ 从上到下打印二叉树 }  面试题22:{ 二叉搜索树的后序遍历序列 }

面试题23:{ 二叉树中和为某一值的路径 }  面试题24:{ 复杂链表的复制 }

面试题25:{ 二叉搜索树与双向链表 }  面试题26:{ 字符串的排列 }

面试题27:{ 最小的k个数 }  面试题28:{ 连续子数组的最大和 }

面试题29:{ 丑数 }  面试题30:{ 第一次只出现一个的字符 }

面试题31:{ 两个链表的第一个公共节点 }  面试题32:{ 数字在排序数组中出现的次数 }

面试题33:{ 二叉树的深度 }  面试题34:{ 翻转单词顺序vs左旋转字符串 }

面试题35:{ 将字符串转换为数字 }

2.两个协议:TCP与HTTP协议

① TCP/IP协议

  参考书目:《图解TCP/IP协议》

网络基础、TCP/UDP、IP、应用层协议 }

  ② HTTP协议

  参考书目:《图解HTTP协议》

{ HTTP请求、HTTP报文、Web攻击技术 }

3.三大原理:计算机原理、操作系统原理与编译原理

① 计算机系统原理

  参考书目:《程序是怎样跑起来的》、《深入理解计算机系统》

② 操作系统原理

  参考书目:《计算机的心智-操作系统之哲学原理》

  Part 0.导论

操作系统导论 { 程序的演变过程、操作系统是什么、操作系统的角色和功能 }

操作系统基本概念 { 硬件基础知识、抽象、内核态与用户态、操作系统结构、系统调用 }

Part 1.CPU管理部分:

进程原理(上){ 进程模型、进程的层次结构、进程的状态、进程的缺陷 }

进程原理(中){ 进程调度的定义、各种调度算法、调度异常之优先级倒挂 }

进程原理(下){ 进程通信概要、各种通信方式 }

线程原理(上){ 线程基础、线程同步 }

线程原理(下){ 死锁描述、死锁必要条件、死锁应对方式、哲学家就餐问题、银行家算法 }

Part 2.内存管理部分:

内存管理(上){ 内存管理概念、基本内存管理 }

内存管理(中){ 页式内存管理、页面置换算法 }

内存管理(下){ 段式内存管理、段页式内存管理 }

Part 3.外存管理部分:

  外存管理(上){ 磁盘定义、磁盘结构、访问时间、磁盘调度算法 }

外存管理(下){ 文件系统、文件夹、文件系统的调用 }

Part 4.设备管理部分:

  设备管理 { I/O基本原理、I/O硬件与软件、I/O软件的分层 }

③ 编译原理

  参考书目:《编译原理(龙书)》

4.基本功:Windows Server/Linux/GitHub/正则表达式

① GitHub : { 基本概念、GitHub使用、Git操作、托管代码 }

Linux :  

  参考书目:《鸟哥的Linux私房菜》

熟练的面向对象之路

轻轻的一句熟悉面向对象,能够将业务逻辑转变为对象之间的交互,是一种大道至简的描述。至于面向对象,很多人都很熟悉,但却又不太熟悉。因此,我决定重新认知面向对象,下面列出我的补课成果:

1.面向对象分析之UML

① OOAD利器之UML基础

2.面向对象设计之设计模式

【创建型】

设计模式的征途-01.单例(Singleton)模式

设计模式的征途-02.简单工厂(Simple Factory)模式

设计模式的征途-03.工厂方法(Factory Method)模式

设计模式的征途-04.抽象工厂(Abstract Factory)模式

设计模式的征途-05.原型(Prototype)模式

设计模式的征途-06.建造者(Builder)模式

【结构型】

① 设计模式的征途-07.适配器(Adapter)模式

设计模式的征途-08.桥接(Bridge)模式

.NET核心原理理解之路

1. .NET核心基础拾遗

① 类型语法基础和内存管理基础

② 面向对象的实现和异常的处理基础

③ 字符串、集合与流的基础

委托、事件、反射与特性的基础

多线程开发基础

ADO.NET与数据库开发基础

⑦ Web Service的开发与应用基础

2.《CLR via C#》读书笔记

暂时就先列出这么多,后期持续更新!

.NET后端开发之路

1.ASP.NET MVC

自己动手写MVC框架:

自己动手写一个简单的ASP.NET MVC框架(第一版)

② 自己动手写一个简单的ASP.NET MVC框架(第二版)

2.ASP.NET WebAPI

3.WCF

数据库技术学习之路

1.MS SQL Server

  T-SQL语言基础:

  T-SQL查询:

2.Oracle

3.MySQL

Web前端开发之路

1.HTML5+CSS3

① Web前端温故知新-CSS基础

② Web前端温故知新-HTML5基础

③ Web前端温故知新-CSS3基础

2.JavaScript

3.Bootstrap

4.AngularJS

AngularJS基础入门初探

移动端开发之路

1.React Native

2.Cordova/PhoneGap

Hybrid App 移动应用开发初探

3.微信小程序

微信小程序开发初探

Agile-敏捷软件开发之路

1.Scrum Guide Scrum指南

① Scrum Guide 原版

② Scrum Guide 中文版

2.Agile 敏捷软件开发:原则、模式与实践

Agile Software Development 敏捷软件开发基础知识

DDD-领域驱动设计之路

TDD-测试驱动开发之路

1.单元测试的艺术

.NET单元测试的艺术:入门

② .NET单元测试的艺术:核心技术

③ .NET单元测试的艺术:测试代码

2.测试驱动开发

 

ASP.NET取得Request URL的各个部分

我們在開發網頁應用程式,時常需要去解析網址(Request.Url)的每個片段,進行一些判斷。例如說 “http://localhost:1897/News/Press/Content.aspx/123?id=1#toc“,我們想要取得網址裡第一層目錄的名字(News)用以判斷不同的頁面標題(Page Title)。

我看很多人都用字串的 IndexOf 方法與 Substring 方法:

Request.Url.PathAndQuery.Substring(1, Request.Url.PathAndQuery.IndexOf(“/”, 1)-1)

這實在太埋沒 .NET 的強大設計了,事實上在 Request 物件就已經提供很多方便的屬性(Property)可供取得網址的片段。

底下這張表就是各種跟 Browser Request 的網址相關的屬性與用法:

網址:http://localhost:1897/News/Press/Content.aspx/123?id=1#toc
Request.ApplicationPath /
Request.PhysicalPath D:\Projects\Solution\web\News\Press\Content.aspx
System.IO.Path.GetDirectoryName(Request.PhysicalPath) D:\Projects\Solution\web\News\Press
Request.PhysicalApplicationPath D:\Projects\Solution\web\
System.IO.Path.GetFileName(Request.PhysicalPath) Content.aspx
Request.CurrentExecutionFilePath /News/Press/Content.aspx
Request.FilePath /News/Press/Content.aspx
Request.Path /News/Press/Content.aspx/123
Request.RawUrl /News/Press/Content.aspx/123?id=1
Request.Url.AbsolutePath /News/Press/Content.aspx/123
Request.Url.AbsoluteUri http://localhost:1897/News/Press/Content.aspx/123?id=1
Request.Url.Scheme http
Request.Url.Host localhost
Request.Url.Port 1897
Request.Url.Authority localhost:1897
Request.Url.LocalPath /News/Press/Content.aspx/123
Request.PathInfo /123
Request.Url.PathAndQuery /News/Press/Content.aspx/123?id=1
Request.Url.Query ?id=1
Request.Url.Fragment
Request.Url.Segments /
News/
Press/
Content.aspx/
123

所以當你看了這張表之後,你還會想用 Request.Url.PathAndQuery.Substring(1, Request.Url.PathAndQuery.IndexOf(“/”, 1)-1) 這種寫法嗎?

用這樣寫 Request.Url.Segments[1].Replace(“/”, “”) 不是又短又直覺嗎? ^_^

以下是產生以上表格的程式碼:

    protected void Page_Load(object sender, EventArgs e)
{

StringBuilder sb = new StringBuilder();

sb.Append(“<table cellpadding=3 cellspacing=0 border=1>”);

sb.Append(“<tr><td colspan=2>”);
sb.Append(“網址:“http://localhost:1897/News/Press/Content.aspx/123?id=1#toc”>http://localhost:1897/News/Press/Content.aspx/123?id=1#toc“);
sb.Append(“</td></tr>”);

// Request.ApplicationPath
        sb.Append(“<tr><td>”);
sb.Append(“Request.ApplicationPath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.ApplicationPath + “</b>”);
sb.Append(“</td></tr>”);

// Request.PhysicalPath
        sb.Append(“<tr><td>”);
sb.Append(“Request.PhysicalPath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.PhysicalPath + “</b>”);
sb.Append(“</td></tr>”);

// System.IO.Path.GetDirectoryName(Request.PhysicalPath)
        sb.Append(“<tr><td>”);
sb.Append(“System.IO.Path.GetDirectoryName(Request.PhysicalPath)”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + System.IO.Path.GetDirectoryName(Request.PhysicalPath) + “</b>”);
sb.Append(“</td></tr>”);

// Request.PhysicalApplicationPath
        sb.Append(“<tr><td>”);
sb.Append(“Request.PhysicalApplicationPath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.PhysicalApplicationPath + “</b>”);
sb.Append(“</td></tr>”);

// System.IO.Path.GetFileName(Request.PhysicalPath)
        sb.Append(“<tr><td>”);
sb.Append(“System.IO.Path.GetFileName(Request.PhysicalPath)”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + System.IO.Path.GetFileName(Request.PhysicalPath) + “</b>”);
sb.Append(“</td></tr>”);

// Request.CurrentExecutionFilePath
        sb.Append(“<tr><td>”);
sb.Append(“Request.CurrentExecutionFilePath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.CurrentExecutionFilePath + “</b>”);
sb.Append(“</td></tr>”);

// Request.FilePath
        sb.Append(“<tr><td>”);
sb.Append(“Request.FilePath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.FilePath + “</b>”);
sb.Append(“</td></tr>”);

// Request.Path
        sb.Append(“<tr><td>”);
sb.Append(“Request.Path”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Path + “</b>”);
sb.Append(“</td></tr>”);

// Request.RawUrl
        sb.Append(“<tr><td>”);
sb.Append(“Request.RawUrl”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.RawUrl + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.AbsolutePath
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.AbsolutePath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.AbsolutePath + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.AbsoluteUri
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.AbsoluteUri”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.AbsoluteUri + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Scheme
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Scheme”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Scheme + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Host
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Host”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Host + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Port
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Port”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Port + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Authority
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Authority”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Authority + “</b>”);
sb.Append(“</td></tr>”);

// local Request.Url.LocalPath
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.LocalPath”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.LocalPath + “</b>”);
sb.Append(“</td></tr>”);

// Request.PathInfo
        sb.Append(“<tr><td>”);
sb.Append(“Request.PathInfo”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.PathInfo + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.PathAndQuery
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.PathAndQuery”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.PathAndQuery + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Query
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Query”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Query + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Fragment
        // 原則上你應該無法從 Request.Url.Fragment 取得任何資料,因為通常 Browser 不會送出 #toc 這個部分
        sb.Append(“<tr><td>”);
sb.Append(“Request.Url.Fragment”);
sb.Append(“</td><td>”);
sb.Append(“<b>” + Request.Url.Fragment + “</b>”);
sb.Append(“</td></tr>”);

// Request.Url.Segments
        sb.Append(“<tr>”);
sb.Append(“<td>”);
sb.Append(“Request.Url.Segments”);
sb.Append(“</td>”);
sb.Append(“<td>”);
string[] segments = Request.Url.Segments;
foreach (string s in segments)
{
sb.Append(“<b>” + s + “</b>”);
sb.Append(“<br/>”);
}
sb.Append(“</td>”);
sb.Append(“</tr>”);

sb.Append(“</table>”);

ltlTable.Text = sb.ToString();
}

开源电子书《我的职业是前端工程师》

 

https://juejin.im/entry/58fbfc36b123db6069aa71c6

2017 年 1 月份,看完村上春树的新书《我的职业是一个小说家》,我便萌发了写一个《我的职业是前端工程师》系列文章的想法——以个人视角来看前端领域的各种技术。整个系列的文章大概有 15 篇左右,从我是如何成为一个前端工程师,到各种前端框架的知识。

关注我的微信公众号(扫描下面的二维码或搜索 Phodal).

QRCode

目录

前端技能

来源:https://github.com/phodal/awesome-growth

  • 基础
    • HTML / CSS
    • JavaScript
    • DOM
  • 中级篇
    • 数据格式(如JSON、XML)
    • RESTful API交互(如jQuery Ajax,Fetch API,ReactiveX)
    • 正则表达式
    • HTML语义化
    • 命令行
    • Node.js
    • DIV / CSS
    • SCSS / SASS
    • 矢量图形 / 矢量图形动画(如SVG)
    • 单页面应用
  • 高级篇
    • ES6 / TypeScript
    • CSS3
    • 面向对象编程
    • 函数式编程
    • MVC / MVVM / MV*
    • 安全性(如跨域)
    • 授权(如HTTP Basic、JWT等等)
  • 工程化
    • 代码质量(如JSLint / ESLint / TSLint / CSLint)
    • 代码分析(如Code Climate)
    • 测试覆盖率
    • 构建系统(gulp、grunt、webpack等等)
    • 自动构建(脚本)
  • 兼容性
    • 跨浏览器测试 (Chrome,IE,Firefox,Safari等等)
    • 跨平台测试(Windows、GNU/Linux,Mac OS等等)
    • 跨设备测试(Desktop,Android,iOS,Windows Phone)
    • 跨版本测试(同一个浏览器的不同版本)
  • 前端特定
    • CSS / CSS3 动画
    • JavaScript 动画
    • Web字体嵌入
    • Icon 字体
    • 图形和图表
    • CSS Sprite(如glue)
    • DOM操作(如jQuery、React等等)
    • 模板引擎(如JSX、Handlebars、JSP、Mustache等等)
  • 软件工程
    • 版本管理(如git、svn)
    • 包管理(如npm、bower)
    • 依赖管理
    • 模块化(如CommonJS、WebPack)
  • 调试
    • 浏览器调试
    • Debug工具
    • Wireshark / Charles抓包
    • 远程设备调试(如Chrome Inspect Devices)
  • 测试
    • 单元测试
    • 服务测试
    • UI测试
    • 集成测试
  • 性能与优化
    • PageSpeed / Yslow 优化
    • 加载优化(如gzip压缩、缓存等等)
    • 性能测试(特别是移动Web)
    • 可用性
    • 压缩(如Minify、Uglify、CleanCSS等等)
  • 设计
    • 切页面
    • 线框图(Wireframe)
    • 响应式设计
    • 网格布局(Grid Layout)
    • Flexbox布局
  • SEO
    • Sitemap(站点地图)
    • 内部链接建设
    • MicroData / MicroFormat
    • 页面静态内容生成
    • H1、H2、H3和strong使用
    • Title、Description优化
    • 页面静态内容生成

LICENSE

Phodal's ArticlePhodal's Book

© 2017 Phodal Huang. This code is distributed under the Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License. See LICENSE in this directory.

待我代码编成,娶你为妻可好

为部署ASP.NET Core准备:使用Hyper-V安装Ubuntu Server 16.10

概述

Hyper-V是微软的一款虚拟化产品,和VMWare一样采用的hypervisor技术。它已经被内嵌到Win10系统内,我们只需要进行简单的安装即可。但是前提是要确保你的机器已经启用虚拟化,可以到任务管理器中查看,如下:

Ubuntu(乌班图)是一个开源的Linux操作系统,同时为企业提供服务器版本。至于其他发行版本如:CentOS、Debian等,这里不是讨论的重点,本篇是以Ubuntu Server 16.10版本进行安装的。且不说Ubuntu资料多,社区广,单凭它是我大学里边接触到的第一任Linux操作系统(先入为主),那么当之无愧的成为了我的首选。

一、安装Hyper-V

1、在控制面板→程序→启用或关闭Windows功能→勾选Hyper-V,然后安装好之后重启计算机

二、配置Hyper-V

1、打开刚才安装好的Hyper-V管理器,右键选择创建虚拟机,然后跟着向导一步一步来

2、修改虚拟机的名字为Ubuntu16.10,然后修改一下虚拟机存储的位置,建议放到空间比较大的一个盘符上

3、选择第一代虚拟机,至于和第二代的区别在哪,请看下图(PS:第二代貌似不支持我的电脑)

4、给它配置一个2G的内存

5、网络适配器没有的话可以暂时先忽略,我们稍后配置,直接下一步。

6、为虚拟机设置一个50G的虚拟硬盘,名称和位置可以默认不做修改

7、选择我们之前下载的Ubuntu16.10 Server版的镜像文件

8、最后一步,完成!

后续也是可以对虚拟机进行设置的,比如把虚拟CPU加到四个核等等

接下来就是配置一个虚拟网络以供虚拟机使用:选择管理器右边的虚拟交换机管理器,打开并创建一个外部虚拟交换机,设置好名称之后选择一个可以访问外网的网络适配器,最后不要忘记将其重新设置为虚拟机的网络适配器

三、安装Ubuntu 16.10 Server版本

1、启动我们的虚拟机,开始安装系统,默认选择英文安装即可,记得要用键盘,鼠标不行!

别问我为什么不选择中文安装,LZ已经亲测没有安装成功,如下图:

2、直接选择安装Ubuntu服务器版,第一个选项

3、语言还是选择英文吧

如果你问我为啥不选择Chinese,因为LZ也已经亲测,会出现乱码,如果你想后续对系统做中文包,就当我没说。

4、接下来你就再也看不到中文了,苟且使用US。

5、不需要配置键盘的,等下选一下就可以了

6、键盘所属国家和布局都选择Chinese,你懂的。

6、然后静静的等待系统的一些相关配置

7、配置你的主机名

8、设置一个账户名称

9、设置一个账户名,然后继续

10、给此账户名设置一个密码然后进行再次验证

11、加密的话就算了。。。

12、设置时钟,如果没问题的话,之后应该会显示是亚洲/上海时区,选择是,然后我们继续

13、配置LVM(百科:LVM全称是逻辑盘卷管理 (Logical Volume Manager),是Linux系统对磁盘分区管理一种机制。

相对于一般的磁盘分区而言LVM是建立在硬盘和分区物理层 之上的一个逻辑层,通过逻辑分区来提高磁盘的利用率)

14、确定选择配置LVM

15、配置你的卷组大小;输入50%,表示一半的逻辑卷组大小

16、确认将分区改动写入磁盘

17、好了,等待安装系统吧

不需要设置代理,继续

不需要更新,以后手动就可以了,之后的软件也直接跳过,然后继续就行了

18、软件安装过程你可以去喝杯水。。。

19、设置GRUB主引导为是

20、大功告成,安装还是很快的。

最后:重启系统之后输入账户和密码登陆,基本上没啥问题了。

写在最后

至此Ubuntu系统的安装告一段落,来来回回折腾了好几次。可惜的是官方已经可以升级到17.04(囧),如果你需要长期支持的话,建议还是安装Ubuntu Server 16.04 LTS!只是这里作为学习和实践为目的的,所以也就无所谓了。接下来主要是部署我们的ASP.NET Core项目,这个才是重中之重。😁