Symfony 4.4自定义Serializer序列化大量实体性能骤降问题
Symfony 3.4升级到4.4后序列化性能暴跌的解决办法
最近把项目从Symfony 3.4升级到4.4,遇到了一个头疼的问题:序列化大量实体的时候性能直接崩了。原来3.4里序列化1444个对象只需要7秒,现在4.4里直接超过30秒触发超时。
原因是Symfony 4.2开始弃用了setCallbacks()和setCircularReferenceHandler()方法,我按照官方指引改成了通过ObjectNormalizer传递上下文的方式,功能倒是正常了,但性能掉得离谱。
升级前的代码(3.4版本)
class MyEntitySerializer extends Serializer { public function __construct(ObjectNormalizer $normalizer, JsonEncoder $encoder) { $dateCallback = function ($dateTime) { return $dateTime instanceof \DateTime ? date_format($dateTime,'d/m/Y') : null; }; $normalizer->setCallbacks([ 'debut' => $dateCallback, 'fin' => $dateCallback, 'demandeDate' => $dateCallback, 'dateCreation' => $dateCallback, ]); $normalizer->setCircularReferenceHandler(function ($object) { if ($object instanceof Affaire) { return $object->getCode(); } elseif ($object instanceof ComplementAffaire) { return $object->getDescription(); } return $object->getId(); }); parent::__construct([$normalizer], [$encoder]); } }
升级后的代码(4.4版本,性能问题版本)
class MyEntitySerializer extends Serializer { public function __construct(ObjectNormalizer $normalizer, JsonEncoder $encoder) { $dateCallback = function ($dateTime) { return $dateTime instanceof \DateTime ? date_format($dateTime,'d/m/Y') : null; }; $defaultContext = [ AbstractNormalizer::CALLBACKS => [ 'debut' => $dateCallback, 'fin' => $dateCallback, 'demandeDate' => $dateCallback, 'dateCreation' => $dateCallback, ], AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) { if ($object instanceof Affaire) { return $object->getCode(); } elseif ($object instanceof ComplementAffaire) { return $object->getDescription(); } return $object->getId(); } ]; $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $normalizer = new ObjectNormalizer($classMetadataFactory,null,null,null,null,null,$defaultContext); parent::__construct([$normalizer], [$encoder]); } }
我试过注入ClassMetadataFactoryInterface,也清过var/cache/和vendor/重新安装依赖,但性能还是没上来。
问题根源和解决方案
其实核心问题出在类元数据没有缓存,以及重复创建ObjectNormalizer实例。
1. 缓存类元数据(最关键)
升级后的代码里每次创建ObjectNormalizer时,都新建了一个ClassMetadataFactory,而且没有加缓存层。这会导致每次序列化都要重新解析实体类的注解,处理大量对象时这个开销会被无限放大。
解决办法是用CacheClassMetadataFactory把元数据缓存起来,比如用文件系统缓存:
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader; use Doctrine\Common\Annotations\AnnotationReader; use Symfony\Component\Cache\Adapter\FilesystemAdapter; class MyEntitySerializer extends Serializer { public function __construct(JsonEncoder $encoder) { $dateCallback = function ($dateTime) { return $dateTime instanceof \DateTime ? $dateTime->format('d/m/Y') : null; // 换成DateTime自身的format更高效 }; // 创建缓存适配器,把元数据存在项目缓存目录 $cache = new FilesystemAdapter('serializer_metadata', 0, dirname(__DIR__).'/var/cache'); // 用缓存包裹类元数据工厂 $classMetadataFactory = new CacheClassMetadataFactory( new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())), $cache ); $defaultContext = [ AbstractNormalizer::CALLBACKS => [ 'debut' => $dateCallback, 'fin' => $dateCallback, 'demandeDate' => $dateCallback, 'dateCreation' => $dateCallback, ], AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) { if ($object instanceof Affaire) { return $object->getCode(); } elseif ($object instanceof ComplementAffaire) { return $object->getDescription(); } return $object->getId(); } ]; $normalizer = new ObjectNormalizer($classMetadataFactory,null,null,null,null,null,$defaultContext); parent::__construct([$normalizer], [$encoder]); } }
2. 复用容器里的ObjectNormalizer实例
升级后的代码里明明注入了ObjectNormalizer,但又自己new了一个,完全浪费了Symfony容器里已经配置好的、可能已经带缓存的实例。更优的做法是直接修改注入实例的默认上下文:
class MyEntitySerializer extends Serializer { public function __construct(ObjectNormalizer $normalizer, JsonEncoder $encoder) { $dateCallback = function ($dateTime) { return $dateTime instanceof \DateTime ? $dateTime->format('d/m/Y') : null; }; // 合并现有默认上下文与自定义配置 $defaultContext = array_merge( $normalizer->getDefaultContext(), [ AbstractNormalizer::CALLBACKS => [ 'debut' => $dateCallback, 'fin' => $dateCallback, 'demandeDate' => $dateCallback, 'dateCreation' => $dateCallback, ], AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object) { if ($object instanceof Affaire) { return $object->getCode(); } elseif ($object instanceof ComplementAffaire) { return $object->getDescription(); } return $object->getId(); } ] ); $normalizer->setDefaultContext($defaultContext); parent::__construct([$normalizer], [$encoder]); } }
3. 其他优化小技巧
- 生产环境一定要启用缓存:dev环境缓存可能不会持久化,切换到prod环境运行
php bin/console cache:clear --env=prod再测试 - 优化日期处理:用
$dateTime->format()代替date_format(),前者是对象方法性能更好;也可以考虑用Symfony自带的DateTimeNormalizer统一处理日期格式 - 减少序列化数据量:如果不需要序列化所有字段,用
AbstractNormalizer::GROUPS定义序列化组,或者用AbstractNormalizer::IGNORED_ATTRIBUTES忽略不需要的字段,能大幅减少处理时间 - 简化回调逻辑:确保回调函数里没有数据库查询、复杂计算等耗时操作,保持逻辑极简
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

