精华内容
下载资源
问答
  • 大家都知道,jq的ajax方法,在请求后台数据失败了(后台请求错误或者网络错误,链接有问题)会有一个错误处理机制,如:error参数会在请求失败后回调此方法。 那我们经常用到的bootstrapTable在请求失败后,怎么处理...

    大家都知道,jq的ajax方法,在请求后台数据失败了(后台请求错误或者网络错误,链接有问题)会有一个错误处理机制,如:error参数会在请求失败后回调此方法。

    那我们经常用到的bootstrapTable在请求失败后,怎么处理呢?

    bootstrapTable 是使用这个参数的:onLoadError

    var loadding = layer.load(1, {shade: [0.1,'#fff']});
    $("#table-today-salesList").bootstrapTable({
                method: 'post',
                dataType: "json",
                dataField: 'rows',
                url:'{$uri}Home/POSRetail/getSalesDatas',
                ......   //此处省略了一些配置参数
                onLoadError: function(){
                  layer.close(loadding);
                  layer.msg("请求失败,网络错误");
                },
                columns : [ {
                    title : '序号',
                    field : '',
                    formatter: function (value, row, index) {
                        return index+1;
                    }
                },{
                    title : '单号',
                    field : 'ref',
                    sortable : true
                }, {
                    title : '销售数量',
                    field : 'salenum',
                    sortable : true,
                    formatter: function (value, row, index) {
                        value=Number(value);
                        if(!value) value=0;
                        return value;
                    }
                }, {
                    title : '销售金额',
                    field : 'salemoney',
                    sortable : true,
                    formatter: function (value, row, index) {
                        value=Number(value);
                        if(!value){
                          value="0.00";
                        }else{
                          value=toMoney(value);
                        }
                        return value;
                    }
                },{
                    title : '业务员',
                    field : 'salesname',
                },
                .......
                }]
              });
    

    转载于:https://www.dsboke.com/2020/05/27/bootstraptable_fu_wu_duan_qing_qiu_shi_bai_hou_de_chu_li_fang_fa/

    展开全文
  • 请求失败服务端响应失败, 在网站后端想上传点东西到服务器出现的问题。
  • 服务端与消费者端都已成功注册在eureka,但是,消费者端无法请求到服务器端。 2、原因与方案 原因:服务端FeignClient命名与服务的应用名称不一致 方案:如下改成一致即可:user-service-provider spring: ...

    1、情形描述

    服务端与消费者端都已成功注册在eureka,但是,消费者端无法请求到服务器端。

    2、原因与方案

    原因:服务端FeignClient命名与服务的应用名称不一致

    方案:如下改成一致即可:user-service-provider

    spring:
      application:
        name: user-service-provider
    @FeignClient(name = ServiceProvider.USER_SERVICE)
    public interface RemoteUserService {
    
        @PostMapping("/user/page")
        Page<UserPageableVO.UserVO> page(@RequestBody Pageable pageable);
    
    }

     

    
    public class ServiceProvider {
        /**
         * 用户服务
         */
        public static final String USER_SERVICE = "user-service-provider";
    }

     

    展开全文
  • 本文主要针对dubbo-spring-boot-starter 2.7.7版本, 对应的 org.apache.dubbo ... 客户端请求发出去之后,服务端会收到这个请求的消息,然后触发调用。服务端这边接收消息的处理链路,也比较复杂,我们回到NettS...

    本文主要针对 dubbo-spring-boot-starter   2.7.7版本, 对应的 org.apache.dubbo 2.7.7 版本的源码。

      本文主要从以下几个点来分析:

    1. 服务端处理请求.
    2. 时间轮(失败重试)。

    服务端接收数据的处理流程:

      客户端请求发出去之后,服务端会收到这个请求的消息,然后触发调用。服务端这边接收消息的处理链路,也比较复杂,我们回到NettServer中创建io的过程。

    @Override
    protected void doOpen() throws Throwable {
            bootstrap = new ServerBootstrap();
    
            bossGroup = NettyEventLoopFactory.eventLoopGroup(1, "NettyServerBoss");
            workerGroup = NettyEventLoopFactory.eventLoopGroup(
                    getUrl().getPositiveParameter(IO_THREADS_KEY, Constants.DEFAULT_IO_THREADS),
                    "NettyServerWorker");
    
            final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
            channels = nettyServerHandler.getChannels();
    
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NettyEventLoopFactory.serverSocketChannelClass())
                    .option(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
                    .childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
                    .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // FIXME: should we use getTimeout()?
                            int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
                            NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
                            if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {
                                ch.pipeline().addLast("negotiation",
                                        SslHandlerInitializer.sslServerHandler(getUrl(), nettyServerHandler));
                            }
                            ch.pipeline()
                                    .addLast("decoder", adapter.getDecoder())
                                    .addLast("encoder", adapter.getEncoder())
                                    .addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
                                    .addLast("handler", nettyServerHandler);
                        }
                    });
            // bind
            ChannelFuture channelFuture = bootstrap.bind(getBindAddress());
            channelFuture.syncUninterruptibly();
            channel = channelFuture.channel();
    
    }
    

      服务端启动的时候,配置的消息处理是handler配置的是nettyServerHandler , 所以我们直接进入到 NettyServerHandler#channelRead ,这个方法负责处理请求。

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
      NettyChannel channel = NettyChannel.getOrAddChannel(ctx.channel(), url, handler);
      handler.received(channel, msg);
    }
    

      服务端收到读的请求是,会进入这个方法。接着通过handler.received来处理msg ,而这个handler 是在服务发布的时候构建得。起链路如下:

      MultiMessageHandler(复合消息处理) ---> HeartbeatHandle(心跳消息处理,接收心跳并发送心跳响应) ---> AllChannelHandler (业务线程转化处理器)---> DecodeHandler (业务解码处理器)---> HeaderExchangeHandler ---> DubboProtocol#requestHandler(new ExchangeHandlerAdapter())

      而在构建 NettyServerHandler 得时候将 this 传了进去。this 即 NettyServer 。NettyServer是  AbstractPeer 得子类。所以 handler.received 此时会调用AbsstractPeer.received方法,这个方法用来判断服务端是否关闭了,如果关闭就直接返回,否则,通过handler处理链进行层层调用。

      我们直接进入 HeaderExchangeHandler.received,交互层请求响应处理,有三种处理方式

    1. handlerRequest,双向请求
    2. handler.received 单向请求
    3. handleResponse 响应消息
    public void received(Channel channel, Object message) throws RemotingException {
            final ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);
            if (message instanceof Request) {
                // handle request. 处理请求
                Request request = (Request) message;
                if (request.isEvent()) {
                    handlerEvent(channel, request);
                } else {
                    if (request.isTwoWay()) {
                        handleRequest(exchangeChannel, request);
                    } else {
                        handler.received(exchangeChannel, request.getData());
                    }
                }
            } else if (message instanceof Response) {
                handleResponse(channel, (Response) message);
            } else if (message instanceof String) {
                if (isClientSide(channel)) {
                    Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());
                    logger.error(e.getMessage(), e);
                } else {
                    String echo = handler.telnet(channel, (String) message);
                    if (echo != null && echo.length() > 0) {
                        channel.send(echo);
                    }
                }
            } else {
                handler.received(exchangeChannel, message);
            }
    }
    

      接着调用handleRequest方法。这个方法中,构建返回的对象Response,并且最终会通过异步的方式来把msg传递到invoker中进行调用 handler.reply .这里得最后一层就是调用 DubboProtocol 类中得  requestHandler 属性构造得 ExchangeHandlerAdapter 内部类的 reply 方法:

    private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
    
            @Override
            public CompletableFuture<Object> reply(ExchangeChannel channel, Object message) throws RemotingException {
            //如果消息类型不是invocation,则抛出异常表示无法识别
                if (!(message instanceof Invocation)) {
                    throw new RemotingException(channel, "Unsupported request: "
                            + (message == null ? null : (message.getClass().getName() + ": " + message))
                            + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress());
                }
            //获得请求参数
                Invocation inv = (Invocation) message;
              // 获取 invoker 领域对象,这个对象是在发布服务的时候构建,然后封装成 exporter 存在map里面的。
                Invoker<?> invoker = getInvoker(channel, inv);
                // need to consider backward-compatibility if it's a callback
                if (Boolean.TRUE.toString().equals(inv.getObjectAttachments().get(IS_CALLBACK_SERVICE_INVOKE))) {
                    String methodsStr = invoker.getUrl().getParameters().get("methods");
                    boolean hasMethod = false;
                    if (methodsStr == null || !methodsStr.contains(",")) {
                        hasMethod = inv.getMethodName().equals(methodsStr);
                    } else {
                        String[] methods = methodsStr.split(",");
                        for (String method : methods) {
                            if (inv.getMethodName().equals(method)) {
                                hasMethod = true;
                                break;
                            }
                        }
                    }
                    if (!hasMethod) {
                        logger.warn(new IllegalStateException("The methodName " + inv.getMethodName()
                                + " not found in callback service interface ,invoke will be ignored."
                                + " please update the api interface. url is:"
                                + invoker.getUrl()) + " ,invocation is :" + inv);
                        return null;
                    }
                }
                RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
                Result result = invoker.invoke(inv); // 发起对应调用
                return result.thenApply(Function.identity());
            }
      //......省略代码
    };
    

      invoker.invoke,发起本地服务调用,但是此时调用之前,invoke并不是一个直接调用的对象,而是包装过的。在 ServiceConfig#doExportUrlsFor1Protocol 构建包装。最后的调用链路如下:

      RegistryProtocol.InvokerDelegate.invoke ---> DelegateProviderMetaDataInvoker.invoke ---> AbstractProxyInvoker.invoke ---> AbstractProxyInvoker(JavassistProxyFactory#getInvoker)

      InvokerDelegate 未实现父类 InvokerWrapper invoke方法。进入到InvokerWrapper.invoke方法,这个是一个Invoker包装类,包装了URL地址信息和真正的Invoker代理对象。

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        return invoker.invoke(invocation);
    }
    

      DelegateProviderMetaDataInvoker:这里是一个委派类,它提供了服务提供者的元数序信息。

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        return invoker.invoke(invocation);
    }
    

      AbstractProxyInvoker:接着进入到AbstractProxyInvoker的invoke方法,在这个方法中,我们可以看到它会调用子类的doInvoke方法,获得返回结果。其中proxy,表示服务端的对象实例,这个实例很显然是在构建动态代理Invoker对象时保存进来的。

    public Result invoke(Invocation invocation) throws RpcException {
            try {
                Object value = doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
                CompletableFuture<Object> future = wrapWithFuture(value);
                CompletableFuture<AppResponse> appResponseFuture = future.handle((obj, t) -> {
                    AppResponse result = new AppResponse();
                    if (t != null) {
                        if (t instanceof CompletionException) {
                            result.setException(t.getCause());
                        } else {
                            result.setException(t);
                        }
                    } else {
                        result.setValue(obj);
                    }
                    return result;
                });
                return new AsyncRpcResult(appResponseFuture, invocation);
            } catch (InvocationTargetException e) {
                if (RpcContext.getContext().isAsyncStarted() && !RpcContext.getContext().stopAsync()) {
                    logger.error("Provider async started, but got an exception from the original method, cannot write the exception back to consumer because an async result may have returned the new thread.", e);
                }
                return AsyncRpcResult.newDefaultAsyncResult(null, e.getTargetException(), invocation);
            } catch (Throwable e) {
                throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
            }
    }
    

      最后进入到具体的子类,也就是在服务的发布的时候通过 构建的

    @Override
    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
            // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
            final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
            return new AbstractProxyInvoker<T>(proxy, type, url) {
                @Override
                protected Object doInvoke(T proxy, String methodName,
                                          Class<?>[] parameterTypes,
                                          Object[] arguments) throws Throwable {
                    return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
                }
            };
    }
    

      然后发起本地的调用即可,将结果返回 。至此,服务消费的处理流程就分析完了。最后附上这个流程的流程图。

    Dubbo中失败重试的设计:

       在Dubbo中,有很多地方涉及到服务失败重试,比如服务注册失败时,会调用一个方法把失败的请求保存起来进行重试。clusterInvoker失败重试

      注册中心失败重试 FailbackRegistry#addFailedRegistered

    private final HashedWheelTimer retryTimer;
    private void addFailedRegistered(URL url) {
            FailedRegisteredTask oldOne = failedRegistered.get(url);
            if (oldOne != null) {
                return;
            }
            FailedRegisteredTask newTask = new FailedRegisteredTask(url, this);
            oldOne = failedRegistered.putIfAbsent(url, newTask);
            if (oldOne == null) {
                // never has a retry task. then start a new task for retry.
                retryTimer.newTimeout(newTask, retryPeriod, TimeUnit.MILLISECONDS);
            }
    }
    

      clusterInvoker(FailbackClusterInvoker)失败重试

    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());
            }
    }
    

      不难发现他们都是基于HashedWhelloTimer这个类来实现的。

    时间轮:

      时间轮这个技术其实出来很久了,在kafka、zookeeper等技术中都有时间轮使用的方式。什么是时间轮呢?

      简单来说: 时间轮是一种高效利用线程资源进行批量化调度的一种调度模型。把大批量的调度任务全部绑定到同一个调度器上,使用这一个调度器来进行所有任务的管理、触发、以及运行。所以时间轮的模型能够高效管理各种延时任务、周期任务、通知任务。 在工作中遇到类似的功能,可以采用时间轮机制。

      时间轮,从图片上来看,就和手表的表圈是一样,所以称为时间轮,是因为它是以时间作为刻度组成的一个环形队列,这个环形队列采用数组来实现,数组的每个元素称为槽,每个槽可以放一个定时任务列表,叫HashedWheelBucket,它是一个双向链表,量表的每一项表示一个定时任务项(HashedWhellTimeout),其中封装了真正的定时任务TimerTask。时间轮是由多个时间格组成,下图中有8个时间格,每个时间格代表当前时间轮的基本时间跨度(tickDuration),其中时间轮的时间格的个数是固定的。

      在下图中,有8个时间格(槽),假设每个时间格的单位为1s,那么整个时间轮走完一圈需要8s钟。每秒钟指针会沿着顺时针方向移动一格,这个单位可以设置,比如以秒为单位,可以以一小时为单位,这个单位可以代表时间精度。

      通过指针移动,来获得每个时间格中的任务列表,然后遍历这一个时间格中的双向链表来执行任务,以此循环。

    时间轮的运行逻辑:

      首先,时间轮在启动的时候,会记录一下当前启动时间,并赋值给一个叫startTime的变量。然后当需要添加任务的时候,首先会计算延迟时间(deadline),比如一个任务的延迟时间是24ms,那么在添加任务时,会将当前时间(currentTime)+24ms-时间轮的启动时间(startTime),然后把这个任务封装成HashedWheelTimeout加入到链表中。

      那么这个任务应该放在哪个时间格里面呢? 通过  deadline%wheel.length 计算.时间轮在运行的时候,会从任务队列中取出10W个进行遍历处理。

    Dubbo中的时间轮实现类是:HashedWheelTimer:

      在FailbackClusterInvoker这个类中,构建了一个HashedWheelTimer,然后增加了一个任务RetryTimerTask到时间轮中。基于这段代码, 我们去分析一下HashedWheelTimer的实现。 FailbackClusterInvoker#addFailed 主要做了两件事情:

    1. 如果时间轮等于 null,则初始化时间轮
    2. 创建重试任务,启动时间轮。

      HashedWheelTimer的构造:

    1. 调用createWheel创建一个时间轮,时间轮数组一定是2的幂次方,比如传入的
    2. ticksPerWheel=6,那么初始化的wheel长度一定是8,这样是便于时间格的计算。tickDuration,表示时间轮的跨度,代表每个时间格的时间精度,以纳秒的方式来表现。
    3. 把工作线程Worker封装成WorkerThread,从名字可以知道,它就是最终那个负责干活的线程。
    public HashedWheelTimer(
                ThreadFactory threadFactory,
                long tickDuration, TimeUnit unit, int ticksPerWheel,
                long maxPendingTimeouts) {
         // 省略判断逻辑代码
            // Normalize ticksPerWheel to power of two and initialize the wheel.
         // 创建时间轮基本的数据结构,一个数组。长度为不小于ticksPerWheel的最小2的n次方
            wheel = createWheel(ticksPerWheel);
         // 这是一个标示符,用来快速计算任务应该呆的格子。
         // 我们知道,给定一个deadline的定时任务,其应该呆的格子=deadline%wheel.length.但是%操作是个相对耗时的操作,所以使用一种变通的位运算代替:
         // 因为一圈的长度为2的n次方,mask = 2^n-1后低位将全部是1,然后deadline&mast == deadline%wheel.length
         // java中的HashMap在进行hash之后,进行index的hash寻址寻址的算法也是和这个一样的
            mask = wheel.length - 1;
    
            // Convert tickDuration to nanos.
         //时间轮的基本时间跨度,(tickDuration传入是1的话,这里会转换成1000000)
            this.tickDuration = unit.toNanos(tickDuration);
    
            // Prevent overflow.
         // 校验是否存在溢出。即指针转动的时间间隔不能太长而导致tickDuration*wheel.length>Long.MAX_VALUE
            if (this.tickDuration >= Long.MAX_VALUE / wheel.length) {
                throw new IllegalArgumentException(String.format(
                        "tickDuration: %d (expected: 0 < tickDuration in nanos < %d",
                        tickDuration, Long.MAX_VALUE / wheel.length));
            }
         //把worker包装成thread
            workerThread = threadFactory.newThread(worker);
    
            this.maxPendingTimeouts = maxPendingTimeouts;
         //如果HashedWheelTimer实例太多,那么就会打印一个error日志
            if (INSTANCE_COUNTER.incrementAndGet() > INSTANCE_COUNT_LIMIT &&
                    WARNED_TOO_MANY_INSTANCES.compareAndSet(false, true)) {
                reportTooManyInstances();
            }
    }
    

      然后来看看时间轮的创建 createWheel

    1. 对传入的ticksPerWheel进行整形
    2. 初始化固定长度的HashedWheelBucket
    private static HashedWheelBucket[] createWheel(int ticksPerWheel) {
            if (ticksPerWheel <= 0) {
                throw new IllegalArgumentException(
                        "ticksPerWheel must be greater than 0: " + ticksPerWheel);
            }
            if (ticksPerWheel > 1073741824) {
                throw new IllegalArgumentException(
                        "ticksPerWheel may not be greater than 2^30: " + ticksPerWheel);
            }
            //对传入的时间轮大小进行整形,整形成2的幂次方
            ticksPerWheel = normalizeTicksPerWheel(ticksPerWheel);
            //初始化一个固定长度的Bucket数组
            HashedWheelBucket[] wheel = new HashedWheelBucket[ticksPerWheel];
            for (int i = 0; i < wheel.length; i++) {
                wheel[i] = new HashedWheelBucket();
            }
            return wheel;
    }
    
    private static int normalizeTicksPerWheel(int ticksPerWheel) {
            int normalizedTicksPerWheel = ticksPerWheel - 1;
            normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 1;
            normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 2;
            normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 4;
            normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 8;
            normalizedTicksPerWheel |= normalizedTicksPerWheel >>> 16;
            return normalizedTicksPerWheel + 1;
    }
    

      完成时间轮的初始化之后,并没有去启动时间轮,继续看FailbackClusterInvoker中的代码。构建了一个RetryTimerTask,也就是一个重试的定时任务,接着把这个任务通过newTimeout加入到时间轮中,其中

    • retryTimerTask,表示具体的重试任务
    • RETRY_FAILED_PERIOD , 表示重试间隔时间,默认为5s

      调用newTimeout方法,把任务添加进来。

    public Timeout newTimeout(TimerTask task, long delay, TimeUnit unit) {
            if (task == null) {
                throw new NullPointerException("task");
            }
            if (unit == null) {
                throw new NullPointerException("unit");
            }
            //统计任务个数
            long pendingTimeoutsCount = pendingTimeouts.incrementAndGet();
            //判断最大任务数量是否超过限制
            if (maxPendingTimeouts > 0 && pendingTimeoutsCount > maxPendingTimeouts) {
                pendingTimeouts.decrementAndGet();
                throw new RejectedExecutionException("Number of pending timeouts ("
                        + pendingTimeoutsCount + ") is greater than or equal to maximum allowed pending "
                        + "timeouts (" + maxPendingTimeouts + ")");
            }
            //如果时间轮没有启动,则通过start方法进行启动
            start();
    
            // Add the timeout to the timeout queue which will be processed on the next tick.
            // During processing all the queued HashedWheelTimeouts will be added to the correct HashedWheelBucket.
            //计算任务的延迟时间,通过当前的时间+当前任务执行的延迟时间-时间轮启动的时间。
            long deadline = System.nanoTime() + unit.toNanos(delay) - startTime;
            //在delay为正数的情况下,deadline是不可能为负数
            //如果为负数,那么说明超过了long的最大值
            // Guard against overflow.
            if (delay > 0 && deadline < 0) {
                deadline = Long.MAX_VALUE;
            }
            //创建一个Timeout任务,理论上来说,这个任务应该要加入到时间轮的时间格子中,但是这里并不是先添加到时间格,
            //而是先加入到一个阻塞队列,然后等到时间轮执行到下一个格子时,再从队列中取出最多100000个任务添加到指定的时间格(槽)中。
            HashedWheelTimeout timeout = new HashedWheelTimeout(this, task, deadline);
            timeouts.add(timeout);
            return timeout;
    }        
    

      任务添加到阻塞队列之后,我们再来看启动方法,start方法会根据当前的workerState状态来启动时间轮。并且用了startTimeInitialized来控制线程的运行,如果workerThread没有启动起来,那么newTimeout方法会一直阻塞在运行start方法中。如果不阻塞,newTimeout方法会获取不到startTime。

    public void start() {
            //workerState一开始的时候是0(WORKER_STATE_INIT),然后才会设置为1(WORKER_STATE_STARTED)
            switch (WORKER_STATE_UPDATER.get(this)) {
                case WORKER_STATE_INIT:
                    if (WORKER_STATE_UPDATER.compareAndSet(this, WORKER_STATE_INIT, WORKER_STATE_STARTED)) {
                        workerThread.start();
                    }
                    break;
                case WORKER_STATE_STARTED:
                    break;
                case WORKER_STATE_SHUTDOWN:
                    throw new IllegalStateException("cannot be started once stopped");
                default:
                    throw new Error("Invalid WorkerState");
            }
            // 等待worker线程初始化时间轮的启动时间
            // Wait until the startTime is initialized by the worker.
            while (startTime == 0) {
                try {
                    //这里使用countDownLauch来确保调度的线程已经被启动
                    startTimeInitialized.await();
                } catch (InterruptedException ignore) {
                    // Ignore - it will be ready very soon.
                }
            }
    }
    

      启动时间轮:调用start()方法, 会调用 workerThread.start(); 来启动一个工作线程,这个工作线程是在构造方法中初始化的,包装的是一个Worker内部线程类。所以直接进入到Worker这个类的run方法,了解下它的设计逻辑

    public void run() {
                // Initialize the startTime.
                // 初始化startTime,表示时间轮的启动时间
                startTime = System.nanoTime();
                // 唤醒被阻塞的start()方法。
                if (startTime == 0) {
                    // We use 0 as an indicator for the uninitialized value here, so make sure it's not 0 when initialized.
                    startTime = 1;
                }
    
                // Notify the other threads waiting for the initialization at start().
                startTimeInitialized.countDown();
    
                do {
                    //返回每tick一次的时间间隔
                    final long deadline = waitForNextTick();
                    if (deadline > 0) {
                        //计算时间轮的槽位
                        int idx = (int) (tick & mask);
                        //移除掉CancelledTask
                        processCancelledTasks();
                        //得到当前指针位置的时间槽
                        HashedWheelBucket bucket =
                                wheel[idx];
                        //将newTimeout()方法中加入到待处理定时任务队列中的任务加入到指定的格子中
                        transferTimeoutsToBuckets();
                        //运行目前指针指向的槽中的bucket链表中的任务
                        bucket.expireTimeouts(deadline);
                        tick++;
                    }
                } while (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_STARTED);
    
                // Fill the unprocessedTimeouts so we can return them from stop() method.
                //如果Worker_State一只是started状态,就一直循环
                for (HashedWheelBucket bucket : wheel) {
                    //清除时间轮中不需要处理的任务
                    bucket.clearTimeouts(unprocessedTimeouts);
                }
                for (; ; ) {
                    //遍历任务队列,发现如果有任务被取消,则添加到unprocessedTimeouts,也就是不需要处理的队列中。
                    HashedWheelTimeout timeout = timeouts.poll();
                    if (timeout == null) {
                        break;
                    }
                    if (!timeout.isCancelled()) {
                        unprocessedTimeouts.add(timeout);
                    }
                }//处理被取消的任务.
                processCancelledTasks();
    }
    

      时间轮指针跳动:这个方法的主要作用就是返回下一个指针指向的时间间隔,然后进行sleep操作。可以想象一下,一个钟表上秒与秒之间是有时间间隔的,那么waitForNextTick就是根据当前时间计算出跳动到下个时间的时间间隔,然后进行sleep,然后再返回当前时间距离时间轮启动时间的时间间隔。

      说得再直白一点:,假设当前的tickDuration的间隔是1s,tick默认=0, 此时第一次进来,得到的deadline=1,也就是下一次跳动的时间间隔是1s。

    private long waitForNextTick() {
                //tick表示总的tick数
                //tickDuration表示每个时间格的跨度,所以deadline返回的是下一次时间轮指针跳动的时间
                long deadline = tickDuration * (tick + 1);
    
                for (; ; ) {
                    //计算当前时间距离启动时间的时间间隔
                    final long currentTime = System.nanoTime() - startTime;
                    //通过下一次指针跳动的延迟时间距离当前时间的差额,这个作为sleep时间使用。
                    // 其实线程是以睡眠一定的时间再来执行下一个ticket的任务的
                    long sleepTimeMs = (deadline - currentTime + 999999) / 1000000;
                    //sleepTimeMs小于零表示走到了下一个时间槽位置
                    if (sleepTimeMs <= 0) {
                        if (currentTime == Long.MIN_VALUE) {
                            return -Long.MAX_VALUE;
                        } else {
                            return currentTime;
                        }
                    }
                    if (isWindows()) {
                        sleepTimeMs = sleepTimeMs / 10 * 10;
                    }
                    //进入到这里进行sleep,表示当前时间距离下一次tick时间还有一段距离,需要sleep。
                    try {
                        Thread.sleep(sleepTimeMs);
                    } catch (InterruptedException ignored) {
                        if (WORKER_STATE_UPDATER.get(HashedWheelTimer.this) == WORKER_STATE_SHUTDOWN) {
                            return Long.MIN_VALUE;
                        }
                    }
                }
    }
    

      transferTimeoutsToBuckets:转移任务到时间轮中,前面我们讲过,任务添加进来时,是先放入到阻塞队列。

      而在现在这个方法中,就是把阻塞队列中的数据转移到时间轮的指定位置。在这个转移方法中,写死了一个循环,每次都只转移10万个任务。然后根据HashedWheelTimeout的deadline延迟时间计算出时间轮需要运行多少次才能运行当前的任务,如果当前的任务延迟时间大于时间轮跑一圈所需要的时间,那么就计算需要跑几圈才能到这个任务运行。最后计算出该任务在时间轮中的槽位,添加到时间轮的链表中。

    private void transferTimeoutsToBuckets() {
                // transfer only max. 100000 timeouts per tick to prevent a thread to stale the workerThread when it just
                // adds new timeouts in a loop.
                // 循环100000次,也就是每次转移10w个任务
                for (int i = 0; i < 100000; i++) {
                    //从阻塞队列中获得具体的任务
                    HashedWheelTimeout timeout = timeouts.poll();
                    if (timeout == null) {
                        // all processed
                        break;
                    }
                    if (timeout.state() == HashedWheelTimeout.ST_CANCELLED) {
                        // Was cancelled in the meantime.
                        continue;
                    }
                    //计算tick次数,deadline表示当前任务的延迟时间,tickDuration表示时间槽的间隔,两者相除就可以计算当前任务需要tick几次才能被执行
                    long calculated = timeout.deadline / tickDuration;
                    // 计算剩余的轮数, 只有 timer 走够轮数, 并且到达了 task 所在的 slot, task 才会过期.(被执行)
                    timeout.remainingRounds = (calculated - tick) / wheel.length;
    
                    // Ensure we don't schedule for past.
                    //如果任务在timeouts队列里面放久了, 以至于已经过了执行时间, 这个时候就使用当前tick,也就是放到当前bucket, 此方法调用完后就会被执行
                    final long ticks = Math.max(calculated, tick);
                    // 算出任务应该插入的 wheel 的 slot, stopIndex = tick 次数 & mask, mask =wheel.length - 1
                    int stopIndex = (int) (ticks & mask);
                    //把timeout任务插入到指定的bucket链中
                    HashedWheelBucket bucket = wheel[stopIndex];
                    bucket.addTimeout(timeout);
                }
    }
    

      运行时间轮中的任务:当指针跳动到某一个时间槽中时,会就触发这个槽中的任务的执行。该功能是通过expireTimeouts来实现这个方法的主要作用是: 过期并执行格子中到期的任务。也就是当tick进入到指定格子时,worker线程会调用这个方法

      HashedWheelBucket是一个链表,所以我们需要从head节点往下进行遍历。如果链表没有遍历到链表尾部那么就继续往下遍历。获取的timeout节点,如果剩余轮数remainingRounds大于0,那么就说明要到下一圈才能运行,所以将剩余轮数减一

      如果当前剩余轮数小于等于零了,那么就将当前节点从bucket链表中移除,并判断一下当前的时间是否大于timeout的延迟时间,如果是则调用timeout的expire执行任务。

    void expireTimeouts(long deadline) {
                HashedWheelTimeout timeout = head;
    
                // process all timeouts
                // 遍历当前时间槽中的所有任务
                while (timeout != null) {
                    HashedWheelTimeout next = timeout.next;
                    //如果当前任务要被执行,那么remainingRounds应该小于或者等于0
                    if (timeout.remainingRounds <= 0) {
                        //从bucket链表中移除当前timeout,并返回链表中下一个timeout
                        next = remove(timeout);
                        //如果timeout的时间小于当前的时间,那么就调用expire执行task
                        if (timeout.deadline <= deadline) {
                            timeout.expire();
                        } else {
                            //不可能发生的情况,就是说round已经为0了,deadline却>当前槽的deadline
                            // The timeout was placed into a wrong slot. This should never happen.
                            throw new IllegalStateException(String.format(
                                    "timeout.deadline (%d) > deadline (%d)", timeout.deadline, deadline));
                        }
                    } else if (timeout.isCancelled()) {
                        next = remove(timeout);
                    } else {
                        //因为当前的槽位已经过了,说明已经走了一圈了,把轮数减一
                        timeout.remainingRounds--;
                    }
                    //把指针放置到下一个timeout
                    timeout = next;
                }
    }
    

      这就是整个时间轮的流程了。

    展开全文
  • as使用AsyncHttpClient总是请求失败,没有找到原因,确保服务端没有问题,但总是请求失败,希望大神解答一下。 ![图片说明](https://img-ask.csdn.net/upload/201707/14/1500024862_139868.png)
  • 今天服务端同事,让我发一个post 请求。然后呢,一直有问题。告诉我签名失败。 后来换了其他的在线模拟post,都是可以的。 后来找到原因了, post 请求,必须要有Content-Type 和 Content-Length。 并且Content-Type...

    今天服务端同事,让我发一个post 请求。然后呢,一直有问题。告诉我签名失败。

    后来换了其他的在线模拟post,都是可以的。

    后来找到原因了,

    post 请求,必须要有Content-Type 和 Content-Length。

    并且Content-Type必须要是application/x-www-form-urlencoded;并且你的所有的post 里面的value 都必须是urlencode过的。

    因为Fiddler 默认不会给你添加

    Content-Type: application/x-www-form-urlencoded
    

    导致你的post 会有问题。所有的post 应该都发不出去。

    那么怎么样设置Content-Length 呢?

    在这里插入图片描述

    在这里有这个选项,勾选之后,自动回加上该参数。

    那么,怎么保证自己的value 是 urlencode 之后的呢?

    Fildder 支持常见的编码转换,转换一下就可以了。

    在这里插入图片描述

    在这里插入图片描述

    大功告成,哎,以后使用Fiddler 还要自己添加头。

    转载于:https://www.cnblogs.com/caoxinyu/p/10568520.html

    展开全文
  • ReactNative 网络请求失败

    千次阅读 2020-04-08 13:29:38
    前提 http 请求可以正常访问,并返回结果 https请求无法到达, 但是...当网上所有的方案都尝试过了, 都没有解决问题的话, 并且保证服务端的证书配置没有问题, 考虑一下域名本身的问题, 是否包含了下划线? 例如 abc_ef....
  • 在Controller中新加了一个方法,客户端就不能请求接口了,当时建WEB API项目是用的VS默认设置,在服务端打断点一直没有进去,而APP端一直报服务端响应失败!奇怪的是连生成的Help API说明都没Controller说明。 ...
  • ![![图片说明](https://img-ask.csdn.net/upload/201704/01/1491021079_550869.png)图片说明](https://img-ask.csdn.net/upload/201704/01/1491021067_128779.png) 以上为服务端代码和运行截图!
  • 一个同事用 MUI 做了一个APP, 一切顺利的上线了. Android, IOS系统都没问题....服务端的程序没有收到请求 Nginx 只有 http method 是 OPTIONS 请求,但是没有对应的 POST 请求 这就非常的像跨域的问题, 但是已经打包...
  • Rpc 客户端请求失败:", err) return err } cancelFn() logger.Log().Debug(reply) c.close() return nil } </code></pre> 失败提示: <code>context deadline exceeded</code></p> 服务端...
  • Kafka服务端

    千次阅读 2021-03-31 14:47:33
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x3swD7s0-1617173249475)(./服务端设计.png)] 上图可以看出有如下几个关键线程和关键类: Acceptor线程:接受并配置新连接的线程。 每个...
  • 点击一下,就会AJAX实时请求服务端服务端处理完毕,返回结果。不管成功或失败,都显示提示。如下图所示。 一直以来,也没什么问题,因为一般都会修改成功。现在业务有变,修改Switchery状态时,服务端会判断...
  • 最近遇到一个bug很有意思,java客户端加密后传给服务端服务端是GO Listen请求后解密处理,但是服务端解密失败。排查过程如下:1.首先请求过程中有中间服务器转发,因此猜想可能是转发数据错误。由于发送数据为二...
  •  最近在修改电商平台的发布商品页面,发布商品时,前端与后台交互采用Ajax Post请求,就这么一个简单的画面,我遇到一个非常奇怪且困扰我很久的问题:  (1)商品发布失败(有时候能发布成功,有点灵异),Post...
  • 前端开发人员使用uni-app开发,无法把cookie 传递给后端, 导致请求接口失败。百度了一圈不知道怎么搞,最后终于在网上找到思路开始整改。 方式: 前端用问号传参方式传递sessionid 后端重写shiro获取sessionid方法 ...
  • 我准备在每一次HTTP请求头中加入自己定义的cookie,以便服务端进行验证。所以我通过自己去组装HTTP请求,cookie确实设置进去了,但由于项目中使用的网络通用组件库,我不得不使用这个(为了代码的统一性),我对其...
  • 但事实上,有些地方用HTTP比HTTPS更适合,而且把服务端升级到TLS 1.2也不是一时半会能够搞定的。幸好苹果还提供了配置,使得所有安全性更低的网络请求也能使用,解决方案就是在info.plist里面增加以下配置: <...
  • 所以11.168.218.23在14:50:14再次使用源端口55462发送SYN包请求建链时,11.168.192.31并未正常回应SYN+ACK,而是延续上一个会话的序号,回应了一个ACK报文,导致会话建立失败,具体交互如下图: ![图片说明]...
  • 深入Kafka服务端

    2021-03-09 21:50:32
    Kafka服务端设计介绍 协议设计 Kafka自定义了一组基于TCP的二进制协议,只要遵守这组协议的格式,就可以向Kafka发送消息,也可以从Kafka中拉取消息,或者做一些其他的事情,比如提交消费位移等。 协议格式设计: ...
  • 服务端请求消息发送给Servlet Servlet生成响应发送给服务器 服务器将响应发送给客户端 Filter 作用 过滤请求和响应 流程 1.请求进入Filter,执行相关操作 2.判断通行,进入Servlet,执行完毕,再返回给...
  • 常见服务端错误码

    2021-05-06 07:55:47
    5**:服务器执行一个完全有效请求失败 100——客户必须继续发出请求 101——客户要求服务器根据请求转换HTTP协议版本 200——交易成功 201——提示知道新文件的URL 202——接受和处理、但处理未完成 203——返回信息...
  • 会默认等待x秒,如果没有响应,则会重新发送数据包,当经过2MSL两倍的最长报文段寿命时间后,如果没有请求发送失败的消息,那么再断连。 MSL:最长报文段寿命,可理解为客户端将数据发送到服务端的这个时间段,也...
  • 硬件提供商的服务端提供了一系列的接口供调用,用户需要向服务端发送HTTP POST的请求,附带JSON参数,“Content-Type”为“application/json”,服务端接收请求后返回响应参数(成功失败信息等等,不是接口的返回值...
  • 来自https://www.cnblogs.com/Nouno/p/5719010.html   java开发接口利用http协议传输数据 这个接口主要用来登录,java服务器提供一个接口,移动...比如0是成功,1是失败。中间数据的传递都是通过http协议完成....
  • 浏览器在ajax异步请求诞生之前, 所有的路由、响应都是由服务端控制的 ajax之后开始出现异步交互, 提高了用户的体验 这里以提交表单, 注册为例 注册 表单具有默认的提交行为,默认是同步,提交表单会把...
  • 有时候我们在请求后端接口的时候,因为服务器长时间没有返回,会出现请求失败failed的情况,这种情况可能是我们请求的资源过多,服务器需要较长时间处理,例如传一个比较大的文件,服务端接受在返回需要一定的时间,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 678
精华内容 271
关键字:

服务端请求失败