
车家号作为一个PGC平台,聚合了全网大多数汽车行业的专家及意见领袖,每天为用户提供大量的汽车类优质内容。用户日浏览量在几千万级,后端的接口也承载亿级的日访问量。
车家号WEB、API、后台管理等系统采用 .net4.5进行开发。一直以来为用户及调用方提供了稳定的服务。由于其只能运行于Windows平台上,其扩展及迁移的能力受到了极大限制。需要将车家号业务转移到Linux平台,可以进行更为灵活的运维,并且具有容器化能力。
方案之一,用java重写,这个对于一个已经维护多年的有大量的业务逻辑在里面的系统来说,工作量是相当大的。只重写接口相对简单,但如果将PC 及后台管理进行重写工作量极大。或前后分离NodeJS 方式,这样也会给前端开发及测试带来巨大大的压力。还有更重要的一点,需求不断的提出来,还要不断有新特性加入进来,如果维护两套异构语言的系统,会给业务及系统的稳定性带来很大的风险。
另一个方案,项目从.net framework 升级为dotnet core,基本上语法方面不需要变化,业务代码不需要变化,新需求的加入,单向同步代码即可。同时,.net core 是跨平台的,另外,可以使用docker进行弹性扩容缩容。.net core 在性能方面从官方数据及后来的一些测试看的确有了明显的提升。
所以,基于这些主要原因,我们选择了方案二,.net core ,并且容器化。
|
时间 |
天数 |
人员 |
人日 |
|
2月21日-2月27日 |
5 |
1人 |
5人日 |
|
4月1日 -5月13日 |
27 |
0.5人 |
13.5人日 |
|
5月14日-5月24日 |
9 |
1.5 人 |
13.5人日 |
|
5月27日-6月6日 |
9 |
2.5人 |
22.5人日 |
|
合计: |
54.5人日 |
升级前期,因存在尝试、学习等因素,人员投入较少。后面调整重心,加强人力投入到升级工作中,快速推进升级上线。
公司范围内,我们是第一个将较大项目进行.net core 升级的团队。没有太多dotnet core的积累,无成功案例可借鉴,同时大量工作也用在了构建基础设施。未来类似规模项目进行升级,时间及人力投入方面会大大减少。

从5.25日开始逐步灰度,tp99也从100ms逐步降到45ms左右,到发稿为止稳定保持于45ms以下。
接口,WEB,后台管理等从外观及行为上没有的变化,做到用户及调用方无感知。
由于core 可运行于Linux上,可以借助于之家云平台,在容器中弹性扩容。
在业务需求不断变更的同时,在第一次全量完成代码同步后,严格从原有业务主干(.net framework + TFS)单向同步到新版本业务主干(.net core+ git),保证业务行为的一致性,并不断同步发布新版本进行灰度,两个版本的行为保持一致性,升级过程无缝切换。如下图所示:

由于.net core兼容性及某些功能的缺失,需要使用重新编译或编写新组件进行兼容,这些作为基础组件,需要进行更多的单元测试及测试项目中的测试,保证其它正确性,稳定性及兼容性。
.net core 已经无法使用.net framework的组件,所有原有组件都需要重新编译适配。(见组件列表)
Asp.net core 中 有些功能已经不再支持,需要重新适配。见附兼容组件
自底向上,逐个项目进行升级。
卸载所有项目。
从基础的底层的类库进行升级,通常其依赖着准备阶段中提到的各种组件。
底层公共项目,通常都可以顺利编译通过。
对于实在无法编译通过的项目,根据其作作决定是否先注释掉,并用异常抛出的方式先行替代(throw new NotImplementedException()),防止未来的误调用或忘记实现。
根据原有项目间依赖并系,逐步上向。
先将接口项目编译通过,由于其不依赖UI等,比较容易通过,替换兼容,运行。
WEB 项目
Asp.net Core 与 asp.net 项目结构不同需要进行调整。
批量替换命名空间
编译,如果有不能通过的,看是否能快速解决或重要性,决定是否跳过。
经过上面的操作,项目基本可以运行,完成升级过程的第一步。
Core 版代码使用git 进行管理,从 TFS某个时间点进行快照,并Push到git库。作为基础,进行升级改造,对组件及代码级别的不兼容进行调整。后面新业务也是在这个版本的基础上进行增量同步。
后面的需求还在继续在TFS上提交,采用由TFS到Git单向同步的方式,TFS上的新需求由负责的开发人员同步至git 上。这种情况将持续到全量上线。
这个阶段经历了大致两个星期。
开发自测:开发人员 自动用例生成工具,生成测试用例,使用JMeter 测试,大规模的覆盖,快速发现并修复问题。另外,使用自动化接口对比工具,完成全量接口对数据对比,完全节省了人工对于接口的重复测试,快速发现并定位问题。
测试人员测试:对于一些重要场景,如作者发现文章及与财务相关的功能,需要人工方式进行二次验证。
在灰度过程中,对错误日志、访问日志、性能日志等多维度进行监测,保证系统负载在可控范围内,并且发现有异常及时修复或回调流量。
系统上是先从PC开始的,等PC上线稳定以后,再上线接口,服务,对外接口等其它系统。
PC:在构成方面虽然比较复杂,但它前面有CDN 及SCS,万一出现问题,来得及进行补救。
接口:流量大,但结构相对简单,如果出现问题,影响面比较大,所以,在PC成功切换后,有了经验及信心,再进行切换。
后台管理:运营使用,有问题可能快速反馈,并能快速修复。
服务:核心业务已经上线,服务,也要跟着上线了。
Open接口:由于流量及用户都不多,并且大多数是Post接口,需要更充分的测试,选择最后上线。
6月6日 最终完成所有系统的全量的上线。
在最初制订方案的时候,考虑到极有可能出现意外,导致新系统有不可预知的问题。需要具有随时切换回旧系统的能力,当然,这个也是灰度的基础。
保证可以切回,要保证:
(1)最重要的,代码要保持同步。
(2)同步发布,新旧两个环境都要保持最新,同步。
(3)在完成稳定迁移,旧的环境一直保持高可用。
在本次迁移过程中,由于Core所在集群故障,紧急切回旧系统,这样避免了一次非常严重的事故。
升级过程中某天,系统的流量突然异常。系统开始变慢,但还能勉强顶住。
随后做出了扩容决定,从8个实例,直接扩容为30个。接下来,就是连锁反应,WEB,API,相继告急,性能极度下降。数据库不断报警。
对于我们从传统的单体应用进行迁移至Docker, 不要急于扩容,一定要看到你的系统是否有单点,这个单点是否可以避免。
本次升级,测试人员投入大概两个半天,进行测试。剩下的都由开发人员进行自测。使用工具进行大规模的覆盖。
测试工具,对比工具等都根据实际业务进行自主开发。在未来也可以重复用于其它或日常工作中。
接口对比及批量对比测试:

自动生成用例及在JMeter中测试:

.net core 在 Linux下是没有相应的访问日志,另一个途径也可以从Nginx获取访问日志,但它与我们现有日志分析及监控系统不兼容。
基于.net core 中间件,对访问进行拦截并记录日志。使用Udp 方式发送至日志收集系统。
这样对系统的访问数量及处理性能通过报表有直观的了解。

.net core 由于是一个新的生态,没有类似于windows下的性能记数器,或Java的Jmx这种工具对性能进行监控。所以,我们需要自己来做一套监控解决方案。
(1) 做SDK,收集系统运行性能指标。
(2) 提交至Logstash
(3) Logstash 写入 ES
(4) 通过Grafana进行图形化展示。

从Java中学习到Spring boot 的Cache注解,按其思路实现注解并应用于.net core , 一个注解,省去大片代码。
代码中使用注解示例效果:
使用注解前后效果TP 99对比:

由于我们的系统极度依赖于缓存层,并且服务会更新缓存,新旧的系统如果可以使用同一套缓存,使用同一个Key,那可以减少很多必要的麻烦。
我们重新编译了ServiceStack.Redis 。保证了缓存的行为的一致性,这为我们节省了至少三分之一的工作量,使得新旧各系统中都是一致的缓存,一致数据。
在当前系统中用到了多个框架,Json.net , FastJson , MS Json , 在代码调试过程中,新旧系统使用返回数据不一致,也发生各种序列化异常,经过分析,原来一旧代码与新代码使用不同的Json序列化方式。
在系统迁移过程中,由于多处使用了这三个框架,只重构掉了一种MS Json,现在还有 Json.net , FastJson 两种在运行着。
后面系统稳定后,应该仅保留一种序列化框架。
当时转core一个原因就是想使用 docker 进行动态扩容、缩容的特性。本次升级过程中,由于所在机房资源不足,docker集群不稳定,迁移机房暂时不具备条件等原因。只能将部分业务,如后台,服务,Open接口等运行在docker上,其它随时压力较大的如WEB ,接口等运行在VM上,在迁移机房后,再将大流量产品进行迁移。
过程中不要把问题放在一个点上,先保证系统可以整体运行,再去解决某个点的技术问题,为团队树立信心,让每个人都可以看得到它是可运行的。
在本次升级过程中,让我们感受到升级.net core很小的学习曲线,快速入手,性能的提升也是一种惊喜。还有,大多数常用的开源组件对core提供了支持,改极少或不用改代码即可使用。
同时,升级过程中,积累了经验,产出了通用性功能及组件,对于其它系统的升级具有指导和借鉴意义。
还有,这个成功的案例,也给有升级想法的团队,树立信心,使他们可以更快迈出这一步。
升级过程发现了一些问题,促使我们对于架构进一步的思考,在后面通过对架构的优化使系统具有更好的扩展能力。
dotcoreActionMessageSdk 发送HttpMessageQuene 组件
dotNews.ServiceStack.Redis Redis连接组件
dotcoreAutohome.DataHelper Sqlserver连接组件
dotCoreCasClient SSO 组件
dotcorejobclient 分布式Job组件
dotcoreNews.Comm 通用类型组件
dotcoreNews.Common.Extends.Log ELK 日志组件
dotcoreNews.Common.Extends.Upload 图片上传组件
dotcoreNews.Common.UploadObject 文件上传组件
dotcoreWebdiyer.MvcPagerCore ajax分页组件
dotcoreHttpContextCurrentCore组件 asp.net core 已经没有HttpContext.Current, 本项目实现 System.Web.HttpContext.Current 功能,最大程度兼容旧代码,减少重写量
core 不再支持 WCF ,未来版本是否支持社区还在争论中,我们采用重写路由的方式进行兼容。
Html.Action 不再支持,重写这个方法,为了区别更别为Html.RenderAction,参数功能不变。
EF Repository 模式代码,基于 sqlSugarCore 改写。保持调用外观不变。
Microsoft.Practices.ServiceLocation 不再支持,使用 Castle.Core
using System.Web.Mvc,不再支持, 使用 usingMicrosoft.AspNetCore.Mvc;
Areas 的支持不再,使用 router
Ashx 一般处理程序不支持,改用 router兼容
[assembly: PreApplicationStartMethod] 不支持,改用 StartUp.cs中调用
HTTP handlers and modules 改为 middleware
自制应用程序指标监控
自制批量用例自动生成
自制指量接口比较工具
Jmeter
作者|
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
我最近决定从我的系统中卸载RVM。在thispage提出的一些论点说服我:实际上,我的决定是,我根本不想担心Ruby的多个版本。我只想使用1.9.2-p290版本而不用担心其他任何事情。但是,当我在我的Mac上运行ruby--version时,它告诉我我的版本是1.8.7。我四处寻找如何简单地从我的Mac上卸载这个Ruby,但奇怪的是我没有找到任何东西。似乎唯一想卸载Ruby的人运行linux,而使用Mac的每个人都推荐RVM。如何从我的Mac上卸载Ruby1.8.7?我想升级到1.9.2-p290版本,并且我希望我的系统上只有一个版本。 最佳答案
我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or
我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195
我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正