最近在业务中,遇到一个有趣的业务场景, 有4个(为了说明问题只说4个) 随机物品, 分别是 A, B ,C ,D;
其中 B C D 的权重是1000,A的权重是1; 也就是说运营希望A 出现的概率很低,A很稀有尽量提高用户抽到第四次才能出A,A必出(保底),但是A 出现的根据随机次数的增加而增加,随机到A后刷新概率从头随机。
实际场景中随机奖品 有十几个,这里要说明的是,当用户非常多,访问量非常大的时候,我们可以对随机结果进行缓存器 ,这样下次随机的时候,就可以快速拿到随机结果。这里强调一点:预缓存随机结果,简化传递参数。
第一次随机出A的概率 1/3001;
第二次随机出A的概率 1/2001;
第三次随机出A的概率 1/1001;
第四次随机出A的概率 1/1;//必出
先写一段测试代码
import java.util.HashMap;
import java.util.Map.Entry;
public class Test {
static HashMap<Integer,Integer> map=new HashMap<>();//记录 随机出现A 在次数中的统计
//生成测试数据A B C D 四个概率的情况进行测试
static String[] values=new String[] {"D","C","B","A"};//把A放到最後,方便保底
static int[] weights=new int[] {1000,1000,1000,1};
public static void main(String[] args) {
int roundCount = 3001000;
for(int i=0;i<roundCount;i++) {//循环10000次,每次都抽到A 才返回
randomA ();
}
System.out.println("随机"+roundCount+"轮 ");
for(Entry<Integer, Integer> entrentryy:map.entrySet()) {
Entry<Integer, Integer> entry = entrentryy;
int count=entry.getKey()+1;
System.out.println("第"+count+"次出现A的数量是:"+entry.getValue());
}
}
private static void randomA( ) {
for(int j=0;j<4;j++) {
RandomModel<String> random=new RandomModel<>();//隨機模型
//存入隨機項
for(int h=j;h<4;h++) {
random.put(weights[h], values[h]);
}
//隨機
String value=random.random();
if("A".equals(value)) {
Integer num= map.get(j);
if(num==null) {
num=0;
}
num++;
map.put(j, num);//統計在第幾次出現 A
break;
}
}
}
}
------------------------------------------------------------------------------------------------------
import java.util.ArrayList;
import java.util.Random;
public class RandomModel<T> {
ArrayList<Item<T>> items=new ArrayList < Item<T>>();//存储所有参与权重的随机数
private int sumWeight;//权重总合
/** 存入一个概率 物品(t)
* @param weight 权重
* @param t
*/
public void put(int weight,T t) {
if(weight<1) throw new RuntimeException("权重值应该大于0的整数");
sumWeight+=weight;
Item<T> item=new Item<>();
item.odds=sumWeight;
item.t=t;
items.add(item);
}
public T random() {
int randomValue=new Random().nextInt(sumWeight);//根据权重总和计算随机数。例如,如果权重总和是100,随机数0~99
for(Item<T> item:this.items) {
if(randomValue<item.odds) {
return item.t;
}
}
throw new RuntimeException("永远都不执行的代码");
}
class Item<T>{
public int odds;
public T t;
}
}
---------------------------------------------------------------------------------------------------------------------
代码基础添加了打印 消耗的时间。
控制台输出:
随机3001000轮 總耗時: 1571毫秒
第1次出现A的数量是:1019
第2次出现A的数量是:1439
第3次出现A的数量是:2986
第4次出现A的数量是:2995556
-----------------------------------------------------------------------------
当用户负载较高,用户请求次数较高时,预缓存随机结果,可以大大提高高峰时的响应时间。
例子中没一轮必出A,当随机出A的时候,就结束这轮随机。
可以利用服务器空间存储结果,以换取对客户端的快速响应改造后的test2
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
public class Test2 {
static HashMap<Integer,Integer> map=new HashMap<>();//记录 随机出现A 在次数中的统计
//生成测试数据A B C D 四个概率的情况进行测试
static String[] values=new String[] {"D","C","B","A"};//把A放到最後,方便保底
static int[] weights=new int[] {1000,1000,1000,1};
public static void main(String[] args) {
int roundCount = 3001000;
for(int i=0;i<roundCount;i++) {//循环10000次,每次都抽到A 才返回
randomValue();
}
long time=System.currentTimeMillis();
for(int i=0;i<roundCount;i++) {//循环10000次,每次都抽到A 才返回
randomFromCache();
}
time=System.currentTimeMillis()-time;
System.out.println("随机"+roundCount+"轮 读取缓存總耗時: "+time+ "毫秒 ");
for(Entry<Integer, Integer> entrentryy:map.entrySet()) {
Entry<Integer, Integer> entry = entrentryy;
int count=entry.getKey()+1;
System.out.println("第"+count+"次出现A的数量是:"+entry.getValue());
}
}
static LinkedList<List<String>> resultCache=new LinkedList<>();//緩存結果
private static void randomValue( ) {
ArrayList<String> arr=new ArrayList<>();
resultCache.addLast(arr);//緩存
for(int j=0;j<4;j++) {
RandomModel<String> random=new RandomModel<>();//隨機模型
//存入隨機項
for(int h=j;h<4;h++) {
random.put(weights[h], values[h]);
}
//隨機
String value=random.random();
arr.add(value);
if("A".equals(value)) {
break;
}
}
}
private static void randomFromCache() {
List<String> arr=resultCache.removeFirst();
for(int j=0;j<arr.size();j++) {
String value=arr.get(j);
if("A".equals(value)) {
Integer num= map.get(j);
if(num==null) {
num=0;
}
num++;
map.put(j, num);//統計在第幾次出現 A
break;
}
}
}
}
--------------------------------------------------执行测试--------------------------------------------------------
控制台输出
随机3001000轮 读取缓存總耗時: 164毫秒
第1次出现A的数量是:1005
第2次出现A的数量是:1523
第3次出现A的数量是:2977
第4次出现A的数量是:2995495
看到响应时间缩短了好几倍,这个就提高了响应客户端的速度。测试代码中包含,统计代码也比较耗时,为了更好的查看使用缓存带来的速度提升,我们去掉统计个数的代码,
Integer num= map.get(j);
if(num==null) {
num=0;
}
num++;
map.put(j, num);//統計在第幾次出現 A
再看结果。
------------------------------------------------------------------------------------------------------------------------
不执行缓存:随机3001000轮 總耗時: 1561毫秒
随机3001000轮 读取缓存總耗時: 109毫秒
这里出了读取缓存需要的时间以外,还要额外在存入缓存时消耗的时间,加上随机数据再存入缓存,需要的时间总体更多,但是对于 有服务器的应用来说,有效的使用缓存,提高了服务器响应时间15倍左右,这对于用于请求多的时候,会大大提高性能。 这里还有线程安全都能问题,由于不是要叙述的重点,所以忽略。
通过使用randomModel 类可以很简单的随机 使用权重的业务,使用jvm(java虚拟机)内存以空间换取时间能大大提高对客户端的响应时间,从而提高负载。