Eureka——服务注册与发现
搭建EurekaServer
1、创建项目,引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2、编写启动类,添加@EnableEurekaServer注解
3、全局配置:
server:
port: 7001
spring:
application:
name: "eureka-server" #eureka实例名
eureka:
instance:
hostname: localhost #eureka实例的主机名
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机版服务注册中心
服务注册
1、创建项目,引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、全局配置:
spring:
application:
name: "user-service" #eureka实例名
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka #单机版服务注册中心
服务发现
1、修改调用者代码,修改访问的url路径,用服务名代替ip、端口:
String url = "http://user-service/user/" + userId;
2、在RestTemplate添加负载均衡注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
Nacos——服务注册与发现
安装Nacos
去github下载,解压
进入bin目录,启动
startup.cmd -m standalone
打开管理网页localhost:8848,账号密码都是nacos
快速入门
1、在父POM的 dependencyManagement 中指定spring-cloud-alibaba的版本
<dependencyManagement>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
2、在要注册的服务POM中,引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3、全局配置
spring:
application:
name: user-service #服务实例名
cloud:
nacos:
server-addr: localhost:8848 #nacos服务地址
4、启动nacos-server,启动要注册的服务(不用写注解)
Nacos服务分级存储模型
服务分级存储模型:
服务——集群——实例
如何设置实例的集群属性:
spring:
application:
name: user-service #服务实例名
cloud:
nacos:
server-addr: localhost:8848 #nacos服务地址
discovery:
cluster-name: HZ #集群名
NacosRule负载均衡
order-service配置:
spring:
application:
name: order-service #服务实例名
cloud:
nacos:
server-addr: localhost:8848 #nacos服务地址
discovery:
cluster-name: HZ
user-service: #要调用的服务名
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #指定负载均衡策略规则类
user-service配置:
spring:
application:
name: user-service #服务实例名
cloud:
nacos:
server-addr: localhost:8848 #nacos服务地址
discovery:
cluster-name: SH
负载均衡策略:
(1)优先选择同集群服务实例列表
(2)本地集群找不到,才去其他集群找
(3)确定了可用实例列表后,再采用随机负载均衡挑选实例
设置服务实例的权重
在Nacos控制台修改
环境隔离
用namespace来做环境隔离;
不同namespace下的服务不可见。
新建namespace:在Nacos控制台
实例加入到namespace:
spring:
application:
name: user-service #服务实例名
cloud:
nacos:
server-addr: localhost:8848 #nacos服务地址
discovery:
cluster-name: SH
namespace: sdsdd-wee-22 #命名空间id
Nacos——配置管理
Nacos实现配置管理
在Nacos控制台上操作,假设我们创建了一个配置文件叫userservice-dev.yaml
,后面会用到。
微服务配置拉取
1、引入Nacos的配置管理客户端依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2、在resources下创建bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml
spring:
application:
name: userservice #服务名称
profiles:
active: dev #开发环境
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml #文件名后缀
2.1、SpringBoot2.4之后不会默认加载bootstrap.yaml,如果需要加载bootstrap.yaml文件需要手动添加依赖
<!--SpringBoot2.4.x之后默认不加载bootstrap.yml文件,需要在pom里加上依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
3、在微服务中,像使用全局配置文件一样使用配置:
@Value("${data.name}")
private String name;
4、启动程序时,就会读到Nacos上的配置了。
配置热更新
Nacos中修改配置后,微服务无需重启就可以感知,但是需要通过下面配置实现:
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
方式二:使用@ConfigurationProperties注解
@Data
@Component
@ConfigurationProperties(prefix = "data")
public class DataProperties {
private String name;
}
多环境配置共享
微服务启动时会从nacos读取多个配置文件:
[spring.application.name]-[spring.profiles.active].yaml,可以存放特定环境的配置
[spring.application.name].yaml,可以存放不同环境共同的配置
多种配置的优先级:
[服务名]-[环境].yaml > [服务名].yaml > 本地配置
Feign——远程调用
入门
1、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2、添加@EnableFeignClients注解
@EnableFeignClients
@SpringBootApplication
public class OrderServiceApplication {}
3、编写FeignClient接口
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
String findById(@PathVariable("id") Integer userId);
}
4、使用FeignClient中定义的方法代替RestTemplate
@Autowired
private UserClient userClient;
@GetMapping("{userId}")
public String getOrderByUserId(@PathVariable("userId") Integer userId) {
String user = userClient.findById(userId);//这里
return String.format("【%s】的订单", user);
}
自定义配置
配置Feign日志:
方式一:配置文件方式
feign:
client:
config:
default: #default表示对所有service的调用生效,也可以写具体的服务名
loggerLevel: BASIC
方式二:java代码
需要先声明一个bean:
public class FeignClientConfiguration {
@Bean
public Logger.Level logLevel() {
return Logger.Level.BASIC;
}
}
如果是全局配置,则把它放到启动类的注解中:
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
如果是局部配置,则把它放到FeignClient接口的注解中:
@FeignClient(value = "userservice", configuration = FeignClientConfiguration.class)
Gateway——网关
入门
1、引入依赖
<!--nacos服务注册发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--网关gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、配置
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
- id: user-service #路由标识
uri: lb://userservice #路由的目标地址
predicates: #路由断言,判断请求是否符合规则
- Path=/user/**
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
路由断言工厂
配置中的predicates就是路由断言,Spring提供了11种基本的Predicate工厂:
网关过滤器
对进入网关的请求和微服务返回的响应做处理,例如:
AddRequestHeader,给请求添加请求头
RemoveRequestHeader,移除请求中的请求头
AddResponseHeader,给响应结果中添加一个响应头
RemoveResponseHeader,从响应结果中移除一个响应头
RequestRateLimiter,限制请求的流量
例子:针对某个路由
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
- id: user-service #路由标识
uri: lb://userservice #路由的目标地址
predicates: #路由断言,判断请求是否符合规则
- Path=/user/**
filters:
- AddRequestHeader=YourName,ZhangCan #这里
例子:针对所有路由
spring:
application:
name: gateway
cloud:
nacos:
server-addr: localhost:8848
gateway:
routes:
- id: user-service #路由标识
uri: lb://userservice #路由的目标地址
predicates: #路由断言,判断请求是否符合规则
- Path=/user/**
filters:
- AddRequestHeader=YourName,ZhangCan
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters:
- AddRequestHeader=YourName,ZhangCan #这里
全局过滤器GlobalFilter
写代码实现过滤逻辑
例子:实现接口+提供组件
@Order(-1)//指定过滤器顺序,值越小越优先
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
//2.获取参数中的authorization参数
String auth = params.getFirst("authorization");
//3.判断参数值是否等于admin
if ("admin".equals(auth)) {
//4.是,放行
return chain.filter(exchange);
}
//5.否,拦截
//5.1.设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
//5.2.拦截请求
return exchange.getResponse().setComplete();
}
}
过滤器链执行顺序
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高
- GlobalFilter通过@Order注解指定order值
- 路由过滤器和defaultFilter的order由Spring指定,默认按照声明顺序从1递增
- 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行
网关CORS跨域配置
只需配置即可:
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
cors-configurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.abc.com"
allowedMethods: # 允许的请求方法
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowedCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
Docker
数据卷命令
docker volume [command]
create:创建
inspect:显示某个volume的信息
ls:列出所有volume
prune:删除未使用的volume
rm:删除volume
自定义镜像
Dockerfile文件:
FROM java:8-alpine
COPY ./docker-demo.jar /tmp/app.jar
EXPOSE 8080
ENTRYPOINT java -jar /tmp/app.jar
创建镜像:
docker build -t javaweb:1.0 .
创建容器:
docker run --name mj -d -p 8080:8080 javaweb:1.0
RabbitMQ
入门
拉取镜像:
docker pull rabbitmq:3-management
运行容器:
docker run \
-e RABBITMQ_DEFAULT_USER=zc \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq \
--hostname mq1 \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
打开管理网页:
http://localhost:15672
消息模型
- Basic Queue:基本消息队列
- Work Queue:工作消息队列
- 发布订阅,根据交换机类型不同分为三种:
-
- Fanout Exchange:广播
- Direct Exchange:路由
- Topic Exchange:主题
Basic Queue
引入AMQP依赖:
<!--AMQP依赖,包含rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
在publisher服务中配置mq:
spring:
rabbitmq:
host: localhost
port: 5672
virtual-host: /
username: zc
password: 123321
编写publisher代码:
@SpringBootTest
public class SpringAMQPTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendMessage2SimpleQueue() {
String queueName = "simple.queue";
String message = "hello, spring amqp!";
rabbitTemplate.convertAndSend(queueName, message);
}
}
在consumer服务中配置mq:
同publisher
在consumer中新建一个类,编写消费逻辑:
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue(String msg) {
System.out.println("消费者接收到消息:【" + msg + "】");
}
}
运行consumer主程序即可接收到消息
Work Queue
本质是多个消费者绑定到一个queue,同一个消息只会被一个消费者处理。
消费者默认预取的消息数量是无限,可以配置它。
Fanout Exchange 广播
会把消息广播到所有绑定的queue中。
在consumer中声明交换机和队列,并绑定它们:
@Configuration
public class FanoutConfig {
//交换机
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("itcast.fanout");
}
//队列1
@Bean
public Queue fanoutQueue1() {
return new Queue("fanout.queue1");
}
//绑定队列1到交换机
@Bean
public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
//队列2
@Bean
public Queue fanoutQueue2() {
return new Queue("fanout.queue2");
}
//绑定队列2到交换机
@Bean
public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
}
在consumer中分别监听2个队列:
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
System.out.println("消费者接收到fanout.queue1的消息:【" + msg + "】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
System.out.println("消费者接收到fanout.queue2的消息:【" + msg + "】");
}
}
在publisher中发送消息给交换机:
@Test
public void sendMessage2FanoutExchange() {
String exchangeName = "itcast.fanout";
String message = "hello, every one";
rabbitTemplate.convertAndSend(exchangeName, "", message);
}
Direct Exchange 路由
会把消息根据规则路由到指定的queue,因此被称为路由模式。
- 每个queue都与exchange设置一个BindingKey
- 发送消息时,会指定消息的RoutingKey
- exchange只会把消息转发到绑定key相同的queue
在consumer中利用@RabbitListener声明Exchange, Queue, RoutingKey:
@Component
public class SpringRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue("direct.queue1"),
exchange = @Exchange(value = "itcast.direct", type = ExchangeTypes.DIRECT),
key = {"red", "blue"}
))
public void listenDirectQueue1(String msg) {
System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue("direct.queue2"),
exchange = @Exchange(value = "itcast.direct", type = ExchangeTypes.DIRECT),
key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg) {
System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
}
}
在publisher中发送消息:
@Test
public void sendMessage2DirectExchange() {
String exchangeName = "itcast.direct";
String message = "hello, blue";
rabbitTemplate.convertAndSend(exchangeName, "blue", message);
}
Topic Exchange 主题
TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以.
分割。
通配符:
- #:代指0个或多个单词
- *:代指一个单词
在consumer中利用@RabbitListener声明Exchange, Queue, RoutingKey:
@Component
public class SpringRabbitListener {
@RabbitListener(bindings = @QueueBinding(
value = @Queue("topic.queue1"),
exchange = @Exchange(value = "itcast.topic", type = ExchangeTypes.TOPIC),
key = "china.#"
))
public void listenTopicQueue1(String msg) {
System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue("topic.queue2"),
exchange = @Exchange(value = "itcast.topic", type = ExchangeTypes.TOPIC),
key = "#.news"
))
public void listenTopicQueue2(String msg) {
System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
}
}
在publisher中发送消息:
@Test
public void sendMessage2TopicExchange() {
String exchangeName = "itcast.topic";
String message = "一条中文消息";
rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}
消息转换器
spring的默认消息转化器会将消息对象进行序列化,然后发送。
如果要修改只需要定义一个MessageConverter类型的bean即可。推荐用JSON方式序列化。
引入依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
声明MessageConverter:
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
接收消息:
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "object.queue")
public void listenObjectQueue(Map<String, Object> msg) {
System.out.println("消费者接收到object.queue的消息:" + msg);
}
}
发送消息:
@Test
public void sendMessage2ObjectQueue() {
Map<String, Object> message = new HashMap<>();
message.put("name", "小明");
message.put("age", 21);
rabbitTemplate.convertAndSend("object.queue", message);
}
Sentinel——流量治理
入门
1、去github release下载网页控制台的jar包,运行即可:
java -jar sentinel-dashboard-1.8.6.jar
打开localhost:8080
2、微服务整合sentinel
引入依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
访问微服务的任意端点,触发sentinel监控