薛定谔的风口猪

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

从属性文件为变量赋值,Spring

有时候,我们需要从一些属性文件为变量赋值,这样修改变量值就不需要修改代码,直接修改属性文件就可以了。这样方便管理和维护。

Spring可以很轻松地通过Annotation的方式注入属性值,以下记录下学习的过程。

配置

1. 新建属性文件

application.properties

里面的格式为: name=value

如:

token=wodinow

2. 配置属性文件路径

<context:property-placeholder location="classpath:application.properties" />

使用注解注入

在一个Bean中, 使用@Value注解使用${name}格式,如在@Controller中

@Controller
public class CoreController {   

    @Value("${developer_id}")
    private String developerID;

    }

为静态变量注入

Spring不能为静态变量注入属性值,但我们可以配置一个setter完成此项工作,如:

@Component
public class SignUtil {  

    //需要从属性文件中读值的静态变量
    private static  String TOKEN;  

    //配置一个setter, 为该静态变量赋值
    @Value("${token}")
    private void setTOKEN(String token) {
        SignUtil.TOKEN = token;
    }  
}

通过VM参数选择本地log4j配置文件

有时候我们项目的log4j配置文件配置的是生产环境,每次本地调试又不想改会本地的调试配置,就可以通过JVM参数去修改此路径。

在项目的参数中加入:

-Dlog4j.configuration=file:log4j配置文件路径

注:路径需要以file:开头,如:

-Dlog4j.configuration=file:E:\Programming\debug_log4j.properties

即可在本地运行时选择本地的配置文件进行日志配置。

在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 服务的内存空间在进行操作。