不温不火的AMP

AMP全称是Accelerated Mobile Pages,由谷歌提出,目的是通过改造一部分html的样式来给移动端网页加速。不过这个式样自从提出来之后除了少许有博文介绍,一直是不温不火,导致我有几次看到都毫无兴趣地略过了。

我们可以从这里了解快速生成一个AMP页面的方法。

如何把网页AMP化?

其实总而言之,从一般的网页到AMP支持的网页只需要几点。

必须的markup形式

  • html 文件以 <!doctype html>开头。

  • html的top的dom元素需要为<html ⚡> tag (或者是<html amp>)

  • 需要有<head><body> tag。(在传统html中它们不是必须的)

  • 包含一个 <link rel="canonical" href="$SOME_URL" /> tag 指向没有导入AMP的原版网页,如果我们仅有AMP网页,那么使用相同的url就行了。

  • 需要把 <meta charset="utf-8"> 作为<head> tag的第一个子元素。

  • 需要包含一个<meta name="viewport" content="width=device-width,minimum-scale=1">的tag,同时推荐其属性包含initial-scale=1

  • <head> tag里面需要包含一个 <script async src="https://cdn.ampproject.org/v0.js"></script> 的tag。

  • 需要在<head>tag中包含<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
    tag。

把AMP化之后和非AMP相应的页面捆绑起来

发布的时候需要,在非AMP的页面中使用<link rel="amphtml" href="https://www.example.com/url/to/amp/document.html">,而在AMP页面中使用<link rel="amphtml" href="https://www.example.com/url/to/amp/document.html"> 来把这个内容相同,但是只不过是对应AMP与否区别的两个页面捆绑起来。

其它注意点

一些tag需要使用AMP专用的形式。比如img tag需要改成 amp-img, 类似的还有video,audio,iframe等等。

另外一些event,class,id的名称也要注意。基本就是注意有些形式不能用。 比如其中带-amp-的id。比较重要的有一点,就是css只能用on page的 style tag来指定。

这些规则比较杂乱也不是本文重点,我们可以参考这里

Hexo中引入AMP。

其实写一个支持AMP的模版是最传统的做法。当然,我们还可以使用这个插件
其实我们发现,这个插件做的事情无非也就是另起炉灶搞一个模版,然后用这个模版渲染新的AMP的页面,然后把新老页面通过上面的link tag捆绑起来而已。

后话

AMP这个概念其实自从提出来一直不温不火,从官网的doc的中文翻译只翻译了标题就可以看出来。另外谷歌已经明确说过AMP对SEO并没有直接效果。至于是否有必要去趟水导入,那就请自己判断了。

用hexo打造非博客风的网站

hexo用来写博客很简单。hexo init my-blog
之后去这里挑个主题,改一下配置就是了。

有时候,我们想要去掉hexo自带生成的index.html而改用我们自己设计好的静态页面。
但同时,想把原来的index.html放到诸如posts/index.html的地方,这样,我们的网页就少了些许博客风,用hexo来放博客以外的东西也就更方便了。

把默认的index.html放到subdir中去

我们找不到root的index.html的模版是因为,默认的index.html是由hexo-generator-index这个插件完成的。
我们去看这个插件的内容,会发现其实非常简单,只是增加了一个生成index.html页面的扩展而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hexo.extend.generator.register('index', require('./lib/generator'));


var pagination = require('hexo-pagination');

module.exports = function(locals) {
var config = this.config;
var posts = locals.posts.sort(config.index_generator.order_by);
var paginationDir = config.pagination_dir || 'page';
var path = config.index_generator.path || '';

return pagination(path, posts, {
perPage: config.index_generator.per_page,
layout: ['index', 'archive'],
format: paginationDir + '/%d/',
data: {
__index: true
}
});
};

这个插件默认会用index模版来帮我们生成顶层的index.html文件。而我们要把这个生成的index.html挪个位置,放到次级目录里面去,不需要修改插件,只需要改一下_config.yml而已。比如下面我们把这个index.html移动到posts/index.html,可以如下配置。

1
2
3
4
index_generator:
path: '/posts'
per_page: 10
order_by: -date

添加已经存在的页面到指定位置

模仿上面index.html插件的写法,我们完全可以在指定位置添加我们指定的文件。
我们新建一个scripts/myplugin.js, 添加如下内容。

1
2
3
4
5
6
7
8
9
var fs = require('hexo-fs');
hexo.extend.generator.register('statics', function(locals){
return {
path: 'index.html',
data: function(){
return fs.createReadStream(/* real static file path */)
}
}
});

real static file path 的地方放上我们实际的需要使用的静态网页就行。
这样在执行hexo g的时候,我们的页面会被复制到index.html的位置。

复制更多的静态页面

通过上面的例子,其实应该可以看出来,我们完全可以按规则复制更多的静态文件到相应的web的目录中去。
讲上例修改为下面的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
var fs = require('hexo-fs');

hexo.extend.generator.register('statics', function(locals){
let files = fs.listDirSync('statics');
return files.map(function(file){
return {
path: file,
data: function(){
return fs.createReadStream('statics/'+ file)
}
}
})
});

然后在博客目录下面建立一个statics的文件夹,里面放上我们预先设计好的静态页面就行了。

做成插件包

为了方便使用,直接做成npm的包了。直接npm install hexo-generator-statics --save 就行。
其他看ReadMe吧。

使用cpuminer快速挖取山寨币monacoin

我是标题党,所谓的快速是指可以快速进入开挖状态。
近年随着市场的饱和,主流的电子货币挖矿市场已经从PC显卡挖矿转为使用专用的硬件挖矿。
然而,在一些山寨币市场,依然存在着用pc挖矿的人们。
下面我们考虑用空闲的服务器开挖山寨币。

挖什么?

这次我们以monacoin为目标币种。
monacoin是基于litecoin的一个fork,其主要的修改是把hash算法从Scrypt修改成了Lyra2REv2。
至于钱包客户端以及矿池的用法基本和litecoin一模一样。

下载钱包

下载地址
解压打开钱包monacoin-pt发现其实基本就是沿用了litecoin的钱包。

加入矿池

这年头已经很少有大拿独自挖矿了,
加入一个矿池就像是合买彩票,独自挖矿就像是独自买彩票,收益和概率当然也是该自己均衡的。
我们这里选择vippool这个矿池。点开注册,填写信息。
把上一步钱包里面的收货地址填入到相应的表相中。

注册成功之后选择添加worker。

添加worker

worker相当于挖矿的机器,你的账号可以添加多个worker来挖矿。

随便给worker取个名字,已经一个password,然后选择添加就行。
假设我建立了一个名字叫 bocchi, password是1234的worker。我们之后会用到这些信息。

cpuminer

下面就是下载挖矿软件了。

github地址

这款cpuminer是一个fork,不过它比原来增加了多种算法的支持,我们挖取monacoin所需要的Lyra2REv2算法也包括在其中。
clone到本地之后基本运行./build.sh就行。不过注意在ubuntu/debian上需要安装的包的说明。

另外有一点,就是在老版本的centos上,gcc的版本可能很低(4.4.x)。这个时候需要修改build.sh

1
2
3
4
5
# Ubuntu 10.04 (gcc 4.4)
# extracflags="-O3 -march=native -Wall -D_REENTRANT -funroll-loops -fvariable-expansion-in-unroller -fmerge-all-constants -fbranch-target-load-optimize2 -fsched2-use-superblocks -falign-loops=16 -falign-functions=16 -falign-jumps=16 -falign-labels=16"

# Debian 7.7 / Ubuntu 14.04 (gcc 4.7+)
extracflags="$extracflags -Ofast -flto -fuse-linker-plugin -ftree-loop-if-convert-stores"

把上面的注释去掉,把下面的加上注释之后在build就行。当然需要安装的包也别忘记安装上。

编译成功之后就是运行啦。

1
./cpuminer -a lyra2rev2 -o stratum+tcp://vippool.net:8888 -u gyorou.bocchi -p 1234

然后就开挖啦。

下图是实际挖了一天的收入。

, 查了一下汇率,大概相当于1日元。

后话

挖矿其实也是一种投资的方式,只不过一旦将手头的钱换成挖矿的硬件,短时间之内是基本无法抽身了。
所以,想要以正常姿势投资的话,还是直接去买虚拟货币之后看涨才是。
另外,如果你要用自己家里的机器挖矿的话,电费也会是一个重要的开支。

看不见的 \u2028

今天准备把Rails中把一些markdown的文档导出到hexo的静态博客之中。

本来是一个很简单的任务,为此我写了个Rails的runner脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require 'erb'
require 'ruby-pinyin'
Post.all.each do |post|
file = File.open("posts/#{ PinYin.of_string(post.title).join('-') }.md", 'w')
contents =<<EOS
---
title: '<%= post.title %>'
date: <%= post.created_at %>
tags: [<% post.categories.each do |tag| %>
"<%= tag.name %>",
<% end %>
]
---
<%= post.content %>
EOS
erb = ERB.new(contents)
file.write(erb.result(binding))
file.close
end

导出来的文本看似也很正常,可是一旦执行hexo g的时候瞬间就会出现类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Unexpected token ILLEGAL
at Function (native)
at Obj.extend._compile (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/nunjucks/src/environment.js:450:24)
at Obj.extend.compile (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/nunjucks/src/environment.js:433:18)
at null.<anonymous> (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/nunjucks/src/environment.js:378:22)
at Object.exports.withPrettyErrors (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/nunjucks/src/lib.js:24:16)
at Obj.extend.render (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/nunjucks/src/environment.js:374:20)
at Obj.extend.renderString (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/nunjucks/src/environment.js:261:21)
at /Users/victory/Documents/victoryxs.github.io/node_modules/hexo/lib/extend/tag.js:56:9
at tryCatcher (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/util.js:24:31)
at Promise._resolveFromResolver (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/promise.js:462:31)
at new Promise (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/promise.js:69:37)
at Tag.render (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/lib/extend/tag.js:55:10)
at Object.tagFilter [as onRenderEnd] (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/lib/hexo/post.js:253:16)
at /Users/victory/Documents/victoryxs.github.io/node_modules/hexo/lib/hexo/render.js:55:19
at tryCatcher (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/util.js:24:31)
at Promise._settlePromiseFromHandler (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/promise.js:489:31)
at Promise._settlePromiseAt (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/promise.js:565:18)
at Promise._settlePromises (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/promise.js:681:14)
at Async._drainQueue (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/async.js:123:16)
at Async._drainQueues (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/async.js:133:10)
at Immediate.Async.drainQueues [as _onImmediate] (/Users/victory/Documents/victoryxs.github.io/node_modules/hexo/node_modules/bluebird/js/main/async.js:15:14)
at processImmediate [as _immediateCallback] (timers.js:367:17)

之类的错误。

我甚至写了个插件去找到底哪篇文章生成出错了。

后来发现一切都是\u2028 这个看不见的字符的锅。一般来说,应该是编辑文档的人复制粘贴的时候不小心引入的。

我们把所有\u2028 删除,再次hexo g,果然世界变得清爽了。

这个问题以前遇到过,不过当时没重视,导致多少年后又浪费了自己小半个下午的时间。

其它参考

使用awk来进行文本的行处理

awk是一门按行处理文本的编程语言。
也许我们已经习惯用shell以外的语言来处理文本,但是有些时候,我们要在一些老古董的服务器上运行一些批量任务的时候,我们还是得重拾起这些依然锋利的工具。

基本用法

awk 基本以pattern{action} 为单位。
比如 awk "NR==2{print $0}" input.txt 输出input.txt中的第二行。
比如 awk "END{print "hello"}" input.txt 在完全处理完所有行之后,最后输出hello

awk 可以写成script形式。比如

1
2
3
#! /bin/awk -f

BEGIN { print "Don't Panic!" }

保存成hello之后,chmod +x hello之后,就可以执行了。

awk 中可以定义变量。

比如下面的例子中使用了一个变量max来输出data中最长的行的长度。

1
2
awk '{ if (length($0) > max) max = length($0) }
END { print max }' data

其他一些方法基本可以参照文档或者这本书

复杂的文本处理

假设我们需要比较txt1txt2 这两个文件,把只包含在txt1中的那些行输出到new_txt1, 同时把只包含在txt2中的那些行输出到new_txt2

1
awk 'NR==FNR{arr[$0];next} { if ($0 in arr) print $0 > "new_txt1"; else print $0 > "new_txt2"}' txt1 txt2

这里FNR可以理解为在当前读取文本中的行数,而NR则是所有文本中的当前行的计数,所以NR==FNR这个条件只有在第一个文本中为true,也就是说NR==FNR{arr[$0];next}将第一个文本中的所有行读到了一个变量arr之中。next读完直接不执行后面的内容,开始处理下面一行。所以只有读取到第二个文本的时候,才会开始执行{ if ($0 in arr) print $0 > "new_txt1"; else print $0 > "new_txt2"}

由此可以看出,awk由一个或者多个pattern{...}构成,如果没有指定pattern,则对于每一行,{...}中的内容总会执行。

awk

开始Golang吧 -- 一系列memo(to be continued)

基本是边看视频边做笔记。

视频地址

注意以上视频需要有safaribooks的账号才能收看。个人用户3000日元/月,
比那些月付成人视频网站性价比高多了。当然,我用的是公司买下的企业子账号。

:=是什么

变量申明

1
var my_var

变量赋值

1
2
var my_var Int
my_var = 1

申明并赋值

1
2
3
4
var my_var = 1

// 或者
my_var := 1

由于已经赋初值,所以变量的类型可以省略。
另外注意 :=的形式只能用在函数中。否则会出现类似

1
expected declaration, found 'IDENT'

的编译错误提示。

变量的scope

在go中,外部scope中定义的变量可以在内部scope中access。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

var my_var = "myvar"

func main() {
myscope()
}

func myscope() {
fmt.Println(my_var)
}

#=> myvar

这点和Javascript类似。

1
2
3
4
5
6
7
8
9
> var my_var = 1
undefined
> function myfunc(){
... console.log(my_var)
... }
undefined
> myfunc()
1
undefined

而在Ruby中,method内部是无法获取外部的变量值的。

1
2
3
4
5
6
7
8
9
10
11
irb(main):001:0> my_var = "myvar"
=> "myvar"
irb(main):002:0> def myfunc
irb(main):003:1> print my_var
irb(main):004:1> end
=> :myfunc
irb(main):005:0> myfunc
NameError: undefined local variable or method `my_var' for main:Object
from (irb):3:in `myfunc'
from (irb):5
from /Users/gyorou/.anyenv/envs/rbenv/versions/2.3.3/bin/irb:11:in `<main>'

在Ruby中,scope的边界取决于def, class, module 这类关键词。但是我们使用block来打开scope的边界。

1
2
3
4
5
6
irb(main):009:0> define_method(:myfunc2) do
irb(main):010:1* print my_var
irb(main):011:1> end
=> :myfunc2
irb(main):012:0> myfunc2()
myvar=> nil

iota

golang 的 Const常量中已经定义的递增的隐含变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

const (
zero = iota
one = iota
two = iota
three = iota
)

func main() {
fmt.Printf("zero:%v\n", zero)
fmt.Printf("one:%v\n", one)
fmt.Printf("two:%v\n", two)
fmt.Printf("three:%v\n", two)
}

//=> result
zero:0
one:1
two:2
three:3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const (
January Month = 1 + iota
February //2
March //3
April //4
May //5
June //6
July //7
August //8
September //9
October //10
November //11
December //12
)
1
2
3
4
5
6
7
8
9
const (
a0 = iota // 0 初始化
a1 // 1
a2 // 2
a3 = 10
a4 // 10
a5 = iota // 5(a3 a4 未使用iota,但是iota保持递增)
a6 // 6
)

从中可以看到的规律是,下一个变量的默认值的计算公式总是和上一个变量相同,唯一变化的是下一个变量的iota会增加1。

array and slice

array 属于value, 而slice则是引用。
在go中array应用场景不多,尽量使用slice。

map

key 的顺序无法保证。如果我们要保证输出的顺序,则可以抽取所有的key生成array之后,排序,然后按照key来取相对的值。

type and interface

定义一个HttpRequest类型

1
2
3
4
5
type HttpRequest struct {
url string
header map[string]string
body string
}

于是我们可以定义这个type的method。

1
2
3
func (http *HttpRequest) GetBody() string {
return http.body
}

完整程序

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

import "fmt"

type HttpRequest struct {
url string
header map[string]string
body string
}

func (http *HttpRequest) GetBody() string {
return http.body
}

func main() {
http := HttpRequest{"www.bocchi.tokyo", map[string]string{}, "fuck your mother"}
fmt.Println(http.GetBody())
}

输出结果fuck your mother

对于任何type我们都可以定义method。

1
2
3
4
type Hex int
func (h Hex) String() string {
return fmt.Sprintf("0x%x", int(h))
}

甚至函数也可以定义自己的method。

1
2
3
4
type HandlerFunc func(w ResponseWriter, r *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

下面再看 interface

1
2
3
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}

Golang中不需要明确指明我们的某个type实现了某个interface,我们只需要实现同名的method就行了。

例如上面,我们的HandlerFunc类型就已经实现了handler的interface,所以我们可以有如下的用法啦。

1
2
3
4
5
6
f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hello, world")
})

// func Handle(pattern string, handler Handler)
http.Handle("/", f)

更多参考

关于JS Scope 的一个误解

最近一个很久没写Ruby的Ruby Engineer。偶尔也是拉起来就能写JS,无论是Node,还是 React Vue 还是Electron,多少都有些经验。可最让人郁闷的是,看到了别人列举出来的面试题目却是一窍不通。比如这里的题目。

言归正传,说来羞愧,一直以为JS所谓的scope只有function才有,而那些block是不存在scope的,所以用var 申明的变量可以在block之外也被引用到。

其实,JS中Block也是有scope的, 使用let和const定义的变量就会和block scope绑定在一起。

所以,说到底,scope存在不存在是一回事情,变量和scope的绑定规则又是另一回事罢了。

哎,快要称为没有大量实战经验的废物。

zsh 和bash 下面的函数传参

在bash下面,函数传参只有简单的替代。所以下面的函数

1
2
3
4
5
#!/bin/bash

function myfunc () {
echo $1
}

在如下的调用中

1
2
# myvar="var with space"
# myfunc $myvar

会被替换成

1
myfunc var with space

于是,输出结果就成了var 而不是我们所期待的var with space

而在zsh下面,我们发现我们能够获得我们期待的结果。

1
2
3
4
5
6
7
8
9
#!/bin/zsh

function myfunc () {
echo $1
}

# myvar="var with space"
# myfunc $myvar
#=> var with space

那么bash里面如何同样获得我们期待的结果呢? 很简单,把参数quote起来就是了。

1
2
# myfunc "$myvar"
#=> var with space

另外,通过$@可以获取所有传给函数的参数。所以上面的函数echo $@也是可以的。

总之,多注意一下是否需要quote就是了。

cache设置不当导致的Mercari用户情报泄漏

前两天,日本最大的二手个人交易应用Mercari的web版发生了大量的用户情报泄漏事件。
很多用户反映自己看到了别人的地址,点数,购买历史,信用卡尾号等等信息。

调查的结果表明,此次用户信息泄漏,是由于在切换CDN 服务商的时候,cache-control的 http header设置的不当,导致CDN误缓存了本来不该缓存的用户情报而引起的。

web服务的成长

一般来说,你的几百个用户的小博客,你帮人做外包的CURD小应用,一台廉价的vps就可以跑地起来。

当你的用户渐渐增长的时候,我们可能需要加上load balancer, 增加服务器的台数来抵抗增加的request数目。

当你的用户再次突破性地成长,你发现单纯累加服务器的台数的效果已经逐渐消失的时候,你就应该考虑使用CDN去帮助你抵抗大量的request。

事故经过

控制缓存(Cache)的策略有很多。比如有些CDN服务商就提供这种简单的配置,这样不需要担心服务器端的配置,工程师也可以把精力放到更重要的地方。
通过CDN的设置来控制Cache是Mercari切换CDN之前的做法。而切换了CDN之后,新的CDN支持通过HTTP 的cache-control header来设置CDN的Cache策略。
于是,Mercari并没有通过CDN的配置来禁止Cache,相反采用了设置HTTP header的方法。新的方法在经过本地修改hosts,简单测试一下后就草草上线了。

切换CDN之前,Mercari在服务器端nginx的关于cache的设置如下所示。

1
2
3
4
5
location / {
# disable cache
expires -1;
proxy_pass https://127.0.0.1:80;
}

用户实际获得的response header的形式如下。

1
2
Cache-Control: no-cache
Expires: Thu, 22 Jun 2017 08:58:21 GMT (上一秒的时间,保证浏览器不cache内容)

这样,仅仅通过在CDN端的设置,就可以控制需要Cache的内容。而服务器端只需要保证自己的设置没有用到Cache即可。

我们知道,设置 cache-control no-cache 可以用来申明本url的以后的所有request都直接发送到server。
cache-control no-store则在response的时候告诉每个传递者不要cache本response的内容。这样在nginx的配置中,通过设置或者不设置cache-control no-cache no-store,可以控制哪些部分需要cache,哪些可以不需要cache。

这一切,如果当然仅仅是想当然。

等到有用户发现看到其他用户信息的时候,事情闹大,再去详细查看CDN服务商的说明,发现只有设置了cache-control private,CDN才会不进行内容的Cache。
而就算有了expires -1的设定,只要存在cache-control的header,CDN会直接无视expires header的相关设定。

事后,Mercari切换回了原来的CDN,并对新的CDN的cache进行了清理。我们再次打开Mercari,发现使用的CDN是Akamai。这个应该是切换之前的CDN吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ nslookup 184.26.231.118
Server: 103.5.140.18
Address: 103.5.140.18#53

Non-authoritative answer:
118.231.26.184.in-addr.arpa name = a184-26-231-118.deploy.static.akamaitechnologies.com.

Authoritative answers can be found from:
26.184.in-addr.arpa nameserver = ns7.reverse.deploy.akamaitechnologies.com.
26.184.in-addr.arpa nameserver = ns2.reverse.deploy.akamaitechnologies.com.
26.184.in-addr.arpa nameserver = ns8.reverse.deploy.akamaitechnologies.com.
26.184.in-addr.arpa nameserver = ns4.reverse.deploy.akamaitechnologies.com.
26.184.in-addr.arpa nameserver = ns6.reverse.deploy.akamaitechnologies.com.
26.184.in-addr.arpa nameserver = ns1.reverse.deploy.akamaitechnologies.com.
26.184.in-addr.arpa nameserver = ns3.reverse.deploy.akamaitechnologies.com.
26.184.in-addr.arpa nameserver = ns5.reverse.deploy.akamaitechnologies.com.
ns6.reverse.deploy.akamaitechnologies.com internet address = 23.61.199.32
ns3.reverse.deploy.akamaitechnologies.com internet address = 23.74.25.32
ns2.reverse.deploy.akamaitechnologies.com internet address = 2.16.40.32
ns5.reverse.deploy.akamaitechnologies.com internet address = 95.100.173.32
ns7.reverse.deploy.akamaitechnologies.com internet address = 184.26.161.32
ns1.reverse.deploy.akamaitechnologies.com internet address = 96.7.50.32
ns4.reverse.deploy.akamaitechnologies.com internet address = 95.100.168.32
ns8.reverse.deploy.akamaitechnologies.com internet address = 2.22.230.32

总结来说

对于用户敏感的内容,加上cache-control private总是没有错误的。

切换前修改hosts,草草点access几下进行测试的方法,也是不够专业。在投入release之前,应该使用一小部分节点进行回归测试,确定没有问题再全部切换。

读懂文档很重要。尤其是文档很坑的事后。

更详细的说明,大家可以参考官方的问题说明报告

体脂器的智商税

周五看到有博文介绍蓝牙体脂器,带app,往秤上一站就可以表示出自己的体脂,肌肉量之类的数据了。
点开amazon一看才3000多,还没看下面评论就直接买了。

Prime会员次日达还是不错的,虽然在我家都可以看到amazon的仓库。(我感觉自取更快一点)

今天兴冲冲回家,打开宅配便box,取出来。
自带三节AAA电池。

正面。

下载了app。扑面而来的国产气息。

站着试一下。

NMB我生日不是9月26号,但是无论我怎么修改都是这样了。

分享到微信的图标让我想到了5年前错过的青春的奶。

最终忍不住手贱去搜了一下这个东西在国内是不是也卖。

oh,好吧。