無標題文檔

使用 Nginx 反向代理阿里云 OSS

上次将 Blog 的主机重新整理下了以后,这次抽空将站点的静态资源也整理了下。

虽然使用又拍云有一段时间了,也非常的稳定,但毕竟要「居安思危」,又拍云同时又提供了「融合云」的第三方存储的同步方案,于是就选择了阿里云的 OSS 作为存储的备份方案。

Aliyun OSS

上图是我目前的方案,客户端这边通过脚本或者其他工具上传到又拍云上,然后又拍云自动将增量数据同步到阿里云的 OSS,最后我再使用 Nginx 反向代理去访问两块同步的资源。

为什么使用反向代理,是因为由于「众所周知」的原因,在国内绑定 HTTP(S) 服务的域名是需要备案的,我觉得麻烦同时也不想浪费个域名做这样的事情。

同时使用反向代理的方式,后面自己还可以控制缓存、负载均衡等,可操作性更大一些。长话短说,在配置阿里云 OSS 的反向代理的时候碰到了些坑,在这里记录下。

首先,在阿里云 OSS 后台务必设置好权限,在 Bucket 中设置读写权限为「公共读」,以及在 Refer 设置为自己实际的情况(很重要)。

下面是目前的针对阿里云 OSS 的 Nginx 反向代理的具体配置,供参考:

location /<base_url>/ {
    proxy_pass http://<bucket>.oss-cn-shanghai.aliyuncs.com/;

    proxy_redirect off;
    proxy_set_header Host '<bucket>.oss-cn-shanghai.aliyuncs.com';
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header User-Agent $http_user_agent;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_cache_valid 200 302 3600m;

    proxy_buffer_size 256k;
    proxy_buffers 4 256k;
    proxy_read_timeout 600s;
    proxy_send_timeout 300s;
    proxy_temp_file_write_size 256k;

    expires max;
    etag off;
}

proxy_set_headerHost 必须要设定自己对应的主机名,例如我的阿里云 OSS 对应的主机在华东2(也就是上海机房)阿里云 OSS 是根据 Host 确定 Bucket 的名称的。顺便提一句,如果 Nginx 主机也是和 OSS 同个机房,那么可以使用内部 VPC 内网节省流量。

OSS Endpoint

由于反向代理的是静态资源,所以我考虑尽可能的多缓存时间以加快速度和减少请求流量,所以设置了 proxy_cache_valid 200 302 3600m 以及 expires max 强制缓存。

然后 reload Nginx 以后就可以看到效果了。由于目前 又拍云 和 阿里云 OSS 的内容是同步的,所以后面使用 Nginx 做负载均衡也是比较方便的事情,这里就不重复贴配置了。

顺便在这里吐槽下,个人不是很推荐买阿里云的香港服务器!因为又是那个「众所周知」的原因,阿里云的香港节点 HTTPS 流量是会被部分 ISP 给污染的

这块个人也是困扰了很久,然而不打算处理了,毕竟现在写 Blog 的人不多看 Blog 的人更少,被墙了反而能够更加自在些。

- EOF -

随谈 Java 的空指针(翻译和整理)

整理自: http://www.yegor256.com/2014/05/13/why-null-is-bad.html

在日常编码中,有个经典的 Java 空指针(NULL)调用如下:

public Employee getByName(String name) {
  int id = database.find(name);
  if (id == 0) {
    return null;
  }
  return new Employee(id);
}

那么这段方法有什么问题?这个方法的最大问题就是有可能会返回 NULL 对象。空指针(NULL)问题在面向对象编程角度上说是个很严重的问题,所有面对对象编程的过程中都会碰到类似的问题。甚至 Tony Hoare 向全世界道歉,忏悔他曾经发明了「空指针」这个玩意

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement.

原来,在程序语言中加入空指针设计,其实并非是经过深思熟虑的结果,而仅仅是因为它很容易实现而已。这个设计是如此的影响深远,以至于后来的编程语言都不假思索的继承了这一设计,这个范围几乎包括了目前业界所有的流行的编程语言。

对许多程序员来说,早就已经习惯了空指针的存在,就像日常生活中的空气和水一样。那么,空指针究竟有什么问题?

额外的错处处理

当然,有经验的编码人员自然会时刻意识到空指针问题的存在,那么有可能在编码的过程中加入额外的判断,就会规避这个问题,例如下面的写法:

// this is a terrible design, don't reuse
Employee employee = dept.getByName("Jeffrey");
if (employee == null) {
  System.out.println("can't find an employee");
  System.exit(-1);
} else {
  employee.transferTo(dept2);
}

那么这样的话就会多了额外的流程去处理因为空指针判断而多加的判断。例如,上面的处理逻辑和流程中,如果按照面向对象的思维书写,不考虑空指针的情况,只是简单的:

dept.getByName("Jeffrey").transferTo(dept2);

就可以。加入空指针判断以后,程序往往会有额外以及复杂的逻辑判断。

语义不清晰

上面的问题自然可以规避,例如在 getByName() 这个方法中加入异常的判断,那么重新命名这个方法的名字为 getByNameOrNullIfNotFound() 。这样子从代码可读性的角度上说,从方法名字就可以判断这个方法会返回什么,
但是自然而然为了表意清晰就会让方法的名字变得越来越长(感觉有点在黑 Objective-C)。

为了表明这个歧义,除了正常的返回值以外,其他的空指(NULL Object)针情况都会返回一个异常。那么这里又会引申出另外一个问题,就是性能。从上面的代码考虑,如果返回了 NULL 以后再抛出一个异常,实际上这段代码是已经被执行了的,然后再加的判断。

例如,在示例中 getByName() 通常是个 Map 然后执行了 get 方法去搜索是否存在这个名字,如果不存在再抛异常,这样就会造成额外的执行时间:

Employee employee = employees.get("Jeffrey");
if (employee == null) {
  throw new EmployeeNotFoundException();
}
return employee;

当然,这个问题也是很好规避,例如只需要先搜索它的索引即可,然后再取值返回:

if (!employees.containsKey("Jeffrey")) { // first search
  throw new EmployeeNotFoundException();
}
return employees.get("Jeffrey"); // second search

那么,这部分操作判断只是针对空指针就多了一次搜索操作。我们考虑再优化下,直接使用迭代器处理:

Iterator found = Map.search("Jeffrey");
if (!found.hasNext()) {
  throw new EmployeeNotFoundException();
}
return found.next();

这样子似乎很好的解决了问题。

但是,我们编写这段代码的业务初衷似乎越来越远,然后方法的名字也变得越来越长,自然而然代码也变得越来越复杂。

如何最大程度得避免空指针?

本篇开头的这个问题一样,空指针的问题可以追溯到计算法发展史时期,同时空指针异常的情况也很多,甚至在程序运行阶段也无法避免空指针的情况。那么,在编码层面,我们需要注意哪些呢?

确认调用的的每个变量都已经被初始化

这点说起来很简单,但事实上随着业务的发展项目代码也会越来越庞大。这时候方法之间调用的关系也会越来越复杂,很难避免使用到的方法都已经明确被初始化。

所以这块单独放在这里,需要我们在编码的实话重点考虑变量存在的可能性,这其实大体上基于自己的实际编码经验。

尽量使用明确的值调用

如果已经明确某个变量(常量)的值,那么是可以安全调用它的方法的。例如对比下面的几行代码:

String a = null;
a.equal("b");   // 会产生空指针异常
"b".equal(a);   // 推荐的写法

很明显使用常量去做调用这代码会更健壮一些。

尽量避免在函数中返回 NULL

当如果在编写方法中考虑返回 NULL,这个时候则需要冷静下是否真的需要这样子做。因为,通常来说会有比返回 NULL 更好的处理方式。

自动装箱需谨慎

自动装箱确实为编写程序带来很多方便,但我们在编程时候也不能滥用自动装箱。

比如,下面这个程序依然存在空指针异常隐患:

Person jack = new Person("jack");
int weight = jack.getWeight();

这种异常在我们使用一些 ORM 框架中会碰到,如果数据库对应的对象并不存在该值,而我们又在类中使用了一个基本类型与之对应,依然就会抛出空指针异常。在这种情况下就尽量使用包装类来对应,并且在使用该值时候先判断是否为空。

遍历谨防集合为空

for (int num : list) {
    // for each num in list
}

及时验证外部数据

在代码运行的过程中,尤其在解析外部数据的时候可能会引发影响不到的问题。例如下面的 Json 数据

{"name": null, age: 28}

如果不处理完善,虽然这可能是外部原因造成,但直接使用 name 属性也会导致空指针的问题。

使用第三方库加强验证

很多第三方的 Common 库都会有验证空指针的方法,例如 Guava 中针对空指针的判断有个单独的包去处理。

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5

或者过滤 NULL 也会更加的方便

Joiner joiner = Joiner.on("; ").skipNulls();
return joiner.join("Harry", null, "Ron", "Hermione");

使用 @NotNull 或 @Nullable 注解

强烈建议多使用注解来增加代码的可读性,例如多增加 @NotNull 或 @Nullable 注解,也可以加强代码静态检查方面可能会造成空指针的可能性,具体可以参见这里

使用 Java8 的 Optional

很多「现代」语言都会有针对变量为空的可选链式判断,例如

Grovvy 语言有一个 ?. 的操作符,可以安全地处理潜在可能的空引用(据说 Java7 曾被建议引入这个但是并没有发布)。它是这么用的:

String version = computer?.getSoundcard()?.getUSB()?.getVersion();

虽然 Java 看起来非常的保守,但好在 Java8 中增加了 Option[T] 这个对象包来代表类型 T 的某一个值存在或者没有。那么上面的代码可以写成:

String name = computer.flatMap(Computer::getSoundcard)
                          .flatMap(Soundcard::getUSB)
                          .map(USB::getVersion)
                          .orElse("UNKNOWN");

看起来似乎有点麻烦,但相信我你会爱上这样的写法,具体可以参见这里

好了,针对空指针的总结和整理先到这里,如果你有更好的意见和建议,欢迎不吝提出。

参考资源

-- eof --

网易云音乐接口的简单分析

注意,此文档有时效性。

由于公司网络的关系,在线听音乐经常会被断断续续,于是考虑批量将音乐(高清)下载到本地。同时,由于熊孩子的缘故,在自己的音乐榜单上「小苹果」等歌曲一直在前排,显得很没有「格调」,于是就萌生了折腾云音乐接口的想法。

网易云音乐的接口相对而言还是比较简单,尤其是大部分前端与服务器端通信的逻辑都已经在 core.js 里面,虽然加了压缩但是没有混淆因此还是很好去处理和理解。

访问云音乐接口每次都需要带个名为 csrf_token 的 GET 参数,这个参数很好找就在 Cookie 里面,而且还是持久化的。

剩下的两个参数大部分都是 POST 过去,分别名为 params 和 encSecKey,里面的字符串经过 encodeURIComponent 处理过。

云音乐请求的加密方式

加密和解密的方法还是在 core.js 这个文件里面能够找到,AES CBC 加密,客户端自己生成 Pair 。两个加密解密的方法都暴露在全局作用域下,分别名为 window.asrsea 以及 window.ecnonasr

部分的接口,例如 feedback/weblog 是没有做任何重复请求处理的,换句话说可以刷。上面说到 csrf_token 其实是持久化的,也没有来源判断等,因此拿到 Cookie 以后就可以自己构造请求。

不过话说回来网易的平台设置做得的确财大气粗,乃至多点并发去做请求都能撑得下来(估计是我的小水管还不够人家的零头)。

顺便提一句,再深入研究下其实可以发现非会员也可以下载收费资源,这里不铺开讲了。

总结下一些看起来是「大道理」的心得:

  1. csrf_token 这些参数应该和时间和请求次数挂钩,更不应该加入到 Cookie 里;
  2. 客户端和服务器端的加密一直是安全方面讨论的主要课题,我的倾向是轻客户端重服务器端,不要把所有的逻辑都写在客户端中;
  3. 前端脚本代码的打包方面尽可能的不要污染全局空间,尤其是框架方面的代码;
  4. 服务器端应该对用户异常行为作出判断,例如业务方面用户频繁下载以及标记打星是否合理;
  5. PS,网易云音乐考虑下 WebSocket
  6. 查找和 Hook 网络接口这块可以从请求方法入手,通常现在端架方面请求都会放在一个方法中处理;
  7. 有点废话,抓取的时候使用 HTTP Keep-Alive 头能够明显的加快请求速度;
  8. 可以考虑多个 IP (代理)去抓取数据,以免实际地址被服务器 Block;
  9. 实际情况中需要考虑的外部因素有很多,例如尽可能的在预算范围内购买足量的存储空间…;
  10. Charles 很好用,这钱花得值…

最后,顺便刷个榜开个玩笑捣蛋下。

云音乐刷榜

-- EOF --

卡西欧 GW-M5610 手表屏幕改反显

当时购买这个手表就是为了好打理,六局电波、太阳能加上两百米的防水等,基本上不用担心它什么时候会不工作。

使用了一段时期以后,感觉到有些审美方面的疲劳,正好又看见了网上电子手表正显改反显的教程,发现不难于是就自己动手改个反显换换口味。

http://pic.yupoo.com/feelinglucky/EBjWZ3jY/medish.jpg

先来看下改之前的样子,其实正显有个好处就是比较清晰(这是在我改了反显以后对比得出的),如果不折腾用户其实真心还是保持原样的较好。

http://pic.yupoo.com/feelinglucky/EBjWTp9a/medish.jpg

GW-M5610 这款手表其实非常好拆解,拧掉四颗螺丝以后就可以打开盖子和岔开机芯了。抱歉过程因为拆的过程太过于顺利,所以就没拍下来。

http://pic.yupoo.com/feelinglucky/EBjWEC7c/medish.jpg

http://pic.yupoo.com/feelinglucky/EBjWF5GA/medish.jpg

然后就是撕开原有机芯显示屏上的偏光片,这里有个小技巧就是撕开原有的偏光片可以用角落用刀片斜切慢慢起开,然后就很好撕开了。至于原有偏光片残留的胶,可以用风油精蘸在棉花棒上擦拭去掉。

而至于哪里去得到新的偏光片呢?本人这块是原有液晶显示屏上剩下的,刚好今天能派上用场。正显改反显的原理很简单,其实就是将偏光片旋转一定的角度,就可以看到效果。

http://pic.yupoo.com/feelinglucky/EBjWFvtq/medish.jpg

依照原样大小贴回去即可,记得一般偏光片犹如手机贴膜一样,上面还覆盖有一层保护膜,使用前将其揭掉即可。

http://pic.yupoo.com/feelinglucky/EBjWGcPW/medish.jpg

按照原步骤装回去即可,这里友情提示下为了保证防水效果,最好再涂抹一层手表防水膏(别问我怎么会有这玩意)。

http://pic.yupoo.com/feelinglucky/EBjWOVOe/medish.jpg

涂抹完防水膏以后,需要将手表泡水里测试一段时间,以求防水效果生效从而能够长期使用。

http://pic.yupoo.com/feelinglucky/EBjWG0vM/medish.jpg

这是最终上手的效果,相比原先的正显而言多了分个性,而少了分显示效果。至于最终是正显好还是反显好呢?有句话说得没错:

「正显是给自己看的,反显是给别人看的。」

下面是些参考连接,想动手前可以参考下:

  1. http://www.iwatch365.com/thread-17184516-1-1.html
  2. http://post.smzdm.com/p/224298/

— EOF —

升级和改造 NAS 小记

自从多年前开始使用 NAS 存储和备份数据,就慢慢得养成了习惯越来越离不开它了。我使用的是群晖低端的 212J,由于仅仅是备份数据没有离线下载等需求,使用至今都没有出现过性能方面的顾虑。

这次考虑折腾 NAS 是从一开始升级硬盘说起。

选择硬盘

因为预算的问题,一直没有购买新的硬盘,使用的是原先移动硬盘拆下来的两块 320g 笔记本硬盘。

时光推进, 320g 的容量越来越明显感觉到捉襟见肘,加上原有的硬盘使用时间也比较长(从 09 年至今),因此更换硬盘的想法就提上了心头。

https://gracecode.b0.upaiyun.com/2014_11_03/1415001492.jpg

https://gracecode.b0.upaiyun.com/2014_11_03/1415001505.jpg

根据目前的行情,还是考虑购买了两个不同品牌的监控硬盘,分别是 希捷(Seagate)的 SV35 系列 和 西数(WD) 的 AV-GP 系列,容量都是 2T。

选择不同个品牌,是因为考虑同个品牌可能会同一个批次出现一样的问题。同时,不选择西数的红盘是因为 AV-GP 系列性价比稍微更高些而已(虽然只有 20 大洋)。

https://gracecode.b0.upaiyun.com/2014_11_03/1415001468.png

后记:新的硬盘安装进去以后,发现工作温度希捷的稍微比西数的要高些。不过两者硬盘性能似乎没有区别,组 Raid1 运行相互之间不会有谁拖谁的后腿之说。

硬件&改造

NAS 属于不折腾的那种硬件,稳定第一。但由于通常都是放在不起眼的地方,灰尘和散热都是我所担心的,毕竟宝贵的数据全部在这小东西的肚子里。

群晖 212J 的原有风扇是 Y.S.Tech
的,从购买至今使用起来一直都很勤勤恳恳,对它唯一诟病的地方就是声音和振动都有点大
。思前想去,毕竟风扇是 NAS 唯一的主动散热设备,所以还是咬咬牙从淘宝上购买了
SANYO DENKI 的 9S 风扇(俗称「9S」)

这个风扇相信玩家都会认识,无论从风噪还是可靠性来讲都比原先的风扇好太多(当然价格也是原有风扇的两倍)。

https://gracecode.b0.upaiyun.com/2014_11_03/1415001521.jpg

同时购买了快散热片加装到了原有主板的主控芯片上,其实这部没多大必要但装了也总比没有要好。

https://gracecode.b0.upaiyun.com/2014_11_03/1415001568.jpg

https://gracecode.b0.upaiyun.com/2014_11_03/1415001634.jpg

顺便给风扇加装了块滤尘网,同时在各个出风口上用双面胶粘贴了防尘垫,用于一般基本的防尘。最后,在安装硬盘的螺丝上加装了橡胶垫子,这里再次感谢下卖家的小心思,替我考虑得很周到。

经过这些步骤以后,重新合上盖子的时候感觉密封性更好了,希望能够使用得更久些。

迁移数据

恢复原有 Raid1 盘的内容并不轻松,我没有使用 Synology Hybrid RAID幸好没用,这玩意很坑),原先认为使用 USB 硬盘盒接上即可读取,发现行不通。

搜索了下,后来才知道群晖的 Raid1 是 Raid1 。于是将原先 NAS 的其中一块硬盘挂在 Linux 下。

先安装必要的软件

apt-get install mdadm lvm2

安装完成以后查看硬盘的分区

fdisk -l /dev/sdb

https://gracecode.b0.upaiyun.com/2014_10_31/1414750074.png<br />

发现有几个分区块,先测试下最大得分区块

mdadm --examine /dev/sdb5

https://gracecode.b0.upaiyun.com/2014_10_31/1414750097.png<br />

发现的确是软件 Raid1 分区,接下来映射成块设备

mdadm -A -R /dev/md9 /dev/sdb5

其实到达这一步,使用其他分区格式的可以直接使用 /dev/md9 设备 mount 到系统了,但是群晖使用了 LVM 分区,因此还需要多几个步骤。

vgscan && vgchange -ay vg1

激活逻辑卷,然后系统会提示你卷下有几个分区,然后用这些映射设备 mount 即可。

mount /dev/vg1/volume1 /media/hd

https://gracecode.b0.upaiyun.com/2014_10_31/1414750111.png<br />

然后在 NAS 上开个 NFS 分区也一并 mount 过来就可以拷贝文件了。

后来发现有网友直接使用 NAS 的 Raid1 的恢复迁移数据,早知道这个方法会方便很多。

下面是些参考资料:

Tips

差不多是最后了,数据方面的迁移和保存毕竟是很谨慎的事情,下面总结下小的 Tips 也算是经验总结了。

接下来考虑是否购入台 UPS 将 NAS 接入到不间断电源中,出于预算考虑估计还得缓缓 :^)

-- eof --

我的照片

嗨!我叫「明城」,八零后、技术男、伪苹果粉、微软无脑黑、宁波佬,现居杭州。

除了我的博客,同时也欢迎您访问我的 GitHubTwitterInstagram 主页。

这个 Blog 原先的名字叫 Gracecode.com 、现在叫 「無標題文檔」 。 其实无所谓叫什么名字,因为我曾经为这个名字伤透了脑筋。最后想到的这个名字都没啥特别的,说到 底是因为我实在给它不了个非常酷的名字。

如果您想联系我,可以发我邮件 echo bWluZ2NoZW5nQG91dGxvb2suY29tCg== | base64 -d

注:本站点所持观点仅代表个人意见,不代表所服务公司的立场。

文章

项目

微信公众号