0%

在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, 等, 也可以参考他们的实现。

在命令行运行java程序时,如果想引入第三方jar包该怎么办呢。方法其实有很多,一般都是去折腾classloader.

这里介绍一种操作相对简单的方法,就是在运行之前把需要的jar包都加入到classpath中。

具体来说,就是写一个shell脚本,定义一个参数,可以就叫CLASSPATH, 也可以叫别的。

CLASSPATH=yourownjar.jar:xxx.jar:/xx/xx/xxx1.jar:”$CLASSPATH”

需要注意的是,自己写的主类所在的jar也要包含在自己定义的classpath中.

然后使用java -classpath命令运行即可:

java -classpath ${CLASSPATH} xx.Main

最近试图运行一个fatjar的时候报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Exception in thread "main" java.lang.SecurityException: Invalid signature file digest for Manifest main attributes
at sun.security.util.SignatureFileVerifier.processImpl(SignatureFileVerifier.java:287)
at sun.security.util.SignatureFileVerifier.process(SignatureFileVerifier.java:240)
at java.util.jar.JarVerifier.processEntry(JarVerifier.java:317)
at java.util.jar.JarVerifier.update(JarVerifier.java:228)
at java.util.jar.JarFile.initializeVerifier(JarFile.java:348)
at java.util.jar.JarFile.getInputStream(JarFile.java:415)
at sun.misc.URLClassPath$JarLoader$2.getInputStream(URLClassPath.java:775)
at sun.misc.Resource.cachedInputStream(Resource.java:77)
at sun.misc.Resource.getByteBuffer(Resource.java:160)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:436)
at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:482)

查了一下,跟jar包的签名有关。关于签名可以参考: http://www.cnblogs.com/jackofhearts/p/jar_signing.html

有些Jar包会在metainf里包含一个.SF:包含原Jar包内的class文件和资源文件的Hash, 用来校验文件的完整度等验证。

但是在打fat-jar的时候,我们是把很多jar包合成了一个,这样fatjar下就会存在各个jar包中的签名文件,但是他们显然无法跟最终的fatjar作校验。

解决方法就是打包时把签名文件全都去掉,如果是使用maven的话,可以使用shade插件:

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
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.7.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>

Redis 集群是redis官方提供的一种集群方案,从3.0开始提供稳定版,应用也已经比较广泛,也经受住了时间考验,个人感觉完全可以取代codis,tweemproxy等集群方案。

cluster原理介绍

cluster是使用数据分片的形式实现的,一个 Redis cluster集群包含 16384 个哈希槽, 任意一个key都可以通过 CRC16(key) % 16384 这个公式计算出应当属于哪个槽。每个槽应当落在哪个节点上,也是事先定好。这样,进行任一操作时,首先会根据key计算出对应的节点,然后操作相应的节点就可以了。

所以说,其实cluster跟单点相比,只是多了一个给key计算sharding值的过程,并没有增加多少复杂度,完全可以放心使用。

在这样的设计下,一些对节点的操作也很方便,操作过程中对client端也不会有影响。
1. 增加节点:将原有节点的某些slot转移到新节点上
2. 删除节点: 将节点上的槽转移到其他节点上以后,移除空节点
3. 节点故障:落到故障节点上的操作会失败,而集群其他部分可以正常访问,这样就不会出现一致性哈希方案的“雪崩”问题。单点的故障也可以通过配置slave解决,节点故障时可以由对应的slave顶替
4. 节点重启: 正常重启即可,节点会自动读之前的配置然后加入集群中。

安装操作流程

然后,我们可以实际安装一次,由于只是测试,装个两三个节点就可以,而如果是生产使用的话,至少需要3个master节点,也推荐同时至少有3个slave节点才能使用。

安装redis

到redis官网(https://redis.io/download)上下载最新的stable版本。

以3.2.8为例,下载完以后执行以下命令

1
2
3
$ tar xzf redis-3.2.8.tar.gz
$ cd redis-3.2.8
$ make

然后解压缩,make就可以了.

redis配置与启动

make之后会在redis的src目录下多出几个命令,包括redis-server, redis-cli等,可以把他们拷到/usr/local/bin下,这样就可以在任意路径下执行。也可以自己指到src目录下进行操作。

启动之前需要先修改一下配置,redis的根目录下有一个redis.conf文件,可以直接改它,也可以拷到别的路径下进行修改。

主要就是把cluster模式打开,然后配一下cluster配置文件。

1
2
cluster-enabled yes
cluster-config-file nodes.conf

其他的配置,port,bind,protect-mode之类的,可以根据需求改一下,不改也没关系。如果是想在一台机器上启动多个实例的话,需要给每个实例一个配置文件, port, cluster-config-file这些配置也需要区分一下。

然后执行以下命令,分别指定不同的配置文件启动。

1
2
redis-server redis-6379.conf &
redis-server redis-6380.conf &

开启cluster

需要的节点都启动以后,就可以开始配置cluster了。推荐的办法是使用官方提供的redis-trib.rb工具包,这个具体操作可以参考官方文档。我们在这里试一下手动操作,这样其实可以加深对cluster原理的理解。

首先通过redis-cli命令进入任意一个redis实例中,然后一次对其他几个节点执行meet命令。

1
Cluster meet xxx:6380

然后执行cluster nodes命令就可以看到节点被加入到集群中了。

这之后还有一步是分配slot, 可以使用cluster setslot命令,不过由于slot比较多,分配起来很麻烦。还有另外一种方式是修改nodes.conf这个配置文件。

Nodes.conf这个配置包含了cluster相关的几乎所有信息,一般情况下无需自己去操作,redis会自动生成,可以打开看一下,里边每个节点都对应一行,一行是类似这种格式:

1
8301106a1a0b7472650f22503abea23024df17fb 127.0.0.1:6379 myself,master - 0 0 1 connected

然后slot的分配情况是加在最后

1
8301106a1a0b7472650f22503abea23024df17fb 127.0.0.1:6379 myself,master - 0 0 1 connected 0-8000

需要注意的是,全部16384(0-16383)个slot都要分配出去,不能留空;集群中每个节点都对应一个Nodes.conf文件,每个文件里的slot配置要一致。

改完以后重启所有节点,在redis-cli中执行cluster info 命令,可以看到一些关键信息

1
2
3
4
5
6
7
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:2
cluster_size:1

所有slot已经分配出去,Cluster stat也变成了ok. 这样一个cluster就算搭建完毕了。

原文地址:http://lichuanyang.top/posts/9329/

我们知道,hbase中存放的数据就是二进制的键值对,不像redis一样提供了各种各样数据结构的支持。如果我们想在hbase中存储set型的数据,该怎么做呢?当然,一种方法就是把这个set当作一个对象整体的序列化之后存到hbase上,但这样后续无论增删改查,都需要先把存储内容拿回来,做相应的修改后,再整体覆盖原有值。这么做显然不太合理。

我们希望的效果至少要包括这么几个特点:
1. 元素自动去重,这是一个set的基本要求;
2. 增删改这些对集合单个元素的操作,无须处理其他元素;
3. 方便查询,包括查询整个集合,和判断某一特定元素是否存在

这里提供一种思路实现这种效果,就是把元素的值存放在hbase的qualifier中,作为一个键,而value则随便塞一个值,不实际使用。

这样,由于hbase本身会对qualifier判重,所以元素不会重复,所有对单个元素的操作,都只需要操作这个qualifier, 需要获取整个集合时,也可以通过直接查这一行来实现。

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
<?xml version="1.0" encoding="UTF-8" ?>
<configuration status="warn">

<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%p] %d %c %l - %m%n"/>
</Console>

<RollingFile name="activity" fileName="/opt/fox.log"
filePattern="/opt/fox.log.%d{yyyy-MM-dd}.gz">
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<PatternLayout pattern="[%p] %d - %m%n" charset="UTF-8"/>
</RollingFile>
<RollingFile name="fox_err" fileName="/opt/fox_err.log"
filePattern="/opt/fox_err..log.%d{yyyy-MM-dd}.gz">
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<PatternLayout pattern="[%p] %d %l - %m%n" charset="UTF-8"/>
</RollingFile>
</Appenders>
<Loggers>
<logger name="com.fox" additivity="false" level="info">
<appender-ref ref="activity" />
<appender-ref ref="activity_err" level="error"/>
</logger>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>

</configuration>

和log4j相比,主要有这么一些变化,

首先整体结构上变化很大,appender、logger都被集中到了各自的一个根节点下。

xml各节点的名称也采用了新设计,名称直接就是有用信息,不再是之前appender xxx=”xxx”, param xxx=”xxx”的形式。

然后一些属性,包括fileName等,只能作为节点属性配置,不能像log4j那样配置成子节点。

此外,log4j2归档时支持压缩,在RollingFile节点的filePattern属性里将文件名后缀写成gz,zip等压缩格式,log4j2会自动选择相应压缩算法进行压缩。

现在发现的就这些,引入这个xml配置,再引用log4j-core, log4j-api包,就可以使用log4j2了。此外,如果有需要,可以用log4j-slf4j-impl,log4j-jcl,log4j-1.2-api分别实现对slf4j, jcl,log4j的兼容。

java里比较两个list的值是否一致,不考虑顺序,有多种方法,比如排序后直接用equals比较,相互之间执行两次containsAll等,这些办法都需要我们给list的元素类实现equals和hashcode方法。但是有一种特殊情况,如果我们并不方便去实习类的equals方法,例如是一个古老的第三方jar包,改代码会带来很多未知问题,这时候该怎么办呢。

其实很简单,万能的apache-commons早就想到了这一点,所以在commons-collections4中增加了外部输入equals和hashcode的方法,甚至equals和hashcode方法本身也不需要我们自己写代码,可以用comons-lang包实现,具体代码如下

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>

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
public static <T> boolean isEqualCollection(Collection<T> l1, Collection<T> l2, final String... exludedFields) {
Equator<T> equator = generateEquator(exludedFields);
return CollectionUtils.isEqualCollection(l1, l2, equator);
}

private static <T> Equator<T> generateEquator(final String... exludedFields) {
Equator<T> equator = new Equator<T>() {
@Override
public boolean equate(T o1, T o2) {
if (o1 == null && o2 == null) {
return true;
}
if (o1 == null || o2 == null) {
return false;
}
if (o1.getClass() != o2.getClass()) {
return false;
}
return EqualsBuilder.reflectionEquals(o1, o2, exludedFields);
}

@Override
public int hash(T o) {
return HashCodeBuilder.reflectionHashCode(o, exludedFields);
}
};
return equator;
}