常见开发问题记录
# 1 Es数据与Mysql数据不一致
原因: 批量操作高峰期,频繁更新数据库导致同步堆积,数据未能及时同步到Es。
解决方案: 批量任务控制并发数,控制更新速率。
# 2 内部feign自调用
描述: 订单运单解藕,外部接口调用临时改内部feign调用自己,服务重启后出现大量错误。
原因: 节点启动有先后顺序,先启动的节点feign调用自己服务时调用的是旧节点,导致调用不到新接口。
解决方案: 避免使用feign调用自己服务这种临时方案。
# 3 服务切oracle后出现内存溢出情况
原因: 按多个运单号查询列表,先查一个月内将命中数据放到内存,查不到的去掉时间范围再查,最后合并内存中的数据及后面查的数据高峰期接口调用量大导致内存迅速被占满。
解决方案:
按运单号查询数据库,由于运单号是全局唯一索引,查询速度足够快,不需要先查一个月再扩大到全部。
直接按运单号查询即可,这样就不需要将运单数据放在缓存中。
# 4 服务偶发性所有接口变慢
原因:
数据库连接数配置未生效,默认只有8,导致业务高峰期获取不到连接数影响接口响应。
代码里面获取的配置路径是spring.datasource.maxActive,配置文件里面配的是sping.datasource.druid.maxActive。
解决方案: 数据库配置调整正确,例:
spring:
datasource:
druid:
initialSize: 20
minIdle: 20
maxActive: 100
maxWait: 60000
2
3
4
5
6
7
# 5 MQ消息发送阻塞影响相关接口调用变慢
原因: MQ消息消费缓慢,致使队列内存达到30多G触发流控。
解决方案:
1.删除多余、无效的消息发送
2.优化下游账单消费速度
3.优化消息体大小,去除不必要字段(后续)
4.调整为惰性队列(后续)
# 6 服务消费MQ消息自动ACK改手动ACK出现消息堆积
原因: MQ消费端指定了headers,导致不是该headers的消息一直未ack。
解决方案: 非指定headers的消息手动确认.
Object o = payload.getHeaders().get(WAYBILL_MQ_HEADER);
if (!String.valueOf(o).equals(WAYBILL_MQ_HEADER_VALUE)) {
channel.basicAck(deliveryTag, false); // 手动确认
return;
}
2
3
4
5
# 7 young gc 业务量大时耗时持续飙高
原因: 猜测跨年代对象引用
解决方案:
JVM调优 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=2M
调优原因:经分析每个线程从MQ拉取的对象信息为0.23M,MQ最大线程数为10,默认为0,
当业务量飙升时,线程扩容到10个,那么将这部分的大对象直接放入老年代,减轻年轻代压力。
# 8 查询接口超时
原因: 使用了多表关联查询,其中还有一张大表,导致了查询速度缓慢。单条达到了40-80ms,当批量任务200条数据的请求过来,每次都需要处理将近8-16S。
解决方案: 拆sql语句。将多表的关联查询,拆成单表查询,并保证每个查询语句都落到索引,提高每张表的查询效率。优化后200条的批量在1s内可执行完成。
# 9 动态列导出列顺序错乱
原因:
在动态列导出时,每次都会去修改VO类的导出字段顺序,在并发导出时,新请求动态列顺序会覆盖上一次动态列顺序,导致生成的文档顺序错乱。
覆盖原因是在类加载时会将类元信息存入元空间,每次修改都会更新元空间中的类信息,而不管有几个线程,在同一个JVM中,指向的类元信息都是同一个,所以在修改类元信息时,会互相影响。
解决方案:
synchronized (voClass) {
List<String> columnSet = new ArrayList<>();
columnSet = customColumns.stream().map(item -> item.getName()).collect(Collectors.toList());
//构建动态导出header
Class<V> headerClass = this.getCustomerHeaderClass(t, voClass, columnSet);
excelWriter.write(resultList, writeSheet);
}
2
3
4
5
6
7
在导出动态列执行write之前,加锁重新设置导出的列顺序,保证每次的列顺序都是当前线程所需要的,防止互相覆盖。
# 10 Feign调用日期格式问题
原因: 在DateFormatConfig的配置中,JsonParser.getText()拿到值并不是时间,而是一个’[’,时间转换的时候就出现问题。
解决方案:
1)在每个小前台调用的DTO上加上JsonFormat注解。 这样的工作量比较大,很多DTO以前都没加上注解。
2)把DateFormatConfig的注解换成下面的这种方式,就不需要在每个DTO上面加上注解。
package com.common.base.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@Configuration
public class DateFormatConfig implements WebMvcConfigurer {
/**
* 更改jackson默认配置
*/
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 对于空的对象转json的时候不抛出错误
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// 禁用遇到未知属性抛出异常
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 序列化BigDecimal时不使用科学计数法输出
objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
//为空不显示
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 日期和时间格式化
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 11 Feign超时问题
打开hystrix/熔断的情况下,一般来说有3个关于超时的配置参数,参数如下
参数说明
ConnectTimeout:连接超时时间,即服务与服务建立连接的超时时间 (可以简单理解为new Socket(SocketAddress,timeout)就会发起的连接超时时间,或者Socket socket = new Socket(); socket.connect(SocketAdress,timeout)建立连接过期时间,如果超时,则会抛出SocketTimeoutException:connect time out异常)
ReadTimeout:在建立连接后,读取数据的超时时间,即当两个服务之间已经建立好了连接,但此时因为服务端操作时间过长(可以理解为socket.getInputStream()这一方法),而在ReadTimeout指定时间内无法获取到数据,则此时会抛出SocketTimeoutException:Read timed out异常。
timeoutInMilliseconds:无论是因为连接时间超过timeoutInMilliseconds,或者是读取数据时间超过timeoutInMilliseconds,都会被hystrix表示为超时,并会抛出异常HystrixTimeoutExcpetion。
优先级问题
1.看完上面三个参数的解释:那么究竟哪个参数会成为最终的超时时间呢,其实这里要分情况,在这之前一定要明白connect和read这两个东西,前者是表示两个服务之间的连接,后者表示连接后的数据读取。
情况一:timeoutInMilliseconds时间小于ConnectTimeout与ReadTimeout时,则无论是连接时间超时还是读取数据时间超时,都会被熔断,抛出HystrixTimeoutException.
情况二: timeoutInMilliseconds时间大于ConnectTimeout与ReadTimeout时,则连接超时时间取ConnectTimeout,读取数据超时时间取ReadTimeout.
2.总结:
对于ConnectTimeout、ReadTimeout、timeoutInMilliseconds这三者的关系,
2.1对于timeoutInMilliseconds与ConnectTimeout对比谁小取谁
2.2对于timeoutInMilliseconds与ReadTimeout对比依然是谁小取谁
# 12 接口请求时好时坏
生产环境接口请求时,结果与预期不一致,返回的结果的是发版前的逻辑。在其中一个节点的服务器上反编译代码发现已经是最新的。通过Arthas的
tt
命令监控该节点上方法调用的入参和返回值,发现也是正常的。
原因: 服务仅其中一个节点有问题,代码是旧的。
解决方案: 检查注册中心,查看所有注册节点,检查每一个节点是否正常,代码是否最新。发现其中有一个节点有问题,代码是旧的,临时下线该节点。