RocketMQ源码 Broker-BrokerStatsManager Broker统计管理组件源码分析
2023-12-14 15:53:12
前言
BrokerStatsManager 主要负责对broker端的系统指标进行统计,如QUEUE_GET_NUMS队列获取数量、QUEUE_GET_SIZE队列获取大小指标的 分钟、小时、天级别的统计数据。它针对的所有指标都是使用后台定时调度线程,对统计条目中的数据进行后台统计计算,存储在统计条目中的对应集合里,以便使用。
源码版本:4.9.3
源码架构图
核心数据结构
最核心的是维护了一个数据统计table,key为统计名称,value为统计条目Set。
// Broker统计信息管理
public class BrokerStatsManager {
// Broker统计调度线程池
private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
"BrokerStatsThread"));
// 商业化统计调度线程池
private final ScheduledExecutorService commercialExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
"CommercialStatsThread"));
// 核心数据结构,数据统计table,key为统计名称,value为统计条目Set
private final HashMap<String, StatsItemSet> statsTable = new HashMap<String, StatsItemSet>();
// 集群名称
private final String clusterName;
// 是否开启队列统计
private final boolean enableQueueStat;
// 统计条目跌落大小
private final MomentStatsItemSet momentStatsItemSetFallSize = new MomentStatsItemSet(GROUP_GET_FALL_SIZE, scheduledExecutorService, log);
// 统计条目跌落时间
private final MomentStatsItemSet momentStatsItemSetFallTime = new MomentStatsItemSet(GROUP_GET_FALL_TIME, scheduledExecutorService, log);
}
统计条目集合内部结构,主要维护一个统计条目表, key为统计指标细分key, value为统计指标对象。
// 统计指标集合
public class StatsItemSet {
// 统计指标表, key为统计指标细分key, value为统计指标对象
private final ConcurrentMap<String/* key */, StatsItem> statsItemTable =
new ConcurrentHashMap<String, StatsItem>(128);
// 统计名称
private final String statsName;
private final ScheduledExecutorService scheduledExecutorService;
private final InternalLogger log;
}
在深入看下统计条目内部结构,维护记录指标值的value、times和记录统计快照的csListMinute、csListHour、csListDay。记录统计快照集合是通过后台线程定时加工出来的,可以在数据行为小结的源码里找到。
// 统计项
public class StatsItem {
// 统计值
private final LongAdder value = new LongAdder();
// 统计次数
private final LongAdder times = new LongAdder();
// 统计快照时间-单位分钟
private final LinkedList<CallSnapshot> csListMinute = new LinkedList<CallSnapshot>();
// 统计快照时间-单位小时
private final LinkedList<CallSnapshot> csListHour = new LinkedList<CallSnapshot>();
// 统计快照时间-单位天
private final LinkedList<CallSnapshot> csListDay = new LinkedList<CallSnapshot>();
// 统计名称
private final String statsName;
// 统计项名称
private final String statsKey;
private final ScheduledExecutorService scheduledExecutorService;
private final InternalLogger log;
}
核心行为
以下是都有维护和使用上边的内存数据结构的行为的源码 + 注释。
// Broker统计信息管理
public class BrokerStatsManager {
public static final String QUEUE_PUT_NUMS = "QUEUE_PUT_NUMS";
public static final String QUEUE_PUT_SIZE = "QUEUE_PUT_SIZE";
public static final String QUEUE_GET_NUMS = "QUEUE_GET_NUMS";
public static final String QUEUE_GET_SIZE = "QUEUE_GET_SIZE";
public static final String TOPIC_PUT_NUMS = "TOPIC_PUT_NUMS";
public static final String TOPIC_PUT_SIZE = "TOPIC_PUT_SIZE";
public static final String GROUP_GET_NUMS = "GROUP_GET_NUMS";
public static final String GROUP_GET_SIZE = "GROUP_GET_SIZE";
public static final String SNDBCK_PUT_NUMS = "SNDBCK_PUT_NUMS";
public static final String BROKER_PUT_NUMS = "BROKER_PUT_NUMS";
public static final String BROKER_GET_NUMS = "BROKER_GET_NUMS";
public static final String GROUP_GET_FROM_DISK_NUMS = "GROUP_GET_FROM_DISK_NUMS";
public static final String GROUP_GET_FROM_DISK_SIZE = "GROUP_GET_FROM_DISK_SIZE";
public static final String BROKER_GET_FROM_DISK_NUMS = "BROKER_GET_FROM_DISK_NUMS";
public static final String BROKER_GET_FROM_DISK_SIZE = "BROKER_GET_FROM_DISK_SIZE";
// For commercial
public static final String COMMERCIAL_SEND_TIMES = "COMMERCIAL_SEND_TIMES";
public static final String COMMERCIAL_SNDBCK_TIMES = "COMMERCIAL_SNDBCK_TIMES";
public static final String COMMERCIAL_RCV_TIMES = "COMMERCIAL_RCV_TIMES";
public static final String COMMERCIAL_RCV_EPOLLS = "COMMERCIAL_RCV_EPOLLS";
public static final String COMMERCIAL_SEND_SIZE = "COMMERCIAL_SEND_SIZE";
public static final String COMMERCIAL_RCV_SIZE = "COMMERCIAL_RCV_SIZE";
public static final String COMMERCIAL_PERM_FAILURES = "COMMERCIAL_PERM_FAILURES";
public static final String COMMERCIAL_OWNER = "Owner";
// Message Size limit for one api-calling count.
public static final double SIZE_PER_COUNT = 64 * 1024;
public static final String GROUP_GET_FALL_SIZE = "GROUP_GET_FALL_SIZE";
public static final String GROUP_GET_FALL_TIME = "GROUP_GET_FALL_TIME";
// Pull Message Latency
public static final String GROUP_GET_LATENCY = "GROUP_GET_LATENCY";
/**
* read disk follow stats
*/
private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.ROCKETMQ_STATS_LOGGER_NAME);
private static final InternalLogger COMMERCIAL_LOG = InternalLoggerFactory.getLogger(LoggerName.COMMERCIAL_LOGGER_NAME);
// Broker统计调度线程池
private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
"BrokerStatsThread"));
// 商业化统计调度线程池
private final ScheduledExecutorService commercialExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
"CommercialStatsThread"));
// 核心数据结构,数据统计table,key为统计名称,value为统计条目Set
private final HashMap<String, StatsItemSet> statsTable = new HashMap<String, StatsItemSet>();
// 集群名称
private final String clusterName;
// 是否开启队列统计
private final boolean enableQueueStat;
// 统计条目跌落大小
private final MomentStatsItemSet momentStatsItemSetFallSize = new MomentStatsItemSet(GROUP_GET_FALL_SIZE, scheduledExecutorService, log);
// 统计条目跌落时间
private final MomentStatsItemSet momentStatsItemSetFallTime = new MomentStatsItemSet(GROUP_GET_FALL_TIME, scheduledExecutorService, log);
public BrokerStatsManager(String clusterName, boolean enableQueueStat) {
this.clusterName = clusterName;
this.enableQueueStat = enableQueueStat;
// 初始化统计条目集合
if (enableQueueStat) {
this.statsTable.put(QUEUE_PUT_NUMS, new StatsItemSet(QUEUE_PUT_NUMS, this.scheduledExecutorService, log));
this.statsTable.put(QUEUE_PUT_SIZE, new StatsItemSet(QUEUE_PUT_SIZE, this.scheduledExecutorService, log));
this.statsTable.put(QUEUE_GET_NUMS, new StatsItemSet(QUEUE_GET_NUMS, this.scheduledExecutorService, log));
this.statsTable.put(QUEUE_GET_SIZE, new StatsItemSet(QUEUE_GET_SIZE, this.scheduledExecutorService, log));
}
this.statsTable.put(TOPIC_PUT_NUMS, new StatsItemSet(TOPIC_PUT_NUMS, this.scheduledExecutorService, log));
this.statsTable.put(TOPIC_PUT_SIZE, new StatsItemSet(TOPIC_PUT_SIZE, this.scheduledExecutorService, log));
this.statsTable.put(GROUP_GET_NUMS, new StatsItemSet(GROUP_GET_NUMS, this.scheduledExecutorService, log));
this.statsTable.put(GROUP_GET_SIZE, new StatsItemSet(GROUP_GET_SIZE, this.scheduledExecutorService, log));
this.statsTable.put(GROUP_GET_LATENCY, new StatsItemSet(GROUP_GET_LATENCY, this.scheduledExecutorService, log));
this.statsTable.put(SNDBCK_PUT_NUMS, new StatsItemSet(SNDBCK_PUT_NUMS, this.scheduledExecutorService, log));
this.statsTable.put(BROKER_PUT_NUMS, new StatsItemSet(BROKER_PUT_NUMS, this.scheduledExecutorService, log));
this.statsTable.put(BROKER_GET_NUMS, new StatsItemSet(BROKER_GET_NUMS, this.scheduledExecutorService, log));
this.statsTable.put(GROUP_GET_FROM_DISK_NUMS, new StatsItemSet(GROUP_GET_FROM_DISK_NUMS, this.scheduledExecutorService, log));
this.statsTable.put(GROUP_GET_FROM_DISK_SIZE, new StatsItemSet(GROUP_GET_FROM_DISK_SIZE, this.scheduledExecutorService, log));
this.statsTable.put(BROKER_GET_FROM_DISK_NUMS, new StatsItemSet(BROKER_GET_FROM_DISK_NUMS, this.scheduledExecutorService, log));
this.statsTable.put(BROKER_GET_FROM_DISK_SIZE, new StatsItemSet(BROKER_GET_FROM_DISK_SIZE, this.scheduledExecutorService, log));
this.statsTable.put(COMMERCIAL_SEND_TIMES, new StatsItemSet(COMMERCIAL_SEND_TIMES, this.commercialExecutor, COMMERCIAL_LOG));
this.statsTable.put(COMMERCIAL_RCV_TIMES, new StatsItemSet(COMMERCIAL_RCV_TIMES, this.commercialExecutor, COMMERCIAL_LOG));
this.statsTable.put(COMMERCIAL_SEND_SIZE, new StatsItemSet(COMMERCIAL_SEND_SIZE, this.commercialExecutor, COMMERCIAL_LOG));
this.statsTable.put(COMMERCIAL_RCV_SIZE, new StatsItemSet(COMMERCIAL_RCV_SIZE, this.commercialExecutor, COMMERCIAL_LOG));
this.statsTable.put(COMMERCIAL_RCV_EPOLLS, new StatsItemSet(COMMERCIAL_RCV_EPOLLS, this.commercialExecutor, COMMERCIAL_LOG));
this.statsTable.put(COMMERCIAL_SNDBCK_TIMES, new StatsItemSet(COMMERCIAL_SNDBCK_TIMES, this.commercialExecutor, COMMERCIAL_LOG));
this.statsTable.put(COMMERCIAL_PERM_FAILURES, new StatsItemSet(COMMERCIAL_PERM_FAILURES, this.commercialExecutor, COMMERCIAL_LOG));
}
public MomentStatsItemSet getMomentStatsItemSetFallSize() {
return momentStatsItemSetFallSize;
}
public MomentStatsItemSet getMomentStatsItemSetFallTime() {
return momentStatsItemSetFallTime;
}
public void start() {
}
public void shutdown() {
this.scheduledExecutorService.shutdown();
this.commercialExecutor.shutdown();
}
// 获取指定统计名称下的统计key的统计条目
public StatsItem getStatsItem(final String statsName, final String statsKey) {
try {
return this.statsTable.get(statsName).getStatsItem(statsKey);
} catch (Exception e) {
}
return null;
}
// 删除指定topic的统计数据
public void onTopicDeleted(final String topic) {
this.statsTable.get(TOPIC_PUT_NUMS).delValue(topic);
this.statsTable.get(TOPIC_PUT_SIZE).delValue(topic);
if (enableQueueStat) {
this.statsTable.get(QUEUE_PUT_NUMS).delValueByPrefixKey(topic, "@");
this.statsTable.get(QUEUE_PUT_SIZE).delValueByPrefixKey(topic, "@");
}
this.statsTable.get(GROUP_GET_NUMS).delValueByPrefixKey(topic, "@");
this.statsTable.get(GROUP_GET_SIZE).delValueByPrefixKey(topic, "@");
this.statsTable.get(QUEUE_GET_NUMS).delValueByPrefixKey(topic, "@");
this.statsTable.get(QUEUE_GET_SIZE).delValueByPrefixKey(topic, "@");
this.statsTable.get(SNDBCK_PUT_NUMS).delValueByPrefixKey(topic, "@");
this.statsTable.get(GROUP_GET_LATENCY).delValueByInfixKey(topic, "@");
this.momentStatsItemSetFallSize.delValueByInfixKey(topic, "@");
this.momentStatsItemSetFallTime.delValueByInfixKey(topic, "@");
}
// 根据group删除统计数据
public void onGroupDeleted(final String group) {
this.statsTable.get(GROUP_GET_NUMS).delValueBySuffixKey(group, "@");
this.statsTable.get(GROUP_GET_SIZE).delValueBySuffixKey(group, "@");
if (enableQueueStat) {
this.statsTable.get(QUEUE_GET_NUMS).delValueBySuffixKey(group, "@");
this.statsTable.get(QUEUE_GET_SIZE).delValueBySuffixKey(group, "@");
}
this.statsTable.get(SNDBCK_PUT_NUMS).delValueBySuffixKey(group, "@");
this.statsTable.get(GROUP_GET_LATENCY).delValueBySuffixKey(group, "@");
this.momentStatsItemSetFallSize.delValueBySuffixKey(group, "@");
this.momentStatsItemSetFallTime.delValueBySuffixKey(group, "@");
}
// 增加队列的put统计数据1次
public void incQueuePutNums(final String topic, final Integer queueId) {
if (enableQueueStat) {
this.statsTable.get(QUEUE_PUT_NUMS).addValue(buildStatsKey(topic, queueId), 1, 1);
}
}
// 增加队列的put统计数据n次
public void incQueuePutNums(final String topic, final Integer queueId, int num, int times) {
if (enableQueueStat) {
this.statsTable.get(QUEUE_PUT_NUMS).addValue(buildStatsKey(topic, queueId), num, times);
}
}
// 增加队列的put统计数据大小
public void incQueuePutSize(final String topic, final Integer queueId, final int size) {
if (enableQueueStat) {
this.statsTable.get(QUEUE_PUT_SIZE).addValue(buildStatsKey(topic, queueId), size, 1);
}
}
// 增加队列的get统计数据1次
public void incQueueGetNums(final String group, final String topic, final Integer queueId, final int incValue) {
if (enableQueueStat) {
final String statsKey = buildStatsKey(topic, queueId, group);
this.statsTable.get(QUEUE_GET_NUMS).addValue(statsKey, incValue, 1);
}
}
// 增加队列的get统计数据大小
public void incQueueGetSize(final String group, final String topic, final Integer queueId, final int incValue) {
if (enableQueueStat) {
final String statsKey = buildStatsKey(topic, queueId, group);
this.statsTable.get(QUEUE_GET_SIZE).addValue(statsKey, incValue, 1);
}
}
// 增加topic的put统计数据1次
public void incTopicPutNums(final String topic) {
this.statsTable.get(TOPIC_PUT_NUMS).addValue(topic, 1, 1);
}
public void incTopicPutNums(final String topic, int num, int times) {
this.statsTable.get(TOPIC_PUT_NUMS).addValue(topic, num, times);
}
public void incTopicPutSize(final String topic, final int size) {
this.statsTable.get(TOPIC_PUT_SIZE).addValue(topic, size, 1);
}
// 增加group的get统计数据1次
public void incGroupGetNums(final String group, final String topic, final int incValue) {
final String statsKey = buildStatsKey(topic, group);
this.statsTable.get(GROUP_GET_NUMS).addValue(statsKey, incValue, 1);
}
public String buildStatsKey(String topic, String group) {
StringBuilder strBuilder;
if (topic != null && group != null) {
strBuilder = new StringBuilder(topic.length() + group.length() + 1);
} else {
strBuilder = new StringBuilder();
}
strBuilder.append(topic).append("@").append(group);
return strBuilder.toString();
}
public String buildStatsKey(String topic, int queueId) {
StringBuilder strBuilder;
if (topic != null) {
strBuilder = new StringBuilder(topic.length() + 5);
} else {
strBuilder = new StringBuilder();
}
strBuilder.append(topic).append("@").append(queueId);
return strBuilder.toString();
}
public String buildStatsKey(String topic, int queueId, String group) {
StringBuilder strBuilder;
if (topic != null && group != null) {
strBuilder = new StringBuilder(topic.length() + group.length() + 6);
} else {
strBuilder = new StringBuilder();
}
strBuilder.append(topic).append("@").append(queueId).append("@").append(group);
return strBuilder.toString();
}
public String buildStatsKey(int queueId, String topic, String group) {
StringBuilder strBuilder;
if (topic != null && group != null) {
strBuilder = new StringBuilder(topic.length() + group.length() + 6);
} else {
strBuilder = new StringBuilder();
}
strBuilder.append(queueId).append("@").append(topic).append("@").append(group);
return strBuilder.toString();
}
public void incGroupGetSize(final String group, final String topic, final int incValue) {
final String statsKey = buildStatsKey(topic, group);
this.statsTable.get(GROUP_GET_SIZE).addValue(statsKey, incValue, 1);
}
// 增加group的get延迟统计数据1次
public void incGroupGetLatency(final String group, final String topic, final int queueId, final int incValue) {
String statsKey;
if (enableQueueStat) {
statsKey = buildStatsKey(queueId, topic, group);
} else {
statsKey = buildStatsKey(topic, group);
}
this.statsTable.get(GROUP_GET_LATENCY).addRTValue(statsKey, incValue, 1);
}
public void incBrokerPutNums() {
this.statsTable.get(BROKER_PUT_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(1);
}
public void incBrokerPutNums(final int incValue) {
this.statsTable.get(BROKER_PUT_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(incValue);
}
public void incBrokerGetNums(final int incValue) {
this.statsTable.get(BROKER_GET_NUMS).getAndCreateStatsItem(this.clusterName).getValue().add(incValue);
}
public void incSendBackNums(final String group, final String topic) {
final String statsKey = buildStatsKey(topic, group);
this.statsTable.get(SNDBCK_PUT_NUMS).addValue(statsKey, 1, 1);
}
public double tpsGroupGetNums(final String group, final String topic) {
final String statsKey = buildStatsKey(topic, group);
return this.statsTable.get(GROUP_GET_NUMS).getStatsDataInMinute(statsKey).getTps();
}
public void recordDiskFallBehindTime(final String group, final String topic, final int queueId,
final long fallBehind) {
final String statsKey = buildStatsKey(queueId, topic, group);
this.momentStatsItemSetFallTime.getAndCreateStatsItem(statsKey).getValue().set(fallBehind);
}
public void recordDiskFallBehindSize(final String group, final String topic, final int queueId,
final long fallBehind) {
final String statsKey = buildStatsKey(queueId, topic, group);
this.momentStatsItemSetFallSize.getAndCreateStatsItem(statsKey).getValue().set(fallBehind);
}
public void incCommercialValue(final String key, final String owner, final String group,
final String topic, final String type, final int incValue) {
final String statsKey = buildCommercialStatsKey(owner, topic, group, type);
this.statsTable.get(key).addValue(statsKey, incValue, 1);
}
public String buildCommercialStatsKey(String owner, String topic, String group, String type) {
StringBuilder strBuilder = new StringBuilder();
strBuilder.append(owner);
strBuilder.append("@");
strBuilder.append(topic);
strBuilder.append("@");
strBuilder.append(group);
strBuilder.append("@");
strBuilder.append(type);
return strBuilder.toString();
}
public enum StatsType {
SEND_SUCCESS,
SEND_FAILURE,
SEND_BACK,
SEND_TIMER,
SEND_TRANSACTION,
RCV_SUCCESS,
RCV_EPOLLS,
PERM_FAILURE
}
}
// 统计条目数据集合
public class MomentStatsItemSet {
// 核心数据结构:Map<统计条目key名称,统计条目>
private final ConcurrentMap<String/* key */, MomentStatsItem> statsItemTable =
new ConcurrentHashMap<String, MomentStatsItem>(128);
// 统计名称
private final String statsName;
// 定时调度线程池
private final ScheduledExecutorService scheduledExecutorService;
private final InternalLogger log;
public MomentStatsItemSet(String statsName, ScheduledExecutorService scheduledExecutorService, InternalLogger log) {
this.statsName = statsName;
this.scheduledExecutorService = scheduledExecutorService;
this.log = log;
this.init();
}
public ConcurrentMap<String, MomentStatsItem> getStatsItemTable() {
return statsItemTable;
}
public String getStatsName() {
return statsName;
}
public void init() {
// 每隔5分钟打印一次
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
printAtMinutes();
} catch (Throwable ignored) {
}
}
}, Math.abs(UtilAll.computeNextMinutesTimeMillis() - System.currentTimeMillis()), 1000 * 60 * 5, TimeUnit.MILLISECONDS);
}
private void printAtMinutes() {
// 迭代遍历,打印当前统计指标所有key对应的数据
Iterator<Entry<String, MomentStatsItem>> it = this.statsItemTable.entrySet().iterator();
while (it.hasNext()) {
Entry<String, MomentStatsItem> next = it.next();
next.getValue().printAtMinutes();
}
}
// 对某一个统计指标对应的统计key,设置值
public void setValue(final String statsKey, final int value) {
MomentStatsItem statsItem = this.getAndCreateStatsItem(statsKey);
statsItem.getValue().set(value);
}
// 删除统计指标对应的统计key
public void delValueByInfixKey(final String statsKey, String separator) {
Iterator<Entry<String, MomentStatsItem>> it = this.statsItemTable.entrySet().iterator();
while (it.hasNext()) {
Entry<String, MomentStatsItem> next = it.next();
if (next.getKey().contains(separator + statsKey + separator)) {
it.remove();
}
}
}
// 删除统计指标对应的统计key(指定了后缀)
public void delValueBySuffixKey(final String statsKey, String separator) {
Iterator<Entry<String, MomentStatsItem>> it = this.statsItemTable.entrySet().iterator();
while (it.hasNext()) {
Entry<String, MomentStatsItem> next = it.next();
if (next.getKey().endsWith(separator + statsKey)) {
it.remove();
}
}
}
// 获取统计指标对应的统计条目key对应的统计条目
public MomentStatsItem getAndCreateStatsItem(final String statsKey) {
MomentStatsItem statsItem = this.statsItemTable.get(statsKey);
if (null == statsItem) {
statsItem =
new MomentStatsItem(this.statsName, statsKey, this.scheduledExecutorService, this.log);
MomentStatsItem prev = this.statsItemTable.putIfAbsent(statsKey, statsItem);
if (null != prev) {
statsItem = prev;
// statsItem.init();
}
}
return statsItem;
}
}
// 统计条目
public class MomentStatsItem {
// 指标值
private final AtomicLong value = new AtomicLong(0);
// 统计指标名称
private final String statsName;
// 统计条目key
private final String statsKey;
private final ScheduledExecutorService scheduledExecutorService;
private final InternalLogger log;
public MomentStatsItem(String statsName, String statsKey,
ScheduledExecutorService scheduledExecutorService, InternalLogger log) {
this.statsName = statsName;
this.statsKey = statsKey;
this.scheduledExecutorService = scheduledExecutorService;
this.log = log;
}
public void init() {
// 每5分钟打印一次
this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
try {
printAtMinutes();
MomentStatsItem.this.value.set(0);
} catch (Throwable e) {
}
}
}, Math.abs(UtilAll.computeNextMinutesTimeMillis() - System.currentTimeMillis()), 1000 * 60 * 5, TimeUnit.MILLISECONDS);
}
public void printAtMinutes() {
log.info(String.format("[%s] [%s] Stats Every 5 Minutes, Value: %d",
this.statsName,
this.statsKey,
this.value.get()));
}
public AtomicLong getValue() {
return value;
}
public String getStatsKey() {
return statsKey;
}
public String getStatsName() {
return statsName;
}
}
文章来源:https://blog.csdn.net/hzwangmr/article/details/134993822
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!