手写RPC框架--11.spi机制

news2024/10/6 12:19:58

spi机制

  • spi机制
    • a.spi介绍
    • b.缓存spi到本地
    • c.加载spi并将实例缓存
    • d.统一spi加载的配置

spi机制

a.spi介绍

SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦

在这里插入图片描述

b.缓存spi到本地

在core模块下的resources包下,创建META-INF.dcyrpc-services

  • 复制Compressor接口的全限定名作为文件名
    • 文件内:复制Compressor接口的实现类的全限定名
  • 复制LoadBalancer接口的全限定名作为文件名
    • 文件内:复制LoadBalancer接口的实现类的全限定名

在这里插入图片描述

在core模块下的config.Configuration配置类的构造器中,写入spi代码:spi发现机制相关配置项

public Configuration() {
    // 1.成员变量的默认配置项

    // 2.spi发现机制相关配置项
    SpiResolver spiResolver = new SpiResolver();
    spiResolver.loadFromSpi(this);

    // 3.读取xml上的配置信息
    XmlResolver xmlResolver = new XmlResolver();
    xmlResolver.loadFromXml(this);

    // 4.编程配置项,dcyRpcBootstrap提供
}

在core模块的con.dcyrpc.config包下,创建SpiResolver类:spi自动发现机制相关配置项

/**
 * spi发现机制相关配置项
 */
public class SpiResolver {

    /**
     * 通过spi方式加载配置项
     * @param configuration
     */
    public void loadFromSpi(Configuration configuration) {
        LoadBalancer loadBalancer = SpiHandler.get(LoadBalancer.class);
        if (loadBalancer != null) {
            configuration.setLoadBalancer(loadBalancer);
        }

        Compressor compressor = SpiHandler.get(Compressor.class);
        if (compressor != null) {
            configuration.setCompressor(compressor);
        }

        Serializer serializer = SpiHandler.get(Serializer.class);
        if (serializer != null) {
            configuration.setSerializer(serializer);
        }
    }
}

在core模块的con.dcyrpc包下,创建SpiHandler类:缓存spi内容

  • 设置Map:将spi的配置信息(原始内容)缓存到map中

  • 设置静态代码块:加载当前类后将spi信息进行保存,逼迫运行时频繁执行IO

@Slf4j
public class SpiHandler {

    // 定义basePath
    private static final String BASE_PATH = "META-INF/dcyrpc-services";

    // 定义缓存:保存spi相关的原始内容
    private static final Map<String, List<String>> SPI_CONTENT = new ConcurrentHashMap<>(8);

    // 加载当前类后将spi信息进行保存,逼迫运行时频繁执行IO
    static {
        // 加载当前工程和jar包中classPath中的资源
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL fileUrl = classLoader.getResource(BASE_PATH);
        if (fileUrl != null) {
            File file = new File(fileUrl.getPath());
            File[] listFiles = file.listFiles();
            if (listFiles != null && listFiles.length > 0) {
                for (File listFile : listFiles) {
                    String key = listFile.getName();
                    List<String> value = getImplNames(listFile);
                    SPI_CONTENT.put(key, value);
                }
            }
        }
    }

    private static List<String> getImplNames(File file) {

        try (
                FileReader fileReader = new FileReader(file);
                BufferedReader bufferedReader = new BufferedReader(fileReader)
        ){
            List<String> implNameList = new ArrayList<>();
            while (true) {
                String line = bufferedReader.readLine();
                if (line == null || line.equals("")) {
                    break;
                } else {
                    implNameList.add(line);
                }
            }

            return implNameList;
        } catch (IOException e) {
            log.error("读取spi文件时发生异常", e);
        }
        return null;
    }

    public static <T> T get(Class<T> clazz) {
        
    }
}

c.加载spi并将实例缓存

在core模块的con.dcyrpc包下的SpiHandler类:完善get()方法:获取一个实现

  • 设置Map:每一个接口对应的实现的实例
  • 1.先查找缓存 2.未命中则构建缓存 3.通过clazz获取与之匹配的实现名称(反射) 4.实例化所有的实现 5.放入缓存

创建getList()方法,与get()方法类似:获取所有和当前服务相关的实例

// 略...
// 缓存:每一个接口对应的实现的实例
private static final Map<Class<?>, List<Object>> SPI_IMPLEMENT = new ConcurrentHashMap<>(32);
// 略...
/**
 * 获取一个实现
 * @param clazz
 * @return 实现类的实例集合
 * @param <T>
 */
public static <T> T get(Class<T> clazz) {

    // 1.先查找缓存
    List<Object> implList = SPI_IMPLEMENT.get(clazz);
    if (implList != null && implList.size() > 0) {
        return (T) implList.get(0);
    }

    // 2.构建缓存
    buildCache(clazz);

    // 3.再次尝试获取第一个
    List<Object> result = SPI_IMPLEMENT.get(clazz);
    if (result == null || result.size() == 0) {
        return null;
    }
    return (T) result.get(0);
}

/**
 * 获取所有和当前服务相关的实例
 * @param clazz
 * @return
 * @param <T>
 */
public static <T> List<T> getList(Class<T> clazz) {

    // 1.先查找缓存
    List<T> implList = (List<T>) SPI_IMPLEMENT.get(clazz);
    if (implList != null && implList.size() > 0) {
        return implList;
    }

    // 2.构建缓存
    buildCache(clazz);

    return (List<T>) SPI_IMPLEMENT.get(clazz);
}

/**
 * 构建clazz相关的缓存缓存
 * @param clazz
 * @return
 */
private static void buildCache(Class<?> clazz) {
    // 1.通过clazz获取与之匹配的实现名称
    String name = clazz.getName();
    List<String> implNameList = SPI_CONTENT.get(name);
    
    if (implNameList == null && implNameList.size() == 0) {
        return;
    }

    // 2.实例化所有的实现
    List<Object> impls = new ArrayList<>();

    for (String implName : implNameList) {
        try {
            Class<?> aClass = Class.forName(implName);
            Object instance = aClass.getConstructor().newInstance();
            impls.add(instance);
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
                 InvocationTargetException e) {
            log.error("实例化【{}】的实现时发生异常", implName, e);
        }
    }

    // 3.放入缓存
    SPI_IMPLEMENT.put(clazz, impls);
}

d.统一spi加载的配置

当完成加载spi后会发现,导入的配置无法得到工厂的统一管理。所以需要修改代码,让工厂对配置进行统一的管理

删除compress包下的CompressWrapper

删除Serialize包下的SerializerWrapper

在core模块下的com.dcyrpc.compress.impl包下的CompressorFactory类进行修改

@Slf4j
public class SerializerFactory {

    private final static Map<String, ObjectWrapper<Serializer>> SERIALIZER_CACHE = new ConcurrentHashMap<>(8);
    private final static Map<Byte, ObjectWrapper<Serializer>> SERIALIZER_CACHE_CODE = new ConcurrentHashMap<>(8);

    static {
        ObjectWrapper<Serializer> jdk = new ObjectWrapper<>((byte) 1, "jdk", new JdkSerializer());
        ObjectWrapper<Serializer> json = new ObjectWrapper<>((byte) 2, "json", new JsonSerializer());
        ObjectWrapper<Serializer> hessian = new ObjectWrapper<>((byte) 3, "hessian", new HessianSerializer());

        SERIALIZER_CACHE.put("jdk", jdk);
        SERIALIZER_CACHE.put("json", json);
        SERIALIZER_CACHE.put("hessian", hessian);

        SERIALIZER_CACHE_CODE.put((byte) 1, jdk);
        SERIALIZER_CACHE_CODE.put((byte) 2, json);
        SERIALIZER_CACHE_CODE.put((byte) 3, hessian);
    }

    /**
     * 使用工厂方法获取一个SerializerWrapper
     * @param serializeType 序列化的类型
     * @return
     */
    public static ObjectWrapper<Serializer> getSerializer(String serializeType) {
        ObjectWrapper<Serializer> serializerObjectWrapper = SERIALIZER_CACHE.get(serializeType);
        if (serializerObjectWrapper == null) {
            log.error("未找到配置的序列化方式【{}】,将采用默认的jdk压缩", serializeType);
            return SERIALIZER_CACHE.get("jdk");
        }
        return serializerObjectWrapper;
    }

    public static ObjectWrapper<Serializer> getSerializer(byte serializeCode) {
        ObjectWrapper<Serializer> serializerObjectWrapper = SERIALIZER_CACHE_CODE.get(serializeCode);
        if (serializerObjectWrapper == null) {
            log.error("未找到配置的序列化方式【{}】,将采用默认的jdk压缩", serializeCode);
            return SERIALIZER_CACHE_CODE.get((byte)1);
        }
        return serializerObjectWrapper;
    }

    /**
     * 添加序列化策略
     * @param serializerWrapper
     */
    public static void addCompressor(ObjectWrapper<Serializer> serializerWrapper) {
        SERIALIZER_CACHE.put(serializerWrapper.getType(), serializerWrapper);
        SERIALIZER_CACHE_CODE.put(serializerWrapper.getCode(), serializerWrapper);
    }
}

在core模块下的com.dcyrpc.serializer.impl包下的SerializerFactory类进行修改

@Slf4j
public class CompressorFactory {

    private final static Map<String, ObjectWrapper<Compressor>> COMPRESSOR_CACHE = new ConcurrentHashMap<>(8);
    private final static Map<Byte, ObjectWrapper<Compressor>> COMPRESSOR_CODE_CACHE = new ConcurrentHashMap<>(8);

    static {
        ObjectWrapper<Compressor> gzip = new ObjectWrapper<>((byte) 1, "gzip", new GzipCompressor());

        COMPRESSOR_CACHE.put("gzip", gzip);

        COMPRESSOR_CODE_CACHE.put((byte) 1, gzip);
    }

    /**
     * 使用工厂方法获取一个CompressWrapper
     * @param compressorType 压缩的类型
     * @return
     */
    public static ObjectWrapper<Compressor> getCompressor(String compressorType) {
        ObjectWrapper<Compressor> compressorObjectWrapper = COMPRESSOR_CACHE.get(compressorType);
        if (compressorObjectWrapper == null) {
            log.error("未找到配置的压缩【{}】,将采用默认的gzip压缩", compressorObjectWrapper);
            return COMPRESSOR_CACHE.get("gzip");
        }
        return compressorObjectWrapper;
    }

    public static ObjectWrapper<Compressor> getCompressor(byte compressorCode) {
        ObjectWrapper<Compressor> compressorObjectWrapper = COMPRESSOR_CODE_CACHE.get(compressorCode);
        if (compressorObjectWrapper == null) {
            log.error("未找到配置的压缩【{}】,将采用默认的gzip压缩", compressorCode);
            return COMPRESSOR_CACHE.get("gzip");
        }
        return compressorObjectWrapper;
    }

    /**
     * 添加压缩策略
     * @param compressor
     */
    public static void addCompressor(ObjectWrapper<Compressor> compressor) {
        COMPRESSOR_CACHE.put(compressor.getType(), compressor);
        COMPRESSOR_CODE_CACHE.put(compressor.getCode(), compressor);
    }
}

在core模块下的resources包下,修改META-INF.dcyrpc-services包类的文件:修改文件内容,code-type-name

  • 如:1-Jdk-com.dcyrpc.serialize.impl.JdkSerializer
  • 如:1-ConsistentHash-com.dcyrpc.loadbalancer.impl.ConsistentHashBalancer
  • 如:1-gzip-com.dcyrpc.compress.impl.GzipCompressor

在core模块下的com.dcyrpc.config包下的,创建ObjectWrapper

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ObjectWrapper<T> {
    private Byte code;
    private String type;
    private T impl;
}

修改config.SpiResolver

  • 将原有的SPI_IMPLEMENT的map的value修改成 List<ObjectWrapper<?>>
    • 会有大量的报错在本类
  • 修改get()
  • 修改getList()
  • 修改buildCache()
private static final Map<Class<?>, List<ObjectWrapper<?>>> SPI_IMPLEMENT = new ConcurrentHashMap<>(32);

public synchronized static <T> ObjectWrapper<T> get(Class<T> clazz) {
    // 1.先查找缓存
    List<ObjectWrapper<?>> implList = SPI_IMPLEMENT.get(clazz);
    if (implList != null && implList.size() > 0) {
        return (ObjectWrapper<T>) implList.get(0);
    }

    // 2.构建缓存
    buildCache(clazz);

    // 3.再次尝试获取第一个
    List<ObjectWrapper<?>> result = SPI_IMPLEMENT.get(clazz);
    if (result == null || result.size() == 0) {
        return null;
    }
    return (ObjectWrapper<T>) result.get(0);
}

public synchronized static <T> List<ObjectWrapper<T>> getList(Class<T> clazz) {
    // 1.先查找缓存
    List<ObjectWrapper<?>> implList =  SPI_IMPLEMENT.get(clazz);
    if (implList != null && implList.size() > 0) {
        return implList.stream().map(wrapper -> (ObjectWrapper<T>) wrapper).collect(Collectors.toList());
    }

    // 2.构建缓存
    buildCache(clazz);
    implList = SPI_IMPLEMENT.get(clazz);
    if (implList != null && implList.size() > 0) {
        return implList.stream().map(wrapper -> (ObjectWrapper<T>) wrapper).collect(Collectors.toList());
    }
    return new ArrayList<>();
}

private static void buildCache(Class<?> clazz) {
    // 1.通过clazz获取与之匹配的实现名称
    String name = clazz.getName();
    List<String> implNameList = SPI_CONTENT.get(name);

    if (implNameList == null && implNameList.size() == 0) {
        return;
    }

    // 2.实例化所有的实现
    List<ObjectWrapper<?>> impls = new ArrayList<>();

    for (String implName : implNameList) {
        try {
            // 进行分割
            String[] codeAndTypeAndImpl = implName.split("-");
            if (codeAndTypeAndImpl.length != 3) {
                throw new SpiException("配置的spi文件不合法");
            }
            Byte code = Byte.valueOf(codeAndTypeAndImpl[0]);
            String type = codeAndTypeAndImpl[1];
            String implementName = codeAndTypeAndImpl[2];

            Class<?> aClass = Class.forName(implementName);
            Object instance = aClass.getConstructor().newInstance();

            ObjectWrapper<?> objectWrapper = new ObjectWrapper<>(code, type, instance);

            impls.add(objectWrapper);
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException |
                 InvocationTargetException e) {
            log.error("实例化【{}】的实现时发生异常", implName, e);
        }
    }

    // 3.放入缓存
    SPI_IMPLEMENT.put(clazz, impls);
}

在common模块下的exceptions包中,创建SpiException

public class SpiException extends RuntimeException{
    public SpiException() {
        super();
    }

    public SpiException(String message) {
        super(message);
    }

    public SpiException(String message, Throwable cause) {
        super(message, cause);
    }
}

修改config.SpiResolver类的loadFromSpi()方法

public void loadFromSpi(Configuration configuration) {
    List<ObjectWrapper<LoadBalancer>> loadBalancerWrappers = SpiHandler.getList(LoadBalancer.class);
    if (loadBalancerWrappers != null && loadBalancerWrappers.size() > 0) {
        configuration.setLoadBalancer(loadBalancerWrappers.get(0).getImpl());
    }

    List<ObjectWrapper<Compressor>> compressorWrappers = SpiHandler.getList(Compressor.class);
    if (compressorWrappers != null) {
        compressorWrappers.forEach(CompressorFactory::addCompressor);
    }

    List<ObjectWrapper<Serializer>> serializerWrappers = SpiHandler.getList(Serializer.class);
    if (serializerWrappers != null) {
        serializerWrappers.forEach(SerializerFactory::addCompressor);
    }
}

修改core模块下的resources文件夹的 dcyrpc.xml文件:修改部分内容

  • provider-demo模块下的resources文件夹的 dcyrpc.xml文件 也是修改一样的内容
<!--压缩方式(二选一)-->
<compressType type="gzip"/>
<compressor code="1" type="gzip" class="com.dcyrpc.compress.impl.GzipCompressor"/>

<!--序列化配置(二选一)-->
<serializeType type="hessian"/>
<serializr code="3" type="Hessian" class="com.dcyrpc.serialize.impl.HessianSerializer" />

修改config.XmlResolver类:修改部分内容

ObjectWrapper<Compressor> compressorObjectWrapper = resolveCompressCompressor(xPath, doc);
CompressorFactory.addCompressor(compressorObjectWrapper);

ObjectWrapper<Serializer> serializerObjectWrapper = resolveSerializer(xPath, doc);
SerializerFactory.addCompressor(serializerObjectWrapper);

private ObjectWrapper<Compressor> resolveCompressCompressor(XPath xPath, Document doc) {
    String expression = "/configuration/compressor";
    Compressor compressor = parseObject(xPath, doc, expression, null);
    Byte code = Byte.valueOf(Objects.requireNonNull(parseString(xPath, doc, expression, "code")));
    String type = parseString(xPath, doc, expression, "type");
    return new ObjectWrapper<>(code, type, compressor);
}

private ObjectWrapper<Serializer> resolveSerializer(XPath xPath, Document doc) {
    String expression = "/configuration/serializr";
    Serializer serializer = parseObject(xPath, doc, expression, null);
    Byte code = Byte.valueOf(Objects.requireNonNull(parseString(xPath, doc, expression, "code")));
    String type = parseString(xPath, doc, expression, "type");
    return new ObjectWrapper<>(code, type, serializer);
}

修改config.Configuration类:删除以下代码

private ProtocolConfig protocolConfig = new ProtocolConfig("jdk");
private Serializer serializer = new JdkSerializer();
private Compressor compressor = new GzipCompressor();

修改DcyRpcBootstrap类:删除以下代码

public DcyRpcBootstrap protocol(ProtocolConfig protocolConfig) {
    configuration.setProtocolConfig(protocolConfig);
    log.info("当前工程使用了:{}协议进行序列化", protocolConfig.toString());
    return this;
}

修改provider模块下的启动类:替换代码

  • .protocol(new ProtocolConfig("jdk"))替换为以下代码
.serialize("jdk")

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1003441.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Scrum工作模式的角色和活动

​Scrum工作模式是一种敏捷软件开发方法&#xff0c;其核心是团队合作和自我组织&#xff0c;旨在通过短周期的迭代开发&#xff0c;实现快速反馈和持续改进。 Scrum工作模式包括以下角色和活动&#xff1a; 1、产品负责人&#xff08;Product Owner&#xff09;&#xff1a;…

Spring-Cloud GateWay+Vue 跨域方案汇总

文章目录 一、简介背景和概述 二、前端跨域解决方案Axios跨域CORS跨域 三、后端跨域解决方案反向代理服务器 四、Spring Cloud中的跨域解决方案Gateway网关的跨域配置 五、基于Vue和Spring Cloud的跨域整合实践**这两种配置只需配置一种即可生效&#xff08;前端or后端&#xf…

Unity和C#游戏编程入门:创建迷宫小球游戏示例

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 当涉及到Unity和C#游戏编…

电脑连接电视怎么做?学会这4个方法,轻松连接!

“我的电脑屏幕太小了&#xff0c;想将它连接到电视上看电影。有什么方法可以将电脑与电视进行连接吗&#xff1f;请教教我&#xff01;” 在今天的数字时代&#xff0c;将电脑连接到电视已经成为了常见的需求。无论是观看电影、演示文稿还是玩游戏&#xff0c;电脑连接电视可以…

蓝桥杯打卡Day7

文章目录 阶乘的末尾0整除问题 一、阶乘的末尾0IO链接 本题思路&#xff1a;由于本题需要求阶乘的末尾0&#xff0c;由于我们知道2*510可以得到一个0&#xff0c;那么我们就可以找出2的数和5的数&#xff0c;但是由于是阶乘&#xff0c;所以5的数量肯定是小于2的数量&#xf…

leetcode:67. 二进制求和

题目&#xff1a; 函数原型&#xff1a; char * addBinary(char * a, char * b) 思路&#xff1a; 二进制相加&#xff0c;首先我们考虑先将字符串逆序。由此要写一个逆序函数reserve。字符串逆序后&#xff0c;从前往后相加&#xff0c;以较长的字符串的长度为标准长度n&#…

前端vue按钮控制切换按钮是否禁用和颜色和显示隐藏,利用v-if和v-else

效果 未输入input前图片 输入input后图片 html <input type"number" placeholder"请输入分润数量" placeholder-class"shareprofit_placeholder_num" v-model"money"> <!-- 金钱 --> {{money}} <!-- 可提现余额 --&g…

518电脑端抽奖软件,可用作婚庆大屏幕滚动抽奖

518抽奖软件简介 518抽奖软件&#xff0c;518我要发&#xff0c;超好用的年会抽奖软件&#xff0c;简约设计风格。 包含文字号码抽奖、照片抽奖两种模式&#xff0c;支持姓名抽奖、号码抽奖、数字抽奖、照片抽奖。(www.518cj.net) 婚礼抽奖活动意义 抽奖类似于买彩票&#x…

大数据技术之Hive:先导篇(一)

目录 一、什么是Hive 二、思考如何设计出Hive功能 2.1 提问 2.2 案例分析 2.3 小结 三、掌握Hive的基础架构 3.1 Hive组件 - 元数据存储 3.2 Hive组件 - Driver驱动程序 3.3 Hive组件 - 用户接口 一、什么是Hive 什么是分布式SQL计算 我们知道&#xff0c;在进行数据统…

cf 交互题

今天cf遇到了交互题&#xff0c;这个交互题的算法很很很简单&#xff0c;但是在交互上卡了&#xff0c;导致交上的代码都不算罚时。&#xff08;更伤心了。 所以&#xff0c;现在写一下交互题的做法&#xff0c;印象深刻嘛。 交互题&#xff0c;就是跟机器进行交互。你代码运…

开始撸 Android 源码

启动找工作模式&#xff0c;发现无比困难。搁在往日&#xff0c;大龄程序员找工作都是一件困难的事情&#xff0c;加上今年形势很差&#xff0c;更是难上加难。关键是我这十几年来主攻的浏览器内核方向&#xff0c;需求量更是几乎为零。在 BOSS 直聘上以 Chromium 为关键词&…

DeepSpeed

DeepSpeed概念 DeepSpeed中用到的技术包括以下几个等级&#xff1a; ZeRO-1&#xff1a;只对optimizer进行切片后分布式保存 ZeRO-2&#xff1a;对optimizer和grad进行切片后分布式保存 ZeRO-3&#xff1a;对optimizer、grad和模型参数进行切片后分布式保存 offload&#xff1…

【RocketMQ】设计理念与核心概念扫盲

【RocketMQ】设计理念与核心概念扫盲 文章目录 【RocketMQ】设计理念与核心概念扫盲一、RocketMQ的设计理念和目标1.1、设计理念1.2、设计目标 二、RocketMQ的核心概念扫盲篇2.1、部署架构2.1.1、Nameserver2.1.2、Broker2.1.3、Client 2.2、消息订阅模型2.2.1、消费模式2.2.2、…

【C++基础】简单工程模式、工厂模式、抽象工程模式

本文参考&#xff1a;简单工厂模式 - 人造恶魔果实工厂1 | 爱编程的大丙​​​​​​ ​​​​​​工厂模式 - 人造恶魔果实工厂2 | 爱编程的大丙​​​​​ ​​​​​抽象工厂模式 - 弗兰奇一家 | 爱编程的大丙 工厂我们就可以得到想要的东西&#xff0c;在程序设计中&…

Nacos使用和注册部分源码介绍

Nacos简单介绍 Nacos致力于帮助您发现、配置和管理微服务。Nacos提供了一组简单易用的特性集&#xff0c;帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos帮助您更敏捷和容易地构建、交付和管理微服务平台。Nacos是构建以“服务”为中心的现代应用架构 (例…

社群团购对接,【概率思维】可以增加你做项目的成功率!

社群团购对接&#xff0c;【概率思维】可以增加你做项目的成功率&#xff01; 今天来聊一个关于概率的问题&#xff0c;我们不管去做社群团购项目、做流量&#xff0c;还是做销售&#xff0c;我们都要有概率思维&#xff0c;有了这个思维以后&#xff0c;就可以增加你的成功率…

input输入框从右边开始输入,光标靠左移动

未设置前 光标在左边 <input type"number" placeholder"请输入分润数量" placeholder-class"shareprofit_placeholder_num" v-model"money">设置后 光标在右边 <input type"number" placeholder"请输入分润数…

C#开发的OpenRA游戏之调试菜单1

C#开发的OpenRA游戏之调试菜单1 在开发一个游戏里,经常需要提供一些调试设置,以便快速地达到需要测试的阶段,否则按正常游戏的进程,就会需要比较久的时间,这样浪费开发人员的时间。 在OpenRA提供一个调试菜单,它就是在下面的界面里: 这个菜单叫做 Debug Menu,当玩家点…

关于游戏开发,还有这些信息你可能不知道

游戏开发是一个复杂而令人兴奋的领域&#xff0c;有许多人不知道的有趣事实和趋势。以下是一些可能令你感兴趣的游戏开发领域的事实&#xff1a; 游戏开发是巨大的产业&#xff1a; 游戏产业已经成为世界上最大的娱乐产业之一&#xff0c;超过电影和音乐产业。这包括移动游戏、…

综合续航达1040公里:腾势计划2024年在香港上市,售价60-100 万

腾势汽车表示&#xff0c;他们计划于2024年在香港地区上市全新的D9车型。这款中大型高端新能源MPV是通过DM-i超级混动技术打造的&#xff0c;由于综合续航能力达到1040公里&#xff0c;且纯电续航最大可达190公里&#xff0c;这款车已经引起了广泛关注。据腾势销售事业部总经理…