月度归档:2015年01月

MySQL高效分页解决方案集(转)

        平时用到的分页方法不多,只从索引、分表、使用子查询精准定位偏移以外,没有使用到其它方法。
后来在看其它博客看到了一些不同的方案,也一直没有整理。今天有时间,整理出来,分享给大家。一,最常见MYSQL最基本的分页方式:

select * from content order by id desc limit 0, 10

在中小数据量的情况下,这样的SQL足够用了,唯一需要注意的问题就是确保使用了索引。随着数据量的增加,页数会越来越多,查看后几页的SQL就可能类似:

select * from content order by id desc limit 10000, 10

一言以蔽之,就是越往后分页,LIMIT语句的偏移量就会越大,速度也会明显变慢。
此时,我们可以通过2种方式:
一,子查询的分页方式来提高分页效率,飘易用的SQL语句如下:

SELECT * FROM `content` WHERE id> (SELECT id FROM `content` ORDER BY id desc LIMIT ".($page-1)*$pagesize.", 1) ORDER BY id desc LIMIT $pagesize

为什么会这样呢?因为子查询是在索引上完成的,而普通的查询时在数据文件上完成的,通常来说,索引文件要比数据文件小得多,所以操作起来也会更有效率。(via)通过explain SQL语句发现:子查询使用了索引!

id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY content range PRIMARY PRIMARY 4 NULL 6264 Using where
2 SUBQUERY content index NULL PRIMARY 4 NULL 27085 Using index

经过飘易的实测,使用子查询的分页方式的效率比纯LIMIT提高了14-20倍!

 

二,JOIN分页方式

select * FROM `content` AS t1
JOIN (SELECT id FROM `content` ORDER BY id desc LIMIT ".($page-1)*$pagesize.", 1) AS t2
WHERE t1.id

经过我的测试,join分页和子查询分页的效率基本在一个等级上,消耗的时间也基本一致。explain SQL语句:

id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY system NULL NULL NULL NULL 1
1 PRIMARY t1 range PRIMARY PRIMARY 4 NULL 6264 Using where
2 DERIVED content index NULL PRIMARY 4 NULL 27085 Using index

三,使用MYSQL的FOUND_ROWS()函数
Mysql FOUND_ROWS() 函数结合SQL_CALC_FOUND_ROWS在SELECT中可以得到两个结果:
1. 得到Limit的内容
2. 得到去除Limit以后所有行数

SELECT语句中经常可能用LIMIT限制返回行数。有时候可能想要知道如果没有LIMIT会返回多少行,但又不想再执行一次相同语句。那么,在SELECT查询中包含SQL_CALC_FOUND_ROWS选项,然后执行FOUND_ROWS()就可以了:

select SQL_CALC_FOUND_ROWS * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();

其中SQL_CALC_FOUND_ROWS 告诉Mysql将sql所处理的行数记录下来,FOUND_ROWS() 则取到了这个纪录。 虽然也是两个语句,但是只执行了一次主查询,所以效率比原来要高很多。

1. 如果在前一条语句中使用SQL_CALC_FOUND_ROWS选项,FOUND_ROWS()将返回第一条语句没有LIMIT时返回的行数。
2. 如果在前一条语句中没有使用SQL_CALC_FOUND_ROWS选项,FOUND_ROWS()将返回前一条语句实际返回的行数。
如 果使用 SELECT SQL_CALC_FOUND_ROWS,MySQL必须计算所有结果集的行数。尽管这样,总比再执行一次不使用LIMIT的查询要快多了吧,因为那样结 果集要返回客户端滴。(另外:应该不单是没有将结果集返回的原因,还有原因可能是比如LIKE之类比较费劲的SQL不需要再去劳累一次。)

-- 注意下面语句中的条件 LIKE
SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name WHERE Name LIKE '%string%' id > 100 LIMIT 10;
SELECT FOUND_ROWS();

-- 上面语句等价于下面语句,但性能方面应该提升非常非常的明显:
SELECT COUNT(*) FROM tbl_name WHERE Name LIKE '%string%' ;
SELECT * FROM tbl_name WHERE Name LIKE '%string%' id > 100 LIMIT 10;

参考博客:

http://blog.hexu.org/archives/1328.shtml

http://hi.baidu.com/thinkinginlamp/blog/item/17476d22d66876a14623e81d.html

http://www.piaoyi.org/php/MySQL-SUBQUERY-index.html

STS下新建spring MVC 工程

File--New--Spring Template Project

选择Spring MVC Project

填写项目名称和基础的包名

Finish之后就完成了。

把项目添加到服务器,然后运行。在浏览器访问,就可以看到项目了。

在浏览器访问,就可以看到项目了。这里乱码是因为默认的不是UTF-8。

访问路径的那个名字应该是可以改的,虽然没试过。

来源:http://blog.csdn.net/wuyt2008/article/details/8594870

Netflix Curator使用

Netflix curator 是Netflix公司开源的一个Zookeeper client library,用于简化zookeeper客户端编程,包含一下几个模块:

  • curator-client - zookeeper client封装,用于取代原生的zookeeper客户端,提供一些非常有用的客户端特性
  • curator-framework - zookeeper api的高层封装,大大简化zookeeper客户端编程,添加了例如zookeeper连接管理、重试机制等
  • curator-recipes - zookeeper recipes 基于curator-framework的实现(除2PC以外)

maven dependency:

  1. <dependency>
  2.     <groupId>com.netflix.curator</groupId>
  3.     <artifactId>curator-recipes</artifactId>
  4.     <version>0.6.4</version>
  5. </dependency>

注意:在www.mvnrepository.com中认为0.32为最新版本,其实迄今为止最新版本为0.64,github trunk中的版本现在是0.65-SNAPSHOT

curator framework 使用

  1.               String path = "/test_path";
  2. CuratorFramework client = CuratorFrameworkFactory.builder()
  3.         .connectString("test:2181").namespace("/test1")
  4.         .retryPolicy(new RetryNTimes(Integer.MAX_VALUE, 1000))
  5.         .connectionTimeoutMs(5000).build();
  6. //create a node
  7. client.create().forPath("/head"new byte[0]);
  8. //delete a node in background
  9. client.delete().inBackground().forPath("/head");
  10. // create a EPHEMERAL_SEQUENTIAL
  11. client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/head/child"new byte[0]);
  12. // get the data 
  13. client.getData().watched().inBackground().forPath("/test");
  14. // check the path exits
  15. client.checkExists().forPath(path);

curator framework使用builder模式和类似nio的chain api,代码非常简洁

curator recipes 使用

InterProcessMutex

用途:进程间互斥锁

示例代码:

  1. String lockName = "/lock1";
  2. InterProcessLock lock1 = new InterProcessMutex(this.curator, lockName);
  3. InterProcessLock lock2 = new InterProcessMutex(this.curator, lockName);
  4. lock1.acquire();
  5. boolean result = lock2.acquire(1, TimeUnit.SECONDS);
  6. assertFalse(result);
  7. lock1.release();
  8. result = lock2.acquire(1, TimeUnit.SECONDS);
  9. assertTrue(result);

原理:每次调用acquire在/lock1节点节点下使用CreateMode.EPHEMERAL_SEQUENTIAL 创建新的ephemeral节点,然后getChildren获取所有的children,判断刚刚创建的临时节点是否为第一个,如果是,则获取锁成功; 如果不是,则删除刚刚创建的临时节点。

注意: 每次accquire操作,成功,则请求zk server 2次(一次写,一次getChildren);如果失败,则请求zk server 3次(一次写,一次getChildren,一次delete)

InterProcessReadWriteLock

示例代码:

  1. @Test
  2. public void testReadWriteLock() throws Exception{
  3.     String readWriteLockPath = "/RWLock";
  4.     InterProcessReadWriteLock readWriteLock1 = new InterProcessReadWriteLock(this.curator, readWriteLockPath);
  5.     InterProcessMutex writeLock1 = readWriteLock1.writeLock();
  6.     InterProcessMutex readLock1 = readWriteLock1.readLock();
  7.     InterProcessReadWriteLock readWriteLock2 = new InterProcessReadWriteLock(this.curator, readWriteLockPath);
  8.     InterProcessMutex writeLock2 = readWriteLock2.writeLock();
  9.     InterProcessMutex readLock2 = readWriteLock2.readLock();
  10.     writeLock1.acquire();
  11.     // same with WriteLock, can read
  12.     assertTrue(readLock1.acquire(1, TimeUnit.SECONDS));
  13.     // different lock, can't read while writting
  14.     assertFalse(readLock2.acquire(1, TimeUnit.SECONDS));
  15.     // different write lock, can't write
  16.     assertFalse(writeLock2.acquire(1, TimeUnit.SECONDS));
  17.     // release the write lock
  18.     writeLock1.release();
  19.     //both read lock can read
  20.     assertTrue(readLock1.acquire(1, TimeUnit.SECONDS));
  21.     assertTrue(readLock2.acquire(1, TimeUnit.SECONDS));
  22. }

原理: 同InterProcessMutext,在ephemeral node的排序算法上做trick,write lock的排序在前。

注意: 同一个InterProcessReadWriteLock如果已经获取了write lock,则获取read lock也会成功

LeaderSelector

示例代码:

  1. @Test
  2. public void testLeader() throws Exception{
  3.     LeaderSelectorListener listener = new LeaderSelectorListener(){
  4.         @Override
  5.         public void takeLeadership(CuratorFramework client)
  6.                 throws Exception {
  7.             System.out.println("i'm leader");
  8.         }
  9.         @Override
  10.         public void handleException(CuratorFramework client,
  11.                 Exception exception) {
  12.         }
  13.         @Override
  14.         public void notifyClientClosing(CuratorFramework client) {
  15.         }};
  16.     String leaderPath = "/leader";
  17.     LeaderSelector selector1 = new LeaderSelector(this.curator, leaderPath, listener);
  18.     selector1.start();
  19.     LeaderSelector selector2 = new LeaderSelector(this.curator, leaderPath, listener);
  20.     selector2.start();
  21.     assertFalse(selector2.hasLeadership());
  22. }

原理:内部基于InterProcessMutex实现,具体细节参见shared lock一节

总结

curator还提供了很多其他的实现,具体参见https://github.com/Netflix/curator/wiki/Recipes

 

参考文章:

Zookeeper开源客户端框架Curator简介

Curator是Netflix开源的一套ZooKeeper客户端框架. Netflix在使用ZooKeeper的过程中发现ZooKeeper自带的客户端太底层, 应用方在使用的时候需要自己处理很多事情, 于是在它的基础上包装了一下, 提供了一套更好用的客户端框架. Netflix在用ZooKeeper的过程中遇到的问题, 我们也遇到了, 所以开始研究一下, 首先从他在github上的源码, wiki文档以及Netflix的技术blog入手.看完官方的文档之后, 发现Curator主要解决了三类问题:

  • 封装ZooKeeper client与ZooKeeper server之间的连接处理;
  • 提供了一套Fluent风格的操作API;
  • 提供ZooKeeper各种应用场景(recipe, 比如共享锁服务, 集群领导选举机制)的抽象封装.

Curator列举的ZooKeeper使用过程中的几个问题
初始化连接的问题: 在client与server之间握手建立连接的过程中, 如果握手失败, 执行所有的同步方法(比如create, getData等)将抛出异常
自动恢复(failover)的问题: 当client与一台server的连接丢失,并试图去连接另外一台server时, client将回到初始连接模式
session过期的问题: 在极端情况下, 出现ZooKeeper session过期, 客户端需要自己去监听该状态并重新创建ZooKeeper实例 .
对可恢复异常的处理:当在server端创建一个有序ZNode, 而在将节点名返回给客户端时崩溃, 此时client端抛出可恢复的异常, 用户需要自己捕获这些异常并进行重试
使用场景的问题:Zookeeper提供了一些标准的使用场景支持, 但是ZooKeeper对这些功能的使用说明文档很少, 而且很容易用错. 在一些极端场景下如何处理, zk并没有给出详细的文档说明. 比如共享锁服务, 当服务器端创建临时顺序节点成功, 但是在客户端接收到节点名之前挂掉了, 如果不能很好的处理这种情况, 将导致死锁.

Curator主要从以下几个方面降低了zk使用的复杂性:
重试机制:提供可插拔的重试机制, 它将给捕获所有可恢复的异常配置一个重试策略, 并且内部也提供了几种标准的重试策略(比如指数补偿).
连接状态监控: Curator初始化之后会一直的对zk连接进行监听, 一旦发现连接状态发生变化, 将作出相应的处理.
zk客户端实例管理:Curator对zk客户端到server集群连接进行管理. 并在需要的情况, 重建zk实例, 保证与zk集群的可靠连接
各种使用场景支持:Curator实现zk支持的大部分使用场景支持(甚至包括zk自身不支持的场景), 这些实现都遵循了zk的最佳实践, 并考虑了各种极端情况.

Curator通过以上的处理, 让用户专注于自身的业务本身, 而无需花费更多的精力在zk本身.

Curator声称的一些亮点:

日志工具
内部采用SLF4J 来输出日志
采用驱动器(driver)机制, 允许扩展和定制日志和跟踪处理
提供了一个TracerDriver接口, 通过实现addTrace()和addCount()接口来集成用户自己的跟踪框架

和Curator相比, 另一个ZooKeeper客户端——zkClient(https://github.com/sgroschupf/zkclient)的不足之处:
文档几乎没有
异常处理弱爆了(简单的抛出RuntimeException)
重试处理太难用了
没有提供各种使用场景的实现

对ZooKeeper自带客户端(ZooKeeper类)的"抱怨":
只是一个底层实现
要用需要自己写大量的代码
很容易误用
需要自己处理连接丢失, 重试等

Curator几个组成部分

  • Client: 是ZooKeeper客户端的一个替代品, 提供了一些底层处理和相关的工具方法.
  • Framework: 用来简化ZooKeeper高级功能的使用, 并增加了一些新的功能, 比如管理到ZooKeeper集群的连接, 重试处理
  • Recipes: 实现了通用ZooKeeper的recipe, 该组件建立在Framework的基础之上
  • Utilities:各种ZooKeeper的工具类
  • Errors: 异常处理, 连接, 恢复等.
  • Extensions: recipe扩展

Client
这是一个底层的API, 应用方基本对这个可以无视, 最好直接从Curator Framework入手
主要包括三部分:
不间断连接管理
连接重试处理

Retry Loop(循环重试)
一种典型的用法:

Java代码
  1. RetryLoop retryLoop = client.newRetryLoop();
  2. while ( retryLoop.shouldContinue() )
  3. {
  4.    try
  5.    {
  6.        // perform your work
  7.        ...
  8.        // it's important to re-get the ZK instance as there may have been an error and the instance was re-created
  9.        ZooKeeper      zk = client.getZookeeper();
  10.        retryLoop.markComplete();
  11.    }
  12.    catch ( Exception e )
  13.    {
  14.        retryLoop.takeException(e);
  15.    }
  16. }

如果在操作过程中失败, 且这种失败是可重试的, 而且在允许的次数内, Curator将保证操作的最终完成.

另一种使用Callable接口的重试做法:

Java代码
  1. RetryLoop.callWithRetry(client, new Callable()
  2. {
  3.       @Override
  4.       public Void call() throws Exception
  5.       {
  6.           // do your work here - it will get retried if needed
  7.           return null;
  8.       }
  9. });

重试策略
RetryPolicy接口只有一个方法(以前版本有两个方法):
public boolean allowRetry(int retryCount, long elapsedTimeMs);
在开始重试之前, allowRetry方法被调用, 其参数将指定当前重试次数, 和操作已消耗时间. 如果允许, 将继续重试, 否则抛出异常.

Curator内部实现的几种重试策略:

  • ExponentialBackoffRetry:重试指定的次数, 且每一次重试之间停顿的时间逐渐增加.
  • RetryNTimes:指定最大重试次数的重试策略
  • RetryOneTime:仅重试一次
  • RetryUntilElapsed:一直重试直到达到规定的时间

Framework
是ZooKeeper Client更高的抽象API
自动连接管理: 当ZooKeeper客户端内部出现异常, 将自动进行重连或重试, 该过程对外几乎完全透明
更清晰的API: 简化了ZooKeeper原生的方法, 事件等, 提供流程的接口

CuratorFrameworkFactory类提供了两个方法, 一个工厂方法newClient, 一个构建方法build. 使用工厂方法newClient可以创建一个默认的实例, 而build构建方法可以对实例进行定制. 当CuratorFramework实例构建完成, 紧接着调用start()方法, 在应用结束的时候, 需要调用close()方法.  CuratorFramework是线程安全的. 在一个应用中可以共享同一个zk集群的CuratorFramework.

CuratorFramework API采用了连贯风格的接口(Fluent Interface). 所有的操作一律返回构建器, 当所有元素加在一起之后, 整个方法看起来就像一个完整的句子. 比如下面的操作:

Java代码
  1. client.create().forPath("/head"new byte[0]);
  2. client.delete().inBackground().forPath("/head");
  3. client.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath("/head/child"new byte[0]);
  4. client.getData().watched().inBackground().forPath("/test");

方法说明:

  • create(): 发起一个create操作. 可以组合其他方法 (比如mode 或background) 最后以forPath()方法结尾
  • delete(): 发起一个删除操作. 可以组合其他方法(version 或background) 最后以forPath()方法结尾
  • checkExists(): 发起一个检查ZNode 是否存在的操作. 可以组合其他方法(watch 或background) 最后以forPath()方法结尾
  • getData(): 发起一个获取ZNode数据的操作. 可以组合其他方法(watch, background 或get stat) 最后以forPath()方法结尾
  • setData(): 发起一个设置ZNode数据的操作. 可以组合其他方法(version 或background) 最后以forPath()方法结尾
  • getChildren(): 发起一个获取ZNode子节点的操作. 可以组合其他方法(watch, background 或get stat) 最后以forPath()方法结尾
  • inTransaction(): 发起一个ZooKeeper事务. 可以组合create, setData, check, 和/或delete 为一个操作, 然后commit() 提交

.

通知(Notification)
Curator的相关代码已经更新了, 里面的接口已经由ClientListener改成CuratorListener了, 而且接口中去掉了clientCloseDueToError方法. 只有一个方法:
eventReceived()            当一个后台操作完成或者指定的watch被触发时该方法被调用

UnhandledErrorListener接口用来对异常进行处理.

CuratorEvent(在以前版本为ClientEvent)是对各种操作触发相关事件对象(POJO)的一个完整封装, 而事件对象的内容跟事件类型相关, 下面是对应关系:

CREATEgetResultCode() and getPath()
DELETEgetResultCode() and getPath()
EXISTSgetResultCode(), getPath() and getStat()
GET_DATAgetResultCode(), getPath(), getStat() and getData()
SET_DATAgetResultCode(), getPath() and getStat()
CHILDRENgetResultCode(), getPath(), getStat(), getChildren()
WATCHEDgetWatchedEvent()

名称空间(Namespace)
因为一个zk集群会被多个应用共享, 为了避免各个应用的zk patch冲突, Curator Framework内部会给每一个Curator Framework实例分配一个namespace(可选). 这样你在create ZNode的时候都会自动加上这个namespace作为这个node path的root. 使用代码如下:

Java代码
  1. CuratorFramework    client = CuratorFrameworkFactory.builder().namespace("MyApp") ... build();
  2.  …
  3. client.create().forPath("/test", data);
  4. // node was actually written to: "/MyApp/test"

Recipe

Curator实现ZooKeeper的所有recipe(除了两段提交)
选举
集群领导选举(leader election)

锁服务
共享锁: 全局同步分布式锁, 同一时间两台机器只有一台能获得同一把锁.
共享读写锁: 用于分布式的读写互斥处理, 同时生成两个锁:一个读锁, 一个写锁, 读锁能被多个应用持有, 而写锁只能一个独占, 当写锁未被持有时, 多个读锁持有者可以同时进行读操作
共享信号量: 在分布式系统中的各个JVM使用同一个zk lock path, 该path将跟一个给定数量的租约(lease)相关联, 然后各个应用根据请求顺序获得对应的lease, 相对来说, 这是最公平的锁服务使用方式.
多共享锁:内部构件多个共享锁(会跟一个znode path关联), 在acquire()过程中, 执行所有共享锁的acquire()方法, 如果中间出现一个失败, 则将释放所有已require的共享锁; 执行release()方法时, 则执行内部多个共享锁的release方法(如果出现失败将忽略)

队列(Queue)
分布式队列:采用持久顺序zk node来实现FIFO队列, 如果有多个消费者, 可以使用LeaderSelector来保证队列的消费者顺序
分布式优先队列: 优先队列的分布式版本
BlockingQueueConsumer: JDK阻塞队列的分布式版本

关卡(Barrier)
分布式关卡:一堆客户端去处理一堆任务, 只有所有的客户端都执行完, 所有客户端才能继续往下处理
双分布式关卡:同时开始, 同时结束

计数器(Counter)
共享计数器:所有客户端监听同一个znode path, 并共享一个最新的integer计数值
分布式AtomicLong(AtomicInteger): AtomicXxx的分布式版本, 先采用乐观锁更新, 若失败再采用互斥锁更新, 可以配置重试策略来处理重试

工具类

Path Cache
Path Cache用于监听ZNode的子节点的变化, 当add, update, remove子节点时将改变Path Cache state, 同时返回所有子节点的data和state.
Curator中采用了PathChildrenCache类来处理Path Cache, 状态的变化则采用PathChildrenCacheListener来监听.
相关用法参见TestPathChildrenCache测试类

注意: 当zk server的数据发生变化, zk client会出现不一致, 这个需要通过版本号来识别这种状态的变化

Test Server
用来在测试中模拟一个本地进程内ZooKeeper Server.

Test Cluster
用来在测试中模拟一个ZooKeeper Server集群

ZKPaths工具类
提供了和ZNode相关的path处理工具方法:

  • getNodeFromPath: 根据给定path获取node name. i.e. "/one/two/three" -> "three"
  •     mkdirs: 根据给定路径递归创建所有node
  •     getSortedChildren: 根据给定路径, 返回一个按序列号排序的子节点列表
  •     makePath: 根据给定的path和子节点名, 创建一个完整path

EnsurePath工具类

直接看例子, 具体的说就是调用多次, 只会执行一次创建节点操作.

Java代码
  1. EnsurePath       ensurePath = new EnsurePath(aFullPathToEnsure);
  2. ...
  3. String           nodePath = aFullPathToEnsure + "/foo";
  4. ensurePath.ensure(zk);   // first time syncs and creates if needed
  5. zk.create(nodePath, ...);
  6. ...
  7. ensurePath.ensure(zk);   // subsequent times are NOPs
  8. zk.create(nodePath, ...);

Notification事件处理
Curator对ZooKeeper的事件Watcher进行了封装处理, 然后实现了一套监听机制. 提供了几个监听接口用来处理ZooKeeper连接状态的变化
当连接出现异常, 将通过ConnectionStateListener接口进行监听, 并进行相应的处理, 这些状态变化包括:

  • 暂停(SUSPENDED): 当连接丢失, 将暂停所有操作, 直到连接重新建立, 如果在规定时间内无法建立连接, 将触发LOST通知
  • 重连(RECONNECTED): 连接丢失, 执行重连时, 将触发该通知
  • 丢失(LOST): 连接超时时, 将触发该通知

从 com.netflix.curator.framework.imps.CuratorFrameworkImpl.validateConnection(CuratorEvent) 方法中我们可以知道, Curator分别将ZooKeeper的Disconnected, Expired, SyncConnected三种状态转换成上面三种状态.

参考

来源:http://macrochen.iteye.com/blog/1366136

 

参考文章: