在搭建2亿全球专利数据库检索系统的过程中,我们遇到了众多技术挑战。这些问题有些来自硬件兼容性问题,有些来自软件配置复杂性,还有些来自性能调优的极限探索。本文将记录我们遇到的主要问题及其解决方案,希望能为后续类似项目的开发者提供参考。
4.1 NVMe SSD RAID阵列卡兼容性问题
在搭建存储系统时遇到的第一个大问题就是RAID控制器卡的兼容性问题。我们最初选用的PERC H750控制器卡在配置NVMe SSD RAID时遇到了限制:H750虽然支持NVMe协议,但要求NVMe SSD必须支持Intel VROC(Virtual RAID on CPU)功能,而我们采购的三星PM9A3系列NVMe SSD不支持这一特性。
解决方案有两个:一是更换为支持VROC的NVMe SSD(如Intel Optane系列),二是使用HBA模式绕过RAID卡直接管理SSD。我们选择了第二种方案,将RAID控制器配置为HBA(Host Bus Adapter)模式,只作为物理通道而不做RAID逻辑。这样我们可以使用Linux的软件RAID(mdadm)来配置RAID阵列,或者直接使用LVM管理。不过这样做会失去RAID控制器的硬件缓存功能,我们在后续通过配置足够的物理内存作为缓存来弥补这一损失。
4.2 MySQL连接数耗尽问题
在压力测试阶段,我们发现MySQL的连接数很容易达到上限,导致应用程序报"Too many connections"错误。通过show processlist命令我们发现存在大量处于"Sleep"状态的连接,这些连接虽然没有实际执行查询,但仍然占用连接资源。
问题根源是应用程序没有正确关闭数据库连接,连接池配置也不合理。我们从两个方面进行了解决:首先,修复了应用程序的连接管理代码,确保每个操作完成后正确关闭连接;其次,调整了MySQL的配置参数:max_connections从151增加到2000,wait_timeout从默认的8小时减少到600秒,interactive_timeout同样设置为600秒。这样即使有连接泄漏,也会在10分钟后被自动释放。
另外,我们还在Nginx层配置了连接池复用机制,减少短连接对数据库的压力。通过php-fpm的进程管理配置,我们限制了最大子进程数量,避免瞬时请求高峰冲垮数据库。
4.3 Elasticsearch集群脑裂问题
在Elasticsearch集群运行过程中,发生了典型的"脑裂"(Split Brain)问题:集群分裂成两个相互独立的小集群,每个小集群都认为自己是主集群,可以接受写入请求。这是一种严重的故障,会导致数据不一致。
脑裂发生的直接原因是集群中master-eligible节点之间的通信中断。在我们的3节点集群中,如果只有一个节点存活,而其他两个节点之间通信正常,那么这两个节点会认为存活的节点已经离线,从而重新选举新的master,导致集群分裂。
我们的解决方案是调整了Elasticsearch的集群分裂策略配置:设置discovery.zen.minimum_master_nodes为2(计算公式为N/2+1,其中N为master-eligible节点数),这样只有至少2个节点认可的情况下才会选举master。同时,我们将gateway.expected_master_nodes和gateway.expected_nodes都设置为3,确保在足够节点恢复后才进行主节点选举。此外,我们还优化了网络配置,增加了心跳超时时间,避免因短暂网络抖动导致的误判。
4.4 Kafka消息积压与消费者组问题
在数据导入阶段,我们遇到了Kafka消息积压严重的问题。生产者发送消息的速度远快于消费者处理速度,导致消息在Kafka中堆积,检索系统看到的索引数据严重滞后。
排查发现问题是多方面的。首先是消费者处理逻辑效率低下:每次处理一条消息都要进行数据库查询和ES写入,频繁的IO操作成为瓶颈。其次是分区策略不合理:只有一个分区,无法利用多消费者并行处理。第三是ES写入的批量大小设置过小。
针对这些问题,我们采取了以下优化措施:重构消费者代码,增加本地缓存减少数据库查询;将Kafka topic的分区数增加到12,以支持12个消费者并行处理;调整ES bulk API的批量大小从500条提升到5000条;引入消息预聚合机制,对相同专利的多次更新进行合并处理。这些优化使消息处理吞吐量提升了约20倍,基本解决了积压问题。
4.5 中文全文检索分词质量问题
在Elasticsearch中配置中文专利检索时,分词质量是一个棘手的问题。默认的IK分词器对专利术语的处理不够理想,很多专业词汇被错误切分。例如"计算机可读存储介质"被切分为"计算机"、"可读"、"存储"、"介质",丢失了"计算机可读存储介质"这个完整的专业术语。
我们通过以下方式优化分词效果:首先,扩展了IK分词器的自定义词典,添加了约5万个专利领域专业词汇;其次,配置了IK分词器的拼音插件支持模糊检索;第三,针对不同字段设置了不同的分词策略:标题和权利要求使用ik_max_word(细粒度分词),摘要使用ik_smart(粗粒度分词);最后,我们还训练了领域专用的词向量模型,用于同义词扩展检索。
4.6 内存溢出与JVM调优
Elasticsearch基于Lucene构建,而Lucene会大量使用堆外内存。当JVM堆设置过大时,会导致系统其他进程可用内存不足;而堆设置过小,又会影响Elasticsearch的缓存效率。更严重的是,在高负载情况下,Elasticsearch有时会出现OOM(内存溢出)导致节点崩溃。
我们的调优经验是:将JVM堆大小设置为物理内存的50%,但不超过32GB(超过32GB JVM的指针压缩会失效,内存效率反而降低);同时通过bootstrap.memory_lock: false(避免锁死内存)和设置合适的indices.fielddata.cache.size来控制内存使用。我们还发现,频繁的深度分页查询是导致内存问题的常见原因,因此我们在应用层增加了最大返回条数限制(search.max_result_window: 10000)。
4.7 数据迁移中的字符集乱码问题
在从旧系统迁移数据到新平台时,我们遇到了字符集编码导致的乱码问题。旧系统使用Latin-1编码,而我们的新系统全面使用UTF-8编码。在迁移过程中,如果不做显式转换,就会出现乱码。
这个问题看似简单,但在实际处理中遇到了很多坑。首先,应用程序层面的字符集设置要确保一致:MySQL的character_set_server配置为utf8mb4,连接建立后执行SET NAMES utf8mb4;Elasticsearch的index设置中也要指定utf8编码。其次,在数据导入脚本中,要显式指定源文件编码和目标编码进行转换。
对于已经产生的乱码数据,我们开发了一个基于规则的字符集检测和修复工具,根据乱码的模式特征(主要是常见的UTF-8和GBK混淆)进行逆向转换。这个工具恢复了约80%的乱码数据,剩余无法恢复的数据只能标记为"待人工处理"或直接丢弃。
经验总结:遇到问题时,详细的日志和监控数据非常重要。在问题发生时,我们庆幸之前的监控部署派上了用场,通过历史数据回放和分析,我们准确定位了大部分问题的根因。建议任何部署都要从一开始就做好日志和监控,不要等问题发生了才想起来要加监控。

<<返回首页
