20 October 2018

最近折腾了一下从零开始在金山云上搭建一个搜索下拉推荐,这里记录一下相关的流程,感谢未来的架构师丁神提供的咨询服务。

使用的数据是github上的bestbuy的产品数据大概4W+,和丁神讨论一下架构,本身数据量很小,其实直接上mysql就可以,因为是个demo为了扩展性,最终定下来的架构是nginx(用来承载搜索框静态页)+ flask(提供搜索建议的web服务)+ es(后端的数据查询)+ redis(用于缓存查询结果)

搭建相关环境

首先通过ssh登录服务器

ssh root@xxx.xxx.xxx.xxx

安装python3

一般linux服务器上都有安装python,不过都是python2版本,因为马上都要退休了,所以安装一个python3

我用的这台机器上yum,直接用yum安装python36


yum install python36

安装完成后可以使用python36启动python3

这里有一个问题是,yum安装的python36没有安装pip,使用pip提示没有这个命令

需要手动安装一个pip

首先下载去 https://pip.pypa.io/en/stable/installing/

wget https://bootstrap.pypa.io/get-pip.py

运行安装pip

python36 get-pip.py

安装ipython,pandas,flask,redis,Elasticsearch

pip install ipython
pip install pandas
pip install flask
pip install redis
pip install Elasticsearch

python的基本环境可以了

安装jupyter notebook

这里推荐装一下jupyter notebook 并配置外网访问,这样就可以抛弃shell直接用notebook的web shell

具体的流程可以看一下:http://cloga.info/python/2017/05/13/efficiency-tool

这里只说一下大概:

安装jupyter

pip install jupyter

生成配置文件

jupyter notebook --generate-config

打开ipython生成密文的密码

from notebook.auth import passwd

passwd()

# 'sha1:xxxxxxxxxxxxxxxxxxxxxxxxxx'

记录sha1:xxxxxxxxxxxxxxxxxxxxxxxxxx

修改配置文件

vim ~/.jupyter/jupyter_notebook_config.py

找到以下的部分

# 174
c.NotebookApp.ip='*'
# 222
c.NotebookApp.open_browser = False
# 229
c.NotebookApp.password = u'sha:ce...刚才复制的那个密文'
# 240
c.NotebookApp.port =8888 #随便指定一个端口

代码上面的数字代表大概的行数,记得去掉注释符号

好了,可以通过浏览器访问jupyter了,不过在此之前,再推荐两个jupyter的插件。

nbextensions,jupyter notebook扩展,可以有很多有用扩展

jupyter-themesjupyter notebook的主题,改变之前性冷淡的样式。

pip install jupyter_contrib_nbextensions
jupyter contrib nbextension install --user
pip install jupyterthemes
# 安装后可以查看安装的主题
jt -l
# 可以指定切换到主题
jt -t monokai

好了 让我们启动一下jupyter notebook,使用nohup避免程序中断

nohup jupyter notebook& 

如果是root用户需要增加 –allow-root后缀

nohup jupyter notebook --allow-root&

好了 可以访问jupyter来编辑和测试代码了

记得在金山云的虚机上开启对应端口,jupyter notebook为8888

nginx承载静态页host

安装nginx

yum install nginx

修改nginx配置,默认是ipv6的版本,金山云只支持ip v4

cd /etc/nginx
vim nginx.config
# 修改39行的数据,删除default server,注释到40行
cd /usr/share/nginx/html

将其中index.html文件修改为需要的autocomplete

      $("#search-input").autocomplete({
        source:function(req, rep) {
		$.ajax({
			url : "http://xxx.xx.xx.xx:5000/query?q="+encodeURIComponent($("#search-input").val()), /*这里是flask提供的json数据的地址*/
			type : "get",
			dataType: "json",
			success: function(data){
				console.log(data);
				rep(data);
			}
		});
	}
      });

主要是修改这个部分,获取搜索建议的ajax数据

页面上方要引入jquery和jquery_ui

页面的其余内容可以参考:https://github.com/algolia/autocomplete.js/blob/master/examples/basic.html

数据处理并存入es

源数据来自这里,https://github.com/BestBuyAPIs/open-data-set ,使用product那个json就可以

创建一个data目录,把数据wget进去

mkdir data
cd data

处理一下数据

这里要注意一下目前使用的是pytho36,因此要在脚本前增加python36的地址,查看的命令为

which python36
# usr/bin/python36

数据处理和写入es使用python脚本


#!/usr/bin/python36

# 处理json数据

import json

import pandas as pd


f = open("products.json", encoding='utf-8')

data = json.load(f)

data_df = pd.DataFrame(data)

data_df = data_df[~data_df['name'].isnull()]

# 只有商品名有用其他字段不需要

data_df = data_df[['name']]

data_df = data_df.drop_duplicates()

# 存入es

from elasticsearch import Elasticsearch

# 配置es的接口地址,由于是用到是金山云提供的es,因此es不需要配置
es = Elasticsearch(['xx.xx.xx.xx:9200', 'xx.xx.xx.xx:9200', 'xx.xx.xx.xx:9200'], sniff_on_start=True)

for record in data_df.to_dict('records'):
    es.index( index="product_list", doc_type="product_name", body=record )

运行脚本写入数据

python36 data_process.py

如果报授权错误,可能需要关闭x-pack

redis的基本命令

安装redis 客户端

yum install redis

访问远程redis

redis-cli -h 192.168.1.103 -p 6379 

# 查看所有key
keys *

# 清空所有key
FLUSHALL

使用金山云的redis需要为访问的ip添加白名单

搜索下拉建议web服务,使用redis缓存结果

创建suggestions.py,根据搜索内容,返回最相近的相近产品名称

#!/usr/bin/python36

from flask import Flask
from flask import request
import json
import pandas as pd
from flask import make_response
import redis
from elasticsearch import Elasticsearch
# 联接es, 金山云提供现成的paas服务
es = Elasticsearch(['xx.x.x.xx:9200', 'xx.x.x.xx:9200', 'xx.x.x.x:9200'], sniff_on_start=True)
# 联接es, 金山云提供现成的paas服务
r = redis.Redis(host='xx.x.x.xxx', port=6379, decode_responses=True) 

app = Flask(__name__)

@app.route('/query', methods=['POST', 'GET'])
def get_suggestions_es_redis():
    # redis缓存有从redis取
    query = request.args.get('q')
    suggestions = r.get(query)
    if suggestions is None:
        # redis缓存中没有则去es中查询
        # es部分搜索使用统配符模式,默认的match是单词匹配
        query_name_contains = {'query': {'wildcard': {'name': '*' + query + '*'}}}
        suggestions = es.search(index="product_list", doc_type="product_name", body=query_name_contains)
        suggestions = [s['_source']['name'] for s in suggestions['hits']['hits']]
        suggestions = json.dumps(suggestions)
        r.set(query, suggestions) 
    if suggestions is not None:
        rst = make_response(suggestions)
    else:
        rst = make_response('')
    # 如果跨域需要加这句
    rst.headers['Access-Control-Allow-Origin'] = '*'
    return rst

在suggestions.py所在目录启动flask服务,否则会报路径错误。

export FLASK_APP=suggestions.py
# 开启debug模式
export FLASK_DEBUG=1
nohup flask run --host=0.0.0.0&

flask默认的端口为5000,flask的地址需要填入静态页面ajax的url部分,记得开启端口

es搜索的基本知识

match是单词匹配,因为这个demo中需要的是部分匹配所以使用通配符模式。

至此一个从0开始的搜索下拉框建议就搭建完成了,同时这个demo也具有一定的扩展性,可以在前面增加load banlance或者在后端增加es的集群来进行扩展



分享到: 更多
友荐云推荐