阿里云rds数据库迁移实战(多数据源)

时间:2022-12-30 07:59:17

  由于某几个业务表数据量太大,数据由业务写,数据部门读。

  写压力不大,读却很容易导致长时间等待问题(读由单独系统进行读),导致连接被占用,从而容易并发稍稍增长导致全库卡死!

  于是,就拆库呗。

  业务系统拆分就不要做了(微服务化),没那工夫。

  直接原系统拆两个数据源出来,对某几个高压力表的写就单独用这个数据源,从而减轻压力。

所以,分库工作就变为了两个步骤:

  1. 两个数据源读写业务;

  2. 将新数据库写动作同步回读库;

  再由于方便性,数据库也是使用阿里的rds数据库,一个变为两个!

  代码上做两个数据源很简单,尤其是在原有代码就写得比较清晰的情况下;

如下是使用springboot和mybatis做的多数据源配置:

  1. 配置多个数据源类;

  2. 启用mybatis多数据源,加载不同配置bean;

  3. 根据扫描路径区别使用的数据源;

  4. 根据扫描路径将需要拆分的表与原表区别;

  5. 测试时可使用同同机器上多库形式运行,上线后为多实例同库运行;

  6. 验证功能可用性;如有问题,及时修改;

  具体配置如下:

// 原数据源配置
@Configuration
@MapperScan(basePackages = MainDataSourceConfig.SCAN_BASE_PACKAGE, sqlSessionFactoryRef = "sqlSessionFactory")
public class MainDataSourceConfig { public static final String SCAN_BASE_PACKAGE = "com.xxx.dao.mapper.main"; /**
* xml 配置文件扫描路径
*/
public static final String SCAN_XML_MAPPER_LOCATION = "classpath:mybatis/mappers/mysql/main/**/*Mapper.xml"; //jdbcConfig
@Value("${jdbc.main.url}")
private String jdbcUrl;
@Value("${jdbc.main.driver}")
private String driverName;
@Value("${pool.main.maxPoolSize}")
private int maxPoolSize;
@Value("${jdbc.main.username}")
private String jdbcUserName;
@Value("${jdbc.main.password}")
private String jdbcPwd;
@Value("${pool.main.maxWait}")
private int jdbcMaxWait;
@Value("${pool.main.validationQuery}")
private String validationQuery; @Bean(name = "druidDataSource")
@Primary
public DruidDataSource druidDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl(jdbcUrl);
ds.setDriverClassName(driverName);
ds.setMaxActive(maxPoolSize);
ds.setUsername(jdbcUserName);
ds.setPassword(jdbcPwd);
ds.setRemoveAbandoned(true);
ds.setMaxWait(jdbcMaxWait);
ds.setValidationQuery(validationQuery);
return ds;
} @Bean(name = "dataSourceTransactionManager")
@Primary
public DataSourceTransactionManager dataSourceTransactionManager(){
DataSourceTransactionManager dm = new DataSourceTransactionManager();
dm.setDataSource(druidDataSource());
return dm;
} @Bean(name="sqlSessionFactory")
@Primary
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] mapperXmlResource = resolver.getResources(SCAN_XML_MAPPER_LOCATION);
sqlSessionFactory.setDataSource(druidDataSource());
sqlSessionFactory.setMapperLocations(mapperXmlResource);
return sqlSessionFactory.getObject();
} } // 新数据源配置,仅仅改了下配置名,但是还不得不另一个配置类
@Configuration
@MapperScan(basePackages = ExtraDataSourceConfig.SCAN_BASE_PACKAGE, sqlSessionFactoryRef = "sqlSessionFactoryExt")
public class ExtraDataSourceConfig { public static final String SCAN_BASE_PACKAGE = "com.xxx.dao.mapper.ext"; /**
* xml 配置文件扫描路径
*/
public static final String SCAN_XML_MAPPER_LOCATION = "classpath:mybatis/mappers/mysql/ext/**/*Mapper.xml"; //jdbcConfig
@Value("${jdbc.ext.url}")
private String jdbcUrl;
@Value("${jdbc.ext.driver}")
private String driverName;
@Value("${pool.ext.maxPoolSize}")
private int maxPoolSize;
@Value("${jdbc.ext.username}")
private String jdbcUserName;
@Value("${jdbc.ext.password}")
private String jdbcPwd;
@Value("${pool.ext.maxWait}")
private int jdbcMaxWait;
@Value("${pool.ext.validationQuery}")
private String validationQuery; @Bean(name = "druidDataSourceExt")
public DruidDataSource druidDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl(jdbcUrl);
ds.setDriverClassName(driverName);
ds.setMaxActive(maxPoolSize);
ds.setUsername(jdbcUserName);
ds.setPassword(jdbcPwd);
ds.setRemoveAbandoned(true);
ds.setMaxWait(jdbcMaxWait);
ds.setValidationQuery(validationQuery);
return ds;
} @Bean(name = "dataSourceTransactionManagerExt")
public DataSourceTransactionManager dataSourceTransactionManager(){
DataSourceTransactionManager dm = new DataSourceTransactionManager();
dm.setDataSource(druidDataSource());
return dm;
} @Bean(name="sqlSessionFactoryExt")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] mapperXmlResource = resolver.getResources(SCAN_XML_MAPPER_LOCATION);
sqlSessionFactory.setDataSource(druidDataSource());
sqlSessionFactory.setMapperLocations(mapperXmlResource);
return sqlSessionFactory.getObject();
} }

  然后,将需要分离的表操作转移到相应的包路径下,即可实现多数据源操作了!

而多数据源配置对于基于xml配置spring来说,可能更加直观更加简单,甚至xml文件都不用分离!

    <!-- 原数据源配置 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.main.url}" />
<property name="username" value="${jdbc.main.username}" />
<property name="password" value="${jdbc.main.password}" />
<property name="maxActive" value="${jdbc.main.maxActive}" />
<property name="maxWait" value="${jdbc.main.maxWait}" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
<property name="mapperLocations">
<list>
<value>classpath:mybatis/mappers/mysql/main/**/*Mapper.xml</value>
</list>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.xxx.dao.automapper.main" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean> <!-- 第二个数据源的配置 -->
<bean name="dataSourceExt" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.ext.url}" />
<property name="username" value="${jdbc.ext.username}" />
<property name="password" value="${jdbc.ext.password}" />
<property name="maxActive" value="${jdbc.ext.maxActive}" />
<property name="maxWait" value="${jdbc.ext.maxWait}" />
</bean>
<bean id="sqlSessionFactoryExt" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSourceExt" />
<property name="configLocation" value="classpath:config/mybatis-config.xml" />
<property name="mapperLocations">
<list>
<value>classpath:mybatis/mappers/mysql/ext/**/*Mapper.xml</value>
</list>
</property>
</bean>
<bean id="transactionManagerExt" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceExt" />
</bean>
<tx:annotation-driven transaction-manager="transactionManagerExt" />
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryExt"/>
<property name="basePackage" value="com.xxx.dao.automapper.ext"/>
</bean>

  所以,还是那句话:不是所有听起来好的东西就一定是好,在这里转换为不是所有听起来方便的东西用起来就一定方便!

代码ok后,还剩下一个问题:独立后的写动作同步问题!

  如果是自行搭建的mysql服务,我们很自然地考虑使用binlog同步(主从)来做!具体配置方法也不复杂,自行查找资料即可!

  如果使用阿里云服务,则不是binlog那样的配置了(但其实质仍然是对binlog的订阅写)。不过倒也都是页面操作!(网上不一定好找资料,但是官网上一定是最全的)

要进行数据库迁移,大体步骤分为:

  1. 先要将现有数据迁移到新实例上;

  2. 将部分新数据写入新实例(部分数据仍直接写现有实例,做到业务剥离的同时还可以减少数据同步的开销);

  3. 将新实例数据同步回原实例库;

DTS服务(数据传输服务),专门用于做数据迁移和数据同步!

  其打开方式为:

  1. 自然是花钱买服务了,买好后才能进入操作页面;这里服务要分两个:一是新实例rds数据库,二是新实例同步回原实例的同步服务;

  2. 设置ip白名单,以使mysql可访问;

  3. 创建高权限用户,如root,以使后续可高权限操作mysql,同步时可使用该账号或者另用一个普通读写账号;

  4. 将全量数据刷入新实例,这里可以选择阿里云免费的数据库迁移服务,也可以自己将数据dump下来,然后自行导致新库;(不过服务既然是免费的,那为啥不用呢!)

  5. 设置单向同步,从新实例到原实例,此时是不会有数据同步的,因为没有新写入;

  6. 数据刷入,同步设置完成后,就可以发布新代码了,此时最好将前端入口停止,否则可能出现数据错乱问题;

  7. 发布代码后,需要自行验证。此时,先选择一台机器进行验证,可以选择两种方式验证:一是自行调用关键接口进行验证;二是将该机器绑定eip外网,使用该外网进行页面访问验证(更完整的验证);验证的方向主要有两个:1. 接口正常响应,没有错误发生(此处应该要有监控设施,否则只能凭感觉);2. 数据有没有正常同步(一般同步都是秒级的);

  8. 将代码发布到集群中,观察各机器运行情况!此处主要查看数据库连接情况,是否存在连接失败情况,应用监控是主要手段,也可以通过mysql的show full processlist; 进行查看应用连接db情况;

  9. 观察正常后,此时可以将前端应用入口打开,此时如有条件,应限制ip访问,使变更进行充分测试无误;

  10. 一切无误后,完全开放访问服务;监控用户数据,迁移完成;

至此,整个迁移就完成了,其实思路是很简单的,关键是要小心操作。一个不小心的操作,就可能带来很大的隐患,毕竟,数据无小事!请保持对数据和代码的敬畏!

临了临了,附几个操作的贴心小技巧,避免入坑!

  1. 买rds数据库时,尽量买与原实例相同的区域(大区和可用区都相同),否则后期在做同步的时候会花更多的钱,因为跨区的网络通信会让你支付更多;

  2. 新实例数据库容量可以稍微降配以节省钱,因为毕竟你是将原来的部分功能拆分出来的,没必要一开始就为全部将来买单;

  3. 买同步服务时,注意几点:1. 按量计算(按小时)比预付费(包月)更贵,但是也更容易订制化,如果仅仅操作两个rds间同步,且短时间内不会下线服务,则建议选择预付费包月形式;2. 将区域选择正确,比如同区域同步将更便宜;3. 能单向同步就不要双向同步了,便宜的同时,也减小了误操作带来的影响;4. 同步性能一般小流量选择small即可,高配的同步用不上关键是贵;

  4. 同步服务尽早开启,但是后期对于账号密码的变更,一定要及时更改同步配置,否则将带来数据一致性问题;(人工发现往往较晚,尽量设置监控报警)

  5. 数据库白名单中,需要加入阿里云数据传输服务的白名单,否则无法检查数据库响应性及同步作业;

  6. 选择同步对象时,尽量以库作为单位!如果选择以表为同步单位,将存在后续新增表时,不会同步回原实例情况。如果实在不能以库作为单位,在后续迭代时,一定记得添加此处同步表;(关注点太多,麻烦)

  7. 后期做数据变更时,注意操作对象所属实例,别一顿操作猛如虎,然后没什么卵用,因为我们只是选择了单向同步;

  8. 自己可以不定期地做checksum检查,以确认同步功能正常工作;(checksum table test)

  9. 代码上分库一定要做准确了,因为这里可能是一定时间内的唯一可信参考资料;(简单但是关键)

最后,我还想说下使用别人服务和自己动手的一些个人感觉:

  1. 使用自己搭建的服务,最大的好处在于可以做任意的改变不受限,而且不需要付出额外的可见费用;

  2. 使用自己的服务的可能坏处是:如果你不是这方面的专家,往往会被自己埋下的各种坑难住;遇到问题没能力处理;考虑方面不周全,容易引发安全问题;对未来的因素没办法考虑,使后期运作困难;如果你是专家,那多半这些都不是事儿;

  3. 使用别人的服务,最大的好处就是简单易用,且有人维护;这些服务往往都是一路填坑过来的,时间越久往往越可靠(百年老字号最佳,哈哈);安全性、扩展性、性能调优、高可用等等;

  4. 使用别人的服务,其坏处主要是钱的问题,这个自不必说。还有个不是钱的问题的坏处,那就是你不能随意订制你想要的功能了,你的能力被别人限制住了,这个可能促使你转场到自己提供服务;另外,各家提供的服务都不一样,不像自己搭建的服务,网上会有各种资料可查,所以有一定的学习成本,具体取决于官方设计与官方文档的完整性(当然一般都会很简单);其实还有一个,就不说了,懂的都懂;

好了,借着数据库迁移的小事,扯了这些淡。只当是抛砖引玉了!欢迎指教!

阿里云rds数据库迁移实战(多数据源)的更多相关文章

  1. 【故障公告】阿里云 RDS 数据库服务器 CPU 100&percnt; 造成全站故障

    非常非常抱歉,今晚 19:34 ~ 21:16 园子所使用的阿里云 RDS 数据库服务器突然出现 CPU 100% 问题,造成全站无法正常访问,由此您带来了很大的麻烦,请您谅解. 故障经过是这样的.1 ...

  2. 云上的芯脏病:奇怪的阿里云 RDS 数据库突发 CPU 近 100&percnt; 问题

    最近遇到了奇怪的阿里云 RDS 数据库突发 CPU 近 100% 问题,遇到了3次. 第一次是10月12日(周六)凌晨 3:24 负载极低的时候开始出现,早上发现后进行了主备切换,恢复了正常. 第二次 ...

  3. 阿里云 RDS 数据库又发 CPU 近 100&percnt; 的&OpenCurlyDoubleQuote;芯脏病”

    最近云界发生了2件事,一件是大事,一件是小事,大事是阿里云与微软合作推出了开放应用模型 Open Application Model(OAM),小事是由于微软 SQL Server 在阿里云上水土不服 ...

  4. 阿里云RDS数据库改造迁移方案

    1. 改造原因 (1) 由于历史原因, 本应该是同一个库的表分布在两个数据库中,需要对这两个库进行合并. (2) 已有的数据库性能无法满足业务的增长需要, 查询卡,慢问题突出. (3) 当前自建Mys ...

  5. 阿里云RDS数据库备份同步到自建库方法(SHELL脚本)

    一.背景: 由于阿里云RDS生产库每天都需要备份且拷贝到自建读库,而如果使用阿里云的自动拷贝到只读实例, 费用太高, 故采用自编写同步脚本方法实现. 二.前提: 1). 已开通阿里云RDS, 且开启定 ...

  6. 【故障公告】阿里云 RDS 数据库突发 CPU 近 100&percnt; 引发全站故障

    今天晚上9点我们收到阿里云的告警通知: [阿里云监控]华东1(杭州)-云数据库RDS版<cnblogsdb> [instanceId=xxx] 于21:00 发生告警, 前往诊断 CPU使 ...

  7. 阿里云RDS数据库备份文件恢复到本地mysql数据库

    一.安装mysql和xtrabackup  (1)安装mysql 因为RDS是5.6版本,所以我们本地的mysql数据库要与RDS版本对应. rpm -ivh http://repo.mysql.co ...

  8. 阿里云RDS数据库sql server 导入数据并添加作业小结

    在阿里云购买ECS服务器和RDS数据库时,要注意网络类型要一致,最好都是VPC,否则ECS不能在内网访问RDS,只能从外网访问:在RDS控制台左侧,数据库安全性的IP白名单中添加ECS外网IP:在数据 ...

  9. 阿里云RDS数据库到期实例被清除,别急着哭(阿里没有删库跑路),或许还有一线生机

    阿里资源到期未续费,数据保存期限: ECS实例的保存期是15天. Redis实例的保存期是7天. RDS实例的保存期也是7天. 过期当天会收到一条短信: [阿里云]尊敬的用户:您的RDS实例(实例ID ...

随机推荐

  1. Ubuntu GNURadio gr-Radar 的安装

    1.安装Ubuntu 进行磁盘管理,设置不少于50G的未分配空间 使用rufus-2.8制作Ubuntu 16.4安装盘 保持U盘插入,重启电脑,开机时进入BIOS设置从该安装盘启动并安装 2.激活w ...

  2. Egret白鹭H5小游戏开发入门(一)

    前言: 好久没更新博客了,以前很多都不会,所以常常写博客总结,倒是现在有点点经验了就懒了.在过去的几个月里,在canvas游戏框架方面,撸过了CreateJS,玩得了Egret,又学过PIXI.js. ...

  3. GridView多行标题行、改造标题行、自定义标题行完美版

    网上找了个找,最终还是自己做的比较靠谱,道理很简单,直接看代码 代码:   /// <summary> /// =================== 两行标题行 ============ ...

  4. 查看TEMP使用情况

    SQL> select * from v$mystat where rownum<2; SID STATISTIC#    VALUE ---------- ---------- ---- ...

  5. mac下brew install 报错

    mac下brew install 报错 错误提示: 原因:是这个brew的权限不正确 修改一下这个brew的权限 chown root:wheel /usr/local/bin/brew

  6. Js模块模式

    模块模式 索引 引子 什么是模块模式 命名空间模式 声明依赖 私有和特权成员 即时函数 揭示模块模式 结语 引子 这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中 ...

  7. linux服务器 jboss-7安装

    jBoss简介 JBoss是一个运行EJB的J2EE应用服务器.它是开放源代码的项目,遵循最新的J2EE规范.从JBoss项目开始至今,它已经从一个EJB容器发展成为一个基于的J2EE的一个web 操 ...

  8. DS控件库 Win7链接列表框的仿Windows开始菜单样式

    Win7链接列表框是依照Windows7的开始菜单开发的,同时进行了属性和功能的扩展. 效果图 项属性 控件属性 控件主要事件 点击项(Sender As Win7链接列表框, Itm As 链接项, ...

  9. django提交post请求

    在做post的时候,view.py用到了下面的方法,如果是POST的method,就通过request.POTST['XX']获得html中name为XX的值,然后将值save到数据库里 models ...

  10. C&num;自省

    [C#自省] 1.根据string,获取type.Type.GetType 方法,获取具有指定名称的 Type,执行区分大小写的搜索. 2.根据obj,获取type.Object.GetType 方法 ...