<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[YXZ's Blog]]></title><description><![CDATA[果然还是太菜了]]></description><link>https://yxz.me/</link><image><url>https://yxz.me/favicon.png</url><title>YXZ&apos;s Blog</title><link>https://yxz.me/</link></image><generator>Ghost 2.1</generator><lastBuildDate>Sun, 19 Apr 2026 06:47:56 GMT</lastBuildDate><atom:link href="https://yxz.me/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[浅谈Javascript异步]]></title><description><![CDATA[<h1 id="0x00js">0x00 先从js运行机制说起</h1>
<p>Javascript是一门事件驱动，非阻塞式I/O模型的语言。虽然在一般的实现上，它是单线程的，但是由于它本身的一些设计机制，导致了它实际上是一门在面向高并发场景下执行效率非常高的语言。<br>
在js中，一旦有事件产生(例如：网络连接建立，网络数据包到达，磁盘I/O操作完成，定时器到时触发)，js就会将其加入到事件队列里，再进行处理。实际上，js在执行的时候，其主要的流程可以看做是一个叫<code>Event Loop</code>的东西</p>
<pre><code>while(true)
{
    if(EventQueueIsNotEmpty)
    {
        Pop the front item of the event queue.
        Execute it.
    }
}
</code></pre>
<p>可以从这段伪代码可以看出，只要某个事件的处理代码开始执行了，除非它本身出让它的执行权，对于js运行环境来说，所有的cpu时间都由它独占，其他的事件必须等待它处理完成才会开始处理。</p>
<p>这里举一个例子</p>
<pre><code>console.log(1)
let</code></pre>]]></description><link>https://yxz.me/2018/05/21/javascript-async/</link><guid isPermaLink="false">5b012fa59f7f1f1b2d675d92</guid><dc:creator><![CDATA[yxz]]></dc:creator><pubDate>Mon, 21 May 2018 12:23:16 GMT</pubDate><media:content url="https://yxz.me/content/images/2018/05/68814585_p0.png" medium="image"/><content:encoded><![CDATA[<h1 id="0x00js">0x00 先从js运行机制说起</h1>
<img src="https://yxz.me/content/images/2018/05/68814585_p0.png" alt="浅谈Javascript异步"><p>Javascript是一门事件驱动，非阻塞式I/O模型的语言。虽然在一般的实现上，它是单线程的，但是由于它本身的一些设计机制，导致了它实际上是一门在面向高并发场景下执行效率非常高的语言。<br>
在js中，一旦有事件产生(例如：网络连接建立，网络数据包到达，磁盘I/O操作完成，定时器到时触发)，js就会将其加入到事件队列里，再进行处理。实际上，js在执行的时候，其主要的流程可以看做是一个叫<code>Event Loop</code>的东西</p>
<pre><code>while(true)
{
    if(EventQueueIsNotEmpty)
    {
        Pop the front item of the event queue.
        Execute it.
    }
}
</code></pre>
<p>可以从这段伪代码可以看出，只要某个事件的处理代码开始执行了，除非它本身出让它的执行权，对于js运行环境来说，所有的cpu时间都由它独占，其他的事件必须等待它处理完成才会开始处理。</p>
<p>这里举一个例子</p>
<pre><code>console.log(1)
let i = 0
setTimeout(function(){
	console.log(2)
},1000)
console.log(3)
while(true)	i++
console.log(4)
</code></pre>
<p>代码中设定了一个定时器，规定在1000ms后输出<code>2</code>。在执行过程中，控制台里只会出现<code>1 3</code>，<code>2</code>永远都不会出现。因为当前执行的事件一直处于执行状态。虽然在1000ms后定时器触发，往控制台输出2的事件加到了事件队列里，但将永远都没有开始执行的机会。</p>
<h1 id="0x01">0x01 为什么需要异步</h1>
<p>在计算机中，相对CPU的执行速度而言，I/O操作(如磁盘读写、网络传输、键盘输入等)是非常非常慢的。这里以磁盘读写为例，要从机械硬盘上读数据，至少需要大概10ms的时间，而在这10ms的时间内，cpu已经可以执行10^7次加法运算了。所以，如果在执行I/O操作时，让cpu一直处于等待状态，无疑是低效的。那么，这个问题如何解决呢？首先明确我们的目的，要在I/O操作完成前不要白白浪费CPU。其中一种解决方法是使用多线程。例如我们编写了一个简单的HTTP服务器，负责将磁盘上的文件发送给请求的用户。那么我们就可以对于每个用户的请求启用一个新的线程来处理，这样就可以大大增加了软件的吞吐量。但是，如果使用多线程的话，就要另外花费精力去处理一些附加的问题，如锁的问题。<br>
假如每个线程都执行下面这一个操作，其中<code>i</code>是一个全局访问的变量，初始为0</p>
<pre><code>int a = i;
a = a+1;
i = a;
</code></pre>
<p>假如我们执行了100个这样的线程，在所有线程执行完后，<code>i</code>的结果往往不是100。因为在多线程的执行的时候，由于实际cpu数量并没有这么多，并不是真正意义上的并发执行，是每个线程执行一段时间，暂停，再让另一个线程执行一段时间，这个过程一般是由操作系统来控制的。所以很有可能在执行完<code>a=a+1</code>时，这个线程就立刻被暂停了，而在其它线程里面<code>i</code>的值被更新，当这个线程恢复执行的时候，它就将旧的<code>a</code>更新到<code>i</code>上。最终100个线程执行完后得到的并不是我们本意要的结果。<br>
那么，要如何解决了？我们可以通过加锁来处理，来保证同时只有一个线程会执行这三行代码，当有线程在执行这三行代码时，其他线程不得进入这个代码区域，来确保对<code>i</code>的值的更新是原子的。<br>
显然，在多线程下，因为有很多事情是无法预知的，这就需要程序员花费额外的精力去处理一些棘手的事情，同时也容易因为考虑不周全产生其他bug。<br>
而javascript的事件驱动模型，就很好的解决了上面这些问题。首先，在js的代码中，并没有并发执行这种概念，意味着同一时间内只会有一段代码执行，并且在执行的过程中不会被其他的事情打断，这样就从执行机制上很好的避免了多线程中需要保证原子操作的要求。同时，对于I/O操作，可以将其交由外部组件处理，要求在I/O操作完成时触发一个事件。这样，代码在执行的过程中，如果遇到I/O操作，就可以将I/O操作完成后要执行的代码封装好，然后将I/O交给外部组件处理，之后即可交出代码的执行权，让执行器执行下一个事件，等到I/O操作完成后再“唤醒”自己，将未完成的操作完成。这样，就能让CPU一直处于工作状态，不会因为等待I/O而空转。<br>
下面，将用一段常用的代码来辅助说明一下</p>
<pre><code>var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function() {
    if(xhr.readyState==4 &amp;&amp; xhr.status==200){
        console.log(xhr.responseText)
    }
}
xhr.open(&quot;GET&quot;,&quot;test.txt&quot;,true)
xhr.send()
console.log(1)
</code></pre>
<p>在执行<code>xhr.send()</code>之后，就会将该http请求交由外部组件处理，同时该函数将会立即返回，执行下面的<code>console.log(1)</code>。注意此时http请求尚未完成。待到该http请求完成时，将会在事件队列中加入这一事件。在事件队列中轮到该事件时将会执行预先定义好的处理代码：</p>
<pre><code>if(xhr.readyState==4 &amp;&amp; xhr.status==200){
        console.log(xhr.responseText)
    }
</code></pre>
<p>很多初学者会认为在<code>xhr.send()</code>执行完后意味着http请求已经完成，这是一种错误的想法。<br>
也有人可能会想，那我能不能在<code>xhr.send()</code>下通过死循环来检测请求是否完成呢？</p>
<pre><code>while(xhr.readyState!=4) continue;
console.log(xhr.responseText)
</code></pre>
<p>然而，很遗憾，代码将永远都不会跳出那个循环。回想下前面提到的<code>Event loop</code>，在http请求完成时，将会将事件放到事件队列等待执行。而这段代码一直在执行，没有机会给请求完成后进行处理，从而<code>xhr.readyState</code>永远都不会得到修改。</p>
<h1 id="0x02promise">0x02 Promise</h1>
<p>在ES6还未定制之前，对于这种需要异步操作的场景，都需要用到<code>回调函数(callback)</code>这种东西，即指定任务完成时该进行什么行为。如上面的</p>
<pre><code>xhr.onreadystatechange = function() {
    if(xhr.readyState==4 &amp;&amp; xhr.status==200){
        console.log(xhr.responseText)
    }
}
</code></pre>
<p>但是，设想一下，如果我们要依次发出多个请求，并且要求每个请求都要在上一个请求完成后再发送，那么就只能再回调函数里面再创建一个请求，再在他的回调函数里面再创建一个请求....这样写下去，可想而知，你的代码的最后面是长这样的：</p>
<pre><code>        })
       })
      })
     })
    })
   })
  })
 })
})
</code></pre>
<p>这样的代码很丑就不说了，关键是非常难以维护。假如要往这一堆代码中加另外一个http请求，就必须要小心翼翼地去修改。这就是常说的回调地狱。<br>
在ES6中，有一种好用的东西叫<code>Promise</code>，通过它就能很优雅的处理要用到异步操作的代码了。<br>
相比而言，Promise更像是一个包装器，它可以将异步操作的代码包装起来，使其调用起来更加优雅，同时使以往使用回调函数难以实现的操作以一种十分简便的方法就能得以实现。<br>
正如Promise字面意思说讲，Promise是一种承诺，承诺交给它的东西一定会完成，无论是成功还是失败。一个Promise对象有三种状态，分别是<code>pending</code>，<code>fulfilled</code>，<code>rejected</code>，分别对应事件正在/还未执行，事件执行完成，事件执行过程抛出了错误。一旦状态发生了改变就意味着状态将不会再发生改变。<br>
在ES6中，<code>Promise</code>对象是一个构造函数，用来生成<code>Promise</code>实例，它接受一个函数，并将会往其传递俩函数，以让函数内部改变该Promise实例的状态。<br>
首先，我们从创建一个<code>Promise</code>实例说起。</p>
<pre><code>let promise = new Promise((resolve, reject) =&gt; {
    console.log(1)
    let xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function() {
        if(xhr.readyState==4){
            if(xhr.status==200)
                resolve(xhr.responseText)
            else
                reject(new Error(xhr.statusText))
        }
    }
    xhr.open(&quot;GET&quot;,&quot;test.txt&quot;,true)
    xhr.send()
})
console.log(2)
promise.then(response =&gt; {
    console.log(response)
}).catch(error =&gt; {
    console.log(error)
})
</code></pre>
<p>在上面的代码中，创建Promise实例时提供的函数会立即执行，而在对于这个函数，Promise提供了俩个参数，分别是<code>resolve</code>与<code>reject</code>，用于给内部代码改变当前Promise实例的状态。<br>
如果代码正常执行，只需调用传进来的<code>resolve</code>函数。如果有需要传出去的内容，作为<code>resolve</code>函数的参数即可，如上文代码中<code>response</code>即为传出去的<code>xhr.responseText</code>。<br>
而如果发生了错误，就该调用<code>reject</code>。<br>
PS:在调用完<code>resolve</code>与<code>reject</code>之后，该promise的实例的状态并不会立即改变，而是会将其放入事件队列中，轮到该事件时状态才会改变。<br>
在创建完promise实例后，如上文所述的行为一样，http请求尚未完成。接下来我们就该指定该异步事件完成后该执行的代码(即上文的<code>回调函数</code>)<br>
Promise提供两个api用于处理结果，<code>then</code>与<code>catch</code>，分别用于处理<code>fulfilled</code>状态与<code>rejected</code>状态的。在指定了处理函数后，同样的，在promise状态发生改变后会将其放入到事件队列中，轮到其时再执行相应的代码。<br>
另外一点需要提到的是，Promise支持链式调用。调用<code>then</code>与<code>catch</code>后会返回一个<code>Promise</code>实例。但注意<code>then</code>和<code>catch</code>的行为是不同的<br>
对于<code>then</code>而言，传递到参数里面的是上一个<code>then</code>里面所<code>return</code>的东西。如果它是一个Promise实例，那么下一个then会在该Promise变为fulfilled状态后执行，而函数里面的得到的就相对应的变成了<code>resolve</code>里面的参数。利用这一个特性我们可以以另一种更加优雅的方式去处理上文提到的那个问题。</p>
<pre><code>function getUrl(url){
    let promise = new Promise((resolve, reject) =&gt; {
    let xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function() {
        if(xhr.readyState==4){
            if(xhr.status==200)
                resolve(xhr.responseText)
            else
                reject(new Error(xhr.statusText))
        }
    }
    xhr.open(&quot;GET&quot;,url,true)
    xhr.send()
    })
    return promise
}
getUrl(first_url).then(e =&gt; {
    console.log(e)
    return getUrl(second_url)
}).then(e =&gt; {
    console.log(e)
    return getUrl(third_url)
}).then(e =&gt; {
    console.log(e)
    console.log(&quot;finish&quot;)
}).catch(error =&gt; {
    console.log(error)
})
</code></pre>
<p>对于<code>catch</code>而言，它的行为与then不同。当某个地方的promise的状态改为rejected时，将会从那个promise实例开始往链后面找，直到找到第一个<code>catch</code>，再将其作为rejected的处理函数。如上面的代码，前三个promise如果出现了rejected，将立即停止后面的then的执行，并交给其后第一个catch进行处理。</p>
<p>此外，Promise还提供了两个非常有用的静态方法，分别是<code>Promise.all()</code> 与<code>Promise.race()</code><br>
他们都接受一个包含promise实例的数组作为参数，并在调用后返回一个promise实例。而他们的行为有所不同，<code>Promise.all()</code>仅在传给他的所有的promise实例状态都改为<code>fullfilled</code>才会将状态改为<code>fullfilled</code>，而<code>Promise.race()</code>只要有一个promise实例状态改为<code>fullfilled</code>就会变为<code>fullfilled</code>。<br>
例如如果我们要拉取100个url，就可以创建100个promise，达到了100并发请求的效果，然后通过<code>Promise.all()</code>就可以方便的等待他们都执行完后统一进行处理。</p>
<h1 id="0x03asyncawait">0x03 async/await</h1>
<p>这是ES2017才引入的新的语法糖，他让异步操作变得更为方便，使得异步操作写起来就像同步代码一样，同时又保留其本身的特性。<br>
使用起来也非常简单。对于一个函数，如果在其声明前添加<code>async</code>标识，就意味着这个函数内部含有异步操作，并且执行器将会对其进行一定的改造：<br>
1、允许其内部使用<code>await</code><br>
2、对于其<code>return</code>的东西，将会自动对其包装成一个<code>Promise</code>实例，当然对于return的东西的类型不同会有不同的行为，例如如果是非promise实例就直接封装，如果是promise实例或者是有<code>then</code>方法的对象，就将其变为等价的promise返回，与上文链式调用中提到的一致。说白了，实际上是将return的东西加一层<code>Promise.resolve(value_to_return)</code>，具体可看这个api的作用，便于理解。<br>
3、对于函数内部抛出的未捕获的异常或者await后面的promise对象状态变为<code>rejected</code>状态且其链上没有<code>catch</code>函数，该函数将会终止运行，并且其返回的promise对象的状态将会改为<code>rejected</code>。</p>
<h2 id="await">await</h2>
<p><code>await</code>这个语法糖也很有趣。await后面跟的是一个promise实例（或者说，同上文一样，非promise实例会调用<code>Promise.resolve()</code>）。实际上await的作用是&quot;等待&quot;其后的promise实例状态改变为<code>fullfilled</code>，再将其内部的<code>resolve</code>里面的参数提取出来，并作为整体的一个返回值。<br>
下面举一个例子：</p>
<pre><code>function getUrlPromise(url){
    let promise = new Promise((resolve, reject) =&gt; {
    let xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function() {
        if(xhr.readyState==4){
            if(xhr.status==200)
                resolve(xhr.responseText)
            else
                reject(new Error(xhr.statusText))
        }
    }
    xhr.open(&quot;GET&quot;,url,true)
    xhr.send()
    })
    return promise
}
async function getUrlsConcurrent(urls) {
	let promises = urls.map(e =&gt; {
		return getUrlPromise(e)
	}
	return Promise.all(promises)
}
async function getUrlsOneByOne(urls){
	let contents = []
	for(let i = 0;i &lt; urls.length; i++)
	{
		contents.push(await getUrlPromise(urls[i])
	}
	return contents;

}
(async () =&gt; {
	let contents = await getUrlsConcurrent([&quot;https://www.baidu.com/&quot;, &quot;https://google.com/&quot;])
	console.log(contents)

})()
</code></pre>
<p>在这个例子中，我们定义了三个函数，其中<code>getUrlPromise</code>并没有使用<code>async</code>，而是返回了一个<code>promise</code>实例，而<code>getUrlsConcurrent</code>和<code>getUrlsOneByOne</code>是<code>async</code>函数，内部使用了<code>await</code>，但是其后跟的是返回promise的一个函数。如<code>getUrlsOneByOne</code>中，每个await的地方都会等待后面的promise执行完成后才会继续执行下去，对于程序员而言这段代码是阻塞式运行的，但实际上他还是异步执行的。多亏了这些语法糖使得代码更为直观。这个函数的行为正如名字所言，将一系列请求依次执行。<br>
而对于<code>getUrlsConcurrent</code>，函数中先是生成了所有的promise实例（这里并没有await所以并不会&quot;阻塞&quot;），再来个<code>Promise.all()</code>，行为就是一次性发出所有请求。<br>
再在最后一个函数里(由于不能直接使用await所以套了一层闭包)，这个await是对一个async函数使用的，实际上和await对promise实例使用时一样的。<br>
在具体执行机制中，对于async函数，在执行时还是会立即执行，但是一旦遇到await操作就会将之后的代码封装成一个事件，扔进事件队列里，等待事件完成时&quot;唤醒&quot;继续执行。当然实际处理可能不是这样，但可以直接这样理解。</p>
<h1 id="">总结</h1>
<p>由此可以看出，随着语言的发展，一些以前实现起来不方便的东西现在就变得非常方便了。这个异步问题的解决是一步一步慢慢来的，从一开始的回调事件再到promise再到async/await（其实在这之前还有一个generator函数，本人认为直接将async/await理解为对promise的封装更易于理解，所以这里就省略了。具体可看阮一峰老师的es6入门教程）。而于此同时javascript由于语言本身的特性导致他在I/O密集型的应用中(如web服务器)会有极高的效率，所以其在后端领域也有了一席之地(nodejs)。可以说，ES6是js语言的一个分水岭，在此之后js是一门非常强大的编程语言。<br>
还有一个问题，就是js是单线程执行的，可能会导致无法在多核机器上发挥全部的性能。实际上这个问题正在被解决(或者说已经)，在浏览器端有<code>web worker</code>，可以充分利用多核资源，在后端有node的<code>cluster</code>，都对这个问题有了一定的解决。</p>
<h1 id="">参考资料</h1>
<p>JavaScript 运行机制详解：再谈Event Loop <a href="http://www.ruanyifeng.com/blog/2014/10/event-loop.html">http://www.ruanyifeng.com/blog/2014/10/event-loop.html</a><br>
Concurrency model and Event Loop - MDN <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop">https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop</a><br>
ECMAScript 6 入门 <a href="http://es6.ruanyifeng.com/">http://es6.ruanyifeng.com/</a><br>
art of node <a href="https://github.com/maxogden/art-of-node">https://github.com/maxogden/art-of-node</a></p>
]]></content:encoded></item><item><title><![CDATA[Proxmox VE 踩坑记录]]></title><description><![CDATA[<p>最近入了一台独立服务器，如果直接拿来跑项目的话就太浪费资源了。于是打算使用<a href="https://www.proxmox.com/">Proxmox VE</a>这款虚拟化管理软件进行VPS管理。</p>
<p>Proxmox VE是一款套开源的虚拟化管理软件，用户可通过网页的方式来管理服务器上使用 <code>kvm</code> 以及 <code>lxc</code> 技术运行的虚拟机。同时提供了一些先进功能的支持，如集群、HA等。</p>
<h1 id="0x00">0x00 安装</h1>
<p>Proxmox VE是基于Debian进行开发的，主要有两种安装方式。<br>
其一是<a href="https://pve.proxmox.com/wiki/Installation">通过官方提供的iso作为一个全新的系统安装</a><br>
另一种方式是在<a href="https://pve.proxmox.com/wiki/Install_Proxmox_VE_on_Debian_Stretch">已有的Debian系统上安装</a><br>
手动安装时请务必保证网卡配置正确，若出错的话在不带IPMI的机子上很难处理。</p>
<p>安装完成后即可通过<code>https://ip:8006/</code>访问管理页面</p>
<p>另外，这里记录一下版本升级的方法。由于Proxmox VE是一家商业公司在运营，所以一些功能是需要购买订阅才能使用的，例如说版本更新功能。但是可以通过一些方法绕过限制。注意这些更新方法请勿用于生产环境中。</p>
<h2 id="">将软件源更改为测试源</h2>
<p>修改<code>/etc/apt/sources.list.d/pve-install-repo.list</code>, 将</p>]]></description><link>https://yxz.me/2018/05/20/proxmox-ve-manual/</link><guid isPermaLink="false">5afe938c9f7f1f1b2d675d8c</guid><dc:creator><![CDATA[yxz]]></dc:creator><pubDate>Sat, 19 May 2018 17:16:18 GMT</pubDate><media:content url="https://yxz.me/content/images/2018/05/64876055_p0.png" medium="image"/><content:encoded><![CDATA[<img src="https://yxz.me/content/images/2018/05/64876055_p0.png" alt="Proxmox VE 踩坑记录"><p>最近入了一台独立服务器，如果直接拿来跑项目的话就太浪费资源了。于是打算使用<a href="https://www.proxmox.com/">Proxmox VE</a>这款虚拟化管理软件进行VPS管理。</p>
<p>Proxmox VE是一款套开源的虚拟化管理软件，用户可通过网页的方式来管理服务器上使用 <code>kvm</code> 以及 <code>lxc</code> 技术运行的虚拟机。同时提供了一些先进功能的支持，如集群、HA等。</p>
<h1 id="0x00">0x00 安装</h1>
<p>Proxmox VE是基于Debian进行开发的，主要有两种安装方式。<br>
其一是<a href="https://pve.proxmox.com/wiki/Installation">通过官方提供的iso作为一个全新的系统安装</a><br>
另一种方式是在<a href="https://pve.proxmox.com/wiki/Install_Proxmox_VE_on_Debian_Stretch">已有的Debian系统上安装</a><br>
手动安装时请务必保证网卡配置正确，若出错的话在不带IPMI的机子上很难处理。</p>
<p>安装完成后即可通过<code>https://ip:8006/</code>访问管理页面</p>
<p>另外，这里记录一下版本升级的方法。由于Proxmox VE是一家商业公司在运营，所以一些功能是需要购买订阅才能使用的，例如说版本更新功能。但是可以通过一些方法绕过限制。注意这些更新方法请勿用于生产环境中。</p>
<h2 id="">将软件源更改为测试源</h2>
<p>修改<code>/etc/apt/sources.list.d/pve-install-repo.list</code>, 将 <code>pve-no-subscription</code> 修改为<code>pvetest</code><br>
然后<code>apt</code>三连即可更新为新版本。</p>
<pre><code>apt-get update
apt-get upgrade
apt-get dist-upgrade
</code></pre>
<h1 id="0x01">0x01 相关设定</h1>
<p>对于kvm虚拟化的虚拟机，若想上传需要用到的iso文件，可以直接通过网页端上传，也可以直接将文件放入<code>/var/lib/vz/template/iso/</code>中<br>
如果想对kvm虚拟机的启动参数进行调整，官方提供了api:<code>qm set</code>，具体可参照官方文档<br>
对于lxc虚拟化的虚拟机，可以直接从系统中下载对应发行版的模板，无需自行下载。<br>
可以直接使用LXC自带的api对lxc虚拟机进行管理，注意<code>-n</code>为虚拟机的id。</p>
<h1 id="0x02">0x02 网络配置</h1>
<p>对于多ip的服务器，本身官方就是按照桥接的方式做好网络配置的，直接在虚拟机中填写分配的ip即可。<br>
对于单ip服务器，可以采用<code>NAT</code>的方法让虚拟机连上外部网络。这里介绍俩种方式。</p>
<h2 id="qemunat">采用QEMU自带的NAT</h2>
<p>对于KVM虚拟机，可以直接在创建虚拟机的时候勾上NAT，这时候就会自动为虚拟机分配一个虚拟的子网并且虚拟机可以通过nat连接到外部网络，基本上是开箱即用。同时也支持端口映射，具体可参考<a href="https://pve.proxmox.com/wiki/Network_Model">官方wiki</a>下的QEMU port redirection。但之前在使用的过程中，发现这个端口映射并不是很稳定。同时虽然这种方法很简单，但是虚拟机之间是隔离的，无法互通数据，这样就非常不灵活。<br>
同时，LXC虚拟机是没有这种开箱即用的NAT的。</p>
<h2 id="iptablesnat">配置iptables创建子网以实现nat</h2>
<p>主要思路是创建一个虚拟桥接设备并创建一个子网，然后将所有虚拟机包括宿主机都连接到这个子网内，再开启iptables的NAT功能。<br>
编辑配置文件<code>/etc/interfaces</code>，以下是参考配置</p>
<pre><code>auto vmbr2
iface vmbr2 inet static
    address 10.0.0.254
    netmask 255.255.255.0
    bridge_ports none
    bridge_stp off
    bridge_fd 0
    post-up echo 1 &gt; /proc/sys/net/ipv4/ip_forward
    post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j MASQUERADE
    post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j MASQUERADE
</code></pre>
<p>以上配置创建了<code>vmbr2</code>并且分配了一个子网<code>10.0.0.0/24</code>，同时宿主机(同时亦为网关)在这个子网内的ip为<code>10.0.0.254</code>。然后开启了内核的转发功能与iptables的NAT功能(其中<code>vmbr0</code>为通向外部网络的设备)。<br>
若想添加端口转发直接在iptables中增加相关条目即可。<br>
例如想要将宿主机<code>vmbr0</code>的80端口的tcp连接转发到10.0.0.102的80端口上：<br>
<code>iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 80 -j DNAT --to 10.0.0.102:80</code><br>
如果想保存转发规则，使之重启后依然有效，则需要在<code>/etc/interfaces</code>相应位置加入</p>
<pre><code>post-up iptables -t nat -A PREROUTING -i vmbr0 -p tcp --dport 80 -j DNAT --to 10.0.0.102:80
post-down iptables -t nat -D PREROUTING -i vmbr0 -p tcp --dport 80 -j DNAT --to 10.0.0.102:80
</code></pre>
<p>通过以上方法就能组建一个灵活的子网了，kvm虚拟机和lxc虚拟机都可接入，并且都可以有端口转发。由于没有DHCP服务器所以要自行分配ip。注意创建虚拟机的时候将其挂载到<code>vmbr2</code>端口下。<br>
我的服务器只有一个ip，所以内部组网就只能采取这种这种的方法了hhhh。为了充分利用资源，我将80，443端口转发到内部一台虚拟机上，这台虚拟机再使用nginx反代到内网的其它虚拟机，以充分利用单个ip。</p>
<h2 id="bbr">启用BBR优化网络</h2>
<p>目前的Proxmox VE版本的linux内核版本比较新，已经包含了bbr模块了。</p>
<h3 id="sysctlconf">修改<code>sysctl.conf</code></h3>
<pre><code>echo &quot;net.core.default_qdisc=fq&quot; &gt;&gt; /etc/sysctl.conf
echo &quot;net.ipv4.tcp_congestion_control=bbr&quot; &gt;&gt; /etc/sysctl.conf
</code></pre>
<h3 id="">保存生效</h3>
<pre><code>sysctl -p
</code></pre>
<h3 id="bbr">检测是否已启用bbr模块</h3>
<pre><code>lsmod | grep bbr
</code></pre>
<p>如果含有bbr即说明内核内已启用bbr模块</p>
]]></content:encoded></item><item><title><![CDATA[记一次删库救火经历&总结数据恢复的一些方法]]></title><description><![CDATA[<p><img src="https://yxz.me/content/images/2018/05/b5fd6074ff2b135.gif" alt="b5fd6074ff2b135"></p>
<h2 id="">事出</h2>
<p>前几天部门上线了个新页面，结果刚上线不到一小时就出现了一点小锅，需要临时改代码。本来代码快要改完了，突然群里面传来了一句话<br>
<img src="https://yxz.me/content/images/2018/05/gg.png" alt="gg"></p>
<h2 id="">救火</h2>
<p>当时心里面就凉了一大半。删数据就算了。。偏偏删的还是最重要的那个表。而这个活动今年刚增加了二维码分享功能，还没上线一小时就记录了341封信件，用户量正在指数级增加，停服是不可能停服的了（错误想法），顿时整个人就懵了。</p>
<h3 id="binlog">尝试使用万能的bin log</h3>
<p>在mysql中，有一种叫<code>bin log</code> 的东西，它是用于记录对数据库数据进行更新操作的所有SQL语句，同时还会在每条语句上附加上timestamp。理论上如果保留了数据库建立以来所有的bin log，无论进行了什么操作都能将数据恢复回来，可以说是删库后的一根救命稻草了。<br>
由于之前有过数据恢复的经验，意识恢复的时候立刻登上服务器查看mysql的配置文件，然而<br>
<img src="https://yxz.me/content/images/2018/05/ggx2.jpg" alt="ggx2"><br>
还有一点热量的心瞬间凉透了。</p>
<h3 id="">疾病乱投医</h3>
<p>之后我们几个部长疯狂的在网上搜索资料，祈祷仍然有救（错误想法，当时就该立刻停掉mysql并导出所有数据）<br>
经过一段时间的搜索，在前人删库的经验上得到了以下的<a href="https://dba.stackexchange.com/questions/23251/is-there-a-way-to-recover-a-dropped-mysql-database">指南</a></p>
<pre><code>如果 数据库引擎是MyISAM 
    则 收拾东西跑路
如果 数据库引擎是Innodb
    则 还能抢救一下
</code></pre>
<p>赶紧看一下数据库配置，</p>]]></description><link>https://yxz.me/2018/05/16/put-out-the-fire-of-dropping-database/</link><guid isPermaLink="false">5afbeaee0a10780f830995f1</guid><dc:creator><![CDATA[yxz]]></dc:creator><pubDate>Wed, 16 May 2018 15:14:54 GMT</pubDate><media:content url="https://yxz.me/content/images/2018/05/65929293_p0.png" medium="image"/><content:encoded><![CDATA[<img src="https://yxz.me/content/images/2018/05/65929293_p0.png" alt="记一次删库救火经历&总结数据恢复的一些方法"><p><img src="https://yxz.me/content/images/2018/05/b5fd6074ff2b135.gif" alt="记一次删库救火经历&总结数据恢复的一些方法"></p>
<h2 id="">事出</h2>
<p>前几天部门上线了个新页面，结果刚上线不到一小时就出现了一点小锅，需要临时改代码。本来代码快要改完了，突然群里面传来了一句话<br>
<img src="https://yxz.me/content/images/2018/05/gg.png" alt="记一次删库救火经历&总结数据恢复的一些方法"></p>
<h2 id="">救火</h2>
<p>当时心里面就凉了一大半。删数据就算了。。偏偏删的还是最重要的那个表。而这个活动今年刚增加了二维码分享功能，还没上线一小时就记录了341封信件，用户量正在指数级增加，停服是不可能停服的了（错误想法），顿时整个人就懵了。</p>
<h3 id="binlog">尝试使用万能的bin log</h3>
<p>在mysql中，有一种叫<code>bin log</code> 的东西，它是用于记录对数据库数据进行更新操作的所有SQL语句，同时还会在每条语句上附加上timestamp。理论上如果保留了数据库建立以来所有的bin log，无论进行了什么操作都能将数据恢复回来，可以说是删库后的一根救命稻草了。<br>
由于之前有过数据恢复的经验，意识恢复的时候立刻登上服务器查看mysql的配置文件，然而<br>
<img src="https://yxz.me/content/images/2018/05/ggx2.jpg" alt="记一次删库救火经历&总结数据恢复的一些方法"><br>
还有一点热量的心瞬间凉透了。</p>
<h3 id="">疾病乱投医</h3>
<p>之后我们几个部长疯狂的在网上搜索资料，祈祷仍然有救（错误想法，当时就该立刻停掉mysql并导出所有数据）<br>
经过一段时间的搜索，在前人删库的经验上得到了以下的<a href="https://dba.stackexchange.com/questions/23251/is-there-a-way-to-recover-a-dropped-mysql-database">指南</a></p>
<pre><code>如果 数据库引擎是MyISAM 
    则 收拾东西跑路
如果 数据库引擎是Innodb
    则 还能抢救一下
</code></pre>
<p>赶紧看一下数据库配置，发现正好用的是Innodb引擎，似乎燃起了一点点希望。<br>
同时，在看到这篇文章的介绍中强调了要立即关闭mysql导出数据时，才想起早该这样做，于是赶紧将该数据库下的所有文件都拷贝到另一文件夹中并拖到本地进行操作。</p>
<h3 id="">事情并没有那么顺利</h3>
<p>回答的作者正是某数据库恢复工具的作者，于是我们找到了他开源的工具</p>
<p><a href="https://github.com/twindb/undrop-for-innodb">undrop-for-innodb</a><br>
并根据指引进行了一番尝试。</p>
<p>该工具要求被删库的数据库文件夹下的<code>libdata1</code> 文件</p>
<p>看了下介绍大概说的是从<code>libdata</code>中读出被删表的表结构，并据此推断出实际存储到数据库中的数据格式，然后将整个服务器硬盘dump出来后，通过raw的方式读取并匹配这样的数据结构模式，以达到数据恢复的目的。</p>
<p>这种方式还是非常巧妙地，但是对于我们而言代价很高，整个服务器的硬盘有1t，将其dump下来需要很长时间。并且我在使用该工具的过程中，发现貌似并没有读取出原表的索引，所以也只能暂时将其放入备选方案中。</p>
<p>BTW：这个工具直接编译会有编译错误，具体参照issue里面的<a href="https://github.com/twindb/undrop-for-innodb/issues/3#issuecomment-360780650">这个回复</a>修改代码即可</p>
<h3 id="">黎明前的曙光</h3>
<p>时间已经到了半夜2点，然而依然没有什么头绪。我翻了下拖下来的文件，发现有俩个文件貌似有点意思<br>
它们分别为<code>ib_logfile0</code> 和 <code>ib_logfile1</code><br>
看到<code>log</code>之后仿佛就有了希望，于是搜索了一番，发现这俩个文件是这个作用的：<br>
Innodb是基于事务的数据库引擎，为了防止执行事务中途数据库崩溃导致数据不一致，它会在每次执行命令前将事务log进这俩个文件中。<br>
看到这里瞬间整个人精神了，这俩个文件各有48M，而我们才上线1小时，望着里面写的日记应该还不算多，或许能从里面还原出所有的数据？</p>
<h3 id="">冥冥之中佛祖保佑</h3>
<p>赶紧拿来一条已知数据中数据类型是int的数据在文件内搜索，果然找到了整个int，然而前后都是乱码。将编码调整为<code>UTF-8</code>后(notepad++是个好东西)出现了大量的中文！说明数据是还能抢救回来的。</p>
<p>但是日记文件中并没有任何规律。搜索一番之后也并没有找到任何关于整个文件结构的说明。有人曾经在mysql支持论坛中提问过这个问题，但是并没有得到有效的答复。想想也是，这个只是一个类似于临时文件的东西，再且其他人删库的话一定是删了上线很久的数据库了，这俩个文件中的数据早已被覆盖，拿来也没用。所以估计要想弄明白的话要去翻mysql的源代码。</p>
<p>然而，冥冥之中上github搜索了一波，发现了一个好像是学生的人写的课程作业，就是用来尝试解析ib_logfile中的增删改语句<br>
<a href="https://github.com/KasperFridolin/mysql_forensics">https://github.com/KasperFridolin/mysql_forensics</a></p>
<p>但是在使用过程中，发现久久没有得到输出，并且内存一直在增长，不知道是不是脚本出现了什么bug，也只好暂时搁置。</p>
<h3 id="">走向胜利</h3>
<p>于是乎，决定手写脚本导出数据。在比对每一条数据后发现，刚好有一个字段存的要么是<code>半年</code>要么是<code>一年</code>。于是决定全文搜索这俩个关键字，然后根据特征将每条数据分离出来。</p>
<p>继续观察后发现，刚好这个固定字段的前一个字段的长度是固定的，然后每条记录后面都会出现表的名字的字样，并且在数据的末尾会有一个二进制标记符表示记录的结束。说干就干，拿起PHP就写代码（没错我就是喜欢用PHP写CLI脚本23333）<br>
于是有了这下面的一段代码</p>
<pre><code class="language-PHP">&lt;?php
$all_data = file_get_contents(&quot;ib_logfile1_other.txt&quot;);
$in = fopen(&quot;ib_logfile1_other.txt&quot;,&quot;r&quot;);
$out = fopen(&quot;out.txt&quot;, &quot;rw&quot;);
$pos = 0;
$data = &quot;&quot;;
$count = [];
function find0x99($data,$start){
  $len = strlen($data);
  while($start &lt; $len)
  {
    if($data[$start] == chr(153))
    {
      return $start;
    }
      
    $start++;
  }
  
  return false;
}
function backfind0x99($data,$start){
  while($start &gt; 0)
  {
    if($data[$start] == chr(153))
    {
      return $start;
    }
      
    $start--;
  }
  
  return false;
}
while($pos &lt; strlen($all_data)) {
  $tmp = strpos($all_data, &quot;一年&quot;, $pos);
  if($tmp ==false)  break;
  $tmp = $tmp - 10;
  $tmp2 = strpos($all_data,'qrcode_msg',$tmp);
  $tmp3=strpos($all_data,&quot;online_msg&quot;,$tmp);
  if($tmp3&gt;0 &amp;&amp; $tmp3&lt;$tmp2)
  {
    $pos = $tmp3;
    continue;
  }
  
  if($tmp2 ==false) break;
  $tmp2 = backfind0x99($all_data,$tmp2);
  if($tmp2 ==false) break;
  $target = substr($all_data,$tmp,$tmp2-$tmp);
  $target = str_replace(&quot; 一年&quot;,&quot;一年 &quot;,$target);
  $id = substr($target,0,8);
  $file = fopen(&quot;out/$id.txt&quot;, &quot;a&quot;);
  fwrite($file, $target .&quot;\n\n\n\n&quot;);
  fclose($file);
  $data =$data . $target . &quot;\n\n\n\n&quot;;
  $pos = $tmp2 + 1;
}
$pos = 0;
while($pos &lt; strlen($all_data)) {
  $tmp = strpos($all_data, &quot;半年&quot;, $pos);
  if($tmp ==false)  break;
  $tmp = $tmp - 10;
  $tmp2 = strpos($all_data,'qrcode_msg',$tmp);
  $tmp3=strpos($all_data,&quot;online_msg&quot;,$tmp);
  if($tmp3&gt;0 &amp;&amp; $tmp3&lt;$tmp2)
  {
    $pos = $tmp3;
    continue;
  }
  if($tmp2 ==false) break;
  $tmp2 = backfind0x99($all_data,$tmp2);
  if($tmp2 ==false) break;
  $target = substr($all_data,$tmp,$tmp2-$tmp);
  $target = str_replace(&quot; 半年&quot;,&quot;半年 &quot;,$target);
  $id = substr($target,0,8);
  $file = fopen(&quot;out/$id.txt&quot;, &quot;a&quot;);
  fwrite($file, $target .&quot;\n\n\n\n&quot;);
  fclose($file);
  $data =$data . $target . &quot;\n\n\n\n&quot;;
  $pos = $tmp2;
}
file_put_contents(&quot;out.txt&quot;, $data);
</code></pre>
<p>最后，成功将所有数据挽救了回来</p>
<h2 id="">总结</h2>
<p>首先，最重要的一点是，在线上数据库上执行SQL语句一定要谨慎再谨慎。</p>
<p>这次某人删库是因为精神状态不好，本来只是想select一下，于是翻mysql的执行记录，找到一条差不多的语句就无意识地按下了enter。而实际上，执行的语句恰好是上线前清空数据表的语句。有个定律好像是说墨菲定律，如果你觉得某件不可能的事情可能发生，那他很大概率会发生。谁会想到竟然会出了删库这种事情，而且起因又这么戏剧化</p>
<p>其次，保存好bin log+定期异地备份。凡事都留一手冗余，因为谁也不知道会发生什么。</p>
<p>再者，如果真的出事故了第一件事要做的事是保留现场，立即关掉mysql+关闭服务器，以防被删除的数据被覆盖了，以便留下哪怕是那么一点点希望。而这次我们得以恢复数据，完全靠的是运气，正好是刚上线就出事故了，写入的数据量少还没被覆盖。</p>
<p><img src="https://yxz.me/content/images/2018/05/fozu.png" alt="记一次删库救火经历&总结数据恢复的一些方法"><br>
冥冥之中，佛祖保佑</p>
]]></content:encoded></item><item><title><![CDATA[博客迁移到Ghost]]></title><description><![CDATA[<p>博客吃灰好久了，翻了下之前写过的文章，发现好像都是黑历史，所以还是都删掉吧。</p>
<p>也忘了博客是什么时候建立的了，印象中换过俩次域名，在wordpress那边最早的文章是2012年2月，而目前使用的域名注册日期是2012年7月，这个博客也算是存活好久了。</p>
<p>近几年来学到了很多东西，但一直懒得把东西都记录下来。那就从现在开始好好写博文吧。</p>]]></description><link>https://yxz.me/2018/05/16/migrate-to-ghost/</link><guid isPermaLink="false">5afbe6630a10780f830995ef</guid><dc:creator><![CDATA[yxz]]></dc:creator><pubDate>Wed, 16 May 2018 08:12:40 GMT</pubDate><media:content url="https://yxz.me/content/images/2018/05/63467690_p0.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://yxz.me/content/images/2018/05/63467690_p0.jpg" alt="博客迁移到Ghost"><p>博客吃灰好久了，翻了下之前写过的文章，发现好像都是黑历史，所以还是都删掉吧。</p>
<p>也忘了博客是什么时候建立的了，印象中换过俩次域名，在wordpress那边最早的文章是2012年2月，而目前使用的域名注册日期是2012年7月，这个博客也算是存活好久了。</p>
<p>近几年来学到了很多东西，但一直懒得把东西都记录下来。那就从现在开始好好写博文吧。</p>
]]></content:encoded></item></channel></rss>