disruptor-3.3.2源码解析(1)-序列

disruptor-3.3.2源码解析(1)-序列

作者:大飞

 

  • Disruptor中的序列-Sequence:
       disruptor中较为重要的一个类是Sequence。我们设想下,在disruptor运行过程中,事件发布者(生产者)和事件处理者(消费者)在ringbuffer上相互追逐,由什么来标记它们的相对位置呢?它们根据什么从ringbuffer上发布或者处理事件呢?就是这个Sequence-序列。
       我们看一下这个类的源代码,先看结构:
class LhsPadding{
    protected long p1, p2, p3, p4, p5, p6, p7;
}
class Value extends LhsPadding{
    protected volatile long value;
}
class RhsPadding extends Value{
    protected long p9, p10, p11, p12, p13, p14, p15;
}

public class Sequence extends RhsPadding{
    static final long INITIAL_VALUE = -1L;
    private static final Unsafe UNSAFE;
    private static final long VALUE_OFFSET;
    static{
        UNSAFE = Util.getUnsafe();
        try{
            VALUE_OFFSET = UNSAFE.objectFieldOffset(Value.class.getDeclaredField("value"));
        }catch (final Exception e){
            throw new RuntimeException(e);
        }
    }
    /**
     * 默认初始值为-1
     */
    public Sequence(){
        this(INITIAL_VALUE);
    }

    public Sequence(final long initialValue){
        UNSAFE.putOrderedLong(this, VALUE_OFFSET, initialValue);
    }
        我们可以注意到两点:
              1.通过Sequence的一系列的继承关系可以看到,它真正的用来计数的域是value,在value的前后各有7个long型的填充值,这些值在这里的作用是做cpu cache line填充,防止发生伪共享。
              2.value域本身由volatile修饰,而且又看到了Unsafe类,大概猜到是要做原子操作了。
 
       继续看一下Sequence中的方法: 
    public long get(){
        return value;
    }
    /**
     * ordered write
     * 在当前写操作和任意之前的读操作之间加入Store/Store屏障
     */
    public void set(final long value){
        UNSAFE.putOrderedLong(this, VALUE_OFFSET, value);
    }
    /**
     * volatile write
     * 在当前写操作和任意之前的读操作之间加入Store/Store屏障
     * 在当前写操作和任意之后的读操作之间加入Store/Load屏障
     */
    public void setVolatile(final long value){
        UNSAFE.putLongVolatile(this, VALUE_OFFSET, value);
    }

    public boolean compareAndSet(final long expectedValue, final long newValue){
        return UNSAFE.compareAndSwapLong(this, VALUE_OFFSET, expectedValue, newValue);
    }

    public long incrementAndGet(){
        return addAndGet(1L);
    }

    public long addAndGet(final long increment){
        long currentValue;
        long newValue;
        do{
            currentValue = get();
            newValue = currentValue + increment;
        }while (!compareAndSet(currentValue, newValue));
        return newValue;
    }
       可见Sequence是一个"原子"的序列。
       总结一下:Sequence是一个做了缓存行填充优化的原子序列。
 
       再看个FixedSequenceGroup类: 
public final class FixedSequenceGroup extends Sequence{
   
    private final Sequence[] sequences;

    public FixedSequenceGroup(Sequence[] sequences){
        this.sequences = Arrays.copyOf(sequences, sequences.length);
    }

    @Override
    public long get(){
        return Util.getMinimumSequence(sequences);
    }

    @Override
    public void set(long value){
        throw new UnsupportedOperationException();
    }
    
    ...

}
        FixedSequenceGroup相当于包含了若干序列的一个包装类,尽管本身继承了Sequence,但只是重写了get方法,获取内部序列组中最小的序列值,但其他的"写"方法都不支持。
 
  • 上面看了序列的内容,框架中也针对序列的使用,提供了专门的功能接口Sequencer:
       Sequencer接口扩展了Cursored和Sequenced。先看下这两个货:
public interface Cursored{
    long getCursor();
}
       Cursored接口只提供了一个获取当前序列值(游标)的方法。
public interface Sequenced{
    /**
     * 数据结构中事件槽的个数(就是RingBuffer的容量)
     */
    int getBufferSize();
    /**
     * 判断是否还有给定的可用容量。
     */
    boolean hasAvailableCapacity(final int requiredCapacity);
    /**
     * 获取剩余容量。
     */
    long remainingCapacity();
    /**
     * 申请下一个序列值,用来发布事件。
     */
    long next();
    /**
     * 申请下N个序列值,用来发布事件。
     */
    long next(int n);
    /**
     * 尝试申请下一个序列值用来发布事件,这个是无阻塞的方法。
     */
    long tryNext() throws InsufficientCapacityException;
    /**
     * 尝试申请下N个序列值用来发布事件,这个是无阻塞的方法。
     */
    long tryNext(int n) throws InsufficientCapacityException;
    /**
     * 在给定的序列值上发布事件,当填充好事件后会调用这个方法。
     */
    void publish(long sequence);
    /**
     * 在给定的序列返回上发布事件,当填充好事件后会调用这个方法。
     */
    void publish(long lo, long hi);
}
       最后看下Sequencer接口: 
public interface Sequencer extends Cursored, Sequenced{
    /** 序列初始值 */
    long INITIAL_CURSOR_VALUE = -1L;
    /**
     * 声明一个序列,这个方法在初始化RingBuffer的时候被调用。
     */
    void claim(long sequence);
    /**
     * 判断一个序列是否被发布,并且发布到序列上的事件是可处理的。非阻塞方法。
     */
    boolean isAvailable(long sequence);
    /**
     * 添加一些追踪序列到当前实例,添加过程是原子的。
     * 这些控制序列一般是其他组件的序列,当前实例可以通过这些
     * 序列来查看其他组件的序列使用情况。
     */
    void addGatingSequences(Sequence... gatingSequences);
    /**
     * 移除控制序列。
     */
    boolean removeGatingSequence(Sequence sequence);
    /**
     * 基于给定的追踪序列创建一个序列栅栏,这个栅栏是提供给事件处理者
     * 在判断Ringbuffer上某个事件是否能处理时使用的。
     */
    SequenceBarrier newBarrier(Sequence... sequencesToTrack);
    /**
     * 获取控制序列里面当前最小的序列值。
     */
    long getMinimumSequence();
    /**
     * 获取RingBuffer上安全使用的最大的序列值。
     * 具体实现里面,这个调用可能需要序列上从nextSequence到availableSequence之间的值。
     * 如果没有比nextSequence大的可用序列,会返回nextSequence - 1。
     * 为了保证正确,事件处理者应该传递一个比最后的序列值大1个单位的序列来处理。
     */
    long getHighestPublishedSequence(long nextSequence, long availableSequence);

    /*
     * 通过给定的数据提供者和控制序列来创建一个EventPoller
     */
    <T> EventPoller<T> newPoller(DataProvider<T> provider, Sequence...gatingSequences);
}
 
       这里要注意一下:
              Sequencer接口的很多功能是提供给事件发布者用的。
              通过Sequencer可以得到一个SequenceBarrier,这货是提供给事件处理者用的。
 
        框架中针对Sequencer接口提供了2种实现:SingleProducerSequencer和MultiProducerSequencer。
       看这两个类之前,先看下它们的基类AbstractSequencer:
public abstract class AbstractSequencer implements Sequencer{

    private static final AtomicReferenceFieldUpdater<AbstractSequencer, Sequence[]> SEQUENCE_UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(AbstractSequencer.class, Sequence[].class, "gatingSequences");
    protected final int bufferSize;
    protected final WaitStrategy waitStrategy;
    protected final Sequence cursor = new Sequence(Sequencer.INITIAL_CURSOR_VALUE);
    protected volatile Sequence[] gatingSequences = new Sequence[0];

    public AbstractSequencer(int bufferSize, WaitStrategy waitStrategy){
        if (bufferSize < 1){
            throw new IllegalArgumentException("bufferSize must not be less than 1");
        }
        if (Integer.bitCount(bufferSize) != 1){
            throw new IllegalArgumentException("bufferSize must be a power of 2");
        }
        this.bufferSize = bufferSize;
        this.waitStrategy = waitStrategy;
    }

    @Override
    public final long getCursor(){
        return cursor.get();
    }

    @Override
    public final int getBufferSize(){
        return bufferSize;
    }

    @Override
    public final void addGatingSequences(Sequence... gatingSequences){
        SequenceGroups.addSequences(this, SEQUENCE_UPDATER, this, gatingSequences);
    }

    @Override
    public boolean removeGatingSequence(Sequence sequence){
        return SequenceGroups.removeSequence(this, SEQUENCE_UPDATER, sequence);
    }

    @Override
    public long getMinimumSequence(){
        return Util.getMinimumSequence(gatingSequences, cursor.get());
    }

    @Override
    public SequenceBarrier newBarrier(Sequence... sequencesToTrack){
        return new ProcessingSequenceBarrier(this, waitStrategy, cursor, sequencesToTrack);
    }

    @Override
    public <T> EventPoller<T> newPoller(DataProvider<T> dataProvider, Sequence... gatingSequences){
        return EventPoller.newInstance(dataProvider, this, new Sequence(), cursor, gatingSequences);
    }
}
       可见,基类基本上的作用就是管理追踪序列和关联当前序列。
 
       先看下SingleProducerSequencer,还是先看结构:
bstract class SingleProducerSequencerPad extends AbstractSequencer{
    protected long p1, p2, p3, p4, p5, p6, p7;
    public SingleProducerSequencerPad(int bufferSize, WaitStrategy waitStrategy){
        super(bufferSize, waitStrategy);
    }
}
abstract class SingleProducerSequencerFields extends SingleProducerSequencerPad{
    public SingleProducerSequencerFields(int bufferSize, WaitStrategy waitStrategy){
        super(bufferSize, waitStrategy);
    }
    protected long nextValue = Sequence.INITIAL_VALUE;
    protected long cachedValue = Sequence.INITIAL_VALUE;
}

public final class SingleProducerSequencer extends SingleProducerSequencerFields{
    protected long p1, p2, p3, p4, p5, p6, p7;

    public SingleProducerSequencer(int bufferSize, final WaitStrategy waitStrategy){
        super(bufferSize, waitStrategy);
    }
       又是缓存行填充,真正使用的值是nextValue和cachedValue。
 
       再看下里面的方法实现:
    @Override
    public boolean hasAvailableCapacity(final int requiredCapacity){       
        long nextValue = this.nextValue;
        long wrapPoint = (nextValue + requiredCapacity) - bufferSize;
        long cachedGatingSequence = this.cachedValue;
        if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue){
            long minSequence = Util.getMinimumSequence(gatingSequences, nextValue);
            this.cachedValue = minSequence;
            if (wrapPoint > minSequence){
                return false;
            }
        }
        return true;
    }
        hasAvailableCapacity方法可以这样理解:
              当前序列的nextValue + requiredCapacity是事件发布者要申请的序列值。
              当前序列的cachedValue记录的是之前事件处理者申请的序列值。
              想一下一个环形队列,事件发布者在什么情况下才能申请一个序列呢?
              事件发布者当前的位置在事件处理者前面,并且不能从事件处理者后面追上事件处理者(因为是环形),
              即 事件发布者要申请的序列值大于事件处理者之前的序列值 且 事件发布者要申请的序列值减去环的长度要小于事件处理者的序列值 
              如果满足这个条件,即使不知道当前事件处理者的序列值,也能确保事件发布者可以申请给定的序列。
              如果不满足这个条件,就需要查看一下当前事件处理者的最小的序列值(因为可能有多个事件处理者),如果当前要申请的序列值比当前事件处理者的最小序列值大了一圈(从后面追上了),那就不能申请了(申请的话会覆盖没被消费的事件),也就是说没有可用的空间(用来发布事件)了,也就是hasAvailableCapacity方法要表达的意思。
 
    @Override
    public long next(){
        return next(1);
    }

    @Override
    public long next(int n){
        if (n < 1) {
            throw new IllegalArgumentException("n must be > 0");
        }
        long nextValue = this.nextValue;
        long nextSequence = nextValue + n;
        long wrapPoint = nextSequence - bufferSize;
        long cachedGatingSequence = this.cachedValue;
        if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue){
            long minSequence;
            while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue))){
                LockSupport.parkNanos(1L); // TODO: Use waitStrategy to spin?
            }
            this.cachedValue = minSequence;
        }
        this.nextValue = nextSequence;
        return nextSequence;
    }
       next方法是真正申请序列的方法,里面的逻辑和hasAvailableCapacity一样,只是在不能申请序列的时候会阻塞等待一下,然后重试。 
 
    @Override
    public long tryNext() throws InsufficientCapacityException{
        return tryNext(1);
    }

    @Override
    public long tryNext(int n) throws InsufficientCapacityException{
        if (n < 1){
            throw new IllegalArgumentException("n must be > 0");
        }
        if (!hasAvailableCapacity(n)){
            throw InsufficientCapacityException.INSTANCE;
        }
        long nextSequence = this.nextValue += n;
        return nextSequence;
    }
       tryNext方法是next方法的非阻塞版本,不能申请就抛异常。 
 
    @Override
    public long remainingCapacity(){
        long nextValue = this.nextValue;
        long consumed = Util.getMinimumSequence(gatingSequences, nextValue);
        long produced = nextValue;
        return getBufferSize() - (produced - consumed);
    }
       remainingCapacity方法就是环形队列的容量减去事件发布者与事件处理者的序列差。 
 
    @Override
    public void claim(long sequence){
        this.nextValue = sequence;
    }
       claim方法是声明一个序列,在初始化的时候用。 
 
    @Override
    public void publish(long sequence){
        cursor.set(sequence);
        waitStrategy.signalAllWhenBlocking();
    }
    @Override
    public void publish(long lo, long hi){
        publish(hi);
    }
       发布一个序列,会先设置内部游标值,然后唤醒等待的事件处理者。
 
       看下剩下的方法: 
    @Override
    public boolean isAvailable(long sequence){
        return sequence <= cursor.get();
    }
    @Override
    public long getHighestPublishedSequence(long lowerBound, long availableSequence){
        return availableSequence;
    }
 
 
       再看下MultiProducerSequencer,还是先看结构:
public final class MultiProducerSequencer extends AbstractSequencer{
    private static final Unsafe UNSAFE = Util.getUnsafe();
    private static final long BASE  = UNSAFE.arrayBaseOffset(int[].class);
    private static final long SCALE = UNSAFE.arrayIndexScale(int[].class);
    private final Sequence gatingSequenceCache = new Sequence(Sequencer.INITIAL_CURSOR_VALUE);
    // availableBuffer是用来记录每一个ringbuffer槽的状态。
    private final int[] availableBuffer;
    private final int indexMask;
    private final int indexShift;

    public MultiProducerSequencer(int bufferSize, final WaitStrategy waitStrategy){
        super(bufferSize, waitStrategy);
        availableBuffer = new int[bufferSize];
        indexMask = bufferSize - 1;
        indexShift = Util.log2(bufferSize);
        initialiseAvailableBuffer();
    }
       MultiProducerSequencer内部多了一个availableBuffer,是一个int型的数组,size大小和RingBuffer的Size一样大,用来追踪Ringbuffer每个槽的状态,构造MultiProducerSequencer的时候会进行初始化,availableBuffer数组中的每个元素会被初始化成-1。
 
       再看下里面的方法实现: 
    @Override
    public boolean hasAvailableCapacity(final int requiredCapacity){
        return hasAvailableCapacity(gatingSequences, requiredCapacity, cursor.get());
    }
    private boolean hasAvailableCapacity(Sequence[] gatingSequences, final int requiredCapacity, long cursorValue){
        long wrapPoint = (cursorValue + requiredCapacity) - bufferSize;
        long cachedGatingSequence = gatingSequenceCache.get();
        if (wrapPoint > cachedGatingSequence || cachedGatingSequence > cursorValue){
            long minSequence = Util.getMinimumSequence(gatingSequences, cursorValue);
            gatingSequenceCache.set(minSequence);
            if (wrapPoint > minSequence){
                return false;
            }
        }
        return true;
    }
       逻辑和前面SingleProducerSequencer内部一样,区别是这里使用了cursor.get(),里面获取的是一个volatile的value值。 
 
    @Override
    public long next(int n){
        if (n < 1){
            throw new IllegalArgumentException("n must be > 0");
        }
        long current;
        long next;
        do{
            current = cursor.get();
            next = current + n;
            long wrapPoint = next - bufferSize;
            long cachedGatingSequence = gatingSequenceCache.get();
            if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current){
                long gatingSequence = Util.getMinimumSequence(gatingSequences, current);
                if (wrapPoint > gatingSequence){
                    LockSupport.parkNanos(1); // TODO, should we spin based on the wait strategy?
                    continue;
                }
                gatingSequenceCache.set(gatingSequence);
            }else if (cursor.compareAndSet(current, next)){
                break;
            }
        }while (true);
        return next;
    }
       逻辑还是一样,区别是里面的增加当前序列值是原子操作。
 
       其他的方法都类似,都能保证多线程下的安全操作,唯一有点不同的是publish方法,看下:
    @Override
    public void publish(final long sequence){
        setAvailable(sequence);
        waitStrategy.signalAllWhenBlocking();
    }
    private void setAvailable(final long sequence){
        setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence));
    }
    private void setAvailableBufferValue(int index, int flag){
        long bufferAddress = (index * SCALE) + BASE;
        UNSAFE.putOrderedInt(availableBuffer, bufferAddress, flag);
    }
    private int calculateAvailabilityFlag(final long sequence){
        return (int) (sequence >>> indexShift);
    }
    private int calculateIndex(final long sequence){
        return ((int) sequence) & indexMask;
    }
       方法中会将当前序列值的可用状态记录到availableBuffer里面,而记录的这个值其实就是sequence除以bufferSize,也就是当前sequence绕buffer的圈数。  
 
    @Override
    public boolean isAvailable(long sequence){
        int index = calculateIndex(sequence);
        int flag = calculateAvailabilityFlag(sequence);
        long bufferAddress = (index * SCALE) + BASE;
        return UNSAFE.getIntVolatile(availableBuffer, bufferAddress) == flag;
    }
    @Override
    public long getHighestPublishedSequence(long lowerBound, long availableSequence){
        for (long sequence = lowerBound; sequence <= availableSequence; sequence++){
            if (!isAvailable(sequence)){
                return sequence - 1;
            }
        }
        return availableSequence;
    }
       isAvailable方法也好理解了,getHighestPublishedSequence方法基于isAvailable实现。
              
  
  • 大概了解了Sequencer的功能和实现,接下来看一下序列相关的一些类:
       首先看下SequenceBarrier这个接口: 
public interface SequenceBarrier{

    /**
     * 等待一个序列变为可用,然后消费这个序列。.
     * 这货明显是给事件处理者使用的。
     */
    long waitFor(long sequence) throws AlertException, InterruptedException, TimeoutException;
    /**
     * 获取当前可以读取的序列值。
     */
    long getCursor();
    /**
     * 当前栅栏是否发过通知。
     */
    boolean isAlerted();
    /**
     * 通知事件处理者状态变化,然后停留在这个状态上,直到状态被清除。
     */
    void alert();
    /**
     * 清楚通知状态。
     */
    void clearAlert();
    /**
     * 检测是否发生了通知,如果已经发生了抛出AlertException异常。
     */
    void checkAlert() throws AlertException;
}
       来看一下SequenceBarrier的实现ProcessingSequenceBarrier: 
final class ProcessingSequenceBarrier implements SequenceBarrier{
    //等待策略。
    private final WaitStrategy waitStrategy;
    //这个域可能指向一个序列组。
    private final Sequence dependentSequence;
    private volatile boolean alerted = false;
    private final Sequence cursorSequence;
    private final Sequencer sequencer;
    public ProcessingSequenceBarrier(final Sequencer sequencer,
                                     final WaitStrategy waitStrategy,
                                     final Sequence cursorSequence,
                                     final Sequence[] dependentSequences){
        this.sequencer = sequencer;
        this.waitStrategy = waitStrategy;
        this.cursorSequence = cursorSequence;
        if (0 == dependentSequences.length){
            dependentSequence = cursorSequence;
        }else{
            dependentSequence = new FixedSequenceGroup(dependentSequences);
        }
    }
    @Override
    public long waitFor(final long sequence)
        throws AlertException, InterruptedException, TimeoutException{
        //先检测报警状态。
        checkAlert();
        //然后根据等待策略来等待可用的序列值。
        long availableSequence = waitStrategy.waitFor(sequence, cursorSequence, dependentSequence, this);
        if (availableSequence < sequence){
            return availableSequence; //如果可用的序列值小于给定的序列,那么直接返回。
        }
        //否则,要返回能安全使用的最大的序列值。
        return sequencer.getHighestPublishedSequence(sequence, availableSequence);
    }
    @Override
    public long getCursor(){
        return dependentSequence.get();
    }
    @Override
    public boolean isAlerted(){
        return alerted;
    }
    @Override
    public void alert(){
        alerted = true; //设置通知标记
        waitStrategy.signalAllWhenBlocking();//如果有线程以阻塞的方式等待序列,将其唤醒。
    }
    @Override
    public void clearAlert(){
        alerted = false;
    }
    @Override
    public void checkAlert() throws AlertException{
        if (alerted){
            throw AlertException.INSTANCE;
        }
    }
}
 
       再看一个SequenceGroup类:
public final class SequenceGroup extends Sequence{

    private static final AtomicReferenceFieldUpdater<SequenceGroup, Sequence[]> SEQUENCE_UPDATER =
            AtomicReferenceFieldUpdater.newUpdater(SequenceGroup.class, Sequence[].class, "sequences");
    private volatile Sequence[] sequences = new Sequence[0];

    public SequenceGroup(){
        super(-1);
    }
    /**
     * 获取序列组中最小的序列值。
     */
    @Override
    public long get(){
        return Util.getMinimumSequence(sequences);
    }
    /**
     * 将序列组中所有的序列设置为给定值。
     */
    @Override
    public void set(final long value){
        final Sequence[] sequences = this.sequences;
        for (int i = 0, size = sequences.length; i < size; i++){
            sequences[i].set(value);
        }
    }
    /**
     * 添加一个序列到序列组,这个方法只能在初始化的时候调用。
     * 运行时添加的话,使用addWhileRunning(Cursored, Sequence)
     */
    public void add(final Sequence sequence){
        Sequence[] oldSequences;
        Sequence[] newSequences;
        do{
            oldSequences = sequences;
            final int oldSize = oldSequences.length;
            newSequences = new Sequence[oldSize + 1];
            System.arraycopy(oldSequences, 0, newSequences, 0, oldSize);
            newSequences[oldSize] = sequence;
        } while (!SEQUENCE_UPDATER.compareAndSet(this, oldSequences, newSequences));
    }
    /**
     * 将序列组中出现的第一个给定的序列移除。
     */
    public boolean remove(final Sequence sequence){
        return SequenceGroups.removeSequence(this, SEQUENCE_UPDATER, sequence);
    }
    /**
     * 获取序列组的大小。
     */
    public int size(){
        return sequences.length;
    }
    /**
     * 在线程已经开始往Disruptor上发布事件后,添加一个序列到序列组。
     * 调用这个方法后,会将新添加的序列的值设置为游标的值。
     */
    public void addWhileRunning(Cursored cursored, Sequence sequence){
        SequenceGroups.addSequences(this, SEQUENCE_UPDATER, cursored, sequence);
    }
}
 
       还有一个SequenceGroups类,是针对SequenceGroup的帮助类,里面提供了addSequences和removeSequence方法,都是原子操作。
 
  • 最后总结:
       上面看了这么多序列的相关类,其实只需要记住三点:
              1.真正的序列是Sequence。
              2.事件发布者通过Sequencer的大部分功能来使用序列。
              3.事件处理者通过SequenceBarrier来使用序列。
 
    
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值