精华内容
下载资源
问答
  • 问题:项目中对发包过程没有使用DPDK,而且此时的多个队列要同时往一个网卡上进行发包。大致问题就是这样,虽然如果使用DPDK会好很多,但这个算是历史遗留问题吧,只能就现在这种情况,尽可能来提高性能。 这里场景...

    2019/07/21
    问题:项目中对发包过程没有使用DPDK,而且此时的多个队列要同时往一个网卡上进行发包。大致问题就是这样,虽然如果使用DPDK会好很多,但这个算是历史遗留问题吧,只能就现在这种情况,尽可能来提高性能。

    这里场景还是没有说清楚,项目中使用的发包函数是libnet提供的,也就是说我如果是发包的话,必然是要走系统网络内核的。


    我来仔细梳理一下,针对这个问题的几个难点。
    (只考虑多个进程发包,不管别的)

    1. 利用什么工具来衡量发包的性能,除了在程序内部记录相关数据,还有什么别的方式吗
    2. 从哪些角度来发现发包的瓶颈。
      现在比较尴尬的地方,就是也不知道从什么地方来验证,也不知道从什么来地方来查看。
      目前为止能用的手段就是一个查看系统的中断数;我还是希望能知道,就是如果采用这种发包方式,怎么样才能尽可能保证性能最优。

    其实说到现在都没有说到重点,还是说自己对这部分的性能调优不太理解。
    既然找不到可说的内容,说明对这部分的内容的确很薄弱,就把能有的一些想法都列举出来,尝试这种这些名目中找到一些可以优化的内容。

    • 本次发包过程中使用的函数,libnet_write底层是用的sendto,这个函数默认是阻塞的。但是尝试了修改这部分代码,并没有什么效果,所以这也是很尴尬。(但要说明的情况是,当时是为了减缓丢包的数量进行的修改,修改后丢包情况并没有减少,所以说到底是不是呢,也不好说)
    • 多个线程同时发包(底层用的是同一个网卡),是不是存在抢系统调用的情况,但这种情况怎么来看
    • 多个线程同时发包,网卡可以实现多队列,是不是可以从这个地方进行优化

    上面的说法还是不够具体,来从函数调用的流程来说明下这个情况。

    1. 一个线程调用sendto这个系统调用;
    2. sendto调用一些底层的函数,包括什么我就不管了,但是他到了底层网卡驱动的时候,是什么情况
    3. 多个线程,同时发包,是否调用多个队列,那么多个队列是否是不同的终端?
    4. 如果队列和发包线程数量不太对称怎么办?

    如此说来,我应该重点关注的是,在网卡发包部分的这个系统调用是怎么分配的,是不是说他们有冲突。


    多线程多队列单网卡发送

    上午简单翻了一下网卡模块那本数,发现这里面的关系远比我想象中的要复杂的多,所以要从中找出关键的部分:系统调用的过程、网卡队列的分配。


    当前的运行状态是配置的单队列,但是是十多个线程同时利用这个网卡进行发包,在中断部分的统计数据上看只有一个核在工作一样,所以这里的数据又说明了什么信息。

    这样看来,我需要进一步了解的信息,包括发包过程是利用了什么过程,按理说中断过程应该只为收包服务的。

    展开全文
  • 本公司生产有6台linux机器,每次发版时,需要前端伙伴把前端项目放到CDN上,然后后端伙伴把index.html同时发布到6台机器上。(index.html是前端项目编译完生成的一文件,有压缩,当然每次发版都不同) 然后用户打开...

    前言

    本公司生产有6台linux机器,每次发版时,需要前端伙伴把前端项目放到CDN上,然后后端伙伴把index.html同时发布到6台机器上。(index.html是前端项目编译完生成的一个文件,有压缩,当然每次发版都不同)

    然后用户打开APP,请求走的路线就是:堡垒机->F5(负载均衡硬件设备)->随机一台linux服务器(如果有需要,继续走其它服务器)->CDN,然后获取到资源后返回给用户。

    本文记录下后端发布index.html的步骤。(如果发布jar项目,同理,可供参考)

     

    生产环境下多linux系统同时发布的步骤

    1.使用软件MobaXterm_Personal_8.6软件,登录堡垒机,并打开6个堡垒机窗口(linux界面的)。

    2.通过堡垒机窗口,输入不同的数字,切换至不同的分组,如:窗口1切换到linux生产机器1,窗口2切换到2,以此类推。

    3.使用任意一个窗口,例如窗口1,打开NAS共享磁盘的路径,将本地的index.html文件拖进去;这个共享磁盘可供6个linux机器访问,并且路径都是一致的。

    4.点击MobaXterm_Personal软件中的MultiExec按钮,就可以同时在6个linux窗口中输入命令了。

    5.输入命令,将index.html复制到指定位置,例如:cp /share/index.html /nginx/index.html

    意思是将index.html从共享磁盘中复制到nginx文件夹下;现在是同时在6台linux机器上输入命令的状态,这6台机器都能访问共享磁盘(/share),然后要复制到的位置都是/nginx。

    6.完成,nginx无需重启。(如果是cp的jar项目,则执行启动命令即可。)

     

    注意事项

    1.注意检查6台linux的ip是否正确,防止人为登陆错误机器,导致后续发版错误

    2.注意备份之前的文件(index.html或jar项目等),使用cp命令备份一下,防止需要回退时无法回退。(回退的原因除了发版错误,还有领导突然不想上线的情况)

     

    展开全文
  • 前几天遇到一问题,领导让我改几小问题,然后立马上线,但是我已经做了其他需求但是还没完成,也不能发布到线上,这时怎么样才能做到,自己的修改能解决领导的任务,同时又自己修改的其他需求不影响到即将要发的...

          前几天遇到一个问题,领导让我改几个小问题,然后立马上线,但是我已经做了其他需求但是还没完成,也不能发布到线上,这时怎么样才能做到,自己的修改能解决领导的任务,同时又自己修改的其他需求不影响到即将要发的包,我之前总是提交需要发包的内容,然后让同事去跟新再打包发布,这样只能找没有修改过自己版本的同事做,如果这个同事也修改了新东西也是比较麻烦的事。

          今天突然想到一个比较好的解决方案,就是自己本地再拉取一下项目,然后把打包需要用到的东西从之前的地方copy过去(例如:依赖,node_modules等),再把之前的修改部分提交,再跟新打包就完美解决了这个问题




    展开全文
  • 2.写这博客的目的是为了提高下自身的知识网络,还有就是在实际项目中使用到的,同时也希望能帮助大家来更好的理解知识点,平时积累下,潜移默化就形成了强大的知识网络了,废话不说了,下面就让我们共同来学习...

    一、背景

    1.前几个博客我们介绍了关于多线程的基础知识以及它们的实际例子,今天我们来说说java并发包下面的知识点,前几篇文章可以参考我之前的博客,同时如果那里有说的不对的,请欢迎留言指正。

    2.写这个博客的目的是为了提高下自身的知识网络,还有就是在实际项目中使用到的,同时也希望能帮助大家来更好的理解知识点,平时多积累下,潜移默化就形成了强大的知识网络了,废话不多说了,下面就让我们共同来学习下吧。

    二、同步容器类(并发包下的)

    1.Vector与ArrayList的区别

    1.1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。

    1.2.Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢

    注意: Vector线程安全、ArrayList线程是不安全的

    Vector源码类(Add()方法源码类)

    ArrayList源码类(Add()方法的源码)

    2.HashTable与HashMap

    2.1.HashMap不是线程安全的,HashMap是一个接口 是map接口的子接口,是将键映射到值的对象,其中键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap允许null key和null value,而hashtable不允许。

    2.2.HashTable是线程安全的一个Collection。

    2.3.HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。
    HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。
    HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。

    注意: HashTable线程安全,HashMap线程不安全

    3.ConcurrentHashMap

    3.1.因为加锁使HashTable安全,但是加锁只能让一个线程操作,所以影响效率,这时候就发明了ConcurrentHashMap,原理如下图:

    3.2.ConcurrentMap接口下有俩个重要的实现 :
    ConcurrentHashMap
    ConcurrentskipListMap (支持并发排序功能。弥补ConcurrentHas hMa p)
    ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个
    小的HashTable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并
    发进行。把一个整体分成了16个段(Segment.也就是最高支持16个线程的并发修改操作。
    这也是在重线程场景时减小锁的粒度从而降低锁竞争的一种方案。并且代码中大多共享变
    量使用volatile关键字声明,目的是第一时间获取修改的内容,性能非常好。

    4.CountDownLatch

    4.1.CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了(类似join()方法)。

    4.2.代码

    public class Test002 {
        public static void main(String[] args) throws InterruptedException {
            System.out.println("等待子线程执行完毕...");
            CountDownLatch countDownLatch = new CountDownLatch(2);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("子线程," + Thread.currentThread().getName() + "开始执行...");
                    countDownLatch.countDown();// 每次减去1
                    System.out.println("子线程," + Thread.currentThread().getName() + "结束执行...");
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("子线程," + Thread.currentThread().getName() + "开始执行...");
                    countDownLatch.countDown();
                    System.out.println("子线程," + Thread.currentThread().getName() + "结束执行...");
                }
            }).start();
            countDownLatch.await();// 调用当前方法主线程阻塞  countDown结果为0, 阻塞变为运行状态
            System.out.println("两个子线程执行完毕....");
            System.out.println("继续主线程执行..");
        }
    }

    4.3.结果

    三、并发队列

    1.在并发队列上JDK提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论哪种都继承自Queue。

    2.ConcurrentLinkedQueue:是一个适用于高并发场景下的队列,通过无锁的方式,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueue.它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素。

    2.1.ConcurrentLinkedQueue重要方法:add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中这俩个方法没有任何区别)
    poll() 和peek() 都是取头元素节点,区别在于前者会删除元素,后者不会。

    2.2.代码(可以自行看下结果)

    ConcurrentLinkedDeque q = new ConcurrentLinkedDeque();
    	q.offer("小明");
    	q.offer("小红");
    	q.offer("小张");
    	q.offer("小诺");
    	q.offer("小华");
    	//从头获取元素,删除该元素
    	System.out.println(q.poll());
    	//从头获取元素,不刪除该元素
    	System.out.println(q.peek());
    	//获取总长度
    	System.out.println(q.size());

    3.BlockingQueue:阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:

    3.1.在队列为空时,获取元素的线程会等待队列变为非空。
    当队列满时,存储元素的线程会等待队列可用。 

    阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

    3.2.BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种:

    1. 当队列满了的时候进行入队列操作

    2. 当队列空了的时候进行出队列操作

    因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空队列进行出队列操作时,它将会被阻塞,除非有另一个线程进行了入队列操作。

    3.在Java中,BlockingQueue的接口位于java.util.concurrent 包中(在Java5版本开始提供),由上面介绍的阻塞队列的特性可知,阻塞队列是线程安全的。

    在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景。

    认识BlockingQueue

    阻塞队列,顾名思义,首先它是一个队列,而一个队列在数据结构中所起的作用大致如下图所示:

    从上图我们可以很清楚看到,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;

    常用的队列主要有以下两种:(当然通过不同的实现方式,还可以延伸出很多不同类型的队列,DelayQueue就是其中的一种)

      先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性。

      后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件。

          多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。然而,在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。好在此时,强大的concurrent包横空出世了,而他也给我们带来了强大的BlockingQueue。(在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒)。

    4.下面两幅图演示了BlockingQueue的两个常见阻塞场景:

    4.1.ArrayBlockingQueue:是一个有边界的阻塞队列,它的内部实现是一个数组。有边界的意思是它的容量是有限的,我们必须在其初始化的时候指定它的容量大小,容量大小一旦指定就不可改变。ArrayBlockingQueue是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。下面是一个初始化和使用ArrayBlockingQueue的例子:

    ArrayBlockingQueue<String> arrays = new ArrayBlockingQueue<String>(3);
    	arrays.add("李四");
    	 arrays.add("张军");
    	arrays.add("张军");
    	// 添加阻塞队列
    	arrays.offer("张三", 1, TimeUnit.SECONDS);

    4.2.LinkedBlockingQueue阻塞队列大小的配置是可选的,如果我们初始化时指定一个大小,它就是有边界的,如果不指定,它就是无边界的。说是无边界,其实是采用了默认大小为Integer.MAX_VALUE的容量 。它的内部实现是一个链表。

    和ArrayBlockingQueue一样,LinkedBlockingQueue 也是以先进先出的方式存储数据,最新插入的对象是尾部,最新移出的对象是头部。下面是一个初始化和使LinkedBlockingQueue的例子:

    LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue(3);
    linkedBlockingQueue.add("张三");
    linkedBlockingQueue.add("李四");
    linkedBlockingQueue.add("李四");
    System.out.println(linkedBlockingQueue.size());

    4.3.PriorityBlockingQueue是一个没有边界的队列,它的排序规则和 java.util.PriorityQueue一样。需要注意PriorityBlockingQueue中允许插入null对象。所有插入PriorityBlockingQueue的对象必须实现 java.lang.Comparable接口,队列优先级的排序规则就是按照我们对这个接口的实现来定义的。另外,我们可以从PriorityBlockingQueue获得一个迭代器Iterator,但这个迭代器并不保证按照优先级顺。

    4.4.代码(使用BlockingQueue来模拟生产者与消费者

    class ProducerThread extends Thread {
        private BlockingQueue queue;
        private volatile boolean flag = true;
        private static AtomicInteger count = new AtomicInteger();
        ProducerThread(BlockingQueue blockingQueue) {
            this.queue = blockingQueue;
        }
        @Override
        public void run() {
            System.out.println("生产者线程启动...");
            try {
                while (flag) {
                    System.out.println("正在生产队列");
                    String data = count.incrementAndGet() + "";
                    // 添加队列
                    boolean offer = queue.offer(data);
                    if (offer) {
                        System.out.println("生产者添加队列" + data + "成功!");
                    } else {
                        System.out.println("生产者添加队列" + data + "失败!");
                    }
                    Thread.sleep(1000);
                }
            } catch (Exception e) {
                // TODO: handle exception
            } finally {
                System.out.println("生产者线程停止...");
            }
        }
        public void stopThread() {
            this.flag = false;
        }
    }
    class ConsumerThread extends Thread {
        private BlockingQueue queue;
        private volatile boolean flag = true;
    
        ConsumerThread(BlockingQueue blockingQueue) {
            this.queue = blockingQueue;
        }
        @Override
        public void run() {
            System.out.println("消费者线程启动....");
            try {
                while (flag) {
                    // 获取完毕,队列会删除掉
                    String data = (String) queue.poll(2, TimeUnit.SECONDS);
                    if (data != null) {
                        System.out.println("消费者获取 data:" + data + "成功...");
                    } else {
                        System.out.println("消费者获取 data:" + data + "失敗..");
                        this.flag = false;
                    }
                }
            } catch (Exception e) {
                // TODO: handle exception
            } finally {
                System.out.println("消费停止....");
            }
        }
    }
    public class Test006 {
        public static void main(String[] args) throws InterruptedException {
            BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10);
            ProducerThread p1 = new ProducerThread(queue);
            // ProducerThread p2 = new ProducerThread(queue);
            ConsumerThread c1 = new ConsumerThread(queue);
            p1.start();
            // p2.start();
            c1.start();
            // 执行10s
            Thread.sleep(10 * 1000);
            p1.stopThread();
            // p2.stopThread();
        }
    }

    4.4.结果

    生产者线程启动...
    正在生产队列
    消费者线程启动....
    生产者添加队列1成功!
    消费者获取 data:1成功...
    正在生产队列
    生产者添加队列2成功!
    消费者获取 data:2成功...
    正在生产队列
    生产者添加队列3成功!
    消费者获取 data:3成功...
    正在生产队列
    生产者添加队列4成功!
    消费者获取 data:4成功...
    正在生产队列
    生产者添加队列5成功!
    消费者获取 data:5成功...
    正在生产队列
    生产者添加队列6成功!
    消费者获取 data:6成功...
    正在生产队列
    生产者添加队列7成功!
    消费者获取 data:7成功...
    正在生产队列
    生产者添加队列8成功!
    消费者获取 data:8成功...
    正在生产队列
    生产者添加队列9成功!
    消费者获取 data:9成功...
    正在生产队列
    生产者添加队列10成功!
    消费者获取 data:10成功...
    生产者线程停止...
    消费者获取 data:null失敗..
    消费停止....

    四、结束

    Always  keep  the faith!!! 

    展开全文
  • java 并发包之Semaphore

    2017-04-04 22:37:32
    例如一奶茶店,同时只能为5个人提供服务,其余的人必须等待其前面五个人服务完毕后才能接受服务,如果所有的人同时一窝蜂的去请求服务那会导致很问题发生甚至奶茶铺崩溃。使用场景: 项目中某个核心接口需要...
  • 锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,如读写锁)。在以前,Java程序是靠synchronized来实现锁功能的,而在Java...
  • 锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,如读写锁)。在以前,Java程序是靠synchronized来实现锁功能的,而在Java...
  • 1 前言 锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,如读写锁)。在以前,Java程序是靠synchronized来实现锁功能的,...
  • 1 前言锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,如读写锁)。在以前,Java程序是靠synchronized来实现锁功能的,而...
  • 这样一来,每次不同城市发包需要修改的地方就会比较且分散,这样就很容易出现疏漏。 所以,我们将不同城市差异化配置单独使用一配置文件整合,代码中再通过读取配置文件来实现上述需求。 项目使用vue-cli@2.9.6...
  • tinker热修复gradle接入

    千次阅读 2017-11-10 16:57:23
    热修复,简单的来讲就是在不需要发包的情况下,修改你线上应用的bug,接入使用后对于我这种小白来说还是很神奇的,同时也考虑了一下,要不要接入我们的项目中,这样就不用因为一小BUG而去再次发包了,不过,就算要...
  • 如许一来,每次差别都市发包须要修正的处所就会比较且疏散,如许就很轻易涌现疏漏。所以,我们将差别都市差异化设置零丁运用一设置文件整合,代码中再经由过程读取设置文件来完成上述需求。项目运用vue-cli@...
  • 最近在处理项目中(android)客户端上传图片给服务端的问题,下面是我的一点拙见,查阅了不少资料,百变不离其宗,都是在客户端编辑一请求头,即是模仿浏览器发包,我们使用浏览器的开发工具->网络(现在大部分...
  • Java程序性能之四

    2007-05-16 19:19:00
    在开发服务器端程序更为重要,要了解的是Java应用服务器的基本框架,Java服务器大量采用线程技术,很多对象要被多个线程同时访问,采用synchronized等技术会影响性能,下边是使用并发包的两个小例子: 使用...
  • 公司拖欠工程款怎么要回?我们知道工程施工的时候是有一定的进度的,按照规定...1、很拖欠工程款的案件是由于违反分包和非法转包造成的,实际施工人在起诉发包人时应当同时将业主(包括开发商、政府、项目所有人)...
  • 「开源前哨」会定期在知乎专栏分享最新、有趣和热门的开源项目,每个项目都有详细的介绍和示例。传送门:https://www.zhihu.com/column/c_1317124962785062912 贡献者:唐尤华、qtvspa、艾凌风、Namco、Daetalus...
  • 第06节、使用线程池支持多个线程同时访问 资源+源码.rar 0009-蚂蚁课堂(每特学院)-2期-NIO编程基础 第01节、IO与NIO区别 第02节、Buffer的数据存取 第03节、make与rest用法 第04节、直接缓冲区与非缓冲区区别 第05...
  • 在其上已经发布了多个私有包。现需要同步老源上的私有包。 查了文档及资料,按照以下步骤可以 初步</em> 实现: 1. 将新源的服务端配置项<code>scopes设置为空数组[]</code>,因为对于当前源的...
  • 复旦nois教材01.rar

    2009-08-05 20:08:37
    1 第一章 绪论....................................................................................................................................1 1.1 概述................................................

空空如也

空空如也

1 2
收藏数 22
精华内容 8
关键字:

多个项目同时发包