Dubbo配置详解

概述

Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

Oinone平台默认使用dubbo-v2.7.22版本,本文以该版本为例进行描述。

基本概念

Dubbo在注册provider/consumer时使用Netty作为RPC调用的核心服务,其具备客户端/服务端(C/S)的基本特性。即:provider作为服务端consumer作为客户端

客户端通过服务中心发现有服务可被调用时,将通过服务中心提供的服务端调用信息,连接服务端并发起请求,从而实现远程调用。

服务注册(绑定Host/Port)

JAVA程序启动时,需要将provider的信息注册到服务中心,并在当前环境为Netty服务开启Host/Port监听,以实现服务注册功能。

在下文中,我们通过绑定Host/Port表示Netty服务的访问地址,通过注册Host/Port表示客户端的访问地址。

使用yaml配置绑定Host/Port

PS:该配置可在多种环境中通用,改变部署方式无需修改此配置。

dubbo:
  protocol:
    name: dubbo
    # host: 0.0.0.0
    port: -1

假设当前环境的可用IP为192.168.1.100

以上配置将使得Netty服务默认绑定在0.0.0.0:20880地址,服务注册地址为192.168.1.100:20880

客户端将通过192.168.1.100:20880调用服务端服务

若发生20880端口占用,则自动向后查找可用端口。如20881、20882等等

若当前可用端口为20881,则以上配置将使得Netty服务默认绑定在0.0.0.0:20881地址,服务注册地址为192.168.1.100:20881

使用环境变量配置注册Host/Port

当服务端被放置在容器环境中时,由于容器环境的特殊性,其内部的网络配置相对于宿主机而言是独立的。因此为保证客户端可以正常调用服务端,还需在容器中配置环境变量,以确保客户端可以通过指定的注册Host/Port进行访问。

以下示例为体现无法使用20880端口的情况,将宿主机可访问端口从20880改为20881。

DUBBO_IP_TO_REGISTRY=192.168.1.100
DUBBO_PORT_TO_REGISTRY=20881

假设当前宿主机环境的可用IP为192.168.1.100

以上配置将使得Netty服务默认绑定在0.0.0.0:20881地址,服务注册地址为192.168.1.100:20881

客户端将通过192.168.1.100:20881调用服务端服务

使用docker/docker-compose启动

需添加端口映射,将20881端口映射至宿主机20881端口。(此处容器内的端口发生变化,若需要了解具体原因,可参考题外话章节)

docker-run

IP=192.168.1.100

docker run -d --name designer-allinone-full \
-e DUBBO_IP_TO_REGISTRY=$IP \
-e DUBBO_PORT_TO_REGISTRY=20881 \
-p 20881:20881 \

docker-compose

services:
  backend:
    container_name: designer-backend
    image: harbor.oinone.top/oinone/designer-backend-v5.0
    restart: always
    environment:
      DUBBO_IP_TO_REGISTRY: 192.168.1.100
      DUBBO_PORT_TO_REGISTRY: 20881
    ports:
     - 20881:20881 # dubbo端口

使用kubernetes启动

工作负载(Deployment)

kind: Deployment
apiVersion: apps/v1
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: designer-backend
          image: harbor.oinone.top/oinone/designer-backend-v5.0
          ports:
            - name: dubbo
              containerPort: 20881
              protocol: TCP
          env:
            - name: DUBBO_IP_TO_REGISTRY
              value: "192.168.1.100"
            - name: DUBBO_PORT_TO_REGISTRY
              value: "20881"

服务(Services)

kind: Service
apiVersion: v1
spec:
  type: NodePort
  ports:
    - name: dubbo
      protocol: TCP
      port: 20881
      targetPort: dubbo
      nodePort: 20881

PS:此处的targetPort为对应Deployment#spec. template.spec.containers.ports.name配置的端口名称。若未配置,可使用20881直接指定对应容器的端口号。

使用kubernetes其他暴露服务方式

在Kubernetes中部署服务,有多种配置方式均可用暴露服务。上述配置仅用于通过Service/NodePort20881端口暴露至宿主机,其他服务可用通过任意Kubernetes节点IP进行调用。

若其他服务也在Kubernetes中进行部署,则可以通过Service/Service方式进行调用。将DUBBO_IP_TO_REGISTRY配置为${serviceName}.${namespace}即可。

若其他服务无法直接访问Kubernetes的master服务,则可以通过Ingress/Service方式进行调用。将DUBBO_IP_TO_REGISTRY配置为Ingress可解析域名即可。

Dubbo调用链路图解

PS: Consumer绑定Host/Port是其作为Provider使用的,下面所有图解仅演示单向的调用链路。

名词解释

  • Provider: 服务提供者(JVM)
  • Physical Machine Provider: 服务提供者所在物理机
  • Provider Container: 服务提供者所在容器
  • Kubernetes Service: Kubernetes Service资源类型
  • Consumer: 服务消费者(JVM)
  • Registration Center: 注册中心;可以是zookeepernacos等。
  • bind: 服务绑定Host/Port到指定ip:port
  • registry: 服务注册;注册Host/Port到注册中心的信息。
  • discovery: 服务发现;注册Host/Port到消费者的信息。
  • invoke: 服务调用;消费者通过注册中心提供的提供者信息向提供者发起服务调用。
  • forward: 网络转发;通常在容器环境需要进行必要的网络转发,以使得服务调用可以到达服务提供者。

物理机/物理机调用链路

image.png

``` mermaid
sequenceDiagram

participant p as Provider<br>(bind 0.0.0.0:20880)
participant m as Physical Machine Provider<br>(bind 192.168.1.100:20881)
participant rc as Registration Center<br>(zookeeper/nacos)
participant c as Consumer<br>(bind 0.0.0.0:20881)

p-->>+m: bind 192.168.1.100:20880
m->>+rc: registry 192.168.1.100:20880
rc->>+c: discovery 192.168.1.100:20880
c->>+m: invoke 192.168.1.100:20880
m-->>+p: forward 0.0.0.0:20881
```

PS: 此处虚线部分表示提供者部署在物理机上,并不存在真实的网络处理。

容器/物理机调用链路

image.png

``` mermaid
sequenceDiagram

participant p as Provider<br>(bind 0.0.0.0:20881)
participant pc as Provider Container<br>(bind 172.17.1.100:20881)
participant m as Physical Machine Provider<br>(bind 192.168.1.100:20881)
participant rc as Registration Center<br>(zookeeper/nacos)
participant c as Consumer<br>(bind 0.0.0.0:20882)

p-->>+pc: bind 172.26.1.100:20881
pc->>+m: mapping 192.168.1.100:20881
pc->>+rc: registry 192.168.1.100:20881
rc->>+c: discovery 192.168.1.100:20881
c->>+m: invoke 192.168.1.100:20881
m->>+pc: forward 172.17.1.100:20881
pc-->>+p: forward 0.0.0.0:20881
```

PS: 此处虚线部分表示提供者部署在容器中,并不存在真实的网络处理。

Kubernetes/物理机(Service/NodePort模式)调用链路

image.png

``` mermaid
sequenceDiagram

participant p as Provider<br>(bind 0.0.0.0:20881)
participant pc as Provider Container<br>(bind 172.17.1.100:20881)
participant ks as Kubernetes Service<br>targetPort 172.17.1.100:20881<br>nodePort 192.168.1.100:20881
participant m as Physical Machine Provider<br>(bind 192.168.1.100:20881)
participant rc as Registration Center<br>(zookeeper/nacos)
participant c as Consumer<br>(bind 0.0.0.0:20882)

p-->>+pc: bind 172.26.1.100:20881
pc->>+ks: mapping 192.168.1.100:20881
ks->>+m: mapping 192.168.1.100:20881
pc->>+rc: registry 192.168.1.100:20881
rc->>+c: discovery 192.168.1.100:20881
c->>+m: invoke 192.168.1.100:20881
m->>+ks: forward 192.168.1.100:20881
ks->>+pc: forward 172.17.1.100:20881
pc-->>+p: forward 0.0.0.0:20881
```

PS: 此处虚线部分表示提供者部署在容器中,并不存在真实的网络处理。

题外话

dubbo-v2.7.22源码中,作者发现Host/Port的获取方式并不对等,这里目前不太清楚是dubbo设计如此还是作者对dubbo设计理解不足。

  • 现象:

    • DUBBO_IP_TO_REGISTRY配置与dubbo.protocol.host无关。
    • DUBBO_PORT_TO_REGISTRY配置优先级高于dubbo.protocol.port配置。
  • 作者理解:

    • 客户端向服务端发起请求时,应使用注册Host/Port进行调用,只要该访问地址可以与服务端连通,则远程调用就可以正常运行。
    • 注册Host/Port绑定Host/Port应支持完全独立配置。当注册Host/Port绑定Host/Port均被配置时,注册Host绑定Host是独立生效的,但绑定Port却强制使用了注册Port。(这一点也是经常在容器环境中无法正常调用的主要原因)

常用配置

yaml配置

dubbo:
  application:
    name: pamirs-test
    version: 1.0.0
  registry:
    address: zookeeper://127.0.0.1:2181
    # group: demo
    # timeout: 5000
  protocol:
    name: dubbo
    # host: 0.0.0.0
    port: -1
    serialization: pamirs
    payload: 104857600
  scan:
    base-packages: pro.shushi
  cloud:
    subscribed-services:
  • dubbo.registry.address: 注册中心地址
  • dubbo.registry.group: 全局group配置
  • dubbo.registry.timeout: 全局超时时间配置
  • dubbo.protocol.name: 协议名称
  • dubbo.protocol.host: 绑定主机IP配置;默认:0.0.0.0
  • dubbo.protocol.port: 绑定主机端口配置;-1表示自动获取可用端口;默认:20880
  • dubbo.protocol.serialization: 序列化配置;Oinone平台必须使用pamirs作为序列化方式。
  • dubbo.protocol.payload: RPC调用数据大小限制;单位:字节(byte)
  • dubbo.scan.base-packages: provider/consumer扫描包路径
  • dubbo.cloud.subscribed-services: 多提供者配置;示例中该参数配置为空是为了避免启动时的警告日志,一般无需配置。

环境变量配置

DUBBO_IP_TO_REGISTRY=127.0.0.1
DUBBO_PORT_TO_REGISTRY=20880
  • DUBBO_IP_TO_REGISTRY:注册Host配置
  • DUBBO_PORT_TO_REGISTRY:注册Port配置

源码参考

  • org.apache.dubbo.config.ServiceConfig#findConfigedHosts
private String findConfigedHosts(ProtocolConfig protocolConfig,
                                 List<URL> registryURLs,
                                 Map<String, String> map) {
    boolean anyhost = false;

    String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND);
    if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) {
        throw new IllegalArgumentException("Specified invalid bind ip from property:" + DUBBO_IP_TO_BIND + ", value:" + hostToBind);
    }

    // if bind ip is not found in environment, keep looking up
    if (StringUtils.isEmpty(hostToBind)) {
        hostToBind = protocolConfig.getHost();
        if (provider != null && StringUtils.isEmpty(hostToBind)) {
            hostToBind = provider.getHost();
        }
        if (isInvalidLocalHost(hostToBind)) {
            anyhost = true;
            logger.info("No valid ip found from environment, try to get local host.");
            hostToBind = getLocalHost();
        }
    }

    map.put(BIND_IP_KEY, hostToBind);

    // registry ip is not used for bind ip by default
    String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY);
    if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) {
        throw new IllegalArgumentException(
                "Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
    } else if (StringUtils.isEmpty(hostToRegistry)) {
        // bind ip is used as registry ip by default
        hostToRegistry = hostToBind;
    }

    map.put(ANYHOST_KEY, String.valueOf(anyhost));

    return hostToRegistry;
}
  • org.apache.dubbo.config.ServiceConfig#findConfigedPorts
private Integer findConfigedPorts(ProtocolConfig protocolConfig,
                                  String name,
                                  Map<String, String> map, int protocolConfigNum) {
    Integer portToBind = null;

    // parse bind port from environment
    String port = getValueFromConfig(protocolConfig, DUBBO_PORT_TO_BIND);
    portToBind = parsePort(port);

    // if there's no bind port found from environment, keep looking up.
    if (portToBind == null) {
        portToBind = protocolConfig.getPort();
        if (provider != null && (portToBind == null || portToBind == 0)) {
            portToBind = provider.getPort();
        }
        final int defaultPort = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(name).getDefaultPort();
        if (portToBind == null || portToBind == 0) {
            portToBind = defaultPort;
        }
        if (portToBind <= 0) {
            portToBind = getRandomPort(name);
            if (portToBind == null || portToBind < 0) {
                portToBind = getAvailablePort(defaultPort);
                putRandomPort(name, portToBind);
            }
        }
    }

    // registry port, not used as bind port by default
    String key = DUBBO_PORT_TO_REGISTRY;
    if (protocolConfigNum > 1) {
        key = getProtocolConfigId(protocolConfig).toUpperCase() + "_" + key;
    }
    String portToRegistryStr = getValueFromConfig(protocolConfig, key);
    Integer portToRegistry = parsePort(portToRegistryStr);
    if (portToRegistry != null) {
        portToBind = portToRegistry;
    }

    // save bind port, used as url's key later
    map.put(BIND_PORT_KEY, String.valueOf(portToBind));

    return portToBind;
}
  • org.apache.dubbo.config.ServiceConfig#getValueFromConfig
private String getValueFromConfig(ProtocolConfig protocolConfig, String key) {
    String protocolPrefix = protocolConfig.getName().toUpperCase() + "_";
    String value = ConfigUtils.getSystemProperty(protocolPrefix + key);
    if (StringUtils.isEmpty(value)) {
        value = ConfigUtils.getSystemProperty(key);
    }
    return value;
}
  • org.apache.dubbo.common.utils.ConfigUtils#getSystemProperty
public static String getSystemProperty(String key) {
    String value = System.getenv(key);
    if (StringUtils.isEmpty(value)) {
        value = System.getProperty(key);
    }
    return value;
}

Oinone社区 作者:张博昊原创文章,如若转载,请注明出处:https://doc.oinone.top/backend/16028.html

访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验

(1)
张博昊的头像张博昊数式管理员
上一篇 2024年8月9日 pm6:02
下一篇 2024年8月12日 pm5:37

相关推荐

  • 如何在代码中使用自增ID和获取序列

    在使用继承IDModel或CodeModel时,id和code是系统默认自动生成, 默认值规则:ID–>分布式ID; CODE–>根据定义的SequenceConfig规则自动生成。 在特定情况下需要落库前先生成ID或者Code,这些场景下可参照如下代码示例 一、使用自增ID 单个字段设置方式 // 主键字段,可以使用mysql的自增能力 @Field.Integer @Field.PrimaryKey(keyGenerator = KeyGeneratorEnum.AUTO_INCREMENT) @Field.Advanced(batchStrategy = FieldStrategyEnum.NEVER) @Field(displayName = "id", summary = "Id字段,⾃增") private Long id; @Field.Integer @Field(displayName = "自增版本") @Field.Sequence(sequence = "SEQ", initial = 1) private Long version; 全局设置方式 该方式会作用到每一个存储模型的id字段,在application.yml配置文件中修改id的生成规则,查找配置项关键字key-generator,默认为DISTRIBUTION(分布式id),可修改为 AUTO_INCREMENT(自增id) 二、手动方式获取序列 获取方式示例1 /** * 在特定场景下需要手动生成Id或者code时,可参照这个示例 */ public void manualSetIdCode(){ DemoItem demoItem = new DemoItem(); //手动生成ID和code Object idObj = Spider.getDefaultExtension(IdGenerator.class).generate(PamirsTableInfo.fetchKeyGenerator(DemoItem.MODEL_MODEL)); demoItem.setId(TypeUtils.createLong(idObj)); Object codeObj = CommonApiFactory.getSequenceGenerator().generate("SEQ",DemoItem.MODEL_MODEL); String code = TypeUtils.stringValueOf(codeObj); demoItem.setCode(code); //…… } 获取方式示例2 1、在系统启动的时候初始化SequenceConfig package pro.shushi.pamirs.demo.core.init; import org.springframework.stereotype.Component; import pro.shushi.pamirs.boot.common.api.command.AppLifecycleCommand; import pro.shushi.pamirs.boot.common.extend.MetaDataEditor; import pro.shushi.pamirs.core.common.InitializationUtil; import pro.shushi.pamirs.demo.api.DemoModule; import pro.shushi.pamirs.demo.core.constant.SeqConstants; import pro.shushi.pamirs.meta.annotation.fun.extern.Slf4j; import pro.shushi.pamirs.meta.api.dto.meta.Meta; import pro.shushi.pamirs.meta.enmu.SequenceEnum; import java.util.Map; /** * DemoMetadataEditor */ @Slf4j @Component public class DemoMetadataEditor implements MetaDataEditor { @Override public void edit(AppLifecycleCommand command, Map<String, Meta> metaMap) { InitializationUtil util = InitializationUtil.get(metaMap, DemoModule.MODULE_MODULE, DemoModule.MODULE_NAME); if (util == null) { log.error("获取初始化序列失败"); return; } bizSequence(util); } private void bizSequence(InitializationUtil util) { util.createSequenceConfig("申请单编码生成", SeqConstants.NABEL_SAMPLE_APPLY_SEQ, SequenceEnum.ORDERLY_SEQ, 8) .setStep(1) .setInitial(80000000L) .setIsRandomStep(false); util.createSequenceConfig("订单编码生成", SeqConstants.NABEL_SAMPLE_ORDER_SEQ_YP, SequenceEnum.ORDERLY_SEQ, 8) .setPrefix("YP") .setStep(1) .setInitial(80000000L) .setIsRandomStep(false); } } 2、在代码中使用序列 public static String getSaleOrderCode() { Object sequence = CommonApiFactory.getSequenceGenerator().generate(SequenceEnum.ORDERLY_SEQ.value(), SeqConstants.NABEL_SAMPLE_STRUCTURE_SEQ); return TypeUtils.stringValueOf(sequence); } public static String getApplyOrderCode(String prefix) { Object sequence = CommonApiFactory.getSequenceGenerator().generate(SequenceEnum.ORDERLY_SEQ.value(), SeqConstants.NABEL_SAMPLE_APPLY_SEQ); return…

    2024年5月25日
    1.7K00
  • Oinone开发实践-业务实现多租户方案

    总体方案 业务项目中,需要隔离的模型自定义增加租户字段进行数据隔离; 参考了Mybatis-Plus插件的TenantSqlParser进行的JPA实现,使用jsqlparser解析并修改SQL; 实现获取当前用户租户ID,SQL增删改查时处理租户字段,实现租户数据的隔离 参考项目: https://github.com/baomidou/mybatis-plus https://github.com/JSQLParser/JSqlParser 具体实现方式 1、业务上定义两个基础抽象模型包含租户字段 定义包含ID的基础抽象模型,且包含租户字段(如:公司编码, 用其他字段作为租户字段也可以,根据实际业务情况灵活修改)。 @Model.model(XXIdModel.MODEL_MODEL) @Model.Advanced(type = ModelTypeEnum.ABSTRACT) @Model(displayName = “带公司CODE的基础ID抽象模型”, summary = “带公司Code的Id模型”) public abstract class XXIdModel extends IdModel { public static final String MODEL_MODEL = “demo.biz.XXIdModel”; @Field.String @Field(displayName = “所属公司编码”, invisible = true, index = true) private String companyCode; } 定义包含Code的基础抽象模型,且包含租户字段(如:公司编码, 用其他字段作为租户字段也可以,根据实际业务情况灵活修改)。 @Model.model(XXCodeModel.MODEL_MODEL) @Model.Advanced(type = ModelTypeEnum.ABSTRACT) @Model(displayName = “带公司CODE的基础Code抽象模型”, summary = “带公司CODE的Code模型”) public abstract class XXCodeModel extends CodeModel { public static final String MODEL_MODEL = “demo.biz.XXCodeModel”; @Field.String @Field(displayName = “所属公司编码”, invisible = true, index = true) private String companyCode; } 2、业务模块的模型需租户隔离的都是继承上面这两个模型; @Model.model(PetPetCompany.MODEL_MODEL) @Model(displayName = “宠物公司”, labelFields = “name”) public class PetPetCompany extends AbstractCompanyCodeModel { public static final String MODEL_MODEL = “demo.PetPetCompany”; @Field.String @Field(displayName = “名称”) private String name; @Field.Text @Field(displayName = “简介”) private String introduction; } 3、自定义扩展Session,Session中设置租户信息 每次请求多把登录用户所属公司编码(companyCode)放到Session中;Session扩展参考:https://doc.oinone.top/oio4/9295.html 4、定义拦截器Interceptor进行数据隔离 数据创建和查询通过拦截器把Session中的中的公司编码(companyCode)设置到隔离字段中;拦截器的java示例代码参考: package pro.shushi.pamirs.demo.core.interceptor; import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.StringValue; import net.sf.jsqlparser.expression.operators.relational.ExpressionList; import net.sf.jsqlparser.expression.operators.relational.ItemsListVisitor; import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList; import net.sf.jsqlparser.expression.operators.relational.NamedExpressionList; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.schema.Column; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.insert.Insert; import net.sf.jsqlparser.statement.select.*; import net.sf.jsqlparser.statement.update.Update; import net.sf.jsqlparser.statement.values.ValuesStatement; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.mapping.SqlSource; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.SystemMetaObject; import…

    2024年4月6日
    1.0K00
  • 函数之异步执行

    总体介绍 异步任务是非常常见的一种开发模式,它在分布式的开发模式中有很多应用场景如: 高并发场景中,我们一般采用把长流程切短,用异步方式去掉可以异步的非关键功能,缩小主流程响应时间,提升用户体验 异构系统的集成调用,通过异步任务完成解耦与自动重试 分布式系统最终一致性的可选方案 本文将介绍oinone是如何结合Spring+TbSchedule来完成异步任务 构建第一个异步任务 新建PetShopService和PetShopServiceImpl 1、 新建PetShopService定义updatePetShops方法 package pro.shushi.pamirs.demo.api.service; import pro.shushi.pamirs.demo.api.model.PetShop; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import java.util.List; @Fun(PetShopService.FUN_NAMESPACE) public interface PetShopService { String FUN_NAMESPACE = "demo.PetShop.PetShopService"; @Function void updatePetShops(List<PetShop> petShops); } 2、PetShopServiceImpl实现PetShopService接口并在updatePetShops增加@XAsync注解 package pro.shushi.pamirs.demo.core.service; import org.springframework.stereotype.Component; import pro.shushi.pamirs.demo.api.model.PetShop; import pro.shushi.pamirs.demo.api.service.PetShopService; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.trigger.annotation.XAsync; import java.util.List; @Fun(PetShopService.FUN_NAMESPACE) @Component public class PetShopServiceImpl implements PetShopService { @Override @Function @XAsync(displayName = "异步批量更新宠物商店",limitRetryNumber = 3,nextRetryTimeValue = 60) public void updatePetShops(List<PetShop> petShops) { new PetShop().updateBatch(petShops); } } a. displayName = "异步批量更新宠物商店",定义异步任务展示名称b. limitRetryNumber = 3,定义任务失败重试次数,,默认:-1不断重试c. nextRetryTimeValue = 60,定义任务失败重试的时间数,默认:3d. nextRetryTimeUnit,定义任务失败重试的时间单位,默认:TimeUnitEnum.SECONDe. delayTime,定义任务延迟执行的时间数,默认:0f. delayTimeUnit,定义任务延迟执行的时间单位,默认:TimeUnitEnum.SECOND 修改PetShopBatchUpdateAction调用异步任务 引入PetShopService 修改conform方法,调用petShopService.updatePetShops方法 package pro.shushi.pamirs.demo.core.action; @Model.model(PetShopBatchUpdate.MODEL_MODEL) @Component public class PetShopBatchUpdateAction { @Autowired private PetShopService petShopService; @Action(displayName = "确定",bindingType = ViewTypeEnum.FORM,contextType = ActionContextTypeEnum.SINGLE) public PetShopBatchUpdate conform(PetShopBatchUpdate data){ List<PetShop> shops = ArgUtils.convert(PetShopProxy.MODEL_MODEL, PetShop.MODEL_MODEL,proxyList); // 调用异步任务 petShopService.updatePetShops(shops); }); return data; } } 不同应用如何隔离执行单元 在schedule跟模块部署一起的时候,多模块独立boot的情况下,需要做必要的配置。如果schedule独立部署则没有必要,因为全部走远程,不存在类找不到的问题 通过配置pamirs.zookeeper.rootPath,确保两组机器都能覆盖所有任务分片,这样不会漏数据 通过pamirs.event.schedule.ownSign来隔离。确保两组机器只取各自产生的数据,这样不会重复执行数据 pamirs: zookeeper: zkConnectString: 127.0.0.1:2181 zkSessionTimeout: 60000 rootPath: /demo event: enabled: true schedule: enabled: true ownSign: demo rocket-mq: namesrv-addr: 127.0.0.1:9876

    2024年5月25日
    1.1K00
  • 后端:如何自定义表达式实现特殊需求?扩展内置函数表达式

    平台提供了很多的表达式,如果这些表达式不满足场景?那我们应该如何新增表达式去满足项目的需求?目前平台支持的表达式内置函数,参考 1. 扩展表达式的场景 注解@Validation的rule字段支持配置表达式校验如果需要判断入参List类型字段中的某一个参数进行NULL校验,发现平台的内置函数不支持该场景的配置,这里就可以通过平台的机制,对内置函数进行扩展。 常见的一些代码场景,如下: package pro.shushi.pamirs.demo.core.action; ……引用类 @Model.model(PetShopProxy.MODEL_MODEL) @Component public class PetShopProxyAction extends DataStatusBehavior<PetShopProxy> { @Override protected PetShopProxy fetchData(PetShopProxy data) { return data.queryById(); } @Validation(ruleWithTips = { @Validation.Rule(value = "!IS_BLANK(data.code)", error = "编码为必填项"), @Validation.Rule(value = "LEN(data.name) < 128", error = "名称过长,不能超过128位"), }) @Action(displayName = "启用") @Action.Advanced(invisible="!(activeRecord.code !== undefined && !IS_BLANK(activeRecord.code))") public PetShopProxy dataStatusEnable(PetShopProxy data){ data = super.dataStatusEnable(data); data.updateById(); return data; } ……其他代码 } 2. 新建一个自定义表达式的函数 校验入参如果是个集合对象的情况下,单个对象的某个字段如果为空,返回false的函数。 例子:新建一个CustomCollectionFunctions类 package xxx.xxx.xxx; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import pro.shushi.pamirs.meta.annotation.Fun; import pro.shushi.pamirs.meta.annotation.Function; import pro.shushi.pamirs.meta.common.constants.NamespaceConstants; import pro.shushi.pamirs.meta.util.FieldUtils; import java.util.List; import static pro.shushi.pamirs.meta.enmu.FunctionCategoryEnum.COLLECTION; import static pro.shushi.pamirs.meta.enmu.FunctionLanguageEnum.JAVA; import static pro.shushi.pamirs.meta.enmu.FunctionOpenEnum.LOCAL; import static pro.shushi.pamirs.meta.enmu.FunctionSceneEnum.EXPRESSION; /** * 自定义内置函数 */ @Fun(NamespaceConstants.expression) @Component public class CustomCollectionFunctions { /** * LIST_FIELD_NULL 就是我们自定义的表达式,不能与已经存在的表达式重复!!! * * @param list * @param field * @return */ @Function.Advanced( displayName = "校验集成的参数是否为null", language = JAVA, builtin = true, category = COLLECTION ) @Function.fun("LIST_FIELD_NULL") @Function(name = "LIST_FIELD_NULL", scene = {EXPRESSION}, openLevel = LOCAL, summary = "函数示例: LIST_FIELD_NULL(list,field),函数说明: 传入一个对象集合,校验集合的字段是否为空" ) public Boolean listFieldNull(List list, String field) { if (null == list) { return false; } if (CollectionUtils.isEmpty(list)) { return false; } for (Object data : list) { Object value =…

    2024年5月30日
    1.9K00
  • RocketMQ消费者出现类似RemotingTimeoutException: invokeSync call timeout错误处理办法

    RocketMQ消费者在macOS中出现类似RemotingTimeoutException: invokeSync call timeout错误处理办法: 命令行中执行脚本 scutil –set HostName $(scutil –get LocalHostName)  重启应用

    2024年6月12日
    1.0K00

Leave a Reply

登录后才能评论