千人千面推荐系统


需求分析

(尚未完善)

业务需求BRD(Bussiness Requirement Document)

用户故事(User Story)

产品PRD(Product Requirement Document)

业务需求BRD

业务目的:仿大众点评系统,做一个H5的应用

业务场景:具备搜索线下门店服务功能、具备推荐线上门店服务功能,类似于猜你喜欢

使用的人员:

1.业务的运营人员

2.需要获得服务的c端用户

用户故事

打开页面可以查看服务分类

打开页面可以查看推荐门店列表

通过搜索栏,搜索关键字对应的门店列表并筛选或排序

产品PRD

用户注册,登录,进入应用首页的流程

运营维护服务分类,商家入驻,门店管理等运营后台的功能

用户通过搜索关键字,筛选条件,找到自己想要的门店服务的过程

用户打开首页后,系统根据用户的历史行为做推荐门店服务的过程

点评搜索推荐prd

名词定义

用户:使用该系统搜索产品的人员

运营后台:提供后台门店、商家、服务类目管理的后台网页

运营:使用运行后台配置门店,商店,服务类目的人员

功能设计

1.运营登录功能:输入用户名、密码登录后台系统

2.运营商家管理功能,商家创建(商家名、商家评分)、商家列表查询(商户id、商户名、商户评分、商户启用禁用)

3.服务类目管理功能:类目创建(名称、图标、排序),类目列表查询(类目id、名称、图标、排序)

4.门店管理功能:门店创建(名字、人均价格、评分、地址、标签、营业时间)、门店列表查询(门店id、名字、人均价格、评分、地址、标签、营业时间)

h5 C端功能:用户注册、用户登录

技术分解

后端业务模块

搜索系统

后端存储系统

推荐系统

前端页面设计

模块设计:

业务模块架构设计、系统模块架构设计

业务实体:

用户管理、商家管理、服务类型管理、门店管理

业务实体的行为:

用户注册、用户登录

商家创建、商家浏览

服务类型创建、服务类型查询浏览

门店创建,门店搜索及筛选,门店推荐

ER模型图

用例设计

系统架构设计

系统架构设计

技术选型

后端业务:Java(jdk1.8),SpringBoot框架

后端存储:mysql数据库(5.6),mybatis接入

搜索系统:elasticsearch分布式搜索引擎,canal

推荐系统:spark mllib机器学习组件

自己的开发环境

jdk1.8、maven3以上、intelliJ Idea

框架搭建

SpringBoot搭建JavaWeb、Mybatis做数据接入、业务系统搭建

项目中依赖的工具包

 <dependencies>
        <dependency>          <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--引入mysql连接工具、引入阿里的德鲁伊连接池、因为mybatis启动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>          <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

Mybatis数据接入

mybatis-generator代码自动生成

全局的异常处理

业务逻辑搭建

用户服务搭建

用户模型、用户行为(用户登录)

运营后台搭建

运营后台的模板

metronic模板

运营后台登录授权
切面权限管理

商户服务搭建

商户入驻
商户查询
商户禁用

品类服务搭建

品类创建

品类的客户端显示

门店服务

门店创建

门店地理位置

门店查询

门店推荐

门店距离计算
门店搜索
门店推荐架构

ElasticSearch的基础

ElasticSearch基本原理及其部署

ES之前的学习 ES是一个搜索数据库,独立的网络上的一个或一组进程节点。

ES再学习,Es中的名词

索引 = 数据库 、类型 = 表、文档 = 行数据

关系型数据库与ES对应的关系表

Relational database Elasticsearch
Database Index
Table Type
Row Document
Column field
Schema mapping
Index Everything id indexed
SQL query dsl
Select * from table GET url
update table set PUT url

ES构建索引:

搜索中的数据库或表定义

关系型数据库中的数据可以被看做是一个文档document,用来构建索引处理,利用分词器对索引进行分词处理,将索引与分词器分词的结果存储在搜索引擎中。

构建文档时候的索引创建

分词

1.搜索是以词为单位做基本的搜索单元

2.依靠分词器构建分词

3.用分词构建倒排索引

知乎对于倒排索引讲解的较为详细

TF-IDF打分

词频TF:Token Frequence,表示document文档包含了多少个这个词,包含的词越多表名越相关

DF文档频率,包含该词的文档总数目

IDF:DF取反

本次项目使用的是ES7.6.2

ElasticSearch基础语法及其应用

分布式索引:ES支持分布式分片技术

分片:ES是一个分布式搜索引擎,每一个索引有一个或多个分片,索引数据被分配到各个分片上,相当于一桶水用N个杯子装。

主从:ES配置主从服务器配置能够提高集群的可靠性,ES的集群有三种状态green、yellow、red

green:表示主服务器与从服务器都处于运行状态,如果其中的一个从服务器宕机,则转为yellow状态

yellow状态:表示有一个从服务器出现异常,查询时可能会产生错误信息,如果主服务器也宕机了,那么就会状态转变成red状态

red状态:表示主服务器出现了异常,数据的可能会产生丢失。

路由:

ES集群搭建

将下载的elasticsearch7.6.2解压三个文件夹,一个作为主服务器,另外两个为从服务器,为一个一主两从分布式系统。

索引的创建、更新、删除

//新建索引或者更新操作
PUT /employee/_doc/1
{
  "name":"itxing",
  "age":25
}
//强制指定创建,若已存在,则失败
POST /employee/_create/2
{
  "name":"xing12",
  "age":30
}

//指定字段修改
POST /employee/_doc/1
{
  "name":"xingzai"
}

//删除索引
delete /emplyee
//删除整个文档
DELETE /employee/_doc/2

//非结构化创建索引
PUT /employee
{
  "settings":{
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}

//查询索引
Get /employee/_doc/1
//查询全部文档
Get /employee/_search

索引的简单查询

//创建结构化的索引
PUT /employee
{
  "settings":{
    "number_of_shards":1,
    "number_of_replicas":1
  },
  "mappings":{
    "properties":{
      "name":{"type":"text"},
      "age":{"type":"integer"}
    }
  }
}
//添加数据
POST /employee/_doc/1
{
    "name":"itxing",
    "age":25
}
//不带条件查询所有的记录
GET /employee/_search
{
  "query":{
    "match_all":{ 
    }
  }
}

索引的复杂查询

//分页查询
GET /employee/_search
{
  "query":{
    "match_all":{}
  },
  "from":0,
  "size":2
}
//带关键字的条件查询
GET /employee/_search
{
  "query":{
    "match":{"name":"兄长"}
  }
}
//带关键字、有排序的查询
GET /employee/_search
{
  "query":{
    "match":{"name":"兄长"}
  },
  "sort":[
    {
      "age":{"order":"desc"}
    }]
}
//带filter
GET /employee/_search
{
  "query": {
    "bool": {
      "filter": [
        {"term":{
          "name":"兄"
        }}
      ]
    }
  }
}
//带聚合
GET /employee/_search
{
  "query": {
    "match": {
      "name": "兄"
    }
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ],
  "aggs": {
    "group_by_age": {
      "terms": {
        "field": "age"
      }
    }
  }
}

ElasticSearch高级语法及其应用

分词过程

对于英文的分词在于一个词有不同的时态变化,例如下面例子中的eat eating 其实都代表的是吃,但是在分词过程中将其当做两个词进行处理,英文分词器使用空格和标点符号进行拆词。

字符串建立索引的过程:字符过滤器(过滤特殊符号外加量词)—> 字符处理器—–>分词处理(分词转换,词干替换)

//重新创建一个索引
PUT /movie/_doc/1
{
  "name":"Eating an apple a day & keeps the doctor away"
}

//查询
GET /movie/_search
{
  "query":{
    "match": {
      "name": "eat"
    }
  }  
}
//查看分词状态
GET /movie/_analyze
{
  "field": "name",
  "text": "Eating an apple a day & keeps the doctor away"
}
//删除索引,使用英文分词器分词
DELETE /movie

//适应结构化方式创建索引,设置自定义的分词器,英文分词器
PUT /movie
{
  "settings":{
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings":{
    "properties":{
      "name":{"type": "text",
      "analyzer": "english"}
    }
  }
}

analyze分析过程

相关性查询手段

Tmdb实例

类型

text:被分析索引的字符串类型

keyword:不能被分析只能被精确匹配的字符串类型

Date:日期类型,可以配合format一起使用

数字类型:long、integer、short、double

boolean:true、false

Object:json嵌套

ip类型

Geo_point地理位置信息

Tmdb实例

TMDB(the movie DataBase)

数据获取与导入数据

掌握实际应用中的索引建立和数据导入过程

kaggle上下载tmdb数据信息

//删除已有的索引数据
DELETE /movie

//tmdb数据信息建立索引,高级搜索
PUT /movie
{
  "settings":{
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings":{
    "properties":{
      "title":{"type": "text","analyzer": "english"},
      "tagline":{"type":"text","analyzer":"english"},
      "release_date":{"type":"date","format":"8yyyy/MM/dd||yyyy/M/dd||yyyy/MM/d||yyyy/M/d"},
      "popularity":{"type":"double"},
      "overview":{"type":"text","analyzer":"english"},
      "cast":{
        "type":"object",
        "properties":{
          "character":{"type":"text","analyzer":"standard"},
          "name":{"type":"text","analyzer":"standard"}
        }
      }
    }
  }
}
//创建完成后导入数据

将数据导入操作,性github上下载导入数据的项目,能够通过使用开源的项目将csv文件导入到ES中。github项目下载地址

项目中需要更改的是:

1.ESConfig类中的相关ES的信息

image-20200923092425101

2.修改自己的csv文件的地址(个人使用的绝对地址)

将数据导入ES

直接访问localhost:8080/es/importdata ,将数据导入到ES中。tmdb数据导入后,学习高级的查询操作

掌握Query DSL(domain Specific Language)

ES中的_score表示文档与搜索字符串的关系紧密程度,计算使用的是TF/IDF,TF表示词频,是一个字符在在document字段中出现的次数;IDF为逆文档频率,代表basketball这样的一个分词在整个文档中出现的频率的倒数;TFNorm(token frequency nomalized):词频归一化。

BM25算法:解决词频问题

//查询steve内容,match按照字段上定义的分词分析后去索引内查询
GET /movie/_search
{
"query": {
  "match": {
    "title": "steve"
  }
}  
}

//term查询,不进行分词的分析,直接去索引内查询
GET /movie/_search
{
  "query":{
    "term": {
      "title": "Steve Jobs"
    }
  }
}

GET /movie/_analyze
{
  "field": "title",
  "text": "steve zissou"
}
//分词后的and和or的逻辑,match使用的是or逻辑关系
GET /movie/_search
{
  "query":{
    "match": {
      "title": "basketball with cartoom alies"
    }
  }
}
//自己制定查询时使用and的关系,文章中对搜索字符串中的分词结果的每一个分词都需要匹配
//使用minimum_should_match,设置最小匹配项,默认设置为1
GET /movie/_search
{
  "query":{
    "match":{
      "title":{
        "query":"basketball love aliens",
        "operator":"or",
        "minimum_should_match": 2
      }
    }
  }
}
//短语查询
GET /movie/_search
{
  "query": {
    "match_phrase": {
      "title": "steve zissou"
    }
  }
}
//多字段查询,多字段权重相同
GET /movie/_search
{
  "query": {
    "multi_match": {
      "query": "basketball with cartoom aliens",
      "fields": ["title","overview"]
    }
  }
}
//查看score计算过程
GET /movie/_search
{
  "explain": true,
  "query": {"match": {
    "title": "steve"
  }}
}
//不同字段不同的权重
GET /movie/_search
{
  "query": {
    "multi_match": {
      "query": "basketball with cartoom aliens",
      "fields": ["title^10","overview"]
    }
  }
}
//优化查询权重
GET /movie/_search
{
  "explain": true, 
  "query": {
    "multi_match": {
      "query": "basketball with cartoom aliens",
      "fields": ["title^10","overview"],
      "tie_breaker": 0.3
    }
  }
}

## bool查询
##must:都必须为true
##must not 都必须为false
##should 其中只有一个为true即可
##为true的越多则得分越高
GET /movie/_search
{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": "basketball with cartoom aliens"}},
        {"match": {"overview": "basketball with cartoom aliens"}}
      ]
    }
  }
}
##不同的词模式匹配方式:
##不同的multi_query有不同的type
##best_fields:默认的得分方式,取得最高的分数作为对应文档的对应分数,“最匹配模式” dis_max
GET /movie/_search
{
  "explain": true,
  "query": {
    "multi_match": {
      "query": "basketball with cartoom aliens",
      "fields": ["title","overview"],
      "type": "best_fields"
    }
  }
}
#most_fields考虑绝大多数的字段相加的分数
GET /movie/_search
{
  "explain": true,
  "query": {
    "multi_match": {
      "query": "basketball with cartoom aliens",
      "fields": ["title","overview"],
      "type": "most_fields"
    }
  }
}
#cross_fields以分词为单位计算栏位的总分
GET /movie/_search
{
  "explain": true,
  "query": {
    "multi_match": {
      "query": "basketball with cartoom aliens",
      "fields": ["title","overview"],
      "type": "cross_fields"
    }
  }
}

查询字符串query String,方便使用AND OR NOT

#filter过滤条件,但条件查询
GET /movie/_search
{
  "query": {
    "bool": {
      "filter":[
        {"term": {"title": "steve"}},
        {"term": {"cast.name": "gaspard"}},
        {"range": {"release_date":{"lte": "2015/01/01"}}},
        {"range": {"popularity": {"gte":"25"}}}
        ]
    }
  },
  "sort": [
    {"popularity": {"order": "desc"}}
  ]
}
##带match打分的filter
GET /movie/_search
{
  "query": {
    "bool": {
      "should": [
        {"match": {"title": "life"}}
      ], 
      "filter": [
        {"term": {"title": "steve"}},
        {"term": {"cast.name": "gaspard"}},
        {"range": {"release_date":{"lte": "2015/01/01"}}},
        {"range": {"popularity": {"gte":"25"}}}
      ]
    }
  }
}

掌握查全率和查准率

两者不可兼得,可以根据参数进行调节选择适合自己的。

查全率:正确结果有n个,查询出来正确的有m,查全率 = m/n

查准率:查出的n个文档有m个正确 m/n

点评数据的接入

中文分词器

IK分词器安装

原理:依旧需要经过字符过滤(中文的停用词)、字符处理(词典词库)、分词过滤(分词转换、词干转换)、分词转化

两种分词方式:ik_smart和ik_max_word(最大努力分词)

构建索引与查询时可以使用不同的分词方式
analyzer指的是构建索引时的分词器
search_analyzer指定的是搜索关键字时候的分词

实践中一般使用的是:构建索引使用的是ik_max_word分词算法,在查询时使用的是ik_smart分词算法

全量索引构建

使用logstash-jdbc构建全量索引

定义字段逻辑、定义字段类型、定义字段analyzer

数据库shop表

##定义门店索引结构
PUT /shop
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "id":{"type":"integer"},
      "name":{"type":"text","analyzer": "ik_max_word","search_analyzer": "ik_smart"},
      "tags":{"type": "text","analyzer": "whitespace","fielddata": true},
      "location":{"type": "geo_point"},
      "remark_score":{"type": "double"},
      "price_per_man":{"type": "integer"},
     "category_id":{"type": "integer"},
     "category_name":{"type": "keyword"},
     "seller_id":{"type": "integer"},
     "seller_remark_score":{"type": "double"},
    "seller_disabled_flag":{"type": "integer"}
    }
  }
}

logstash-input-jdbc,类似于一个管道,将mysql与ES进行数据同步

ELK:ElasticSearch、Kibana、logstash

自定义mysql与logstash的配置文件

logstash相关文件

在bin文件中创建自己的mysql文件夹以及mysql的数据配置

添加mysql相关配置

input{
    jdbc{
        #mysql数据库连接,commentdb为数据库名
        jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/commentdb"
        #用户名和密码
        jdbc_user => "root"
        jdbc_password=>"123"

        #驱动
        jdbc_driver_library => "G:\elasticsearch\logstash-7.6.2\bin\mysql\mysql-connector-java-5.1.46.jar"
        #驱动类名
        jdbc_driver_class => "com.mysql.jdbc.Driver"
        jdbc_paging_enabled => "true"
        jdbc_page_size => "50000"
        #执行sql文件路径+名称
        statement_filepath => "G:\elasticsearch\logstash-7.6.2\bin\mysql\jdbc.sql"
        #设置监听间隔,各个字段含义,分、时、天、月、年,全部设置为*表示每分钟执行一次sql文件
        schedule => "* * * * *"
    }
}

output{
    elasticsearch{
     #ES的IP地址以及端口
     hosts => ["localhost:9200"]
     #索引名称
     index => "shop"
     document_type => "_doc"
     #自增ID需要关联的数据库中有一个id字段,对应索引的id
     document_id => "%{id}"
    }
    stdout{
        #JSON格式输出
        codec => json_lines
    }
}

启动方式,cmd命令行进入到logbash的bin目录下,指定启动配置

logstash.bat -f mysql/mysql.conf

查询语句jdbc.sql

select a.id,a.name,a.tags,concat(a.latitude,',',a.longitude) as location,
a.remark_score,a.price_per_man,a.category_id,b.name as category_name,a.seller_id,
c.remark_score as seller_remark_score,c.disabled_flag as seller_disabled_flag
from shop a
inner join category b 
on a.category_id = b.category_id
inner join seller c
on a.seller_id = c.id

增量索引构建

配置文件中新增两部分,设置timezone以及指定默认的创建时间

input{
    jdbc{
        #1.增量索引构建,添加一个timezone
        jdbc_default_timezone => "Asia/Shanghai"

        #mysql数据库连接,commentdb为数据库名
        jdbc_connection_string => "jdbc:mysql://127.0.0.1:3306/commentdb"
        #用户名和密码
        jdbc_user => "root"
        jdbc_password=>"123"

        #驱动
        jdbc_driver_library => "G:\elasticsearch\logstash-7.6.2\bin\mysql\mysql-connector-java-5.1.46.jar"
        #驱动类名
        jdbc_driver_class => "com.mysql.jdbc.Driver"
        jdbc_paging_enabled => "true"
        jdbc_page_size => "50000"

        #2.增量索引构建,添加一个
        last_run_metadata_path => "G:\elasticsearch\logstash-7.6.2\bin\mysql\last_value_meta.conf"
        #执行sql文件路径+名称
        statement_filepath => "G:\elasticsearch\logstash-7.6.2\bin\mysql\jdbc.sql"
        #设置监听间隔,各个字段含义,分、时、天、月、年,全部设置为*表示每分钟执行一次sql文件
        schedule => "* * * * *"
    }
}
output{
    elasticsearch{
     #ES的IP地址以及端口
     hosts => ["localhost:9200"]
     #索引名称
     index => "shop"
     document_type => "_doc"
     #自增ID需要关联的数据库中有一个id字段,对应索引的id
     document_id => "%{id}"
    }
    stdout{
        #JSON格式输出
        codec => json_lines
    }

}

sql语句文件的更新

select a.id,a.name,a.tags,concat(a.latitude,',',a.longitude) as location,
a.remark_score,a.price_per_man,a.category_id,b.name as category_name,a.seller_id,
c.remark_score as seller_remark_score,c.disabled_flag as seller_disabled_flag
from shop a
inner join category b 
 on a.category_id = b.category_id
inner join seller c
on a.seller_id = c.id
 where a.update_at > :sql_last_value
or b.update_at > :sql_last_value
or c.update_at > :sql_last_value 

启动出现错误,文件不能读取

image-20201003092640148

该错误是将mysql的连接工具的版本写错,修改与文件夹下的连接jar包一致的版本

简单的查询测试,在ES中的索引名为shop

//根据距离进行排序
GET /shop/_search
{
  "query": {
    "match": {
      "name": "凯悦"
    }
  },
  "_source": "*",
  "script_fields": {
    "distance": {
      "script": {
        "source": "haversin(lat,lon,doc['location'].lat,doc['location'].lon)",
        "lang": "expression",
        "params": {"lat":31.37,"lon":127.12}
      }
    }
  },
  "sort": [
    {
      "_geo_distance": {
        "location":{
          "lat": 31.37,
          "lon": 127.12
        },
        "order": "asc",
        "unit": "km",
        "distance_type": "arc"
      }
    }
  ]
}

Java与ES的接入

方式一:Node接入Node Client

将java应用程序也作为一个Node节点,作为集群中节点的一部分。

方式二:Transport接入transport client

作为一种客户端通信方式进行与ES服务器交互。

方式三:Http接入rest client

rest风格是一种基于http的一种数据访问的方式

引用进阶

定制化分词器

扩展词库
同义词

相关搜索重塑


文章作者: it星
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 it星 !
 上一篇
java操作excel java操作excel
it星
java操作excel 使用apache提供的工具POI操作Excel1.Maven的依赖 <dependency> <groupId>org.apache.poi</groupId
2020-04-07
下一篇 
mysql知识点学习 mysql知识点学习
mysql的一些概念与面试常见问题,烦人(小声bb)。 Mysql常见的问题索引相当于一个字典,存放一个文件及在文件中的偏移量。索引存在的位置是存放在磁盘位置。基本概念:局部性原理(时间局部性和空间局部性)和磁盘预读(以页为单位) 1.my
2020-03-30
  目录