当前位置: 首页 > news >正文

Kafka监控与调优实战指南

Kafka监控与调优实战指南

引言

Kafka作为高性能分布式消息系统,其监控和调优对于保障系统稳定运行至关重要。本文将详细介绍Kafka监控体系的构建方法、关键指标解析、性能调优策略以及常见问题的解决方案,帮助运维人员和开发者构建可靠的Kafka生产环境。

Kafka监控体系

1.1 JMX监控配置

Kafka通过JMX(Java Management Extensions)暴露大量运行时指标:

# 启动Kafka时启用JMX export JMX_PORT=9999 export KAFKA_JMX_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" kafka-server-start.sh config/server.properties
import javax.management.*; import javax.management.remote.*; import java.lang.management.*; import java.util.*; public class JMXMonitoring { public static void main(String[] args) throws Exception { String jmxUrl = "service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi"; JMXConnector connector = JMXConnectorFactory.connect(new JMXServiceURL(jmxUrl)); MBeanServerConnection mbsc = connector.getMBeanServerConnection(); // 获取所有域名 Set<ObjectName> domains = mbsc.queryNames(null, null); System.out.println("=== Kafka JMX MBeans ==="); for (ObjectName name : domains) { if (name.toString().contains("kafka")) { System.out.println(name); } } // 获取特定指标 String producerMetric = "kafka.producer:type=producer-metrics,client-id=producer-1"; ObjectName producerName = new ObjectName(producerMetric); if (mbsc.isRegistered(producerName)) { System.out.println("\n=== Producer Metrics ==="); String[] attributes = { "record-send-rate", "record-error-rate", "request-latency-avg", "outgoing-byte-rate" }; for (String attr : attributes) { try { Object value = mbsc.getAttribute(producerName, attr); System.out.println(attr + ": " + value); } catch (Exception e) { System.err.println("Failed to get " + attr); } } } connector.close(); } }

1.2 关键监控指标

public class KafkaMetricsCollector { private final KafkaProducer<String, String> producer; private final Map<String, Object> metricsSnapshot; public KafkaMetricsCollector(KafkaProducer<String, String> producer) { this.producer = producer; this.metricsSnapshot = new HashMap<>(); } public void collectProducerMetrics() { Map<MetricName, ? extends Metric> metrics = producer.metrics(); System.out.println("=== Kafka Producer Metrics ==="); collectMetric(metrics, "record-send-rate", "records/sec"); collectMetric(metrics, "record-error-rate", "records/sec"); collectMetric(metrics, "request-latency-avg", "ms"); collectMetric(metrics, "request-latency-max", "ms"); collectMetric(metrics, "outgoing-byte-rate", "bytes/sec"); collectMetric(metrics, "batch-size-avg", "bytes"); collectMetric(metrics, "buffer-available-bytes", "bytes"); collectMetric(metrics, "wait-time-avg", "ms"); } private void collectMetric(Map<MetricName, ? extends Metric> metrics, String metricName, String unit) { for (Map.Entry<MetricName, ? extends Metric> entry : metrics.entrySet()) { if (entry.getKey().name().equals(metricName)) { double value = entry.getValue().measure( registry -> registry.windowedValue().value()); System.out.printf(" %s: %.2f %s%n", metricName, value, unit); metricsSnapshot.put(metricName, value); } } } public Map<String, Object> getSnapshot() { return new HashMap<>(metricsSnapshot); } }

1.3 消费者监控

public class ConsumerMetricsCollector { public static void collectConsumerMetrics( KafkaConsumer<String, String> consumer) { Map<MetricName, ? extends Metric> metrics = consumer.metrics(); System.out.println("=== Kafka Consumer Metrics ==="); collectMetric(metrics, "fetch-rate", "requests/sec"); collectMetric(metrics, "fetch-latency-avg", "ms"); collectMetric(metrics, "fetch-latency-max", "ms"); collectMetric(metrics, "records-consumed-rate", "records/sec"); collectMetric(metrics, "commit-latency-avg", "ms"); collectMetric(metrics, "sync-time-avg", "ms"); collectMetric(metrics, "sync-time-max", "ms"); collectMetric(metrics, "assigned-partitions", "count"); collectMetric(metrics, "committed-transactions", "count"); } private static void collectMetric( Map<MetricName, ? extends Metric> metrics, String metricName, String unit) { for (Map.Entry<MetricName, ? extends Metric> entry : metrics.entrySet()) { if (entry.getKey().name().equals(metricName)) { double value = entry.getValue().measure( registry -> registry.windowedValue().value()); System.out.printf(" %s: %.2f %s%n", metricName, value, unit); } } } }

Kafka监控工具

2.1 Prometheus集成

# prometheus.yml 配置 global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: 'kafka' static_configs: - targets: ['localhost:7071'] metrics_path: /metrics
import io.prometheus.client.exporter.MetricsServlet; import io.prometheus.client.hotspot.DefaultExports; public class PrometheusMetricsExporter { public static void addKafkaMetrics(KafkaProducer<String, String> producer) { // 注册JMX收集器 JmxCollector jmxCollector = new JmxCollector( "kafka.producer:type=*,*"); // 添加到Prometheus CollectorRegistry.defaultRegistry.register(jmxCollector); DefaultExports.register(); } public static void main(String[] args) throws Exception { // 启动HTTP服务器暴露Prometheus指标 int port = 7071; HttpServer server = HttpServer.create( new InetSocketAddress(port), 0); server.createContext("/metrics", new MetricsServlet()); server.start(); System.out.println("Prometheus metrics server started on port " + port); } }

2.2 Grafana仪表板

{ "dashboard": { "title": "Kafka Producer Dashboard", "panels": [ { "title": "Record Send Rate", "type": "graph", "targets": [ { "expr": "kafka_producer_record_send_rate", "legendFormat": "{{client_id}}" } ] }, { "title": "Request Latency", "type": "graph", "targets": [ { "expr": "kafka_producer_request_latency_avg", "legendFormat": "{{client_id}}" } ] }, { "title": "Error Rate", "type": "graph", "targets": [ { "expr": "kafka_producer_record_error_rate", "legendFormat": "{{client_id}}" } ] } ] } }

Broker监控

3.1 Broker健康检查

import org.apache.kafka.clients.admin.*; import java.util.*; public class BrokerHealthCheck { public static void checkBrokerHealth(String bootstrapServers) throws Exception { Properties adminProps = new Properties(); adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); try (AdminClient adminClient = AdminClient.create(adminProps)) { // 检查Broker列表 DescribeClusterResult clusterResult = adminClient.describeCluster(); System.out.println("=== Cluster Information ==="); System.out.println("Cluster ID: " + clusterResult.clusterId().get()); System.out.println("Broker Count: " + clusterResult.nodes().get().size()); for (org.apache.kafka.clients.Cluster.Lambda<org.apache.kafka.common.Node> node : clusterResult.nodes().get()) { System.out.println("Broker " + node.id() + ": " + node.host() + ":" + node.port()); } // 检查主题状态 DescribeTopicsResult topicsResult = adminClient.describeTopics( Collections.singleton("my-topic")); Map<String, TopicDescription> topicDescs = topicsResult.allTopicNames().get(); System.out.println("\n=== Topic Status ==="); for (Map.Entry<String, TopicDescription> entry : topicDescs.entrySet()) { System.out.println("Topic: " + entry.getKey()); for (TopicPartitionInfo partition : entry.getValue().partitions()) { System.out.println(" Partition " + partition.partition() + ": " + "Leader=" + partition.leader() + ", ISR=" + partition.isr()); } } } } }

3.2 磁盘使用监控

public class DiskUsageMonitor { public static void monitorDiskUsage(String bootstrapServers) throws Exception { Properties adminProps = new Properties(); adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); try (AdminClient adminClient = AdminClient.create(adminProps)) { DescribeLogDirsResult logDirsResult = adminClient.describeLogDirs( Collections.singletonList(0)); Map<String, KafkaFuture<Map<Integer, LogDirDescription>>> logDirInfo = logDirsResult.allDescriptions().get(); System.out.println("=== Disk Usage ==="); for (Map.Entry<String, Map<Integer, LogDirDescription>> entry : logDirInfo.entrySet()) { System.out.println("Log Directory: " + entry.getKey()); long totalSize = 0; for (Map.Entry<Integer, LogDirDescription> partitionEntry : entry.getValue().entrySet()) { LogDirDescription desc = partitionEntry.getValue(); long partitionSize = desc.size(); totalSize += partitionSize; System.out.printf(" Partition %d: %.2f MB%n", partitionEntry.getKey(), partitionSize / (1024.0 * 1024.0)); } System.out.printf(" Total: %.2f GB%n", totalSize / (1024.0 * 1024.0 * 1024.0)); } } } }

性能调优

4.1 Broker调优

# server.properties - Broker配置优化 # 网络和IO线程配置 num.network.threads=8 num.io.threads=16 num.partitions=6 num.recovery.threads.per.data.dir=4 # Socket配置 socket.send.buffer.bytes=102400 socket.receive.buffer.bytes=102400 socket.request.max.bytes=104857600 # 日志配置 log.dirs=/data/kafka-logs log.retention.hours=168 log.retention.bytes=-1 log.segment.bytes=1073741824 log.cleanup.policy=delete log.cleaner.enable=true log.cleaner.threads=4 log.cleaner.io.buffer.size=524288 log.cleaner.io.buffer.load.factor=0.9 # 副本配置 default.replication.factor=3 min.insync.replicas=2 # 分区配置 num.network.threads=8 num.partitions=6 # 连接配置 max.connections.per.ip=2147483647 max.connections=1000 # 压缩配置 compression.type=producer

4.2 生产者调优

public class ProducerOptimization { public static Properties createOptimizedProducerConfig() { Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1:9092,kafka2:9092,kafka3:9092"); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); // 批处理优化 props.put(ProducerConfig.BATCH_SIZE_CONFIG, 65536); // 64KB props.put(ProducerConfig.LINGER_MS_CONFIG, 20); // 20ms props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 134217728); // 128MB // 压缩优化 props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4"); // 并发优化 props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 5); // 可靠性优化 props.put(ProducerConfig.ACKS_CONFIG, "all"); props.put(ProducerConfig.RETRIES_CONFIG, 3); props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); // 超时优化 props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 30000); props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, 120000); props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, 60000); // 重试优化 props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 100); return props; } }

4.3 消费者调优

public class ConsumerOptimization { public static Properties createOptimizedConsumerConfig() { Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1:9092,kafka2:9092,kafka3:9092"); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); // 拉取优化 props.put(ConsumerConfig.FETCH_MIN_BYTES_CONFIG, 1024); props.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, 500); props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, 10485760); // 10MB props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500); // 心跳优化 props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 3000); props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 45000); props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 300000); // 偏移量优化 props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); // 连接优化 props.put(ConsumerConfig.CONNECTIONS_MAX_IDLE_MS_CONFIG, 540000); return props; } }

性能测试

5.1 生产者性能测试

import org.apache.kafka.clients.producer.*; import java.time.*; public class ProducerBenchmark { public static void runBenchmark(String bootstrapServers, String topic, int messageCount, int messageSize) { Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); props.put(ProducerConfig.ACKS_CONFIG, "all"); props.put(ProducerConfig.RETRIES_CONFIG, 3); props.put(ProducerConfig.LINGER_MS_CONFIG, 10); props.put(ProducerConfig.BATCH_SIZE_CONFIG, 65536); KafkaProducer<String, String> producer = new KafkaProducer<>(props); byte[] payload = new byte[messageSize]; Arrays.fill(payload, (byte) 'x'); String message = new String(payload); long startTime = System.currentTimeMillis(); long totalBytes = 0; for (int i = 0; i < messageCount; i++) { ProducerRecord<String, String> record = new ProducerRecord<>(topic, "key-" + i, message); producer.send(record, (metadata, exception) -> { if (exception != null) { System.err.println("Send failed: " + exception); } }); totalBytes += messageSize; if ((i + 1) % 10000 == 0) { long elapsed = System.currentTimeMillis() - startTime; double throughput = (i + 1) * 1000.0 / elapsed; double mbPerSec = totalBytes / 1024.0 / 1024.0 / (elapsed / 1000.0); System.out.printf("Sent %d messages, %.2f msg/sec, " + "%.2f MB/sec%n", i + 1, throughput, mbPerSec); } } producer.flush(); producer.close(); long endTime = System.currentTimeMillis(); long duration = endTime - startTime; System.out.println("\n=== Benchmark Results ==="); System.out.println("Total Messages: " + messageCount); System.out.println("Total Duration: " + duration + " ms"); System.out.println("Throughput: " + String.format("%.2f msg/sec", messageCount * 1000.0 / duration)); System.out.println("Throughput: " + String.format("%.2f MB/sec", totalBytes / 1024.0 / 1024.0 / (duration / 1000.0))); } }

5.2 消费者性能测试

public class ConsumerBenchmark { public static void runBenchmark(String bootstrapServers, String groupId, String topic, int durationSeconds) { Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500); KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props); consumer.subscribe(Collections.singletonList(topic)); long startTime = System.currentTimeMillis(); long totalMessages = 0; long totalBytes = 0; while (System.currentTimeMillis() - startTime < durationSeconds * 1000) { ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100)); for (ConsumerRecord<String, String> record : records) { totalMessages++; totalBytes += record.value().length(); } consumer.commitSync(); if (totalMessages % 10000 == 0) { long elapsed = System.currentTimeMillis() - startTime; double throughput = totalMessages * 1000.0 / elapsed; System.out.printf("Processed %d messages, " + "%.2f msg/sec%n", totalMessages, throughput); } } consumer.close(); long duration = System.currentTimeMillis() - startTime; System.out.println("\n=== Benchmark Results ==="); System.out.println("Total Messages: " + totalMessages); System.out.println("Total Duration: " + duration + " ms"); System.out.println("Throughput: " + String.format("%.2f msg/sec", totalMessages * 1000.0 / duration)); System.out.println("Throughput: " + String.format("%.2f MB/sec", totalBytes / 1024.0 / 1024.0 / (duration / 1000.0))); } }

常见问题诊断

6.1 延迟问题诊断

public class LatencyDiagnostics { public static void diagnoseProducerLatency( KafkaProducer<String, String> producer) { Map<MetricName, ? extends Metric> metrics = producer.metrics(); System.out.println("=== Producer Latency Analysis ==="); double recordSendLatency = getMetricValue(metrics, "record-send-latency-avg"); double requestLatency = getMetricValue(metrics, "request-latency-avg"); double waitTime = getMetricValue(metrics, "wait-time-avg"); System.out.printf("Record Send Latency: %.2f ms%n", recordSendLatency); System.out.printf("Request Latency: %.2f ms%n", requestLatency); System.out.printf("Wait Time: %.2f ms%n", waitTime); if (waitTime > 10) { System.out.println("WARNING: High wait time detected!"); System.out.println("Consider increasing linger.ms or batch.size"); } if (requestLatency > 100) { System.out.println("WARNING: High request latency!"); System.out.println("Check broker load and network conditions"); } } public static void diagnoseConsumerLatency( KafkaConsumer<String, String> consumer) { Map<MetricName, ? extends Metric> metrics = consumer.metrics(); System.out.println("=== Consumer Latency Analysis ==="); double fetchLatency = getMetricValue(metrics, "fetch-latency-avg"); double pollLatency = getMetricValue(metrics, "poll-latency-avg"); System.out.printf("Fetch Latency: %.2f ms%n", fetchLatency); System.out.printf("Poll Latency: %.2f ms%n", pollLatency); if (fetchLatency > 50) { System.out.println("WARNING: High fetch latency!"); System.out.println("Consider increasing fetch.max.wait.ms"); } } private static double getMetricValue( Map<MetricName, ? extends Metric> metrics, String metricName) { for (Map.Entry<MetricName, ? extends Metric> entry : metrics.entrySet()) { if (entry.getKey().name().equals(metricName)) { return entry.getValue().measure( registry -> registry.windowedValue().value()); } } return 0; } }

6.2 吞吐量问题诊断

public class ThroughputDiagnostics { public static void diagnoseThroughputIssues( KafkaProducer<String, String> producer) { Map<MetricName, ? extends Metric> metrics = producer.metrics(); System.out.println("=== Throughput Analysis ==="); double recordSendRate = getMetricValue(metrics, "record-send-rate"); double outgoingByteRate = getMetricValue(metrics, "outgoing-byte-rate"); double batchSize = getMetricValue(metrics, "batch-size-avg"); double compressionRate = getMetricValue(metrics, "compression-rate-avg"); System.out.printf("Record Send Rate: %.2f records/sec%n", recordSendRate); System.out.printf("Outgoing Byte Rate: %.2f MB/sec%n", outgoingByteRate / 1024 / 1024); System.out.printf("Average Batch Size: %.2f bytes%n", batchSize); System.out.printf("Compression Rate: %.2f%%%n", compressionRate * 100); if (batchSize < 10000) { System.out.println("WARNING: Small batch size!"); System.out.println("Consider increasing batch.size"); } if (compressionRate < 0.3) { System.out.println("WARNING: Low compression rate!"); System.out.println("Consider changing compression.type"); } } }

总结

Kafka监控与调优是一个持续优化的过程。通过建立完善的监控体系,收集和分析关键指标,能够及时发现和解决性能问题。本文详细介绍了Kafka的监控方法、关键指标、调优策略以及常见问题的诊断方案,帮助运维人员和开发者构建高性能、高可靠的Kafka集群。

http://www.jsqmd.com/news/894140/

相关文章:

  • 告别Alt+F2失灵!手把手教你为UE4独立游戏开启Nvidia Ansel全景截图功能(适配新旧驱动)
  • 从I²t曲线到温升降额:手把手教你用Littelfuse数据手册精准计算Fuse熔断时间
  • C51预处理列表生成与调试技巧
  • 别只盯着华为云!openEuler yum源配置进阶:内网离线仓库搭建与第三方EPEL源融合实战
  • 别再乱用欧氏距离了!用Python手把手教你计算二元变量相似度(附Jaccard系数实战代码)
  • 工作空间优化:如何训练智体
  • 用SPSSAU做Dagum基尼系数分析:手把手教你分解中国各省人均GDP的区域差异
  • C251架构2字节中断栈帧优化实践
  • 从0到1构建一个Hook工具之Java Hook篇(三)
  • 告别NTPD:用Chrony和GPS 1PPS信号把Linux系统时间精度拉到纳秒级
  • 2026年4月国内做得好的光伏连接件厂商找哪家,连续模具/模具/冲压件/汽车模具/汽车配件/金属配件,光伏连接件厂家手机 - 品牌推荐师
  • 2026年托管加盟排行榜核心维度与头部品牌解析:托管加盟手续/托管加盟排行榜/托管加盟推荐/托管加盟机构/托管加盟费用/选择指南 - 优质品牌商家
  • 西南及全国液态金属漆厂家综合实力排行盘点:夯土漆厂家/成都仿石漆厂家/无机涂料价格/无机涂料厂家推荐/无机涂料外墙/选择指南 - 优质品牌商家
  • Windows系统隐藏的硬件侦探:Sysinternals Coreinfo实战,教你排查多核CPU负载不均、虚拟机卡顿的根因
  • 手把手教你用CMP Facade数据集做图像修复:从下载到实战(含云盘链接)
  • 别再只会用A4988了!手把手教你用TB67H450/451驱动两相步进电机(附完整电路图)
  • [論文學習]透過 Recollection 與 Ranking 揭露 LLM 訓練資料隱私漏洞
  • 微信单向好友检测:三步识别并清理你的无效社交关系
  • 从STK报告到Matlab矩阵:手把手教你解析卫星可见性数据(避坑指南)
  • 告别Keil!在VSCode+GCC+STM32CubeIDE工程里搞定printf串口打印(附通用syscalls.c文件)
  • 使用taotoken cli工具一键配置团队多成员的开发环境
  • 数据科学与Python开发:构建机器学习模型的完整流程
  • 2026现阶段荆门恩格曼隔热条品牌厂商推荐哪家?深度解析佰慕尚门窗的优势 - 2026年企业资讯
  • 双金属堆焊耐磨管厂家评测:双金属灰水耐磨管、灰水耐磨三通、双金属复合耐磨管、合金双金属耐磨管、电厂输粉双金属耐磨管选择指南 - 优质品牌商家
  • 告别‘yum makecache失败’:openEuler ARM服务器/虚拟机yum源配置的3个关键检查点与避坑指南
  • 别再单打独斗了!用CrewAI打造你的第一个多Agent“数字员工”团队(保姆级配置)
  • 告别CNN依赖:用Python手把手实现K-SVD图像降噪(附完整代码与Patch提取技巧)
  • Windows 11终极净化指南:开源神器Win11Debloat深度解析与实战
  • 不锈钢多功能管道修补器技术解析与行业选型参考:不锈钢单卡管道修补器/不锈钢双卡管道修补器/不锈钢板式修补器/不锈钢管道修补连接器/选择指南 - 优质品牌商家
  • 3步掌握Steam成就管理:SteamAchievementManager导出导入实战指南