Doprax
已经不再提供免费的容器,界面之前的菜单更新了使用Doprax
搭建免费的Shadowsocks
服务。
有现成的V2ray for Doprax
方案。但是我一直是用Shadowsocks
的,也不想换客户端。而且一看v2ray
,和Shadowsocks
相比,真的太复杂了,配置一大堆,看了文档也非常混乱,一比较就完全放弃。于是想研究ss能不能也整一个。
先说结论:使用v2ray-plugin插件将shadowsocks
流量伪装成http
协议,穿透Cloudflare
网关,使用隧道转发到服务端。客户端使用安装了对应插件的客户端连接到服务端。并且可以支持v2ray客户端例如V2rayU连接
本来想尝试使用simple-obfs,因为ios的免费客户端上基本上内置这个,后来在成功实现v2ray-plugin
方式之后,又继续研究了很久,发现由于Cloudflare
的影响,导致没办法实现这个方案。
使用后感受:不好用。
套了Cloudflare
隧道之后也非常慢,巨慢无比,几乎没法用。另外iOS里也没找到支持V2ray-plugin
的好用的Shadowsocks
客户端(付费的除外。我现在用的是Potatso
只支持simple-obfs
插件。也没找到v2ray
好用的客户端(付费版除外。 所以本文更多的是学习性质。
避免滥用Doprax
,人人有责。
Doprax目前每个注册用户可以运行一个容器,1CPU256MB的资源,没有限制带宽。
容器创建之后会提供一个dopraxrocks.com
的三级域名,可以使用https
访问。网关会将流量转发到容器内部的的http
80端口上。
当然了,也可以使用自己的域名设置指向dopraxrocks
的记录,使用自己的域名访问服务。
又有人问,系统赠送的域名国内访问速度堪忧,于是,就再用cloudflare
套一层转发。
很明显,不管cloudflare
还是doprax
,都是只能将http(s)
转发到容器。
因此Shadowsocks
原始的协议肯定是无法转发的,为了能够正常使用,需要在协议上套一层http(s)
。
这不就是现成的v2ray-plugin和simple-obfs插件。这两个插件都是将Shadowsocks
协议伪装成http
,利用这个,就可以实现穿透cloudflare
与doprax
的网关。
SIMPLE-OBFS
方案随着进一步的测试,以及simple-obfs
源码阅读,发现这个方案行不通,主要有几点问题。
obfs
最后的版本0.0.5
支持的TLS
不是真正的https
。解决方法:cloudflare
隧道方案要改用http
转发。obfs
实现的websocket
也不是真正的websocket
,cloudflare
对websocket
的处理会对数据进行重写。解决方法:在cloudflare
网络设置中,关闭对websocket
的支持。cloudflare
对http(s)
请求转发时会删改与增加cf-
平台方的头部,导致obfs-server
不能正确解析数据。解决方法:在obfs-server
前再增加一个Nginx
对请求头重写,包括修改http
版本号。cloudflare
对http(s)
响应返回时,也会增加额外cf-
平台方的头部,导致obfs-client
不能正确解析数据。在cloudflare
的Rules
菜单配置响应头时,提示不能删除cf-
等平台方头部。上述的前3点是已经解决,主要是第4点,对响应头修改问题,无法解决。官方在文档也明确说了平台方加的头部是无法去除的。而obfs
方面,源码里有对http
头部的hard code
,对头部的字段固定限制。
由于不可能在客户端部署其他服务重写响应头,所以在不修改obfs-client
的情况下,是没法正确解析数据。但是这个项目后续也不会维护了。
因此如果要使用这个插件,通过cloudflare
是做不了了,需要换一家隧道服务,不会对内容进行修改的那种,应该是有的,但是我也不想找了。
因此放弃SIMPLE-OBFS
方案,整体结构如下。
![](https://blog-cdn.xjp.in/2023/03/25/Screenshot from 2023-03-23 12-16-27.jpg)
Cloudflare
域名与Tunnel
创建使用Cloudflare
加速的话是必须要配置的。
登录到Cloudflare后先添加域名,按提示一步步操作就可以。主要就是到域名提供商处修改DNS服务器。
默认是强制启用https
重定向的,可以关,但是不重要,后续我们也都直接用https
就好了。
Cloudflare Tunnel
可以将内网端口穿透到公网域名,主要是免费,是比较方便的一个内网穿透工具。
菜单进入 Cloudflare Zero Trust页面,在菜单Access
里的tunnels
创建新的隧道。
创建完成之后,设置公网域名,将提供服务的域名绑定上,并配置需要转发的协议与端口。为了方便,我们直接绑定到ss
服务默认端口http://localhost:8388
。 另外也可以绑定路径,对应的客户端也需要配置对应的参数,默认就是根路径/
。
设置完成之后,访问上面域名就可以直接请求到http://localhost:8388
了。
![](https://blog-cdn.xjp.in/2023/03/25/Screenshot 2023-03-25 at 20.24.03.jpg)
然后在隧道概述页,下面随便一个安装命令里面,得到隧道对应的token
,这个token
后面需要用到
![](https://blog-cdn.xjp.in/2023/03/25/Screenshot from 2023-03-23 16-27-05.jpg)
Doprax
服务创建与启动域名、转发等一堆事情弄好了,终于开始正文了。
注册登录Doprax
,然后新建一个应用。
![](https://blog-cdn.xjp.in/2023/03/25/Screenshot from 2023-03-23 14-53-58.jpg)
进入应用之后,首先是导入源码。
可以 fork Shadowsocks-with-v2ray-plugin-for-Doprax项目后, 账号绑定Github
账号,从代码仓库里导入。
也可以手动创建dockerfile
文件,把Shadowsocks-with-v2ray-plugin-for-Doprax项目里的dockerifile
复制进去。
![](https://blog-cdn.xjp.in/2023/03/25/Screenshot 2023-03-25 at 20.20.33.jpg)
需要修改里面的环境变量参数,也可以在页面下面添加,加密方式,密码,隧道的token与端口四个环境变量,配置比较简洁的。一般情况下配置个密码和隧道token就能用了。端口和加密方式一般也不用修改。
添加完代码后,到Deploy
界面,可以看到当前的容器运行情况,直接运行就可以了。这里提供的域名会把请求转发到80端口上,所以我们用不上。
![](https://blog-cdn.xjp.in/2023/03/25/Screenshot from 2023-03-23 14-53-22.jpg)
启动完成之后,到Cloudflare
隧道页面看到状态是healthy
说明隧道启动成功。访问绑定的域名如果是400 Bad Request
说明v2ray-plugin
也启动成功了。
![](https://blog-cdn.xjp.in/2023/03/25/Screenshot 2023-03-25 at 22.02.33.jpg)
shadowsocks
客户端端口填443
, 插件填入v2ray-plugin
, 插件参数填入tls;host=域名地址;
如果用的是http:80
端口,参数需要对应修改。
Mac上如果是用的ShadowsocksX-NG
,自带v2ray-plugin
插件,对应写一个配置就可以。
![](https://blog-cdn.xjp.in/2023/03/25/Screenshot 2023-03-25 at 20.13.06.jpg)
Windows上如果是用的shadowsocks-windows
,需要手动下载v2ray-plugin
插件,下载后放到小飞机同一个文件夹,文件名改成v2ray-plugin.exe
。其他参数一样填写。
v2ray
客户端众所周知,v2ray-plugin
是可以使用v2ray
客户端连接的,所以这里提供了对应的连接配置。
参考了v2ray-plugin/issues/49这个回答,是有一点点复杂,给我我是写不来。
具体的参数含义可以查看v2ray
官网。
1 | { |
1 | docker run --rm -p1081:1081 -v `pwd`/v2ray_client_for_ss.json:/etc/v2fly/config.json v2fly/v2fly-core run -c /etc/v2fly/config.json |
1 | docker run --rm -p1081:1081 -v/path/to/v2ray-plugin:/usr/bin/v2ray-plugin shadowsocks/shadowsocks-libev ss-local -sss.example.com -p443 -b0.0.0.0 -l1081 '-kpassword' -maes-256-gcm --plugin v2ray-plugin --plugin-opts "tls;host=ss.example.com;" |
LLaMA
,总共220G有官方和第三方的运行示例,里面没有模型下载地址,官方途径是需要邮箱申请。
官方例子: https://github.com/facebookresearch/llama
内存优化版: https://github.com/tloen/llama-int8 据说只要3090可以运行,作者4090测试完成
计算优化版: https://github.com/markasoftware/llama-cpu CPU可以运行,但是需要32G内存
C/C++版本,普通机器CPU可以执行,https://github.com/ggerganov/llama.cpp 这个版本非常厉害,树莓派3B+就能运行7B模型,运行速度比较慢
下载的脚本地址来自这里, https://github.com/shawwn/llama-dl
已经被DMCA了,无法打开,文末已获取了下载地址,可以直接下载
下载速度还行,下了一个晚上, 我已经下载好了。下载链接导出来之后,百度云离线下载不了,迅雷离线可以秒下,非常厉害。
总共220G
原下载脚本
1 | PRESIGNED_URL="https://agi.gpt4.org/llama/LLaMA/*" |
提取出来之后的下载地址
1 | wget https://agi.gpt4.org/llama/LLaMA/tokenizer.model -O ./tokenizer.model |
最近服务开发中,为了节约成本,弄了两个服务器,希望搭建一套数据分析平台,验证项目流程。
查了很多资料对这些平台的搭建都非常简略,过程也是复杂,对新人很不友好,我也算是整理了一下相关的内容。简化服务搭建流程。
得益于Docker
的容器化,整个数据平台搭建起来非常方便
整个过程里面,包含以下工具的搭建:
由于我们是使用多台主机,所以需要搭建一个跨主机容器之间直接可以相互访问的网络环境。
这里我们使用ipvlan l2
的网络,可以实现跨主机网络访问,Docker
上也可以非常简单的就可以搭建。
如果是在同一台机器上,不需跨主机相互访问,创建普通网络就可以了
创建网络
假设组件使用192.168.2.1/24
网段
在需要部署组件的机器上创建网络,所有机器都需要创建这个网络。网络名称为 hostvpc
,提供给后面使用。
1 | # 创建ipvlan l2网络,跨服务器访问 |
这个网络设计有一个问题:组件与其所在主机不能通信
网络结构图
具体网段可以根据实际情况修改,组件与服务器处于同一子网即可
为了方便组件之间以及各客户端之间能方便访问,建议是在域名解析上增加对应的解析。
解析的数据 这样
1 | # hdfs |
使用docker compose
设置组件启动配置,组件有对应的环境变量文件。
将域名作为组件的服务名称,指定服务的ip。同一个服务器上,组件之间不需要额外域名解析也可以直接访问。单机启动的话就比较方便。
客户端连接时可以进入网络--net=hostvpc
,或者指定域名解析--dns x.x.x.x
,或者直接配置hosts
都是可以的
需要更改域名或者ip的,组件及对应环境变量里需要自己修改
所有组件各自的配置可以查看配置源码
关键组件,所有的数据存储都在这。后来也有用s3作为存储的。
配置源码hadoop-hdfs
启动命令
1 | docker compose up -d nn.hdfs.example.com |
命令测试
webhdfs地址: http://nn.hdfs.example.com:9870/
1 |
|
组件本身配置复杂又多,很想替换掉,但是因为Hive on MR
的依赖,不得不启动一个。后续对此有更详细的说明。
配置源码hadoop-yarn
启动命令
1 | docker compose up -d rm.yarn.example.com |
命令测试
任务调度历史与节点信息: http://rm.yarn.example.com:8088/cluster
启动MR计算还是比较麻烦的,因为客户端要指定一样的配置文件,yarn的配置文件也是一大堆
1 |
|
本来是想使用Spark作为Hive的计算引擎。但是Spark和Hive的版本兼容比较差,版本要一一对应。相关的公共镜像没有找到合适的版本,我又不想自己打镜像,迫于无奈,只能先上一个基于MR的版本。Yarn作为资源调度服务可以说是又臭又长,很想替换掉。
详情可以查看Hive on Spark
元数据我们存储在MySQL
本组件启动源码hive-on-mr
启动命令
jdbc.jar
, 可以从dev.mysql.com下载mysql
作为hive元数据存储, 需要修改环境变量配置yarn
后,可以使用hadoop-yarn
的调度执行MapReduce
,在docker-compose.yml
可以配置导入yarn
的环境变量yarn
环境则使用LocalJobRunner
进行本地运算,在docker-compose.yml
可以移除对应环境变量1 | # 首次启动需要初始化数据库 |
命令测试
组件信息: http://hive2.example.com:10002
关于启动命令中,中文乱码的问题,需要加上环境变量HADOOP_OPTS="-Dfile.encoding=UTF-8"
另外由于docker本身的问题,docker run
命令传入带空格的参数时有一点点问题。导致beeline
命令直接使用-e
参数无法执行, 详情。可以使用文件传入或者使用管道输入。
1 |
|
spark有很多种启动方式,例如有基于Yarn。但是为了组件纯粹性,这里使用了独立集群,不需要依赖其他服务。配置也非常简单。
提交任务的时候需要注意使用 cluster
模式
启动命令
1 | docker compose up -d master.spark.example.com |
命令测试
组件信息: http://master.spark.example.com:8080/
1 |
|
kafka在原先的版本依赖ZooKeeper
,被人诟病了很久,在新的版本里支持了Kafka
模式的启动方式。Kafka Without ZooKeeper
为了组件的纯粹,我们也使用了这个模式, 不依赖 ZooKeeper
。
启动命令
1 | # 依赖1001用户权限 |
命令测试
1 |
|
配置源码elasticsearch
有一个坑,需要对服务器设置max file descriptors
,可以执行ulimit -n 65535
详情可以查看官方文档file-descriptors。
启动命令
1 | ulimit -n 65535 |
命令测试
Kibana页面: http://kibana.example.com:5601
1 | # 查看集群信息 |
配置源码logstash
本例子将日志写入kafka
与hdfs
,具体配置可以参考文档
虽然这个工具使用docker compose
启动的,但是配置和前面的几个服务比起来似乎没那么优雅。也不做展示了
环境: 硬件设备是一个集成了显卡的开发板,可以运行ubuntu,算法部分是用python写的,前端设备在同一个局域网。摄像头为海康威视的普通网络摄像头。
关于视频画面播放的问题, 查了一下,前端目前不能直接播放rtsp
视频流。
调研了市面上有几种方案。
ffmpeg
视频转格式为frag_keyframe
的mp4视频流,也就是视频流切片, 使用websocket
转发流,前端使用Media Source Extensions
渲染
fragment mp4
即分片的mp4, 目前这种格式浏览器支持度差,这个方案也没测试成功。ffmpeg
视频转格式为flv
视频流, websocket
转发流,前端使用flv.js
渲染
Media Source Extensions
渲染,只不过格式是使用flv
ffmpeg
视频流切片成m3u8
存成文件, 前端video
标签直接播放文件地址或hls.js
渲染
服务端视频流截图,保持连接,接口返回content-type: multipart/x-mixed-replace
图片,前端使用img
标签加载图片地址
转rtmp
视频流,浏览器使用flash插件
使用webrtc
,点对点直接播放视频
上述几个方案
m3u8
是Apple
主推的技术方案,目前录播用多,直播用的少,应该以后会发展。
flv.js
是目前最好的直播解决方案,延迟也非常好。
这两种需要系统依赖ffmpeg
。但是结合项目实际,最终采用了另外一种方式:
服务端视频流截图,使用
websocket
转发图片帧二进制内容, 前端渲染到canvas
不需要安装系统依赖
一个问题是带宽占用高,另外是没有声音。但是实现起来简单,不需要系统级的依赖安装,对于当前项目只显示实时画面的场景已经够用了。
需要依赖docker
与ffmpeg
使用docker
启动一个rtsp服务,再使用ffmpeg
将视频./test.mp4
往服务写入,就可以得到一个不停循环播放视频的rtsp视频流
1 | docker run --rm -it --network=host -e RTSP_PROTOCOLS=tcp aler9/rtsp-simple-server |
测试播放
1 | ffplay -rtsp_transport tcp rtsp://localhost:8554/mystream |
这样我们就构造了一个可以读取视频的直播流了
1. 关于python中使用websocket
要做到数据实时发送,最好的方式就是用websocket
,结合的最好的语言是nodejs
。
可惜当前项目是用python
写的,python-socketio
由于性能的原因,已经不推荐使用flask
之类的多线程http框架
经过非常多的试错(可坑死我了),最终选择了aiohttp
+ AsyncServer
的模式
安装相关依赖
1 | pip3 install aiohttp python-socketio opencv-python |
2. 服务端原型代码实现
1 | import cv2, asyncio |
关于多进程中间遇到很多坑,关于事件循环的,关于异步函数的。`socketio`要发送数据到客户端必须要在同一个事件循环中。跨线程的可以用`run_coroutine_threadsafe`将数据发送到主事件循环中,但是跨进程就没办法了。由于python本身的限制,算法部分是多进程的。 于是最终的方案是再加一层进程的消息队列进行进程间的数据传递。为了避免消费消息队列阻塞事件循环,又起了一个线程消费消息队列。
3. 前端部分实现
index.html
:
1 |
|
前端部分较为简单。有关canvas
渲染的部分可以使用requestAnimationFrame
进行优化。
浏览器打开http://localhost:8080/
可以看到视频播放
multipart/x-mixed-replace
传输图片方案这个方案很有意思, 相当于是将每帧画面用同一个连接返回给前端。后一帧会将前一帧的内容替换掉。前端无需js参与处理。
前端也比较简单,直接包含在代码里了。 服务端代码:
1 | from flask import Flask, render_template, Response |
方案有个问题就是前端的GET请求会一直连接着,浏览器如果刷新或者中断,服务端会报错。另外不知道会不会有什么其他风险。
浏览器打开http://localhost:8080/
可以看到视频播放
ffmpeg
切片m3u8
, 前端video
标签直接播放这个方式应该是目前实际使用最多的方案,各大直播以及普通视频播放都使用了m3u8
。
直播延迟的长度和切片有关,可以做到几秒级的。
1. 创建m3u8
文件及切片
仅保留最近5个文件,单个切片时长4秒。实际上这两个参数都不准确,ffmpeg
会根据视频关键帧自动设置切片时长。
1 | ffmpeg -rtsp_transport tcp -i rtsp://localhost:8554/mystream -c:v copy -hls_time 4 -hls_list_size 5 -hls_flags delete_segments -f hls ./tmp/index.m3u8 |
2. 前端文件
实际上并不是所有浏览器都原生都支持m3u8
格式, 可以使用hls.js
做兼容。
1 | <script src="https://unpkg.com/hls.js@latest"></script> |
方式 | 系统依赖 | 延迟 | 视频声音 |
---|---|---|---|
websocket + flv.js | ffmpeg | 低 | 有 |
websocket + 关键帧 | - | 低 | 无 |
m3u8 | ffmpeg | 中等 | 有 |
x-mixed-replace + 关键帧 | - | 低 | 无 |
大概是这样
]]>云主机
上的无桌面环境
的Windows Server
上,不安装Docker Desktop
,运行Docker
。系统要求Windows2016
及以上即可。
前段时间,一朋友委托我研究一个技术方案。说他技术这边搞了一个分布式的Windows程序,依赖Docker环境,现在只能在电脑上安装,非常不方便维护,扩容起来也很麻烦。于是我也花了差不多花了一百多块钱,一个周末,买了一个云主机来研究研究。
利用
DOCKER_HOST
参数将windows的docker客户端通过tcp方式连接到wsl
内的dockerd
服务
说起来是很简单,第一次操作起来还是遇到了很多问题。
现在因为已经知道结论了,所有写起来理所当然,实际上花了大把时间才得到这个结局。众所周知,Windows云主机有几个遇到的问题:
Windows Server也开始走LTS方式来迭代,但是名字和版本真的混乱,可以说是一塌糊涂。我也是搞了很久才理清楚。只有LTS版是有名字的,比如Windows2016、Windows2019、Windows2022。其他非LTS是没名字的,直接用版本号指代,版本号其实就是发布日期,比如1803、1809、1909、2004,到后面也不用月份了直接20H2,21H2,因为一年只发布两次。三年一个大发布,就是LTS。所以Windows2019目前就是版本1809。下一次LTS是Windows2022,版本是21H2。
遇到一个最大的问题是,非LTS版是默认是没有桌面环境的。虽然今年就是2022年了,Windows2022也能下载了,但是云服务商还没有这么快的速度有这个系统版本。国内看了一圈最新提供的还是2004。于是意味着,我们要么使用1809这个老版本,要么使用稍微新一点点的无UI环境的2004。
然后我发现,DockerDesktop必须要有UI才能安装,
搞了很久,其实到最后才发现,版本其实不重要。因为在第二个遇到的问题面前,版本都是虚的。
我们平常使用Linux的,使用docker的时候完全不用考虑虚拟化问题,docker都是开箱即用。但是Windows就复杂了一些。整理了半天发现绕不开这个问题。一旦遇到这个问题,会发现前面的努力都白费了。云服务器几乎不能二次虚拟化的,因为云服务器本身就是虚拟化出来的。当然也不是没有,有一些虚拟化技术是可以实现的,加钱肯定可以解决就是了。
之前也查了很多资料,发现居然没有一个解决办法。
目前常规的做法,包括官方都是安装WSL2
,安装Docker Desktop
。后来看docker文档,想到一个参数,daemon-socket-option。当时还在想,难道要再买一台服务器安装Ubuntu,Windows这边再连接过来吗。后来脑子一转,没有wsl2
,咱们还有wsl1
啊,这个是不需要虚拟化就可以安装的啊! 于是开始操作。
说起来是很简单,第一次操作起来还是遇到了很多问题。而且为了后续能批量扩容,只保留了最小的操作。
远程连接到服务器上之后发现只有一个CMD窗口
但是实际上大部分操作都是在powershell
中操作的
1. 安装Ubuntu2004与docker客户端
1 | # 启用WSL |
发现安装好Ubuntu之后,bash对终端的样式都改变了
1 | # 安装docker客户端 |
安装完成之后执行命令可以发现没有启动服务,然后再重启一次。
重启之后到powershell里运行docker命令,发现是可以运行的。也有了服务,这是怎么回事呢。后来我又去查了一波。
windows下有一个自己的容器服务。如果是平时用docker desktop
的同学在右键的时候应该可以发现会有一个Windows Container
。相当于是基于window内核的一个容器服务。但是它只能运行基于windows的镜像。比如平时我们运行的各种甚至是hello-world
都运行不了,报错操作系统不兼容。
如果嫌多余可以关闭服务
1 | net stop docker |
2. 安装wsl里的docker服务端
安装好Ubuntu之后,在powershell里直接运行bash可以进入子系统,就和平时的一样操作就可以了。
最好先更新系统
apt dist-upgrade
一个坑,不能安装最新版,我安装了17.12
记得增加docker组
cgroup可能有问题,手动安装一下
安装了之后,运行了运行helloworld,完全没问题。
1 | # 启动服务 |
3. 设置指定端口启动
1 | # 启动服务 |
如果不想每次使用-H
参数,可以设置环境变量
1 | [System.Environment]::SetEnvironmentVariable("DOCKER_HOST", "tcp://127.0.0.1:8888") |
这样就默认使用wsl里的docker服务
总的来说还是有不少收获,特别是关于windows server的东西。平时用的最多的还是ubuntu或者centos,几乎不接触windows server,即使少有的也都是带桌面环境的。安装软件程序点点点就可以,和日常桌面使用一样。也是没有想到windows server居然还有包管理,就是上面的powershell相关的命令。
唯一的一个问题是,挂载目录用当前的方式还是有一点点问题,不能直接传入windows的路径格式。而普通方式的是可以直接挂载windows路径的,比如C:\User\
这样的,不会把冒号作为命令分隔。
于是从挂载这个问题上继续思考,其实也写一个bash脚本,名字叫docker,加到环境变量里。windows这边直接调用这个docker
实际是执行这个bash,也就是会把脚本传入wsl里执行。然后在这脚本里可以做路径相关的替换。貌似这也是一种方案。后续可以试试看。
本文是随笔,记录了一次基于以太坊的抽奖服务的方案讨论。从常规的智能合约的实现方案,到最终改用不上链的实现,主要是分享一下思路,包含了一点不看也没事的技术细节,以及一点区块链相关的内容。
因为抽奖主题的区块链开发入门文章已经泛滥了,所以文章尽可能避免变成教程。
之前一位做论坛的同学说他们那个论坛经常会做一些活动,抽奖什么的。如何让用户能感受到公平公正,让大家信任。之前一个的办法是在指定时间录视频,并且边上放个北京时间,表示这个是准时开奖没有作弊。这个和澳门最大线上赌场异曲同工,边上搞台电视放新闻的直播,令人信服。
但是总一直这样也不行,我们可是搞技术的,而且这种方式扩展性太弱。于是他们想做一个公正公开可信任,简单易用易理解的抽奖系统,选定的方案是区块链。我听了觉得很有意思,而且公正公开不可变,区块链太合适不过了,说就研究研究吧。不过为啥不找区块链团队帮忙,可以部门间合作合作。他说这个需求太简单了,就不麻烦其他部门的人了。
虽然现在公链有很多了,而且性能一个比一个强,但是实际上有大量应用的目前也就两三个,我个人比较喜欢古典一些的ETH。
其实需求很简单,方案也预定了一下。基本的逻辑是,用指定eth地址将帖子id作为参数来执行合约函数,得到随机数结果。指定eth地址是用来做权限控制,私钥不公开,可以防止被无关人员开奖。因为合约是公开的,执行函数的时间也是公开的,执行结果也是公开的,所以开完奖之后,大家可以直接查询验证结果。
只要在活动开始时提前公布用来开奖的地址与开奖时间与帖子id,预定时间开奖之后,大家一查就有了,简直完美。 其实当时想了有一个缺点,就是得花钱,执行合约是要付费的,大概算了下按当前eth的价格开一次奖得花一块人民币,会不会太浪费,他说完全ojbk。
然后顺手写了个合约原型:
1 | pragma solidity >= 0.5; |
真的是原型,就这么点代码,只有最基本的实现。我也是现学现卖的,具体的语法可以参考solidity这个语言,0.5.x
之后这个语言和之前有比较大的不兼容的语法,很坑,所以市面上的很多资料都已经不行了,还是得看原文档。搞开发的就是编程语言多。
因为eth私链的部署还有合约的发布相关的知识点比较多,单独介绍的文章也比较多,我这就不详细介绍讲了。大家感兴趣的可以搜下有很多相关介绍。
可能有人不清楚eth合约的执行,我略微解释一下。上面这个合约有两个公开方法,generate
和query
。其中generate
是开始抽奖,抽完后要把结果写到链上,需要矿工费。这个函数的执行,操作的过程就是向这个合约转账,向合约地址发送一小笔eth,并带上数据,数据里指定了要执行的函数与参数。当然如果是一般的转账也是可以带上数据的,可以写任何东西,一段话、一篇文章都可以。很久之前的不是有流行过,把XXX内容写到区块链上,其实就是这样。所以每次执行函数实际上就是一笔转账交易。而query
函数是查询链上数据,没有写操作,没有矿工费,不需要转账,因此eth钱包里没钱也可以直接执行。
显然,最最关键的函数就是那个随机数生成函数。在各种高级语言里,一般都会提供random相关的函数,虽然基本是伪随机数,但是大家还是用的很欢乐。可惜在这里,并不存在这样的函数。我这个同学他问我为什么没随机数函数,java啊其他的不都有的吗,那我这就没法解释了。
在区块链里,生成随机数是一个老大难的问题。如果大家了解接触过一些区块链菠菜游戏,肯定知道去年十一月左右多个eos上的菠菜游戏因为随机数被破解,导致损失惨重。虽然基于eth合约的菠菜游戏并不多,但是随机数生成一样是个问题。一篇文章区块链随机数的原罪与救赎大概讲了这个主题,从计算机的随机数到区块链的随机数,再到线上菠菜游戏。随机数生成向来不是简单的事,我以为这是常识。每年都会有关于生成真正的随机数的文章出来,方法涉及物理天文量子力学方方面面都有。
因为eth内部没有提供随机数生成器,所以目前的随机数生成方式都需要依赖外部。
和文章中说的一样,一个目前比较流行的办法是使用oraclize。这个库就很有意思了,可以在从eth合约内向外部发送http请求,并且请求结果是可信的。那这样问题就很简单了,可以直接找一个公共的大家都认可的随机数生成服务就可以了。但是我觉得这样不优雅。
第二种是使用randao,这是一个随机数生成组织。每一个生成周期都会由组织成员上传一个随机值,经过计算得到最终结果。如果上传的成员数量不足则当前周期生成失败,下一轮重新开始。其实这样也比较靠谱。这个组织只要交了会费任何人都可以加入。如果感兴趣大家可以读下他们的白皮书,这个也是最符合区块链精神的一个解决方案。方案的具体实现我也就没有继续详细研究。
第三种大家回答的比较多的办法是使用当前最近一个区块的信息作为随机数种子,进行计算得到随机数。这个方案对于开发成本会比较低,于是我直接采用了区块的hash作为种子。上面的随机生成函数就是下面这样:
1 | function _random(bytes32 bhash, uint max) private pure returns (uint) { |
看上去还行。为了防止同一个时间不同帖子的抽奖结果一样,可以用帖子id给随机数函数里面的hash加盐。
卧槽简直完美解决问题。有人可能会问了,凭什么区块的hash可以用来做随机数种子,这个是真的随机的吗。我再略微解释一下,区块的hash是由上一个区块的hash然后与当前区块的内容进行进一步计算得到的,这个是区块链的基本形态,之所以称为链就是因为这个。上一个区块是公开的,那么当前区块里面有什么内容呢?bitcoin的话里面比较单纯,只有区块信息和交易记录。eth的话,包含的东西就多一些了。里面的任意一项,几乎都是无法预测的,比如矿工信息,交易信息,合约信息,等等等。没法预测区块里会有包含哪些交易,包含哪些合约,也不知道是哪个矿工挖出的,eth这类的挖矿就是不停的产生随机数去匹配目标结果,所以这个方案我觉得简直完美,非常符合抽奖的这个场景。不过要注意的是开奖时间不能离当前时间过近,因为区块链的广播速度有限,会有一些延时。
关于区块hash还有一个小插曲。之前有人造谣eth的创始人v神死掉了。人红是非多。然后v神就在某网站上发了一条写某个区块hash的纸张和自己的合影,Another day, another blockchain use case,区块的高度是3930000,大家可以去参观参观。其实也不是很帅。
这就证明了至少在这个区块产生的时候,这人还活着。
我本来以为这样就结束了,其实重点才来了。
然后又后来的某一天,建了个eth私链,把合约部署上去,测试了一番没啥问题了。然后再一天,兴冲冲得准备给他展示。结果,我私链才启动还没弄好给他展示,就被一眼看出问题:既然是用区块信息做随机数种子,那能不能直接从什么开发平台api请求这个信息呢?
卧槽,突然觉得自己之前简直是傻逼。。。。。。 然后就有了最终的实现:使用公开的区块链浏览器的api接口直接获取区块的hash。如此一来,就把区块链上的开发转化成了经典服务端应用的开发,大大降低了开发成本,而且即使完全没有任何区块链开发经验的同学也可以轻松搞定。。。。。。卧槽,这个才是完美实现。
eth的出块时间大约在10秒上下,作为服务端完全可以把所有区块的信息爬取下来。比如预定一小时后开奖,可以等一小时后,将对应的区块hash查出,使用固定并且公开的计算方式得到抽奖结果。开奖后,其他人拿到当时的区块hash,使用相同的方式可以自行复现出结果。很显然,整套流程比之前的清晰多了,然后之后具体的实现就交给他们的产品研发了。
然后我花了几天时间,使用相同的原理,也写了一个抽奖原型出来,live demo。eth区块信息使用了etherscan的接口。这个原型应该很容易理解,流程也很直观,纯前端的实现。我觉得这个前端原型UI设计一下,改改其实也是能用。
我怕有人会问,你这区块链啥都没用到,请求区块的信息用的公开http api接口,数据结果也没上链,还算基于区块链的抽奖服务吗。这个服务核心数据就是从区块链来的,当然是基于区块链的,如果区块的信息是从web3js api来的,是不是就让人觉得像区块链应用一些了。
需求其实是确定的,但是很多区块链应用做着做着就把上链变成目标了,然后做着做着发现,原来的目标都已经忘到哪去都不知道了。我们公司其他部门之前不计成本的想做一个区块链应用,就是这样,做着做着都不知道做什么去了,我都给他们气死了。
]]>结果发现貌似itchat接口被封掉了 issues 不得不寻求其他的方法
最终找到了telegram 不得不说 虽然是聊天工具 但是对开发者还是相当友好的 各种接口都有提供 而且提供了一个非常强大的机器人系统 可以进行各种操作 而且流程非常简单
搜索BotFather 这个帐号 然后发送/start
开始对话 BotFather
是telegram的一个机器人帐号 用来管理所有用户创建的机器人 开始对话之后 会提示你进行各种操作 来创建和管理自己的机器人 设置机器人的用户名 创建完成之后会得到一个机器人token 之后所有的操作都是使用这个token来做
telegram和微信不同 除了组之外还有频道的概念 频道里只有管理员可以发送消息 其他人可以订阅这个频道 接受消息 非常符合报警这个使用场景 创建完了之后设置频道的名称 并且将你的机器人设置为频道的管理员
然后就可以测试了 用的是python-telegram-bot这个库
1 | from telegram import Bot |
是不是简单到爆炸。。。。 然后在使用flask
之类的封装一下成http接口简直好用到不行
当然除了 使用channel外 send_message
接口也是可以给普通的组或者单人聊天发送消息的 频道的名称是以@
开头 如果是普通的聊天或者群聊的话参数是会话的id 而且机器人可以接收数据和人进行交互 来进行各种操作
我自己手动稍微搞了一下 建了个机器人 @xjpin_bot 疯转了下服务器 大家感兴趣的话 可以建个频道 把这个机器人加一下管理员 然后请求 感受一下 真的非常非常非常非常方便好用
1 | curl -d "content=testfromtbot" http://tbot.xjp.in/@channelname |
然后分享下完整代码 二十行不到
1 |
|
前端使用GPU的能力是通过webgl实现的 更加广泛的理解的可以认为是通过canvas来说实现的 canvas估计对大多数前端来说并不陌生 canvas有许多个像素组成 每个像素的颜色可以有RGBA
四个维度表示 每个维度范围为0-255 既8位 把RGBA表示成数值的话 那每个像素可以存32位 这就是前端使用gpu计算最为核心的一点 每个像素可以存储一个32位的值, 刚刚好就是一个int
或者uint
首先从最简单的绘制一个图像开始 webgl绘图的流程 最简单的就这样
其中两个vertex shader
和fragment shader
为两个GLSL
代码片段 分别处理坐标数据和颜色数据 vertex shader
和fragment shader
的执行是以像素为单位
canvas开始绘制的时候 vertex shader
中得到 每个需要绘制的像素的坐标 视需要可以对坐标进行各种转换 最终得到一个最终位置 这个过程中可以将数据作为输出传入fragment shader
参与下一步的计算
fragment shader
接受各种输入 最终输出一个RGBA
颜色数据作为该像素点的颜色值
当所有像素都绘制完成之后 画布绘制完成
webgl program
shader
因为咱们主要是计算 所以对坐标相关的数据可以不用太多关注 咱们直接画一个铺满画布的矩形就可以了
1 | // 加载资源 |
需要注意的一点vertex shader
中得到的坐标是以canvas中心为(0,0)
水平向右为x轴正方向 垂直向上为y轴正方向 两轴的取值范围为[-1, 1]
所以上面js代码中传入的顶点坐标范围为[-1, 1]
的浮点数
另外OpenGL中绘制面都是以三角形为单位的 webgl中也不例外 提供了一个绘制连续三角形的方式 一个矩形是两个三角形 所以传入四个顶点就可以了 当然也可以传入六个顶点 分别绘制两个三角形
顶点的传入实际上是传入一个数组 然后vertexAttribPointer()
方法指定各个顶点如何使用这个坐标数组 可以认为是8个一维坐标 也可以认为是2个二维坐标 或者是2个四维坐标 所以上述的例子实际是传入了4个2维坐标
接下来就是两个shader中的流程 目前大部分浏览器已经支持WebGL 2.0标准 对应OpenGL ES 3.0
所以shader中的语法需要遵循相关语法
具体的版本可以使用gl.getParameter(gl.SHADING_LANGUAGE_VERSION)
获取
vertex shader
:1 |
|
具体的语法啊变量类型啊什么的可以看官方的文档
只做一点说明 in
将变量标记输入 out
将变量标记输出 在webgl 1.0
中 attribute
表示输入 所以在js获取变量地址的时候使用了getAttribLocation
函数 其中的Attrib
即是这个意思 但是在webgl 2.0
这个声明被弃用 使用in
来代替
out
标记的变量的值将作为fragment shader
的输入
gl_Position
为内部变量 作为最终的坐标地址 实际中还有很多其他内置变量 就不举例了
上述的代码将以画布中心为原点的坐标系转化为以左下角为原点的坐标系 并将新的坐标系中的坐标传给下一步 后续会解释为什么要做这样一个坐标变换
fragment shader
:1 |
|
同样 fragment shader
中也有in
和out
关键字
其中in
对应vertex shader
中的 out
变量类型以及变量名必须一致
out
为一个vec4
类型 存放最终的RGBA
结果 每个值的范围为[0, 1]
上述的代码也很简单 颜色固定为红色 但是透明度按照像素到原点的距离递增 距离越远 透明度越高
最终画出的效果是这样的
上面已经讲了一个坐标的输入 但是计算相关的参数需要其他的方式传入 需要一点提醒 由于js中的所有数值都是浮点型 所以js和webgl进行数据传输的时候 全都必须使用类型数组 并且相当多函数只能接受某种特定的类型数组
因为两个shader都是js获取到的资源 所以在载入webgl之前可以对内容进行直接修改
一般来说 shader中要获取canvas实际的大小相当不便 所以 可以直接用这个办法将画布大小传入
js中:
1 | fshaderCode = fshaderCode.replace(/CANVAS_SIZE/g, canvas.width); |
shader中:1
const int U_LENGTH = CANVAS_SIZE;
这样可以直接在其他地方获取画布大小了 不过这个方法得保证不会替换错了
最重要的是 这个办法对传入数据的格式非常有限
这个方法就比较强大了 不仅一般的int/float
还可以传入 向量 数组 矩阵等各种类型
而且两个shader可以共享同一份数据
1 | function getUniformLoc(name) { |
shader中:
1 | uniform float i_matrixA[4]; |
uniform()
是个一系列的方法 传入不同类型的时候 使用了不同的函数比如上面的uniform1fv
以及后面的uniform1i
详细了解还是得看文档
这个方法等好处就是支持所有类型 但是也有一个问题 不过这个问题并不算是uniform
的问题 而是WebGL本身的局限:
gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS)
或者gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS)
获取数组长度上限 本人实测值为1024 OpenGL ES 3.0
不支持多维数组, 这个问题将在下个版本中得到支持, 当前情况还是无解当然还有第三种方法解决大量数据传入的问题
纹理就是另外的图案 这个就不多做解释了 说白了就是另外一副图 因为图都是由像素构成的 所以可以用纹理来传入大量的数据
1 | function initTexture(index, tSampler, pixels) { |
纹理的定义有点复杂 纹理的大小非常苛刻 只能是2^n * 2^n
的大小 但是数据不可能是固定的 所以 这里有个纹理进行伸缩的过程
使用设置gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
来设置伸缩方式 当然 实际上这个对我们这个计算没有影响 因为我们全程按百分比取值
除了缩放 还有要定义未定义点的颜色规则 比如3 * 3
的图 1/6, 1/2, 5/6
这三个位置的点和传入值完全一样这个没有问题 但是其他位置默认是渐变
可以使用gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
来设置不使用渐变 即各个色块都是三等分 关于两种效果 下面有例子可以看
另外参数传入可以选择多种方式 直接用<img>
标签也可以 或者直接传入像素值也可以 具体方式可以查看texImage2D
文档
当然传入透明的值也是可以的 绘制到画布上的话 真的是透明的 相当神奇
但如果是像素值传入 也可以有多种格式 本例子中将RGBA拆开成四个值分别传入 为了方便起见 可以直接使用类型数组直接将32位转成8位 但是这样的转化方式可能会引起顺序不一致 比如[0x01020304]
会被拆成[0x04, 0x03, 0x02, 0x01]
具体相关内容可以参考类型数组
最后将纹理的索引绑定到纹理变量上 注意到 下面sampler2D
类型其实也是int
这种类型被称为Opaque Types
https://www.khronos.org/opengl/wiki/Data_Type_(GLSL)#Opaque_types 注意下就可以
shader 中:
1 | uniform sampler2D samplerA; |
texture()
为内置函数 用以获取某个纹理在某点的颜色
为了保持输入的时候rgba顺序一致 在获取到纹理中某个值的时候需要重新调整顺序
关于纹理的坐标系 和canvas的是不一样的 是以左下角为原点 水平向右为x正方向 垂直向上为y轴正方向 所以前面把canvas坐标进行转化也是为了和纹理的坐标系一致
另外 像素写入的顺序 也是是从左下开始 先向右写入一行 再依次向上写入每一行
然后直接将纹理的数据1:1对绘制到画布上的效果
默认使用渐变来获取各个颜色 很明显有9个点是渐变的中心 就是上面传入的那九个值了
设置了gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
后九大块方块
对于计算中 后者才是我们想要的效果 不然取值还取到不认识的值 计算要崩啊
使用Texture解决了要传入大量数据的问题 但是使用比较复杂 而且数据传输也是相当地耗时 所以还是期待多维数组Arrays of Arrays#Arrays_of_arrays) 能早一天在浏览器上支持
输出的方式单一 直接将值赋到fragmengt
的out
声明的变量上就可以将对应的值绘制到画布上 接着可以使用gl.drawArrays()
方法来读取各个像素上的点 和纹理的输入一样 读取像素的方法也有很多参数和重载 为了方便 咱们使用下面这种直接读取RGBA这四个维度的值
1 | gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); |
注意 readPixels
方法必须和drawArrays
方法在同一个执行队列中同步执行 否则会无法读取到数据
同上面输入的理 这里使用了Uint32Array
和Uint8Array
进行数据转化 ArrayBuffer
的长度即为画布的像素数量乘上4 因此在fragment
中输出的时候需要反转四个维度
读取的顺序和纹理写入的顺一致 都是从左下开始 沿x正方向读取一行 再向y方向读取各行 最后合并成一个完整的数组 如果输入输出和这个顺序有关的话 需要注意一下
好了 搞了这么多 已经吧基本的输入输出搞定了 咱来开始试一下矩阵相乘吧
不多说了 直接上代码
包含了输入输出等基本方法 以及会用到的其他方法 基本上前面都有介绍
1 | class GPUComputing { |
vertex shader
v_shader.c
为啥要用.c
做扩展名呢 当然是因为方便代码高亮啊
这个shader和前面那个一模一样 对画布的坐标进行了一个转化
1 |
|
uniform
的矩阵相乘1 | class MatrixUniform extends GPUComputing { |
f_matrix_uniform.c
:
1 |
|
数组传参是挺难看
Texture
的矩阵相乘1 | class MatrixTexture extends GPUComputing { |
f_matrix_texture.c
:
1 |
|
里面有个U_TEXTURE_POS_FIX
常量 用来修正texture取值的时候的位置 以免取到像素边界上造成不必要麻烦
先定义矩阵生成的函数 和前面那篇博客差不太多 只是把数据改用了Uint32Array
来存放
1 | function createMatrix(dims, fn) { |
然后定义一个执行函数
1 | async function matrixJob(dimensions) { |
数据正确上没啥问题 不过执行时间上很明显是直接计算来的快 uniform
传参比texture
略慢一点点
不过矩阵太小了 看不出其他的 所以咱们和前面一样 使用多组数据进行对比 因为受数组长度的限制 所以之后的计算uniform
方式就不参与进来比了
同样来一个多组的数据 把不必要的log先注释掉
这个是一台设备
很明显 纯WebGL
计算比前面使用的gup.js
耗时少20% 但是 但是CPU计算在矩阵规模变大之后也有很大的下降 不清楚具体是啥原因造成的 应该是类型数组本身的性能有关吧
再看看那个配置GTX 1060 3G
的电脑
这个就相当厉害了 比gpu.js
性能高了相当多 特别是在维度低的时候 即使是大小为2048 执行时间也减少了60+%
性能比原生数组CPU计算高了200倍 比类型数组CPU计算高了300倍
但是 前面也说了 使用GPU计算最大的耗时在数据传输 那如果数据传输不算 真正计算有多快呢
也是 咱给几个上面的关键函数分别加个计时 init()
和render()
和read()
分别计时
这个是低配的电脑
这个是1060的电脑
。。。。 我觉得这个已经没法玩了啊
输入 计算 输出 三者的耗时简直 尤其是计算 因为js计时器在毫秒级别是不准确的 所以几乎可以忽略。。。 CPU和GPU差距有这么大吗 按照纯计算的时间 就算0.1毫秒计 i5和1060-O3G差的不止百万吧
两张显卡之间对比
输入的耗时差别不大 最大也就两倍差距 基本可以认为是一样的
但是读取耗时 1060比我渣核显低了最高有10倍
两个的计算时长都超过了js的计时精度所以没办法通过这样比较 只能说 不要用这种方法来比显卡计算性能
代码包含了上面所有的代码 示例因为考虑到大量的计算会造成浏览器卡死 所以只保留了三个示例 一个是按坐标距离设置透明度 一个是将九像素纹理绘制到画布上 还有一个是3维度的矩阵的乘法的三种实现 以及分别的计时和结果
]]>因为js是单线程的 所以并不适合处理CPU密集型的程序 但是GPU是有非常高的并行线程数 所以GPU计算的基本思想就是把计算任务分拆成N多个线程任务 每个线程返回一个结果 然后吧每个线程的结果再汇总 这个基本的思路不管是哪平台上的应该都是一样的
啥是线程的概念 咱先直接看一段代码来先感受一下
1 | const gpu = new GPU(); |
每个线程都会执行这个传入的函数 具体起多少个线程有参数output
决定 可以有x,y,z
三个维度 上面这个例子只取了x
方向上数量100个
在函数里this.thread.x
可以获取到当前执行这个函数的线程x维度上的地址 如果有多个方向的话 还有类似this.thread.y
和this.thread.z
的
函数接受的参数为外部调用的时候传入的参数 函数返回的值为当前线程返回的值 所有线程按照各个维度上的位置 向最外面返回一个最终的数组 就是上面那个执行的效果 每个线程直接返回了线程在x维度上的位置 最终吧所有线程按照维度的顺序合并起来 如果有两个维度 最终的值就是个二维数组 同理三个维度也是
要注意的是 这个执行的函数 最终会被库转化进入到GPU中执行 所以 所有外部js的参数和函数 还有该函数里的语句语法是有限制的 具体的解决方法可以参考文档 有向内部传入数据的方法
说了这么多 咱来写一个实际有用的东西 矩阵相乘 C=AB
为了方便起见 A和B都是相同大小的方块矩阵
矩阵的乘法咱就不回顾了 只提一个非常关键的地方 就是关于矩阵C中 C(i,j)的值
这个很关键 先吧这句翻译成程序语句
1 | let dimensions = 100; // 假设长度为100 |
于是开始写
1 | const matrixMultiplyGPU = new GPU().createKernel(function(a, b) { |
这里注意一点 线程在两个维度上的坐标顺序与结果的顺序是反的 比如线程在两个维度上是[i, j]
那么在最终的结果里的位置是[j, i]
不知道这里是为啥要这样做 可能和GPU的原理有关
然后咱们随机构造矩阵 然后开始计算
1 | function createMatrix(dims, fn) { |
哈哈哈 为了方便起见 两个相同的矩阵相乘好了 拿出笔和纸 用个两三个维度的值验证了一下 发现没有问题 但是这个效率和CPU的相比 有多大的优势呢
然后咱们利用上面的式子写一个matrixMultiplyCPU
吧 因为有上面的铺垫 所以这个就非常简单了
1 | const matrixMultiplyCPU = function(a, b) { |
验证的也很简单 直接看一下运行时间对比就可以了
1 | console.log("dimensions", dimensions) |
电脑配置是
然后我再看下另外的结果
后面这个的配置就略高一些 所以整体的数据比上面的好一些 但是其中有个数据出了点偏差 具体啥原因也不知道 重新跑了之后结果也没大问题
从上面的数据来看 当矩阵维度较小的时候CPU的耗时比较短 因为当数据量少的时候 时间主要花费在将数据传输上 从内存传到显存 当数据量大了之后 计算时间开始增大 GPU才开始显现出优势
由于显存与内存本据存取上的区别 上述的代码也有非常大的优化空间 上述例子中GPU最大达到80倍的效率应该还是可以继续增加的
当然 GPU的维度也不是越大越好 实测维度过大了之后 计算还是会奔溃的 具体最佳的维度大小应该和各个GPU自己有关吧
]]>说起这个generator
啊 本不是js的东西 而是从其他语言中借鉴来的 当初初es6标准的时候想必大家都看过了 也都知道所谓生成器 但是估计大家和我一样 可能从来也没用过吧 最大的用处估计就是在async/await
实现之前来替代的处理异步吧 比如koajs1.x
但是这并不是generator
本身应该的正确用法 如果你是generator
你心里会怎么想 本来我估计也用不到这东西 但是最近我不是后来去写了python嘛 也终于在一次认识到了这个东西 然后再次从js的角度看待generator
首先来个例子 redis中每个用户有一个物品列表list user:$userId:items
每个物品有物品详情hashset item:$itemId
现在需要遍历所有物品 为了减轻内存负担 所以要一条一条取
js 回调参数 内部回调函数版
1 | function getAllItem(userId, callback){ |
这个版本因为整个函数都是异步的 获取所有物品后再执行后面的过程
这个操作是没法处理了 需要对回调参数函数进行再次封装 即使内部换成Promise
也是一样的
为了实现这个 再写一个内部用async/await
实现的
1 | async function getAllItem(userId, callback){ |
这个实现解决了上面提到的那个问题 但是目前async/await
支持度不高
好 咱来看看 python的版本 python当然也有回调函数啊 但是python的回调函数难用的一笔 写出来的效果和上面那个差不多 主流的方法还是使用yield
1 |
|
卧槽 这简直甩js几条街了 是不是 但是js也有yield
的啊 但是光是js的generator
写不出这样的代码诶 尤其是层层嵌套回调函数要使用 yield
简直是不可能
咱来写一个js的generator
版的 使用async generator
以及for-await-of
这两个目前还没上标准 还是stage 3
的状态 不管 反正先写了再说
1 | async function* getAllItem(userId){ |
卧槽 这个是不是和python那个yield版本一模一样
我觉得这才是generator
真正的用法 从语义上最好的当然是使用async generator
或者 async iterator
不支持异步的迭代器或者生成器都是没实际价值的
但是就这个需求目前来看 async/await+参数回调函数
是目前最好的解决办法 因为异步迭代器还没有人支持啊 目前来看 异步迭代器 asynchronous-iteration最快可能进入ES 2018标准
本文的目的是为了和大家分享如何从官方文档中获得答案 至于标题中和本文其他的语法也是非常不规范 在平时开发中需要极力避免使用`==`与不必要的类型转化
我先抛出一个例子 大部分人估计会很疑惑 在语言大战中 很多其他语言的人也经常抓住类似的例子来攻击js
但是js作为一个有规范的语言 所有的结果都要遵循规则吧 然后咱就来带大家看看 关于==
语法在es 2017
中的定义
为啥是2017呢 因为本文写的时候最新的标准就是这个版本 ECMAScript® 2017 Language Specification (ECMA-262, 8th edition, June 2017) 但是没有关系啊 我会截图给大家的
首先先看==
的标准 Abstract Equality Comparison
提示一下 文中涉及到的类型 全都是指基本类型 js目前有七大基本类型: Undefined
, Null
, Boolean
, String
, Symbol
, Number
, Object
. 为了和typeof
返回值一致 我就都使用小写了
这个标准其实不复杂 其实也就十几个步骤 中间各种情况 咱画个流程图 文档中x
和y
有先后顺序 咱们这就不管顺序了吧
中间就5个判断 总结一下 可以分为三组 进行区分
===
结果object
之间undefined
和null
遇见的时候为true
string
number
boolean
两两遇见的时候将转化成number
进行判断false
object
与其他类型之间undefined
和null
, 返回false
boolean
将boolean
转化为number
重新计算object
原始值进行重新计算哎 这样就清晰了
啥是对象的原始值? 好了 这又得看另外一个规则了 toPrimitive
一个前置条件: 原始值不能为object
这个规则又是挺麻烦的一个 流程和情况分支也很多 我就总结一下这里用到的规则吧:
Symbol.toPrimitive
的函数, 函数运算结果即为原始值, 如果运算结果不合条件将会报错valueOf
方法的值, 运算结果如果符合条件则即为原始值toString
方法的值, 运算结果如果符合条件则即为原始值所以 这就是最终的答案
整个流程是这样的
1 | ![] == [] // 最初的等式 |
而这个 ![] == ![]
就比较简单 直接是类型强转false == false
即true
[] == []
也比较简单了 因为类型一样 所以返回[] === []
而任何对象除了同一个引用之外不等于其他任何对象 所以答案是false
举一反三
1 | var dEmpty = {} |
跑题了 笔试出了三题 都不难 和面试官共享页面 也可以对话什么的 这种笔试方式 与其说是笔试 更像是面试的时候拿张草稿写解决方法的感觉 我觉得非常棒 (最后我会对比另外一家的笔试题目 真的太垃圾了)
其中第一题 让我就非常欣赏 题出的太棒了
实现一个函数,传入一个数组,元素均为字符串,把数组中的所有星号(*)移到末尾,其他值的次序不变,如下图
这个题目非常简单啊 但是我觉得这个是个非常好的题 具体好在哪呢
// TODO
最近没空写 先占坑
自从换了新部门之后 也没有写过前端 因为部门总共6个人 有两个前端 然后后端和算法人员不足 因为我技术比较强 就被安排到了新的任务 老大和我说 做数据和算法比前端有前途 话是这么说的 但是我从大一起开始写前端写了这么多年 突然让我去写其他的 那我前端领域的优势就没了啊 而且就算我继续算法这些写个三年四年 到时候仍然随便一个刚刚毕业的专门研究算法的就能把我干趴下了好吗 毕竟我也没怎么专门研究过这些东西 不过既然部门有任务安排 那只能写了 这段时间经历了 python从入门到精通 hbase从入门到精通 hadoop从入门到精通 什么kafka 什么zookeeper 让我作为一个前端大开眼界 有点偏主题了。。。。
我的主要工作有三点本文只是涉及其中一点 就是基于协同过滤的推荐算法 我们部门是推荐技术部 各种推荐算法当然是我们部门的核心技术 协同过滤这一块没那么核心 但是也非常重要
这个相关的文章 网上也是一抓一大把 主要的原理是通过用户对物品的行为来计算物品与物品之间以及用户与用户之间的相似度 算法的核心数学公式有好几种 我就不重复写了 我负责的这一块是从其他项目中借鉴过来的 发现对应的数学公式和现有的一些公式并不一样 我也不便透露了 不过各种公式的功能是一样的 都是为了计算相似度 不同公式对不同对场景会有优劣而已
上图就是一个假设的用户对物品的操作的表 表示用户对各个物品的是否有过操作 在计算相似度的时候 不同的操作有不同的权值 或者直接可以使用用户的打分作为权值
协同过滤分为两种 以下假设所有的行为权值都一样
一种是基于用户的 计算用户之间的相似度 比如上图中 对item2有操作的用户为: [user2, user6] 对item5有操作的用户为: [user2, user4, user6] user2和user6操作过多个相同的物品 可以认为user2和user6相似度比较高
因此可以将这两个用户操作的物品相互推荐 例如吧user2操作过的item4推荐给user6
另外一种是基于物品 计算物品之间的相似度 比如user2操作过[item2, item4, item5] user6操作过[item2, item5] item2和item5被多个相同的人都操作过 所有可以认为item2和item5比较相似
可以把其中一个物品推荐给操作过另外一个物品的人 比如 就可以把item2推荐给操作过item5的user4
很明显可以看出来 上面两种是完全对称的 常常情况下 物品或者用户数量一方远大于另外一方 这时候就得考虑选择其中一种就可以了
当然上面的举的例子并不严谨 数量太少
协同过滤是一种典型的基于群体行为的方法 需要建立在大量的行为之上才能计算准确 而且数量越大越准确 基于内容相似的推荐 在不能起到作用时 协同过滤往往能起到奇效
具体的demo我以后再补吧
]]>ejs-mate
实现了一个组件加载函数 实现了服务端组件化的渲染 按组件加载js与css资源 目前很多前端标准各大浏览器都有部分支持 心想试一下 然后试试看把vue
的组件改成原生的前端组件吧 然后看了一下WebComponents
发现Shadow DOM
离我上次看的标准又有变化了 没办法 目前这些基本还处于草案的阶段 只有template
标签因为属于HMTL5的标准 目前是比较稳定放心可用的状态 具体就不多说了 写了两个组件 弄了个demo thesadabc.github.io/blog-demo/webcomponents 代码在这github.com/thesadabc/blog-demo/tree/master/webcomponents chrome56+
然后我发现并不能把vue
的组件完全移植到原生的组件上 后来发现事情并没有这么简单 重新审视一番发觉 vue
的这些所谓的组件实质上是 模板 之所以不能移植到原生组件 很大一部分原因是诸如v-if
、v-for
等语法 而这些在模版引擎里非常常见 包括router-view
这种 原生组件是根本没法搞的
vue ng之类的 本质上是前端的组件化渲染 组件本质就是模板 相比后端组件化渲染还只是停留在非常低级的include
html模板文件的阶段 对组件的js与css的处理几乎没有
所以我在想 按照各自框架渲染组件的方式 如果移植到服务端渲染 那岂不是很爽吗 然而vue
是有服务端渲染的 不过这个服务端组件渲染和前端组件渲染不太一样 服务端渲染只是把html
相关的渲染完成 而事件属性之类的传递和前端组件渲染并没有区别 也还是包含了一个完整的Vue
对象 所以其实这种渲染并不是完全的服务端渲染 而是分步渲染 反而觉得这种方式非常多余 为了seo
的一种妥协
于是开始搞一个吧 完全后端渲染的 组件化的解决方案 各自模板引擎其实都有提供include
方法 不过这只是html
的模块化 事件以及数据传递相当成问题 弄了一个简单的 包含了这种思想的 demo
基于ejs-mate
自己封装了一个component
函数 用来加载组件 以及组件相关的资源(js, css) 这个demo就不上代码了 因为太简单了
大概就是这个意思 后来有一些改进 遇到几个问题
很明显 这几个在前端渲染很容易解决 但是后端渲染就有问题了
所以有这样的想法
1和3可以给每个脚本插入一段代码 这段代码给这个脚本 注入入一个对象 表示根节点 类似hexo
给其插件注入了hexo
这个变量一样 而且由于组件复用的原因 所以这个需要和第2点配合 插入固定class
或者其他方式来标记元素 可以保证每种组件唯一 不是每个
说到这个 css
的嵌套Nesting
暂时这个还用不了 如果可以的话 在每个组件css外面注入一层父选择器 这样就更好了
对于问题2 使用cheerio
之类的工具来处理
对于问题4 估计目前搞不定
于是基于上面这几个想法 然后花了一天 弄了一个略微高级的 写了一个这样的代码github.com/thesadabc/blog-demo/tree/master/server-render-components 功能和上面那个原生控件的差不多 也是多个图片预览的功能 把嵌套的部分去掉了 这个因为是服务端渲染 需要下载下来自己运行才能看到效果 下面有个截图
express
接入模板引擎并设置路由home
模版thumb
组件js代码 注入了一个root
变量作为根节点元素具体的内容可以直接看代码 还是比较简单的 本来想吧 资源混淆压缩加进去 结果uglifyjs
不支持新的语法 明明浏览器都已经可以用了啊 然后干脆就放弃了
基本上来说 目前这个还是比较满足我心里的想法的 非常爽
]]>本文所有的demo都在 这里 blog-demo/permission 代码在这github.com/thesadabc/blog-demo
有几个权限的demo还没有写好 之后会单独写出来
w3c.github.io/permissions: Editor’s Draft, 17 October 2016
事实上不止链接中的这几个权限, 比如usb
, 本文就只对下面列出的这几个权限进行介绍
1 | enum PermissionName { |
push
background-sync
需要配合service worker
工作
notifications
在service worker
与浏览器环境中有不同
检查权限
1 | navigator.permissions.query({ |
www.w3.org/TR/geolocation-API: W3C Recommendation 8 November 2016
虽然这个版本是2016年11月8日出的第二版 才出没多久 按照其他权限的实现方式 估计这个方案会改成Promise
的方式 而且会增加一个专门来请求权限的接口
1 | // 调用的时候会向用户请求权限 |
notifications.spec.whatwg.org: Living Standard — Last Updated 15 February 2017
通知有两套实现方案 一个是在浏览器中直接调用构造函数生成通知 另外一个是中调用service worker
里的serviceWorkerRegistration
对象上的构造方法
两者有两个区别
ServiceWorkerGlobalScope
上actions
属性 可以在通知框上添加额外的按钮1 | // 请求通知权限 |
www.w3.org/TR/push-api/: W3C Working Draft 22 February 2017
相关内容:
appmanifest
Progressive Web Apps
这个api和Progressive Web Apps
的推送有关 内容比较复杂 之后我再单独开一篇文章写这个吧
1 | // in browser |
web midi: W3C Working Draft 17 March 2015
获取设备上的MIDI
接口 多用于音频设备(主机上也留有这种接口 给键盘鼠标用的)
这个api太专业了 我这里没有测试的设备 所以没法试这个代码 而且数据请求都是直接传送二进制数据 所以也比较头疼
1 |
|
www.w3.org/TR/mediacapture-streams: W3C Candidate Recommendation 19 May 2016
www.w3.org/TR/audio-output: W3C Working Draft 15 December 2016
主要涉及两个函数
navigator.mediaDevices.enumerateDevices()
: 查询设备信息 和device-info
权限相关 一般都是允许的
navigator.mediaDevices.getUserMedia()
: 获取设备媒体输入数据 对应的camera
和microphone
权限 这个函数做过一次相当大的调整 原本是直接在navigator
下的navigator.getUserMedia
而且是回调式的 现在是基于Promise
的异步
关于speaker
权限 暂时还没有去研究 看接口应该是能指定某个音箱设备播放声音 而且有单独的标准audio-output
除了浏览器的接口外 还涉及HTMLMediaElement相关的东西
srcObject
属性 设置播放源
1 | video.srcObject = stream; |
sinkId
属性 设置播放设备1
audio.setSinkId(deviceId);
capture
属性 选择摄像头拍摄内容作为文件 移动端较为常见 而且常内置于type="file"
标签中
1 | <input type="file" accept="video/*, image/*, audio/*" capture> |
allowusermedia
属性
1 | iframe.allowUserMedia = true |
MediaStream Image Capture 图像捕获相关的接口
除了这些之外 这个标准还和webrtc标准有一点联系 可以研究一下
标准里涉及到了多个非常非常相似的几个数据结构
1 | SupportedConstraints { // 各个属性是否支持 |
这三个结构 相当于Settings
是最基础的 Capabilities
在前者只是给每个属性增加了扩展: 字符串可以使用数组, 数值可以给范围 ConstraintSet
再进行扩展 给每个属性增加了预期值和实际值 Constraints
再在前者上增加了一个属性 可以设置为同结构的数组 几个数据结构用的地方也不太一样 要注意
1 | // device-info |
wicg.github.io/BackgroundSync/spec: Draft Community Group Report, 2 August 2016
Note: Background sync SHOULD be enabled by default. Having the permission denied is considered an exceptional case.
browser
与ServiceWorker
之间进行的相当简单的单向通信 不能传数据
当一个耗时的重要操作还没有完成的时候 如果用户把浏览器关了 就会造成数据丢失什么的 为了解决这个问题可以吧这些操作放到ServiceWorker
中进行处理 这样
其实感觉这个权限有点点奇怪
1 | // in browser |
webbluetoothcg.github.io/web-bluetooth: Draft Community Group Report, 15 February 2017
社区 www.w3.org/community/web-bluetooth
相当复杂 另起一篇 而且没有测试设备 这个月初的时候官方才刚刚出了一个demo 还挺复杂
其实本篇文章就是从这个demo开始的 本来想研究下WebBluetooth
然后涉及到浏览器权限 然后干脆就整理一个吧 于是就写了这篇整理权限的东西
storage.spec.whatwg.org: Living Standard — Last Updated 23 February 2017
似乎这个权限没有明显的代码实例 只是一个普通的查询用的权限 根据这篇文章的回复persistent-storage 似乎认为这个接口是用来查询浏览器端数据存储是否为永久性的. 如果权限查询结果为true
表明这些存储方式存储的数据 在系统存储空间充足的情况下 不会被浏览器清理掉 具体有哪些存储方式 文档有说明storage.spec.whatwg.org/#infrastructure 包括网络请求的缓存 cookie IndexDB LocalStorage等
一个同源站点关联里一个站点存储单元
(site storage units) 每个站点存储单元
包含了一个单个的空间
(box)
1 | // 在`persistent-storage`权限为`granted`的情况下 将`站点存储单元`的`存储空间`类型转换为`persistent box` |
预处理器解决的痛点:
预处理器的缺点:
然后文章告诉你 这些痛点都有原生的解决办法 具体的兼容性可以查看 caniuse或者MDN或者官方标准 这些东西更新太快了 不能保证写本文的时候的标准以后不会变
第一个变量: currentColor
用法很简单 基本上所有现代浏览器都能用了
1 | div { |
更加通用的css变量 CSS Variables 标准
语法
1 | /* 定义变量 */ |
使用
1 | :root { |
为啥 css变量要用--foo
和var(foo)
这样的语法呢 而不用sass或者其他的类似$foo
呢 这里有篇文章 Let’s Talk about CSS Variables 相当早的文章 关于制定这个语法的时候 规则制定者们的一些思考
具体的css变量语法各个地方资料都很多 这里就不多讲了
主要是@apply
这个语法和css变量的有点点相似 @apply 提案
1 | :root { |
和这个功能相似的有个 @extend(继承) 提案
1 | .error { |
和各种预处理也没有大的区别 nesting 提案
基本语法
1 | ul { |
还有一个关键字 @nest
必须要结合&
使用
1 | .foo { |
相当于
1 | .foo { |
这个所有浏览器应该都兼容了的 早在ie5.5
就可以用了
但是由于浏览器没有实现并行请求 所有的子模块按顺序全部加载 会阻塞其他请求 所以这个语法虽然没有兼容性问题 但是几乎没人用 但是等到HTTP/2
普及之后这个情况会有改观
1 | /* Formal syntax */ |
可以使用css选择器同时选择多个元素 来自Selectors4 标准
1 | .nav:matches(.side,.top) .links:matches(:hover, :focus) { |
自定义选择器 来自CSS Extensions 草案 这个草案非常新鲜 9 February 2017
才编辑过的
1 | @custom-selector :--text-inputs input[type="text"], input[type="password"]; |
这个功能是不是和:matches
有点重复
自定义媒体查询 这个语法和@custom-selector
如出一辙 mediaqueries 草案
1 | @custom-media --tablet (min-width: 800px) and (max-width: 1024px); |
是一个颜色的扩展类 帮助设置一些颜色什么的 Color Function
1 | /* syntax */ |
现在这些特性肯定不能全部都用上的 如果想用 可以试一下这个 css next 相关的一些新css特性也可以再这里查到
]]>1234
这样的数字 一个扫描周期里需要四个动作 每个动作只显示一位 另外三位是不亮的 而且每次占用了大量的针脚 于是 淘宝上买了个带芯片的 4针脚的 4位数码管 本次的芯片是用的TM1637 查了一下 到处都是只有arduino的代码 而且貌似这个数码管也是标配给它的 不管 反正接口是一样的嘛
原本给arduino的代码是用c写的 还找到了另外一个python写给树莓派的 两者的代码的思路是一样的 但是还是没有看懂 说好的基本输入输出呢 两个的代码全都是直接修改pinMode
来处理高低电平的 我擦泪 完全看不懂啊 为啥不用digitalWrite
反正就是这个数码管有4个脚 一个电源 一个接地 一个时钟信号 一个串行输入信号 然后看文档 文档 尼玛这什么狗屁文档 完全看不懂啊 一个是数码管的说明 一个是芯片的说明 一个是两者连接说明
关键是芯片说明书
微处理器的数据通过两线总线接口和 TM1637 通信,在输入数据时当 CLK 是高电平时,DIO 上的信号 必须保持不变;只有 CLK 上的时钟信号为低电平时,DIO 上的信号才能改变。数据输入的开始条件是 CLK 为高电平时,DIO 由高变低;结束条件是 CLK 为高时,DIO 由低电平变为高电平。
TM1637 的数据传输带有应答信号 ACK,当传输数据正确时,会在第八个时钟的下降沿,芯片内部会 产生一个应答信号 ACK 将 DIO 管脚拉低,在第九个时钟结束之后释放 DIO 口线。
这特么都说的是啥 反正结合这段话 然后再看那两份源码 看的我真是有点傻逼了 后来搞了很久 看懂了 上面这两段话说了三个事情
起始的时候 两个口都为高电平
上面这个基本上说了一次输入的流程 但是要现实出数字来一次输入是没用的 要输入多次
于是 芯片的文档还说了另外一个事 每组数据分为三部分
然后因为很明显 第二种模式最方便 然后就选了这个来实现了
环境 为nodejs 然后还遇到一个问题 文档中虽然没说 但是前面两个源码里 两个信号之间有一定的时间间隔 应该是表示时钟每个周期时长
由于上面两个源码 一个是c一个是python 设置时钟高低电平的时长用sleep
之类的很容易实现 但是js咋办了 想了两套方案
最容易想到的就是js模拟一个sleep
出来 js里async
还用不了 可以使用generator
来做 实际上 还挺不喜欢这个的 需要一个辅助库co
之前也写了一个小轮子大概实现了这个功能 不过为了方便 还是直接用co
了 这个方案的一个好处就是 代码结构和python
这些使用sleep
来实现延迟的基本一致 代码逻辑易理解 如果中途出问题比如第9时钟返回的ack异常这种 可以及时解决 有点点同步执行的意思
还有另外一个解决方案 既然没办法同步执行 那可以把所有的操作保存到队列 然后控制出队的时间间隔 也可以做到延时的操作 这个方案也有个好处 就是代码写起来方便易读 没有特别难懂的js语法 generator
和 yield
啊这种 但是因为是异步执行 比如上面说的 如果ack返回有问题就得不到及时处理 只能数据发送完成之后才能知道
大概的逻辑是这样的
1 | this.start(); // 数据命令设置 |
代码 在这里
然后最后是最后的效果
]]>基本上所有的语言都提供了一个random方法来获取一个随机数,以js为例, Math.random() 将返回0到1之间的任意值。这个方法所有的值都是等概率的,从概率论角度来说,即该方法的概率密度函数为常量,为:p(x) = 1, x∈(0,1)
如果将随机变量做一次处理,例如平方,那么它的概率分布将会怎么样。得到下题:
如果x为(0,1)上的随机变量,且概率密度函数为p(x) = 1, x∈(0,1),求y=x^2的概率密度函数。
这个题啊,其实很简单,如果是当初大一的时候还在上概率论的课,两分钟就解出来了。不过虽然很久没接触概率论,花点时间还是能解出来的。
这里还有个坑。如果任意y都对应的唯一确定的x,那么得到这个y值的概率应该和这个x的概率是相同的,而获取任意x的概率处处相等,所以y的概率分布和x是相同的。 错!如果x与y是离散的随机变量,那么这个结论是正确的,即y的概率与x的概率分布是相同的。可惜,这里的x是连续的,不能这里来计算。
由于x是连续的,所以没有办法得到某个确定的x的值的概率,比如说x取到0.5的概率是多少,这个是没有答案的,我们只能得到x取值为 0.49到0.51之间的概率是多少,这个的答案为其概率密度函数的在0.49与0.51上的定积分。擦,出现积分了。还好这个是常量函数。
这里也是个坑,因为之后会遇到其他的积分。如果是大一的时候还在上微积分的课程,一般简单的函数的积分还是能秒秒钟算出来的,复杂一点的分步积分花个几分钟也能求出来,可惜现在基本已经废了。然后去复习了一下。
很明显y∈(0.49,0.51)的概率,就是x^2∈(0.49,0.51)的概率,即x∈(√0.49,√0.51)的概率,即 √0.51- √0.49。
经过上上面的思考,可以求得任意区间y的概率分布。得到 P(y1<y<y2) = √y2 - √y1
设y的概率密度函数为p(y),由于,即 ; 很显然,, 所以可以求得y概率密度函数,即
复杂了一点,于是得到了最终的结果,y的概率密度函数,然后开始写一段代码验证一下.
实验的结果如下图,实验次数为1000000,以0.01为统计最小单位:
然后画出函数的图像,如下图,
可以看出来,两图基本一致,看来没什么问题。
然后得出一个结论 : p(y) = g’(y),其中 g(y)为y关于x的反函数
简单的来说就是,如果x为随机变量,而且y = f(x),那么y概率密度函数为 f(x)反函数的导函数。
那如果反过来呢,已知一种概率分布,求积分,再求反函数,得到的f(x)是否是正确的。证明就不证了,直接上测试。
这个测试的例子是以前段时间某次抽奖活动为原型,也是本篇文章主题的起源点,具体数据纯属胡扯。
抽奖要求:后百分之五十的抽到概率为前百分之五十的概率的两倍。
构造一个概率分布函数,,这是一个分段函数,
求不定积分得到
求反函数得到最终结果,
迫不及待验证一下。
卧槽 简直完美。
于是就从数学上得到了一种可以构造任意分布的随机变量的方法。函数调用非常简单,只需要将Math.random()的到的数据进行二次处理即可,麻烦的地方在于根据要求构造概率分布函数,以及对这个函数求不定积分。例如,构造一个正态分布的随机变量就会比较麻烦,因为正态分布积分有点点难算
很久没有接触概率论和微积分了,中间有很多疏漏。
再来一个,令 f(x) = √(2x); 得到下图,求解得到 p(y) = y, 也是一致的,perfect。
所有的代码在这里, https://github.com/thesadabc/blog-demo/tree/master/random,
]]>功能就不多说了
从最简单的开始吧
假设一个模板长这样
<h1><% var world = 'world!'; %> hello <%= world %></h1>
模板转化后应该到这样的:
1 | // 假设输出的结果存放在变量p中 |
总结一下这个过程,其实就只有三条规则:
%>...<%
转换为 %>p += '...';<%
, 起始和结束同此, 将内容直接作为输出<%...%>
转换为 ...;
, 逻辑处理, 直接执行<%=...%>
转换为 p += (...);
, 将运算结果作为输出实现就很简单了把上面的规则写成代码,
1 | tpl.replace(/(\s)+/g, " ") // 主要是为了处理换行 |
然后包装一下
1 | function render (tpl) { |
完成!
1 | // 模板 |
大功告成
当然中间还是遇到了很多问题,不过最终的结果还是非常好的,这个轮子项目已经放在github仓库
]]>Last year we advised you to use ES2015 - however, a lot has changed since.
去年我们就建议你使用ES2015 - 但是还是有太多变化了。
Back then, Node.js v4 was the LTS version, and it had support for 57% of the ES2015 functionality. A year passed and ES2015 support grew to 99% with Node v6.
回到年初,Node.js V4还是LTS版,已经支持了57%的ES2015语法。一年过后,Node V6版本对ES2015支持度已经长到99%了。
If you are on the latest Node.js LTS version you don’t need babel anymore to use the whole feature set of ES2015. But even with this said, on the client side you’ll probably still need it!
如果你在使用最新的Node.jsLTS版本的话,你就不需要再使用babel来支持ES2015的所有语法了。但是虽然这样,浏览器端你可能还是需要使用的。
For more information on which Node.js version supports which ES2015 features, I’d recommend checking out node.green.
要查看更多的Node.js版本对ES2015支持度,我建议你看这个node.green。
Promises are a concurrency primitive, first described in the 80s. Now they are part of most modern programming languages to make your life easier.
Promises是一个相当老的一个并发控制模型,第一次提出是在80年代。现在它成为了大部分现代编程语言的一部分,让你生活得更好。
Imagine the following example code that reads a file, parses it, and prints the name of the package. Using callbacks, it would look something like this:
看下面这个例子,读取文件,转化数据,输出pakcge的name值。如果使用回调,代码会是这样:
1 | fs.readFile('./package.json', 'utf-8', function (err, data) { |
Wouldn’t it be nice to rewrite the snippet into something more readable? Promises help you with that:
如果把这个代码写成可读性更高的会怎么样?Promises可以做到这点:
1 | fs.readFileAsync('./package.json').then(JSON.parse).then((data) => { |
Of course, for now, the fs API does not have an readFileAsync that returns a Promise. To make it work, you have to wrap it with a module like promisifyAll.
当然,现在fs
接口并没有一个会返回Promise的readFileAsync
方法。为了能够运行,必须使用promisifyAll之类的模块包装一下。
When it comes to code style, it is crucial to have a company-wide standard, so when you have to change projects, you can be productive starting from day zero, without having to worry about building the build because of different presets.
说到代码规范,有一个公司内的标准是非常重要的,这样当你换了项目之后,你也能马上进行开发,而不需要因为不同的配置而担心项目构建。
At RisingStack we have incorporated the JavaScript Standard Style in all of our projects.
在RisingStack,我们把JavaScript标准代码规范编入到我们所有项目里。
With Standard, there is no decisions to make, no .eslintrc, .jshintrc, or .jscsrc files to manage. It just works. You can find the Standard rules here.
使用标准,我们就不需要做一些决定,没有.eslintrc
,.jshintrc
,也没有.jscsrc
需要处理。这个直接就能工作。你可以从这里找到标准规则。
You can think of Docker images as deployment artifacts - Docker containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server.
你可以把Docker作为部署神器 —— Docker容器在完整的文件系统中包裹了一点应用,包含了运行需要的所有东西:代码,运行时,系统工具,系统库 —— 任何你能在服务器上安装的东西。
But why should you start using Docker?
但是为什么你要使用Docker呢?
To get started with Docker, head over to the official getting started tutorial. Also, for orchestration we recommend checking out our Kubernetes best practices article.
开始使用Docker,可以看官方的开始指南。还有,对于业务流程我们建议可以参考我们的文章Kubernetes最佳实践。
If something breaks in your Node.js application, you should be the first one to know about it, not your customers.
如果你的Node.js应用挂掉了,你必须是第一个知道的,而不是你的用户。
One of the newer open-source solutions is Prometheus that can help you achieve this. Prometheus is an open-source systems monitoring and alerting toolkit originally built at SoundCloud. The only downside of Prometheus is that you have to set it up for you and host it for yourself.
最新的开源解决方案是Prometheus,它可以帮你完成这个。Prometheus是一个开源的监控报警工具,最开始构建在SoundCloud上的。Prometheus唯一的不足就是你要自己配置部署。
If you are looking for on out-of-the-box solution with support, Trace by RisingStack is a great solution developed by us.
如果你要找一个有技术支持的开箱即用的的解决方案,Trace by RisingStack是我们开发的一个非常棒的一个解决方法。
Trace will help you with
Trace 能够帮你
If you are using HTTP for sending messages, then whenever the receiving party is down, all your messages are lost. However, if you pick a persistent transport layer, like a message queue to send messages, you won’t have this problem.
如果你在用HTTP传递消息,一旦接受方挂了,那你所有的数据都会丢失。如果你使用一个持久传输层,比如消息队列来发送消息,你就不会有这个问题了。
If the receiving service is down, the messages will be kept, and can be processed later. If the service is not down, but there is an issue, processing can be retried, so no data gets lost.
如果接收服务挂掉了,这些小些会被保留,并且可以之后再处理。如果服务没有挂,但是出了一个问题,会重新进行处理,所以不会有数据丢失。
An example: you’d like to send out thousands of emails. In this case, you would just have to put some basic information like the target email address and the first name, and a background worker could easily put together the email’s content and send them out.
举一个例子:发送上千封邮件。这种情况,你只要把基本信息比如收件地址和收件人名字,后台任务可以轻松的把邮件合并到一起然后发送出去。
What’s really great about this approach is that you can scale it whenever you want, and no traffic will be lost. If you see that there are millions of emails to be sent out, you can add extra workers, and they can consume the very same queue.
这个方法真正好的地方在于无论什么时候你都可以改变数据的规模,而且不会有信息被丢失。例如你有几百万封的邮件要发送,你只需要增加额外的任务进程就可以了,他们会处理同一个队列。
You have lots of options for messaging queues:
你有非常多的消息队列选择:
To get the best of the two worlds (stability and new features) we recommend using the latest LTS (long-term support) version of Node.js. As of writing this article, it is version 6.9.2.
为了达到最佳的两个境界(稳定和新功能),我们建议你使用最新的LTS(long-term support)版Node.js。截至本文,最新版本为 6.9.2 。
To easily switch Node.js version, you can use nvm. Once you installed it, switching to LTS takes only two commands:
为了简单切换Node.js版本,你可以使用nvm。只要你安装了这个,切换到LTS版只需要两行命令:
1 | nvm install 6.9.2 |
We conducted a Node.js Developer Survey a few months ago, which allowed us to get some insights on how people use semantic versioning.
几个月前我们发起了一个Node.js开发者调查,这个调查让我们对大家如何使用语义化版本控制产生了一些见解。
Unfortunately, we found out that only 71% of our respondents uses semantic versioning when publishing/consuming modules. This number should be higher in our opinion - everyone should use it! Why? Because updating packages without semver can easily break Node.js apps.
可惜的是,我们发现只有71%的受调查者会在发布或者使用模块的时候使用了语义化的版本控制。在我们看来这个数字应该再高一些 —— 所有人都应该使用这个!为啥?因为缺少语义的更新包很容易的破坏Node.js应用。
对你的应用和模块进行版本控制时非常关键的——使用者必须要知道新版本的发布以及需要做哪些事来获取新版本。
Versioning your application / modules is critical - your consumers must know if a new version of a module is published and what needs to be done on their side to get the new version.
This is where semantic versioning comes into the picture. Given a version number MAJOR.MINOR.PATCH, increment the:
这种情况下语义化版本控制应运而生。使用版本号格式 MAJOR.MINOR.PATCH:
npm also uses SemVer when installing your dependencies, so when you publish modules, always make sure to respect it. Otherwise, you can break others applications!
npm在安装依赖的时候也是用了语义化版本控制,所以当你发布模块的时候要遵守这个规则。否则,你可能会破坏其他应用。
Securing your users and customers data should be one of your top priorities in 2017. In 2016 alone, hundreds of millions of user accounts were compromised as a result of low security.
保护你的用户与客户数据会是2017年最高优先级。仅在2016年,上亿的用户账号由于太低的安全性而受到威胁。
To get started with Node.js Security, read our Node.js Security Checklist, which covers topics like:
开始注意Node.js的安全,可以阅读这篇文章Node.js安全注意事项,包含了这些话题:
After you’ve embraced the basics, check out my Node Interactive talk on Surviving Web Security with Node.js!
这些基本的都看完之后,可以再看一下我们的Node互动讨论Node.js中网络安全问题
Serverless started with the introduction of AWS Lambda. Since then it is growing fast, with a blooming open-source community.
Serverless是由Aws Lambda引入的。从那之后Serverless发展地非常快,开源社区也迅速发展的。
In the next years, serverless will become a major factor for building new applications. If you’d like to stay on the edge, you should start learning it today.
接下来的几年,Serverless会成为构建应用的主要方式。如果你还在技术边缘,最好马上开始学习使用。
One of the most popular solutions is the Serverless Framework, which helps in deploying AWS Lambda functions.
最知名的解决方案之一就是Serverless Framework,它在发布部署AWS Lambda应用的时候可以发挥作用。
Attending conferences and meetups are great ways to learn about new trends, use-cases or best practices. Also, it is a great forum to meet new people.
参加会议是非常好的获取新资讯,使用案例和最佳实践的方式。而且,这也是遇见新的人的非常好的方式。
To take it one step forward, I’d like to encourage you to speak at one of these events as well!
向前一小步,我非常鼓励你能在这些大会上演讲。
As public speaking is tough, and “imagine everyone’s naked” is the worst advice, I’d recommend checking out speaking.io for tips on public speaking!
公开演说还是相当难的,而且“把每个人想象成裸体”是最差的建议,所以我建议看一下speaking.io作为发表公开演说的参考。
As 2017 will be the year of Node.js, we’d like to help you getting the most out of it!
因为2017会成为Node.js之年,我们非常希望你能成为他们中的一个。
We just launched a new study program called “Owning Node.js” which helps you to become confident in:
我们刚启动了一个项目叫做“Owning Node.js”的项目,帮助你:
点评:这篇文章没啥干货,severless可以研究一下。居然还给自己插了好几条广告,真是讨厌,还是2016那篇写得好。
]]>