标签:
门面模式、观察者模式、责任链模式、模式组合
设计原则
流程清晰、责任明确、易于扩展、兼顾性能。
场景描述:
需要提供一个服务开发若干接口可以是REST或者WEBSERVICE实现,协议数据包含两大类数据内容结构化数据和图片数据。整个服务需要完成结构化数据的输入、图片数据的存储、数据校验、数据处理。要求如下:
- 数据校验部分涉及的数据字典都存储在数据库中,该模块下需要定时同步数据字典。
- 提供过滤机制,支持后续通过该切点多数据就行修正、补全以及非法数据的过滤操作。
- 图片存储需要支持扩展、已便于后续切换为其它存储系统。
- 数据处理模块,应该支持多种业务场景,常见的:数据推送、流量计算
数据流设计
- 前端设备为数据生产者,常见的物联网终端设备都可以产生源源不断的数据“事件”。
- 接入服务提供接口给前端设备由前端设备按规范接入数据
- 数据校验模块,以数据字典为准对接入的数据执行数据校验,校验失败直接返回前端
- 过滤器模块,在图片数据未落地前提供对校验通过的数据的切入点,可以实现数据标准化、异常数据过滤等操作
- 图片存储模块,选择合适的存储实现将图片落地到分布式存储并补全结构化数据中的图片路径
- 数据分发模块收集所有对数据事件处理的操作“任务”,并将事件下发给每个任务模块
- 数据处理模块是具体的每个事件处理单元,可以是数据推送任务、流量计算任务等
流程控制
HandleFace模块借鉴“门面模式”,用来协调其它模块来驱动整个数据流。ImageServer模块负责图片落地,ValidationServer模块负责执行数据校验逻辑,WatchServer模块负责执行数据下发到任务逻辑,FilterChain模块加载执行过滤逻辑。
@Service
public class HandleFace {
private static final Log log = LogFactory.getLog(HandleFace.class);
// 图片存储模块
@Autowired
private ImageServer imageServer;
// 数据校验模块
@Autowired
private ValidationServer validationServer;
// 数据分发模块
@Autowired
private WatchServer watchServer;
// 数据过滤,前端不需要重新上传
FilterChain filterChain = new FilterChain();
// 提供给事件的接口
public String handle(ExternalCar event) {
log.debug("received event:" + event);
// 字段校验
String checkState = validationServer.validation(event);
if (!StringUtils.equals("OK", checkState)) {
return checkState;
}
if (!filterChain.doFilter(event, filterChain)) {
return "OK";
}
// 图片存储
imageServer.storage(event);
// 数据加工
watchServer.notifyObserver(event);
return "OK";
}
}
ValidationServer模块实现
ValidationServer模块提供validation接口输入事件对象,输出验证结果,文中以返回“OK”为验证通过
,validation接口内部以各个场景下的具体实现为准。
ImageServer模块实现
ImageServer模块提供storage接口负责具体调用图片存储逻辑并完善结构化信息,同时返回存储状态结果。其内部通过调用ImageDao接口的具体实现执行针对特定存储的文件上传逻辑。
@Component
public class ImageServer {
@Autowired
private ImageDao imageDao;
public List<Boolean> storage(ExternalCar externalCar) {
try {
return imageDao.saveImage(externalCar);
} catch (Exception e) {
e.printStackTrace();
}
return new ArrayList<>();
}
}
下图定义了ImageDao接口及部分实现类。
FilterChain模块实现
FilterChain模块使用“责任链模式”实现事件过滤逻辑。过滤器Filter接口定义如下,通过实现Filter接口来插入过滤逻辑,doFilter返回fasle标识数据被过滤,返回true标识数据未被过滤也可能执行了一些数据纠正之类的逻辑。
public interface Filter {
void init();
boolean doFilter(ExternalCar externalCar, FilterChain chain);
void destroy();
}
FilterChain定义过滤器链,用来链式的执行所有实现Filter的过滤器逻辑。
public class FilterChain implements Filter {
// 收集所有过滤器的实现
public static final List<Filter> filters = new ArrayList<>();
private int index = 0;
// 添加到过滤器链
public FilterChain addFilter(Filter filter) {
filters.add(filter);
return this;
}
// 执行过滤器逻辑
@Override
public boolean doFilter(ExternalCar externalCar, FilterChain chain) {
if (index == filters.size()) {
return true;
}
Filter filter = filters.get(index);
index++;
return filter.doFilter(externalCar, chain);
}
@Override
public void init() {
}
@Override
public void destroy() {
}
}
将过滤器Filter的实现注册到FilterChain中,在Spring环境下可以通过BeanPostProcessor提取需要的Bean对象
@Repository
public class TaskBeanPostProcessor implements BeanPostProcessor {
@Autowired
private WatchServer watchServer;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//所有实现了Task接口的bean注册到异步任务列表
if (bean instanceof Task) {
Task task = (Task) bean;
RegisterCenter.tasks.add(task);
}
//将容器中所有实现了Filter的bean注册到过滤器列表
if (bean instanceof Filter) {
Filter filter = (Filter) bean;
filter.init();
FilterChain.filters.add(filter);
}
//将所有实现了ReceiveObserver的bean注册到观察者列表
if (bean instanceof ReceiveObserver) {
ReceiveObserver observer = (ReceiveObserver) bean;
watchServer.registerObserver(observer);
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
WatchServer模块实现
WatchServer模块是接口ReceiveObserverable的实现,ReceiveObserverable接口设计为观察者模式,提供对象加入、退出观察者队列的API.观察者对象的发现和注册机制同Filter一样。
@Component
public class WatchServer implements ReceiveObserverable {
public static List<ReceiveObserver> observers = new ArrayList<>();
public WatchServer() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(ReceiveObserver observer) {
observers.add(observer);
}
@Override
public void removeObserver(ReceiveObserver observer) {
observers.remove(observer);
}
@Override
public void notifyObserver(ExternalCar event) {
for (ReceiveObserver observer : observers) {
observer.receive(event);
}
}
}
ReceiveObserver接口定义了观察者接收时间的接口,每一个数据消费者都要实现ReceiveObserver接口,处理自己的逻辑。ReceiveObserver的子类可以是同步的或者异步的处理收到的每一个事件对象。同步处理很简单直接在实现类中处理时间对象即可下面给出一个异步场景下的实例。
ReceiveObserver 的异步消费实例
现有设计中WatchServer对所有观察者对象的调用都是同步的顺序的,如果某个观察者失败或者阻塞可能对整个数据分发过程造成困扰。其一数据分发使用多线程,降低多个观察者之间的强耦合性,其二要求每个观察者不阻塞数据接收流程。这里有不阻塞为例。
为了统一任务的注册、管理和调度基于Runnable设计一个简易的异步任务调度框架同时定义几个生命周期方法:
public abstract class Task implements Runnable {
/** 初始化方法,建议在这里创建队列绑定路由 */
public abstract void init();
/** 执行方法,执行任务 */
public abstract void execute();
/** 销毁方法,任务执行后的清理工作 */
public abstract void destory();
@Override
public void run() {
try {
init();
this.execute();
} finally {
destory();
}
}
}
以一个将事件对象发送到Kafka的场景为例,下面给出参考代码,容易出问题就是忽略线程的启动顺序问题,已进行标注
@Component
@ConfigurationProperties(prefix = "kafka")
public class KafkaSend extends Task implements ReceiveObserver {
private static Log log = LogFactory.getLog(KafkaSend.class);
private String topic;
private int queuesize;
private volatile Producer<String, String> producer;
private volatile ArrayBlockingQueue<ExternalCar> queue;
public static final Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getName().contains("tp1") || f.getName().contains("tp2") || f.getName().contains("tp3");
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
}).create();
@Autowired
private ProducerConfig producerConfig;
@Autowired
private MetricRegistry metricRegistry;
private Counter kafkaSuccess;
private Counter kafkaFail;
@Override
public void execute() {
while (true) {
try {
ExternalCar externalCar = queue.take();
String carMessage = gson.toJson(externalCar);
producer.send(new KeyedMessage<String, String>(topic, carMessage));
kafkaSuccess.inc();
log.debug("kafka send :" + carMessage);
} catch (Exception e) {
kafkaFail.inc();
log.error("kafka-master 发送异常", e);
}
}
}
@Override
public void init() {
queue = new ArrayBlockingQueue<>(queuesize);
producer = new Producer<>(producerConfig);
kafkaSuccess = metricRegistry.counter(MetricRegistry.name(KafkaSend.class, "kafka", "success"));
kafkaFail = metricRegistry.counter(MetricRegistry.name(KafkaSend.class, "kafka", "fail"));
log.info("kafka-master 初始化成功...");
}
@Override
public void receive(ExternalCar externalCar) {
try {
// 如果数据先进程kafka发送线程未初始化完毕则阻塞
while (queue != null) {
queue.put(externalCar);
}
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
public int getQueuesize() {
return queuesize;
}
public void setQueuesize(int queuesize) {
this.queuesize = queuesize;
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
@Override
public void destory() {
// kafka发送线程结束,阻塞接收线程
queue = null;
}
}
可见KafkaSend同时实现Task和ReceiveObserver接口,接收来自于WatchServer推送的数据尽可能不阻塞接收流程,同时KafkaSend需要以线程的方式启动以执行将数据由内部队列输出到Kafka的逻辑。Task的驱动由TaskServer来统一驱动。
@Service
public class TaskServer implements Runnable {
private static final Log log = LogFactory.getLog(TaskServer.class);
@Override
public void run() {
log.info("总任务数:" + RegisterCenter.tasks.size());
ExecutorService taskThreadPool = Executors.newFixedThreadPool(RegisterCenter.tasks.size());
for (Task task : RegisterCenter.tasks) {
taskThreadPool.execute(task);
}
}
}
通常情况下建议在Spring容器加载完毕后发布相关的业务逻辑,
@SpringBootApplication
public class WebServiceApplication {
private static Log log = LogFactory.getLog(WebServiceApplication.class);
public static void main(String[] args) throws SchedulerException {
ConfigurableApplicationContext context = SpringApplication.run(WebServiceApplication.class, args);
Scheduler scheduler = context.getBean(Scheduler.class);
// 启动Schedule 服务
scheduler.start();
TaskServer taskServer = context.getBean(TaskServer.class);
// 驱动所有的Task线程
taskServer.run();
EndpointImpl endpoint = context.getBean(EndpointImpl.class);
endpoint.publish("/TgsService");
log.info("WEBSERVICE 发布成功...");
}
}