Прежде всего, я довольно новичок в Hibernate, и я застрял в проблеме около недели или около того. Я не могу найти решение самостоятельно, поэтому я должен попросить о помощи здесь.Высокое использование ЦП с Hibernate FullTextQuery.list()
Я пытаюсь построить простой на основе местоположения API со следующей стопке:
- Hibernate JPA (4.3.8) + Hibernate Search (5.1);
- MySQL db около 30000 объектов;
- Daoism project;
- Spring Framework для управления транзакциями db;
- Tomcat 7.
API о поиске терминалов (банкоматов) вокруг вас. У меня есть три типа объектов:
- Терминал;
- Сделка (не транзакция db, но транзакция терминала);
- TransactionCurrency.
Каждый терминал имеет несколько транзакций (ассоциация «один ко многим»). Транзакция имеет несколько транзакционных валют (опять-таки, связь один-ко-многим).
Проблема заключается в высоком использовании процессора (60-80%) при поиске терминалов.
Терминал объект:
@Entity
@Table(name="terminals",
indexes = {
@Index(name = "INDEX_terminals_id", columnList="id", unique = true)
})
@Indexed
@Spatial(name="distance", spatialMode = SpatialMode.HASH)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Terminal implements Serializable {
@Id
@Column(name = "id", nullable = false)
protected int id = 0;
@Field
@Column(name = "name", nullable = false)
protected String name;
@Field
@Column(name = "owner", nullable = false)
protected String owner;
@Field
@Column(name = "terminal_class", nullable = false)
protected int terminalClass;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "terminal", fetch = FetchType.EAGER)
@OrderColumn(name="position")
@IndexedEmbedded
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
protected List<Transaction> transactions = new ArrayList<Transaction>();
@Field
@Column(name = "description", nullable = false)
protected String description;
@Version
private long version = -1;
private Timestamp tsCreated = null;
private Timestamp tsLastModified = null;
@PrePersist
public void prePersist(){
if (transactions != null && !transactions.isEmpty()) {
for (Transaction t : transactions) {
t.setTerminal(this);
}
}
Timestamp now = new Timestamp(System.currentTimeMillis());
if(tsCreated == null)tsCreated = now;
tsLastModified = now;
}
public Terminal() {
}
...
@Override
public boolean equals(Object o) {
...
}
@Override
public int hashCode() {
...
}
}
Вот метод поиска Терминала DAO:
public List<Terminal> getByParams(HashMap<String, Object> params) {
List<Terminal> terminals = new ArrayList<Terminal>();
List results = null;
if (params != null && !params.isEmpty()) {
EntityManager em = this.persistenceProvider.entityManager();
Session session = em.unwrap(Session.class);
FullTextSession fullTextSession = Search.getFullTextSession(session);
// create native Lucene query using the query DSL
QueryBuilder qb = fullTextSession.getSearchFactory()
.buildQueryBuilder().forEntity(Terminal.class).get();
BooleanJunction bj = qb.bool();
Integer count = (Integer) params.get("count");
Integer offset = (Integer) params.get("offset");
Integer maxDist = (Integer) params.get("maxDist");
Double gpsLatitude = (Double) params.get("gpsLatitude");
Double gpsLongitude = (Double) params.get("gpsLongitude");
if (count != null && offset != null && maxDist != null
&& count > 0 && offset >= 0 && maxDist > 0) {
for (Map.Entry<String, Object> entry : params.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if ((value != null && value instanceof Integer && (Integer) value > -1
&& !key.equals("count") && !key.equals("offset") && !key.equals("maxDist")) ||
value != null && value instanceof String) {
bj.must(qb.keyword().onField(key).matching(value).createQuery());
}
}
bj.must(qb.spatial()
.onField("distance")
.within(maxDist, Unit.KM)
.ofLatitude(gpsLatitude)
.andLongitude(gpsLongitude)
.createQuery());
Query luceneQuery = bj.createQuery();
FullTextQuery hibQuery = fullTextSession.createFullTextQuery(luceneQuery, Terminal.class);
Sort distanceSort = new Sort(
new DistanceSortField(gpsLatitude, gpsLongitude, "distance"));
hibQuery.setSort(distanceSort);
hibQuery.setProjection(FullTextQuery.SPATIAL_DISTANCE, FullTextQuery.THIS);
hibQuery.setSpatialParameters(gpsLatitude, gpsLongitude, "distance");
hibQuery.setFetchSize(count);
hibQuery.setFirstResult(offset);
hibQuery.setReadOnly(true);
// execute search
results = hibQuery.list();
Iterator<Object[]> iterator = results.iterator();
while (iterator.hasNext()) {
Object[] resultObject = iterator.next();
if (resultObject.length == 2) {
double distanceInMeters = (Double) resultObject[0] * 1000;
Terminal terminal = (Terminal) resultObject[1];
terminal.setDistance(distanceInMeters);
terminals.add(terminal);
}
}
} else {
log.error("Empty param list passed to TerminalDAO class. Cannot find terminals.");
}
}
return terminals;
}
Я использовал Apache Benchmark с 10 одновременных и 100 в общем объеме запросов на Core i7 2,5 ГГц с 16 Гб оперативной памяти (так что аппаратное обеспечение в порядке):
ab -c 10 -n 100 "http://localhost:8080/getTerminals?class=1&lat=46.2317893&lon=50.168378&count=100"
Как вы можете видеть отклик, содержащийся в 1 00 терминалов вблизи пройденного местоположения. Запросы были быстрыми, но я беспокоюсь о потреблении процессора.
Согласно профилировщику, самым интенсивным методом cpu был FullTextQuery.list().
После прочтения Hibernate документы и некоторые исследования, я попробовал несколько вещей:
- Использование Нетерпеливый/отложенной загрузки с коллекциями;
- выключить/включить кэш L2;
- setReadOnly (true) на FullTextQuery;
- реализовать конструкторы со всеми свойствами.
но ничего не помогло.
Это обычная проблема для одновременных запросов (выбирается из db)? Может ли Hibernate Search обрабатывать их? Я что-то упускаю?
Полный пример проекта можно найти здесь: https://github.com/xvonabur/hib_debug
Я признателен за любую помощь.
Есть что-то, что называется поисковой системой Lucene, будет полезно для вас http://hibernate.org/search/documentation/getting-started/ –