遇到的问题(坑)
Redis
应用场景
- 保存热点数据
- 常用的尺寸、公差、工序信息
- 更新日志
- 生产订单信息
- 分布式锁(lua脚本实现)
- 用来保证数据一致性
缺点
- 不保证原子性
- 储存不了图片、视频、音频(memcached 可以)
- 可能出现与数据库数据不一致的问题,
- 可能会造成:缓存雪崩,缓存击穿,缓存穿透等问题
- 不支持回滚
- 因为数据在内存中,所以需要经常持久化
- 受物理内存限制,当到达最大数值时,将存不进去
优点
-
查询速度快:
1. 单线程:避免了频繁的上下文切换
2. 纯内存操作
3. 采用了非阻塞式I/O多路复用机制
-
合理的数据结构
- String(常用):缓存、计数、共享Session等
- Hash:客户下单信息、前台访问信息
- List:简单的消息队列
- Set:尺寸、公差、工序信息
- Sorted Set(有序的):存放排名、下单顺序、优先级等
-
查询原理:基于链表 的 跳表(Zset)时间复杂度:O(logn)
-
延伸:
-
为什么使用跳表不用B+树?
因为redis是基于内存的操作,而B+树是主要是为了IO操作。
-
跳表是不是很浪费内存?
索引只储存关键值和指针,所以占用空间很小,相对值来说可以忽略不计。
-
跳表高效的动态插入或删除时间复杂度相同【O(logn)】
不更新索引的情况下,频繁向像个索引节点中插入数据,极端情况下有可能退化为单链表
-
跳表保持平衡性的方法?
通过随机函数的方法,插入的数据随机生成索引,这样不至于性能过度退化
-
淘汰策略
key根据过期时间、随机、使用热度进行淘汰
过期策略
- **惰性删除:**当key被访问时检查该key的过期时间,若已过期则删除;已过期未被访问的数据仍保持在内存中,消耗内存资源;
- **定期删除:**每隔一段时间,随机检查设置了过期的key并删除已过期的key;维护定时器消耗CPU资源;
哨兵机制
- 搭建集群,最少主从
- 建立哨兵机制
Activiti
- windows系统的数据库默认表不区分大小写,而Linux数据库默认区分大小写,因为表中有一些数据开发环境可以使用,所以为了提升效率,选择清除部分无效数据后进行表迁移,当初在迁移的时候忽略了Linux数据库区分大小写,因为Activiti如果没有对应的表就会启动失败,所以一直启动失败,后来看了报错和网上找了一些资料,才发现这个问题。解决方案:
- 在Linux系统上创建表,重新加载数据(根据环境创建表)
- 手动更改表名大小写
- 修改数据库配置为不区分表名大小写:
my.cnf中关键配置:lower_case_table_names = 1
EasyExcel
-
公式的数据导入异常
- 场景:导入的时候发现部分数据无法解析
- 排查原因:
- 查看未读取到的数据特点(含有公式)
- 通过断点调试,判断文件流读取的数据是否正常
- 找到异常的代码块
- 解决方案:
- 修改代码,是数据类型少判断一层(字符串只判断了日期)
- 含有公式的数据不导入,在服务器进行计算
-
格式转换
- 转换异常导致读取不到(如日期等)
- 解决方案:
- 创建类型转换工具类
- 使用@JsonFormat注解进行转换(常用在日期)
- 解决方案:
- 自定义处理逻辑(一般比较复杂,如需要进行字符串拆分、拼接等)
- 转换异常导致读取不到(如日期等)
-
大文件上传解析问题(限、拆、)
- 太大导致内存溢出
1. 解决方案:
1. 限制上传大小
2. 分片上传:给每段小文件添加文件信息(索引、状态、大小等)
3. 多线程拆分解析 - 多sheet导入
- 解决方案:多线程并发解析,实质依旧是进行拆分处理
注意多线程的数据一致性问题
- 太大导致内存溢出
RabbitMQ
服务启动失败
-
问题出现场景:
服务启动失败
之前启动成功过
-
问题排查:
-
检查端口冲突
- rabbitmq-server -detached
-
检查日志文件:erl_crash.dump的Slogan的值中找
- rabbit_memory_monitor:磁盘空间不足
- duplicate_node_name:节点名字重复(命中)
- start failed with file locked:文件被锁定
-
解决方案:
- 停止节点
- 停止所有节点:rabbitmqctl stop
- 停止指定节点:rabbitmqctl -n 节点名 stop
- 清理磁盘空间
- 检查文件权限
重启服务
- 停止节点
-
消费失败问题
- 出现场景:
- 微信token过期(数据错误)
- 网络波动(消息超时)
- 业务逻辑、代码问题(路由失败(参数异常异常,如公众号token))
- RabbitMQ服务器出现故障或崩溃(被拒绝(业务异常))
- 消息数量超出最大值或容量
- 解决方案:
- 优化业务代码逻辑、sql
- 使用消息确认机制和事务机制
- 设置消息唯一标识
- 合理配置数据
- x-max-length:最大消费数量。在快溢出时给管理员发送通知(创建队列时)
- x-overflow:溢出行为。根据公司业务实际场景设置(丢弃/保留 前面或后面的数据)
- 使用集群
- 循环消费
- 循环到一定次数的时候加入到死信队列中手动消费
- 消息丢弃(不重要的、避免占用服务器资源)
消息丢失问题
根据数据模型可以分为生产者、消息队列、消费者三个维度进行分析
- 生产者
- 提交时丢失:事务机制:利用异常回滚的特点保证业务和消息的原子性从而保证一致性
- 发送时丢失:确认机制:将路由通道设置为confirm模式,通过唯一id和消息执行状态来判断生产者中消息是否路由成功
- 消息队列
- 持久化机制:配合confirm模式和事务机制进行将消息存储在磁盘上,保证当服务宕机时数据不丢失
- 消费者
- 手动ACK机制:存在重复消费、性能下降的隐患
- 错误日志机制:异常捕获记录日志
消费有序性问题
- 队列与消费者一对一:通过一定规则进行拆分队列,然后被拆分的队列可满足按顺序消费
- 加顺序字段:判读顺序字段从而判断消息顺序
消息堆积问题
- 出现场景:生产者的生产速度与消费者的消费速度不匹配
- 消费者消费能力弱
- 消息消费失败反复重试
- 消费端出了问题,导致不消费了或者消费极其慢
- 解决方案:
- 优化代码逻辑
- 合理设置消费端数量(多线程批量消费)
- 临时扩容处理堆积的消息
- 重导:当设置了过期时间后,虽然没有了大量消息积压,但是会丢失大量数据,所以将即将过期的消息先持久化,然后写一个脚本将重新发送到mq中
- 采用手动丢弃+重导机制
报表的制作、打印
常见框架的内置框架组件不适于适合公司的业务场景
-
解决方案:
-
前端使用基础表格的代码进行编写
-
缺点:
- 保存数据和打印数据可能存在不一致
- 因为打印用到了创建新的页面的方式,返回后数据不能保存
解决方案:放在session中,,打印后数据从session中获取,在跳转路由时清空此session
-
优点:响应快、预览方便、数据更灵活
-
-
后端使用pdf制作工具进行解析然后返回前端 -itextpdf(依赖)
- 缺点:
- 频繁进行IO操作,效率低
- 数据不灵活
- api响应文件,占用系统资源
- 优点:
- 保存数据一致
- 预览方便,可保存
- 缺点:
-
后端使用easyExcel进行报表导出
-
数据库设计
部分数据表字段不确定
解决方案:利用横纵互转的方法设计为动态表 ( 字段名、类型、值),然后通过map来传输数据
- 本质:
- (本次解决方案)通过动态sql中的case和group by来实现(纵转横)
- case:查询函数
- 简单查询:写法比较简洁,功能受限(本次解决方案)
- 搜索查询:可以进行判断等
- case:查询函数
- 通过pivot函数来实现(横转纵)
- (本次解决方案)通过动态sql中的case和group by来实现(纵转横)
SELECT <非透视的列>,
[第一个透视的列] AS <列名称>,
[第二个透视的列] AS <列名称>,
...
[最后一个透视的列] AS <列名称>,
FROM
(<生成数据的 SELECT 查询>)
AS <源查询的别名>
PIVOT
(
<聚合函数>(<要聚合的列>)
FOR
[<包含要成为列标题的值的列>]
IN ( [第一个透视的列], [第二个透视的列],
... [最后一个透视的列])
) AS <透视表的别名>
<可选的 ORDER BY 子句>;
业务中加入微信消息模板时,响应超时
问题排查:
-
服务器内存或cpu的使用率
-
JVM内存GC频率是否正常(没有明显异常,使用JDK自带的JvisualVM或阿尔萨斯(arthas))
-
是否造成线程阻塞
-
排查代码
-
查看JVM线程状态,使用jstack打印线程快照(
可查看: prio:线程的优先级,tid:线程id ,nid: 操作系统映射的线程id,表示线程栈的起始地址。
因为业务之间采用了线程隔离机制,每个业务都是会起一个线程去处理,看了在线程池中空闲线程没问题,所以问题排除
-
问题原因:业务中加入了发送微信消息模板,期间会发送请求,导致业务运行时间过长,超出了最大响应时长,所以造成响应超时
解决方案:
思路:异步执行
- 使用消息队列:缺点:当消息发送失败时,通知不及时
- 创建或调用空闲线程:缺点:当消息发送失败时影响业务
消息队列重复消费
出现场景:
- 消费端异常或崩溃时,重启
- 网络抖动,重复发送给消费者
- 消息重试机制(本项目中可能遇到,因为初始消费时间设置最长为1s)
- 业务逻辑重复发送
解决方案:
- 设置消息过期时间
- 利用唯一标识进行消息去重,幂等性控制
- 消息确认机制
- 并发控制:分布式锁(数据库、缓存),数据库事务机制
Tomcat启动失败
失败原因:
文件抽象路径代理配置出现问题;
解决思路:
-
首先查看服务是否在运行 ps - ef | grep tomcat
-
没有在运行,查看端口是否被占用 netstat -ntulp | grep 8080
-
没有被占用,以日志的方式启动 ./catalina.sh run
发现没有启动,报错:有资源没有找到。
检查发现是server.xml中文件路径<context> 配置错误
解决方案:
修改contexrt标签中的错误配置
Mysql备份时增量备份无效
问题排查:
1. 执行定时方法,查看运行日志;(无报错)
2. 检查bin-log是否开启;``进入mysql:show variables like '%log_bin%'``(已开启)
3. 手动执行命令语句;(提示找不到部分目录或文件)
- 发现mysql日志文件不在指定的文件夹下,所以判断配置文件异常
- 问题:配置bin-log时只写了自定义目录;
- 解决办法:自定义目录后需要跟日志文件名
代码问题
- (1)beanutils.copyproperties失效
- 原因:
- 当属性中包含集合或数组时,这个方法属于浅拷贝,无法复制数组或集合
- 属性名或类型不一致
- 没有提供set/get方法
- 解决方案
- 用json进行转换
- 在对象内重写clone方法
- 生产上出现bug,本地环境与生产差距大,没办法立即发布新版本时
解决方案:采用替类的方式,将影响降低到最小
-
先把生产上的 .jar 或者 .war 包下载下来。
-
本地环境找到生产版本时期的 bug 所在类的源码,然后进行修复,让 IDE 自动编译,然后找到编译后 .class 文件。
-
最后将修复后 .class 文件替换生产包中的文件。
-
如果修改子jar包,需要先解压jar 》》》》jar -xvf .**.jar
-
替换后打包》》》》jar -cfM0 **.jar ./
替换 .class 文件时,需要注意在使用第三方压缩工具时可能会出现问题,如使用7-zip时项目会启动失败,而WinRAR成功。原因:前者会重复压缩文件
多线程问题
- ThreadLocal
-
序列号生成:uuid结合ThreadLocalRandom.current()
-
pageHelper、MyBatis:底层分页隔离、SqlSession会话隔离。保证了线程安全和数据隔离性。
-
- 多线程数据一致性
-
解决方案:
- 使用线程安全的数据结构:concurrent包下的类、CopyOnWriteArrayList等
- 保证数据的三大特性
-
原子性:原子类>>如AtomicInteger、AtomicLong等
-
有序性:
- 同步机制:锁机制
- 线程调度设计:通信机制,使用wait()、notify()、notifyAll()等,并发工具类,如CountDownLatch、CyclicBarrier、Semaphore等
-
可见性:使用volatile关键字修饰共享变量,涉及到 JVM 内存模型
-
-
编程式事务回滚:CountDownLatch(线程安全的计数+线程等待方法)、AtomicBoolean(具有原子性的Boolean类)
-
并发开单处理:对不同的开单有不同的策略
- 开工序单:使用Redis进行缓存,使用了限流策略,将正在运行工单的存在缓存中,因为要保证开单的唯一性,同时还能避免连续点击导致的重复开单问题,同时设置过期时间以防服务异常导致未能及时删除缓存导致无法开单的情况。
- 开订单:使用缓存口令池策略,每天生成一定量顺序的口令,这个口令即内部的一个生产订单号。
-
数据安全性
- 文件上传下载处理方式
因为合同文件、产品图纸是属于行业隐私,在一开始的时候并没有考虑到这些数据可能会被盗用的问题,在后续维护了这个问题
解决方案:Nginx获取到请求中的token,然后通过lua脚本读取Redis中的用户token信息进行安全验证
- 对于金额等企业机密信息处理方式
1. 通过对称加密方式加密订单金额,然后根据用户权限去进行解密
2. 访问控制加密,对于接口权限控制到了按钮级
3. 对数据进行脱敏处理,保存到数据库中的数据为加密信息
4. 建立监控和审计策略,对公司隐私信息,如客户信息、原材料报价等,统计访问信息和对单位时间内频繁访问的用户进行预警措施