自从我上次访问内置工具中的特定功能以来已经有一段时间了。基本上它是一个简单的input
文本框字段,我们允许人们执行搜索。在人们搜索时,我们会调出一个自动填充,其中列出的5个项目与用户搜索的内容相匹配。
在这个项目开始的很长一段时间里,我相信我们的自动完成工作真的很快 - 只要我们在这个字段中找到三个字符,我们就会立即得到自动完成 - 我们使用elasticsearch来索引我们的记录。现在自动完成工作但是花了太长时间才能得到一些结果。很多时候,用户可以输入一个完整的搜索字符串,当他/她点击进入后,在点击此页面之前转到我们的搜索结果页面,自动完成将最终出现。好吧,由于用户已经前往搜索的结果页面,因此无法启动。
我以为我发布了我们正在做的事情,看看我们是否遗漏了某些东西或者它的硬件是否相关(因为我们正在使用VM进行搜索)。无论如何,我们有一个基本的输入文本框:
<input runat="server" spellcheck="false" autocomplete="off" id="search" type="text" placeholder="Search..." class="search" maxlength="100" />
在此搜索框的条目中,我们调用一个搜索的函数:
function DoAJAXSearch() {
var search = $("#search").val().trim();
var resultsToReturn = 5;
var module;
//have they selected a specific module to search on?
if (search.length > 0) {
if ($('#ddlEntity').val() === "") {
module = 0;
} else {
module = $('#<%= ddlEntity.ClientID %> option:selected').val();
}
$("#search").autocomplete({
source: function (request, response) {
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "/Code/WebServices/Search.asmx/DoSearch",
dataType: "json",
data: '{"searchQuery":"' + search + '","module":"' + module + '","resultsToReturn":"' + resultsToReturn + '"}',
success: function (data) {
response($.map(data.d, function (item) {
return {
entity: item.EntityType,
value: item.Summary,
url: item.EntityLink,
picture: item.PicturePath == null ? "/Images/noimagefound.png" : item.PicturePath
}
}));
}
});
},
minLength: 3,
select: function (event, ui) {
//log the search back to the database
LogSearch(module, search, true);
//end log search back to database
//open the selected item
window.open(ui.item.url, "_self");
},
open: function () {
},
close: function () {
},
error: function (xmlHttpRequest, textStatus) {
alert(textStatus);
},
search: function () {
$(this).addClass('autocompleteloading');
},
response: function () {
$(this).removeClass('autocompleteloading');
}
}).data("ui-autocomplete")._renderItem = function (ul, item) {
ul.addClass("addShadow");
var face;
if (item != null) {
if (item.value.length > 50) {
face = item.value.substr(0, 50) + '...';
} else {
face = item.value;
}
return $("<li>")
.append("<a title='" + item.value + "' href='" + item.url + "'><div style='float:left;width:100%;'><div class='dPicture'><img width='48px' height='48px' src='" + item.picture + "' /></div><div class='dPicture'><div class='dPictureFields'><b>" + item.entity + "</b></div><div class='dPictureFields'>" + face + "</div></div></div><div class='clear-fix'></div></a></li>")
.appendTo(ul);
}
return false;
};
}
}
基本上,如您所见,我们允许用户搜索所有模块或特定模块(如果他们选择了一个模块)。我们只称这种网络方式:&#34; DoSearch&#34;输入3个字符。
DoSearch是asmx文件中的一个方法(因为这是一个Web表单项目)。它非常冗长,我知道非常难看,但重点是捕获用户输入的内容并构建我们的弹性搜索结果。尽管我讨厌它目前所拥有的东西:
public List<ElasticSearchResult> DoSearch(string searchQuery, int module, int resultsToReturn = 0)
{
//get tag
//user can add a tag to the search, "SearchQuery [C++]" looking for anything with SearchQuery that is tagged C++
string tag = string.Empty;
int pos = searchQuery.IndexOf('[', 0);
if (pos > -1)
{
int pos2 = searchQuery.IndexOf(']', pos);
if (pos2 > -1)
{
//we've got [ ]
tag = searchQuery.Substring(pos + 1, pos2 - pos - 1).ToLower();
}
if (pos > 0)
searchQuery = searchQuery.Substring(0, pos - 1);
}
//The search engine only sees /t, not a tab, so replace it with a space
searchQuery = searchQuery.Replace("\t", " ");
//Reserved characters: + - && || ! ( ) { } [ ] ^ " ~ * ? : \ /
//Remove reserved characters the user won't use
var reserverCharacters = new List<string>
{
"+",
"&&",
"||",
"(",
")",
"{",
"}",
"[",
"]",
"^",
"~",
":",
"\\",
"/"
};
searchQuery = reserverCharacters.Aggregate(searchQuery,
(current, character) => current.Replace(character, ""));
//To prevent search errors, trim * and whitespace
searchQuery = searchQuery.Trim(' ', '*');
//Define the descriptors so they can be reused throughout this function
var companySearchDescriptor = new SearchDescriptor<ElasticSearchCompany>();
companySearchDescriptor
.Size(35)
.Query(q => q
.QueryString(query => query
.AnalyzeWildcard()
.OnFieldsWithBoost(b => b
.Add(f => f.Summary, 1.6)
.Add(f => f.Summary.Suffix("ngram"), 1.5)
.Add(f => f.Description, 1.0)
.Add(f => f.City.Suffix("ngram"), 1.1)
.Add(f => f.City, 1.0)
.Add(f => f.Tags, 1.0))
.Query(searchQuery)));
var contactSearchDescriptor = new SearchDescriptor<ElasticSearchContact>();
contactSearchDescriptor
.Size(35)
.Query(q => q
.QueryString(query => query
.AnalyzeWildcard()
.OnFieldsWithBoost(b => b
.Add(f => f.Summary, 1.6)
.Add(f => f.Summary.Suffix("ngram"), 1.5)
.Add(f => f.Description, 1.0)
.Add(f => f.Email, 1.1)
.Add(f => f.Email.Suffix("ngram"), 1.0)
.Add(f => f.Title, 1.1)
.Add(f => f.Title.Suffix("ngram"), 1.0)
.Add(f => f.Tags, 1.0))
.Query(searchQuery)));
var userSearchDescriptor = new SearchDescriptor<ElasticSearchUser>();
userSearchDescriptor
.Size(35)
.Query(q => q
.QueryString(query => query
.AnalyzeWildcard()
.OnFieldsWithBoost(b => b
.Add(f => f.Summary, 1.6)
.Add(f => f.Summary.Suffix("ngram"), 1.5)
.Add(f => f.Description, 1.0)
.Add(f => f.Email, 1.1)
.Add(f => f.Email.Suffix("ngram"), 1.0)
.Add(f => f.Title, 1.1)
.Add(f => f.Title.Suffix("ngram"), 1.0)
.Add(f => f.Tags, 1.0))
.Query(searchQuery)));
var corporationSearchDescriptor = new SearchDescriptor<ElasticSearchCorporation>();
corporationSearchDescriptor
.Size(35)
.Query(q => q
.QueryString(query => query
.AnalyzeWildcard()
.OnFieldsWithBoost(b => b
.Add(f => f.Summary, 1.6)
.Add(f => f.Summary.Suffix("ngram"), 1.5)
.Add(f => f.Description, 1.0)
.Add(f => f.Tags, 1.0))
.Query(searchQuery)));
var documentSearchDescriptor = new SearchDescriptor<ElasticSearchDocument>();
documentSearchDescriptor
.Size(35)
.Query(q => q
.QueryString(query => query
.AnalyzeWildcard()
.OnFieldsWithBoost(b => b
.Add(f => f.Summary, 2.0)
.Add(f => f.Summary.Suffix("ngram"), 1.5)
.Add(f => f.Title, 1.4)
.Add(f => f.Title.Suffix("ngram"), 1.0)
.Add(f => f.Description, 1.0)
.Add(f => f.Tags, 1.0))
.Query(searchQuery)));
var checklistSearchDescriptor = new SearchDescriptor<ElasticSearchChecklist>();
checklistSearchDescriptor
.Size(35)
.Query(q => q
.QueryString(query => query
.AnalyzeWildcard()
.OnFieldsWithBoost(b => b
.Add(f => f.Summary, 2.0)
.Add(f => f.Summary.Suffix("ngram"), 1.9)
.Add(f => f.Summary.Suffix("ngram_no_punct"), 1.9)
.Add(f => f.Id, 2.0)
.Add(f => f.Milestone, 1.6)
.Add(f => f.RecordName, 1.5)
.Add(f => f.RecordName.Suffix("ngram"), 1.4)
.Add(f => f.RecordName.Suffix("ngram_no_punct"), 1.4)
.Add(f => f.ParentName, 1.3)
.Add(f => f.ParentName.Suffix("ngram"), 1.2)
.Add(f => f.ParentName.Suffix("ngram_no_punct"), 1.2)
.Add(f => f.Description, 1.0)
.Add(f => f.Tags, 1.0))
.Query(searchQuery)));
var issueSearchDescriptor = new SearchDescriptor<ElasticSearchIssue>();
issueSearchDescriptor
.Size(35)
.Query(q => q
.QueryString(query => query
.AnalyzeWildcard()
.OnFieldsWithBoost(b => b
.Add(f => f.Summary, 1.3)
.Add(f => f.Summary.Suffix("ngram"), 1.2)
.Add(f => f.Description, 1.0)
.Add(f => f.IssueNumber, 2.0)
.Add(f => f.LessonLearned, 1.0)
.Add(f => f.Resolution, 1.0)
.Add(f => f.Type, 1.0)
.Add(f => f.Tags, 1.0))
.Query(searchQuery)));
//SIMILIAR CODE FOR ALL OTHER MODULES BASED ON ISSUE ABOVE
//TOO MUCH CODE TO POST
//Need the client before you can make a call to the search servers
var esClient = Code.Utilities.GetElasticClient();
if (esClient == null) return null;
IMultiSearchResponse result;
//Each case(other than default) handles if the user chose to only search for a single type of module
switch (module)
{
case (int) Module.Company:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchCompany>(a => companySearchDescriptor));
break;
}
case (int) Module.Contact:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchContact>(a => contactSearchDescriptor));
break;
}
case (int) Module.Corporate:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchCorporation>(a => corporationSearchDescriptor));
break;
}
case (int) Module.Checklist:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchChecklist>(a => checklistSearchDescriptor));
break;
}
case (int) Module.Document:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchDocument>(a => documentSearchDescriptor));
break;
}
case (int) Module.Issue:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchIssue>(a => issueSearchDescriptor));
break;
}
case (int) Module.TaskReminder:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchTaskReminder>(a => taskSearchDescriptor));
break;
}
case (int) Module.LessonLearned:
{
result =
esClient.MultiSearch(search => search
.Search<ElasticSearchLessonLearned>(a => lessonLearnedSearchDescriptor));
break;
}
case (int) Module.Program:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchProgram>(a => programSearchDescriptor));
break;
}
case (int) Module.Project:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchProject>(a => projectSearchDescriptor));
break;
}
case (int) Module.SubProject:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchSubProject>(a => subprojectSearchDescriptor));
break;
}
case (int) Module.ServiceOrder:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchServiceOrder>(a => serviceOrderDescriptor));
break;
}
case (int) Module.Meeting:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchMeeting>(a => meetingSearchDescriptor));
break;
}
case (int) Module.Tip:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchTip>(a => tipSearchDescriptor));
break;
}
case (int) Module.Incident:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchIncident>(a => incidentSearchDescriptor));
break;
}
case (int) Module.Material:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchMaterial>(a => materialSearchDescriptor));
break;
}
case (int) Module.Training:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchTraining>(a => trainingSearchDescriptor));
break;
}
case (int) Module.User:
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchUser>(a => userSearchDescriptor));
break;
}
default: //Handles the "All" type search
{
//LessonLearned results are Issues, don't want to search them twice in "All" type search
if (MySession.Current.User.IsAdmin)
{
//allow user search
result = esClient.MultiSearch(search => search
.Search<ElasticSearchCompany>(a => companySearchDescriptor)
.Search<ElasticSearchContact>(a => contactSearchDescriptor)
.Search<ElasticSearchUser>(a => userSearchDescriptor)
.Search<ElasticSearchCorporation>(a => corporationSearchDescriptor)
.Search<ElasticSearchChecklist>(a => checklistSearchDescriptor)
.Search<ElasticSearchDocument>(a => documentSearchDescriptor)
.Search<ElasticSearchIssue>(a => issueSearchDescriptor)
.Search<ElasticSearchTaskReminder>(a => taskSearchDescriptor)
.Search<ElasticSearchProgram>(a => programSearchDescriptor)
.Search<ElasticSearchProject>(a => projectSearchDescriptor)
.Search<ElasticSearchSubProject>(a => subprojectSearchDescriptor)
.Search<ElasticSearchServiceOrder>(a => serviceOrderDescriptor)
.Search<ElasticSearchMeeting>(a => meetingSearchDescriptor)
.Search<ElasticSearchTraining>(a => trainingSearchDescriptor)
.Search<ElasticSearchIncident>(a => incidentSearchDescriptor)
.Search<ElasticSearchMaterial>(a => materialSearchDescriptor)
.Search<ElasticSearchTip>(a => tipSearchDescriptor));
}
else
{
result = esClient.MultiSearch(search => search
.Search<ElasticSearchCompany>(a => companySearchDescriptor)
.Search<ElasticSearchContact>(a => contactSearchDescriptor)
.Search<ElasticSearchCorporation>(a => corporationSearchDescriptor)
.Search<ElasticSearchChecklist>(a => checklistSearchDescriptor)
.Search<ElasticSearchDocument>(a => documentSearchDescriptor)
.Search<ElasticSearchIssue>(a => issueSearchDescriptor)
.Search<ElasticSearchTaskReminder>(a => taskSearchDescriptor)
.Search<ElasticSearchProgram>(a => programSearchDescriptor)
.Search<ElasticSearchProject>(a => projectSearchDescriptor)
.Search<ElasticSearchSubProject>(a => subprojectSearchDescriptor)
.Search<ElasticSearchServiceOrder>(a => serviceOrderDescriptor)
.Search<ElasticSearchMeeting>(a => meetingSearchDescriptor)
.Search<ElasticSearchTraining>(a => trainingSearchDescriptor)
.Search<ElasticSearchIncident>(a => incidentSearchDescriptor)
.Search<ElasticSearchMaterial>(a => materialSearchDescriptor)
.Search<ElasticSearchTip>(a => tipSearchDescriptor)
);
}
break;
}
}
List<ElasticSearchResult> esSearchResultList =
ParseSearchResults(result, MySession.Current.User.IsAdmin).ToList();
if (!string.IsNullOrEmpty(tag) && tag.Length > 0)
esSearchResultList = esSearchResultList.Where(x => x.Tags.Any(a => a == tag)).ToList();
//we need to reserve the order of the result
//so we added a new property called sort order which we will update below
int counter = 0;
foreach (var esr in esSearchResultList)
{
esr.SortOrder = counter;
counter++;
}
//Remove any results the user doesn't have permission to see
var entityResultList = new List<ElasticSearchResult>();
//this is a inteermediate place holder for records
var filteredResultList = new List<ElasticSearchResult>();
//this will have the final result after filtering
//get the search entity list
var entityList = CachedData.EntitySearchList;
using (var db = DataCenterAccess.NewConnection())
{
//loop thru the entity search to filter what the current user can /cannot see
foreach (var entity in entityList)
{
//get user permission for current entity
Answer permission =
(Answer) MySession.Current.Permission.GetPermission(entity.EntityID, (int) ActionEnum.View);
if (permission == Answer.Yes)
{
//if yes get all records for that entity type
entityResultList = esSearchResultList.Where(x => x.EntityId == entity.EntityID).ToList();
}
else if (permission == Answer.Originator)
{
//if originator ; get only record the user can see for that entity type
entityResultList =
esSearchResultList.Where(
x =>
x.EntityId == entity.EntityID &&
x.AddedByUserId == MySession.Current.User.UserId).ToList();
}
//add the result to the final result list
filteredResultList.AddRange(entityResultList);
}
//now we need to isolate the records that are private based on the team record security
var privateRecords = filteredResultList.Where(x => x.Private).ToList();
//also make sure the filtered list does not have any of the private records
filteredResultList = filteredResultList.Where(x => !x.Private).ToList();
if (privateRecords.Any())
{
//if we have any private records
//we need to loop thru and see if the user is on a team that has access to the record
foreach (var esr in privateRecords)
{
if (RIMSBL.Code.DAL.UserHasTeamPermission(db, esr.EntityId, esr.RecordId,
MySession.Current.User.UserId))
filteredResultList.Add(esr);
}
}
}
//0 means return all. If not 0, then limit to that number of search results
if (resultsToReturn != 0)
{
filteredResultList = filteredResultList.OrderBy(x => x.SortOrder).Take(resultsToReturn).ToList();
}
return filteredResultList;
}
我不确定问题是否与数据有关,因为我们只处理数十万条记录中的内容(跨所有模块)。我们做的一件事就是当您在搜索文本框中键入字母时,我们不断调用DoSearch,并在完成后返回状态代码。因此,如果你键入7个字符,搜索将在第3个字符上调用,直到第7个字符(总共5次),如chrome控制台中所示:
我能想到的另一件事是,我们可能只需要更好的硬件。此服务器统计信息:
我知道搜索服务器内存不足但在我请求额外硬件之前我想知道我是否遗漏了任何内容。
答案 0 :(得分:1)
首先,你必须衡量它是服务器还是客户端(浏览器)。在服务器代码中添加一个断点并逐步执行它以获得想法。
然后。要防止向服务器发出多个请求,请在自动完成初始化中添加0.6 * 0.3 * 0.9
选项:
delay
这种方式自动完成仅在用户暂停键入550毫秒或更长时间时才有效。试验价值。
另外,我注意到您在$("#search").autocomplete({
delay: 550,
//continued...
});
函数中每次重新初始化自动完成,这是预期的行为吗?该函数DoAJAXSearch
何时被完全调用?在每次击键?难怪它那么慢。自动完成只应在页面加载时初始化一次。
这样的事情:
DoAJAXSearch
PS。一台16GB的服务器完全没问题,不应该是asp.net的瓶颈。