如何预估接口上线后的 QPS

问题引入

这个问题其实是一个非常实际的问题,因为我们在开发需求后,例如:新增了一个接口

有一个步骤是值得做的,那就是预估这个接口的QPS

因为我们是可以去调配对应服务器的数量和运行配置的

  • 例如我可以从2个节点新增为4个节点
  • 例如我可以将节点的内存从2G变成4G,2核CPU变成4核CPU

如果因为 没有正确的预估 以及 没有正确的调整,导致接口的QPS过高,扛不住系统崩溃,那就是严重的外网事故了

注意,这里说的预估QPS,是假设有一个真实的项目、真实的环境,预估这个接口上线后会有多少 QPS而不是在预估我们的系统接口本身能抗住多少QPS而不崩溃

如何预估

预估一个接口的 QPS,本质上是在考虑这个接口被调用的频繁程度

1.分析业务

  • 业务类型: 不同的业务类型的QPS是不太一样的,再细化一些,接口的做的事情是不同的,QPS是不同的,例如这个接口是 获取商城首页数据(QPS估计会高)、删除好友(QPS估计会低)
  • 活跃用户: 有多少活跃用户,活跃用户多(QPS估计会高),活跃用户少(QPS估计会低)

2.计算平均的QPS:根据活跃用户数和业务类型估算

  • 假设有100w个活跃用户(数值由活跃用户决定)
  • 假设经过历史数据的统计,每个用户平均每天产生10次请求(数值由业务类型决定)
  • 则可估算出平均每秒请求量(QPS)为 (10 * 100w)/ 86400= 116

3.计算峰值的QPS:假设业务类型是有峰值情况(特殊事件带来的流量波动)的,即我们的接口可能会达到更高的QPS

  • 峰值情况通常是的平均值的数倍,假设峰值为平均值的3倍,则峰值QPS约为348

通过预估接口上限后可能的峰值QPS后,就可以相对应的调整服务器节点的数量、服务器的运行配置需要能抗住峰值QPS,记得给自己留点缓冲,即把值设置的略大一些

如何预估/回答接口本身能抗住多少 QPS

与哪些因素有关

后端服务器集群节点数量,数量越多,QPS越高

后端服务器节点的运行配置:运行内存、Cpu核数等等,硬件资源决定单节点处理能力

接口本身做的事情

  1. 做的事情多耗时长(预估QPS会相对应低)
  2. 做的事情少耗时短(预估QPS会相对应高)

系统架构:

  1. 完善的流量负载均衡架构,实现流量的有效分发和负载均衡,避免成为QPS瓶颈
  2. 缓存技术,减轻数据库的压力,避免数据库成为QPS瓶颈
  3. 集群模式的数据库,避免数据库成为QPS瓶颈
  4. 静态资源通过CDN加速,避免成为QPS瓶颈

如何预估?

首先需要知道一个请求处理完毕的时间(这个请求里可能做很多事情,但是这个我们暂时不管),一个请求处理完毕的时间我们是可以知道的。(我们去调用这个接口多次得到平均值即可)

理论计算公式

QPS(单节点) = 线程数(如 Tomcat 线程池)/ 平均请求处理时间(秒)

  • 例如:若线程池大小为 200,单个请求处理时间为 50ms(0.05秒)
  • SprigBoot 默认使用Tomcat,而Tomcat线程池的最大线程数就是200,所以在默认配置下,SprigBoot 应用可以并发处理 200 请求则 QPS(单节点) = 200 / 0.05= 4000

QPS(集群)= QPS(单节点)* 节点数

  • 例如集群有4个节点,则 QPS(集群)= 4000 * 4 = 16000

单个请求处理的时间是受到 “系统架构”、“接口本身做的事情”“后端服务器节点运行配置”“后端服务器节点数量”等等因素共同影响的 但这实际上是一个近似已知值(我们去调用这个接口多次得到平均值即可)

QPS瓶颈

我们想想QPS的瓶颈在哪?其实很多方面,就像前面说的QPS与哪些因素有关但一般来说 QPS 最可能的瓶颈在于数据库 (例如MySQL)

应该说多少QPS

面试官如果问接口的QPS多少,如何回答比较合理?

首先我们一定可以知道这个接口处理的平均耗时是多久?假设是200ms

一些项目背景暂时假设为如下情况,因个人而定

  1. 假设有 n 台后端服务器
  2. 假设后端服务器的配置是 2核4G内存
  3. 假设接口做了一些事情
  4. 假设对于一些系统架构上,做了一些优化、或者没有做优化

那么

  1. QPS(单节点) = 线程数(如 Tomcat 线程池)/ 平均请求处理时间(秒)若线程池大小为 200,单个请求处理时间为 100ms(0.01秒)则 QPS(单节点) = 200 / 0.01= 2000
  2. QPS(集群)= QPS(单节点)* 节点数 = 2000 * n

没有压测的情况下,我们就可以说这个理论计算得出的值。但是理论仅仅是理论如果有压测,那其实是另外更好的一种情况,因为压测得到的数据往往是更加准确的。例如压测过程:使用 JMeter 进行了压力测试,逐步增加并发线程数,直到接口的响应时间超过预设阈值(如 200ms)或错误率超过 1%。此时就可以知道接口可以抗住的QPS

示例

面试官:“你在项目中负责的订单查询接口,QPS 是多少?”

你的回答: “订单查询接口的 QPS 在‘双11’大促期间峰值达到 5,000,日常平均 QPS 约为 1,200。我们通过以下步骤得出这一数据:

  1. 压测阶段:JMeter 进行了压力测试,逐步增加并发线程数,发现当 QPS 达到 2,000 时,响应时间达到300ms。
  2. 瓶颈分析:通过 Arthas 追踪发现,80% 的请求耗时在数据库查询上。
  3. 优化措施(仅为举例,具体情况依据个人而定):引入 Redis 缓存热点商品数据,缓存命中率提升至 90%;对订单表按用户 ID 进行分库分表,单表数据量从 1 亿减少到 1 千万;将日志记录改为异步写入 Kafka,减少主线程耗时。
  4. 优化结果:最终压测 QPS 提升到 5,000,且响应时间稳定在 80ms 以内。 此外,线上监控显示,实际高峰期的 QPS 与压测结果基本一致,系统未出现超时或宕机。”

如何设计一个秒杀系统

秒杀系统场景特点

  • 秒杀一般是访问请求数量远远大于库存数量,只有少部分用户能够秒杀成功
  • 秒杀时大量用户会在同一时间同时进行抢购,网站瞬时访问流量激增
  • 秒杀业务流程比较简单,一般就是下订单减库存

秒杀架构设计理念

  • 限流: 鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进⼊服务后端秒杀程序。
  • 削峰:对于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值。高峰值流量是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰
    的常用的方法有前端添加一定难度的验证码,后端利用缓存和消息中间件等技术。
  • 异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。
  • 内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。
  • 可拓展:当然如果我们想支持更多用户,更大的并发,最好就将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了。像淘宝、京东等双十一活动时会增加大量机器应对交易高峰。

设计思路

  • 将请求拦截在系统上游,降低下游压力:秒杀系统特点是并发量极大,但实际秒杀成功的请求数量却很少,所以如果不在前端拦截很可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时
  • 充分利用缓存:利⽤缓存预减库存,拦截掉大部分请求
  • 消息队列:这是⼀个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理

前端方案

  • 页面静态化:将活动页面上的所有可以静态的元素全部静态化,并尽量减少动态元素。通过CDN来抗峰值。
  • 禁止重复提交:用户提交之后按钮置灰,禁止重复提交
  • 用户限流:在某一时间段内只允许用户提交一次请求,比如可以采取IP限流

后端方案
服务端控制器层(网关层)
限制uid(UserID)访问频率:我们上面拦截了浏览器访问的请求,但针对某些恶意攻击或其它插件,在服务端控制层需要针对同⼀个访问uid,限制访问频率。

服务层
  上面只拦截了一部分访问请求,当秒杀的用户量很大时,即使每个用户只有一个请求,到服务层的请求数量还是很大。比如我们有100W用户同时抢100台手机,服务层并发请求压力至少为100W。

  • 把需要秒杀的商品的主要信息以及库存初始化到redis缓存中
  • 做请求合法性的校验(比如是否登录),如果请求非法,直接给前端返回错误码,进行相应的提示
  • 进行内存标识的判断 (true 已经秒杀结束,false 未秒杀结束,下面第4步会写入),如果内存标识为true,直接返回秒杀结束
  • redis中使用 decr 进行预减库存操作,判断:如果decr后库存量小于0,则把内存标记置为true (已经秒杀结束,第3步会用到),且返回秒杀结束
  • 用redis的布隆过滤器来判断是否已经秒杀到了(下面第7步会写入),防止重复秒杀,如果重复秒杀,直接返回重复秒杀的错误码。具体做法是:先用redis的布隆过滤器来判断是否秒杀过,如果布隆过滤器判断已经秒杀过了, 则再次查库确认是否秒杀过了,之所以再次查库确认是因为布隆过滤器对可能存在的数据是有误判率的;但是它对不存在的数据的判断是百分百准确的,所以如果redis的布隆过滤器判断没秒杀过,就直接放过去进行秒杀
  • 发送成功秒杀到的MQ消息给相应的业务端进行处理,并给用户端返回排队中,如果客户端收到排队中的消息,则自动进行轮询查询,直到返回秒杀成功或者秒杀失败为止
  • 相应的业务端进行处理:真正处理秒杀的业务端,再次进行校验(比如秒杀是否结束,库存是否充足等)、将用户和商品id作为key存入redis的布隆过滤器来标识该用户秒杀该商品成功(第5步会用到)、减库存(这里的是真正的减库存,操作数据库的库存)、生成秒杀订单、返回秒杀成功

注意:就算请求走到了真正处理业务的这一端,也有可能秒杀失败,比如秒杀结束,库存不足,真正减库存失败,秒杀单生成失败等等,一旦失败,则返回秒杀结束

优化:将秒杀接口隐藏:用户点击秒杀按钮的时候,根据用户id生成唯一的加密串存入缓存并返回给客户端,然后客户端再次请求的时候带着加密串过来,后端进行校验是否合法,若不合法,直接返回请求非法

限制某个接口的访问频率:可以用拦截器配合自定义注解来实现,这么做可以和具体的业务分离减少⼊侵,使用起来也非常方便

数据库层

  • 数据库层是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截掉,数据库层只承担“能力范围内”的访问请求。所以,上面通过在服务层引入队列和缓存,让最底层的数据库高枕无忧
  • 为防止秒杀出现负数订单数大于真正的库存数,所以在真正减库存,update 库存的时候应该加上 where 库存 > 0,而且需要给秒杀订单表加上用户id和商品id联合的唯一索引

10亿订单表如何做分库分表?

场景痛点:某电商平台的MySQL订单表达到7亿行时,出现致命问题:

-- 简单查询竟需12秒!
SELECT * FROM orders WHERE user_id=10086 LIMIT 10;
​
-- 统计全表耗时278秒
SELECT COUNT(*) FROM orders;

核心矛盾

  1. B+树索引深度达到5层,磁盘IO暴增。

  2. 单表超200GB导致备份时间窗突破6小时。

  3. 写并发量达8000QPS,主从延迟高达15分钟。

分库分表核心策略

垂直拆分:先给数据做减法      
•   按列拆分:将一张宽表的字段按业务属性分散到不同的表中 

优化效果: - 核心表体积减少60% - 高频查询字段集中提升缓存命中率

水平拆分:终极解决方案

按行拆分:将表数据按某种规则(如ID范围、哈希值)分散到多个结构相同的表中。

分片键选择三原则**:
  1.离散性:避免数据热点(如user_id优于status)
  2.业务相关性:80%查询需携带该字段
  3.稳定性:值不随业务变更(避免使用手机号)

   基因嵌入:将用户的后几位id编号(gene)嵌入到订单ID的生成 ==>比较有名的就是淘宝19位订单编号,后六位是用户ID的后六位 然后再根据订单编号的用户ID进行分库分表==>分8个库 每个库16张表  注意这种建立起来的库是用户库

关键突破**:通过基因嵌入,使相同用户的订单始终落在同一分片,同时支持通过订单ID直接定位分片  
**跨分片查询**  

三个高频场景对应三种查询方式 :(缺少订商家发货)

  •    核心矛盾:商家与订单是多对多关系,一个商家的订单会分散在所有分片中。
  •    方案:Elasticsearch二级索引
  •    思想:创建一个辅助索引,也就是附录==>通俗来讲,就好比字典的拼音查询(主查询)以及偏旁笔画查询(二级查询)  

实施步骤

将订单数据同步到ES:

{"mappings": {"properties": {"merchant_id": { "type": "keyword" },"order_id": { "type": "keyword" },"user_gene": { "type": "integer" }}}
}

查询实现:

SearchResponse response = client.prepareSearch("orders").setQuery(QueryBuilders.termQuery("merchant_id", merchantId)).addSort("create_time", SortOrder.DESC).setSize(100).get();

数据迁移

双写迁移方案

img

灰度切换步骤

  1. 开启双写(新库写失败需回滚旧库)
  2. 全量迁移历史数据(采用分页批处理)
  3. 增量数据实时校验(校验不一致自动修复)
  4. 按用户ID灰度流量切换(从1%到100%)

性能指标

场景拆分前拆分后
用户订单查询3200ms68ms
商家订单导出超时失败8s完成
全表统计不可用1.2s(近似)

总结

  1. 分片键选择大于努力:基因分片是订单系统的最佳拍档。
  2. 扩容预留空间:建议初始设计支持2年数据增长。
  3. 避免过度设计:小表关联查询远比分布式Join高。效
  4. 监控驱动优化:重点关注分片倾斜率>15%的库。

如何实现A网站登录后,B网站自动登录

有这么个场景,公司下有多个不同域名的站点,我们期望用户在任意一个站点下登录后,在打开另外几个站点时,也是已经登录的状态,这么一过程就是单点登录。因为多个站点都是用的同一套用户体系,所以单点登录可以免去用户重复登录,让用户在站点切换的时候更加流畅,甚至是无感知。

题述问题,本质上是 单点登录 的问题

单点登录(Single Sign-On,简称 SSO) :是一种用户认证机制,允许用户在一次登录后,访问多个系统或应用程序而无需再次登录

基于Cookie

适用情况: A网站和B网站必须是同一域名下的两个网站,如 a.example.com 和 b.example.com

可以通过共享 Cookie 的方式来实现 SSO

  • 优点是实现起来简单,浏览器天然就支持
  • 缺点是限制必须不能跨域,如果是两个不相关的网站,就不能通过Cookie实现SSO

基于Token

      使用标准化的令牌(如 JWT)来携带用户认证信息。用户登录后,服务端颁发一个令牌(Token)给到前端,前端在访问其他的网站的时候,把这个 Token 放到 HTTP Header 中带过去,然后就可以基于这个Tokne来验证用户身份了
     这个方案可以解决跨域的问题,只要支持HTTP的协议即可。但是缺点是Token可能会被泄漏。存在一定的安全隐患

基于共享Session

用户登录后,系统将用户信息存储到公共的第三方存储,如Redis或数据库

例如使用Redis,那么所有业务系统都必须知道该公共Redis的实例信息,才能够访问获取用户信息

通过CAS

CAS 是 Central Auth Service,也就是认证中心服务

注意和并发编程中的 Compare And Swap 区分

多个需要实现单点登录的系统,都接入这个统一认证中心,所有的登录、认证都通过这个认证中心来实现

  1. 用户访问子系统时重定向到 CAS 服务器登录
  2. CAS 服务器验证用户身份并返回一个票据(Ticket)
  3. 子系统使用票据向 CAS 验证,获取用户信息

这个方案一般适合于企业内部做集成,可以让业务系统不再关心登录、授权,而是只关注业务逻辑,只需要做一次接入就行了。比如说公司的很多内部平台之间,就可能是用了同一个登录服务。

优点就是接入成本低,缺点就是这个认证中心本身的开发成本还是挺高的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93664.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93664.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/93664.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Web安全】深入浅出理解“SQL注入-伪静态注入”及空格限制绕过技巧

文章目录什么是伪静态注入?伪静态注入中如何绕过空格限制?1. 用注释符替代空格2. 用不可见字符(URL 编码)替代3. 用括号分隔语句4. 用特殊符号替代核心逻辑往期文章【Web安全】一次性搞懂 ReDOS 漏洞原理/检测/防御 【Web安全】一…

【读论文】Step-Audio 2 深度解读:迈向工业级语音交互的「全能型选手」

引言:step-Audio升级 语音交互技术,作为人机交互最自然、最直接的方式之一,正以前所未有的速度发展。从简单的语音指令到流畅的语音对话,我们对 AI 的期望越来越高。然而,要让 AI 真正成为我们的“知心伙伴”,仅仅能“听懂”和“说出”还远远不够。 一个理想的语音 AI,…

java web 重定向

目录结构 demo\day20\src\com\demo\service\Dome1.javademo\day20\src\com\demo\service\Dome2.javademo\day20\src\com\demo\service\Dome3.javademo\day20\src\com\demo\service\Dome4.javademo\day20\web\WEB-INF\lib\javax.servlet.jardemo\day20\web\index.jspdemo\day20\…

MySQL(配置)——MariaDB使用

一、简介 MariaDB 和 MySQL 作为两个流行的关系型数据库管理系统,它们的区别可以从多个角度来探讨。尽管 MariaDB 最初是 MySQL 的一个分支,但随着时间的推移,它们逐渐在功能、性能和开发方向上有所不同。MariaDB 是 MySQL 的一个分支&#x…

Web3:赛道划分与发展趋势解析

区块链技术现在已经从单一的加密货币支付系统发展为涵盖金融、艺术、组织治理和社区文化的多元生态系统。这次我们将深入解析 DeFi(去中心化金融)、NFT(非同质化代币)、DAO(去中心化自治组织)与 MEME&#…

LeetCode 283 - 移动零

思路 使用双指针法,一次遍历完成原地修改。 慢指针 slow:指向下一个非零元素应该被放置的位置。快指针 fast:遍历整个数组,寻找非零元素。 当 fast 遇到非零数时,将其值赋给 slow 指向的位置,然后 slow 前进…

8. 网络层

在复杂的网络环境中确定一个合适的路径.1. IP协议1. 基本概念IP协议:提供一种能力(有非常大的概率,做到某事),把数据报从主机A,跨网络,送到主机B --> 必须要有方式,标识通信两端唯一性!&…

【通识】线性代数(Linear Algebra)

线性代数被广泛应用于抽象代数和泛函分析中;通过解析几何,线性代数能被具体表示,线性代数被泛化为算子理论。而非线性模型被近似为线性模型,应用场景多为自然科学和社会科学。 费马和笛卡尔的工作,线性代数出现于十七世…

Qt 嵌入式界面优化技术

在嵌入式系统中,界面性能直接影响用户体验和系统稳定性。由于嵌入式设备通常资源受限(如低性能 CPU、有限内存、小尺寸屏幕),需针对性优化 Qt 界面以实现流畅显示和高效交互。本文从渲染引擎、资源管理、布局优化到硬件加速&#…

去除视频字幕 4 : 下一步,打算研究 Video Inpainting (视频修复):

就是说,到现在,才算是真正开始,才发现真正的问题。 尝试去除视频上的字幕,使用 IOPaint, 效果很初级。。。问题描述 请帮我分析此时的效果。 此时的右侧字幕区域,闪烁不停!我原本以为效果会很好。实际非常…

代码随想录算法训练营第五十五天|图论part5

并查集理论基础 初始化: void init() {for (int i 0; i < n; i) {father[i] i;} } 寻根&#xff1a; // 并查集里寻根的过程 int find(int u) {return u father[u] ? u : father[u] find(father[u]); // 路径压缩 } 判断u跟v是否同根 // 判断 u 和 v是否找到同一个根 b…

安卓模拟器 adb Frida hook 抓包

基本步骤 adb connect 127.0.0.1:62001adb forward tcp:27042 tcp:27042 adb forward tcp:27043 tcp:27043adb shell./data/local/tmp/frida-server再开启cd D:\linuxdir\python\fridapython main.py下载夜神模拟 https://www.yeshen.com/ 安装adb 点击下载adb&#xff08…

编程与数学 03-002 计算机网络 14_网络性能分析

编程与数学 03-002 计算机网络 14_网络性能分析一、网络性能指标&#xff08;一&#xff09;带宽、时延、吞吐量等指标的定义与测量方法&#xff08;二&#xff09;性能指标对网络应用的影响二、网络性能的测试方法&#xff08;一&#xff09;使用网络测试工具&#xff08;如Wi…

AT9880B参数特征

AT9880B 是一款高性能北斗单模卫星导航接收机 SOC 单芯片&#xff0c;芯片集成射频前端和数字基带、北斗多频卫星信号处理引擎、电源管理功能。 芯片支持接收中国北斗二号和北斗三号&#xff0c;支持接收 B1I、B1C、B2I、B3I、B2a 和 B2b 等频点信号。主要特征 支持北斗二号/三…

eBPF 赋能云原生: WizTelemetry 无侵入网络可观测实践

引言 随着 KubeSphere 企业版 4.2.0 的正式发布&#xff0c;WizTelemetry 可观测平台 2.0 也同步亮相。作为本次升级中的重磅模块之一&#xff0c;它迅速引发了开发与运维团队的广泛关注。 本系列文章将系统解读 WizTelemetry 的核心能力与落地实践。继前两篇介绍了平台架构与指…

【JAVA安全-Fastjson系列】Fastjson 1.2.24 反序列化漏洞分析及测试环境构建【复习回顾】

Fastjson 1.2.24 反序列化漏洞分析及测试环境构建 漏洞背景 Fastjson 是阿里巴巴开源的一个高性能 Java JSON 库&#xff0c;广泛用于 Java 对象的序列化和反序列化。在 1.2.24 及之前的版本中&#xff0c;存在一个严重的安全漏洞&#xff0c;攻击者可以通过构造恶意的 JSON 字…

关于神经网络CNN的搭建过程以及图像卷积的实现过程学习

通过如下博客内容学习了CNN搭建的步骤&#xff0c;按照博主的思路完成了cnn网络的构建并完成50个epoch的训练并画出损失函数的曲线图时有满满的成就感 PyTorch深度学习实战&#xff08;3&#xff09;——使用PyTorch构建神经网络_pytorch 神经网络-CSDN博客 通过如下博客内容…

nodejs 实现Excel数据导入数据库,以及数据库数据导出excel接口(核心使用了multer和node-xlsx库)

项目地址&#xff1a;https://gitee.com/LiangDouJun/nodejsExcel 一、实现效果 1、数据库数据导出 2、excel导入 二、代码实现 // 根据环境加载对应的配置文件 const env process.env.NODE_ENV || development; require(dotenv).config({ path: .env.${env} });const expr…

VUE2 学习笔记8 v-text/html/cloak/once/pre/自定义

除了之前已经介绍过的v-on v-bind v-for v-if v-show&#xff0c;vue还有很多其他的指令。v-textv-text是Vue内置指令。内置指令&#xff0c;是Vue内部定义好的&#xff0c;开发的时候直接拿来用就行了。v-text用于向其所在的标签添加文本。<body><div id"root&q…

vue 使用postcss-pxtorem 实现适老化

1. 安装依赖 npm install postcss-pxtorem -D2. 配置 Vite (vite.config.js) import { defineConfig } from vite import vue from vitejs/plugin-vue import postcsspxtorem from postcss-pxtoremexport default defineConfig({plugins: [vue()],css: {postcss: {plugins: [po…