Mysql2ch,一个同步MySQL数据到ClickHouse的项目

介绍 mysql2ch 是一个用于同步 MySQL 到 ClickHouse 的工具,支持全量同步与增量同步。 特性 支持全量同步与增量同步。 支持 DDL 与 DML,当前支持 DDL 字段新增与删除与重命名,支持所有的 DML。 支持 redis 和 kafka 作为消息队列。 依赖 kafka,缓冲 MySQL binlog 的消息队列,当使用 kafka 作为消息队列时需要。 redis,缓存 MySQL binlog position 与 file。 安装 pip install mysql2ch 使用 配置文件 [core] # 当前支持kafka和redis作为消息队列 broker_type = kafka mysql_server_id = 1 # redis stream最大长度,多出的消息会按照FIFO删除 queue_max_len = 200000 init_binlog_file = binlog.000024 init_binlog_pos = 252563 # 跳过删除的表,多个以逗号分开 skip_delete_tables = # 跳过更新的表,多个以逗号分开 skip_update_tables = # 跳过的DML,多个以逗号分开 skip_dmls = # 每多少条消息同步一次,生产环境推荐20000 insert_num = 1 # 每多少秒同步一次,生成环境推荐60秒 insert_interval = 1 [sentry] # sentry environment environment = development # sentry dsn dsn = https://xxxxxxxx@sentry.test.com/1 [redis] host = 127.0.0.1 port = 6379 password = db = 0 prefix = mysql2ch # 启用哨兵模式 sentinel = false # 哨兵地址 sentinel_hosts = 127.0.0.1:5000,127.0.0.1:5001,127.0.0.1:5002 sentinel_master = master [mysql] host = 127.0.0.1 port = 3306 user = root password = 123456 # 需要同步的数据库 [mysql.test] # 需要同步的表 tables = test # 指定kafka分区 kafka_partition = 0 [clickhouse] host = 127.0.0.1 port = 9000 user = default password = # need when broker_type=kafka [kafka] # kafka servers,multiple separated with comma servers = 127.0.0.1:9092 topic = mysql2ch 全量同步 你可能需要在开始增量同步之前进行一次全量导入,或者使用--renew重新全量导入,该操作会删除目标表并重新同步。 ...

May 5, 2020

flask-admin之自定义ModelView使不同行显示不同样式

前言 如果有使用过django-suit—一个 django admin 的美化框架,这个框架的话,其 admin 设置中可以使用 suit_row_attributes 这个方法,在后台表单中设置表单列表中不同的行有不同的属性。这个方法在有些情况下很有用,比如说订单列表可以根据不同的订单状态在表单中呈现不同的样式,比如支付成功的可以设置为 bootstrap 的 table-success,支付失败的可以设置为 table-danger,这样在查看后台表单的时候可以更加一目了然。代码类似于下面这样: def suit_row_attributes(self, obj->Order, request): css = { 2: 'table-success', }.get(obj.status) if css: return {'class': css} 这样在订单状态为 2 的一行的时候,该行会显示为绿色。 关于 flask-admin 由于最近在使用 flask 这个框架,对应的 admin 管理使用的是flask-admin,同样需要像 django-suit 这样设置 row attributes。 关于 django 和 flask,flask 是一个微服务框架,本身只提供了基本的请求响应处理等功能,并且可以很快的搭建一个 rest api 服务,但是如果需要其他的,比如 ORM、权限管理、后台管理等扩展功能,就需要一个一个集成第三方扩展;而 django 提供的是一站式全方位解决方案,包含 admin 管理、ORM、权限控制、模板引擎等,具体该使用哪个框架,需要视业务场景而定。 回到 flask-admin,这个扩展的功能还是比较强大的,包含了 model view 显示,action 等功能,并且可以很方便的自定义。然而关于上面提到的需求,官方是没有自带的解决方案的,所以就只能重写 list.html 这个模板了。 解决方案 官方文档里有写: To override any of the built-in templates, simply copy them from the Flask-Admin source into your project’s templates/admin/ directory. As long as the filenames stay the same, the templates in your project directory should automatically take precedence over the built-in ones. ...

March 18, 2019

Django之自定义Field实现admin直接上传图片到七牛云并存储链接

Django自带的admin模块的功能实际上已经足够强大了,但是我们不免有一些自定义化的需求,比如我们在后台存储图片时,为了提高系统的性能,我们会使用第三方cdn服务去存储图片等静态资源,然后将链接存入数据库中,但是将图片上传到cdn,然后复制链接存入数据库又是比较繁琐的一个过程,作为程序员,肯定要寻求更高效的解决方法,比如自定义Field。 实际上覆盖admin的字段在Django中有两种方式,一种是自定义Form,然后在admin.py对应的ModelAdmin中覆盖form属性,比如: class MyForm(forms.Form): class Meta: widgets = { 'field':CustomWidget() } # admin.py class MyAdmin(admin.ModelAdmin): form = MyForm 这样就将field字段覆盖为CustomWidget,在CustomWidget中,就可以自定义template和css等。这样的话在每个需要自定义的Field都需要这样做一次,如果使用频繁的话就不怎么推荐,推荐另一种更通用的方式,自定义models.py中的Field。 1、构建widgets 要实现上传七牛的功能,首先在项目同名的目录下建一个widgets.py,实现自己的QiniuWidgets类,另外说一点的是,该实现参考了django-ckeditor项目,可以在admin中集成富文本编辑器,还是很不错的,推荐使用。 class QiniuWidgets(forms.FileInput): template_name = 'admin/qiniu_input.html' def __init__(self, attrs=None, app=None, table=None, unique_list=None): """ :param attrs: :param app: app :param table: 数据模型 :param unique_list: 唯一标识列表,除了id """ super(QiniuWidgets, self).__init__(attrs) self.unique = unique_list if settings.DEBUG: env = 'dev' else: env = 'pro' self.filename_prefix = '{}/{}/{}/'.format(env, app, table) def format_value(self, value): return value def value_from_datadict(self, data, files, name): file = files.get(name) # type:InMemoryUploadedFile file_data = b''.join(chunk for chunk in file.chunks()) # 取出二进制数据 file_type = file.name.split('.')[-1] # 得到文件的后缀 unique_filename = '_'.join(list(map(lambda x: data.get(x), self.unique))) file_name = self.filename_prefix + '{}_{}.{}'.format(name, unique_filename, file_type) # 构造文件的唯一文件名 q = QiniuUtils(settings.QINIU_ACCESS_KEY, settings.QINIU_SECRET_KEY) # 七牛上传实例 q.delete(settings.QINIU_BUCKET_NAME, file_name) # 删除已经存在的 q.upload(settings.QINIU_BUCKET_NAME, file_name, file_data) # 上传新的 http = 'https://' if settings.QINIU_USE_SSL else 'http://' url = http + settings.QINIU_DOMAIN + '/' + file_name # 拼接最终的url return url def render(self, name, value, attrs=None, renderer=None): context = self.get_context(name, value, attrs) template = loader.get_template(self.template_name).render(context) return mark_safe(template) 现在QiniuWidgets介绍类中方法的含义: ...

February 13, 2019

一步一步打造我的第一个Android应用之缘起Android

前言 在我大二下学期的时候,开始接触到了Android开发。最开始的原因也是只是一个念头:我想要做一款Android应用,不管是什么功能的,当时也没什么具体的概念。其实在大二上学期的时候我已经自学了PHP,当时也是因为对微信开发有了兴趣。而学习微信开发需要会一门服务器端开发语言,自己只会一些c语言和c++,于是上网百度了一番。不知道看到谁说PHP语言简单易上手,而我只是一个尚未入门的菜鸟,具体的也是不知道的,于是就在慕课网上找了一个PHP的入门教程开始跟着学起来。 PHP&微信开发 这里简单谈一下PHP的学习过程是因为后面的Android应用的后端是用thinkphp开发的。刚开始学的时候确实觉得PHP确实挺简单的,因为当时已经有了c++的底子,而PHP的语法跟c++也是很相似的,所以入门也是比较快。而入门之后就开始进阶教程,也是感觉比较轻松。PHP学的感觉有一些基础之后,就开始找了一个PHP微信开发的教程开始学习,也是慕课网上的教程。微信开发的学习就不是那么顺利了,因为之前学PHP基本上只要懂得语法就行了,而语法的学习是比较简单的。而对于微信开发,最好是有一些web开发的经验,实际上微信开发也是web开发的一种罢了,而对于我这个菜鸟恰恰是最欠缺的,因为基本上我在学校学的都是语言本身的基本的东西而没有涉及到具体的开发,所以学习起来很是吃力。微信开发最开始的token验证就很是花了一番功夫。对于我来讲,接触到的一些新的名词比如token,get&post请求,url,服务器等等对于我来讲都是一头雾水,不知其然。而且又没有谁可以引导入门,完全都是靠自己摸索和在网上寻找有关的教程、博客等。其实现在看来,很多东西都是差临门一脚,只要入门了就会好很多了。而且像互联网这些知识,新的东西是层出不穷,知识的更新也是日新月路,所以自学是非常重要的,如果不能跟紧潮流,那很容易就会被淘汰掉的。后来自己捣鼓出一个微信公众号后就再也没深入研究了,有一些基本的功能比如天气提醒、讲个笑话、听个音乐什么的。没有继续深入的原因是,究其原理微信开发实际上就是web后端开发罢了,微信把用户的请求发送到你的服务器上,而开发者负责处理请求并返回结果等,并无太大难度可言,对于一个掌握基本技能的后端开发者来讲,跟着官方文档来并没有太大的难度。另一方面就是自己本身也是基于学习的,也没有真正的业务来交给我做,所以也就不再接触了。 关于Web 只会PHP的话只有自己一个人是做不出什么东西的来的,一般来讲还需要一个做前端的人。只是由于自己性格比较内向,很少去接触什么社团之类的,再加上学校的这些方面的氛围确实不是很好,很少有一些什么开发团队创业团队什么的,有也是很少的,不得不说这是大学生活的一个遗憾了。既然没有人来帮着做前端,那就只能自给自足了。最开始的时候其实是想学习web开发的,也就是html+css+js这些东西,做网站开发。只是随着学习的深入,我发现想做好一个网站远远不是只要会html等就行了,还需要好看的UI,人性化的交互设计等等。而我确实对这些并没有多大的天赋,做不出好看的网页谈何web开发呢?或许是从内心深处对设计网页、写网页、写css等不感兴趣,几次想搭建一个自己的个人博客都半途而废(直到我遇见了hexo…)。于是学完基础的一些东西后,就没有再深入地进行学习了,然后我现在的前端水平也仅限于看得懂html+css+js,可以小改一下界面。所以说我其实很佩服那些网页做得很炫酷的人,怎么说呢,人也许总是会佩服那些有已短之长的人。 关于Android 开头已经说过了,学习Android是因为一个偶然的念头。那是在大二暑假的时候,当时我会自己所学的PHP加前端知识做了一个简陋的快递查询的小网友,以现在的眼光来看,那真的是太简陋了,只有简单的输入框和淡黄色背景,当时做出来还觉得非常地有成就感,我找了一下,没想到还找到了当时在QQ空间发的截图: 代码估计已经被我删掉了,确实也没什么价值。而且因为后来换了一次服务器而忘了备份数据库的原因,对应的数据库也没了,这估计是我犯的最愚蠢的一次错误了,也有不熟悉的原因,还好并没有太大的损失,都是自学时写的一些小东西,不过这一次的教训一定要牢牢深记。 还是回到Android开发吧。因为暑假的时间也比较多,想着不能就这么浪费啊,还是学一些新的知识吧。因为Android应用接触的还是很多的,Android手机现在使用得也非常普遍,所以就决定,学Android吧。于是网上找教程,找视频什么的,我现在还记得我是在腾讯课程找的一个Android入门教程,然后开始跟着视频搭建开发坏境,安装jdk,配置Java环境,下载AndroidStudio等等,说到这里,不得不提一下了,原生Android使用Java语言开发的,而我们学校当时并没有开Java这门课,我们专业到大三的时候才开始学习。没办法,只能学一门新的语言了。所幸Java语言的语法跟c++非常的相似,而Android开发大所用到的只是Java的基础语法,更多的是熟悉各种库api的使用,视图的搭建等等,以及那一套开发的通过流程。入门还是很简单的,我现在的水平估计也只是比入门好那么一些,不过也已经能开发出一个简单的app了,具体该如何深入与进阶我还不知道该怎么做,我觉得等参与的实际项目多了进步也就很快了。 最后 还是放一下app的截图吧。 写这个系列的原因是通过完成这个app,学到了很多东西,但是却没有好好的去理一下。通过写博客的方式,不仅可以为后来者提供一点帮助,也可以让自已温习一下学过的知识,加深自己的印象。我也希望自己能把写博客这个习惯坚持下去,因为我从别人的博客中得到的帮助真的很多很多,我也希望自己能够像别人一样。

February 15, 2017

再谈快速排序

前言 说到快速排序,在学数据结构的时候都已经学到了,不过当时总是有些云里雾里的,就算是照着书上的代码敲出来,也不是很明白是什么意思。后来在大三上学期的时候又接触到了快速排序,虽然实现的方法不一样。数据结构将的时候右边和右边一起向中间靠近,而算法导论将的是从做往右靠近,其实大概的思路都是差不多的。这里不想说一些时间复杂度,算法分析什么的,网上的资料很多,也不班门弄斧了。只是把自己的一点理解写出来,也许有后来的新手对于快速排序有相同的一点疑问,而且恰好看到我这偏文章并得以解惑,那就很是不错了。 分析 快速排序主要的思想就是在待排序的数组中取一个数字作为标兵,暂且就这么理解了。然后以这个标兵为界,把比标兵小的放在标兵的左边,把比标兵大的放在标兵的右边,这样我们可以知道不管左边或者右边的是怎么样,但是这个标兵已经处于排好序的数组中的正确的位置了。当然这是升序排序,降序也是一样的道理,思想就是这么简单。但是这个数字怎么取,取哪一个,然后怎么实现标兵的左边大,右边小,这就是重点所在了。最理想的情况当然是每次取到最中间大小的那个数,因为这样的话每次两边都刚好可以分到一半;而最坏的情况就是取到最大或者最小的,这样每次标兵虽然在正确的位置,但会存在一边没有元素,而另一边则是剩下的所有元素,这已经退化为插入排序了。所以为了尽量得到好的标兵,优化的快速排序算法有三数取中快速排序,随机化的快速排序,随机化三数取中快速排序等。三数取中指的是从数组的开头、结尾、中间的中取一个中间大小的数作为标兵,这样取出的数很可能靠近最优标兵;而随机化指的是随机从数组取出一个数作为标兵;而随机化三数取中则是前面两种算法的集中。 partition 为什么要一直说这个标兵呢?标兵为什么这么重要?那就不得不说快速排序的最重要的操作 partition 了。 partition 接受一个待排序的数组作为参数,返回标兵的正确位置。另外,这里的一个编程技巧是将标兵移到最后一个位置,这样在遍历数组的过程中便于交换。完整的 partition 函数如下: /** * @param A 传入的数组 * @param l 最左边位置 * @param r 最右边位置 * @return 标兵的位置 */ int partition(int A[], int l, int r) { //最后一个元素 int x = A[r]; int i = l - 1; for (int j = l; j < r; j++) { //如果不大于最后一个元素,就放在i的左边,并且i的位置加1 if (A[j] <= x) { i++; int temp = A[i]; A[i] = A[j]; A[j] = temp; } } //交换i位置与最后位置的元素,这样i的左边的元素都小于A[i],右边的元素都大于A[i] int temp = A[i + 1]; A[i + 1] = A[r]; A[r] = temp; //返回标兵的位置 return i + 1; } 完整的代码 #include <iostream> using namespace std; void quick_sort(int A[], int p, int r); int partition(int A[], int l, int r); void print(int A[], int length); int main(int argc, char const *argv[]) { int length; cout << "请输入待排序的个数:"; cin >> length; int A[length]; cout << "请输入排序的数字,以空格分开:"; for (int i = 0; i < length; ++i) { cin >> A[i]; } quick_sort(A, 0, length - 1); print(A, length); return 0; } /** * @param A 传入的数组 * @param l 最左边位置 * @param r 最右边位置 * @return 标兵的位置 */ int partition(int A[], int l, int r) { //最后一个元素 int x = A[r]; int i = l - 1; for (int j = l; j < r; j++) { //如果不大于最后一个元素,就放在i的左边,并且i的位置加1 if (A[j] <= x) { i++; int temp = A[i]; A[i] = A[j]; A[j] = temp; } } //交换i位置与最后位置的元素,这样i的左边的元素都小于A[i],右边的元素都大于A[i] int temp = A[i + 1]; A[i + 1] = A[r]; A[r] = temp; //返回标兵的位置 return i + 1; } void print(int *A, int length) { for (int i = 0; i < length; i++) { cout << A[i] << " "; } cout << endl; } void quick_sort(int A[], int p, int r) { if (p < r) { //q即是标兵的正确位置 int q = partition(A, p, r); //对左边部分继续调用quick_sort quick_sort(A, p, q - 1); //对右边部分继续调用quick_sort quick_sort(A, q + 1, r); } } 结语 算法一道真的是博大精深,作为一个计算机专业的,我的算法其实也是很差,比较汗颜。回想刚接触这个专业时什么都不懂,到现在更是觉得学海无涯。有道是一入 IT 深似海,从此妹纸是路人,咳,不是,从此青春是路人。且行且学习吧。 ...

January 31, 2017