文末附上项目的下载地址
1、数据同步方案选型
es和数据库之间的数据同步和数据库数据的数据迁移、主从同步、数据实时同步是相似的在各个方案之间,选择和定制适合特定业务场景的数据同步策略,为构建高效、稳定、可扩展的系统奠定基础。
1.1 方案1、程序处理(直接调用、服务接口调用)
1、先写入数据库
2、调用es接口写入es(es处理数据异常时,要重新放回es,一直异常,要排查程序)
实现简单。耦合,影响性能,一个位置发生异常,程序终止
方案二、异步通知
1、写入数据库
2、发布消息(发布消息异常的处理机制)
3、监听消息,更新es(同样要处理异常消息)
可靠性依赖mq的稳定性
方案三、监听数据库的binlog
1、写入数据库
2、利用一些中间件入canal等工具,监听日志
3、通知es的服务更新es
耦合度最低,要开启binlog,影响数据库性能,依赖数据管理员
2、黑马酒店admin代码(增删改/生产消息)
2.1 目录结构
2.2 MqContents代码
package com.toto.es.hotel.constants;
public class MqContents {
/**
* 交换机
*/
public final static String HOTEL_EXCHANGE = "hotel.topic";
/**
* 监听新增和修改的队列
*/
public final static String HOTEL_INSERT_QUEUE = "hotel.insert.queue";
/**
* 监听删除的队列
*/
public final static String HOTEL_DELETE_QUEUE = "hotel.delete.queue";
/**
* 监听新增和修改的队列的路由键
*/
public final static String HOTEL_INSERT_KEY = "hotel.insert";
/**
* 监听删除的队列的路由键
*/
public final static String HOTEL_DELETE_KEY = "hotel.delete";
}
2.3 HotelMapper代码
package com.toto.es.hotel.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.toto.es.hotel.pojo.Hotel;
public interface HotelMapper extends BaseMapper<Hotel> {
}
2.4 Hotel代码
package com.toto.es.hotel.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("tb_hotel")
public class Hotel {
@TableId(type = IdType.INPUT)
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String longitude;
private String latitude;
private String pic;
}
2.5 PageResult代码
package com.toto.es.hotel.pojo;
import lombok.Data;
import java.util.List;
@Data
public class PageResult {
private Long total;
private List<Hotel> hotels;
public PageResult() {
}
public PageResult(Long total, List<Hotel> hotels) {
this.total = total;
this.hotels = hotels;
}
}
2.6 IHotelService代码
package com.toto.es.hotel.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.toto.es.hotel.pojo.Hotel;
public interface IHotelService extends IService<Hotel> {
}
2.7 HotelService代码
package com.toto.es.hotel.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.toto.es.hotel.mapper.HotelMapper;
import com.toto.es.hotel.pojo.Hotel;
import com.toto.es.hotel.service.IHotelService;
import org.springframework.stereotype.Service;
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
}
2.8 HotelController代码
package com.toto.es.hotel.web;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.toto.es.hotel.constants.MqContents;
import com.toto.es.hotel.pojo.Hotel;
import com.toto.es.hotel.pojo.PageResult;
import com.toto.es.hotel.service.IHotelService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.security.InvalidParameterException;
@RestController
@RequestMapping("hotel")
public class HotelController {
@Autowired
private IHotelService hotelService;
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/{id}")
public Hotel queryById(@PathVariable("id") Long id){
return hotelService.getById(id);
}
@GetMapping("/list")
public PageResult hotelList(@RequestParam(value = "page", defaultValue = "1") Integer page, @RequestParam(value = "size", defaultValue = "1") Integer size){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.orderByAsc("id");
Page<Hotel> result = hotelService.page(new Page<>(page, size), wrapper);
return new PageResult(result.getTotal(), result.getRecords());
}
@PostMapping
public void saveHotel(@RequestBody Hotel hotel){
hotelService.save(hotel);
rabbitTemplate.convertAndSend(MqContents.HOTEL_EXCHANGE, MqContents.HOTEL_INSERT_KEY, hotel.getId());
}
@PutMapping()
public void updateById(@RequestBody Hotel hotel){
if (hotel.getId() == null) {
throw new InvalidParameterException("id不能为空");
}
hotelService.updateById(hotel);
rabbitTemplate.convertAndSend(MqContents.HOTEL_EXCHANGE, MqContents.HOTEL_INSERT_KEY, hotel.getId());
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") Long id) {
hotelService.removeById(id);
rabbitTemplate.convertAndSend(MqContents.HOTEL_EXCHANGE, MqContents.HOTEL_DELETE_KEY, id);
}
}
2.9 HotelAdminApplication代码
package com.toto.es.hotel;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Slf4j
@MapperScan(basePackages = "com.toto.es.hotel.mapper")
@SpringBootApplication
public class HotelAdminApplication {
public static void main(String[] args) throws UnknownHostException {
ConfigurableApplicationContext application = SpringApplication.run(HotelAdminApplication.class,args);
Environment env = application.getEnvironment();
String ip = InetAddress.getLocalHost().getHostAddress();
String port = env.getProperty("server.port");
log.info("\n----------------------------------------------------------\n\t" +
//"ElasticSearch Demo: \thttp://localhost" + ip + ":" + port + "\n" +
"ElasticSearch Demo: \thttp://localhost:" + port + "\n" +
"----------------------------------------------------------");
openE("http://localhost:" + port + "");
}
private static void openE(String url) {
String runCmd = "cmd /c start " + url;
Runtime run = Runtime.getRuntime();
try {
run.exec(runCmd);
} catch (Exception e) {
System.out.println("启动项目自动打开浏览器失败");
}
}
// 最新版
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Bean
public ElasticsearchClient slasticsearchClient() {
BasicCredentialsProvider credsProv = new BasicCredentialsProvider();
credsProv.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "elastic"));
RestClient restClient = RestClient.builder(HttpHost.create("http://127.0.0.1:9200"))
.setHttpClientConfigCallback(hc -> hc.setDefaultCredentialsProvider(credsProv))
.build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
2.10 application.yaml代码
server:
port: 8099
spring:
datasource:
url: jdbc:mysql://127.0.0.1:23306/totograin?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
logging:
level:
com.toto: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
type-aliases-package: com.toto.es.hotel.pojo
3、黑马酒店demo代码(消费消息)、补全上一章代码
3.1 目录结构
3.2 MqConfig代码
package com.toto.es.hotel.config;
import com.toto.es.hotel.constants.MqContents;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MqConfig {
@Bean
public TopicExchange topicExchange() {
return new TopicExchange(MqContents.HOTEL_EXCHANGE, true, false);
}
@Bean
public Queue insertQueue() {
return new Queue(MqContents.HOTEL_INSERT_QUEUE, true);
}
@Bean
public Queue deleteQueue() {
return new Queue(MqContents.HOTEL_DELETE_QUEUE, true);
}
@Bean
public Binding insertBinding() {
return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqContents.HOTEL_INSERT_KEY);
}
@Bean
public Binding deleteBinding() {
return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqContents.HOTEL_DELETE_KEY);
}
}
3.3 MqContents代码
package com.toto.es.hotel.constants;
public class MqContents {
/**
* 交换机
*/
public final static String HOTEL_EXCHANGE = "hotel.topic";
/**
* 监听新增和修改的队列
*/
public final static String HOTEL_INSERT_QUEUE = "hotel.insert.queue";
/**
* 监听删除的队列
*/
public final static String HOTEL_DELETE_QUEUE = "hotel.delete.queue";
/**
* 监听新增和修改的队列的路由键
*/
public final static String HOTEL_INSERT_KEY = "hotel.insert";
/**
* 监听删除的队列的路由键
*/
public final static String HOTEL_DELETE_KEY = "hotel.delete";
}
3.4 HotelListener代码
package com.toto.es.hotel.mq;
import com.toto.es.hotel.constants.MqContents;
import com.toto.es.hotel.service.impl.HotelService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class HotelListener {
@Autowired
private HotelService hotelService;
/**
* 监听酒店新增和修改的消息
* @param id
*/
@RabbitListener(queues = MqContents.HOTEL_INSERT_QUEUE)
public void listenHotelInsertOrUpdate(Long id) {
System.out.println("接收到新增消息:" + id);
hotelService.insertById(id);
}
/**
* 监听酒店删除的消息
* @param id
*/
@RabbitListener(queues = MqContents.HOTEL_DELETE_QUEUE)
public void listenHotelDelete(Long id) {
hotelService.deleteById(id);
System.out.println("接收到删除消息:" + id);
}
}
4、写在最后
跟着黑马程序员的培训,对ElasticSearch7的RestAPI进行了ElasticSearch8版本的替换,翻阅了ElasticSearch8的一些文档,对ElasticSearch的开发有了初步的入门,后续会根据业务场景进行完善自己ElasticSearch的知识体系。