MSSQL·最佳實踐·RDSSDK實現資料庫遷移上阿里雲RDSSQLServer

風移發表於2018-07-18

title: MSSQL · 最佳實踐 · RDS SDK實現資料庫遷移上阿里雲RDS SQL Server

author: 風移

摘要

至今,我們完成了SQL Server備份還原專題系列七篇月報分享:三種常見的資料庫備份、備份策略的制定、查詢備份鏈、資料庫的三種恢復模式與備份之間的關係、利用檔案組實現冷熱資料隔離備份方案、如何監控備份還原進度、以及阿里雲RDS SQL自動化遷移上雲的一種解決方案,本期我們分享如何使用阿里雲RDS SDK來實現使用者線下資料備份檔案遷移上阿里雲RDS SQL Server。

適用場景

對於那些不想通過RDS控制檯來實現資料庫上雲RDS SQL Server,具有程式設計能力的,可以考慮使用RDS SDK來實現線下資料庫遷移上雲RDS SQL例項上。

實現分析

由於使用者線下資料庫備份檔案位於OSS中,所以遷移上雲需要設計到OSS相關的SDK和RDS相關的SDK。

OSS詳情

由於使用者的線下資料庫備份檔案存放在OSS上的Bucket中,因此從OSS中獲取資料庫備份檔案需要使用到OSS的SDK;從OSS上獲取備份檔案的同時,我們還需要知道OSS Bucket所在的Region,因為遷移上雲要求RDS例項和OSS Bucket處在同一個Region中。從這些分析來看,我們必須要知道OSS Bucket名字,所在的Endpoint和備份檔名bak_file_key。

RDS詳情

RDS例項是指使用者需要遷移上雲的目標RDS SQL例項,我們需要知道RDS SQL例項的版本資訊(RDS SQL 2008R2和2012及以上版本輸入引數稍有不同),例項所在的Region(RDS例項需要和OSS Bucket處在同一個Region)以及目標例項上資料庫的名字。從分析來看RDS例項,我們需要知道RDS SQL ID和資料庫名字。

輸入引數

訪問阿里雲資源,使用者需要帶上阿里雲賬戶或者子賬戶的AK,即access_key_id和access_key_secret,因此這兩個引數也必不可少。所以,最後我們的輸入引數定義為以下七個引數。

access_key_id        :   阿里雲使用者 access key id

access_key_secret    :   阿里雲使用者access key secret

rds_instance_id        :   RDS SQL例項ID

database_name        :   目標例項資料庫名字

oss_endpoint        :   OSS Endpoint地址

oss_bucket             :   OSS Bucket名

bak_file_key        :   使用者備份在OSS Bucket中的檔名

具體實現

實現分析完畢後,以下章節詳細介紹具體的實現方法,包括:

準備工作

程式碼實現

使用方法

準備工作

安裝python

首先請根據python官網的引導安裝合適的Python版本,推薦安裝2.7.10。
安裝完畢後,檢視Python版本

Windows

C:>c:Python27python.exe -V
Python 2.7.10

上面的輸出表明您已經成功安裝了Python 2.7.10版本。如果提示“不是內部或外部命令”,請檢查配置“環境變數”-“Path”,增加Python的安裝路徑和pip命令的目錄。如圖:
01.png

Mac /Linux/Unix

$ python -V
Python 2.7.10

安裝SDK依賴包

使用pip安裝或者git clone原始碼安裝,任選其一,推薦pip安裝,簡單方便快捷。

Pip安裝

pip install aliyun-python-sdk-rds
pip install oss2

原始碼安裝

# git 克隆openapi
git clone https://github.com/aliyun/aliyun-openapi-python-sdk.git

# 安裝阿里雲 SDK 核心庫
cd aliyun-python-sdk-core
python setup.py install

# 安裝阿里雲 RDS SDK
cd aliyun-python-sdk-rds
python setup.py install

# git 克隆OSS SDK
git clone https://github.com/aliyun/aliyun-oss-python-sdk.git
cd aliyun-oss-python-sdk

# 安裝OSS2
python setup.py install

程式碼實現

在本文,使用python版RDS SDK實現資料庫遷移上雲RDS SQL Server,當然你也可以使用C#版、Java版等其他版本,詳細的程式碼實現如下:

#!/usr/bin/python

# -*- coding: utf-8 -*-
"""***************************************************************************************
# Script name    : RdsSQLCreateMigrateTask.py
# Author        : jianming.wjm@alibaba-inc.com
# Create Date   : 2018-06-10 19:27
# Language         : Python 2.7.10
# Dev platform  : Mac OS X 10.12.6

# Purpose        : 
                    This script is for Migration user SQL Server databases localy 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 a offline databases to alibaba cloud RDS SQL Server instance.
                    We achieve those accomplishments by call alibaba cloud RDS OPENAPI.

# Limitation    :
    RDS Edition : Support RDS edition listed below
                    `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`

# Usage         :
    Help         : python RdsSQLCreateMigrateTask.py -h
    Example     : 
                    RdsSQLCreateMigrateTask.py -k <access_key_id> -s <access_key_secret> -i <rds_instance_id> -d <database_name> -e <oss_endpoint> -b <oss_bucket> -f <bak_file_key>
                    
                    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
                    database_name        :    The database name will be on RDS.
                    oss_endpoint        :   OSS Endpoint address, fg: oss-cn-beijing.aliyuncs.com
                    oss_bucket             :   OSS Bucket name, fg: test-oss-bucket
                    bak_file_key        :   The backup file key on OSS,fg: Migration/TestMigration_FULL_20180523225534.bak

                : calling example:
                $ python RdsSQLCreateMigrateTask.py -k LTAIKeRvKPRwkaU3 -s BbZ7xhrertQ0dfgMqfAZPByhnp4G2k -i rm-2zesz4564ud8s7123 -d testmigrate -e oss-cn-beijing.aliyuncs.com -b test-oss-bucket -f Migration/TestMigration_FULL_20180523225534.bak


# 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-06-11 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 =  bak_file_key = database_name = ``

    # usage help
    try:
        opts, args = getopt.getopt(argv,"hk:s:i:d:e:b:f:",["access_key_id=", "access_key_secret=", "rds_instance_id=", "database_name=", "oss_endpoint=", "oss_bucket=", "bak_file_key="])
    except getopt.GetoptError:
        print (`%s -k <access_key_id> -s <access_key_secret> -i <rds_instance_id> -d <database_name> -e <oss_endpoint> -b <oss_bucket> -f <bak_file_key>` % (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> -d <database_name> -e <oss_endpoint> -b <oss_bucket> -f <bak_file_key>` % (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 ("-d", "-D", "--database_name"):
            database_name = arg
        elif opt in ("-e", "-E", "--oss_endpoint"):
            oss_endpoint = arg
        elif opt in ("-b", "-B", "--oss_bucket"):
            oss_bucket = arg
        elif opt in ("-f", "-F", "--bak_file_key"):
            bak_file_key = arg

    # show the input parameters
       print ("
*********************Input variables****************************************
" 
           "access_key_id = %s
access_key_secret = %s
rds_instance_id = %s
database_name = %s
" 
           "oss_endpoint = %s
oss_bucket = %s
bak_file_key = %s
" 
           "***************************************************************************
"
           % (access_key_id, access_key_secret, rds_instance_id, database_name, oss_endpoint, oss_bucket, bak_file_key))


       ### check RDS & OSS region to make sure they are located in the same region.

       # get RDS details
       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"]

       # get OSS Bucket
       success, oss_details, sign_url = oss_bucket_details(access_key_id, access_key_secret, oss_endpoint, oss_bucket, bak_file_key)
       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()

       # everything is OK, we`d go to the real business.
       print ("--[%s] will be migrated to your RDS [%s] and the database name will be [%s]." % (bak_file_key, rds_instance_id, database_name))

       # RDS & OSS Bucket are in the same region.
       print ("
*********************Migration response**********************************")
       do_migrate_database(access_key_id, access_key_secret, rds_instance_id, database_name, oss_endpoint, oss_bucket, bak_file_key, rds_db_version, sign_url)
       print ("************************************************************************")

"""
action to migrate database into RDS
"""
def do_migrate_database(access_key_id, access_key_secret, rds_instance_id, database_name, oss_endpoint, oss_bucket, bak_file_key, rds_db_version, sign_url):
    request = CreateMigrateTaskRequest.CreateMigrateTaskRequest()
    request.set_DBInstanceId(rds_instance_id)
    request.set_DBName(database_name)
    request.set_BackupMode("FULL")
    request.set_IsOnlineDB(True)
    if rds_db_version == `2008r2`:
        request.set_DBName(database_name.lower())
        request.set_OSSUrls(sign_url)
    else:
        request.set_OSSUrls("")
        request.set_OssObjectPositions("%s:%s:%s" % (oss_endpoint, oss_bucket, bak_file_key)) # OSSEndpoint:OSSBucket:OSSFileKey
        request.set_CheckDBMode("SyncExecuteDBCheck")

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

    if success:
        print response
    else:
        print ("OPENAPI Response Error !!!!! : %s" % response)


"""
get RDS SQL Instance details via RDS OPENAPI.
"""
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


"""
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


"""
get OSS Bucket details via OSS OPENAPI
"""
def oss_bucket_details(access_key_id, access_key_secret, oss_endpoint, oss_bucket, bak_file_key):
    try:
        bucket = oss2.Bucket(oss2.Auth(access_key_id, access_key_secret), oss_endpoint, oss_bucket)
        bucket_info = bucket.get_bucket_info()
        return True, bucket_info, bucket.sign_url(`GET`, bak_file_key, 24 * 3600)
    except Exception as e:
        return False, e, None


"""
process entrance main.
"""
if __name__ == `__main__`:
    main(sys.argv[1:])

當然,以上程式碼,你也可以去這裡下載以上python指令碼。

使用方法

檢視Help

你只需要使用-h來檢視指令碼的使用方法:

$ python ~/Downloads/RdsSQLCreateMigrateTask.py -h
~/Downloads/RdsSQLCreateMigrateTask.py -k <access_key_id> -s <access_key_secret> -i <rds_instance_id> -d <database_name> -e <oss_endpoint> -b <oss_bucket> -f <bak_file_key>

一個例子

以下是一個具體的例子:

$ python ~/Downloads/RdsSQLCreateMigrateTask.py -k LTAIKeRvKPRwkaU3 -s BbZ7xhrertQ0dfgMqfAZPByhnp4G2k -i rm-2zesz4564ud8s7123 -d testmigrate -e oss-cn-beijing.aliyuncs.com -b test-oss-bucket -f Migration/OPENAPIDemo/TestMigration_FULL_20180523225534.bak

輸出結果

執行以上命令以後的結果輸出,分為三個部分:

第一部分輸入引數:展示所有你的輸入引數,以便查詢輸入錯誤

第二部分提示資訊:告訴你,哪一個備份檔案會被遷移到哪個例項的哪一個資料庫

第三部分呼叫返回:RDS OPENAPI SDK的返回資訊

*********************Input variables****************************************
access_key_id = LTAIKeRvKPRwkaU3
access_key_secret = BbZ7xhrertQ0dfgMqfAZPByhnp4G2k
rds_instance_id = rm-2zesz4564ud8s7123
database_name = testmigrate
oss_endpoint = oss-cn-beijing.aliyuncs.com
oss_bucket = test-oss-bucket
bak_file_key = Migration/OPENAPIDemo/TestMigration_FULL_20180523225534.bak
***************************************************************************

--[Migration/OPENAPIDemo/TestMigration_FULL_20180523225534.bak] will be migrated to your RDS [rm-2zesz4564ud8s7123] and the database name will be [testmigrate].

*********************Migration response**********************************
{u`DBInstanceId`: u`rm-2zesz4564ud8s7123`, u`BackupMode`: u`FULL`, u`MigrateTaskId`: u`107081`, u`RequestId`: u`F5419B88-7575-47BA-BDEB-64D280E36C54`, u`TaskId`: u`70630059`, u`DBName`: u`testmigrate`}
************************************************************************

最後總結

利用本篇文件,你可以很清楚的瞭解到使用RDS SDK OPENAPI的最佳實踐的同時,又可以實現使用者線下資料庫遷移上阿里雲RDS SQL Server的全自動實現方法。

參考連結

阿里雲幫助文件中的CreateMigrateTask

阿里雲RDS OPENAPI SDK,在github中的原始碼,aliyun-openapi-python-sdk


相關文章