当前位置:   article > 正文

java项目: ElasticSearch+Spark构建高相关性搜索服务&千人千面推荐系统

elasticsearch+spar

1 概述

搜索服务必备三个点, 完整而丰富的待搜索数据源,搜索引擎(存储搜索数据并且简历关键词索引), 相关语义的理解(解决语句的理解)

推荐系统: 良好的训练测试数据集, 个性化的千人千面的召回排序算法, 可解释且有实际意义的评价指标。

本项目基于一个项目迭代开发,先搭建最简单的线性框架, 最终加入es用于搜索, spark用于推荐。

项目设计:给出需求—- 设计技术方案— 完成具体的实施。

基本技术栈: spring boot + mybatis等。

2 需求分析

业务需求方输出BRD, 产品给出用户故事,并将业务需求方的文档翻译成PRD让程序员去开发。

业务需求(需求方将目标、功能、大致效果整理成brd, 表明自己的需求。)

  • 仿造大众点评做H5应用

  • 具备搜索线下门店的服务功能

  • 推荐线下门店服务功能(没有主动搜索欲望和目标)
    在这里插入图片描述
    用户故事(用用户角度把应用将清楚)

  • 打开首页能够查看服务分类

  • 打开首页能够查看推荐的列表

  • 可以通过搜索栏搜索关键字对应的店家, 并各种筛选和排序。

产品PRD(精细化具体功能, 就是一个产品角度的功能拆分)

  • 用户的登录、注册、进入首页的流程。
  • 运营维护服务分类、商家入驻、门店管理等运营后台功能。
  • 用户通过搜索关键字, 筛选条件找到自己想要的门店。
  • 推荐, 就不说了。
  • 必须给出具体的闭环实现,比如做什么按钮, 点击什么实现什么等等。

在这里插入图片描述
技术分析

  • 后端业务模块
  • 后端存储系统
  • 搜索系统
  • 推荐系统
  • 前端展示

模块设计之业务模块架构设计(要和PRD对称)

  • 实现用户, 商家、门店,服务类型四个业务实体。
  • 业务实体行为

在这里插入图片描述

  • 我们给出业务实体的ER图
    在这里插入图片描述
    系统模块架构设计(偏向于技术层面框架)

在这里插入图片描述
在这里插入图片描述

注意全量是指所有数据, 增量是指更改的实时数据。推荐的样本其实已经提前计算好了。

3 项目基础搭建【业务系统之基础能力】

  • 首先先把开发环境搭建好, 然后导入springboot库, 并且创建如下的项目列表。 controller是负责页面转发的, dal是数据库操作, model是书库类实体, service分为接口和具体的实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FbBs5Bda-1655800356930)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/8352e0e1-368b-45ed-abdf-07f642d66103/Untitled.png)]

  • 安装mysql然后创建用户表, 安装mybastis的jar包,配置plugins自动生成, 并且配置好xml, 这个xml中配置账号密码等等配置, (这里配置的model代替了三层model的分层, 这层的model带实际具体的操作)。
  • 然后在这个大项目中新建mven项目mybatis-autogenerator并给出mybatis自动生成命令, 用于自动生成对应的代码和配置, 这些会放到大的web项目中固定的位置, 如model等。
  • 在web项目中的配置数据库的配置, 如使用连接池,连接数据地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8JV5SWId-1655800356930)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/ac8927db-0c33-429f-9a65-0782c87cb191/Untitled.png)]

  • 在启动类中给出数据的mapper类位置, 然后在实现业务接口的实体业务类中去通过注解的方式调用数据实体访问。 (这里返回的是json格式)

异常处理

  • 在common包下, 创建一个common-result类去处理数据, 并且将状态封装到类中。并且创建一个common_error定义一些错误类型。 (这些code和msg都是要提前定义的, 而且在企业级开发的时候非常重要。)还要结合aop来处理发生的异常, 这里面的内容非常复杂,各种问题都要考虑。

静态请求

  • 处理前端静态资源和模板引擎thymeleaf渲染后的html文件。静态资源配置一下就行了。
  • thymeleaf是处理动态的, 需要返回的是view, 在模板文件中进行变量替换。

4 用户服务、运营后台、商户服务的搭建

用户模型前后端

  1. 先写注册后端接口, 首先在后端用一个@类接收,然后对齐进行赋值到新的数据实体类, 然后使用派生的用户服务中注册函数用来注册, 在注册函数中会启动事务, 进行数据的插入, 如果插入不行比如唯一字段宠物, 就会报响应的通用error。 此外还要对一些手机号等进行规则限制,@notblack去控制并通过commonutils去处理数据出问题的error。然后再使用postman 使用post模仿数据。
  2. 写登录接口时候, 需要在xml和实体类中自定义sql。 然后在service中写方法去判断数据是否成功, 然后在controller中注入一个bean关于Httpservletrequest, 这个是所有线程共享的, 然后让其将当前用户的状态放入到Httpservletrequest的session中保存。
  3. 退出操作就是注销掉seesion就行。
  4. 获取用户信息接口就是从session中获取。
  5. 在后端js部分,写一个ajax请求获取当前用户的状态,在里面根据获取的数据去更改自己组件的状态。此外写登录按钮的click函数, 如果是登录跳转到新登录界面, 如果是注销就返回最开始的index。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U4is1LJY-1655800480955)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/35d4a5c8-92de-4572-b188-b54ddca9b275/Untitled.png)]

  1. 在js中写入和登录注册的click函数, 如果是登录按钮成功的话就进入首页, 如果是注册就进行到新的html中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SDEczXWO-1655800480955)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/d90dd0fd-eaaf-475b-a0c3-4081091db64a/Untitled.png)]

运营后台

  1. 运营后台模板是公司看的, 不会像用户那样, 需要自定义。 因此大家都会用一些好看的模板,例如metroc 并且会有权限管理这些。
  2. 在项目中新添加一个admin controller, 这里直接返回的就是界面。 这个登录的逻辑基本和之前用户的是差不多的。 需要注意需要控制器AOP去动态判断用户是否有权限访问, 而且关闭那些不希望不登录就能够访问的地方。
  3. admin控制台首页开发: 将各个模板的子模块加入进来, 然后通过ajax请求发送获取数据进行填充。
  4. 添加商家: 当用户点击商家创建的时候, 进入ceate的html界面, create界面中点击到确定之后提交json到后端, 返回结果到前端。
  5. 对查询搞成分页形式的, 在controller中设置当前页面, 并且要查询多少页放入到select中,进入到数据库去查。 这个也非常复杂。
  6. admin的启动和禁用。

商户入驻: 商户创建、商户查询、商户禁用

  • 新建一个商户表,然后走一套用户那一套, 然后将数据发送到前端。 数据表: 商户id, 商户名, 创建时间, 更新时间, 商户的所有店家平均分,是否启动标志位
  • 在mybatis 配置中添加这个表,自动生成model层内容和dal层内容, 如果有一些高级的数据库操作需要自己修改配置写sql。
  • 在service包中创建商家service子包, 里面定义接口提供对商家数据类的查询, 创建, 禁用操作。
  • 在service的impl层中写接口的实现。 然后在controller层写商家的controller, 在具体的modelAndView中将业务层给出的数据放入静态前端界面。
  • 在静态页面的index.html中,添加商家入住数据到里面, 并且在前端和后端添加响应的代码 。
  • 将查询改成分页查询, 避免数据过多直接卡死, 这里面是直接用一个中间件, 然后还要改前后端的页面。

5 基础服务: 品类和门店

品类服务

  • 实现运营后台进行品类创建,查询以及暴露到用户前端三个功能。
  • 创建品类管理表, 还是走之前的往常流程。
  • 注意这里相比较商家服务的区别是会有用户端的一些接口提供。

门店服务

  • 门店服务是最核心的技术,需要门店创建,门店地理位置, 门店查询这几个服务。 其中搜索推荐就是集中在这个服务当中。
  • 我们再来介绍一下表的内容, 主要包含创建时间、更新时间、名字、id、 分数、价格、经纬度, 品类, 标签, 营业时间、 地址、外部关联的商家id。 可以看出这是一个非常复杂的表。
  • 创建完表之后还是创建myvatis, 然后写创建、查询,获取单个门店的几个操作。
  • 这里面由于门店的表会包含商家和品类, 因此这个数据类里面要提供方法能够获取自己对应的商家类和品类数据类。
  • 在这里面去写运营后端加前台的操作。
    在这里插入图片描述
  • 注意这里解析经纬度是需要接入百度的api, 前端自己用jqury解析的。
  • 我们需要在运营后台将门店的数据量和商家的数量给统计到前面。

6 点评门店搜索推荐V1.0

  • 章节目标:
    • 搜索: 通过搜索关键词, 匹配到数据库中包含关键词的信息, 展现到前端。
    • 推荐: 根据用户的特征做一些简单的线性推荐。
  • 推荐1.0 : 创建用户前端的controller, 推荐接口中接收前端的经纬度, 然后将所有的门店全部推荐进去。
    在这里插入图片描述
  • 在model字段中增加distance, 通过经纬度计算的距离 + 评分的综合成绩, 然后用sql 按照distance 进行排序查找门店。
  • 搜索服务1.0 : 搜索服务也是类似, 除了经纬度额外包含了关键词。 这里在自定义sql中加入name 模糊匹配like函数就行。 | 在前端增加相应的html和js | 这时候我们得到的搜索还是比较简单的, 没有一些目录和价格等的排序, 因此还需要继续加sql去定制 | 但是这种搜索还是有问题的,比如我们只能搜索like规则下的关键字, 而且实际搜索的数据是非常大的, 排序模型也只能使用一些简单的线性加权规则, 因此我们需要使用es中间件 |

7 点评门店搜索的数据介入

分词器

  1. 用es的插件去安装ik中文分词器。
  2. ik分词器工作流程中字符处理是和英文不一样的, 主要就是根据词库词典内容去拆分。 此外还有一种智能中文分词, 会将中华人民共和国搞成一个分词, 不会拆分中华和人民这些词。 | 一般我们在构建索引的时候是标准最大化分词模式 ,自己想要搜索词用智能模式 , 因为它比较贴近用户的语义, 如果搜索词召回的数据比较少就再用最大化分词, 还是不行就逐字分的模式 |

点评搜索索引创建

  1. 需要自己先要定义好自己的索引结构、字段类型,分词器模式 : 整数id, 智能化分词的店名, 标签,地理位置信息, 点评分数, 类目类型id, 类目类型名, 商家的分数, 商家启动或者关闭状态。
    在这里插入图片描述

  2. 全量索引构建 : 一般我们使用logstash-input-jdbc中间件去将数据库数据搞到es中 , 和elk中e是一样, 起到数据收集功能。 | 这里面有个问题就是如果sql中在执行事务, logstash-input-jdbc拿到的是快照之前的数据,就会出现数据不一致, 这种是可以接受的, 因为搜索不是很强调内容的过于细节的变动,而且增量数据补偿也可以弥补。 | 还是在es中安装logstash-input-jdbc插件— 填写数据库的配置等, 放入sql语句, sql服务器地址, 放入映射关系
    在这里插入图片描述

  3. 增量(实时修改)索引构建: 增量索引就是通过sql语句去搜索更新日期大于* 的内容,然后对这些数据进行更新。 | 我们也是在es中配置中, 然后在之前的全量索引sql语句中加入时间的判断,当大于自己当前的时间, 更新。 | 到这就实现了刚开始更新全量, 后面实时更新增量的工作, 但是其实这个增量还是比较慢, 而且数据量大的时候会出现更新还没更新完, 还要检测, 结果发现还有很多要更新, 我们后面继续优化。

7 点评门店搜索的应用层接入

搜索的模型

  1. 最基本的搜索模型 : 我们根据坐标自定义es语句去计算距离, 然后和搜索的关键字一起定制化打分, 按照分数进行排序 | 这些不同打分权重是根据实际业务去调整的 | 还可以设置关键字只算召回, 排序时候只考虑距离。

  2. 注意下面是es的工作流程, 一般es搜索过来的内容取到id, 还要去数据库中再找详细的图片等信息的, es只是负责让你方便的搜, 实际搜索的内容需要用户拿到es返回之后请求sql数据库。

在这里插入图片描述
2. java接入 : 一般我们用restful http 去接入es集群 | 先在java项目中加入包, 然后创建配置, 给出http es client 对象, 然后服务端调用http client 服务对象操作数据 | 如下图所示, 这是一个最最简单的es搜索结果
在这里插入图片描述
4. 复杂排序模型的java封装 : 前面我们简单的java接入是用的高级api, 用函数封装的。 但是我们实际写的搜索模型是比较复杂的, 因此我们复制已经写好的es语句, 替换里面的变量, 以http的get请求发送过去, 最终得到结果。 | 但是这里面的es语句非常复杂, 如果后序要修改非常麻烦, 因为我们直接传入的json文档, 因此我们可以参照mybatis封装sql语句的操作, 以json对象来封装这些底层的语句。 虽然第一次写非常繁琐, 但是后序修改的时候就不用怎么改了, 能够修改部分会加else留空 | 可以看到我们还能根据目录操作进行过滤了, 整体非常流畅 |
在这里插入图片描述
5. 将之前用sql写的标签过滤搞成es过滤 , 用来解决无法识别空格的问题。 至此我们的这个搜索模型可以满足大部分需求的, 整个搜索非常强大了。
6. 额外的改进空间: 分词定制化, 我们需要对凯悦等行业词汇进行专门的定制分词, 对于一些词过滤掉等等 | 相关性 : 对于一些包含语境的词无法理解, 例如我们说休息其实是想搜住宿, 但是我们的带搜索内容中没有休息, 只有和住宿相关的, 而你搜的休息和住宿对于es而言, 没有相关性, 搜不出来。 | 索引实时性: 我们的搜索是一分钟一次, 但是对于高并发的领域太慢了。

8 搜索相关性的改造

定制化中文词库

  1. 扩展专业词库: 创建新的词库, 然后配置到es中使用, 例如凯悦这个词, 单独搞成词组到词库中 , 不过需要注意我们搜索的时候凯悦还是分开的, 我们不能重新删除所有索引再来一次全量更新, 那样太麻烦了, 所以我们可以通过自己通过update by query去创建一个凯悦的搜索分词, 这样可以去找实际存储的凯悦分词对应数据 | 注意这个扩展词库在专业领域非常常用,例如电商汽车等垂直领域都要做大量的这些专业 词修改工作 | 一般我会选热更新词库, 自动更新就不用重启去扩展词库 |
  2. 同义词 : 我们在词库中一个词位置写苹果,iphone就能代表同义词。 然后设置指定这个包含同义词的分词器到 商家名等关键字的搜索中, 然后更新增量索引 | 最后我们就可以通过苹果搜出iphone了。

重塑相关性

  1. 影响召回 : 如果我们搜索住宿, 索引里面没有住宿这个门店, 而住宿更像是一种目录, 因此我们增加目录相关性词, 把住宿和休息等词扩展到词库中连接到酒店这个目录, 然后在搜索的时候增加如果匹配到搜索的词是目录词,就把这个词不用按照name来搜, 按照目录来召回, 这样来看就影响了召回, 也更能理解人的需求 | 影响
  2. 影响排序: 将这个相关词的命中了, 写到es的function中去影响排序分数。 | 一般我们的相关词如果影响了召回, 就不太会设置影响排序, 因为根本没有意义, 一般是先开一个如果效果不好的话再开第二个 |

9 准实时性的改进

  1. canel实时增量更新: 之前我们的input_jdbc在面对大量的数据时候,难以处理, 而且每次都是遍历检查时间, 这样太慢了。 我们使用canel伪装成mysql的从库, 每次数据更新的时候master通知canel具体的更新内容, 然后canel根据通知去将数据放入es中, 这样更为方便。 | 这里面canal-adopter是比较简单的, 因此我们使用java代码实际连接canal去做消费, 然后根据不同的业务数据修改, 做出不同的查询与修改, 并更新到es中, 比如你的sql一个门店数据的修改, 就需要改好多个es的index, 因此这些逻辑还是比较复杂的

11 推荐系统

基础知识

  1. 推荐模型; 推荐模型有三种, 基于价格或者销量再结合场景的规则推荐, 解释性非常强, 很容易进行修改, 但是修改因为是人为的, 可能增加了这个权重后另外一个就 下降, 而且无法结合用户过往信息, 规则过于复杂 。 基于机器学习的推荐, 一般是先在过往的数据训练后再预测,效果好放入实际场景中应用。 这种方法对数据质量要求高, 容易找到全局最优解, 而且能够挖掘到人们直接观察不到的潜在信息, 但是解释性差 | 一般机器学习算法先训练到离线的指标, 例如查全率等表现良好,然后放入线上去看看点击率等, 都好的话就上线 | 一般对比模型时候会进行AB测试, 就是在同等时间同等条件下, 同时进行两个模型的对比, 看看哪个好, 不然你在夏天测试购物肯定比冬天测试的各个指标更好, 这样对比不公平。
  2. 推荐系统如下: C端的用户发送web请求到推荐服务中,选择好场景规则如美食或者购物等, 不同的场景可以配置不同的算法 | 首先进入到粗排, 因为数据量非常大而且本身我们没有指定内容, 因此需要先对海量数据先根据用户画像等离线模型进行大致的排序推荐, 然后将id放入到mysql或者redis中 , 进入到精排 | 进入到精排需要提取各个特征做模型预测, 一般这里的模型和粗排的是不一样的。 | 此外我们的所有模型都是通过大数据平台管理的数据进行训练的 | 重排是一些做活动等强制增加内容到排序中 |
    在这里插入图片描述

算法解析

  1. 个性化召回算法ALS : 将用户特征矩阵和产品特征矩阵相乘,让这个乘积得到的用户-产品矩阵无限接近已经存在少量确定数据矩阵中, 然后就能通过比例预测大部分空白处的值是多少, 从而进行排序后输出。
  2. 个性化排序算法LR:对于已经粗排的数据, 我们作为输入结合用户的各个特征做一个是否点击的机器学习的线性预测,这里面结合的用户数据就非常多了, 主要是根据点击率来做具体的排序。

spark

  1. spark介绍 : spark是一个大数据的处理平台,例如流式计算机器学习等, 而hadoop是大数据的存储,一般 spark要借助hdfs的数据存储才能一起工作。 | spark能够对上万条无法直接加载到单个机器内存的数据进行分流处理得到结果,此外还能进行窗口读取流数据 | 一般都是用spark连接多个sql数据库, 然后在spark中调用sql和计算 |
  2. java实现als: 推荐算法是当用户web请求接入后实现的, 因此是写到了web服务项目代码中, 而且多开一个线程进行离线的训练, 训练完成后模型进行保存 | 保存完成后我们在推荐请求中封装als给出的粗排id
  3. java实现lr : 启动spark后根据用户的特征去预测各个粗排的点击率, 然后排序后输出返回给前端 |

问题

  1. pom.xml中子类默认父类的版本,如果要定制需要自己设置version。
  2. 如果要接入mybatis的自动生成字段, 需要在plugins中增加相关的配置。

Summary

  1. 搜索部分其实分数加权这些可以结合用户的画像等, 不用完全根据用户的搜索内容, 因此可以使用深度学习, 此外一些分词器中的同义词感知也可以使用深度学习的向量, 因此搜索是有深度学习的应用空间的。其实就是使用推荐的算法结合去做搜索 | 自适应加模型
  2. 整体学下来基本知道推荐和搜索是怎么搞的了。

Reference

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/325723
推荐阅读
相关标签
  

闽ICP备14008679号