3 索引库数据管理
Elasticsearch基础学习: 学成在线-课程预览及课程发布 Elasticsearch研究v1.2
秒杀商品数量庞大,我们要想实现快速检索,不建议直接使用关系型数据库查找。不建议使用Redis缓存所有数据,因为秒杀商品量大,会影响Redis的性能,并且Redis的条件检索能力偏弱。我们可以使用Elasticsearch,它在海量数据存储与检索上,能力卓越,市场使用面广。
3.1 批量导入
我们需要将秒杀商品数据导入到ES索引库中,但秒杀商品数量庞大,所以我们应该分页查询并导入,流程如下:
1)service总数量查询
我们先在seckill-goods
中编写相关方法实现数据查询,因为要用到分页,所以先查询总数量,然后再实现分页集合查询。
在seckill-goods
的com.seckill.goods.service.SkuService
中添加count方法,用于查询秒杀商品总数量:
/**
* 总数量加载 *
@return
*/
Integer count();
在seckill-goods
的com.seckill.goods.service.impl.SkuServiceImpl
中添加count方法,用于实现查询秒杀商品总数量:
/**
* 总数量加载
* @return
* */
@Override
public Integer count() {
Example example = new Example(Sku.class);
Example.Criteria criteria = example.createCriteria();
//秒杀剩余商品数量>0
criteria.andGreaterThan("seckillNum",0);
//状态为参与秒杀,1:普通商品,2:参与秒杀
criteria.andEqualTo("status","2");
//秒杀结束时间>=当前时间
criteria.andGreaterThanOrEqualTo("seckillEnd",new Date());
return skuMapper.selectCountByExample(example); }
在seckill-goods
的com.seckill.goods.controller.SkuController
中添加count方法,用于实现查询秒杀商品总数量:
/**
* *
* Sku数量加载
* @return */
@PostMapping(value = "/count" )
public Integer count()
{
return skuService.count();
}
2)service分页集合数据查询
在seckill-goods
的com.seckill.goods.service.SkuService
中添加list方法,用于查询秒杀商品:
/**
*
* 分页加载 *
@param page *
@param size *
@return
*/
List<Sku> list(int page, int size);
在seckill-goods
的com.seckill.goods.service.impl.SkuServiceImpl
中添加list方法,用于实现查询秒杀商品:
/*** * 分页加载 * @param page * @param size * @return */
@Override
public List<Sku> list(int page, int size) {
//分页
PageHelper.startPage(page,size);
//条件构建
Example example = new Example(Sku.class);
Example.Criteria criteria = example.createCriteria();
//秒杀剩余商品数量>0
criteria.andGreaterThan("seckillNum",0);
//状态为参与秒杀,1:普通商品,2:参与秒杀
criteria.andEqualTo("status","2");
//秒杀结束时间>=当前时间
criteria.andGreaterThanOrEqualTo("seckillEnd",new Date());
return skuMapper.selectByExample(example);
}
在seckill-goods
的com.seckill.goods.controller.SkuController
中添加list方法,用于实现查询秒杀商品:
/*** * Sku分页条件加载 * @param page * @param size * @return */
@GetMapping(value = "/list/{page}/{size}" )
public List<Sku> list(@PathVariable int page, @PathVariable int size){
//调用SkuService实现分页条件查询
Sku List<Sku> skus = skuService.list(page, size);
return skus;
}
3)Feign接口编写
在seckill-goods-api
的com.seckill.goods.feign.SkuFeign
中编写feign方法,分别调用刚才的count、list方法,代码如下:
/***
* Sku数量加载
* @return
*/
@PostMapping(value = "/sku/count" )
Integer count();
/***
* Sku分页条件加载
* @param page
* @param size
* @return
*/
@GetMapping(value = "/sku/list/{page}/{size}" )
List list(@PathVariable(value ="page") Integer page, @PathVariable(value = "size")Integer size);
4)批量导入实现
数据添加到数据库:
1 1.将数据接到,并转成JavaBean->数据库表中对应的一个JavaBean
2.调用API,把JavaBean保存到数据库
将数据添加到索引库,流程和上面类似,也需要先创建一个能体现索引库的JavaBean映射对象,将要保存到索引库的数据赋值给JavaBean,利用API将JavaBean保存到索引库。
我们首先编写一个和索引库中一一对应的实体Bean,代码如下:
@Document(indexName = "goodsindex",type = "skuinfo")
public class SkuInfo implements Serializable {
//Sku相关的数据
//商品id,同时也是商品编号
@Id //唯一标识符,ES中对应的_id
private String id;
/***
* SKU名称
* type =FieldType.Text:指定当前name属性所对应的域的类型为Text类型,该类型支持分词支持创建索引
* FiledType.Keyword:不分词
* searchAnalyzer="ik_smart":搜索所使用的分词器
* analyzer = "ik_smart":添加索引所使用的分词器
*/
@Field(type =FieldType.Text ,searchAnalyzer = "ik_smart",analyzer = "ik_smart",store =false)
private String name;
//商品价格,单位为:元
private Long price;
//秒杀价
private Long seckillPrice;
//商品图片
private String image;
//更新时间
private Date updateTime;
//类目ID
private String category1Id;
//类目ID
private String category2Id;
//类目ID
private String category3Id;
//类目名称
@Field(type = FieldType.Keyword)
private String category1Name;
//类目名称
@Field(type = FieldType.Keyword)
private String category2Name;
//类目名称
@Field(type = FieldType.Keyword)
private String category3Name;
//品牌名称
@Field(type = FieldType.Keyword)
private String brandName;
//开始时间,用于做搜索
@Field(type = FieldType.Keyword)
private String bgtime;
//品牌ID
private String brandId;
private Date seckillBegin;//秒杀开始时间
private Date seckillEnd;//秒杀结束时间
private Integer status; //秒杀状态,1普通商品,2秒杀
//规格
private String spec;
//..get..set略
}
集成SpringData Elasticsearch实现索引导入流程:
1.配置Elasticsearch地址信息
2.编写Dao代码,继承ElasticsearchRepository<T,ID>
3.在Service中分页调用查询秒杀商品集合
4.分页导入秒杀商品集合数据到Elasticsearch中
当前项目已经集成好了SpringDataElasticsearch,我们只需要实现相关的操作过程即可。
bootstrap.yml添加es配置:
spring:
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: es-server:9300
创建dao
public interface SkuInfoMapper extends ElasticsearchRepository<SkuInfo,String> {
}
在seckill-search
的com.seckill.search.service.SkuInfoService
中编写导入索引,代码如下:
/***
* 导入所有
*/
void addAll();
在seckill-search
的com.seckill.search.service.impl.SkuInfoServiceImpl
中编写导入索引,代码如下:
@Autowired
private SkuFeign skuFeign;
/***
* 使用Feign导入所有
*/
@Override
public void addAll() {
//从第1页开始,每页处理500条
int pageNum=1;
int size=500;
//查询所有数量
Integer count = skuFeign.count();
//总页数
int totalPage = count%size==0? count/size : (count/size)+1;
//分页查询,并将所有数据导入到索引库中
for (int i = 0; i <totalPage ; i++) {
List list = skuFeign.list(pageNum, size);
//将数据转换成List<SkuInfo>
List<SkuInfo> skuInfos =JSON.parseArray( JSON.toJSONString(list) ,SkuInfo.class);
//规格处理
for (SkuInfo skuInfo : skuInfos) {
//获取秒杀时间
Date seckillBegin = skuInfo.getSeckillBegin();
if(seckillBegin!=null){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyymmddhhmmss");
String bgtime = simpleDateFormat.format(seckillBegin);
skuInfo.setBgtime(bgtime);
}
}
skuInfoMapper.saveAll(skuInfos);
//页数递增
pageNum++;
}
}
注意:我们可以发现下面这段代码以后在其他地方有可能也会用得着,我们可以把它单独抽取出一个方法来:
抽取后:
在seckill-search
的com.seckill.search.controller.SearchController
中编写导入索引,代码如下:
/***
* 所有记录导入到搜索引擎中
*/
@GetMapping(value = "/all/add")
public Result addAll(){
//添加数据到索引库中
skuInfoService.addAll();
return new Result(true, StatusCode.OK,"导入所有数据到索引库成功!");
}
4)测试
启动seckill-goods
、seckill-search
、seckill-gateway
,访问刚才编写的批量导入的方法,访问地址:http://localhost:8001/api/search/all/add
3.2 增量导入
增量导入,也就是某个商品设置成秒杀商品的时候,或者发生变更的时候,能实现增量备份(只将修改的数据同步修改索引库),所以我们还需要实现单个商品导入索引库,我们可以在变更方法(增删改)中调用这边同步方法,但随着系统的增加,这种方法容易有漏网之鱼,我们可以采用canal实现数据库增量监听,然后调用seckill-search
的单个操作方法。
1)索引操作方法编写
在seckill-search
的com.seckill.search.service.SkuInfoService
中添加modify方法,代码如下:
/****
* 单条索引操作
* @param type: 1:增加,2:修改,3:删除
* @param skuInfo
*/
void modify(Integer type, SkuInfo skuInfo);
在seckill-search
的com.seckill.search.service.impl.SkuInfoServiceImpl
中添加modify实现方法,代码如下:
/***
* 单条索引操作
* @param type
* @param skuInfo
*/
@Override
public void modify(Integer type, SkuInfo skuInfo) {
if(type==1 || type==2){
//时间转换
skuInfoConverter(skuInfo);
//增加数据到索引库
skuInfoMapper.save(skuInfo);
}else{
skuInfoMapper.deleteById(skuInfo.getId());
}
}
在seckill-search
的com.seckill.search.controller.SkuInfoController
中添加modify方法,代码如下:
/***
* 将一条记录导入到搜索引擎中
*/
@PostMapping(value = "/modify/{type}")
public Result add(@PathVariable(value = "type")Integer type, @RequestBody SkuInfo skuInfo){
//添加数据到索引库中
skuInfoService.modify(type,skuInfo);
return new Result(true, StatusCode.OK,"操作一条数据到索引库成功!");
}
2)Feign接口编写
在seckill-search-api
中编写Feign接口,实现调用modify方法,代码如下:
@FeignClient(value = "seckill-search")
public interface SkuInfoFeign {
/***
* 将一条记录导入到搜索引擎中
*/
@PostMapping(value = "/search/modify/{type}")
Result modify(@PathVariable(value = "type")Integer type, @RequestBody SkuInfo skuInfo);
}
3.3 商品搜索
根据秒杀页面的需求,多数是查询指定秒杀时段下的秒杀商品,同时还会有分页,当然,如果有复杂的查询,我们Elasticsearch也都满足。我们可以根据多数秒杀需求,实现按照秒杀时段分页查询数据。
编写Dao
public interface SkuInfoMapper extends ElasticsearchRepository<SkuInfo,String> {
/***
* 根据bgtime分页查询
* @param time
* @param pageable
* @return
*/
Page<SkuInfo> findByBgtime(String time, Pageable pageable);
}
编写Servicecom.seckill.search.service.SkuInfoService
创建搜索方法,代码如下:
/***
* 搜索
*/
Page<SkuInfo> searchPage(Map<String,String> searchMap);
编写Servicecom.seckill.search.service.impl.SkuInfoServiceImpl
创建搜索实现方法,代码如下:
/***
* 搜索实现
* @param searchMap
* @return
*/
@Override
public Page<SkuInfo> searchPage(Map<String, String> searchMap) {
//根据开始时间查询 findByBgtime(Dao)
return skuInfoMapper.findByBgtime(searchMap.get("starttime"), PageRequest.of(pageNumber(searchMap)-1,20));
}
/***
* 获取当期页
* @param searchMap
* @return
*/
public int pageNumber(Map<String, String> searchMap){
try {
return Integer.parseInt( searchMap.get("pageNum") );
} catch (NumberFormatException e) {
return 1;
}
}
编写Servicecom.seckill.search.controller.SearchController
创建搜索方法调用,代码如下:
/***
* 秒杀分页查询
* pageNum:当前页
* starttime:秒杀活动开始时间
*/
@GetMapping
public Page search(@RequestParam(required = false) Map<String,String> searchMap){
if(searchMap==null){
return null;
}
return skuInfoService.searchPage(searchMap);
}