话到嘴边又不知道该怎么描述,也算是拾起了很久没用的博客吧,这也算是自己少见的文字输出方式了所以还是来写一写.

之所以会写这周搞的一些新东西这样的博客,主要是这周摸🐟的时光似乎有些多了,也是四处搞了很多新东西和一些之前遗留的事,就来记录一下吧.

敏捷学习仓库合并

在许久之前看了基于GitHub的敏捷学习方法之道与术之后就深受影响然后搞了一个仓库Agile-Learning也坚持搞了一段时间(MySQL文档翻译就是这样促成的),但是过年回来之后惰性发作也就许久没有再去动这个仓库也就闲置了许久,最近打算重新使用之后发现private的仓库不能无偿使用wiki,也就借着public的机会把这个仓库和github.io的仓库给合并了顺便整理了一下吧.

因为写博客的分支是在backup上的,所以对博客访问也没有什么影响,也是方便自己管理,之前没有public敏捷学习的仓库也是因为打算实践一段时间找到自己的使用方式之后再public出来,现在也是想用wiki就用public的方式来督促一下自己吧算是.

用wiki整合知识

承接上文现在也是打算吧一些开发和日常过程中自己Google过的不熟悉的东西整理到wiki里面去了(毕竟记性不好,还是写下来靠谱):
个人知识点整理用wiki

对于wiki的话其实也就是一个MarkDown文件的文件夹,大致的目录如下:

主要是*_Footer.md*,_Sidebar.md,Home.md三个文件,需要注意的是没有Home.md这个文件的话进入wiki看到的就是文件列表了.还有一点需要吐槽的是wiki默认的git方式是https的,需要自己手动改成ssh方式的不然就还是需要账号密码而不能用密钥的方式了.

博客HTTPS

关于https,很早之前就已经用上了,当时使用的是cloudflare的免费HTTPS方式,但是现在发现GitHub Pages已经自己支持HTTPS了,就改用GitHub自带的了:

VSCode remote

因为现在一直在用VSCode写MarkDown而在经历了整合仓库之后在clone的时候发现网速巨慢,大概需要10-30min才能把仓库clone下来,实在是无法忍受之后想到了用阿里云的服务器来写博客,之前也是对VSCode remote略有耳闻就Google并且尝试了一下,目前来看在写Markdown这一块还是很不错的并且由于文件保存在服务器上所以哪怕没有保存的修改也不会丢失.

步骤

  1. 下载openssh:OpenSSH-Win64.zip
  2. 添加到Path
  3. PowerShell使用ssh xxx@xxx.xxx.xxx尝试连接服务器
  4. VSCode安装Remote的插件
  5. 在本地的.ssh/config文件下进行服务器的配置:
  6. 完成之后如下就已经可以了:

Windwos Terminal && WSL

先来一个效果图吧(想自定义主题的但是目前好像文章不是很多还得好好研究一下):

Win10下的安装也很简单就不在赘述了,下面就放一下配置文件好了:

1
2
3
4
5
6
7
8
9
{
"globals" :
{
"alwaysShowTabs" : true,
"copyOnSelect" : false,
"defaultProfile" : "{9acb9455-ca41-5af7-950f-6bca1bc9722f}",
"initialCols" : 120,
"initialRows" : 30,
"keybindings" :

这里主要是defaultProfile这个字段,对应下面每个terminal类型的guid,我改成了默认启动WSL的窗口.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"profiles" : 
[
{
"acrylicOpacity" : 0.75,
"closeOnExit" : true,
"colorScheme" : "Vintage",
"commandline" : "wsl.exe",
"cursorColor" : "#FFFFFF",
"cursorShape" : "bar",
"fontFace" : "Fira Code",
"fontSize" : 10,
"guid" : "{9acb9455-ca41-5af7-950f-6bca1bc9722f}",
"historySize" : 9001,
"icon" : "ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png",
"name" : "WSL",
"padding" : "0, 0, 0, 0",
"snapOnInput" : true,
"startingDirectory" : "//wsl$/Ubuntu/home/tang",
"useAcrylic" : true
},

这是WSL的配置文件,icon这个是需要更换的,目录是在(我的文件夹权限有点问题就用命令行来查看了):

guidicon一起改掉就ok了.

我还更换了一下字体:"fontFace" : "Fira Code", 关于字体可以自行Google.

终端主题在下面的"schemes" : 里有自带默认的几个风格.修改到"colorScheme" : "Vintage",这个字段就可以应用.

可以看出这一次巨硬还是很良心的,而且配置文件也很容易修改,可以说是Win开发的一大福音了.

id_rsa

以前的我只知道莽,无脑生成密钥就完事了.

但是在经历了许多环境和服务器的摧残之后,还是认识到了使用一套自己的密钥的重要性了,于是在生成了一套之后把GitHub和服务器都替换了一下,现在就very方便了(顺便保存在了1password里).

Pi_hole

可以查看示例:Pi_hole Admin

知道这东西是在老莱的频道发现的:【官方双语】世界清净了…用树莓派屏蔽所有在线广告!#linus谈科技

感觉还挺有意思的但是介于没有树莓派然后又想到服务器不是有公网ip吗,于是就在阿里服务器上搭了一个,一开始感觉还非常不错但是在经历了更多网页的洗礼之后发现还是不太行,现在用的是adblock+Pi hole的双重过滤方式.

需要注意的是如果在服务器上部署需要在安全组里开放53的UDP端口,是DNS需要的端口.

安装的话由于有一键脚本,直接跟着做就成了所以也就不在赘述了.

1
curl -sSL https://install.pi-hole.net | bash

(后期补充:docker脚本)

1
2
3
4
5
6
7
8
9
10
11
docker run -d \
--name pihole \
-p 53:53/tcp -p 53:53/udp \
-p 8081:80 \
-p 443:443 \
-e TZ="Asia/Shanghai" \
-v "$(pwd)/etc-pihole/:/etc/pihole/" \
-v "$(pwd)/etc-dnsmasq.d/:/etc/dnsmasq.d/" \
--dns=127.0.0.1 --dns=1.1.1.1 \
--restart=unless-stopped \
pihole/pihole:latest

自建RSS

我也算是个轻度的RSS使用者了吧,之前一直在使用Inoreader但是最近发现广告变多了而且免费账户着实有些弟弟,就想着反正有个服务器就克服一下懒癌尝试一下吧.

然后Google了一顿还是发现之前看过的:如何搭建属于自己的 RSS 服务,高效精准获取信息感觉还不错就跟着做了,不得不说:真香. 在docker的加持之下安装是非常之简单了,唯一的坑是Mercury 的全文插件,现在已经需要自建了, 然后参考了:通过Docker安装Tiny RSS,mercury全文插件及mercury-parser-api;申请域名通配符证书,安装nginx并配置ssl 使用docker安装了Mercury Parser API.

直接使用

1
docker run -p 3000:3000 -d --restart=always  wangqiru/mercury-parser-api

然后配置API地址就可以了,当然之前是使用API KEY的形式.

然后把Inoreader的订阅源导入过来就成了,有一点值得吐槽的是Inoreader导出的opml文件竟然是xml格式的,然后直接导入ttrss就会失败,但是改一下后缀就ok了.

TabNine

据说是杀手级AI代码补全工具,毕竟现在是”AI大数据”的时代就下载下来蹭一下热度呗,实际的使用体验还算是不错,写Markdown的时候竟然也会提示单词的补全,好评.

在idea也装了插件,补全智能程度还算是非常的不错,在填参数的时候甚至会帮你补全后面的’)’.

介绍的文章也很多我也就不再唠叨用就完事了(毕竟我只是个记录贴 :)

Perface

TDD rules:

  1. write new code only if you first have a failing automated test.
  2. eliminate duplication. 消除重复设计

Technical implications:

  1. design organically: running code providing feedback between decisions.
  2. write your own test.
  3. development env must provide rapid response to small change.
  4. design must be highly cohesive, loosely coupled components(高内聚,低耦合) to make testing easy.

Programing order:

  1. Red-write a little that doesn’t work.
  2. Green-make the test work quickly.
  3. Refactor-eliminate all the duplication created.
  • 红:写测试
  • 绿:写代码通过测试
  • 重构:消除重复设计,优化结构

Section I: MoneyExample

  • a to-do list to remind what need to do.
  • when write a test,imagine the perfect interface for operation.
  • Dependency is the key problem in software development at all scales.
  • If you can make steps too small, you can certainly make steps the right size.(为什么测试要足够小)

TDD cycle:

  1. write a test.
  2. make it run.
  3. make it right.

the goal is clean code works.

First we can talk about whether the system should work like this or like that. Once we decide on the correct behavior, we can talk about the best way of achieving that behavior

首先需要考虑系统是怎么样的,确定实现的思路之后就找到最佳的实现办法.(TDD难点:对系统的总体认识和设计)

That is a risk you actively manage in TDD. We aren’t striving for perfection. By saying everything two ways, as both code and tests, we hope to reduce our defects enough to move forward with confidence.

TDD开发的风险:测试和编码同时进行会引入缺陷.

The different phases have different purposes. They call for different styles of solution, different aesthetic viewpoints.

不同的阶段所注重的点是不同的,所以在初期设计阶段我们可以忍受重复的设计和复制粘贴代码,一切都是为了尽快完成这个阶段(clean code是重构的任务).

消除类冗余的过程:

  1. 把子类的公共代码移到父类中.
  2. 对父类的其他子类进行简化.
  3. 合并equals()函数到父类.

对equals()和hashCode()函数的覆盖和重写过程是发生在重构中的,并且已经有许多的测试来对重构做支撑.

在制造对象的时候使用工厂方法(factory method).

使用工厂模式和合理的构造器参数将重复代码移动到父类中去.

With the tests you can decide whether an experiment would answer the question faster. Sometimes you should just ask the computer.

在TDD中,重构时由于有足够多的测试来支撑改动所以可以使用测试来迅速验证想法而如果没有测试验证想法就只能依赖思考和论证.

在重构的过程中将子类代码消除后就要将其删除.

使用多态来消除显式的类型判定.

Section II: Example: xUnit

对TDD测试的要求:

  • Easy to write for programmers. 易于编写
  • Easy to read for programmers. 易于阅读
  • Quick to execute. 快速执行
  • Order independent. 顺序无关
  • Deterministic. 确定的:执行结果不随执行次数变化
  • Piecemeal. 零碎(足够小)
  • Composable. 可组合:可以以各种组合方式运行测试
  • Versionable. 多版本
  • A priori. 先验(在代码能运行之前就写好测试)
  • Automatic. 自动化
  • Helpful when thinking about design. 对系统的设计的思考有帮助(测试先行需要对系统有良好的组织).

Lots of refactoring has this feel—separating two parts so you can work on the separately. If they go back together when you are finished, fine, if not, you can leave them separate.

Here is another general pattern of refactoring—take code that works in one instance and generalize it to work in many by replacing constants with variables.

先用常量进行测试,通过之后将常量位置改为变量就能在更多情况下使用.

测试模式:

  1. 创建对象
  2. 激活(测试)对象
  3. 检查结果

测试的矛盾:

  • Performance 性能:测试的执行要越快越好
  • Isolation 隔离:测试之间不要互相耦合并且测试执行不依赖于其执行顺序

Section III: Patterns

  • 自动化测试很重要
  • 测试之间相互独立
  • 开始编码之前列出测试清单
  • 测试优先(测试先行)
  • 使用断言
  • 使用容易理解的测试数据
  • 使测试数据的意图明显

从测试中发现问题:

  • 冗长的设置(初始化)代码 -> 对象太大
  • 冗余的设置 -> 对象间耦合
  • 测试运行时间过长 -> 测试不会被运行或运行有问题

Is TDD Dead?

在大略阅读了Google软件测试之道和TDD之后,感觉TDD最明显的特征就是快速的实现和重构,自然这其中需要很强的”clean code”的能力,例如自然的使用工厂模式和提取参数使子类的方法向上转移到父类之上.

而TDD的难点也就在于在系统开发之前,如何进行合理有效的拆分,而一旦没有清晰的思路,所谓的测试先行也就变成了冥思测试用例而不得,导致开发速度变得更慢.

TDD的难以施行在结合Google的测试历程之后会发现,精通测试确实是”太难了”,需要开发人员在编码和测试两个方向都有较好的能力才能比较顺利的施行,而开发编写测试本身就会”遭到质疑”.哪怕强如Google也花了很长时间才让开发人员能够适应自己测试的开发节奏,可见在全新的组织中尝试实行TDD会是很痛苦的过程.

当然,TDD也并非是开发方法论的全部.个人感觉关键还是需要在编程时有时刻重构的思维和编写”clean code”的能力,而TDD更像是对这两种要求的结合.当你习惯了重构和能够写出”clean code”的时候,T不TDD也就变得没有那么重要了.

14.7.4 幻影行

所谓的幻读就是同一个事务在不同的时间执行相同的查询产生不同的结果行.例如,如果一个SELECT执行2次,但是第二次返回的行和第一次的不一样,这些行就被称为”幻影行”.

假设在child表的id列上有索引并且你想要对表中id值大于100的所有行进行加锁和读取,为了在之后更新选中列中的数据:

1
SELECT * FROM child WHERE id > 100 FOR UPDATE;

查询从id大于100的第一个记录开始扫描.假设表包含90和102.如果扫描范围之内的索引记录没有对插入的间隙加锁,另一个会话可以插入一个id为101的新行到表中.如果你在相同的事务中执行相同的SELECT,你将会在查询结果中看到一个id为101的新行(一个”幻影”).如果我们把这一系列的行视为一个数据项的话,新的幻影数据将违反事务的隔离准则即:在事务期间读取的数据不会发生改变.

为了避免幻读,InnoDB使用了一个叫做next-key锁的算法,它将索引行锁定与间隙锁定相结合.InnoDB以这样的方式执行行级锁定:当它搜索或扫描表索引时,它会在遇到的索引记录上设置共享锁或独占锁.因此,行级锁其实是索引记录锁.此外,索引记录上的next-key锁也会影响索引记录前的间隙.也就是说,next-key锁是索引记录锁加上索引记录锁前间隙锁.如果一个会话在索引记录R上有一个共享或者独占锁,其他会话就不能在索引记录R之前的间隙插入一个新的索引记录了.

当InnoDB扫描索引时,也会对最后一个索引记录后的间隙加锁.在前面的例子中就发生了这种情况:为了避免任何比100大的id插入到表中,InnoDB设置的锁也包括了id为102之后的间隙锁.

你可以使用next-key锁在你的应用中实现唯一性检查:如果你在共享模式下读取你的数据并且你要插入的行中没有重复键,那你就可以安全的插入行数据并且知道在你读取期间设置的next-key锁会组织任何人在同时插入和你重复键的行数据.因此,next-key锁能够使你锁定某些表中不存在的数据.

14.7.5 InnoDB的死锁

死锁是不同的事务因为互相持有其他事务需要的锁而不能继续处理的情况.因为每个事务都会等待需要的资源释放同时也不会释放已经获取的资源.

当事务锁定多个表中的行时(通过例如UPDATE或SELECT…FOR UPDATE语句),可能会发生死锁,但是顺序相反.当这类语句锁定索引记录和间隙时也会发生死锁,由于时间问题每个事务都获得了一些锁而没有其他的锁.

为了减少死锁发生的可能性,最好使用事务而不是LOCK TABLES语句;保持插入或更新数据的事务足够小,使其不会长时间保持连接状态;当不同的事务更新多个表或大量行的时候,对每个事务使用相同的操作顺序(例如SELECT…FOR UPDATE);对SELECT…FOR UPDATE和UPDATE…WHERE语句用到的行创建索引.产生死锁的概率不会受事务隔离级别的影响,因为隔离级别改变的是读取操作的行为,而死锁的发生是因为读操作.

当死锁检测启用(默认启用)并且确实发生死锁的时候,InnoDB会检测到这种情况并且回滚其中的一个事务.如果死锁检测使用 innodb_deadlock_detect配置选项禁用,InnoDB根据 innodb_lock_wait_timeout设置在死锁时回滚事务.因此,即使你的应用逻辑正确,你也必须处理事务要重试的情况.查看InnoDB用户事务的最后一个死锁,使用 SHOW ENGINE INNODB STATUS命令.如果在事务结构或应用程序错误处理中频繁发生死锁,使用 innodb_print_all_deadlocks参数运行MySQL来启用关于MySQL错误日志中所有和死锁相关的信息的打印.

14.7.5.1 一个InnoDB死锁的例子

下面的例子演示了当进行加锁请求的时候一个错误是如何造成死锁的.例子包括2个客户端,A和B.

首先,客户端A创建一个表包含一行数据然后开启一个事务.在事务中,A使用select的共享模式(share mode)对行数据加了S锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)

mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;
+------+
| i |
+------+
| 1 |
+------+

接下来,客户端B开始一个事务并且从表中删除该行:

1
2
3
4
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> DELETE FROM t WHERE i = 1;

删除操作需要一个X锁.这个锁不能被授予因为其和客户端A所持有的S锁不相容,所以这个请求进入了加锁请求队列并且客户端B阻塞了.

最后,客户端A也尝试从表中删除该行:

1
2
3
mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction

在这里死锁发生了因为客户端A需要一个X锁来删除该行.然而这个加锁请求不能被授予因为客户端B已经请求了一个X锁并且在等待客户端A释放S锁.由于B对X锁的请求比A更早,持有S锁的A也不能获取到X锁.结果是InnoDB为其中一个客户端产生了一个错误并且释放它的锁.这个客户端会返回下面的错误:

1
2
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction

在这个时候,另一个客户端的加锁请求就可以被授予并且从表中删除行了.

14.7.5.2 死锁的发现和回滚

当死锁检测启用的时候(默认),InnoDB会自动检测事务的死锁并且回滚一个或者多个事务来打破死锁.InnoDB会尝试选择一个小的事务来进行回滚,事务的大小由插入,更新或删除的行的数量来决定.

InnoDB在innodb_table_locks = 1(默认值)和 autocommit = 0的时候知道表锁的存在,并且MySQL层面知道行级锁.换句话说,InnoDB在表被MySQL的LOCK TABLE加锁或者InnoDB以外的引擎设置锁的时候是不能检测死锁的.通过设置innodb_lock_wait_timeout系统变量来改善这种情况.

当InnoDB执行一个完整的事务回滚的时候,这个事务设置的所有锁都会被释放.然而,如果由于错误而回滚单个的SQL语句,则该语句设置的锁有可能会被保留.发生这种情况是因为InnoDB用这样一种格式来存储行级锁:它无法知道哪些锁是被哪些语句设置的.

如果SELECT语句在事务中调用存储的函数,并且函数中的语句失败了,这个语句会回滚.此外,如果在这之后执行了ROLLBACK那么整个事务会被回滚.

如果InnoDB监控器输出的 LATEST DETECTED DEADLOCK部分包括这样的信息 “TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACTION,”这表示等待列表中的事务已经达到了200.超过200个事务的等待列表会被视为死锁并且等待列表中的事务会尝试回滚.如果加锁线程必须查看等待列表中有超过1,000,000个锁的事务时也会产生相同的错误.

禁用死锁检测

在高并发系统上,当许多线程等待同一个锁时,死锁检测会导致速度减慢.有时,在发生死锁时,禁用死锁检测并依赖innodb_lock_wait_timeout设置进行事务回滚可能更有效.可以使用innodb_deadlock_detect配置选项禁用死锁检测.

14.7.5.3 如何最小化和处理死锁

这一节的内容以上一节死锁的知识为基础.介绍了如何组织数据库操作来最小化死锁和应用程序中所需要的错误处理.

死锁在支持事务的数据库中是一个经典问题,但是其不危险除非其频繁到你无法执行某些事务.通常你必须在应用程序中准备处理死锁回滚而重新提交事务.

InnoDB使用自动行级锁.即使是插入或者删除单行的事务也可能会遇到死锁.这是因为这些操作通常不够”原子”;它们会自动对被插入或删除的行的索引记录设置锁.

你可以使用以下技术处理死锁并降低其发生的可能性:

  • 在任何时候,使用 SHOW ENGINE INNODB STATUS命令来确定最近的死锁发生的原因.这可以帮助你调整应用程序以避免死锁.

  • 如果频繁的死锁警告引起了关注,通过启用innodb_print_all_deadlocks配置选项来收集更多的调试信息.每个死锁(不只是最后一个)的信息都记录在MySQL的错误日志里.在你完成debug之后禁用这个选项.

  • 如果因为死锁(而导致回滚),总是准备好重新提交事务.死锁不危险.只是重新提交一遍而已.

  • 保持事务短小并且执行时间短使其不易发生冲突.

  • 在进行一组相关更改后立即提交事务,以使它们不易发生冲突.特别是,不要使用未提交的事务使交互式mysql会话长时间保持打开状态.

  • 如果你使用加锁读(SELECT...FOR UPDATESELECT... LOCK IN),尝试使用更低的事务隔离级别例如READ COMMITED.

  • 当在一个事务中修改多个表或者相同表中不同的行的时候,每次都用相同的顺序执行.事务就会形成良好的队列而不会死锁.例如,将数据库操作组织到应用程序中的函数中,或调用存储的函数,而不是在不同的位置编写多个类似的INSERT,UPDATE和DELETE语句.

  • 在你的表中添加仔细选择的索引.然后,你的查询需要扫描更少的索引记录,从而设置更少的锁.使用EXPLAIN SELECT确定MySQL服务器认为哪些索引最适合您的查询.

  • 使用更少的锁.如果你允许SELECT从旧快照返回数据,就不要在其中添加FOR UPDATE或LOCK IN SHARE MODE子句.在这里可以使用READ COMMITTED隔离级别,因为同一事务中的每个一致读取都从其自己的新快照读取.

  • 如果没有其他帮助,请使用表级锁定序列化你的事务.将LOCK TABLES与事务表(如InnoDB表)一起使用的正确方法是使用SET autocommit = 0(不是START TRANSACTION)开始事务,然后LOCK TABLES,并且在您明确提交事务之前不要调用UNLOCK TABLES.

    1
    2
    3
    4
    5
    SET autocommit=0;
    LOCK TABLES t1 WRITE, t2 READ, ...;
    ... do something with tables t1 and t2 here ...
    COMMIT;
    UNLOCK TABLES;

    表级锁可防止对表的并发更新,从而避免死锁,但代价是对繁忙系统的响应性较低.

  • 序列化事务的另一种方法是创建一个只包含一行的辅助“信号量”表.让每个事务在访问其他表之前更新该行.这样,所有事务都以串行方式发生.请注意,InnoDB即时死锁检测算法在这种情况下也适用,因为序列化锁是一个行级锁.使用MySQL表级锁定时,必须使用超时方法来解决死锁.

0%