程序Bug的产生,通常分为三种类型
-
逻辑漏洞:
低级错误,程序执行后无法达到想要效果。 -
越界访问:
访问了非法区域,造成程序崩溃。 -
条件考虑不全面:
你以为你万无一失,但你永远都不知道输入参数究竟是什么!
如何防范未知Bug:异常捕获
异常捕获一般依靠try,catch语句。很好理解:try(尝试)一下,如果有问题,直接捕获 (catch)住,防止程序崩溃。
如果核心流程处理到一半服务器崩了怎么处理?
这里同时存在三个问题:
- 问题排查以及快速恢复
- 异常数据修复
- 服务高可用,规避服务宕机
1、先抢通业务
当发现服务器宕机后,最关键的是抢通业务,而不是抢修服务器。
因此,需要做应急方案。最好准备2个网站服务器,他们存放的内容相同,而ip不同,并且机房的地理位置不同。这样第一时间发现宕机问题后,可以迅速的通过修域名记录,指向目前正常的网站空间。而且2个主机,同时宕机的可能性就大大降低了。
2、定位服务器崩溃原因
- 内存溢出,磁盘资源耗尽
- 线程死锁,进程过多或者不断创建,耗尽资源导致
- 数据库慢查询,连接数过多,临时表不够用,程序死锁
- 主备数据不一致
- 应用程序异常
- 流量负载过大
- DOSS攻击
- 散热问题
3、异常数据修复
- 写数据做事务控制,保障数据安全。
- 磁盘备份,重启服务时恢复数据。
- 记录关键日志。
4、服务高可用
- 服务多实例集群部署,负载均衡策略访问,做好服务降级、服务限流。
- 数据库读写分离、分库分表方案。
- 做好服务性能测试、压力测试。
开发中常见问题
空指针异常
在访问或操作对象之前,检查对象是否为null
类型转换异常
在进行类型转换之前,先检查对象的类型是否与目标类型兼容。
金额数值计算精度问题
金额一般都是用BigDecimal
循环条件错误
可能会导致死循环或者无法执行
文件操作异常
文件操作之前,先检查文件是否存在、是否可读写等。
事务不起作用没有回滚
异常被try.catch吃了,手动抛了别的异常Exception,默认情况下只会回滚RuntimeException
访问权限不是public;方法用final,static修饰;方法内部调用;
未开启事务;未被spring管理,需要创建bean实例。
内存溢出异常
在编写程序时,要注意控制内存的使用,及时释放不需要的对象。
性能问题
比如使用缓存、减少对象创建、优化算法等。
大数据量导出Excel存在问题及优化方案
问题1:一次性获取全部数据到内存当中,容易引起系统OOM。
解决方案:分页查询数据,分批处理。
问题2:分页查询存在深度分页问题,数据偏移量越大导致sqL查询变慢。
解决方案:使用标签记录优化(id自增且连续)、索引覆盖优化。
问题3:查询数据串行耗时长。
解决方案:可以通过线程编排,并行执行sql查询,最后顺序导出到excel。
问题4:一个excel文件数据过大,用户存在打不开的情况。
解决方案:通过easyexcel多sheet页导出数据。
实际开发中,如何正确使用多线程?
- 处理并行任务:多线程可以同时处理多个任务,提高程序的执行效率。比如批量处理数据、同时上传多个文件等。
- 事件驱动的编程:Java多线程可以用于事件驱动的编程,如GUI、网络编程等。
- 并发访问共享资源:多线程可以应用于并发访问共享资源的场景,如数据库连接池。
- 高效的IO操作:在网络编程中,Java多线程可以提供高效的IO操作,如同时读写多个Socket。
- 多任务协同处理:在复杂的任务中,不同的任务可以以各自独立的方式并行运行,最终合并结果。
- 节约资源:多线程可以提高CPU和内存的使用效率,使得我们能够更好地利用系统资源。
- 提高用户体验:在一些高并发场景下,如网站、游戏等,使用多线程可以提高用户体验,使得用户能够更快地得到反馈。
- 线程安全问题:
多个线程同时访问共享资源时可能导致数据不一致或异常。解决方案包括使用同步机制(如synchronized关键字、Lock对象)、使用线程安全的数据结构、避免共享状态等。 - 死锁问题:
多个线程因相互等待对方释放资源而无法继续执行。解决方案包括避免循环等待资源、按照固定顺序获取资源、设置超时时间等。 - 上下文切换问题:
线程切换需要耗费一定的时间和资源,如果线程频繁切换,会降低程序性能。解决方案包括合理设计线程数量、减少线程间的竞争、使用线程池等。 - 数据同步问题:
多个线程访问共享数据时,可能出现数据不一致的问题。解决方案包括使用锁来保证数据的原子性、使用volatile关键字保证可见性、使用线程安全的数据结构等。 - 过度创建线程问题:
创建线程需要消耗系统资源,如果过度创建线程,可能导致系统资源耗尽。解决方案包括使用线程池来复用线程、合理设置线程池大小等。
定时任务如果集群,如何保证不被重复执行?
- 独立部署,将定时任务独立出来,成为一个单独的项目工程,单一部署
- 配置实现,配置文件设置一个标识符号,定时任务读取此配置文件此属性, 读取到ture执行定时任务,否则不执行
- 利用分布式锁,虽然两个机器都会运行定时任务,但是一个时刻只有一台机器会真正的执行定时任务的核心方法
一个订单30分钟未支付自动取消功能,有几种实现方案?
- 数据库轮询
该方案通常是在小型项目中使用,即通过一个线程定时的去扫描数据库,通过订单时间来判断是否有超时的订单,然后进行 update 或 delete 等操作。优点:简单易行,支持集群操作。
缺点:对服务器内存消耗大;
存在延迟,比如你每隔 3 分钟扫描一次,那最坏的延迟时间就是 3 分钟;
假设你的订单有几千万条,每隔几分钟这样扫描一次,数据库损耗极大。 - JDK 的延迟队列
该方案是利用 JDK 自带的 DelayQueue 来实现,这是一个无界阻塞队列,该队列只有在延迟期满的时候才能从中获取元素,放入 DelayQueue 中的对象,是必须实现 Delayed 接口的。优点:效率高,任务触发时间延迟低。
缺点:服务器重启后,数据全部消失,怕宕机;集群扩展相当麻烦;代码复杂度较高;
因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现 OOM 异常。 - 时间轮算法
时间轮算法可以类比于时钟,按某一个方向按固定频率轮动,每一次跳动称为一个 tick。优点:效率高,任务触发时间延迟时间比 delayQueue 低,代码复杂度比 delayQueue 低。
缺点:服务器重启后,数据全部消失,怕宕机;集群扩展相当麻烦;
因为内存条件限制的原因,比如下单未付款的订单数太多,那么很容易就出现 OOM 异常。 - Redis 缓存
思路一:利用 Redis 的 zset,zset 是一个有序集合,每一个元素 (member) 都关联了一个 score, 通过 score 排序来取集合中的值。
思路二:使用 Redis 的 Keyspace Notifications,中文翻译就是键空间机制,就是利用该机制可以在 key 失效之后,提供一个回调,实际上是 Redis 会给客户端发送一个消息。是需要 Redis 版本 2.8 以上。优点:由于使用 Redis 作为消息通道,消息都存储在 Redis 中。如果发送程序或者任务处理程序挂了,重启之后,还有重新处理数据的可能性;做集群扩展相当方便;时间准确度高。
缺点:需要额外进行 Redis 维护。 - 使用消息队列
可以采用 RabbitMQ 的延时队列。RabbitMQ可以实现延迟队列。优点:高效,可以利用 RabbitMQ 的分布式特性轻易的进行横向扩展,消息支持持久化增加了可靠性。
缺点:本身的易用度要依赖于 RabbitMQ 的运维。因为要引用 RabbitMQ, 所以复杂度和成本变高。
为什么不用eureka非要用nacos作为注册中心?
nacos在自动或手动下线服务,使用消息机制通知客户端,服务实例的修改很快响应;Eureka只能通过任务定时剔除无效的服务。
nacos可以根据namespace命名空间,DataId,Group分组,来区分不同环境(dev,test,prod),不同项目的配置。
Mq如何保证消息不丢失?
丢数据一般分为两种,一种是mq把消息丢了,一种就是消费时将消息丢了。
Mq如何保证消息顺序的一致性?
如何避免消息一直堆积在mq服务器端?
在遇到消息堆积的时候,先检查下导致堆积的原因,可能有如下几种:
- 消费失败时大量重试导致消息堆积。
- 消费者程序的故障:如 程序死锁,io阻塞等。
- 消费者资源瓶颈:目前的主流消息队列,单个节点消息收发的性能可以达到万级别甚至10万级+的水平。除非容量预估没有做好,一般不会出现这种问题。即使出现这种问题,通过Scale Out Broker 的实例数也是比较轻松可以解决的。
消息堆积的解决方案:
- 提高消费者数量;更多的消费者将允许同时处理更多的消息,并减少消息堆积。
- 调整超时设置;例如,在某些情况下,因为某些原因(例如网络延迟),MQ 消费者需要等待更长时间才能接收到新的消息。
- 批量操作;例如,在生产者端,您可以使用管道来一次性发送多条信息。在消费者端,您可以使用批处理来一次性处理多个消息。
- 数据结构优化;例如,在使用MQ时,可以通过在消息中添加一些元信息来优化处理流程,或者采用更合适的数据结构存储消息,以减少在 MQ 中积累的消息数量。
Mq异步消费,如何获取到返回结果?
以下用Rabbit为例:
- 异步操作,获取回调消费结果,需要实现RabbitTemplate.ConfirmCallback 接口,然后重写 confirm()方法。
- 获取回调结果,指的是获取消息是否被消费端正常消费而返回的结果,并不是消费端返回的处理结果,这一点得注意,如果需要等待消费端返回处理结果,则需要做同步操作,而不是做回调操作。
- 需要做同步操作时,应该rabbitTemplate.convertSendAndReceive()方法,返回结果类型是Object,需要根据消费端返回的数据类型来决定强转的类型。
- 异步则使用rabbitTemplate.convertAndSend()方法。