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

Kafka分区策略深度解析

Kafka分区策略深度解析

引言

Kafka的分区策略是决定消息如何分配到不同分区的核心机制,直接影响着系统的性能、可靠性和可扩展性。合理设计分区策略能够最大化Kafka的吞吐量,同时保证消息的顺序性和负载均衡。本文将深入探讨Kafka的各种分区策略,包括默认策略、自定义策略以及不同场景下的最佳实践。

分区基础理论

1.1 分区的作用

分区是Kafka实现并行处理和水平扩展的基础。每个分区是一个有序的、不可变的消息序列,具有以下重要作用:

  • 并行处理:不同分区可以被不同的消费者并行处理
  • 负载均衡:消息分散到不同分区,实现负载均衡
  • 顺序保证:单分区内消息有序,跨分区不保证顺序
  • 扩展性:可以通过增加分区数来提高吞吐量
import org.apache.kafka.clients.admin.*; import org.apache.kafka.common.*; import java.util.*; public class PartitionBasics { public static void createTopicWithPartitions() throws Exception { Properties adminProps = new Properties(); adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); try (AdminClient adminClient = AdminClient.create(adminProps)) { // 创建具有多个分区的主题 NewTopic topic = new NewTopic("my-topic", 6, (short) 3); // 或者使用分区配置 Map<Integer, List<Integer>> replicasAssignments = new HashMap<>(); replicasAssignments.put(0, Arrays.asList(0, 1, 2)); replicasAssignments.put(1, Arrays.asList(1, 2, 0)); replicasAssignments.put(2, Arrays.asList(2, 0, 1)); replicasAssignments.put(3, Arrays.asList(0, 1, 2)); replicasAssignments.put(4, Arrays.asList(1, 2, 0)); replicasAssignments.put(5, Arrays.asList(2, 0, 1)); NewTopic customTopic = new NewTopic("custom-partition-topic", replicasAssignments); CreateTopicsResult result = adminClient.createTopics( Arrays.asList(topic, customTopic)); result.all().get(); System.out.println("主题创建成功"); } } public static void describeTopic(String topicName) throws Exception { Properties adminProps = new Properties(); adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); try (AdminClient adminClient = AdminClient.create(adminProps)) { DescribeTopicsResult result = adminClient.describeTopics(Collections.singleton(topicName)); Map<String, TopicDescription> description = result.all().get(); TopicDescription topic = description.get(topicName); System.out.println("主题: " + topic.name()); System.out.println("分区数: " + topic.partitions().size()); for (TopicPartitionInfo partition : topic.partitions()) { System.out.println("分区 " + partition.partition() + ": " + "Leader=" + partition.leader() + ", Replicas=" + partition.replicas() + ", ISR=" + partition.isr()); } } } }

1.2 分区数量设计原则

分区数的选择需要综合考虑多个因素:

public class PartitionDesign { public static class PartitionCalculator { /** * 计算合适的分区数 * @param targetThroughput 目标吞吐量(消息/秒) * @param consumerThroughput 单个消费者处理能力(消息/秒) * @param producerThroughput 单个生产者发送能力(消息/秒) * @return 建议的分区数 */ public static int calculatePartitionCount( int targetThroughput, int consumerThroughput, int producerThroughput) { // 消费者数量限制 int partitionsForConsumers = targetThroughput / consumerThroughput; // 生产者数量限制 int partitionsForProducers = targetThroughput / producerThroughput; // 取较大值,并预留一定余量 int basePartitions = Math.max( partitionsForConsumers, partitionsForProducers); // 考虑未来扩展,预留30%余量 return (int) (basePartitions * 1.3); } public static void main(String[] args) { // 假设目标吞吐量为10000条/秒 // 单个消费者处理能力为1000条/秒 // 单个生产者发送能力为5000条/秒 int targetThroughput = 10000; int consumerThroughput = 1000; int producerThroughput = 5000; int recommendedPartitions = calculatePartitionCount( targetThroughput, consumerThroughput, producerThroughput); System.out.println("建议分区数: " + recommendedPartitions); } } }

默认分区策略

2.1 DefaultPartitioner

Kafka默认使用DefaultPartitioner,它根据消息的key来决定分区:

import org.apache.kafka.clients.producer.*; import org.apache.kafka.common.*; import org.apache.kafka.common.utils.Utils; import java.util.*; public class DefaultPartitionerDemo { public static class CustomDefaultPartitioner implements Partitioner { private int numPartitions; @Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, org.apache.kafka.common.Cluster cluster) { List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); numPartitions = partitions.size(); if (keyBytes == null) { // 无key时,使用轮询策略 return roundRobinPartition(topic, cluster); } else { // 有key时,使用hash策略 return hashPartition(keyBytes); } } private int roundRobinPartition(String topic, org.apache.kafka.common.Cluster cluster) { List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); int nextValue = nextValue(topic); return Math.abs(nextValue % partitions.size()); } private int hashPartition(byte[] keyBytes) { // 使用murmur2哈希算法 return Math.abs(Utils.murmur2(keyBytes) % numPartitions); } private int nextValue(String topic) { // 使用随机值模拟轮询 return new Random().nextInt(1000); } @Override public void close() {} @Override public void configure(Map<String, ?> configs) {} } public static void demonstrate() { Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost: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.PARTITIONER_CLASS_CONFIG, CustomDefaultPartitioner.class.getName()); KafkaProducer<String, String> producer = new KafkaProducer<>(props); // 演示不同key的消息发送到不同分区 for (int i = 0; i < 10; i++) { ProducerRecord<String, String> record = new ProducerRecord<>("demo-topic", "user-" + i, "Message " + i); producer.send(record, (metadata, exception) -> { System.out.println("Key: user-" + i + " -> Partition: " + metadata.partition()); }); } producer.close(); } }

2.2 轮询分区策略

RoundRobinAssignor将所有主题的分区轮询分配给消费者:

public class RoundRobinDemo { public static void demonstrateRoundRobin() { Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); props.put(ConsumerConfig.GROUP_ID_CONFIG, "round-robin-group"); 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.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, "org.apache.kafka.clients.consumer.RoundRobinAssignor"); KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props); consumer.subscribe(Arrays.asList("topic-a", "topic-b", "topic-c")); } }

自定义分区策略

3.1 基于业务规则的分区策略

import org.apache.kafka.clients.producer.*; import org.apache.kafka.common.*; import java.util.*; public class BusinessPartitionStrategy { public static class BusinessAwarePartitioner implements Partitioner { private static final String VIP_PREFIX = "VIP:"; private static final String BATCH_PREFIX = "BATCH:"; @Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, org.apache.kafka.common.Cluster cluster) { List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); int numPartitions = partitions.size(); if (key == null) { throw new IllegalArgumentException( "Key is required for business-aware partitioning"); } String keyStr = key.toString(); // VIP用户消息发送到高优先级分区 if (keyStr.startsWith(VIP_PREFIX)) { return 0; // 第一个分区用于VIP用户 } // 批量任务消息发送到专用分区 if (keyStr.startsWith(BATCH_PREFIX)) { return 1; // 第二个分区用于批量任务 } // 普通消息使用hash分区 if (keyBytes != null) { return Math.abs( org.apache.kafka.common.utils.Utils.murmur2(keyBytes) ) % numPartitions; } // 其他情况随机选择 return new Random().nextInt(numPartitions); } @Override public void close() {} @Override public void configure(Map<String, ?> configs) {} } public static void demonstrate() { Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost: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.PARTITIONER_CLASS_CONFIG, BusinessAwarePartitioner.class.getName()); KafkaProducer<String, String> producer = new KafkaProducer<>(props); // VIP用户消息 producer.send(new ProducerRecord<>("business-topic", "VIP:user-123", "VIP用户登录")); // 批量任务消息 producer.send(new ProducerRecord<>("business-topic", "BATCH:task-001", "批量处理任务")); // 普通消息 producer.send(new ProducerRecord<>("business-topic", "normal-user-456", "普通用户操作")); producer.close(); } }

3.2 基于权重的分区策略

public class WeightedPartitionStrategy { public static class WeightedPartitioner implements Partitioner { private Map<String, Integer> partitionWeights; private int totalWeight; @Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, org.apache.kafka.common.Cluster cluster) { List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); int numPartitions = partitions.size(); if (key == null) { return new Random().nextInt(numPartitions); } // 解析权重配置 String keyStr = key.toString(); String[] parts = keyStr.split(":"); if (parts.length >= 2) { String userId = parts[0]; int weight = Integer.parseInt(parts[1]); // 根据权重选择分区 int selectedPartition = selectByWeight(weight, numPartitions); return selectedPartition; } // 默认使用hash分区 return Math.abs( org.apache.kafka.common.utils.Utils.murmur2(keyBytes) ) % numPartitions; } private int selectByWeight(int weight, int numPartitions) { // 权重越高,被选择的概率越高 int random = new Random().nextInt(totalWeight); if (random < weight) { return 0; // 高权重分区 } else if (random < weight * 2) { return 1; // 中权重分区 } return random % numPartitions; } @Override public void close() {} @Override public void configure(Map<String, ?> configs) { // 从配置中读取权重 partitionWeights = new HashMap<>(); partitionWeights.put("high", 50); partitionWeights.put("medium", 30); partitionWeights.put("low", 20); totalWeight = 100; } } }

3.3 地理位置分区策略

public class GeoPartitionStrategy { public static class GeoAwarePartitioner implements Partitioner { private static final Map<String, Integer> REGION_PARTITIONS = new HashMap<>(); static { REGION_PARTITIONS.put("cn-north", 0); REGION_PARTITIONS.put("cn-east", 1); REGION_PARTITIONS.put("cn-south", 2); REGION_PARTITIONS.put("us-west", 3); REGION_PARTITIONS.put("eu-central", 4); } @Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, org.apache.kafka.common.Cluster cluster) { List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); if (key == null) { return new Random().nextInt(partitions.size()); } String keyStr = key.toString(); // 从key中提取地区信息 String region = extractRegion(keyStr); Integer partition = REGION_PARTITIONS.get(region); if (partition != null && partition < partitions.size()) { return partition; } // 默认使用hash return Math.abs( org.apache.kafka.common.utils.Utils.murmur2(keyBytes) ) % partitions.size(); } private String extractRegion(String key) { // 从key中提取地区,例如 "user-123:cn-north" if (key.contains(":")) { return key.split(":")[1]; } return "unknown"; } @Override public void close() {} @Override public void configure(Map<String, ?> configs) {} } }

分区再分配

4.1 触发分区再分配

public class PartitionReassignment { public static void reassignPartitions() throws Exception { Properties adminProps = new Properties(); adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); try (AdminClient adminClient = AdminClient.create(adminProps)) { // 定义新的分区分配 Map<TopicPartition, List<Integer>> newAssignments = new HashMap<>(); TopicPartition partition0 = new TopicPartition("my-topic", 0); TopicPartition partition1 = new TopicPartition("my-topic", 1); TopicPartition partition2 = new TopicPartition("my-topic", 2); // 将分区0分配给broker 1,2,3 newAssignments.put(partition0, Arrays.asList(1, 2, 3)); // 将分区1分配给broker 0,2,3 newAssignments.put(partition1, Arrays.asList(0, 2, 3)); // 将分区2分配给broker 0,1,3 newAssignments.put(partition2, Arrays.asList(0, 1, 3)); AlterPartitionReassignmentsResult result = adminClient.alterPartitionReassignments(newAssignments); result.all().get(); System.out.println("分区重新分配完成"); } } public static void generateReassignmentPlan() throws Exception { Properties adminProps = new Properties(); adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); try (AdminClient adminClient = AdminClient.create(adminProps)) { Map<TopicPartition, List<Integer>> proposedAssignments = new HashMap<>(); proposedAssignments.put( new TopicPartition("my-topic", 0), Arrays.asList(0, 1)); proposedAssignments.put( new TopicPartition("my-topic", 1), Arrays.asList(1, 2)); proposedAssignments.put( new TopicPartition("my-topic", 2), Arrays.asList(0, 2)); AlterPartitionReassignmentsResult result = adminClient.alterPartitionReassignments(proposedAssignments); result.all().get(); } } }

4.2 分区数量调整

public class PartitionCountAdjustment { public static void increasePartitions() throws Exception { Properties adminProps = new Properties(); adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); try (AdminClient adminClient = AdminClient.create(adminProps)) { // 创建新的分区配置 NewPartitions newPartitions = NewPartitions.increaseTo(12); Map<String, NewPartitions> partitionsToAdd = new HashMap<>(); partitionsToAdd.put("my-topic", newPartitions); CreatePartitionsResult result = adminClient.createPartitions(partitionsToAdd); result.all().get(); System.out.println("分区数已从6增加到12"); } } public static void increasePartitionsWithAssignment() throws Exception { Properties adminProps = new Properties(); adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); try (AdminClient adminClient = AdminClient.create(adminProps)) { // 指定新分区的副本分配 Map<Integer, List<Integer>> newPartitionAssignments = new HashMap<>(); newPartitionAssignments.put(6, Arrays.asList(0, 1)); newPartitionAssignments.put(7, Arrays.asList(1, 2)); newPartitionAssignments.put(8, Arrays.asList(0, 2)); NewPartitions newPartitions = NewPartitions.increaseTo(9, newPartitionAssignments); Map<String, NewPartitions> partitionsToAdd = new HashMap<>(); partitionsToAdd.put("my-topic", newPartitions); CreatePartitionsResult result = adminClient.createPartitions(partitionsToAdd); result.all().get(); } } }

分区策略最佳实践

5.1 高吞吐场景

public class HighThroughputPartitioning { public static Properties createHighThroughputConfig() { 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"); // 使用默认的DefaultPartitioner props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "org.apache.kafka.clients.producer.DefaultPartitioner"); // 确保消息有key以实现均匀分布 props.put(ProducerConfig.ACKS_CONFIG, "1"); return props; } public static void highThroughputProducer() { KafkaProducer<String, String> producer = new KafkaProducer<>(createHighThroughputConfig()); // 使用时间戳作为key的一部分,实现更好的负载均衡 for (int i = 0; i < 100000; i++) { String key = String.format("%d-%d", System.currentTimeMillis(), i % 1000); ProducerRecord<String, String> record = new ProducerRecord<>("high-throughput-topic", key, "Message " + i); producer.send(record); } producer.flush(); producer.close(); } }

5.2 低延迟场景

public class LowLatencyPartitioning { public static Properties createLowLatencyConfig() { 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.LINGER_MS_CONFIG, 0); props.put(ProducerConfig.BATCH_SIZE_CONFIG, 0); // 快速失败 props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, 5000); return props; } }

5.3 顺序敏感场景

public class OrderingSensitivePartitioning { public static class OrderPreservingPartitioner implements Partitioner { @Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, org.apache.kafka.common.Cluster cluster) { List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); if (key == null) { throw new IllegalArgumentException( "Key must not be null for order-preserving partitioning"); } // 同一key的所有消息必须发送到同一分区 return Math.abs( org.apache.kafka.common.utils.Utils.murmur2(keyBytes) ) % partitions.size(); } @Override public void close() {} @Override public void configure(Map<String, ?> configs) {} } public static void demonstrateOrderedMessages() { Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost: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.PARTITIONER_CLASS_CONFIG, OrderPreservingPartitioner.class.getName()); KafkaProducer<String, String> producer = new KafkaProducer<>(props); // 同一订单的消息使用相同的key,确保顺序 String orderId = "order-12345"; producer.send(new ProducerRecord<>("orders", orderId, "创建订单")); producer.send(new ProducerRecord<>("orders", orderId, "支付订单")); producer.send(new ProducerRecord<>("orders", orderId, "发货订单")); producer.send(new ProducerRecord<>("orders", orderId, "完成订单")); producer.close(); } }

常见问题与解决方案

6.1 分区不均匀

public class PartitionBalanceIssues { public static void diagnoseUnbalancedPartitions() throws Exception { Properties adminProps = new Properties(); adminProps.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092"); try (AdminClient adminClient = AdminClient.create(adminProps)) { DescribeLogDirsResult logDirsResult = adminClient.describeLogDirs(Collections.singletonList(0)); Map<String, KafkaFuture<Map<Integer, LogDirDescription>>> logDirInfo = logDirsResult.allDescriptions().get(); Map<Integer, Long> partitionSizes = new HashMap<>(); for (Map.Entry<String, Map<Integer, LogDirDescription>> entry : logDirInfo.entrySet()) { for (Map.Entry<Integer, LogDirDescription> partitionEntry : entry.getValue().entrySet()) { partitionSizes.put( partitionEntry.getKey(), partitionEntry.getValue().size() ); } } // 计算分区大小差异 long maxSize = partitionSizes.values().stream() .max(Long::compare).orElse(0L); long minSize = partitionSizes.values().stream() .min(Long::compare).orElse(0L); double imbalanceRatio = (double) (maxSize - minSize) / maxSize; System.out.println("分区不均衡比例: " + String.format("%.2f%%", imbalanceRatio * 100)); } } }

6.2 Key设计问题

public class KeyDesignBestPractices { public static class CompositeKeyStrategy implements Partitioner { @Override public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, org.apache.kafka.common.Cluster cluster) { List<PartitionInfo> partitions = cluster.partitionsForTopic(topic); if (key == null) { return new Random().nextInt(partitions.size()); } String keyStr = key.toString(); // 复合key设计:entityType:entityId // 例如: user:12345, order:67890, product:11111 String[] keyParts = keyStr.split(":"); if (keyParts.length >= 2) { String entityType = keyParts[0]; String entityId = keyParts[1]; // 同一实体的所有消息发送到同一分区 String compositeKey = entityType + ":" + entityId; return Math.abs( org.apache.kafka.common.utils.Utils.murmur2( compositeKey.getBytes()) ) % partitions.size(); } return Math.abs( org.apache.kafka.common.utils.Utils.murmur2(keyBytes) ) % partitions.size(); } @Override public void close() {} @Override public void configure(Map<String, ?> configs) {} } }

总结

分区策略是Kafka系统设计中至关重要的一环,合理选择和配置分区策略能够显著提升系统的性能和可靠性。本文详细介绍了Kafka的各种分区策略,包括默认策略、轮询策略、自定义策略等,并提供了针对不同场景的最佳实践方案。

在实际应用中,需要根据业务特点选择合适的分区策略:

  • 高吞吐场景:使用hash分区,确保消息均匀分布
  • 低延迟场景:禁用批处理,直接发送
  • 顺序敏感场景:确保相同key的消息发送到同一分区
  • 业务隔离场景:使用自定义分区策略实现业务隔离

通过深入理解分区策略的原理和应用场景,可以更好地设计和优化Kafka消息系统。

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

相关文章:

  • Day4:一维差分
  • DWM1000官方例程深度解剖:从工程结构到API接口,为移植到任意STM32平台铺路
  • AI智能体记忆存储实战:SQLite+FTS5方案对比向量数据库
  • AI 赋能复合材料力学:机器学习、PINN 与多尺度仿真实战
  • 销售拜访录音怎么整理成客户跟进记录?4款热门转写工具实测盘点
  • 2026-05-27:非负元素轮替。用go语言,给定整数数组 nums 和整数 k。操作规则如下: 1.数组中所有非负数参与处理;它们需要像循环轮替一样整体向左移动 k 位。轮替的含义是,移出数组末端
  • 本地AI助手实战:基于Whisper与LLM的语音控制智能体开发
  • 乐迪信息:船舶违规停靠AI自动识别,港口管理更规范
  • 1.注册阿里云账号,申请通义千问 API 密钥
  • 从调用链到关系图:多智能体系统故障建模与图算法分析实践
  • ZYGO白光干涉仪物镜系统结构特点与大视场(Large Field-of-View)实现途径探讨
  • AI编码智能体如何重塑软件工程:从工具到协作者的实践变革
  • 走进 GEO 新时代:详解中立监测平台搜极星的核心能力
  • Covfefe
  • 正式入驻爱发电!软硬件全栈开发者的开源创作计划
  • 告别跳转失败:STM32 IAP升级中App过大导致的栈溢出问题分析与解决
  • 告别模拟IIC!用STM32CubeMX HAL库轻松驱动CH455G数码管(STM32F030F4P6实战)
  • AI代理系统调试优化:基于文件架构的极致可调试性实践
  • AI代理记忆管理:从TTL到智能过期的架构与工程实践
  • CrewAI多智能体系统:从原理到实战的AI团队协作框架
  • 不止于移动:用Unity的Joystick插件为你的PC/主机游戏打造自定义控制器UI
  • 构建本地语音控制AI助手:人机回环执行与隐私优先设计
  • 从合成数据到合成系统:AI数据生成的范式革命与实战指南
  • 米勒效应原理和解决方案
  • 学了PMP不知道做什么?日薪1W+的项目管理讲师可以考虑!
  • 02-认知篇-基础-AOT编译原理
  • 编程语言:Go语言并发编程实战
  • 告别变砖!NRF52832串口DFU升级实战:从Bootloader配置到Secure DFU签名全流程避坑
  • C#正课二十一(单例模式)
  • Claude Code 最常用斜杠命令