PHP高并发情形下怎么防止商品库存超卖

本篇文章给大家带来了关于PHP的相关知识,其中主要介绍了关于在高并发情况下防止商品库存超卖的相关问题,主要解决高并发对数据库产生的压力以及竞争状态下如何解决商品库存超卖,希望对大家有帮助。

本篇文章给大家带来了关于PHP的相关知识,其中主要介绍了关于在高并发情况下防止商品库存超卖的相关问题,主要解决高并发对数据库产生的压力以及竞争状态下如何解决商品库存超卖,希望对大家有帮助。

PHP高并发情形下怎么防止商品库存超卖

千万级数据并发如何处理?进入学习

可以通过《php高并发测试:防止库存超卖的案例讲解》查看基于本篇文章的测试案例。【推荐学习:《PHP教程》】

商城系统中,抢购和秒杀是很常见的营销场景,在一定时间内有大量的用户访问商场下单,主要需要解决的问题有两个:

  • 高并发对数据库产生的压力;

  • 竞争状态下如何解决商品库存超卖;

高并发对数据库产生的压力

对于第一个问题,使用缓存来处理,避免直接操作数据库,例如使用 Redis。

竞争状态下如何解决商品库存超卖

对于第二个问题,需要重点说明。

常规写法:查询出对应商品的库存,判断库存数量否大于 0,然后执行生成订单等操作,但是在判断库存是否大于 0 处,如果在高并发下就会有问题,导致库存量出现负数。

测试表 sql

把如下表数据导入到数据库中

/*
Navicat MySQL Data Transfer

Source Server : 01 本地localhost
Source Server Version : 50553
Source Host : localhost:3306
Source Database : test

Target Server Type : MYSQL
Target Server Version : 50553
File Encoding : 65001

Date: 2020-11-06 14:31:35
*/

SET FOREIGN_KEY_CHECKS=0;

— —————————-
— Table structure for products
— —————————-
DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT ;ID;,
`title` varchar(50) DEFAULT NULL COMMENT ;货品名称;,
`store` int(11) DEFAULT ;0; COMMENT ;货品库存;,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT=;货品表;;

— —————————-
— Records of products
— —————————-
INSERT INTO `products` VALUES (;1;, ;稻花香大米;, ;20;);

— —————————-
— Table structure for order_log
— —————————-
DROP TABLE IF EXISTS `order_log`;
CREATE TABLE `order_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`content` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL COMMENT ;日志内容;,
`c_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ;创建时间;,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

— —————————-
— Table structure for order
— —————————-
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
`oid` bigint(20) unsigned NOT NULL DEFAULT ;0; COMMENT ;订单号;,
`product_id` int(11) DEFAULT ;0; COMMENT ;商品ID;,
`number` int(11) DEFAULT ;0; COMMENT ;购买数量;,
`c_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ;创建时间;,
PRIMARY KEY (`oid`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COMMENT=;订单表;;

下单处理代码

<?php

db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

//step2 查询商品信息
$sql = ;select * from products where id={$product_id};;
$result = mysqli_query($con, $sql);
$row = mysqli_fetch_assoc($result);

//step3 判断商品下单数量是否大于商品库存数量
//此处在高并发下,可能出现上一个下单后还没来得及更新库存,下一个下单判断库存数不是最新的库存
if ($row[;store;] > 0) {

sleep(1);
//step4 更新商品库存数量(减去下单数量)
$sql = ;update products set store=store-{$buy_num} where id={$product_id};;
if (mysqli_query($con, $sql)) {
echo ;更新成功;;
//step5 生成订单号创建订单
$oid = build_order_no();
create_order($oid, $product_id, $buy_num);
insertLog(;库存减少成功,下单成功;);
} else {
echo ;更新失败;;
insertLog(;库存减少失败;);
}

} else {
echo ;没有库存;;
insertLog(;库存不够;);
}

function db()
{
global $con;
$con = new mysqli(;localhost;,;root;,;root;,;test;);
if (!$con) {
echo ;数据库连接失败;;
}
}

/**
* 生成唯一订单号
*/
function build_order_no()
{
return date(;Ymd;) . str_pad(mt_rand(1, 99999), 5, ;0;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
global $con;
$sql = ;INSERT INTO `order` (oid, product_id, number) values(;$oid;, ;$product_id;, ;$number;);;
mysqli_query($con, $sql);
}

/**
* 记录日志
*/
function insertLog($content)
{
global $con;
$sql = ;INSERT INTO `order_log` (content) values(;$content;);;
mysqli_query($con, $sql);
}

将库存字段字段设为 unsigned

因为库存字段不能为负数,在下单后更新商品库存时,如果出现负数将返回 false

<?php
db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

//step2 查询商品信息
$sql = ;select * from products where id={$product_id} for UPDATE;;//利用for update 开启行锁
$result = mysqli_query($con, $sql);
$row = mysqli_fetch_assoc($result);

//step3 判断商品下单数量是否大于商品库存数量
if ($row[;store;] > 0) {

sleep(1);
//step4 更新商品库存数量(减去下单数量)
$sql = ;update products set store=store-{$buy_num} where id={$product_id};;
if (mysqli_query($con, $sql)) {
echo ;更新成功;;
//step5 生成订单号创建订单
$oid = build_order_no();
create_order($oid, $product_id, $buy_num);
insertLog(;库存减少成功,下单成功;);
} else {
// 如果出现负数将返回false
echo ;更新失败;;
insertLog(;库存减少失败;);
}
} else {
//商品已经抢购完
echo ;没有库存;;
insertLog(;库存不够;);
}

function db()
{
global $con;
$con = new mysqli(;localhost;,;root;,;root;,;test;);
if (!$con) {
echo ;数据库连接失败;;
}
}

/**
* 生成唯一订单号
*/
function build_order_no()
{
return date(;Ymd;) . str_pad(mt_rand(1, 99999), 5, ;0;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
global $con;
$sql = ;INSERT INTO `order` (oid, product_id, number) values(;$oid;, ;$product_id;, ;$number;);;
mysqli_query($con, $sql);
}

/**
* 记录日志
*/
function insertLog($content)
{
global $con;
$sql = ;INSERT INTO `order_log` (content) values(;$content;);;
mysqli_query($con, $sql);
}

使用 mysql 的事务,锁住操作的行

在下单处理过程中,使用 mysql 的事务将正在下单商品行数据锁定

<?php
db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

mysqli_query($con, ;BEGIN;); //开始事务

//step2 查询商品信息
$sql = ;select * from products where id={$product_id} for UPDATE;;//利用for update 开启行锁
$result = mysqli_query($con, $sql);
$row = mysqli_fetch_assoc($result);

//step3 判断商品下单数量是否大于商品库存数量
if ($row[;store;] > 0) {

sleep(1);
//step4 更新商品库存数量(减去下单数量)
$sql = ;update products set store=store-{$buy_num} where id={$product_id};;
if (mysqli_query($con, $sql)) {
echo ;更新成功;;
//step5 生成订单号创建订单
$oid = build_order_no();
create_order($oid, $product_id, $buy_num);
insertLog(;库存减少成功,下单成功;);
mysqli_query($con, ;COMMIT;);//事务提交即解锁
} else {
echo ;更新失败;;
insertLog(;库存减少失败;);
mysqli_query($con, ;ROLLBACK;);//事务回滚即解锁
}
} else {
//商品已经抢购完
echo ;没有库存;;
insertLog(;库存不够;);
mysqli_query($con, ;ROLLBACK;);//事务回滚即解锁
}

function db()
{
global $con;
$con = new mysqli(;localhost;,;root;,;root;,;test;);
if (!$con) {
echo ;数据库连接失败;;
}
}

/**
* 生成唯一订单号
*/
function build_order_no()
{
return date(;Ymd;) . str_pad(mt_rand(1, 99999), 5, ;0;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
global $con;
$sql = ;INSERT INTO `order` (oid, product_id, number) values(;$oid;, ;$product_id;, ;$number;);;
mysqli_query($con, $sql);
}

/**
* 记录日志
*/
function insertLog($content)
{
global $con;
$sql = ;INSERT INTO `order_log` (content) values(;$content;);;
mysqli_query($con, $sql);
}

使用非阻塞的文件排他锁

在处理下单请求的时候,用 flock 锁定一个文件,如果锁定失败说明有其他订单正在处理,此时要么等待要么直接提示用户” 服务器繁忙”,计数器存储抢购的商品数量,避免查询数据库。

阻塞 (等待) 模式:并发时,当有第二个用户请求时,会等待第一个用户请求完成、释放锁,获得文件锁之后,程序才会继续运行下去。

<?php
db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

$fp = fopen(;lock.txt;, ;w;);
if (flock($fp, LOCK_EX)) { //文件独占锁,阻塞
//step2 查询商品信息
$sql = ;select * from products where id={$product_id};;
$result = mysqli_query($con, $sql);
$row = mysqli_fetch_assoc($result);

//step3 判断商品下单数量是否大于商品库存数量
if ($row[;store;] > 0) {
//处理订单
sleep(1);
//step4 更新商品库存数量(减去下单数量)
$sql = ;update products set store=store-{$buy_num} where id={$product_id};;
if (mysqli_query($con, $sql)) {
echo ;更新成功;;
//step5 生成订单号创建订单
$oid = build_order_no();
create_order($oid, $product_id, $buy_num);
insertLog(;库存减少成功,下单成功;);
} else {
echo ;更新失败;;
insertLog(;库存减少失败;);
}
} else {
//商品已经抢购完
echo ;没有库存;;
insertLog(;库存不够;);
}
flock($fp, LOCK_UN); //释放锁

}
fclose($fp);

function db()
{
global $con;
$con = new mysqli(;localhost;,;root;,;root;,;test;);
if (!$con) {
echo ;数据库连接失败;;
}
}

/**
* 生成唯一订单号
*/
function build_order_no()
{
return date(;Ymd;) . str_pad(mt_rand(1, 99999), 5, ;0;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
global $con;
$sql = ;INSERT INTO `order` (oid, product_id, number) values(;$oid;, ;$product_id;, ;$number;);;
mysqli_query($con, $sql);
}

/**
* 记录日志
*/
function insertLog($content)
{
global $con;
$sql = ;INSERT INTO `order_log` (content) values(;$content;);;
mysqli_query($con, $sql);
}

非阻塞模式:并发时,第一个用户请求,拿得文件锁之后。后面请求的用户直接返回系统繁忙,请稍后再试

<?php
db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

$fp = fopen(;lock.txt;, ;w;);
if (flock($fp, LOCK_EX|LOCK_NB)) { //文件独占锁,非阻塞
//step2 查询商品信息
$sql = ;select * from products where id={$product_id};;
$result = mysqli_query($con, $sql);
$row = mysqli_fetch_assoc($result);

//step3 判断商品下单数量是否大于商品库存数量
if ($row[;store;] > 0) {
//处理订单
sleep(1);
//step4 更新商品库存数量(减去下单数量)
$sql = ;update products set store=store-{$buy_num} where id={$product_id};;
if (mysqli_query($con, $sql)) {
echo ;更新成功;;
//step5 生成订单号创建订单
$oid = build_order_no();
create_order($oid, $product_id, $buy_num);
insertLog(;库存减少成功,下单成功;);
} else {
echo ;更新失败;;
insertLog(;库存减少失败;);
}
} else {
//商品已经抢购完
echo ;没有库存;;
insertLog(;库存不够;);
}
flock($fp, LOCK_UN); //释放锁

} else {
//系统繁忙,请稍后再试
echo ;系统繁忙,请稍后再试;;
insertLog(;系统繁忙,请稍后再试;);
}
fclose($fp);

function db()
{
global $con;
$con = new mysqli(;localhost;,;root;,;root;,;test;);
if (!$con) {
echo ;数据库连接失败;;
}
}

/**
* 生成唯一订单号
*/
function build_order_no()
{
return date(;Ymd;) . str_pad(mt_rand(1, 99999), 5, ;0;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
global $con;
$sql = ;INSERT INTO `order` (oid, product_id, number) values(;$oid;, ;$product_id;, ;$number;);;
mysqli_query($con, $sql);
}

/**
* 记录日志
*/
function insertLog($content)
{
global $con;
$sql = ;INSERT INTO `order_log` (content) values(;$content;);;
mysqli_query($con, $sql);
}

使用 redis 队列

  • 因为 pop 操作是原子的,即使有很多用户同时到达,也是依次执行,推荐使用

  • mysql 事务在高并发下性能下降很厉害,文件锁的方式也是

1.先将商品库存到 redis 队列

<?php

db();
global $con;

// 查询商品信息
$product_id = 1;
$sql = ;select * from products where id={$product_id};;
$result = mysqli_query($con, $sql);
$row = mysqli_fetch_assoc($result);
$store = $row[;store;];

// 获取商品在redis缓存的库存
$redis = new Redis();
$result = $redis->connect(;127.0.0.1;, 6379);
$key = ;goods_store_; . $product_id;
$res = $redis->llen($key);
$count = $store – $res;

for ($i=0; $i<$count; $i++) {
$redis->lpush($key, 1);
}
echo $redis->llen($key);

function db()
{
global $con;
$con = new mysqli(;localhost;,;root;,;root;,;test;);
if (!$con) {
echo ;数据库连接失败;;
}
}

2. 抢购、秒杀逻辑

<?php

db();
global $con;

//step1 接收下单参数
$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量

//step2 下单前判断redis队列库存量
$redis = new Redis();
$result = $redis->connect(;127.0.0.1;,6379);
$count = $redis->lpop(;goods_store_; . $product_id);
if (!$count) {
insertLog(;error:no store redis;);
return ;秒杀结束,没有商品库存了;;
}

sleep(1);
//step3 更新商品库存数量(减去下单数量)
$sql = ;update products set store=store-{$buy_num} where id={$product_id};;
if (mysqli_query($con, $sql)) {
echo ;更新成功;;
//step4 生成订单号创建订单
$oid = build_order_no();
create_order($oid, $product_id, $buy_num);
insertLog(;库存减少成功,下单成功;);
} else {
echo ;更新失败;;
insertLog(;库存减少失败;);
}

function db()
{
global $con;
$con = new mysqli(;localhost;,;root;,;root;,;test;);
if (!$con) {
echo ;数据库连接失败;;
}
}

/**
* 生成唯一订单号
*/
function build_order_no()
{
return date(;Ymd;) . str_pad(mt_rand(1, 99999), 5, ;0;, STR_PAD_LEFT);
}

function create_order($oid, $product_id, $number)
{
global $con;
$sql = ;INSERT INTO `order` (oid, product_id, number) values(;$oid;, ;$product_id;, ;$number;);;
mysqli_query($con, $sql);
}

/**
* 记录日志
*/
function insertLog($content)
{
global $con;
$sql = ;INSERT INTO `order_log` (content) values(;$content;);;
mysqli_query($con, $sql);
}

redis 乐观锁防止超卖

<?php

$redis =new Redis();
$redis->connect(;127.0.0.1;, 6379);
$redis->watch(;sales;);//乐观锁 监视作用 set() 初始值0
$sales = $redis->get(;sales;);

$n = 20;// 库存
if ($sales >= $n) {
exit(;秒杀结束;);
}

//redis开启事务
$redis->multi();
$redis->incr(;sales;); //将 key 中储存的数字值增一 ,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
$res = $redis->exec(); //成功1 失败0

if ($res) {
//秒杀成功
$con = new mysqli(;localhost;,;root;,;root;,;test;);
if (!$con) {
echo ;数据库连接失败;;
}

$product_id = 1;// 商品ID
$buy_num = 1;// 购买数量
sleep(1);

$sql = ;update products set store=store-{$buy_num} where id={$product_id};;

if (mysqli_query($con, $sql)) {
echo ;秒杀完成;;
}

} else {
exit(;抢购失败;);
}

面试官:列举几种PHP拓展的实现手段及其性能比较?

转载2022-03-28 14:00:024068 + php学习QQ群:609135716关于 PHP 扩展的几种实现手段

1.php 原生扩展开发 c 语言,注:【ext_skel.php】脚本创建

2.zephir

3.php-cpp

4.php-x

5.cgo

  • 封装 zendapi 模式

  • CGO 嵌套 C 和 GO 代码,用 GO 去编译了 php 扩展骨架和 GO 的具体实现

等。。。不限上面几种方式。

围绕【zephir,cgo,PHP 开启 JIT】4 种模式下,通过斐波那契数列计算性能,来查看运行效果。

zephir 代码生成扩展

//Main 类
final class Zimuge
{
public static function calcFibonacci(int i){
if (i < 2) {
return i;
}
return self::calcFibonacci(i – 1) + self::calcFibonacci(i – 2);
}编译安装
zephir build

cgo 代码生成扩展

package main
/*
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
static int le_go2php;
PHP_MINIT_FUNCTION(go2php)
{
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(go2php)
{
return SUCCESS;
}
PHP_RINIT_FUNCTION(go2php)
{
return SUCCESS;
}
PHP_RSHUTDOWN_FUNCTION(go2php)
{
return SUCCESS;
}
PHP_MINFO_FUNCTION(go2php)
{
php_info_print_table_start();
php_info_print_table_header(2, "go2php support", "enabled");
php_info_print_table_end();
}
PHP_FUNCTION(go2php_print)
{
zend_long a,b;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_LONG(a)
ZEND_PARSE_PARAMETERS_END();
b = calcFib(a);
RETURN_LONG(b);
}
ZEND_BEGIN_ARG_INFO(null, 0)
ZEND_END_ARG_INFO()
const zend_function_entry go2php_functions[] = {
PHP_FE(go2php_print, null)
PHP_FE_END
};
zend_module_entry go2php_module_entry = {
STANDARD_MODULE_HEADER,
"go2php",
go2php_functions,
PHP_MINIT(go2php),
PHP_MSHUTDOWN(go2php),
PHP_RINIT(go2php),
PHP_RSHUTDOWN(go2php),
PHP_MINFO(go2php),
"0.1.0",
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_GO2PHP
ZEND_GET_MODULE(go2php)
#endif
*/
import "C"
func main() {}package main
import "C"
//export calcFib
func calcFib(i int) int {
if i < 2 {
return i
}
return calcFib(i-1)+calcFib(i-2)
}

编译&链接

CGO_CFLAGS="-g \\
-I`/root/download/php8/bin/php-config –include-dir` \\
-I`/root/download/php8/bin/php-config –include-dir`/main \\
-I`/root/download/php8/bin/php-config –include-dir`/TSRM \\
-I`/root/download/php8/bin/php-config –include-dir`/Zend \\
-I`/root/download/php8/bin/php-config –include-dir`/ext \\
-I`/root/download/php8/bin/php-config –include-dir`/ext/date/lib \\
-DHAVE_CONFIG_H" CGO_LDFLAGS="-Wl,–export-dynamic -Wl,–unresolved-symbols=ignore-all" go build -p 1 -gcflags "-l" -buildmode=c-shared -o go2php.so

测试用 php 脚本代码

<?php
const COUNT = 30;
function calcFibonacci(int $i): int {
if ($i < 2) {
return $i;
}
return calcFibonacci($i – 1) + calcFibonacci($i – 2);
}
// CGO 速度
$startTime = microtime(true);
for($i = 1; $i <= COUNT; $i++) {
if($i != COUNT) {
go2php_print($i);
}else {
echo go2php_print($i)."\\n";
}
}
$time = microtime(true) – $startTime;
echo "CGO: {$time} 秒\\n";
//zephir 速度
$startTime = microtime(true);
for($i = 1; $i <= COUNT; $i++) {
if($i != COUNT) {
Lsz\\Zimuge::calcFibonacci($i);
}else {
echo Lsz\\Zimuge::calcFibonacci($i)."\\n";
}
}
$time = microtime(true) – $startTime;
echo "zephir: {$time} 秒\\n";
// PHP JIT 速度
$startTime = microtime(true);
for($i = 1; $i <= COUNT; $i++) {
if($i != COUNT) {
calcFibonacci($i);
}else {
echo calcFibonacci($i)."\\n";
}
}
$time = microtime(true) – $startTime;
echo "PHP: {$time} 秒\\n";

不使用 PHP JIT 的情况下测试

php test.php
->执行结果取一个平均
832040
CGO: 0.059875011444092 秒
832040
zephir: 8.5679790973663 秒
832040
PHP: 0.75995492935181 秒

使用 PHP JIT 的情况下测试

php -dopcache.enable_cli=1 -dopcache.jit_buffer_size=100M test.php
->执行结果取一个平均
832040
CGO: 0.046900987625122 秒
832040
zephir: 5.5882248878479 秒
832040
PHP: 0.10621190071106 秒

cgo 和 zephir 编译后的 so文件,通过php.ini 引入进来

执行测试脚本需要保证so正确读取进来。

命令 php -m 或者 php –ri xx.so 进行确认。

[PHP Modules]
Core
ctype
curl
date
dom
FFI
fileinfo
filter
gd
go2php
hash
iconv
json
libxml
lsz
mbstring
mysqlnd
openssl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
redis
Reflection
session
SimpleXML
SPL
sqlite3
standard
swoole
tokenizer
xml
xmlreader
xmlwriter
yaf
Zend OPcache
zephir_parser
zimuge
[Zend Modules]
Zend OPcache

使用PHP版本

php -v
PHP 8.1.3 (cli) (built: Feb 27 2022 19:40:08) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.3, Copyright (c) Zend Technologies
with Zend OPcache v8.1.3, Copyright (c), by Zend Technologies

结论:

JIT 能提高 php 的性能。

想学习 go 又不想放弃 php 可以用玩下 CGO。

zephir 虽然计算性能不太好,但是写 PHP 扩展实现起来比较简单。

以上就是面试官:列举几种PHP拓展的实现手段及其性能比较?的详细内容,更多请关注钦钦技术栈其它相关文章!

转载至:php中文网【www.php.cn】

版权声明:本文(即:原文链接:https://www.qin1qin.com/catagory/24425/)内容由互联网用户自发投稿贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 630367839@qq.com 举报,一经查实,本站将立刻删除。

(0)
上一篇 2022年9月21日 下午3:01
下一篇 2022年9月21日 下午3:02
软件定制开发公司

相关阅读

发表回复

登录后才能评论
通知:禁止投稿所有关于虚拟货币,币圈类相关文章,发现立即永久封锁账户ID!