Caddy 2 is a powerful, enterprise-ready, open source web server with automatic HTTPS written in Go
– Elixir and Phoenix Framework
First try of Live View from Phoenix
First released Published
Rails - 2004
Elixir - 2011
Phoenix - 2014
Erlang - 1986
show all mix command
mix help
install hex - a package manager
mix local.hex
install phx_new package
mix archive.install hex phx_new
create a phoenix project
mix phx.new hxdemo
basically 3 methods is used
def mount(_params, _session, socket) do {:ok, assign(socket, counter: 0)}end
def render(assigns) do ~L""" Counter <% @counter %> <button phx-click="inc">+</button> """end
def handle_event("inc", _params, socket) do {:noreply, assign(socket, count: socket.assigns.count + 1)}end
Your private PaaS platform
https://github.com/dokku/dokku
http://dokku.viewdocs.io/dokku/
# for debian systems, installs Dokku via apt-getwget https://raw.githubusercontent.com/dokku/dokku/v0.21.4/bootstrap.sh;sudo DOKKU_TAG=v0.21.4 bash bootstrap.sh# or installed by Dockerdocker run \--env DOKKU_HOSTNAME=dokku.me \--name dokku \--publish 3022:22 \--publish 8080:80 \--publish 8443:443 \--volume ~/dokku:/mnt/dokku \--volume /var/run/docker.sock:/var/run/docker.sock \dokku/dokku:0.21.4
# on the Dokku host# create an appdokku apps:create ruby-getting-started# install the postgres plugin# plugin installation requires root, hence the user changedokku plugin:install https://github.com/dokku/dokku-postgres.git# create a postgres service with the name railsdatabasedokku postgres:create railsdatabase# each official datastore offers a `link` method to link a service to any applicationdokku postgres:link railsdatabase ruby-getting-started
# from your local machine# SSH access to github must be enabled on this hostgit clone https://github.com/heroku/ruby-getting-startedcd ruby-getting-startedgit remote add dokku dokku@dokku.me:ruby-getting-startedgit push dokku master
fatal: ‘ruby-getting-started’ does not appear to be a git repository
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
cat ~/.ssh/id_rsa.pub | docker exec -i dokku dokku ssh-keys:add ryan
dokku plugin:install https://github.com/dokku/dokku-letsencrypt.gitdokku config:set --no-restart myapp DOKKU_LETSENCRYPT_EMAIL=your@email.tlddokku letsencrypt myappdokku letsencrypt:auto-renew myapp
https://github.com/dokku/dokku-letsencrypt
http://dokku.viewdocs.io/dokku/deployment/application-deployment/
http://dokku.viewdocs.io/dokku/getting-started/install/docker/
http://dokku.viewdocs.io/dokku/deployment/user-management/
Thanks for your listening!
]]>这时候一般会选择一台最便宜的云主机自搭一台做 CI 或 CD。
为什么不用第三方服务?
因为
说回到主题。配置这么低的云主机当然会有所限制,碰到的问题是编译打包 javascript 文件。
我用 docker build 都能编译 Dockerfile,却在 webpack 打包 JS 文件是内存不足!
1年前这个问题困扰我很久,我以为没有硬件问题办法解决,直接缴械投降了。
1年后看到别人的方案: 使用 swapfile 交换内存区来解决。嗯,真香现场啊。
马上复制一份留作后用:
# Allocate a file for swapsudo fallocate -l 2048m /mnt/swap_file.swap# Change permissionsudo chmod 600 /mnt/swap_file.swap# Format the file for swapping devicesudo mkswap /mnt/swap_file.swap# Enable the swapsudo swapon /mnt/swap_file.swap
原文链接:
https://medium.com/@dpaluy/the-ultimate-guide-to-dokku-and-ruby-on-rails-5-9ecad2dba4a3
Here is the extension list of VS Code I used for Ruby on Rails development.
Virtual Box
或者 VMware
来运行一个VM。常常让人觉得 Docker 对 OSX 的亲和力不够。
最近才看到有一个基于 OSX 原生底层的 VM 叫做 xhyve。我尝鲜了一把,虽然遇到了一个小坑,但是克服之后用起来感觉很不错,赶紧来看看吧。
brew install xhyve docker-machine-driver-xhyve docker docker-machine
如果之前有安装过 docker 系列的的,推荐升级或者重新安装一遍。
docker-machine create default --driver xhyveeval $(docker-machine env)
网上有推荐加 nfs 参数的,如果需要在host机和VM之间互传数据的话。
docker-machine create default --driver xhyve -—xhyve-experimental-nfs-share
docker psdocker run hello-world
参数 -v
带的本地 volume 映射不上去。
docker run -v .:/usr/app sh
运行后进入到 container 时发现并没有相应的文件。
执行
grep Virtio9p ~/.docker/machine/machines/default/config.json
如果该值是 false
改成 true
,再重启 VM 就解决了。
docker-machine restart
很好奇为什么不是默认开启?可以关注下官方的issue
使用 vhyme 后极大的降低了 VM 的开启速度。
他也极好的支持了使用 swarm
的来增加 node 的测试。我在后面篇幅将进行尝试。
因为这个私有库需要从公网进行访问,所以必然要做些鉴权的处理。我们简单实用 htpasswd 实现的 HTTP Basic Authentication
来进行鉴权。
使用以下命令来生成一个 md5 加密的密码文件
docker run --entrypoint htpasswd registry:2 \-Bbn testuser testpasswd > auth/htpasswd
你可以多次运行来添加多个账号密码。
因为 HTTP Basic Authentication
是以明文来传递密码信息的,所以需要Web应用打开 HTTP TLS。
具体的工作是我们要生成两个文件,可以通过 Let's Encrypt
来免费获得。
运行一下命令:
docker run -d \ -p 5000:5000 \ --restart=always \ --name registry \ -v "$(pwd)/auth:/auth" \ -v "$(pwd)/ssl:/ssl" \ -v registry:/var/lib/registry \ -e REGISTRY_AUTH=htpasswd \ -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ -e REGISTRY_HTTP_TLS_CERTIFICATE=/ssl/server.crt \ -e REGISTRY_HTTP_TLS_KEY=/ssl/server.key \ registry:2
执行后运行 docker ps
可以在输出中看到。
执行以下命令输入密码后尝试是否能成功。
docker login -u testuser yourdomain.com:5000
先给本地 image 加标签,再 push 到私有库即可。
docker pull hello-worlddocker tag hello-world yourdomain.com:5000/hello-worlddocker push yourdomain.com:5000/hello-world
就像使用共有库一样只是前面要加上私有库域名信息。
docker run yourdomain.com:5000/hello-world
]]>主要是为了满足自用,爱看书的朋友可以下载来试试,无广告哦~~
仿的是【追书神器】App,使用的也是他们的免费接口。
Update(2020-09-26): 追书神器的免费接口已经不能返回小说内容,现在已经基本不能使用这个APP了。
留存以后找其他书源。
最大的不同是【书山追更】使用了网页形式的直上直下的翻页形式,不像现在流行的仿实体书的翻页效果。
Vue
框架 wepy
Vue
的 mpvue
React
的 taro
。另外还有几个小众的:
因为之前比较熟悉Vue
,所以没有太费脑力就选了 mpvue
。
看了文档觉得虽然有所限制,比如:
感觉虽然有限制,但是好像没有特别大的关系。多费点代码还是能克服的。
谁知道,刚码了两个页面就遇到了坑。
倒不是某个框架的问题,搜了一下其他的框架基本也都有这些问题。
还没有具体看过 mpvue
源码,但是看了前人介绍,有些是因为小程序的特色所导致的。
比如为什么页面再次打开会有之前的状态值存留。
个人感觉以下内部世界被割裂成了3个世界:
mpvue 对象 -> 小程序Page对象 -> View render
原生的 Page 里 通过 setData
把数据传输给视图进行显示,
当页面关闭后,页面对象消失,但是 mpvue
对象会重复使用,
每次页面重开时会使用之前的 mpvue 对象,造成了数据的残留。
理想状态是 Page 关闭时,销毁 mpvue 对象,打开时再重新生成。这样整个生命周期就与预期一致了。
正因为以上的原因,造成了很多意想不到的问题(keng)。
也探索了一些解决方法:
问题1:数据残留
export default { data () { return { a: 23 } }}
对策:onUnload里重置初始值
export default { data () { return { a: 23 } }, onUnload () { Object.assign(this.$data, this.$options.data()) }}
这里的 this.$options
是Vue
内部使用的属性,保留着初始的对象值。
可以把他写成一个Vue plugin,这样就可以省却每个页面都要写的尴尬。
问题2:computed 内获取的参数不会变化
export default { computed: { id () { return this.$root.$mp.query.id } }}
对策:写入 mounted 里,赋值到 data
对象内
export default { data () { return { id: null } }, mounted () { this.id = this.$root.$mp.query.id }}
问题: 组件文件名不要使用HTML标签名
组件名用了 article, 引入时不报错,但是组件内部模板不显示,也不报错。
即使引入时改了,也还是不显示:
import Article2 from './article'
对策: 为了减少麻烦,一定要使用非HTML标签名,引入时也最好名称一致。
问题4:自定义组件时写的事件处理函数不起作用
<template lang='pug'> .container tag1( @click='handler' v-for='item in items' :key='item.id' )</template>
对策:如果想再调用时处理事件的话,需要在自定义内部通过$emit
发出来,
自定义组件所有的事件都需要自己发射到父组件。
暂时就先记这么多,后续有再跟进。
多说一句: mpvue
是基于 Vue
2.4.1 基础上开发的,所有 2.4.1 以上的功能就不要去用了。
rxjs
,想尝试下用 rxjs
改写微信官方小游戏。rxjs
是响应式编程的 Javascript 库。
响应式的概念就是声明式编程的方法,代表的有 React, Vue。 在我理解他与传统的命令式编程方法最大的区别在于: 不是根据事件来处理每个逻辑,而是先设置好每个事件的 flow,只要有一个事件触发了,那么这些事件会带动触发其他事件,再接着触发其他事件,最后汇集到一个状态量中。而 View 上的更新是只更根据状态做显示。
除了现在流行的使用类来组织代码,还有一种完全不同的代码组织方式,那就是函数式编程。
这可能是另外一个话题,但是有趣的是使用 rxjs
你可以完全只使用函数来写代码。
不再啰嗦,看代码。
const clock$ = interval(1000 / FPS, animationFrameScheduler)const playerSubject$ = new BehaviorSubject()const bg$ = {...}const player$ = {...}const enemies$ = {...}const bullets$ = {...}const explosions$ = {...}const collision$ = {...}const restart$ = {...}const state$ = merge(bg$, player$, enemies$, bullets$, collision$, explosions$, restart$) .pipe( startWith(getInitState()), scan((state, reducer) => reducer(state)) )bgmAudio.play()state$.subscribe(function(state) { playerSubject$.next(state.player) render(state)})
状态流是由上面提到的背景,敌机,飞机,子弹等流合成,在 subscribe
的回调函数里面,只要根据最新的 state
画图就可以了。
function getInitState() { return { bg: { img: bgImg, width: BG_WIDTH, height: BG_HEIGHT, top: 0 }, player: { img: playerImg, width: PLAYER_WIDTH, height: PLAYER_HEIGHT, x: screenWidth / 2 - PLAYER_WIDTH / 2, y: screenHeight - PLAYER_HEIGHT - 30 }, enemies: [], bullets: [], explosions: { images: aniImgs, frames: [] }, gameover: false, score: 0 }}
与传统的类不太一样,现在前端开发中特别是单页应用中越来越用到一个概念就是要有一个全局的状态来管理共享数据,来起到所有组件内同步更新的作用。这里也沿用了这种方式,特别是在调试的时候可以很方便的查看游戏内部的状态。
下面我们再来细看每个流:
每秒60帧,每个时钟单位约16.67毫秒,这是大部分游戏的设置值。帧率越大画面越流畅。
const clock$ = interval(1000 / FPS, animationFrameScheduler) .pipe(share())
这里用到一个 share()
,是因为这个时钟流会多次用到,所以做了共享。
地图背景的更新,每个时钟单位 top 值增加 2 个像素值
const bg$ = clock$ .pipe( map(() => state => { if (state.gameover) return state state.bg.top = state.bg.top > screenHeight ? 0 : state.bg.top + 2 return state }) )
飞机位置的移动要响应触屏移动,而触屏时需要根据飞机当前值来确定要不要触发移动,所以需要当前的状态,而状态又是由飞机流来生成的,是一个循环。如果碰到这种循环的情况要用到 Subject 来解决, Subject 相当于一个中介的概念。内部是一个 pub
sub
模式。
const playerSubject$ = new BehaviorSubject()const touchstart$ = fromEvent(canvas, 'touchstart').pipe(share())const touchend$ = fromEvent(canvas, 'touchend')const touchmove$ = fromEvent(canvas, 'touchmove')
飞机的移动只在触屏时触发,所以由 touchstart$ 作为发起源,当手指在飞机的触屏移动是, playerMove$ 就会发出当前的位置
const playerMove$ = touchstart$.pipe( withLatestFrom(playerSubject$), filter(([ev, player]) => { return checkIsFingerOnAir(ev, player) }), mergeMap(() => { return touchmove$.pipe(takeUntil(touchend$)) }), map(ev => ev.touches[0]), // tap(console.log),)
player$ 根据触屏的位置来改变飞机的位置
const player$ = playerMove$ .pipe( map(({clientX, clientY}) => (state) => { if (state.gameover) return state const {player} = state player.x = range(clientX - player.width / 2, 0, screenWidth - player.width) player.y = range(clientY - player.height / 2, 0, screenHeight - player.height) return state }) )
子弹流比较简单,每个 clock 单位改变已有子弹的y值,但超出屏幕时从列表内删除,每 20 个 clock 单位产生新的子弹。
const bullets$ = clock$.pipe( map(frame => state => { if (state.gameover) return state const player = state.player // 移动子弹 state.bullets.forEach(function(bullet, index) { bullet.y -= 6 if (bullet.y < -BULLET_HEIGHT) { state.bullets.splice(index, 1) } }) // 动态生成 if (frame % 20 === 0) { state.bullets.push({ img: bulletImg, x: player.x + player.width / 2 - BULLET_WIDTH / 2, y: player.y - BULLET_HEIGHT, width: BULLET_WIDTH, height: BULLET_HEIGHT }) bulletAudio.play() } return state }))
爆炸动画的流,由新产生的爆炸流,和改变显示的图片来完成。爆炸由碰撞检测产生,这里用了 explosionSubject$ 来做中转。当所有的爆炸图片显示完时,从列表移除,不再显示。
const explosionSubject$ = new Subject()const newExplo$ = explosionSubject$.pipe( map(enemy => state => { const frame = Object.assign({}, enemy, {index: 0}) state.explosions.frames.push(frame) return state }))const exploPlay$ = clock$.pipe( map(() => state => { for (const frame of state.explosions.frames) { frame.index += 1 frame.y += 6 } state.explosions.frames = state.explosions.frames.filter(frame => frame.index < state.explosions.images.length) return state }))const explosions$ = merge(exploPlay$, newExplo$)
敌机流也比较简单,每 30 个 clock 单位新产生一个敌机,每个 clock 单位移动
const enemies$ = merge(clock$) .pipe( map(frame => state => { if (state.gameover) return state // 移动敌机 state.enemies.forEach(function(enemy, index) { enemy.y += 6 if ( enemy.y > screenHeight + enemy.height) { state.enemies.splice(index, 1) } }) // 生成 if (frame % 30 === 0) { state.enemies.push({ img: enemyImg, x: rnd(0, screenWidth - ENEMY_WIDTH), y: -ENEMY_HEIGHT, width: ENEMY_WIDTH, height: ENEMY_HEIGHT, speed: 6 }) } return state }) )
碰撞流做了两个检测,一个检测子弹是否击中敌机,如果击中,则 explosionSubject$.next(enemy) 下,让爆炸显示。另一个则是检测敌机与我方飞机的碰撞检测,如果击中,则游戏结束。
const collision$ = clock$.pipe( map(() => state => { if (state.gameover) return state state.bulltes = state.bullets.filter(bullet => { state.enemies.every((enemy, index) => { if (isCollideWith(enemy, bullet)) { state.enemies.splice(index, 1) boomAudio.play() state.score += 1 explosionSubject$.next(enemy) return false } return true }) }) for (const enemy of state.enemies) { if (isCollideWith(state.player, enemy)) { state.gameover = true break } } return state }))
重新游戏流,检测是否在游戏终止时,点击了重新游戏这个按钮,是则重置游戏状态,开始新游戏
const restart$ = touchstart$.pipe( filter(ev => { const {clientX, clientY} = ev.touches[0] return ( clientX > btnArea.startX && clientX < btnArea.endX && clientY > btnArea.startY && clientY < btnArea.endY ) }), map(() => state => state.gameover ? getInitState() : state))
所有以上的流 merge 之后通过 reducer 来完成每次状态的更新。我们这里所有的流发出的不是一般的数据,而是函数,所以当接收到函数时,我们把当前 state
传入这个函数中去,更新状态里相应的数据,返回新的状态。而状态流放出的则是当前的最新状态值。
const state$ = merge(bg$, player$, enemies$, bullets$, collision$, explosions$, restart$) .pipe( startWith(getInitState()), scan((state, reducer) => reducer(state)) )
订阅这个 state$ 状态流,每时钟单位根据状态值画图。
其中
playerSubject$.next(state.player)
这行代码是通知之前的 playerSubject$ 来中转更新飞机位置
bgmAudio.play()state$.subscribe(function(state) { playerSubject$.next(state.player) render(state)})
游戏在我的小米Note3手机的微信测试了下,帧率一般在56 ~ 60 左右,可以看到,使用 rxjs
是可以写游戏代码的。
代码通过 webpack
打包后,832k ,有点大,但是离微信的上限4M还是很大空余空间的。
rxjs
可以改变代码的编写方式,甚至思维方式。数据在各个流之间进行流动,最后汇集到一个状态内,我们只要管理状态到视图的更新显示,感觉整体的结构还是很直观的。
与原来的代码比较,原来的代码游戏在 Game over
时有bug,手指还是可以拖动飞机的移动, rxjs
版不会有这个问题。
感觉 rxjs
的潜力还没有被挖掘出来,可能在处理多人联机游戏的 rxjs
带来的好处将更大。
本篇例子中完全舍去了类的使用,只使用到了函数。但是重点不在这里,完全可以使用类结合 rxjs
来使用,毕竟 es6
支持了类,当然要发挥它的长处。
后奉上源码。
先说下大体思路:
升级Image很简单,只要从服务器拉下来最新的数据就行了
docker pull quay.io/sameersbn/redmine:latest
到工作目录 /data, 使用 docker-compose 来关闭级容器
cd /datadocker-compose stopdocker rm data_redmine_1
因为代码升级,新增加了些环境参数的配置
REDMINE_SECRET_TOKENDB_NAME
这两个一个是log提示我的,另一个发现服务升级完后,旧用户登录不了了,查了原因才知道是因为数据库名称的默认值变了。
可能受影响的还有其他不少参数,需要在详情页仔细看看。
docker-compose start
docker exec -it data_redmine_1 bashcd $WORKDIRRAILS_ENV=production bundle exec rake db:migrate
]]>h(x) = XW + b
方阵尺寸
X = (100,784), batch size: 100, 特征尺寸: 784
W = (784,10), 特征尺寸,输出个数
b = (10,1), 输出个数,1
h = (100,10) batch size, 输出个数
softmax,确定图像是属于10个数字中的哪一个
tanh, sigmoid, relu,深度神经网络中推荐使用 relu
输出值范围
交叉熵 Cross entropy:
- reduce\_sum(Y\_ * log(Y))
梯度下降算法: SGD
步长: 0.01, 太大速度快但可能取不到最精确值,太小则速度慢
缺点: 步长选择比较困难
下降初期能够很好的加速
前期放大梯度,后期约束梯度
依赖人工设置全局学习率
Adagrad的优化,不需要人工设置全局学习率
训练初中期,加速效果不错,很快
训练后期,反复在局部最小值附近抖动
Adadelta 的一个特例
本质上是带有动量项的RMSprop
max pooling, 比如 5x5 的池中取最大一个值作为输出值
步长的意义在于缩小特征尺寸。比如输入 28x28 的方阵如果使用 2x2 的步长,则输出的方阵缩小为 14x14
在建立深度学习的层中,一般是缩小特征尺寸,增大通道大小。整个特征个数是呈现变小的趋势。
No. 方阵 kernel 和通道 步长1 28x28x1 5x5,1,4 1x1 2 28x28x4 4x4,4,8 2x2 3 14x14x8 4x4,8,12 2x2 4 7x7x12 全连接 5 200 softmax 6 10 用于10个数字
比如手写的数字 4 个通道是比较小的,需要增加到6个,是个经验值
No. 方阵 kernel 和通道 步长1 28x28x1 5x5,1,6 1x1 2 28x28x6 4x4,6,12 2x2 3 14x14x12 4x4,12,24 2x2 4 7x7x24 全连接 5 200 softmax 6 10 用于10个数字
https://zhuanlan.zhihu.com/p/22252270
http://ycszen.github.io/2016/08/24/SGD%EF%BC%8CAdagrad%EF%BC%8CAdadelta%EF%BC%8CAdam%E7%AD%89%E4%BC%98%E5%8C%96%E6%96%B9%E6%B3%95%E6%80%BB%E7%BB%93%E5%92%8C%E6%AF%94%E8%BE%83/
https://yq.aliyun.com/articles/250639
https://www.tuicool.com/articles/vieuIbi
https://www.tuicool.com/articles/bayI7ne
其实用 SMTP 服务器的话,都是用 smtp 协议来发邮件,都是文本形式的协议,协议其实挺简单的。
总的来说3个大步骤
详细来说一下这个过程:
HELO
helo qq.com250 smtp.qq.com
AUTH base64编码的用户名,密码,每个单独一行
auth login334 VXNlcm5hbWU6aW5mb0BqaWFveHVlYmFuZy5jb20=334 UGFzc3dvcmQ6xxxxxxxxxxxxxxxxxxxxxxxxxxx235 Authentication successful
MAIL FROM:
mail from: info@jiaoxuebang.com250 Ok
RCPT TO:
rcpt to: ryan@jiaoxuebang.com250 Ok
DATA 邮件数据,包括邮件头部,正文, 以 换行.
换行 结束
data354 End data with <CR><LF>.<CR><LF>subject: testtest.250 Ok: queued as
250 显示已经进入队列等待发送了。
]]>DLL
接口,如果用 C
来写固然是可以的。Ruby
来怎么对接。基本上网上推荐了2种方法:
fiddle
ffi
fiddle
是 Ruby
标准库自带的,用法比较简单,但是官方文档特别少。ffi
是一个独立的 Gem
,他是基于 libffi
的一个外部扩展来实现的,官网文档比较多。
感觉 fiddle
使用比较简单,我用的 DLL
也不是很复杂,就只试了 fiddle
。
首先推荐看下这篇基础教程 ,大体就知道怎么用了。
但是很快就遇到了一个问题,int[]
这种数组怎么传过去。
搜了下,看到了这篇文章,试了一下用 a.pack('i' * a.size)
方法的确可以。
遇到的第二个问题是如何传 UNICODE
字符串,搜了一下没有搜到。
于是我就想说不定 ffi
这边的文档会有些提示。
果然官方Wiki
里有一篇例子讲到了,使用需要encode('UTF-16LE')
一下,
有一点我试的时候不在末尾加 \0
也是没问题的。
1 | require 'fiddle' |
从 Octopress -> middleman-blog -> Hexo,博客没写几篇引擎倒是换的很勤,自嘲一下!
自从 2014 年切换到 middleman-blog, 已经有3年时间了,但是 middleman-blog
发展却停滞了。
究其原因,我想到了几点:
我之前还提交过一个 PR 给 middleman-blog
,
没想到新版本更新后竟然被忽略掉了,有点莫名其妙。
说下体验
bower
来管理的,现在主流已经换到 npm
了。所以想换一个基于 nodejs
的微博引擎,其实我是想用基于 nuxt
的,但是目前还没有,所以用了 hexo
,专门用于生成博客的,毕竟术业有专攻,没有什么坏处。
如果按 官方Migration 来, 出来的网址是这样的:
http://localhost:4000/2017/09/03/2017-09-03-title
但是我们要的是这样的:
http://localhost:4000/2017/09/03/title
所以还要配置
#permalink: :year/:month/:day/:title/permalink: :year/:month/:day/:post_title/
感觉 Hexo
3 出来后文档有点乱。这个也是我看了源码查到的,之前烦恼了好几天。
其他主要就是自己选个 主题 就行了,我选的是 next 主题。
为了网址加上 RSS, 搜了下需要要加 next-generator-feed
,但是加了后会报错,说找不到 process-nextick-args
,于是 npm i process-nextick-args –save
总体感觉 Hexo
小问题比较多,但是社区是很活跃的,Star 有 18k,希望以后不用再换博客引擎了。
本篇准备用一个案例来说明 ImageMagick
的用法。
以下为每个处理的命令
假设 图片名为 base.png
去掉白色背景改为透明,叠加后可显示底下图片
convert base.png -transparent white base.nobg.png
复制一张同样大小的白色图片
# 用白色填充全部范围convert base.png -fill white -draw 'rectangle 0,0 6400,6400' base.white.png
切分白色图片为8x8大小的图片
# +repage 用于消除虚拟画布大小convert base.white.png -crop 12.5%x12.5% +repage sp-%02d.png
在每张小图片中间添加红色序号文字,依次为00 ~ 63
for f in sp-*.pngdo no=${f:3:2} convert $f -gravity center -fill red -pointsize 30 -annotate 0 $no label-${no}.pngdonerm sp-*.png
合并小图片为一张大图片
# 增加 -goemetry +0+0 参数是为了不添加padding# -tile 8x8 指定 8行8列montage label-*.png -goemetry +0+0 -tile 8x8 concat.label.pngrm label-*.png
叠加透明图片到有序号的图片上
composite base.nobg.png -gravity center concat.label.png base.label.png
切分叠加后的图片为 8x8 的64张图片
convert base.label.png -crop 12.5%x12.5% +repage sp-%02d.png
合并打乱顺序后的小图片为一张图片
montage `ls sp-*.png | ruby -e 'puts STDIN.readlines.shuffle'` -goemetry +0+0 -tile 8x8 out.pngrm sp-*.png# shell 没有可以打乱顺序的命令,这里使用了ruby的一行代码来搞定
ssh
相关的工具了,比如 scp
, rsync
。因为他们都是建立在传输层基础上的安全协议。ftp
则不然,因为他们访问主机时的登录交互容易被窃听,有泄密的可能性。ftp
也有对应的安全版本 sftp
, 但是用的就很少了,因为 sftp
也是基于 ssh
的,但是 ssh
自带传输特效,所有 sftp
就很少用的了。本篇为什么要写 ftp
,是因为对接老的系统的时候,是超出我们控制的。
最近的项目中就需要把本地的数据包传到服务器上,因此找了几个 ftp
工具来分析了下。
主要的需求是
找了3个来分析了下功能 ftp
, ncftp
, lftp
ftp
首先当然是用系统自带的 ftp
,
put
命令不能指定目标路径,需要先登录后 cd
到目标路径mput
密码可以带入 user@pass:host
中,放在 ~/.netrc 里面
在 ~/.netrc 里的格式是
machine host/ip login USERNAME password PASSWORD
ncftp
ncftp
可以满足上面的所有需求
上传整个目录,包括子目录
ncftpput -u user -p pass -R host /host/dir /local/dir
上传到指定路径
ncftpput -u user -p pass host /host/dir /local/path
隐藏密码
提示输入密码
ncftpput -u user host /host/dir /local/path
放入配置文件
ncftpput -f login.cfg host /host/dir /local/path
login.cfg 文件是这个格式
host example.comuser USERNAMEpass PASSWORD
lftp
lftp
满足了我的所有需求
镜像整个目录,包括子目录
lftp -e 'mirror -R /local/dir /host/dir' host
上传到指定路径
lftp -e 'put -O /host/dir/ /local/path; bye' host
密码方式与 ftp
相同
使用 ncftp
或者 lftp
登录后,都可以使用上下键历史命令回显,Tab
键显示候选文件,文件补全等功能。他们的功能比较相近,由于满足了我要的需求,没有再深入比较了。
tail -f
已经很难分析出有用信息了。即使这样用有时候也会比较麻烦,因为当你的应用服务器增多时,每个服务器都是单独写到本机日志文件内,造成了分析日志的难度。
这个时候就很需要一个工具来收集各个服务器日志来统一处理。
今天说的就是有关于这个问题的解法。
日志收集的工具也有不少 Flume,storm。 今天说的是 ELK stack 这个套件。这个套件由 3 个部分组成:
简单地说: Logstash
用来收集数据;ElasticSearch
存储数据;Kibana
是表现层,Web 接口用来对接用户UI。
相信 ElasticSearch
大家可能听说的,是做全文搜索的,跟 Solr
差不多是基于 Lucene
来做的。他有几个特点:
当然 Solr
现在也可以放到多台服务器上组成集群了,但是 ElasticSearch
从一开始就是这么设计的,这种与生俱来的特性与后天修补出来的体验是否一样,你懂得。
ElasticSearch
在 ELK
套件中处于数据存储的身份,无疑是最核心的。
Logstash
可以单独使用,比如把各个服务器的日志收集起来集成到一个日志文件内,这样只要看一个日志文件,就不需要费力到每个服务器查看日志了。
当然结合今天介绍的套件使用无疑是最强大的。
Logstash
作为输入接口,他有很多插件可用,对接文件,对接网络接口,甚至对接 ElasticSearch 作为输入。
比如在我们的例子中,要收集多台日志文件,Logstash 可以开 TCP 端口,各个日志服务器使用 filebeat 代理来检查日志输出,一旦有新的日志输出到文件,filebeat
就会把该内容发送到 Logstash
配置的端口内,这样就完成了日志的收集工作。
Logstash
在收集了日志后,还可以对数据进行 分析,拆解成多个字段,再输出到 ElasticSearch
中,这样就能使用 ElasticSearch
强大的功能对这些字段进行搜索了。
Kibana
作为最后的用户 UI 接口,支持了很多分析的功能。比如分析每天的PV情况,一天内访问的高峰与低谷,异常攻击情况,瓶颈的处理等。
下面是配置的 Rails4 日志的分析结构
1 | # Rails4 log prefix |
补充要说的是,时间戳默认是以 Logstash
收到的日志数据的系统时间为每条日志时间戳的,这与时间日志发生的时间并不一致,
需要抽出日志内容里的时间为日志时间戳,这个需要在以下的处理:
1 | data { |
完整的Logstash
配置文件例子
1 | input { |
最近项目里要把 Redmine 从盛大云到阿里云,正好可以发挥 Docker 的强项。这里分析一下需要实现的功能:
虽然 gitolite 也有docker 提供,但是 redmine 需要访问 git 下面的 repo 文件,所以会有权限问题。
Docker 现在对 权限映射 这方面的支持还不是很好。所以我决定把 gitolite 安装在宿主机器内部。
所以 gitolite 按照官网的安装步骤安装。旧的 repo 数据只要复制覆盖 /home/git/repositories 就可以了。
在 Docker hub 里搜了一下可用的 Redmine 之后,
发现 sameersbn/redmine 版本 比官方版本 多了很多星,
连下载量也比官方多了一倍不止。比较了下内容之后发现官方版无法支持外发邮件,所以只有选择 sameersbn
版了。
如果是完全新安装不需要迁移数据的话,可以直接使用提供的样例 docker-compose.yml 文件了。
所以 Redmine 这部分还是比较简单,使用了
1 | redmine: |
USERMAP_UID=1001 USERMAP_GID=1001
1001 是 git 用户的 uid, gid。restart: always
让这个 container 自启动上面的样例中数据库使用的是被定制化过的 Postgres ,但是我觉得官方的版本完全够用了。
迁移的策略是这样的,
先用 docker 启动一个最基本的 postgres image: docker-compose up -f /data/pg.yml
postgresql: image: postgres environment: - POSTGRES_USER=redmine - POSTGRES_PASSWORD=secret - PGDATA=/data volumes: - /data/pg:/data
开启一个 postgres client 导入数据库旧数据,默认数据库名字是redmine
$ docker run --link data_postgresql_1:db -v /data:/data -it postgres bash$ psql -h $DB_PORT_5432_TCP_ADDR -U redmine redmine < /data/pg_old_data.sql
链接 Redmine docker 到 postgresql docker 就可以完工了。
完整的 docker-compose.yml 文件
1 | postgresql: |
按我的理解,Docker 架构在服务器之上,从服务器上多衍生出了一层,
所以可以跨平台运行在各个系统之上,达到一致的用户体验。
并且 Docker 可以快速导入一个定制好系统,
比如可以把开发人员的系统环境复制一份给测试人员使用,体验真的很好。
Docker 发展很快,但我觉得就目前的阶段还是不太适合商用环境,
毕竟真正商用时是多主机配合工作的,这点上 Docker 还有很大的空间需要完善。
作为一个开发,测试用环境或者小范围商用时是 Docker 的确带来了巨大的用户体验。
在这篇博文里简单记录下 Docker 如何与 Rails 一起配合使用。以下是在 Mac OSX 的环境里使用时为例。
brew install boot2docker
boot2docker up
返回结果
Waiting for VM and Docker daemon to start....................oooooooStarted.Writing /Users/ryan/.boot2docker/certs/boot2docker-vm/ca.pemWriting /Users/ryan/.boot2docker/certs/boot2docker-vm/cert.pemWriting /Users/ryan/.boot2docker/certs/boot2docker-vm/key.pemTo connect the Docker client to the Docker daemon, please set: export DOCKER_HOST=tcp://192.168.59.103:2376 export DOCKER_CERT_PATH=/Users/ryan/.boot2docker/certs/boot2docker-vm export DOCKER_TLS_VERIFY=1
这个命令实际上时启动了一个 Virtual Box
虚拟机,跑了一个 Linux 内核的系统。
如果你是使用的是一个 Linux 内核的电脑,就可以少这一部分开销了。
复制上一步的返回结果到终端上执行。如果有多个终端需要使用时,每个都要设置这些系统变量
export DOCKER_HOST=tcp://192.168.59.103:2376export DOCKER_CERT_PATH=/Users/ryan/.boot2docker/certs/boot2docker-vmexport DOCKER_TLS_VERIFY=1
docker run --name my-postgres -e POSTGRES_PASSWORD=postgres -d postgres
这里把 postgres 的密码设为 postgres, 可以配合在 Rails app 厘米使用这个账号。
如果第一次执行该命令,Docker 会先从社区共享的 Image 里下载 postgres 最新的数据,
来创建 PostgreSQL 容器,所以需要花费几分钟时间。
docker ps
返回结果
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES139f96ea6d0e postgres:latest "/docker-entrypoint. 2 minutes ago Up 2 minutes 5432/tcp my-postgres
在终端进入 Rails 主目录,
修改 config/database.yml 文件里数据库的设置
host: ENV['DB_PORT_5432_TCP_ADDR']username: postgrespassword: postgres
DB_PORT_5432_TCP_ADDR
这个环境变量是在 Docker 链接数据库容器到 Rails 应用容器时自动设置的,通过该地址可以找到数据库
生成 Dockerfile 文件,供 build 使用
echo 'FROM rails:onbuild' > Dockerfile
执行 build 来生成 docker image
docker build -t my-rails .
docker run --name my-bash --link my-postgres:db -v "$PWD":/usr/src/app -it my-rails /bin/bash
此时已经进入了 Docker 容器内的一个交互环境下,我们可以运行 Rails 命令来生成数据表结构
rake db:create db:migrate
要退出容器时,运行 exit
就可以了
docker run --name my-rails --link my-postgres:db -v "$PWD":/usr/src/app -p 3000:3000 -d my-rails
open http://192.168.59.103:3000
为什么是 192.168.59.103
地址而不是 localhost
?
因为 Docker 实际上是运行在虚拟机里面的,所有要访问虚拟机的 IP 才可以访问到。
这个地址是在运行 boot2docker up
时的返回结果里面有显示。