需求分析
(尚未完善)
业务需求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)
数据获取与导入数据
掌握实际应用中的索引建立和数据导入过程
//删除已有的索引数据
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的信息
2.修改自己的csv文件的地址(个人使用的绝对地址)
直接访问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_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的配置文件
在bin文件中创建自己的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
启动出现错误,文件不能读取
该错误是将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的一种数据访问的方式