3 索引库数据管理

Elasticsearch基础学习: 学成在线-课程预览及课程发布 Elasticsearch研究v1.2

​ 秒杀商品数量庞大,我们要想实现快速检索,不建议直接使用关系型数据库查找。不建议使用Redis缓存所有数据,因为秒杀商品量大,会影响Redis的性能,并且Redis的条件检索能力偏弱。我们可以使用Elasticsearch,它在海量数据存储与检索上,能力卓越,市场使用面广。

3.1 批量导入

​ 我们需要将秒杀商品数据导入到ES索引库中,但秒杀商品数量庞大,所以我们应该分页查询并导入,流程如下:

1)service总数量查询

​ 我们先在seckill-goods中编写相关方法实现数据查询,因为要用到分页,所以先查询总数量,然后再实现分页集合查询。

seckill-goodscom.seckill.goods.service.SkuService中添加count方法,用于查询秒杀商品总数量:

  /**
  * 总数量加载 *
  @return
  */
  Integer count();

seckill-goodscom.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-goodscom.seckill.goods.controller.SkuController中添加count方法,用于实现查询秒杀商品总数量:

/**
* *
* Sku数量加载
* @return */
@PostMapping(value = "/count" )
public Integer count()
{
return skuService.count();
}

2)service分页集合数据查询

seckill-goodscom.seckill.goods.service.SkuService中添加list方法,用于查询秒杀商品:

 /**
 *
 * 分页加载 *
 @param page *
 @param size *
 @return
 */
 List<Sku> list(int page, int size);

seckill-goodscom.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-goodscom.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-apicom.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-searchcom.seckill.search.service.SkuInfoService中编写导入索引,代码如下:

/***
 * 导入所有
 */
void addAll();

seckill-searchcom.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-searchcom.seckill.search.controller.SearchController中编写导入索引,代码如下:

/***
 * 所有记录导入到搜索引擎中
 */
@GetMapping(value = "/all/add")
public Result addAll(){
    //添加数据到索引库中
    skuInfoService.addAll();
    return new Result(true, StatusCode.OK,"导入所有数据到索引库成功!");
}

4)测试

启动seckill-goodsseckill-searchseckill-gateway,访问刚才编写的批量导入的方法,访问地址:http://localhost:8001/api/search/all/add

3.2 增量导入

增量导入,也就是某个商品设置成秒杀商品的时候,或者发生变更的时候,能实现增量备份(只将修改的数据同步修改索引库),所以我们还需要实现单个商品导入索引库,我们可以在变更方法(增删改)中调用这边同步方法,但随着系统的增加,这种方法容易有漏网之鱼,我们可以采用canal实现数据库增量监听,然后调用seckill-search的单个操作方法。

1)索引操作方法编写

seckill-searchcom.seckill.search.service.SkuInfoService中添加modify方法,代码如下:

/****
 * 单条索引操作
 * @param type: 1:增加,2:修改,3:删除
 * @param skuInfo
 */
void modify(Integer type, SkuInfo skuInfo);

seckill-searchcom.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-searchcom.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);
}