ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

项目中遇到过什么问题

项目中遇到过什么问题 1.spring cloud gateway 使用 redis-ratelimiter 来做限流要引入spring-boot-starter-data-redis-reactive。Spring Cloud Gateway 是 WebFlux 响应式框架限流必须用spring-boot-starter-data-redis-reactive不能用普通的 spring-boot-starter-data-redis否则会报错。Gateway 的RedisRateLimiter底层需要ReactiveRedisTemplateReactiveRedisConnectionFactory响应式 Bean只有spring-boot-starter-data-redis-reactive才会自动装配这两个 Bean 如果你只引入普通spring-boot-starter-data-redisLettuce/Jedis 阻塞式✅项目可以正常启动不会启动报错❌一访问带 RequestRateLimiter 的路由直接抛出 Bean 找不到异常1. 最常见报错标准日志No qualifying bean of type org.springframework.data.redis.core.ReactiveRedisTemplatejava.lang.String, java.lang.String available或者No qualifying bean of type org.springframework.data.redis.connection.ReactiveRedisConnectionFactory available含义RedisRateLimiter 尝试注入响应式 Redis 模板 Bean但是容器里没有因为普通 starter 只创建阻塞式 RedisTemplate不创建 ReactiveRedisTemplate。追问阻塞式 RedisTemplatespring-data-redis里的RedisTemplate是同步阻塞的 Redis 客户端 API底层Jedis本身就是阻塞 IOLettuce虽然底层是 Netty 非阻塞但RedisTemplate封装的方法是同步阻塞调用一句话调用redisTemplate.opsForValue().get(key)这行代码当前线程会卡住等待 Redis 返回结果期间什么都不干拿到结果才继续往下执行。1. 对比RedisTemplate阻塞 vs ReactiveRedisTemplate响应式特性RedisTemplate阻塞ReactiveRedisTemplate响应式依赖 starterspring-boot-starter-data-redisspring-boot-starter-data-redis-reactive返回值String、Object普通对象MonoT/FluxT线程行为调用时阻塞当前线程等待 IO不阻塞线程注册回调IO 就绪后再执行适用场景Spring MVCServlet 线程池WebFlux事件循环 EventLoop 线程Spring Cloud Gateway2. 阻塞的直观代码例子// 阻塞式 RedisTemplate String value redisTemplate.opsForValue().get(demo:key); // 执行get()的时候当前线程停在这里 // 网络慢、Redis卡顿线程就一直卡在这行等待返回Servlet 体系下这没问题Servlet 线程池有很多线程卡住一个只是消耗一个线程线程池可以扩容。但是 Spring Cloud Gateway 是 WebFlux只有少量 EventLoop 事件线程Gateway 默认就几个 EventLoop 线程比如等于 CPU 核心数EventLoop 线程绝对不能被阻塞。 如果在 EventLoop 里调用阻塞 RedisTemplate一旦 Redis 慢EventLoop 线程卡死没法处理其他请求请求排队堆积网关整体卡死雪崩这就是为什么 Gateway 限流组件强制要求ReactiveRedisTemplate。3. 补充Lettuce 的特殊点很多面试会问Lettuce 底层通信是 Netty 非阻塞但是RedisTemplate包装的是同步 API内部会调用.block()等待结果变成阻塞调用ReactiveRedisTemplate直接用 Lettuce 的响应式 API不 block误区纠正不是 Lettuce 响应式Jedis 阻塞。 Lettuce 两套 API 都有看你用的是RedisTemplate还是ReactiveRedisTemplate。4. 回到你刚才的 Gateway 限流场景Gateway 的RedisRateLimiter是 WebFlux 过滤器运行在 EventLoop 线程它内部代码直接注入ReactiveRedisTemplate。只引入普通 redis starter容器没有ReactiveRedisTemplateBean → 请求进来直接报 Bean 找不到如果你强行手动创建ReactiveRedisTemplate但用阻塞 API属于高危操作高并发会 eventLoop 阻塞。面试精简版阻塞式 RedisTemplate是 SpringDataRedis 提供的同步 Redis 操作模板。调用方法时当前线程会阻塞等待 Redis IO 返回。适用于 Servlet MVC。Gateway 基于 WebFlux 事件循环不能使用阻塞 API必须用 ReactiveRedisTemplate不能用普通 RedisTemplate。2.Gateway 网关服务 gateway‑server❗重要不要引入 spring‑boot‑starter‑webgateway 使用 webflux。Gateway 依赖 webflux不能共存 web。3.Spring Cloud 版本和 Boot 版本不匹配网关启动异常。4.Feign 调用服务名写错大小写敏感。5.Nacos服务注册成功但是 Feign 调用报错找不到实例检查 namespace、group 两边必须一致。exNamespace 写错服务注册在 dev 命名空间消费方在 public服务列表看不见。6.Nacos-配置中心❗SpringBoot3必须引入 spring‑cloud‑starter‑bootstrap否则 bootstrap.yml 不加载读取不到 nacos 配置。忘记加RefreshScope修改 nacos 配置程序不刷新。7.Seata Server 有两种存储模式file内存存储重启事务数据丢失只用于 demo 测试。db数据库存储生产必须事务信息持久化 mysql。8.Seata有发起方加 GlobalTransactional(rollbackFor Exception.class)远程被调用微服务只需要普通 Transactional。Feign 调用XID 全局事务 ID 会自动透传 HeaderSeata 拦截 Feign 请求自动传递 xid不需要手动处理。rollbackFor Exception.class一定要写默认 RuntimeException 才回滚普通 Exception 不会回滚。9.启动微服务后访问一次接口Sentinel 控制台就能看到该服务。Sentinel 是懒加载默认第一次访问资源才会注册到控制台配置eager:true启动就注册。10.blockHandler 和 fallback 分不清blockHandler 只管 sentinel 拦截fallback 管业务异常。懒加载不配置 eager:true服务启动控制台看不到服务必须访问一次接口。11.网关流控普通流控区别网关支持基于http 请求属性IP、header、query 参数流控普通 Sentinel 是方法维度没有原始 http 请求信息。12.为什么网关有 Sentinel还需要服务内 Sentinel场景 1RPC 调用不走网关服务之间 Feign/Dubbo 互相调用根本不经过 Gateway。例A 服务调用 B 服务的接口这个流量不会经过网关Sentinel Gateway 感知不到。 如果 B 服务只有网关 Sentinel没有服务内 Sentinel一旦 A 疯狂调用 B直接雪崩。场景 2网关是粗粒度需要接口级防护网关只能按路由限流/order/** 全部算一类。 但业务上下单接口压力大查询订单压力小。网关没法区分。 服务内 Sentinel 可以单独对 /order/create 做独立限流查询接口不受影响。场景 3多层故障隔离第一层网关挡住外部超大流量防止洪水直接打进集群第二层服务内即使网关放行流量单个接口异常、RPC 超时服务自己熔断故障锁在当前服务不扩散。网关挂了、绕过网关的请求服务自身还有一层保护。场景 4热点参数限流热点参数限流是普通 Sentinel 的能力Sentinel Gateway 不支持。 比如秒杀限制同一个用户 ID 高频下单这种需要解析请求参数做热点限流放在服务内部更合适。面试一句话回答Sentinel Gateway 是网关入口防护只处理经过网关的 HTTP 流量做路由级的限流熔断但是微服务之间的 RPC 调用不经过网关网关监控不到。 所以一般是双层防护网关挡外部大流量服务内部 Sentinel 负责接口、RPC 调用、热点参数的细粒度防护实现多层故障隔离防止雪崩。13.LoadBalancer没有内置权重策略。 如果需要权重负载均衡使用 Nacos Discovery 自带权重能力或者自己自定义 LoadBalancer 策略。Nacos 控制台 → 服务管理 → 实例列表可以修改实例权重。权重范围0‑100权重 0不会接收流量。场景灰度发布。新启动实例权重设置很小 (10)老实例权重 90少量流量打到新版本验证没问题再把权重拉满。开启权重负载均衡spring: cloud: nacos: discovery: # 开启nacos权重负载均衡底层替换LoadBalancer实例选择逻辑 weight: true开启 weight:true 之后会覆盖掉 LoadBalancer 自带的 round_robin/random 策略优先使用 Nacos 权重算法。14.LoadBalancer 重试机制调用服务实例失败自动换一个实例重试。⚠️注意重试一定要注意幂等性非幂等接口新增订单不要开启重试会产生重复数据。15.重试如何保证接口幂等方案 1全局唯一业务幂等 Token最常用适合新增类接口创建订单、生成单据流程上游调用方在业务发起之前生成全局唯一幂等 TokenUUID / 雪花 ID。把 Token 放在请求头Idempotency‑Token或者请求体中每次重试复用同一个 Token不要生成新 Token。✅重点LoadBalancer 重试不会重新生成请求复用原始请求对象Token 自然不变这点很关键。下游服务收到请求先拿 Token 去 Redis / 数据库幂等表查询是否已经处理过。如果不存在执行业务逻辑执行成功后把 Token 存入 Redis设置过期时间比如 24h。如果 Token 已经存在直接返回上一次成功的处理结果不再执行业务逻辑。Redis 伪代码// 使用setnx保证原子性 Boolean ok redisTemplate.opsForValue().setIfAbsent(token,processed,24, TimeUnit.HOURS); if(!ok){ // 已经处理过直接返回历史结果 return 历史成功响应; } // 执行业务逻辑 createOrder() // 业务执行完成key已经写入后续重试全部直接返回注意必须保证 检查 写入 Token 是原子操作不能先查询再 set并发下会穿透Redis 用SETNX数据库用唯一索引。Token 过期时间大于业务最大重试窗口。方案 2数据库唯一约束唯一索引适合数据库落库场景给业务唯一标识建立数据库唯一索引。 例如订单外部业务号、单据号。第一次请求插入成功。重试请求再次插入相同唯一值数据库抛出唯一索引冲突异常。下游捕获唯一冲突异常把它转化成正常成功响应返回给上游。优点简单不需要 Redis 缺点只针对新增插入不适合更新操作高并发下数据库压力大。方案 3基于业务状态机更新、扣减类业务库存、订单状态流转适合状态流转业务比如订单状态待支付→已支付→已发货库存扣减。更新语句带上前置状态条件状态机约束只允许状态流转一次。示例库存扣减-- 只有库存扣减数量才执行扣减重复重试条件不满足update返回0行 update stock set count count‑#{num} where id#{id} and count #{num};订单状态更新-- 只能从【待支付】修改为【已支付】重复重试条件不命中更新行数0 update order set statusPAID where id#{orderId} and statusWAIT_PAY;执行完 SQL 判断 update 返回影响行数返回 0执行业务成功返回 0代表状态已经变更过是重试请求直接返回成功不再执行业务。这个方案非常适配分布式事务、Seata、库存扣减场景。16.loadbalancer客户端负载均衡消费端本地选实例直连目标服务不是 Nginx 这种服务端 LB。内置策略轮询 round_robin、随机 random。权重负载均衡靠 Nacos‑Discovery 扩展LoadBalancer 本身没有权重策略。LoadBalanced给 RestTemplate 增加拦截器解析服务名替换真实 ip 端口。17.服务器部署1、开发 / 测试环境最常见单台机器混部全部组件一台 Linux 服务器把下面全部装在同一台机器不同端口区分Nacos端口 8848Seata TC端口 8091Sentinel Dashboard8080Prometheus9090Grafana3000✅优点省机器部署简单本地测试、联调非常合适。❌缺点资源争抢一个组件 CPU / 内存打满会影响其他组件不能用于正式生产。2、中小规模生产2 台机器推荐很多中小公司生产就是这么玩的机器 ANacos Seata TC机器 BPrometheus Grafana Sentinel Dashboard理由Nacos Seata TC 属于微服务核心控制面放一组机器监控类组件Prometheus/Grafana/Sentinel 控制台属于观测面放另一组Sentinel Dashboard 压力很小占用资源极低随便和监控放一起没问题。注意生产 Nacos、Seata TC 建议集群部署多实例不是单实例。比如 Nacos 至少 3 节点集群Seata TC 至少 2 节点集群。3、大规模生产4 组独立机器隔离高可用、大流量、企业规范严格场景分开独立机器 / 独立 K8s 节点组Nacos 集群注册 配置中心Seata TC 集群Sentinel Dashboard单实例即可压力很小监控集群Prometheus Grafana好处资源隔离互不影响故障域隔离。重点Sentinel Dashboard 本身压力很小绝大多数场景不需要单独一台服务器不需要单独集群。不需要必须部署在 4 台服务器。组件是独立进程但进程可以混部。测试环境全部组件部署在同一台服务器依靠不同端口隔离。中小生产一般分成两组一组放 Nacos 和 Seata TC微服务控制面另一组放 Prometheus、Grafana、Sentinel Dashboard监控观测组件。大规模生产为了故障隔离会分开部署到多台机器。注意Sentinel Dashboard 只是控制台资源消耗很小不需要独占一台服务器。❗️❗️❗️Sentinel、Seata 的客户端RM/TM、sentinel client是嵌在业务服务进程内18.2. Nacos 配置不生效RefreshScope 失效现象Nacos 修改配置程序没有拿到新值。 根因忘记加RefreshScopeBean 被Conditional提前初始化bootstrap 配置没有加载SpringBoot3 需要引入spring‑cloud‑starter‑bootstrap。3. 权重灰度不生效现象Nacos 控制台修改实例权重流量没有按权重分配。 根因没有开启 spring.cloud.nacos.discovery.weighttrueLoadBalancer 默认轮询不会读取权重。19.Q1介绍下你项目用的微服务技术栈整体架构是怎样的答 我们采用 Spring Cloud 微服务体系。注册 配置中心Nacos服务注册发现、配置动态管理支持 namespace/group 做环境隔离支持权重实现灰度发布。网关Spring Cloud Gateway基于 WebFlux 响应式作为统一入口负责路由转发、鉴权、限流、跨域。远程调用OpenFeign 做声明式 HTTP 调用负载均衡使用 Spring‑Cloud‑LoadBalancer替代旧的 Ribbon。容错防护Sentinel 做流量防护限流、熔断降级、热点参数限流规则持久化到 Nacos防止服务雪崩。分布式事务Seata AT 模式业务无侵入解决微服务之间数据一致性问题。可观测体系三支柱Metrics 指标Micrometer 采集指标Prometheus Pull 模式拉取 /actuator/prometheusGrafana 做大盘可视化Alertmanager 实现告警推送。Tracing 链路追踪SpringBoot3 废弃 Sleuth使用 Micrometer‑Tracing底层 Brave 实现Span 上报 Zipkin全链路 TraceId 透传定位慢调用。Logging 日志日志打印 traceId、spanIdLoki 做日志聚合三者依靠 traceId 关联排查问题。请求流程前端请求打到 Gateway 网关 → Gateway 路由转发LoadBalancer 从 Nacos 选取实例 → Feign 调用业务微服务服务之间 Seata XID、Tracing traceId 自动通过 Http Header 透传。20.看门狗锁 API 上相关参数影响看门狗是否开启参数说明看门狗状态lock.lock()无参加锁✅ 开启看门狗自动续期lock.tryLock(long waitTime, TimeUnit unit)只填等待时间不填 leaseTime✅ 开启看门狗lock.lock(long leaseTime, TimeUnit unit)指定 leaseTime❌ 看门狗禁用leaseTime 到期锁释放lock.tryLock(long waitTime, long leaseTime, TimeUnit unit)指定 leaseTime❌ 看门狗禁用核心对比表特性lock()tryLock(waitTime, unit)阻塞行为无限阻塞拿不到锁就一直等永不超时有限阻塞最多等待 waitTime超时直接返回 false返回值void成功拿到锁才返回拿不到一直卡着booleantrue 拿到false 超时没拿到中断响应等待锁时不响应中断线程 interrupt不会抛异常拿到锁之后才会标记中断状态等待锁期间可以响应中断被 interrupt 直接抛出InterruptedException使用场景必须拿到锁才能继续不允许失败不希望无限卡死有兜底降级逻辑防止死锁、服务雪崩平时生产的隔离级别是什么RC 还是 RR线上生产用的是RCRead Committed读已提交。项目中防重放是什么Ex5 分钟时间戳防重放是请求中携带发送时间requestTime服务端收到请求后比较它和服务器当前时间。如果相差太久就认为这可能是一份被截获后重新发送的旧请求直接拒绝。// 防重放校验long diffMinutes Math.abs(System.currentTimeMillis() - time) / (1000 * 60);if (diffMinutes REQUEST_EXPIRE_MINUTES) {log.info(防重放检查不通过);return Response.fail(HttpStatus.UNAUTHORIZED, 请求已过期);}log.info(完成防重放检查);什么是“重放攻击”假设业务方在 12:00 发送了一个合法请求{ businessLineCode: LINE_A, receiptTypeCode: INVOICE, fileId: F001, requestTime: 1789224000000 }攻击者即使无法解密或修改它也可能截获整个 RSA 密文然后在 13:00 原样再发送一次第一次合法业务方发送 第二次攻击者原样复制并重新发送
返回列表