微服务及相关中间件

微服务架构的思想

微服务是一种架构风格,旨在将应用程序拆分为一组小型、独立的服务,每个服务都专注于执行特定的业务功能。每个服务都可以使用自己的技术栈、数据库和开发团队,通过轻量级通信机制进行交互。

微服务架构的优点:

  • 独立开发和部署:故障隔离。

  • 可伸缩性好:便于业务的扩展。

  • 维护性高:每个服务关注一个特定的业务功能,修改和维护起来更容易。

微服务架构的缺点:

  • 复杂性增强:服务间的通信,数据的一致性,以及分布式事务等问题

  • 运维难度大,成本高;对团队的协作有更高的要求

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远程调用

官网: https://dubbo.apache.org

  • 作用:

    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熔断/限流/降级

官网: introduction | 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消息队列

    <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);
            }
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RabbitMQ是一种常用的消息代理中间件,也是微服务架构中常用的消息中间件之一。它是一个开源的、高性能的、可扩展的消息代理系统,基于AMQP(高级消息队列协议)实现。 在微服务架构中,不同服务之间需要进行通信和传递消息,这时候就需要一个消息中间件来协调和传递消息。RabbitMQ作为一种可靠的、可扩展的消息代理,能够有效地解耦微服务之间的通信。它能够接收、存储和转发消息,并保证消息的可靠性传递。 RabbitMQ的核心概念包括生产者、消费者、队列和交换机。生产者负责产生消息并发送到队列,消费者从队列中获取消息并进行处理。队列是消息的缓冲区,保证消息的暂存和可靠传递。交换机定义了消息应该如何路由到队列。 使用RabbitMQ作为微服务中间件具有许多优势。首先,RabbitMQ支持多种消息传递模式(例如点对点、发布/订阅等),可以根据需求选择合适的模式。其次,RabbitMQ提供了可靠的消息传递机制,能够确保消息的安全传递。此外,RabbitMQ还支持消息的持久化,即使在系统故障或重启后,消息也能够被恢复。 另外,RabbitMQ具备良好的可扩展性和高可用性。它支持集群部署,能够处理大量的消息和并发请求。同时,RabbitMQ还提供了监控和管理工具,可以对消息的发送和消费进行监控和管理。 总之,RabbitMQ作为一种可靠的、可扩展的消息代理中间件,对于微服务架构的实现是非常有价值和重要的。它能够解耦微服务之间的通信,保证消息的可靠传递,提供高可用性和可扩展性,并具备监控和管理的功能。因此,在微服务架构中,RabbitMQ是一种非常合适的中间件选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值