Mobility

聚沙成塔

原文地址: https://lcy362.github.io/posts/19890/

Hexo是一款开源的博客系统。对于一个后端程序员来说,不想折腾前端的东西,但是csdn,博客园之类的用起来还是不太方便,自己搭博客又麻烦,做出来还丑。偶然间看到了hexo,这个对后端程序员来说可以说是非常友好了。所以也写篇文章记录一下hexo安装,一些关键配置,以及部署到github的过程。

安装及初始化

参考官方文档 就可以了。hexo是基于node.js的,用过node的自然没有任何问题,没用过也没关系,照着说明文档做就可以了。

hexo支持直接向github的page发布,只需要配置好自己的github信息就可以。

主题

hexo有很多定制主题, 按个人喜好使用吧, 我用的是next , 这款主题功能非常多,统计、搜索之类的都是一条配置都搞定了。不过这款的一些基础配置和其他主题似乎是有些区别的,所以用了以后如果以后想换别的可能会有点困难。

内容迁移

hexo提供了多个从其他博客迁移数据的插件,rss,blogger等等都可以。

以博客园为例,博客园的博客可以导出一个rss文件,然后我们用hexo-migrator-rss就可以生成hexo格式的文件了,不过有可能需要做一些微调。

插件及第三方服务

前边说了,hexo配合next主题,很多工具用起来会非常方便。

主要参考next的文档就可以了,不过这份文档有些老了,具体的还要参考next的主题配置文件,里边的说明也比较详细。

推荐一些比较有用的:

  • 百度、google等的统计工具: 在next里是把自己的id配上就可以,就不用去加js代码了
  • gitment: 基于github issue的评论系统, 用github账号登录以后就可以发评论了。毕竟看技术博客的人github账号大家都有,这样比其他评论系统方便些。
  • hexo-generator-searchdb: 一个本地搜索工具,使用之后在博客首页加个搜索框
  • hexo-generator-feed: 生成rss文件,以支持订阅
  • hexo-abbrlink:默认的文章地址是带文章标题的,特别是中文标题真的是反人类,这个会给每篇文章生成一个id,然后用id做地址
  • hexo-generator-robotstxt, hexo-generator-sitemap: 生成sitemap,robots.txt, 帮助搜索引擎爬数据,不多说了
  • leancloud: 统计每篇文章访问量,并且在页面上显示

包括部署到github, 也有现成的配置可以用,简单配置一下就好了。

弄完之后,就是大家现在看到的样子了。

Jstorm的UI中提供了大量非常详细的监控参数,对于我们排查问题帮助非常大,关于UI,可以参考我之前的另一篇文章: https://lcy362.github.io/posts/31996/ 。 不过,UI这种方式用起来有时可能会不太方便,比如需要查历史数据的时候。所以我们希望将监控数据输出到别的存储介质中,方便后续查询、分析。

由于jstorm的监控相比于apache-storm进行了完全的重写,所以网上查到的storm的监控输出方式并不适用于jstorm. 而jstorm除了官方文档以外实在缺少资料,官方文档又太简略,给的只是一些线索性的东西,具体还要结合这些线索去翻阅源码。所以我整理了一个jstorm监控数据输出的例子。

首先需要实现MetricUploader这个接口,不过其实我们并不会实际使用这个接口里的哪个方法,主要是要去用它的TopologyMetricsRunnable这个参数,然后用这个参数去取监控信息。所以理论上只要拿到TopologyMetricsRunnable就行,并不一定非要实现MetricUploader接口。我的做法是实现MetricUploader,然后自己起一个定时的线程池,定时去取监控数据。

jstorm的metric数据存在rocksdb里,这里取的数据实质上是用jstorm封装好的接口去查询rocksdb。

具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ClusterSummary clusterInfo = client.getClient().getClusterInfo();
//get list of topologies in this cluster
List<TopologySummary> topologies = clusterInfo.get_topologies();
for (TopologySummary topology : topologies) {
//get topology id and name
//the id is used for query, name for human reading
logger.info("topology info " + topology.get_id() + " " + topology.get_name());
TopologyMetric metric = metricsRunnable.getTopologyMetric(topology.get_id());
//get data of "component metrics" page in jstorm UI
MetricInfo componentMetric = metric.get_componentMetric();
Map<String, Map<Integer, MetricSnapshot>> metrics = componentMetric.get_metrics();
for (Map.Entry<String, Map<Integer, MetricSnapshot>> oneMetric : metrics.entrySet()) {
String[] key = oneMetric.getKey().split("@");
String metricKey = key[1] + "@" + key[2] + "@" + key[6];
//get(60) to get data in 1 min, also can get(600) for 10min, and so on
logger.info("metric one minute data for " + metricKey + " " + oneMetric.getValue().get(60));
}
}

整个流程比较清晰,首先需要去查询集群中topology的列表,然后使用每一个topology id去查询metric信息,得到一个TopologyMetric类, TopologyMetric里包含topologyMetric,componentMetric,workerMetric等属性,这个分别与UI页面里对应。

以componentMetric为例, 可以使用componentMetric.get_metrics(); 拿到具体的监控metric数据, 一个metric是一个Map<String, Map<Integer, MetricSnapshot>>, 其中key是一个@符分隔的字符串,里边包含topology名,component名,数据项等关键的key信息,value里这个map的key是一个时间,单位为秒,对应UI上1分钟,2分钟那几页,value就是具体的监控数据,这个数据其实比UI展示出来的更丰富,除了均值外,还有诸如95线,99线等。

在这个例子里,我只是用打日志的方式,将部分数据输出。具体用的时候,可以根据需求使用hbase, redis,mysql等存储介质。

具体代码可以查看 https://github.com/lcy362/StormTrooper/blob/master/src/main/java/com/trooper/storm/monitor/MetricUploaderTest.java

apache-camel 作为数据路由的利器,使用起来非常方便。不过与此同时,也有一个问题,就是由于封装的过于完善,隐藏了很多技术细节,所以一旦有问题,排查会比较困难。好在官方提供了一个debug工具,可以帮助我们正常的打断点、调试,http://camel.apache.org/debugger.html, 在本文中会对官方文档做一些补充。

首先需要引入camel-test包:

1
2
3
4
5
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test</artifactId>
<version>2.16.2</version>
</dependency>

之后新建一个类并实现CamelTestSupport 。

1
public class CamelDebugger extends CamelTestSupport {

CamelTestSupport 中有大量的方法,可以根据需要选择一些进行实现,介绍一下其中一些比较重要的。

1. createCamelContext() 这个方法可以定义自己的camelContext进行测试.

2. createRouteBuilder() 这个方法则是使用默认的camelContext,但是加入自己的route

3. debugBefore 和 debugAfter, 这两个方法分别在一条消息被处理前后被执行, 参数里包括exchange, processor等必要信息。真正debug时,也就是在这两个方法里写日志或者打断点。

具体例子代码可以查看: https://github.com/lcy362/CamelDemo/blob/7aef2cc7661236499896022f6976c160b73b68e7/src/main/java/com/mallow/demo/camel/debugger/CamelDebugger.java

问题

用过storm或者jstorm的都知道,如果在bolt代码中发生了没被catch住的异常,所在worker进程会退出。本文就从源码角度分析一下具体设计,其实并不是“有异常然后进程崩了”这么简单。

阅读全文 »

除了通过properties,xml等格式的配置文件对log4j进行配置外,log4j还提供了各种接口,可以用代码动态修改log4j的配置,例如给一个logger增加一个appender。方法很简单,就是新建一个appder,然后添加到logger上,示例代码如下:

1
2
3
4
5
6
7
8
9
KafkaLog4jAppender kafkaAppender = new KafkaLog4jAppender();
kafkaAppender.setBrokerList(broker);
kafkaAppender.setTopic(topic);
kafkaAppender.setCompressionType("gzip");
kafkaAppender.setSyncSend(false);
kafkaAppender.setLayout(new PatternLayout(layout));
kafkaAppender.activateOptions();
logger.addAppender(kafkaAppender);
logger.setLevel(Level.INFO);

这里以一个kafkaappender做例子,其他的,例如DailyRollingFileAppender等,都是类似的。

有时候我们需要写一些简单的性能测试代码,恰好在stackoverflow上看到一篇经验之谈,https://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java, 怎样写基准测试来尽量屏蔽掉环境的影响。

翻译出来贴在这儿:

来自Java HotSpot作者的撰写微基准的提示:

规则0:阅读有关JVM和微型基准测试的好论文。比如https://www.ibm.com/developerworks/java/library/j-jtp02225/。不要对这种测试有太高的期望;它们对JVM性能的测试仅能起到有限效果。

规则1:始终包含一个预热阶段,它一直运行,直到触发所有的初始化和编译。 (预热阶段的迭代次数可以减少,经验法则是几万次循环。)

规则2:始终使用-XX:+PrintCompilation -verbose:gc 等参数来运行,这样可以确定编译阶段和JVM的其他部分在计时时是否进行了一些意外的工作。

规则2.1:在计时和预热阶段的开始和结束打印消息,这样可以确定计时时是否有规则2的输出。

规则3:了解-client与-server之间的区别,还有OSR和常规汇编之间的区别。 -server优于-client, 常规编译优于OSR

规则4:注意初始化的影响,第一次计时不要打印结果,除非是在测试类加载的过程,规则2是你对抗这种效果的第一道防线。

规则5:注意编译器优化和重新编译的效果。计时时不要使用任何代码路径,因为编译器可能会基于一些乐观假设进行优化,导致根本不会使用该路径,从而可以对代码进行垃圾和重新编译。规则2是你对抗这种效果的第一道防线。

规则6:使用适当的工具读取编译器的工作过程,并对它产生一些令人惊讶的代码做好。在形成关于什么使事情更快或更慢的理论之前自己检查代码。

规则7:减少测量中的噪音。在一台安静的机器上运行基准测试,并运行几次,抛弃异常值。使用-Xbatch将编译器与应用程序串行化,并考虑设置 -XX:CICompilerCount = 1以防止编译器与其自身并行运行。

规则8:使用一些库用来做基准测试,因为它可能更有效率。比如JMH,Caliper,UCSD Benchmarks for Java等。

在java里, 我们可以使用Executors.newFixedThreadPool 来创建线程池, 然后就可以不停的创建新任务,并用线程池来执行了。

在提交任务时,如果线程池已经被占满,任务会进到一个队列里等待执行。

这种机制在一些特定情况下会有些问题。今天我就遇到一种情况:创建线程比线程执行的速度要快的多,而且单个线程占用的内存又多,所以很快内存就爆了。

想了一个办法,就是在提交任务之前,先检查目前正在执行的线程数目,只有没把线程池占满的时候在去提交任务。

代码很简单:

1
2
3
4
5
6
7
8
9
          int threadCount = ((ThreadPoolExecutor)executor).getActiveCount();
// System.out.println("running : " + threadCount);
while (threadCount == POOL_SIZE) {
TimeUnit.MILLISECONDS.sleep(1);
threadCount = ((ThreadPoolExecutor)executor).getActiveCount();
// System.out.println("running : " + threadCount);
}

executor.execute

maven里的mirror和repository是两个比较容易混淆的概念,它们的作用都是配置远程maven仓库的地址。顾名思义,repository就是直接配置站点地址,mirror则是作为站点的镜像,代理某个或某几个站点的请求,实现对repository的完全代替。

repository

有两种形式可以配置多个repository, 配置多个profile或者在同一个profile中配置多个repository.配置多profile时,还需要配置activeProfiles使配置生效。

配置示例:

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
      <profiles>
</profile>
<profile>
<id>central</id>
<repositories>
<repository>
<id>central</id>
<url>http://search.maven.org/</url>
<!-- <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled>
</snapshots> -->
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>http://search.maven.org/</url>
<releases>
<enabled>false</enabled>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
<profile>
<id>aliyun</id>
<repositories>
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<!-- <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled>
</snapshots> -->
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<releases>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>

<activeProfiles>
<activeProfile>aliyun</activeProfile>
<activeProfile>central</activeProfile>
</activeProfiles>

单profile,多repository的配置也类似。

这样就实现了多站点的配置。下载依赖时,maven会按照配置从上到下的顺序,依次尝试从各个地址下载,成功下载为止。

mirror

个人感觉mirror的存在有些鸡肋,如果不想用repository里配的地址,完全可以直接改掉,而不用再加一条mirror配置。

如果setting.xml和pom里都配置了repository, 配置的mirror是可以对两个配置文件都生效的,这可能是mirror存在的唯一意义。

mirror的配置示例:

1
2
3
4
5
6
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>

使用mirrorOf指定这个镜像是针对哪个repository的,配置成*就表示要代理所有repository的请求。

需要注意的是,与repository不同,配置到同一个repository的多个mirror时,相互之间是备份的关系,只有在仓库连不上的时候才会切换另一个,而如果在能连上的情况下找不到包,是不会尝试下一个地址的。

所以,一般情况下,无论是配置国内的maven仓库,还是配置nexus之类私服,都可以直接配置成repository, 这样即使配置的这些仓库有些问题导致一些包下不下来,也可以继续用别的仓库尝试。

eclipse里有一个功能叫做“打可执行(runnable) jar包”, 用这个功能可以把一个工程自身和所有依赖包打成一个fat jar,并且指定Main方法,这样直接使用java jar xxx.jar就可以运行代码了。

但是在不使用eclipse的时候呢?其实,借助maven,我们很容易实现同样功能。maven提供了一个shade plugin,可以用来打fat jar, 同时也提供了指定main方法的功能。

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
<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.sonatype.haven.HavenCli</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>

然后在用maven打包的时候就可以打出直接可运行的包了。

activemq提供了一种插件(plugin)开发的机制(http://activemq.apache.org/developing-plugins.html), 可以方便的添加各种自定义功能。其效果类似于直接去修改activemq的源码,无论从方便性还是风险程度上,使用plugin都要比去修改源码要好很多。通过这种方式,理论上我们可以实现几乎所有能想到的功能。

开发指南

首先需要在工程中添加activemq的依赖,为了方便,可以直接用activemq-all包。

1
2
3
4
5
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-all</artifactId>
<version>5.13.2</version>
</dependency>

Actviemq plugin的开发非常方便,只需要实现两个类。

一个是plugin类, 需要implements BrokerPlugin这个接口,然后将接口的installPlugin方法实现了即可。

这个方法的唯一作用就是指定一个broker类,代码如下:

1
2
3
public Broker installPlugin(Broker broker) throws Exception {
return new LimitQueueSIzeBroker(broker);
}

接下来就是在实现一个broker类,一般情况下可以extends BrokerFilter 这个类,然后选择需要的方法重写,构造方法和重写的每个方法执行完毕后,执行以下super类的对应方法,这样就不会对activemq的实际运行造成影响。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public FoxBroker(Broker next) {
super(next);
}

@Override
public void send(ProducerBrokerExchange producerExchange, Message messageSend) throws Exception {
try {
String ip = producerExchange.getConnectionContext().getConnection().getRemoteAddress();
String destinationName = messageSend.getDestination().getPhysicalName();
logger.info("send_" + destinationName + " " + ip);
} catch (Exception e) {
logger.error("activemq send log error: " + e, e);
}
super.send(producerExchange, messageSend);
}

这里我们重写了activemq的send方法, 并且增加了一些日志。

然后打包,将jar包放到activemq的lib目录下。再在activemq.xml下增加plugins这个模块,并把需要的插件依次列在里边。

1
2
3
4
<plugins>
<bean xmlns="http://www.springframework.org/schema/beans" id="testPlugin" class="com.mallow.activemq.FoxBrokerPlugin"/>
<bean xmlns="http://www.springframework.org/schema/beans" id="purgePlugin" class="com.mallow.activemq.LimitQueueSizePlugin"/>
</plugins>

重启activemq,再收发消息的时候就能看到效果了。

例子

可以参考我在github上放的代码: https://github.com/lcy362/FoxActivemqPlugin

里边提供了两个非常简单的插件示例。FoxBrokerPlugin会在收发消息时记录生产者、消费者的ip; LimitQueueSizePlugin则可以控制队列大小,队列中积压1000条消息以后新的消息就会被丢弃,在测试环境下会比较有用。

此外,activemq自己也提供了几个常用的插件,包括LoggingBrokerPlugin, StatisticsBrokerPlugin, 等, 也可以参考他们的实现。

0%