关于鲁棒性的思考

鲁棒性关注的重点在于系统的稳定性,在不同场景下衍生了复杂的设计考量,且本身是一个广泛且难以具像化的特性。因此,针对特定目标实现鲁棒性分析,形成切实可行的鲁棒性意识,保障安全性。

基于鲁棒性分析,以设计规约为目标,有三个维度可以拆解:输入、处理、输出;以代码规范为核心,我们可以从三个方面来分析,分别为:代码质量、代码性能以及代码优雅。

设计规约

  失败设计思维

针对输入和处理环节,失败设计思维是保证鲁棒性的有效设想。该思维要贯穿代码生命周期始终,把失败当作代码设计中合理存在,提前准备好从运行失败的场景中恢复。倡导防御式编程思想,拒绝契约式编程。

  • 入参判空、有效性检验。

  • 系统设计时识别弱依赖,并针对性地设计降级、限流等应急预案,保证核心逻辑正常可用。

  • 在考虑主干功能的同时,要充分考虑评估异常流程与业务边界。

正例:当系统弱依赖于多个外部服务时,如果下游服务耗时过长,则会严重影响当前调用者,必须采取相应降级措施,比如,当调用链路中某个下游服务调用的平均响应时间或错误率超过阈值时,系统自动进行降级或熔断操作,屏蔽弱依赖负面影响,保护当前系统主干功能可用。

反例:用户在淘宝付款过程中,银行扣款成功,发送给用户扣款成功短信,但是支付宝入款时由于断网演练产生异常,淘宝订单页面依然显示未付款,导致用户投诉。

  图式表达设计

针对处理环节,图式表达设计保证鲁棒性的有效举措。在复杂多变的业务场景中,图式表达往往能够以清晰、结构化的展现业务关联关系,对技术链路包括失败异常分支也有充分的分析帮助。

  • 如果某个业务对象状态超过3个,使用状态图来表达并且明确状态变化的触发条件;状态图的核心是对象状态,首先明确对象有多少种状态,然后明确状态间是否存在直接转换关系,再明确触发状态转换的条件是什么,最终输出状态转移图。注:状态图中的状态在代码中必须集中定义。

  • 如果系统中某个功能的调用链路上涉及对象超过3个,使用时序图来表达并且明确调用环节的输入与输出。时序图反映了一些列对象间的交互和协作关系,可以清晰立体地反映系统间调用纵深链路。

  • 如果系统中模型类超过5个,并且存在复杂的依赖关系,使用类图来表达并且明确类之间的关系。

  • 如果系统中超过2个对象之间存在协作关系,并且需要表示复杂的处理流程,使用活动图来表示。

正例:淘宝订单状态有已下单、待付款、已付款、待发货、已发货、已收货等。比如已下单与已收货这两种状态之间是不可能有直接转换关系的。

  异常错误处理

针对输出环节,异常错误处理是保障鲁棒性的重要依据。业务代码必然会有错误失败出现,是否符合预期表现,是否在正常处理流中,是否可以快速对错误定位,往往要有一定的判断依据。面对异常分支,就需要异常错误输出,也是系统监控的基础。

  • 错误码设计。错误码能够快速知晓错误来源,同时也能给予依赖者的确定性表达,提高鲁棒性。

  • 异常日志输出。控制异常日志输出级别,error级别只记录系统逻辑出错、异常或者其他重要的错误信息。

  实战Case

  • 需求背景

聚划算章鱼互动升级为“聚财气”频道,新增气泡奖励玩法。气泡奖励分登录奖励和时长奖励,其中时长奖励包括奖励1倒计时30秒、奖励2每日9点以及奖励3每日20点。

场景演示:用户在10:00进入频道后,收取完登陆奖励,唤起了一个30秒后的奖励的气泡;30秒后用户点击领奖,唤起了一个提示今日20:00可领的提示(该奖励未领);用户次日再来,收取完登陆奖励后唤起了30秒后的奖励气泡…

实现效果

  • 技术设计

通过气泡任务的需求描述,简单分析可以得知,任务开始到权益发放间有状态变更,气泡任务间有优先级逻辑。因此,基于设计规约,我们可以对需求进行清晰的分析和开发设计。

1、图式表达设计

气泡任务的复杂度主要在于多状态的变更,所以采用图式表达方式完成状态的变迁。可以看出,运用状态图是较合适的。(状态图:主要用于描述一个对象在其生存期间的动态行为,表现为一个对象所经历的状态序列,引起状态转移的事件,以及因状态转移而伴随的动作)

气泡任务状态图

气泡任务间展示状态图

2、失败设计思维

针对气泡任务,失败设计思维的侧重在于防御式编程和服务降级限流。在防御式编程中,利用断言型接口,对气泡透传前置条件校验、状态扭转识别以及有效性检验。同时,在服务降级预案中,考虑到气泡任务并不影响玩法频道的用户主流程,因此设计了两种预案:一是奖励资格和权益发放大面积失败或异常时,气泡任务全部降级处理;二是特定气泡逻辑存在异常问题时,该气泡降级关闭。此外,设定服务限流阈值,在大促流量高峰时保护系统稳定。

3、异常错误处理

异常错误处理主要在于失败后的反应动作和前台用户表达。气泡任务状态转移中,会存在奖励资格和权益发放失败的现象。失败的发生有着难以枚举的原因。针对失败,首先保持幂等性,进行系统重试或者用户行为重试;其次,失败异常日志输出,利用错误码设计尽可能准确描述失败原因;最后,异常和错误监控,基于分钟级错误日志统计报警,开发同学可第一时间介入定位问题。另外重要的一点是,由于真正使用的是用户,所以前台表达一定要是友好的、便于理解的,不然歧义的表述会造成大面积舆情发生。

  • 小结

基于上述三点,贯穿气泡任务的设计、开发等过程,不同维度地保证了系统鲁棒性。此外,在实际开发阶段,气泡任务采用了责任链模式来实现的,可动态调整气泡间依赖关系,提供一定的扩展性。

代码鲁棒性

以具体场景和实例来描述代码规范和技巧,提升代码鲁棒性和系统稳定性。

  代码质量

  • 集合处理

在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要使用含有参数类型为BinaryOperator,参数名为mergeFunction的方法,否则当出现相同key值时会抛出IllegalStateException异常。

「说明」参数mergeFunction的作用是当出现key重复时,自定义对value的处理策略。

正例:

List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);

pairArrayList.add(new Pair<>(“version”, 6.19));

pairArrayList.add(new Pair<>(“version”, 10.24));

pairArrayList.add(new Pair<>(“version”, 13.14));

Map<String, Double> map = pairArrayList.stream().collect(

// 生成的map集合中只有一个键值对:{version=13.14}

Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));

反例:

String[] departments = new String[] {“iERP”, “iERP”, “EIBU”};

// 抛出IllegalStateException异常

Map<Integer, String> map = Arrays.stream(departments)

.collect(Collectors.toMap(String::hashCode, str -> str));


在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要注意当value为null时会抛NPE异常。

「说明」在java.util.HashMap的merge方法里会进行如下的判断

public static T requireNonNull(T obj) {

if (obj == null)

throw new NullPointerException();

return obj;

}

反例:

List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);

pairArrayList.add(new Pair<>(“version1”, 4.22));

pairArrayList.add(new Pair<>(“version2”, null));

Map<String, Double> map = pairArrayList.stream().collect(

// 抛出NullPointerException异常

Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));


Collections类返回的对象,如:emptyList()/singletonList()等都是immutable list,不可对其进行添加或者删除元素的操作。

ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常:在subList场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生ConcurrentModificationException 异常。

「说明」subList()返回的是ArrayList的内部类SubList,并不是 ArrayList本身,而是ArrayList 的一个视图,对于SubList的    所有操作最终会反映到原列表上。列表改动均会引起checkForComodification异常

private void checkForComodification() {

if (this.modCount != l.modCount)

throw new ConcurrentModificationException();

}


在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断。

「说明」在ArrayList#addAll方法的第一行代码即Object[] a = c.toArray();其中c为输入集合参数,如果为null,则直接抛出异常。

泛型通配符<? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);<? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)。

「说明」PECS (Producer Extends Consumer Super)原则:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。因此,频繁往外读取内容的,适合用<? extends T>。经常往里插入的,适合用<? super T>。

不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator迭代器对象加锁。

反例:

List list = new ArrayList<>();

list.add(“targetItem”);

list.add(“other”);

for (String item : list) {

if (“targetItem”.equals(item)) {

list.remove(item);

}

}

正例:

Iterator iterator = list.iterator();

while (iterator.hasNext()) {

String item = iterator.next();

if (删除元素的条件) {

iterator.remove();

}

}

  • 计算处理

禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。

「说明」BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。如:BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0.100000001490116119384765625

正例:

优先推荐入参为String的构造方法,或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString,而Double的toString按double的实际能表达的精度对尾数进行了截断。

BigDecimal recommend1 = new BigDecimal(“0.1”);

BigDecimal recommend2 = BigDecimal.valueOf(0.1);

  • 日期处理

获取当前毫秒数:System.currentTimeMillis(); 而不是new Date().getTime()

「说明」如果想获取更加精确的纳秒级时间值,使用System.nanoTime的方式。在JDK8中,针对统计时间等场景,推荐使用Instant类。

日期格式化时,传入pattern中表示年份统一使用小写的y。

「说明」日期格式化时,yyyy表示当天所在的年,而大写的YYYY代表是week in which year,意思是 当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的YYYY就是下一年。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

即使是面试跳槽,那也是一个学习的过程。只有全面的复习,才能让我们更好的充实自己,武装自己,为自己的面试之路不再坎坷!今天就给大家分享一个Github上全面的Java面试题大全,就是这份面试大全助我拿下大厂Offer,月薪提至30K!

我也是第一时间分享出来给大家,希望可以帮助大家都能去往自己心仪的大厂!为金三银四做准备!
一共有20个知识点专题,分别是:

Dubbo面试专题

JVM面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Java并发面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Kafka面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

MongDB面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

MyBatis面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

MySQL面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Netty面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

RabbitMQ面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Redis面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Spring Cloud面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

SpringBoot面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

zookeeper面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

常见面试算法题汇总专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

计算机网络基础专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

设计模式专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
[外链图片转存中…(img-WhmddoxW-1712764590905)]

Redis面试专题

[外链图片转存中…(img-LcUbnrIk-1712764590906)]

Spring Cloud面试专题

[外链图片转存中…(img-sEQ6WwAe-1712764590906)]

SpringBoot面试专题

[外链图片转存中…(img-YjuNgzJz-1712764590906)]

zookeeper面试专题

[外链图片转存中…(img-uy24CgTs-1712764590906)]

常见面试算法题汇总专题

[外链图片转存中…(img-V6JZI2TN-1712764590906)]

计算机网络基础专题

[外链图片转存中…(img-BUBoi0P0-1712764590906)]

设计模式专题

[外链图片转存中…(img-W70s6nsq-1712764590907)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值