王某某的笔记

记录我的编程之路

zkCleanup

zookeeper使用一段时间后占用了非常多的磁盘空间

1
2
3
4
[root@node2 zookeeper]# du -h --max-dept=1
294G ./version-2
294G .

使用自带的zkCleanup进行清理

1
2
3
4
5
6
./zkCleanup.sh 
Usage:
PurgeTxnLog dataLogDir [snapDir] -n count
dataLogDir -- path to the txn log directory
snapDir -- path to the snapshot directory
count -- the number of old snaps/logs you want to keep, value should be greater than or equal to 3
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@node2 bin]# ./zkCleanup.sh /data/zookeeper -n 5

......
Removing file: Mar 10, 2018 7:57:04 PM /data/zookeeper/version-2/log.3150c663239
Removing file: Feb 4, 2018 5:39:46 AM /data/zookeeper/version-2/log.313054d1843
Removing file: Apr 20, 2018 7:40:19 PM /data/zookeeper/version-2/log.31607ac00d7
Removing file: Apr 6, 2018 3:33:47 PM /data/zookeeper/version-2/log.3153be932d2
Removing file: Mar 31, 2018 3:46:12 PM /data/zookeeper/version-2/log.3152f255a35
Removing file: Mar 19, 2018 1:28:12 AM /data/zookeeper/version-2/log.31516d3c5d7
Removing file: Jan 16, 2018 10:16:43 AM /data/zookeeper/version-2/log.31205b9f349
Removing file: Mar 26, 2018 7:05:37 AM /data/zookeeper/version-2/log.315245d23e8
Removing file: Apr 12, 2018 6:31:10 AM /data/zookeeper/version-2/log.31548340db4
......

设置自动清理

修改zoo.cfg配置文件中的 autopurge.snapRetainCount 和 autopurge.purgeInterval 两个参数实现定时清理

去掉注释即可

1
2
3
4
5
6
# The number of snapshots to retain in dataDir
autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
autopurge.purgeInterval=1

autopurge.purgeInterval 这个参数指定了清理频率,单位是小时,需要填写一个1或更大的整数,默认是0,表示不开启自动清理功能。

autopurge.snapRetainCount 这个参数和上面的参数搭配使用,这个参数指定了需要保留的快照文件数目,默认是保留3个。

2018-04-02 18:47

共享锁

共享锁就是允许多个线程同时获取一个锁,一个锁可以同时被多个线程拥有。

共享锁【S锁】

又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。


排它锁

排它锁,也称作独占锁,一个锁在某一时刻只能被一个线程占有,其它线程必须等待锁被释放之后才可能获取到锁。

排他锁【X锁】

又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

java

ReentrantLock就是一种排它锁。
CountDownLatch是一种共享锁。
这两类都是单纯的一类,即,要么是排它锁,要么是共享锁。

ReentrantReadWriteLock是同时包含排它锁和共享锁特性的一种锁。

使用ReentrantReadWriteLock的写锁时,使用的便是排它锁的特性
使用ReentrantReadWriteLock的读锁时,使用的便是共享锁的特性

获取顺序
此类不会将读取者优先或写入者优先强加给锁访问的排序。但是,它确实支持可选的公平 策略。

  • 非公平模式(默认)
    当非公平地(默认)构造时,未指定进入读写锁的顺序,受到 reentrancy 约束的限制。连续竞争的非公平锁可能无限期地推迟一个或多个 reader 或 writer 线程,但吞吐量通常要高于公平锁。

  • 公平模式
    当公平地构造线程时,线程利用一个近似到达顺序的策略来争夺进入。当释放当前保持的锁时,可以为等待时间最长的单个 writer 线程分配写入锁,如果有一组等待时间大于所有正在等待的 writer 线程 的 reader 线程,将为该组分配写入锁。
    如果保持写入锁,或者有一个等待的 writer 线程,则试图获得公平读取锁(非重入地)的线程将会阻塞。直到当前最旧的等待 writer 线程已获得并释放了写入锁之后,该线程才会获得读取锁。当然,如果等待 writer 放弃其等待,而保留一个或更多 reader 线程为队列中带有写入锁自由的时间最长的 waiter,则将为那些 reader 分配读取锁。

ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。

所有 ReadWriteLock 实现都必须保证 writeLock 操作的内存同步效果也要保持与相关 readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。

与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。

一、调整副本数

如调整副本数为0

1
2
3
4
5
6
curl -XPUT 'node3:9205/downloads/_settings' -d '{
"index": {
"number_of_replicas": "0"
}
}'

返回

1
{"acknowledged":true}

二、调整索引分片

索引分片数在索引创建好了之后就不能调整了,只能重建索引

(ES 5.X 版本中有一个缩小分片的api,需要先设置为只读,然后缩减过程需要大量的IO)

先创建索引

1
2
3
4
5
6
7
8
curl -XPUT 'http://localhost:9200/wwh_test2/' -d '{
"settings" : {
"index" : {
"number_of_shards" : 2,
"number_of_replicas" : 2
}
}
}'

或者同时指定mappings

1
2
3
4
5
6
7
8
9
10
11
12
curl -XPOST localhost:9200/test -d '{
"settings" : {
"number_of_shards" : 1
},
"mappings" : {
"type1" : {
"properties" : {
"field1" : { "type" : "string", "index" : "not_analyzed" }
}
}
}
}'

之后再进行重新索引

1
2
3
4
5
6
7
8
curl -XPOST 'http://localhost:9200/_reindex' -d '{
"source": {
"index": "twitter"
},
"dest": {
"index": "new_twitter"
}
}'

开关索引

关闭

1
2
curl -XPOST 'localhost:9200/lookupindex/_close'

打开

1
2
curl -XPOST 'localhost:9200/lookupindex/_open'

问题以及解决步骤记录

由于启动的问题导致了集群状态为 yellow

出现分片UNASSIGNED

(应该是所谓的出现了脑裂)

关闭主节点后,重新选择了之前的主节点之后,数据没这么乱,稍微好点

部分分片一直处于INITIALIZING,并且分片不均衡,节点1上之前有分片1 、 3 、4,现在变成了分片 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
curl -s "http://node3:9205/_cat/shards" 

...
...
downloads 1 p STARTED 641603 24.2gb 192.168.1.92 node-2
downloads 1 r INITIALIZING 192.168.1.93 node-3
downloads 2 r INITIALIZING 192.168.1.92 node-2
downloads 2 p STARTED 641049 26.5gb 192.168.1.93 node-3
downloads 3 r INITIALIZING 192.168.1.91 node-1
downloads 3 p STARTED 639813 26.3gb 192.168.1.93 node-3
downloads 4 r INITIALIZING 192.168.1.91 node-1
downloads 4 p STARTED 642036 26.1gb 192.168.1.93 node-3
downloads 0 r INITIALIZING 192.168.1.92 node-2
downloads 0 p STARTED 640368 25.6gb 192.168.1.93 node-3
...
...

使用下面的命令关闭分片复制

1
2
3
4
5
curl -XPUT 'node3:9205/downloads/_settings' -d '{
"index": {
"number_of_replicas": "0"
}
}'

返回

1
{"acknowledged":true}

此时节点状态变成绿色,之前节点上的备份分片被删除,查看数据文件
(目录:/data/dap/es/data/dap_es/nodes/0/indices/downloads)
之前:

1
2
3
4
5
6
7
[root@node1 downloads]# du -h --max-dept=1
27G ./1
27G ./3
27G ./4
8.0K ./_state
16K ./2
80G .

之后:
可以看到删除了之前的分片数据,并重新迁移分片数据,数据文件慢慢变大

1
2
3
4
[root@node1 downloads]# du -h --max-dept=1
8.0K ./_state
13G ./0
13G .

此时状态为:RELOCATING

1
2
3
4
5
6
7
8
9
10
11
curl -s "http://node3:9205/_cat/shards" 

...
...
downloads 1 p STARTED 641603 24.2gb 192.168.1.92 node-2
downloads 2 p RELOCATING 641049 26.5gb 192.168.1.93 node-3 -> 192.168.1.92 MNskHyFsQfC0PixAx-3hBQ node-2
downloads 3 p STARTED 639813 26.3gb 192.168.1.93 node-3
downloads 4 p STARTED 642036 26.1gb 192.168.1.93 node-3
downloads 0 p RELOCATING 640368 25.6gb 192.168.1.93 node-3 -> 192.168.1.91 pkjX2YIbTnm3A49re8COPg node-1
...

ES将逐个分片进行迁移

1
2
3
4
5
downloads          1 p STARTED    641603  24.2gb 192.168.1.92 node-2                                               
downloads 2 p RELOCATING 641049 26.5gb 192.168.1.93 node-3 -> 192.168.1.92 MNskHyFsQfC0PixAx-3hBQ node-2
downloads 3 p STARTED 639813 26.3gb 192.168.1.93 node-3
downloads 4 p STARTED 642036 26.1gb 192.168.1.93 node-3
downloads 0 p STARTED 640368 25.6gb 192.168.1.91 node-1

可以看到节点1已经迁移完成

等待迁移完成

此时在ES的head插件上可以看到全部变成绿色了,之前迁移的分片的紫色的

再将复制分片数改为1

1
2
3
4
5
curl -XPUT 'node3:9205/downloads/_settings' -d '{
"index": {
"number_of_replicas": "1"
}
}'

返回

1
{"acknowledged":true}

查看状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
curl -s "http://node3:9205/_cat/shards"

...
...
downloads 1 p STARTED 641603 24.2gb 192.168.1.92 node-2
downloads 1 r INITIALIZING 192.168.1.91 node-1
downloads 2 p STARTED 641049 26.5gb 192.168.1.92 node-2
downloads 2 r INITIALIZING 192.168.1.93 node-3
downloads 3 r INITIALIZING 192.168.1.92 node-2
downloads 3 p STARTED 639813 26.3gb 192.168.1.93 node-3
downloads 4 r INITIALIZING 192.168.1.91 node-1
downloads 4 p STARTED 642036 26.1gb 192.168.1.93 node-3
downloads 0 p STARTED 640368 25.6gb 192.168.1.91 node-1
downloads 0 r INITIALIZING 192.168.1.93 node-3
...
...

系统将再次变成yellow状态

再查看数据文件,可以看到备份的数据文件再增长

完了之后就变成绿色状态了



处理方法2

另外一种处理方式,当出现UNASSIGNED,强行指定

1
curl -s "http://node3:9205/_cat/shards" | grep UNASSIGNED
1
2
3
4
5
6
7
8
9
10
11
curl -XPOST 'node3:9205/_cluster/reroute' -d '{
"commands" : [ {
"allocate" : {
"index" : "downloads",
"shard" : 4,
"node" : "node-1",
"allow_primary" : true
}
}
]
}'

5.0 之后的ES 改成了 allocate_replica

1
2
3
4
5
6
7
8
9
10
11
{
"commands": [
{
"allocate_replica": {
"index": "mail_store",
"shard": 1,
"node": "slave2",
}
}
]
}

将未分配的副本分片分配给节点

注意如果主分片也未分配,则需要先分配主分片

将主分片分配给包含陈旧副本的节点

1
2
3
4
5
6
7
8
9
10
11
12
{
"commands": [
{
"allocate_stale_primary": {
"index": "mail_store",
"shard": 1,
"node": "slave2",
"accept_data_loss": true
}
}
]
}

使用此命令可能会导致所提供的分片ID发生数据丢失。如果稍后具有良好数据副本的节点重新加入群集,则该数据将被使用此命令强制分配的旧副本数据覆盖。为确保这些影响得到充分理解,该命令需要accept_data_loss明确设置专用字段才能true使其工作。

还有一个命令是:allocate_empty_primary

将空主分片分配给节点。
使用此命令会导致索引到此分片中的所有数据(如果它先前已启动)完全丢失。如果稍后具有数据副本的节点重新加入群集,则该数据将被删除!为确保这些影响得到充分理解,该命令需要accept_data_loss明确设置专用字段才能true使其工作。



其他资料

动态设置es索引副本数量

1
2
3
curl -XPUT 'http://node3:9200/xxindex/_settings' -d '{  
"number_of_replicas" : 2
}'

设置es不自动分配分片

1
2
3
curl -XPUT 'http://node3:9200/xxindex/_settings' -d '{  
"cluster.routing.allocation.disable_allocation" : true
}'

需要先关闭索引

手动移动分片

1
2
3
4
5
6
7
8
9
10
curl -XPOST "http://node3:9200/_cluster/reroute' -d  '{  
"commands" : [{
"move" : {
"index" : "xlog",
"shard" : 0,
"from_node" : "es-0",
"to_node" : "es-3"
}
}]
}'

手动分配分片

1
2
3
4
5
6
7
8
9
curl -XPOST "http://node3:9200/_cluster/reroute' -d  '{  
"commands" : [{
"allocate" : {
"index" : ".kibana",
"shard" : 0,
"node" : "es-2",
}
}]
}'

取消分配

1
2
3
4
5
6
7
8
9
curl -XPOST "http://ESnode:9200/_cluster/reroute" -d '{
"commands" : [ {
"cancel" : {
"index" : "ops",
"shard" : 0,
"node" : "es_node_one"
}
} ]
}'

主分片和启动状态下的分片不能取消

Gecko

Gecko是套开放原始码的、以C++编写的网页排版引擎。目前为Mozilla家族网页浏览器以及Netscape 6以后版本浏览器所使用。这软件原本是由网景通讯公司开发的,现在则由Mozilla基金会维护。 这套排版引擎提供了一个丰富的程序界面以供因特网相关的应用程序使用,例如网页浏览器、HTML编辑器、客户端/服务器等等。虽然最初的主要对象是Mozilla的衍生产品,如Netscape和Mozilla Firefox,现在已有很多其他软件现在利用这个排版引擎。Gecko是跨平台的,能在Microsoft Windows、Linux和Mac OS X等主要操作系统上运行。

排版引擎
网页浏览器的排版引擎也被称为页面渲染引擎,它负责取得网页的内容(HTML、XML、图象等等)、整理信息(例如加入CSS等),以及计算网页的显示方式然后会输出至显示器或打印机。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要排版引擎。

参考网页:
https://baike.baidu.com/item/gecko/7348782?fr=aladdin

geckodriver

使用W3C WebDriver兼容客户端与基于Gecko的浏览器进行交互的代理。

该程序提供由WebDriver协议描述的HTTP API 与Gecko浏览器(如Firefox)进行通信的功能。它通过充当本地和远程端之间的代理将呼叫转换为Firefox远程协议。

geckodriver是一个单独的HTTP服务器,它是完整的WebDriver远程实现。通过符合W3C WebDriver标准的客户端库(或客户端),您可以与geckodriver HTTP服务器进行交互。

geckodriver是用Mozilla的一种系统编程语言Rust编写的 。最重要的是,它依靠 webdriver crate 来提供HTTPD,并完成大部分编组WebDriver协议的繁重工作。geckodriver将WebDriver 命令,响应和错误转换为Marionette协议,并作为WebDriver和Marionette之间的代理 。

参考网页:
https://github.com/mozilla/geckodriver

支持的客户端

Selenium 用户必须更新到3.5或更高版本才能使用geckodriver。遵循W3C WebDriver规范的其他客户端也受支持。

支持的Firefoxen

geckodriver尚未完成功能。这意味着它尚未完全符合WebDriver标准或与Selenium完全兼容。
Firefox 55及更高版本的支持最好,但通常Firefox版本越新,体验越好,因为它们具有更多的错误修复和功能。某些功能只会在最新的Firefox版本中提供。

功能

具体见官方文档,包括:

  • 证书信任
  • 页面加载策略
  • 浏览器代理设置
  • firefox的二进制文件设置,启动参数、profile、日志等设置。
  • 计算指针,获取元素,执行点击操作等等

使用

使用文档可以在 MDN 上进行查看

下载地址:
https://github.com/mozilla/geckodriver/releases

Selenium

如果您通过 Selenium 使用 Geckodriver,则必须确保您拥有3.5或更高版本。由于geckodriver实现了 W3C WebDriver标准,而不是旧版驱动程序正在使用的Selenium wire协议,因此在从FirefoxDriver切换到geckodriver时,可能会遇到不兼容性和迁移问题。

一般来说,Selenium 3启用geckodriver作为Firefox的默认WebDriver实现。随着Firefox 47的发布,FirefoxDriver不得不停止支持 Gecko中新的多处理架构。

除非您通过设置Java VM系统属性 ==webdriver.gecko.driver== 来覆盖它,否则Selenium客户端绑定将从系统环境变量 ==Path== 中选取geckodriver二进制可执行文件。

1
System.setProperty("webdriver.gecko.driver", "/home/user/bin");

或者将它作为参数传递给java启动器:

1
% java -Dwebdriver.gecko.driver=/home/user/bin YourApplication

根据使用的编程语言不同,绑定的方法可能有所不同。
但如果在系统的环境变量Path上进行设置,geckodriver将被使用。
在bash兼容shell中,可以通过导出或设置PATH变量使其他程序知道其位置:

1
2
3
% export PATH=$PATH:/home/user/bin
% whereis geckodriver
geckodriver: /home/user/bin/geckodriver

在Window系统上,您可以通过右键单击我的电脑并选择属性来更改系统路径。在出现的对话框中,导航高级 → 环境变量 → Path。

或者在Windows控制台窗口中:

1
$ set PATH=%PATH%;C:\bin\geckodriver

参数

–binary

1
-b BINARY/--binary BINARY

指定Firefox二进制文件的路径。默认情况下,geckodriver会尝试查找并使用Firefox的系统安装,但是可以使用此选项更改该行为。请注意,创建新会话时传递binary的moz:firefoxOptions对象的功能 将覆盖此选项。

在Linux系统上,它将使用通过搜索环境变量找到的第一个firefox二进制文件PATH,这大致相当于调用 whereis并提取第二列:

1
2
% whereis firefox
firefox: /usr/bin/firefox /usr/local/firefox

在Windows系统上,geckodriver通过扫描Windows注册表来查找系统Firefox。

–connect-existing

1
--connect-existing

将geckodriver连接到现有的Firefox实例。这意味着geckodriver会放弃启动新的Firefox会话的默认设置。

现有的Firefox实例必须启用Marionette。要在Firefox中启用远程协议,您可以传递该 -marionette标志。除非marionette.port首选项已由用户设置,否则Marionette将在端口2828上进行监听。因此,在使用时–connect-existing,您可能还必须使用 –marionette-port设置正确的端口。

–host

1
--host HOST

用于WebDriver服务的地址。默认为127.0.0.1。

–log

1
--log LEVEL

设置Gecko和geckodriver日志级别。可能的值是fatal, error,warn,info,config,debug,和trace。

–marionette-port

1
--marionette-port PORT

为geckodriver连接到Marionette 远程协议选择端口。

在geckodriver启动并管理Firefox进程的默认模式下,它将选择由系统分配的空闲端口,设置在配置文件中的 marionette.port 将被优先使用。

当使用–connect-existing 时,Firefox进程不在geckodriver的控制下,它只会连接到PORT。

–port

1
-p PORT/--port PORT

用于WebDriver服务的端口。默认为4444。

一个有用的技巧是可以绑定到0来让系统自动分配一个空闲端口。

–jsdebugger

1
--jsdebugger

Firefox启动时附加浏览器工具箱调试器。这对调试Marionette内部件很有用。

-v[v]

1
-v[v]

通过-v 或 -vv 来调整日志记录的详细程度,这类似于传–log debug和–log trace。


Demo

java
pom文件导入依赖

1
2
3
4
5
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.9.1</version>
</dependency>

示例代码:

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
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;

public class Test {

public static String BROWSER_PATH = "D:\\Program Files\\Mozilla Firefox\\firefox.exe";

public static String GECKODRIVER_PATH = "D:\\Program Files\\Mozilla Firefox\\geckodriver.exe";

public static void main(String[] args) throws InterruptedException {

System.setProperty("webdriver.firefox.bin", BROWSER_PATH);

System.setProperty("webdriver.gecko.driver", GECKODRIVER_PATH);

WebDriver driver = new FirefoxDriver();

driver.get("http://www.baidu.com");

Thread.sleep(1000);

WebElement searchBox = driver.findElement(By.id("kw"));

searchBox.sendKeys("geckodriver");

searchBox.submit();

Thread.sleep(10000);

driver.quit();
}
}

其他

Marionette

Marionette是一种远程协议,它允许进程外程序与基于Gecko的浏览器进行通信,仪器和控制。

它提供了与基于Gecko的浏览器(如Firefox和Fennec)的内部JavaScript运行时和UI元素进行交互的接口。它可以控制文档和内容文档,提供高级别的控制和复制或模拟用户交互的能力。

用法

1
2
3
4

% firefox -marionette

1491228343089 Marionette INFO Listening on port 2828

这将绑定到一个TCP套接字上,使用这个协议客户端可以和 Marionette 进行通信。

Selenium

Selenium 是 ThoughtWorks 提供的一个强大的基于浏览器的开源自动化测试工具。
Selenium 是一个用于 Web 应用程序测试的工具,测试直接自动运行在浏览器中,就像真正的用户在手工操作一样。支持的浏览器包括 IE、Chrome 和 Firefox 等。这个工具的主要功能包括:测试与浏览器的兼容性 - 测试您的应用程序看是否能够很好地工作在不同浏览器和操作系统之上;测试系统功能 - 创建回归测试检验软件功能和用户需求;支持自动录制动作,和自动生成 .NET、Perl、Python、Ruby 和 Java 等不同语言的测试脚本。

Selenium 1 (又叫 Selenium RC 或 Remote Control)

Webdriver

Selenium 2,又名 WebDriver,它的主要新功能是集成了 Selenium 1.0 以及 WebDriver, 是两个项目的合并,既兼容 Selenium API 也支持 WebDriver API。

WebDriver 曾经是 Selenium 的竞争对手(最开始是google的一个人弄的,主要用于避免在JavaScript的沙箱环境里存在的各种限制),他主要是通过利用浏览器原生API的方式来操控浏览器执行各种动作(还包括系统级别的调用来模拟用户输入)。

Selenium WebDriver 就是对浏览器提供的原生API进行封装,使其成为一套更加面向对象的Selenium WebDriver API。
使用这套API可以操控浏览器的开启、关闭,打开网页,操作界面元素,控制Cookie,还可以操作浏览器截屏、安装插件、设置代理、配置证书等。由于使用的原生API,其速度与稳定性都会好很多。但浏览器厂商各不相同,提供的驱动各异(ChromeDriver、FirefoxDriver(xpi插件)、InternetExplorerDriver(exe)等),API也会有差异(好像都走JSON Wire Protocol,并且向W3C标准靠拢)。Selenium 对不同厂商的各个驱动进行了封装,如:selenium-chrome-driver、selenium-edge-driver、selenium-firefox-driver等。
还包括了对移动应用进行测试的AndroidDriver和iOS WebDriver,以及一个基于HtmlUnit的无界面实现HtmlUnitDriver。

WebDriver API可以通过Python、Ruby、Java和C#访问

WebDriver是一个用来进行复杂重复的web自动化测试的工具。意在提供一种比Selenium1.0更简单易学,有利于维护的API。它没有和任何测试框架进行绑定,所以他可以很好的在单元测试和main方法中调用。一旦创建好一个Selenium工程,你马上会发现WebDriver和其他类库一样:它是完全独立的,你可以直接使用而不需要考虑其他配置,这个Selenium RC是截然相反的。

两者的差异

  • 对于所有类型的浏览器Selenium- RC都是使用的同一种方法:
    当浏览器启动时,向其中注入javascript,从而使用这些js来驱动浏览器中的AUT(Application Under Test)。
  • WebDriver并没有使用这种技术,它是通过调用浏览器原生的自动化API直接驱动浏览器。

Selenium IDE

Selenium IDE (集成开发环境) 是一个创建测试脚本的原型工具。它是一个 Firefox 插件,提供创建自动化测试的建议接口。Selenium IDE 有一个记录功能,能记录用户的操作,并且能选择多种语言把它们导出到一个可重用的脚本中用于后续执行。


ChromeDriver - WebDriver for Chrome

WebDriver是一个开源工具,用于在许多浏览器上自动测试webapps。它提供了导航到网页,用户输入,JavaScript执行等功能。ChromeDriver是一个独立的服务,它为 Chromium 实现 WebDriver 的 JsonWireProtocol 协议。

目前正在实现并转向W3C标准。 ChromeDriver适用于Android版Chrome和桌面版Chrome(Mac,Linux,Windows和ChromeOS)。

官网地址:
https://sites.google.com/a/chromium.org/chromedriver/home

ChromeDriver 是 google 为网站开发人员提供的自动化测试接口,它是 selenium2chrome浏览器 进行通信的桥梁。selenium 通过一套协议(JsonWireProtocol :https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol)和 ChromeDriver 进行通信,selenium 实质上是对这套协议的底层封装,同时提供外部 WebDriver 的上层调用类库。

大概的工作流程:

在代码中 new ChromeDriver() 时,selenium会随机挑选一个端口调用chromedriver程序,调用成功后 chromedriver 会在指定的端口启动一个服务(会有一个进程)

1
2
>tasklist | find "chromedriver"
chromedriver.exe 7848 Console 1 13,740 K

selenium 中使用 apache 的 commons-exec 来运行 chromedriver.exe 启动 ChromeDriver 服务
直接在控制台运行 chromedriver.exe 时,默认端口是9515

selenium 通过指定的端口和约定的协议来和 ChromeDriver 进行通信,一个ChromeDriver可用管理多个chrome。
(具体细节可以看看协议部分,ChromeDriver如何控制chrome可能需要去看源码~~)

1
2
3
4
5
6
7
8
>tasklist | find "chrome"
chromedriver.exe 14692 Console 1 14,888 K
chrome.exe 20952 Console 1 72,204 K
chrome.exe 7288 Console 1 9,052 K
chrome.exe 15524 Console 1 10,000 K
chrome.exe 13036 Console 1 96,028 K
chrome.exe 11836 Console 1 29,836 K
chrome.exe 2788 Console 1 59,876 K

selenium 中多个 WebDriver 实例对应一个 chromedriver 进程,一个 chromedriver 进程管理多个 chrome 进程。
一个 WebDriver 实例对应一个浏览器窗口。

在代码中直接 new ChromeDriver() 将会启动一个 chromedriver 进程
使用 RemoteWebDriver 则只会连接到 chromedriver 服务,不会启动一个新的进程,连接不上会报错。
不管哪种方式都会打开一个浏览器窗口。

1
WebDriver driver = new RemoteWebDriver(new URL("http://127.0.0.1:9515"), DesiredCapabilities.chrome());

使用

官方指导页面

https://sites.google.com/a/chromium.org/chromedriver/getting-started

基于java

  1. 创建Maven项目
    引入相关依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>3.7.1</version>
    </dependency>
  2. 确保Chromium / Google Chrome安装在认可的位置

ChromeDriver希望您将Chrome安装在您平台的默认位置。您还可以强制ChromeDriver通过设置特殊功能来使用自定义位置。

1
2
ChromeOptions options = new ChromeOptions();
options.setBinary("/path/to/other/chrome/binary");
  1. 下载与你安装的chrome对应的chromedriver
chromedriver 版本 chrome 版本
ChromeDriver 2.36 Chrome v63-65
ChromeDriver 2.35 Chrome v62-64
ChromeDriver 2.34 Chrome v61-63
ChromeDriver 2.33 Chrome v60-62

(这里随便复制了几个)
下载地址:
https://sites.google.com/a/chromium.org/chromedriver/downloads

  1. 指定ChromeDriver所在位置,可以通过两种方法指定:
  • 通过配置ChromeDriver.exe位置到path环境变量

  • 通过设置webdriver.chrome.driver 系统属性实现-

  1. 创建一个新的ChromeDriver的实例,并调用get方法打开页面

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class BaseTest {

public static void main(String[] args) {
// 设置ChromeDriver的路径
System.setProperty("webdriver.chrome.driver", "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chromedriver.exe");

WebDriver driver = new ChromeDriver();

driver.get("http://www.baidu.com/");

//driver.quit();
}
}

执行这段代码将打开一个浏览器窗口,并访问百度
同时浏览器上将显示:Chrome 正受到自动测试软件的控制

代码执行完成后chrome并不会关闭,需要调用 driver.quit(); 才能关闭浏览器窗口。

打开百度并进行搜索:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws InterruptedException {

// 如果不设置将搜索环境变量
System.setProperty("webdriver.chrome.driver", "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chromedriver.exe");

WebDriver driver = new ChromeDriver();
driver.get("http://www.baidu.com/");

Thread.sleep(3000);

WebElement searchBox = driver.findElement(By.id("kw"));

searchBox.sendKeys("ChromeDriver");
searchBox.submit();

Thread.sleep(3000);
driver.quit();

}

高效使用

ChromeDriver 启动ChromeDriver服务器进程,并在调用退出时终止它。
在大型测试时每个测试都会创建一个ChromeDriver实例,这将会浪费大量时间。有两种方法可以解决这个问题:

1. 使用ChromeDriverService

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
@RunWith(BlockJUnit4ClassRunner.class)
public class ChromeTest extends TestCase {

private static ChromeDriverService service;
private WebDriver driver;

@BeforeClass
public static void createAndStartService() {
service = new ChromeDriverService.Builder()
.usingDriverExecutable(new File("path/to/my/chromedriver"))
.usingAnyFreePort()
.build();
service.start();
}

@AfterClass
public static void createAndStopService() {
service.stop();
}

@Before
public void createDriver() {
driver = new RemoteWebDriver(service.getUrl(),
DesiredCapabilities.chrome());
}

@After
public void quitDriver() {
driver.quit();
}

@Test
public void testGoogleSearch() {
driver.get("http://www.google.com");
// rest of the test...
}
}

2. 在运行测试之前单独启动ChromeDriver服务器,并使用Remote WebDriver连接到它

在控制台执行chromedriver(如果没有添加环境变量的话需要到对应目录下执行)

1
2
3
F:\temp>chromedriver
Starting ChromeDriver 2.37.543627 (63642262d9fb93fb4ab52398be4286d844092a5e) on port 9515
Only local connections are allowed.

Java 代码:

1
2
3
4
5
public static void main(String[] args) throws MalformedURLException {
WebDriver driver = new RemoteWebDriver(new URL("http://127.0.0.1:9515"), DesiredCapabilities.chrome());

driver.get("http://www.baidu.com");
}

I2P是一个匿名网络,它只暴露一个简单的层,提供给应用程序之间进行匿名和安全的通讯。 这个网络本身是严格基于消息的(通过IP方式),但也存在一个库可用于在其之上传输可靠的信息流(通过TCP方式)。 所有的通讯都是端到端加密的(发送一条消息总共会进行四层加密),甚至是终结点(目标)也是加密的标识(本质上是一对公钥)。

官方网站: https://geti2p.net/
维基百科:https://zh.wikipedia.org/wiki/I2P

安全性

TOR 是洋葱路由,而 I2P是大蒜路由。是洋葱路由的改进版,安全性更好、隐匿性更强。

TOR 和 I2P 的相同点在于:
都是经过若干个网络节点来加密和中转数据,并防止你的真实 IP 暴露。

两者的差别在于:

  • TOR 使用【同一条网络链路】实现数据的发送和接收;
  • I2P 使用【多条网络链路】发送数据和接受数据——并且发送和接收数据的链路,数量可以是不同的。

抗封杀

在抗封杀方面,I2P 比 TOR 要坚挺。

TOR 每次启动时,需要先连接到某个 TOR 的目录服务器,获取网络上可用节点的信息。由于目录服务器数量有限,GFW 就把互联网上所有的 TOR 目录服务器的 IP 地址都列入黑名单。后来,TOR 官网提供网桥中继,帮助网友接入 TOR 网络。但是TOR 的网桥中继,数量依然不太多。据说 GFW 专门有人在盯着 TOR 官网更新的网桥中继地址——每次有新的中继地址贴出来,就列入黑名单。经过 GFW 的不懈努力,大部分 TOR 的网桥都被封杀。所以最近2年,TOR 在天朝内不太好使。

I2P 为啥封杀不了:
  I2P 使用 Kad 算法(用过电驴或电骡的网友,应该听说过)来获取网络节点的信息。这么做有几个好处:

  1. 不需要目录服务器
  2. Kad 算法拿到的节点信息只是整个 I2P 网络的一小部分
  3. 每一台运行 I2P 的主机都可以成为中继,帮别人转发数据(类似于 P2P 下载)

 
由于上述好处,GFW 很难把所有 I2P 节点都列入黑名单。

缺点

速度慢,速度慢是 I2P 为了安全性而不得不付出的代价。

使用

先去官网下载,然后按照,再运行

 I2P 本身不提供 GUI 界面,但是提供 Web 界面。I2P 启动后,你只需在浏览器地址栏输入 http://127.0.0.1:7657/ 即可看到 I2P 的控制界面。

补种

这是非常关键的一步

I2P 是依靠 Kad 网络算法,通过不断扩散,来获取越来越多的节点信息。但是这个 Kad 网络算法不是万能的——它需要一些初始的种子,才能开始工作。在 I2P 的安装包中,已经内置了若干种子(节点信息)。但是GFW 早就把这些内置的节点彻底封杀了。所以在国内,第一次启动 I2P 会找不到网络。这时候就需要补种(reseed)。

通常来说,补种只需要做一次,之后你的 I2P 就可以一直联网了。如果你的 I2P 停了很长时间(几个月)没有运行,那么下一次运行的时候,可能会无法联网,这时候就需要再补种。

补种(Reseeding)是一个网络引导(Bootstrip)过程,新用户通过这个过程发现其他I2P用户,很久未上线的老用户在已知的有效节点很少时也会进入引导/补种状态。

访问界面:
http://127.0.0.1:7657/configreseed
在下面填上能够翻墙的代理地址和端口,点击 保存修改 + 立刻开始网络引导

设置浏览器的 HTTP 代理 为 127.0.0.1:4444, 然后才能浏览 I2P 站点。

http://i2pwiki.i2p

shell判断命令执行情况

命令执行失败时退出shell

判断

1
if ! command; then echo "command failed"; exit 1; fi

判断上一个命令的返回值不为0

1
2
command
if [ "$?" -ne 0 ]; then echo "command failed"; exit 1; fi

逻辑与
执行多个命令

1
command1  && command2

&&左边的命令(命令1)返回真(即返回0,成功被执行)后,&&右边的命令(命令2)才能够被执行;换句话说,“如果这个命令执行成功&&那么执行这个命令”。

逻辑或

1
command1 || command2

||则与&&相反。如果||左边的命令(command1)未执行成功,那么就执行||右边的命令(command2);或者换句话说,“如果这个命令执行失败了||那么就执行这个命令。

1
command || { echo "command failed"; exit 1; }

YAML

方便人类读写的一种数据串行化格式

基本语法规则:

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进时不允许使用Tab键,只允许使用空格
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • #表示注释,从这个字符一直到行尾,都会被解析器忽略

支持的数据结构:

  • 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
  • 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
  • 纯量(scalars):单个的、不可再分的值

对象表达方式

对象的一组键值对,使用冒号结构表示

1
animal: pets

键值对写成一个行内对象

1
hash: { name: Steve, foo: bar } 

数组

一组连词线开头的行,构成一个数组

1
2
3
- Cat
- Dog
- Goldfish

数据结构的子成员是一个数组,则可以在该项下面缩进一个空格

1
2
3
4
-
- Cat
- Dog
- Goldfish

转为 JavaScript 如下

1
[ [ 'Cat', 'Dog', 'Goldfish' ] ]

数组也可以采用行内表示法

1
2
animal: [Cat, Dog]

复合结构

1
2
3
4
5
6
7
8
9
languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: use.perl.org

纯量

  • 字符串
  • 整数
  • 浮点数
  • Null
  • 时间
  • 日期
  • 布尔值

数值直接以字面量的形式表示

1
number: 12.30

布尔值用true和false表示

1
isSet: true

null用 ==~== 表示

1
parent: ~ 

等同于如下JavaScript

1
{ parent: null }

时间采用 ISO8601 格式

1
iso8601: 2001-12-14t21:59:43.10-05:00 

后面的 -05:00 表示时区

日期采用复合 iso8601 格式的年、月、日表示

1
date: 1976-07-31

使用两个感叹号,强制转换数据类型

1
2
e: !!str 123
f: !!str true

字符串

字符串默认不使用引号表示

1
2
str: 这是一行字符串

如果字符串之中包含空格或特殊字符,需要放在引号之中

1
2
str: '内容: 字符串'

单引号和双引号都可以使用,双引号不会对特殊字符转义

1
2
s1: '内容\n字符串'
s2: "内容\n字符串"

单引号之中如果还有单引号,必须连续使用两个单引号转义

1
str: 'labor''s day' 

字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格

1
2
3
str: 这是一段
多行
字符串

转为 JavaScript

1
{ str: '这是一段 多行 字符串' }

多行字符串可以使用 ==|== 保留换行符,也可以使用 ==>== 折叠换行

1
2
3
4
5
6
this: |
Foo
Bar
that: >
Foo
Bar

转为 JavaScript

1
{ this: 'Foo\nBar\n', that: 'Foo Bar\n' }

==+== 表示保留文字块末尾的换行,==-== 表示删除字符串末尾的换行

1
2
3
4
5
6
7
8
9
s1: |
Foo

s2: |+
Foo


s3: |-
Foo

转为 JavaScript

1
{ s1: 'Foo\n', s2: 'Foo\n\n\n', s3: 'Foo' }

字符串之中可以插入 HTML 标记

1
2
3
4
5
message: |

<p style="color: red">
段落
</p>

转为 JavaScript 如下

1
{ message: '\n<p style="color: red">\n  段落\n</p>\n' }

引用

锚点 ==&== 和别名 ==*==,可以用来引用

1
2
3
4
5
6
7
8
9
10
11
defaults: &defaults
adapter: postgres
host: localhost

development:
database: myapp_development
<<: *defaults

test:
database: myapp_test
<<: *default

等同于下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
defaults:
adapter: postgres
host: localhost

development:
database: myapp_development
adapter: postgres
host: localhost

test:
database: myapp_test
adapter: postgres
host: localhost

& 用来建立锚点(defaults),**<<** 表示合并到当前数据,* 用来引用锚点。

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
@echo off

rem 想用bat 做一个递归清理文件
rem 目前就是递归执行一下 maven的清理命令
rem 语法真的是渣

rem echo 当前盘符:%~d0
rem echo 当前盘符和路径:%~dp0
rem echo 当前批处理全路径:%~f0
rem echo 当前盘符和路径的短文件名格式:%~sdp0
rem echo 当前CMD默认目录:%cd%

echo ************************************
echo ************ 清理数据 **************
echo ************************************

IF "%1"=="" (
echo 清理目录:%cd%
set prodir=%cd%
) ELSE (
echo 当前参数参数是:[%1]
echo 清理目录:%1
set prodir=%1
if exist %1/ (
cd %1
) else (
echo 目录:[%1]不存在,即将退出!
pause
exit
)
)


rem echo 路径:
rem chdir


for /D %%s in (*) do (

echo 当前循环的值是:%%s


REM 跳过git目录

IF %%s==.git (
echo 这是一个git目录,跳过

) ELSE (
echo 进入目录:%%s
cd %%s
REM echo 判断pom文件是否存在
REM dir

IF EXIST pom.xml (
echo 存在pom文件,开始执行清理命令...

mvn clean

@echo off

) ELSE (
echo 不存在pom文件,进入下一层目录
REM goto BB
REM call %~f0 %cd%
call %~f0

echo 子目录处理完成

)

echo 退出目录:%%s

cd ..
)

echo 本次循环结束,循环值是:%%s
echo ============================================================

)

echo 执行完成

0%