开发者社区> 风移> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

MSSQL-最佳实践-实例级别数据库上云RDS SQL Server

简介: --- title: MSSQL-最佳实践-实例级别数据库上云RDS SQL Server author: 风移 --- # 摘要 到目前,我们完成了SQL Server备份还原专题系列八篇月报分享:三种常见的数据库备份、备份策略的制定、查找备份链、数据库的三种恢复模式与备份之间的关系、利用文件组实现冷热数据隔离备份方案、如何监控备份还原进度、阿里云RDS SQL自动化迁移上云的一种
+关注继续查看

title: MSSQL-最佳实践-实例级别数据库上云RDS SQL Server

author: 风移

摘要

到目前,我们完成了SQL Server备份还原专题系列八篇月报分享:三种常见的数据库备份、备份策略的制定、查找备份链、数据库的三种恢复模式与备份之间的关系、利用文件组实现冷热数据隔离备份方案、如何监控备份还原进度、阿里云RDS SQL自动化迁移上云的一种解决方案以及上个月分享的RDS SDK实现数据库迁移上阿里云,本期我们分享如何将用户线下或者ECS上自建实例级别数据库一键迁移上阿里云RDS SQL Server。

适用场景

在我们上一个月分享的RDS SDK实现数据库迁移上阿里云RDS SQL Server方案中,我们实现了如何将用户线下或者ECS上自建的SQL Server实例中的一个用户数据库自动化迁移上云到RDS SQL Server,话句话说,它实现的是数据库级别的迁移上云方案,即每次迁移上云用户线下一个数据库。
但是,有的用户可能会遇到这样的场景,我的线下有几十上百SQL Server实例,每个实例又有几十上百个数据库,总共就有成千上万个数据库迁移上云。如果是数据库级别的迁移上云方案显得力不从心,效率低下。为了解决用户大批量数据库迁移上云RDS for SQL Server,简化上云操作步骤,提高上云效率,实例级别数据库上云RDS SQL Server是我们迫切需要解决场景。

实现分析

由于在前一个月分享的RDS SDK实现数据库迁移上阿里云RDS SQL Server中,我们已经实现了单个数据库迁移上云方法,因此实现实例级别的迁移上云我们可以采用如下方案:
将用户线下实例上所有的数据库全量备份文件上传到OSS的一个文件夹中
遍历OSS上该文件夹所有的数据库备份文件
每一个备份文件生成一个迁移上云任务

输入参数

基于以上的分析,我们的实现方法需要包含如下六个输入参数,以及这六个输入参数的解析参见下表:

access_key_id        :   阿里云用户 access key id
access_key_secret    :   阿里云用户access key secret
rds_instance_id    :   RDS SQL实例ID
oss_endpoint        :   OSS Endpoint地址
oss_bucket         :   OSS Bucket名
directory            :   用户数据库备份文件在OSS上文件夹路径,如果是根目录,请传入“/”

具体实现

准备工作

参见上一个月的月报分享MSSQL · 最佳实践 · RDS SDK实现数据库迁移上阿里云RDS SQL Server中的准备工作部分。

代码实现

在本文,我们使用python版RDS SDK实现数据库迁移上云RDS SQL Server,当然你也可以使用C#版、Java版等其他版本,详细的代码实现如下:

#!/usr/bin/python

# -*- coding: utf-8 -*-

"""***************************************************************************************
# Script name    : RDSSQLCreateMigrateTasksBatchly.py
# Author        : jianming.wjm@alibaba-inc.com
# Create Date   : 2018-05-17 19:27
# Language         : Python 2.7.10
# Run platform  : Mac OS X 10.12.6

# Purpose        : 
                    This script is for batchly Migration user offline SQL Server databases to alibaba cloud RDS SQL Server.
                    Users' FULL backup files are located on theirselves' OSS Bucket folder already.
                    This script helps users to do migration all offline databases backed-up under the OSS Bucket folder to RDS SQL.
                    We achieve those accomplishments by call alibaba cloud RDS OPENAPI.

# Limitation    :
    RDS Edition : Support RDS edition listed below
                    '2012','2012_web','2012_std', '2012_ent', '2012_std_ha', '2012_ent_ha',
                       '2014_web','2014_std', '2014_ent', '2014_std_ha', '2014_ent_ha',
                       '2016_web','2016_std', '2016_ent', '2016_std_ha', '2016_ent_ha'

# Preparation    :
                  1. python 2.7.x installing (I'm using 2.7.10)
                  2. pip install aliyun-python-sdk-rds
                  3. pip install oss2

# Usage         :
    Help         : python RDSSQLCreateMigrateTasksBatchly.py -h
    Example     : 
                    python ~/Downloads/RDSSQLCreateMigrateTasksBatchly.py -k <access_key_id> -s <access_key_secret> -i <rds_instance_id> -e <oss_endpoint> -b <oss_bucket> -d <directory>
                    
                    variables description
                    access_key_id        :    alibaba cloud user access key id, fg: LTAIKeRvKPRwkaU3
                    access_key_secret    :    alibaba cloud user access key secret, fg: BbZ7xhrertQ0dfgMqfAZPByhnp4G2k
                    rds_instance_id        :   RDS SQL instance ID, fg: rm-2zesz4564ud8s7123
                    oss_endpoint        :   OSS Endpoint address, fg: oss-cn-beijing.aliyuncs.com
                    oss_bucket             :   OSS Bucket name, fg: atp-test-on-ecs
                    directory             :   Sub folder name under OSS Bucket, fg: Migration/OPENAPIDemo


# Output         : There two sesction output, one is the input variables and the other is the migration requests and response.
*********************Input variables*************************************

************************************************************************

*********************Migration requests**********************************

************************************************************************


# Modify Author : jianming.wjm@alibaba-inc.com
# Modify Date   : 2018-05-19 21:43
# Function:
#**************************************************************************************
"""

import json
import os
import sys, getopt
import re
import oss2
import time

from aliyunsdkcore.client import AcsClient
from aliyunsdkrds.request.v20140815 import DescribeMigrateTasksForSQLServerRequest
from aliyunsdkrds.request.v20140815 import CreateMigrateTaskRequest
from aliyunsdkrds.request.v20140815 import DescribeDBInstanceAttributeRequest


def main(argv):
    access_key_id =  access_key_secret =  rds_instance_id =  oss_endpoint =  oss_bucket =  directory = ''

    # usage help
    try:
        opts, args = getopt.getopt(argv,"hk:s:i:e:b:d:",["access_key_id=", "access_key_secret=", "rds_instance_id=", "oss_endpoint=", "oss_bucket=", "directory="])
    except getopt.GetoptError:
        print ('%s -k <access_key_id> -s <access_key_secret> -i <rds_instance_id> -e <oss_endpoint> -b <oss_bucket> -d <directory>' % (sys.argv[0]))
        sys.exit(2)

    for opt, arg in opts:
        if opt == '-h':
            print ('%s -k <access_key_id> -s <access_key_secret> -i <rds_instance_id> -e <oss_endpoint> -b <oss_bucket> -d <directory>' % (sys.argv[0]))
            sys.exit()
        elif opt in ("-k", "-K", "--access_key_id"):
            access_key_id = arg
        elif opt in ("-s", "-S", "--access_key_secret"):
            access_key_secret = arg
        elif opt in ("-i", "-I", "--rds_instance_id"):
            rds_instance_id = arg
        elif opt in ("-e", "-E", "--oss_endpoint"):
            oss_endpoint = arg
        elif opt in ("-b", "-B", "--oss_bucket"):
            oss_bucket = arg
        elif opt in ("-d", "-D", "--directory"):
            if arg.endswith("/"):
                directory = arg
            else:
                directory = str("%s/" % arg)

    # show the input parameters
       print ("\n*********************Input variables*************************************\n" \
           "access_key_id = %s\naccess_key_secret = %s\nrds_instance_id = %s\noss_endpoint = %s\noss_bucket = %s\ndirectory = %s\n" \
           "************************************************************************"
           % (access_key_id, access_key_secret, rds_instance_id, oss_endpoint, oss_bucket, directory))


       # check RDS & OSS region to make sure they are located in the same region.
       success, rds_details = rds_instnace_details(access_key_id, access_key_secret, rds_instance_id)
       if not success:
           print ("%s" % rds_details)
           sys.exit()

       rds_db_version, rds_engine, rds_region = rds_details["EngineVersion"], rds_details["Engine"], rds_details["RegionId"]

       success, oss_details = oss_bucket_details(access_key_id, access_key_secret, oss_endpoint, oss_bucket)
       if not success:
           print ("%s" % oss_details)
           sys.exit()

       oss_region = oss_details.location
       # support db version checking.


       if rds_engine != 'SQLServer' \
           or rds_db_version not in [    '2008r2', '2012','2012_web','2012_std', '2012_ent', '2012_std_ha', '2012_ent_ha',
                                       '2014_web','2014_std', '2014_ent', '2014_std_ha', '2014_ent_ha',
                                       '2016_web','2016_std', '2016_ent', '2016_std_ha', '2016_ent_ha']:
           print("RDS engine doesn't support, this is only for RDS SQL Server engine.")
           sys.exit()

       # RDS & OSS Bucket are not under the same region.
       if not oss_region.endswith(rds_region):
           print("RDS & OSS Bucket are not located in the same region.")
           sys.exit()

       # RDS & OSS Bucket are in the same region.
       print ("\n*********************Migration requests**********************************")
       full_migrate(access_key_id, access_key_secret, rds_instance_id, oss_endpoint, oss_bucket, directory, rds_db_version)
       print ("************************************************************************")


def rds_instnace_details(access_key_id, access_key_secret, rds_instance_id):
    request = DescribeDBInstanceAttributeRequest.DescribeDBInstanceAttributeRequest()
    request.set_DBInstanceId(rds_instance_id)
    success, response = _send_request(access_key_id, access_key_secret, request)

    if success:
        if response["Items"]["DBInstanceAttribute"]:
            # print response["Items"]["DBInstanceAttribute"][0]["EngineVersion"]
            # print response["Items"]["DBInstanceAttribute"][0]["RegionId"]
            return True, response["Items"]["DBInstanceAttribute"][0]
        else:
            return False, "Couldn't find specify RDS [%s]." % rds_instance_id
    
    
    return False, response


def full_migrate(access_key_id, access_key_secret, rds_instance_id, oss_endpoint, oss_bucket, directory, rds_db_version):
    """
    this supoort full backup files migration.
    """

    # get all backup objects under sub_folder
    key_parts_list, do = oss_list_objects(access_key_id, access_key_secret, oss_endpoint, oss_bucket, directory), 0

    # foreach object
    for key_parts in key_parts_list:
        print ("\n--%s. [%s] migrate to your RDS: [%s] and the database name will be: [%s]." % (do, key_parts.file_key, rds_instance_id, key_parts.db_name))
        do += 1

        # print ("%s" % key_parts.sign_url)

        request = CreateMigrateTaskRequest.CreateMigrateTaskRequest()
        request.set_DBInstanceId(rds_instance_id)
        request.set_DBName(key_parts.db_name)
        request.set_BackupMode("FULL")
        request.set_IsOnlineDB(True)
        if rds_db_version == '2008r2':
            request.set_DBName(key_parts.db_name.lower())
            request.set_OSSUrls(key_parts.sign_url)
        else:
            request.set_OSSUrls("")
            request.set_OssObjectPositions("%s:%s:%s" % (oss_endpoint, oss_bucket, key_parts.file_key)) # OSSEndpoint:OSSBucket:OSSFileKey
            request.set_CheckDBMode("SyncExecuteDBCheck")

        success, response = _send_request(access_key_id, access_key_secret, request)

        if success:
            print response

            print ("--I'm sleeping for 2 seconds....")
            time.sleep(2)
        else:
            print ("OPENAPI Response Error !!!!! : %s" % response)

"""
send request to OPENAPI
and get the response details
"""
def _send_request(access_key_id, access_key_secret, request, region='cn-hangzhou'):
    request.set_accept_format('json')
    try:
        # clt = AcsClient(access_key_id, access_key_secret, 'cn-hangzhou')
        clt = AcsClient(access_key_id, access_key_secret, region)
        response_str = clt.do_action_with_exception(request)
        response_detail = json.loads(response_str)
        return True, response_detail
    except Exception as e:
        return False, e


class oss_key_parts(object):
    """
    if the whole object file key looks like blow:
    Migration/OPENAPIDemo/TestMigration_FULL_20180518153544.bak
    
    then

    : param str file_key: OSS object file key.
    : param str sub_folder: OSS sub folder name, such as Migration/OPENAPIDemo
    : param str file_name: OSS object file name, such as TestMigration_FULL_20180518153544.bak
    : param str db_name: database name, such as 'TestMigration'
    : param str bak_type: backup type , such as 'FULL'
    : param str date: backup date time, such as '20180518153544'
    : param str ext: backup file extendsion, such as 'bak'

    """
    def __init__(self):
        self.file_key = ''
        self.sub_folder = ''
        self.file_name = ''
        self.db_name = ''
        self.bak_type = ''
        self.date = ''
        self.ext = ''
        self.sign_url = ''

"""
parse the OSS file key string into oss key parts
and return oss_key_parts object.
"""
def oss_key_parse(file_key):

    key_parts = oss_key_parts()
    try:
        if file_key.find('/') >= 0:
            file_key_parts = file_key.rsplit('/', 1)
        else:
            file_key_parts = file_key
            file_key_parts = ['/', file_key]

        key_parts.file_key = file_key
        key_parts.sub_folder = file_key_parts[0]
        key_parts.file_name = file_key_parts[1]

        key_list = file_key_parts[1].rsplit('_', 2)

        key_parts.db_name, \
        key_parts.bak_type, \
        key_parts.date, \
        key_parts.ext = key_list[0], \
                        key_list[1], \
                        key_list[2].rsplit('.', 1)[0], \
                        key_list[2].rsplit('.', 1)[1]
    except Exception, e:
        pass

    return key_parts

def oss_list_objects(access_key_id, access_key_secret, oss_endpoint, oss_bucket, directory):
    """
    list all OSS objects under the specified sub folder
    and return the objects list.
    """
    bucket = oss2.Bucket(oss2.Auth(access_key_id, access_key_secret), oss_endpoint, oss_bucket)
    key_parts_list = []

    # OSS Bucket Root
    if directory == '/':
        for object_info in oss2.ObjectIterator(bucket, delimiter='/'):
            if not object_info.is_prefix():
                key_part = oss_key_parse(object_info.key)

                # get object sign_url
                key_part.sign_url = bucket.sign_url('GET', object_info.key, 24 * 3600)

                if key_part.ext in['bak', 'trn', 'log', 'diff']:
                    key_parts_list.append(key_part)
                else:
                    print ("Warning!!!!!, [%s] is not backup file, filtered." % (key_part.file_key))
    else:
        for i, object_info in enumerate(oss2.ObjectIterator(bucket, prefix=directory)):
            # have to the backup files, not folder
            if not object_info.is_prefix():
                if object_info.key.startswith(directory) and object_info.key != directory:
                    # print ("%s" % (object_info.key))
                    key_part = oss_key_parse(object_info.key)
                    
                    # get object sign_url
                    key_part.sign_url = bucket.sign_url('GET', object_info.key, 24 * 3600)

                    if key_part.ext in['bak', 'trn', 'log', 'diff']:
                        key_parts_list.append(key_part)
                    else:
                        print ("Warning!!!!!, [%s] is not a vaild backup file, filtered." % (key_part.file_key))

    if not key_parts_list:
        print("There is no backup file on OSS Bucket [%s] under [%s] folder, check please." % (oss_bucket, directory))

    return key_parts_list


def oss_bucket_details(access_key_id, access_key_secret, oss_endpoint, oss_bucket):
    try:
        bucket = oss2.Bucket(oss2.Auth(access_key_id, access_key_secret), oss_endpoint, oss_bucket)
        bucket_info = bucket.get_bucket_info()
        # print ("bucket name:%s, region: %s" % (bucket_info.name, bucket_info.location))
        return True, bucket_info
    except Exception as e:
        return False, e

if __name__ == '__main__':
    main(sys.argv[1:])

当然,以上代码,你也可以去下载以上python脚本。

使用方法

我们从以下三个方面简要介绍下如何使用实例级别一键迁移上云:
查看Help
一个例子
输出结果

查看Help

你只需要使用-h来查看脚本的使用方法:

python ~/Downloads/RDSSQLCreateMigrateTasksBatchly.py -h
~/Downloads/RDSSQLCreateMigrateTasksBatchly.py -k <access_key_id> -s <access_key_secret> -i <rds_instance_id> -e <oss_endpoint> -b <oss_bucket> -d <directory>

一个例子

以下是一个具体的例子:

python ~/Downloads/RDSSQLCreateMigrateTasksBatchly.py -k LTAIQazXKPRwwErT -s BMkIUhroubQOLpOMqfA09IKlqp4G2k -i rm-2zesz5774ud8s71i5 -e oss-cn-beijing.aliyuncs.com -b atp-test-on-ecs -d Migration/OPENAPIDemo

输出结果

执行以上命令以后的结果输出,分为两个部分:
第一部分输入参数:展示所有你的输入参数,以便查询输入错误
第二部分提示信息:告诉你,哪一个备份文件会被迁移到哪个实例的哪一个数据库
如下的一个实例的输出信息:

*********************Input variables*************************************
access_key_id = LTAIQazXKPRwwErT
access_key_secret = BMkIUhroubQOLpOMqfA09IKlqp4G2k
rds_instance_id = rm-2zesz5774ud8s71i5
oss_endpoint = oss-cn-beijing.aliyuncs.com
oss_bucket = atp-test-on-ecs
directory = Migration/OPENAPIDemo/
************************************************************************

*********************Migration requests**********************************

--0. [Migration/OPENAPIDemo/TestCollation_FULL_20180523225534.bak] migrate to your RDS: [rm-2zesz5774ud8s71i5] and the database name will be: [TestCollation].
{u'DBInstanceId': u'rm-2zesz5774ud8s71i5', u'BackupMode': u'FULL', u'MigrateTaskId': u'106121', u'RequestId': u'67E0DD7F-7219-4F67-AAE7-B27273921303', u'TaskId': u'68244691', u'DBName': u'TestCollation'}
--I'm sleeping for 2 seconds....

--1. [Migration/OPENAPIDemo/TestMigration_FULL_20180523225534.bak] migrate to your RDS: [rm-2zesz5774ud8s71i5] and the database name will be: [TestMigration].
{u'DBInstanceId': u'rm-2zesz5774ud8s71i5', u'BackupMode': u'FULL', u'MigrateTaskId': u'106122', u'RequestId': u'0916CD14-861B-4BF7-A68A-409E3996B0D3', u'TaskId': u'68244695', u'DBName': u'TestMigration'}
--I'm sleeping for 2 seconds....

--2. [Migration/OPENAPIDemo/testdb_FULL_20180523225534.bak] migrate to your RDS: [rm-2zesz5774ud8s71i5] and the database name will be: [testdb].
{u'DBInstanceId': u'rm-2zesz5774ud8s71i5', u'BackupMode': u'FULL', u'MigrateTaskId': u'106123', u'RequestId': u'5835B154-2EE3-4059-BFC4-6F798CDCE546', u'TaskId': u'68244699', u'DBName': u'testdb'}
--I'm sleeping for 2 seconds....
************************************************************************

最后总结

利用本篇文章,我们可以轻松实现用户线下或者ECS自建的SQL Server实例级别数据库一键迁移上云,以此来极大的提高迁移上云效率,简化操作,大大提升了用户迁移上云体验。

参考链接

SQL Server实例级别数据库上云

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
PolarDB MySQL 5.7/RDS 5.7升级到PolarDB MySQL 8.0最佳实践
升级概述PolarDB MySQL 5.7/RDS 5.7 向 8.0 升级过程中,经常遇到的问题主要是性能问题、语法兼容性问题,以及周边组件是否的支持,查询的性能问题一般是由于优化器升级导致执 行计划有变,此类问题需要对性能低下的语句进行针对性的性能优化,但性能问题基本不会引发业务报错以及代码的改写问题,此类问题不在本文讨论范围之内。本文主要讨论真实的兼容性问题,此类问题需要在数据库升级过程中,
0 0
2022云栖精选—云数据库RDS重磅功能发布与最佳实践
彭祥 阿里云数据库事业部资深技术专家 RDS产品部负责人 许鸿斌 阿里云数据库事业部高级产品专家
0 0
【最佳实践】高性价比的数据归档解决方案(DMS + AnalyticDB PostgreSQL)
发布全新数据归档方案,依托DMS + AnalyticDB PostgreSQL Serverless版本,帮助客户用低价格实现海量数据的持久化,还可以对归档数据进行完善管理、高效寻回、查看并进行分析
0 0
基于PostgreSQL的索引推荐原理及最佳实践
基于PostgreSQL讲述索引推荐的原理、实现及最佳实践。
0 0
「助力降本增效」RDS MySQL云盘版只读基础版发布及其最佳实践
阿里云RDS MySQL云盘版只读在原来 "高可用只读" 的基础上推出了"基础版只读"。尽管"基础版只读"在价格上比"高可用版只读"便宜了40%左右,两者对于故障的容错能力、异常的应对能力方面还是有差异的。本文通过两款产品的差异性对比,对"基础版只读"的最佳实践进行分享。
0 0
【走进RDS】之RDS PostgreSQL索引推荐原理及最佳实践
很多开发人员都知道索引对于数据库的查询性能至关重要,一个好的索引能使数据库的性能提升成千上万倍。但给数据库加索引是一项相对专业的工作,需要对数据库的运行原理有一定了解。同时,加了索引有没有性能提升、性能提升了多少,这些都是加索引前就想知道的。这项繁杂的工作有没有更好的方案呢?有!就是今天重磅推出的索引推荐。
0 0
分布式 PostgreSQL 集群(Citus),分布式表中的分布列选择最佳实践
分布式 PostgreSQL 集群(Citus),分布式表中的分布列选择最佳实践
0 0
【ECS最佳实践】性能测试及ECS+RDS构建云服务器主动防御系统部署开源蜜罐系统Hfish
云服务器ECS(Elastic Compute Service)是阿里云提供的性能卓越、稳定可靠、弹性扩展的IaaS(Infrastructure as a Service)级别云计算服务。云计算最基础的概念便是云服务器,对Openstack概念有了解的,对接触ECS就不会有隔阂。
0 0
【ECS最佳实践】ECS+RDS构建云服务器主动防御系统部署开源蜜罐系统Hfish及ECS周边功能测试
我已经是阿里云ECS产品的老用户了,阿里的云计算产品性能可靠性毋庸置疑,这次分享一个开源蜜罐系统Hfish的单节点搭建,并围绕ECS周边的技术功能做个简单举例。
0 0
前沿分享|数澜科技联合创始人&副总裁 江敏:基于云原生数据仓库AnalyticDB PostgreSQL的最佳实践
本篇内容为2021云栖大会-云原生数据仓库AnalyticDB技术与实践峰会分论坛中,数澜科技联合创始人&副总裁江敏关于“基于云原生数据仓库AnalyticDB PostgreSQL的最佳实践”的分享。
0 0
+关注
风移
阿里云数据库专家,负责SQL Server数据库产品线。SQL Server从业10年,经历过SQL 2000、SQL 2005、SQL 2008、SQL 2008R2、SQL 2012、SQL 2014、SQL 2016和SQL on Linux各个版本。
文章
问答
文章排行榜
最热
最新
相关电子书
更多
云数据库RDS MySQL从入门到高阶
立即下载
PolarDB for PostgreSQL 源码与应用实战
立即下载
PolarDB for PostgreSQL 开源必读手册
立即下载