仅仅作为记录,为了不让树莓派吃灰。主要参考官网这篇文章。
Docker 的好用程度已经不比多说,经过这两年的发展已经非常成熟,还记得一年前买的书已经跟不上Docker的发展了,所以这里还是推荐 Docker 的官方文档,要比市面上存在所有书籍要详细。不过要是想要了解 Docker 的内部技术还是有不少好书可以参考。跑偏了,回到正题。
Docker 官方已经支持 Raspbian Jessie,所以可以直接安装:
curl -sSL https://get.docker.com | sh
Docker client 只能被 root 或者 docekr group 中的用户使用,所以将 pi 用户添加到 docker
用户组:
sudo usermod -aG docker pi
如果拉取了 busybox
镜像,可能会发现工作不正常,这只是因为有些镜像是为了 PC 或者 x86_64
架构设计的,可能未来版本会修复,所以应该使用那些设计为了在 ARM 上运行的镜像,目前 Docker 团队维护了一小部分镜像,可以在arm32v6这个用户下找到。
可以使用 arm32v6/alpine
作为基础镜像,同样也可以使用 Resin.io 制作的镜像,该镜像被用在了当前 Docker 中,这是一个轻量版本的 Raspberry Jessie。
比如说想要制作一个在树莓派上能够跑的镜像,可以以 resion/rpi-raspbian
作为基础镜像
FROM resin/rpi-raspbian:latest
ENTRYPOINT []
RUN apt-get update && \
apt-get -qy install curl ca-certificates
CMD ["curl", "https://docker.com"]
或者也可以
FROM arm32v6/alpine:3.5
RUN apk add --no-cache curl ca-certificates
CMD ["curl", "https://docker.com"]
build 命令
docker build -t curl_docker .
docker run curl_docker
如果不怎么使用 Raspberry Pi 连接显示器,或者不怎么使用 GPU,可以限制 gpu 内存的占用,修改 /boot/config.txt
添加下面一行:
gpu_mem=16
percona-toolkit 源自 Maatkit 和 Aspersa 工具,这两个工具是管理 MySQL 的最有名的工具,但 Maatkit 已经不维护了,全部归并到 percona-toolkit。Percona Toolkit 是一组高级的命令行工具,用来管理 MySQL 和系统任务,主要包括:
这里主要介绍在线修改表结构功能。
在运维 MySQL 数据库时,我们总会对数据表进行 ddl 变更,修改添加字段或者索引,对于 MySQL 而已,ddl 显然是一个令所有 MySQL dba 诟病的一个功能,因为在 MySQL 中在对表进行 ddl 时,会锁表,当表比较小,比如小于 1w 上时,对前端影响较小,当时遇到千万级别的表 就会影响前端应用对表的写操作。在 5.1 之前都是非常耗时耗力的,在 5.1 之后随着 Plugin innodb 的出现在线加索引的提高了很多,但是还会影响(时间缩短了); 不过 5.6 可以避免上面的情况。目前 InnoDB 引擎是通过以下步骤来进行 DDL 的 (注:mysql 版本查看:mysql -V
) :
insert into tmp_table select * from original_table
我们可以看见在 InnoDB 执行 DDL 的时候,原表是只能读不能写的。为此 perconal 推出一个工具 pt-online-schema-change ,其特点是修改过程中不会造成读写阻塞。
pt-online-schema-change
工作原理
alter-foreign-keys-method
参数的值,检测外键相关的表,做相应设置的处理。没有使用 alter-foreign-keys-method 指定特定的值,该工具不予执行_new
—-_原表名_new
官网下载地址:
Mac 下使用 brew 安装:
brew install percona-toolkit
pt-online-schema-change 使用
pt-online-schema-change [OPTIONS] DSN
OPTIONS 参数说明:
--user:
-u,连接的用户名
--password:
-p,连接的密码
--database:
-D,连接的数据库
--port
-P,连接数据库的端口
--host:
-h,连接的主机地址
--socket:
-S,连接的套接字文件
--ask-pass
隐式输入连接 MySQL 的密码
--charset
指定修改的字符集
--defaults-file
-F,读取配置文件
--alter:
结构变更语句,不需要 alter table 关键字。可以指定多个更改,用逗号分隔。如下场景,需要注意:
不能用 RENAME 来重命名表。
列不能通过先删除,再添加的方式进行重命名,不会将数据拷贝到新列。
如果加入的列非空而且没有默认值,则工具会失败。即其不会为你设置一个默认值,必须显示指定。
删除外键 (drop foreign key constrain_name) 时,需要指定名称_constraint_name,而不是原始的 constraint_name。
如:CONSTRAINT `fk_foo` FOREIGN KEY (`foo_id`) REFERENCES `bar` (`foo_id`),需要指定:--alter "DROP FOREIGN KEY _fk_foo"
--alter-foreign-keys-method
如何把外键引用到新表?需要特殊处理带有外键约束的表,以保证它们可以应用到新表。当重命名表的时候,外键关系会带到重命名后的表上。
该工具有两种方法,可以自动找到子表,并修改约束关系。
auto: 在 rebuild_constraints 和 drop_swap 两种处理方式中选择一个。
rebuild_constraints:使用 ALTER TABLE 语句先删除外键约束,然后再添加。如果子表很大的话,会导致长时间的阻塞。
drop_swap: 执行 FOREIGN_KEY_CHECKS=0, 禁止外键约束,删除原表,再重命名新表。这种方式很快,也不会产生阻塞,但是有风险:
1, 在删除原表和重命名新表的短时间内,表是不存在的,程序会返回错误。
2, 如果重命名表出现错误,也不能回滚了。因为原表已经被删除。
none: 类似"drop_swap"的处理方式,但是它不删除原表,并且外键关系会随着重命名转到老表上面。
--[no]check-alter
默认 yes,语法解析。配合 --dry-run 和 --print 一起运行,来检查是否有问题(change column,drop primary key)。
--max-lag
默认 1s。每个 chunk 拷贝完成后,会查看所有复制 Slave 的延迟情况。要是延迟大于该值,则暂停复制数据,直到所有从的滞后小于这个值,使用 Seconds_Behind_Master。如果有任何从滞后超过此选项的值,则该工具将睡眠 --check-interval 指定的时间,再检查。如果从被停止,将会永远等待,直到从开始同步,并且延迟小于该值。如果指定 --check-slave-lag,该工具只检查该服务器的延迟,而不是所有服务器。
--check-slave-lag
指定一个从库的 DSN 连接地址,如果从库超过 --max-lag 参数设置的值,就会暂停操作。
--recursion-method
默认是 show processlist,发现从的方法,也可以是 host,但需要在从上指定 report_host,通过 show slave hosts 来找到,可以指定 none 来不检查 Slave。
METHOD USES
=========== ==================
processlist SHOW PROCESSLIST
hosts SHOW SLAVE HOSTS
dsn=DSN DSNs from a table
none Do not find slaves
指定 none 则表示不在乎从的延迟。
--check-interval
默认是 1。--max-lag 检查的睡眠时间。
--[no]check-plan
默认 yes。检查查询执行计划的安全性。
--[no]check-replication-filters
默认 yes。如果工具检测到服务器选项中有任何复制相关的筛选,如指定 binlog_ignore_db 和 replicate_do_db 此类。发现有这样的筛选,工具会报错且退出。因为如果更新的表 Master 上存在,而 Slave 上不存在,会导致复制的失败。使用–no-check-replication-filters 选项来禁用该检查。
--[no]swap-tables
默认 yes。交换原始表和新表,除非你禁止 --[no]drop-old-table。
--[no]drop-triggers
默认 yes,删除原表上的触发器。 --no-drop-triggers 会强制开启 --no-drop-old-table 即:不删除触发器就会强制不删除原表。
--new-table-name
复制创建新表的名称,默认 %T_new。
--[no]drop-new-table
默认 yes。删除新表,如果复制组织表失败。
--[no]drop-old-table
默认 yes。复制数据完成重命名之后,删除原表。如果有错误则会保留原表。
--max-load
默认为 Threads_running=25。每个 chunk 拷贝完后,会检查 SHOW GLOBAL STATUS 的内容,检查指标是否超过了指定的阈值。如果超过,则先暂停。这里可以用逗号分隔,指定多个条件,每个条件格式: status 指标 =MAX_VALUE 或者 status 指标:MAX_VALUE。如果不指定 MAX_VALUE,那么工具会这只其为当前值的 120%。
--critical-load
默认为 Threads_running=50。用法基本与 --max-load 类似,如果不指定 MAX_VALUE,那么工具会这只其为当前值的 200%。如果超过指定值,则工具直接退出,而不是暂停。
--default-engine
默认情况下,新的表与原始表是相同的存储引擎,所以如果原来的表使用 InnoDB 的,那么新表将使用 InnoDB 的。在涉及复制某些情况下,很可能主从的存储引擎不一样。使用该选项会默认使用默认的存储引擎。
--set-vars
设置 MySQL 变量,多个用逗号分割。默认该工具设置的是: wait_timeout=10000 innodb_lock_wait_timeout=1 lock_wait_timeout=60
--chunk-size-limit
当需要复制的块远大于设置的 chunk-size 大小,就不复制。默认值是 4.0,一个没有主键或唯一索引的表,块大小就是不确定的。
--chunk-time
在 chunk-time 执行的时间内,动态调整 chunk-size 的大小,以适应服务器性能的变化,该参数设置为 0, 或者指定 chunk-size, 都可以禁止动态调整。
--chunk-size
指定块的大小,默认是 1000 行,可以添加 k,M,G 后缀。这个块的大小要尽量与 --chunk-time 匹配,如果明确指定这个选项,那么每个块就会指定行数的大小。
--[no]check-plan
默认 yes。为了安全,检查查询的执行计划。默认情况下,这个工具在执行查询之前会先 EXPLAIN, 以获取一次少量的数据,如果是不好的 EXPLAIN, 那么会获取一次大量的数据,这个工具会多次执行 EXPALIN, 如果 EXPLAIN 不同的结果,那么就会认为这个查询是不安全的。
--statistics
打印出内部事件的数目,可以看到复制数据插入的数目。
--dry-run
创建和修改新表,但不会创建触发器、复制数据、和替换原表。并不真正执行,可以看到生成的执行语句,了解其执行步骤与细节。--dry-run 与 --execute 必须指定一个,二者相互排斥。和 --print 配合最佳。
--execute
确定修改表,则指定该参数。真正执行。--dry-run 与 --execute 必须指定一个,二者相互排斥。
--print
打印 SQL 语句到标准输出。指定此选项可以让你看到该工具所执行的语句,和 --dry-run 配合最佳。
--progress
复制数据的时候打印进度报告,二部分组成:第一部分是百分比,第二部分是时间。
--quiet
-q,不把信息标准输出。
建立测试表:
CREATE TABLE `online_table` (
`id` int(11) primary key,
`name` varchar(10) DEFAULT NULL,
`age` int(11) DEFAULT NULL
) engine = innodb default charset utf8;
–-dry-run
不真实执行:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "ADD COLUMN content text" D=test,t=online_table --print --dry-run
使用 –-execute
真实执行:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "ADD COLUMN content text" D=test,t=online_table --print --execute
查看表结构:
> describe online_table;
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(10) | YES | | NULL | |
| age | int(11) | YES | | NULL | |
| content | text | YES | | NULL | |
+---------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
注:上面的执行可能会出现异常:
Error setting innodb_lock_wait_timeout: DBD::mysql::db do failed: Variable ‘innodb_lock_wait_timeout’ is a read only variable [for Statement “SET SESSION innodb_lock_wait_timeout=1”]. The current value for innodb_lock_wait_timeout is 50. If the variable is read only (not dynamic), specify –set-vars innodb_lock_wait_timeout=50 to avoid this warning, else manually set the variable and restart MySQL.
根据提示(innodb_lock_wait_timeout 是静态参数),加上–set-vars innodb_lock_wait_timeout=50 即可:
pt-online-schema-change --user=root --passwor=root --host=localhost --alter "ADD COLUMN content text" D=test,t=user --set-vars innodb_lock_wait_timeout=50 --print --execute
表结构
> describe tmp_test;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
添加字段:
pt-online-schema-change --user=root --host=127.0.0.1 --alter "ADD COLUMN content text" D=crm_production,t=tmp_test --ask-pass --print --execute
Enter MySQL password:
报错,因为该工具在检测到服务器选项中有任何复制相关的筛选会退出,需要指定:–no-check-replication-filters
Replication filters are set on these hosts:
database2
replicate_do_db = crm_production
Please read the –check-replication-filters documentation to learn how to solve this problem. at /usr/local/bin/pt-online-schema-change line 8015, line 2.
加上参数:–no-check-replication-filters
pt-online-schema-change --user=root --host=127.0.0.1 --alter "ADD COLUMN content text" D=crm_production,t=tmp_test --ask-pass --no-check-replication-filters --print --execute
除了 add column,也可以 modify column,drop column,对于 change column 则需要指定:–no-check-alter
测试表:
create table tt(
id int not null auto_increment,
name varchar(10),primary key(id)
) engine=innodb default charset utf8;
create table xx(
id int not null auto_increment,
tt_id int not null,
name varchar(10),
primary key(id)
) engine=innodb default charset utf8;
alter table xx add foreign key fk_xx_tt_id(tt_id) references tt(id);
添加字段:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "ADD COLUMN content text" D=test,t=tt --no-check-replication-filters --print --execute
执行错误退出,提示需要指定:–alter-foreign-keys-method 参数来操作有外键的表。要是没有外键而加了参数的话会出现: No foreign keys reference test.xx; ignoring –alter-foreign-keys-method。
使用 –-alter-foreign-keys-method
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "ADD COLUMN content text" D=test,t=tt --no-check-replication-filters --alter-foreign-keys-method auto --print --execute
注:对可靠性要求不高可以用 auto 模式更新,要是可靠性要求高则需要用 rebuild_constraints 模式。即:
--alter-foreign-keys-method rebuild_constraints
测试表:
CREATE TABLE `tmp_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(10) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
增加字段:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "ADD COLUMN content text" D=test,t=tmp_test --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --print --execute
删除字段:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "DROP COLUMN content " D=test,t=tmp_test --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --quiet --execute
修改字段:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "MODIFY COLUMN age TINYINT NOT NULL DEFAULT 0" D=test,t=tmp_test --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --quiet --execute
改名:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "CHANGE COLUMN age address varchar(30)" D=test,t=tmp_test --no-check-alter --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --quiet --execut
索引:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "ADD INDEX idx_address(address)" D=test,t=tmp_test --no-check-alter --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --print --execute
删除索引:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "DROP INDEX idx_address" D=test,t=tmp_test --no-check-alter --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --print --execute
其他可选项:
–no-drop-old-table
参数:
上面的测试都是把原表删除了,要是不删除原表则: –no-drop-old-table,这样会让原表(_test_binlog_old)保留。
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "ADD COLUMN a text" D=test,t=test_binlog --no-check-replication-filters --no-drop-old-table --print --execute
–max-load
选项
要是在线上环境上添加字段,但又不想影响到服务,可以用参数:–max-load 去执行该工具,默认是 Threads_running=25,即当前有这么多线程在运行的时候就暂停数据的复制,等少于该值则继续复制数据到新表:
pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "add INDEX idx_address(address)" D=test,t=tmp_test --no-check-alter --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --max-load=Threads_running=2 --statistics --print --execute
暂停的时候标准输出里面会有: Pausing because Threads_running=2。等到运行的线程数小于给定的值,则就继续复制数据,直到完成。
当业务量较大时,修改操作会等待没有数据修改后,执行最后的 rename 操作。因此,在修改表结构时,应该尽量选择在业务相对空闲时,至少修改表上的数据操作较低时,执行较为妥当。如果对外键表操作时,四种外键操作类型需要根据表的数据量和可靠程度,进行选择。处于可靠性的原因,尽量使用 rebuild_constraints
类型,如果没有可靠性要求,可以使用 auto 类型。
由于可能存在一定的风险,在操作之前,建议对数据表进行备份,可以使得操作更安全、可靠。使用该工具的前提是处理的表需要有主键或则唯一索引。当处理有外键的表时,需要加 –alter-foreign-keys-method
参数,值可以根据情况设置。当是主从环境,不在乎从的延迟,则需要加 –recursion-method=none
参数。当需要尽可能的对服务产生小的影响,则需要加上 –max-load
参数。
Java 8 中 stream 大大简化了 Collection 的操作,所以这篇文章就简单的了解下 stream 的基本用法,关于 collect,flatmap,map 等等更加高级的用法可能还需要另开一篇 总结。
Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。
有很多种方法
stream()
方法或者 parallelStream()
,比如 Arrays.asList(1,2,3).stream()
Arrays.stream(Object[])
方法,比如 Arrays.stream(new int[]{1,2,3})
Stream.of(Object[])
, IntStream.range(int, int)
或者 Stream.iterate(Object, UnaryOperator),如 Stream.iterate(0, n -> n * 2),或者 generate(Supplier<T> s)
如 Stream.generate(Math::random)中间操作会返回一个新的流,并且操作是延迟执行的 (lazy),它不会修改原始的数据源,而且是由在终点操作开始的时候才真正开始执行。 这个 Scala 集合的转换操作不同,Scala 集合转换操作会生成一个新的中间集合,显而易见 Java 的这种设计会减少中间对象的生成。
distinct 保证输出的流中包含唯一的元素,它是通过 Object.equals(Object) 来检查是否包含相同的元素。
List<String> list = Arrays.asList("a", "b", "a", "c").stream()
.distinct()
.collect(Collectors.toList());
System.out.println(list); //[a, b, c]
filter 返回的流中只包含满足断言 (predicate) 的数据。
List<Integer> list = IntStream.range(1, 10)
.filter(i -> i % 2 == 0)
.boxed()
.collect(Collectors.toList());
System.out.println(list); // [2, 4, 6, 8]
对于原始类型,stream 无法处理,需要调用 boxed 将其装换成对应的 wrapper class
List<Integer> ints = IntStream.of(1,2,3,4,5)
.boxed()
.collect(Collectors.toList());
map 方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。
List<Integer> list = Stream.of("A", "B", "C")
.map(c -> c.hashCode())
.collect(Collectors.toList());
System.out.println(list); // [65, 66, 67]
也可以有 mapToInt
Stream.of("a1", "a2", "a3")
.map(s -> s.substring(1))
.mapToInt(Integer::parseInt)
.max()
.ifPresent(System.out::println); // 3
flatmap
方法混合了 map
+ flattern
的功能,它将映射后的流的元素全部放入到一个新的流中。它的方法定义如下:
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
可以看到 mapper 函数会将每一个元素转换成一个流对象,而 flatMap
方法返回的流包含的元素为 mapper 生成的所有流中的元素。
List<List<String>> lists = Arrays.asList(Arrays.asList("a", "b"), Arrays.asList("c", "d"));
List<String> collect = lists.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(collect); // [a, b, c, d]
limit 方法指定数量的元素的流。对于串行流,这个方法是有效的,这是因为它只需返回前 n 个元素即可,但是对于有序的并行流,它可能花费相对较长的时间,如果你不在意有序,可以将有序并行流转换为无序的,可以提高性能。
List<Integer> l = IntStream.range(1,100).limit(5)
.boxed()
.collect(Collectors.toList());
System.out.println(l);//[1, 2, 3, 4, 5]
peek 方法方法会使用一个 Consumer 消费流中的元素,但是返回的流还是包含原来的流中的元素。
String[] arr = new String[]{"a","b","c","d"};
Arrays.stream(arr)
.peek(System.out::println) //a,b,c,d
.count();
sorted()
将流中的元素按照自然排序方式进行排序,如果元素没有实现 Comparable,则终点操作执行时会抛出 java.lang.ClassCastException
异常。 sorted(Comparator<? super T> comparator)
可以指定排序的方式。
对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。
List<String> list = Arrays.asList("ac", "ab", "bc", "dc", "ad", "ea").stream()
.sorted((a, b) -> {
if (a.charAt(0) == b.charAt(0)) {
return a.substring(1).compareTo(b.substring(1));
} else {
return b.charAt(0) - a.charAt(0);
}
}).collect(Collectors.toList());
System.out.println(list);
skip 返回丢弃了前 n 个元素的流,如果流中的元素小于或者等于 n,则返回空的流。
终点操作,这些操作都是返回 Void ,所以不能在调用之后再使用中间操作。
这一组方法用来检查流中的元素是否满足断言。
public boolean allMatch(Predicate<? super T> predicate)
public boolean anyMatch(Predicate<? super T> predicate)
public boolean noneMatch(Predicate<? super T> predicate)
举例
System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(i -> i > 0)); //true
System.out.println(Stream.of(1, 2, 3, 4, 5).anyMatch(i -> i > 0)); //true
System.out.println(Stream.of(1, 2, 3, 4, 5).noneMatch(i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().allMatch(i -> i > 0)); //true
System.out.println(Stream.<Integer>empty().anyMatch(i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().noneMatch(i -> i > 0)); //true
count 返回流中的元素数量,返回类型 long
long count = Stream.of(1, 2, 3, 4).count();
System.out.println(count);
实现为
mapToLong(e -> 1L).sum();
collect
方法是一个非常有用的终止操作,可以将 stream 转化成各种需要的结果,List,Set,Map 等等。collect
接受 Collector 作为参数,该参数支持四种操作:a supplier, an accumulator, a combiner and a finisher。听起来非常复杂,其实 Java 8 通过内建的 Collectors
类提供了绝大多数方法。
使用一个 collector 执行 mutable reduction 操作。辅助类 Collectors 提供了很多的 Collector,可以满足我们日常的需求,你也可以创建新的 Collector 实现特定的需求。它是一个值得关注的类,你需要熟悉这些特定的收集器,如聚合类 averagingInt、最大最小值 maxBy minBy、计数 counting、分组 groupingBy、字符串连接 joining、分区 partitioningBy、汇总 summarizingInt、化简 reducing、转换 toXXX 等。
.collect(Collectors.toList());
findAny()
返回任意一个元素,如果流为空,返回空的 Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于 findFirst()
,但是有可能多次执行的时候返回的结果不一样。
findFirst() 返回第一个元素,如果流为空,返回空的 Optional。
forEach 遍历流的每一个元素,执行指定的 action。它是一个终点操作,和 peek 方法不同。这个方法不担保按照流的 encounter order 顺序执行,如果对于有序流按照它的 encounter order 顺序执行,你可以使用 forEachOrdered 方法。
Stream.of(1,2,3,4,5).forEach(System.out::println);
max 返回流中的最大值,min 返回流中的最小值。
reduce 是常用的一个方法,事实上很多操作都是基于它实现的。在流上执行一个缩减操作,返回的结果是 Optional
,其中包含缩减的结果。
它有几个重载方法:
pubic Optional<T> reduce(BinaryOperator<T> accumulator)
pubic T reduce(T identity, BinaryOperator<T> accumulator)
pubic <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
第一个方法使用流中的第一个值作为初始值,后面两个方法则使用一个提供的初始值。
Optional<Integer> total = Stream.of(1,2,3,4,5).reduce( (x, y) -> x +y);
Integer total2 = Stream.of(1,2,3,4,5).reduce(0, (x, y) -> x +y);
将流中的元素放入到一个数组中。
Java 8 默认的流是不能被重用的,一旦使用 terminal operations 流就被关闭了。
Stream<String> stream =
Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
stream.anyMatch(s -> true); // ok
stream.noneMatch(s -> true); // exception
重复调用同一个流时,比如上面的例子,就会抛出异常
java.lang.IllegalStateException: stream has already been operated upon or closed
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
at com.winterbe.java8.Streams5.test7(Streams5.java:38)
at com.winterbe.java8.Streams5.main(Streams5.java:28)
为了克服这种限制,就必须创建 stream supplier,然后在每一次 intermediate operations 时调用
Supplier<Stream<String>> streamSupplier =
() -> Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
streamSupplier.get().anyMatch(s -> true); // ok
streamSupplier.get().noneMatch(s -> true); // ok
每一次调用 get()
方法都会创建一个新的 stream。
很早以前买一台VPS主要的功能就是翻墙,然后常年也仅仅是跑一个 Shadowsocks,后来渐渐的发现其实有一台服务器即使只有单核1G,也能够用来做很多事情。以前我也看过一些文章讲述如何充分利用起VPS,但大部分除了说自建网站,挂机刷 YouTube 赚钱外也都没有什么实质性的内容。而自从开始接触Docker,我渐渐的发现了很多服务,因此我自己搜罗了一些。当然其实 GitHub 上有一个 Awesome Selfhosted 这里面列举了成百上千种可以自己托管部署的服务,几乎可以代替掉日常生活中用到的80%的服务,可以自建 nextcloud 代替 Dropbox,可以用 WordPress 代替 Blogger,可以用 GitLab,gogs 代替 GitHub,你甚至可以构建自己的即时通讯,邮件服务,社交媒体1 等等。
当然这一切都无法离开一台可用稳定的VPS,并且借助Docker几乎可以做到无痛搭建,备份,迁移,不用在花费时间在环境和部署中。下面就分享一些我觉得很好用的服务,可以用来充分利用你的机器。
总得来说一般可以将 VPS 用于如下几类用途:
自建博客
文件共享服务,使用 Go 书写,可以用来快速分享本地文件给别人。支持 Web UI,也支持 API 上传。
一款文件夹浏览,一般情况下开启 Nginx 允许浏览目录的设置也足够了,h5ai提供了一些额外的功能,比如二维码,搜索等等
一款较 GitLab 轻便的 Git 托管服务。
持续集成 CI
内网穿透
签到服务,binux 写的自动签到服务
爬虫
无污染 DNS 服务器
作为跳板机 ssh 连接其他电脑用
aria2 挂机下载
Docker Volumes 机制通常用来给 Docker 容器保存持久化数据,使用 Volumes 有很多优势:
Volumes 通常也优于容器的可写层,使用 Volumes 不会增加容器的体积,并且 Volumes 的内容存储在外部独立于容器的生命周期。如果容器不产生持久化数据,可以考虑使用 tmpfs mount来避免数据存储在其他可能的地方,避免增加容器的体积。
最开始 -v
或者 --volume
选项是给单独容器使用, --mount
选项是给集群服务使用。但是从 Docker 17.06 开始,也可以在单独容器上使用 --mount
。通常来讲 --mount
选项也更加具体(explicit)和”啰嗦”(verbose),最大的区别是
-v
选项将所有选项集中到一个值--mount
选项将可选项分开如果需要指定 volume driver 选项,那么必须使用 --mount
-v
或 --volume
: 包含三个 field,使用 :
来分割,所有值需要按照正确的顺序。第一个 field 是 volume 的名字,并且在宿主机上唯一,对于匿名 volume,第一个field通常被省略;第二个field是宿主机上将要被挂载到容器的path或者文件;第三个field可选,比如说 ro
--mount
: 包含多个 key-value 对,使用逗号分割。--mount
选项更加复杂,但是各个值之间无需考虑顺序。
type
,可以为 bind
, volume
, tmpfs
, 通常为 volume
source
也可以写成 src
,对于 named volumes,可以设置 volume 的名字,对于匿名 volume,可以省略destination
可以写成 dst
或者 target
该值会挂载到容器readonly
可选,如果使用,表示只读volume-opt
可选,可以使用多次两个例子
docker run -d \
--name=nginxtest \
--mount source=nginx-vol,destination=/usr/share/nginx/html \
nginx:latest
docker run -d \
--name=nginxtest \
-v nginx-vol:/usr/share/nginx/html \
nginx:latest
Gogs 是一个能够自建Git托管服务的开源项目,用 Go 语言实现。因为较之 GitLab 轻量化一些,所以受到一定欢迎。
使用 Docker 来搭建 Gogs 服务时,需要额外依赖 MySQL,网上一般的教程都是先启动一个 MySQL 容器,开放端口,然后在启动 Gogs 容器配置。其实可以使用 docker-compose
一次性启动好。
version: '3.3'
services:
gogsdb:
image: mysql:5.7
container_name: gogsdb
restart: always
environment:
MYSQL_DATABASE: gogs
MYSQL_ROOT_PASSWORD: gogs
MYSQL_USER: gogs
MYSQL_PASSWORD: gogs
volumes:
- db_data:/var/lib/mysql_gogs
ports:
- "13306:3306"
networks:
- gogs-network
gogsapp:
depends_on:
- gogsdb
image: gogs/gogs
container_name: gogsapp
restart: always
ports:
- "322:22"
- "3000:3000"
volumes:
- app_data:/data
networks:
- gogs-network
volumes:
db_data:
app_data:
networks:
gogs-network:
driver: bridge
在第一次安装的时候你可能遇到下面的错误:
数据库设置不正确:dial tcp 127.0.0.1:13306: getsockopt: connection refused
或者
Database setting is not correct: dial tcp 127.0.0.1:13306: getsockopt: connection refused
这个错误的意思就是他表达的意思,gogs 想要在 127.0.0.1:13306
这个地址和端口连接 MySQL 失败了,我找了一圈之后发现,这里的地址需要填写 MySQL 容器的网关地址。
sudo docker inspect gogsdb
查看输出中的 Gateway
字段,填写这个地址。
最新的内容可以参考: https://github.com/einverne/dockerfile/tree/master/gogs
以下的内容适用于 docker-compose
版本 version 2
和 version 3
。先来看 Docker 官方文档中关于 Docker Compose and Django的例子,可以使用 depends_on
来访问容器中的数据
version: '2'
services:
db:
image: postgres
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
- db
而比如下面使用
web:
links:
- db
则表示当启动 db 容器时会随机分配一个本地端口比如32777来连接容器3306端口,每一次修改或者重启容器都会改变该端口,使用 links 来保证每一次都能够连接数据库,而不需要知道具体端口是什么。比如说启动了一个 MySQL 容器
docker run -d --name=my-mysql --env="MYSQL_ROOT_PASSWORD=mypassword" -P mysql
docker inspect <container-id> | grep HostPort
会显示该容器的本地端口。
当 docker-compose
执行 V2 文件时会自动在容器间创建一个网络,每一个容器都能够立即通过名字来访问到另外的容器。 因此,不再需要 links,links 过去通常用来开始db容器和web server容器网络之间的通讯,但是这一步已经被 docker-compose
做了。1
当使用 depends_on
来定义服务之间的依赖关系时会造成下面的影响2
docker-compose up
会依据依赖顺序启动服务docker-compose up
启动时 SERVICE 会自动包括 SERVICE 的依赖看这个例子:
version: '2'
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
这个例子中 db ,redis 容器启动顺序要优先于 web 容器;当启动 web 容器时会自动创建 redis 和 db 容器。
不过需要注意的是, depends_on
不会等到 db 和 redis 容器 ready
再启动,web 容器仅仅等到 redis 和 db 容器启动就开始启动。具体可参考官网启动顺序了解。
Privoxy 是一款不进行网页缓存且自带过滤功能的代理服务器,本文主要使用其 socks 转 http 代理的功能。Privoxy 也能够过滤网页内容,管理 Cookie,控制访问,去广告、横幅、弹窗等等,因此可以作为广告过滤。
Privoxy is a non-caching web proxy with advanced filtering capabilities for enhancing privacy, modifying web page data and HTTP headers, controlling access, and removing ads and other obnoxious Internet junk. GNU GPLv2 开源
因为 shadowsocks,v2ray 都是将代理转为本地 socks5 代理,所以如果需要使用 http 代理,就需要借助 Privoxy 。如果只需要在本地启用 http 代理,也可以使用 proxychains。
在 Linux 下安装非常简单
sudo apt install privoxy
默认的配置文件地址在 /etc/privoxy/config
目录下。假设本地 1080 端口已经启动(不管是本地 sslocal 还是 v2ray 本地都需要启动)然后要将本地 1080 socks5 代理转成 http 代理,重要的配置只有两行
# 把本地 HTTP 流量转发到本地 1080 SOCKS5 代理
forward-socks5t / 127.0.0.1:1080 .
# 可选,默认监听本地连接
listen-address 127.0.0.1:8118
如果想要将 http 代理非常到局域网中,可以使用 listen-address 0.0.0.0:8118
。 Privoxy 默认的端口为 8118,可以自行修改。修改完成保存之后使用如下命令启动
sudo /etc/init.d/privoxy start
sudo /etc/init.d/privoxy reload # 不重启服务的情况下重新加载配置
可以在终端进行测试 export http_proxy=http://127.0.0.1:8118 && curl ip.gs
应该显示代理的 IP 地址。如果监听 0.0.0.0:8118
,那么局域网中,使用 ip:8118 也能够使用该 HTTP 代理,并且所有的流量都经由 HTTP 转发到 SOCKS5 代理,并走 shadowsocks 或者 v2ray 到墙外。
使用浏览器配置 HTTP 代理,然后访问 http://p.p 如果看到 Privoxy 启动成功表示一切 OK。
当启动 sudo /etc/init.d/privoxy start
时出现如下错误:
systemctl status privoxy.service
● privoxy.service - Privacy enhancing HTTP Proxy
Loaded: loaded (/lib/systemd/system/privoxy.service; enabled; vendor preset: enabled)
Active: failed (Result: exit-code) since Sun 2018-03-11 17:49:40 CST; 4s ago
Process: 23666 ExecStopPost=/bin/rm -f $PIDFILE (code=exited, status=0/SUCCESS)
Process: 23668 ExecStart=/usr/sbin/privoxy --pidfile $PIDFILE --user $OWNER $CONFIGFILE (code=exited, status=1/FAILURE)
Main PID: 21029 (code=exited, status=15)
Mar 11 17:49:39 VM-145-149-ubuntu systemd[1]: Stopped Privacy enhancing HTTP Proxy.
Mar 11 17:49:39 VM-145-149-ubuntu systemd[1]: Starting Privacy enhancing HTTP Proxy...
Mar 11 17:49:40 VM-145-149-ubuntu systemd[1]: privoxy.service: Control process exited, code=exited status=1
Mar 11 17:49:40 VM-145-149-ubuntu systemd[1]: Failed to start Privacy enhancing HTTP Proxy.
Mar 11 17:49:40 VM-145-149-ubuntu systemd[1]: privoxy.service: Unit entered failed state.
Mar 11 17:49:40 VM-145-149-ubuntu systemd[1]: privoxy.service: Failed with result 'exit-code'.
绝大部分情况下是配置文件错误,仔细检查 /etc/privoxy/config
文件,是否有重复配置,或者输入错误。
其核心配置文件在 /etc/privoxy/config
文件中,之前配置过转发 socks 流量就在该文件中,不过通常情况下会需要修改另外两类文件:
match-all.action
,default.action
,user.action
default.filter
, user.filter
match-all.action
,default.action
,default.filter
建议不要修改, Privoxy 升级时会覆盖掉,自定义内容可以放入 user.action
和 user.filter
文件中。
action 文件定义 Privoxy 动作,比如
{+block{禁止访问垃圾百度}}
.baidu.com
{+block}
是一个动作,block 后面的 {}
注释,可省略; .baidu.com
是上述动作对象,分为两个部分,host 和 path, host 部分支持通配符,path 部分指的是 /
后部分网址,支持 POSIX 1003.2 正则表达式。更加具体的可以参考官网文档。上述配置生效之后 baidu.com
的任何请求都会返回 403 。
filter 文件定义过滤响应的规则,比如
FILTER: replaceText 替换文本
s|网易|Google|g
FILTER
大写表示定义过滤规则, replaceText
表示规则名称,后面接注释;第二行定义具体规则,如果使用过 vi 或者 sed 工具,那么一定很熟悉这个 s
替换命令。
定义了 user.filter
过滤规则之后,需要在 user.action
文件中应用规则
{+filter{replaceText}}
.163.com
这样访问 163.com 网站中任何带有“网易”的字都会被替换为 Google,当然如果网页启用了 HTTPS,那么 Privoxy 也无能为力。Privoxy 唯一能够对 HTTPS 网站做的就是 block 了。这也就意味着屏蔽 HTTPS 网站页面内广告的能力下降了。
当前 Privoxy 配置的 action 和 filter 文件可以在代理情况下访问 http://config.privoxy.org/show-status 这个网址查看到。
前面也提到过 Privoxy 的广告过滤,不过需要注意的是使用去广告功能可能丢失一定的匿名性 1。
下载 user.action 和 user.filter 两个文件分别替换 /etc/privoxy/
目录下的默认文件,重启 Privoxy 。
系统运行一段时间之后难免容器会出现问题,出现问题并不可怕,可怕的是不知道问题出现在哪里,这个时候查看当前容器运行的日志就能够排查出一些问题。
在之前的文章 中,学会了如何创建,查看,移除等等管理容器的方法,其实查看日志也和这些方法类似。
比如要查看容器所有运行的日志可以使用,下面的 containerId 都可以被替换为容器的名字:
docker logs [containerId]
如果要持续观察容器的日志输出,可以使用 -f
或者 --follow
参数
docker logs -f [containerId]
但是这个命令在不同系统上,有的时候会打印出全部的日志,就和没加 -f
参数一样,所以有的时候要查看日志最末尾几行可以使用 --tail
docker logs --tail 100 [containerId]
如果想要查看某个时间之后的日志,可以使用 --since
docker logs --since 2018-05-01 [containerId]
同理如果要查看直到某个时间之前的日志也可以使用 --until
docker logs --until 2018-05-01 [containerId]
腾讯云对象存储 Cloud Object Storage ,简称 COS,是腾讯云为企业和个人开发者提供的存储海量数据的分布式存储服务。
在控制面板申请对象存储基本信息,创建存储桶,输入名字,选择地域,选择访问权限,然后访问秘钥,可以得到如下信息:
bucket: backup-1251234567 (格式为 bucketname-appid)
SecretId: SecretId
SecretKey: SecretKey
region: ap-beijing
appid: 123456789
安装必要的应用
wget https://github.com/tencentyun/cosfs/releases/download/v1.0.2/cosfs_1.0.2-ubuntu16.04_amd64.deb
sudo apt update && sudo apt install gdebi-core
sudo gdebi release-cosfs-package
配置文件,设置 bucket name, access key/id 等信息,将其存放在 /etc/passwd-cosfs
文件中,文件权限设置为 640
echo my-bucket:my-access-key-id:my-access-key-secret > /etc/passwd-cosfs
chmod 640 /etc/passwd-cosfs
然后将 cos bucket mount 到指定目录
cosfs appid:bucket-name mount-point -ourl=my-cos-endpoint -odbglevel=info -oallow_other
这里的 cos-endpoint
不同地区不一样,比如北京是 http://cos.ap-beijing.myqcloud.com
,其他地区根据 region 不同设置不同值
项目可参考官方项目
COSFS 工具使用