记一次从Rails至Golang的接口迁移

背景

我们有部分业务逻辑比较复杂的线上项目是由Rails框架快速开发而来的,但其中的部分API(Restful)代码需要服务于几十万同时在线的物联网设备。随着设备量的不断增加, 对这部分代码的性能需求就越来越高。 在高峰时段, 业务所在服务器节点经常出现Passenger队列拥塞的情况, 非常影响服务质量 -- 不仅仅是这个高频API业务, 而且也会影响其他低频API的业务。 所以需要把这部分代码单独提取出来, 用更高效的方式来实现。

迁移前面对的问题:

  • 需要拆分的高频API比较独立,并且基本是读数据库(极少写)
  • 需要做到无缝迁移, 不能中断线上业务的运行
  • API访问了大量的MySQL数据表,Rails的数据模型(Active Record)如何迁移
  • 如何测试 - 测试代码的迁移,以及线上测试

为何选择Golang

运行时高效,低内存。拥有活跃的社区,以及非常多的三方开源库。也考虑过使用Openresty(nginx + lua),运行效率更高。 但相对于Golang来说, Openresty的社区不够活跃, 也找不到可以快速替换Rails的数据模型的方法,一句一句的拼SQL,开发效率极低,代码维护也比较困难。

迁移步骤

确定需要使用的开源软件

这一步非常重要。 如果没有开源代码的支撑,什么都自己实现,要做到快速开发上线,是极不现实的。由于大量开源软件的存在,当前大部分软件的开发的前提之一就是评估和测试各种可能要用到的开源软件。

从我们的要迁移的项目来说, 需要一个HTTP服务框架,数据层方面需要访问Redis以及Mysql数据库。

  • HTTP服务框架
    Golang自带的net/http包已经足够好,但是最终还是选择了使用Gin(github.com/gin-gonic/gin),和net/http一样的轻量高效。从架构上来看,Gin类似于Rails使用的Rack中间件。
  • Redis客户端
    github.com/garyburd/redigo/redis,长久以来一直使用,习惯了。
  • Mysql Driver
    github.com/go-sql-driver/mysql,也没什么可选的。

由于迁移工作量最大的部分在数据模型上面,所以需要一个数据模型框架(ORM)能够支撑快速的开发。清单包含了Golang当前比较流行的ORM框架。

在gorm,gorp,upper/db与sqlboiler中,最终选择了sqlboiler。初步选择sqlboiler的原因是其文档中有这么一句“While attempting to migrate a legacy Rails database, we realized how much ActiveRecord benefitted us in terms of development velocity. Coming over to the Go database/sql package after using ActiveRecord feels extremely repetitive, super long-winded and down-right boring.” 并且sqlboiler的文档有一份看起来还不错的benchmark报告。由此可见开源软件的文档有多么重要,丝毫不逊于代码本身,甚至比代码更重要,毕竟大部分人是看脸的。

生成数据模型

通过sqlboiler命令行工具可以非常容易的将现有Mysql的数据表转换为数据模型(通过模板生成访问数据表的GO代码),使用命令前需要配置~/.config/sqlboiler/sqlboiler.tom,让sqlboiler能够访问数据库和数据表。

sqlboiler -w tbl1,tbl2,tbl3,tbl4,tbl5 mysql

该命令生成一个models文件夹, 里面包含了访问tbl1,tbl2,tbl3,tbl4,tbl5这些表的代码,以及测试代码。现在我们已经拥有了一个的Mysql数据接入层了。

使用这些生成代码的风格如下:

db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/example_db?parseTime=true")
if err != nil {
    panic(fmt.Sprintf("can not connect to mysql: %s", err))
}
db.SetMaxOpenConns(5)
db.SetMaxIdleConns(3)
db.SetConnMaxLifetime(3 * time.Minute)

boil.SetDB(db)
users, err := models.UsersG().All()
users, err := models.UsersG(qm.Where("age > ?", 30), qm.Limit(5), qm.Offset(6)).All()
shop, err := models.ShopsG(qm.InnerJoin("router on router.shop_id = shops.id"), qm.Where("router.sn = ?", sn)).One()

更多细节可以参见文档。

补全数据模型

前面提到,访问数据库的代码是根据模板生成的,功能很单一。在组合复杂功能的时候需要对模型进行扩展, 其实迁移数据模型大部分的工作量都在这里。sqlboiler文档中建议了三种方法。个人比较喜欢第3种风格,示例如下:

package modext

type ShopExt struct {
    M  *models.Shop
    ar *models.AuthenticationResource
    sn string
}

func (s *ShopExt) BusinessHours() (string, string) {
    if s.M == nil || !s.M.BusinessHours.Valid {
        return "", ""
    }

    h := string(s.M.BusinessHours.String)
    hs := strings.Split(h, "-")
    if len(hs) == 2 {
        return hs[0], hs[1]
    }
    return "", ""
}

...

对比下Rails的代码, 代码量明显增加(错误处理, 异常处理等), 通常一行Rails代码,用Golang重写需要十多行。

class Shop < ActiveRecord::Base
  ...
  def start_business_hours
    business_hours.to_s.split('-')[0].to_s
  end
  ...
end

sqlboiler的缺点

  • 只有显式设置外键的表,才会生成关联模型。我们现有Rails数据库,完全没有用到外键, 关联查询基本依靠手动的JOIN和多次查询,而不能像Rails可以设置belongs_to,has_one,has_many
  • 不支持查询缓存,如果某些数据在一次请求中需要多次查询,需要显式将它的引用缓存起来, 比如上面例子中的 ar *models.AuthenticationResource,以减少数据库查询。
  • 当前不支持在线对数据表做增加列的操作,我们自己打了个patch来解决这个问题。如果要使用这个补丁,可以将sqlboiler作为vendor package。

测试代码迁移

按Golang的风格写测试代码就可以了,利用Golang版本的fixtures可以快速迁移现有测试数据,但要注意它与Rails版本并不完全兼容。

线上测试和部署

对于迁移后的代码最好先做线上测试,再灰度上线,以确保旧代码和新代码的平稳过渡。如果前端部署了nginx作为API gateway,这个问题会非常容易解决。部署环境如下:

                                |--- node of old code
                        |-SLB1->|--- node of old code
                        |       |--- node of old code
SLB ---> API GW(nginx)--|
                        |       |--- node of new code
                        |-SLB2->|--- node of new code
                                |--- node of new code

首先,我们可以主动模拟客户端的请求同时访问SLB1和SLB2,完成AB测试。

小贴士:

对于JSON返回值的比较,可以使用reflect.DeepEqual,数组类型需要先排序再比较

比较直观的线上工具可以使用http://jsondiff.com。

其次, 可修改前端nginx的分发权重,做灰度上线。 比如, 设置10%的流量到新业务,如果一切如常,再逐步提高权重,直至全部流量导入新模块。

最后下线旧模块,完成切换。

迁移后的效果

本地压力测试显示,使用同样的redis和mysql配置,用10倍于Rails版本的流量对Golang版本进行压测, CPU占用约为Rails版本的40%, 内存占用仅为20%。

Golang版本上线后,如果处理每秒大约250的请求数(涉及大约10个关联表查询),总共耗费的CPU接近0.8个核, 内存100M,非常的环保。由于该功能从Rails服务中移除,剩余Rails代码在忙时也不会再报Passenger队列拥塞的告警。

结论

  • 负载能力大幅提升,资源占用大幅下降,完全符合我们追求高效的目标。
  • 首次迁移因为需要评估三方软件,需要写大量的Go代码来扩展数据模型,以及需要解决遇到的问题,所以比较耗费人力。
  • 考虑到数据模型是完全可以重用的,后续只需再补充扩展就可以了。所以后期的维护成本并不会高,应该只是接近或略大于Rails项目的维护成本。
时间: 2024-12-05 01:36:56

记一次从Rails至Golang的接口迁移的相关文章

登录凭证的方式

目前登录凭证的方式无非两种: 一个是通过服务器端的session,一个是通过浏览器的cookie. 简而言之:session和cookie 常规的是通过session 步骤: 1,用户在浏览器登录 2,后台鉴权,若登录成功,则把用户信息写入session,servlet自动生成JSESSIONID 返回浏览器; 3,浏览器把JSESSIONID 写入cookie 说明:cookie是浏览器的存储文件,存储的只是JSESSIONID,而不是用户信息; JSESSIONID 只是一个钩子,用户信息其

关于Scala的前景

近些年,基于JVM语言的发展风起云涌,Scala语言尤为抢眼.我们不妨从以下几个方面来介绍下Scala语言的发展前景: Scala语言的起源 Scala语言源自瑞士洛桑联邦理工学院,由奥德斯基教授2001年带领小组致力于Scala语言.标准库和编译器的开发.这里透露一个小花絮,奥德斯基教授创建Scala语言的一个主要原因是不满Java语言相对复杂的语法. Scala语言的发展现状 Scala语言最直接的好处是兼容Java,这就意味着可以无缝使用所有Java的类库和框架.Scala程序会被编译成J

Ruby on Rails的活动记录纵览

用Ruby on Rails来编写数据库管理系统是非常快速的.Ruby on Rails之所以有如此高的生产率,不 光是Ruby的语法灵活,而这一切在很大程序上是拜活动记录(Active Record)所赐.那么什么是活动记 录呢?如果用一句话来解释,那就是一个可以将数据映射成对象的框架(这有些类似于Hibernate).也 许你会说:"这有什么,现在这种框架多得是,Hibernate不是也非常强大吗?",但如果你用了Ruby on Rails(以下简称为RoR),你就会发现它和其它的

尝试用capistrano部署ruby on rails应用

部署网络应用服务是很麻烦的事情,安装程序,升级数据库schema,切换版本,重启服务.步骤越多,人工参与越多,越容易出问题. Capistrano是ruby on rails提供的部署方案,原名叫SwitchTower,多好记的名字,结果和人重了,换成现在这个,估计是为了赌气才起成这样,反正我再也不能拼对了.它集成了很多部署程序必须的步骤,借助ssh.版本管理系统(支持svn.cvs等等好几种)和rails的migration,只要做好配置,就可以在很大程度上实现部署自动化. Capistran

如何提高 Ruby On Rails 性能

大家总是说 Rails 好慢啊,这差不多已经成为 Ruby and Rails 社区里的一个老生常谈的问题了.然而实际上这个说法并不正确.只要正确使用 Rails,把你的应用运行速度提升 10 倍并不困难.那么如何优化你的应用呢,我们来了解下面的内容. 1.1 优化一个 Rails app 的步骤 导致你的 Rails 应用变慢无非以下两个原因: 在不应该将 Ruby and Rails 作为首选的地方使用 Ruby and Rails.(用 Ruby and Rails 做了不擅长做的工作)

Golang加密解密之RSA(附带php)_Golang

RSA加密算法简史 RSA是1977年由罗纳德·李维斯特(Ron Rivest).阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的.当时他们三人都在麻省理工学院工作.RSA就是他们三人姓氏开头字母拼在一起组成的. RSA加密算法原理 学过算法的朋友都知道,计算机中的算法其实就是数学运算.所以,再讲解RSA加密算法之前,有必要了解一下一些必备的数学知识.我们就从数学知识开始讲解. 必备数学知识 RSA加密算法中,只用到素数.互质数.指数运算.模运算等

golang(4)使用beego + ace admin 开发后台系统 CRUD

1,关于ace admin ace admin 是一个非常好的后台系统ui. 集成了很多的好东西.非常的方便开发后天系统,而且能很漂亮. 上面有一堆的例子.非常的漂亮. http://ace.jeka.by/ 之前还是收费的.后来在github 上面放了一个项目. 但是没有源码.是压缩之后的代码.而且,付费地址也不再了. 是一个过期了的模板 3 年前的了. github 地址: https://github.com/bopoda/ace 项目已经没有人维护了.也没有源码了,原来的购买地址都没有了

对优化Ruby on Rails性能的一些办法的探究_ruby专题

1.导致你的 Rails 应用变慢无非以下两个原因: 在不应该将 Ruby and Rails 作为首选的地方使用 Ruby and Rails.(用 Ruby and Rails 做了不擅长做的工作) 过度的消耗内存导致需要利用大量的时间进行垃圾回收. Rails 是个令人愉快的框架,而且 Ruby 也是一个简洁而优雅的语言.但是如果它被滥用,那会相当的影响性能.有很多工作并不适合用 Ruby and Rails,你最好使用其它的工具,比如,数据库在大数据处理上优势明显,R 语言特别适合做统计

[喵咪Golang(1)]Go语言开篇

[喵咪Golang(1)]Go语言开篇 前言 哈喽大家好啊!喵咪我今天又来开坑了,最近学习和使用了go语言了一段时间,也在组合一些好的组件编写phalgo开发框架.在想是不是可以来写一个套关于go语言的文章和一些好的组件的介绍使用,虽然自己也是半桶水但是我相信在编写的过程中能给自己很多收获,最好也能帮助大家了解和熟悉golang这门语言,话不多说那么就开始今天的go语言之旅吧~ 附上: 喵了个咪的博客:w-blog.cn phalgo地址:github.com/wenzhenxi/phalgo