微服务架构的思想
微服务是一种架构风格,旨在将应用程序拆分为一组小型、独立的服务,每个服务都专注于执行特定的业务功能。每个服务都可以使用自己的技术栈、数据库和开发团队,通过轻量级通信机制进行交互。
微服务架构的优点:
-
独立开发和部署:故障隔离。
-
可伸缩性好:便于业务的扩展。
-
维护性高:每个服务关注一个特定的业务功能,修改和维护起来更容易。
微服务架构的缺点:
-
复杂性增强:服务间的通信,数据的一致性,以及分布式事务等问题
-
运维难度大,成本高;对团队的协作有更高的要求
Nacos
官网: What is Nacos
由于存在服务与服务之间的调用问题;我们需要将服务注册到nacos中。
Nacos是什么?
Nacos致力于帮助服务注册、发现、配置和管理服务。Nacos提供了一组简易的特性集,帮助快速实现动态服务发现、服务配置、服务元数据及流量管理。
Nacos基本结构
-
nacos-server:Nacos服务端,在页面中管理的进程
-
nacos-client:连接nacos-server 根据提供的接口功能进行调用的进程。这个进程一般需要整合到微服务中运行,使用微服务信息和nacos-server进行交互。
Nacos的功能及使用
<!--nacos-注册抓取-client--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--nacos-discovery-->
注册发现
-
作用:
将服务注册到nacos中,用于被其他服务发现并使用。 注册发现接口功能: nacos-server nacos-client之间交互逻辑 众多接口,可以分为两大类,写操作(注册注销), 读操作(读取服务,监听服务)
-
注册:将自己应用实例信息 放到nacos. 目的是让别人可以发现.
-
发现: 读取别人在nacos注册信息. 目的是可以调用别人.
在注册时可以选择为永久实例还是临时实例:
-
永久实例:保证日常使用,永远不会删除,nacos会主动去探测心跳是否正常
-
临时实例:多用于高峰并发时,结束后会直接停止;它要主动向nacos提供心跳。
在注册时还可以将服务进行分组,存放在不同的命名空间中;从而实现隔离的开发,灰度开发等。
-
-
如何使用:
第一步在.pom文件添加依赖
第二步在yml配置文件中添加nacos相关属性
#微服务配置 #在微服务架构中,表示微服务名字 #不同的命令表示功能不一样,相同的名字,功能接口都相同 spring: application: name: csmall-business #微服务cloud配置 cloud: #微服务cloud中组件nacos nacos: #nacos中注册发现的功能 discovery: #填写nacos的服务端地址 server-addr: localhost:8848 #当前启动进程是永久实例还是临时实力 true 表示临时实力 false表示永久实例 #true是默认值 ephemeral: true #nacos实例的ip地址 ip: 127.0.0.1 #命名空间 namespace: f033ea8e-15ca-4f37-b112-127edc03de9e #分组 group: 1.0 #心跳检测的间隔时间 heart-beat-interval: 5 #超时心跳删除时间 ip-delete-timeout: 20
配置中心
-
作用:
一般在项目中,我们会用到很多公用的配置信息;例如:数据库连接信息、mybatis的配置信息等。可将公用的配置信息放在nacos的配置中心。
-
如何使用:
在微服务项目中的resources文件夹中添加bootstrap.yml配置文件;当项目启动时优先读取此配置文件。
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--springboot版本大于2.3.X,需要引入这个依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency>
spring: profiles: #开启不同环境配置 active: dev #bootstrap定义不同环境,在同一个文件bootstrap.yaml #---区分,每个环境中定义名称 --- spring: config: activate: on-profile: dev #告诉bootstrap 远程配置中心nacos地址 和我们要读取的配置文件(先读默认的) application: #所有的application.name都是服务名称 name: csmall-stock cloud: nacos: config: #必须配置的值 server-addr: localhost:8848 #文件类型 后缀 默认是properties txt json yaml xml html file-extension: yaml #前缀,默认是服务名称,不想使用默认值,可以自定义 #prefix: sdlaslkdfjsdlj #多环境运行的 namespace 命名空间的id #namespace: f033ea8e-15ca-4f37-b112-127edc03de9e #持续发布中,保证灰度发布 分组 #group: 1.0 #是否支持刷新.远程配置一旦修改,本地进程内存数据是否刷新,如果刷新,true,不需要重启 #false 重启,内存才变动 默认也是true refresh-enabled: true #读取指定的文件 shared-configs: - data-id: redis.yaml group: 1.0 refresh-enable: true - data-id: datasource.yaml group: 1.0 refresh-enable: true - data-id: es.yaml group: 1.0 refresh-enable: true --- spring: config: activate: on-profile: test
Dubbo远程调用
-
作用:
Dubbo是一个RPC框架,序列化底层通信协议。
当我们需要服务与服务之间内部的远程调用时,我们可以借助Nacos和Dubbo结合使用。

-
实现:
方法一(使用apache):
引入依赖
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.8</version> </dependency>
provider的applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!--定义应用 dubbo概念中也有应用--> <dubbo:application name="${spring.application.name}"> <!--qos关闭--> <dubbo:parameter key="qos" value="false"/> </dubbo:application> <!--注册中心配置--> <dubbo:registry id ="nacos" protocol="nacos" address="localhost:8848" use-as-config-center="false" use-as-metadata-center="false"/> <!--dubbo配置协议 设置ip port -1 同一个服务器端口 会从20880开始向后顺延 协议名称--> <dubbo:protocol name="dubbo" port="-1"/> <!--dubbo配置spring中的 暴露接口 和接口实现 创建过程交给spring管理,使用dubbo在用--> <!--provider 对应service consumer 对应 referrence--> <!--springxml 相当于bean标签--> <dubbo:service interface="com.tarena.luban.demo.cart.api.DubboTestApi" ref="dubboTestRpcApi" registry="nacos"/> </beans>consumer的applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!--dubbo应用配置--> <dubbo:application name="${spring.applicaiton.name}"> <dubbo:parameter key="qos" value="false"/> </dubbo:application> <!--注册中心--> <dubbo:registry id="nacos" protocol="nacos" address="localhost:8848" use-as-metadata-center="false" use-as-config-center="false"/> <!--通信协议--> <dubbo:protocol name="dubbo" port="-1"/> <!--consumer角色 reference 底层是个代理对象 注入使用--> <!--check = false 表示消费者启动时候会不会检查抓取的provider存在还是不存在--> <dubbo:reference id="dubboTestApi" interface="com.tarena.luban.demo.cart.api.DubboTestApi" registry="naocs" check="false"/> </beans>由于使用applicationContext.xml 对Dubbo应用进行了配置,需要在启动类中添加@ImportResource(“applicationContext.xml”)
方法二(使用阿里巴巴):
引入依赖:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-dubbo</artifactId> </dependency>
yaml配置:
spring: application: name: csmall-stock dubbo: application: #dubbo客户端 在nacos注册,携带的服务名称 name: ${spring.application.name} protocol: #port表示dubbo底层通信协议使用的端口 -1 自增的端口号 #20880开始,查看当前服务,哪个没有被占用 port: -1 name: dubbo registry: #告诉dubbo注册中心类型和地址 address: nacos://localhost:8848 #禁止dubbo在nacos中自动创建一些无效的配置文件 use-as-config-center: false use-as-metadata-center: false cloud: #明确当前服务,订阅的其他服务,如果需要调用其他服务,用多个服务拼接, #不配置这个属性,会导致当前的dubbo客户端默认订阅nacos所有服务 # 不给值,订阅空 subscribed-services: consumer: #如果当前程序 有consumer的角色,不会在启动时检查provider是否存在 #如果当前程序不是consumer,这个配置没有任何作用 check: false配置类+注解:
配置类添加 @EnableDubbo 注解 》》 provider业务层添加 @DubboService 注解 》》 consumer调用处使用 @DubboReference 注解引入。
-
负载均衡
负载均衡的计算策略:加权随机,加权轮训,最小连接值,一致性hash。
优先级:provider>consumer;局部>全局。
<dubbo:server interface="inter" ref="bean" loadbalance="random" weight="6"/>dubbo: provider: loadbalance: roundrobin
注解 @Reference(version = "${product.service.version}",loadbalance="roundrobin")
-
重试机制
可能存在网络抖动的情况,造成调用的失败,可以设置重试机制,进行多次调用。
<dubbo:reference id="stockApi" interface="com.tarena.luban.demo.stock.api.StockApi" registry="nacos" check="false" retries="5" timeout="2000000"/>
Sentinel熔断/限流/降级
<dependencies> <!--sentinel alibaba cloud 组件--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> </dependencies>
核心概念
-
资源
资源是Sentinel的关键概念。它可以是Java应用程序的任何内容,例如:由应用程序提供的服务,一段代码,一个方法,一个类等等。只要通过Sentinel API 定义的代码,就是资源,能够被Sentinel保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
如何定义资源:
方法一(硬编码):
//准备一个sentinel资源入口,进入sentinel统计责任链中
Entry entry=null;
try{
//对entry赋值,赋值过程,会真正进入sentinel计算统计责任链中
entry= SphU.entry("sayHello");
String result=helloService.sayHello(name);
return result;
}catch (BlockException e){
log.info("当前资源sayHello受到了限制",e);
}finally {
//释放entry资源
if(entry!=null){
entry.exit();
}
}
方法二(注解定义):
@SentinelResource(value="资源名")
@SentinelResource( value = "serviceHi", blockHandler = "aaa", blockHandlerClass = 类名.class fallback = "bbb", fallbackClass = 类名.class)
注解的底层原理:就是使用AOP切面技术将SphU.entry("资源名")放到切面编程中。
blockHandler: 用于编写处理降级后的逻辑代码。
fallback: 用于编写出现其他异常的逻辑代码
-
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
我们可以将规则存放在Nacos配置中心内。使用时直接读取。
熔断
-
作用:
在多个服务调用过程中,由于某一个服务的实例故障,导致调用失败、延迟、等待;挨个相传导致整个大系统瘫痪。此时我们就需要对出现故障的服务实例进行熔断策略:就好比一个断路器,牺牲局部来保全大局。
断路器的三种状态:闭合,断开,半开.
-
规则
{ "resource": "sayHello", "count": 100, "grade": 0, "timeWindow": 10, "minRequestAmount": 10, "slowRatioThreshold": 0.5, "statIntervalMs": 10000 }resource: 绑定已经定义好的资源名称
count: 熔断触发的阈值(不再调用这个资源,而是访问降级策略)。如果grade=0 count表示慢调用临界RT(响应时间单位毫秒),超过这个数字,就记录一次慢调用。grade=1,count值应该是>0小于1的小数,表示异常比例;grade=2 count配置整数,表示异常出现的次数。
grade: 熔断类型 0 默认值 慢调用比例 1 异常比例 2异常数
timeWindow: 如果触发熔断,持续时间,单位秒。
minRequestAmount: 最少统计请求数量,如果没达到,即使超过count的阈值,也不熔断
slowRatioThreshold: 慢调用比例,只有在grade=0的时候才有用.
statIntervalMs: 统计时长,计算判断熔断规则是否违反的逻辑中,有很多都需要统计时间段 单位是毫秒数
限流
-
相关概念:
QPS: 每秒查询的数量
RT: 一次请求的响应时间
PV: 一次请求计算一次pv
QPS=并发量/RT
-
作用: 当访问量剧增时,可以对请求进行限流控制;当达到流量压测值时,超出的请求禁止访问,直接返回失败
-
规则
{ "resource": "app", "count": 1, "grade": 1, "strategy": 1, "limitApp": "limitApp", "controlBehavior": 0 }resource: 绑定已经定义好的资源名称
count: 限流阈值,至于阈值表示的何种含义 取决于grade grade=1 qps数量 grade=0 并发数量(web应用并发表示同时存在请求个数)
grade: 1 qps 流控类型; 0 并发 流控类型
strategry: 限流的模式
0 直接限流::针对resource的资源做限流
1 关联限流:limitApp 属性值也表示一个资源,如果app的资源访问超过阈值了,限制limitApp。
例如: 双11,所有资源都需要给下单让路.
2 链路 limitApp 指定的资源,查看当前资源的链路是否是通过limitApp进入的,如果不是,不限制流量。如果是则限制流量.(限制的是来源)
limitApp: 当前资源关联的其它的某个资源,是否生效取决于strategy的值 1 2有用,而且作用不同.
controlBehavior: 限流效果,当访问已经违反了限流规则时,表现状态
0: 直接拒绝抛异常
1: Warm up 慢慢的拒绝抛异常
2: 排队 后续请求不拒绝 排队到一个queue队列(有上限)
降级
-
作用:
退而求其次的原则;在保证核心业务的前提下,提供可用数据。当发生了熔断和限流的情况时,我们可以将其请求降级处理,返回一个可以接受的结果。
Getaway网关
1.概念
微服务网关是一个用于管理和监控微服务的入口,用于转发和路由来自客户端的请求。微服务网关可以将来自客户端的请求转发给后端的多个微服务,同时也可以处理跨域、身份验证、限流、缓存、流量控制等一系列与微服务相关的功能,从而简化了微服务架构的服务开发的复杂度。
网关的架构图:

当客户端发出请求后,请求进入网关,经过GatewayHandlerMapping来匹配传入的请求路径和谓词(即根据预定义的路由规则进行映射),经过GatewayWebHandler进行实际的处理和转发请求;期间经过过滤器可以做一些自定义的处理(例如:解析JWT,检查权限等);从nacos中拿到所有服务注册的路由信息;由负载均衡进行计算代理向后调用项目,从而返回数据。
2. 如何使用:
依赖:
<!--网关发现服务后,进行负载均衡的转发调用--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <!--网关核心依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
application.yml配置:
server: #端口 port: 10000 spring: application: # spring应用名称 name: luban-demo-gateway main: # springcloud gateway 底层框架webflux 和tomcat里的servlet有冲突 # spring-boot-starter-web 需要这个值避免冲突 web-application-type: reactive cloud: gateway: # 为入门案例转发,做一个路由配置 # routes 是一个list类型属性 # 表示可以在网关中配置的路由规则 routes: # id 全局唯一 - id: cart-test # 指定访问一个uri: http://127.0.0.1:9011 uri: lb://luban-demo-cart predicates: # Path 路径匹配请求 /**匹配所有进入网关的请求. - Path=/cart/** filters: # 可以将断言匹配的路径path 去处[n]级前缀路径,然后拼接uri - StripPrefix=1 - id: stock-test #uri: http://127.0.0.1:9002 uri: lb://luban-demo-stock predicates: - Path=/stock/** filters: - StripPrefix=1
3. Path路由匹配规则
| 规则 | 说明 | 案例 |
|---|---|---|
| /? | 匹配/开始的任意一个字符 | /a, /b, /c |
| /* | 匹配/开始的任意一个字符串 | /abc, /bc |
| /** | 匹配/开始的任意多个字符串 | /a/b/c, /a/c |
4.跨越处理
spring:
application:
# spring应用名称
name: luban-demo-gateway
main:
# springcloud gateway 底层框架webflux 和tomcat里的servlet有冲突
# spring-boot-starter-web 需要这个值避免冲突
web-application-type: reactive
cloud:
gateway:
# 可以配置跨域
globalcors:
cors-configurations:
"[/**]":
#允许 origin来源的配置
allowed-origin-patterns: "*"
#- "order.luban.com"
#- "cart.luban.com"
#- "**.luban.com"
#允许跨域cors时的头
allowed-headers: "*"
#- "Accept"
#- "Authorization"
#允许跨域的方法
allowed-methods: "*"
#- "GET"
#- "POST"
5.网关过滤器
每个web应用都可以存在过滤器和拦截器,SpringMVC中也有过滤器和拦截器;逻辑基本相似,只是不同组件中的使用习惯和编码api不同。
常用于用户认证授权(spring security框架)
-
过滤器特点:
1.过滤器各司其职
2.顺序执行需要符合业务逻辑(越简单、越快速的过滤器越在前面执行)
3.特殊的情况,过滤器可以实现拦截的效果(请求和响应对象在过滤器整条链路中)
-
使用案例:
@Component public class FirstFilter implements GlobalFilter, Ordered { /** 网关中 编写过滤逻辑的方法 * @param exchange webflux 底层给我们封装的一个对象 包装了请求和响应数据 * @param chain 过滤链,贯穿整个web容器的一个过滤器连接对象 * @return Mono SpringWebFlux 返回结果 SpringMVC Model ModelAndView */ @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { /** 在此处写需要在过滤器执行的逻辑*/ //request对象 包含请求url地址 path 请求query参数 请求头数据 //从exchange拿到请求对象 ServerHttpRequest request = exchange.getRequest(); //请求路径 RequestPath path = request.getPath(); URI uri = request.getURI(); System.out.println("获取uri:"+uri.toString()); System.out.println("获取path:"+path.toString()); //请求参数 localhost:10000/abc?name=haha&age=18&girlFriends[0]=王翠花 &girlFriends[1]=刘翠花 MultiValueMap<String, String> queryParams = request.getQueryParams(); //key 是string value List<String> //name=haha name=List<String>{"haha"} //name[0]=haha&name[1]=gaga 不会像springmvc一样整理成list数据,只会单独封装 //query在当前方法的value值永远都是一个元素的list对象. List<String> name = queryParams.get("name"); System.out.println("name值元素个数:"+name.size()); System.out.println("数据分别是:"+name.toString()); //jwt解析,获取的headers Authorization //获取头数据,打印展示 HttpHeaders headers = request.getHeaders(); //从所有头中,获取一个名称的头 List<String> headerValues = headers.get("Accept"); System.out.println("当前头的元素个数:"+headerValues.size()); System.out.println(headerValues); //request其它api request.getId(); //能解析客户端ip request.getRemoteAddress(); return chain.filter(exchange); } /** 此方法用于设置过滤器执行的顺序;返回值越小越靠前。 */ @Override public int getOrder() { return 0; } }
RocketMQ消息队列
-
rocketmq-dashboard(辅助观察仪表盘)
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<!--传递进来一个rocket-client 4.9.3 和当前软件版本没有冲突-->
<!--如果编写测试案例的时候,出现了匪夷所思的异常 可以尝试将版本更新4.9.4-->
<version>2.2.2</version>
<!--<exclusions>
<exclusion>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
</exclusion>
</exclusions>-->
</dependency>
<!--<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>4.9.4</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
<version>4.9.4</version>
</dependency>-->
rockermq :适用于高吞吐、强一致性的消息通信场景。它主要用于大规模互联网应用的消息中间件,支持延迟消息、事务消息、顺序消息等特性。
rabbitmq :适用于灵活性较高、消息处理逻辑较为复杂的场景。它支持多种消息协议,包括AMQP、STOMP、MQTT等,并提供了丰富的特性和插件机制。
kafka :适用于处理大规模的实时数据流。它是一种高吞吐、高可靠、低延迟的分布式消息系统,广泛应用于日志收集、实时流处理、事件驱动架构等场景。
消息队列可以实现哪些功能?
异步处理、削峰填谷、解耦合、可扩展、顺序性等等。
RocketMQ核心概念和运行原理
核心概念
1.nameserver :注册中心,支持Topic、broker的动态注册和发现。
2.broker :主要负责消息的存储,投递和查询以及服务高可用的保证。(同时配置多个主从结构,形成一个分布式消息队列的集群)
3.Topic主题 :一类消息的集合,由broker管理。一个rocketMq的集群中可以有多个主题。
4.queue队列 :存储消息的物理实体(最小单位),一个Topic中可以包含多个Queue(分布式体现的关键),每个Queue中存放的就是该Topic的消息。一个Topic的Queue也被称为一个Topic中消息的分区(partition)。
5.生产者组 :
-
从技术角度讲:一个生产者组可以向多个主题发送消息,同一个主题也可以接收多个不同生产者组发送的消息。
-
从业务角度:只要能保证发送给不同主题的消息,是同一种类型的消息,就可以组合。否则会出现消费失败,消费故障.。(一般情况下 一个分组的生产者 只给一个主题发送消息,如果需要发送给多个主题,多创建一个生产者分组)。
6.消费者组 :消费者进程通过连接nameserver获取注册信息,根据主题中的队列个数,根据主题中记录的消费者组的信息,确定直接绑定的队列。消费者最大数量就是队列数量。
7.消费点位 :同一个消费组 共享一份消费位点(consumerOffset)记录的,所以无论多少个消费者,哪个消费者都不会对同一个主题的消息在绑定时 重0计算消费位点。
8.keys和tags :
-
keys:绑定到消息的一个业务数据,用来查询统计使用的。
-
tags:给消息绑定标签,消费端消费时,可以根据标签进行过滤消费。
运行原理
RockerMQ的使用案例
1.添加相应依赖
2.配置yaml文件
server:
port: 8999
# 满足rocketmq的自动配置逻辑的属性
rocketmq:
# nameserver 连接的nameserver
name-server: localhost:9876
# defualMQProducer 的属性条件 必须指定producer.group
producer:
group: test-producer-group
# 不是必须的
consumer:
group: test-consumer-group
3.发送消息
package com.tarena.demo.luban.all.main.controller;
@RestController
@RequestMapping("/base/order")
@Api(tags = "订单")
public class OrderController {
@Autowired
private RocketMQTemplate rocketMQTemplate;
@PostMapping("/add")
@ApiOperation("新增订单的功能")
public JsonResult addOrder(OrderAddParam orderAddParam){
//发送消息 Message是rocketMQTemplate支持发送的消息参数
//和底层api方法的Message不是同一个类,相当于将底层Message包装了一次.
//orderService.addOrder(orderAddParam);
//发送一条消息 目的实现业务层代码的调用
//新增订单 orderAddParam作为payLoad发送 需要确定这个类型是否实现了序列化接口
//准备一个消息对象
Message<OrderAddParam> message= MessageBuilder.withPayload(orderAddParam).build();
//发送消息 同步发送
//***:** 这种格式的desitination 底层翻译的时候 解析成 topic:tag
rocketMQTemplate.syncSend("luban-demo-order-topic:addOrder",message);
return JsonResult.ok("新增订单完成!");
}
}
4.消费消息
/**
topic:消费端绑定主题
consumerGroup:消费者分组
selectorExpression: 顾虑的标签
*/
@Component
@RocketMQMessageListener(
topic = "rocket-topic-a",
consumerGroup = "${rocketmq.consumer.group}",
selectorExpression = "*")
public class MyConsumerListener implements RocketMQListener<String> {
/**
* 每个listener自定义的对象,底层都会开启一个消费进程 绑定这个listerner
* 在底层消费者中,监听consumerMessage方法里,调用这个类的onMessage;
* 调用之前,已经实现了对象消息数据的转化
* 接口有泛型,底层方法逻辑,会根据泛型,将消息message进行反序列化和数据封装
* @param name 根据泛型反序列化的body对象
* 对于消费成功还是失败,spring整合rocketmq: 只要抛异常,就返回失败,不抛异常就是正常
*/
@Override
public void onMessage(String name) {
System.out.println("消费端接收到消息:"+name);
}
}
分布式事务消息
在一个消费平台,当用户选购完商品后,下单付款;此时即需要生成订单信息和减少库存量(本地事务)、并且清除购物车。(既有本地事务、又需要异步处理时)此时需要保证整个流程为一个整体,要么全部成功,要么全部失败。
此时需要实现分布式事务,生产者先发送一个半消息,当事务协调返回接收成功后,开始执行本地事务,成功则commit,执行异步消息,消费者消费消息。
发送半消息
/** 发送半消息测试 */
@GetMapping("/half")
public String sendHalf(){
Message message=MessageBuilder.withPayload("半消息").build();
rocketMQTemplate
.sendMessageInTransaction(
"trans-test-topic",
message,"业务数据");
return "success";
}
本地事务监听器
/**
和消费端监听,类似的
1.spring bean对象
2.实现一个接口 事务监听接口
3.使用注解
当前这个类,就是本地代码,不能和半消息发送的客户端代码分开,到两个应用程序
*/
@Component
@RocketMQTransactionListener
@Slf4j
public class MyTransactionConsumerListener implements RocketMQLocalTransactionListener {
/**
* @param message 半消息中的消息对象
* @param o 业务数据
* @return
*/
@Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
log.info("当前执行本地事务的逻辑,time:{}");
log.info("本次本地事务,执行结束后返回UNKNOWN");
return RocketMQLocalTransactionState.UNKNOWN;
}
/**
* 回调方法 只有上面的本地事务方法返回UNKONNW或者超时,才会调用.
* @param message
* @return
*/
@Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
log.info("进入到回调方法,说明本地事务方法执行返回UNKNOWN或者抛异常,或者超时");
return RocketMQLocalTransactionState.UNKNOWN;
}
}
executeLocalTransaction
进程中一旦发送了半消息,说明开始消息事务流程,就会进入这个方法,执行本地业务,通过本地事务,控制返回结果.
checkLocalTransaction
在本地事务方法中,返回结果是unknown和超时和异常,会调用.
Redis
官网:Redis
中文网址:Redis中文网
命令参考网址:https://www.cnblogs.com/antLaddie/p/15362191.html
基本数据类型:String、List、Hash、Set、Zset。
<!-- Spring Boot支持Redis编程的依赖项 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>${spring-boot.version}</version>
</dependency>
配置类
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Serializable> redisTemplate(
RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
return redisTemplate;
}
}
使用案例:
@SpringBootTest
public class RedisTests {
// 如果操作与值相关,需要获取XxxOperations才能调用对应的API
// -- 例如存入或取出字符串、对象类型的值时,需要先获取ValueOperations对象
// 如果操作与值无关,直接调用RedisTemplate的API即可
// -- 例如执行keys或delete时,直接调用RedisTemplate的API,并不需要事先获取XxxOperations对象
@Autowired
RedisTemplate<String, Serializable> redisTemplate;
// 存入字符串类型的值
@Test
void setValue() {
ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();
ops.set("username1", "张三");
System.out.println("向Redis中存入数据,完成!");
}
@SpringBootTest
public class RedisTests {
// 如果操作与值相关,需要获取XxxOperations才能调用对应的API
// -- 例如存入或取出字符串、对象类型的值时,需要先获取ValueOperations对象
// 如果操作与值无关,直接调用RedisTemplate的API即可
// -- 例如执行keys或delete时,直接调用RedisTemplate的API,并不需要事先获取XxxOperations对象
@Autowired
RedisTemplate<String, Serializable> redisTemplate;
// 存入字符串类型的值
@Test
void setValue() {
ValueOperations<String, Serializable> ops = redisTemplate.opsForValue();
ops.set("username1", "张三");
System.out.println("向Redis中存入数据,完成!");
}
}
计时任务:
计划任务指的是:在满足一定条件下,会周期性执行的任务,通常,可能是每过多长时间就执行一次,或到了某个特定的时间点就执行一次。
计划任务可能是比较耗时的,在Spring Boot项目中,默认不允许执行计划任务,需要在配置类上添加@EnableScheduling注解,以启用计划任务!
则在项目中的根包下创建config.ScheduleConfiguration类,在类上添加@Configuration注解,并添加@EnableScheduling注解:
@Configuration
@EnableScheduling
public class ScheduleConfiguration {
}
然后,在任何组件类中,自定义方法(公有的、void返回值类型、无参数列表),并在方法上添加@Scheduled注解,则此方法就是一个计划任务方法,然后,配置注解参数,以决定什么时候执行计划任务,例如:
@Slf4j
@Component
public class CacheSchedule {
// fixedRate:执行频率,以毫秒为单位
@Scheduled(fixedRate = 5 * 1000)
public void xxx() {
log.debug("CacheSchedule.xxx()");
}
}
Redis分布式锁
案例代码
引入依赖、配置yaml
/**
* @author java@tedu.cn
* @version 1.0
*/
@Component
@RocketMQMessageListener(
topic = "business-order-topic",
consumerGroup = "${rocketmq.consumer.group}",
selectorExpression = "orderAdd")
@Slf4j
public class OrderAddConsumerListener implements RocketMQListener<MessageExt> {
@Autowired
private IOrderService orderService;
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void onMessage(MessageExt msg) {
//拿到底层消息对象的body
byte[] body = msg.getBody();
//尝试先解析成string
String orderJson=new String(body, StandardCharsets.UTF_8);
System.out.println(orderJson);
OrderAddDTO orderAddDTO=
JSON.toJavaObject(JSON.parseObject(orderJson),OrderAddDTO.class);
System.out.println(orderAddDTO);
//1.生成锁的key值,生成当前这把锁的随机数
//准备锁key
String msgKeyLock="msg:order:add:"+msg.getMsgId();
//准备随机数 4 6 8位
String randCode=new Random().nextInt(9000)+1000+"";
ValueOperations<String, String> stringOps = redisTemplate.opsForValue();
try{
//补充消息消费的抢锁机制
//2.抢锁 setnx msgKeyLock randCode expire 10s
Boolean tryLockSuccess = stringOps
.setIfAbsent(msgKeyLock, randCode, 10, TimeUnit.SECONDS);
//3.判断 抢锁成功还是失败
if(!tryLockSuccess){
//3.2 失败了 可以等待5秒重新抢锁,也可以直接结束
//尝试这里使用while编写等待5秒重新抢的逻辑
log.info("有别人抢锁了,msgKey:{},value:{}",msgKeyLock,randCode);
return;
}
//3.1 成功了 执行orderAdd
orderService.orderAdd(orderAddDTO);
}catch (CoolSharkServiceException e){
//业务异常,说明订单新增业务性失败,比如库存没了
log.error("库存减少失败,库存触底了:{},异常信息:{}",orderAddDTO,e.getMessage());
}finally {
//释放锁 读以下锁的value值,等于当前生成value才释放
String s = stringOps.get(msgKeyLock);
if (s!=null && s.equals(randCode)){
//del msgKeyLock
redisTemplate.delete(msgKeyLock);
}
}
}
}
626

被折叠的 条评论
为什么被折叠?



