分类 Coding 下的文章

云服务器中通过Docker镜像的方式使用Mysql数据库,数据安全考量定期备份数据库到阿里云OSS中

本文主要实现在阿里云ECS服务器中,通过脚本的方式实现将Docker中的Mysql数据库备份到阿里云OSS中。其他云服务商和其他环境的MySQL也可以使用类似的方式。具体方式不再赘述。

环境和工具

  1. 阿里云ECS服务器,安装Ubuntu或任何带有定时任务cron的Linux操作系统
  2. 阿里云OSS bucket和相应访问的账户
  3. 阿里云OSS命令行工具ossutil,可以到这里下载或查看详情
  4. MySQL镜像

具体步骤

开通OSS和相应子账号

  1. 进入阿里云OSS(对象存储)控制台,如果没有开通OSS服务可能会提示权限和开通确认。
  2. 创建bucket/存储桶,输入名字,比如xxxxbackup,记住这个名字以及界面中出现的Endpoint(地域节点),后续会用到。
    WX20220720-115411.png
  3. 创建一个有API的阿里云子账号,并赋予OSS读写权限。记住该账号的API和Secret,后续会用到。帮助文档
    WX20220720-115547.png

下载OSS命令行工具ossutil到本地系统

  1. 通过wget https://gosspublic.alicdn.com/ossutil/1.7.13/ossutil64下载到本地。这里是64位操作系统,32位可以去下载界面查看对应的下载地址。
  2. 增加运行权限:chmod +x ossutil64
  3. 移动到Path中:sudo mv ossutil64 /bin/

编写脚本

这里需要注意docker容器的名称,容器中应该有MYSQL用户名和密码的环境变量;示例中使用的是root作为用户名;另外备份临时文件夹也需要提前使用mkdir建好

脚本内容示例:

#!/bin/sh 

mysql_dump_path=备份文件临时目录,如/var/dbbackup/ 

#oss-config 
oss_endpoint="阿里云OSS Endpoint" 
oss_bucket="阿里云OSS bucket名称" 
oss_accesskeyid="子账号AccessKeyId" 
oss_accesskeysecret="子账号 AccessKey Secret" 
backup_name=`date +%Y%m%d%H%M%S`
cd ${mysql_dump_path} 

# 使用 mysqldump 备份整个数据库到临时文件夹中
# 注意docker修改docker镜像名称,这里是some-mysql
docker exec some-mysql sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases' > ${backup_name}.sql

# 打包/pack 
tar zcf ${backup_name}.tar.gz ${backup_name}.sql 
rm -f ${backup_name}.sql 

# 备份到OSS
ossutil64 cp ${backup_name}.tar.gz oss://${oss_bucket}/mysql/backup/${backup_name}.tar.gz -f -e ${oss_endpoint} -i ${oss_accesskeyid} -k ${oss_accesskeysecret} 
rm -f ${backup_name}.tar.gz 
echo "备份完成"

将脚本保存至任意一个不错的位置,比如/var/backup/dbbackup.sh,赋予可执行权限:sudo chmod +x /var/backup/dbbackup.sh

试着运行一下脚本文件sudo /var/backup/dbbackup.sh,再去oss控制台该bucket下-文件管理处查看是否有备份文件存入。如果一切没有问题,则就可以加入cron了。

加入cron计划任务

注:如果脚本位置和临时备份文件夹位置是当前用户无法直接编辑的,则应使用一下方法,在系统级别运行计划任务;用户级别会因为权限问题出错

使用你喜欢的编辑器打开/etc/crontab文件,并在文件末尾加上这行:

23 2 * * * root /var/backup/dbbackup.sh

这表示在每天的02:23:00运行我们的备份脚本。

重新启动cron计划任务:sudo service cron reload

在次日2:23分之后检查OSS控制台后台,即可发现整个数据库已经备份到阿里云OSS。

参考

  1. 使用ossutil定时备份自建mysql数据库到阿里云OSS
  2. Cron介绍
  3. docker hub: mysql

问题和原因

jupyter notebook通过nbconvert调用Latex导出PDF,由于nvconvert中转换模版中并不包含中文支持的package申明,导致Latex渲染时无法识别中文字符和对应的字体。出现类似:

LaTeX Error: Unicode character 正 (U+6B63) not set up for use with LaTeX.

的报错。导出的PDF文件中,所有中文字符都显示为空白。

解决方案

有两种方法可以解决:

  1. 先从Notebook导出为Latex,再使用如TexShop等工具打开Latex文件并添加中文支持,之后导出
  2. 修改nbconvert的模版,直接添加中文支持,这样即可直接导出PDF

这里说第二种办法:

首先通过jupyter --paths获得Jupyter相关路径,

% jupyter --paths

输出类似:

config:
    /Users/xxx/.jupyter
    /Users/xxx/custom_path/venv/etc/jupyter
    /usr/local/etc/jupyter
    /etc/jupyter
data:
    /Users/xxx/Library/Jupyter
    /Users/xxx/custom_path/venv/share/jupyter
    /usr/local/share/jupyter
    /usr/share/jupyter
runtime:
    /Users/xxx/Library/Jupyter/runtime

找到...share/jupyter文件夹所在位置,并定位到...share/jupyter/nbconvert/templates/latex文件夹,里面有nbconvert转换LaTex模版,
nvconvert-latex.png
通过修改base.tex.j2模版文件,添加xeCJK包申明和相关字体即可影响所有相关模版:

    \usepackage{xeCJK}
    \setCJKmainfont{STSong}

添加完成后,模版文件应类似:

((*- block header -*))
    ((* block docclass *))\documentclass[11pt]{article}((* endblock docclass *))

    ((* block packages *))
    \usepackage{xeCJK}
    \setCJKmainfont{STSong}
    \usepackage{iftex}
    ...

注意,字体部分可以通过Mac的字体书(Font Book)找到合适的中文字体名称,不一定非得使用宋体(STSong)。另外除了主要字体,也可以定义其他情况的字体,若加粗和倾斜时应用的字体。

相关文章

github:jupyter/nbconvert
Mac环境下jupyter notebook导出PDF显示中文的解决方案 (nbconvert 6, python 3.9)
convert notebook (contains Chinese words) to PDF successfully but those Chinese words are missing

通过在.Net Core应用中运行Python脚本,可以非常方便的进行某些功能的处理。特别是考虑到Python是解释型语言,不需要像.Net 那样编译再部署。

通过使用NuGet中的IronPythonIronPython.StdLib:

public class PythonScript
{
    private ScriptEngine _engine;

    public PythonScript()
    {
        _engine = Python.CreateEngine();
    }

    public TResult RunFromString<TResult>(string code, string variableName)
    {
        // for easier debugging write it out to a file and call: _engine.CreateScriptSourceFromFile(filePath);
        ScriptSource source = _engine.CreateScriptSourceFromString(code, SourceCodeKind.Statements);
        CompiledCode cc = source.Compile();

        ScriptScope scope = _engine.CreateScope();
        cc.Execute(scope);

        return scope.GetVariable<TResult>(variableName);
    }
}

然后使用运行脚本:

var py = new PythonScript();
var result = py.RunFromString<int>("d = 8", "d");
Console.WriteLine(result);

原生的ProcessProcessStartInfo (个人实践出来的)

注意,需要通过执行含有Python可执行文件和参数的shell脚本来实现执行Python脚本, 有点绕,具体就是:

1, 创建一个run.batWindows系统:

C:\YOUR_PYTHON_PATH\python.exe %*

2, 创建一个run.sh文件给LInux系统:

#!/bin/bash
/usr/bin/python3 "$@"

3, 使用Process and ProcessStartInfo 来运行脚本:

string fileName = null;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
    fileName = "path_to_bat/run.bat"
}
else
{
    fileName = "path_to_bat/run.sh"
}

ProcessStartInfo start = new ProcessStartInfo
{
    FileName = fileName,
    Arguments = string.Format("\"{0}\" \"{1}\"", script, args),
    UseShellExecute = false,
    CreateNoWindow = true,
    RedirectStandardOutput = true,
    RedirectStandardError = true
};
using Process process = Process.Start(start);

变量script指代需要执行的Python脚本所在位置,变量args指代脚本需要的参数,若有多个参数,酌情修改程序片段。

参考

  1. How to use Python in .NET-Core application?
  2. How to run python script from C# code by using command

Regex 正则表达式

A regular expression (shortened as regex or regexp; also referred to as rational expression) is a sequence of characters that specifies a search pattern in text. Usually such patterns are used by string-searching algorithms for "find" or "find and replace" operations on strings, or for input validation. It is a technique developed in theoretical computer science and formal language theory.

正则表达式(英语:Regular Expression,常简写为regex、regexp或RE),又称正则表示式、正则表示法、规则表达式、常规表示法,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。

Regular Expression的Regular一般被译为正则正规常规。此处的Regular即是规则规律的意思,Regular Expression即“描述某种规则的表达式”之意。

正则表达式用来做什么:

  1. 判断一个句子中是否有Lizzy, Hello everyone, my name is Li Zhiyan, you can call me Lizy.
  2. 判断一个字符串中是否有数字, Twenty two is 22
  3. 判断一个字符串中有英文字母, 23n20230n;23+&*^&*
  4. 判断一个字符串中是否有John或者Jone, Look! John's AirPods is becoming AirPod.
  5. 判断一个字符串是否是手机号码, 15195812506
  6. 判断一个字符串是否一Leon开头, Leon is from China.
  7. 判断一个字符串是否是邮件地址, [email protected]
  8. 使用非字符串分割字符串,A, B, c, D, E;F|g

正则表达式表达了什么

  1. 断言(Assertions)
    表示一个匹配在某些条件下发生。断言包含先行断言、后行断言和条件表达式。
  2. 字符类(Character Classes)
    区分不同类型的字符,例如区分字母和数字。
  3. 组和范围(Groups and Ranges)
    表示表达式字符的分组和范围。
  4. 量词(Quantifiers)
    表示匹配的字符或表达式的数量。
  5. Unicode 属性转义(Unicode Property Escapes)
    基于 unicode 字符属性区分字符。例如大写和小写字母、数学符号和标点。

在Python中,可以使用re模块和Pattern来做正则:

import re
p = re.compile(pattern, flags=0)
re.search(pattern, string, flags=re.IGNORECASE)
re.match(pattern, string, flags=0)
re.split(pattern, string, maxsplit=0, flags=0)
re.findall(pattern, string, flags=0)

Pattern.search(string[, pos[, endpos]])
Pattern.match(string[, pos[, endpos]])
Pattern.split(string, maxsplit=0)
Pattern.findall(string[, pos[, endpos]])

在JavaScript中,可以使用var re = /pattern/flags;

import re

p1 = re.compile('Lizzy')
s = 'Hello everyone, my name is Li Zhiyan, you can call me Lizzy.'
p1.search(s)

# [\d] = [^\D]
p2 = re.compile('[0-9]') # \d = [0123456789]n 
p2.search('Twenty two is 22')

p3 = re.compile('[a-zA-Z]') # \w (word) = [a-zA-Z0-9_]
p3.search('23n20230n;23+&*^&*')
<re.Match object; span=(2, 3), match='n'>


特殊符号

  1. \d: 匹配一个数字。等价于[0-9]。
  2. \D: 匹配一个非数字字符。等价于1
  3. \w: 匹配一个单字字符(字母、数字或者下划线)。等价于 [A-Za-z0-9_]。
  4. \W: 匹配一个非单字字符。等价于 2
  5. \t: 匹配一个水平制表符 (U+0009)。
  6. \v: 匹配一个垂直制表符 (U+000B)。
  7. \f: 匹配一个换页符 (U+000C)。
  8. \n: 匹配一个换行符 (U+000A)。
  9. \r: 匹配一个回车符 (U+000D)。
  10. \s: 匹配一个空白字符,包括空格、制表符、换页符和换行符。等价于[ \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]。
  11. \S: 匹配一个非空白字符。等价于 3
  12. .: 匹配除了换行符\n以外的任意一个字符
  13. \u{hhhh} or \u{hhhhh}: 仅当设置了u标志时)匹配一个十六进制数表示的 Unicode 字符。
# 使用|作为选择
p4 = re.compile('John|Jone')
p4.search('Look! John\'s AirPods is becoming AirPod.')

# 匹配次数 {m,n}
p5 = re.compile('^1[0-9]{10}') # '1\d{10}'
# p5.search('15195812506')
p5.match('15195812506')
<re.Match object; span=(0, 11), match='15195812506'>


匹配次数

  1. {m,n} m次到n次(包含)
  2. {m,} 至少m次
  3. {m} m次
  4. ? 0到1次
  5. + 至少1次
  6. * 任意次(0到∞)

指定匹配位置

  1. ^: 匹配字符串的开头
  2. $: 匹配字符串的末尾
  3. \b: 匹配单词(word)的边界
  4. \B: 匹配非单词边界
p6 = re.compile('^Leon')
s6 = 'Leon is from China.'
p6.search(s6)

# [email protected], \[email protected]  @b.com [\w-] a@b._
p7 = re.compile('[\w\.\-\+]+@[\w-]+[\w\-\.]+([a-zA-Z0-9]+)$')
p7.match('[email protected]')

p8 = re.compile('[,;\|]')
p8.split('A, B, c, D, E;F|g')
['A', ' B', ' c', ' D', ' E', 'F', 'g']


总结

转义

如果你需要使用任何特殊字符的字面值(例如,搜索字符''),你必须通过在它前面放一个反斜杠来转义它。 例如,要搜索'a'后跟''后跟'b',你应该使用/a*b/- 反斜杠“转义”字符'*',使其成为文字而非特殊符号。

高级话题

使用插入语

任何正则表达式的插入语都会使这部分匹配的副字符串被记忆。一旦被记忆,这个副字符串就可以被调用于其它用途,如同使用括号的子字符串匹配之中所述。

通过标志进行高级搜索

一般而言,各种语言中实现的正则表达式有可选参数 (flags) 允许全局和不分大小写搜索等。这些参数既可以单独使用也能以任意顺序一起使用, 并且被包含在正则表达式实例(模式)中。

Python中可以使用re.IGNORECASE不分大小写搜索:

import re
p = re.compile(pattern, flags=0)
re.search(pattern, string, flags=re.IGNORECASE)

JavaScript则是:

标志描述
g全局搜索。
i不区分大小写搜索。
m多行搜索。
s允许 . 匹配换行符。
u使用unicode码的模式进行匹配。
y执行“粘性(sticky)”搜索,匹配从目标字符串的当前位置开始。
let re = /^\w+\.\S+/gi;

用特殊字符检验输入

字符 含义
(x) 像下面的例子展示的那样,它会匹配 'x' 并且记住匹配项。其中括号被称为捕获括号。
模式`/(foo) (bar) \1 \2/`中的 '(foo)' 和 '(bar)' 匹配并记住字符串 "foo bar foo bar" 中前两个单词。模式中的`\1`和`\2`表示第一个和第二个被捕获括号匹配的子字符串,即 foo 和 bar,匹配了原字符串中的后两个单词。注意`\1`、`\2`、...、`\n` 是用在正则表达式的匹配环节,详情可以参阅后文的 `\n` 条目。而在正则表达式的替换环节,则要使用像 `$1`、`$2`、...、`$n` 这样的语法,例如,`'bar foo'.replace(/(...) (...)/, '$2 $1')`。$& 表示整个用于匹配的原字符串。
(?:x) 匹配 'x' 但是不记住匹配项。这种括号叫作非捕获括号,使得你能够定义与正则表达式运算符一起使用的子表达式。
看看这个例子 `/(?:foo){1,2}/`。如果表达式是 `/foo{1,2}/,{1,2}` 将只应用于 'foo' 的最后一个字符 'o'。如果使用非捕获括号,则 `{1,2}` 会应用于整个 'foo' 单词。
x(?=y) 匹配'x'仅仅当'x'后面跟着'y'.这种叫做先行断言。
例如,`/Jack(?=Sprat)/`会匹配到'Jack'仅当它后面跟着'Sprat'。`/Jack(?=Sprat|Frost)/`匹配‘Jack’仅当它后面跟着'Sprat'或者是‘Frost’。但是‘Sprat’和‘Frost’都不是匹配结果的一部分。
(?<=y)x 匹配'x'仅当'x'前面是'y'.这种叫做后行断言。
例如,`/(?<=Jack)Sprat/`会匹配到' Sprat '仅仅当它前面是' Jack '。`/(?<=Jack|Tom)Sprat/`匹配‘ Sprat ’仅仅当它前面是'Jack'或者是‘Tom’。但是‘Jack’和‘Tom’都不是匹配结果的一部分。
x(?!y) 仅仅当'x'后面不跟着'y'时匹配'x',这被称为正向否定查找。
例如,仅仅当这个数字后面没有跟小数点的时候,`/\d+(?!\.)/` 匹配一个数字。正则表达式`/\d+(?!\.)/.exec("3.141")`匹配‘141’而不是‘3.141’

Reference / 参考

  1. Wiki:正则表达式
  2. JavaScript:正则表达式
  3. Python:Regular expression operations
  4. Java:Pattern
  5. .NET:Regular Expression Language - Quick Reference

  1. 0-9
  2. A-Za-z0-9_
  3. \f\n\r\t\v\u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff

Salesforce Advice

Reduce class level variables data storage

Don't use class level variables to store a large amounts of data.

User SOQL for Loops

Utilize SOQL For Loops to iterate and process data from large queries.

for(Conatct ctt : [Select Id,Name,Account.Name FROM Contact]) {
    // do your work
}

Shorten variables lifetime

Construct methods and loops that allow variables to go out of scope as soon as they are no longer needed.

Other Advice

Using Efficient Algorithms

Being the heart of any code, algorithms can make or break the running of your application. A good space-efficient algorithm can save a lot of your heap memory while simultaneously performing smoothly. Before developing any logic or writing any code snippet, sit, and think through the algorithms to use and choose the one that serves your purpose and makes use of the heap memory efficiently. Try not to split the parts of your algorithms into different functions; rather try to keep them in line when used only within that code.

Built-in Apex libraries

Try to leverage and make use of the inbuilt Apex libraries that Salesforce provides other than writing your custom logic for everything. For example, use the Math class for performing mathematical operations, use JSONGenerator class to create JSON structures, and many more. One line of method calling code is way better than rewriting the logic on your own. This not only saves you time but also the heap memory is managed efficiently.

Avoid using temporary variables

Creating temporary variables is a favorite practice of many developers. We unknowingly create such variables in our code without realizing that we can do without them. These variables take up unnecessary space and add up to the heap memory.

Shorten variable names & declarations

You can spare characters when naming temporary variables like loop counter variables as i instead of myCounter. You should keep meaningful names according to the business.
You can also combine variable declarations like:

Integer i = 0, j = 10;

Shorten Field API Name

We all love to give meaningful names to the custom fields in Salesforce objects. Little do we know that a long API name impacts the code heap size. For example, it is counted in SOQL query size, code that references the field, and each time it is accessed. Maximum length of SOQL statements is 100K.

Remove unnecessary debug statements

Since all the debug statements are counted against the code length, it is necessary to keep a cap on them in order to limit the consumed heap size of the Apex code. It is fine to use them while doing the apex development but should be removed or minimized in the final production version of the code.

Reference