目录
order-service中 的 UserClient、User 都复制到 feign-api 项目中 加入依赖
在 order-service 启动类添加注解开启 Feign
创建 SpringBoot 工程 gateway,引入网关依赖
引用:
微服务技术栈 - 乐心湖's Blog | 技术小白的技术博客
GitHub:

- @Bean
- public RestTemplate restTemplate(){
- return new RestTemplate();
- }
-
-
-
-
- public Order queryOrderAndUserById(Long orderId) {
- // 1.查询订单
- Order order = orderMapper.findById(orderId);
- // TODO: 2021/8/15 2.查询用户
- User user = restTemplate.getForObject("http://localhost:8081/user/" + order.getUserId(), User.class);
- // 3. 将用户信息封装进订单
- order.setUser(user);
- // 4.返回
- return order;
- }
新建eureka-service 服务
- //pom 依赖
-
org.springframework.cloud -
spring-cloud-starter-netflix-eureka-server -
-
- //@EnableEurekaServer 开启 eureka 的注册中心功能
-
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
-
- @SpringBootApplication
- @EnableEurekaServer
- public class EurekaApplication {
- public static void main(String[] args) {
- SpringApplication.run(EurekaApplication.class, args);
- }
- }
-
-
- // application.yml
- server:
- port: 10086
- spring:
- application:
- name: eureka-server
- eureka:
- client:
- service-url:
- defaultZone: http://127.0.0.1:10086/eureka
-
将order user 注册到eureka服务
- //将 user-service、order-service 都注册到 eureka
-
org.springframework.cloud -
spring-cloud-starter-netflix-eureka-client -
- 在启动类上添加注解:@EnableEurekaClient
-
-
- spring:
- application:
- #name:orderservice
- name: userservice
- eureka:
- client:
- service-url:
- defaultZone: http://127.0.0.1:10086/eureka
服务拉取
- //@LoadBalanced 注解,用于开启负载均衡。
- //SpringCloud 底层提供了一个名为 Ribbon 的组件,来实现负载均衡功能。
-
-
-
- @Bean
- @LoadBalanced
- public RestTemplate restTemplate(){
- return new RestTemplate();
- }
nacos安装
- //拉取镜像
- docker pull nacos/nacos-server
-
-
- //运行
- docker run --name nacos -d -p 8848:8848 --privileged=true --restart=always -e JVM_XMS=256m -e JVM_XMX=256m -e MODE=standalone -e PREFER_HOST_MODE=hostname nacos/nacos-server
-
-
- //访问
- http://localhost:8848/nacos/#/configurationManagement?dataId=&group=&appName=&namespace=&pageSize=&pageNo=
服务注册
- //在 cloud-demo 父工程中引入 SpringCloudAlibaba 的依赖:
-
-
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-alibaba-dependencies</artifactId>
- <version>2.2.6.RELEASE</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
-
- //然后在 user-service 和 order-service 中的pom文件中引入 nacos-discovery 依赖:
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
- </dependency>
-
-
- //在 user-service 和 order-service 的 application.yml 中添加 nacos 地址:
-
- spring:
- cloud:
- nacos:
- server-addr: 127.0.0.1:8848
-
-
- //浏览器访问:http://localhost:8080/order/101,正常访问,同时负载均衡也正常。
-
-
-
配置文件
nacos 地址必须放在优先级最高的 bootstrap.yml 文件

- //首先,在 user-service 服务中,引入 nacos-config 的客户端依赖
- <!--nacos配置管理依赖-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
- </dependency>
-
-
- //然后,在 user-service 中添加一个 bootstrap.yml 文件,内容如下:
-
- spring:
- application:
- name: userservice # 服务名称
- profiles:
- active: dev #开发环境,这里是dev
- cloud:
- nacos:
- server-addr: localhost:8848 # Nacos地址
- config:
- file-extension: yaml # 文件后缀名
-
-
- //在nacos控制台新增配置文件 :
- data-id: userservice-dev.yaml
-
- logging:
- level:
- com.xn2001: debug
- pattern:
- dateformat: MM-dd HH:mm:ss:SSS
-
-
-
- //在 user-service 中的 UserController 中添加业务逻辑,读取 pattern.dateformat 配置并使用:
-
-
-
- @Value("${logging.pattern.dateformat}")
- private String dateformat;
-
- @GetMapping("now")
- public String now(){
- //格式化时间
- return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
- }
-
-
- //启动服务后,访问:http://localhost:8081/user/now
-
-
-
-
其实在服务启动时,nacos 会读取多个配置文件,例如:
[spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml[spring.application.name].yaml,例如:userservice.yaml这里的 [spring.application.name].yaml 不包含环境,因此可以被多个环境共享。
- @FeignClient("userservice")
- public interface UserClient {
- @GetMapping("/user/{id}")
- User findById(@PathVariable("id") Long id);
- }
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
- <dependency>
- <groupId>com.xn2001.feign</groupId>
- <artifactId>feign-api</artifactId>
- <version>1.0</version>
- </dependency>
@EnableFeignClients(basePackages = "com.xn2001.feign.clients")
- @Autowired
- private UserClient userClient;
-
- public Order queryOrderAndUserById(Long orderId) {
- // 1.查询订单
- Order order = orderMapper.findById(orderId);
- // TODO: 2021/8/20 使用feign远程调用
- User user = userClient.findById(order.getUserId());
- // 3. 将用户信息封装进订单
- order.setUser(user);
- // 4.返回
- return order;
- }
- <!--网关-->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-gateway</artifactId>
- </dependency>
- <!--nacos服务发现依赖-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
- </dependency>
-
- //创建 application.yml 文件,内容如下:
-
- server:
- port: 10010 # 网关端口
- spring:
- application:
- name: gateway # 服务名称
- cloud:
- nacos:
- server-addr: localhost:8848 # nacos地址
- gateway:
- routes: # 网关路由配置
- - id: user-service # 路由id,自定义,只要唯一即可
- # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
- uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
- predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件
- @Component
- public class AuthorizeFilter implements GlobalFilter, Ordered {
-
- // 测试:http://localhost:10010/order/101?authorization=admin
- @Override
- public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
- // 获取第一个 authorization 参数
- String authorization = exchange.getRequest().getQueryParams().getFirst("authorization");
- if ("admin".equals(authorization)){
- // 放行
- return chain.filter(exchange);
- }
- // 设置拦截状态码信息
- exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
- // 设置拦截
- return exchange.getResponse().setComplete();
- }
-
- // 设置过滤器优先级,值越低优先级越高
- // 也可以使用 @Order 注解
- @Override
- public int getOrder() {
- return 0;
- }
- }
- spring:
- cloud:
- gateway:
- globalcors: # 全局的跨域处理
- add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
- corsConfigurations:
- '[/**]':
- allowedOrigins: # 允许哪些网站的跨域请求 allowedOrigins: “*” 允许所有网站
- - "http://localhost:8090"
- allowedMethods: # 允许的跨域ajax的请求方式
- - "GET"
- - "POST"
- - "DELETE"
- - "PUT"
- - "OPTIONS"
- allowedHeaders: "*" # 允许在请求中携带的头信息
- allowCredentials: true # 是否允许携带cookie
- maxAge: 360000 # 这次跨域检测的有效期
docker pull rabbitmq:3-management
docker run \ -e RABBITMQ_DEFAULT_USER=admin \ -e RABBITMQ_DEFAULT_PASS=123456 \ --name mq \ --hostname mq1 \ -p 15672:15672 \ -p 5672:5672 \ -d \ rabbitmq:3-management
去掉 \
访问:
http://localhost:15672/#/queues/%2F/object.queue
- public class PublisherTest {
- @Test
- public void testSendMessage() throws IOException, TimeoutException {
- // 1.建立连接
- ConnectionFactory factory = new ConnectionFactory();
- // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
- factory.setHost("192.168.211.128");
- factory.setPort(5672);
- factory.setVirtualHost("/");
- factory.setUsername("admin");
- factory.setPassword("123456");
- // 1.2.建立连接
- Connection connection = factory.newConnection();
- // 2.创建通道Channel
- Channel channel = connection.createChannel();
- // 3.创建队列
- String queueName = "simple.queue";
- channel.queueDeclare(queueName, false, false, false, null);
- // 4.发送消息
- String message = "Hello RabbitMQ!";
- channel.basicPublish("", queueName, null, message.getBytes());
- System.out.println("发送消息成功:[" + message + "]");
- // 5.关闭通道和连接
- channel.close();
- connection.close();
- }
- }
- public class ConsumerTest {
- public static void main(String[] args) throws IOException, TimeoutException {
- // 1.建立连接
- ConnectionFactory factory = new ConnectionFactory();
- // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
- factory.setHost("192.168.211.128");
- factory.setPort(5672);
- factory.setVirtualHost("/");
- factory.setUsername("admin");
- factory.setPassword("123456");
- // 1.2.建立连接
- Connection connection = factory.newConnection();
- // 2.创建通道Channel
- Channel channel = connection.createChannel();
- // 3.创建队列
- String queueName = "simple.queue";
- channel.queueDeclare(queueName, false, false, false, null);
- // 4.订阅消息
- channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
- @Override
- public void handleDelivery(String consumerTag, Envelope envelope,
- AMQP.BasicProperties properties, byte[] body) {
- // 5.处理消息
- String message = new String(body);
- System.out.println("接收到消息:[" + message + "]");
- }
- });
- System.out.println("等待接收消息中");
- }
- }
pringAMQP 是基于 RabbitMQ 封装的一套模板,并且还利用 SpringBoot 对其实现了自动装配,使用起来非常方便。
SpringAMQP 的官方地址:Spring AMQP
- <!--AMQP依赖,包含RabbitMQ-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-amqp</artifactId>
- </dependency>
- spring:
- rabbitmq:
- host: 192.168.150.101 # 主机名
- port: 5672 # 端口
- virtual-host: / # 虚拟主机
- username: admin # 用户名
- password: 123456 # 密码
- @Component
- public class RabbitMQListener {
- @RabbitListener(queues = "simple.queue")
- public void listenSimpleQueueMessage(String msg) throws InterruptedException {
- System.out.println("消费者接收到消息:【" + msg + "】");
- }
- }
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class SpringAmqpTest {
- @Autowired
- private RabbitTemplate rabbitTemplate;
- @Test
- public void testSimpleQueue() {
- // 队列名称
- String queueName = "simple.queue";
- // 消息
- String message = "你好啊,乐心湖!";
- // 发送消息
- rabbitTemplate.convertAndSend(queueName, message);
- }
- }
路由
在 Fanout 模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到 DirectExchange
- @RabbitListener(bindings = @QueueBinding(
- value = @Queue(value = "direct.queue1"),
- exchange = @Exchange(value = "xn2001.direct"),
- key = {"a","b"}
- ))
- public void listenDirectQueue1(String msg){
- System.out.println("接收到direct.queue1的消息:【" + msg + "】" + LocalTime.now());
- }
-
- @RabbitListener(bindings = @QueueBinding(
- value = @Queue(value = "direct.queue2"),
- exchange = @Exchange(value = "xn2001.direct"),
- key = {"a","c"}
- ))
- public void listenDirectQueue2(String msg){
- System.out.println("接收到direct.queue2的消息:【" + msg + "】" + LocalTime.now());
- }
- /**
- * direct
- * 向交换机发送消息
- */
- @Test
- public void testDirectExchangeToA() {
- // 交换机名称
- String exchangeName = "xn2001.direct";
- // 消息
- String message = "hello, i am direct to a!";
- rabbitTemplate.convertAndSend(exchangeName, "a", message);
- }
-
- /**
- * direct
- * 向交换机发送消息
- */
- @Test
- public void testDirectExchangeToB() {
- // 交换机名称
- String exchangeName = "xn2001.direct";
- // 消息
- String message = "hello, i am direct to b!";
- rabbitTemplate.convertAndSend(exchangeName, "b", message);
- }
Topic 与 Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic 类型可以让队列在绑定Routing key 的时候使用通配符!
Spring 会把你发送的消息序列化为字节发送给 MQ,接收消息的时候,还会把字节反序列化为 Java 对象。

- <dependency>
- <groupId>com.fasterxml.jackson.dataformat</groupId>
- <artifactId>jackson-dataformat-xml</artifactId>
- <version>2.9.10</version>
- </dependency>
- @Bean
- public MessageConverter jsonMessageConverter(){
- return new Jackson2JsonMessageConverter();
- }

挂载指令:
docker run -it -v /D/docker/wordcount:/home/root/a_dir/ IMAGE
冒号前后分别为本地路径和要挂载到的路径
IMAGE为镜像名称注意
本地路径不能像通常那样写作D:/,而是要写成/D/,否则会报错Error response from daemon: invalid mode
要挂载的路径/home/root/a_dir/原先就存在
docker run -d --name es -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" -e "discovery.type=single-node" -v es-data:/D/es/data -v es-plugins:/D/es/plugins --privileged --network es-net -p 9200:9200 -p 9300:9300 elasticsearch:7.12.1
这里磁盘挂在了,也有文件,但是没有生效,容器内在线安装
- docker exec -it es /bin/bash
-
- ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
-
参考:
docker安装es、ik分词器、kabana_Andy_Health的博客-CSDN博客_docker es安装ik

GET /hotel/_search
{
"query": {
"match_all": {}
}
}POST /_analyze
{
"analyzer": "ik_max_word",
"text": "努力工作天天向上"
}
PUT /hotel
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name":{
"type": "text",
"analyzer": "ik_max_word",
"copy_to": "all"
},
"address":{
"type": "keyword",
"index": false
},
"price":{
"type": "integer"
},
"score":{
"type": "integer"
},
"brand":{
"type": "keyword",
"copy_to": "all"
},
"city":{
"type": "keyword",
"copy_to": "all"
},
"starName":{
"type": "keyword"
},
"business":{
"type": "keyword"
},
"location":{
"type": "geo_point"
},
"pic":{
"type": "keyword",
"index": false
},
"all":{
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}GET hotel
POST /md/_doc/1
{
"info":"study",
"email":"123@163.com"
}GET /hotel/_doc/61083
// 查询所有
GET /hotel/_search
{
"query": {
"match_all": {
}
}
}
//条件查询
GET /hotel/_search
{
"query": {
"match": {
"name": "7天"
}
}
}// term查询
GET /hotel/_search
{
"query": {
"term": {
"city.keyword": {
"value": "上海"
}
}
}
}
// range查询
GET /indexName/_search
{
"query": {
"range": {
"FIELD": {
"gte": 10, // 这里的gte代表大于等于,gt则代表大于
"lte": 20 // lte代表小于等于,lt则代表小于
}
}
}
}// geo_bounding_box查询 范围查询
GET /indexName/_search
{
"query": {
"geo_bounding_box": {
"FIELD": {
"top_left": { // 左上点
"lat": 31.1,
"lon": 121.5
},
"bottom_right": { // 右下点
"lat": 30.9,
"lon": 121.7
}
}
}
}
}// geo_distance 查询 附近查询
GET /indexName/_search
{
"query": {
"geo_distance": {
"distance": "15km", // 半径
"FIELD": "31.21,121.5" // 圆心
}
}
}
//距离排序
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance" : {
"location": "31.034661,121.612282", //圆心
"order" : "asc", //排序
"unit" : "km" //单位
}
}
]
}
- package com.md.es;
-
- import com.md.es.constants.HotelConstants;
- import org.apache.http.HttpHost;
- import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.client.indices.CreateIndexRequest;
- import org.elasticsearch.client.indices.GetIndexRequest;
- import org.elasticsearch.common.xcontent.XContentType;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.jupiter.api.Test;
-
- import java.io.IOException;
-
- /**
- * TODO add description
- *
- * Created at 2022/11/1 11:28
- *
- * @author cengzq5
- */
- public class EsIndexTest {
-
- private RestHighLevelClient restHighLevelClient;
-
- @BeforeEach
- public void test() throws IOException {
- restHighLevelClient = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://127.0.0.1:9200")));
- }
-
- @AfterEach
- public void close() throws IOException {
- restHighLevelClient.close();
- }
-
-
- /**
- * 创建索引
- */
- @Test
- void createHotelIndex() throws IOException {
- //指定索引库名
- CreateIndexRequest hotel = new CreateIndexRequest("hotel");
- //写入JSON数据,这里是Mapping映射
- hotel.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
- //创建索引库
- restHighLevelClient.indices().create(hotel, RequestOptions.DEFAULT);
- }
-
- /** 判断索引是否存在
- * @Description p
- * @Author cengzq5
- * @Date 2022/11/1 14:02
- * @return void
- */
- @Test
- void existHotelIndex() throws IOException {
- GetIndexRequest hotel = new GetIndexRequest("hotel");
- boolean exists = restHighLevelClient.indices().exists(hotel, RequestOptions.DEFAULT);
- System.out.println(exists);
- }
-
-
- /**
- * @Description 删除索引
- */
- @Test
- void deleteHotelIndex() throws IOException {
- DeleteIndexRequest hotel = new DeleteIndexRequest("hotel");
- restHighLevelClient.indices().delete(hotel,RequestOptions.DEFAULT);
- }
-
- }
- package com.md.es;
-
- import com.alibaba.fastjson.JSON;
- import com.md.es.pojo.Hotel;
- import com.md.es.pojo.HotelDoc;
- import com.md.es.pojo.PageResult;
- import com.md.es.service.HotelService;
- import com.md.es.service.HotelServiceImpl;
- import org.apache.http.HttpHost;
- import org.elasticsearch.action.bulk.BulkRequest;
- import org.elasticsearch.action.delete.DeleteRequest;
- import org.elasticsearch.action.get.GetRequest;
- import org.elasticsearch.action.get.GetResponse;
- import org.elasticsearch.action.index.IndexRequest;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.action.update.UpdateRequest;
- import org.elasticsearch.client.RequestOptions;
- import org.elasticsearch.client.RestClient;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.common.xcontent.XContentType;
- import org.elasticsearch.index.query.QueryBuilders;
- import org.junit.Test;
- import org.junit.jupiter.api.AfterEach;
- import org.junit.jupiter.api.BeforeEach;
- import org.junit.runner.RunWith;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.junit4.SpringRunner;
-
- import javax.annotation.Resource;
- import java.io.IOException;
- import java.util.List;
-
- /**
- * TODO add description
- *
- * Created at 2022/11/1 14:29
- *
- * @author cengzq5
- */
- @SpringBootTest
- @RunWith(SpringRunner.class)
- public class EsDocTest {
-
-
- @Resource
- private RestHighLevelClient restHighLevelClient;
-
- // @BeforeEach
- // public void test() throws IOException {
- // restHighLevelClient = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://127.0.0.1:9200")));
- // }
- //
- // @AfterEach
- // public void close() throws IOException {
- // restHighLevelClient.close();
- // }
-
-
- @Resource
- private HotelService hotelService;
-
- private final String index = "hotel";
-
- /**
- * @Description 新增文档
- * @Author cengzq5
- * @Date 2022/11/1 15:11
- * @return void
- */
- @Test
- public void insertDoc() throws IOException {
- Hotel hotel = hotelService.getById(61083L);
- HotelDoc hotelDoc = new HotelDoc(hotel);
- IndexRequest indexRequest = new IndexRequest(index).id(hotelDoc.getId().toString());
- indexRequest.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
- restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
- }
-
-
- /**
- * @Description 查询文档
- * @Author cengzq5
- * @Date 2022/11/1 15:14
- * @return void
- */
- @Test
- public void getDocById() throws IOException {
- GetRequest hotel = new GetRequest(index,"61083");
- GetResponse document = restHighLevelClient.get(hotel, RequestOptions.DEFAULT);
- String sourceAsString = document.getSourceAsString();
- HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
- System.out.println(hotelDoc);
- }
-
-
- /**
- * @Description 删除文档
- * @Author cengzq5
- * @Date 2022/11/1 15:15
- * @return void
- */
- @Test
- public void testDeleteDocumentById() throws IOException {
- DeleteRequest hotel = new DeleteRequest("hotel", "61083");
- restHighLevelClient.delete(hotel,RequestOptions.DEFAULT);
- }
-
-
- /**
- * @Description 增量修改文档
- *
- * 修改文档有两种方式:
- *
- * 全量修改:直接覆盖原来的文档
- * 增量修改:修改文档中的部分字段
- * 在 RestClient 的 API 中,全量修改与新增的 API 完全一致,判断依据是 ID
- *
- * 如果新增时,ID已经存在,则修改
- * 如果新增时,ID不存在,则新增
- *
- * @Author cengzq5
- * @Date 2022/11/1 15:18
- * @return void
- */
- @Test
- public void updateDoc() throws IOException {
- UpdateRequest request = new UpdateRequest(index,"61083");
- request.doc(
- "price","333",
- "starName","钻石"
- );
- restHighLevelClient.update(request,RequestOptions.DEFAULT);
- }
-
-
- @Test
- public void testBulk() throws IOException {
- BulkRequest bulkRequest = new BulkRequest();
- List<Hotel> hotelList = hotelService.list();
- hotelList.forEach(item -> {
- HotelDoc hotelDoc = new HotelDoc(item);
- bulkRequest.add(new IndexRequest("hotel")
- .id(hotelDoc.getId().toString())
- .source(JSON.toJSONString(hotelDoc), XContentType.JSON));
- });
- restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
- }
-
-
- /**
- * @Description 全量查询
- * @Author cengzq5
- * @Date 2022/11/1 15:58
- * @return void
- */
- @Test
- public void matchAll() throws IOException {
- SearchRequest request = new SearchRequest(index);
- request.source().query(QueryBuilders.matchAllQuery());
- SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
- PageResult pageResult = HotelServiceImpl.handleResponse(search);
- System.out.println(pageResult);
- }
- }
- package com.md.es;
-
- import com.md.es.constants.MqConstants;
- import org.junit.Test;
- import org.junit.runner.RunWith;
- import org.springframework.amqp.rabbit.core.RabbitTemplate;
- import org.springframework.boot.test.context.SpringBootTest;
- import org.springframework.test.context.junit4.SpringRunner;
-
- import javax.annotation.Resource;
-
- /**
- * TODO add description
- *
- * Created at 2022/11/1 16:34
- *
- * @author cengzq5
- */
- @RunWith(SpringRunner.class)
- @SpringBootTest
- public class MqTest {
-
- @Resource
- private RabbitTemplate rabbitTemplate;
-
- @Test
- public void deleteDoc(){
- rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_DELETE_KEY, 61083L);
- }
- }





聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。
桶(Bucket)聚合:用来对文档做分组
度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
管道(pipeline)聚合:其它聚合的结果为基础做聚合
当用户在搜索框输入字符时,我们应该提示出与该字符有关的搜索项,提示完整词条的功能,就是自动补全了。
下载后直接执行 bin 目录就是执行的脚本 jmeter.bat,其中包含启动脚本
Sentinel是阿里巴巴开源的一款微服务流量控制组件
下载后 jar 包后,运行代码:java -jar sentinel-dashboard-1.8.1.jar
java -Dserver.port=8090 -jar sentinel-dashboard-1.8.1.jar
访问 http://localhost:8080 页面,就可以看到 Sentinel 的控制台了。
order-service 中整合 Sentinel
- 1)引入 Sentinel 依赖
- <!--sentinel-->
- <dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
- </dependency>
- 2)配置控制台
-
- 修改 application.yml 文件,添加下面内容:
-
- server:
- port: 8088
- spring:
- cloud:
- sentinel:
- transport:
- dashboard: localhost:8080

10个请求有一半失败

feign: sentinel: enabled: true # 开启feign对sentinel的支持

显示出链路
分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务,例如
在数据库水平拆分、服务垂直拆分之后,一个业务操作通常要跨多个数据库、服务才能完成。例如电商行业中比较常见的下单付款案例,包括下面几个行为:

在没有分布式情况下,新增订单成功,数据库新增一条数据,但是账号服务或者库存服务会失败,不会插入数据。

Seata 事务管理中有三个重要的角色

下载 seata-server 包,下载中心
1.4.2 版本
修改 conf 目录下的 registry.conf 文件
- registry {
- # tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等
- type = "nacos"
-
- nacos {
- # seata tc 服务注册到 nacos的服务名称,可以自定义
- application = "seata-tc-server"
- serverAddr = "127.0.0.1:8848"
- group = "DEFAULT_GROUP"
- namespace = ""
- cluster = "default"
- username = "nacos"
- password = "nacos"
- }
- }
-
- config {
- # 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
- type = "nacos"
- # 配置nacos地址等信息
- nacos {
- serverAddr = "127.0.0.1:8848"
- namespace = ""
- group = "DEFAULT_GROUP"
- username = "nacos"
- password = "nacos"
- dataId = "seataServer.properties"
- }
- }
为了让 TC 服务的集群可以共享配置,我们选择了 Nacos 作为统一配置中心。因此服务端配置文件 seataServer.properties 文件需要在Nacos 中配好。在 Nacos 后台新建一个配置文件:http://localhost:8848/nacos/
- # 数据存储方式,db代表数据库
- store.mode=db
- store.db.datasource=druid
- store.db.dbType=mysql
- # 这是MySQL8的驱动,MySQL5使用的是com.mysql.jdbc.Driver
- store.db.driverClassName=com.mysql.cj.jdbc.Driver
- # 数据库地址、用户名、密码都需要修改成你自己的数据库信息。
- store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
- store.db.user=root
- store.db.password=123456
- store.db.minConn=5
- store.db.maxConn=30
- store.db.globalTable=global_table
- store.db.branchTable=branch_table
- store.db.queryLimit=100
- store.db.lockTable=lock_table
- store.db.maxWait=5000
- # 事务、日志等配置
- server.recovery.committingRetryPeriod=1000
- server.recovery.asynCommittingRetryPeriod=1000
- server.recovery.rollbackingRetryPeriod=1000
- server.recovery.timeoutRetryPeriod=1000
- server.maxCommitRetryTimeout=-1
- server.maxRollbackRetryTimeout=-1
- server.rollbackRetryTimeoutUnlockEnable=false
- server.undo.logSaveDays=7
- server.undo.logDeletePeriod=86400000
- # 客户端与服务端传输方式
- transport.serialization=seata
- transport.compressor=none
- # 关闭metrics功能,提高性能
- metrics.enabled=false
- metrics.registryType=compact
- metrics.exporterList=prometheus
- metrics.exporterPrometheusPort=9898
进入 bin 目录,运行其中的 seata-server.bat 即可
进入cmd启动,运行seata-server.bat
注意:默认启动端口为8091,确认是否被占用
pom依赖
- dependency>
- <groupId>com.alibaba.cloud</groupId>
- <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
- <exclusions>
- <!--版本较低,1.3.0,因此排除-->
- <exclusion>
- <artifactId>seata-spring-boot-starter</artifactId>
- <groupId>io.seata</groupId>
- </exclusion>
- </exclusions>
- </dependency>
- <!--seata starter 采用1.4.2版本-->
- <dependency>
- <groupId>io.seata</groupId>
- <artifactId>seata-spring-boot-starter</artifactId>
- <version>${seata.version}</version>
- </dependency>
注意,所有服务都要配置
- seata:
- registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
- type: nacos # 注册中心类型 nacos
- nacos:
- server-addr: 127.0.0.1:8848 # nacos地址
- namespace: "" # namespace,默认为空
- group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
- application: seata-tc-server # seata服务名称
- username: nacos
- password: nacos
- tx-service-group: seata-demo # 事务组名称
- service:
- vgroup-mapping: # 事务组与cluster的映射关系
- seata-demo: default
使用模式:
- seata:
- data-source-proxy-mode: XA
2)给发起全局事务的入口方法添加 @GlobalTransactional 注解
重启服务,发送多于库存的订单请求
发现并没有新增库存订单