ARTEMIS-684 Random is not equaly distributed among different VMs

This commit is contained in:
Clebert Suconic 2016-08-23 17:10:03 -04:00
parent 2bff3d2b9b
commit 92c5d5cd50
7 changed files with 116 additions and 58 deletions

View File

@ -26,7 +26,11 @@ import org.apache.activemq.artemis.api.core.SimpleString;
public class RandomUtil {
// Constants -----------------------------------------------------
protected static final Random random = new Random(System.currentTimeMillis());
protected static final Random random = new Random();
public static Random getRandom() {
return random;
}
// Attributes ----------------------------------------------------
@ -75,7 +79,7 @@ public class RandomUtil {
}
public static int randomInterval(final int min, final int max) {
return min + randomMax(max - min);
return min + random.nextInt(max - min);
}
public static int randomMax(final int max) {

View File

@ -16,7 +16,7 @@
*/
package org.apache.activemq.artemis.api.core.client.loadbalance;
import org.apache.activemq.artemis.utils.Random;
import org.apache.activemq.artemis.utils.RandomUtil;
/**
* {@link RandomConnectionLoadBalancingPolicy#select(int)} returns a (pseudo) random integer between
@ -24,8 +24,6 @@ import org.apache.activemq.artemis.utils.Random;
*/
public final class RandomConnectionLoadBalancingPolicy implements ConnectionLoadBalancingPolicy {
private final Random random = new Random();
/**
* Returns a pseudo random number between {@code 0} (inclusive) and {@code max} exclusive.
*
@ -34,6 +32,6 @@ public final class RandomConnectionLoadBalancingPolicy implements ConnectionLoad
*/
@Override
public int select(final int max) {
return random.getRandom().nextInt(max);
return RandomUtil.randomInterval(0, max);
}
}

View File

@ -16,15 +16,13 @@
*/
package org.apache.activemq.artemis.api.core.client.loadbalance;
import org.apache.activemq.artemis.utils.Random;
import org.apache.activemq.artemis.utils.RandomUtil;
/**
* {@link RandomConnectionLoadBalancingPolicy#select(int)} chooses a the initial node randomly then subsequent requests return the same node
*/
public final class RandomStickyConnectionLoadBalancingPolicy implements ConnectionLoadBalancingPolicy {
private final Random random = new Random();
private int pos = -1;
/**
@ -33,7 +31,7 @@ public final class RandomStickyConnectionLoadBalancingPolicy implements Connecti
@Override
public int select(final int max) {
if (pos == -1) {
pos = random.getRandom().nextInt(max);
pos = RandomUtil.randomInterval(0, max);
}
return pos;

View File

@ -18,7 +18,7 @@ package org.apache.activemq.artemis.api.core.client.loadbalance;
import java.io.Serializable;
import org.apache.activemq.artemis.utils.Random;
import org.apache.activemq.artemis.utils.RandomUtil;
/**
* RoundRobinConnectionLoadBalancingPolicy corresponds to a round-robin load-balancing policy.
@ -31,8 +31,6 @@ public final class RoundRobinConnectionLoadBalancingPolicy implements Connection
private static final long serialVersionUID = 7511196010141439559L;
private final Random random = new Random();
private boolean first = true;
private int pos;
@ -41,7 +39,7 @@ public final class RoundRobinConnectionLoadBalancingPolicy implements Connection
public int select(final int max) {
if (first) {
// We start on a random one
pos = random.getRandom().nextInt(max);
pos = RandomUtil.randomInterval(0, max);
first = false;
}

View File

@ -1,39 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.utils;
import java.io.Serializable;
public class Random implements Serializable {
private static int extraSeed;
private static final long serialVersionUID = 40335522290950498L;
private static synchronized long getSeed() {
long seed = System.currentTimeMillis() + Random.extraSeed++;
return seed;
}
private final java.util.Random random = new java.util.Random(Random.getSeed());
public java.util.Random getRandom() {
return random;
}
}

View File

@ -0,0 +1,102 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.tests.unit.core.util;
import java.util.HashSet;
import org.apache.activemq.artemis.tests.util.SpawnedVMSupport;
import org.apache.activemq.artemis.utils.RandomUtil;
import org.junit.Assert;
import org.junit.Test;
/** This test will start many parallel VMs, to make sure each VM would generate a good distribution of random numbers */
public class RandomUtilDistributionTest {
public static void main(String[] arg) {
long start = Long.parseLong(arg[0]);
try {
Thread.sleep((start - System.currentTimeMillis()) / 2);
}
catch (Exception e) {
}
while (System.currentTimeMillis() < start) {
Thread.yield();
}
int value;
value = RandomUtil.randomInterval(0, 255);
System.exit(value);
}
@Test
public void testDistribution() throws Exception {
int numberOfStarts = 50;
int iterations = 10;
int value = 0;
for (int i = 0; i < iterations; i++) {
int v = internalDistributionTest(numberOfStarts);
value += v;
}
// I'm using an extra parenthesis here to avoid rounding problems.
// Be careful removing it (make sure you know what you're doing in case you do so)
int minimumExpected = (int)((iterations * numberOfStarts) * 0.80);
System.out.println("value=" + value + ", minimum expected = " + minimumExpected);
Assert.assertTrue("The Random distribution is pretty bad. All tries have returned duplicated randoms. value=" + value + ", minimum expected = " + minimumExpected, value > minimumExpected);
}
private int internalDistributionTest(int numberOfTries) throws Exception {
long timeStart = System.currentTimeMillis() + 5000;
Process[] process = new Process[numberOfTries];
int[] value = new int[numberOfTries];
try {
for (int i = 0; i < numberOfTries; i++) {
process[i] = SpawnedVMSupport.spawnVM(RandomUtilDistributionTest.class.getName(), true, "" + timeStart);
}
HashSet<Integer> valueSet = new HashSet<>();
for (int i = 0; i < numberOfTries; i++) {
value[i] = process[i].waitFor();
Assert.assertTrue(value[i] >= 0);
valueSet.add(process[i].exitValue());
}
System.out.println("Generated " + valueSet.size() + " randoms out of " + numberOfTries + " tries");
return valueSet.size();
}
finally {
for (Process p : process) {
if (p != null) {
p.destroy();
}
}
}
}
}

View File

@ -32,7 +32,6 @@ import java.nio.ByteBuffer;
import org.junit.Assert;
import org.apache.activemq.artemis.utils.DataConstants;
import org.apache.activemq.artemis.utils.Random;
import org.apache.activemq.artemis.utils.RandomUtil;
import org.apache.activemq.artemis.utils.UTF8Util;
@ -44,8 +43,7 @@ public class UTF8Test extends ActiveMQTestBase {
byte[] bytes = new byte[20000];
Random random = new Random();
random.getRandom().nextBytes(bytes);
RandomUtil.getRandom().nextBytes(bytes);
String str = new String(bytes);
@ -59,12 +57,11 @@ public class UTF8Test extends ActiveMQTestBase {
@Test
public void testValidateUTFOnDataInput() throws Exception {
for (int i = 0; i < 100; i++) {
Random random = new Random();
// Random size between 15k and 20K
byte[] bytes = new byte[15000 + RandomUtil.randomPositiveInt() % 5000];
random.getRandom().nextBytes(bytes);
RandomUtil.getRandom().nextBytes(bytes);
String str = new String(bytes);