xtrabackup是一个MySQL备份还原的常用工具,实际使用过程应该都是shell或者python封装的自动化脚本,尤其是备份。
对还原来说,对于基于完整和增量备份的还原,还原差异备份需要指定增量备份等等一系列容易出错的手工操作,以及binlog的还原等,如果纯手工操作的话非常麻烦。
即便是你记性非常好,对xtrabackup非常熟悉,纯手工操作的话,非常容易出错,其实也上网找过,还原没有发现太好用的自动化还原脚本。
于是就自己用Python封装了xtrabackup备份和还原的过程,可以做到自动化备份,基于时间点的自动化还原等等。

需要对xtrabackup有一定的了解,包括流式备份,压缩备份,Xtrabackup还原,mysqlbinlog还原等等。

备份

1,基于xtrabackup的流式压缩备份。
2,周六/或者任意时间的第一次备份为完整备份,其他时间为基于上一次备份的增量备份。
3,将备份开始时间,结束时间,备份路径等信息写入一个日志文件,方便后续自动化还原的时候解析。

效果如下:不管是什么时候,第一次必须为完整备份,然后根据上述规则,继续执行备份的话为基于最新一次备份的增量备份,每备份完成后生成修改备份日志列表信息。

实现:

 # -*- coding: utf-8 -*-
import os
import time
import datetime
import sys
import socket
import shutil
import logging logging.basicConfig(level=logging.INFO
#handlers={logging.FileHandler(filename='backup_log_info.log', mode='a', encoding='utf-8')}
) host = "127.0.0.1"
port = ""
user = "root"
password = "root"
cnf_file = "/usr/local/mysql57_data/mysql7000/etc/my.cnf"
backup_dir = "/usr/local/backupdata"
backupfilelist = os.path.join(backup_dir,"backupfilelist.log")
backup_keep_days = 15 #获取备份类型,周六进行完备,平时增量备份,如果没有全备,执行完整备份
def get_backup_type():
backup_type = None
if os.path.exists(backupfilelist):
with open(backupfilelist, 'r') as f:
lines = f.readlines()
if(lines):
last_line = lines[-1] #get last backup name
if(last_line):
if(time.localtime().tm_wday==6):
backup_type = "full"
else:
backup_type = "incr"
else:
backup_type = "full"
else:
backup_type = "full"
else:
#full backup when first backup
open(backupfilelist, "a").close()
backup_type = "full"
return backup_type #获取最后一次备份信息
def get_last_backup():
last_backup = None
if os.path.exists(backupfilelist):
with open(backupfilelist, 'r') as f:
lines = f.readlines()
last_line = lines[-1] # get last backup name
if (last_line):
last_backup = os.path.join(backup_dir, last_line.split("|")[-1])
return last_backup.replace("\n","") #探测实例端口号
def get_mysqlservice_status():
mysql_stat = 0
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = s.connect_ex((host, int(port)))
#port os open
if (result == 0):
mysql_stat = 1
return mysql_stat #清理过期的历史备份信息
def clean_expired_file():
for backup_name in os.listdir(backup_dir):
if os.path.isdir(backup_name):
bak_datetime = datetime.datetime.strptime(backup_name.replace("_full","").replace("_incr",""), '%Y%m%d%H%M%S')
if(bak_datetime<datetime.datetime.now() - datetime.timedelta(days=backup_keep_days)):
shutil.rmtree(os.path.join(backup_dir, backup_name)) #完整备份
def full_backup(backup_file_name):
os.system("[ ! -d {0}/{1} ] && mkdir -p {0}/{1}".format(backup_dir,backup_file_name))
logfile = os.path.join(backup_dir, "{0}/{1}/backuplog.log".format(backup_dir,backup_file_name))
backup_commond = ''' innobackupex --defaults-file={0} --no-lock {1}/{6} --user={2} --password={3} --host="{4}" --port={5} --tmpdir={1}/{6} --stream=xbstream --compress --compress-threads=8 --parallel=4 --extra-lsndir={1}/{6} > {1}/{6}/{6}.xbstream 2>{7} '''.\
format(cnf_file,backup_dir,user,password,host,port,backup_file_name,logfile)
execute_result = os.system(backup_commond)
return execute_result #增量备份
def incr_backup(backup_file_name):
os.system("[ ! -d {0}/{1} ] && mkdir -p {0}/{1}".format(backup_dir, backup_file_name))
current_backup_dir = "{0}/{1}".format(backup_dir, backup_file_name)
logfile = os.path.join(backup_dir, "{0}/{1}/backuplog.log".format(backup_dir, backup_file_name))
#增量备份基于上一个增量/完整备份
incremental_basedir = get_last_backup()
backup_commond = '''innobackupex --defaults-file={0} --no-lock {6} --user={2} --password={3} --host={4} --port={5} --stream=xbstream --tmpdir={6} --compress --compress-threads=8 --parallel=4 --extra-lsndir={6} --incremental --incremental-basedir={7} 2> {8} > {6}/{9}.xbstream '''\
.format(cnf_file,backup_dir,user,password,host,port,current_backup_dir,incremental_basedir,logfile,backup_file_name)
# print(backup_commond)
execute_result = os.system(backup_commond)
return execute_result #刷新binlog,意义不大,原本计划在完整备份之后执行一个binlog的切换,暂时弃用
def flush_log():
flush_log_commond = ''' mysql -h${0} -u${1} - p${2} -P${1} mysql - e"flush logs" '''.format(user,password,host,port)
os.system(flush_log_commond) if __name__ == '__main__':
mysql_stat = get_mysqlservice_status()
backup_type = get_backup_type()
if mysql_stat <= 0 :
logging.info("mysql instance is inactive,backup exit")
sys.exit(1)
try:
start_time = datetime.datetime.now().strftime('%Y%m%d%_H%M%S')
logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+"--------start backup")
#flush_log()
backup_file_name = start_time
execute_result = None
if(backup_type == "full"):
backup_file_name = backup_file_name+"_full"
logging.info("execute full backup......")
execute_result = full_backup(backup_file_name)
if (execute_result == 0):
logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------begin cleanup history backup")
logging.info("execute cleanup backup history......")
clean_expired_file()
logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------finsh cleanup history backup")
else:
backup_file_name = backup_file_name + "_incr"
logging.info("execute incr backup......")
execute_result = incr_backup(backup_file_name)
if(execute_result==0):
finish_time = datetime.datetime.now().strftime('%Y%m%d%_H%M%S')
backup_info = start_time+"|"+finish_time+"|"+start_time+ "_" + backup_type
with open(backupfilelist, 'a+') as f:
f.write(backup_info + '\n')
logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+"--------finish backup")
else:
logging.info(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "--------xtrabackup failed.please check log")
except:
raise
sys.exit(1)

还原

说直白一点,以这里的基于时间点或者是position的还原,就是一个不断找文件的过程,
1,首先任何还原,都需要一个创建于还原点前的完整备份。
2,基于上述完整备份,利用还原的时间点与xtrbackup的备份日志去做对比来获取所需的增量备份(0个或者1个或者多个)。
3,基于上面两步找到的(完整+增量)备份,利用最后一个备份的position,用于第一个binlog还原时指定start-position,
   同时利用binlog的最后修改时间与还原的时间点对比,决定使用那些binlog,同时最后一个binlog要指定stop-datime= 还原的时间点

1,如何还原时间点的最新的一个完整备份
备份的时候维护一个备份信息,如下,这里是backfilelist.log,包括备份开始时间,结束时间,备份类型,备份路径等。
可以根据备份开始时间,找到第一个早于还原时间点的完整备份  

2,如果找到恢复所需要的差异备份
同1,从完整备份开始,依次向后找各个增量备份,直到最后一个早于还原时间点的差异备份,可能有一个或者多个

3,如何找到差异备份之后,需要哪些binlog
基于binlog文件自身的最后修改时间属性信息,从2中找到的最后一个差异备份的时间,开始向后依次找binlog,可能有一个或者多个 

自动还原demo

如下是一个基于时间点来还原数据库的demo,没写入两条数据,执行一次备份(上述备份会自动区分完整备份或者差异备份)
三次备份之后,继续写两条数据,flush logs,然后继续分两次分别写两条数据,目的是将数据分散到不同的binlog中,最后删除全部数据
然后基于删除数据之前的时间点来自动生成还原数据库的shell,执行shell即可达到还原数据库的目的。

如下执行基于时间点的rextrabackup.py文件之后,时间点为"2019-08-01 18:50:59",也就是发生删除操作的前一个时间点,来生成的还原信息。
其实只需要重定向到一个shell文件中,执行shell文件即可自动化还原,或者直接在python脚本中执行这些命令,即可自动化完成还原操作。
这里为了显示,打印了出来。

可以发现,基于时间点的还原,找到的文件是预期的:
1个完整备份,2个增量备份,2个binlog日志中的一部分数据,
其中binlog日志还原的start-position成功地衔接到最后一个增量备份的position,同时最后一个binlog日志的还原停留在指定的时间点。

自动生成的shell还原代码

################uncompress backup file###################
innobackupex --apply-log --redo-only /temp/restoretmp/20190801184134_full
innobackupex --apply-log --redo-only /temp/restoretmp/20190801184134_full --incremental-dir=/temp/restoretmp/20190801184335_inc
innobackupex --apply-log --redo-only /temp/restoretmp/20190801184134_full --incremental-dir=/temp/restoretmp/20190801184518_inc
innobackupex --apply-log /temp/restoretmp/20190801184134_full
################stop mysql service###################
systemctl stop mysqld_7000
####################backup current database file###########################
mv /usr/local/mysql57_data/mysql7000/data /usr/local/mysql57_data/mysql7000/data_20190801185855
mkdir /usr/local/mysql57_data/mysql7000/data
chown -R mysql.mysql /usr/local/mysql57_data/mysql7000/data
################restore backup data###################
innobackupex --defaults-file=/usr/local/mysql57_data/mysql7000/etc/my.cnf --copy-back --rsync /temp/restoretmp/20190801184134_full
chown -R mysql.mysql /usr/local/mysql57_data/mysql7000/data
################stop mysql service###################
systemctl start mysqld_7000
################restore data from binlog###################
cd /usr/local/mysql57_data/mysql7000/log/bin_log
mysqlbinlog mysql_bin_1300.000001 --skip-gtids=true --start-position=982 | mysql mysql -h127.0.0.1 -uroot -proot -P7000
mysqlbinlog mysql_bin_1300.000002 --skip-gtids=true --stop-datetime="2019-08-01 18:50:59" | mysql -h127.0.0.1 -uroot -proot -P7000

日志信息

实现

# -*- coding: utf-8 -*-
import os
import time
import datetime
import sys
import socket
import logging logging.basicConfig(level=logging.INFO
#handlers={logging.FileHandler(filename='restore_log_info.log', mode='a', encoding='utf-8')}
) host = "127.0.0.1"
port = ""
user = "root"
password = "root"
instance_name = "mysqld_7000"
stop_at = "2019-08-01 18:50:59"
cnf_file = "/usr/local/mysql57_data/mysql7000/etc/my.cnf"
backup_dir = "/usr/local/backupdata/"
dest_dir = "/temp/restoretmp/"
xtrabackuplog_name = "backuplog.log"
backupfilelist = os.path.join(backup_dir,"backupfilelist.log") #根据key值,获取MySQL配置文件中的value
def get_config_value(key):
value = None
if not key:
return value
if os.path.exists(cnf_file):
with open(cnf_file, 'r') as f:
for line in f:
if (line.split("=")[0]):
if(line[0:1]!="#" and line[0:1]!="["):
if (key==line.split("=")[0].strip()):
value =line.split("=")[1].strip()
return value def stop_mysql_service():
print("################stop mysql service###################")
print("systemctl stop {}".format(instance_name)) def start_mysql_service():
print("################stop mysql service###################")
print("systemctl start {0}".format(instance_name)) #返回备份日志中的最新的一个早于stop_at时间的完整备份,以及其后面的增量备份
def get_restorefile_list():
list_backup = []
list_restore_file = []
if os.path.exists(backupfilelist):
with open(backupfilelist, 'r') as f:
lines = f.readlines()
for line in lines:
list_backup.append(line.replace("\n",""))
if (list_backup):
for i in range(len(list_backup) - 1, -1, -1):
list_restore_file.append(list_backup[i])
backup_name = list_backup[i].split("|")[2]
if "full" in backup_name:
full_backup_time = list_backup[i].split("|")[1]
if(stop_at<full_backup_time):
break
else:
list_restore_file = None
#restore file in the list_restore_log
list_restore_file.reverse()
return list_restore_file #解压缩需要还原的备份文件,包括一个完整备份以及N个增量备份(N>=0)
def uncompress_backup_file():
print("################uncompress backup file###################")
list_restore_backup = get_restorefile_list() #如果没有生成时间早于stop_at的完整备份,无法恢复,退出
if not list_restore_backup:
raise("There is no backup that can be restored")
exit(1) for restore_log in list_restore_backup:
#解压备份文件
backup_name = restore_log.split("|")[2]
backup_path = restore_log.split("|")[2]
backup_full_name = os.path.join(backup_dir,backup_path,backup_name)
backup_path = os.path.join(backup_dir,restore_log.split("|")[-1])
#print('''[ ! -d {0} ] && mkdir -p {0}'''.format(os.path.join(dest_dir,backup_name)))
os.system('''[ ! -d {0} ] && mkdir -p {0}'''.format(os.path.join(dest_dir,backup_name)))
#print("xbstream -x < {0}.xbstream -C {1}".format(backup_full_name,os.path.join(dest_dir,backup_name)))
os.system("xbstream -x < {0}.xbstream -C {1}".format(backup_full_name,os.path.join(dest_dir,backup_name)))
#print("cd {0}".format(os.path.join(dest_dir,backup_name)))
os.system("cd {0}".format(os.path.join(dest_dir,backup_name)))
#print('''for f in `find {0}/ -iname "*\.qp"`; do qpress -dT4 $f $(dirname $f) && rm -f $f; done '''.format(os.path.join(dest_dir,backup_name)))
os.system('''for f in `find {0}/ -iname "*\.qp"`; do qpress -dT4 $f $(dirname $f) && rm -f $f; done'''.format(os.path.join(dest_dir,backup_name))) current_backup_begin_time = None
current_backup_end_time = None
#比较当前备份的结束时间和stop_at,如果当前备份开始时间小于stop_at并且结束时间大于stop_at,解压缩备份结束
with open(os.path.join(dest_dir,backup_name,"xtrabackup_info"), 'r') as f:
for line in f:
if line and line.split("=")[0].strip()=="start_time":
current_backup_begin_time = line.split("=")[1].strip()
if line and line.split("=")[0].strip()=="end_time":
current_backup_end_time = line.split("=")[1].strip()
#按照stop_at时间点还原的最后一个数据库备份,结束从第一个完整备份开始的解压过程
if current_backup_begin_time<=stop_at<=current_backup_end_time:
break #返回最后一个备份文件,需要备份文件中的xtrabackup_info,解析出当前备份的end_time,从而确认需要哪些binlog
return backup_name #根据返回最后一个备份文件,需要备份文件中的xtrabackup_info,结合stop_at,确认需要还原的binlog文件,以及binlog的position信息
def restore_database_binlog(last_backup_file):
print("################restore data from binlog###################")
binlog_dir = get_config_value("log-bin")
if not (backup_dir):
binlog_dir = get_config_value("log_bin")
print("cd {0}".format(os.path.dirname(binlog_dir))) last_backup_file =os.path.join(dest_dir,last_backup_file,"xtrabackup_info")
#parse backuplog.log and get binlog name and position backup_position_binlog_file = None
backup_position = None
with open(last_backup_file, 'r') as f:
lines = f.readlines()
for line in lines:
if "binlog_pos = filename " in line:
backup_position_binlog_file = line.replace("binlog_pos = filename ", "").split(",")[0]
backup_position_binlog_file = backup_position_binlog_file.replace("'", "")
backup_position = line.replace("binlog_pos = filename ", "").split(",")[1].strip()
backup_position = backup_position.split(" ")[1].replace("'", "")
pass
else:
continue
# /usr/local/mysql57_data/mysql8000/log/bin_log/mysql_bin_1300
binlog_config = get_config_value("log-bin")
binlog_path = os.path.dirname(binlog_config)
binlog_files = os.listdir(binlog_path) #如果没有找到binlog,忽略binlog的还原
if not binlog_files:
exit(1) #对binlog文件排序,按顺序遍历binlog,获取binlog的最后的修改时间,与stop_at做对比,判断还原的过程是否需要某个binlogfile
binlog_files.sort() binlog_files_for_restore = []
# 恢复数据库的指定时间点
stop_at_time = datetime.datetime.strptime(stop_at, '%Y-%m-%d %H:%M:%S')
for binlog in binlog_files:
if (".index" in binlog or "relay" in binlog):
continue #保留最后一个备份中的binlog,以及其后面的binlog,这部分binlog会在还原的时候用到
if (int(binlog.split(".")[-1]) >= int(backup_position_binlog_file.split(".")[-1])):
binlog_files_for_restore.append(binlog) binlog_file_count = 0
#第一个文件,从上最后一个差异备份的position位置开始,最后一个文件,需要stop_at到指定的时间
for binlog in binlog_files_for_restore:
if not os.path.isdir(binlog):
#binlog物理文件的最后修改时间
binlog_file_updatetime = datetime.datetime.strptime(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(os.stat(binlog_path+"/"+binlog).st_mtime)),'%Y-%m-%d %H:%M:%S')
#判断binlog的生成时间,是否大于stop_at,对于修改时间大于stop_at的日志,需要全部还原,不需要stop_at指定截止点
if stop_at_time > binlog_file_updatetime :
if (binlog_file_count < 1):
if (len(binlog_files_for_restore) == 1):
# 找到差异备份之后的第一个binlog,需要根据差异备份的position,来过来第一个binlog文件
restore_commond = '''mysqlbinlog {0} --skip-gtids=true --start-position={1} --stop-datetime="{2}" | mysql mysql -h{3} -u{4} -p{5} -P{6}''' \
.format(binlog, backup_position, stop_at, host, user, password, port)
print(restore_commond)
binlog_file_count = binlog_file_count + 1
else:
# 找到差异备份之后的第一个binlog,需要根据差异备份的position,来过来第一个binlog文件
restore_commond = '''mysqlbinlog {0} --skip-gtids=true --start-position={1} | mysql mysql -h{2} -u{3} -p{4} -P{5}''' \
.format(binlog, backup_position, host, user, password, port)
print(restore_commond)
binlog_file_count = binlog_file_count + 1
else:
# 从第二个文件开始,binlog需要全部还原
restore_commond = '''mysqlbinlog {0} --skip-gtids=true | mysql mysql -h{1} -u{2} -p{3} -P{4}''' \
.format(binlog, host, user, password, port)
print(restore_commond)
binlog_file_count = binlog_file_count + 1
else:
if (binlog_file_count < 1):
restore_commond = '''mysqlbinlog {0} --skip-gtids=true --start-position={1} --stop-datetime={2} | mysql -h{3} -u{4} -p{5} -P{6}'''.format(binlog, backup_position,stop_at,host,user,password,port)
print(restore_commond)
binlog_file_count = binlog_file_count + 1
else:
if (binlog_file_count >= 1):
restore_commond = '''mysqlbinlog {0} --skip-gtids=true --stop-datetime="{1}" | mysql -h{2} -u{3} -p{4} -P{5}'''.format(binlog, stop_at,host,user,password,port)
print(restore_commond)
binlog_file_count = binlog_file_count + 1
break def apply_log_for_backup():
list_restore_backup = get_restorefile_list()
start_flag = 1
full_backup_path = None for current_backup_file in list_restore_backup:
#解压备份文件
current_backup_name = current_backup_file.split("|")[2]
current_backup_fullname = os.path.join(dest_dir, current_backup_name)
if(start_flag==1):
full_backup_path = current_backup_fullname
start_flag = 0
print("innobackupex --apply-log --redo-only {0}".format(full_backup_path))
else:
print("innobackupex --apply-log --redo-only {0} --incremental-dir={1}".format(full_backup_path,current_backup_fullname))
#apply_log for full backup at last(remove --read-only parameter)
print("innobackupex --apply-log {0}".format(full_backup_path)) def restore_backup_data():
print("####################backup current database file###########################")
datadir_path = get_config_value("datadir")
print("mv {0} {1}".format(datadir_path,datadir_path+"_"+ datetime.datetime.now().strftime('%Y%m%d%H%M%S')))
print("mkdir {0}".format(datadir_path))
print("chown -R mysql.mysql {0}".format(datadir_path))
print("################restore backup data###################")
list_restore_backup = get_restorefile_list()
full_restore_path= dest_dir + list_restore_backup[0].split("|")[-1].replace(".xbstream","")
print("innobackupex --defaults-file={0} --copy-back --rsync {1}".format(cnf_file,full_restore_path))
print("chown -R mysql.mysql {0}".format(datadir_path)) def restore_database():
#解压缩需要还原的备份文件
last_backup_file_path = uncompress_backup_file()
#对备份文件apply-log
apply_log_for_backup()
#停止mysql服务
stop_mysql_service()
#恢复备份
restore_backup_data()
#启动MySQL服务
start_mysql_service()
#从binlog中恢复数据
restore_database_binlog(last_backup_file_path) if __name__ == '__main__':
restore_database()

最后不要忘了清理战场:
1,解压缩的备份文件还留在指定的路径中,
2,还原之前备份的data文件,以data_日期命名的文件,也没有清理

挤时间写出来的,粗略测了一下没有问题,以实现功能为主,没有进一步封装,后续会以此为基础进行优化。

基于Python和Xtrbackup的自动化备份与还原实现的更多相关文章

  1. 新手入门贴之基于 python 语言的接口自动化 demo 小实战

    大家好,我是正在学习接口测试的菜鸟.近期通过自己的学习,完成了一个关于测试接口的接口自动化demo.下面想跟大家分享一下,主要的思路是根据接口文档确定测试用例,并将测试用例写在excel中.因为只是小 ...

  2. 基于python的App UI自动化环境搭建

    Android端Ui 自动化环境搭建 一,安装JDK.SDK 二,添加环境变量 Widows:1.系统变量→新建 JAVA_HOME 变量E:\Java\jdk1.7.0 jdk安装目录 2.系统变量 ...

  3. 基于Python+requests搭建的自动化框架-实现流程化的接口串联

    框架产生目的:公司走的是敏捷开发模式,编写这种框架是为了能够满足当前这种发展模式,用于前后端联调之前(后端开发完接口,前端还没有将业务处理完毕的时候)以及日后回归阶段,方便为自己腾出学(mo)习(yu ...

  4. elasticsearch备份和还原(基于hdfs)

    备份和还原,为什么elasticsearch还需要备份呢,明明可以设置副本做到高可用,那怕啥呢? 其实在实际的生产环境中,一般最终的结果数据都是要备份的,这样的做的目的,就是能够以最快的速度还原数据, ...

  5. Centos610-oracle 备份和还原

    前言 本文是为基于Centos6.*(linux)系列的Oracle备份和还原的操作记录,其中根据expdp和impdp不同参数可实现不同场景下的导出导入,为不同OS下面的Oracle迁移打下基础. ...

  6. python脚本:在Ubuntu16系统上基于xtrabackup2.4和mysql5.7实现数据库数据的自动化备份和恢复,亲测有效!

    1 安装教程 官网安装教程:https://www.percona.com/doc/percona-xtrabackup/2.4/installation/apt_repo.html -------- ...

  7. 一次完整的自动化登录测试-基于python+selenium进行cnblog的自动化登录测试

    Web登录测试是很常见的测试!手动测试大家再熟悉不过了,那如何进行自动化登录测试呢!本文作者就用python+selenium结合unittest单元测试框架来进行一次简单但比较完整的cnblog自动 ...

  8. 基于Python实现的死链接自动化检测工具

    基于Python实现的死链接自动化检测工具   by:授客 QQ:1033553122 测试环境: win7 python 3.3.2 chardet 2.3.0 脚本作用: 检测系统中访问异常(请求 ...

  9. web自动化 基于python+Selenium+PHP+Ftp实现的轻量级web自动化测试框架

    基于python+Selenium+PHP+Ftp实现的轻量级web自动化测试框架   by:授客 QQ:1033553122     博客:http://blog.sina.com.cn/ishou ...

随机推荐

  1. ARTS-S sed指定行添加

    sed -i 's#^AAAA.*#&BBBB#g' a.txt 在以AAA开始的行懂添加BBBB

  2. HtmlAgilityPack 获取节点的子节点

    这个问题真的是好无语 var table = doc.DocumentNode.SelectSingleNode("//table[@class='ddd']"); var a = ...

  3. base64编码的字符串与图片相互转换

    #region 图片转为base64编码的字符串---ImgToBase64String /// <summary> /// 图片转为base64编码的字符串 /// </summa ...

  4. 【算法】331- JS洗牌算法

    点击上方"前端自习课"关注,学习起来~ 最近的一个塔罗牌项目中,有一个洗牌的需求,其实也就是随机打乱数组,遂网上搜了下,再此做个整理- 塔罗牌 举例来说,我们有一个如下图所示的数组 ...

  5. 实习生4面美团Java岗,已拿offer!(框架+多线程+集合+JVM)

    美团技术一面 1.自我介绍 说了很多遍了,很流畅捡重点介绍完. 2.问我数据结构算法好不好 挺好的(其实心还是有点虚,不过最近刷了很多题也只能壮着胆子充胖子了) 3.找到单链表的三等分点,如果单链表是 ...

  6. Neety的基础使用及说明

    BIO(缺乏弹性伸缩能力,并发量小,容易出现内存溢出,出现宕机 每一个客户端对应一个线程 伪异步IO:创建线程池,由线程池里边的线程负责连接处理,M个个请求进来时,会在线程池创建N个线程.容易出现线程 ...

  7. java获取每月的第一天和最后一天

    // 获取当前年份.月份.日期 Calendar cale = null; cale = Calendar.getInstance(); // 获取当月第一天和最后一天 SimpleDateForma ...

  8. ubuntu16.04没有办法使用CRT,或者SSH工具的解决办法

    首先要明确一点,ubuntu16.04是默认没有安装SSH工具的 情况1 首先需要切换到root模式,然后在进行安装 设置root密码 sudo passwd 然后  sudo apt-get ins ...

  9. WebService创建、发布及在IIS上部署

    一.项目创建 1.     首先打开VS,这里我以VS2013为例 2.     点击“新建项目”,依次选择“Web”——>“Visual Studio 2012”——>“ASP.NET空 ...

  10. 【BZOJ 3771】Triple

    Problem Description 给出 \(n\) 个物品,第 \(i\) 个物品体积为 \(a_i\) . 对于每个体积 \(V\) ,求选出 \(3\) 个物品,体积之和为 \(V\) 的方 ...