搜索系统的实现
搜索服务发布
调用服务传递过来一个查询条件,根据查询条件进行查询。返回查询结果。参数中包括分页条件。
参数:
String queryString
Int page
Int rows
返回结果:返回json数据。
包含查询结果的列表。使用商品的pojo来描述。SearchItem
包含查询结果总记录数。
包含查询结果的总页数。
包含当前页码。
包含查询的状态。
包含错误信息。
创建一个SearchResult
包含四个属性:
- 商品列表
- 查询结果总记录数
- 查询结果的总页数
- 当前页码
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class SearchResult { private List<SearchItem> itemList; private Long recordCount; private int pageCount; private int curPage; } |
使用TaotaoResult包装一个SearchResult返回结果。
Dao层
根据查询条件进行查询,返回查询结果。
参数:SolrQuery对象
返回结果:
- 查询结果的商品列表
- 查询结果的总记录数
返回SearchResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
@Repository public class SearchDaoImpl implements SearchDao { @Autowired private SolrServer solrServer; @Override public SearchResult search(SolrQuery query) throws Exception { //执行查询 QueryResponse response = solrServer.query(query); //取查询结果列表 SolrDocumentList solrDocumentList = response.getResults(); List<SearchItem> itemList = new ArrayList<>(); for (SolrDocument solrDocument : solrDocumentList) { //创建一个SearchItem对象 SearchItem item = new SearchItem(); item.setCategory_name((String) solrDocument.get("item_category_name")); item.setId((String) solrDocument.get("id")); item.setImage((String) solrDocument.get("item_image")); item.setPrice((Long) solrDocument.get("item_price")); item.setSell_point((String) solrDocument.get("item_sell_point")); //取高亮显示 Map<String, Map<String, List<String>>> highlighting = response.getHighlighting(); List<String> list = highlighting.get(solrDocument.get("id")).get("item_title"); String itemTitle = ""; if (list != null && list.size() > 0) { //取高亮后的结果 itemTitle = list.get(0); } else { itemTitle = (String) solrDocument.get("item_title"); } item.setTitle(itemTitle); //添加到列表 itemList.add(item); } SearchResult result = new SearchResult(); result.setItemList(itemList); //查询结果总数量 result.setRecordCount(solrDocumentList.getNumFound()); return result; } } |
Service层
- 接收查询条件、分页条件。
- 创建SolrQuery对象,设置查询条件、分页条件。
- 调用dao进行搜索
- 计算总页数,把总页数设置到SearchResult对象中,设置当前页属性。
- 返回SearchResult
参数:
- 查询条件
- Page
- Rows
返回结果:
SearchResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
@Service public class SearchServiceImpl implements SearchService { @Autowired private SearchDao searchDao; @Override public SearchResult search(String queryString, int page, int rows) throws Exception { //创建查询条件 SolrQuery query = new SolrQuery(); //设置查询条件 query.setQuery(queryString); //设置分页条件 query.setStart((page-1)*rows); query.setRows(rows); //设置默认搜索域 query.set("df", "item_title"); //设置高亮 query.setHighlight(true); query.addHighlightField("item_title"); query.setHighlightSimplePre("<font class=\"skcolor_ljg\">"); query.setHighlightSimplePost("</font>"); //执行查询 SearchResult searchResult = searchDao.search(query); //计算总页数 Long recordCount = searchResult.getRecordCount(); int pageCount = (int) (recordCount / rows); if (recordCount % rows > 0) { pageCount++; } searchResult.setPageCount(pageCount); searchResult.setCurPage(page); return searchResult; } } |
发布服务。Controller层
搜索服务的url:/search/q?keyword=xxx&page=1&rows=30
参数keyword、page、rows
返回结果:json数据,使用TaotaoResult包装SearchResult。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
@Controller public class SearchController { @Autowired private SearchService searchService; @RequestMapping("/q") @ResponseBody public TaotaoResult search(@RequestParam(defaultValue="")String keyword, @RequestParam(defaultValue="1")Integer page, @RequestParam(defaultValue="30")Integer rows) { try { //转换字符集 keyword = new String(keyword.getBytes("iso8859-1"), "utf-8"); SearchResult searchResult = searchService.search(keyword, page, rows); return TaotaoResult.ok(searchResult); } catch (Exception e) { e.printStackTrace(); return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e)); } } } |
分析在portal中实现搜索
调用taotao-search发布的服务,实现搜索。使用HttpClient调用服务。返回json数据。需要把json转换成java对象。把java对象传递给页面。
请求的url:http://localhost:8082/search.html
参数:q:查询条件
返回结果:jsp页面(search.jsp)
Search.jsp分析:
数据:
Query:查询条件
totalPages:总页数
itemList:商品列表(每个元素可以是SearchItem)
Page:当前页
Service层
参数:查询条件、page、rows。
根据查询调用taotao-search发布的服务,查询商品列表。得到json数据,需要把json转换成java对象,返回SearchResult。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
@Service public class SearchServiceImpl implements SearchService { @Value("${SEARCH_BASE_URL}") private String SEARCH_BASE_URL; @Override public SearchResult search(String keyword, int page, int rows) { //调用服务查询商品列表 Map<String, String> param = new HashMap<>(); param.put("keyword", keyword); param.put("page", page + ""); param.put("rows", rows + ""); //调用服务 String json = HttpClientUtil.doGet("SEARCH_BASE_URL", param); //转换成java对象 TaotaoResult taotaoResult = TaotaoResult.formatToPojo(json, SearchResult.class); //取返回的结果 SearchResult searchResult = (SearchResult) taotaoResult.getData(); return searchResult; } } |
接收三个参数:查询条件、page、rowsController
调用服务查询商品列表。
把商品列表传递给jsp、参数回显。
返回逻辑视图(search.jsp)
请求的url:/search
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
@Controller public class SearchController { @Autowired private SearchService searchService; @RequestMapping("/search") public String search(@RequestParam("q")String keyword, @RequestParam(defaultValue="1")Integer page, @RequestParam(defaultValue="60")Integer rows, Model model) { //get乱码处理 try { keyword = new String(keyword.getBytes("iso8859-1"), "utf-8"); } catch (UnsupportedEncodingException e) { keyword = ""; e.printStackTrace(); } SearchResult searchResult = searchService.search(keyword, page, rows); //参数传递 给页面 model.addAttribute("query", keyword); model.addAttribute("totalPages", searchResult.getPageCount()); model.addAttribute("itemList", searchResult.getItemList()); model.addAttribute("page", searchResult.getCurPage()); //返回逻辑视图 return "search"; } } |
修改ItemSearch:解决图片显示不出来的问题:
Solr集群
Solr集群的架构
SolrCloud
需要用到solr+zookeeper
要完成的集群结构
Zookeeper
- 集群管理
主从的管理、负载均衡、高可用的管理。集群的入口。Zookeeper必须是集群才能保证高可用。Zookeeper有选举和投票的机制。集群中至少应该有三个节点。
- 配置文件的集中管理
搭建solr集群时,需要把Solr的配置文件上传zookeeper,让zookeeper统一管理。每个节点都到zookeeper上取配置文件。
- 分布式锁
- 忘了
集群需要的服务器
Zookeeper:3台
Solr:4台
伪分布式,zookeeper三个实例、tomcat(solr)需要四个实例。
Zookeeper需要安装jdk。
集群搭建步骤
第一部分:Zookeeper集群搭建
第一步:需要把zookeeper的安装包上传到服务器。
第二步:把zookeeper解压。
第三步:把zookeeper向/usr/local/solr-cloud目录下复制三份。
第三步:配置zookeeper。
- 在zookeeper01目录下创建一个data文件夹。
- 在data目录下创建一个myid的文件
- Myid的内容为1(02对应“2”,03对应“3”)
- Zookeeper02、03以此类推。
- 进入conf文件,把zoo_sample.cfg文件改名为zoo.cfg
- 修改zoo.cfg,把dataDir=属性指定为刚创建的data文件夹。
- 修改zoo.cfg,把clientPort指定为不冲突的端口号(01:2181、02:2182、03:2183)
- 在zoo.cfg中添加如下内容:
server.1=192.168.25.154:2881:3881
server.2=192.168.25.154:2882:3882
server.3=192.168.25.154:2883:3883
第四步:启动zookeeper。
Zookeeper的目录下有一个bin目录。使用zkServer.sh启动zookeeper服务。
启动:./zkServer.sh start
关闭:./zkServer.sh stop
查看服务状态:./zkServer.sh status
第二部分:搭建solr集群
第一步:安装四个tomcat,修改其端口号不能冲突。8080~8083
第二步:向tomcat下部署solr。把单机版的solr工程复制到tomcat下即可。
第三步:为每个solr实例创建一solrhome。
第四步:为每个solr实例关联对应的solrhome。修改web.xml
第五步:修改每个solrhome下的solr.xml文件。修改host、hostPort两个属性。分别是对应的ip及端口号。
第六步:把配置文件上传到zookeeper。需要使用
/root/solr-4.10.3/example/scripts/cloud-scripts/zkcli.sh命令上传配置文件。
把/usr/local/solr-cloud/solrhome01/collection1/conf目录上传到zookeeper。
需要zookeeper集群已经启动。
使用zookeeper的zkCli.sh命令。
1 2 3 |
./zkcli.sh -zkhost 192.168.25.154:2181,192.168.25.154:2182,192.168.25.154:2183 -cmd upconfig -confdir /usr/local/solr-cloud/solrhome01/collection1/conf -confname myconf |
第八步:告诉solr实例zookeeper的位置。需要修改tomcat的catalina.sh添加
JAVA_OPTS=”-DzkHost=192.168.25.154:2181,192.168.25.154:2182,192.168.25.154:2183″
每个节点都需要添加。
第九步:启动每个solr实例。
第十步:集群分片。
将集群分为两片,每片两个副本。
http://192.168.25.154:8080/solr/admin/collections?action=CREATE&name=collection2&numShards=2&replicationFactor=2
第十一步:删除不用collection1
http://192.168.25.154:8080/solr/admin/collections?action=DELETE&name=collection1
使用solrJ连接集群
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@Test public void testSolrClout() throws Exception { //创建一个SolrServer对象 CloudSolrServer solrServer = new CloudSolrServer("192.168.25.154:2181,192.168.25.154:2182,192.168.25.154:2183"); //设置默认的collection solrServer.setDefaultCollection("collection2"); //创建一个文档对象 SolrInputDocument document = new SolrInputDocument(); document.addField("id", "test01"); document.addField("item_title", "title1"); //添加文档 solrServer.add(document); //提交 solrServer.commit(); } |