使用NoSQL实现高并发CRM系统实践(源代码+解析)

又想速度快,又要大数据,又要保证数据不出错,还要拥抱变化,改需求的时候不那么痛苦,特别是字段的调整,按照以前的做法,想想就头疼。使用NoSQL,简直就是随心所欲,再奇葩的数据结构,处理起来也很容易。下面看我如何用NoSQL数据库实现高并发,高可靠的CRM系统。

1、前言

随着facebook、微博等WEB2.0互联网网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。

NoSQL项目的名字上看不出什么相同之处,但是,它们通常在某些方面相同:它们可以处理超大量的数据。

目前NoSQL数据库的发展非常迅速,各大公司都在大量使用,比如GOOGLE、360、百度,在不久的将来必将成为主流。

为了适应大数据处理的需要,在将来较长的一段时间里能够让系统适应高并发的访问,我们在开发CRM系统的过程中,经过不断摸索和实践,通过使用NoSQL数据库,解决了传统关系型数据很难解决的难题,实现了大数据和高并发的处理能力。

2、系统展示

3、性能数据

测试环境是CentOS系统,ngnix + php5.0, i7 2600K处理器,8G内存,SSDB 1.6.8.8版本。

针对最常用的功能,以前数据量最大的功能进行测试。

用PHP程序模拟UI层直接调用业务逻辑接口,插入大量数据,并对大量数据进行读取。


功能


测试说明


测试结果(毫秒)


添加客户资料


添加100万条数据


15203


客户资料查询


对100万条客户资料进行查询1万次


20


下订单


添加100万条数据


58653


订单查询


对100万条订单进行查询1万次


25


添加新消息


添加100万条数据


13454


获取新消息


对100万条消息数据进行查询1万次


18

4、系统架构

在使用数据库的选择上,我们使用同时具有高性能,并且接口使用很简单的SSDB。因为SSDB的作者是我的朋友,同时SSDB已经在百度、360等大公司大量应用,所以不用担心可靠性问题。

开发语言使用PHP。

4.1 系统需求

2014年禾葡兰借着微信营销的大好机会,在几个月时间里,团队发展到了100人以上,每日订单数超过200单。

在快速发展的过程中,对于数据处理的需求日益增长,通过数据和统计进行决策,使经营水平和收入更上一个台阶,并能够适应将来的大规模扩展的需要。

目前纯手工统计和计算比较容易出错,而且效率很低,很难适应快速增长的需要,为了解决这些问题,欣所罗门核心团队成员一致认为有必要开发一个软件系统来解决问题。

4.2 需要解决的3大难点

1、事务处理

在数据完整性要求很高的CRM系统中,必需要事务处理机制,保证原子操作。但是,NoSQL数据库最大的缺点就是不支持事务处理。

要想实现事务处理,目前最好的方法是使用zookeeper,可以模拟实现分布式事务。

2、数据关系

和关系数据库不同的是,SSDB没有关系的概念。所有数据间的关系必需在开始的时候设计好。

3、组合条件查询

由于NoSQL数据库没有模式,因此,想要根据数据内容里的某个字段对数据集进行筛选,根本不可能。所以,我们使用lucene实现全文索引,解决了组合条件查询的问题。

4.3 系统设计

由于目前流行的PHP开发框架MVC框架都不支持NoSQL数据,同时避免由于开源框架带来的安全问题,因此不使用MVC框架。仅在UI层使用php的smarty模板引擎。

系统统一入口文件index.php,通过m参数引用ui目录和controller目录下相应的文件。

代码目录结构

/  CRM系统根目录

/index.php (统一入口文件)

|-------common/  (用于存放公共函数)

|--------common.php  (公共函数接口文件,使用公共函数只需要引用一个文件就可以。)

|-------config/  (用于存放配置信息)

|-------model/  (数据对像)

|-------data/  (数据层,对数据的读写封装。)

|-------controller/  (业务逻辑层)

|-------ui/  (smarty引擎和模板)

|-------libs/  (smarty引擎目录)

|-------configs/ (smarty引擎配置文件目录)

|-------plugins/ (smarty引擎自定义的一些实用插件目录)

|-------templates/ (smarty引擎模板目录)

|-------templates_c/ (smarty引擎模板编译目录)

|-------cache/ (smarty缓存目录)

5、涉及内容

一、 数据库连接

二、 自动编号ID实现

三、 数据的读写

四、 数据关系实现

五、 分布式事务实现

六、 全文索引

七、 组合条件查询

6、源码解析

一、数据库连接

SSDB的数据库连接很简单。

include_once(‘SSDB.php‘);  
try{  
    $ssdb = new SimpleSSDB(‘127.0.0.1‘, 8888);  
}catch(SSDBException $e){  
    die(__LINE__ . ‘ ‘ . $e->getMessage());  
}
include_once(‘SSDB.php‘);
try{
    $ssdb = new SimpleSSDB(‘127.0.0.1‘, 8888);
}catch(SSDBException $e){
    die(__LINE__ . ‘ ‘ . $e->getMessage());
}

二、 自动编号ID实现

一般来说,我们在进行数据设计的时候,都会给每条记录设置一个自动编号的ID。但是在NoSQL数据中,需要自己来实现自动编号ID。

我们通过SSDB的incr接口,就可以模拟自动编号ID的实现。

系统初始化的时候,我们在数据库中增加一条数据。

$ssdb->set(‘hpl_product_autoincrement_id‘, 0);
$ssdb->set(‘hpl_product_autoincrement_id‘, 0);

添加新记录的时候,使用incr接口得到新的自动编号ID,由于incr接口是原子操作,所以不会出现重复的ID。

$id = $ssdb->incr(‘hpl_product_autoincrement_id‘, 1);
$id = $ssdb->incr(‘hpl_product_autoincrement_id‘, 1);

三、 数据的读写

首先,我们要创建数据的model类。下面是产品model类。

class ProductModel{  
    public $id;                 //编号  
    public $catalogid;  //所属分类  
    public $name;               //产品名称  
    public $cost;               //产品成本价格  
    public $price;          //销售价格  
    public $saleprice;  //优惠价格  
    public $amount;         //产品数量  
    public $facetype;       //适合皮肤类型  
    public $desc;               //产品描述  
    public $code;               //产品编号  
    public $weight;         //净含量(克)  
    public $effect;         //主要功效  
    public $crowd;          //适合人群  
    public $addtime;        //产品添加时间  
    public $status;         //产品状态(0 = 下架 1 = 上架)  
}
class ProductModel{
	public $id;					//编号
	public $catalogid;	//所属分类
	public $name;				//产品名称
	public $cost;				//产品成本价格
	public $price;			//销售价格
	public $saleprice;	//优惠价格
	public $amount;			//产品数量
	public $facetype;		//适合皮肤类型
	public $desc;				//产品描述
	public $code;				//产品编号
	public $weight;			//净含量(克)
	public $effect;			//主要功效
	public $crowd;			//适合人群
	public $addtime;		//产品添加时间
	public $status;			//产品状态(0 = 下架 1 = 上架)
}

新添加记录。

$product = new ProductModel();  
$product->id = $id;  
  
...  
  
$key = ‘hpl_product_‘.$id;  
$json = json_encode($product);  
$ssdb->hset(‘hpl_product‘, $key, $json);
$product = new ProductModel();
$product->id = $id;

...

$key = ‘hpl_product_‘.$id;
$json = json_encode($product);
$ssdb->hset(‘hpl_product‘, $key, $json);

增加索引,根据产品ID进行索引,在使用数据的时候就可以根据产品ID获取数据列表(如果有多种排列方式就需要创建多个索引)。

$key = ‘hpl_product_‘.$id;  
$ssdb->zset(‘hpl_product_id‘, $key, $id);
$key = ‘hpl_product_‘.$id;
$ssdb->zset(‘hpl_product_id‘, $key, $id);

根据索引获取产品列表(前10个)。

$products = array();  
//根据索引取出产品列表  
$items = $ssdb->zscan(‘hpl_product_id‘, ‘‘, ‘‘, ‘‘, 10);  
foreach($items as $key=>$score){  
    //取出产品信息  
    $json = $ssdb->hget(‘hpl_product‘, $key);  
    $products[] = json_decode($json);  
}

四、数据关系实现

在添加产品的时候,需要把产品和产品分类关联起来,可以根据产品分类列出产品。使用SSDB我们只需要对每个产品分类创建一个列表就可以了。

添加关系,在列表里添加一条数据。

$hname = ‘hpl_product_catalog_‘.$catalogid;  
$hkey = ‘hpl_product_‘.$id;  
$ssdb->hset($hname, $hkey, $id);
$hname = ‘hpl_product_catalog_‘.$catalogid;
$hkey = ‘hpl_product_‘.$id;
$ssdb->hset($hname, $hkey, $id);

删除关系,当改变产品分类或者删除产品的时候执行。

$hname = ‘hpl_product_catalog_‘.$catalogid;  
$hkey = ‘hpl_product_‘.$id;  
$ssdb->hdel($hname, $hkey);
$hname = ‘hpl_product_catalog_‘.$catalogid;
$hkey = ‘hpl_product_‘.$id;
$ssdb->hdel($hname, $hkey);

根据分类取出产品列表(前10个)。

$products = array();  
//根据分类取出产品列表  
$hname = ‘hpl_product_catalog_‘.$catalogid;  
$keys = $ssdb->hkeys($hname, ‘‘, ‘‘, 10);  
foreach ($keys as $key) {  
    //取出产品信息  
    $json = $ssdb->hget(‘hpl_product‘, $key);  
    $products[] = json_decode($json);  
}
$products = array();
//根据分类取出产品列表
$hname = ‘hpl_product_catalog_‘.$catalogid;
$keys = $ssdb->hkeys($hname, ‘‘, ‘‘, 10);
foreach ($keys as $key) {
    //取出产品信息
    $json = $ssdb->hget(‘hpl_product‘, $key);
    $products[] = json_decode($json);
}

五、分布式事务实现

由于CRM系统对数据统一性的需要,所以必需要有事务来支持。比如在下订单的时候,如果没有事务,在并发处理时就有可能导致产品库存出错。由于SSDB数据库没有实现事务处理,我们使用zookeeper来实现分布式锁的处理,保证关键业务的原子操作。

事务处理的实现我们分别由前台和后台实现,前台系统发送业务处理请求,后台进程收到消息后进行处理,后台进程处理结束后设置处理标志,前台系统每隔一段时间查询一次标志位,检查业务是否处理完成。

后台实现使用JAVA语言编写。

/** 
Executor.java 
*/  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.InputStream;  
import java.io.OutputStream;  
  
import org.apache.zookeeper.KeeperException;  
import org.apache.zookeeper.WatchedEvent;  
import org.apache.zookeeper.Watcher;  
import org.apache.zookeeper.ZooKeeper;  
  
public class Executor  
    implements Watcher, Runnable, DataMonitor.DataMonitorListener  
{  
    String znode;  
  
    DataMonitor dm;  
  
    ZooKeeper zk;  
  
    String filename;  
  
    String exec[];  
  
    Process child;  
  
    public Executor(String hostPort, String znode, String filename,  
            String exec[]) throws KeeperException, IOException {  
        this.filename = filename;  
        this.exec = exec;  
        zk = new ZooKeeper(hostPort, 3000, this);  
        dm = new DataMonitor(zk, znode, null, this);  
    }  
  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        if (args.length < 4) {  
            System.err  
                    .println("USAGE: Executor hostPort znode filename program [args ...]");  
            System.exit(2);  
        }  
        String hostPort = args[0];  
        String znode = args[1];  
        String filename = args[2];  
        String exec[] = new String[args.length - 3];  
        System.arraycopy(args, 3, exec, 0, exec.length);  
        try {  
            new Executor(hostPort, znode, filename, exec).run();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
  
    /*************************************************************************** 
     * We do process any events ourselves, we just need to forward them on. 
     * 
     * @see org.apache.zookeeper.Watcher#process(org.apache.zookeeper.proto.WatcherEvent) 
     */  
    public void process(WatchedEvent event) {  
        dm.process(event);  
    }  
  
    public void run() {  
        try {  
            synchronized (this) {  
                while (!dm.dead) {  
                    wait();  
                }  
            }  
        } catch (InterruptedException e) {  
        }  
    }  
  
    public void closing(int rc) {  
        synchronized (this) {  
            notifyAll();  
        }  
    }  
  
    static class StreamWriter extends Thread {  
        OutputStream os;  
  
        InputStream is;  
  
        StreamWriter(InputStream is, OutputStream os) {  
            this.is = is;  
            this.os = os;  
            start();  
        }  
  
        public void run() {  
            byte b[] = new byte[80];  
            int rc;  
            try {  
                while ((rc = is.read(b)) > 0) {  
                    os.write(b, 0, rc);  
                }  
            } catch (IOException e) {  
            }  
  
        }  
    }  
  
    public void exists(byte[] data) {  
        if (data == null) {  
            if (child != null) {  
                System.out.println("Killing process");  
                child.destroy();  
                try {  
                    child.waitFor();  
                } catch (InterruptedException e) {  
                }  
            }  
            child = null;  
        } else {  
            if (child != null) {  
                System.out.println("Stopping child");  
                child.destroy();  
                try {  
                    child.waitFor();  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
            try {  
                FileOutputStream fos = new FileOutputStream(filename);  
                fos.write(data);  
                fos.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
            try {  
                System.out.println("Starting child");  
                child = Runtime.getRuntime().exec(exec);  
                new StreamWriter(child.getInputStream(), System.out);  
                new StreamWriter(child.getErrorStream(), System.err);  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}  
  
  
  
/** 
DataMonitor.java 
 */  
import java.util.Arrays;  
  
import org.apache.zookeeper.KeeperException;  
import org.apache.zookeeper.WatchedEvent;  
import org.apache.zookeeper.Watcher;  
import org.apache.zookeeper.ZooKeeper;  
import org.apache.zookeeper.AsyncCallback.StatCallback;  
import org.apache.zookeeper.KeeperException.Code;  
import org.apache.zookeeper.data.Stat;  
import com.udpwork.ssdb.SSDB;  
import com.udpwork.ssdb.Link;  
import com.udpwork.ssdb.MemoryStream;  
import com.udpwork.ssdb.Response;  
  
public class DataMonitor implements Watcher, StatCallback {  
  
    ZooKeeper zk;  
  
    String znode;  
  
    Watcher chainedWatcher;  
  
    boolean dead;  
  
    DataMonitorListener listener;  
  
    byte prevData[];  
  
    public DataMonitor(ZooKeeper zk, String znode, Watcher chainedWatcher,  
            DataMonitorListener listener) {  
        this.zk = zk;  
        this.znode = znode;  
        this.chainedWatcher = chainedWatcher;  
        this.listener = listener;  
        // Get things started by checking if the node exists. We are going  
        // to be completely event driven  
        zk.exists(znode, true, this, null);  
    }  
  
    /** 
     * Other classes use the DataMonitor by implementing this method 
     */  
    public interface DataMonitorListener {  
        /** 
         * The existence status of the node has changed. 
         */  
        void exists(byte data[]);  
  
        /** 
         * The ZooKeeper session is no longer valid. 
         * 
         * @param rc 
         *                the ZooKeeper reason code 
         */  
        void closing(int rc);  
    }  
  
    public void process(WatchedEvent event) {  
        String path = event.getPath();  
        if (event.getType() == Event.EventType.None) {  
            // We are are being told that the state of the  
            // connection has changed  
            switch (event.getState()) {  
            case SyncConnected:  
                // In this particular example we don‘t need to do anything  
                // here - watches are automatically re-registered with   
                // server and any watches triggered while the client was   
                // disconnected will be delivered (in order of course)  
                break;  
            case Expired:  
                // It‘s all over  
                dead = true;  
                listener.closing(KeeperException.Code.SessionExpired);  
                break;  
            }  
        } else {  
            if (path != null && path.equals(znode)) {  
                // Something has changed on the node, let‘s find out  
                //读取订单信息  
                //znode = /product/order/  
                ...  
                  
                //计算库存是否充足  
                ...  
                  
                //生成订单数据  
                ...  
                  
                //减少产品库存  
                ...  
                  
                zk.exists(znode, true, this, null);  
            }  
        }  
        if (chainedWatcher != null) {  
            chainedWatcher.process(event);  
        }  
    }  
  
    public void processResult(int rc, String path, Object ctx, Stat stat) {  
        boolean exists;  
        switch (rc) {  
        case Code.Ok:  
            exists = true;  
            break;  
        case Code.NoNode:  
            exists = false;  
            break;  
        case Code.SessionExpired:  
        case Code.NoAuth:  
            dead = true;  
            listener.closing(rc);  
            return;  
        default:  
            // Retry errors  
            zk.exists(znode, true, this, null);  
            return;  
        }  
  
        byte b[] = null;  
        if (exists) {  
            try {  
                b = zk.getData(znode, false, null);  
            } catch (KeeperException e) {  
                // We don‘t need to worry about recovering now. The watch  
                // callbacks will kick off any exception handling  
                e.printStackTrace();  
            } catch (InterruptedException e) {  
                return;  
            }  
        }  
        if ((b == null && b != prevData)  
                || (b != null && !Arrays.equals(prevData, b))) {  
            listener.exists(b);  
            prevData = b;  
        }  
    }  
}

php zookeeper处理类

class OrderWorker extends Zookeeper {  
   
  const CONTAINER = ‘/product/order‘;  
   
  protected $acl = array(  
                    array(  
                      ‘perms‘ => Zookeeper::PERM_ALL,  
                      ‘scheme‘ => ‘world‘,  
                      ‘id‘ => ‘anyone‘ ) );</p><p>  private $znode;  
   
  public function __construct( $host = ‘‘, $watcher_cb = null, $recv_timeout = 10000 ) {  
    parent::__construct( $host, $watcher_cb, $recv_timeout );  
  }  
   
  //添加订单  
  public function Add($order) {  
    if( ! $this->exists( self::CONTAINER ) ) {  
      $this->create( self::CONTAINER, null, $this->acl );  
    }  
   
    $this->znode = $this->create( self::CONTAINER . ‘/w-‘,  
                                  null,  
                                  $this->acl,  
                                  Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE );  
                                    
    $this->set($this->znode, json_encode(array(‘status‘=>0, ‘data‘=>$order, ‘orderid‘=>‘‘)));  
   
    $this->znode = str_replace( self::CONTAINER .‘/‘, ‘‘, $this->znode );  
   
    return $this->znode;   
  }  
    
  //获取订单处理信息  
  public function Get($znode){  
   $data = $this->get(self::CONTAINER .‘/‘.$znode);  
   $json = json_decode($data);  
   return $json;  
  }  
    
  //删除订单znode  
  public function Del($znode){  
   $this->delete(self::CONTAINER .‘/‘.$znode);  
  }  
   
}
class OrderWorker extends Zookeeper {
 
  const CONTAINER = ‘/product/order‘;
 
  protected $acl = array(
                    array(
                      ‘perms‘ => Zookeeper::PERM_ALL,
                      ‘scheme‘ => ‘world‘,
                      ‘id‘ => ‘anyone‘ ) );</p><p>  private $znode;
 
  public function __construct( $host = ‘‘, $watcher_cb = null, $recv_timeout = 10000 ) {
    parent::__construct( $host, $watcher_cb, $recv_timeout );
  }
 
  //添加订单
  public function Add($order) {
    if( ! $this->exists( self::CONTAINER ) ) {
      $this->create( self::CONTAINER, null, $this->acl );
    }
 
    $this->znode = $this->create( self::CONTAINER . ‘/w-‘,
                                  null,
                                  $this->acl,
                                  Zookeeper::EPHEMERAL | Zookeeper::SEQUENCE );
                                  
    $this->set($this->znode, json_encode(array(‘status‘=>0, ‘data‘=>$order, ‘orderid‘=>‘‘)));
 
    $this->znode = str_replace( self::CONTAINER .‘/‘, ‘‘, $this->znode );
 
    return $this->znode; 
  }
  
  //获取订单处理信息
  public function Get($znode){
   $data = $this->get(self::CONTAINER .‘/‘.$znode);
   $json = json_decode($data);
   return $json;
  }
  
  //删除订单znode
  public function Del($znode){
   $this->delete(self::CONTAINER .‘/‘.$znode);
  }
 
}

PHP前台下订单:

$worker = new OrderWorker( ‘127.0.0.1:2181‘ );  
$znode = $worker->Add($order);   
echo json_encode(array(‘code‘=>0, ‘znode‘=>$znode));
echo json_encode(array(‘code‘=>0, ‘znode‘=>$znode));

PHP前台检测订单:

$worker = new OrderWorker( ‘127.0.0.1:2181‘ );  
$data = $worker->Get($znode);  
if (intval($data->status) == 1)  
{  
    $worker->Del($znode);  
    echo json_encode(array(‘status‘=>1, ‘msg‘=>‘订单处理成功‘, ‘orderid‘=>$data->orderid));  
}  
else if (intval($data->status) == 2)  
{  
    $worker->Del($znode);  
    echo json_encode(array(‘status‘=>2, ‘msg‘=>‘订单处理失败‘));  
}  
else  
{  
    echo json_encode(array(‘status‘=>0, ‘msg‘=>‘正在处理‘));  
}
$worker = new OrderWorker( ‘127.0.0.1:2181‘ );
$data = $worker->Get($znode);
if (intval($data->status) == 1)
{
	$worker->Del($znode);
	echo json_encode(array(‘status‘=>1, ‘msg‘=>‘订单处理成功‘, ‘orderid‘=>$data->orderid));
}
else if (intval($data->status) == 2)
{
	$worker->Del($znode);
	echo json_encode(array(‘status‘=>2, ‘msg‘=>‘订单处理失败‘));
}
else
{
	echo json_encode(array(‘status‘=>0, ‘msg‘=>‘正在处理‘));
}

六、全文索引

创建索引

import java.io.File;    
import java.io.FileReader;    
import java.io.IOException;    
    
import org.apache.lucene.analysis.standard.StandardAnalyzer;    
import org.apache.lucene.document.Document;    
import org.apache.lucene.document.Field;    
import org.apache.lucene.document.LongField;    
import org.apache.lucene.document.StringField;    
import org.apache.lucene.document.TextField;    
import org.apache.lucene.index.IndexWriter;    
import org.apache.lucene.index.IndexWriterConfig;    
import org.apache.lucene.store.Directory;    
import org.apache.lucene.store.FSDirectory;    
import org.apache.lucene.util.Version;    
    
  
public class IndexOrders {    
        
    private IndexWriter writer = null;    
      
    public void Add(String indexPath, OrderModel order)    
            throws IOException {    
  
        // 获取放置索引文件的位置,若传入参数为空,则读取search.properties中设置的默认值。    
        if (indexPath == null) {    
            indexPath = LoadProperties.getProperties("indexDir");    
        }    
        final File indexDir = new File(indexPath);    
        if (!indexDir.exists() || !indexDir.canRead()) {    
            System.out    
                    .println("Document directory ‘"    
                            + indexDir.getAbsolutePath()    
                            + "‘ does not exist or is not readable, please check the path");    
            System.exit(1);    
        }    
    
            
        try {    
            // 创建索引库IndexWriter    
            if(writer == null){    
                initialIndexWriter(indexDir);    
            }    
            index(writer, order);    
        } catch (IOException e) {    
            e.printStackTrace();    
        }  
    }    
      
    public void Close()  
    {  
            if (null != writer)  
            {  
             writer.close();   
            }  
    }  
      
    
    private void initialIndexWriter(File indexDir) throws IOException {    
        Directory returnIndexDir = FSDirectory.open(indexDir);    
        IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_48,new StandardAnalyzer(Version.LUCENE_48));    
        writer = new IndexWriter(returnIndexDir, iwc);    
    
    }    
    
    private void index(IndexWriter writer, OrderModel order) throws IOException {    
    
            // 创建文档Document  
            Document doc = new Document();    
            Field orderidField = new StringField("orderid", order.orderid, Field.Store.YES);    
            doc.add(orderidField);    
              
            ...  
    
            //向索引库中写入文档内容    
            writer.addDocument(doc);    
        }    
    }    
}

七、组合条件查询

由java类实现查询,因为PHP可以直接调用java程序,所以PHP只需要把java类返回的结果显示出来就可以。

int pageIndex=1;  
int pageSize=1;  
int start = (pageIndex - 1) * pageSize;   
  
String query_fields[]=new String[]{"filename","content"};//对哪几个字段进行查询检索  
File file_index_dir = new File(indexDir);  
try {  
    Directory directory = new SimpleFSDirectory(file_index_dir);  
    IndexReader indexReader = DirectoryReader.open(directory);  
  
    // 创建搜索类  
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);  
      
    TermQuery query1 = new TermQuery(new Term("orderid", orderid));  
    TermQuery query2 = new TermQuery(new Term("contact", contact));  
    BooleanQuery query = new BooleanQuery();  
    query.add(query1, BooleanClause.Occur.MUST);    
    query.add(query2, BooleanClause.Occur.MUST);  
  
    int max_result_size = start + pageSize;    
      
    TopScoreDocCollector topDocs = TopScoreDocCollector.create(max_result_size, false);    
    indexSearcher.search(query, topDocs);    
  
    int rowCount = topDocs.getTotalHits();  //满足条件的总记录数  
    int pages = (rowCount - 1) / pageSize + 1; //计算总页数    
    TopDocs tds = topDocs.topDocs(start, pageSize);    
    ScoreDoc[] scoreDoc = tds.scoreDocs;    
          
    for (int i = 0; i < scoreDoc.length; i++) {  
        // 内部编号  
        int doc_id = scoreDoc[i].doc;  
          
        // 根据文档id找到文档  
        Document mydoc = indexSearcher.doc(doc_id);  
          
        //读取搜索结果  
        mydoc.get("orderid");  
        mydoc.get("contact");  
        mydoc.get("addr");  
        ...  
    }  
} catch (Exception e) {  
    e.printStackTrace();  
}

有问题可以加我QQ沟通:10980327

使用NoSQL实现高并发CRM系统实践(源代码+解析)

时间: 2024-10-29 19:10:22

使用NoSQL实现高并发CRM系统实践(源代码+解析)的相关文章

高并发IM系统架构优化实践

互联网+时代,消息量级的大幅上升,消息形式的多元化,给即时通讯云服务平台带来了非常大的挑战.高并发的IM系统背后究竟有着什么样的架构和特性 本文要点: 网易云信整体架构解析 云信中的客户端连接和接入点管理 服务化和高可用 网易IM云分层架构图解析 底层客户端SDK,覆盖了安卓,iOS,windows PC桌面端,web网页端和嵌入式设备等多个平台.在SDK层使用的网络协议有4层的TCP协议和基于7层的Socket.IO协议,后者专门用于Web SDK中提供长连接能力:除了集成到应用App中的SD

高并发秒杀系统--课程总结与思考

[高并发秒杀系统的开发流程及技术要点] DAO层 1.数据库设计和实现,手写DDL 2.Mybatis理解和使用技巧,主配置,XML中SQL的编写 3.Mybatis与Spring的整合,包扫描,DAO实现,别名识别 Servcie层 4.业务接口的设计和封装,使用者角度设计接口 5.SpringIOC配置技巧,注解+XML 6.Spring声明式是事务使用和理解 Web层 7.Restful接口运用 8.SpringMVC的使用技巧 9.前端交互分析过程 10.Bootstrap和JS的使用,

Java高并发秒杀系统API之SSM框架集成swagger与AdminLTE

初衷与整理描述 Java高并发秒杀系统API是来源于网上教程的一个Java项目,也是我接触Java的第一个项目.本来是一枚c#码农,公司计划部分业务转java,于是我利用业务时间自学Java才有了本文,本来接触之初听别人说,c#要转java很容易,我也信了,但是真正去学习的时候还是踩了无数个坑,好在朋友有几个做安卓的,向他们讨教了一些经验,但是他们做安卓的和web又是两个方向,于是继续一个人默默采坑避雷之旅,首先上手的是下面这个Java高并发秒杀系统API. 学习java的初衷一个是公司转行,二

全流程开发 GO实战电商网站高并发秒杀系统

获取资源点击这里:全流程开发 GO实战电商网站高并发秒杀系统 第1章 课程介绍[学前须知] 本章对这门课程进行说明,包括:秒杀系统涉及模块的介绍,秒杀核心的知识点的介绍,课程的学习规划等. 1-1 课程介绍试看 第2章 需求整理&系统设计 [明确需求] 本章对秒杀系统整体需求进行梳理,明确系统具体需求,讲解系统原型设计工具的使用,并结合秒杀系统进行整体架构设计. 2-1 需求分析 2-2 系统架构设计 2-3 [总结&扩展]需求整理&系统设计 2-4 [勤于思考,夯实学习成果]阶段

互联网高并发架构技术实践

一.什么是高并发 高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求. 高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Throughput),每秒查询率QPS(Query Per Second),并发用户数等. 响应时间:系统对请求做出响应的时间.例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间. 吞吐量:单位时间内处理的请求数量. QPS:每秒响应

php高并发秒杀系统的搭建总结思考(一)

秒杀系统大致分为三大块.客户端,服务器,后台管理.秒杀系统具有大流量高并发的特点. 对于web前端的处理,一般是页面静态化+CDN分布式缓存. 因为静态页面的处理速度是最快的.假设单台服务器nginx,1秒内可以处理的静态页面请求是1w,处理php程序可能是500每秒.这样在效率上就差很多.原因是php属于动态语言,服务器需要解释运行,这当中可能大量的I/O操作,加载扩展等.这就导致处理的时间比较长. 所以对于秒杀产品,一般都是在活动快要开始时,上线静态页面. 原文地址:https://blog

聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类

这篇说说java.util.concurrent.atomic包里的类,总共12个.网上有非常多文章解析这几个类.这里挑些重点说说. 这12个类能够分为三组: 1. 普通类型的原子变量 2. 数组类型的原子变量 3. 域更新器 普通类型的原子变量的6个, 1. 当中AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference分别相应boolean, int,  long, object完毕主要的原子操作 2. AtomicMarkableRe

聊聊高并发(二十七)解析java.util.concurrent各个组件(九) 理解ReentrantLock可重入锁

这篇讲讲ReentrantLock可重入锁,JUC里提供的可重入锁是基于AQS实现的阻塞式可重入锁.这篇 聊聊高并发(十六)实现一个简单的可重入锁 模拟了可重入锁的实现.可重入锁的特点是: 1. 是互斥锁,基于AQS的互斥模式实现,也就是说同时只有一个线程进入临界区,唤醒下一个线程时也只能释放一个等待线程 2. 可重入,通过设置了一个字段exclusiveOwnerThread来标示当前获得锁的线程.获取锁操作是,如果当前线程是已经获得锁的线程,那么获取操作成功.把当前状态作为获得锁次数的计数器

聊聊高并发(四十一)解析java.util.concurrent各个组件(十七) 任务的异步执行和状态控制

聊聊高并发(三十九)解析java.util.concurrent各个组件(十五) 理解ExecutorService接口的设计这篇说了ExecutorService接口扩展了Executor接口,在执行任务的基础上,提供了执行框架生命周期的管理,任务的异步执行,批量任务的执行的能力.AbstractExecutorService抽象类实现了ExecutorService接口,提供了任务异步执行和批量执行的默认实现.这篇说说任务的异步执行和状态控制 说明一点,使用Executor框架执行任务的方式