我是如何快速帮助红星美凯龙搭建研发框架的?

3,021 阅读11分钟
原文链接: www.yangguo.info

这篇文章最早发布在聊聊架构微信公众号,结果引发了大家的众多评论,有对红星所做事情的疑惑也有对技术合理性的质疑。不论哪种情况,很高兴抛出了一个让大家还有兴趣聊两句的话题,还是那句话不求共鸣,只求博君一笑,不论是嘲笑还是微笑!

互联网和移动互联网的飞速崛起,对传统行业造成了破坏性的冲击和几乎窒息的压迫,迫使他们必须做出改变和应对。在这个都想拿到船票的时刻,技术的重要性已经可以媲美商业模式,因为再好的商业模式都离不开技术来落地,所以谁的技术领先,谁就有可能最快抵达终点。但是众所周知,传统行业的IT部都是支撑部门,在信息化建设的研发能力上是先天不足的。红星美凯龙是一家超过30年的老牌传统企业,虽然过去几年在信息化的建设和互联网的探索上投入了大量的资本,但是在技术方面的积淀收效甚微。随着红星的港股上市,再次开启互联网战略,成立红星悦家互联网集团,我们在吸收了过去经验教训的基础上,重新架构红星的技术体系。

研发团队组建

研发团队的组建一直以来困扰着诸多创业公司,对于红星来说也不例外。虽然红星在线下有较强的优势,但是对于互联网确实不折不扣的新兵,每一位候选人都带着深深的疑惑和担忧,让我们感到在吸引优秀技术人才上的无力感。在内部推荐、猎头、人事三管齐下的努力下,技术团队迅速壮大起来,因为我们都在努力传达一个讯息,那就是红星对互联网业务是认真的。不论是做人还是做事,就怕认真,我们在努力给技术人员创造舒适工作环境的同时也让技术人员拥有更多的话语权。任何事情都是两面性的,快速扩张的团队在磨合上出现了不小的问题,这不是最突出的,难点在突然发现出现了技术栈的分裂。我们在一开始就设计了基于Java的整套后端解决方案,由于种种原因,部分核心业务团队开始使用PHP。这个时候,我们开始意识到问题的严重性,当然并不是因为否定PHP是世界上最好的语言,而是我们发现这样会完全打乱我们早期的整体架构规划。对于一家创业公司,如果很多基础设施不能做到公用不能求同存异,研发成本和技术架构复杂度会陡然上升,吃过苦头的公司不在少数,当然这也是他们吹牛的资本。在技术管理层的强烈要求下,该业务团队很快切换了过来,不论是人员招聘还是新需求的研发上都进行了管控。红星技术团队初期遇到的磨合问题,跟所有迅速扩展组建的技术团队一样,大家来至五湖四海四海八荒,不论是技术决策还是业务需求,我们都本着对事不对人,所以很快就度过了那段最为动荡的时光。

新架构的设计与落地

对于重装归来的红星悦家,我们希望在技术方面能够夯实每一步,所以在技术团队组建之初就开始了架构设计。在新技术和新架构层出不穷的当下,在技术选型和技术方案的敲定上,始终秉承着不熟不用。没有成功案例、开源者的背景不好、社区活跃度低、团队接收度低的都不采用。我们要的不是屌炸天,而是稳定可靠易用,当然我们也不是独断专行,而是兼蓄并包,只要团队成员的选择能够通过架构委员会的challenge。整体架构大概如下:

红星架构

虽然我们内心保守,但毕竟新业务没有太多的历史负担,加上大家都来至行业内技术口碑都还不错的公司,所以在短短的两个月之内就攒出来了这样一套并不算太过时的架构。

监控

监控的重要性不言而喻,一开始我们就将监控摆在了首要位置,不论是容量规划、服务的Qos、代码质量管理,都需要监控作为支撑。在网络、负载、IO、流量等常规监控上,我们选择了Zabbix。在日志分析和报表展示上,我们选择了Kibana,它是我们日志平台的核心组件。在业务逻辑的Metrics上,我们选择了Graphite,代码端我们使用了Dropwizard的Metrics库再配合metrics-spring,实现了埋点的最小侵入性。Graphite中的Metrics数据在Grafana进行Dashboard展现,同时Graphite的REST接口提供数据给Zabbix进行业务Metrics报警。当某个核心业务逻辑的Rate、响应时长超过预定阀值时就会触发。在服务治理上,我们选择了Dubbo,在调用链的追踪上,开始我们选择了自研,代号为Dragon,通过Dubbo的Attachment将TraceId和Parent SpanId带给RPC下一跳,就能将整个调用链给串起来。Dragon的界面如下,从截图中能够清晰的了解Dragon传达的信息,线上的数值表示网络耗时,红色表示异常,绿色表示正常。

dragon1

dragon2

dragon3

dragon4

同时我们修改Log4j MDC将TraceId和日志进行了绑定。由于Kibana对日志的切割和索引,我们通过TraceId就能够很轻松的将一次跨域多个服务的Trace所打印的日志给搜索出来。虽然Dragon看起来不错,但是我们发现Pinpoint做的更多更好,并且通过Java agent侵入性几乎为零,所以最终我们放弃了自研的Dragon。调用链的追踪可以认为是微观粒度的,是分析某一个请求,宏观层面我们还是依靠Metrics和Dubbo自带的monitor。由于Pinpoint有丰富的插件,所以收集的信息非常多,这也是我们选择它的主要原因。

pinpoint2

pinpoint3

pinpoint4

技术细节

对外服务层可以理解为展示层和应用层的结合,七层的负载均衡我们使用的是Nginx。在该层中我们进行应用逻辑的协调和拼装,这层绝大部分REST服务是无状态的,对于需要上下文的逻辑,会话也维持在这层,这一层非常薄。核心逻辑层维护着业务逻辑和业务对象的状态,该层是领域的体现,也是服务治理的核心层。这层的复杂度一般是最高的,因为领域建模、数据持久化、业务流转等都在此完成。为了降低复杂度提高可维护性,这层的项目要严格进行分层架构。基础服务层比较简单,就是一些常规的基础服务,例如:短信网关、邮件网关、全局唯一ID生成器、Pinpoint、推送服务等等。

为了尽可能让发布简单,不论是WEB服务还是Dubbo服务,我们都使用JSW打包成Zip包来发布,对应的maven插件为appassembler-maven-plugin。WEB服务我们基于Spring Boot,通过内嵌Tomcat来实现Zip的创建。配置中心我们选择了Disconf,不但让代码和配置分离,并且让我们一个包能够运行在多套环境。使用Disconf得注意配置文件的加载顺序,特别是在Spring Boot项目中,WEB配置优先于Disconf相关Bean加载,可以使用@PropertySource通过URL来引入Disconf的配置。Swagger用来生成REST接口文档,代码即文档,让接口的文档维护和测试更加容易。随着开源组件的引入越来越多,要迅速搭建一套系统越来越不容易,所以我们抽象出了多种类型的Archetype,各种基础代码和配置都全部自动生成,降低基础组件的使用的成本。在核心库的版本管理上,我们通过maven的parent来管理,从而加强约束将冲突降低最低,也为统一的版本升级提供了便利。在服务降级和熔断方面,我们选择了Netflix OSS中的Hystrix,不过在我们还是使用Spring Cloud Netflix来集成,不过这个还没有大规模推广。

读写分离作为最基础的需求,我们没有选择Proxy,而是在Mybatis和Spring的基础上做了二次封装。实现的原理就是事务内的所有SQL语句和没有事务的insert、update、delete走主库,select走从库,但如果select要强制路由到主库,可以在SQL语句头上加/*master*/来实现路由。通过重写Spring的AbstractRoutingDataSource类来实现动态数据源,在事务管理上通过重写Spring的DataSourceTransactionManager来实现事务对数据源的选择,然后再动态代理Mybatis的SqlSession在InvocationHandler的实现中进行路由,如果发现事务已经开启就直接走主库,如果没有事务再进行SQL语句解析从而来选择主从库。网上也有大量的实现,不过都比较粗糙,一定要大量测试,并且弄明白里面所有的门门道道。在分库分表方面,我们选择了 sharding-jdbc,不过目前还没有用到核心的业务逻辑中,在Dragon的研发时,抱着测试该库的目的,没有使用HBase,选择使用它进行了分库分表,目前使用下来还是不错。当涉及到分布式事务时,我们使用Spring的best efforts 1pc来尽量保证数据的一致性。全局ID生成器的用途广泛,特别是在分库分表业务中,我们选用了twitter的snowflake

缓存和分布式Session是每一个可以水平扩展系统都不可能避免的需求,Spring作为技术方案的基石,Spring的Cache和Session自然也作为了我们的首选方案。虽然Spring作为Pivital的看家项目,但在使用中也给我们带来了一些麻烦。Session需要使用Redis的命令,已经上线运行的Twemproxy不支持,所以我们只好切换到Redis的sentinel集群。Session由于发布不久,功能不是很强大,所以我们也做了二次扩展,并且修复了超时设置的小bug。虽然使用起来并不是很完美,但是基于对Pivital的信任,我们还是坚定紧跟Spring的发展。

在基础设施选型和建设上,不论是MHA的Mysql、FastDFS+GraphicsMagick、RabbitMQ的Mirror Queue、Sentinel的Redis还是大数据相关的Hadoop、Hbase都是很成熟的解决方案,我们的主要精力都放在了运维方面。对于基础设施,我们的原则是尽可能少,因为运维成本很高,要运维好更难。

安全放在最后讲,并不是因为安全不重要,而是大家都知道很重要,要做好却很难,特别是红星悦家这种刚起步的公司。在内部没有安全团队,并且安全意识比较薄弱的情况下,我们通过内部培训和外部合作来共同提高系统安全。过去一年,我们实现了全站的HTTPS,在账号体系的设计上为了简化动态盐和慢哈希的使用,我们使用了Spring-security中的BCryptPasswordEncoder。XSS防御使用OWASP提供的ESAPI提供的函数进行编码。在CSRF方面,一是遵循Restful风格,二是通过一次性Token和Refer检验;SQL注入防御上就只使用了PreparedStatement,避免了绝大部分攻击。不过在业务驱动的重压之下,虽然安全很多时候被放到了最后一位,不论是内部的理念灌输还是外部渗透测试,我们都一直在努力。

写在最后

一家之言,不登大雅之堂,仅供大家参考。悦家作为刚起步一年的初创公司,虽然技术架构上有了一个简单的雏形并且没有经历过大流量的考验,但是从无到有从粗放到精细的架构和技术变迁正是技术的最大乐趣所在,所以我们也诚邀有志之士共建红星悦家的技术体系,特别是精通容器/服务编排相关技术的大牛。非知之艰,行之惟艰,与君共勉!