無標題文檔

使用 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 --

使用 Docker 部署 VPS 备忘

本 Blog 的代码也有很多的年头,当时使用的技术过了几年在目前看来有些的陈旧。同时,在 Vultr 的服务器空间也即将到期,所以考虑干脆做个迁移顺便将目前 VPS 上的服务也一起整理了下。

这一整理,发现了不少的巨坑,这在这里记录下。先说说在迁移前 VPS 目前的主要问题

  1. 部分代码老旧,同时又有新的代码,运行环境就有好几套;
  2. 尝试使用 Node 的工具进行开发,然而 Nginx 以及 php-fpm 独占了 80 端口,配置文件越来越复杂;
  3. 权限分配不合理,有部分的权限使用 root 运行有安全上的风险;
  4. 备份机制不合理,没有做到企业级的多点备份(虽然个人 VPS 没必要,但图个心安)。

其他情况不多说,重新整理以后的 VPS 应用架构图如下,相对来说还是不是很复杂:

https://gracecode.b0.upaiyun.com/2017_10_12/1507794332@800.png

综合价格以及习惯的因素(可能是情怀更多?),大部分选择了阿里云的服务。同时针对以上的问题,这边的解决方案是:

  1. 使用不同的 Docker 集群将 php5 和 php7 的应用分别调用,相互不受影响。老旧的代码就让它们在 php5 环境中「颐养天年」;
  2. 最前端使用 Tengine 分发请求到每个对应的 Docker,方便管理;
  3. 使用 Docker 以后,权限这块就相对来说好控制很多,宿主机不受任何的影响;
  4. 配置文件使用 git 管理起来,并托管到 Bitbucket,方便部署;
  5. 备份使用阿里云的 OSS 存档服务,价格很便宜而且也是安心很多。

同时,在这里汇总下碰到的几个坑和心得,供参考

  1. 官方 Docker 的 php7-fpm 是没有带 gd 库的,所以要自己 build 一个版本(生产环境顺便把 debug 这些都去掉);
  2. 因为只是在最外面 Router 应用服务使用 https,所以 PHP 应用内部需要增加判断下,防止判断失败,详见这里。简单的说,就是加个判断
/**
 *  Handle SSL reverse proxy
 *  @see https://www.variantweb.net/blog/wordpress-behind-an-nginx-ssl-reverse-proxy/
 */
if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
    $_SERVER['HTTPS']='on';
}

if (isset($_SERVER['HTTP_X_FORWARDED_HOST'])) {
    $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
}
  1. Alpine Linux 的 apk 源有时候会不稳定,Docker build 的时候会有出错的几率,多重试就好。

--

补充下,有关 HTTPS 转发这块,khsing 提出了很好的建议,非常感谢:

刚才看了你的使用 Docker 那篇文,里面处理 WP 处理 https protocol
的方法还挺硬的,这事儿我之前在阿里云部署的适合也遇到了,不过我是在 nginx 上做了处理,这样就不用动 PHP 代码了。

map $http_x_forwarded_proto $forwarded_https { default off; https on; }
fastcgi_param HTTPS $forwarded_https;

-- eof --

2017,仍然在前行

如果您能看到这篇文章,那么首先要感谢您,您还在持续关注我的博客。同时,也说明我主机已经成功迁移到了 阿里云 的香港节点(同时顺便推荐下 Linode,只是目前京东的节点不稳定)。

这年头能够关注并且持续写博客的人越来越少,这个缘分也是越来越难得。自己写博客的频率和时间也是越来越低,以前基本上是每天都会写,渐渐的发现每个月而现在看记录来讲是要按年来记了。

时间飞梭,技术也在不断的变更,这个博客出去内容以外基本上代码都变更了相当多,而且服务器也是迁移了多次。这次迁移花费了我不少的心思:首先,将服务都使用 Docker 管理起来;其次,对应的配置以及代码上碰到了不少的坑,逐一的解决。

不管怎么样,这里算是个小小的自留地,记录自己心得和过往的经验,也算是为数不多较为矫情的事情。

2017,生活仍然在继续,而我们仍然在前行。

再见,丁香园

已经正式从丁香园离开两个月,时间过得很快。

说起来离职的过程并不算是十分得愉快,然而两个月过去了心态也平静了很多,应该可以从初心上去写点文字回忆在丁香园的这段时期。

初心

2010 年的时候,@Fenng 来找我问有个项目你或许可以试试,这家公司的名字叫做丁香园。那时候个人个人在阿里的时候正处在转型期(瓶颈期?),所以抱着聊聊看的心态和创始人 李天天 一起吃了个吃饭。

印象很深,这顿饭吃完了以后,我自己就有感觉是应该为自己的理想和初心去实践些什么。后面的一来二回持续沟通了以后,就做下了这个决定:离开阿里去当时这个没任何名气的小公司见证团队从小到大的过程。

我当时在博客里说明了我离开阿里的想法,私底下很多朋友和同事很不解我的决定。毕竟,在阿里(淘宝)四年的时间不算短,无论个人发展方面还是待遇方面,当时的丁香园是没有任何优势的。

然而,时至今日我还是坚持我这个决定,并不后悔。

技术

在淘宝做了四年的前端,来到丁香园的时候整个公司的技术团队不足十人。技术方面,由于丁香园在前端方面是没有任何的框架以及代码规范可言,业务的高速发展对于优化和重构这块是势在必行的。

现在回过头来想,开始我可能过于理想化以及出于技术方面的思维惯性(说白了就是没经验),还是沿用了淘宝的技术架构。

在前端框架的选型上,使用了淘宝前端团队自己编写的 KISSY,这个后来是个大坑。KISSY 无论在框架的接受程度上还是可维护性、扩展性方面,相比当时业内的通用知名框架没有任何的优势可言。

甚至在一段时间内,我还在和团队的其他前端逐个在说明 KISSY 框架如何使用,乃至至一段时间内都影响到了业务的发展。

悬崖勒马,经过考虑团队还是重新选择到了 jQuery 框架。好在当时的业务高速发展,冗余代码迅速被迭代才没有被后来的绝大多数前端们发现竟然还有这段黑历史 :^)

然而由此带来的影响,再后来 node.js 等推出和成熟,在技术方面的由于前一次的“吃亏”,团队并没有坚持选择非常新的技术。

PHP 作为前端和后台的“粘合剂”,在丁香园的服务器中存在了很久一段时间(至今历史代码还在)。

疲于应付业务和需求技术方面在很长的一段时期是没有任何的规范可言,这可能是小团队的通病。

客户端的兴起,业务驱动个人逐渐技术方面转向了客户端这边。因此,比较遗憾的是前端这边并没有推动太多新的技术和流程,然后又要去挖移动端这个“坑”。

在丁香园的六年多,可以说有 70% 的时间都给了移动端这边:团队、技术、架构、甚至采购。这一切的内容对于我而言都是从零开始,而我也是乐在其中。

期间产品两个 iOS 和 Android 平台的版本分别获得了 App Store 以及豌豆荚的推荐和奖励,这在我个人职业生涯中也是莫大的鼓舞很肯定。

产品的迭代用户数量的攀升,带来了团队的壮大以及需求的不断增长,移动团队在公司十分被重视,各种资源都能够得到满足,有段时间内移动端团队相对于其他技术团队独立,这是好事也是坏事就不详细说。

说回到了技术方面,由于没有 QA 团队,所以在质量保证方面产品团员、开发、甚至运营都需要承担一部分的测试和质量保证工作。

“吃自己的狗粮”

这是团队的传统也是为什么不设 QA 这个职位的初衷。现在看来,这个话题还是颇有争议的,或许可以单独开一篇文章去讨论的。

业务的洗礼、QA 角色的“平均”,所以丁香园在技术方面的选型以及步进是偏向于保守的。

不过期间也做出过尝试,令人感到自豪和欣慰的是 iOS 端(2015年)开始逐渐的迁移到 Swift2,在一段时间内由于这块新技术调整造成产品迭代缓慢,产品和需求方都表示理解。

再后面,Android 也想考虑尝试下例如 Kotlin 等技术,比较遗憾我无法参与其中了,这是后话。

业务

写了一半,可能会招惹非议,略过吧。

团队

回到团队本身,如果说总分有十分,我完全毫无保留得给我们的团队打分九分,留一分让我剩下用岁月慢慢给到你们。

我爱这个团队,我爱团队的每个成员,我爱你们。

很多人崇尚的扁平化、足够的自由度,以及“漠视 KPI”,等看起来很美好的关键词,都或许在这个团队都得以实现。

同时,管理层面的 Sense 就是十分关注组员的成长,无论是技术还是个人方面。团队的信息透明也是我所推崇的,所有的信息都不会被二次咀嚼以后,再分别给到团队分别不同的人。

技术方面,能够给予足够的自由度以及尽可能的尝试,这里回过头来思考可能会抛出好几个问题。例如,如何保证团队的自我驱动能力?新技术的尝试如何保证项目本身的质量和进度?等等。

这些我问题坦白讲,我至今也在继续摸索和求证。

很是感谢在丁香园的几年,在管理方面的很多想法都能得到实践以及总结,团队从小到大的过程并不是所有管理者所能亲身经历。

Fenng 曾经和我说或许如果当时留在阿里待遇方面可能会更加可观,我可以很如实的回答,丁香园给予我的这些经历可能留在阿里这些年都无法换回,感谢这六年。

--

2016 年丁香园发生了些不愉快的事情,管理层的变更、团队的调动以及后续管理者的价值观、技术均无法认同,这是我离开团队的根本原因。

从丁香园离职了以后,我也有过抱着巨大的怨念去回忆以及诉说我的离职过程,现在回过头来想其实十分没必要以及幼稚。然而时至今日,还能听到很多或许有关于我以及团队的刺耳言论,个人有时还是无法控制自己的情感。

爱的深,可能就会更在乎它的每个细节。我现在已经可以自我调节和理智面对,对于那些信息的源头的始作俑者,我只想对你们说,你们是永远都不会 GET 和理解我对这个团队的情感以及初心。它对于我而言不仅仅是我一份工作的证明,怎么可能去伤害。

夏虫不可语冰

再见丁香园,曾经和现在都爱过的。

-- EOF --

我的照片

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

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

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

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

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

文章

项目

微信公众号