Lucene的Directory的详细使用与性能测试(6)

文章目录

  • 第6章 Directory
    • 6.1 Directory介绍
      • 6.1.1 FSDirectory
        • 1)SimpleFSDirectory:
        • 2)NIOFSDirectory:
        • 3)MMapDirectory:
        • 4)FSDirectory子类对比
      • 6.2.2 RAMDirectory
    • 6.2 Directory性能测试环境搭建
      • 6.2.1 数据库环境准备
      • 6.2.2 项目环境准备
      • 6.2.3 测试环境准备
      • 6.2.4 JVM参数控制
        • 1)JVM内存划分
        • 2)JVM参数说明
        • 3)指定内存参数
    • 6.3 Directory性能测试
      • 6.3.1 数据准备
      • 6.3.2 400W数据查询测试
        • 1)Windows平台
        • 2)Linux平台
      • 6.3.3 并发查询测试
        • 1)Windows平台
        • 2)Linux平台

第6章 Directory

6.1 Directory介绍

Lucene使用Directory来关联一个目录用于存储索引文件,Directory为存储文件列表提供了一个抽象层;其子类非常丰富,不同的子类底层采用的IO模型不同,达到的性能也不一样。Directory将决定Luceen底层采用什么方式将数据写入到磁盘,是影响Lucene性能的一大关键;

Directory下面子类实现共有三大类:

  • FSDirectory:用于在文件系统中存储索引文件的Directory实现的基类。
  • RAMDirectory:基于内存的目录实现,但不适用于大型索引
  • FilterDirectory:用于在其他类型的Directory上添加限制,或为测试添加额外的健全性检查;

Tips:FilterDirectory仅用于内部目的,在下一版本中可能会以不兼容的方式进行更改

不同的Directory底层对于如何存实现索引具有不同的功能,性能上也有很大的差异

6.1.1 FSDirectory

用于在文件系统中存储索引文件的Directory实现的基类。目前有三个核心子类:

1)SimpleFSDirectory:

SimpleFSDirectory底层采用RandomAccessile来完成索引操作,SimpleFSDirectory并发性能很差(多个线程将成为瓶颈),因为当多个线程从同一个文件读取时,它是同步的,通常使用NIOFSDirectory或MMapDirectory会更好。

Lucene中的SimpleFSDirectory底层是通过使用Java的文件系统API来实现的。具体实现方式如下:

  1. SimpleFSDirectory类继承自FSDirectory类,FSDirectory类封装了文件系统的基本操作,包括打开、关闭、读取文件等。
  2. SimpleFSDirectory类使用了Java文件系统API中的FileInputStream和FileOutputStream来实现文件的读取和写入。
  3. 在SimpleFSDirectory的构造函数中,会打开一个文件,并创建一个FileInputStream对象。
  4. 在SimpleFSDirectory的写入操作中,会将数据写入到FileOutputStream中的缓冲区,并使用FileOutputStream的flush()方法将缓冲区中的数据刷新到磁盘上。
  5. 在SimpleFSDirectory的读取操作中,会使用FileInputStream从文件中读取数据,并返回读取的数据。
  6. 在SimpleFSDirectory的关闭操作中,会关闭FileInputStream和FileOutputStream对象,释放资源。

总之,SimpleFSDirectory底层的实现是使用Java的文件系统API来实现文件的读取和写入操作。这种方式比较简单,但可能不如NIOFSDirectory的方式效率高。

2)NIOFSDirectory:

NIOFSDirectory底层采用Java中的NIO来解决索引文件并发能力的。它允许多个线程在不同步的情况下从同一个文件读取,但不幸的是,在Windows平台下由于Sun的JRE错误,NIOFSDirectory的性能并非很出色(FileChannel在windows下是同步操作),NIOFSDirectory这对于Windows来说是一个糟糕的选择,但在所有其他平台上这是首选。

Lucene中的NIOFSDirectory类底层是通过使用Java的NIO文件系统API来实现的。具体实现方式如下:

  1. NIOFSDirectory类继承自FSDirectory类,FSDirectory类封装了文件系统的基本操作,包括打开、关闭、读取文件等。
  2. NIOFSDirectory类使用了Java NIO文件系统API中的FileChannel类和MMapByteBuffer类来实现文件的读取和写入。
  3. 在NIOFSDirectory的构造函数中,会打开一个文件,并创建一个FileChannel对象。
  4. 在NIOFSDirectory的写入操作中,会将数据写入到FileChannel中的缓冲区,并使用FileChannel的force()方法将缓冲区中的数据刷新到磁盘上。
  5. 在NIOFSDirectory的读取操作中,会使用FileChannel的read()方法从文件中读取数据,并读取到MMapByteBuffer中的缓冲区中。
  6. 在NIOFSDirectory的关闭操作中,会关闭FileChannel和MMapByteBuffer对象,释放资源。

此外如果一个访问该类的线程,在IO阻塞时被interrupt或cancel,将会导致底层的文件描述符被关闭,后续的线程再次访问NIOFSDirectory时将会出现ClosedChannelException异常,此种情况应用SimpleFSDirectory代替。

Tips:NIOFSDirectory在Windows操作系统的性能比较差,甚至可能比SimpleFSDirecory的性能还差。

3)MMapDirectory:

MMapDirectory在读取时使用内存映射IO,不需要采取锁机制,并能很好的支持多线程读操作。如果您有足够的虚拟内存,这是一个不错的选择

具体来说,MMapDirectory底层会使用操作系统提供的系统调用来创建一个内存映射文件,将文件映射到进程的虚拟地址空间中。这样,进程就可以直接访问这个文件,而不需要通过读写文件的操作来访问。通过使用内存映射文件技术,可以提供高效的文件访问方式,同时避免了频繁的磁盘I/O操作,提高了程序的性能和效率。

MMapDirectory的具体实现方式如下:

  1. 通过操作系统提供的内存映射文件(Memory Mapping)功能,将索引文件映射到进程的虚拟地址空间中。
  2. Lucene对索引文件的访问,不再是通过读写文件的方式,而是直接访问内存中的数据。
  3. 当需要对索引进行修改时,MMapDirectory会使用操作系统提供的内存映射文件(Memory Mapping)功能,将新的数据写回到磁盘上的索引文件中。

Tips:由于Sun的JRE中的这个错误,MMapDirectory的IndexInput.close()无法关闭底层操作系统文件句柄。只有当GC最终收集底层对象时(这可能需要相当长的一段时间),文件句柄才会关闭。这将消耗额外的临时磁盘使用,并且可能会消耗很长的时间

关于内存映射:内存映射是一种常见的操作系统技术,它允许程序将磁盘文件映射到进程的地址空间中,从而使得程序可以像访问内存一样访问文件。这种技术可以极大地简化文件的读写操作,并且可以提高程序的性能。

4)FSDirectory子类对比
类名读操作写操作特点
SimpleFSDirectoryjava.io.RandomAccessFilejava.io.RandomAccessFile简单实现,并发能力差
NIOFSDirectoryjava.nio.FileChannelFSDirectory.FSIndexOutput并发能力强
MMapDirectory内存映射FSDirectory.FSIndexOutput读取操作基于内存

对于FSDirectory的最佳子类的使用,直接采用FSDirectory的open方法Lucene将根据当前系统环境自动选择最佳的FSDirectory实现,如没有定制化要求,最好使用open方法来获取FSDirectory最佳实现;

  • FSDirectory的获取:
@Test
public void test1() throws Exception {FSDirectory dir1 = FSDirectory.open(Paths.get("D:/index"));FSDirectory dir2 = MMapDirectory.open(Paths.get("D:/index"));FSDirectory dir3 = SimpleFSDirectory.open(Paths.get("D:/index"));FSDirectory dir4 = NIOFSDirectory.open(Paths.get("D:/index"));// 在windows平台中,不管是哪种方式获取FSDirectory都将返回MMapDirectorySystem.out.println(dir1.getClass());          // class org.apache.lucene.store.MMapDirectorySystem.out.println(dir2.getClass());          // class org.apache.lucene.store.MMapDirectorySystem.out.println(dir3.getClass());          // class org.apache.lucene.store.MMapDirectorySystem.out.println(dir4.getClass());          // class org.apache.lucene.store.MMapDirectory
}

如果想要指定不同的FSDirectory,可以使用new关键字来创建对应的实例:

@Test
public void test2() throws Exception {FSDirectory dir1 = new MMapDirectory(Paths.get("D:/index"));FSDirectory dir2 = new SimpleFSDirectory(Paths.get("D:/index"));FSDirectory dir3 = new NIOFSDirectory(Paths.get("D:/index"));System.out.println(dir1.getClass());          // class org.apache.lucene.store.MMapDirectorySystem.out.println(dir2.getClass());          // class org.apache.lucene.store.SimpleFSDirectorySystem.out.println(dir3.getClass());          // class org.apache.lucene.store.NIOFSDirectory
}

6.2.2 RAMDirectory

RAMDirectory将数据存储在内存中,该类针对小型内存驻留索引进行了优化,但不适合大量索引的情况。因为它使用1024字节的内部缓冲区大小,在数据量非常大的情况下,内部将会频繁创建字节数组,性能较低,另外也不适用于多线程的情况。在索引数据量大的情况下建议使用MMapDirectory代替。

Tips:小型的系统推荐使用,如果大型的,索引文件达到G级别上,推荐使用FSDirectory。

RAMDirectory的底层实现是基于内存的。它通过将所有的索引文件加载到内存中,从而加快索引和查询的速度。具体实现方式如下:

  1. RAMDirectory使用一个HashMap来存储所有的文件和相应的读写器。
  2. 当需要添加一个文件时,RAMDirectory会创建一个RandomAccessFile对象,并将文件数据读取到内存中。
  3. 当需要查询一个文件时,RAMDirectory会根据文件的名称或ID在HashMap中查找对应的读写器,并返回该读写器对应的文件数据。
  4. 当需要删除一个文件时,RAMDirectory会关闭相应的读写器并从HashMap中删除该文件的数据。
  5. 当需要更新一个文件时,RAMDirectory会更新相应的读写器并重新写入文件数据到内存中。

RAMDirectory的底层实现是通过内存缓存来加速索引和查询的操作,适用于临时索引或小型的索引。在实际应用中,可以根据需要选择不同的目录实现,以满足不同的需求。

6.2 Directory性能测试环境搭建

6.2.1 数据库环境准备

1)准备测试表:

DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods`  (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品名称',`title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题',`price` decimal(10, 2) NULL DEFAULT NULL COMMENT '价格',`pic` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图片',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

2)创建存储过程:

创建存储过程用于模拟查询数据

create procedure test_insert(count int)
begindeclare i int default 1;while i<=count do INSERT INTO goods values(i,								-- id'huawei shouji',							-- name'huawei 5g zhineng youxi shouji',			-- titleleft(RAND()*10000,7),		-- priceuuid()							-- pic);set i=i+1;end while;
end;

6.2.2 项目环境准备

  • 1)引入依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.1.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.1</version></dependency><!--Lucene核心包--><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-analyzers-common</artifactId><version>8.0.0</version></dependency><!--lucene高亮查询依赖--><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-highlighter</artifactId><version>8.0.0</version></dependency><!--IK分词器--><dependency><groupId>com.github.magese</groupId><artifactId>ik-analyzer</artifactId><version>8.0.0</version></dependency><!--MySQL驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.18</version></dependency></dependencies><build><plugins><!--SpringBoot打包插件--><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>1.4.2.RELEASE</version></plugin></plugins></build>
  • 2)实体类:
package com.dfbz.entity;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;/*** @author lscl* @version 1.0* @intro:*/
@Entity
@Table(name = "goods")@AllArgsConstructor
@NoArgsConstructor
@Data
public class Goods implements Serializable {@Idprivate Integer id;@Columnprivate String name;@Columnprivate String title;@Columnprivate Double price;@Columnprivate String pic;
}

3)dao

package com.dfbz.dao;import com.dfbz.entity.Goods;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;import java.util.List;/*** @author lscl* @version 1.0* @intro:*/
@Repository
public interface GoodsDao extends JpaRepository<Goods,Integer> {
}
  • yml配置文件:
spring:datasource:driver-class-name: com.mysql.jdbc.Driver# Linux地址url: jdbc:mysql://192.168.28.161:3306/test?useUnicode=true&characterEncoding=utf8# Windows地址# url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8username: rootpassword: admin
server:port: 8080# Windows Lucene目录
luceneDir: C:/index# Linux Lucene目录
#luceneDir: /root/index

6.2.3 测试环境准备

1)插入索引

package com.dfbz.controller;import com.dfbz.dao.GoodsDao;
import com.dfbz.entity.Goods;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.wltea.analyzer.lucene.IKAnalyzer;import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;/*** @author lscl* @version 1.0* @intro:*/
@RequestMapping("/index")
@RestController
public class TestIndexOperationController {@Value("${luceneDir}")private String luceneDir;@Autowiredprivate GoodsDao goodsDao;/*** 插入模拟数据** @param type* @param page* @param size* @return* @throws Exception*/@GetMapping("/create/{type}/{page}/{size}")public String insert(@PathVariable String type, @PathVariable Integer page, @PathVariable Integer size) throws Exception {Directory dir = null;if ("simple".equals(type)) {dir = new SimpleFSDirectory(Paths.get(luceneDir));} else if ("nio".equals(type)) {dir = new NIOFSDirectory(Paths.get(luceneDir));} else if ("mmap".equals(type)) {dir = new MMapDirectory(Paths.get(luceneDir));} else if ("ram".equals(type)) {dir = new RAMDirectory();}Analyzer analyzer = new IKAnalyzer();IndexWriterConfig config = new IndexWriterConfig(analyzer);config.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);IndexWriter indexWriter = new IndexWriter(dir, config);System.out.println("开始插入【" + type + "】:");long startTime = System.currentTimeMillis();for (int i = 0; i < page; i++) {// 每次查询固定的条数Page<Goods> pageData = goodsDao.findAll(PageRequest.of(i, size));List<Goods> goodsList = pageData.getContent();List<Document> docs = new ArrayList<>();for (Goods goods : goodsList) {// 创建一篇文档Document doc = new Document();// 添加域doc.add(new StringField("id", goods.getId() + "", Field.Store.YES));doc.add(new TextField("name", goods.getName(), Field.Store.YES));doc.add(new TextField("title", goods.getTitle(), Field.Store.YES));doc.add(new DoublePoint("price", goods.getPrice()));doc.add(new StoredField("pic", goods.getPic()));docs.add(doc);}indexWriter.addDocuments(docs);System.out.println("第【" + (i + 1) + "】次插入,本次插入【" + size + "】条");}indexWriter.close();dir.close();long endTime = System.currentTimeMillis();String result = "【" + type + "】插入完毕,共插入【" + (page * size) + "】条记录,花费【" + (endTime - startTime) + "】毫秒";System.out.println(result);return result;}/*** 删除数据** @param type* @return* @throws Exception*/@GetMapping("/delete/{type}")public String insert(@PathVariable String type) throws Exception {Directory dir = null;if ("simple".equals(type)) {dir = new SimpleFSDirectory(Paths.get(luceneDir));} else if ("nio".equals(type)) {dir = new NIOFSDirectory(Paths.get(luceneDir));} else if ("mmap".equals(type)) {dir = new MMapDirectory(Paths.get(luceneDir));} else if ("ram".equals(type)) {dir = new RAMDirectory();}Analyzer analyzer = new IKAnalyzer();IndexWriterConfig config = new IndexWriterConfig(analyzer);config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);IndexWriter indexWriter = new IndexWriter(dir, config);indexWriter.deleteAll();indexWriter.close();dir.close();return "ok";}
}

2)查询索引

package com.dfbz.controller;import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.io.IOException;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;/*** @author lscl* @version 1.0* @intro:*/
@RestController
@RequestMapping("/query")
public class TestQueryController {private Directory dir;private IndexReader reader;private IndexSearcher searcher;@Value("${luceneDir}")private String luceneDir;/*** 普通查询测试400W数据** @param type* @return* @throws Exception*/@GetMapping("/test_query/{type}/{dataCount}")public List<String> test_query(@PathVariable String type, @PathVariable Integer dataCount) throws Exception {System.out.println(type + "测试结果如下: ");if ("simple".equals(type)) {dir = new SimpleFSDirectory(Paths.get(luceneDir));} else if ("nio".equals(type)) {dir = new NIOFSDirectory(Paths.get(luceneDir));} else if ("mmap".equals(type)) {dir = new MMapDirectory(Paths.get(luceneDir));} else if ("ram".equals(type)) {dir = new RAMDirectory(FSDirectory.open(Paths.get(luceneDir)), IOContext.DEFAULT);}reader = DirectoryReader.open(dir);searcher = new IndexSearcher(reader);StandardAnalyzer analyzer = new StandardAnalyzer();QueryParser queryParser = new QueryParser("title", analyzer);Query query = queryParser.parse("huawei");LinkedList<String> resultInfo = new LinkedList<>();for (int i = 1; i <= 10; i++) {long startTime = System.currentTimeMillis();TopDocs topDocs = searcher.search(query, dataCount);long endTime = System.currentTimeMillis();String result = "第【" + i + "】次的查询时间为【" + (endTime - startTime) + "】";resultInfo.addLast(result);System.out.println(result);}return resultInfo;}/*** 测试并发查询    查询数据10W, 200个线程并发查询** @param type* @return* @throws Exception*/@GetMapping("/test_concurrent/{type}/{dataCount}/{threadCount}")public List<String> test_concurrent(@PathVariable String type,@PathVariable Integer dataCount,@PathVariable Integer threadCount) throws Exception {System.out.println(type + "测试结果如下: ");if ("simple".equals(type)) {dir = new SimpleFSDirectory(Paths.get(luceneDir));} else if ("nio".equals(type)) {dir = new NIOFSDirectory(Paths.get(luceneDir));} else if ("mmap".equals(type)) {dir = new MMapDirectory(Paths.get(luceneDir));} else if ("ram".equals(type)) {dir = new RAMDirectory(FSDirectory.open(Paths.get(luceneDir)), IOContext.DEFAULT);}reader = DirectoryReader.open(dir);searcher = new IndexSearcher(reader);StandardAnalyzer analyzer = new StandardAnalyzer();QueryParser queryParser = new QueryParser("title", analyzer);Query query = queryParser.parse("huawei");LinkedList<String> resultInfo = new LinkedList<>();for (int i = 1; i <= 10; i++) {List<Thread> threadList = new LinkedList<>();CountDownLatch count = new CountDownLatch(threadCount);for (int j = 0; j < threadCount; j++) {threadList.add(new Thread(() -> {try {TopDocs topDocs = searcher.search(query, dataCount);ScoreDoc[] scoreDocs = topDocs.scoreDocs;} catch (IOException exception) {exception.printStackTrace();}count.countDown();}));}long startTime = System.currentTimeMillis();for (Thread thread : threadList) {thread.start();}count.await();long endTime = System.currentTimeMillis();String result = "第【" + i + "】次的查询时间为【" + (endTime - startTime) + "】";resultInfo.addLast(result);System.out.println(result);}return resultInfo;}
}

6.2.4 JVM参数控制

1)JVM内存划分

每个 Java 程序都只能使用一定量的内存,这种限制是由于 JVM 的启动参数决定的。影响JVM内存的主要是堆内存;

堆内存包括:年轻代、老年代、永久代内存,这三块内存对JVM的性能有着重要的影响;在Jdk1.8将永久代从堆内存中独立出来了并且永久代被元空间(Metaspace)所替代,并且元空间位于本地内存中,其大小受限于可用的系统内存大小。

  • Jdk1.8之前:总的堆内存=新生代+老年代+永久代
  • Jdk1.8及之后:总的堆内存=新生代+老年代;

Tips:在JDK7及以前,堆内存包含了方法区,那个时候方法区的实现为永久代,到了JDK8之后,方法区从堆内存中独立了出来,并且实现方式改为了元空间;

  • heap space:堆内存;
    • 新生代:用于存储新创建的对象。新生代分为三个区域:Eden空间、From Survivor空间(S0)、To Survivor空间(S1)。大部分对象都会在新生代中被创建,并通过垃圾回收进行管理。新生代的特点是快速分配和回收。
    • 老年代:用于存储经过多次垃圾回收后仍然存活的对象。老年代的特点是稳定性高,对象存活时间长,垃圾回收频率相对较低。
  • 永久代:是方法区的实现,JDK8之后被称为元空间,用于存储类信息、常量池、方法静态变量以及时编译器编译后的代码等等。存储的内容比较稳定,但也可能因为内存溢出等问题导致性能下降。

这两个区域的最大内存大小是由 JVM 启动参数 -Xmx-XX:MaxPermSize(永久代)/-XX:MaxMetaspaceSize(元数据区) 指定,如果没有指定,则会根据操作系统版本、JVM 版本和物理内存的大小来确定。

当创建新的对象时,堆内存中的空间不足以存放新创建的对象,就会引入 java.lang.OutOfMemoryError Java heap space 错误,并且只要超过了堆内存的最大限度,就会报错,和物理内存没有直接关系。

2)JVM参数说明
参数说明
-Xms指定 JVM 的初始内存大小(堆内存)。建议和 -Xmx 大小一样,防止因为内存收缩或突然增大带来的性能影响。
-Xmx指定JVM 的最大内存大小(堆内存)。建议为物理内存的80%。
-Xmn指定 JVM 中 NewGeneration(年轻代)的大小。这个参数很影响性能。 如果你的程序需要比较多的临时内存,建议设置到512M。 如果用的少,尽量降低这个数值,一般来说128或256足以使用了。 sun官方推荐配置为整个堆内存的3/8;
-XX:OldSize用来指定老年代的初始大小。 开发者不能直接调整Jvm老年代的最大内存,因为老年代内存的大小是由JVM自动管理的。 但是开发者可以调整Jvm的堆内存、年轻代内存、年轻代与老年代的比例来影响老年代的最大值;
-XX:NewRatio设置老年代和年轻代的比例。
-XX:PermSize指定 JVM 中 Perm Generation(永久代)的初始值。这个参数需要看你的实际情况。可以通过 jmap 命令看看到底需要多少。
-XX:MaxPermSize指定 Perm Generation(永久代) 的最大值。
-XX:MaxMetaspaceSize在Java 8中,元空间的大小默认是随着可用内存的增加而增加的,因此无需手动设置其大小。 如果需要限制,可以使用该参数来设置最大元空间大小
-Xss指定桟大小。一般来说,web框架下的应用需要256K。 如果程序中有大规模的递归行为,请考虑设置到512K或1M。 这个参数对性能的影响比较大的,在相同物理内存下,减小这个值能生成更多的线程。
3)指定内存参数

在Runtime类中存在有如下几个方法可以获取当前Jvm示例所占用的内存情况:

返回值方法名作用
Runtimepublic static getRuntime()获取当前进程的Runtime实例
longmaxMemory()Jvm能够从操作系统申请的最大内存,如果内存本身没有限制,则返回值Long.MAX_VALUE
longtotalMemory()Jvm已经向操作系统申请的内存大小,也就是虚拟机这个进程已占用的所有内存。
longfreeMemory()获取JVM中的空闲内存量,JVM已经占用,但实际并未使用的内存
  • 示例代码:
package com.dfbz.demo01;import org.junit.Test;/*** @author lscl* @version 1.0* @intro:*/
public class Demo01 {@Testpublic void test1() throws Exception {// 获取Jvm最大能够使用的内存System.out.println("maxMemory: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "M");// Jvm已经向操作系统申请的内存System.out.println("totalMemory: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "M");// Jvm中此时空闲的内存System.out.println("freeMemory: " + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "M");}
}
  • 执行结果:
maxMemory: 3495M
totalMemory: 236M
freeMemory: 217M
  • 设置Jvm参数:
-Xms 3072m -Xmx3072m

执行代码效果:

maxMemory: 2944M
totalMemory: 2944M
freeMemory: 2851M

Tips:Jvm实际申请的内存和参数指定的内存会有点差别

6.3 Directory性能测试

6.3.1 数据准备

【1-调用存储过程想数据库插入400W数据】

begin
call test_insert(4000000);
commit;

【2-使用idea对项目进行打包】

【3-启动jar包】

java -Xms3072m -Xmx3072m -Xmn800m -jar 02_Lucene_Proformance_Test-1.0-SNAPSHOT.jar
  • -Xms:堆内存的初始化大小,避免由于频繁申请堆空间而造成性能损耗从而影响测试效果
  • -Xmx:堆内存的最大大小,设置过小可能会导致堆内存溢出
  • -Xmn:堆内存中年轻代的内存大小

【4-每次插入40W数据,分10批次插入】

http://localhost:8080/index/create/simple/10/400000

共用时:【224537】毫秒

Tips:采用什么Directory插入数据对查询测试不影响

【使用Luke工具查看】

6.3.2 400W数据查询测试

1)Windows平台
  • SimpleFSDirectory:http://localhost:8080/query/test_query/simple/4000000
  • NIOFSDirectory:http://localhost:8080/query/test_query/nio/4000000
  • MMapFSDirectory:http://localhost:8080/query/test_query/mmap/4000000
  • RAMFSDirectory:http://localhost:8080/query/test_query/ram/4000000

由于400W数据对Lucene来说也并不是特别多,这个数据量的测试具备很大的波动性,测试结果的偶然性较高,有兴趣的小伙伴可以自己私下测试,我们直接观察下面已经测试好的结果:

在实际测试过程中,由于第一次测试有机器性能预热等因素会影响测试结果,因此测试时最好测试11次,取后面10次的结果作为测试样本;

  • 下面是测试10次累计时长比较:

可以观察到,在Windows平台各个Directory的查询性能没有太大的差别;

2)Linux平台

在Linux平台安装Java环境:

yum -y install java-1.8.0-openjdk

将打好的jar包上传到Linux平台,使用同样的内存参数将jar包启动起来:

java -Xms3072m -Xmx3072m -Xmn800m -jar 02_Lucene_Proformance_Test-1.0-SNAPSHOT.jar

重新插入400W数据:

http://localhost:8080/index/create/simple/10/400000

执行各个Directory进行测试:

  • SimpleFSDirectory:http://192.168.28.161:8080/query/test_query/simple/4000000
  • NIOFSDirectory:http://192.168.28.161:8080/query/test_query/nio/4000000
  • MMapFSDirectory:http://192.168.28.161:8080/query/test_query/mmap/4000000
  • RAMFSDirectory:http://192.168.28.161:8080/query/test_query/ram/4000000

测试10次详细情况:

10次查询累计时长报告:

可以观察到,在Linux平台各个Directory查询性能有一定的差距。

6.3.3 并发查询测试

1)Windows平台

将MySQL中的数据控制到10W:

truncate goods;begin;
call test_insert(100000);
commit;select count(1) from goods;

将C:/index文件夹删除,重新向索引库中插入10W数据:

http://localhost:8080/index/create/simple/1/100000

执行查询测试:

  • SimpleFSDirectory:http://localhost:8080/query/test_concurrent/simple/100000/200
  • NIOFSDirectory:http://localhost:8080/query/test_concurrent/nio/100000/200
  • MMapFSDirectory:http://localhost:8080/query/test_concurrent/mmap/100000/200
  • RAMFSDirectory:http://localhost:8080/query/test_concurrent/ram/100000/200

测试10次详细报告如下:

10次测试总消耗时长:

可以看到在Windows平台中,NIO的并发插入性能还不如Simple;

2)Linux平台

将/root/index目录删除,重新插入10W数据到索引库:

http://192.168.28.161:8080/index/create/simple/1/100000

执行查询测试:

  • SimpleFSDirectory:http://192.168.28.161:8080/query/test_concurrent/simple/100000/200
  • NIOFSDirectory:http://192.168.28.161:8080/query/test_concurrent/nio/100000/200
  • MMapFSDirectory:http://192.168.28.161:8080/query/test_concurrent/mmap/100000/200
  • RAMFSDirectory:http://192.168.28.161:8080/query/test_concurrent/ram/100000/200

测试10次详细报告如下:

10次测试总消耗时长:

在Linux平台中,NIO的性能要高于Simple

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/4565.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

HTML+javaScript+CSS

文章目录 HTMLjavaScriptCSS属性区块表单层叠样式表选择器常用属性盒子模型相关属性浮动float定位&#xff08;position&#xff09; JS操作节点事件点击事件onclick()聚焦事件、失焦事件鼠标移入移出事件 定时任务延迟定时任务重复定时任务 判断哪个单选框被选中设置按钮失效冒…

Linux系统每日定时备份mysql数据

一、创建存储脚本的文件夹 创建文件夹&#xff0c;我的脚本放在/root/dbback/mysql mkdir ... cd /root/dbback/mysql 二、编写脚本 vi backup_mysql.sh 复制脚本内容 DB_USER"填写用户名" DB_PASSWORD"填写密码" DB_NAME"数据库名称" # …

【计算机网络】零碎知识点(易忘 / 易错)总结回顾

一、计算机网络的发展背景 1、网络的定义 网络是指将多个计算机或设备通过通信线路、传输协议和网络设备连接起来&#xff0c;形成一个相互通信和共享资源的系统。 2、局域网 LAN 相对于广域网 WAN 而言&#xff0c;局域网 LAN 主要是指在相对较小的范围内的计算机互联网络 …

数据同步的技术支持有哪些?

数据同步是指将不同系统、设备或应用程序中的数据进行实时或定期的更新、复制和传输的过程。通过数据同步&#xff0c;可以确保数据的一致性和可用性&#xff0c;避免数据的丢失或错误。常见的数据同步技术包括推式同步、拉式同步、ETL工具同步等。 一、推式数据同步 定义&…

Kaggle入门指南(Kaggle竞赛)

https://www.kaggle.com/ 文章目录 Kaggle 入门指南1. Kaggle 的功能概述1.1 竞赛1.2 数据集1.3 学习与教程1.4 社区 2. 注册与设置2.1 创建账户2.2 完善个人资料 3. 探索数据集3.1 查找数据集3.2 下载数据集示例代码&#xff1a;加载数据集 3.3 数据预处理示例代码&#xff1a…

桌面终端安全管理软件有哪些?5大主流的终端安全防护系统盘点,2024人气爆款推荐!

“守一而制万机&#xff0c;安内方可攘外”。在纷繁复杂的数字化世界中&#xff0c;只有确保内部系统的安全稳定&#xff0c;才能有效地抵御外部威胁。 其中&#xff0c;桌面终端作为信息交换和存储的重要节点&#xff0c;在安全管理方面显得尤为重要。 本文将为您盘点2024年五…

灰度梯度的表示形式、非极大值抑制、Canny算子、otsu

灰度梯度的表示形式主要有两种&#xff1a;梯度的幅度&#xff08;magnitude&#xff09;和梯度的方向&#xff08;direction&#xff09;。 1. **梯度的幅度&#xff08;Gradient Magnitude&#xff09;**&#xff1a; 梯度的幅度表示在某个方向上像素灰度变化的强度。它通…

WLAN高级技术

下面是对每一部分的详细解析&#xff1a; 1. 禁用信息中心并设置设备名称 <Huawei>sys [Huawei]un in e Info: Information center is disabled. [Huawei]sysname sw1 分析&#xff1a; un in e&#xff1a;禁用信息中心&#xff0c;防止后续配置过程中出…

Serverless GPU:助力AI推理加速

近年来&#xff0c;AI技术发展迅猛&#xff0c;企业纷纷寻求将AI能力转化为商业价值&#xff0c;然而&#xff0c;在部署AI模型推理服务时&#xff0c;却遭遇成本高昂、弹性不足及运维复杂等挑战。本文将探讨云原生Serverless GPU如何从根本上解决这些问题&#xff0c;以实现AI…

Python异常检测 - LSTM(长短期记忆网络)

系列文章目录 Python异常检测- Isolation Forest&#xff08;孤立森林&#xff09; python异常检测 - 随机离群选择Stochastic Outlier Selection (SOS) python异常检测-局部异常因子&#xff08;LOF&#xff09;算法 Python异常检测- DBSCAN Python异常检测- 单类支持向量机(…

Python毕业设计选题:基于django+vue的论坛BBS系统

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 公告信息管理 帖子信息管理 签到积分管理 系统…

moffee模型部署教程

一、介绍 moffee 是一个开源幻灯片制作工具&#xff0c;可以将 markdown 文档转换为干净、专业的幻灯片。 moffee 处理布局、分页和样式 &#xff0c;因此您可以专注于您的内容。需要学习的内容很少 。moffee 使用简单的语法来根据您的喜好安排和设计内容。实时网络界面会在您…

MyBatis学习笔记(一)

一、介绍 (一)什么是框架及优势 框架&#xff08;Framework&#xff09;是整个或部分系统的可重用设计&#xff0c;表现为一组抽象构件及构件实例间交互的方法;另一种定义认为&#xff0c;框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。…

【MySQL系列】字符集设置

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

白杨SEO:百度在降低个人备案类网站搜索关键词排名和流量?怎样应对?【参考】

很久没有写百度或者网站这块内容了&#xff0c;一是因为做百度网站朋友越来越少&#xff0c;不管是个人还是企业&#xff1b;二是百度上用户搜索与百度给到网站的流量都越来越少。 为什么想到今天又来写这个呢&#xff1f;因为上个月有个朋友来咨询我说网站百度排名全没了&…

Edge浏览器打开PDF无法显示电子签章

Edge浏览器打开PDF无法显示电子签章 直接说处理方式 直接说处理方式 浏览器地址栏&#xff0c;输入 edge://flags/搜索&#xff1a;pdf禁用&#xff1a;New PDF Viewer效果如下

2024年【汽车修理工(高级)】考试总结及汽车修理工(高级)试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 汽车修理工&#xff08;高级&#xff09;考试总结是安全生产模拟考试一点通总题库中生成的一套汽车修理工&#xff08;高级&#xff09;试题及解析&#xff0c;安全生产模拟考试一点通上汽车修理工&#xff08;高级&a…

Redis内存管理——针对实习面试

目录 Redis内存管理Redis的内存淘汰机制有哪些?说说过期的数据的删除策略&#xff1f;Redis是如何判断数据是否过期的&#xff1f;Redis如何处理大Key问题&#xff1f; Redis内存管理 Redis的内存淘汰机制有哪些? Redis的内存淘汰机制主要包括以下几种策略&#xff1a; noev…

2024年中国工业大模型行业发展研究报告|附43页PDF文件下载

工业大模型伴随着大模型技术的发展&#xff0c;逐渐渗透至工业&#xff0c;处于萌芽阶段。 就大模型的本质而言&#xff0c;是由一系列参数化的数学函数组成的计算系统&#xff0c;且是一个概率模型&#xff0c;其工作机制是基于概率和统计推动进行的&#xff0c;而非真正的理解…

hhdb数据库介绍(2-1)

数据库基础服务 HHDB Server支持MySQL原生通讯协议&#xff0c;支持数据定义、数据操作、分区表、数据库管理语句、事务、锁、字符集与校对集等常用数据库基础服务。其中在数据操作中解决了跨库查询和跨库数据排序等难点问题。并支持强一致事务与跨库死锁检测。 数据定义 支…