咨询:如何基于用户特定统计调整Elasticsearch搜索结果得分
嘿,这个需求完全可以用Elasticsearch实现,而且不用把用户行为数据硬塞到company主文档里——咱们可以用**侧栏索引(Side Index)结合函数得分查询(Function Score Query)**来搞定,我给你拆解下具体思路:
核心思路:分离主数据与用户行为数据
不用在company文档里嵌套用户行为,而是单独创建一个存储用户-公司交互记录的索引,我习惯叫它user_company_interactions,结构可以设计成这样:
{ "user_id": "用户唯一标识GUID", "company_id": "关联的company文档ID", "open_count": 8, // 用户打开该公司的次数 "last_open_time": "2024-05-20T16:40:00Z" // 最后一次打开的时间 }
每个用户和公司的交互是独立文档,更新起来特别方便——用户打开公司页面时,直接用upsert操作更新这个索引即可,完全不会影响主索引的结构和性能。
查询时结合行为数据做个性化评分
接下来就是在搜索company时,通过Elasticsearch的function_score查询,拉取当前用户的交互数据,把这些数据作为评分因子加入最终得分。
举个具体的查询示例(假设当前用户ID是user_789,搜索关键词是“互联网公司”):
{ "query": { "function_score": { // 基础搜索查询:匹配关键词 "query": { "match": { "name": "互联网公司" } }, // 自定义评分函数:结合用户行为数据 "functions": [ // 因子1:打开次数越多,加分越高 { "script_score": { "script": { "source": """ def interaction = params.interaction_data; // 如果有用户交互记录,返回打开次数的加权值,否则返回0 return interaction != null ? interaction.open_count * 0.6 : 0; """, "params": { "interaction_data": { "index": "user_company_interactions", "id": "user_789#{{doc['id'].value}}" // 用「用户ID+公司ID」作为交互文档的唯一ID } } } } }, // 因子2:最近打开的公司,加分更多 { "script_score": { "script": { "source": """ def interaction = params.interaction_data; if (interaction == null) return 0; // 计算距离当前的天数,最近7天打开的加2分,30天内加1分,超过的不加 def days = ChronoUnit.DAYS.between(Instant.parse(interaction.last_open_time), Instant.now()); return days <=7 ? 2.0 : (days <=30 ? 1.0 : 0); """, "params": { "interaction_data": { "index": "user_company_interactions", "id": "user_789#{{doc['id'].value}}" } } } } } ], "boost_mode": "sum" // 把基础查询得分和自定义因子得分相加,你也可以用multiply等模式 } } }
关键优化点
- 交互文档ID设计:用
user_id#company_id作为user_company_interactions的文档ID,能直接通过ID快速定位到用户对某公司的交互记录,比用term查询更高效。 - 更新策略:用户打开公司页面时,用
upsert操作自动创建/更新交互记录:POST user_company_interactions/_update/user_789#company_123 { "script": { "source": "ctx._source.open_count +=1; ctx._source.last_open_time = params.now", "params": {"now": "2024-05-20T17:00:00Z"} }, "upsert": { "user_id": "user_789", "company_id": "company_123", "open_count":1, "last_open_time": "2024-05-20T17:00:00Z" } } - 性能优化:可以开启ES的请求缓存,或者在应用层缓存用户近期的交互数据,减少重复查询;脚本里尽量避免复杂计算,也可以把预处理好的权重值直接存在交互文档里。
结论
完全只用Elasticsearch就能实现你的需求,核心就是把用户行为数据和主业务数据分离存储,查询时通过函数得分机制把两者结合起来,既保证了主索引的干净,又能灵活实现个性化排序。
内容的提问来源于stack exchange,提问作者Daniel Lerps




