1、五大组件
注册中心 euraka nacos
负载均衡 ribbon,feign集成了ribbon
熔断降级
路由网关 gateway
配置中心
https://blog.csdn.net/qq904748592/article/details/127572608
2、nacos
nacos默认以集群形式启动
单机模式启动命令
#切换⽬录
cd nacos/bin
#命令启动
./startup.sh -m standalone
2、路由网关
Predict决定了请求由哪一个路由处理,在路由处理之前,需要经过“pre”类型的过滤器处理,处理返回响应之后,可以由“post”类型的过滤器处理
三大组件:路由 断言 过滤器
路由:ID、目标URI 、断言、过滤器 ,如果断言为true,将匹配路由
3、springcloud gateway oauth2
https://blog.csdn.net/const_/article/details/123742701
4、如果服务全都启动之后,注册中心服务器挂掉,不会影响现有服务之间的调用,因为调用使用的是本地的服务列表,但是如果新增或者删除掉了服务,则不能正确访问。服务是定期从注册中心更新服务列表到本地
nacos仅仅是服务注册作用, Rinnon 实现负载均衡
5、Feign工作原理
主要技术就是动态代理和反射
启动类@EnableFeignClients注解扫描所在包及其子包下所有具有@FeignClient注解的接口,
创建代理类实现feign接口中的方法:
- 代理类获取当前类所实现的接口也就是Feign接口
- 获取接口上的注解@FeignClient(name = “product-service”)
- 获取注解上的属性值
- 获取方法的引用和注解@RequestMapping(“/product/{pid}”)
- 获取传入的参数
- 拼接成url,通过ribbon获取本地缓存的服务名,将服务器替换成ip和port
- 使用RestTemplate进行网络调用
- 内部解析返回的结果字符串,获取方法返回值字节码,
- 将返回的字符串解析成方法返回值对象
创建对象并放入到sping容器中。
调用的是代理类的方法
6、容错方案
隔离 :不把所有的线程用于访问其中的一个服务,对线程进行隔离,防止对某一个服务的调用占用所有的线程
超时:超过指定时间后,断开请求,释放线程 实际中超时阈值不要超过2s(调用第三方接口,处理时间未知, 超时时间不可控,要使用异步调用)
限流:从接口处限制请求数
熔断:连续多次访问不成功,自动断开,偶尔发送请求,如果可用则熔断关闭,不可用则为熔断开启
降级:无法调用服务时,调用本地的fallback
常见容错组件:Hystrix Resilience4j sentinel
7、sentinel
项目中添加依赖
<!--sentinel组件-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
spring:
cloud:
sentinel:
transport:
port: 9999 #跟控制台交流的端⼝,随意指定⼀个未使⽤的端⼝即可
dashboard: localhost:8089 # 指定控制台服务的地址
启动sentinel控制台
java -Dserver.port=8089 -Dcsp.sentinel.dashboard.server=localhost:8089 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar
直接使⽤jar命令启动项⽬(控制台本身是⼀个SpringBoot项⽬)
通过浏览器访问localhost:8089 进⼊控制台 ( 默认⽤户名密码是 sentinel/sentinel )
8 GateWay
先经过断言 后经过过滤器
GatewayHandlerMapping 和 Gateway web Handler 指的就是断言,相当于地址映射和web请求
过滤分为前置拦截和后置拦截。通用规则可以写在过滤器中
过滤器分为局部和全局,GatewayFilter:应⽤到单个路由或者⼀个分组的路由上。 GlobalFilter:应⽤到所有的路由上
编写Filter类时,注意名称是有固定格式xxxGatewayFilterFactory
如果为TimeGatewayFilterFactory
在application.yml中配置
filters:
- Time = true
9、微服务拆分原则
基于业务逻辑,根据职责范围划分,如果业务之前耦合较大,不应进行拆分 会出现大量的网络调用
基于稳定性,依据修改频率高低的业务进行拆分
基于可靠性,
基于高性能,
10、任务调度
任务调度是为了自动完成特定任务,在约定的特定时刻去执行任务的过程
使用spring的定时任务,在集群的情况下,可能会出现任务重复执行的问题
11、消息中间件RocketMQ
可以解决代码耦合问题,如订单服务调用积分服务,如果为直接远程调用积分微服务,则下单流程紧密依赖积分微服务,如果积分微服务出现故障,则下单流程失败。积分的操作实时性要求没有那么高, 可以使用消息中间件处理
流量削峰,controller层接收到请求后,放入到消息队列,业务层对消息队列按照一定速率进行消费,以减少数据库压力,数据库的每秒并发2500左右
数据分发,广播机制
一般一个业务对应一个队列,也就是topic,实际存储在messagequeue中,一个topic中有四个messagequeue(四个写队列,四个读队列),如果只有一个队列,为了保证线程安全,必须在写操作的时候进行上锁,四个队列提高效率。
通过内部维护一个index来实现:index对队列的size做取模运算,分配到对应队列。
队列size4可以自定义设置,最好与cpu核心数一致
发送的消息封装成message对象存入messagequeue中
RocketMQ安装步骤
1.将压缩包上传服务器,把 rocketmq-all-4.4.0-bin-release.zip 拷⻉到 /usr/local/software
2.使⽤解压命令进⾏解压到 /usr/local ⽬录
unzip /usr/local/software/rocketmq-all-4.4.0-bin-release.zip -d /usr/local
3.软件⽂件名重命名
mv /usr/local/rocketmq-all-4.4.0-bin-release/ /usr/local/rocketmq-4.4/
4.设置环境变量 ,修改/etc/profile
export JAVA_HOME=/usr/local/jdk1.8
export ROCKETMQ_HOME=/usr/local/rocketmq-4.4
export PATH=$JAVA_HOME/bin:$ROCKETMQ_HOME/bin:$PATH
修改后执行 source profile
5.修改脚本中的JVM相关参数,修改⽂件如下
vi /usr/local/rocketmq-4.4/bin/runbroker.sh
vi /usr/local/rocketmq-4.4/bin/runserver.sh
修改启动参数配置 ,因为rocketmq默认需要内存8G,虚拟机中不修改的话启动会报错,实际应用中不需要修改!!!
JAVA_OPT="${JAVA_OPT} -server -Xms1g -Xmx1g -Xmn512m"
6.修改配置⽂件
vi /usr/local/rocketmq-4.4/conf/broker.conf
新增配置如下:
7.启动NameServer
# 1.启动NameServer
nohup sh mqnamesrv &
# 2.查看启动⽇志
tail -f ~/logs/rocketmqlogs/namesrv.log
8.启动Broker
#1.启动Broker
nohup sh mqbroker -n 部署的IP地址:9876 -c /usr/local/rocketmq-
4.4/conf/broker.conf &
#2.查看启动⽇志
tail -f ~/logs/rocketmqlogs/broker.log
9.使⽤命令查看是否开启成功
jps #查看java进程命令
需要看到 NamesrvStartup 和 BrokerStartup 这两个进程
消息发送
同步发送
应用程序向中间件发送消息,需要等待消息中间件将信息存储完毕并响应回去后,应用程序才继续向下执行
异步发送
应用程序向中间件发送消息,消息中间件收到消息之后直接返回给程序,应用程序继续向下执行(此时消息并没有完全存储到磁盘),存储完成后会将存储的结果通过回调通知程序
一次性发送
不需要知道消息是否被消息中间件存储,业务程序只管发送消息,使用场景:日志
消息消费
注意:集群模式和广播模式针对的是同一个消费者组下的消费者,对与不同消费者组的消费者来说,所有消息都是可见的,rocketmq的消费是逻辑上的消费,并没有真正的删除数据,所有数据都会保留72小时
集群模式
在消费模式为集群的模式下,同一条消息只能被其中一台机器消费
广播模式
集群中的每台机器都能消费到所有消息
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("helloConsumerGroup");
consumer.setMessageModel(MessageModel.BROADCASTING); //默认为集群模式
顺序消费
将需要顺序消费的消息,在发送时,发送至同一个队列(一个topic下一般有四个队列),消费者使用一个线程消费
生产者端
定义一个MessageQueueSelector,重写select方法,根据业务确定队列index,发送时使用producer.send(msg,selector,10);参数10可以是Object类型的任意参数,用来确定使用哪个队列。
// 发送时选择队列,以实现顺序消费
MessageQueueSelector selector = new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, Message message, Object o) {
Long id=(Long) o;
// 根据订单id确定放入哪个队列(条件可变)
int index=(int)(id%list.size());
return list.get(index);
}
};
for (int i = 0; i < 10; i++) {
Message msg = new Message(topic,("RocketMQ顺序消息"+i).getBytes(Charset.defaultCharset()));
producer.send(msg,selector,10);
}
消费者端
设置从队列的哪里开始消费,MessageListener设置为MessageListenerOrderly
// 设置从哪开始消费
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
//
consumer.setMessageListener(new MessageListenerOrderly() {
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
for (MessageExt msg : list) {
System.out.println("线程"+Thread.currentThread()+"队列ID"+msg.getQueueId()+"消息内容"+new String(msg.getBody(), Charset.defaultCharset()));
}
return ConsumeOrderlyStatus.SUCCESS;
}
});
延迟消费
使用场景:超时未支付 取消订单, 超时未领取红包,取消等,若使用定时任务,系统性能开销较大
发送时标记为延时消息,消息中间件有一个基于内存的定时器,每秒钟扫描,到达指定时间后,消费者才能够消费到此延时消息,rocket不支持任意时长的延时,默认包含18个级别的延时等级,可以再broker.conf中进行修改
发送时生产者对message进行设置
Message msg = new Message(topic,("RocketMQ顺序消息"+i).getBytes(Charset.defaultCharset()));
msg.setDelayTimeLevel(3);
过滤消费
tag过滤
生产者实例化Message时添加一个tag
Message msg = new Message(topic,"tagA",("RocketMQ消息").getBytes(Charset.defaultCharset()));
消费者订阅时设置订阅tag
consumer.subscribe("helloTopic","tagA || tagC");
属性过滤(sql92过滤)
生产者对message添加属性
msg.putUserProperty("age","22");
消费者端根据数据过滤
consumer.subscribe("helloTopic", MessageSelector.bySql("age<30"));
使用属性过滤需要在broker.conf中添加enablePropertyFilter=true
12、springboot集成rocketmq
引入依赖
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.0.4</version>
</dependency>
application.yml
rocketmq:
name-server: 192.168.164.129:9876
producer:
group: my-group
生产者发送
@Autowired
private RocketMQTemplate rocketMQTemplate;
/*发送同步消息*/
@Test
public void sendMsg(){
Message msg= MessageBuilder.withPayload("boot发送同步消息").build();
rocketMQTemplate.send("hellotopic-boot",msg);
}
/*发送异步消息*/
@Test
public void sendAsyncMsg() throws InterruptedException {
Message msg= MessageBuilder.withPayload("boot发送异步消息").build();
System.out.println("发送前");
rocketMQTemplate.asyncSend("hellotopic-boot", msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
System.out.println("发送状态"+sendResult.getSendStatus());
}
@Override
public void onException(Throwable throwable) {
System.out.println("消息发送失败");
}
});
System.out.println("发送完毕");
TimeUnit.SECONDS.sleep(5);
}
/*发送一次性消息*/
@Test
public void sendOnewayMsg(){
Message msg= MessageBuilder.withPayload("boot发送一次性消息").build();
rocketMQTemplate.sendOneWay("hellotopic-boot",msg);
}
/*顺序发送*/
@Test
public void sendOrderly(){
// 设置队列选择器
rocketMQTemplate.setMessageQueueSelector(new MessageQueueSelector() {
@Override
public MessageQueue select(List<MessageQueue> list, org.apache.rocketmq.common.message.Message message, Object o) {
String idstr=(String)o;
Long id= Long.parseLong(idstr);
int index= (int) (id%list.size());
return list.get(index);
}
});
Message msg= MessageBuilder.withPayload("boot发送一次性消息").build();
rocketMQTemplate.sendOneWayOrderly("hellotopic-boot",msg,"10");
}
//延时消费
@Test
public void sendDelay(){
Message msg=MessageBuilder.withPayload("发送延时消息").build();
rocketMQTemplate.syncSend("hellotopic-boot",msg,3000,3);
System.out.println("发送时间"+new Date());
}
/*过滤发送,主题:标签*/
@Test
public void sendFilterMsg(){
Message msg=MessageBuilder.withPayload("发送延时消息").build();
rocketMQTemplate.sendOneWay("hellotopic-boot:TagA",msg);
System.out.println("发送时间"+new Date());
}
//sql92表达式过滤
@Test
public void send92SQLMsg(){
Message msg=MessageBuilder.withPayload("发送延时消息").setHeader("age","22").build();
rocketMQTemplate.sendOneWay("hellotopic-boot:TagA",msg);
System.out.println("发送时间"+new Date());
}
消费者消费
@Component
@RocketMQMessageListener(consumerGroup = "bootconsumer",topic = "hellotopic-boot")
//@RocketMQMessageListener(consumerGroup = "bootconsumer",topic = "hellotopic-boot",messageModel = MessageModel.BROADCASTING) //广播模式
public class HelloTopicListenser implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
System.out.println("收到消息"+new String(messageExt.getBody(), Charset.defaultCharset()));
}
}
//顺序消费
@Component
@RocketMQMessageListener(consumerGroup = "orderlytopicGroup",topic = "orderlytopic-boot",consumeMode = ConsumeMode.ORDERLY)
public class OrderlyTopicListenser implements RocketMQListener<MessageExt> {
@Override
public void onMessage(MessageExt messageExt) {
System.out.println("收到顺序消息:"+new String(messageExt.getBody(), Charset.defaultCharset())+ " 队列ID:"+ messageExt.getQueueId());
}
}
//过滤消费,标签过滤
@RocketMQMessageListener(consumerGroup = "orderlytopicGroup",topic = "orderlytopic-boot",consumeMode = ConsumeMode.ORDERLY,selectorExpression = "TagA")
//sql92过滤,selectorType默认是标签过滤
@RocketMQMessageListener(consumerGroup = "orderlytopicGroup",topic = "orderlytopic-boot",selectorType = SelectorType.SQL92,selectorExpression = "age <25")
{
System.out.println("收到顺序消息:"+new String(messageExt.getBody(), Charset.defaultCharset())+ " 队列ID:"+ messageExt.getQueueId());
}
}
//过滤消费,标签过滤
@RocketMQMessageListener(consumerGroup = "orderlytopicGroup",topic = "orderlytopic-boot",consumeMode = ConsumeMode.ORDERLY,selectorExpression = "TagA")
//sql92过滤,selectorType默认是标签过滤
@RocketMQMessageListener(consumerGroup = "orderlytopicGroup",topic = "orderlytopic-boot",selectorType = SelectorType.SQL92,selectorExpression = "age <25")