文章

"知人者智,自知者明"

引言

时间如白驹过隙,转眼间已然2019,最近到处都是各种总结性文章,回首自己的2018诸如此类,于我而言,对于这过去的一年挤一挤,不说几万字,大几千字还是能水出来的,然而我不写,至少现在不写。距离上一篇杂记差不多一个月出头,按照步调这应该是过去的这一个月中在工作学习中一些积累的记录,实际也是如此,至少在敲下这段文字时我确实是这么想的,相对以往来说,这篇文章敲的比平常晚很多,是说这一个月没什么好记的么?不,正相反,这个月还是尝试了一些东西的比如说:

  • 消息队列:用Nodejs连接activeMq和reabbitMq及应用。
  • 基于Nodejs的VUE SSR解决方案,nuxt。
  • 根据注释生成文档:常见的一些根据注释生成文档工具的考察和apiDoc的使用。
  • typescript和jest的实际应用带来的一些问题及解决。
  • 各种杂七杂八的技术文章的汇总,语法糖,线程安全与NodeJs进程管理,http2.0 及一些看完就忘现在没法立刻想起来的东西。

随便一条即使只是浅浅的提一下,水一篇文章也是足够的,同时现在网上也的确有很多这类文章,最近我也看了不少,然而不得不说大部分这类文章用处实在有限,除了特色的大量的一模一样或者复制不全的文章外,优质的或者说有参考价值的文章很少,很多入门文章碍于双方的水平,对于完全不懂时来说能起个快速入门的作用,但是想应用于工程实践时反而是南辕北辙,但也不是说它们一无是处,这些针对没有官方中文文档的库的N手资料中有时会有一手资料的链接,这对于英文搜索能力还不是很强的人(比如我)实在是帮大忙了,阅读能通过各种软件手段辅助,搜索这个就真没辙了,关键字很难找准。
扯远了,简单来说,在实践中,没有比翻文档更好的解决问题渠道,而自己总结性敲一遍之后,遇到问题大概率还是要以翻文档为主,那我敲总结性文字意义何在?浮现出这个念头后,再动手敲总结的动力不可避免的降低了。那么为什么我又坐在这里水一篇不是很有参考价值的文章了呢?是因为想通这种笔记的意义仅仅是记下来?还是将意义寄希冀于会帮到不知道存不存的哪个角落里的萌新?亦或者只是机械的顺着习惯又写开了?
我想没什么大道理,与是否有价值无关,仅仅只是:“我愿意!”。
其实最初写博客的初衷似乎是提升语文表达能力来着,现在能水这么长的引言也算是有所进步了吧。
回归正题,这篇笔记分为两个部分:

  • reabbitMq的一些个人理解
  • N手资料的再整理利用(杂七杂八的文章的阅读杂记)

reabbitMq面面观

mq(mesage queue)也就是常说的消息队列,是指进程间通信(IPC)的一种形式,除了消息队列外,常见的通信形式还有信号(signal),管道(pipe),套接字(socket)什么的,比较起来,它比信号和管道传递的信息量要多,同时它是异步的。这使它可以应用在更复杂的场景中,不过异步的也意味着接收方往往需要通过类似轮询的方式来获取信息。
常见的实现有:activeMq,rabbitMq,kafka,rocketMq等等..你要乐意也可以把redis当消息队列使。
最近我尝试了activeMq和rabbitMq结果发现,activeMq支持的协议比较多,花样也很多,然而,Nodejs中目前并没有好用的库,可见用Nodejs的使activeMq的估计不多。相对来说,rabbitMq在易用性上要强的多,别的不说,官网的示列就包括Nodejs,其它大多数activeMq能做的,它也能做。

安装及使用

reabbitMq的安装:略
Nodejs中的使用:

    npm install amqplib --save

amqplib 这个库提供两个风格的写法,一种是基于callback的,另一种是基于promise的,个人比较习惯基于promise的。
简单的列子:

const amqp = require('amqplib');
let client = amqp.connect('amqp://localhost');
let q = 'tasks';
// 生产者
client.then(function(conn) {
  return conn.createChannel();
}).then(function(ch) {
  return ch.assertQueue(q).then(function(ok) {
    // 发送一段字符串,注意不管传递什么都必须转成Buffer
    return ch.sendToQueue(q, Buffer.from('something to do'));
  });
}).catch(console.warn);
// 消费者
open.then(function(conn) {
  return conn.createChannel();
}).then(function(ch) {
  return ch.assertQueue(q).then(function(ok) {
    return ch.consume(q, function(msg) {
      if (msg !== null) {
        console.log(msg.content.toString());
        //如果不发送确认,下次再有人来取依旧会取到值,可以在发送的时候通过设置ack:false 来取消
        ch.ack(msg);
      }
    });
  });
}).catch(console.warn);

工作模式说明

这个列子是基本的工作队列(Work queues)的工作模式,rabbitMq具体有以下几种模式。

  • 工作队列(Work queues):异步发送接收,也就是说发布的时候如果没有消费者会先压在队列里,当有新的消费者订阅时会一次性把队列中压着的信息都取到,如果消费者没有发送确认,还有别的新的消费者的话依旧会获得所有的信息。如果同时有多个消费者同时在订阅的时,生产者发送多条信息,消费者会轮流收到信息。
  • 发布订阅模式(Publish/Subscribe): 同步发送接收,如果生产者发布时没有消费者时信息会丢,同时有多个消费者时,生产者发送的信息会同时通知所有的消费者。
  • 路由(Routing):基本和发布订阅模式一样,区别在于,消费者可以有选择的订阅同一个任务的不同子频道,生成者发布的时候可以根据不同的子频道发送。
  • 主题(Topics):基本与路由模式一致,区别在于子频道可以很复杂,不是单纯的字符串而时一些描述(主题)。
  • 远程调用(RPC):这个我就没看了..可以用来做微服务。

以上是对rabbitMq的工作模式的简单介绍,介于鄙人才学疏浅不能保证理解一定无误,一会附上rabbitMq官网例子的连接,里面有各个模式的各种语言示例,虽然备注说不能直接用于生产环境中,但是个人感觉结合amqplibd的API文档处理处理也就差不多了。
本来想附上我根据官网改的列子,后来想想算了吧,和官网的差不多只是换了自己习惯的写法,什么错误处理,断线重连之类的也没做,让别人看可能不如直接看官网的列子。

参考资料

这是这一节介绍中最有价值的部分,基本使用中遇到的问题都能在这两个连接中得到解决。
官网示例:get started width RabbitMq
amqplibAPI文档:amqplib API

阅读杂记

随着年岁的虚涨记性是一年不如一年,而平时日常工作学习中接触到的知识也越发趋向于碎片化,经常会有一个不怎么常用的知识点多次重复的去查搜索引擎或文档的情况,这类知识都具备并不难理解,非常必要,但并不常用。对于这些东西,我会习惯性的记个小tip,或者干脆用一次查一次。前者如果不定期整理,最后会很尴尬,后者如果在一个相对长的时间粒度后去查会发现一切都是那么陌生...而且,即使是恭维也很难说这个方法很好用....
闲言少叙下面开始记录。

通过rsync来实现文件传输

不知道什么原因,我通过将树莓派上的文件夹设置成共享文件复制大文件的时候总是卡住不动,后来选择通过scp来进行文件上传下载依旧是会出错。于是我又找了一个方法,通过rsync来实现文件传输和同步。

# 下载单文件并重命名
rsync -P --rsh='ssh -p 22'  [email protected]:/home/pi/Downloads/download.zip /mnt/d/test.zip 
# 下载文件夹
rsync -avz --rsh='ssh -p 22'  [email protected]:/home/pi/Downloads/ /mnt/d/Download/  
# 目录上传
rsync -avz ./libserver1.0.1 [email protected]:/home/cxf/backup  目录上传
# 文件上传
rsync -avz ./libserver1.0.1./* [email protected]:/home/cxf/backup 文件上传

总的来说用这个传输该慢还是慢但至少不会卡住了....

基于SSH的正向代理和反向代理

ssh是我个人感觉是最低成本的内网穿透方式了,虽然简单好用,但是论传输速率一类的可能并不理想,网络环境比较差的时候基本不能用。ssh自身是不会断线重连的,不过解决这个可以通过使用autossh。

# 反向代理(将本地8082端口代理到192.168.10.1的8072端口上)
ssh -fCNR 8072:localhost:8082 [email protected]
# 正向代理(将只有本地能访问的8072端口代理到8082端口上,并允许所有人访问)
ssh -fCNL *:8082:localhost:8072 admin@localhost
# autossh 版本
# 反向代理
autossh -p 22 -NR 8072:localhost:8082 [email protected]
# 正向代理
autossh -p 22 -NR *:8082:localhost:8072 admin@localhost

可以看到,autossh和ssh的语法几乎一致,在实际应用中其实可以使用frp来代替这种做法,然而我并没有去研究。而如果不涉及到内网穿透只是想要一个代理或者局域网内的转发一类的则可以通过net或者iptable之类的来做,这些方面虽然有用到但并没有深入研究,而是以使用别人写好的语句为主。

在nodejs中使用sqlite3

如果要评选一个世界上使用的最多的数据库,那么非sqlite莫属,在几乎所有的终端上都有它的身影,实际上如果你不介意nodejs那庞大的依赖包而想写一个桌面程序,sqlite几乎是首选的数据库。它正的很方便,在一些玩具项目中直接使用它也会是一个好的选择毕竟安装依赖的时候顺便就把数据库装好了,这实在是方便的无以复加。
nodejs下的安装

npm install sqlite3 --save 

与其它依赖没有任何不同,这会顺手装一个sqlite在依赖里。

与数据库连接:


function linkDataBase(dataBaseAddr){
  let promise = new Promise((resolve, reject) => {
      let db = new sqlite3.Database(dataBaseAddr, (err) => {
          if (err) {
              console.log('sqlite: 数据库初始化失败!');
              reject();
              return
          }
          resolve(db);

      });
  })
  return promise
}
//这里我简单的包装了一下,由于这里连接是异步的,所以最好注意一下什么时候连接上了。
//附带一提如果在连接完成前重复多次调用连接,似乎没有什么大碍,不过还是避免这么做比较好
//dataBaseAddr 为一个文件路径,如果不存在则会创建。如果传入":memory:" 则会把数据放在内存里。

实际上也许是sqlite3的nodejs包比较古老,几乎所有的api都是回调式的,想用的舒服包装一下会比较好。
不过,库自身其实提供了多次操作的api,以避免多次操作时金字塔式的回调。
几个基本api
*. exec:接收sql语句,及回调函数,通常用来执行创建表之类的语句,回调函数只有err一个参数,并不反回结果。
*. run:和exec基本一样,但是传递多一个规则参数,可以使用一些方便的写法而不是直接输入sql语句,回调也只有err参数
*. get:传递和run一样区别是会返回结果,但如果结果有多个只会返回第一个
*. all:与get一样,但会返回一个数组,如果只查出一个也会包在数组里

//常见调用方法
db.run('INTER INTO FROM table VALUES($id,$name)',{
    $id:1,
    $name:'TOM'
},(err)=>{

})

db.run('INTER INTO FROM table VALUES(?,?)',[1,'TOM'],(err)=>{
  
})
db.run('INTER INTO FROM table VALUES(?,?)',1,'TOM',(err)=>{
  
})

sqlite多条语句预处理(可以用于模拟事务)
类似下面这样:

var db = new sqlite3.Database(db_path);
db.run("CREATE TABLE foo (id INT, txt TEXT)",(err)=>{
  db.run("BEGIN TRANSACTION");
  var stmt = db.prepare("INSERT INTO foo VALUES(?, ?)");
  for (var i = 0; i < count; i++) {
  stmt.run(i, randomString());
  }
  db.run("COMMIT TRANSACTION");
});

sqlite 的导出,导入,这里依赖sqlite3的客户端具体来说就是去sqlite3的官网下载相关文件并加到环境变量里。

# 导出
sqlite3 fmsSql.db  .dump > c:\workSpace\test.sql
# 或者
 .output 'path'
 .dump
# 导入
 .read 'd:/sqlite.sql'

关于promise包装,实际上我写了类似下面这样的代码

function packPromise(opt: {
    method: string;
    args: any;
}) {
    let { db, state } = this.common;
    let { args, method } = opt;
    let promise = new Promise((resolve, reject) => {
        if (state === 'conection') {
            db[method](...args, (err, result) => {
                if (err) {
                    reject(err);
                    loggerErr.error(`sqlite ${method}: 执行失败!, ${args[0]} ${err}`);
                    return;
                }
                if (!result) {
                    result = true;
                }
                resolve(result);
            });
        } else {
            reject(false)
        }
    })
    return promise
}
function query(sqlStr: string) {
    return this.packPromise({
        method: 'exec',
        args: sqlStr
    });
}

这是从一个对象中拉出来的,并不完全,也就是说没法直接跑起来,不过个人感觉应该并不难懂,也就不改了。
实际上现在来看只是为了包装成promise其实有看起来更简洁的办法。

//util 结合async 来异步执行
const util = require('util')
//包装
const readyAsync = util.promisify(fs.readFile);

async function init(){
 let data = await readyAsync('path');
 data = JSON.parse(data);
 console.log(data);
}
init();

上面这个列子是把一个接受回调的api包装了一下,实际上并没有意义,只是想说明util.promisify 这个api的效果罢了。
也就是说实际上,把sqlite3上的api用util.promisify包装一下就能把回调风格的改成async await风格的了。

结语

这篇总结从开始写到写完经历了差不多一个月,中途过了个年,虽然还想要不要写个年度总结,然而慕然回首大有一种空寂之感,又是一年过去了,虽然期间也经历了很多,然而,和至今的每一年一样,只是日子又过去了,不在此之上,也不在此之下。将目光转回这篇笔记,似乎依然没什么特别,只是又一次总结而已。然而岁末年初总有种莫名的焦虑,似乎总想找些意义来,现在看来也许依然还能打一篇笔记也是意义所在吧。

评论

This is just a placeholder img.