导入商品数据到索引库-dao层、使用solrj来测试索引库、导入商品数据-service层、实现从数据库导入数据到索引库

导入商品数据到索引库-dao层

我们先来看看我们要导入数据的sql语句并且查看查询结果。

在这里插入图片描述
select a.id,a.title,a.sell_point,a.price,a.image,b.name item_category_name,c.item_desc
from tb_item a
left join tb_item_cat b on a.cid=b.id
left join tb_item_desc c on a.id=c.item_id
where a.status=1

针对来自三张表的数据,我们最好使用一个pojo来接收这些数据,而且这个pojo还会作为查询结果的载体,因此服务层和表现层都会用到这个pojo,我们最好把它放到taotao-common工程的pojo目录下。我们新建SearchItem类(记得要实现序列化,因为要进行网络传输)。如下图所示。
在这里插入图片描述

private String id;
private String title;
private String sell_point;
private long price;
private String image;
private String item_category_name;
private String item_desc;

下面解释下为何id字段类型为string,我们到/usr/local/solr/solrhome/collection1/conf目录下,打开schema.xml,搜索<field name=“id"就可以找到如下图所示的这一行,可以看到id的类型为"string”。数据库中id字段的类型是long,为了避免long和string转换带来的问题,我们就使用string类型。
在这里插入图片描述

price字段类型之所以定义为long型是为了避免使用浮点数类型(float),价格精确到分,也就是由原来的以元单位的价格乘以100倍(数据库中的价格存储的都是以分为单位的价格)。

由于我们要导入的数据来自于三张表,用逆向工程生产成的代码已经解决不了问题了,需要我们手动来写Mapper文件。那么Mapper文件我们应该放到哪儿呢?可能有人认为应该放到taotao-manager-dao工程下,但是这其实是不太合理的,我们搜索服务只是引用taotao-manager-dao工程的一些东西,搜索服务的这个操作非常特例,别的工程都用不着,因此我们放到taotao-manager-dao工程不太合适。我们直接放到taotao-search-service工程下更合适。

我们在taotao-search-service工程下新建一个"com.taotao.search.mapper"包,并在该包下新建SearchItemMapper接口类,在接口中添加一个接口getSearchItemList,如下图所示。
在这里插入图片描述

我们把taotao-manager-dao工程下的某个mapper.xml文件拷贝一份到com.taotao.search.mapper包下,留下头部和其余的删掉,在mapper中定义一个sql语句。id为接口的名字,resultType是我们定义的pojo类的全类名。
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.taotao.search.mapper.SearchItemMapper" >
  <select id="getSearchItemList" resultType="com.taotao.common.pojo.SearchItem">
  	SELECT
		a.id,
		a.title,
		a.sell_point,
		a.price,
		a.image,
		b. NAME item_category_name,
		c.item_desc
	FROM
		tb_item a
	LEFT JOIN tb_item_cat b ON a.cid = b.id
	LEFT JOIN tb_item_desc c ON a.id = c.item_id
	WHERE
		a.`status` = 1
  </select>
</mapper>

使用solrj来测试索引库

我们使用solrj来操作solr服务,一般习惯先建一个测试类测试下增删改查方法是否好使,这样可以大大减少出错概率,提升开发效率。

我们的taotao-search-service工程还没有添加对solrj的依赖,因此需要先添加对solrj的依赖,在taotao-search-service工程的pom.xml文件当中添加如下依赖(之所以不用写版本号是因为在taotao-parent工程已经统一定义版本号了)

<!-- solr客户端 -->
	<dependency>
		<groupId>org.apache.solr</groupId>
		<artifactId>solr-solrj</artifactId>
	</dependency>

下面我们新建一个测试类TestSolrJ,如下图所示。先测试添加文档操作。注意:参加的字段必须是schema.xml中定义的。
在这里插入图片描述

package com.taotao.solrj;
 
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.common.SolrInputDocument;
import org.junit.Test;
 
public class TestSolrJ {
    
	@Test
	public void testAddDocument() throws Exception{
		//创建一个SolrServer对象,创建一个HttpSolrServer对象,需要指定solr服务的url
		//如果有多个collection则需要指定要操作哪个collection,如果只有一个,可以不指定
		SolrServer solrServer = new HttpSolrServer("http://192.168.156.22:8080/solr/collection1");
		//创建一个文档对象SolrInputDocument
		SolrInputDocument document = new SolrInputDocument();
		//向文档中添加域,必须有id域,域的名称必须在schema.xml中定义
		document.addField("id", "1111");
		document.addField("item_title", "海尔空调");
		document.addField("item_sell_point", "送电暖宝一个哦");
		document.addField("item_price", 10000);
		document.addField("item_image", "http://www.123.jpg");
		document.addField("item_category_name", "电器");
		document.addField("item_desc", "这是一款最新的空调,质量好,值得信赖!!");
		//将document添加到索引库
		solrServer.add(document);
		//提交
		solrServer.commit();
	}
}

执行上面的方法,成功后,我们到Solr首页,点击"Execute Query"按钮,即可查询到我们刚才添加的文本。
在这里插入图片描述

大家发现了没有,在查询出来的数据当中没有item_desc字段,这是因为在schema.xml文件中指定该字段不保存,如下图所示,indexed="true"表示分词存储,可以查询stored="false"表示不存储内容。stored="false"该字段就不会随查询结果一起显示,stored="true"才会显示。

下面我们来测试通过ID删除文档

@Test
public void testDeleteDocument() throws Exception{
//创建一个SolrServer对象,创建一个HttpSolrServer对象,需要指定solr服务的url
SolrServer solrServer = new HttpSolrServer(“http://192.168.156.22:8080/solr/collection1”);
//通过id来删除文档
solrServer.deleteById(“1111”);
//提交
solrServer.commit();
}

删除完之后,我们再查询,发现已经没有刚才我们添加的那个文档了,如下图所示。
在这里插入图片描述

下面我们再测试另外一种删除方法,由于刚才我们把仅存的一条文档删除了,现在我们再添加两条数据,如下图所示。
在这里插入图片描述

通过搜索删除代码如下

   @Test
    	public void deleteDocumentByQuery() throws Exception{
    		//创建一个SolrServer对象,创建一个HttpSolrServer对象,需要指定solr服务的url
    		SolrServer solrServer = new HttpSolrServer("http://192.168.156.22:8080/solr/collection1");
    		//通过价格来删除文档
    		solrServer.deleteByQuery("item_price:20000");
    		//提交
    		solrServer.commit();
    	}

删除后,我们再查询,发现已经没有价格为2000的那个文档记录了,如下图所示。
在这里插入图片描述

至于修改操作其实就是添加操作,ID一样的,新记录会覆盖老记录,从而达到修改的目的

下面我们进行查询操作,如下所示。

@Test
public void queryDocument() throws Exception{
	//创建一个SolrServer对象,创建一个HttpSolrServer对象,需要指定solr服务的url
	SolrServer solrServer = new HttpSolrServer("http://192.168.156.22:8080/solr/collection1");
	//通过id来删除文档
	SolrQuery query = new SolrQuery();
	query.setQuery("id:222");
	QueryResponse response = solrServer.query(query);
	SolrDocumentList list = response.getResults();
	for(SolrDocument document : list){
		String id = document.getFieldValue("id").toString();
		String title = document.getFieldValue("item_title").toString();
		System.out.println(id);
		System.out.println(title);
	}
}

导入商品数据-service层

上节课我们一起学习了使用Solrj来操作索引库。这节我们一起来学习下Service层代码编写。

首先,在taotao-search-interface工程新建一个接口
SearchItemService.java

接着在taotao-search-service工程新建实现类SearchItemServiceImpl,实现SearchItemService接口。

在代码中要使用SearchItemMapper,Spring容器需要能够管理它才行,我们到applicationContext-dao.xml文件当中,原来的扫描范围是com.taotao.mapper,而我们的Mapper文件所在的包是com.taotao.search.mapper,因此需要增加一个扫描的范围,添加方式是以","分隔

在实现类中还用到了SolrServer,而默认Spring是没有管理这个类的,因此我们需要配置一下。我们单独建个applicationContext-solr.xml文件来管理。

写完服务,下面要做的便是发布服务,发布配置:<dubbo:service interface=“com.taotao.search.service.SearchItemService” ref=“searchItemServiceImpl” timeout=“300000”/>

这样,Service层我们就写完了。

实现从数据库导入数据到索引库

错误1:
一般来说报Could not resolve placeholder ‘xxx’ in string value “${xxx}” 错误的原因都是由于项目中有多个地方的xml文件中用到了
<context:property-placeholder location=“classpath:xxx.properties”/>
这种问题 在网上查的是说使用了重复的property-placeholder 可能是在别的xml 也用了property-placeholder
解决方法 加上 ignore-unresolvable=“true”
Attribute : ignore-unresolvable
Specifies if failure to find the property value to replace a key should be ignored. Default is
“false”, meaning that this placeholder configurer will raise an exception if it cannot resolve a
key. Set to “true” to allow the configurer to pass on the key to any others in the context that
have not yet visited the key in question.
大概意思是 开启后 允许配置程序将密钥传递给上下文中的任何其他配置程序

错误2:
在这里插入图片描述
明明我在maven配置中修改了tomcat插件的端口号为8084,但是运行的时候还是在8080运行了

查到了maven tomcat启动时修改默认端口
tomcat默认端口是8080,如果想修改默认端口,可以有两种方法:
1.在pom.xml里修改

<!-- 配置Tomcat插件 -->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <port>80</port>
                    <!-- http://127.0.0.1:{port}/{path} -->
                    <path>/</path>
                </configuration>
            </plugin>

那么在MAVEN 的run/debug configurations 中Goals填写tomcat7:run 启动的时候就会指定TOMCAT的端口是80了。
需要注意的一点就是在Goals中,如果填写的是tomcat:run ,那么启动时候的端口不会是80的端口,而是MAVEN默认的TOMCAT(8080)

2.在Run Configurations修改启动方式,加上-Dmaven.tomcat.port=端口号 tomcat7:run
tomcat有两种插件,tomcat-maven-plugin 和tomcat7-maven-plugin ,当用tomcat插件,Goals里用tomcat:run,如果用的tomcat7插件,Goals则用tomcat7:run,这两种不能混用,不然在pom.xml改了默认端口就不会生效
在这里插入图片描述
为什么maven pom中插件配置了tomcat端口号是8084,还是在8080运行了?

在这里插入图片描述

在这里插入图片描述
Dubbo服务调用Failed to invoke the method错误记录

在开发过程中我遇到一个问题:
一个多模块项目,服务与应用之间采用dubbo进行调用,启动服务后用浏览器访问一切都好,但当采用fiddler进行模拟外系统请求时却死活调不通,报错如下:
错误原因:
com.alibaba.dubbo.rpc.RpcException: Failed to invoke the method importItemsToIndex in the service com.taotao.search.service.SearchItemService. Tried 3 times of the providers [192.168.173.1:20882] (1/1) from the registry 192.168.173.148:2181 on the consumer 192.168.173.1 using the dubbo version 2.5.3. Last error is: Invoke remote method timeout. method: importItemsToIndex, provider: dubbo://192.168.173.1:20882/com.taotao.search.service.SearchItemService?anyhost=true&application=taotao-manager-web&check=false&dubbo=2.5.3&interface=com.taotao.search.service.SearchItemService&methods=importItemsToIndex&pid=22815&revision=0.0.1-SNAPSHOT&side=consumer&timestamp=1567302714919, cause: Waiting server-side response timeout. start time: 2019-09-01 09:52:10.141, end time: 2019-09-01 09:52:11.145, client elapsed: 0 ms, server elapsed: 1004 ms, timeout: 1000 ms, request: Request [id=3, version=2.0.0, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=importItemsToIndex, parameterTypes=[], arguments=[], attachments={path=com.taotao.search.service.SearchItemService, interface=com.taotao.search.service.SearchItemService, version=0.0.0}]], channel: /192.168.173.1:60481 -> /192.168.173.1:20882

Caused by: com.alibaba.dubbo.remoting.TimeoutException: Waiting server-side response timeout. start time: 2019-09-01 09:52:10.141, end time: 2019-09-01 09:52:11.145, client elapsed: 0 ms, server elapsed: 1004 ms, timeout: 1000 ms, request: Request [id=3, version=2.0.0, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=importItemsToIndex, parameterTypes=[], arguments=[], attachments={path=com.taotao.search.service.SearchItemService, interface=com.taotao.search.service.SearchItemService, version=0.0.0}]], channel: /192.168.173.1:60481 -> /192.168.173.1:20882

2019-09-01 09:52:28,116 [DubboClientHandler-192.168.173.1:20882-thread-1] [com.alibaba.dubbo.remoting.exchange.support.DefaultFuture]-[WARN] [DUBBO] The timeout response finally returned at 2019-09-01 09:52:28.115, response Response [id=1, version=null, status=20, event=false, error=null, result=RpcResult [result=com.taotao.common.pojo.TaotaoResult@5f61b63f, exception=null]], channel: /192.168.173.1:60481 -> /192.168.173.1:20882, dubbo version: 2.5.3, current host: 192.168.173.1

原因分析
客户端日志可以看到是超时错误。
接着给出信息:客户端、服务端花费的时间;
在看服务端,是通道被关闭,无法发送信息。
整体看起来,就是超时了,客户端没有得到返回信息,服务端不能发送信息。
是超时的配置
检查客户端、服务端的超时配置;发现客户端的超时时间没有设置,设置为一个较长时间,就可以了。
超时设置方法:Dubbo超时设置方法
超时机制

Dubbo是阿里开源的分布式远程调用方案(RPC),由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间。

Provider可以配置的Consumer端主要属性有timeout、retries、loadbalance、actives和cluster。Provider上应尽量多配置些Consumer端的属性,让Provider实现者一开始就思考Provider的服务特点与服务质量。配置之间存在着覆盖,具体规则如下:

  1. 方法级配置别优于接口级别,即小Scope优先
  2. Consumer端配置优于Provider配置,优于全局配置
  3. Dubbo Hard Code的配置值(默认)

根据规则2,纵使消费端配置优于服务端配置,但消费端配置超时时间不能随心所欲,需要根据业务实际情况来设定。如果超时时间设置得太短,复杂业务本来就需要很长时间完成,服务端无法在设定的超时时间内完成业务处理;如果超时时间设置太长,会由于服务端或者网络问题导致客户端资源大量线程挂起。

超时配置

Dubbo消费端
全局超时配置
<dubbo:consumer timeout=“5000” />

指定接口以及特定方法超时配置
<dubbo:reference interface=“com.foo.BarService” timeout=“2000”>
<dubbo:method name=“sayHello” timeout=“3000” />
</dubbo:reference>

Dubbo服务端
全局超时配置

<dubbo:provider timeout=“5000” />

指定接口以及特定方法超时配置
<dubbo:provider interface=“com.foo.BarService” timeout=“2000”>
<dubbo:method name=“sayHello” timeout=“3000” />
</dubbo:provider>

Dubbo协议超时实现

Dubbo协议超时实现使用了Future模式,主要涉及类DubboInvoker,ResponseFuture, DefaultFuture。
ResponseFuture.get()在请求还未处理完或未到超时前一直是wait状态;响应达到后,设置请求状态,并进行notify唤醒。

public Object get() throws RemotingException {
return get(timeout);
}

public Object get(int timeout) throws RemotingException {
    if (timeout <= 0) {
        timeout = Constants.DEFAULT_TIMEOUT;
    }
    if (! isDone()) {
        long start = System.currentTimeMillis();
        lock.lock();
        try {
            while (! isDone()) {
                done.await(timeout, TimeUnit.MILLISECONDS);
                if (isDone() || System.currentTimeMillis() - start > timeout) {
                    break;
                }
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
        if (! isDone()) {
            throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));
        }
    }
    return returnFromResponse();
}

public static void received(Channel channel, Response response) {
try {
DefaultFuture future = FUTURES.remove(response.getId());
if (future != null) {
future.doReceived(response);
} else {
logger.warn("The timeout response finally returned at "
+ (new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss.SSS”).format(new Date()))
+ ", response " + response
+ (channel == null ? “” : ", channel: " + channel.getLocalAddress()
+ " -> " + channel.getRemoteAddress()));
}
} finally {
CHANNELS.remove(response.getId());
}
}

private void doReceived(Response res) {
    lock.lock();
    try {
        response = res;
        if (done != null) {
            done.signal();
        }
    } finally {
        lock.unlock();
    }
    if (callback != null) {
        invokeCallback(callback);
    }
}

所以这里我把超时时间设置的足够大。给他足够的时间进行导入,从而得以导入成功
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值