薛定谔的风口猪

站在巨人的肩膀上学习,猪都能看得很远

在windows上使用_netrc文件让Git记住用户名和密码

每次写octopress博客的git push 和 rake deploy 都要问一次用户名密码,真的非常烦。以下是一个简单的方法让git记住用户名和密码:

  • 定义一个用户环境变量%HOME%, value=%USERPROFILE%。(windows会把路径自动替换为用户路径)
  • 在%HOME%路径下新建一个文件_netrc
  • _netrc文件中增加下面的配置:

    machine login password machine login password

如:

machine github.com
login cnblogs_user
password cnblogs_pwd

Maven 配置 BAE SDK

百度BAE的官方文档没有一个完整的使用Maven配置BAE SDK的例子,而BAE官方博客上的配置也不可用。

经过多日的调试和BAE客服的交流,最后成功配置出了一个maven 配置的 pom.xml文件。今天决定分享一下,可能是现在网上能搜到的第一篇可用的配置步骤了。

1.建立Maven项目

2.下载BAE SDK 这里使用的是baev3-sdk-1.0.1(下载还是必须的)

3.添加如下repository 到pom.xml中

<repositories>
   <repository>
        <id>bae</id>
        <url>http://maven.duapp.com/nexus/content/groups/public/</url>
        <releases>
            <enabled>true</enabled>
        </releases>
   </repository>
</repositories>

4.添加依赖包:

    <!-- ===================bae=============================== -->

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>com.baidu.bae</groupId>
        <artifactId>baev3-sdk</artifactId>
        <version>1.0.1</version>
    </dependency>

    <dependency>
        <groupId>com.baidu</groupId>
        <artifactId>mcpack</artifactId>
        <version>1.0.9</version>
    </dependency>

    <dependency>
        <groupId>org.apache.thrift</groupId>
        <artifactId>libthrift</artifactId>
        <version>0.9.1</version>
    </dependency>

    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.2.2</version>
    </dependency>

    <dependency>
        <groupId>net.sf.ezmorph</groupId>
        <artifactId>ezmorph</artifactId>
        <version>1.0.6</version>
    </dependency>

     <dependency>
        <groupId>net.sf.json-lib</groupId>
        <artifactId>json-lib</artifactId>
        <version>2.4</version>
        <classifier>jdk15</classifier>
    </dependency>

    <dependency>
        <groupId>commons-beanutils</groupId>
        <artifactId>commons-beanutils</artifactId>
        <version>1.8.0</version>
    </dependency>

    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpmime</artifactId>
        <version>4.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpcore</artifactId>
        <version>4.2</version>
        <type>jar</type>
    </dependency>

    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.2.5</version>
    </dependency>

    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.6</version>
    </dependency>

    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>1.4</version>
    </dependency> 

    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.5</version>
    </dependency>

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.1</version>
    </dependency>   

    <!-- Log4j -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.5</version>
    </dependency>

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.5</version>
    </dependency>   

5.到这一步,你可能会发现如下错误:

Missing artifact com.baidu:mcpack:jar:1.0.0.9

这时候,去到下载的BAE SDK下载的dependencies目录下,找到mcpack-1.0.0.9.jar

在cmd命令行中使用如下命令(需要配置好maven的环境变量):

mvn install:install-file -Dfile=下载路径\baev3-sdk-1.0.1\dependencies\mcpack-1.0.0.9.jar -DgroupId=com.baidu -DartifactId=mcpack -Dversion=1.0.9 -Dpackaging=jar

安装mcpack到本地路径

然后去本地repo下查看是否存在此依赖包(默认在window用户目录下.m2)去查看本地安装的mcpack包。

查看mcpack是否安装成功

6.更新maven 项目,应该就可以了。

测试分布式日志

log4j.properties中加入如下类似配置:

log4j.rootLogger=DEBUG,BAE
##################分布式日志###############
log4j.appender.BAE=com.baidu.bae.api.log.BaeLogAppender
log4j.appender.BAE.ak=BAE的API KEY
log4j.appender.BAE.sk=BAE的的Secrete Key
log4j.appender.BAE.Threshold = DEBUG
log4j.appender.BAE.layout=org.apache.log4j.SimpleLayout

maven install 并发布。在扩展服务中测试分布式日志

测试分布式日志

如果能看到日志打印成功,则证明BAE的SDK配置成功了

Git Push Error: RPC Failed; Result=22, HTTP Code = 411 解决方案

有时候git push 会报类似的错误,如在BAE上push一个项目较大的时候,可能就会报此错误。由于遇到很多次,今天记录下解决方法:

默认情况下,Git设置了HTTP操作的最大值为一MB,所以当你push代码的时候如果超过这个值,则可能发生错误。

解决方法如下:

  1. 进入git 目录
  2. 扩大允许的最大值:

     git config http.postBuffer *bytes*
    

如:扩大到500MB:

    git config http.postBuffer 524288000

再次push,问题应该就解决了。

Stateless的web架构

分布式架构中用户状态的问题

传统的web架构中,我们通常会用使用session保存用户的当前状态用以标记一个用户,例如用户在不同的请求中都能找到他的购物车中的物品。

但随着用户量的增长,无法避免的,我们需要使用使用分布式的系统。而在分布式场景,问题将会变得复杂起来。

例如,现在有三台应用服务器A,B,C。第一次用户的请求被负载均衡器路由到了A服务器,相关的状态被保存了起来,那么下次一个请求过来的时候,假如负载均衡器把他的请求路由到了B上,所有的已有状态都将丢失。就好像你刚刚在购物车上抢到了一台小米手机,准备付款的时候发现购物车居然是空的!这是我们需要急切避免的问题!

Sticky Session

要解决这种场景,如果我们使用原来的架构,就必须更改负载均衡器的策略,可使用一个sticky session 的策略。

即同一个用户的请求都转发到同一台的服务器,这样,session就不用丢失。服务器依旧可以在session中找到用户的相关信息。负载均衡器可以在查看HTTP头中的Cookies(我们设置用户的标识到其中)去判断应该路由到哪台具体的服务器上,以便获取到local session。

用sticky session解决这类问题有两个较为明显的好处

  1. 所有的应用代码都不需要修改,本来单机使用session的,分布式环境依旧可以使用。
  2. 有利于命中本机的RAM缓存,例如可以有效的存储某些用户的静态信息在本机,下次有效的使用缓存增加响应速度

但是,sticky session 有如下坏处

  1. 如果一台服务器宕机,该服务器上的session就会丢失(这是local session的通病)。这对于状态敏感的应用,如购物车,是极大的问题。
  2. 由于负载均衡器使用了sticky,这可能导致负载很不均衡。
  3. 如果负载过重,希望横向扩展,不能即时的收到效果。因为原来的用户的所有存有session的请求都会路由到原来的服务器。

Stateless Archetecture

可见,sticky session 很难解决用户状态不丢失的问题,那么要避免sticky session缺点而又解决这类的用户状态的问题,现在流行的架构是无状态的(stateless),也就是说,不使用session,server端不保留用户的任何状态。

一旦我们把应用做成无状态的,有很多好处

1 . 最明显的就是易于横向扩展!服务器不需要维护用户的状态,所以每一台服务器去处理用户的请求,都是一样的。负载均衡器可以使用最简单最优的策略,如随机/轮询等策略负载到具体的应用服务器上。

2 . 即便应用服务器宕机,也不会丢失用户状态,因为状态没有保存在该机上。而当需要增加机器已处理大量用户请求,由于无状态,可以让新的机器快速的拥有一定负载(load)。

严格意义上说,无状态的架构应该是指整个server不保存客户端操作的状态(client state),也叫应用状态(application state),这不包括资源的状态(resource state),资源状态是必须保存的,例如用户曾经购买过的商品等等这些需要持久化的状态。

但实际上,用户操作的过程必然是有状态的,例如你浏览微博的时候,浏览到第二页,那么下一页就是第三页,这个浏览到的当前页面,就是用户的操作状态,我们希望的这个状态不由server去保存,而把状态转移出去。

应用服务器前接客户端,后接数据库。所以我们可以把状态转移到这两者之一。

1 .转移到client

这是真正的无状态,整个server保存任何客户操作状态,而是由客户端自己维持,这也是REST的约束之一。例如,用户在第二页的时候,下一页对应的请求和在第三页的时候的下一页请求是不一样的,这由客户端自己处理并发送请求,服务端只需要接受参数就足够知道要做什么样的操作,不需要访问任何服务端保存的用户状态。例如我们可以把用户的相关状态通过cookie设置在HTTP Response,这样应用服务器获取状态的责任就转移到客户端本身。例如标识一个用户ID(加密的hash串)之类的,可以设置到cookie中,但这仅限于某些不敏感的状态。而且,cookie中能设置的数据大小也很有限。

2 .转移到数据库(分布式缓存)中。

另外一个更为可取的方法是使用数据库或者分布式缓存(如memcache)存储用户的状态,需要注意的这不算是无状态的架构,而顶多只能算web server的无状态,这样无状态的服务器依旧可以使用可靠的方式获取到用户的状态而做出合理的逻辑处理,并且session不依赖于单一机器,所以即便出现机器宕机,也不会丢失用户状态。

5分钟速写自定义搜索引擎插件

最近搞了几个火狐自定义的搜索引擎插件,就像这里这些插件:

搜索引擎插件

对于某些经常使用搜索的内外网站,如豆瓣或者公司内部的Bug号搜索,代码搜索等,都可以提供一定程度的便利。

今天把写的过程分享一下,5分钟便可自己写上一个了。

语法部分

详细的官网文档是:https://developer.mozilla.org/zh-CN/docs/Mozilla/Add-ons/Creating_OpenSearch_plugins_for_Firefox

我们直接上一个例子,这样上手最快。

先简单浏览下写搜索引擎插件的语法:

<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
                       xmlns:moz="http://www.mozilla.org/2006/browser/search/">
  <ShortName>engineName</ShortName>
  <Description>engineDescription</Description>
  <InputEncoding>inputEncoding</InputEncoding>
  <Image width="16" height="16" type="image/x-icon">data:image/x-icon;base64,imageData</Image>
  <Url type="text/html" method="method" template="searchURL">
    <Param name="paramName1" value="paramValue1"/>
    ...
    <Param name="paramNameN" value="paramValueN"/>
  </Url>
  <Url type="application/x-suggestions+json" template="suggestionURL"/>
  <moz:SearchForm>searchFormURL</moz:SearchForm>
</OpenSearchDescription>

这样一个xml文件,即便什么都不看,都能大致模仿而写出一个。

解释一下其中某些标签的作用:

ShortName: 搜索引擎的简称,最后会显示到界面中

Image:使用指向一个图标的URL来代表这个搜索引擎,可以使用链接,也可以使用http://software.hixie.ch/utilities/cgi/data/data 生成base64编码的data: URI。

URL:这是我们关心的重点。其中两个上面两个URL例子,其中一个type=text/html,另一个type=application/x-suggestions+json

type="text/html" 用来指定进行搜索查询的URL.

type="application/x-suggestions+json" 用来指定获取搜索建议(search suggestions)的URL. 如下图所示:

搜索建议

例子——豆瓣

大致介绍到这里,直接上一个我写的豆瓣的例子

首先,在 火狐安装路径”%PROGRAM_FILES%\Mozilla Firefox\searchplugins”下新建一个xml文件,如douban.xml

然后复制上面的模板,进行修改,以下是我的douban.xml(注:由于base64图片编码太长,以下省略为……)

<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/"
   xmlns:moz="http://www.mozilla.org/2006/browser/search/">
  <ShortName>豆瓣搜索</ShortName>
  <Description>使用豆瓣进行搜索</Description>
  <InputEncoding>UTF-8</InputEncoding>
  <Image width="16" height="16" type="image/x-icon">..........</Image>
  <Url type="text/html" method="GET" template="http://www.douban.com/search">
     <Param name="source" value="suggest"/>
     <Param name="q" value="{searchTerms}"/>
  </Url>
  <Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>
  <moz:SearchForm>http://www.douban.com/search</moz:SearchForm>
</OpenSearchDescription>

其中值得留意的地方就是value ="{searchTerms}"这里,{serachTerms}表示的是用户在搜框输入的字符串。

而最后的SearchForm表示跳往搜索页的 URL. 这使得Firefox能让用户直接浏览目的网站.这是火狐限定的语法部分,不是标准的opensource部分。

关于搜索建议

这里我使用的搜索建议是谷歌的,原封不动的使用这段即可。

  <Url type="application/x-suggestions+json" method="GET" template="https://www.google.com/complete/search?client=firefox&amp;q={searchTerms}"/>

假如我使用“初恋”作为关键字,将返回类似的以下JSON格式:

["初恋",["初恋这件小事","初恋50次","初恋那件小事","初恋","初恋未满","初恋限定","初恋大作战","初恋的回忆","初恋情人","初恋逆袭系统"]]

所以如果需要使用自己的搜索建议,需要保持相应的JSON格式,并且需要保证在500毫秒内返回,关于这点,有空再另外写一篇博文。

最后保存,重启火狐浏览器,就应该能够看到自己增加的小插件啦。


注1:如果浏览器还是没有找到这个插件的话,打开%AppData%\Mozilla\Firefox\Profiles\XXXXX.default下,prefs.js,里面加入/修改以下的配置:

user_pref("browser.search.selectedEngine", "engine_name");

以上解决方案来源于:adding a custom search engine tofirefox

注2: 在我本机中,每次修改xml文件后,即使重启火狐都无法获得最新的配置,需要重命名为另外一文件。如果遇到一直修改都无法生效的时候,可以尝试一下这个方法。

发布分享

写完之后并本机测试后,如果希望可以分享给其他人都使用,可以注册一个开发者账号,然后到https://addons.mozilla.org/zh-CN/developers/addon/submit/1 提交这个xml文件就可以供大家使用了。

大家可以在https://addons.mozilla.org/zh-CN/firefox/addon/doubanserach 找到我豆瓣的这个例子

谈谈性能瓶颈及简单调优

随着系统访问量的上升,系统资源的消耗,系统的响应通常会越来越慢。这时候我们需要对系统的性能进行相关分析,找到性能瓶颈。

从代码的角度来看,性能瓶颈很可能出现在几个关键资源:CPU,内存,IO。

CPU

由于每个CPU的每个核一个时间只能执行一个线程,Linux采用的是抢占式的调度。

上下文切换

如果有频繁的上下文切换,则会造成内核占据较多的CPU使用,降低系统性能。典型的例子有是有非常强烈的锁竞争情况。这会导致当前进程频繁的进入阻塞或者休眠状态,使得应用响应下降。

这类型的解决方法有,

  1. 减小Thread的数量
  2. 降低锁竞争,如分多把锁。

线程一直处于Running

还有另外一种情况是线程一直处于Running状态,这会导致该线程消耗大部分的CPU,通常情况是进行循环,或者大批量计算。

例如

while(somevalue!=XXX){//
   ;//死循环
}   

或者有一个大批量的数据操作,如对集合进行很大数据量的增加操作:

for(int i=0;i<10000;i++){
    list.add(value[i])
}

以上两种例子都会使得线程一直处于running状态而不释放CPU,一个可取的方法是进行部分操作后进行Thread.sleep(),让出CPU。

如上面第二个例子:

for(int i=0;i<10000;i++){
   list.add(value[i])
   if(i%50==0){//每50个就让出一次CPU 
        Thread.sleep(1);
    }
}

当然,对于第一个例子,假如是希望线程中的协作的话,最好使用的是monitor object 的wait()/notify()之类的方法。

while(!some_condition){
    condition.wait();//挂起等待notify
}

内存

如果消耗了过多的JVM Heap内存,将会频繁触发GC,将大大影响系统的性能。

使用对象缓存池

使用对象池可以一定程度创建对象所花费的CPU和内存

采用合理的缓存失效算法

上面讲到了对象池降低内存消耗,但假如放入太多对象到缓存池里面,反而会造成更严重的内存消耗,这是因为池本身对于对象持有引用,从而可能造成频繁的Full GC。所以,需要控制池中对象的数量。

当池中对象达到最大值后,如果需要加入新的对象,则需要采用合理的失效算法清除池中的对象。如FIFO,LRU,LFU。

中途释放不用的大对象引用

如:

void foo(){
  Object bigObject=new Object();
  bigObject.doSomething();//下面不需要了
  // 下面有很多其他耗时,耗内存的操作的话,可考虑释放bigObject的引用
  bigObject=null;

  //some long opertions
}

合理使用WeakReference 和 SoftReference

有些对象我们允许在某些情况下即使我们还有引用,也要被GC。这时候可以考虑使用弱引用或者软引用。

当某些对象用作类似缓存对象的时候,内存不足就可以被回收的话,这类对象可以使用软引用。

而当某些对象A如果依附于某个对象B存在,如果B不存在了,A就没有必要存在,并且A的存在与否不应该阻碍B是否存在,那么A引用B的时候可以考虑使用弱引用。

关于两者区别可以参考what is the difference between a soft reference and a weak reference

文件IO

文件IO严重的主要原因是多个线程在写大量的数据在同一个文件,导致文件变得很大,写入速度越来越慢,并造成线程竞争文件锁激烈。

解决此类问题的方向有:

异步写文件

把文件的写入操作改为异步,如写日志的时候使用log4jAsynAppender

批量读写

如大数据插入数据库改改为批量的插入操作数据库的操作:

  PreparedStatement ps = c.prepareStatement("INSERT INTO employees VALUES (?, ?)");

  ps.setString(1, "John");
  ps.setString(2,"Doe");
  ps.addBatch();

  ps.clearParameters();
  ps.setString(1, "Dave");
  ps.setString(2,"Smith");
  ps.addBatch();

  ps.clearParameters();
  int[] results = ps.executeBatch();

具体可参考:batch insert in java

限制文件大小

无论数据库表,还是日志文件,我们都应该限制其的大小。

有必要的话,对于数据库表,需要分拆成小表,增加读写速度。

对于文件如日志文件则需要设置一个最大值,超过后生成另外一个新文件。如log4j中使用RollingFileAppendermaxFileSize属性。

SQL优化的一些技巧

最近在学习MySQL的优化,今天整理下一些对于开发人员有必要了解的一些技巧。

关于索引

很多SQL优化都和索引有关,所以,在了解SQL优化前,最好先理解什么是索引,索引做的是什么。关于这点,stackoverflow上有很好的一个问题:how does database indexing work

简单说,索引就是把数据库中的字段进行了一定规则的建立额外的排序,使得SQL查找可以快速找到所需的数据块,避免全表搜索。

关于如何建立索引,以下有一张图可以给出一个较好的指导:

method 1

索引列相关的SQL优化技巧

  • 避免在索引列使用通配符%开头

如(’%.com’),这将会令MySQL无法使用改列索引,而使用%结尾则可以(如’www.%‘)使用索引。

如果需要经常基于某索引列作以通配符开头的查询,如查询所有.com结尾的ip email like '%.com',可以在数据库中保存改列的反序值(如reverse_email),然后搜索的时候使用 reverse_email like REVERSE('%.com'),则可以使用到reverse_email的索引了。

  • 避免在索引列使用函数或者计算

where trunc(create_date)=trunc(:date1)这样的where 条件将无法使用到create_date的索引。

  • 避免在索引列上出现数据类型转换

  • 避免在索引字段上使用not,<>


其他技巧:

  • 尽量避免使用相关子查询

如:

SELECT c.Name, 
       c.City,
       (SELECT CompanyName FROM Company WHERE ID = c.CompanyID) AS CompanyName 
FROM Customer c

其中子查询 SELECT CompanyName....的结果与外层查询结果相关,这样会导致每一个外层查询的结果都会返回到子查询中查询一遍,导致性能下降。这种子查询大多可以改造为表的join:

SELECT c.Name, 
       c.City, 
       co.CompanyName 
FROM Customer c 
    LEFT JOIN Company co
        ON c.CompanyID = co.CompanyID
  • 避免循环中使用SQL

如:

//查询满足
SELECT a.id,a.author_id,a.title //找出满足某条件的文章的作者 
FROM article a
WHERE a.type=2
AND a.created> '2011-06-01';


//For 循环这些记录,然后查询作者信息,
select id, name,age
from arthor where id=:author_id 

这类问题常被称作N+1问题,即每对应外层的每一行都生成了一条SQL语句,这导致了很多的SQL语句重复执行。

这种SQL通常也可以通过join而被改写为单条SQL语句:

SELECT a.id, a.title,au.author_id,au.author_name,au.age
FROM artitle a
INNER JOIN author au on a.author_id = au.id
WHERE a.type=2
AND a.created> '2011-06-01';
  • 不要使用SELECT *

使用SELECT * 有很多的坏处,例如:

  1. 选择过多的列导致不必要的开销。有些时候我们只需要两列,但select * 会把所有的列(可能20列)全部返回,这是额外的IO开销。
  2. SELECT * 不容易针对化的建立索引。由于不知道该 SQL语句中具体需要哪些列,就很难针对化的设计所需的索引。而且,即便按照所有的列都设计了索引,一旦表结构发生了增加列的情况,此索引也会失效,而且后来的人很难发觉。
  3. MySQL 引擎需要解释*所代表的列,也会带来一定的开销

更多相关讨论可以参考:why is select * considered harmful

  • 拆分大的INSERT/DELETE语句

如果有一个很大批量的INSERT/DELETE(需要锁表)语句需要执行,例如对几十万行的语句执行,可以考虑分量的一批一批执行,每次执行完后放开CPU,这样可以避免阻塞其他线程的操作。如:

    while (1) {
        //每次只做1000条
        pst.execute("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
        if row return 0 {
            // 没得可删了,退出!
            break;
        }
        // 每次都要sleep一段时间让出CPU
        sleep(50000);
    }
  • 当只需要一行数据的时候,使用LIMIT 1

当你查询表的有些时候,如果我们知道只会有一条结果,加上 LIMIT 1 可以增加性能。这样一样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。 请看下面伪代码:

    // 没有效率的:
    result_set= ps.execute_query("SELECT user_name FROM user WHERE country = 'China'");
    if (result_set.hasNext()) {
        // ...
    }

    //更好效率的:
    result_set= ps.execute_query("SELECT user_name FROM user WHERE country = 'China' LIMIT 1");
    if (result_set.hasNext()) {
        // ...
    }
  • 避免在WHERE子句中使用in,not in

可以使用 exist 和not exist代替 in和not in。

//低效
SELECT order_id,order_num,customer_name  FROM ORDERS WHERE CUSTOMER_NAME NOT IN 
(SELECT CUSTOMER_NAME FROM CUSTOMER)

//高效    
SELECT order_id,order_num,customer_name FROM ORDERS WHERE not exist 
(SELECT CUSTOMER_NAME FROM CUSTOMER where CUSTOMER.customer_name = ORDERS.customer_name)

关于缓存

  • 在MySQL中使用缓存把查询结果保留能有效减小SQL查询时间

  • 在应用程序中使用缓存

如:

IF CACHE NOT EMPTY
SELECT FROM CACHE

IF CACHE EMPTY
    SELECT TABLE 
    PUT INTO CACHE

但需要注意一旦表发生了改变,需要移除CACHE的相关数据。

注: 可用流行的memcached框架缓存查询结果,减小数据库压力。

快速入门Memcached

最近学习Memcahced,使用了一天时间搭建了memcached的集群,并使用memcached的客户端spymemcached成功访问到集群。今天整理下学习的笔记。

Linux下安装Memcached

Ubuntu为例。

1.更新本地仓库

sudo apt-get update

2.安装memcached Service

sudo apt-get install memcached

3.安装成功后,使用ps aux | grep memcached可检查memcached服务是否已启动。你可能会看到类似下面的信息,证明memcached服务已经启动。

memcache  1027  0.0  0.1  46336  1080 ?        Sl   00:38   0:00 /usr/bin/memcached -m 64 -p 11211 -u memcache -l 0.0.0.0
jaskey    2477  0.0  0.0   4372   832 pts/1    S+   00:49   0:00 grep --color=auto memcached

注:默认情况下,memcached的服务进程只在默认的localhost监听。所以如果我们需要从其他机器访问该服务,需要修改监听的ip

修改memcached服务监听地址

打开配置文件:memcached.conf(在/etc下)

# Specify which IP address to listen on. The default is to listen on all IP addresses
# This parameter is one of the only security measures that memcached has, so make sure
# it's listening on a firewalled interface.
-l 127.0.0.1

找到相关-l 的配置,修改为0.0.0.0即可

其余重要还有-m(内存大小),-p(默认端口号)

# Start with a cap of 64 megs of memory. It's reasonable, and the daemon default
# Note that the daemon will grow to this size, but does not start out holding this much
# memory
-m 64

# Default connection port is 11211
-p 11211

使用telnet与memcached通信

1.首先,安装telnet客户端

sudo apt-get install telnet

2.使用telnet访问

telnet localhost 11211

其中11211为memcached默认端口。

如果你能看到类似以下的输出,则证明访问成功。

Trying 127.0.0.1...Connected to localhost.Escape character is '^]'.

我们可以使用stats命令获得memcached的基本信息

Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
stats
STAT pid 1027
STAT uptime 1487
STAT time 1414256583
STAT version 1.4.13
STAT libevent 2.0.16-stable
STAT pointer_size 32
STAT rusage_user 0.040002
STAT rusage_system 0.252015
STAT curr_connections 5
STAT total_connections 6
STAT connection_structures 6
STAT reserved_fds 20
STAT cmd_get 0
STAT cmd_set 0
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 0
STAT get_misses 0
STAT delete_misses 0
STAT delete_hits 0
STAT incr_misses 0
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 7
STAT bytes_written 0
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 262144
STAT hash_is_expanding 0
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT bytes 0
STAT curr_items 0
STAT total_items 0
STAT evictions 0
STAT reclaimed 0
END

往memcached存储/获取值

使用add命令

add newkey 0 60 5
abcde

如果现实STORED则为存储成功:

然后就可以使用get newkey获取到这个存储的值了。

get newkey
VALUE newkey 0 5
abcde
END

命令解析

<command name> <key> <flags> <exptime> <bytes>

常用command name 有 add , set, replace, append

flags

是一个16为无符号整形,memcached server会把这个flags和key一起存储起来,并且访问该key的时候,也会返回这个flags。我们可以根据需要设置这个key的格外信息。

exptime

值超时的时间,单位为秒。如果设置为0,则不会超时。

bytes

存储的值的大小,在我们这个例子,由于我们需要存储abcde,所以我们设置改参数为5。


建立分布式memcached集群

到此为止,我们已经可以访问到默认启动的memcached服务了,但是实际上我们需要一个memcached集群。我们可以在多台机器上启动memcached服务,这样就可以获取一个无限制内存大小的缓存服务。然后使用memcached客户端连接上去。

鉴于在学习阶段,我们可以尝试在不同的端口上启动memcached,然后获得一个本地集群。

启动memcached: memcached -d -l 0.0.0.0 -m 64 -p 12122

其中-d参数表示启动为daemon, -l 指定监听ip,-p监听端口,-m指定服务的内存大小。

然后使用ps aux |grep memcached确认端口的确运行成功。

使用memcached客户端

memcached的守护进程是对不知道集群的存在和server设置的。实际上,是memcached client把数据分布式的存储在不同memcached服务上。所以,同一份存储的数据,你只能在一个memcached服务中访问,其他的memcached都无法获得。

我们这里以java语言作为例子,演示如何使用java访问memcached server。这里使用的是spymemcached这个memcached client。

maven中添加spymemacached依赖:

<dependency>
    <groupId>net.spy</groupId>
    <artifactId>spymemcached</artifactId>
    <version>2.10.1</version>
</dependency>

代码示例如下:

public class MemcachedDemo 
{

    static final  InetSocketAddress[] servers=new InetSocketAddress[]{  //创建好需要连接的memcached集群的ip和端口
        new InetSocketAddress("192.168.56.101",11211),
        new InetSocketAddress("192.168.56.101",11212)
    };

    public static void main( String[] args ) throws IOException{
        System.out.println( "Begin memcached" );
        MemcachedClient client=new MemcachedClient(servers);//建立memcached client对象连接到集群,注:spymemcahced,会处理重新连接


        //存储两个对象,一个String类型,一个自定义对象(需要实现Serializable接口)
        client.set("city", 60, "shenzhen");//expired in 60 seconds
        System.out.println( "city is set" );
        client.set("emp", 0, new Employee("jaskey", 23));//never expired,注:Employee对象需要实现java.io.Serializable接口        

        //从memcached server中获取对象
        Employee empFromServer=(Employee)client.get("emp");
        String city=(String)client.get("city");

        System.out.println("emp from memcached: "+empFromServer);
        System.out.println("city from memcached: "+city);

        client.shutdown();
        }
}

输出:

Begin memcached
2014-10-26 01:56:51.431 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/192.168.56.101:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2014-10-26 01:56:51.435 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/192.168.56.101:11212, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2014-10-26 01:56:51.443 INFO net.spy.memcached.MemcachedConnection:  Connection state changed for sun.nio.ch.SelectionKeyImpl@72a7d24a
emp is set
emp from memcached: Employee("jaskey", 23)
city from memcached: shenzhen
2014-10-26 01:56:51.492 INFO net.spy.memcached.MemcachedConnection:  Shut down memcached client

其中,get操作是同步的,如果希望使用异步get,可以使用asyncGet方法返回一个Future对象:

    Future<Object> fobject = client.asyncGet("emp");
    try {
        Employee emp=(Employee)fobject.get(10, TimeUnit.SECONDS);//设置10秒的timeout
        System.out.println("emp from memcached"+emp);
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (TimeoutException e) {
        e.printStackTrace();
    }

关于集群

如果这时候,你使用telnet命令分别访问不同的memcached服务,你很可能发现emp这个值只存在于其中的一个服务,而其他服务是获取不到的。

这样证明一份数据只被保存了一遍,然而对于客户端而言,具体保存在哪里却是完全透明的,因为spymemcahced把这个数据映射的工作做了。

这样我们就好像操作一份很大内存空间的缓存一样,而实际上,我们是对分布在不同memcached 服务的内存空间在进行操作。

大学生与烧饼小贩

最近写技术的东西比较多,刚好也在看《黑客与画家》,里面讲到的关于如何创造财富这一论点引起了我极大的共鸣和思考,觉得既然博客折腾起来了,就简单整理下,写下点东西。刚好最近大学生校园招聘的季节,就从这个话题切入扯点东西吧。

在当今社会,什么东西都离不开钱,似乎没有钱,就生存不下去了(似乎的确是这样子)。然后,我们从出生那天起,就被安排着如何成为一个会赚钱的人。

上一个好的小学,报读各种英语培训班、奥数班,目的是为了考上好的中学。在中学和无数人死磕排名,好像排后了一名就世界末日一般的前景,每个人都被用成绩贴上了各种标签,无他,为了高考考个好成绩。

高考罢,要挑一个好的学校,读一门好的专业。好的学校就是就业率高,好的专业就是毕业容易找到好的工作,好的工作就是钱多活少又稳定。呵呵。没有一个人会问学校的学术氛围,也没有几个人会考虑孩子/自己到底喜欢的是什么,毕竟学生已经被训练为不会思考的机器,一辈子都在同一条起跑线上竞争,不需要思考,只需要永远的跑,跟着前面跑,跟着大家跑。所以,突然有了选择的时候,反而迷茫了,下意识的也只会跟着大众走。于是,金融、计算机、工程这些专业变得炙手可热。随之而来的,随便一所不沾边的学校都乱开一通专业。

在这个时候,你会发现,家长、学校变化都十分巨大。学校不再有很多的竞争逼着你走,虽然学生会丑陋的政治斗争依旧十分激烈,但我们可以不参加。而家长似乎也对你的成绩不那么过问了。无他,因为这么多年来,似乎已经到了收割的季节——到了毕业那一年,任务就完成了——孩子成为了一个大学生,理应是一个会赚钱的人咯!

在这个时候,才发现,就业竞争是如此的激烈。几万人应聘那零丁的几个职位,挤破头来最后拿个3 4千的工作。所有人都不知道这一辈是对呢?还是错?反正都这样干,那就都这样吧。毕业了也就那样了,无无聊聊朝九晚五,挤着公交地铁,天天看着房价涨、工资甚至还赶不上物价。眼看着街边一个卖烧饼的收入都一万多了,才可能回想,这一辈子到底得到了什么?

其实这也难怪,一个毕业后只能对着电脑打打字、写几份文件的,比不上一个卖烧饼的,又有什么出奇的?

但肯定很多人出来骂娘了,这TM不公平啊!凭啥!

问题就在这里,很多大学生总觉得这不公平、家长们也觉得无奈:农民工兄弟做的是体力活,我做的好歹他们不会做,说得自己做的东西像战斗机一样是什么高科技。坦白说,即便是如我们这种码农一样的职业,也就用着美国人的电脑,在美国人的系统上用美国人的语言模仿着美国人的代码,也不见得多么高端。

这里似乎有一个误区,我们总潜意识的想着用付出去衡量收获,这可能和读书时代我们宣扬“一分耕耘,一分收获”有关。这完全是一个错误的观点。在市场经济下,能交换收获的,只有输出。

这本来是一个很原始的道理,但随着历史的发展,被慢慢淡化了,所以有必要从头谈谈。

在古时候,没有货币出现的时候,人们会使用物品交换的方式去获得自己所需的东西。如果我需要鸡蛋,但我没有鸡去生蛋,那么我可能用羊去换。找到一家需要羊的,又拥有鸡蛋可换的人,跟他们去交换。然后假如我需要另外的东西,就再找合适的人交换。

这样发展到后面,人们的需求越来越多,在局限的一个地方要找到双方都刚好互补需求的另外一个人,十分不容易。于是,一般等价物便出现了,这类东西的一个最重要的属性是稀有,一般说来是稀有金属,如金、银。人们可以用自己生产的物品交换这一等价物品(卖),然后用这个等价物去购买另外的商品。

再到后来,携带金属交换并不便利,而且计算也没那么灵活。便出现了货币,直至纸币。人们去工作去赚钱,赚到了钱就可以买自己需要的东西。

在这里,一个概念容易被模糊——钱似乎就是财富。但这并不正确。钱只是换取、衡量财富的一种物品,它本身并不具备价值。假如突然有一天,银行印了一大堆钞票满天飞,那么你手上的钱瞬间就沦为废纸。

人类发明了一般等价物和后来的货币,为的不是创造财富,而是为了交换财富。所以,真正的财富,是我们用钱交换(买/卖)的东西,这里面说的很可能是一种物质的商品,也有可能是一种服务。

那么,理清楚这点之后,对于我们理解大学生就业、自己拿多少工资这种问题有什么作用呢?作用十分明显,乃至答案呼之欲出。我们去打工,不是卖自己的时间,自由、劳动力去交换钱(这些是表面的),深沉次的是,我们在出售“生产力”(并不是劳动力)。简单说,就是企业买了我们的某段时间、自由,并用我们的某些技能去为他们服务。而至于他们给多少钱,这并不取决于我们的服务时间、服务态度,而是取决于我们这段时间的“生产力”——能创造多少财富。

对,财富是可以创造出来的,这似乎每个人都懂,但似乎又不是。如果我们把钱=财富的话,这个命题就是假命题。因为钱的多少是由银行决定的,而银行把钱印到全世界都是,这并不增加社会的财富,反而让所有人都变穷了。明显,钱不是财富。那么什么是财富?

举一个例子,有一天,马浓再也敲不动代码了,然后被老板炒了。身上没有钱,穷得只剩下有一台破单车,还要不能跑的。然后他把它修了一下,能跑起来了,于是拿去卖了,获得了100块,够吃一个月的满头了!这一个过程中,马浓就创造了财富,他本来不拥有任何可以交换钱的物品(至少换不了100块),现在有了,因为他创造了一量价值100块(准确地说,价值1个月的馒头)的单车,这个世界因为马浓这一个行为,财富得到了上升。

所以,财富不是一定的,而是不断的变化的(人类历史就是不断创造财富的过程),这并不是一个零和游戏!马云他成功成为了中国首富,但没有从社会剥夺过一分一毫。相反,他让整个中国的财富迅速上升——很多店主依靠淘宝赚了一桶金,很多老百姓因为淘宝买到了更为便宜的衣服。

但这样一看,似乎只有生产,或者说只有物质生产才是创造财富的过程?也不对!再举个例子,淘宝卖的衣服,衣服本身是财富,但是他一开始并不能成为社会的财富,假如他生产出来的衣服一直都摆在库存没人穿,他们从其量也就一堆布料。

然后这时候,马浓去应聘做了一个物流人员,做起了把衣服从浙江发往广东的工作,使得广东的老百姓能买到浙江的衣服了。在这个过程中,假如没有马浓的输送,衣服无法成为你和我的财富。也就是说,服务也是一个创造财富的过程,由于物流人员的服务,我能够买到衣服,而卖家能卖到钱。这繁荣了整个淘宝,使得大家继续不断地创造更多的财富。而马浓也因此赚到了每月近万元的收入。

扯得有点远,我们回到大学生就业和卖烧饼的例子。大学生工作获得的工资,其实就是帮助企业去创造财富的过程,虽然这一过程似乎都被模糊为帮企业赚钱了。一个企业用4000块请你工作,是因为他觉得你能创造多余4000块的财富(假如一个月后人民币没有贬/升值)。这和市场买卖是一个意思,你值4000块,自然有人用4000买,如果你3000肯卖,肯定很多人愿意买。如果你希望自己能卖个10000块,最后肯定的结果就是没人肯买你(即便买了也会立刻退货),最后经过市场的催化,会逐渐趋向于你最终的“市值”。而没有任何一个人逼你去卖一家烧饼小摊的烧饼,然总有很多人愿意去买,因为他创造了烧饼这一财富,而你认为他卖得并不贵(坦白说,是挺贵的,但市场告诉你,他值这个价),最后他赚得了10000多的收入。

Fair enough! 十分公平。也许你的工作并不比卖烧饼的轻松(不过很多的确比烧饼轻松得多),而且我们的前期投入也肯定比小贩们投入得多得多(无论教育费用,时间),但这就是市场给予我们的回应。一个人的收入,与他创造财富的能力相匹配,这是我能想到的最为公平且合理的资源配置方式。当然,除了创造财富外,还有另外一种获得财富的手段,那就是偷窃。这不包括真真意义上的偷,而还包括贪污、资源垄断,他们和偷窃都拥有一样的特点——没有创造财富而获取创造出来的财富,这在中国还十分的严重,但对比历史已经好得多得多。至少,以前的首富都是达官贵人,现在我们的首富却是马云。

那么,自己能拿多少钱?应该问的,不是自己付出了多少,自己的学历,自己的身份,而是问自己到底能创造多少财富,在市场化的社会里,最简单、最公平的获取财富的手段,就是创造财富了。

如何在eclipse中修改源目录路径

在我们使用Maven或者Gradle的时候,源码目录要求是:src/main/java。

但是如果我们直接用已经构建好了的eclipse项目,无论怎么新建文件夹,都不能构建出这样的源目录结构。eclipse会把main.java视为包(package)。

要解决这个问题,我们需要先让eclipse不要把src视为源目录(source folder)。方法:

右键src目录—>build path —> remove from build path.

这样以后,我们就可以建立我们的main和java文件夹在src下。 然后右键java文件夹—>select build path –> use as source folder, 这样就可以把源目录指向src/main/java了