Huffman编码
注意: 原创技术博客,转载请注明原文地址。
Huffman编码简介
依然记得初次接触Huffman
编码,是在大一的《计算机组成原理》课程上,老师采用Huffman
编码实现了一种CPU
(虚拟机字节码同理)变长指令集。当时感觉特别神奇,后来又在《数据结构》课程上接触到了Huffman Tree
(霍夫曼树),算是对Huffman
编码有了一个比较全面的认识。
1 | #!/usr/bin/env python |
注意: 原创技术博客,转载请注明原文地址。
依然记得初次接触Huffman
编码,是在大一的《计算机组成原理》课程上,老师采用Huffman
编码实现了一种CPU
(虚拟机字节码同理)变长指令集。当时感觉特别神奇,后来又在《数据结构》课程上接触到了Huffman Tree
(霍夫曼树),算是对Huffman
编码有了一个比较全面的认识。
1 | #!/usr/bin/env python |
注意: 本文的所有优化仅针对InnoDB存储引擎。
MySQL索引可以加快查询速度,但是索引并不是越多越好,索引虽然可以加快数据的查询速度,但是索引文件本身需要占用存储空间,数据的插入,删除,和修改也需要索引保持同步,据一线有经验的DBA介绍,索引列占表所有列的30%是比较合理的。
1 | show index from `table`; |
MySQL
information_schema
数据库TABLES
表官方手册
1 | select concat(round(sum(INDEX_LENGTH/(1024*1024)), 2), 'MB') as 'Total Index Size' from `information_schema`.`TABLES` where `table_schema` = 'order' and `table_name` = 'order_base'; |
注意: 以下两种情况,不建议建立索引。
table
,我们要在表table
的column
列上建立一个索引,我们使用如下SQL
语句计算该索引的选择性:1 | select count(distinct `column`) / select count(*) from `table`; |
前缀索引是一种与索引选择性相关联的索引优化技术,顾名思义,使用列的前缀代替整个列作为Key,当前缀长度合理时,既可以做到前缀索引的选择性接近全列索引,同时因为索引Key变短而减少索引文件的大小和维护开销。
想象一下存在如下业务场景,一张存放订单信息的基本表order
,订单号长度为24位(如:61700123215824),表结构如下:
id | order_id |
---|---|
1 | 61700123215824 |
2 | 61500280698102 |
3 | 61500280756582 |
首先计算一下,全列索引选择性:
1 | select round(count(distinct `order_id`) / count(*), 2) from `order`; |
结果为1.00,选择前缀长度为5,看一下索引的选择性:
1 | select round(count(distinct left(`order_id`, 5)) / count(*), 2) from `order`; |
结果为0.00,5个字符长度的前缀,看来不行,我们增加到10,看一下索引的选择性:
1 | select round(count(distinct left(`order_id`, 10)) / count(*), 2) from `order`; |
结果为0.63,差强人意,我们继续以5位单位递增,看一下索引的选择性:
1 | select round(count(distinct left(`order_id`, 15)) / count(*), 2) from `order`; |
由于round的四舍五入,结果为1.00,那前缀能不能更少一些,在公司的业务系统内试了一下,前缀长度为11时,索引选择性为0.9999,四舍五入为1。通过夹逼的方法(😆默默的想起高数的夹逼定理),找到了最佳的索引前缀长度为11,接下来我们创建前缀索引:
创建前缀索引
1 | alter table `order` add index `idx_order_id`(`order_id`(11)); |
注意: 前缀索引的缺点
order_id
列的全部信息,所以并不能用于covering index。快速将开发分支代码合并至测试分支。
1 | export DEV=dev |
1 | function merge_dev() { |
统计工程代码行数
1 | find ./ -name "*.py" -exec wc -l {} \;|awk 'BEGIN{total=0}{print $1"\t"$2; $total+=$1}END{print "total lines: "$total}' |
显示代码行最多的文件
1 | find ./ -name "*.py" | xargs -n 1 wc -l|sort -nr |
显示处于TIME_WAIT
的SOCKET
个数
1 | netstat -n|awk '/^tcp/{a[$6]++}END{for (j in a) print a[j], j}'|sort -nr|head -n 10 |
Bash Shell快捷键默认为Emacs编辑模式,与Emacs编辑器快捷键兼容,可通过set -o vi
切换至vi编辑模式,通过set -o emacs
切换回默认的Emacs编辑模式,本文所列快捷键均为Emacs模式下快捷键。
本人工作中一直使用Emacs编辑模式,Emacs编辑模式下的Alt键默认非Meta键(Emacs中使用频率很高的组合键),需要在终端中打开该项设置。
[1]: Bash官方手册
1 | show variables like '%innodb_old_blocks_pct%'; -- 37% |
Thrift由Facebook开发,解决了由不同语言编写的服务之间的调用问题。2009年Facebook将Thrift贡献给Apache基金会,成为一个开源项目。Thrift为典型的C/S架构,采用IDL(Interface Description Language),定义接口。
Thrift提供了命令行工具thrift,根据指定的IDL定义文件,生成不同语言的代码。因Thrift的依赖比较多,所以建议读者,采用自己开发平台的包管理工具进行安装。本人平时在macOS下进行开发工作,采用brew install thrift,便可自动完成了thrift的安装。
Thrift IDL的语法比较接近C++,熟悉C++的读者,可以迅速掌握Thrift IDL的语法。由于IDL语法上比较接近C++,所以Thrift IDL比较偏向于静态语言,Python、Ruby等动态语言开发者在使用IDL定义接口时需要特别注意。
Thrift IDL支持C/C++风格的“//”,“/*,*/”注释,也支持Python风格的”#”注释。
struct——结构体类型,类似于C/C++中的结构体类型,将不同类型的数据聚合到一块儿。(注:描述面向对象语言中的类)。
1 | struct User { |
注意:
enum枚举类型
1 | enum UserType { |
容器类型比较类似于C++ STL中的容器类型,Thrift提供了三种容器类型:list,map,set。
Thrift支持C/C++风格的,typedef关键字,用于声明自定义类型。
1 | typedef i32 Integer |
Thrift支持自定义异常,语法同struct类型的定义相似,如下所示:
1 | exception NotFound { |
注:在编写服务端代码时,仅仅在代码中抛出异常,是不够的,需要在定义接口时,指明该接口可能抛出的异常
Thrift IDL中的Service,与Java中的Interface有异曲同工之妙。Thrift定义服务的语法如下:
1 | service <name> { |
定义一个获取用户的Service示例如下:
1 | service UserService { |
注:service中接口定义之间的分割符号,可以是”,“,也可以是”;“。
Thrift支持C++风格的命明空间,等同于Java、Python中的package的概念。Thrift定义命名空间的语法如下:
1 | namespace cpp com.example.project |
示例文件:
1 | // filename: user.thrift |
在Shell里面输入如下命令,生成Python代码:
1 | thrift -out py --gen py:new_style,utf8strings,coding=utf-8 |
--gen参数,指定待目标语言,冒号后以都好分割的为thrift为该语言提供的选项。
-out参数,指定thrift生成代码的存放路径(**注:仅有一个-**)
更详细的用法,可通过thrift --help
获取。
[1]Thrift官方手册
[3]Thrift官方示例
公司最近上线了“订单管理系统”,系统内有比较多的分页展示逻辑,所以我单独拿出了点时间,对MySQL的分页做了总结。
提到MySQL分页,我们通常会首先考虑,使用偏移量offset+limit的办法实现。下面以实现目标:
查询订单表order
,并按订单创建时间create_time
,降序排序,每页50
条记录
,为例进行说明。首先我们想到的sql语句如下:
1 | select *from `order` order by `create_time` desc limit 0, 50; |
多次执行该操作,随着页数增加(即:偏移量增大),该查询语句的性能随之下降,耗时比较明显。当并发量上来时,这对MySQL数据库的压力是致命的。以翻页至200页为例,MySQL会查询200 * 50
条记录,最后只返回50
条记录,前面的200 * (50 - 1)
条记录,将会被丢弃。
因为订单的属性很多,所以并无法为每一列建立索引(当然为每一列都建立索引,是简单粗暴的)。优化此类分页查询的一个最简单的办法,尽可能使用索引覆盖扫描,而不是查询所有列,然后在关联返回所需要的列。优化后的SQL语句如下所示:
1 | select *from `order` inner join (select id from `order` order by `create_time` limit desc 10000, 50) as `tmp` using(`id); |
这里MySQL扫描了尽可能少的页面,获取需要访问的记录后,然后再去关联查询,获取了所需的列,该种用法还存在若干类似的变种。
最后,也是我们确定的技术选型。推荐的用法,前端每次查询都传入上次查询记录的max_id,或者min_id传递给我们,然后根据id的索引去优化该操作。(注:只有id列的单调性与目标列的单调性一致时可采用该方案)产品设计上,我们不返回具体的页数,只提供了当前页,前后10页跳页的功能,解决了该问题。
向后翻页
1 | select *from `order` order by `create_time` desc where `id` < min_id limit 50; |
向前跳页
1 | select *from `order` order by `create_time` desc where |
向前翻页
1 | select *from `order` order by `create_time` desc where `id` > max_id limit 50; |
向后跳页
1 | select *from `order` order by `create_time` desc where `id` > max_id limit 450, 50; |
当数据库,采用分库分表,或者中间件时的分页操作,较为复杂,需要根据具体情况确定,本次不做陈述。
本人是一个vim党,平时的开发工作都是在vim下完成的,但是…vim的官方手册就达2000多页,所以本人将平时遇到的一些比较有趣的插件与配置记录在配置文件中,一是方便他人借阅,分享一些实用的技巧,另一方面也是自我的慢慢积累。
Python2.7版本,代码缩进是不允许空格与制表符Tab混合使用的,否则会抛出IndentationError异常,这困扰过很多初学者,明明看着缩进都对,但是就是报错。
1 | set lcs=tab:>-,trail:- |
在开发工作中,经常需要输入一些常量值,因为数值比较大,不太容易计算的原因,很多同学直接输入了常量表达式。比如,存在时间戳t = int(time.time()),现在需要获取该时间戳24小时前的时间戳,可以这样写:
1 | _t = int(time.time()) - 24 * 3600 |
对于Python这种解释性语言来说,这种开销就落到了运行时,所以在vim中插入表达式值这个技巧显得比较实用。
在插入模式下(注意是插入模式),按下,然后键入=,=后面输入需要计算的表达式,摁下Enter便可以计算出该表达式的值。
有时候需要在服务器上修改一些配置,或者脚本,而服务器的vim未配置,没有代码注释插件,该怎么达到目的呢?这时,可以按下
进入列选择模式,选中需要注释代码的前几列,可通过o或O调整选择块的范围,然后按下I,输入注释符,Python中为’#’,然后按下 ,便可大功告成(注意:按下A则在块后面添加)。
大多数同学退出vim,都是在命令行下输入,:wq,退出,因为键位的缘故,个人感觉这样不是很方便,好在vim提供了更加便捷的方式。
关闭文件时,只需在普通模式下,输入ZZ,(即左侧shift+z,z按下两次),效果等同于:x命令,文件内容有改动,保存修改并退出,更改文件修改时间,如果文件未有改动则退出,不更改文件修改时间。
想要丢弃对文件的更改,大多数同学都是用,:q!,强制退出的。在vim中也可以在普通模式下输入ZQ,强制退出。
注意:此用法需要当前用户,在sudoer文件中授权。
在工作中经常遇到,root用户的vim未进行任何配置,而用普通用户去编辑的时候,保存却没有权限的尴尬。下面的vim命令可以,调用sudo进行修改保存,非常实用。
1 | :w !sudo tee % |
常见选择方式:v - 选择字符,V - 行选择,
- 列选择。
大多数同学,进行多行代码选择时会用V开启行选择,然后通过j,k,o选择,代码块,或者是g跳转等,这样子虽然速度不慢,但是还是不够优雅。
vim中有个段落的概念(paragraphs),大家可以通过:tab help paragraphs,查看paragraphs的描述,或者该博客
在开发工作中,我习惯将不同小功能,用空格划分为不同段落(Python开发中,这也是Pep8建议的),然后用段落移动命令,在段落间移动,用vip命令选择整个段落,很方便。
命令 | 效果 |
---|---|
]] | foo |
[[ | bar |
) | 向前移动一条一语句(forward) |
( | 向后移动一条语句(backward) |
{ | 向前移动至下一段落 |
} | 向后移动至下一段落 |
vap | 选择一个段落 |
vip | 选择一个段落 |
在vim中,用w,b,e,ge,等可以在单词之前移动,这种移动方式,大大方便了我们的编程,但是也有一些不尽如人意的地方,比如,下面的函数foo,有a,b,c三个参数,想要把光标从a移动至b,则需要输入2w,(vim将逗号,作为一个单词),这样子不是很方便(用习惯的同学除外),vim有个iskeyword选项,定义了“单词”的组成字符,可以将“,”,“.”,做为单词的一部分,下次移动的时候直接按下w便可从参数a,移动至参数b。(个人习惯,不喜勿喷^_^)
1 | def foo(a, b, c): |
1 | :set iskeyword+=\, |
或
1 | :set iskeyword+=\,\. |
1 | PREFIX=$(cd `dirname $0`; pwd) |
1 | ssh user@host 'mkdir -p .ssh && cat >> .ssh/authorized_keys' < ~/.ssh/id_rsa.pub |
1 | nohup /usr/bin/ssh -N -L 0.0.0.0:8000:10.10.10.10:12000 sa.demo.com & |
1 | openssl pkcs12 -in demo.p12 -out demo.pem -nodes |
1 | ls *.py|xargs -n 1 wc -l|awk 'BEGIN{total=0}{$total+=$1}END{print "total lines:\t"$total}' |
Kafka
常见操作命令行集锦导出变量定义: $ZK_HOSTS
定义在~/.bashrc
中的导出变量,为Zookeeper
集群地址
查看Kafka Cluster中的topic
1 | ./bin/kafka-topics.sh --list --zookeeper $ZK_HOSTS |
查看Kafka某个topic的信息
1 | ./bin/kafka-topics.sh --describe --topic benchmark --zookeeper $ZK_HOSTS |
新建一个1个partition的topic readbench
1 | ./bin/kafka-topics.sh --create --topic readbench --partitions 1 --replication-factor 1 --zookeeper $ZK_HOSTS``` |
新建一个5分区,复制因子为1的topic
1 | ./bin/kafka-topics.sh --create --topic name --partitions 5 --replication-factor 1 --zookeeper $ZK_HOSTS |
以group_name为消费组消费名称为name的topic中的数据
1 | ./bin/kafka-console-consumer.sh --topic name --partition 0 --consumer-property group.id=group_name --from-beginning --bootstrap-server 10.95.134.86:9092,10.95.134.86:9093,10.95.134.86:9094 |
往topic name中生产数据
1 | bin/kafka-console-producer.sh --broker-list 10.95.134.86:9092,10.95.134.86:9093,10.95.134.86:9094 --sync --topic name |
物理删除kafka中的一个topic(kafka默认是逻辑删除)
1 | ./bin/kafka-topics.sh --delete --topic name --zookeeper $ZK_HOSTS |
Zookeeper
集群中name
标识的topic
1 | ./bin/zkCli.sh rmr /brokers/topics/name |
Kafka
中为topic增加partition(不能够删除partition,仅支持增加)
1 | ./bin/kafka-topics.sh --alter --topic name --zookeeper $ZK_HOSTS --partitions 3 |
Kafka中为topic更新配置参数
1 | ./bin/kafka-topics.sh --alter --topic readbench --zookeeper $ZK_HOSTS --config cleanup.policy=delete |
Kafka集群中添加新的broker时,需要将一些topic的存储压力分散到新的broker上去,这时需要kafka reassign工具,分三步操作
1 | cat >> topics-to-move.json << EOF |
1 | ./bin/kafka-reassign-partitions.sh --zookeeper $ZK_HOSTS --topics-to-move-json-file topic-to-move.json --broker-list "1,2" --generate |
将输出信息中的Proposed partition reassignment configuration一栏下的json字符串保存为reassign.json文件。
1 | echo '{"version":1,"partitions":[{"topic":"readbench","partition":0,"replicas":[3]}]}' >> reassign.json |
1 | ./bin/kafka-reassign-partitions.sh --zookeeper $ZK_HOSTS --reassignment-json-file reassign.json --execute |
1 | /bin/kafka-reassign-partitions.sh --zookeeper $ZK_HOSTS --reassignment-json-file reassign.json --verify |