Gson 是 Google 发布的一个用于序列化和反序列化 json 的工具库,可以非常轻松的实现 json 到 java Object 的转变,也同样非常简单的可以将一个 Java 实例序列化为 json。
Gson 包中主要的类有 Gson, GsonBuilder, JsonParser 等等。
JsonParser 是将 json 串解析成 JsonElement 的工具类。JsonParser 有三个 parse()
方法,分别接受不同类型的参数:
内部实现时使用 JsonReader 类进行解析。
Gson 最基本的使用方法,无非就是 toJson()
和 fromJson()
两个函数,对于简单类,可以使用如下方式:
String json = gson.toJson(target); // serializes target to Json
MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2
如果类中含有数组,会需要用到 toJson(Object, Type)
和 fromJson(String, Type)
这两个方法:
Type listType = new TypeToken<List<String>>() {}.getType();
List<String> target = new LinkedList<String>();
target.add("blah");
Gson gson = new Gson();
String json = gson.toJson(target, listType);
List<String> target2 = gson.fromJson(json, listType);
Gson 默认情况下会使用 POJO 一致的属性名去解析和生成 json 字串,但是如果想要解析和生成的时候重命名字段,可以使用 @SerializedName
来重命名。
比如 json 中的字段叫做 email_address
,而在 Java 类中,可以改为 emailAddress。这样,gson 在生成时会自动将 emailAddress 属性,改为 email_address
@SerializedName("email_address")
public String emailAddress;
如果 emailAddress
在 json 中还有其他的方式,也可以使用:
@SerializedName(value = "emailAddress", alternate = {"email", "email_address"})
public String emailAddress;
在将 json 转化为 Java Object 的时候,可以自定义 Deserializer ,格式化其中的某一些字段,比如下面内容,将 dateOfBirth
修改了格式。
public class ActorGsonDeserializer implements JsonDeserializer<ActorGson> {
private SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy hh:mm:ss a");
@Override
public ActorGson deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext)
throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
JsonElement jsonImdbId = jsonObject.get("imdbId");
JsonElement jsonDateOfBirth = jsonObject.get("dateOfBirth");
JsonArray jsonFilmography = jsonObject.getAsJsonArray("filmography");
ArrayList<String> filmList = new ArrayList<>();
if (jsonFilmography != null) {
for (int i = 0; i < jsonFilmography.size(); i++) {
filmList.add(jsonFilmography.get(i).getAsString());
}
}
ActorGson actorGson = null;
try {
actorGson = new ActorGson(jsonImdbId.getAsString(), sdf.parse(jsonDateOfBirth.getAsString()), filmList);
} catch (ParseException e) {
e.printStackTrace();
}
return actorGson;
}
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(ActorGson.class, new ActorGsonDeserializer())
.create();
在 java 序列化是,一旦变量被 transient
修饰,变量将不再是持久化的一部分,变量内容在序列化后无法获得访问。同样如果在使用 Gson 序列化 json 的时候,添加关键字 transient 同样,Gson 也会忽略 该字段:
private transient int id;
需要注意的是,如果一个 field 是 static
静态变量,gson 也会排除。Gson 在创建的时候可以使用 excludeFieldsWithModifiers
来指定排除的 field:
Gson gson = new GsonBuilder()
.excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
.create();
最早不知道 transient
关键字的时候,看文档中只写了 @Expose
注解,但其实效果是一样的。使用 @Expose
注解来保留关心的字段,其他不需要的字段可以不注解,同样能够达到效果。
private int id; // 忽略 id
@Expose private String name; // 保留 name
如果使用 @Expose
注解,那么则需要使用 GsonBuilder()
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
如果有更加复杂的排除规则,比如某一批 Field,或者指定的 Class 不需要 serialize ,可以使用 ExclusionStrategy
来自定规则。
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.jutils.gson.entity.Car;
import com.jutils.gson.entity.PersonStrategy;
/**
* ExclusionStrategy
*
* A strategy (or policy) definition that is used to decide whether or not a field or top-level class should be serialized or deserialized as part of the JSON output/input.
*/
public class ExcluStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes f) {
// ignore id field
if (f.getDeclaringClass() == PersonStrategy.class && f.getName().equals("id")) {
return true;
}
return false;
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
if (aClass == Car.class) {
return true;
}
return false;
}
}
比如说上面这个,就忽略 PersonStrategy 类中的 Field “id”,还有 Car 类。如果使用这种方式,那么需要在构造 gson 时:
Gson gson = new GsonBuilder().setExclusionStrategies(new ExcluStrategy()).create();
如果熟悉了这个 ExclusionStrategy
就可以书写自己的注解。
注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {
}
AnnotationExclusionStrategy 类
public class AnnotationExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getAnnotation(Exclude.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}
测试方法
@Test
public void annotationTest() {
PersonAnnotation annotation = new PersonAnnotation(1L, "name", 10);
Gson gson = new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
String s = gson.toJson(annotation);
System.out.println(s);
}
使用 GsonBuilder 来构造 Gson,然后传入 FieldNamingPolicy,这个方法接受很多参数,不仅可以做到将小写下划线转驼峰,还有其他很多功能。
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
FieldNamingPolicy 还有这些
IDENTITY
Using this naming policy with Gson will ensure that the field name is unchanged.
LOWER_CASE_WITH_DASHES
Using this naming policy with Gson will modify the Java Field name from its camel cased form to a lower case field name where each word is separated by a dash (-).
LOWER_CASE_WITH_UNDERSCORES
Using this naming policy with Gson will modify the Java Field name from its camel cased form to a lower case field name where each word is separated by an underscore (_).
UPPER_CAMEL_CASE
Using this naming policy with Gson will ensure that the first "letter" of the Java field name is capitalized when serialized to its JSON form.
UPPER_CAMEL_CASE_WITH_SPACES
Using this naming policy with Gson will ensure that the first "letter" of the Java field name is capitalized when serialized to its JSON form and the words will be separated by a space.
某一些情况下在反序列化 json 到 Object 时,在某些字段 JSON 中缺失时,想要给 Object 提供一个默认值,但是 Gson 在处理原始类型时,比如 int 字段,如果缺失会自动赋值为 0,某些情况下是不符合预期的。Gson 在设定默认值时需要,在 Object 构造函数中初始化该字段,并且实现 InstanceCreator
接口。
public class RawDataInstanceCreator implements InstanceCreator<RawData> {
@Override
public RawData createInstance(Type type) {
return new RawData();
}
}
构造时传入:
Gson gson = new GsonBuilder()
.registerTypeAdapter(RawData.class, new RawDataInstanceCreator())
.create();
举一个比较通俗的例子就是,当一条 JSON 数据返回时,字段值是一个 Double,但是返回的时候是放在字符串中返回的。这个时候就需要用到 TypeAdapter。
class DoubleTypeAdapter extends TypeAdapter<Number> {
@Override
public void write(JsonWriter jsonWriter, Number number) throws IOException {
jsonWriter.value(number);
}
@Override
public Number read(JsonReader jsonReader) throws IOException {
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
return 0D;
}
String result = jsonReader.nextString();
if ("".equals(result)) {
return 0D;
}
return Double.parseDouble(result);
}
}
然后创建 Gson 时:
Gson gson = new GsonBuilder()
.registerTypeAdapter(Double.class, new DoubleTypeAdapter()).create();
下面是小米路由器折腾记录,包括开启 SSH,然后安装 MT 工具箱,主要是为了其中的两个插件,一个是去广告,一个是 SS 代理,不过附带竟然发现了 frp 插件,开心啊。下面就是具体的记录。
下载 开发版,在后台点击上传安装开发版的 bin,然后等待重启,完成开发版安装。
小米帐号绑定小米路由器,设置路由器可正常上网,并使用手机版小米 WiFi 绑定路由器,然后在绑定小米账号的前提下,进入 https://d.miwifi.com/rom/ssh 这个网站,然后找到 SSH 登录的 root 密码,之后会用到。
工具包使用方法:小米路由器需升级到开发版 0.5.28 及以上,小米路由器 mini 需升级到开发版 0.3.84 及以上,小米路由器 3 即将支持。注意:稳定版不支持。 请将下载的工具包 bin 文件复制到 U 盘(FAT/FAT32 格式)的根目录下,保证文件名为 miwifi_ssh.bin; 断开小米路由器的电源,将 U 盘插入 USB 接口; 按住 reset 按钮之后重新接入电源,指示灯变为黄色闪烁状态即可松开 reset 键; 等待 3-5 秒后安装完成之后,小米路由器会自动重启,之后您就可以尽情折腾啦 :)
如果 Chrome 浏览器出现错误提示:”This site can’t be reached. d.miwifi.com refused to connect. ERR_CONNECTION_REFUSED”,需要手动将http
替换为https
。
在使用 ssh 登录路由器之前确认路由器的 IP 地址,比如我下面例子中会使用 192.168.31.1
来举例。
MT 工具箱是目前第三方插件里面最为方便易用的插件集合
KMS 服务器,VSFTP 服务器,VPN 服务器,远程管理,ARIA2,Koolproxy 广告过滤,阿呆喵广告过滤,Shadowsocks,webshell, frp 服务
Misstar Tools 2.0 工具箱安装,经过上面的几个步骤,开启 SSH 之后,使用 ssh root@192.168.31.1
来连接路由器,使用之前获取的 SSH root 密码登录,进去之后 passwd
修改 root 密码,以方便下一次使用,然后直接执行如下代码,就能安装 MT 工具箱。
wget http://www.misstar.com/tools/appstore/install.sh -O /tmp/install.sh && chmod a+x /tmp/install.sh && /tmp/install.sh
卸载
wget http://www.misstar.com/tools/uninstall.sh -O /tmp/uninstall.sh && chmod +x /tmp/uninstall.sh && /tmp/uninstall.sh
在开启 SSH,并且安装 Misstar Tools 工具箱的前提下,有两种方法可以安装 Shadowsocks 插件,第一种就是使用备份的文件,传入路由器之后运行安装,第二种直接在安装页面修改页面内容,推荐使用第二种方法。
chmod +x ./install_ss & ./install_ss add
chmod +x ./install_ss & ./install_ss del
from: http://bbs.xiaomi.cn/t-13765387
使用 Chrome 打开 MT 插件管理页面,使用开发者工具,定位页面中任意一个 安装 按钮,然后找到代码中的 id="ftp"
字样,修改为 id="ss"
,然后点安装,成功后会回到 MT 工具箱首页,配置使用即可。
按照上面官方的步骤操作完路由器就已经获取 root 权限了,再使用 ssh 工具连接路由即可,建议在进行下一步操作之前备份原版分区文件
root@XiaoQiang:~# cat /proc/mtd
dev: size erasesize name
mtd0: 07f80000 00020000 "ALL"
mtd1: 00080000 00020000 "Bootloader"
mtd2: 00040000 00020000 "Config"
mtd3: 00040000 00020000 "Bdata"
mtd4: 00040000 00020000 "Factory"
mtd5: 00040000 00020000 "crash"
mtd6: 00040000 00020000 "crash_syslog"
mtd7: 00040000 00020000 "reserved0"
mtd8: 00400000 00020000 "kernel0"
mtd9: 00400000 00020000 "kernel1"
mtd10: 02000000 00020000 "rootfs0"
mtd11: 02000000 00020000 "rootfs1"
mtd12: 03580000 00020000 "overlay"
mtd13: 012a6000 0001f000 "ubi_rootfs"
mtd14: 030ec000 0001f000 "data"
查看 U 盘挂载的位置命令:
df -h
U 盘一般是 /extdisks/
开头,后面的可能不一样,我的是: /extdisks/sda4/
备份小米路由器 3G 原版分区到文件,每行是一条命令,分别执行,最后一条可能会报错,可不用理会,最有用的是 mtd0-mtd4
dd if=/dev/mtd0 of=/extdisks/sda4/rom/ALL.bin
dd if=/dev/mtd1 of=/extdisks/sda4/rom/Bootloader.bin
dd if=/dev/mtd2 of=/extdisks/sda4/rom/Config.bin
dd if=/dev/mtd3 of=/extdisks/sda4/rom/Bdata.bin
dd if=/dev/mtd4 of=/extdisks/sda4/rom/Factory.bin
dd if=/dev/mtd5 of=/extdisks/sda4/rom/crash.bin
dd if=/dev/mtd6 of=/extdisks/sda4/rom/crash_syslog.bin
dd if=/dev/mtd7 of=/extdisks/sda4/rom/reserved0.bin
dd if=/dev/mtd8 of=/extdisks/sda4/rom/kernel0.bin
dd if=/dev/mtd9 of=/extdisks/sda4/rom/kernel1.bin
dd if=/dev/mtd10 of=/extdisks/sda4/rom/rootfs0.bin
dd if=/dev/mtd11 of=/extdisks/sda4/rom/rootfs1.bin
dd if=/dev/mtd12 of=/extdisks/sda4/rom/overlay.bin
dd if=/dev/mtd13 of=/extdisks/sda4/rom/ubi_rootfs.bin
dd if=/dev/mtd14 of=/extdisks/sda4/rom/data.bin
首先,下载 Breed 刷入不死 breed,
下载地址:https://breed.hackpascal.net/ (搜索 breed-mt7621-xiaomi-r3g.bin)
用命令下载,在电脑终端中运行
wget https://breed.hackpascal.net/breed-mt7621-xiaomi-r3g.bin
将文件传入路由器
scp breed-mt7621-xiaomi-r3g.bin root@192.168.31.1:/tmp/
进入路由器
ssh root@192.168.31.1
cd /tmp
mtd -r write breed-mt7621-xiaomi-r3g.bin Bootloader
机器会重新启动,指示灯变蓝,确保电脑设置为自动获取 IP 地址,使用网线连接。刷入成功后,断掉电源,用东西顶住复位键不松开,然后再接上电源等待 10 秒左右放开复位键,浏览器输入 http://192.168.1.1 即可进行 Breed Web 恢复界面:
如果要刷入其他固件,打开 Breed Web 恢复控制台,点击左侧“固件更新”,钩选“固件”,选择固件,再点“上传”
更多内容可查看: http://www.right.com.cn/forum/thread-161906-1-1.html
小米路由器基本上沿用了 Linux 的目录结构,但是也有一些区别,在用了一段时间之后发现某些目录被写满了导致一些第三方服务无法开启,也是很恼人了。这里及列一下这些目录的作用,以便于清理。
/ 根目录
bin 二进制可执行命令
boot bootloader 启动相关
data 用户数据文件
dev 设备文件,驱动等等
etc 配置文件
extdisks 外置硬盘挂载点
lib 共享库
mnt 临时挂载点
opt 可选程序安装点
proc 系统内存映射虚拟目录,可用来获取系统信息
root 系统管理员主目录
sbin 系统管理命令
sys
userdisk 路由硬盘(一般为内置)
usr 存放应用程序和文件
tmp 临时存放点
www 浏览器网页存放区
以前也整理过一篇 TP LINK MR12U 刷 Openwrt ,路由器刷机和手机刷机异曲同工。不过刷机需谨慎,稍有错误就有可能导致硬件损坏。前些时候入手了一个斐讯的 K2P,
大致分为几步,第一步开启 telnet,刷入 uboot,然后通过 uboot 刷入新固件。
因为新版本固件 V22.7.8.2版本及之后 需要使用 特殊工具 才能开启 telnet ,所以先要使用这个工具来开启 telnet。然后再使用 a大 的脚本安装 breed。
Opboot 及 Breed进入方法:
如果你当前是官改或其他第三方固件,请在opboot或breed输入:
地址: http://www.right.com.cn/forum/thread-221578-1-1.html
官改版本比较稳定,占用内存小,同时也带去广告,SS/SSR,内网穿透等等功能
地址: http://www.right.com.cn/forum/thread-216468-1-1.html
潘多拉版本,同时也支持 SS/SSR,去广告,定时重启等等功能
地址: http://www.right.com.cn/forum/thread-255053-1-1.html
地址: http://www.right.com.cn/forum/thread-240730-1-1.html
K2P基于openwrt最新稳定版chaos_calmer的固件
荒野无灯 http://www.right.com.cn/forum/thread-218704-1-1.html 这个固件和原版固件的性能对比可以参考这一篇 http://www.acwifi.net/3173.html
下载地址:http://files.80x86.io/router/rom/K2P/
k2p 全套其他的固件,可以参考这个帖子 http://www.right.com.cn/forum/thread-254947-1-1.html
最后总结下,其实大部分的教程都能在恩山找到,在斐讯的论坛里面,找精华贴,能过滤很多杂乱的信息。并且通过 Google 关键词搜索也能够很快的找到答案。
Apache commons 包中 CollectionUtils 常用方法介绍。
org.apache.commons.collections.CollectionUtils;
addAll(Collection<C> collection, C[] elements)
将后面的元素添加到集合中,或者是添加一个非空元素 addIgnoreNull(Collection<T> collection, T object)
。
将两个有序的列表合二为一,使用 collate()
方法
List<Customer> sortedList = CollectionUtils.collate(list1, list2);
使用 collect()
方法
Collection<Address> addressCol = CollectionUtils.collect(list1,
new Transformer<Customer, Address>() {
public Address transform(Customer customer) {
return customer.getAddress();
}
});
containsAll(Collection<?> coll1, Collection<?> coll2)
判断是否 coll2 中所有元素都在 coll1 中
containsAny(Collection<?> coll1, Collection<?> coll2)
判断 coll2 中有没有至少一个元素在 coll1 中
CollectionUtils.isSubCollection(list3, list1)
可以使用该函数来判断两个集合是否是包含关系。
使用 CollectionUtils.filter
可以用来过滤 Collection 中不满足条件的元素。这个函数接收两个参数,第一个参数为列表,第二个参数为 Predicate 用来设置条件。
boolean isModified = CollectionUtils.filter(linkedList1,
new Predicate<Customer>() {
public boolean evaluate(Customer customer) {
return Arrays.asList("Daniel","Kyle").contains(customer.getName());
}
});
filter()
函数是当 Predicate
返回 false 时移除元素;filterInverse()
函数是当 Predicate
返回 true 时移除元素。
如果想要获取过滤的结果,可以使用select
和 selectRejected
这两个函数。
CollectionUtils.isEmpty(null): true
CollectionUtils.isEmpty(new ArrayList()): true
CollectionUtils.isEmpty({a,b}): false
CollectionUtils.isNotEmpty(null): false
CollectionUtils.isNotEmpty(new ArrayList()): false
CollectionUtils.isNotEmpty({a,b}): true
集合a: {1,2,3,3,4,5} 集合b: {3,4,4,5,6,7}
CollectionUtils.union(a, b)(并集): {1,2,3,3,4,4,5,6,7}
CollectionUtils.intersection(a, b)(交集): {3,4,5}
CollectionUtils.disjunction(a, b)(交集的补集): {1,2,3,4,6,7}
CollectionUtils.disjunction(b, a)(交集的补集): {1,2,3,4,6,7}
CollectionUtils.subtract(a, b)(A与B的差): {1,2,3}
CollectionUtils.subtract(b, a)(B与A的差): {4,6,7}
更多的内容可以 Google: https://www.google.com/search?q=CollectionUtils
示例代码可以到 https://github.com/einverne/jutils/tree/master/src/test 来查看。
又到一年的年末,2017 年对于我来说是变化最大的一年,这一年里完成了身份的转变,自此步入社会。这一年年初的时候定下目标年度 100 本书,看来又是无法完成了,现在看豆瓣的标注,也仅仅只有 30 本左右,距离目标甚是遥远。这里也不想再找借口,2017 年中,很大一部分时间共享给了王者荣耀和荒野行动,这两个手游占据了原本打散的碎片化时间。今年也一度差点荒废了 Kindle,Kinde10000 的消失,曾一度让 Kindle 在床头吃灰。
2017 年的读书记录如果再按照以前的分类划分的话,也没太多的变化,唯一增加的分类可能是增加了金融相关的一些书籍。那下面就总结一下好了。
排在首位的永远是小说,在去年读过了大部分的东野圭吾的小说之后,今年似乎找不到主题,除了零星捡漏又看了几部东野圭吾的小说之外,其他作者的很少涉猎。不过今年读过的东野圭吾的小说中也还是有让我记忆比较深刻的几本:
今年看的专业书虽然数量有所增长,但是还是没达到目标。
还有好多都零零落落没看完。
OpenFalcon 是一款企业级、高可用、可扩展的开源监控解决方案,提供实时报警、数据监控等功能,由小米公司开源。使用 Falcon 可以非常容易的监控整个服务器的状态,比如磁盘空间,端口存活,网络流量等等。
最近有些监控需求所以看了一下其中涉及到概念。
Open-Falcon,采用和 OpenTSDB 相似的数据格式:metric、endpoint 加多组 key value 的 tags,举两个例子:
{
metric: load.1min,
endpoint: open-falcon-host,
tags: srv=falcon,idc=aws-sgp,group=az1,
value: 1.5,
timestamp: `date +%s`,
counterType: GAUGE,
step: 60
}
{
metric: net.port.listen,
endpoint: open-falcon-host,
tags: port=3306,
value: 1,
timestamp: `date +%s`,
counterType: GAUGE,
step: 60
}
下面是一段 Python 上报数据的代码,其中涉及到的参数都是必须传的。
#!-*- coding:utf8 -*-
import requests
import time
import json
ts = int(time.time())
payload = [
{
"endpoint": "test-endpoint",
"metric": "test-metric",
"timestamp": ts,
"step": 60,
"value": 1,
"counterType": "GAUGE",
"tags": "idc=lg,loc=beijing",
},
{
"endpoint": "test-endpoint",
"metric": "test-metric2",
"timestamp": ts,
"step": 60,
"value": 2,
"counterType": "GAUGE",
"tags": "idc=lg,loc=beijing",
},
]
r = requests.post("http://127.0.0.1:1988/v1/push", data=json.dumps(payload))
print r.text
说明:
metric
: 最核心的字段,监控指标名称(监控项),代表这个采集项具体度量的是什么,比如是 cpu_idle
呢,还是 memory_free
, 还是 qps
endpoint
: 标明 metric 的主体(属主),比如 metric 是 cpu_idle,那么 endpoint 就表示这是哪台机器的 cpu_idle,一般使用机器的 hostnametimestamp
: 表示上报该数据时的 unix 时间戳,注意是整数,代表的是秒value
: 代表该 metric 在当前时间点的值,float64step
: 表示该数据采集项的上报周期,这对于后续的配置监控策略很重要,必须明确指定。counterType
: 是 Open Falcon 定义的数据类型,取值只能是COUNTER
或者GAUGE
二选一,前者表示该数据采集项为计时器类型(累加值),后者表示其为原值 (注意大小写,这个值是一个波动的值)
- GAUGE:即用户上传什么样的值,就原封不动的存储
- COUNTER:指标在存储和展现的时候,会被计算为 speed,即(当前值 - 上次值)/ 时间间隔
tags
: 监控数据的属性标签,一组逗号分割的键值对,对 metric 进一步描述和细化,可以是空字符串。比如 idc=lg,比如 service=xbox 等,多个 tag 之间用逗号分割说明:这 7 个字段都是必须指定
Metric 监控指标,时序数据。
包括不同类型:
endpoint 通常来指服务器节点。
在 Falcon 中有这样几个不同的接口
Meter 用来累加,可以输出累加和,变化率。
Gauge 用来记录瞬时值,支持 int 64, float 64 类型
Histogram 用来计算统计分布,输出最小值,最大值,平均值,75,95,99 百分比等等。
最近遇到的需求就是如果一段时间内,OpenFalcon 没有收集到数据,也就是 agent 没有采集到数据,程序挂了,或者没有执行,那么就报警。在最开始的时候查看了一下 OpenFalcon 报警函数
all(#3): 最新的 3 个点都满足阈值条件则报警
max(#3): 对于最新的 3 个点,其最大值满足阈值条件则报警
min(#3): 对于最新的 3 个点,其最小值满足阈值条件则报警
sum(#3): 对于最新的 3 个点,其和满足阈值条件则报警
avg(#3): 对于最新的 3 个点,其平均值满足阈值条件则报警
diff(#3): 拿最新 push 上来的点(被减数),与历史最新的 3 个点(3 个减数)相减,得到 3 个差,只要有一个差满足阈值条件则报警
pdiff(#3): 拿最新 push 上来的点,与历史最新的 3 个点相减,得到 3 个差,再将 3 个差值分别除以减数,得到 3 个商值,只要有一个商值满足阈值则报警
lookup(#2,3): 最新的 3 个点中有 2 个满足条件则报警
这一下子就懵了,报警触发的条件都是根据最近上报的几个点的阈值来触发的,而我的需求可能是一段时间内根本没有上报数据。
然后仔细查看文档之后,发现 OpenFalcon 有一个 Nodata 配置,Nodata 的配置正好解决了上面的需求,当机器一段时间内中断上报时,Nodata 配置会上报一个指定的值,然后报警函数就能够根据 Nodata 上报的值来报警。
Nodata 的配置在 OpenFalcon 的后台,在 Nodata 页面添加 Nodata ,填写
name
nodata 的名字,标示什么中断了endpoint
选择 Endpoint ,机器列表,一行一个metric
指定 metrictags
指定 tagstype
暂时只支持 GAUGE周期
秒,与原始监控指标一致上报中断时补发值
当自定义上报中断的时候 Nodata 就会补发,通过补发的值,比如正常的取值是 >0
的正数值,那么补发的值可以写上 -1
,然后通过最近连续的三个 -1
来触发报警。
在服务端接收到打点数据时,在报表中会有一些内置的指标,比如说 75-percentile, 95-percentile, 99-percentile, 999-percentile,CPS-1-min, CPS-5-min, CPS-15-min 等等指标。
对于 percentile 的指标,含义如下:
对于这些指标,还有 min, max, mean 这些指标,这些都比较简单不再赘述。
而另外一种, falcon 中叫做 Meter, 有两种
最近看了一篇文章 介绍了 Open Falcon 项目的起由,这里面提到了 Open Falcon 为了解决 zabbix 的一些问题而被提出
argparse 模块可以轻松的写出友好的命令行交互,让使用者轻松定义命令行参数及使用参数。
首先通过 argparse
来创建一个 ArgumentParser
对象:
parser = argparse.ArgumentParser(description='Process some integers.')
ArgumentParser
对象会保存命令行基本的信息。
构造好了 ArgumentParser
对象之后使用 add_argument()
来往其中添加参数。
>>> parser.add_argument('integers', metavar='N', type=int, nargs='+',
... help='an integer for the accumulator')
>>> parser.add_argument('--sum', dest='accumulate', action='store_const',
... const=sum, default=max,
... help='sum the integers (default: find the max)')
这些信息都被保存在对象中,直到 parse_args()
函数被调用。 integers
参数保存了一个或者一个以上列表的int值, accumulate
参数则会根据传入的参数选择 sum()
或者 max()
方法。
ArgumentParser
通过 parse_args()
来解析和使用参数
>>> parser.parse_args(['--sum', '7', '-1', '42'])
Namespace(accumulate=<built-in function sum>, integers=[7, -1, 42])
关于 ArgumentParser
对象更多的使用方法可以参考官方文档
argparse内置6种动作可以在解析到一个参数时进行触发:
argparse能将参数定义组合成“群组”。默认情况下是使用两个群组,一个是选项的群组,另一个是必须的与位置相关的参数群组。
在基础解析器中使用add_argument_group()来创建一个“身份认证”群组,然后逐个添加身份认证相关的选项到该群组。
import argparse
parser = argparser.ArgumentParser(add_help=False)
group = parser.add_argument_group('authentication')
group.add_argument('--user', action="store")
group.add_argument('--password', action="store")
定义互斥的选项是选项分组特性的一个特例,使用add_mutually_exclusive_group()
而不是add_argument_group()
。
import argparse
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument('-a', action='store_true')
group.add_argument('-b', action="store_true")
print parser.parse_args()
你可以配置单个参数的定义使其能够匹配所解析的命令行的多个参数。根据需要或期望的参数个数,设置nargs为这些标识值之一:
值 | 含义 |
---|---|
N |
参数的绝对个数(例如:3) |
? |
0或1个参数 |
* |
0或所有参数 |
+ |
所有,并且至少一个参数 |
argparse将所有参数值都看作是字符串,除非你告诉它将字符串转换成另一种数据类型。add_argument()
的type参数以一个转换函数作为值,被ArgumentParser
用来将参数值从一个字符串转换成另一种数据类型。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-i', type=int)
parser.add_argument('-f', type=float)
parser.add_argument('--file', type=file)
try:
print parser.parse_args()
except IOError, msg:
parser.error(str(msg))
要想将一个输入参数限制为一个预定义集中的某个值,则使用choices参数。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--mode', choices=('read-only', 'read-write'))
print parser.parse_args()
values的类型取决于nargs的值。如果该参数允许多个值,则values会是一个列表,即使其仅包含一个列表项。
前段时间 Google 1T 空间到期,这几年积累的邮件,照片,Drive 中的内容已经超过了了 20+GB,突然一下子 Google 的空间就满了,Gmail 提醒我空间不足可能无法接收邮件,Drive 无法上传文件,而我查看了一下空间占用,发现 Google Photos 占用超过了 12G,网上搜了一圈,发现 Google Photos 提供一键压缩,然后瞬间就可以继续无限存储了。
因此诞生了这样一篇文章,来看看现在的 Google Photos 怎么来管理照片。其实到现在为止我还是很怀念被关闭的 Picasa,虽然数据无缝的同步到了新的 Google Photos 中,但是这个产品层面的转变,让我很多习惯都改变了,并且以前很多好用的功能都无法找到代替品。
这里不仅能看到 Google Photos https://drive.google.com/#quota
在桌面网页版设置中 https://photos.google.com/settings,可以点击 RECOVER STORAGE
按钮来一键将 Google Photos 中以前保存的无压缩的照片压缩到 Google 支持的高清不占用空间的格式。Google Photos 在计算空间时 16 megapixel 及以下的照片不算空间,而一般 iPhone 7 拍的照片也只有 12 megapixel,所以完全不用担心损失画质,并且我曾经对比过Google Photos 压缩过的图片和原片,肉眼无法看出差别。这个操作不会影响存放在 Google Drive 中的原图,但是会压缩曾经上传到 Blogger,Google Map,Panoramio,Google+ 和 Google Hangouts 中大于 16 megapixels 的图片。
Google 一路改过来,Picasa -> Google+ Photos -> Google Photos,外链是原来越难看。
Picasa
Google Photos
https://lh3.googleusercontent.com/7wqn4oULz6lckUdJbPZzT094xyJiDewL03QS5BZk5kz9c5vP_wvk7fGxSOtxYvAG9N98blJGfZTAdweG_rYnC7sLIrjBAJfX0uIsub_P6waW9AzWmhM0M23BRRVOjNVn52CZIpKbmxMdr9jyjI27zdFCKJb7ZenbDtsFhkr-O7gaXcKuQkQnXJPOI3I8rgYcLGDa3vggdeyCVneimFmRCWfRX75LtJegBshiF7Jy4-fE6slJalAUqtwRAmBck2kaAofuZOHlpAmSsmaNPuaIWoMuTu_AgX1FU1Xx0f9HONI-F0awr0vPl0en1klvBMWXS8RbW5GHrbOrDXtQbpveJ-KnfXPmtTkjxF7vakMMUvWugpg2eohkWh354ndUoqooOP3LrVc-Q8TjrP_9xxkJLOGCdK-Exh2mhXKiTnz_89iPD5dfI27ZmZdY4cVblw2qAOtJ-KVuGXT6oQy0mjttwuiVJONCz-Hl2Tvuncl9ZTCq0XvOBx6wJWDZELxtust5CXIsrsO4L5cSYlxNG_CBXTuR=s945-no
API 导出
https://lh3.googleusercontent.com/bhmJ-XtoL1wMCw3Q2YA0Ff_L8m52OgFbxYfalNjLO22e=s0
我惊讶于同一个照片,在不同的产品中的地址都是不同的,当然基于产品的逻辑倒也不能说什么,但是 Google Photos 的链接也太难看了。
下面是很早之前发过的帖子
其实到现在Google Photos的功能都不及Picasa
而Photos自带的功能看似神奇却也是Picasa自带功能的延伸
其实最后总结一下就是Google 搞了一个阉割的相册管理工具,未成熟却先发布,说是为了产品线统一其实最后就是把阉割了的Picasa直接放出来给用户用了。
最早的时候,从来都是每一次一个相册一个相册管理我的照片,按照日期和活动名字来命名每一个相册,如果分享的话直接同步到 Picasa 然后分享整个相册,然而移动互联网发展的今天 Google Photos 会自动备份手机中的所有图片,默认会给每一天新建一个相册,虽然现在 Google Photos 中间不会显示每天的相册名字,但是如果在其他地方用API来引用 Google Photos 中的内容会异常头疼,所以现在我已经放弃了。
最原先的时候我会精心挑选几张highlight 的照片来作为一个精选照片,而现在 Google 把所有的不管好坏的照片都备份了,以前 Google+ Photos 还能够自动挑选一些比较精华的照片,但是说实话那个照片也是比较鸡肋的,所以现在的 Google Photos 我都是在备份之后,挑选一些照片 Snapseed 或者 VSCO 再修一修上传。
h5ai 自己的介绍说自己的是为HTTP WEB服务设计的一款现代化的文件索引,主要面向文件,提供了现代化的可视化界面。他是一款功能强大的文件目录列表程序,由德国开发者 Lars Jung 主导开发,提供多种文件列表呈现方式,支持多种主流WEB服务器,可以在线预览文本,图片,音频,视频。
依赖:PHP 5.5+ and works fine with Apache httpd, lighttpd, nginx and Cherokee
在没有 h5ai 之前,我都使用 nginx 的自带的显示文件列表配置
location / {
audoindex on;
}
不过不管是 Apache 还是 Nginx 提供的文件列表都是非常简易的,只会显示当前文件夹下的文件,如果都是压缩文件还好,遇到一些多媒体,图片,音频,视频等等就会有一些不便。
h5ai 的安装非常方便,下载,解压,配置 Nginx,配置 DNS,访问即可,如果需要高级功能,可以再配置,主要的配置修改
sudo apt install php7.0
index index.html index.htm /_h5ai/public/index.php;
配置 Nginx,在 /etc/nginx/sites-available/
下创建 drive.einverne.info
server {
listen 80;
listen [::]:80;
# SSL configuration
#
# listen 443 ssl default_server;
# listen [::]:443 ssl default_server;
# Add index.php to the list if you are using PHP
root /var/www/drive.einverne.info/html;
index index.html index.htm /_h5ai/public/index.php;
server_name drive.einverne.info;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
# autoindex on;
try_files $uri $uri/ =404;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php7.0-fpm.sock;
}
}
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
# listen 80;
# listen [::]:80;
#
# server_name example.com;
#
# root /var/www/example.com;
# index index.html;
#
# location / {
# try_files $uri $uri/ =404;
# }
#}
再将域名的 DNS 配置 A 记录解析到 VPS
在配置的 root 目录 /var/www/drive.einverne.info/html
下
wget https://release.larsjung.de/h5ai/h5ai-0.29.0.zip
unzip h5ai-0.29.0.zip
然后刷新浏览器即可。 哦对了要确保安装了 PHP 7.0 的哈。
h5ai 安装完成之后,可以到 domain/_h5ai/public/index.php
查看 h5ai 的相关信息,默认密码为空。页面中可以查看当前 h5ai 开启的选项。
Ubuntu 16.04 以上
apt-get -y install zip
apt-get -y install ffmpeg # 视频缩略图
apt-get -y install imagemagick # PDF 缩略图
编辑文件 /_h5ai/public/index.php
,在底部增加:
function auth ()
{
$valid_passwords = array ("账号" => "密码");
$valid_users = array_keys($valid_passwords);
$user = $_SERVER['PHP_AUTH_USER'];
$pass = $_SERVER['PHP_AUTH_PW'];
$validated = (in_array($user, $valid_users)) && ($pass == $valid_passwords[$user]);
if (!$validated) {
header('WWW-Authenticate: Basic realm="My Realm"');
header('HTTP/1.0 401 Unauthorized');
die ("Not authorized");
}
}
在头部 <?php
的下一行,增加
auth();
位于 _h5ai/private/conf
目录下。
打包下载: 搜索 “download” 127 行,enabled 由 false 改为 true。
文件信息及二维码: 搜索 “info” 185 行,enabled 由 false 改为 true。
默认简体中文: 搜索 “l10n” 202 行,enabled 由 false 改为 true。
文件及文件夹多选: 搜索 “select” 323 行,enabled 由 false 改为 true。
还有二维码等等功能,看一眼配置基本就能明白。
修改 domain/_h5ai/public/index.php
页面的默认密码:
首先生成自定义 sha512 密码:http://md5hashing.net/hashing/sha512 然后搜索 “passhash”,大概第 10 行,将其密码改成自己生成的。
如果使用 LNMP 一键安装的环境,可能需要修改 php 配置,vim /usr/local/php/etc/php.ini
搜索 scandir、exec、passthru,将其从被禁用的函数中删除。
本文总结一下我常使用的 Chrome 默认快捷键,Chrome 默认的快捷键已经非常全面,几乎可以不用鼠标操作浏览器的一切行为,我总以为使用不管是使用鼠标还是手势来进行网页浏览是非常耗时的一件事情,如果能够像 Vim 的哲学一样,不用离开键盘来浏览网页,不仅提高的是效率,也能够培养起,不再做内容的消费者,而是做内容的产生者这样的意识。
首先需要申明一下的是这些都是在 Linux 环境下,Mac 系统及 Windows 系统可能略微有差别,具体请查阅 Google 官方的 Help
下面是 Chrome 自身的一些常用默认快捷键
MacOS 快捷键 | Linux 快捷键 | 作用 |
---|---|---|
Cmd + [ |
Alt + Left | back current page |
Cmd + ] |
Alt + Right | Forward current page |
Cmd + y | Ctrl + h | 在新标签页打开历史记录 |
Cmd + Shift + j | Ctrl + j | 在新标签页打开下载记录 |
Ctrl + k | 将光标定位到 Omnibox ,也就是 Chrome 自定搜索 | |
Cmd + l | Ctrl + l | 将光标快速定位到地址栏,可以输入地址,或者直接进行 Google 搜索 |
Cmd + t | Ctrl + t | 新标签页 |
Cmd + d | Ctrl + d | 收藏当前页面 |
Cmd + Shift + t | Ctrl + Shift + t | 重新打开关闭的标签页 |
Cmd + w | Ctrl + w | 快速关闭当前标签页 |
Cmd + p | Ctrl + p | 打印 或者 保存为 PDF |
Cmd + n | Ctrl + n | 新开浏览器窗口 |
Cmd + Shift + n | Ctrl + Shift + n | 新开隐身窗口 |
Cmd + r | Ctrl + r 或者 F5 | 刷新 |
Cmd + Option + u | Ctrl + u | 查看源码 |
Ctrl + Shift + i | 审查元素 | |
Ctrl + Shift + j | 开发者工具 | |
Ctrl + Tab | 切换到下一个打开的标签页 | |
Ctrl + Shift + Tab | 切换到上一个打开的标签页 | |
Ctrl + 单击 | 在新标签页打开 | |
Cmd + 1/2/3/4 | Ctrl/Alt + 1/2/3/4 | 快速切换标签页 |
Cmd + f | Ctrl + f | 页面内搜索 |
以上就是最最常用的到的一些快捷键,容易忽略的我使用加粗显示了。
我们都知道 Google 的地址栏其实非常强大,Google 把它叫做 Omnibox,不仅可以预测下一个输入,也能够使用之前的记录保存多个搜索引擎。比如我经常使用的一种方式是输入 book.douban.com 然后因为豆瓣图书已经是 Chrome 认为的一个搜索引擎,所以再输入完毕之后 Tab,此时会进入搜索,然后输入图书的名字,回车,就会直接在豆瓣图书中搜索该图书。
对于默认使用 Google 作为地址栏的搜索引擎,那么直接输入回车就会在 Google 进行搜索。默认情况下只要访问过的网站提供搜索功能 Chrome 会自动记录这些网站到 search engines 中,可以在 chrome://settings/searchEngines
查看到。我们都知道 Chrome 中默认的 Google 搜索通常都带一系列的参数,默认情况下这些参数都不大必要,所以我会定义我自己的搜索 query,新增一个搜索引擎,其中的三个输入框分别填入
MyGoogle
google.com__
https://www.google.com/search?q=%s
然后在设置 MyGoogle 作为默认搜索引擎即可。使用同样的方法可以添加自己的专属搜索引擎,比如搜索 Gmail,Google Drive 等等。
然而 Omnibox 其实不仅能够提供 Tab Search 功能,它远比用户想象的要强大的多,甚至可以把它想象成 Google 的搜索栏。这里举一些例子
一个很常见的场景就是在网页浏览中看到一个新的词,想要搜索这个词,有很多方法可以实现,右击搜索也行,复制粘贴到新页面回车也好,不过最简单的方式就是直接选中然后拉到 Tab 位置。这时 Chrome 会使用默认的搜索引擎在新标签页中进行搜索。
在网页中我们可能经常在一个又一个链接中迷失方向,这个时候不用担心,Chrome 记录了每一个浏览过的页面,长按返回键,可以看到一长串的浏览记录,点击其中的一个能看到 Chrome 非常快速的加载了,甚至断网也能够加载,因为 Chrome 已经缓存了访问过的页面。
Chrome 自带一些内部页面,这些页面有些非常常见的用来配置,管理插件,查看历史等等作用的,但更多的是开启一些真正测试功能的,一些 Chrome 内部的数据也能够看到,比如 Omnibox 中根据预测出现的地址等等。我们可以通过 chrome://chrome-urls/
这个页面来查看所有 Chrome 自带的内部地址。
在 Chrome 中使用 Vim 下的快捷键,现在市场上也有不少的在 Chrome 下使用 Vim 快捷键的插件了,但 Vimium 已经用了很多年,没有遇到太大的问题。
下载地址:https://chrome.google.com/webstore/detail/vimium/dbepggeogbaibhgnhhndojpepiihcmeb
使用 shift + /
查看所有快捷键,绝大部分都是 Vim 的快捷键,不在赘述。
页面内跳转
快捷键 | 作用 |
---|---|
j | 向下 |
k | 向上滚动 |
d | 向下翻页 |
u | 向上翻页 |
f | 显示快捷键导航 |
F | 新标签页打开此链接 |
H | 回退 |
L | Forward |
b | 弹出 vomnibar 来打开收藏夹中的网址 |
B | 弹出 vomnibar,但在新标签页打开选中的网址 |
o | 打开搜索工具栏,在收藏夹,历史记录中搜索,如果没有匹配则直接使用默认的搜索引擎搜索,1.66 版本后 o 可搜索打开的标签页 |
O | 功能和 o 一致,区别为在新标签页中显示搜索结果 |
r | 刷新 |
yy | 复制当前页地址 |
gi | 将光标放到第一个输入框中,在 Google 搜索结果页面非常有用 |
gg | 顶部 |
G | 导航到页面底部 |
标签页
快捷键 | 作用 |
---|---|
J 或 gT | 左边的标签页 |
K 或 gt | 右边的标签页 |
t | 创建新标签页 |
x | 关闭当前的标签页 |
X | 恢复刚刚关闭的标签页 |
g0 | 跳转到第一个标签页 |
g$ | 跳转到最后一个标签页 |
快捷键 | 作用 |
---|---|
Ctrl + Shift + W |
关闭当前浏览器窗口 |
虽然写了这么多 Chrome 常用的快捷键,但如果使用鼠标,还有一个非常常用的,那就是鼠标中键点击关闭标签页。