iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >java重试机制使用RPC要考虑什么
  • 915
分享到

java重试机制使用RPC要考虑什么

2023-07-05 14:07:32 915人浏览 薄情痞子
摘要

这篇文章主要介绍“java重试机制使用rpc要考虑什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java重试机制使用RPC要考虑什么”文章能帮助大家解决问题。1 为什么重试如果简单对一个RPC交

这篇文章主要介绍“java重试机制使用rpc要考虑什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java重试机制使用RPC要考虑什么”文章能帮助大家解决问题。

    1 为什么重试

    如果简单对一个RPC交互过程进行分类,我们可以分为三类:响应成功、响应失败、没有响应。

    java重试机制使用RPC要考虑什么

    对于响应成功和响应失败这两种情况,消费者很好处理。因为响应信息明确,所以只要根据响应信息,继续处理成功或者失败逻辑即可。但是没有响应这种场景比较难处理,这是因为没有响应可能包含以下情况:

    (1) 生产者根本没有接收到请求

    (2) 生产者接收到请求并且已处理成功,但是消费者没有接收到响应

    (3) 生产者接收到请求并且已处理失败,但是消费者没有接收到响应

    假设你是一名RPC框架设计者,究竟是选择重试还是放弃调用呢?其实最终如何选择取决于业务特性,有的业务本身就具有幂等性,但是有的业务不能允许重试否则会造成重复数据。

    那么谁对业务特性最熟悉呢?答案是消费者,因为消费者作为调用方肯定最熟悉自身业务,所以RPC框架只要提供一些策略供消费者选择即可。

    2 怎么做重试

    2.1 集群容错策略

    dubbo作为一款优秀RPC框架,提供了如下集群容错策略供消费者选择:

    Failover: 故障转移
    Failfast: 快速失败
    Failsafe: 安全失败
    Failback: 异步重试
    Forking:  并行调用
    Broadcast:广播调用

    (1) Failover

    故障转移策略。作为默认策略当消费发生异常时通过负载均衡策略再选择一个生产者节点进行调用,直到达到重试次数

    (2) Failfast

    快速失败策略。消费者只消费一次服务,当发生异常时则直接抛出

    (3) Failsafe

    安全失败策略。消费者只消费一次服务,如果消费失败则包装一个空结果,不抛出异常

    (4) Failback

    异步重试策略。当消费发生异常时返回一个空结果,失败请求将会进行异步重试。如果重试超过最大重试次数还不成功,放弃重试并不抛出异常

    (5) Forking

    并行调用策略。消费者通过线程池并发调用多个生产者,只要有一个成功就算成功

    (6) Broadcast

    广播调用策略。消费者遍历调用所有生产者节点,任何一个出现异常则抛出异常

    2.2 源码分析

    2.2.1 Failover

    Failover故障转移策略作为默认策略,当消费发生异常时通过负载均衡策略再选择一个生产者节点进行调用,直到达到重试次数。即使业务代码没有显示重试,也有可能多次执行消费逻辑从而造成重复数据:

    public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {    public FailoverClusterInvoker(Directory<T> directory) {        super(directory);    }    @Override    public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {        // 所有生产者Invokers        List<Invoker<T>> copyInvokers = invokers;        checkInvokers(copyInvokers, invocation);        String methodName = RpcUtils.getMethodName(invocation);        // 获取重试次数        int len = getUrl().getMethodParameter(methodName, Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;        if (len <= 0) {            len = 1;        }        RpcException le = null;        // 已经调用过的生产者        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size());        Set<String> providers = new HashSet<String>(len);        // 重试直到达到最大次数        for (int i = 0; i < len; i++) {            if (i > 0) {                // 如果当前实例被销毁则抛出异常                checkWhetherDestroyed();                // 根据路由策略选出可用生产者Invokers                copyInvokers = list(invocation);                // 重新检查                checkInvokers(copyInvokers, invocation);            }            // 负载均衡选择一个生产者Invoker            Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);            invoked.add(invoker);            RpcContext.getContext().setInvokers((List) invoked);            try {                // 服务消费发起远程调用                Result result = invoker.invoke(invocation);                if (le != null && logger.isWarnEnabled()) {                    logger.warn("Although retry the method " + methodName + " in the service " + getInterface().getName() + " was successful by the provider " + invoker.getUrl().getAddress() + ", but there have been failed providers " + providers + " (" + providers.size() + "/" + copyInvokers.size() + ") from the reGIStry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le);                }                // 有结果则返回                return result;            } catch (RpcException e) {                // 业务异常直接抛出                if (e.isBiz()) {                    throw e;                }                le = e;            } catch (Throwable e) {                // RpcException不抛出继续重试                le = new RpcException(e.getMessage(), e);            } finally {                // 保存已经访问过的生产者                providers.add(invoker.getUrl().getAddress());            }        }        throw new RpcException(le.getCode(), "Failed to invoke the method " + methodName + " in the service " + getInterface().getName() + ". Tried " + len + " times of the providers " + providers + " (" + providers.size() + "/" + copyInvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le.getCause() != null ? le.getCause() : le);    }}

    消费者调用生产者节点A发生RpcException异常时(例如超时异常),在未达到最大重试次数之前,消费者会通过负载均衡策略再次选择其它生产者节点消费。试想如果生产者节点A其实已经处理成功了,但是没有及时将成功结果返回给消费者,那么再次重试可能就会造成重复数据问题。

    2.2.2 Failfast

    快速失败策略。消费者只消费一次服务,当发生异常时则直接抛出,不会进行重试:

    public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> {    public FailfastClusterInvoker(Directory<T> directory) {        super(directory);    }    @Override    public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {        // 检查生产者Invokers是否合法        checkInvokers(invokers, invocation);        // 负载均衡选择一个生产者Invoker        Invoker<T> invoker = select(loadbalance, invocation, invokers, null);        try {            // 服务消费发起远程调用            return invoker.invoke(invocation);        } catch (Throwable e) {            // 服务消费失败不重试直接抛出异常            if (e instanceof RpcException && ((RpcException) e).isBiz()) {                throw (RpcException) e;            }            throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0,                                   "Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName()                                   + " select from all providers " + invokers + " for service " + getInterface().getName()                                   + " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost()                                   + " use dubbo version " + Version.getVersion()                                   + ", but no luck to perfORM the invocation. Last error is: " + e.getMessage(),                                   e.getCause() != null ? e.getCause() : e);        }    }}
    2.2.3 Failsafe

    安全失败策略。消费者只消费一次服务,如果消费失败则包装一个空结果,不抛出异常,不会进行重试:

    public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> {    private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class);    public FailsafeClusterInvoker(Directory<T> directory) {        super(directory);    }    @Override    public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {        try {            // 检查生产者Invokers是否合法            checkInvokers(invokers, invocation);            // 负载均衡选择一个生产者Invoker            Invoker<T> invoker = select(loadbalance, invocation, invokers, null);            // 服务消费发起远程调用            return invoker.invoke(invocation);        } catch (Throwable e) {            // 消费失败包装为一个空结果对象            logger.error("Failsafe ignore exception: " + e.getMessage(), e);            return new RpcResult();        }    }}
    2.2.4 Failback

    异步重试策略。当消费发生异常时返回一个空结果,失败请求将会进行异步重试。如果重试超过最大重试次数还不成功,放弃重试并不抛出异常:

    public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {    private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class);    private static final long RETRY_FAILED_PERIOD = 5;    private final int retries;    private final int failbackTasks;    private volatile Timer failTimer;    public FailbackClusterInvoker(Directory<T> directory) {        super(directory);        int retriesConfig = getUrl().getParameter(Constants.RETRIES_KEY, Constants.DEFAULT_FAILBACK_TIMES);        if (retriesConfig <= 0) {            retriesConfig = Constants.DEFAULT_FAILBACK_TIMES;        }        int failbackTasksConfig = getUrl().getParameter(Constants.FAIL_BACK_TASKS_KEY, Constants.DEFAULT_FAILBACK_TASKS);        if (failbackTasksConfig <= 0) {            failbackTasksConfig = Constants.DEFAULT_FAILBACK_TASKS;        }        retries = retriesConfig;        failbackTasks = failbackTasksConfig;    }    private void addFailed(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker) {        if (failTimer == null) {            synchronized (this) {                if (failTimer == null) {                    // 创建定时器                    failTimer = new HashedWheelTimer(new NamedThreadFactory("failback-cluster-timer", true), 1, TimeUnit.SECONDS, 32, failbackTasks);                }            }        }        // 构造定时任务        RetryTimerTask retryTimerTask = new RetryTimerTask(loadbalance, invocation, invokers, lastInvoker, retries, RETRY_FAILED_PERIOD);        try {            // 定时任务放入定时器等待执行            failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS);        } catch (Throwable e) {            logger.error("Failback background works error,invocation->" + invocation + ", exception: " + e.getMessage());        }    }    @Override    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {        Invoker<T> invoker = null;        try {            // 检查生产者Invokers是否合法            checkInvokers(invokers, invocation);            // 负责均衡选择一个生产者Invoker            invoker = select(loadbalance, invocation, invokers, null);            // 消费服务发起远程调用            return invoker.invoke(invocation);        } catch (Throwable e) {            logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: " + e.getMessage() + ", ", e);            // 如果服务消费失败则记录失败请求            addFailed(loadbalance, invocation, invokers, invoker);            // 返回空结果            return new RpcResult();        }    }    @Override    public void destroy() {        super.destroy();        if (failTimer != null) {            failTimer.stop();        }    }        private class RetryTimerTask implements TimerTask {        private final Invocation invocation;        private final LoadBalance loadbalance;        private final List<Invoker<T>> invokers;        private final int retries;        private final long tick;        private Invoker<T> lastInvoker;        private int retryTimes = 0;        RetryTimerTask(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker, int retries, long tick) {            this.loadbalance = loadbalance;            this.invocation = invocation;            this.invokers = invokers;            this.retries = retries;            this.tick = tick;            this.lastInvoker = lastInvoker;        }        @Override        public void run(Timeout timeout) {            try {                // 负载均衡选择一个生产者Invoker                Invoker<T> retryInvoker = select(loadbalance, invocation, invokers, Collections.singletonList(lastInvoker));                lastInvoker = retryInvoker;                // 服务消费发起远程调用                retryInvoker.invoke(invocation);            } catch (Throwable e) {                logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);                // 超出最大重试次数记录日志不抛出异常                if ((++retryTimes) >= retries) {                    logger.error("Failed retry times exceed threshold (" + retries + "), We have to abandon, invocation->" + invocation);                } else {                    // 未超出最大重试次数重新放入定时器                    rePut(timeout);                }            }        }        private void rePut(Timeout timeout) {            if (timeout == null) {                return;            }            Timer timer = timeout.timer();            if (timer.isStop() || timeout.isCancelled()) {                return;            }            timer.newTimeout(timeout.task(), tick, TimeUnit.SECONDS);        }    }}
    2.2.5 Forking

    并行调用策略。消费者通过线程池并发调用多个生产者,只要有一个成功就算成功:

    public class ForkinGClusterInvoker<T> extends AbstractClusterInvoker<T> {    private final ExecutorService executor = Executors.newCachedThreadPool(new NamedInternalThreadFactory("forking-cluster-timer", true));    public ForkingClusterInvoker(Directory<T> directory) {        super(directory);    }    @Override    public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {        try {            checkInvokers(invokers, invocation);            final List<Invoker<T>> selected;            // 获取配置参数            final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS);            final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);            // 获取并行执行的Invoker列表            if (forks <= 0 || forks >= invokers.size()) {                selected = invokers;            } else {                selected = new ArrayList<>();                for (int i = 0; i < forks; i++) {                    // 选择生产者                    Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);                    // 防止重复增加Invoker                    if (!selected.contains(invoker)) {                        selected.add(invoker);                    }                }            }            RpcContext.getContext().setInvokers((List) selected);            final AtomicInteger count = new AtomicInteger();            final BlockingQueue<Object> ref = new LinkedBlockingQueue<>();            for (final Invoker<T> invoker : selected) {                // 在线程池中并发执行                executor.execute(new Runnable() {                    @Override                    public void run() {                        try {                            // 执行消费逻辑                            Result result = invoker.invoke(invocation);                            // 存储消费结果                            ref.offer(result);                        } catch (Throwable e) {                            // 如果异常次数大于等于forks参数值说明全部调用失败,则把异常放入队列                            int value = count.incrementAndGet();                            if (value >= selected.size()) {                                ref.offer(e);                            }                        }                    }                });            }            try {                // 从队列获取结果                Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);                // 如果异常类型表示全部调用失败则抛出异常                if (ret instanceof Throwable) {                    Throwable e = (Throwable) ret;                    throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e);                }                return (Result) ret;            } catch (InterruptedException e) {                throw new RpcException("Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e);            }        } finally {            RpcContext.getContext().clearAttachments();        }    }}
    2.2.6 Broadcast

    广播调用策略。消费者遍历调用所有生产者节点,任何一个出现异常则抛出异常:

    public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {    private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class);    public BroadcastClusterInvoker(Directory<T> directory) {        super(directory);    }    @Override    public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {        checkInvokers(invokers, invocation);        RpcContext.getContext().setInvokers((List) invokers);        RpcException exception = null;        Result result = null;        // 遍历调用所有生产者节点        for (Invoker<T> invoker : invokers) {            try {                // 执行消费逻辑                result = invoker.invoke(invocation);            } catch (RpcException e) {                exception = e;                logger.warn(e.getMessage(), e);            } catch (Throwable e) {                exception = new RpcException(e.getMessage(), e);                logger.warn(e.getMessage(), e);            }        }        // 任何一个出现异常则抛出异常        if (exception != null) {            throw exception;        }        return result;    }}

    3 怎么做幂等

    经过上述分析我们知道,RPC框架自带的重试机制可能会造成数据重复问题,那么在使用中必须考虑幂等性。幂等性是指一次操作与多次操作产生结果相同,并不会因为多次操作而产生不一致性。常见幂等方案有取消重试、幂等表、数据库、状态机。

    3.1 取消重试

    取消重试有两种方法,第一是设置重试次数为零,第二是选择不重试的集群容错策略。

    <!-- 设置重试次数为零 --><dubbo:reference id="helloService" interface="com.java.front.dubbo.demo.provider.HelloService" retries="0" /><!-- 选择集群容错方案 --><dubbo:reference id="helloService" interface="com.java.front.dubbo.demo.provider.HelloService" cluster="failfast" />

    3.2 幂等表

    假设用户支付成功后,支付系统将支付成功消息,发送至消息队列。物流系统订阅到这个消息,准备为这笔订单创建物流单。

    但是消息队列可能会重复推送,物流系统有可能接收到多次这条消息。我们希望达到效果是:无论接收到多少条重复消息,只能创建一笔物流单。

    解决方案是幂等表方案。新建一张幂等表,该表就是用来做幂等,无其它业务意义,有一个字段名为key建有唯一索引,这个字段是幂等标准。

    物流系统订阅到消息后,首先尝试插入幂等表,订单编号作为key字段。如果成功则继续创建物流单,如果订单编号已经存在则违反唯一性原则,无法插入成功,说明已经进行过业务处理,丢弃消息。

    这张表数据量会比较大,我们可以通过定时任务对数据进行归档,例如只保留7天数据,其它数据存入归档表。

    还有一种广义幂等表就是我们可以用Redis替代数据库,在创建物流单之前,我们可以检查Redis是否存在该订单编号数据,同时可以为这类数据设置7天过期时间。

    3.3 状态机

    物流单创建成功后会发送消息,订单系统订阅到消息后更新状态为完成,假设变更是将订单状态0更新至状态1。订单系统也可能收到多条消息,可能在状态已经被更新至状态1之后,依然收到物流单创建成功消息。

    解决方案是状态机方案。首先绘制状态机图,分析状态流转形态。例如经过分析状态1已经是最终态,那么即使接收到物流单创建成功消息也不再处理,丢弃消息。

    3.4 数据库锁

    数据库锁又可以分为悲观锁和乐观锁两种类型,悲观锁是在获取数据时加锁:

    select * from table where col='xxx' for update

    乐观锁是在更新时加锁,第一步首先查出数据,数据包含version字段。第二步进行更新操作,如果此时记录已经被修改则version字段已经发生变化,无法更新成功:

    update table set xxx,version = #{version} + 1 where id = #{id} and version = #{version}

    关于“java重试机制使用RPC要考虑什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网精选频道,小编每天都会为大家更新不同的知识点。

    --结束END--

    本文标题: java重试机制使用RPC要考虑什么

    本文链接: https://www.lsjlt.com/news/352059.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • java重试机制使用RPC要考虑什么
      这篇文章主要介绍“java重试机制使用RPC要考虑什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“java重试机制使用RPC要考虑什么”文章能帮助大家解决问题。1 为什么重试如果简单对一个RPC交...
      99+
      2023-07-05
    • java重试机制使用RPC必须考虑幂等性原理解析
      目录文章概述1 为什么重试2 怎么做重试2.1 集群容错策略(1) Failover(2) Failfast(3) Failsafe(4) Failback(5) Forking(6...
      99+
      2023-03-19
      java重试机制RPC幂等性 RPC幂等性原理
    • 为什么使用 Java 接口重定向时要考虑 JavaScript?
      Java 接口重定向是一个非常常见的技术,但是在使用的时候,我们需要考虑 JavaScript 的影响。本文将介绍为什么使用 Java 接口重定向时要考虑 JavaScript,并提供一些演示代码。 首先,让我们简单介绍一下 Java 接口...
      99+
      2023-07-10
      接口 重定向 javascript
    • 海外云主机租用要考虑什么
      海外云主机租用要考虑:1、性价比,同样的海外云主机配置和性能保证能否符合业务的要求;2、宽带质量,海外云主机是否能降低网站或应用响应时间,保证业务的稳定运行;3、服务商的资质是否合格,有无获得国家的资质审批,备案是否合理。具体内容如下:1、...
      99+
      2024-04-02
    • 网站制作需要考虑什么问题
      这篇文章将为大家详细讲解有关网站制作需要考虑什么问题,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。制作网站之前需要进行详细策划在制作网站之前,需要对网站整体框架进行详细的策划,无论是主网页还是副网页,展现...
      99+
      2023-06-27
    • ​租用台湾云主机需要考虑什么
      租用台湾云主机的考虑因素:1. 租用台湾云主机要考虑性价比,同样的配置和性能保证能否符合业务的要求。2.  租用台湾云主机要考虑宽带质量,是否能够降低网站或应用响应时间,保证业务的稳定运行。3. 台湾云主机服务商的资质是否合格,有...
      99+
      2024-04-02
    • 选择云主机要考虑什么情况
      选择云主机要考虑的情况:1. 考虑企业网站的类型,注意操作系统、存储模式的选择。2. 留意网站的访问量,观察高峰期以及日均流量的数值。3. 注意网站的数据量,做好长期规划,方便硬盘的选择。4. 分析网站的目标用户,根据客户群体的地域因素选择...
      99+
      2024-04-02
    • 选择虚拟主机需要考虑什么
      选择虚拟主机时需要考虑以下几个因素:1. 价格:虚拟主机的价格因服务提供商、服务等级、服务器配置等因素而有所不同。需要根据预算选择适...
      99+
      2023-08-26
      虚拟主机
    • 租用高防服务器要考虑什么机房因素
      租用高防服务器时考虑的机房因素包括以下几点: 机房的地理位置:选择距离用户较近的机房可以提高访问速度,减少延迟。 机房的网络...
      99+
      2024-05-06
      高防服务器
    • 服务器租用要考虑什么因素
      服务器性能:根据网站或应用的需求,选择适合的服务器性能,包括处理器、内存、存储等配置。 带宽和流量:根据网站或应用的访问量和...
      99+
      2024-04-26
      服务器
    • 租用高防服务器需要考虑什么
      租用高防服务器需要考虑的因素:1.防火墙是否装在电脑硬件上。2.选择合适的线路。3.高防服务器的带宽大小。4.高防服务器的防御能力。5.机房存储要大。具体内容如下:一、软件方面,防火墙必须安装在电脑硬件上,否则无法使用,他是防范电脑内部与外...
      99+
      2024-04-02
    • 租用香港服务器时要考虑什么
      在租用香港服务器时,有几个关键因素需要考虑: 服务器性能:确保所选择的香港服务器具有足够的性能来满足您的需求,包括处理速度、存储...
      99+
      2024-04-09
      香港服务器 服务器
    • Web服务器租用要考虑什么因素
      Web服务器租用要考虑什么因素,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。 Web服务器租用要考虑什么因素?搭建网站的时候很重要的一点就是服务器的选...
      99+
      2023-06-07
    • 日志路径的重要性:为什么你应该考虑在ASP中使用JavaScript?
      在ASP(Active Server Pages)的开发中,日志路径是一个非常重要的概念。它指的是记录应用程序运行时产生的信息的位置。这些信息可以帮助开发人员在应用程序出现问题时快速定位和解决问题。在ASP中,使用JavaScript来记...
      99+
      2023-07-02
      path javascript 日志
    • 租用服务器时要考虑什么因素
      服务器性能:包括CPU、内存、存储和带宽等方面的性能,要根据业务需求选择合适的配置。 可靠性和稳定性:服务器的稳定性和可靠性...
      99+
      2024-04-26
      服务器
    • 选择香港云主机时需要考虑什么问题
      选择香港云主机时需要考虑以下问题: 价格:不同云主机服务提供商的价格可能会有所不同,需要根据自己的预算来选择合适的云主机方案。 ...
      99+
      2024-04-16
      香港云主机 云主机
    • 购买国外专用服务器要考虑什么
      购买国外专用服务器要考虑:1、国外专用服务器的基础指标,选择带宽高且具备较高防御性的机房;2、国外专用服务器的防御需求,保证业务的安全运行;3、国外专用服务器租用商的发展前景,保证在不同发展时期的网站能有良好的网络体验感。具体内容如下:1、...
      99+
      2024-04-02
    • 租用美国云服务器时要考虑什么
      租用美国云服务器时,需要考虑以下几个方面: 价格:不同的云服务器提供商可能有不同的价格策略,需要根据自己的需求和预算选择合适的价...
      99+
      2024-04-09
      美国云服务器 云服务器
    • 租用视频服务器要考虑什么因素
      租用视频服务器要考虑:1、根据网站的流量和同时在线人数选择所需带宽资源,保证能满足网站的使用需求;2、服务器的计算能力,包括服务器的CPU和内存;3、硬盘存储容量要大,才能保证视频资源的丰富;4、数据中心的安全指数、网络资源的布置、电源等是...
      99+
      2024-04-02
    • 租用香港服务器时需要考虑什么
      在租用香港服务器时,需要考虑以下几个方面: 服务器性能:选择适合自己需求的服务器性能,包括 CPU、内存、硬盘等配置。 网络速度和...
      99+
      2024-04-09
      香港服务器 服务器
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作