zookeeper分布式锁

Zookeeper是一个分布式协调服务。这样我们就可以利用Zookeeper来协调多个分布式进程之间的活动。比如在一个分布式环境中,为了提高可靠性,我们的集群的每台服务器上都部署着同样的服务。但是,一件事情如果集群中的每个服务器都进行的话,那相互之间就要协调,编程起来将非常复杂。而如果我们只让一个服务进行操作,那又存在单点。通常还有一种做法就是使用分布式锁,在某个时刻只让一个服务去干活,当这台服务出问题的时候锁释放,立即fail over到另外的服务。这在很多分布式系统中都是这么做,这种设计有一个更好听的名字叫Leader Election(leader选举)。比如HBase的Master就是采用这种机制。但要注意的是分布式锁跟同一个进程的锁还是有区别的,所以使用的时候要比同一个进程里的锁更谨慎的使用。
测试环境 spootboot+ zookeeper+ curator 实现分布式锁
1 pom
 
<!-- zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.11</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- zookeeper curator 分布式锁 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
1 可以重入锁和非可重入锁 InterProcessMutex 和 InterProcessSemaphoreMutex
/**
* 可以重入锁
* 只有等前一个获取锁的线程释放锁后,下一个线程才能获取锁
Thread[Thread-20,5,main] acquire lock
Thread[Thread-20,5,main] release lock
Thread[Thread-19,5,main] acquire lock
Thread[Thread-19,5,main] release lock
*/
public static void InterProcessMutex() {

for ( int i = 0; i <= 4; i++) {
new Thread( new Runnable() {
@Override
public void run() {

//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));

client.start();

//指定锁路径
String lockPath = "/zkLockRoot/lock_1";

//创建锁,为可重入锁,即是获锁后,还可以再次获取,本例以此为例
InterProcessMutex typeLock = new InterProcessMutex(client, lockPath);

//创建锁,为不可重入锁,即是获锁后,不可以再次获取,使用和重入锁类似
//InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(client, lockPath);
while ( true) {
try {
try {
//获取锁
typeLock.acquire();
System.out.println(Thread.currentThread() + " acquire lock");
Thread.sleep( 5000);
} catch ( Exception e) {
} finally {
//释放锁
typeLock.release();
System.out.println(Thread.currentThread() + " release lock");
}
} catch ( Exception ex) {
System.out.println(ex);
}
}
}
}).start();
}
}
2 读写锁 InterProcessReadWriteLock
 
/**
* 读写锁
* 可以同时有多个线程获取读锁,但同时只能有一个线程获取写锁
Thread[Thread-15,5,main] acquire read lock
Thread[Thread-17,5,main] acquire read lock
Thread[Thread-17,5,main] release read lock
Thread[Thread-15,5,main] release read lock
Thread[Thread-21,5,main] acquire write lock
Thread[Thread-21,5,main] release write lock
Thread[Thread-14,5,main] acquire read lock
Thread[Thread-14,5,main] release read lock
Thread[Thread-20,5,main] acquire write lock
Thread[Thread-20,5,main] release write lock
Thread[Thread-19,5,main] acquire write lock
Thread[Thread-19,5,main] release write lock
*/
public static void InterProcessReadWriteLock(){
for ( int i = 0; i <= 4; i++) {
new Thread( new Runnable() {
@Override
public void run() {

//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));

client.start();

//指定锁路径
String lockPath = "/zkLockRoot/lock_1";

//创建读写锁
InterProcessReadWriteLock lock = new InterProcessReadWriteLock(client, lockPath);

//获取读锁
InterProcessMutex readLock = lock.readLock();

while ( true) {
try {
try {
//获取锁
readLock.acquire();
System.out.println(Thread.currentThread() + " acquire read lock");
Thread.sleep( 1000);
} catch ( Exception e) {
} finally {
//释放锁
readLock.release();
System.out.println(Thread.currentThread() + " release read lock");
}
} catch ( Exception ex) {
System.out.println(ex);
}
}
}
}).start();
}

for ( int i = 0; i <= 4; i++) {
new Thread( new Runnable() {
@Override
public void run() {

//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));

client.start();

//指定锁路径
String lockPath = "/zkLockRoot/lock_1";

//创建读写锁
InterProcessReadWriteLock lock = new InterProcessReadWriteLock(client, lockPath);

//获取读锁
InterProcessMutex writeLock = lock.writeLock();

while ( true) {
try {
try {
//获取锁
writeLock.acquire();
System.out.println(Thread.currentThread() + " acquire write lock");
Thread.sleep( 1000);
} catch ( Exception e) {
} finally {
//释放锁
writeLock.release();
System.out.println(Thread.currentThread() + " release write lock");
}
} catch ( Exception ex) {
System.out.println(ex);
}
}
}
}).start();
}
}
3 信息量
/**
* 信息量
* 即同时最多只能允许指定数量的线程访问临界资源
Thread[Thread-17,5,main] acquire semaphore lock
Thread[Thread-16,5,main] acquire semaphore lock
Thread[Thread-15,5,main] acquire semaphore lock
Thread[Thread-17,5,main] release semaphore lock
Thread[Thread-16,5,main] release semaphore lock
Thread[Thread-15,5,main] release semaphore lock
Thread[Thread-13,5,main] acquire semaphore lock
Thread[Thread-14,5,main] acquire semaphore lock
Thread[Thread-18,5,main] acquire semaphore lock
Thread[Thread-13,5,main] release semaphore lock
Thread[Thread-14,5,main] release semaphore lock
Thread[Thread-18,5,main] release semaphore lock
*/
public static void InterProcessSemaphoreV2(){
for ( int i = 0; i <= 5; i++) {
new Thread( new Runnable() {
@Override
public void run() {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));
client.start();
//指定锁路径
String lockPath = "/zkLockRoot/lock_1";
//信号量
InterProcessSemaphoreV2 semaphoreLock = new InterProcessSemaphoreV2(client, lockPath, 3);

while ( true) {
try {
//获取锁
Lease lease =semaphoreLock.acquire();
try {
System.out.println(Thread.currentThread() + " acquire semaphore lock");
Thread.sleep( 3000);
} catch ( Exception e) {
} finally {
//释放锁
semaphoreLock.returnLease(lease);
System.out.println(Thread.currentThread() + " release semaphore lock");
}
} catch ( Exception ex) {
System.out.println(ex);
}
}
}
}).start();
}
}
4 分布式栅栏
/**
* 分布式栅栏
* 设置栅栏它会阻塞所有节点上的等待进程,当条件满足时,移除栅栏,所有等待的线程将继续执行:
Thread[Thread-13,5,main] setBarrier
Thread[Thread-18,5,main] rearch barrier, waiting
Thread[Thread-17,5,main] rearch barrier, waiting
Thread[Thread-15,5,main] rearch barrier, waiting
Thread[Thread-14,5,main] rearch barrier, waiting
Thread[Thread-16,5,main] rearch barrier, waiting
Thread[Thread-13,5,main] removeBarrier
Thread[Thread-14,5,main] leave barrier, finish
Thread[Thread-16,5,main] leave barrier, finish
Thread[Thread-18,5,main] leave barrier, finish
Thread[Thread-15,5,main] leave barrier, finish
Thread[Thread-17,5,main] leave barrier, finish
*/
public static void DistributedBarrier(){
new Thread( new Runnable() {
@Override
public void run() {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));
client.start();
//指定锁路径
String lockPath = "/examples/barrier";
//创建分布式栅栏
DistributedBarrier barrier = new DistributedBarrier(client, lockPath);
try {
//设置栅栏
barrier.setBarrier();
System.out.println(Thread.currentThread() + " setBarrier");
Thread.sleep( 10000);
//移除栅栏
barrier.removeBarrier();
System.out.println(Thread.currentThread() + " removeBarrier");
} catch ( Exception e) {

}
}
}).start();

for ( int i = 1; i <= 5; i++) {
new Thread( new Runnable() {
@Override
public void run() {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));
client.start();
//指定锁路径
String lockPath = "/examples/barrier";
//创建分布式栅栏
DistributedBarrier barrier = new DistributedBarrier(client, lockPath);
try {
Thread.sleep( 1000);
System.out.println(Thread.currentThread() + " rearch barrier, waiting");
barrier.waitOnBarrier();
System.out.println(Thread.currentThread() + " leave barrier, finish ");
} catch ( Exception e) {
System.out.println(e);
}
}
}).start();
}
}
5 分布式双栅栏
/**
* 双栅栏
* 双栅栏允许指定数量的客户端在计算的开始和结束时同步。
* 当指定数量的进程加入到双栅栏时,进程开始计算,当计算全部完成时,一起离开栅栏
Thread[Thread-17,5,main] rearch barrier, enter
Thread[Thread-15,5,main] rearch barrier, enter
Thread[Thread-14,5,main] rearch barrier, enter
Thread[Thread-16,5,main] rearch barrier, enter
Thread[Thread-13,5,main] rearch barrier, enter
Thread[Thread-13,5,main] rearch barrier, work
Thread[Thread-14,5,main] rearch barrier, work
Thread[Thread-15,5,main] rearch barrier, work
Thread[Thread-16,5,main] rearch barrier, work
Thread[Thread-17,5,main] rearch barrier, work
Thread[Thread-17,5,main] leave barrier, leave
Thread[Thread-14,5,main] leave barrier, leave
Thread[Thread-16,5,main] leave barrier, leave
Thread[Thread-13,5,main] leave barrier, leave
Thread[Thread-15,5,main] leave barrier, leave
*/
public static void DistributedDoubleBarrier(){
for ( int i = 1; i <= 5; i++) {
final int sleep=i;
new Thread( new Runnable() {
@Override
public void run() {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));
client.start();
//指定锁路径
String lockPath = "/examples/barrier";
//创建分布式栅栏
DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(client, lockPath, 5);
try {
//进入
System.out.println(Thread.currentThread() + " rearch barrier, enter");
barrier.enter();
Thread.sleep( 1000*sleep);
//计算
System.out.println(Thread.currentThread() + " rearch barrier, work");
barrier.leave();
//离开
System.out.println(Thread.currentThread() + " leave barrier, leave ");
} catch ( Exception e) {
System.out.println(e);
}
}
}).start();
}
}

Zookeeper是一个分布式协调服务。这样我们就可以利用Zookeeper来协调多个分布式进程之间的活动。比如在一个分布式环境中,为了提高可靠性,我们的集群的每台服务器上都部署着同样的服务。但是,一件事情如果集群中的每个服务器都进行的话,那相互之间就要协调,编程起来将非常复杂。而如果我们只让一个服务进行操作,那又存在单点。通常还有一种做法就是使用分布式锁,在某个时刻只让一个服务去干活,当这台服务出问题的时候锁释放,立即fail over到另外的服务。这在很多分布式系统中都是这么做,这种设计有一个更好听的名字叫Leader Election(leader选举)。比如HBase的Master就是采用这种机制。但要注意的是分布式锁跟同一个进程的锁还是有区别的,所以使用的时候要比同一个进程里的锁更谨慎的使用。
测试环境 spootboot+ zookeeper+ curator 实现分布式锁
1 pom
 
 
<!-- zookeeper -->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.11</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- zookeeper curator 分布式锁 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
1 可以重入锁和非可重入锁 InterProcessMutex 和 InterProcessSemaphoreMutex
/**
* 可以重入锁
* 只有等前一个获取锁的线程释放锁后,下一个线程才能获取锁
Thread[Thread-20,5,main] acquire lock
Thread[Thread-20,5,main] release lock
Thread[Thread-19,5,main] acquire lock
Thread[Thread-19,5,main] release lock
*/
public static void InterProcessMutex() {

for ( int i = 0; i <= 4; i++) {
new Thread( new Runnable() {
@Override
public void run() {

//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));

client.start();

//指定锁路径
String lockPath = "/zkLockRoot/lock_1";

//创建锁,为可重入锁,即是获锁后,还可以再次获取,本例以此为例
InterProcessMutex typeLock = new InterProcessMutex(client, lockPath);

//创建锁,为不可重入锁,即是获锁后,不可以再次获取,使用和重入锁类似
//InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(client, lockPath);
while ( true) {
try {
try {
//获取锁
typeLock.acquire();
System.out.println(Thread.currentThread() + " acquire lock");
Thread.sleep( 5000);
} catch ( Exception e) {
} finally {
//释放锁
typeLock.release();
System.out.println(Thread.currentThread() + " release lock");
}
} catch ( Exception ex) {
System.out.println(ex);
}
}
}
}).start();
}
}
2 读写锁 InterProcessReadWriteLock
 
 
/**
* 读写锁
* 可以同时有多个线程获取读锁,但同时只能有一个线程获取写锁
Thread[Thread-15,5,main] acquire read lock
Thread[Thread-17,5,main] acquire read lock
Thread[Thread-17,5,main] release read lock
Thread[Thread-15,5,main] release read lock
Thread[Thread-21,5,main] acquire write lock
Thread[Thread-21,5,main] release write lock
Thread[Thread-14,5,main] acquire read lock
Thread[Thread-14,5,main] release read lock
Thread[Thread-20,5,main] acquire write lock
Thread[Thread-20,5,main] release write lock
Thread[Thread-19,5,main] acquire write lock
Thread[Thread-19,5,main] release write lock
*/
public static void InterProcessReadWriteLock(){
for ( int i = 0; i <= 4; i++) {
new Thread( new Runnable() {
@Override
public void run() {

//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));

client.start();

//指定锁路径
String lockPath = "/zkLockRoot/lock_1";

//创建读写锁
InterProcessReadWriteLock lock = new InterProcessReadWriteLock(client, lockPath);

//获取读锁
InterProcessMutex readLock = lock.readLock();

while ( true) {
try {
try {
//获取锁
readLock.acquire();
System.out.println(Thread.currentThread() + " acquire read lock");
Thread.sleep( 1000);
} catch ( Exception e) {
} finally {
//释放锁
readLock.release();
System.out.println(Thread.currentThread() + " release read lock");
}
} catch ( Exception ex) {
System.out.println(ex);
}
}
}
}).start();
}

for ( int i = 0; i <= 4; i++) {
new Thread( new Runnable() {
@Override
public void run() {

//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));

client.start();

//指定锁路径
String lockPath = "/zkLockRoot/lock_1";

//创建读写锁
InterProcessReadWriteLock lock = new InterProcessReadWriteLock(client, lockPath);

//获取读锁
InterProcessMutex writeLock = lock.writeLock();

while ( true) {
try {
try {
//获取锁
writeLock.acquire();
System.out.println(Thread.currentThread() + " acquire write lock");
Thread.sleep( 1000);
} catch ( Exception e) {
} finally {
//释放锁
writeLock.release();
System.out.println(Thread.currentThread() + " release write lock");
}
} catch ( Exception ex) {
System.out.println(ex);
}
}
}
}).start();
}
}
3 信息量
/**
* 信息量
* 即同时最多只能允许指定数量的线程访问临界资源
Thread[Thread-17,5,main] acquire semaphore lock
Thread[Thread-16,5,main] acquire semaphore lock
Thread[Thread-15,5,main] acquire semaphore lock
Thread[Thread-17,5,main] release semaphore lock
Thread[Thread-16,5,main] release semaphore lock
Thread[Thread-15,5,main] release semaphore lock
Thread[Thread-13,5,main] acquire semaphore lock
Thread[Thread-14,5,main] acquire semaphore lock
Thread[Thread-18,5,main] acquire semaphore lock
Thread[Thread-13,5,main] release semaphore lock
Thread[Thread-14,5,main] release semaphore lock
Thread[Thread-18,5,main] release semaphore lock
*/
public static void InterProcessSemaphoreV2(){
for ( int i = 0; i <= 5; i++) {
new Thread( new Runnable() {
@Override
public void run() {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));
client.start();
//指定锁路径
String lockPath = "/zkLockRoot/lock_1";
//信号量
InterProcessSemaphoreV2 semaphoreLock = new InterProcessSemaphoreV2(client, lockPath, 3);

while ( true) {
try {
//获取锁
Lease lease =semaphoreLock.acquire();
try {
System.out.println(Thread.currentThread() + " acquire semaphore lock");
Thread.sleep( 3000);
} catch ( Exception e) {
} finally {
//释放锁
semaphoreLock.returnLease(lease);
System.out.println(Thread.currentThread() + " release semaphore lock");
}
} catch ( Exception ex) {
System.out.println(ex);
}
}
}
}).start();
}
}
4 分布式栅栏
/**
* 分布式栅栏
* 设置栅栏它会阻塞所有节点上的等待进程,当条件满足时,移除栅栏,所有等待的线程将继续执行:
Thread[Thread-13,5,main] setBarrier
Thread[Thread-18,5,main] rearch barrier, waiting
Thread[Thread-17,5,main] rearch barrier, waiting
Thread[Thread-15,5,main] rearch barrier, waiting
Thread[Thread-14,5,main] rearch barrier, waiting
Thread[Thread-16,5,main] rearch barrier, waiting
Thread[Thread-13,5,main] removeBarrier
Thread[Thread-14,5,main] leave barrier, finish
Thread[Thread-16,5,main] leave barrier, finish
Thread[Thread-18,5,main] leave barrier, finish
Thread[Thread-15,5,main] leave barrier, finish
Thread[Thread-17,5,main] leave barrier, finish
*/
public static void DistributedBarrier(){
new Thread( new Runnable() {
@Override
public void run() {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));
client.start();
//指定锁路径
String lockPath = "/examples/barrier";
//创建分布式栅栏
DistributedBarrier barrier = new DistributedBarrier(client, lockPath);
try {
//设置栅栏
barrier.setBarrier();
System.out.println(Thread.currentThread() + " setBarrier");
Thread.sleep( 10000);
//移除栅栏
barrier.removeBarrier();
System.out.println(Thread.currentThread() + " removeBarrier");
} catch ( Exception e) {

}
}
}).start();

for ( int i = 1; i <= 5; i++) {
new Thread( new Runnable() {
@Override
public void run() {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));
client.start();
//指定锁路径
String lockPath = "/examples/barrier";
//创建分布式栅栏
DistributedBarrier barrier = new DistributedBarrier(client, lockPath);
try {
Thread.sleep( 1000);
System.out.println(Thread.currentThread() + " rearch barrier, waiting");
barrier.waitOnBarrier();
System.out.println(Thread.currentThread() + " leave barrier, finish ");
} catch ( Exception e) {
System.out.println(e);
}
}
}).start();
}
}
5 分布式双栅栏
/**
* 双栅栏
* 双栅栏允许指定数量的客户端在计算的开始和结束时同步。
* 当指定数量的进程加入到双栅栏时,进程开始计算,当计算全部完成时,一起离开栅栏
Thread[Thread-17,5,main] rearch barrier, enter
Thread[Thread-15,5,main] rearch barrier, enter
Thread[Thread-14,5,main] rearch barrier, enter
Thread[Thread-16,5,main] rearch barrier, enter
Thread[Thread-13,5,main] rearch barrier, enter
Thread[Thread-13,5,main] rearch barrier, work
Thread[Thread-14,5,main] rearch barrier, work
Thread[Thread-15,5,main] rearch barrier, work
Thread[Thread-16,5,main] rearch barrier, work
Thread[Thread-17,5,main] rearch barrier, work
Thread[Thread-17,5,main] leave barrier, leave
Thread[Thread-14,5,main] leave barrier, leave
Thread[Thread-16,5,main] leave barrier, leave
Thread[Thread-13,5,main] leave barrier, leave
Thread[Thread-15,5,main] leave barrier, leave
*/
public static void DistributedDoubleBarrier(){
for ( int i = 1; i <= 5; i++) {
final int sleep=i;
new Thread( new Runnable() {
@Override
public void run() {
//创建zookeeper客户端
CuratorFramework client = CuratorFrameworkFactory.newClient( "127.0.0.1:2181", new ExponentialBackoffRetry( 1000, 3));
client.start();
//指定锁路径
String lockPath = "/examples/barrier";
//创建分布式栅栏
DistributedDoubleBarrier barrier = new DistributedDoubleBarrier(client, lockPath, 5);
try {
//进入
System.out.println(Thread.currentThread() + " rearch barrier, enter");
barrier.enter();
Thread.sleep( 1000*sleep);
//计算
System.out.println(Thread.currentThread() + " rearch barrier, work");
barrier.leave();
//离开
System.out.println(Thread.currentThread() + " leave barrier, leave ");
} catch ( Exception e) {
System.out.println(e);
}
}
}).start();
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值