CI_Knight

且行善举,莫问前程。

0%

如果做网站加速,CDN是有很多种选择的,如果只做国内业务可以选择任意一家CDN都没问题,国外一般选择CloudFront和Cloudflare。Cloudflare在我看来,使用非常方便,傻瓜都可以操作,基本都是一键设置,不过功能相对于CloudFront少很多,毕竟CloudFront是AWS的一个服务。CloudFront优势在于可以和AWS的其他服务相结合,实现特定的需求。所以我更倾向于使用CloudFront,如果是个人静态博客之类的直接使用Cloudflare足矣。

这两个服务,国外加速效果都非常好,国内访问高峰期,有时会慢到让人无法接受,如果公司使用还是要对国内访问进行加速的,国内加速我更推荐使用CloudFront,虽说Cloudflare有很多黑科技,比如argo之类加速功能,但是比起CloudFront逊色不少。

由于国内特色的网络环境,想要解析到国内的服务器,一定要做网站备案,这就导致Cloudflare和CloudFront在国内并没有加速节点。不过Cloudflare有Partner曲线救国的方式,设置起来就比较麻烦了,而且最终还是逃不过备案。

我最终选择的方案是GeoDNS区域解析,因为域名的NS在Cloudflare托管,所以我优先选择使用Cloudflare的Load Balance,可以根据区域进行自定义解析,从使用体验上讲,该服务收费而且中国IP识别准确率也很低,划分区域相对简单,是根据大陆板块划分,没有根据国家划分区域,所以只好放弃Cloudflare。在一番对比后,国内的DNSPod,AliyunDNS准确率都很高,最后我还是选择了AWS的route53服务。

服务器可以考虑使用Alibaba Cloud的OSS服务,香港的OSS对于国内访问速度还是非常快的,而且比较稳定。将中国的解析执行OSS就可以了。当然这只是前端页面加速,如果是其他业务,也可以使用HAProxy或者Nginx做转发,就可以实现更多特殊的需求了。

如果写同步的程序,我都是使用 requests 发送 http 请求,异步程序,我更倾向于使用 aiohttp,这两个都是非常优秀的工具包,但是写异步代码,如果掌握不精很容易坑了自己。接下来我会讲述一下我在使用中遇到的一个问题。

版本和软件环境

1
2
3
System: MacOS 10.15
Python: 3.7.6
aiohttp: 3.5.4

aiohttp client 抛了异常

下面是简化过的代码。

http.py
1
2
3
4
5
6
7
8
9
10
11
12
import aiohttp

class Http:
def __init__(self):
connector = aiohttp.TCPConnector()
self.session = aiohttp.ClientSession(connector=connector)

async def get(self, url):
async with self.session.get(url) as r:
return await r.text()

http = Http()
main.py
1
2
3
4
5
6
7
8
9
from base import b

async def main():
r = await b.get("https://blog.ibeats.top/robots.txt")
print(r)

if __name__ == "__main__":
import asyncio
asyncio.run(main())

这时候执行 main.py 就抛出了异常

RuntimeError: Timeout context manager should be used inside a task

打断点查看,确实是在 with timer 抛出的错误,这情况很有可能是没在事件循环内实例化 session。代码少还是很容易看出来,运行事件循环前导入了 http,并且实例化了 session。

所以从根本上上解决问题就是导入http时不要初始化 session,然后代码可以改成这样。

http.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import aiohttp

class Http:
def __init__(self):
self.session = None

async def get_session(self):
if self.session is None:
connector = aiohttp.TCPConnector()
self.session = aiohttp.ClientSession(connector=connector)

return self.session

async def get(self, url):
session = await self.get_session()
async with session.get(url) as r:
return await r.text()

http = Http()

这样就可以就可以放心的在任何地方初始化了。

为什么不能运行前实例化session。

运行事件循环前也可以实例化 session。但是不要使用 asyncio.run 方法,可以自己创建一个loop来运行事件循环。

1
2
3
4
5
6
async def main():
...

if __name__ == "__main__":
loop = async.get_event_loop()
loop.run_until_complete(main())

为什么会这样,我们要进入 asyncio 内部看一下了,CPython 有用 Python 实现的 asyncio 代码,就不用直接看C了。在看过 aiohttp 代码后,aiohttp 初始化 session 时,使用的是 asyncio.get_event_loop() ,asyncio.run() 是自己创建的事件循环。那么我将代码简化后写出来再分析一下。

runners.py
1
2
3
4
5
6
7
8
9
10
11
from events import *
def run()
if events._get_running_loop() is not None:
raise RuntimeError("asyncio.run() cannot be called from a running event loop")

loop = events.new_event_loop()
try:
events.set_event_loop(loop)
return loop.run_until_complete(main)
finally:
...
events.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class _RunningLoop(threading.local):
loop_pid = (None, None)

_event_loop_policy = None
_running_loop = _RunningLoop()

def get_running_loop():
loop = _get_running_loop()
if loop is None:
raise RuntimeError('no running event loop')
return loop

def _get_running_loop():
running_loop, pid = _running_loop.loop_pid
if running_loop is not None and pid == os.getpid():
return running_loop

def _set_running_loop(loop):
_running_loop.loop_pid = (loop, os.getpid())

def _init_event_loop_policy():
global _event_loop_policy
with _lock:
if _event_loop_policy is None: # pragma: no branch
from . import DefaultEventLoopPolicy
_event_loop_policy = DefaultEventLoopPolicy()

def get_event_loop_policy():
if _event_loop_policy is None:
_init_event_loop_policy()
return _event_loop_policy

def get_event_loop():
current_loop = _get_running_loop()
if current_loop is not None:
return current_loop
return get_event_loop_policy().get_event_loop()

aiohttp使用get_event_loop,就是说如果不调用 set_event_loop,当执行 asyncio.run 时,会重新创建一个事件循环,导致事件循环不是同一个,运行事件循环时,aiohttp 里抛出,所以启动事件循环时,也使用 get_event_loop 就能保证最后使用的是同一个事件循环,当然还是不建议这么做,稍微控制不好就会耽误很长的时间找问题所在,最后得不偿失了。

参考

2022年03月26日更新

又一年过去了,我自己的搬瓦工也到期了,一年中维护服务也是比较麻烦的,时不时端口被屏蔽,也有封IP的风险。现在已经换到JustMySocks了,搬瓦工自己的服务,比较稳定,线路也不错,多人开车还是挺划算的,当然用别的机场就要放弃一些安全,建议买$9.88/M的,五条线路无限设备。如果购买可以使用我的推荐链接https://justmysocks.net/members/aff.php?aff=20643


2021年03月12日更新

目前已经换到了搬瓦工,AkkoCloud毕竟是小服务商,虽然说线路不错,但是没事断个网也是很难受的,非重度用户不影响。如果使用搬瓦工也可以使用我的推荐链接https://bandwagonhost.com/aff.php?aff=59518,非常感谢。


之前聊过如何科学上网,如果不了解可以先看一下《如何科学上网》。科学上网要有一个好的服务器,所以我推荐AkkoCloud,这也是我在使用的服务器,如果想购买可以使用我的推荐链接https://www.akkocloud.com/aff.php?aff=256注册购买,非常感谢。

AkkoCloud介绍

这家服务商还比较新,知道的人也比较少,所以不会像搬瓦工那样,便宜的服务器经常处于售空的状态,即便补货你也是抢不到。

线路和带宽

翻墙肯定是要稳定,推荐美国圣何塞CN2 GIA,如果你不知道什么是CN2 GIA可以谷歌搜索一下,CN2线路还是很稳定的,高峰期基本不会丢包。更重要的是,三网优化。

带宽最低是50mbps,双向600G流量,油管2k基本无压力,最大是150mbps,1500G流量。当然越大的价格越贵。

奈非?HBO?

比较不错的是,AkkoCloud基本上是可以解锁流媒体的,可以看Netflix、HBO和Disney+,亲身体验是没问题的,但是官方不保证可以解锁流媒体服务,如果需要换IP,也可以加钱更换。

价格与付款

支持支付宝支付,最便宜的50元一个月。不时会推出一个活动,打个八折,还是非常优惠的。建议买75元一个月的,带宽能达到100mbps。

关于注册

手机号可以不填,邮箱最好填真实的,虽然说不会验证你的邮箱,但是是用来接收invoice的。

MC服务器

AkkoCloud整体偏向于二次元,所以还提供我的世界服务器托管,10块钱一个月,太良心了。

为什么翻墙?

和世界接轨。

后记

最近发现搬瓦工有个补货监控网站,网站链接https://status.bwgyhw.cn/,他们也有TG群的补货通知,感兴趣的加入。如果有买搬瓦工服务器的,可以点击我的邀请链接https://bandwagonhost.com/aff.php?aff=59518,在此谢过了。

美股月内四次熔断,比特币也从一万美元跌至最低三千八百美元,在比特币产量减半之前,首先实现了市值减半,可以说很刺激了。随着币价的暴跌,也出现了各种流言,我就听到了一个这样的消息,真实唯恐天下不乱。

“比特币系统出错,矿工已经四小时没有出块了,后台增发数量造成算力漏洞被攻击,可以双花了。黑客利用这个漏洞凭空造出了一千万个比特币出来”。

虽然说的有头有尾,但是稍微了解比特币的人直接可以确认这是谣言了。那么我们就针对这个谣言来聊聊细节吧。

比特币挖矿

挖矿在区块链中就是出块的意思,也就是矿机做哈希计算得到合法并且符合预期结果,进而获得更多节点的认可并入区块链。所有的块连接到一起就形成区块链,这就像是链表一样的结构,然后通过共识进而达到无法篡改的目的。那么如果说比特币系统出错,或者算力大幅度减少,后台也不可能增发数量(额,比特币是没有后台的),也不太可能一直不出块(也只有算力暴跌百分之九十才有可能很难出块),或者在一些区块链浏览器看一下谣言也不证自破了。

目标(Target)

Target是一个256位的数,在挖矿时,块哈希必须小于或等于网络当前的Target。Target越小就越难计算。Target每2016个块调整一次难度(大概两周的时间),每个比特币客户端将生成2016块的实际时间与两周的目标(2016*10分钟)进行了比较,并通过百分比差异修改了目标,单次调整Target永远不会向下降低(增加难度)超过4倍,向上升高(降低难度)超过0.75倍。目标中的变化也叫做Difficulty adjustment(难度调整),

在2017年8月从BTC分叉之前,都是2016个块重新计算一次难度。由于早期一个版本的BUG,调整了算法,变成了每2015个块调整一次难度。

区块链以Bits的形式存储Target,十六进制有固定的公式来计算,比如区块在存储的值为0x1b0404cb(以小阶数排序存储:CB 04 04 04 1B),十六进制标识为:

0x0404cb * 2**(8*(0x1b - 3)) = 0x00000000000404CB000000000000000000000000000000000000000000000000

比特币定义创世纪块中使用的目标为Difficulty Level 1(难度就是difficulty_1_target),Bits值为0x1d00ffff,十六进制也就是:

0x00ffff * 2**(8 * (0x1d - 3)) = 0x00000000FFFF0000000000000000000000000000000000000000000000000000

难度(Difficulty)

哈希计算出来的值,哈希值前面的0越多,计算出来的难度就越大。也就是一个计算难度的衡量标准。

Target用十六进制来表示,Difficulty就可以这样计算:

difficulty = difficulty_1_target / current_target

比如在0x1b0404cb的难度就是

1
2
3
0x00000000FFFF0000000000000000000000000000000000000000000000000000 /
0x00000000000404CB000000000000000000000000000000000000000000000000
= 16307.420938523983 (bdiff)

也可以

1
2
3
0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF /
0x00000000000404CB000000000000000000000000000000000000000000000000
= 16307.669773817162 (pdiff)

还有一种就是,btc.com通过算例推算出的难度。

在挖矿时,谁计算的快,也就是谁的算力大,就有更大机会得到挖矿奖励,目前大概是12.5个比特币奖励,区块包括矿工费所以一般大于12.5个。下次奖励减半时间大概是2020年5月14日。具体可以看https://btc.com/stats/halving

比特币大概每十分钟出一个块

为了保持每十分钟一个块,比特币每2016个块重新计算一次网络的难度。如果说上一个2016个块的周期时间大于两个星期,那么就要降低难度,反之亦然。这样我们就可以通过时间比率来调整难度了。

可以看我的实现github.com/ciknight/microchain/blob/master/microchain/chain.py#L76,我使用比率来计算,并且规定比率在4到1/4之间。

如果想要了解更多的比特币知识

可以看一下《Mastering Bitcoin》这本书,想了解更多技术相关可以看比特币的开发手册和Bitcoin wiki。当然比特币只是区块链技术的一种,还有其他的链可以学习,他们互相取长补短,不知道在未来的某一天,区块链会不会改变我们的生活。

参考

hexo使用的是markdown渲染博客的,不过在写文章的时候,我vim并没有设置auto wrap,一般我会自己去换行。但是 markdown 语法中换行就是分段,一行多了换行后分段导致行间距增大,感觉是多了一个空格,排版就会变得很难看。如果从 hexo 的 markdown 渲染引擎入手我感觉会异常麻烦,所以,我打算写个脚本把换行去掉合并成一行,这样就简单的解决了这个问题,修复原来的文字,后续的不再手动换行了。

也可以使用hexo-renderer-markdown-it引擎解决这个问题,不过我还是建议使用标准,而不是利用引擎来解决这个问题。

花了几十分钟写完,代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from glob import glob
from typing import List

files = glob("*/**/*.md", recursive=True)


def fmt(content: List[str]) -> str:
data: List[str] = []
lens = len(content)
n = 0
while True:
if "---" in content[n]:
tmp: List[str] = []
tmp.append("---")
while True:
n += 1
tmp.append(content[n])
if "---" in content[n]:
while True:
n += 1
if content[n] == "":
continue
break
break
continue
data.append("\n".join(tmp))

tmp = []
while True:
if "```" in content[n]:
_tmp: List[str] = []
_tmp.append(content[n])
while True:
n += 1
if lens == n:
n -= 1
break

_tmp.append(content[n])
if "```" in content[n]:
while True:
n += 1
if lens == n:
n -= 1
break

if content[n] == "":
continue
break
break
continue

data.append("\n".join(_tmp))

tmp.append(content[n])
i = n + 1
n += 1
if i == lens or content[i] == "":
data.append("".join(tmp))
break

if lens == n:
break

return "\n\n".join(data)


for f in files:
with open(f) as fp:
c = fp.read()

data = fmt(c.split("\n"))
with open(f, "w") as fp:
fp.write(data)

当然代码依然有一些问题,没有覆盖到所有的 markdown 语法,所以很多问题手动修改了。不过使用 Python 编写脚本确实能提升不少的效率,所以Python 是很值得学习的一门语言。

今天早上醒来,突然感到浑身发冷无力,感觉大事不好,赶紧拿被子把自己裹起来。片刻之后,虽然身体暖和了,但是一阵恶心涌了上来。真的大事不好,让老婆赶紧给我量体温,在这节骨眼上要是发烧,真的是比死还惨了,心理是真的难受,再加上最近疫情严重谣言众多,突然感觉自己可能是患病了,然后就不理性了起来。在乌合之众里讲过,群众都是不理性的,看过众多谣言之后,自己也不就变得不理性了,人云亦云,最终分不清真假。如果真的被感染,那我可爱的女儿和老婆该如何是好,我打好年华才刚开始,就要结束了么。

接下来就是恶心,反胃,腹泻,无力,发热,没有食欲,全都对上了,我这不完了。最后还是听了父亲的话,吃了点挂面后,吃了消炎药和退烧药,开始消热。一个小时后,不在那么难受了,我妈给我煮了面,吃过之后继续消热,终于体温降到了37度,这才舒服一些。

看过人民日报的安抚文章之后,其实想想,这些也都会流感的症状,如果贸然去发热门诊,也加大了感染的风险,不如先自诊隔离的好。虽然退烧了,也要观察两天才好。虽然可能虚惊一场,但是仔细想想,恐慌才是人群的致命弱点。这些天微信群各种谣言四起,不明白为什么会有这些谣言,但是这些谣言只会让人们更加慌乱,这时候,更应该万众一心对抗肺炎。我也并不推崇国外的个人主义,更喜欢人们万众一心对抗肺炎。

这段时间,肺炎盖过了很多的热点新闻,不过人们并没有忘记。红三,故宫,伤医,隐瞒疫情,我们并没有忘记,更希望能够秋后算账。在没有言论自由的社会,还能做些什么呢,只能希望会越来越好,也许现在就是最适合的。

还有武汉加油,今天真的感受到了他们的恐惧感,希望不要传播谣言,多多鼓励他们,为他们加油。