放牛的

放牛的日子,是人生初恋的诗...

0%

数据来源:Latency Numbers Every Programmer Should Know

有点过时的数据:Latency numbers every programmer should know

时间单位换算: 1s = 1,000ms = 1,000,000us = 1,000,000,000ns

毫秒(ms):1s = 1,000ms

微秒(us):1ms = 1,000us

纳秒(ns):1us = 1,000ns

等待事件 2000 2005 2010 2015 2018
L1缓存引用 6ns 1ns 1ns 1ns 1ns
分支预测错误 19ns 3ns 3ns 3ns 3ns
二级缓存引用 25ns 4ns 4ns 4ns 4ns
互斥锁加锁/解锁 94ns 17ns 17ns 17ns 17ns
内存引用 100ns 100ns 100ns 100ns 100ns
压缩1KB 11us 2us 2us 2us 2us
普通网络发送2KB 45us 8us 1us 250ns 88ns
SSD随机读 18us 17us 17us 16us 16us
从内存顺序读1MB 301us 95us 30us 9us 5us
在同一个数据中心环游 500us 500us 500us 500us 500us
从SSD顺序读1MB 5ms 2ms 494us 156us 78us
磁盘寻址 10ms 7ms 5ms 4ms 3ms
从磁盘顺序读1MB 20ms 7ms 3ms 2ms 1ms
从CA发包到Netherlands再回来 150ms 150ms 150ms 150ms 150ms

0. 缘起

开始用MySQL8.x,没注意字符集的事,只设了utf8mb4,COLLATE用了默认的utf8mb4_0900_ai_ci。

后来发现,每个现场的数据库,基本都是低版本,没有utf8mb4_0900_ai_ci这个字符集。

为了统一,将之前的utf8mb4_general_ci改为utf8mb4_0900_ai_ci。

总结:

之前的字符集:utf8mb4, utf8mb4_0900_ai_ci

修改后的字符集:utf8mb4, utf8mb4_general_ci

1. 思路

  • 修改database的字符集
  • 修改表的字符集
  • 修改列的字符集

2. 操作

2.1 全库备份,有备无患

2.2 修改库的字符集

ALTER DATABASE SJZC CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

2.3 修改表的字符集

2.3.1 生成SQL

1
2
3
4
5
6
7
8
9
SELECT
CONCAT( 'ALTER TABLE ', table_name, ' CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;' )
FROM
information_schema.TABLES AS T,
information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` AS C
WHERE
C.collation_name = T.table_collation
AND T.table_schema = 'SJZC'
AND ( C.CHARACTER_SET_NAME != 'utf8mb4' OR C.COLLATION_NAME != 'utf8mb4_general_ci' );

2.3.2 执行

2.4 修改列的字符集

2.4.1 生成varchar列字符集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SELECT
CONCAT(
'ALTER TABLE `',
table_name,
'` MODIFY `',
column_name,
'` ',
DATA_TYPE,
'(',
CHARACTER_MAXIMUM_LENGTH,
') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci',
( CASE WHEN IS_NULLABLE = 'NO' THEN ' NOT NULL' ELSE '' END ),
';'
)
FROM
information_schema.COLUMNS
WHERE
TABLE_SCHEMA = 'SJZC'
AND DATA_TYPE = 'varchar'
AND ( CHARACTER_SET_NAME != 'utf8mb4' OR COLLATION_NAME != 'utf8mb4_general_ci' );

2.4.2 生成其他列字符集(text等)

1
2
3
4
5
6
7
8
SELECT
CONCAT( 'ALTER TABLE `', table_name, '` MODIFY `', column_name, '` ', DATA_TYPE, ' CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci', ( CASE WHEN IS_NULLABLE = 'NO' THEN ' NOT NULL' ELSE '' END ), ';' )
FROM
information_schema.COLUMNS
WHERE
TABLE_SCHEMA = 'SJZC'
AND DATA_TYPE != 'varchar'
AND ( CHARACTER_SET_NAME != 'utf8mb4' OR COLLATION_NAME != 'utf8mb4_general_ci' );

2.4.3 执行

Spring Scheduler 和 Quartz都支持CRON来定义调度周期。

CRON表达式由「空格」分割的6部分组成,分别描述「秒、分、时、天、月、周」。

每部分可以用「星号(*)、数字(0~9)、斜线(/)、逗号(,)、横线(-)」表示,月 和 周还可以用前3个英文字母。

  • 数字(0~9):
  • 星号(*):所有取值范围的数字
  • 斜线(/):代表每,*/5 即每5个单位
  • 逗号(,):几个离散的数字
  • 横线(-):连续的数字

例子:

  • “0 0 * * * *” = 每天每个小时的整点
  • “*/10 * * * * *” = 每10秒钟
  • “0 0 8-10 * * *” = 每天的8,9,10整点
  • “0 0 6,19 * * *” = 每天的6点和19点两个整点
  • “0 0/30 8-10 * * *” = 每天的8:00, 8:30, 9:00, 9:30, 10:00 和 10:30
  • “0 0 9-17 * * MON-FRI” = 周一到周五每天的9~17点整点
  • “0 0 0 25 12 ?” = 每个圣诞夜0点

SQL用like做模糊匹配,如下所示:

1
2
3
select *
from student
where name like '张%'

这样就能匹配到所有姓「张」的学生。

但这里有个问题,就是当我要匹配的内容中有「%」该怎么办?

这就用到escape了。

比如: 找出名字中含有「%」的论文:

1
2
3
select *
from paper
where name like '%\%%' escape '\'

escape的意思是:后面的内容\是前面字符串%\%%的转义字符。这样前面字符串中的第2个%将不再认为是通配符,而被认为是要匹配的内容。

escape 只作用于一个like,当出现多个like都需要转义时,每一个like后面都需要加escape

比如: 找出名字 或 内容中含有「%」的论文:

1
2
3
select *
from paper
where name like '%\%%' escape '\' or content like '%\%%'

注: 本文转自 和菜头 的 槽边往事。

一般来说,富人不会分享自己发家致富的秘密。所以,有互联网以来,我看到许多在网上教人致富的人都发了财。在英文博客界,写关于如何发财的博文成为了一个专门的门类。而真正的有钱人只会用心灵鸡汤敷衍一下大众,根本不会提供任何方法论,更不要说是致富秘诀了。

也有例外。硅谷投资人Naval Ravikant在5月31日突然一口气发了40条语录,内容就是关于如何不靠运气而致富。随后这40条文字被不断转发,翻译为多国语言,这一事件史称“推文风暴”。Naval是印度裔移民,20多年来一直在美国硅谷创业。他最著名的创业项目是AngelList,世界股权众筹平台的鼻祖;他最著名的投资项目有两个,一个是Twitter,一个是Uber。

奇怪的是,国内互联网上的反响并不强烈。按理说这应该是中国人最感兴趣的话题了,而且要比各种心灵鸡汤高好几个段位,为什么会遭到冷遇呢?我分析了一下原因,主要有两点:1、网上的版本翻译得不好,每个中文字都认识,连起来看就不懂了;2、致富这个话题在中文互联网里,已经被鸡汤严重污染了,所以许多人根本理解不了Naval在说什么。

因此,我决定自己翻译并且注释一下Naval的40条语录:

How to Get Rich (without getting lucky)

《如何不靠运气致富》

作者:Naval Ravikant

翻译&注释:和菜头

  1. Seek wealth, not money or status. Wealth is having assets that earn while you sleep. Money is how we transfer time and wealth. Status is your place in the social hierarchy.

去寻求财富,而非金钱或地位。财富就是你拥有资产,而资产在你睡觉的时候都还在为你赚钱;金钱是我们转换时间和财富的工具;身份是你在社会等级体系里所处的位置。

  1. Understand that ethical wealth creation is possible. If you secretly despise wealth, it will elude you.

要明白一件事:一个人完全可以不靠坑蒙拐骗站着赚取财富。如果你在暗中鄙视财富,那么财富也会躲着你。

  1. Ignore people playing status games. They gain status by attacking people playing wealth creation games.

别去理会那些热衷于玩身份游戏的人,他们通过攻击那些创造财富的人以获得自己的身份。

  1. You’re not going to get rich renting out your time. You must own equity — a piece of a business — to gain your financial freedom.

你不会通过出租自己的时间而变得富有。你必须拥有产权,也就是生意的一部分,以此才能赢得个人财务自由。

  1. You will get rich by giving society what it wants but do

es not yet know how to get. At scale.

提供社会大众想要但是他们还不知道如何获取的东西,你就会因此而致富。但有一点:你必须规模化地供应社会。

  1. Pick an industry where you can play long term games with long term people.

选择一个你可以长期从事的产业,寻找一批可以一起长期共事的人。

  1. The Internet has massively broadened the possible space of careers. Most people haven’t figured this out yet.

互联网极大拓展了一个人职业生涯的可能性。绝大多数人对此毫无认知。

  1. Play iterated games. All the returns in life, whether in wealth, relationships, or knowledge, come from compound interest.

玩就玩复利游戏。无论是财富,人际关系或者是知识,所有你人生里获得的回报,都来自复利。

  1. Pick business partners with high intelligence, energy, and, above all, integrity.

在选择商业合作伙伴的时候,选择那些高智商、精力旺盛的家伙,但在这一切之上,他应该是个正直诚实的人。

  1. Don’t partner with cynics and pessimists. Their beliefs are self-fulfilling.

不要和愤世嫉俗者和悲观主义者合作,因为他们会任由坏事发生,以此证明他们的负面看法是正确的。

  1. Learn to sell. Learn to build. If you can do both, you will be unstoppable.

学会如何销售,学会如何创建。如果你同时能做到这两件事,你的成功将无可阻挡。

  1. Arm yourself with specific knowledge, accountability, and leverage.

用独到知识,责任感和杠杆武装自己。

  1. Specific knowledge is knowledge that you cannot be trained for. If society can train you, it can train someone else, and replace you.

独到知识是那种不可以通过培训而获得的知识。这是因为,如果这种知识可以经由培训而得,那么其他人同样也可以,并且以此取代你。

  1. Specific knowledge is found by pursuing your genuine curiosity and passion rather than whatever is hot right now.

在真正的好奇心和热情驱使你前进的路上,你更有可能获得独到知识,而不是在追逐潮流热点的闻风起舞脚步里。

  1. Building specific knowledge will feel like play to you but will look like work to others.

创建独到知识的过程对于你就像是在玩,而对于别人则像是工作。

  1. When specific knowledge is taught, it’s through apprenticeships, not schools.

不能通过学校教育教会一个人独到知识,它只能通过学徒制口传身教。

  1. Specific knowledge is often highly technical or creative. It cannot be outsourced or automated.

独到知识通常极富技术性和创造性,因此它不能被外包或自动实现。

  1. Embrace accountability, and take business risks under your own name. Society will reward you with responsibility, equity, and leverage.

拥抱责任感,押上自己的声誉以承担商业风险。社会也会以责任,产权和杠杆作为回报。

  1. The most accountable people have singular, public, and risky brands: Oprah, Trump, Kanye, Elon.

最具责任感的人都具有独一无二的、世人皆知的、敢于冒险的个性特征,如奥普拉、川普、坎耶、埃隆。

  1. “Give me a lever long enough, and a place to stand, and I will move the earth.” — Archimedes

只要给我一根足够长的杠杆,一处可以立足的地方,我就能撬起地球。——阿基米德

  1. Fortunes require leverage. Business leverage comes from capital, people, and products with no marginal cost of replication (code and media).

财富增长需要使用杠杆。商业杠杆有三个来源:1、资本;2、人力;3、复制起来边际成本为零的产品(如:代码和媒体)。

  1. Capital means money. To raise money, apply your specific knowledge, with accountability, and show resulting good judgment.

资本的意思就是钱。想要融资,那就运用你的独到知识,配合你责任感,展示出你良好的判断力。

  1. Labor means people working for you. It’s the oldest and most fought-over form of leverage. Labor leverage will impress your parents, but don’t waste your life chasing it.

人力指的就是为你干活的人,它是最古老也是争夺最激烈的杠杆。人力杠杆会让你父母因为你手下有许多人为你工作而感到骄傲,但你不要浪费生命去追求这一点。

  1. Capital and labor are permissioned leverage. Everyone is chasing capital, but someone has to give it to you. Everyone is trying to lead, but someone has to follow you.

资本和劳动力是需要征得许可才能使用的杠杆。每个人都在追逐资本,但总得有个什么人给你才行;每个人都想要领导其它人,但总得有什么人愿意跟着你才行。

  1. Code and media are permissionless leverage. They’re the leverage behind the newly rich. You can create software and media that works for you while you sleep.

代码和媒体是无需要许可即可使用的杠杆。它们是新贵人群背后的杠杆,你可以通过自己创建的软件和媒体,在睡觉时仍然为你干活。

  1. An army of robots is freely available — it’s just packed in data centers for heat and space efficiency. Use it.

一支机器人军团已经集结待命,只是为了节约空间和热效能,它们被打包放进数据中心。去用吧。

  1. If you can’t code, write books and blogs, record videos and podcasts.

如果你不会编程,那你还可以写书和博客,或者做视频或者音频节目。

  1. Leverage is a force multiplier for your judgement.

杠杆能够成倍地放大你的判断力(所产生的效能)。

  1. Judgement requires experience, but can be built faster by learning foundational skills.

判断力需要经验,但它可以通过学习基本技能的方法更快速地建立起来。

  1. There is no skill called “business.” Avoid business magazines and business classes.

并不存在一种叫做“商业”的能力。尽量避开商业杂志和商业课程。

  1. Study microeconomics, game theory, psychology, persuasion, ethics, mathematics, and computers.

去学习微观经济学、博弈论、心理学、说服术、伦理学、数学和计算机科学。

  1. Reading is faster than listening. Doing is faster than watching.

读比听快,做比看快。

  1. You should be too busy to “do coffee,” while still keeping an uncluttered calendar.

你应该忙得没有社交的时间才对,与此同时你应该始终保证日程安排井井有条。

  1. Set and enforce an aspirational personal hourly rate. If fixing a problem will save less than your hourly rate, ignore it. If outsourcing a task will cost less than your hourly rate, outsource it.

你应该为自己设定一个有抱负的个人时薪数,并且坚持执行。如果解决一个问题所能节省下来的成本低于你的个人时薪,那就忽略这个问题好了;如果一项任务的外包成本低于你的个人时薪,就把它外包出去。

  1. Work as hard as you can. Even though who you work with and what you work on are more important than how hard you work.

尽管你跟谁一起工作、做什么工作,要远比你的努力程度更加重要。但还是要倾尽全力去工作。

  1. Become the best in the world at what you do. Keep redefining what you do until this is true.

你所做的事情,要努力做到世界最好。不断重新定义你在做什么,直到真的做到世界最好。

  1. There are no get rich quick schemes. That’s just someone else getting rich off you.

这个世界上并没有快速赚钱致富的方法,如果你想要找寻这种方法,那它只会让别人从你身上赚钱致富。

  1. Apply specific knowledge, with leverage, and eventually you will get what you deserve.

运用你的独到知识,配合上杠杆,最终你会得到你应该得到的东西。

  1. When you’re finally wealthy, you’ll realize that it wasn’t what you were seeking in the first place. But that’s for another day.

终有一天当你变得富有,你会发现那一切并不是你最开始想要的东西。但是那就是另外一回事了。

注释:

1、财富就是你睡着觉,你的资产也在为你继续赚钱。这是一个越来越被广泛接受的定义。Naval Ravikant是硅谷狂热的数字货币支持者,所以,他的话另有所指。从前后文来看,他所谓的资产并不等于是传统意义上的房产、股票、收藏,而是偏向于他反复提及的:软件和媒体。

2、出租时间概念,许多人理解为打工,认为打工就是出租自己的时间以换取金钱。其实并非如此,Naval所指的出租时间概念,指的是一个人的财富增长,是否直接关系到他的时间。一个小卖部的老板,他并不为谁打工,但是他的财富增长需要他长时间守在店里,因此,他依然是出租时间换钱。但一个淘宝点卡店老板则不同,他的点卡销售是全自动的,不需要24小时守着,而且也不需要只做这一样生意。这就是Naval所谓互联网拓宽了个人职业生涯的一个例子。

3、equity我翻译为产权,不是一个很好的翻法。但是Naval前文提到assets,很明显,作为投资人他非常清楚地知道这两个字眼之间的区别。equity无论是翻译为股票、权益或者是资产,原文说“ You must own equity — a piece of a business — to gain your financial freedom.”,这是和出租时间概念做对应的。出租时间的人,在商业链条里作为生产资料出现,不拥有任何产权,也就无法通过商业行为获利,所以,我这里勉强翻译为产权。

4、specific knowledge我翻译为独到知识,没有翻译为特定知识、专业知识或者是特殊知识。原因是在我的理解中,specific knowledge不是书本知识,也不是学校教授的知识,更不可能在网上免费获取。一方面,它只能提供自己实践来获取;另一方面,它只能通过前人口耳相传。这种知识是做成一件事情的关键,属于知识体系中不共的那一部分。所以,我翻译为独到知识。

5、“Give me a lever long enough, and a place to stand, and I will move the earth.” — Archimedes 这话不像是阿基米德说的。更像是一次抬杠的结果:

“给我一个支点,我就能撬起地球!”

“那么,您站在哪儿呢?”

“好吧,给我一个支点,再给我一个站立的地方,我就能撬起地球。”

“那么,您用空气就能撬起地球了?”

“好吧,给我一根足够长的杠杆,一处可以立足的地方,我就可以翘起地球!”

“那么,阿基米德先生,支点又不需要了吗?”

“滚!”

6、accountability我本想翻译为“靠谱程度”,想想还是算了。

7、号称是“四十条语录”,但是我就找见了39条。

8、结合上下文看,Leverage一词始终翻译为“杠杆”其实也不大对头。Naval一再强调代码、博客、播客、视频节目,我觉得Leverage在他那里,有些时候应该相当于是个人影响力的代名词,或者可以简单理解为放大器。

使用SVN命令行的机会不多,一般也不想用,但无可奈何之时,也要用它来救命。

1. checkout,从仓库把代码拉到本地

1
2
3
4
5
6
# 介绍:
svn checkout path
# 例子:
svn checkout svn://host/project/www
# 简写:
svn checkout svn://host/project/www

2. status,当前目录下的状态

这大概是最常用的指令了。

1
2
3
4
5
6
# 介绍:
svn status [path]
# 例子:
svn status
# 简写:
svn st

3. add,文件交给SVN管理

如果只是增加了文件,没有add,虽然在这个文件夹下,svn是不会自动管理的,除非你add。

1
2
3
4
5
6
# 介绍:
svn add path
# 例子:
svn add *
svn add readme.txt
svn add *.java

4. commit,将文件提交

1
2
3
svn commit -m "comment"
#简写
svn ci -m "comment"

5. delete,删除文件

1
svn delete path -m "comment"

工作中,我习惯性的写单元测试。

我并没有受过专业的单元测试训练,也没有别人的指导和要求,是自己这几年工作下来,在没有外在要求的环境中,自己养成的习惯。

我知道这个习惯给我的工作带来了不小的帮助,这次把它写出来,是自己的一次总结,写给自己。

如果你读到了,也可以作为参考。

1. 什么是单元测试?

先看「维基百科」怎么说:

计算机编程中,单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

通常来说,程式设计师每修改一次程式就会进行最少一次单元测试,在编写程式的过程中前后很可能要进行多次单元测试,以证实程式达到软件规格书要求的工作目标,没有程序错误;虽然单元测试不是什么必须的,但也不坏,这牵涉到专案管理的政策决定。

每个理想的测试案例独立于其它案例;为测试时隔离模块,经常使用stubs、mock[1]或fake等测试马甲程序。单元测试通常由软件开发人员编写,用于确保他们所写的代码符合软件需求和遵循开发目标。它的实施方式可以是非常手动的(透过纸笔),或者是做成构建自动化的一部分。

下面是我个人理解。

1.1 是开发过程的一部分

「单元测试」虽名为「测试」,却是开发人员干的活,在开发阶段测试自己「即将写的代码」或「刚写好的代码」是否完成自己预期的功能。

在写代码的过程中,要知道自己在写的功能是干什么的,怎么知道这部分功能是对的,整个思考实现的过程,脑中一直都有个抽象的模型。当完成模型中的一小块功能后,马上验证下对与不对,再继续后面的工作。

1.2 是为开发服务的

我不太赞成强制为所有代码写单元测试,并过度强调测试覆盖,这样的结果会让我全身不自在,像被绑起来一样。写代码是要有一定的自由度的。

要求高的时候,单元测试是产品的一部分,要为产品的质量保驾护航。大部分情况下,单元测试是为我服务,而不是我去满足单元测试的各种要求。

换句话说,是因为它能给我带来帮助,对我有益,我才愿意去写它。

1.3 是一个验证的工作

我知道自己写的代码,大多数时候,不是一次就能写对的。

与其让别人告诉我代码有问题,不如我自己先验证一下。

1.4 是针对单一功能

单元测试是针对单一功能,如果功能很多,出现了问题将很难准确定位。

写代码应该是件简单的事,将多个简单的功能都写对了,再将之组合起来,出现问题的概率会大大降低。

单一功能更方便测试,功能多了,要测试的点也多了,单元测试往往没办法写下去。

2. 为什么要写单元测试?

如果没有外在的要求,可能很多人不愿意写单元测试。我为什么要写单元测试?

2.1 直接写出来的代码,往往是不对的

或许有人能将代码一次写对,这是我羡慕的。

我也想一次把代码写对,但我的经验告诉我,这对我太难,难到几乎做不到。

于是我对自己降低了要求,不要求自己第一次写出来的代码是对的,要求自己每次提交的代码是对的。

在提交代码之前,多测试一下,这样做到的概率大大提高了。

2.2 更低的调试成本

自己要测试,用的Java又是静态语言,Hotswap并不是很有效,经常需要重启JVM。

如果做的项目小,启动时间在10秒以内,那也罢了。

大多数时候,不是启Tomcat,就是启Weblogic,启动时间都是分钟级别的,这样测试验证的时间成本太高了,改代码10秒钟,测试一下3分钟,结果是:更少的测试。

用单元测试就好多了,就算用Spring加载一大堆东西,启动时间也在10秒内。

2.3 代码修改后,仍需要验证

有句话叫:代码不是写出来的,是改出来的。

很少有代码是写出来不用修改的。或是因为需求变了,或是因为前期没有考虑周全,或是因为实际情况与预期的不完全一致。

不管什么原因导致的修改,改了总需要再验证一下,看看改的是不是对的。

所以,单元测试不只是第一次写的时候有用。如果换个人过来改,有没有 单元测试,区别不是一般的大。

2.4 验证部分功能

如果不写单元测试,想测试部分功能,往往是做不到的。组合在一起是一个功能,内部有很多小功能组成,而出问题是其中的一个,要测试这一个的需要满足N个条件,这种情况下,测试变成了个麻烦事。

有了单元测试,就方便多了。写代码的人,更愿意用代码解决问题,而不是花很多时间去搭建测试环境、创造测试条件。

3. 如何知道该写一个单元测试了?

代码分2种:一种要单元测试的,另一种是不需要写单元测试的。

如何区分这2种呢?

3.1 一个独立的功能

业务上是独立的功能,一般是要写一个测试Case的。

功能的划分,是设计阶段 要完成的。

独立的功能是单元测试的必要条件。

如果一个人说,他的代码没办法写单元测试,我会将之等价于:没有设计,没有模块化,自己都不知道在写什么,代码一团糟。。。

这种情况,不改代码真的没办法写单元测试。

3.2 容易出错

一个地方是否容易出错,写代码的人是能感觉到的。

比如:我对Mybatis写的查询,全部要做单元测试。因为它的SQL是写到XML文件中,没有编译器的保证,肉眼识别没那么靠谱,测一下就放心多了。

3.3 是接口

接口几乎总是满足测试条件的,也几乎总是要测试的。

3.4 有算法

用Java写的代码,需要设计算法的时候并不多,一旦有,就需要认真对待,算法要考虑周全嘛!

所以,写算法的代码时,就要准备好各个测试点。

4. 怎么写一个单元测试?

写单元测试要做哪些事?

4.1 测试的功能本身独立

独立的功能是一个基本要求,功能或大或小,要独立起来算一个功能,有输入输出,更直白的说,有一个函数,最好是纯函数。

如果没有,先安全的处理下代码。

参考书籍:《重构,改善既有的代码设计》。

4.2 工具生成测试代码框架

这是开发工具都会带的功能,根据源代码,生成测试这些代码的框架性的代码。

比如:Intellij IDEA的「JUnit」插件。

4.3 基本功能的测试

常规的输入输出,必做的。

4.4 异常Case测试

我是会分情况来做异常Case测试。经常将之省去。

4.5 性能测试

用单元测试跑跑,测一下性能也无不可。代码灵活嘛!

用单元测试的功能,启若干个线程,压一压性能,有时还是比专业工具更得心应手。

5. 方法与技巧

如何知道自己测试的结果是对的?

5.1 Print

将输出打印出来,自己判断一下。

没办法自动化,要靠人来判断。

用起来最简单。

还可以再偷个懒,debug启动,加个断点,连输出都不需要了。

5.2 Assert

单元测试自动化的基石。

image-20200226233216316

5.3 Mock

复杂的单元测试,能避免最好,如果一定需要,那就用吧!

比如下面的情况:

006tNbRwly1fwp1amtetkj30jj05tmza

当类A中有属性B 和 C,B中又有D和E时,为了测试A,需要先创建4个对象b,c,d,e,而我们却不测试它,这不合道理。

我们可以做2个假的对象mockB和 mockC,用假的对象返回期望值,来测试类A。

006tNbRwly1fwp1akyjotj30ji06z40f

6. 例子

最后,放几个我曾经写的单元测试,作为例子。

6.1 简单的Print

1
2
3
4
5
6
@Test
public void testAllXkqy() throws Exception {
Set<String> strings = repository.allXkqy("20180607A");
System.out.println(strings.size());
System.out.println(strings);
}

6.2 一个Assert的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void testHourDateTime() throws Exception {
assertEquals(of(2016, 1, 1, 7, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 0, 0, 0)));
assertEquals(of(2016, 1, 1, 7, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 0, 0)));
assertEquals(of(2016, 1, 1, 7, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 0)));

assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 0, 0, 1)));
assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 0, 59, 100)));

assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 2, 0)));
assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 12, 0)));
assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 32, 0)));
assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 52, 0)));

assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 8, 59, 59)));
assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 9, 0, 0, 0)));
assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 9, 0, 0)));
assertEquals(of(2016, 1, 1, 8, 0, 0, 0), hourDateTime(of(2016, 1, 1, 9, 0)));
assertEquals(of(2016, 1, 1, 9, 0, 0, 0), hourDateTime(of(2016, 1, 1, 9, 0, 0, 1)));
}

6.3 复杂一点的

使用Mock,自定义返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Before
public void before() throws Exception {
metricService = mock(MetricService.class);
hourSampleCache = new AccumulateHourSampleCache(metricService);
}

@Test
public void testSample() throws Exception {
when(metricService.sample("r.abc", of(2016, 1, 1, 9, 0, 0))).thenReturn(null);
assertNull(hourSampleCache.sample("r.abc", of(2016, 1, 1, 9, 0, 0)));

DataPoint dp2 = new DataPoint("r.abc", of(2016, 1, 1, 8, 0), "3.14", 192);
when(metricService.sample("r.abc", of(2016, 1, 1, 9, 0, 0))).thenReturn(dp2);
assertNull(hourSampleCache.sample("r.abc", of(2016, 1, 1, 9, 0, 0)));

DataPoint dp = new DataPoint("r.abc", of(2016, 1, 1, 9, 0), "3.14", 192);
when(metricService.sample("r.abc", of(2016, 1, 1, 9, 0))).thenReturn(dp);
assertEquals(dp, hourSampleCache.sample("r.abc", of(2016, 1, 1, 9, 0, 0)));

when(metricService.sample("r.abc", of(2016, 1, 1, 9, 0, 0))).thenReturn(null);
assertEquals(dp, hourSampleCache.sample("r.abc", of(2016, 1, 1, 9, 0, 0)));
}

6.4 更复杂一点的

下面是一个更复杂的例子,在使用Mock的基础上,增加了内部调用方法次数的验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Before
public void before() throws Exception {
metricService = Mockito.mock(MetricService.class);
repairRecorder = Mockito.mock(RepairRecorder.class);
hourlyMetricWriter = new HourlyMetricWriter(metricService, repairRecorder);
}

@Test
public void testWrite() throws Exception {
Mockito.when(metricService.read(metricHourly, ldt, ldt.withHour(23), false)).thenReturn(
Collections.emptyList());
hourlyMetricWriter.write(dph);
Mockito.verify(metricService, Mockito.times(1)).read(metricHourly, ldt, ldt.withHour(23), false);
Mockito.verify(metricService, Mockito.times(2)).write(Matchers.any(DataPoint.class));
Mockito.verify(metricService, Mockito.times(1)).write(dph);
Mockito.verify(metricService, Mockito.times(1)).write(new DataPoint(metricDaily, ldt, "3.14", 192));
}

6.5 测试性能

自己写几行代码,简单的测测性能。

这是不是单元测试,似乎并不重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
@Test
public void write() throws Exception {
final int minHandle = 1179;
final int maxQueueSize = 1000;

final int threadCount = 5;
final int tagsPerThread = 1000;
final int records = 10000;
final int sendCycle = 60;

final int maxHandle = minHandle + tagsPerThread * threadCount * 2;
// int maxHandle = 3000;

final TagManagerImpl tagManager = new TagManagerImpl("192.168.1.98", 6767, "lpp", "12345");

ExecutorService threadPool = Executors.newFixedThreadPool(threadCount + 1);

final DateTime dateTime = new DateTime(2014, 2, 1, 0, 0, 0, 0);
final int logCycle = 60 * 60;

final BlockingQueue<Map<Integer, TagValue>> queue = new LinkedBlockingQueue<>();

threadPool.execute(new Runnable() {
@Override
public void run() {
for (int i = 0; i < records * sendCycle; i += sendCycle) {
DateTime dt = dateTime.plusSeconds(i);
Map<Integer, TagValue> map = new HashMap<>(tagsPerThread);
for (int j = minHandle; j < maxHandle; j += 2) {
map.put(j, new TagValue(dt, TagValue.DOUBLE, i + j, (short) 192));
if (map.size() == tagsPerThread) {
queue.offer(map);
map = new HashMap<>(tagsPerThread);
}
while (queue.size() > maxQueueSize) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (i % logCycle == 0) {
logger.info(String.format("Minute:%d", i / logCycle));
}
}
}
});

TimeUnit.SECONDS.sleep(1);
long startTime = System.currentTimeMillis();
for (int i = 0; i < threadCount; i++) {
threadPool.execute(new Runnable() {
private int count = 0;

@Override
public void run() {
try {
while (!queue.isEmpty()) {
Map<Integer, TagValue> take = queue.take();
tagManager.writeVal(new ArrayList<>(take.keySet()), new ArrayList<>(take.values()));
count++;

if (count % 100 == 0) {
logger.info(String.format("has been write count:%d, queue size:%d", count, queue.size()));
}
}
logger.info(String.format("This thread write total:%d", count));
} catch (Throwable e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
try {
threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
}

long endTime = System.currentTimeMillis();

logger.info(String.format("客户端数:%d", threadCount));
logger.info(String.format("每个客户端位号数:%d", tagsPerThread));
logger.info(String.format("每位号记录数:%d", records));
logger.info(String.format("数据频率:%d", sendCycle));
logger.info(String.format("用时:%d", (endTime - startTime) / 1000));
}

1. Oracle的DBLink是什么?

有多台Oracle服务器,A服务器的用户a,想访问B服务器的用户b的数据,就在A服务器上a用户下面建一个DBLink,指向B服务器的b用户。

创建DBLink的语法:

1
2
3
CREATE DATABASE LINK B_b 
CONNECT TO b IDENTIFIED BY password_of_b
USING 'host_of_B:1521/SID_of_B';

建好DBLink后,a用户如何访问B用户的表T呢?

我们知道a用户访问同服务器下另外一个用户x的表T,可以使用「x.T」;
与之类似,访问DBLink「B_b」的表T,可以使用「T@B_b」。

1
2
select *
from T@B_b

2. 同义词 SYNONYM 又是什么呢?

一个Oracle服务上,有多个用户(Schema),a用户想访问b用户下的表T,我们当然可以使用「b.T」来访问,但这样老是要带上b的用户名,不是很方便,有没有更方便的办法呢?

有啊,办法就是同义词:则在a用户下,建一个同义词,指向b用户的表B。

创建同义词SYNONYM的语法:

1
create synonym "a".T for "b"."T"

3. 例子:DBLink 与 同义词结合使用

在服务器 192.168.110.54 上的XKCA1这个用户下,想访问 10.199.132.7 这台服务器上 db_dhsz_dev 用户下的 DHSZ_FP_ZZSFP_QB 表。

我们需要先建 DBLink,让XKCA1这个用户,可以访问 db_dhsz_dev 用户的数据;
再建个同义词,让DHSZ_FP_ZZSFP_QB表看起来像是 XKCA1这个用户自己的表。

1
2
3
4
5
6
7
8
9
10
11
CREATE DATABASE LINK XKCA_DHSZ 
CONNECT TO db_dhsz_dev IDENTIFIED BY db_dhsz_dev
USING '10.199.132.7:1521/nsxygl';


CREATE OR REPLACE SYNONYM "XKCA1"."DHSZ_FP_ZZSFP_QB" FOR "DHSZ_FP_ZZSFP_QB"@"XKCA_DHSZ";


select *
from DHSZ_FP_ZZSFP_QB

4. 简单的总结

  • DBLink是Schema级别的,跨服务访问的概念
  • 同义词SYNONYM是对象(如 表)级别的,跨用户(可以通过DBLink跨服务)访问的概念

ArangoDB集群

本文以简短的篇幅,介绍ArangoDB集群相关的内容,包括 结构、工作原理 和 安装部署,并在最后介绍了一套适合我们的操作指南。

1. ArangoDB 集群介绍

关于ArangoDB集群的资料,能看到内容非常少,主要集中在官方文档中的 「架构」这一节。

地址:https://docs.arangodb.com/3.3/Manual/Scalability/Architecture.html

1.1 概述

ArangoDB,是无单点故障的,主-主模式的,CP架构的集群。

这一句话有3个点:

  • CP架构:即在CAP中,选择了一致性高于可用性
  • 主-主模式:意思是每一个点都可以写;
  • 无单点故障:说明有冗余容灾机制,有选举机制。

1.2 集群的结构

集群中有4种节点:Agent,Coordinator,Primary 和 Secondary。前三者必选,Secondary可选。

官方文档中,此处应该要有张架构图,可惜文档不是我写的,它没有。

我从其他地方找了几张图,供参考。

simple_cluster

cluster_topology-1

deep-dive-into-the-native-multi-model-database-arangodb-75-638

所有节点都是同一个命令启动的,只是参数不同。

Agent:集群的配置中心,Raft一致性协议解决配置冲突,高可用的KV存储。

Coordinator:接收客户端请求,无状态,与Primary交互。

Primary:主存储节点,多个,所谓的主-主指的就是它了。

Secondary:可选,可以理解成Primary的备份。

1.3 分布式机制

1.3.1 可用性

可用性,要求数据保存副本,即做数据冗余。ArangoDB数据存储的基本单位是Shard,听上去比较抽象。换句话说,一个Shard就是一个Document,就容易理解了。

预备知识

ArangoDB的数据数组组织:ArangoDB -> DataBase -> Collection -> Document,从前到后,都是一对多的关系。

ArangoDB:一个库,下面可以建多个DataBase

DataBase:数据库,下有多个Collection

Collection:集合,下有多个Document

Document:文档,集合中的元素

在创建集合时,可以指定副本数,默认是根据_key做Hash来分发,具体算法文档中没有明确说明,猜测可能是一致性Hash

006tKfTcgy1fsoq12todoj30i00ckgmg

1.3.2 一致性

ArangoDB是CP集群,即一致性高于可用性。它的一致性,指的是在Primary级别采用同步复制,保证强一致;Secondary则从Primary异步复制,保证最终一致。

因为客户端的读写只操作Primary,所以,ArangoDB是一个强一致性的分布式数据库。

  • 同步复制:参考下一节「写流程」
  • 异步复制:可以理解成定时备份

1.4 写流程

  • 客户端发起请求
  • 负载均衡,找一个Coordinator
  • Coordinator根据shardBy,找到Leader
  • Leader自己存好后,发送Followers
  • 所有Followers返回成功后,Leader告诉Coordinator写入成功
  • 如果Leader 3秒还没有收到Follower返回,认为其掉线
  • 如果Coordinator 15秒没有心跳,认为其掉线,触发重选Leader

注意: Leader和Follower不同于一些集群的Master-Slave。Leader是针对Shard选举的,集群中所有的Primary地位是一样的。并不存在Master Primary

1.5 读流程

TODO 暂时还没有发现相关文档

2. 方案选型

ArangoDB集群,常用的共有4种方案:Mesos/DCOS,Docker/Compose,ArangoDB-Starter,纯手工。

因为ArangoDB中的节点,有4种角色(3个必须,1个非必须),配置起来比较复杂,一般不建议用纯手工的方式配置。

2.1 Mesos/DCOS

Mesos/DCOS是官方首推的集群方案,官方性能测试也是在此环境上进行的。但搭建DCOS至少需要5台机器,一旦搭好了DCOS,后面会非常简单。目前来看,项目中配置机器数量不会达到5台,故此方案不适用。

2.2 Docker/Compose

Docker/Compose是另外一套比较好的方案,官方有提供Docker镜像,使用Compose编排集群内的节点,使用起来会非常灵活,又一目了然。但是,Docker早已停止支持CentOS6,而现场的机器目前没有升级到7及以上的计划,并在看的到的未来,仍会在6上运行。故此方案也不适用。

2.3 ArangoDB-Starter

最后,只剩下官方提供的ArangoDB-Starter这一套方案了。该方案比纯手工做了一步简化:把3个必须的角色打包,集成到一个命令中。

3. 安装ArangoDB(内网)

本文以CentOS 6.5 和 ArangoDB 3.3.8 为例,离线安装。

3.1 安装依赖

在连网的CentOS 6.5上,用yum deplist arangodb3可以查出,3.3.8版本依赖3个包:

1
2
3
bash.x86_64 4.1.2-48.el6
glibc.x86_64 2.12-1.209.el6
openssl.x86_64 1.0.1e-57.el6

前2个包已经有了,第3个包需要下载安装。

下载地址:https://centos.pkgs.org/6/centos-x86_64/openssl-1.0.1e-57.el6.x86_64.rpm.html

1
rpm -ivh openssl-1.0.1e-57.el6.x86_64.rpm

3.2 安装ArangoDB

ArangoDB 分社区版 和 企业版,生产环境建议用 企业版。从福建反馈来看,之前用社区版不稳定,出现假死、自动重启等问题,改用企业版后都没出现过。

从官方下载企业版:https://www.arangodb.com/download-arangodb-enterprise/install-enterprise

安装:

1
rpm -ivh arangodb3e-3.3.8-1.x86_64.rpm

注: 如果之前有装过 社区版,则需要加--replacepkgs参数,替换安装。

4. 部署:ArangoDB-Starter

4.1 简介

以下是该方案的特点:

  • ArangoDB安装包自带,无需额外安装
  • 可以单机,也可以多台机器
  • 创建集群时有Master,集群创建好了则无此概念
  • 启动和运行至少需要3个节点,少于3个节点无法正常工作
  • 节点数 > 3,无单点故障
  • 节点会启动agent,dbserver,coordinator这3个角色
  • 默认AgencySize为3,即前3个节点会启动agent,第4个开始,只启动dbserver 和 coordinator

4.2. 使用方式

注: 以下指令在3.3.8版本测试通过,不同版本可能会有微小的区别。

1
2
3
4
5
6
7
# 启动Master
arangodb --starter.data-dir=/root/arangodb/db1 start

# 启动其他
arangodb --starter.data-dir=/root/arangodb/db2 --starter.join 10.199.132.222 start
arangodb --starter.data-dir=/root/arangodb/db3 --starter.join 10.199.132.222 start
arangodb --starter.data-dir=/root/arangodb/db4 --starter.join 10.199.132.222 start

重申: Master 概念只是创建集群时的叫法,创建好之后,无Master节点,所有节点地位是一样的。

全部停止:

1
pkill -f arangod

再次启动:

el l
1
2
3
4
arangodb --starter.data-dir=/root/arangodb/db1 start
arangodb --starter.data-dir=/root/arangodb/db2 start
arangodb --starter.data-dir=/root/arangodb/db3 start
arangodb --starter.data-dir=/root/arangodb/db4 start

注: 本示例在多台机器上同样可用。

5. AQL常用写法

对于熟悉SQL的同学,可以通过AQL与SQL的比较,来学习这门查询语言,其中的例子非常有用。

AQL与SQL的比较:https://www.arangodb.com/why-arangodb/sql-aql-comparison/

链接中有简单的例子,下面列举有用的,复杂一点的几个例子。

5.1 分组查询并排序

集合TPIN_VERTEX_WWD 存在团伙的Key:community_label,根据条件抽出团伙。

1
2
3
4
5
6
7
8
9
10
for v in TPIN_VERTEX_WWD
filter v.ishuman == "0" // 过滤企业,and 用 多个filter,or 用一个filter,条件中间加 or 关键字
filter v.community_label > 0 // 过滤community_label 大于0的,等于0的不是团伙
collect thKey = v.community_label // 按照 团伙的Key来分组
AGGREGATE xkje = SUM(v.xkje), // 分组内部的SUM
qys = count(v), // 内部的count
xkfps = SUM(v.xkfps) // 另一个SUM
filter qys > 3 // 根据分组后的条件来过滤,即 group by having
sort xkje desc // 用分组后的来排序
return {key: thKey, xkje: xkje, qys: qys, xkfps: xkfps}

5.2 使用变量

查询某一团伙所有的点和边。团伙只和点关联,和边并没有关联。

如果用图遍历来查团伙中所有的点和边,数据量大了,图深了,性能非常差。图的深度设的少了,很可能会不全。

但是,如果单独查 点,再根据点查边,就会非常快。

1
2
3
4
5
6
7
LET ids = (FOR v IN TPIN_VERTEX_WWD 
FILTER v.community_label == @thKey
RETURN v._id) // 点的数组赋值为一变量,放到最前面
FOR e IN TPIN_EDGE_WWD
FILTER e._from in ids
FILTER e._to in ids
RETURN DISTINCT e

注意: 此例中,不要将 LET 放在 FOR 之后,FILTER之前,那样性能会差。实际测试过一个例子,边总数为2000,点的数量为1000,时间上一个是9ms,一个是118ms。

6. AQL性能优化

要把AQL想象成编程语言,而不是SQL。

6.1 使用LET

见 5.2 使用变量

6.2 图查询广度优先

在用图查询时,一般都加以下选项,用广度优先,性能好很多。

1
OPTIONS {bfs: true, uniqueVertices: 'global'}

6.3 索引

测试报上来一个问题,我们几个开发机器上都不能重现,仔细看了下异常提示,确定是这台Windows测试机器少dll。

这个项目用到leveldbjni-all这个内置jni的包,但少了系统的dll,这个Jar就不能正常工作了。

1. 如何知道少了哪些dll呢?

找到一个比较好的工具:DllWalker

这是一个绿色软件,从官方下载下来,双击depends.exe即可运行,点 打开,选 leveldbjni.dll,这个软件会分析出来 缺少哪些dll,当把这次提示的dll都装好后,F5刷新一下,可能还会有新的未安装的dll,这些是刚才安装的dll的依赖。

2. dll到哪儿找呢?

这个网站不错:dll-files.com

我要找的dll,都能搜到,并且提供多个版本供选择。下载过来的一个包含dll的压缩包,需要解压出dll才能放到Windows/System32下面去。