SofaTracer源码分析之日志采样原理
在SofaTracer中,有两种类型的日志采样,一是固定比率的采样,这也是默认的采样方式,二是自定义采样器。固定比率的采样只需要我们在项目的配置文件里面填上下列信息:
com.alipay.sofa.tracer.samplerName=PercentageBasedSampler
com.alipay.sofa.tracer.samplerPercentage=100
自定义采样器需要我们提供了一个Sampler接口的实现类,并重写sample方法,然后在配置文件里指定我们自定义的采样器。
com.alipay.sofa.tracer.samplerCustomRuleClassName = xxx.CustomOpenRulesSamplerRuler
1、Sampler接口
我们先来看看Sampler接口的类图:
里面最重要的一个方法就是sample(SofaTracerSpan sofaTracerSpan),方法返回SamplingStatus,这个SamplingStatus维护一个布尔类型的采样标记,和一些tags,在SofaTracer中,也只有唯一的一个实现类SofaTracerPercentageBasedSampler。
2、SofaTracerPercentageBasedSampler
SofaTacer中的日志采样是采用蓄水池算法获取一个BitSet。关于蓄水池算法和这个BitSet,后面我们专门说。
private final BitSet sampleDecisions;
private final SamplerProperties configuration;
public SofaTracerPercentageBasedSampler(SamplerProperties configuration) {
int outOf100 = (int) (configuration.getPercentage());
this.sampleDecisions = randomBitSet(100, outOf100, new Random());
this.configuration = configuration;
}
在SofaTracerPercentageBasedSampler中:
@1:维护一个SamplerProperties,这个SamplerProperties里面的值就是我们在配置文件配置的采样比率和自定义的类的类名。
@2:维护一个BitSet,这个BitSet的大小是100,在构建这个BitSet时,要从所有日志中抓取的日志比例就是我们传入的百分比取整数,如果我们传的是100,那么就会全部采样,如果是20,那么就会采样20%.
2.1 SofaTracerPercentageBasedSampler#sample
public SamplingStatus sample(SofaTracerSpan sofaTracerSpan) {
SamplingStatus samplingStatus = new SamplingStatus();
Map<String, Object> tags = new HashMap<String, Object>();
//@1
tags.put(SofaTracerConstant.SAMPLER_TYPE_TAG_KEY, TYPE);
tags.put(SofaTracerConstant.SAMPLER_PARAM_TAG_KEY, configuration.getPercentage());
tags = Collections.unmodifiableMap(tags);
samplingStatus.setTags(tags);
//@2
if (this.configuration.getPercentage() == 0) {
samplingStatus.setSampled(false);
return samplingStatus;
} else if (this.configuration.getPercentage() == 100) {
samplingStatus.setSampled(true);
return samplingStatus;
}
//@3
boolean result = this.sampleDecisions.get((int) (this.counter.getAndIncrement() % 100));
samplingStatus.setSampled(result);
return samplingStatus;
}
@1:往SamplingStatus的tags属性里面填充相关信息,这里面主要放的是:
sampler.type--->PercentageBasedSampler
sampler.param--->我们配置的采样比率
@2:这是两个采样的极端,如果配置的比率为0,那么就不采样,如果配置的是100的话,那么就会全部采样,默认百分比为100
@3:根据蓄水池算法获取的BitSet,然后用一个原子整数变量自增后跟100取模,用这个结果作为下标从BitSet里面取值,如果为true,则采样。
2.2 SofaTracerPercentageBasedSampler#randomBitSet
public static BitSet randomBitSet(int size, int cardinality, Random rnd) {
BitSet result = new BitSet(size);
int[] chosen = new int[cardinality];
int i;
//@1
for (i = 0; i < cardinality; ++i) {
chosen[i] = i;
result.set(i);
}
//@2
for (; i < size; ++i) {
int j = rnd.nextInt(i + 1);
if (j < cardinality) {
result.clear(chosen[j]);
result.set(i);
chosen[j] = i;
}
}
return result;
}
这就是根据蓄水池算法获取到的Bitset,在stackoverflow上,可以看到实现。
@1:从下标0开始,直到(cardinality-1),设置BitSet
@2: 在BitSet中随机设置下标值,已达到分散的目的。
3、BitSet
BitSet的实现位于java.util包中。BitSet类实现了一个按需增长的位向量。BitSet的每一个组件都有一个boolean值。用非负的整数将BitSet的位编入索引。可以对每个编入索引的位进行测试、设置或者清除。通过逻辑与、逻辑或和逻辑异或操作,可以使用一个 BitSet修改另一个 BitSet的内容。默认情况下,set 中所有位的初始值都是false。每个位 set 都有一个当前大小,也就是该位 set 当前所用空间的位数。注意,这个大小与位 set 的实现有关,所以它可能随实现的不同而更改。位 set 的长度与位 set 的逻辑长度有关,并且是与实现无关而定义的。
1、如果指定了bitset的初始化大小,那么会把他规整到一个大于或者等于这个数字的64的整倍数。比如64位,bitset的大小是1个long,而65位时,bitset大小是2个long,即128位。做这么一个规定,主要是为了内存对齐,同时避免考虑到不要处理特殊情况,简化程序。
2:BitSet的size方法:返回此 BitSet 表示位值时实际使用空间的位数,值是64的整数倍
length方法:返回此 BitSet 的“逻辑大小”:BitSet 中最高设置位的索引加 1
3.1 使用场景
常见的应用场景是对海量数据进行一些统计工作,比如日志分析、用户数统计等。例如:
有1千万个随机数,随机数的范围在1到1亿之间。将1到1亿之间没有在随机数中的数求出来
public static void main(String[] args)
{
Random random=new Random();
List<Integer> list=new ArrayList<>();
for(int i=0;i<10000000;i++)
{
int randomResult=random.nextInt(100000000);
list.add(randomResult);
}
BitSet bitSet=new BitSet(100000000);
for(int i=0;i<10000000;i++)
{
bitSet.set(list.get(i));
}
System.out.println("0~1亿不在上述随机数中有"+bitSet.cardinality());
for (int i = 0; i < 100000000; i++)
{
if(!bitSet.get(i))
{
System.out.println(i);
}
}
}