在已通过去的2016年,整个后端技术市场上,若是说哪一种技术最火,我想微服务应该无人能够出其右吧,能够说火爆大江南北。不少公司的后端服务架构体系都在逐渐从单应用体系转型到微服务体系,咱们也不例外的投入这股转型浪潮中。能够说,在可预见的将来几年,微服务的架构体系定将在整个系统架构中成长为苍天大树。
在作微服务中,须要作不少技术的评估,选型等等,好在这些使人头疼的事不须要个人参与(小菜鸟一枚,上不得台面),那么在谈到如今的微服务架构,每每都会把其中的调度协议抓出来大谈特谈一番,被畅聊的比较多的多是:基于高性能的远程调用rpc协议以及很是轻量级的基于请求-响应模式的http协议。因此可能被提的比较多的几个关键字大概是:dubbo,thrift,restful,以及后边咱们最后选定的spring全家桶 spring cloud。在这里不想说去作框架之间的偏激的比较,任何一种技术框架都有其侧重点,以及相对的弱化区,在小子看来,技术框架历来都没有强弱的概念,只要能够解决自身业务痛点的技术,都是好技术。java
接下来,说一下为何咱们最后会选择使用目前来讲没有很火的spring cloud吧。之因此选择它,第一天然是基于对于spring社区几乎绝对的信任。对于整个Java开发体系来说,spring社区的影响力之大,便如好莱坞与电影行业之发展同样,因此做为一个javaer,自己对于spring出品的东西,都抱着很是高的期待与信任。第二点是考虑到更为轻量的restful协议,至于说有没有比远程调用协议更优越,小子也不知道(没有使用过rpc协议作过东西,因此没有更多的去了解)。第三点考虑则是基于spring boot微应用理念开发,自己spring cloud就是由spring社区整合旗下的其余框架造成的一个全新的生态,而spring boot微应用的理念简直就是为了微服务架构而生(略偏激)。很是期待,彻底相信spring cloud在将来必定能够大放异彩……spring
那在作评估与选型的时候,是否有其余更多其余的考虑,我暂时也不知道,若是有的话,我后续会再补充进来。这边推荐一个连接,基于DD老师分享的关于dubbo与spring cloud的一些简单对比:http://blog.didispace.com/microservice-framework/后端
推送模块是我接手微服务架构的第一个应用的开发,而且整个系统的设计过程都是由我本身来驱动,自我思考,而老大则是做为建议参考,从旁指导。(菜鸟一枚,羞涩羞涩)因此我才考虑将本身的一些感悟以及思考记录下来,若是有哪一位网友不当心看到,而且对于他提供了一些微不足道的帮助,我想我花的时间与精力来写这一系列文章(后续在持续改进推送模块中,若是有好的改动,好的设计,或者优化,我会持续写,能够当成一个简单的系列)的目的就达到啦。由于开源,分享,可使我快乐。微信
公司如今处于发展阶段,目前对接了几种推送的第三方平台:短信平台,app推送平台,以及微信公众号推送。至关的简陋,由于如今初步转型,后续业务上确定会持续对接各大平台来作消息推送,因此初步的设计须要作好可扩展的考虑。(不过请注意,切勿过分设计……)restful
首先,基于咱们选择了spring cloud来作平台架构,协议上的选择无疑就是很是轻量,而且极为成熟的基于http的restful协议。因此暂时性的,整个推送中心咱们的对于提供两种接口,一种是https的rest请求,一种则是基于消息推送的消息队列。数据结构
底层第三方平台的对接,咱们采用的是是spring 提出的starter的形式,经过pom文件引入,这种形式对于后续的第三方平台持续追加都是毫无压力的,很是轻松,而且很是轻量;因此第一步的框架构建大概以下图架构
(图1)app
在starter上提供一个service来处理不一样的消息,以及即将调用的不一样的第三方平台。starter虽然看似简单,实则不容易,特别是咱们这种多系统,多平台的对接状况(接入参数的多样性)。框架
推送模块就是来作消息推送的,因此离不开业务的格局,因此有必要分析具体的业务。基于业务的设计基本上每一个公司都有不一样的业务需求,因此结果天然也是天差地别,这里以咱们公司的业务做为说明记录,消息推送主要是以系统内部触发消息,以消息队列的形式来触发推送,不少时候都是一些提醒,好比短信提供,公众号提醒;另外就是经过调用推送接口来给指定用户推送内容服务,活动等等;第三点经过接口调用做为其余平台整合,扩展使用。因此我初步把咱们的消息类型分类,提取几个拿出来讲,以下图:函数
(图-2)
仔细考虑,好比一个注册消息触发,须要作短信推送,把注册相关的内容(送代金券之列)推送到用户手中。好比一个收款的消息触发,须要短信提醒交易状况,须要公众号提醒交易额,须要app推送相关流水等等……一条消息进来,有时候仅仅须要单个平台进行推送,有时候却须要多个平台同时去作推送。因此消息处理的一套集合大概是 <消息类型 * 平台>这样一个结果集,如此凌乱,可能后续很是臃肿的结果集,若是请求的入口设计(其余服务调用推送服务必须遵循的协议)不合理,后续根本没法持续维护。
初步设计:提供一个公共的数据模型DTO(数据传输对象 data transfer object),以及一些推送系统内部固定的常量的jar包,目的是为了让我后续的业务不断扩展能够带来极大的方便性,因此个人DTO必须在最开始就尽量考虑周全(固然,彷佛也没有多少好考虑的,哈哈)
public class SenderMessageDTO { /** 消息平台类型*/ private String sender; /** 平台标识*/ private String sys; /** 消息类型*/ private String msgType; // ....... }
有了这个jar包以后,整个推送的入口算是敲定了,其余服务如果须要使用推送服务,那么只须要引入jar包,而后按照我实现约定好的协议来配置它们须要的内容,包括推送方式,推送时间,推送通道等等。
入口敲定以后,我既定的下一层属于消息服务层;
如图2所画,整个服务层的架构是水平方向扩展的,因此可能咱们想到的第一点就是实现接口,来达到统一入口的目的。实现方式以下:
/** * 消息处理器 * @author lennon * 2017年3月8日 下午2:45:20 */ @Service public class MessageService { /** * 发送消息:调用消息的处理器 * @param senderMessageVO */ public String send(SenderMessageVO senderMessageVO){ IMessageServiceType iMessageServiceType = MessageServiceTypeHolder.getMessageServiceType(senderMessageVO.getMsgType()); if(iMessageServiceType != null) { return iMessageServiceType.messageHandler(senderMessageVO); } } }
首先定义一个消息服务类,考虑消息种类繁多:注册,登陆,充值,下单……因此,在作水平扩展的时候,有两个方案:
1、以继承体系来维护,即定义好消息基础类(抽象类,或者普通类),目的就是,水平扩展的子类只须要继承这个基础类一次(Java不容许多继承的方式),因此从目前分析的角度来讲,这种方式并无太大问题。
2、基础类不变,抽取公共接口,让全部消息业务类实现接口。这么作的方式就不存在说多继承的问题,而且水平扩展也毫无障碍。
从上边的代码均可以看出最后选择的是第二种方案,具体实现是:定义消息类型(以枚举类型,或者字符串常量),消息业务类实现了一个接口
/** * 消息类型 处理接口 * @author lennon * 2017年3月8日 下午2:46:07 */ public interface IMessageServiceType { public String messageHandler(SenderMessageVO messagevo); }
按道理将,如今的消息体系的调用状况应该以下:
http/https 请求进入推送服务中,映射到action层进行处理,action层注入了messageservice对象进行业务处理。messageservice注入holder对象。那么这个holder是用来作什么的?很明显,holder实际上是一个HashMap,经过注解与接口实现来扫描到全部消息类型业务处理的单例对象,而后保存在HashMap中。目的就是:经过messageservice就能够以SenderMessageDTO的对象中msgType精确的找到消息类型业务处理对象。引入这种holder的模式,是基于系统后续扩展(百分之百确认)来考虑,只须要实现接口,加入注解,不须要改变messageservice的代码,也不会影响其余的代码(基于开闭原则)。
在设计完消息服务层以后,须要考虑的是如何发送消息,应该说,如何简单的发送消息,而且从代码设计层面考虑如何毫无压力引入其余平台的设计。在starter引入以后,简单的调用starter来发送消息,实际上是很是简单,而且毫无压力的,那么为什么还须要作这样的一层设计?举个例子,假如如今有十个公众号,如何调用微信接口来推送消息呢?简单。调用分装好的starter接口,选择好哪一个公众号的信息就OK。
闭眼思考,过度简单的东西容易让人怀疑,因此尝试死磕本身吧(罗振宇最常提的一句话,死磕本身)。若是不作分装,这一层的代码放在消息服务层,是否是有十个公众号,就要有十层的if - else来断定公众号?有人说,statert上分装了微信调用的接口,能够在这一层做考虑,把公众号到 信息丢到starter去,而后直接调用。绝对不能够,斩钉截铁的肯定的告诉你,不能够!为什么?
请思考starter的优点!
starter不是推送服务专属服务,它有不少其余的事要作,因此它只能维护其余平台的接口对接,至于上一层如何调用,坚定不能嵌入starter中,一个臃肿的starter注定不是好的starter。
不谈其余可能有点糟糕的设计,聊聊本身的怎么写(站在如今的小子的经验以及认知的角度,小子认为不能更好了的设计),设计与消息服务的设计类似:
/** * 消息发送接口 * @ClassName: Sender * @author lennon * @date 20170307 * @since JDK1.8 */ @Service public class Sender { /** * 发送消息 * @param messageInfo */ public MessageWithObjectVO sendMessage(SenderMessageVO messageInfo){ ISenderType iSenderType = SenderTypeHolder.getSender(String.valueOf(messageInfo.getSender())); if(iSenderType != null) { return iSenderType.sendMessage(messageInfo); } return CommonMessageEnum.OPERATE_ERROR.getMessage(); } }
从消息发送器的设计角度来分析,sender经过holder来调用不一样的第三方平台对接的上层接口(第三方平台底层是starter封装)由于不想要直接调用starter接口,因此才会有了sender的这个设计与实现。后续添加各类starter的时候,messageservice容易形成各类改动,这是不容许的。之后再添加starter第三方接口,实现senderType而后来调用新的平台,新平台的引入对于消息服务不会产生直接影响。
这里举个例子来分析微信公众号的推送(中间经历了一次改动):
a、按照公众号来分类形式,经过注解+实现接口的形式能够将十个公众号分为十个类,而且后续假如增长其余公众号推送,依旧是注解+实现接口,根本不须要作很大的代码改动。(看起来的感受是完美的?)
b、经老大点醒,这里是属于严重的过分设计,仔细分析公众号之间的调用区别,其实仅仅只是key与srcret的不一样而已,若是为此去搞很是多类,那之后若是有一百个公众号,是否是要维护一百个类?想一想就打了个机灵。他提供的解决方案是:定义好一个数据结构,来存放全部的key与secret,说到底无非就是
Map<String, Map<String, String>> map;
即使是一百公众号,对于系统而言,不外乎就是往配置文件增长一百个新的key与secret。
说完消息服务,说完消息发送器,剩下的就是消息服务来对接消息发送器了。
这里存在比较大的问题是:不一样的消息可能须要根据不一样的需求来调用不一样的消息发送器。
有两种方案:经过为每一个具体的消息类型服务定义一个接口来分别实现第三方平台接口,由于不一样的业务须要作不一样的业务处理。另外就是不要搞得那么复杂不一样的平台接口就写不一样的函数,按照须要调用,类稍微庞大一点点而已。
……并无什么好的方案,悲剧。
整个的大概的设计过程相似酱着……
欢迎指教!