SegmentFault defcon最新的文章
2016-07-30T21:02:41+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
Sublime Text 快捷键笔记
https://segmentfault.com/a/1190000006122712
2016-07-30T21:02:41+08:00
2016-07-30T21:02:41+08:00
defcon
https://segmentfault.com/u/defcon
1
<pre><code>Ctrl + R 跳到指定打开文件对应method
Ctrl + Enter 跳转到下一行
Ctrl + D 选中单词后,按Ctrl+D可同时选择下一个相同的单词
Ctrl + L 选择当前光标所在行,继续按,即可选择下一行
Ctrl + Shift + L 选择多行
Ctrl + G 跳转到第几行
Ctrl + W 关闭打开文件
Ctrl + Shift + W 关闭所有打开的文件
Ctrl + N 新建窗口
Ctrl + P 搜索项目中的文件
Ctrl + X 删除当前行
Ctrl + K + B 关闭左侧边栏
Ctrl + / 注释当前行
Ctrl + F 查找文件内容
Ctrl + Shift + F 查找和替换
Ctrl + H 当前文件替换
Ctrl + K + K 删除从光标开始位置到行尾内容
Ctrl + Shift + M 选择括号(块)内容
Ctrl + M 移动光标到括号起始或者结束位置
Ctrl + Shift + [ 选中后按快捷键可折叠代码
Ctrl + Shift + ] 选中后按快捷键展开代码
Ctrl + K + O 展开所有折叠代码
Alt + F3 选择文本后,按此快捷键则在当前文件选择所有相同内容,可以批量编辑
/* 分屏 */
Alt + Shift + 2 左右分屏
Alt + Shift + 3 左右分屏3列
Alt + Shift + 4 左右分屏4列
Alt + Shift + 5 等分成4屏
Alt + Shift + 8 等分成上下2屏
Alt + Shift + 9 等分成上下3屏
F11 全屏
Shift + F11 全屏免打扰模式
</code></pre>
Sublime Text 3 plugin(插件) 推荐
https://segmentfault.com/a/1190000005861999
2016-07-03T14:02:08+08:00
2016-07-03T14:02:08+08:00
defcon
https://segmentfault.com/u/defcon
1
<p>前言: <em>使用Sublime Text进行前端开发和阅读C++和C相关代码已经很长时间,这里总结下在使用Sublime Text3 (这里都特指Sublime Text3)使用的一些相关插件和小技巧:(不间断更新..)</em></p>
<h3>1.<a href="https://link.segmentfault.com/?enc=6n8ro1qCQrJDL7zj0wvrrQ%3D%3D.FkqhRuX1OvO9splqMpvfBuLggCaS1y4gHTYLxT%2BIuGo%3D" rel="nofollow">Emmet</a>
</h3>
<p>官网对Emmet的简介:</p>
<pre><code>Emmet is a plugin for many popular text editors which greatly improves HTML & CSS workflow:
</code></pre>
<p>安装:</p>
<pre><code>Ctrl + shift + p > Package Control: Install Package > emmet</code></pre>
<p>你在Sublime Text3中输入以下</p>
<pre><code>nav#menuSystem.navMenu.isOpen>div#hotelLogo>div.navMenuIcon.logoIcon+div#arrowPointer+ul#navMenuMain>li.navMenuItem.navMenuItem$$$*10>div.navMenuIcon{Item $}+a{Item $}</code></pre>
<p>在windows中按下ctrl + e 或者 tab键,看看发生什么吧?<br>或者我们输入</p>
<pre><code>html:5</code></pre>
<p>看看会发生什么?</p>
<p>然后你在样式表<style>标签内输入</p>
<pre><code>fz20</code></pre>
<p>再看看是什么?</p>
<p>具体的语法和使用参考文档<br><a href="https://link.segmentfault.com/?enc=%2BC33%2Fd0O8WoNDWvRYSZ%2FmA%3D%3D.RueYMgJYDk44SgNr%2BYg9kYyMn1rEyuW7YM52HNFS0tA%3D" rel="nofollow">官网</a> <br><a href="https://link.segmentfault.com/?enc=KS6a8xxhdXN2BxSb8K2YTQ%3D%3D.mrD0k44c4UWIYnNiXY%2BrMwCU3PVKZrs7kooWXpu7Vzc7btDBPg2tlDKsTce4gGWNRA4QbaTAvb%2Fu1U9W0ouj1%2BCjHRT%2B4zc05Pov71k8kco%3D" rel="nofollow">w3cplus</a></p>
<h3>2.<a href="https://link.segmentfault.com/?enc=JZX5Vdcd9OFjCh2qcx5Scw%3D%3D.RES9vJ%2FV%2Bz3NQDzP1yLlju9X4qhIbPeA7XxeTIBvRhhza2lcEwSmXCDrZkbF02k3" rel="nofollow">Trimmer</a>
</h3>
<p>Trimmer主要是为了处理多余的空格和一些特殊字符用的。说实话,感觉不是特别需要。但有个功能还是挺不错的,就是去除多余的行。<br>安装:</p>
<pre><code>Ctrl + shift + p > Package Control: Install Package > trimmer</code></pre>
<p>使用方法:</p>
<pre><code>Ctrl + shift + p > 输入 Trimmer:Collapse lines</code></pre>
<p>观察变化。Trimmer还有其他的一些功能,比如替换特殊字符什么的,自己可以去查看文档。</p>
<p>插件文档:<br><a href="https://link.segmentfault.com/?enc=kd4ez3W%2FTZvkyvPWQmuXDA%3D%3D.kRB%2FbwRS9UDzNAZbtQp3zWSgVG4ZM3vVX%2FEtxQEN4a9oRBanE55R5SXyX4uff6N5" rel="nofollow">trimmer github repo</a></p>
<h3>3.<a href="https://link.segmentfault.com/?enc=iT9YOBSNosatdmhT6IOu4Q%3D%3D.Bi6uZGyTO%2FM21JN2cdQWhLj3IL5Sn5dQKJPdfF%2FnvAHtcQlhOQ%2F384nwvAIDyeBYE5vzKJZSaVw3zaA5ZZX65Q%3D%3D" rel="nofollow">Autoprefixer</a>
</h3>
<p>这个插件主要是css私有属性补全功能。在</p>
<p>安装:</p>
<pre><code>Ctrl + shift + p > Package Control: Install Package > autoprefixer</code></pre>
<p>使用方法:<br> 比如我输入transform 按tab键(要在冒号之前)会自动补全所有的私有属性。</p>
<pre><code>-webkit-transform: ;
-ms-transform: ;
-o-transform: ;
transform: ;
</code></pre>
<h3>4.<a href="https://link.segmentfault.com/?enc=Ck3ieS9iR56vOsKkCbH0Dg%3D%3D.Uaoq3HvmpVNI4uzvJ1Skmst7Ps9mzJ7p0O9RKp1EsmYMFQ73TP7kA8ckguSPP5DQ" rel="nofollow">Tag</a>
</h3>
<p>格式化html标签</p>
<p>安装:</p>
<pre><code>Ctrl + shift + p > Package Control: Install Package > tag</code></pre>
<p>使用方法:</p>
<p>打开html文件, Ctrl + shift + p > Tag: Auto-Format Tags On Document 即可格式化html文件</p>
<h3>5.<a href="https://link.segmentfault.com/?enc=HUAR4YShQZ73LSUzrq0g8A%3D%3D.oo2eWDG1mOjSmHwInUzSQqk%2Fpf3H4ATi5bA%2FvJA6nELHr7SIRKUjWLruE%2BXDjURUrQkyt1pvYWNg8MiJUR731A%3D%3D" rel="nofollow">Bracket Highlighter</a>
</h3>
<p>这个插件很实用。比如你在一个嵌套很多层的函数中去查找某个代码块,那么你只要光标选择起始符号,那么就是在左侧标记处结束位置。<br>直接上图:</p>
<p><img src="/img/bVyK6O" alt="clipboard.png" title="clipboard.png"></p>
<p>安装:</p>
<pre><code>Ctrl + shift + p > Package Control: Install Package > Bracket Highlighter</code></pre>
<h3>6.<a href="https://link.segmentfault.com/?enc=b9JdV6TFVnl2I2XrSuP0ug%3D%3D.80QrL8WrOsaXyVGIsnAULKTz8WvqfzvNwbYVLlMnLD0%3D" rel="nofollow">CSScomb</a>
</h3>
<p>这个插件主要在css选择器按照css属性类别进行分开,便于维护和查找。说的我自己都觉得拗口。看图:<br>Before:</p>
<p><img src="/img/bVyK60" alt="clipboard.png" title="clipboard.png"></p>
<p>After:</p>
<p><img src="/img/bVyK61" alt="clipboard.png" title="clipboard.png"></p>
<p>使用方法:</p>
<p>打开css文件, Ctrl + shift + p > Run CSScomb 即可格式化css文件<br>或者右击 -> Run CSScomb <br>或者 Ctrl + shift + c</p>
<h3>7.<a href="https://link.segmentfault.com/?enc=NnCGUV6In6MhETRWrx9HkA%3D%3D.Lqlt7omC1CagQjTupWIq9mhdfYtkJBlTOUfyuFw%2FB9gSuQpQgkdXvhFo1R%2FVq7n7" rel="nofollow">Alignment</a>
</h3>
<p>多行表达式等号对齐功能<br>未完待续。。</p>
<h3>8.<a href="https://link.segmentfault.com/?enc=hfnyhwVMuC9vC46yWql47A%3D%3D.pv0Se536EGP2C7%2Fwc5SV5gPTf%2FjrAnegE8NrejNIILyZUbjTxxqnHmLYfjmPtQAp" rel="nofollow">ConvertToUTF8</a>
</h3>
<p>sublime text3 对gbk或者gb2312的编码格式支持不太好,而我们因为历史遗留原因需要编辑和查看这些格式的文件,那么我们需要安装这个插件。<br>安装:</p>
<pre><code>Ctrl + shift + p > Package Control: Install Package > ConvertToUTF8</code></pre>
<h3>9.<a href="https://link.segmentfault.com/?enc=wbmLJX6OhcYHDAYco%2BDCiQ%3D%3D.fNjsr8blHHhBerCtLlL0cYKpfX8VDxkTrVKP%2FvCqmvtJXGphF8Yd0cIVUN6Sxp37" rel="nofollow">Git</a>
</h3>
<p>未完待续。。。</p>
<p>原文链接:<a href="https://segmentfault.com/a/1190000005861999">https://segmentfault.com/a/1190000005861999</a></p>
设置docker容器(container)时区
https://segmentfault.com/a/1190000005612603
2016-06-01T15:37:15+08:00
2016-06-01T15:37:15+08:00
defcon
https://segmentfault.com/u/defcon
3
<p>利用docker来部署服务,经常遇到的一个问题是如何解决容器内的时区问题.</p>
<p>假设现在启动的镜像是tomcat:8.0.35-jre8</p>
<pre><code>/*直接用宿主机器上的时区默认覆盖容器内的时区配置文件即可,也就是跟宿主机器同样的时区配置 */
# docker run -v /etc/localtime:/etc/localtime:ro --name=tomcat tomcat:8.0.35-jre8</code></pre>
<p>但是我相信如果写过java的人仍然发现通过java 中new Date()获取到的仍然是容器默认的时区,而是宿主机器上的时区配置,因为java是通过获取timezone来设置时间的。不废话,继续看以下命令:</p>
<pre><code>/* 这里配置的环境变量 Asia/Shanghai就是我所需要的时区 */
# docker run -e TZ="Asia/Shanghai" -v /etc/localtime:/etc/localtime:ro --name=tomcat tomcat:8.0.35-jre8</code></pre>
<p>通过这样的启动方式,就是OK了。<br>当然聪明人肯定不会自己每次都在启动的时候加这些配置,当然在基础镜像里面搞好咯。</p>
<p>原文连接:<a href="https://segmentfault.com/a/1190000005612603">https://segmentfault.com/a/1190000005612603</a></p>
CentOS7配置ntp服务
https://segmentfault.com/a/1190000005591893
2016-05-30T09:56:57+08:00
2016-05-30T09:56:57+08:00
defcon
https://segmentfault.com/u/defcon
0
<p>安装ntp</p>
<pre><code># yum install ntp
</code></pre>
<p>开启ntp服务</p>
<pre><code># systemctl start ntpd </code></pre>
<p>配置防火墙,允许其他主机可以连接到此主机上的ntp服务</p>
<pre><code># firewall-cmd --add-service=ntp --permanent
# firewall-cmd --reload</code></pre>
<p>ntp的配置文件默认在/etc/ntp.conf</p>
<p>原文连接<a href="https://segmentfault.com/a/1190000005591893">https://segmentfault.com/a/1190000005591893</a></p>
windows和linux服务器rsync文件同步实践(一)
https://segmentfault.com/a/1190000004923750
2016-04-12T16:25:20+08:00
2016-04-12T16:25:20+08:00
defcon
https://segmentfault.com/u/defcon
0
<p>早期的业务系统各个节点之间文件的同步都是通过在业务层暴露设计的一些同步API,然后文件同步的调度器自动对所有服务进行调用和同步文件,中间也涉及到我们一些协议的交互,典型的两阶段提交(2PC) + 快速失败(fail fast)。简单来说通过我们自己封装的同步交互协议,容错性较高。 然而在某些场景中,我们只需要定时同步一些较少的文件,同时对一致性要求也不是很高,那么按照上面的做法就有点高射炮打蚊子了。这里我们就采用 rsync进行文件同步。 <br> 在linux中默认都安装了rsync,我们可以通过运行以下命令查看rsync版本号:</p>
<pre><code>/* yum list installed | grep rsync */
# rsync --version</code></pre>
<p><img src="/img/bVuON6" alt="clipboard.png" title="clipboard.png"></p>
<p>可以看到我机器上安装的是version 3.0.6版本.<br>当然也可以自己重新安装新版本的rsync.<br>Step 1: 查看机器是否安装了rsync</p>
<pre><code>/* rpm -qa | grep rsync */
# yum list installed | grep rsync</code></pre>
<p><img src="/img/bVuORS" alt="clipboard.png" title="clipboard.png"></p>
<p>Step 2:卸载rsync</p>
<pre><code># yum remove rsync
# wget ""</code></pre>
<p>稍后继续写..<br>rsync问题总结:<br>ref:<a href="https://link.segmentfault.com/?enc=fUkzCWXmDjxfGOOPTMUT7w%3D%3D.%2F3IwM%2BGjrPh5mnTuLU8EhOduE1JhGceH6DHMbiQ1JHxdiDNojbrK03LZI0%2BNnLmN" rel="nofollow">http://ju.outofmemory.cn/entry/87914</a></p>
如何构建自己的 create maven archetype(一)
https://segmentfault.com/a/1190000004490721
2016-02-25T17:34:40+08:00
2016-02-25T17:34:40+08:00
defcon
https://segmentfault.com/u/defcon
0
<p>每次创建maven web项目总是要根据maven-archetype-webapp生成,然后进行一些繁琐的配置。可能因为历史原因,maven-archetype-weapp目前的版本仍然是1.0,实际上已经无法满足当前的需 求了。那么我们就自己来创建属于自己的maven-archetype-webapp.</p>
<p>创建maven archetype用两种方式:</p>
<ol>
<li><p>使用maven-archetype-plugin插件根据已有项目骨架导出</p></li>
<li><p>自定义archetype描述文件</p></li>
</ol>
<p>这篇文章我们讲解第一种方法,也是最简单的方法:</p>
<p>假设我现在根据eclipse提供的maven-archetype-webapp 1.0创建一个基础的webapp项目,然后在这基础上做一些配置,具体的配置根据你自己的情况而定。</p>
<p><img src="/img/bVs0nF" alt="clipboard.png" title="clipboard.png"><br><strong>上面是我的项目脚手架的目录结构</strong></p>
<p>进入我刚才新建的项目根目录:E:\eclipse-mars\workspace\maven-webapp<br><strong>运行以下命令</strong>:</p>
<p><img src="/img/bVs0ox" alt="clipboard.png" title="clipboard.png"></p>
<p>此时我们创建的archetype已经创建完毕.</p>
<p><strong>此时:E:\eclipse-mars\workspace\maven-webapp\target\generated-sources\archetype就是我们创建的maven archetype所在根目录。</strong></p>
<p>进入archetype所在根目录,运行 mvn install 即可安装。<br>这时候如果我们在eclipse中新建maven项目即可看到刚才我们创建的maven archetype</p>
<p><img src="/img/bVs0o7" alt="clipboard.png" title="clipboard.png"></p>
<p>原文链接<a href="https://segmentfault.com/a/1190000004490721">https://segmentfault.com/a/1190000004490721</a></p>
CentOS 下解决ssh登录 locale 警告
https://segmentfault.com/a/1190000004378075
2016-01-28T10:45:33+08:00
2016-01-28T10:45:33+08:00
defcon
https://segmentfault.com/u/defcon
5
<p>最近登录一台CentOS 6机器,发现每次登录都提示如下警告:</p>
<pre><code>-bash: warning: setlocale: LC_CTYPE: cannot change locale (en_US.UTF-8): No such file or directory
-bash: warning: setlocale: LC_COLLATE: cannot change locale (en_US.UTF-8): No such file or directory
-bash: warning: setlocale: LC_MESSAGES: cannot change locale (en_US.UTF-8): No such file or directory
-bash: warning: setlocale: LC_NUMERIC: cannot change locale (en_US.UTF-8): No such file or directory
-bash: warning: setlocale: LC_TIME: cannot change locale (en_US.UTF-8): No such file or directory
</code></pre>
<p>这种警告当然不能容忍,所以要fix it.</p>
<pre><code># tee /etc/environment <<- 'EOF'
LANG=en_US.utf-8
LC_ALL=
EOF
# source /etc/environment
/* 生成 en_US.UTF-8 locale文件 CentOS没有locale-gen命令*/
# localedef -v -c -i en_US -f UTF-8 en_US.UTF-8</code></pre>
<p>输入以下命令,重新登录就不会出现这个问题了。<br>但是到底是什么原因导致这样的警告?<br>首先我们需要知道<a href="https://link.segmentfault.com/?enc=T5aG%2FWBYOCdVgm%2FBi129Tw%3D%3D.hFWCPWtTD2P%2Fx4%2F00a%2FI4n2Mx4n4J2DAuBstCAJlfECUHE5JZmGgK%2BBXpFjkDUPiFnf9eYX6wppCVWW8uKz92ll8nNqQg0ZNgeyyduTCtic%3D" rel="nofollow">什么是locale</a> ,这篇文章说的挺好。</p>
<p>首先看我们的登录警告提示:-bash: warning: setlocale: LC_CTYPE: cannot change locale (en_US.UTF-8): No such file or directory.因为我们已经设置了默认地区_语言.字符集为 en_US.UTF-8,但是在系统中没有定义这个问题。</p>
<p>我们可以通过运行 locale -a 查看系统当前支持的locale定义。在上面我们看到,我们通过运行“localedef -v -c -i en_US -f UTF-8 en_US.UTF-8” 生成相应的locale配置文件。之后系统就不会报错了。</p>
centos6/7 squid 安装教程
https://segmentfault.com/a/1190000004336385
2016-01-20T10:07:14+08:00
2016-01-20T10:07:14+08:00
defcon
https://segmentfault.com/u/defcon
1
<p>因为内网限制,需要通过搭建squid代理服务器进行访问。这篇文章主要把我搭建的过程分享出来。</p>
<pre><code>/* 我这边主要通过rpm安装,具体的源码编译安装自己去看官网吧 */
/* 添加 yum squid源 */
/*参考ref http://wiki.squid-cache.org/SquidFaq/BinaryPackages#CentOS*/
# tee /etc/yum.repo.d/squid.repo << EOF
> [squid]
> name=Squid repo for CentOS Linux - $basearch
> #IL mirror
> baseurl=http://www1.ngtech.co.il/repo/centos/$releasever/$basearch/
> failovermethod=priority
> enabled=1
> gpgcheck=0
> EOF
# yum update
# yum install squid
# service squid start </code></pre>
<p>发现启动失败,设置/etc/squid/squid.conf visible_hostname值 <br><img src="/img/bVsl6V" alt="clipboard.png" title="clipboard.png"></p>
<p>启动再次失败,发现是权限的问题 </p>
<p><img src="/img/bVsl7l" alt="clipboard.png" title="clipboard.png"></p>
<p>检查本机上是否启动selinux安全模块</p>
<pre><code> /* 提示: 也可以使用 getenforce */
# sestatus -v</code></pre>
<p><img src="/img/bVsmap" alt="clipboard.png" title="clipboard.png"></p>
<p>关闭selinux有两种方式<br>第一种是修改 selinux配置文件,重启机器,永久生效<br>第二种是setenforce 0 设置暂时关闭,机器重启后,selinux更改恢复成enable状态</p>
<pre><code># cat /etc/selinux/config</code></pre>
<p><img src="/img/bVsmfx" alt="clipboard.png" title="clipboard.png"></p>
<p>我们这里暂时就直接设置命令setenforce 0<br>启动squid,发现现在可以启动了。</p>
<p>那么现在开始squid的配置之旅。<br>下几篇文章,我将深入介绍selinux和squid的配置。</p>
jenkins 集成 docker tomcat 自动化测试脚本
https://segmentfault.com/a/1190000004297705
2016-01-13T09:49:50+08:00
2016-01-13T09:49:50+08:00
defcon
https://segmentfault.com/u/defcon
1
<p>最近在搞jenkins + docker CI,把自己写的集成脚本分享出来给大家。里面很多路径或者名称是我自己的,自行替换掉。</p>
<pre><code>#!/bin/sh
# Jenkins Build Shell Script
# Author zdzhou@iflytek.com
set -e
# Get running docker image name
cid=`docker ps | grep 'isearch' | awk {'print $1'}`
echo $cid
# If exists running isearch docker image, stop and remove it
if [ -n "$cid" ]
then
echo Get the running docker container id of isearch: $cid
docker stop $cid
docker rm $cid
else
echo There is no running isearch docker container
fi
# Copy target war to dest directory
cd ${JENKINS_HOME}/workspace/${JOB_NAME}/itv-web/
echo Current work directory `pwd`
cp target/itv-web.war /usr/local/tomcat/webapps
echo Run docker image
docker run -d -p 8080:8080 -v /usr/local/isearch:/usr/local/isearch -v /usr/local/tomcat/webapps:/usr/local/tomcat/webapps --name=isearch${SVN_REVISION} isearch
# Wait for starting docker container
totalWait=0
until [ "`/usr/bin/docker inspect -f {{.State.Running}} isearch${SVN_REVISION}`" == "true" ]
do
totalWait=$[ $totalWait + 2 ]
if (( $totalWait > 10 ))
then
echo "Start docker container timeout"
exit 1
fi
echo "Waiting for starting docker container: $totalWait minute"
sleep 2m
done
echo "Start docker container success "
# Wait for starting tomcat
totalWait=0
until [ "`curl -o /dev/null --silent -m 10 --retry 1 --connect-timeout 10 --head --write-out '%{http_code}\n' http://127.0.0.1:8080/itv-web/v3/videosearch/?appid=aginomoto`" = "200" ]
do
totalWait=$[ $totalWait + 3 ]
if (($totalWait > 36 ))
then
echo "Start tomcat timeout"
exit 1
fi
echo "Wait for starting tomcat: "$totalWait" minute"
sleep 3m
done
echo "Start tomcat service success"
# Run automatic function test script
echo "Start automatic function test"
export LOG_HOME=${WORKSPACE}/test.log.d/${BUILD_NUMBER}
cd /data/jenkins_home/test.framework.d
exec ./automatic_test.sh
</code></pre>
<p>原文链接<a href="http://segmentfault.com/a/1190000004297705">http://segmentfault.com/a/1190000004297705</a></p>
数据库操作中遇到的坑
https://segmentfault.com/a/1190000004252446
2016-01-05T16:54:11+08:00
2016-01-05T16:54:11+08:00
defcon
https://segmentfault.com/u/defcon
0
<p>这篇文章主要是记录我在使用数据库中遇到的一些坑,不定期更新</p>
<p>## 案例一 SqlServer 2008 ##</p>
<ul>
<li><p>场景:<br> 数据对接,替合作伙伴完成影视搜索功能</p></li>
<li><p>具体实现<br> 合作伙伴提供数据接口,然后我们通过爬取合作伙伴的接口进行数据同步。每次我们都是全量爬取,但是入库操作是增量的。比如如果我们发现影视库中已经含有该条影视id,而且影视信息没有发生变化,那么不入库,如果已有该条影视,但是信息发生变化做update操作。如果库中没有该条影视id,做insert操作。如果全量爬取后,发现库中的某些影视id没有在这次爬取中爬到,那么对所有没有爬到的库中影视id做下线操作。当然如果发现库中已经下线的影视id,本次爬取到了,那么要做上线操作。每条影视数据,也就是对应数据库中的某一行都有相应的时间戳,表明对此次操作的时间。我们的数据平台系统不定期读取库中的数据。假如数据平台第一次全量读取库中所有的数据,同时会记录库中所有行数据中最高的时间戳。下次数据平台再次读取时,会查找大于上次时间戳的行数据,因为只有行数据中时间戳大于上次记录的时间戳,才表明数据发生了变化。然后数据平台进行建索引等等...</p></li>
<li><p>坑 <br> 合作伙伴反映很多数据搜不到了。明明接口中有相应的影视数据,但是我们搜索不到。</p></li>
<li><p>定位<br> 我们发现库中其实爬取到这些影视数据,但是数据平台在读取的时候没有读取到。考虑会不会是因为时间戳的原因?<br> 猜测:<br> 因为插入或者修改的事务是以单条影视为事务的,所以可能存在这种情况,当数据平台开启查询事务(默认read committed),因为时间戳没有做索引,所以数据库默认做全表查询,进行到一半的时候,爬取线程开始更新数据,一共更新200条,假设已经开始更新第1条记录,记录时间戳d1 但是数据平台已经开始读到100条记录了,所以更新的第1条记录没有查到。最后数据平台查找到的更新包含第100条到200条,然后找到最大的时间戳d2。数据平台处理完毕后,记录最大的时间戳d2。因为第1条记录更新那行上次没有查找到,下次查找是以行时间戳 > d2作为条件的,因为d2 > d1 所以下次数据平台查询就没有找到上次更新过的第一条记录。这样爬取所做的更新操作没有反映到数据平台中,新增的数据没有反映到数据平台同理。</p></li>
<li>
<p>解决方法<br> 第一种方法:一次全量爬取所做的更新,插入或者上下线操作全部放到同一个事务中提交,这样数据平台因为是(read committed)不会出现一次查询只能查询到部分更改的情况。</p>
<p>第二种方法:修改数据平台读取数据默认的事务级别,提高到Serializable级别,这样就不会存在数据平台开启的查询事务跟爬取线程所做的其他事务做互斥。不存在数据平台只读取到部分更改记录的情况。</p>
<p>第三种方法:修改数据根据上次读取到的最大时间戳做增量读取的协议。每条影视行增加两个时间戳,一个时间戳表示爬取线程做更新的最新时间戳,爬取线程每次更新影视的该时间戳。另一个时间戳表示数据平台读取该条记录的上一次时间。数据平台读取数据的条件是每行记录爬取更新的时间戳大于数据平台上一次读取的时间戳即可。</p>
<p>未完待续</p>
</li>
</ul>
<p>原文链接:<a href="http://segmentfault.com/a/1190000004252446">http://segmentfault.com/a/1190000004252446</a></p>
centos 7 学习笔记(一)
https://segmentfault.com/a/1190000004179739
2015-12-22T00:00:54+08:00
2015-12-22T00:00:54+08:00
defcon
https://segmentfault.com/u/defcon
0
<p>这篇文章主要记录自己在安装centos 7中遇到的一些问题以及个人工作的一些笔记:</p>
<ul><li>
<p>window7硬盘安装 centos7</p>
<p>wingrup</p>
<p>diskgenius</p>
<p>ntfs fat32 exfat</p>
<p>ntfs-3g</p>
<p>root(hd0, 9)<br> kernel /isolinux/vmlinuz linux repo=hd:/sda10:/<br> initrd /isolinux/initrd.img<br> boot</p>
<p>预先分配一个fat32的硬盘,容量足够烦的下磁盘就ok<br> 预装分配一个没有指定格式的空白分区,供centos安装使用</p>
<p>问题: 目前还没有解决iso文件超过4G,如何使用fat32文件系统安装?</p>
</li></ul>
<hr>
<ul><li><p>centos7 minimal 配置网络</p></li></ul>
<pre><code>$ nmcli d
$ nmtui
</code></pre>
centos rocksdb 性能测试笔记(二)
https://segmentfault.com/a/1190000004007466
2015-11-17T17:11:03+08:00
2015-11-17T17:11:03+08:00
defcon
https://segmentfault.com/u/defcon
1
<p>接着<a href="http://segmentfault.com/a/1190000003975291">centos rocksdb 性能测试笔记(一)</a><br>然后我就考虑使用hdparm禁用写入缓存,在centos下发现一直报驱动错误。在ubuntu上面禁用writeCache倒是可以的。<br><img src="/img/bVqYux" alt="clipboard.png" title="clipboard.png"></p>
<p>关键是关机重启之后,测试时取rocksdb中数据时发现系统cache仍然在不断的增加。</p>
<p>禁用失败,现在就开始换一种思路,在centos中运行一个docker实例,指定docker占用内存3.2G(服务所占内存) + 2G (额外容忍分配内存),主要是限制docker系统cache占用的内存不能多于2G,然后遍历数据库中的所有影视名称构造测试用例去搜索影视,测试搜索性能。</p>
<p>docker官方要求centos7,内核最低版本 3.10。在centos6.5中要想安装docker至少内核版本是2.6.32-431。</p>
<pre><code># cat /proc/version
</code></pre>
<p><img src="/img/bVqYwO" alt="clipboard.png" title="clipboard.png"><br>满足要求。在centos6.5中我们通过docker-io进行安装</p>
<pre><code>/* RHEL/CentOS 6 64-Bit yum install epel-release */
# wget http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
# rpm -ivh --force epel-release-6-8.noarch.rpm
# yum install docker-io /*因为docker基本要求centos7,所有我们通过docker-io进行安装*/</code></pre>
<pre><code># yum pull centos /*从docker-hub拉取centos镜像*/
# docker run -it --name=isearch --memory=5g -p 8090:8090 -v /usr/local/isearch:/usr/local/isearch centos /* /usr/local/isearch是搜索服务所需的一些安装环境和数据所在目录 */</code></pre>
<p>此时进入container内部,开始安装java环境</p>
<pre><code># wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/7u75-b13/jdk-7u75-linux-x64.tar.gz" /*下载jdk1.7 */
# tar -zxvf jdk-7u75-linux-x64.tar.gz
</code></pre>
<p>后面省略一些配置了。。。</p>
<p>启动isearch服务...<br>isearch(docker容器)运行状态,内存限定了有木有?<br><img src="/img/bVq5qA" alt="clipboard.png" title="clipboard.png"><br>然后我观察宿主机器的cache,发现也就2G左右,主要是我之前限定5G,除去搜索服务占用内存之外剩余的内存。。也就是限定了isearch(docker容器)使用的内存只有5G,哪怕是系统cache.</p>
<p>现在就进行愉快的测试吧。。哈哈。。。</p>
<p>原文链接<a href="http://segmentfault.com/a/1190000004007466">http://segmentfault.com/a/1190000004007466</a></p>
centos rocksdb 性能测试笔记(一)
https://segmentfault.com/a/1190000003975291
2015-11-10T14:48:33+08:00
2015-11-10T14:48:33+08:00
defcon
https://segmentfault.com/u/defcon
1
<p>最近把服务内存中的部分数据迁移到磁盘,存储引擎使用的rocksdb.迁移完毕,就迫不及待的进行了性能测试。我嘞个去,性能刚刚的。其实内心已经埋下了怀疑的种子,怎么可能?然后用top命令查看了下。</p>
<pre><code># top</code></pre>
<p><img src="/img/bVqPYp" alt="图片描述" title="图片描述"></p>
<p>呵呵,果然是filesystem cache的原因。但是作为有着严格求证精神的程序员(其实是对操作系统原理不算特别熟悉),赶紧写了个测试用例来证明自己的推测。</p>
<p>首先关闭服务,然后刷新和清除系统cache</p>
<pre><code>#sync
#echo 1 > /proc/sys/vm/drop_caches
#echo 2 > /proc/sys/vm/drop_caches
#echo 3 > /proc/sys/vm/drop_caches</code></pre>
<p><img src="/img/bVqQho" alt="图片描述" title="图片描述"></p>
<p>已知rocksdb数据库文件中存放了所有的影视实体信息,然后编写测试用例=》根据所有的影视id遍历所有的数据。我给测试用例分配了-Xmx1024M -Xms1024M</p>
<p><img src="/img/bVqQir" alt="图片描述" title="图片描述"></p>
<p>测试性能日志<br><img src="/img/bVqQit" alt="图片描述" title="图片描述"></p>
<p>再跑一次测试用例,观察性能日志<br><img src="/img/bVqQiG" alt="图片描述" title="图片描述"></p>
<p>尼玛,发现没有,78186/4377 = 17.862919808088倍啊.<br>如果你再次运行上面的命令,刷新和清理系统缓存,再跑测试用例,发现耗时跟第一次差不多。这个我就不贴具体的实例了。当然你可以用iostat观察磁盘的io情况,发现几乎没有读请求。因为全cache住了。<br><img src="/img/bVqQjB" alt="图片描述" title="图片描述"></p>
<p><strong>测试已经证明了我们的结果。</strong><br>关于操作系统中buffer和cache的机制,自己感兴趣的可以去查下。</p>
<p>那如果这样,那么测试得出的结论对于第一次请求耗时来说毫无意义。这时候再想想我们是否可以把文件系统的cache给关闭掉,然后进行测试?</p>
<p>下一篇文章我们继续>></p>
<p>原文链接<a href="http://segmentfault.com/a/1190000003975291">http://segmentfault.com/a/1190000003975291</a></p>
java位运算和字节编码(二)
https://segmentfault.com/a/1190000003826303
2015-10-07T19:28:08+08:00
2015-10-07T19:28:08+08:00
defcon
https://segmentfault.com/u/defcon
1
<p><a href="http://segmentfault.com/a/1190000003758605">上一篇文章</a>我们讨论到byte强制转换成int。然后抛出了一个问题,就是byte直接强制转换成int一定会没问题吗?还是因为上面我们推导的那个例子只是个特例。好吧,正式回答下,一定是没有问题的。虽然发生了符号位扩展,但是没有任何问题。这就是计算机中原码和补码的奥秘之处。</p>
<p>我们来看下JDK中Integer类中的静态方法numberOfLeadingZeros</p>
<pre><code> public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) { n += 16; i <<= 16; }
if (i >>> 24 == 0) { n += 8; i <<= 8; }
if (i >>> 28 == 0) { n += 4; i <<= 4; }
if (i >>> 30 == 0) { n += 2; i <<= 2; }
n -= i >>> 31;
return n;
}</code></pre>
<p>乍一看,啥跟啥。完全看不懂。不过等你看懂了就会发现位运算的巧妙之处。这个函数的作用呢其实就是计算整形变量i高位连续为零的个数。比如 i = 1 时 二进制补码就是原码也就是 00000000 00000000 00000000 00000001,那么得到的结果应该是31。这个函数本身有什么用呢,自己去猜吧。好吧,不卖光子了,比如你想知道一个int整形的比1021这个整数更大而且属于2的次方幂。<br>1 << (32 - (Integer.numberOfLeadingZeros(i - 1))),得到的结果应该是1024。</p>
<p>未完待续 ...<br>原文链接:<a href="http://segmentfault.com/a/1190000003826303">http://segmentfault.com/a/1190000003826303</a></p>
java位运算和字节编码(一)
https://segmentfault.com/a/1190000003758605
2015-09-17T14:58:20+08:00
2015-09-17T14:58:20+08:00
defcon
https://segmentfault.com/u/defcon
1
<p>我们都知道计算机存储的是二进制,长度是8个比特。</p>
<pre><code>byte b = 10;
int i = -10;
long l = 10L;</code></pre>
<p>那么以上几个变量如何用二进制表示呢?<br>很多人第一印象肯定是 b变量占用一个字节,也就是8位,所以 b用二进制表示就是 1010,高位补零,所以是 00001010。那么i是int,占用4个字节,也就是32位,但因为是负数,所以最高位是100000000 00000000 00000000 00001010。同理 l变量则是 00000000 00000000 00000000 00000000 000000000 00000000 00000000 00001010。 </p>
<p>上面的推理过程中存在一处错误,就是在java中存储的是补码,而不是原码。正数的原码,反码和补码相同。负数则不是一样。以上面 int i = -10 作为例子。</p>
<pre><code>变量 i
原码 1000 1010
# 反码是符号位不变,其他位取反
反码 1111 0101
#补码则是在反码的基础上加1
补码 1111 0110
</code></pre>
<p>所以-10 在计算机中正确的表示应该是 1111 0110。<br>上面我们已经学习原码,反码和补码相关的概念,至于为什么要用补码,感兴趣的可以自己去查。 </p>
<p>我们也知道在网络传输中,存储的就是二进制相关的byte数组。那么现在我们需要往消息中写入int或者long相关的信息,如何转换为byte数组中的相关项?第一个想到的办法就是强制转换。</p>
<pre><code>int x = 135;
System.out.println((byte)x); /** 输出-121 **/
x = -135;
System.out.println((byte)x); /** 输出-121 **/
</code></pre>
<p>可见输出的答案并不如你所愿。想知道为什么这样吗?感兴趣的可以自己去推导。<br>额,还是帮你们推导一遍吧。我们以 -135为例。<br>首先我们知道 int 占用4个字节,而byte占用1个字节,同时你也看到 -135已经超出了一个字节所能表示的范围[-128, 127]。如果强制转换后还能显示出正确结果,那才恐怖啊。</p>
<pre><code>-135
原码 10000000 00000000 00000000 10000111
反码 11111111 11111111 11111111 01111000
补码 11111111 11111111 11111111 01111001
</code></pre>
<p>我们知道java中存储的是补码,可是因为byte只占一个字节,所以转换的时候只取到最低位那个字节也就是 01111001作为转换后补码存在。因为正数的原码和补码相同,也即是这个字节的原码是01111001,原值是 121。</p>
<p>刚才我们讨论的是int强制转换成byte类型可能存在的问题。那么如果我需要把byte类型的强制转换成int类型的会出现什么样的问题。</p>
<pre><code>byte num = 120;
System.out.println((int)num);
num = -120;
System.out.println((int)num);
</code></pre>
<p>大家看到这两行示例代码时。尽量自己尝试推导出结果来,不要看我下面的推导过程。<br>我们以-120作为例子,尝试推导下(看到没有,我喜欢负数)。</p>
<pre><code>-120
原码 11111000
反码 10000111
补码 10001000
##此刻开始转换为int类型,占4个字节,符号位1,用1填充高位三个字节。如果符号位是0,高位会用0填充。
补码 11111111 11111111 11111111 10001000 /** 存储在内存中的int整形补码 **/
反码 11111111 11111111 11111111 10000111
原码 10000000 00000000 00000000 01111000
原值 -120</code></pre>
<p>所以 当 byte num = -120 时, (int)num = -120 <br>我相信很多人都没有自己推导,直接看我的推导的。那么再给你个机会,尝试推导下当byte num = 120的时候推导过程。</p>
<p>通过byte强制转换成int 好像一切都很顺利,也没有int强制转换成byte相关的问题。<br>当真的是那样吗?下一篇我们继续探讨这个话题。</p>
<p>原文链接 <a href="http://segmentfault.com/a/1190000003758605/">http://segmentfault.com/a/1190000003758605/</a></p>
CentOS 6.5 编译 rocksdb java
https://segmentfault.com/a/1190000003717096
2015-09-07T17:36:39+08:00
2015-09-07T17:36:39+08:00
defcon
https://segmentfault.com/u/defcon
0
<p><strong>Step1 安装git服务,用于从github拉取rocksdb源代码.如果你想从源码安装最新版git,参考</strong><a href="https://link.segmentfault.com/?enc=8FSlHS07SwRpC%2B9vHl3wGg%3D%3D.kWiicWPmKDz2X66RaTz8SA7iMARWKB0YBFbfiDQLTg9eAvuBSZ2vkEbEV2MMjOHqQRv3DSHwRNZO5IUYaavECj3aqKAoRJsU%2BO4%2FwO5obUFq1jTLb0fXVFexTvX2KjbO" rel="nofollow">源码安装git</a></p>
<pre><code># yum install git
</code></pre>
<p>安装后可以使用git --version 查看版本号</p>
<p><strong>Step2 从github拉取rocksdb代码</strong></p>
<pre><code># git clone git@github.com:facebook/rocksdb.git</code></pre>
<p>关于git clone拉取代码时出现permission denied具体的配置参考<a href="https://link.segmentfault.com/?enc=fg4nymo55mdR6THKqJf0%2FQ%3D%3D.tB8OxGM5Dj5zCiKu3%2B8pYPGSuFt6HGLBkIPBkG41AmVBDzPlcjrrYVLgPkbB7S70Nb9wVrw22PlfhCkZ1%2F4n1A%3D%3D" rel="nofollow">github ssh 配置</a></p>
<p><strong>Step3 更新g++ 版本,因为rocksdb需要支持C++11特性</strong><br>因为从源码编译安装依赖比较多,容易出问题。这里暂时推荐使用源安装 参考<a href="https://link.segmentfault.com/?enc=8pT2mgPajkx6ntDzMBXy9Q%3D%3D.amZX8IFGksZDhYL1n7dlhPjAPwyq91mimk051zRUJIRDelzQpZg%2B1wZbzBVaEd2w0c10e4xKYEszE8yYuGSttksaREJfN8CayWbWSYu9VxDnNFGH%2FsPRCPEKNB6%2FP64m" rel="nofollow">安装gcc4.8.2</a>不过这个回答中repo的baseUrl写错了。替换为<a href="https://link.segmentfault.com/?enc=1ZehRqR9ZrHyB985TxuR1A%3D%3D.EpFuQmITmXgr1AO5mE2FEAe7681qbh5IdX2Dvm8cz%2BuNzQyTKo%2FS9BTMQ2bXO%2BJo" rel="nofollow">http://people.centos.org/tru/devtools-2/6/</a>$basearch/RPMS/</p>
<pre><code># yum install devtoolset-2-gcc-4.8.2 devtoolset-2-gcc-c++-4.8.2 devtoolset-2-binutils.x86_64
# ln -s /opt/rh/devtoolset-2/root/usr/bin/* /usr/local/bin/
# hash -r
# g++ --version</code></pre>
<p>这是显示的g++版本应该是4.8.2了<br>你可以使用as --version查看当前的binutils版本。因为CentOS自带的binutils版本过低,会导致rocksdb编译通不过。</p>
<p><strong>Step4 设置环境变量 </strong></p>
<pre><code># export JAVA_HOME=/usr/java/jdk1.7.0_71/
# export PATH=$PATH:/usr/java/jdk1.7.0_71/bin</code></pre>
<p><em>以上设置的环境变量都是临时的,建议设置成永久全局变量</em>。命令如下</p>
<pre><code># vim /etc/profile.d/java.sh</code></pre>
<p>把上面的两行命令复制到此文件内,保存退出,然后运行以下命令刷新环境变量</p>
<pre><code># source /etc/profile
</code></pre>
<p><strong>Step5 开始构建rocksdb java</strong></p>
<pre><code># cd /usr/local/rocksdb
# make rocksdbjava</code></pre>
<p><strong><em>注意:在构建的过程运行的到进行编译测试用例的时候,需要下载jar包。这时候如果出现错误,重新运行命令 make rocksdbjava .如果仍然报错,比如打不开某个文件,把java/test-libs下的jar全部删掉。</em></strong></p>
<p>原文链接:<a href="http://segmentfault.com/a/1190000003717096">http://segmentfault.com/a/1190000003717096</a> 爬虫们,不要乱爬啊。</p>
CentOS 6.6 64位 install apktool ia32-libs
https://segmentfault.com/a/1190000003054979
2015-08-05T14:24:33+08:00
2015-08-05T14:24:33+08:00
defcon
https://segmentfault.com/u/defcon
0
<p>前面的步骤一些基本步骤我不再重复。<br>因为apktool需要32位的安装环境。</p>
<pre><code>yum provides ld-linux.so.2
yum install glibc.i686
yum install zlib-1.2.3-29.el6.i686
yum install libstdc++.i686 0:4.4.7-11.el6
如果第二个报错,在yum后面加上 --setopt=protected_multilib=false</code></pre>
CentOS 6.5/6.6 安装(install)mysql 5.7 最完整版教程
https://segmentfault.com/a/1190000003049498
2015-08-04T10:34:30+08:00
2015-08-04T10:34:30+08:00
defcon
https://segmentfault.com/u/defcon
11
<p>Step1: 检测系统是否自带安装mysql</p>
<pre><code># yum list installed | grep mysql
</code></pre>
<p>Step2: 删除系统自带的mysql及其依赖<br>命令:</p>
<pre><code># yum -y remove mysql-libs.x86_64
</code></pre>
<p>Step3: 给CentOS添加rpm源,并且选择较新的源<br>命令:</p>
<pre><code># wget dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm
# yum localinstall mysql-community-release-el6-5.noarch.rpm
# yum repolist all | grep mysql
# yum-config-manager --disable mysql55-community
# yum-config-manager --disable mysql56-community
# yum-config-manager --enable mysql57-community-dmr
# yum repolist enabled | grep mysql
</code></pre>
<p>Step4:安装mysql 服务器<br>命令:</p>
<pre><code># yum install mysql-community-server
</code></pre>
<p>Step5: 启动mysql<br>命令:</p>
<pre><code># service mysqld start
</code></pre>
<p>Step6: 查看mysql是否自启动,并且设置开启自启动<br>命令:</p>
<pre><code># chkconfig --list | grep mysqld
# chkconfig mysqld on
</code></pre>
<p>Step7: mysql安全设置<br>命令:</p>
<pre><code># mysql_secure_installation
</code></pre>
<p>参考相关文档地址: <br><a href="https://link.segmentfault.com/?enc=%2FJo0XubGTxjPRmTe0j0OhQ%3D%3D.Txi1Jz17KZKyp7XwIaWSrmIiZJUWD6IdUAujTusja1naP2Gu83%2B8Dc4BNMBNdFt7ddV4i16Oi7KdpiBBP3RiRo%2B1%2FinhNof03WL8UegPRHXeB6e3%2FDP%2BNMyE9kCovLpn" rel="nofollow">http://www.rackspace.com/know...</a><br><a href="https://link.segmentfault.com/?enc=8YuL0F0B8C%2FKmIBiZXl0%2BA%3D%3D.Cxkqxkz3QL4xBDCV5ew6zCxysOuxgVZNonBxtq6u5jbTnCAZ18Ce7HXZdAjLX%2B264WCbuvKtym1JVrEx%2BLULsIqSJzLOovQWxLrRcIijZKI%3D" rel="nofollow">http://dev.mysql.com/doc/refm...</a><br><a href="https://link.segmentfault.com/?enc=1i06l%2BRzyqGGmGakO4ip6A%3D%3D.PH9QhJS3K45TrT6q5QEoA15Hfzm5Pzqhp55jm2fmRXHYZlMxPM3LxM2sgaYviuk4%2FG8jk7qIzySsK3SYUUBq5jTV05i%2Fsjoy5iGjhA86RF4%3D" rel="nofollow">http://www.cnblogs.com/xiaolu...</a></p>
编程浅谈
https://segmentfault.com/a/1190000003017324
2015-07-25T01:46:53+08:00
2015-07-25T01:46:53+08:00
defcon
https://segmentfault.com/u/defcon
4
<p>不知不觉毕业已三年有余,加上自己在大学或者实习的时间,作为一名程序员差不多已有四年到五年。记得还在实习期间,经常炫耀自己写Flex,就鄙视那些用JSP等模板引擎渲染成前端页面的人。比如自己用了Spring Mybatis等框架就开始嘲笑那些还在用servlet的人。偶尔还会陷入某种语言之争,或者陷入对某种语言的偏执或者偏见。</p>
<p>其实即便在工作大概2年左右,偶尔还会陷入类似的一些陷阱,比如我经常拿来说的就是除了会写Java还会写C#,javascript和c++,或者说我学会了用某个框架或者所谓的新技术,比如开始学redis,docker这些所谓的高大上的东西.但从去年下半年开始,我好像开始慢慢领悟到编程的本质。我更加偏向于脱离于编程语言层面的一些东西,比如各种编程范式的区别,各种编程语言的并发模型和内存模型,异构系统之间的交互,接口的设计,底层的网络通信,各种协议的设计初衷以及区别,分布式服务架构的设计考虑的容错,资源隔离,监控,服务之间通信的异常处理,服务的优雅降级等等问题。比如在服务化的架构设计中,对于服务发现有多少种方式,比如常见的网关,富客户端以及服务注册中心的机制,以及他们之间的优缺点和设计过程中的一些折衷。比如一上来有个架构师就跟你说用服务注册,然后巴拉巴拉再说用什么开源框架,这种架构师多半是半吊子。在工作中当然也遇到一些架构师,他们设计架构其实不是在设计,而是在套用架构,他们硬是把自己的业务设计的符合这套架构。再比如说你一跟他说需要共享session,他们马上立即吐口而出什么redis,memcached. 跟他说搜索,他就说lucene,跟他说分布式他就直接说什么一致性哈希,再比如跟他说存储就说mysql,mongodb.这些人全都是半吊子先生,甚至在我看来就是压根还没有入门。再举服务发现这个例子,一上来就说zookeeper,有些人也许还知道keepalived, 更有心的也许还知道eureka。那我就问在使用zookeeper如何解决网络分割问题?他肯定说额,什么是网络分割。去年在团队分享过一次redis相关的知识,其实我并没有分享redis的哪些api,而是分享了redis的一些应用场景以及和传统关系型数据库的一些区别,以及redis内部本身一些比如主从数据同步的方式等。第二天主管问其他同事,有些说没有干货。这就是不同层次关心不同问题的典型案例。说实话那个api有什么可讲的,直接看文档就好了。</p>
<p>今天在技术中心评审中其实也发现了这个问题,好像大家对使用了某个框架或者所谓的技术很感兴趣,就好像使用了某个框架就会了什么武林秘籍一样。说实话编程到一定的年限,把使用了某种框架当做新技术我觉得是很可悲的一件事,反正我熟悉一个新的技术要么熟悉内部原理借鉴思路,要么可以做二次开发,我可从来不认为简单用了下框架就是什么新技术。所以可以看到这一年来,我真没有说过什么其他框架名词,我只说解决了什么问题。今天在评审中涉及对我们团队一些开发规范的质疑,说实话要说流程规范,文档规范的确很欠缺,对于程序员来说开发规范和编写好文档的确是一项很重要的技能,我在团队也一直在强调开发的规范化和工程化,但目前似乎没有引起什么重视,但问题越拖越会暴露出瓶颈的。但说实话比如我设计的架构我需要文档化,但你知道我们的开发周期跟大团队的开发周期完全不是一个量级的。你们经常几个人一个团队做好几个人月,甚至还不包括需求分析阶段,而我们呢?关于技术中心的编程范型,从个人角度来说,也许对新人还有一定的用处,但对我们来说毫无用处,如果你连为什么毫无用处都不知道,我想还是不要编程算了。技术中心目前的通用技术储备还蛮落后的,至少要落后一线互联网公司五年左右、举个例子吧,我刚来讯飞的时候,发现大部分前端开发都只是是用用jQuery和开始探索前端mvc模式,可是你知道我作为一名不是全职前端人员,差不多1年前就那么做了吗?</p>
webapp最佳实践
https://segmentfault.com/a/1190000002775214
2015-05-18T15:08:34+08:00
2015-05-18T15:08:34+08:00
defcon
https://segmentfault.com/u/defcon
0
<p>//author defconzhou<br>
//history 2015-05-16</p>
<p>//web开发最佳实践</p>
<p>//内存管理,主动释放掉不需要的对象</p>
<p>//及时释放元素不再需要的的监听器 listener</p>
<p>//递归引用定时器,过多监听器引起的内存泄露 ref <a rel="nofollow" href="http://252190908.iteye.com/blog/1969500">http://252190908.iteye.com/blog/1969500</a></p>
<p>//使用chrome调试来查找系统的瓶颈</p>
<p>//尽量使用原生js来操作dom树</p>
<p>//更新dom树在某些情况,可以使用document fragments来修改</p>
<p>//css中2D动画改为3D,强制开启GPU加速</p>
<p>//将js代码独立出来</p>
<p>//前端尽量避免DOM过多插入</p>
<p>//使用本地缓存 localstorage sqlite ajax缓存</p>
<p>//限定模块的高度,避免页面出现太大的波动</p>
<p>//先界面,后数据</p>
<p>//避免click触发,使用touch</p>
<p>//使用本地滚动条 -webkit-overflow-scrolling: touch;</p>
<p>//选择合适的图片尺寸,尽量能做到针对不同的屏幕大小,返回不同的图片尺寸</p>
<p>//Best Pratice ref <a rel="nofollow" href="http://www.w3.org/2010/09/MWABP/">http://www.w3.org/2010/09/MWABP/</a></p>
<p>//Protecting Your Web App ref <a rel="nofollow" href="https://developer.amazon.com/public/solutions/platforms/webapps/docs/best-practices.html">https://developer.amazon.com/public/solutions/platforms/webapps/docs/b...</a><br>
//Plan for Changes in Device Orientation <a rel="nofollow" href="https://developer.amazon.com/public/solutions/platforms/webapps/faq#Web">https://developer.amazon.com/public/solutions/platforms/webapps/faq#We...</a> App Development<br>
//Use Responsive Design Techniques <a rel="nofollow" href="https://developer.amazon.com/public/solutions/platforms/webapps/docs/web-app-security.html">https://developer.amazon.com/public/solutions/platforms/webapps/docs/w...</a><br>
//Lock Orientation in Portrait or Landscape Mode<br>
//Instruct Customers to Rotate the Device<br>
//Turn Off Touch Feedback<br>
// body {<br>
// -webkit-tap-highlight-color: rbga(255, 255, 255, 0);<br>
// -webkit-user-select: none;<br>
// }<br>
//Make Sure Your Web App Fills the Screen<br>
//Best Practices for Game Apps<br>
//Best Practices for Using Element<br>
//Best Practices to Optimize Compositing Layers for Web Apps<br>
//Best Practices for JavaScript</p>
<p>//Use web storage in place of cookies ref <a rel="nofollow" href="http://www.html5rocks.com/en/tutorials/speed/quick/">http://www.html5rocks.com/en/tutorials/speed/quick/</a><br>
//Use CSS Transitions instead of JavaScript animation<br>
//Use client-side databases instead of server roundtrips<br>
//JavaScript improvements lend considerable performance advantages<br>
//Use cache manifest for live sites, not just offline apps<br>
//Enable hardware acceleration to enhance visual experience<br>
//For CPU-heavy operations, Web Workers deliver<br>
// HTML5 Form attributes and input types<br>
//Use CSS3 effects instead of requesting heavy image sprites<br>
//WebSockets for faster delivery with less bandwidth than XHR</p>
<p>//Minimize HTTP Requests ref <a rel="nofollow" href="https://developer.yahoo.com/performance/rules.html">https://developer.yahoo.com/performance/rules.html</a><br>
//Use a Content Delivery Network<br>
//Add an Expires or a Cache-Control Header<br>
//Gzip Components<br>
//Put Stylesheets at the Top<br>
//Put Scripts at the Bottom<br>
//Avoid CSS Expressions<br>
//Make JavaScript and CSS External<br>
//Reduce DNS Lookups<br>
//Minify JavaScript and CSS<br>
//Avoid Redirects<br>
//Remove Duplicate Scripts<br>
//Configure ETags<br>
//Make Ajax Cacheable<br>
//Flush the Buffer Early<br>
//Use GET for AJAX Requests<br>
//Post-load Components<br>
//Preload Components<br>
//Reduce the Number of DOM Elements<br>
//Split Components Across Domains<br>
//Minimize the Number of iframes<br>
//Reduce Cookie Size<br>
//Use Cookie-free Domains for Components<br>
//Minimize DOM Access<br>
//Develop Smart Event Handlers<br>
//Choose <link> over @import<br>
//Avoid Filters<br>
//Optimize Images<br>
//Optimize CSS Sprites<br>
//Don't Scale Images in HTML<br>
//Make favicon.ico Small and Cacheable<br>
//Keep Components under 25K<br>
//Pack Components into a Multipart Document<br>
//Avoid Empty Image src</p>
<p>//cache ref <a rel="nofollow" href="https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn">https://developers.google.com/web/fundamentals/performance/optimizing-...</a><br>
//ETag If-None-Match</p>
<p>// performance ref <a rel="nofollow" href="http://www.html5rocks.com/en/features/performance">http://www.html5rocks.com/en/features/performance</a> <a rel="nofollow" href="https://developers.google.com/speed/pagespeed/insights/?url=m.yuyin.tv&tab=mobile">https://developers.google.com/speed/pagespeed/insights/?url=m.yuyin.tv...</a><br><a rel="nofollow" href="http://developer.android.com/guide/webapps/best-practices.html">http://developer.android.com/guide/webapps/best-practices.html</a><br><a rel="nofollow" href="https://www.owasp.org/index.php/Category:OWASP_Best_Practices:_Use_of_Web_Application_Firewalls">https://www.owasp.org/index.php/Category:OWASP_Best_Practices:_Use_of_...</a><br><a rel="nofollow" href="http://www.toptal.com/android/developing-mobile-web-apps-when-why-and-how">http://www.toptal.com/android/developing-mobile-web-apps-when-why-and-...</a><br><a rel="nofollow" href="https://developers.google.com/apps-script/guides/html/best-practices">https://developers.google.com/apps-script/guides/html/best-practices</a></p>
<p><a rel="nofollow" href="http://www.html5rocks.com/en/tutorials/speed/static-mem-pools/">http://www.html5rocks.com/en/tutorials/speed/static-mem-pools/</a><br><a rel="nofollow" href="http://www.html5rocks.com/en/tutorials/webdatabase/todo/">http://www.html5rocks.com/en/tutorials/webdatabase/todo/</a><br><a rel="nofollow" href="https://html.spec.whatwg.org/multipage/browsers.html#manifests">https://html.spec.whatwg.org/multipage/browsers.html#manifests</a><br><a rel="nofollow" href="http://www.html5rocks.com/en/tutorials/appcache/beginner/">http://www.html5rocks.com/en/tutorials/appcache/beginner/</a><br><a rel="nofollow" href="http://www.mobify.com/blog/beginners-guide-to-http-cache-headers/">http://www.mobify.com/blog/beginners-guide-to-http-cache-headers/</a></p>
Java大数据内存序列化浅谈(一)
https://segmentfault.com/a/1190000002519091
2015-01-28T13:45:14+08:00
2015-01-28T13:45:14+08:00
defcon
https://segmentfault.com/u/defcon
1
<p><strong>谈任何技术前,不讨论实际的应用场景都是耍流氓。</strong></p>
<p>首先说下自身项目背景。业务只读不写,且数据量不大,这两个特性很大程度上决定了我们的系统架构。</p>
<blockquote>
<p><strong>Step 0</strong> 数据平台持久化数据到数据库(SQLServer)中,然后压缩数据库文件成压缩文件<br><strong>Step 1</strong> 上传压缩文件到云端,利用云端进行自动同步<br><strong>Step 2</strong> 通知各个节点下载数据,下载到指定目录<br><strong>Step 3</strong> 解压文件,附加数据库文件为新库<br><strong>Step 4</strong> 节点读取新数据库数据到内存,新旧数据内存切换,新数据生效,旧数据失效</p>
</blockquote>
<p>通过描述看到我们的业务服务器其实把数据库作为一个临时数据存储,一旦读取过新数据,便不再访问数据库,因为数据全部读到内存当中。我们</p>
nginx中健康检查(health_check)机制深入分析
https://segmentfault.com/a/1190000002446630
2014-12-26T14:03:01+08:00
2014-12-26T14:03:01+08:00
defcon
https://segmentfault.com/u/defcon
5
<p>很多人都知道nginx可以做反向代理和负载均衡,但是关于nginx的健康检查(health_check)机制了解的不多。其实社区版nginx提供的health_check机制其实很薄弱,主要是通过在upstream中配置max_fails和fail_timeout来实现,这边文章主要是深入分析社区版的health_check机制,当然还有更好的一些建议,比如商业版的nginx plus或者阿里的tengine,他们包含的健康检查机制更加完善和高效,如果你坚持使用nginx社区版,当然还可以自己写或者找第三方模块来编译了。</p>
<hr>
<p>首先说下我的测试环境,CentOS release 6.4 (Final) + nginx_1.6.0 + 2台tomcat8.0.15作为后端服务器。(声明:以下所有配置仅仅为测试所用,不代表线上环境真实所用,真正的线上环境需要更多配置和优化。)<br>
nginx配置如下:</p>
<pre><code>#user nobody;
worker_processes 1;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log logs/access.log main;
sendfile on;
keepalive_timeout 65;
upstream backend {
server localhost:9090 max_fails=1 fail_timeout=40s;
server localhost:9191 max_fails=1 fail_timeout=40s;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://backend;
proxy_connect_timeout 1;
proxy_read_timeout 1;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
</code></pre>
<p>}</p>
<p>关于nginx和tomcat的配置的基本配置不再说明,大家可以去看<a rel="nofollow" href="http://nginx.org/en/docs/">官方文档</a>。<br>
我们可以看到我在upstream 指令中配置了两台server,每台server都设置了max_fails和fail_timeout值。</p>
<hr>
<p>现在开始启动nginx,然后启动后台的2台server, 故意把在Tomcat Listener中Sleep 10分钟,也就是tomcat启动要花费10分钟左右,端口已开,但是没有接收请求,然后我们访问<a rel="nofollow" href="http://localhost/response/">http://localhost/response/</a> (response这个接口是我在tomcat中写的一个servlet接口,功能很简单,如果是9090的server接收请求则返回9090,如果是9191端口的server则返回9191.),现在观察nginx的表现。</p>
<hr>
<p>我们查看nginx中</p>
<h2> access.log</h2>
<pre><code>192.168.42.254 - - [29/Dec/2014:11:24:23 +0800] "GET /response/ HTTP/1.1" 504 537 720 380 "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36" 2.004 host:health.iflytek.com
192.168.42.254 - - [29/Dec/2014:11:24:24 +0800] "GET /favicon.ico HTTP/1.1" 502 537 715 311 "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36" 0.000 host:health.iflytek.com
</code></pre>
<h2> error.log</h2>
<pre><code>2014/12/29 11:24:22 [error] 6318#0: *4785892017 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 192.168.42.254, server: health.iflytek.com, request: "GET /response/ HTTP/1.1", upstream: "http://192.168.42.249:9090/response/", host: "health.iflytek.com"
2014/12/29 11:24:23 [error] 6318#0: *4785892017 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 192.168.42.254, server: health.iflytek.com, request: "GET /response/ HTTP/1.1", upstream: "http://192.168.42.249:9191/response/", host: "health.iflytek.com"
2014/12/29 11:24:24 [error] 6318#0: *4785892017 no live upstreams while connecting to upstream, client: 192.168.42.254, server: health.iflytek.com, request: "GET /favicon.ico HTTP/1.1", upstream: "http://health/favicon.ico", host: "health.iflytek.com"
</code></pre>
<p><strong>(为什么要在listener中设置睡眠10分钟,这是因为我们的业务中需要做缓存预热,所以这10分钟就是模拟服务器启动过程中有10分钟的不可用。)</strong></p>
<hr>
<p>观察日志发现在两台tomcat启动过程中,发送一次请求,nginx会自动帮我们进行重试所有的后端服务器,最后会报 no live upstreams while connecting to upstream错误。这也算是nginx做health_check的一种方式。这里需要特别强调一点,我们设置了proxy_read_timeout 为 1秒。后面再重点讲解这个参数,很重要。</p>
<hr>
<p>等待40s,现在把9090这台服务器启动完成,但是9191这台服务器仍然是正在启动,观察nginx日志表现。</p>
<p>access.log</p>
<pre><code>192.168.42.254 - - [29/Dec/2014:11:54:18 +0800] "GET /response/ HTTP/1.1" 200 19 194 423 "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36" 0.210 host:health.iflytek.com
192.168.42.254 - - [29/Dec/2014:11:54:18 +0800] "GET /favicon.ico HTTP/1.1" 404 453 674 311 "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36" 0.212 host:health.iflytek.com
</code></pre>
<h2>error.log</h2>
<p>没有打印任何错误</p>
<p><strong>浏览器返回9090,说明nginx正常接收请求。</strong></p>
<p>我们再次请求一次。</p>
<h2>access.log</h2>
<pre><code>192.168.42.254 - - [29/Dec/2014:13:43:13 +0800] "GET /response/ HTTP/1.1" 200 19 194 423 "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36" 1.005 host:health.iflytek.com
</code></pre>
<p>说明正常返回,同时返回9090</p>
<p>error.log</p>
<pre><code>2014/12/29 13:43:13 [error] 6323#0: *4801368618 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 192.168.42.254, server: health.iflytek.com, request: "GET /response/ HTTP/1.1", upstream: "http://192.168.42.249:9191/response/", host: "health.iflytek.com"
</code></pre>
<p><strong>发现nginx error.log 增加了一行upstream time out的错误。但是客户端仍然正常返回,upstream默认是轮训的负载,所以这个请求默认会转发到9191这台机器,但是因为9191正在启动,所以这次请求失败,然后有nginx重试转发到9090机器上面。</strong></p>
<hr>
<p>OK,但是fail_timeout=40s是什么意思呢?我们要不要重现一下这个参数的重要性?Let's go !<br>
现在你只需要静静的做个美男子,等待9191机器启动完毕!多发送几次请求!然后咦,你发现9191机器返回9191响应了噢!fail_timeout=40s其实就是如果上次请求发现9191无法正常返回,那么有40s的时间该server会不可用,但是一旦超过40s请求也会再次转发到该server上的,不管该server到底有没有真正的恢复。所以可见nginx社区版的health_check机制有多么的薄弱啊,也就是一个延时屏蔽而已,如此周而复始!如果你用过nginx plus其实你会发现nginx plus 提供的health_check机制更加强大,说几个关键词,你们自己去查! zone slow_start health_check match ! 这个slow_start其实就很好的解决了缓存预热的问题,比如nginx发现一台机器重启了,那么会等待slow_starts设定的时间才会再次发送请求到该服务器上,这就给缓存预热提供了时间。</p>
Java中异常处理进阶篇(一)
https://segmentfault.com/a/1190000000685622
2014-09-21T18:22:36+08:00
2014-09-21T18:22:36+08:00
defcon
https://segmentfault.com/u/defcon
0
<p>关于Java异常一些基本的知识我这里就不在说了,大家可以去网上学习下。今天我主要讲解一下Java中异常处理的一些原则,我会尽量以代码的例子带大家去直观的感受异常的处理。<br>
首先我们第一个疑惑的问题是异常到底是直接处理,还是向上抛出,供调用方处理或者框架统一集中处理,最后就是我们讲解如何写出一个我们自己的异常处理框架。</p>
<hr>
<p><strong>关于异常处理我总结了以下观点:</strong><br>
1.对于一些自定义的异常或者可预见的一些异常,应用异常,比如用户密码不正确,需要的某个资源不存在等等,抛出自定义检查型异常,但是真没有必要每个异常都定义一个异常类,让操作及时中止,然后交由异常处理框架统一处理。<br>
2.对于调用一些涉及到需要关闭资源的操作,及时在finally里面关闭资源,同时转义成检查性异常,交由异常处理框架统一处理<br>
3.一些无法预知的异常,系统异常,比如空指针等异常,无需捕获,交由异常处理框架统一处理<br>
4.无论是你喜欢抛出检查性异常还是转义成非检查性异常,涉及到分包开发或者交由外部调用的必须在方法内注明可能抛出的异常类型,文档一定要健全。</p>
<p>我上面说的一般是J2EE开发相关的,如果涉及到性能要求比较苛刻,可能这个处理就不太合适了。同时可以看出来我喜欢用非检查型异常,提倡约定和文档,不喜欢检查性异常,不喜欢一层层的抛出。这个仁者见仁,智者见智,我说的也就是一家之言吧。</p>
<p>下面我们开始看各种示例代码:</p>
<p>后面接着写。。</p>
Java中转UTC时间字符串(含有T Z)为local时间
https://segmentfault.com/a/1190000000646877
2014-08-24T16:13:45+08:00
2014-08-24T16:13:45+08:00
defcon
https://segmentfault.com/u/defcon
5
<p>在Java中我们需要转换相应格式的字符串,很多时候我们想到用SimpleDateFormat类来解析。但是最近我在调用一个第三方的接口时返回的JSON字符串中有个expires字段的值是2014-08-24T09:27:42Z。第一反应肯定是跟时区关联的。那么如何解析成Date类呢?</p>
<pre><code>SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(df.parse("2014-08-23T09:20:05Z").toString());
</code></pre>
<p>现在看看打印出的信息是不是你要的吧?博客的标题含有 TZ这两个关键词,主要是为了方便 别人检索,因为很多人不知道这就是UTC时间啊。这下别人搜TZ 也许能搜到这篇博客了。</p>
ubuntu 14.04 nginx 配置和管理
https://segmentfault.com/a/1190000000585066
2014-06-21T20:36:22+08:00
2014-06-21T20:36:22+08:00
defcon
https://segmentfault.com/u/defcon
1
<h2><a rel="nofollow" href="http://blog.segmentfault.com/defcon/1190000000584992">关于如何安装nginx参见我上篇博客</a></h2>
<p><strong>启动nginx</strong></p>
<p>nginx一般安装过后会自动设置nginx环境变量,直接在命令行输入nginx来启动</p>
<pre><code>nginx
</code></pre>
<p>或者进入到nginx可执行文件安装目录来启动,输入以下命令</p>
<pre><code>/usr/sbin/nginx
</code></pre>
<p><strong>快速关闭nginx</strong></p>
<pre><code>nginx -s stop
</code></pre>
<p><strong>平滑关闭nginx</strong></p>
<pre><code>nginx -s quit
</code></pre>
<p>或者</p>
<pre><code>kill -s quit pid
</code></pre>
<p>这里的pid是nginx的主进程pid<br>
我们可以通过 ps -aux | grep nginx 来查看nginx的pid</p>
Ubuntu 14.04 安装 nginx
https://segmentfault.com/a/1190000000584992
2014-06-21T18:25:22+08:00
2014-06-21T18:25:22+08:00
defcon
https://segmentfault.com/u/defcon
0
<p>切换到root,这个可以不用跟我一样,我主要是为了方便,但这不是个好习惯</p>
<pre><code>sudo su
</code></pre>
<p>切换到用户主目录 iflytek是我的用户名,你们需要修改这个目录</p>
<pre><code>cd /home/iflytek
</code></pre>
<p>下载nginx签名密钥<br>
For Debian/Ubuntu, in order to authenticate the nginx repository signature and to eliminate warnings about missing PGP key during installation of the nginx package, it is necessary to add the key used to sign the nginx packages and repository to the apt program keyring.</p>
<pre><code>wget http://nginx.org/keys/nginx_signing.key
apt-key add nginx_signing.key
</code></pre>
<p>lsb_release -c 可以查看系统的codename<br>
我用的是Ubuntu 14.04 所以我的codename是trusty<br>
添加nginx源</p>
<pre><code>echo "deb http://nginx.org/packages/mainline/ubuntu/ trusty nginx" >> /etc/apt/sources.list
echo "deb-src http://nginx.org/packages/ubuntu/ trusty nginx" >> /etc/apt/sources.list
</code></pre>
<p>安装nginx</p>
<pre><code>apt-get update
apt-get install nginx
</code></pre>
<p>切换root到iflytek</p>
<pre><code>sudo su iflytek
</code></pre>
ThreadLocal 内部实现和应用场景(慎用,可能有内存泄露)
https://segmentfault.com/a/1190000000537475
2014-06-08T04:34:47+08:00
2014-06-08T04:34:47+08:00
defcon
https://segmentfault.com/u/defcon
2
<p>很多人都知道java中有ThreadLocal这个类,但是知道ThreadLocal这个类具体有什么作用,然后适用什么样的业务场景还是很少的。今天我就尝试以自己的理解,来讲解下ThreadLocal类的内部实现和应用场景,如果有什么不对之处,还望大家指正。</p>
<p>首先明确一个概念,那就是ThreadLocal并不是用来并发控制访问一个共同对象,而是为了给每个线程分配一个只属于该线程的对象(这么粗暴的解释可能还不太准确),更准确的说是为了实现线程间的数据隔离。而ThreadLocal应用场景更多是想共享一个变量,但是该变量又不是线程安全的,那么可以用ThreadLocal维护一个线程一个实例。有时候ThreadLocal也可以用来避免一些参数传递,通过ThreadLocal来访问对象。</p>
<p>首先我们先看下ThreadLocal(jdk1.7)内部的实现:<br>
ThreadLocal get方法</p>
<pre><code> public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
</code></pre>
<p>ThreadLocal set方法</p>
<pre><code> public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
</code></pre>
<p>get和set方法是ThreadLocal类中最常用的两个方法。</p>
<blockquote>
<p>get方法</p>
</blockquote>
<p>代码很容易理解,首先我们通过Thread.currentThread得到当前线程,然后获取当前线程的threadLocals变量,这个变量就是ThreadLocalMap类型的。然后根据当前的ThreadLocal实例作为key,获取到Entry对象。</p>
<blockquote>
<p>set方法</p>
</blockquote>
<p>代码同样很容易理解。同样根据Thread.currentThread得到当前线程,如果当前线程存在threadLocals这个变量不为空,那么根据当前的ThreadLocal实例作为key寻找在map中位置,然后用新的value值来替换旧值。</p>
<p>在ThreadLocal这个类中比较引人注目的应该是ThreadLocal->ThreadLocalMap->Entry这个类。这个类继承自WeakReference。关于弱引用的知识,以后我会抽时间写篇文章来介绍下。</p>
<p>最近在我们的web项目中servlet需要频繁创建SimpleDateFormat这个对象,进行日期格式化。因为创建这个对象本身很费时的,而且我们也知道SimpleDateFormat本身不是线程安全的,也不能缓存一个共享的SimpleDateFormat实例,为此我们想到使用ThreadLocal来给每个线程缓存一个SimpleDateFormat实例,提高性能。同时因为每个Servlet会用到不同pattern的时间格式化类,所以我们对应每一种pattern生成了一个ThreadLocal实例。</p>
<blockquote>
<p>DateFormatFactory</p>
</blockquote>
<pre><code>public class DateFormatFactory {
private static final Map<DatePattern, ThreadLocal<DateFormat>> pattern2ThreadLocal;
static {
DatePattern[] patterns = DatePattern.values();
int len = patterns.length;
pattern2ThreadLocal = new HashMap<DatePattern, ThreadLocal<DateFormat>>(len);
for (int i = 0; i < len; i++) {
DatePattern datePattern = patterns[i];
final String pattern = datePattern.pattern;
pattern2ThreadLocal.put(datePattern, new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat(pattern);
}
});
}
}
//获取DateFormat
public static DateFormat getDateFormat(DatePattern pattern) {
ThreadLocal<DateFormat> threadDateFormat = pattern2ThreadLocal.get(pattern);
//不需要判断threadDateFormat是否为空
return threadDateFormat.get();
}
</code></pre>
<p>}</p>
<blockquote>
<p>DatePattern 枚举类</p>
</blockquote>
<pre><code>public enum DatePattern {
TimePattern("yyyy-MM-dd HH:mm:ss"),
DatePattern("yyyy-MM-dd"),
public String pattern;
private DatePattern(String pattern) {
this.pattern = pattern;
}
</code></pre>
<p>}</p>
<p>这样我们就可以每次调用DateFormatFactory.getDateFormat获取到对应的时间格式化类了。之前我们提到使用ThreadLocal同时可以避免参数传递。假如我们这个Servlet要调用到其他类的方法,而且方法内需要使用时间格式化类。按照正常情况下我们把该时间格式化类作为参数进行传递,但如果有了ThreadLocal这个类,我们可以不需要作为参数传递了,可以在方法类通过ThreadLocal得到时间格式化类。当然代码的通用性就差了。</p>