kata_小规模流处理kata。 第1部分:线程池

kata

我再次为我的公司在GeeCON 2016上举办了编程竞赛。 这次分配需要设计并根据以下要求选择实施系统:

一个系统每秒发送大约一千个事件。 每个Event至少具有两个属性:

  • clientId –我们期望一个客户端每秒最多可以处理几个事件
  • UUID –全球唯一

消耗一个事件大约需要10毫秒。 设计此类流的使用者:

  1. 允许实时处理事件
  2. 与一个客户端有关的事件应按顺序进行处理,即,您不能并行处理同一clientId事件
  3. 如果10秒钟内出现重复的UUID ,请将其删除。 假设10秒钟后不会出现重复

这些要求中没有几个重要的细节:

  1. 1000个事件/秒和10毫秒消耗一个事件。 显然,我们至少需要10个并发使用者才能实时消费。
  2. 事件具有自然的聚合ID( clientId )。 在一秒钟内,我们可以为给定的客户端预期一些事件,并且不允许我们同时或无序处理它们。
  3. 我们必须以某种方式忽略重复的消息,最有可能的是通过记住最近10秒钟内的所有唯一ID。 这使大约一万个UUID得以临时保留。

在本文中,我将指导您完成一些正确的解决方案,并尝试一些失败的尝试。 您还将学习如何使用少量精确定位的指标来解决问题。

天真的顺序处理

让我们通过迭代解决这个问题。 首先,我们必须对API进行一些假设。 想象一下:

interface EventStream {void consume(EventConsumer consumer);}@FunctionalInterface
interface EventConsumer {Event consume(Event event);
}@Value
class Event {private final Instant created = Instant.now();private final int clientId;private final UUID uuid;}

典型的基于推送的API,类似于JMS。 一个重要的注意事项是EventConsumer正在阻止,这意味着直到EventConsumer消耗了前一个Event ,它才交付新的Event 。 这只是我所做的一个假设,并没有彻底改变需求。 这也是JMS中消息侦听器的工作方式。 天真的实现只附加了一个侦听器,该侦听器需要大约10毫秒才能完成:

class ClientProjection implements EventConsumer {@Overridepublic Event consume(Event event) {Sleeper.randSleep(10, 1);return event;}}

当然,在现实生活中,这个使用者会在数据库中存储一些东西,进行远程调用等。我在睡眠时间分配中添加了一些随机性,以使手动测试更加实际:

class Sleeper {private static final Random RANDOM = new Random();static void randSleep(double mean, double stdDev) {final double micros = 1_000 * (mean + RANDOM.nextGaussian() * stdDev);try {TimeUnit.MICROSECONDS.sleep((long) micros);} catch (InterruptedException e) {throw new RuntimeException(e);}}}//...EventStream es = new EventStream();  //some real implementation here
es.consume(new ClientProjection());

它可以编译并运行,但是为了确定未满足要求,我们必须插入少量指标。 最重要的指标是消息消耗的延迟,以消息创建到开始处理之间的时间来衡量。 我们将为此使用Dropwizard指标 :

class ClientProjection implements EventConsumer {private final ProjectionMetrics metrics;ClientProjection(ProjectionMetrics metrics) {this.metrics = metrics;}@Overridepublic Event consume(Event event) {metrics.latency(Duration.between(event.getCreated(), Instant.now()));Sleeper.randSleep(10, 1);return event;}}

提取ProjectionMetrics类以分离职责:

import com.codahale.metrics.Histogram;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Slf4jReporter;
import lombok.extern.slf4j.Slf4j;import java.time.Duration;
import java.util.concurrent.TimeUnit;@Slf4j
class ProjectionMetrics {private final Histogram latencyHist;ProjectionMetrics(MetricRegistry metricRegistry) {final Slf4jReporter reporter = Slf4jReporter.forRegistry(metricRegistry).outputTo(log).convertRatesTo(TimeUnit.SECONDS).convertDurationsTo(TimeUnit.MILLISECONDS).build();reporter.start(1, TimeUnit.SECONDS);latencyHist = metricRegistry.histogram(MetricRegistry.name(ProjectionMetrics.class, "latency"));}void latency(Duration duration) {latencyHist.update(duration.toMillis());}
}

现在,当您运行朴素的解决方案时,您会Swift发现中值延迟以及99.9%的百分数无限增长:

type=HISTOGRAM, [...] count=84,   min=0,  max=795,   mean=404.88540608274104, [...]median=414.0,   p75=602.0,   p95=753.0,   p98=783.0,   p99=795.0,   p999=795.0
type=HISTOGRAM, [...] count=182,  min=0,  max=1688,  mean=861.1706371990878,  [...]median=869.0,   p75=1285.0,  p95=1614.0,  p98=1659.0,  p99=1678.0,  p999=1688.0[...30 seconds later...]type=HISTOGRAM, [...] count=2947, min=14, max=26945, mean=15308.138585757424, [...]median=16150.0, p75=21915.0, p95=25978.0, p98=26556.0, p99=26670.0, p999=26945.0

30秒后,我们的应用程序平均会延迟15秒处理事件。 并非完全实时 。 显然,缺少并发是任何原因。 我们的ClientProjection事件使用者大约需要10毫秒才能完成,因此每秒可以处理多达100个事件,而我们还需要一个数量级。 我们必须以某种方式扩展ClientProjection 。 而且我们甚至都没有触及其他要求!

天真线程池

最明显的解决方案是从多个线程调用EventConsumer 。 最简单的方法是利用ExecutorService

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;class NaivePool implements EventConsumer, Closeable {private final EventConsumer downstream;private final ExecutorService executorService;NaivePool(int size, EventConsumer downstream) {this.executorService = Executors.newFixedThreadPool(size);this.downstream = downstream;}@Overridepublic Event consume(Event event) {executorService.submit(() -> downstream.consume(event));return event;}@Overridepublic void close() throws IOException {executorService.shutdown();}
}

我们在这里使用装饰器模式 。 实现EventConsumer的原始ClientProjection是正确的。 但是,我们将其与EventConsumer另一个实现并发包装。 这将使我们能够编写复杂的行为而无需更改ClientProjection本身。 这样的设计促进:

  • 松散耦合:各种EventConsumer彼此都不了解,可以自由组合
  • 单一责任:每个人都做一份工作,然后委派给下一个组成部分
  • 开放/封闭原则 :我们可以在不修改现有实现的情况下更改系统的行为。

打开/关闭原理通常通过注入策略和模板方法模式来实现。 在这里,它甚至更简单。 整体接线如下:

MetricRegistry metricRegistry =new MetricRegistry();
ProjectionMetrics metrics =new ProjectionMetrics(metricRegistry);
ClientProjection clientProjection =new ClientProjection(metrics);
NaivePool naivePool =new NaivePool(10, clientProjection);
EventStream es = new EventStream();
es.consume(naivePool);

我们精心设计的指标表明情况确实好得多:

type=HISToOGRAM, count=838, min=1, max=422, mean=38.80768197277468, [...]median=37.0, p75=45.0, p95=51.0, p98=52.0, p99=52.0, p999=422.0
type=HISTOGRAM, count=1814, min=1, max=281, mean=47.82642776789085, [...]median=51.0, p75=57.0, p95=61.0, p98=62.0, p99=63.0, p999=65.0[...30 seconds later...]type=HISTOGRAM, count=30564, min=5, max=3838, mean=364.2904915942238, [...]median=352.0, p75=496.0, p95=568.0, p98=574.0, p99=1251.0, p999=3531.0

然而,我们仍然看到延迟的规模越来越小,在30秒后,延迟达到了364毫秒。 它一直在增长,所以问题是系统的。 我们……需要……更多……指标。 请注意, NaivePool (您很快就会知道为什么它是naive )有正好有10个线程NaivePool 。 这应该足以处理数千个事件,每个事件需要10毫秒来处理。 实际上,我们需要一点额外的处理能力,以避免垃圾收集后或负载高峰时出现问题。 为了证明线程池实际上是我们的瓶颈,最好监视其内部队列。 这需要一些工作:

class NaivePool implements EventConsumer, Closeable {private final EventConsumer downstream;private final ExecutorService executorService;NaivePool(int size, EventConsumer downstream, MetricRegistry metricRegistry) {LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();String name = MetricRegistry.name(ProjectionMetrics.class, "queue");Gauge<Integer> gauge = queue::size;metricRegistry.register(name, gauge);this.executorService = new ThreadPoolExecutor(size, size, 0L, TimeUnit.MILLISECONDS, queue);this.downstream = downstream;}@Overridepublic Event consume(Event event) {executorService.submit(() -> downstream.consume(event));return event;}@Overridepublic void close() throws IOException {executorService.shutdown();}
}

这里的想法是手动创建ThreadPoolExecutor ,以提供自定义的LinkedBlockingQueue实例。 我们稍后可以使用该队列来监视其长度(请参阅: ExecutorService – 10个技巧 )。 Gauge将定期调用queue::size并将其报告给您需要的地方。 度量标准确认线程池大小确实是一个问题:

type=GAUGE, name=[...].queue, value=35
type=GAUGE, name=[...].queue, value=52[...30 seconds later...]type=GAUGE, name=[...].queue, value=601

容纳待处理任务的队列的大小不断增加,这会损害延迟。 线程池大小从10增加到20最终报告了不错的结果,并且没有停顿。 但是,我们仍然没有解决重复项,也没有针对同一clientId防止事件的同时修改。

晦涩的锁定

让我们从避免对同一clientId的事件进行并发处理开始。 如果两个事件接连发生,并且都与同一个clientId相关,那么NaivePool将选择它们并开始同时处理它们。 首先,我们至少通过为每个clientId设置一个Lock来发现这种情况:

@Slf4j
class FailOnConcurrentModification implements EventConsumer {private final ConcurrentMap<Integer, Lock> clientLocks = new ConcurrentHashMap<>();private final EventConsumer downstream;FailOnConcurrentModification(EventConsumer downstream) {this.downstream = downstream;}@Overridepublic Event consume(Event event) {Lock lock = findClientLock(event);if (lock.tryLock()) {try {downstream.consume(event);} finally {lock.unlock();}} else {log.error("Client {} already being modified by another thread", event.getClientId());}return event;}private Lock findClientLock(Event event) {return clientLocks.computeIfAbsent(event.getClientId(),clientId -> new ReentrantLock());}}

这肯定是朝错误的方向前进。 复杂程度不堪重负,但是运行此代码至少表明存在问题。 事件处理管道如下所示,一个装饰器包装了另一个装饰器:

ClientProjection clientProjection =new ClientProjection(new ProjectionMetrics(metricRegistry));
FailOnConcurrentModification failOnConcurrentModification =new FailOnConcurrentModification(clientProjection);
NaivePool naivePool =new NaivePool(10, failOnConcurrentModification, metricRegistry);
EventStream es = new EventStream();es.consume(naivePool);

偶尔会弹出错误消息,告诉我们其他一些线程已经在处理同一clientId事件。 对于每个clientId我们关联一个检查的Lock ,以便确定当前是否有另一个线程不在处理该客户端。 尽管丑陋,但实际上我们已经接近残酷的解决方案。 而不是因为另一个线程已经在处理某个事件而无法获取Lock时失败,让我们稍等一下,希望Lock可以被释放:

@Slf4j
class WaitOnConcurrentModification implements EventConsumer {private final ConcurrentMap<Integer, Lock> clientLocks = new ConcurrentHashMap<>();private final EventConsumer downstream;private final Timer lockWait;WaitOnConcurrentModification(EventConsumer downstream, MetricRegistry metricRegistry) {this.downstream = downstream;lockWait = metricRegistry.timer(MetricRegistry.name(WaitOnConcurrentModification.class, "lockWait"));}@Overridepublic Event consume(Event event) {try {final Lock lock = findClientLock(event);final Timer.Context time = lockWait.time();try {final boolean locked = lock.tryLock(1, TimeUnit.SECONDS);time.stop();if(locked) {downstream.consume(event);}} finally {lock.unlock();}} catch (InterruptedException e) {log.warn("Interrupted", e);}return event;}private Lock findClientLock(Event event) {return clientLocks.computeIfAbsent(event.getClientId(),clientId -> new ReentrantLock());}}

这个想法非常相似。 但是, tryLock()失败,它最多等待1秒,以希望释放给定客户端的Lock 。 如果两个事件很快相继发生,一个事件将获得一个Lock并继续执行,而另一个事件将阻止等待unlock()发生。

不仅这些代码确实令人费解,而且还可能以许多微妙的方式被破坏。 例如,如果几乎同一时间发生同一客户clientId两个事件,但是显然是第一个事件呢? 这两个事件将同时请求Lock ,并且我们无法保证哪个事件会首先获得不公平的Lock ,从而可能会乱序使用事件。 肯定有更好的办法…

专用线程

让我们退后一步,深吸一口气。 您如何确保事情不会同时发生? 好吧,只需使用一个线程! 事实上,这是我们一开始所做的,但吞吐量并不令人满意。 但是我们不关心不同的clientId的并发性,我们只需要确保具有相同clientId事件始终由同一线程处理即可!

也许您会想到创建从clientIdThread的映射? 好吧,这将过于简单化。 我们将创建数千个线程,每个线程在大多数时间都根据需求空闲(对于给定的clientId每秒只有很少的事件)。 一个不错的折衷方案是固定大小的线程池,每个线程负责clientId的众所周知的子集。 这样,两个不同的clientId可能会终止在同一线程上,但是同一clientId将始终由同一线程处理。 如果出现同一clientId两个事件,则它们都将被路由到同一线程,从而避免了并发处理。 实现非常简单:

class SmartPool implements EventConsumer, Closeable {private final List<ExecutorService> threadPools;private final EventConsumer downstream;SmartPool(int size, EventConsumer downstream, MetricRegistry metricRegistry) {this.downstream = downstream;List<ExecutorService> list = IntStream.range(0, size).mapToObj(i -> Executors.newSingleThreadExecutor()).collect(Collectors.toList());this.threadPools = new CopyOnWriteArrayList<>(list);}@Overridepublic void close() throws IOException {threadPools.forEach(ExecutorService::shutdown);}@Overridepublic Event consume(Event event) {final int threadIdx = event.getClientId() % threadPools.size();final ExecutorService executor = threadPools.get(threadIdx);executor.submit(() -> downstream.consume(event));return event;}
}

关键部分就在最后:

int threadIdx = event.getClientId() % threadPools.size();
ExecutorService executor = threadPools.get(threadIdx);

这个简单的算法将始终对相同的clientId使用相同的单线程ExecutorService 。 不同的ID可在同一池中结束,例如,当池大小是20 ,客户机72747等,将使用相同的线程。 但这可以,只要一个clientId始终使用同一线程即可。 此时,不需要锁定,并且可以保证顺序调用,因为同一客户端的事件始终由同一线程执行。 旁注:每个clientId一个线程无法扩展,但是每个clientId一个角色(例如,在Akka中)是一个很好的主意,它可以简化很多工作。

为了更加安全,我在每个线程池中插入了平均队列大小的指标,从而使实现更长:

class SmartPool implements EventConsumer, Closeable {private final List<LinkedBlockingQueue<Runnable>> queues;private final List<ExecutorService> threadPools;private final EventConsumer downstream;SmartPool(int size, EventConsumer downstream, MetricRegistry metricRegistry) {this.downstream = downstream;this.queues = IntStream.range(0, size).mapToObj(i -> new LinkedBlockingQueue<Runnable>()).collect(Collectors.toList());List<ThreadPoolExecutor> list = queues.stream().map(q -> new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, q)).collect(Collectors.toList());this.threadPools = new CopyOnWriteArrayList<>(list);metricRegistry.register(MetricRegistry.name(ProjectionMetrics.class, "queue"), (Gauge<Double>) this::averageQueueLength);}private double averageQueueLength() {double totalLength =queues.stream().mapToDouble(LinkedBlockingQueue::size).sum();return totalLength / queues.size();}//...}

如果您偏执狂,您甚至可以为每个队列创建一个指标。

重复数据删除和幂等

在分布式环境中,当生产者至少有一次保证时,接收重复事件是很常见的。 这种行为的原因不在本文讨论范围之内,但我们必须学习如何解决该问题。 一种方法是将全局唯一标识符( UUID )附加到每条消息,并确保在消费者方面不会对具有相同标识符的消息进行两次处理。 每个Event都有这样的UUID 。 根据我们的要求,最直接的解决方案是简单地存储所有可见的UUID并在到达时验证接收到的UUID从未见过。 按原样使用ConcurrentHashMap<UUID, UUID> (JDK中没有ConcurrentHashSet )会导致内存泄漏,因为随着时间的推移,我们将不断积累越来越多的ID。 这就是为什么我们仅在最近10秒内查找重复项。 从技术上讲,您可以拥有ConcurrentHashMap<UUID, Instant> ,当遇到该问题时,它会从UUID映射到时间戳。 通过使用后台线程,我们可以删除10秒钟以上的元素。 但是,如果您是快乐的Guava用户,则具有声明性驱逐策略的Cache<UUID, UUID>将达到目的:

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;import java.util.UUID;
import java.util.concurrent.TimeUnit;class IgnoreDuplicates implements EventConsumer {private final EventConsumer downstream;private Cache<UUID, UUID> seenUuids = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build();IgnoreDuplicates(EventConsumer downstream) {this.downstream = downstream;}@Overridepublic Event consume(Event event) {final UUID uuid = event.getUuid();if (seenUuids.asMap().putIfAbsent(uuid, uuid) == null) {return downstream.consume(event);} else {return event;}}
}

为了保证生产安全,我至少可以想到两个指标可能会有用:缓存大小和发现的重复项数量。 让我们也插入以下指标:

class IgnoreDuplicates implements EventConsumer {private final EventConsumer downstream;private final Meter duplicates;private Cache<UUID, UUID> seenUuids = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build();IgnoreDuplicates(EventConsumer downstream, MetricRegistry metricRegistry) {this.downstream = downstream;duplicates = metricRegistry.meter(MetricRegistry.name(IgnoreDuplicates.class, "duplicates"));metricRegistry.register(MetricRegistry.name(IgnoreDuplicates.class, "cacheSize"), (Gauge<Long>) seenUuids::size);}@Overridepublic Event consume(Event event) {final UUID uuid = event.getUuid();if (seenUuids.asMap().putIfAbsent(uuid, uuid) == null) {return downstream.consume(event);} else {duplicates.mark();return event;}}
}

最终,我们拥有了构建解决方案的所有要素。 这个想法是由彼此包装的EventConsumer实例组成管道:

  1. 首先,我们应用IgnoreDuplicates拒绝重复项
  2. 然后,我们调用SmartPool ,它将始终将给定的clientId到同一线程,并在该线程中执行下一阶段
  3. 最后,调用真正执行业务逻辑的ClientProjection

您可以选择在SmartPoolClientProjection之间放置FailOnConcurrentModification步骤,以提高安全性(设计上不应进行并发修改):

ClientProjection clientProjection =new ClientProjection(new ProjectionMetrics(metricRegistry));
FailOnConcurrentModification concurrentModification =new FailOnConcurrentModification(clientProjection);
SmartPool smartPool =new SmartPool(12, concurrentModification, metricRegistry);
IgnoreDuplicates withoutDuplicates =new IgnoreDuplicates(smartPool, metricRegistry);
EventStream es = new EventStream();
es.consume(withoutDuplicates);

我们花了很多工作才能提出相对简单且结构合理的解决方案(我希望您同意)。 最后,解决并发问题的最佳方法是……避免并发,并在一个线程中运行受竞争条件约束的代码。 这也是Akka actor(每个actor处理单个消息)和RxJava( Subscriber处理的一条消息)背后的思想。 在下一部分中,我们将在RxJava中看到声明式解决方案。

翻译自: https://www.javacodegeeks.com/2016/10/small-scale-stream-processing-kata-part-1-thread-pools.html

kata

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

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

相关文章

Oracle数据库的trigger(触发器)

触发器 Trigger PL/SQL 程序中的触发器的结构类似于函数和过程 , 与函数和过程不同 , 触发器是在事件发生时隐式地运行的。相当于Java语言中的事件监听器

hpux oracle9,oracle 9.2.0.8在HP-UX 11.31 下的安装步骤和注意事项

在hp_unix ia64位操作系统安装oracle 数据库步骤1. 操作系统安装好后打补丁和调参数&#xff1a;(1) 打上patch(PHKL_38038)&#xff0c;安装oracle9i必须打此补丁(2) 配置好网卡IP&#xff0c;可用xmanager调用系统图形化界面(3) 按方案调整内核参数2. 数据库安装(1) 命令行创…

bat 存储过程返回值_为什么不推荐使用存储过程?

之所以有这个题目&#xff0c;我既不是故意吸引眼球&#xff0c;也不想在本文对存储过程进行教科书般论述。最近项目中遇到的存储过程问题&#xff0c;让我想起了去年在武汉出差时一位同事的发问&#xff1a;我觉得存储过程挺好用的&#xff0c;为什么你不建议用&#xff1f;当…

aws lambda使用_使用Lambda,Api Gateway和CloudFormation在AWS云上使用Java

aws lambda使用在上一篇文章中&#xff0c;我们实现了基于Java的aws lambda函数&#xff0c;并使用CloudFront进行了部署。 由于我们已经设置了lambda函数&#xff0c;因此我们将使用AWS API Gateway将其与http端点集成。 Amazon API Gateway是一项完全托管的服务&#xff0c;…

数据库的序列

序列的特性:产生连续的不同的数字值用来作为数据表的主键。 序列是数据库中的独立对象表可以用序列产生的值作为主键 , 也可以不用序列可以为一个或多个表产生主键 , 也可以不用 建议:一个序列为一个表产生主键序列这种对象在 Oracle、db2 等数据库中有 , 在 mysql、sql serve…

java mic波形识别_会议季Mic Drop:您不应该错过的13场Java演讲

java mic波形识别您的老板没有派您参加真正的会议吗&#xff1f; 我们为您准备了最好的讲座 九月份的一些重大事件闻名于世&#xff1a;秋季的第一天&#xff0c;甚至全国熏肉日。 这也是召开会议最忙的月份之一&#xff0c;一些大型Java事件涵盖了平台的新的重要更新。 在以…

oracle utl inaddr,oracle11g之ACL拙见

错误样例(使用UTL_HTTP发送http请求时&#xff0c;报出如下错误)&#xff1a; 原因&#xff1a; 1、Oracle允许使用几个PL/SQL API(UTL_TCP&#xff0c; UTL_SMTP&#xff0c; UTL_MAIL&#xff0c; UTL_HTTP和 UTL_INADDR)访问外部网络服务&#xff0c;这些API都使用TCP协议。…

Mac下载JDK/安装JDK/卸载JDK

文章目录下载JDK安装JDK配置环境变量卸载JDK下载JDK 访问这个地址&#xff1a;https://www.oracle.com/java/technologies/downloads/#java18 进入下载JDK的界面后&#xff0c;下拉界面到下图所示的位置&#xff1a; 或者访问这个地址&#xff1a;https://www.oracle.com/cn/…

希腊字母常用指代意义及其中文读音

希腊字母常用指代意义及其中文读音序号大写小写英语音标注音英文汉语名称常用指代意义1Αα/lfə/alpha阿尔法角度、系数、角加速度、第一个、电离度、转化率 2Ββ/bi:tə/ 或 /beɪtə/beta贝塔角度、系数、磁通系数3Γγ/gmə/gamma伽玛电导系数、角度、比热容比 4Δδ/delt…

java redis 命令_命令界面:使用Java中的动态API处理Redis

java redis 命令Redis是一个数据存储&#xff0c;支持190多个文档化命令和450多个命令排列。 社区积极支持Redis开发&#xff1b; 每个主要的Redis版本都附带新命令。 今年&#xff0c;Redis向第三方供应商开放&#xff0c;以开发可扩展Redis功能的模块。 对于客户端开发人员和…

kettle 只有一个输入记录期待设置变量并且至少已经收到2个变量._OPNET学习笔记2...

双击主机网络打开对应的节点模型&#xff0c;从图中可以看到网络的分层&#xff1a;应用层&#xff0c;传输层&#xff0c;网络层&#xff0c;链路层&#xff1b;其中在传输层中我们用到的主要是UDP&#xff08;对实时性要求比较高&#xff09;&#xff0c;网络层又包括IP封装层…

oracle10g启动顺序,oracle 10g rac维护:开机 关机顺序,流程

维护oracle rac的时候&#xff0c;可能需要关闭数据库&#xff0c;甚至关闭服务器。具体的开机关机顺序如下&#xff1a;(oracle 用户下)1.关机顺序(1)关闭集群oememctl stop dbconsole -cluster(2)关闭数据库实例srvctl stop instance -d EDWPRD -i EDWPRD1srvctl stop instan…

python tkinter控件_Python3 tkinter基础 Label pack 设置控件在窗体中的位置

? python : 3.7.0 OS : Ubuntu 18.04.1 LTS IDE : PyCharm 2018.2.4 conda : 4.5.11 type setting : Markdown ? 普通布局 code """ Author : 行初心 Date : 18-10-1 Blog : www.cnblogs.com/xingchuxin GitHub : github.com/GratefulHeartCoder ""…

oracle的mins,分钟的英文缩写,10min还是10mins!

请问,分钟的英文缩写是min,小时的英文缩写是h,还是hr?秒的..._知乎[图文]时间分钟的英文缩写是什么minutes 10 minutes意思是十分钟的&#xff0c;比如说路程 10minutes就是十分钟 没有10minutes这种表达[图文]分钟 min【minute】 超过多少分钟就用介词past表示超过 还差几分钟…

lucene使用3.0.3_Jirasearch 2.0狗粮:使用Lucene查找我们的Jira问题

lucene使用3.0.3几年前&#xff0c;我首先构建并发布了Jirasearch &#xff0c;它是用于薄型包装Lucene服务器的有趣的狗粮测试用例&#xff0c;以针对我们的Jira问题公开强大的搜索UI。 这很好地展示了Lucene的许多重要功能&#xff1a; 使用块联接查询来建模父文档&#xf…

域名层级划分

文章目录教科书的定义域名结构示例教科书的定义 域名级数是指一个域名由多少级组成&#xff0c;域名的各个级别被 . 分开&#xff0c;最右边的那个词称为“顶级域名”。“顶级域名”又称之为“一级域名”&#xff0c;例如&#xff1a;.com、.org、.net、.cn 等。“二级域名”就…

java执行python脚本_使用Runtime.getRuntime().exec()在java中调用python脚本

举例有一个Python脚本叫test.py&#xff0c;现在想要在Java里调用这个脚本。假定这个test.py里面使用了拓展的包&#xff0c;使得pythoninterpreter之类内嵌的编译器无法使用&#xff0c;那么只能采用java调用控制台进程&#xff0c;即 Runtime.getRuntime().exec()&#xff0c…

linux 4.1.16 ftrace 进程调度,Linux内核进程调度overview(1)

一、概述决定何时、如何选择一个新进程运行的这组规则叫做&#xff1a;调度策略(scheduling policy)。Linux的调度是基于分时技术(time sharing)&#xff1a;多个进程以“时间多路复用”方式运行&#xff0c;因为CPU的时间呗分成“片”(slice)&#xff0c;给每个可运行进程分配…

postman测试工具,如何对参数使用md5加密

先创建环境变量password&#xff0c;并定义了变量值&#xff08;例如&#xff1a;123456&#xff09;访问登录接口通常是post提交登录用户名称和登录密码&#xff0c;且以json格式提交&#xff0c;所以在body-raw-json&#xff0c;填写json串如下图所示&#xff1a; 接着在pre-…

开源java性能分析工具_Java性能监控:您应该知道的5个开源工具

开源java性能分析工具鲜为人知但有用&#xff1a;开源应用程序性能监视的状态 对于任何应用程序来说&#xff0c;最重要的事情之一就是性能。 我们要确保用户获得他们能获得的最佳体验&#xff0c;并想知道我们的应用已启动并正在运行。 这就是为什么我们大多数人至少使用一种…