一个好的设计是怎样炼成的

之前做过一个需求——编辑、发布新闻,需求中有这样一个场景,编辑好的新闻有两种发布模式,可以选择立即发布,也可以选择延时发布,即,在未来的某一个时间点进行发布。

简单分析下,其实质是一个定时的问题,这类问题在网上有很多针对这类型的帖子,相信各位应该有看到过,大致有以下几种方案:

一、使用JUC中的 ScheduleExecutorService.schedule(Runnable,long,TimeUint)方法;

二、使用RabbitMq的延时队列和延时消息;

三、使用redis的过期key回调监听特性。

该怎么选择?有一个很有效的方法:当面临选择时,列出每一种选择的优缺点,对比优缺点,选择对自己最有利的方案。

先说方案二,若使用rabbitmq,那么就必须得建一个延时队列,操作相对其他两种繁琐、复杂。这个当然不是重点,重点是rabbitmq的延时队列需要在创建队列的时候就指定的,且创建后不可修改,而需求是未来的任意一个时间点,并不是一个明确的时间点,如果要满足这个需求,那就需要在未来每一个时间点都建立一个队列,这显然是不可能的。所以方案二NO PASS.

再说方案一,该方案编码相对来说是最简单的,只需要创建一个线程池,然后当有延时发布的新闻时只要将其放入到线程池中,设置好新闻生效的时长就ok了。但是有这样一个问题,创建ScheduleExecutorService该类型线程池的核心线程数的大小虽然可以自定义,但是最大线程池数参数的值是固定的、且无法修改的——Integer.MAX_VALUE,这个大小可以认为是无限的,那么如果出现大量的新闻都选择延时发布(非常有可能),那么就会创建出大量的线程,使CPU使用率飙升,甚至拉满、假死状态,这个是非常危险的,而且无法由设计者控制,完全取决于发布者,无法控制的设计绝对不是一个好的设计。方案一在我这里  也是NO PASS.

redis过期key回调,是目前各大论坛帖子使用最多、讨论最多、算是一个比较成熟的、用于延时需求的方案。用起来也是比较简单的,只需要implement MessageListener类实现onMessage(Message,byte[])方法即可,然后在该方法中做发布新闻的操作。整个需求就完成了。

设计的意义是什么?每个人应该都有自己的答案。我个人认为设计的意义是在满足需求的同时尽最大可能去避免各种异常场景。服务器宕机也属于异常场景中一种,想一下当redis服务器宕机了怎么办?redis有持久化机制,没错,但,即使是配置了原始快照+增量更新这两种持久化模式也是有极小的几率出现数据丢失的情况,怎么避免呢?

首先在新闻发布表中新增一个字段,字段意义--当前新闻是否已发布 ,其次开启一个定时任务周期扫描新闻表,获取还没有发布的新闻,然后根据key值到redis去查询当前key是否存在,若不存在则重新添加到redis中。

设计思路大致如上图,极大的保证了数据的完整性,即使出现极小可能的服务宕机的场景也无需运营介入,数据可以自动恢复。

以上有两个细节地方需要注意一下。

一、新增的字段最好加入索引,当新闻表数据量较大时可以避免全表扫描造成的长耗时,且,最好不要将

      发布时间字段作为索引,可能无法走到索引。

二、定时任务执行的时间点和执行周期也是一个值得注意的点,

 执行周期参考的点有两点,

  1. 服务器的可用率;

  1. 当出现极端情况,数据丢失,新闻发布者能接受的最长的恢复时长;

 执行时间点不要在业务繁忙时间段作为定时任务的执行时段,最好选择空闲时间段,比如凌晨一两点。

  

好的设计并不仅仅指会提高服务的可用性,针对不同的场景,比如提升服务的性能、提升服务数据的一致性、或者仅仅是结构更合理,编码更优雅等。

一个好的设计并不是一蹴而就的,是在设计过程中或者在编码环节一次一次发现问题,并给出最佳实践,是在过程中不断优化,精炼最终形成的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值