Flink源码分析系列文档目录 请点击:Flink 源码分析系列文档目录 源代码分支 release-0.9.0 Hudi 源代码GitHub地址:apache/hudi: Upserts, Deletes And Incremental Processing on Big Data. (github.com) HoodieTableFactory Flink通过SPI机制加载org.apache.flink.table.factories.Factory接口的实现类。Hudi的hudi-flink/src/main/resources/META-INF/services/org.apache.flink.table.factories.Factory文件内容如下: org.apache.hudi.table.HoodieTableFactory 这个类是Flink SQL创建Table Sink和Source的入口类。本篇我们从这个类开始,分析HoodieTableSink的创建过程。创建TableSink的入口方法逻辑如下: @Override public DynamicTableSink createDynamicTableSink(Context context) { // 获取create table是否with子句附带的参数 Configuration conf = FlinkOptions.fromMap(context.getCatalogTable().getOptions()); // 获取表的物理Schema,意思是不包含计算字段和元数据字段 TableSchema schema = TableSchemaUtils.getPhysicalSchema(context.getCatalogTable().getSchema()); // 检查参数合理性 // 检查hoodie.datasource.write.recordkey.field和write.precombine.field配置项是否包含在表字段中,如果不包含则抛出异常 sanityCheck(conf, schema); // 根据table定义和主键等配置,Hudi自动附加一些属性配置 setupConfOptions(conf, context.getObjectIdentifier().getObjectName(), context.getCatalogTable(), schema); // 返回HoodieTableSink return new HoodieTableSink(conf, schema); } HoodieTableSink Flink SQL在执行过程中最终被解析转换为Flink的TableSink或者TableSource。本篇我们关注数据写入Hudi的过程。HoodieTableSink写入数据的逻辑位于getSinkRuntimeProvider方法。它的内容和解析如下所示: @Override public SinkRuntimeProvider getSinkRuntimeProvider(Context context) { return (DataStreamSinkProvider) dataStream -> { // setup configuration // 获取checkpoint超时配置 long ckpTimeout = dataStream.getExecutionEnvironment() .getCheckpointConfig().getCheckpointTimeout(); // 设置Hudi的instant commit超时时间为Flink的checkpoint超时时间 conf.setLong(FlinkOptions.WRITE_COMMIT_ACK_TIMEOUT, ckpTimeout); // 获取schema对应每列数据类型 RowType rowType = (RowType) schema.toRowDataType().notNull().getLogicalType(); // bulk_insert mode // 获取写入操作类型,默认是upsert final String writeOperation = this.conf.get(FlinkOptions.OPERATION); // 如果写入操作类型配置的为bulk_insert,进入这个if分支 if (WriteOperationType.fromValue(writeOperation) == WriteOperationType.BULK_INSERT) { // 创建出批量插入operator工厂类 BulkInsertWriteOperator.OperatorFactory operatorFactory = BulkInsertWriteOperator.getFactory(this.conf, rowType); // 获取分区字段 final String[] partitionFields = FilePathUtils.extractPartitionKeys(this.conf); if (partitionFields.length > 0) { // 创建出key生成器,用于指定数据分组,keyBy算子使用 RowDataKeyGen rowDataKeyGen = RowDataKeyGen.instance(conf, rowType); // 如果启用write.bulk_insert.shuffle_by_partition if (conf.getBoolean(FlinkOptions.WRITE_BULK_INSERT_SHUFFLE_BY_PARTITION)) { // shuffle by partition keys // 数据流按照分区字段值进行keyBy操作 dataStream = dataStream.keyBy(rowDataKeyGen::getPartitionPath); } // 如果需要按照分区排序 if (conf.getBoolean(FlinkOptions.WRITE_BULK_INSERT_SORT_BY_PARTITION)) { // 创建一个排序operator SortOperatorGen sortOperatorGen = new SortOperatorGen(rowType, partitionFields); // sort by partition keys // 为datastream增加一个排序操作符 dataStream = dataStream .transform("partition_key_sorter", TypeInformation.of(RowData.class), sortOperatorGen.createSortOperator()) .setParallelism(conf.getInteger(FlinkOptions.WRITE_TASKS)); ExecNode$.MODULE$.setManagedMemoryWeight(dataStream.getTransformation(), conf.getInteger(FlinkOptions.WRITE_SORT_MEMORY) * 1024L * 1024L); } } // 为dataStream加入批量写入operator并返回 return dataStream .transform("hoodie_bulk_insert_write", TypeInformation.of(Object.class), operatorFactory) // follow the parallelism of upstream operators to avoid shuffle .setParallelism(conf.getInteger(FlinkOptions.WRITE_TASKS)) .addSink(new CleanFunction<>(conf)) .setParallelism(1) .name("clean_commits"); } // 对于非批量写入模式,采用流式写入 // stream write int parallelism = dataStream.getExecutionConfig().getParallelism(); // 创建流式写入operator StreamWriteOperatorFactory operatorFactory = new StreamWriteOperatorFactory<>(conf); // 将数据从RowData格式转换为HoodieRecord DataStream dataStream1 = dataStream .map(RowDataToHoodieFunctions.create(rowType, conf), TypeInformation.of(HoodieRecord.class)); // bootstrap index // TODO: This is a very time-consuming operation, will optimization // 是否启动时加载索引 if (conf.getBoolean(FlinkOptions.INDEX_BOOTSTRAP_ENABLED)) { // 如果启用,会在启动时自动加载索引,包装为IndexRecord发往下游 dataStream1 = dataStream1.rebalance() .transform( "index_bootstrap", TypeInformation.of(HoodieRecord.class), new ProcessOperator<>(new BootstrapFunction<>(conf))) .setParallelism(conf.getOptional(FlinkOptions.INDEX_BOOTSTRAP_TASKS).orElse(parallelism)) .uid("uid_index_bootstrap_" + conf.getString(FlinkOptions.TABLE_NAME)); } // 按照record key分区,然后使用ucketAssignFunction分桶 // 再按照分桶id分区,使用StreamWriteFunction流式写入 DataStream pipeline = dataStream1 // Key-by record key, to avoid multiple subtasks write to a bucket at the same time .keyBy(HoodieRecord::getRecordKey) .transform( "bucket_assigner", TypeInformation.of(HoodieRecord.class), new BucketAssignOperator<>(new BucketAssignFunction<>(conf))) .uid("uid_bucket_assigner_" + conf.getString(FlinkOptions.TABLE_NAME)) .setParallelism(conf.getOptional(FlinkOptions.BUCKET_ASSIGN_TASKS).orElse(parallelism)) // shuffle by fileId(bucket id) .keyBy(record -> record.getCurrentLocation().getFileId()) .transform("hoodie_stream_write", TypeInformation.of(Object.class), operatorFactory) .uid("uid_hoodie_stream_write" + conf.getString(FlinkOptions.TABLE_NAME)) .setParallelism(conf.getInteger(FlinkOptions.WRITE_TASKS)); // compaction // 如果需要压缩(表类型为MERGE_ON_READ,并且启用了异步压缩) if (StreamerUtil.needsAsyncCompaction(conf)) { // 首先在coordinator通知checkpoint完毕的时候生成压缩计划 // 然后使用CompactFunction压缩hudi table数据 return pipeline.transform("compact_plan_generate", TypeInformation.of(CompactionPlanEvent.class), new CompactionPlanOperator(conf)) .setParallelism(1) // plan generate must be singleton .rebalance() .transform("compact_task", TypeInformation.of(CompactionCommitEvent.class), new ProcessOperator<>(new CompactFunction(conf))) .setParallelism(conf.getInteger(FlinkOptions.COMPACTION_TASKS)) .addSink(new CompactionCommitSink(conf)) .name("compact_commit") .setParallelism(1); // compaction commit should be singleton } else { return pipeline.addSink(new CleanFunction<>(conf)) .setParallelism(1) .name("clean_commits"); } }; } 从上面源代码我们可大致梳理出数据入Hudi表的流程: 如果配置了批量插入,采用BulkInsertWriteOperator批量写入数据。根据是否需要排序的要求,决定是否采用SortOperator。 将RowData格式的数据转换为Hudi专用的HoodieRecord格式。 根据配置需要,确定是否使用BootstrapFunction加载索引,此步骤耗时较长。 根据数据的partition分配数据的存储位置(BucketAssignFunction)。 将数据通过流的方式落地StreamWriteFunction。 如果是MOR类型表,且开启了异步压缩,schedule一个压缩操作(CompactionPlanOperator和CompactFunction)。 批量插入相关 BulkInsertWriteOperator BulkInsertWriteOperator使用BulkInsertWriteFunction进行批量数据插入操作。 BulkInsertWriteFunction的初始化逻辑位于open方法中,代码如下所示: @Override public void open(Configuration parameters) throws IOException { // 获取批量插入数据作业的taskID this.taskID = getRuntimeContext().getIndexOfThisSubtask(); // 创建writeClient,它负责创建index,提交数据和回滚,以及数据增删改查操作 this.writeClient = StreamerUtil.createWriteClient(this.config, getRuntimeContext()); // 根据table类型和写入操作类型推断操作类型 this.actionType = CommitUtils.getCommitActionType( WriteOperationType.fromValue(config.getString(FlinkOptions.OPERATION)), HoodieTableType.valueOf(config.getString(FlinkOptions.TABLE_TYPE))); // 获取上一个进行中的instant时间戳 this.initInstant = this.writeClient.getLastPendingInstant(this.actionType); // 发送一个WriteMetadataEvent到coordinator,结束上一批数据写入过程 sendBootstrapEvent(); // 初始化writerHelper,用于辅助进行数据批量插入 initWriterHelper(); } 该function遇到每一个元素,通过writerHelper将这个元素写入到Parquet文件中。 @Override public void processElement(I value, Context ctx, Collector out) throws IOException { this.writerHelper.write((RowData) value); } 每一批数据结束后,会调用endInput方法。执行writeHelper关闭和通知coordinator批量插入完毕。 public void endInput() { final List writeStatus; try { // 关闭writeHelper this.writerHelper.close(); // 获取所有HoodieRowDataCreateHandle对应的writeStatus,每个数据写入的partitionPath对应一个handle writeStatus = this.writerHelper.getWriteStatuses().stream() .map(BulkInsertWriteFunction::toWriteStatus).collect(Collectors.toList()); } catch (IOException e) { throw new HoodieException("Error collect the write status for task [" + this.taskID + "]"); } // 发送本批数据已完全写入的event给coordinator final WriteMetadataEvent event = WriteMetadataEvent.builder() .taskID(taskID) .instantTime(this.writerHelper.getInstantTime()) .writeStatus(writeStatus) .lastBatch(true) .endInput(true) .build(); this.eventGateway.sendEventToCoordinator(event); } SortOperator SortOperator用于将一批插入的数据排序后再写入。开启write.bulk_insert.sort_by_partition配置项会启用此特性。 它的初始化逻辑位于open方法,内容和分析如下: @Override public void open() throws Exception { super.open(); LOG.info("Opening SortOperator"); // 获取用户代码classloader ClassLoader cl = getContainingTask().getUserCodeClassLoader(); // 获取RowData序列化器 AbstractRowDataSerializer inputSerializer = (AbstractRowDataSerializer) getOperatorConfig().getTypeSerializerIn1(getUserCodeClassloader()); // 创建Hudi专用的序列化器,传入参数为RowData字段数 this.binarySerializer = new BinaryRowDataSerializer(inputSerializer.getArity()); NormalizedKeyComputer computer = gComputer.newInstance(cl); RecordComparator comparator = gComparator.newInstance(cl); gComputer = null; gComparator = null; // 获取作业的内存管理器 MemoryManager memManager = getContainingTask().getEnvironment().getMemoryManager(); // 使用Flink提供的二进制MergeSort工具对RowData排序 this.sorter = new BinaryExternalSorter( this.getContainingTask(), memManager, computeMemorySize(), this.getContainingTask().getEnvironment().getIOManager(), inputSerializer, binarySerializer, computer, comparator, getContainingTask().getJobConfiguration()); // 排序工具包含了排序线程,合并线程以及溢写Thread,该方法启动这些线程 this.sorter.startThreads(); // 创建结果收集器,用于发送结果到下游 collector = new StreamRecordCollector<>(output); // register the the metrics. // 创建监控仪表,包含内存已用字节数,溢写文件数和溢写字节数 getMetricGroup().gauge("memoryUsedSizeInBytes", (Gauge) sorter::getUsedMemoryInBytes); getMetricGroup().gauge("numSpillFiles", (Gauge) sorter::getNumSpillFiles); getMetricGroup().gauge("spillInBytes", (Gauge) sorter::getSpillInBytes); } SortOperator每次接收到一个RowData类型数据,都把它放入BinaryExternalSorter的缓存中。 @Override public void processElement(StreamRecord element) throws Exception { this.sorter.write(element.getValue()); } 当一批数据插入过程结束时,SortOperator将sorter中以排序的二进制RowData数据顺序取出,发往下游。 @Override public void endInput() throws Exception { BinaryRowData row = binarySerializer.createInstance(); MutableObjectIterator iterator = sorter.getIterator(); while ((row = iterator.next(row)) != null) { collector.collect(row); } } RowDataToHoodieFunction 负责将RowData映射为HoodieRecord,转换的逻辑位于toHoodieRecord方法中。 private HoodieRecord toHoodieRecord(I record) throws Exception { // 根据AvroSchema,将RowData数据转换为Avro格式 GenericRecord gr = (GenericRecord) this.converter.convert(this.avroSchema, record); // 获取HoodieKey,它由record key字段值和partitionPath(分区路径)共同确定 final HoodieKey hoodieKey = keyGenerator.getKey(gr); // 创建数据载体,该对象包含RowData数据 HoodieRecordPayload payload = payloadCreation.createPayload(gr); // 获取操作类型,增删改查 HoodieOperation operation = HoodieOperation.fromValue(record.getRowKind().toByteValue()); // 构造出HoodieRecord return new HoodieRecord<>(hoodieKey, payload, operation); } BootstrapFunction 通途为加载时候生成索引。该特性通过index.bootstrap.enabled配置项开启。索引在接收到数据的时候开始加载,只加载index.partition.regex配置项正则表达式匹配的partition path对应的索引。加载完毕之后,该算子将不再进行任何其他操作,直接将数据发往下游。 public void processElement(I value, Context ctx, Collector out) throws Exception { // 标记是否已启动,初始值为false if (!alreadyBootstrap) { // 获取hoodie表元数据所在路径 String basePath = hoodieTable.getMetaClient().getBasePath(); int taskID = getRuntimeContext().getIndexOfThisSubtask(); LOG.info("Start loading records in table {} into the index state, taskId = {}", basePath, taskID); // 遍历表包含的所有partitionPath for (String partitionPath : FSUtils.getAllFoldersWithPartitionMetaFile(FSUtils.getFs(basePath, hadoopConf), basePath)) { // pattern为index.partition.regex配置项的值,决定加载哪些partition的index,默认全加载 if (pattern.matcher(partitionPath).matches()) { // 加载分区索引 loadRecords(partitionPath, out); } } // wait for others bootstrap task send bootstrap complete. // 等待其他task启动完毕 waitForBootstrapReady(taskID); // 标记已启动完毕 alreadyBootstrap = true; LOG.info("Finish sending index records, taskId = {}.", getRuntimeContext().getIndexOfThisSubtask()); } // send the trigger record // 把数据原封不动发往下游 // 该算子不操作数据,仅仅是通过数据触发加载索引的操作 out.collect((O) value); } loadRecords方法加载partition的索引。索引是Indexrecord格式,保存了record key,partition path(两者合起来为HoodieKey)和所在fileSlice的对应关系。 private void loadRecords(String partitionPath, Collector out) throws Exception { long start = System.currentTimeMillis(); // 根据存储格式,创建对应的格式处理工具,目前支持Parquet和Orc BaseFileUtils fileUtils = BaseFileUtils.getInstance(this.hoodieTable.getBaseFileFormat()); // 获取table对应的avro schema Schema schema = new TableSchemaResolver(this.hoodieTable.getMetaClient()).getTableAvroSchema(); // 获取并行度,最大并行度和taskID final int parallelism = getRuntimeContext().getNumberOfParallelSubtasks(); final int maxParallelism = getRuntimeContext().getMaxNumberOfParallelSubtasks(); final int taskID = getRuntimeContext().getIndexOfThisSubtask(); // 获取时间线上最后一个已提交的instant Option latestCommitTime = this.hoodieTable.getMetaClient().getCommitsTimeline() .filterCompletedInstants().lastInstant(); // 如果这个instant存在 if (latestCommitTime.isPresent()) { // 获取这个commit时间之前的所有FileSlice List fileSlices = this.hoodieTable.getSliceView() .getLatestFileSlicesBeforeOrOn(partitionPath, latestCommitTime.get().getTimestamp(), true) .collect(toList()); for (FileSlice fileSlice : fileSlices) { // 判断这个fileSlice是否归本task加载 // 如果不是则跳过 if (!shouldLoadFile(fileSlice.getFileId(), maxParallelism, parallelism, taskID)) { continue; } LOG.info("Load records from {}.", fileSlice); // load parquet records // 加载FlieSlice中的数据文件 fileSlice.getBaseFile().ifPresent(baseFile -> { // filter out crushed files // 根据文件类型,校验文件是否正常 if (!isValidFile(baseFile.getFileStatus())) { return; } final List hoodieKeys; try { // 获取Partition对应的HoodieKey hoodieKeys = fileUtils.fetchRecordKeyPartitionPath(this.hadoopConf, new Path(baseFile.getPath())); } catch (Exception e) { throw new HoodieException(String.format("Error when loading record keys from file: %s", baseFile), e); } // 发送indexRecord(各个HoodieKey和fileSlice的对应关系)到下游,这里是列存储文件的index for (HoodieKey hoodieKey : hoodieKeys) { out.collect((O) new IndexRecord(generateHoodieRecord(hoodieKey, fileSlice))); } }); // load avro log records // 加载所有avro格式log文件的路径 List logPaths = fileSlice.getLogFiles() // filter out crushed files .filter(logFile -> isValidFile(logFile.getFileStatus())) .map(logFile -> logFile.getPath().toString()) .collect(toList()); // 扫描log文件,合并record key相同的数据 HoodieMergedLogRecordScanner scanner = FormatUtils.logScanner(logPaths, schema, latestCommitTime.get().getTimestamp(), writeConfig, hadoopConf); try { // 遍历合并后的数据,遍历他们的record key // 发送IndexRecord到下游,这里处理的是log文件中数据的index for (String recordKey : scanner.getRecords().keySet()) { out.collect((O) new IndexRecord(generateHoodieRecord(new HoodieKey(recordKey, partitionPath), fileSlice))); } } catch (Exception e) { throw new HoodieException(String.format("Error when loading record keys from files: %s", logPaths), e); } finally { scanner.close(); } } } BucketAssignFunction 执行数据分桶操作。为每一条数据分配它的存储位置。如果开启了索引加载(BootstrapFunction),BucketAssignFunction会把索引数据(IndexRecord)加载入operator状态缓存中。 @Override public void processElement(I value, Context ctx, Collector out) throws Exception { // 如果接收到的是索引数据 // 如果启用的加载索引,上一节的BootstrapFunction会产生IndexRecord // 这里需要根据索引,更新recordKey和储存位置的对应关系 if (value instanceof IndexRecord) { IndexRecord indexRecord = (IndexRecord) value; // 设置operator StateHandler当前处理的key为record key this.context.setCurrentKey(indexRecord.getRecordKey()); // 更新indexState为索引数据对应的位置 // 将IndexRecord携带的recordKey和location信息对应存入indexState中 this.indexState.update((HoodieRecordGlobalLocation) indexRecord.getCurrentLocation()); } else { // 进入此分支伤命接收到的事HoodieRecord,开始处理数据过程 processRecord((HoodieRecord) value, out); } } 数据处理过程位于processRecord方法,逻辑如下所示: private void processRecord(HoodieRecord record, Collector out) throws Exception { // 1. put the record into the BucketAssigner; // 2. look up the state for location, if the record has a location, just send it out; // 3. if it is an INSERT, decide the location using the BucketAssigner then send it out. // 获取HoodieKey,分别拿出recordKey和partitionPath final HoodieKey hoodieKey = record.getKey(); final String recordKey = hoodieKey.getRecordKey(); final String partitionPath = hoodieKey.getPartitionPath(); // 封装了HoodieRecord的存储位置,即这条HoodieRecord对应哪个文件 final HoodieRecordLocation location; // Only changing records need looking up the index for the location, // append only records are always recognized as INSERT. // 获取index中保存的location信息 HoodieRecordGlobalLocation oldLoc = indexState.value(); // 如果操作类型为UPSERT,DELETE或者UPSERT_PREPPED,isChangingRecords为true if (isChangingRecords && oldLoc != null) { // Set up the instant time as "U" to mark the bucket as an update bucket. // 如果index的partitionPath和当前HoodieRecord的不同 if (!Objects.equals(oldLoc.getPartitionPath(), partitionPath)) { // 由index.global.enabled配置项控制 // 表示一个相同key的record到来但是partitionPath不同,是否需要更新旧的partitionPath if (globalIndex) { // if partition path changes, emit a delete record for old partition path, // then update the index state using location with new partition path. // 创建一个删除元素发给下游,删除老的partitionPath信息 HoodieRecord deleteRecord = new HoodieRecord<>(new HoodieKey(recordKey, oldLoc.getPartitionPath()), payloadCreation.createDeletePayload((BaseAvroPayload) record.getData())); deleteRecord.setCurrentLocation(oldLoc.toLocal("U")); deleteRecord.seal(); out.collect((O) deleteRecord); } // 通过BucketAssigner获取新的存储位置 location = getNewRecordLocation(partitionPath); // 更新IndexState为新的partitionPath和location updateIndexState(partitionPath, location); } else { location = oldLoc.toLocal("U"); // 加入更新数据的位置信息到bucketAssigner this.bucketAssigner.addUpdate(partitionPath, location.getFileId()); } } else { // 如果不是数据更新操作 location = getNewRecordLocation(partitionPath); this.context.setCurrentKey(recordKey); } // always refresh the index // 确保数据更新操作刷新索引(indexState) if (isChangingRecords) { updateIndexState(partitionPath, location); } // 设置record的存放位置,发送给下游 record.setCurrentLocation(location); out.collect((O) record); } StreamWriteFunction 用于写入HoodieRecord到文件系统中。 @Override public void processElement(I value, KeyedProcessFunction.Context ctx, Collector out) { bufferRecord((HoodieRecord) value); } processElement又调用了bufferRecord方法。在存入数据到buffer之前,先检查是否需要flush bucket和buffer。先提前判断如果某条数据加入bucket后将超过了bucket大小限制,会flush这个bucket。buffer为多个bucket的最大占用内存数量总和,如果buffer空闲容量耗尽,Hudi挑一个当前数据写入最多的bucket执行flush。代码如下所示: private void bufferRecord(HoodieRecord value) { // 根据HoodieRecord的partitionPath和fileId构建出bucketID final String bucketID = getBucketID(value); // 根据bucketID缓存了一组DataBucket,保存在buckets变量 // 如果bucketID对应的DataBucket不存在,这里创建一个新的并放入buckets中 // bucket batch大小设置为write.batch.size // partitionPath和fileID与HoodieRecord一致 DataBucket bucket = this.buckets.computeIfAbsent(bucketID, k -> new DataBucket(this.config.getDouble(FlinkOptions.WRITE_BATCH_SIZE), value)); // 将HoodieRecord转换为DataItem // DataItem为数据保存在buffer中的格式,在flush之前DataItem会再转换回HoodieRecord final DataItem item = DataItem.fromHoodieRecord(value); // buffer中已存元素大小加上当前dataitem是否大于batch size,如果大于需要flush boolean flushBucket = bucket.detector.detect(item); // 检查buffer size是否超过最大缓存容量 // 最大缓存容量为write.task.max.size - 100MB - write.merge.max_memory boolean flushBuffer = this.tracer.trace(bucket.detector.lastRecordSize); // 如果需要flushBucket if (flushBucket) { // 如果bucket数据被writeClient成功写入 if (flushBucket(bucket)) { // tracer持有的缓存使用量减掉bucket容量 this.tracer.countDown(bucket.detector.totalSize); // 清空bucket bucket.reset(); } } else if (flushBuffer) { // 如果需要清空buffer,找到大小最大的bucket然后flush它 // find the max size bucket and flush it out // 找到所有的bucket,按照totalSize从大到小排序 List sortedBuckets = this.buckets.values().stream() .sorted((b1, b2) -> Long.compare(b2.detector.totalSize, b1.detector.totalSize)) .collect(Collectors.toList()); // 取出第一个bucket,即totalSize最大的bucket final DataBucket bucketToFlush = sortedBuckets.get(0); // flush这个bucket if (flushBucket(bucketToFlush)) { this.tracer.countDown(bucketToFlush.detector.totalSize); bucketToFlush.reset(); } else { LOG.warn("The buffer size hits the threshold {}, but still flush the max size data bucket failed!", this.tracer.maxBufferSize); } } // 将record加入bucket中 bucket.records.add(item); } CompactionPlanOperator 如果符合数据压缩的条件(Merge on Read表,并且启用异步压缩),CompactionPlanOperator将会生成数据压缩计划。CompactionPlanOperator不处理数据,只在checkpoint完成之后,schedule一个compact操作。 @Override public void notifyCheckpointComplete(long checkpointId) { try { // 获取Hoodie表 HoodieFlinkTable hoodieTable = writeClient.getHoodieTable(); // 回滚之前没进行完的压缩操作 CompactionUtil.rollbackCompaction(hoodieTable, writeClient, conf); // schedule一个新的压缩操作 scheduleCompaction(hoodieTable, checkpointId); } catch (Throwable throwable) { // make it fail safe LOG.error("Error while scheduling compaction at instant: " + compactionInstantTime, throwable); } } scheduleCompaction方法: private void scheduleCompaction(HoodieFlinkTable table, long checkpointId) throws IOException { // the last instant takes the highest priority. // 获取最近一个活跃的可被压缩的instant Option lastRequested = table.getActiveTimeline().filterPendingCompactionTimeline() .filter(instant -> instant.getState() == HoodieInstant.State.REQUESTED).lastInstant(); if (!lastRequested.isPresent()) { // do nothing. LOG.info("No compaction plan for checkpoint " + checkpointId); return; } // 获取这个instant的时间 String compactionInstantTime = lastRequested.get().getTimestamp(); // 如果当前正在压缩的instant时间和最近一个活跃的可被压缩的instant时间相同 // 说明schedule的compact操作重复了 if (this.compactionInstantTime != null && Objects.equals(this.compactionInstantTime, compactionInstantTime)) { // do nothing LOG.info("Duplicate scheduling for compaction instant: " + compactionInstantTime + ", ignore"); return; } // generate compaction plan // should support configurable commit metadata // 创建HoodieCompactionPlan HoodieCompactionPlan compactionPlan = CompactionUtils.getCompactionPlan( table.getMetaClient(), compactionInstantTime); if (compactionPlan == null || (compactionPlan.getOperations() == null) || (compactionPlan.getOperations().isEmpty())) { // do nothing. LOG.info("No compaction plan for checkpoint " + checkpointId + " and instant " + compactionInstantTime); } else { this.compactionInstantTime = compactionInstantTime; // 获取要压缩的instant HoodieInstant instant = HoodieTimeline.getCompactionRequestedInstant(compactionInstantTime); // Mark instant as compaction inflight // 标记该instant状态为inflight(正在处理) table.getActiveTimeline().transitionCompactionRequestedToInflight(instant); table.getMetaClient().reloadActiveTimeline(); // 创建压缩操作 List operations = compactionPlan.getOperations().stream() .map(CompactionOperation::convertFromAvroRecordInstance).collect(toList()); LOG.info("CompactionPlanOperator compacting " + operations + " files"); // 逐个发送压缩操作到下游 for (CompactionOperation operation : operations) { output.collect(new StreamRecord<>(new CompactionPlanEvent(compactionInstantTime, operation))); } } } CompactFunction 接前一步生成的压缩计划,执行数据压缩过程。 @Override public void processElement(CompactionPlanEvent event, Context context, Collector collector) throws Exception { // 获取要压缩的instant final String instantTime = event.getCompactionInstantTime(); // 获取压缩操作 final CompactionOperation compactionOperation = event.getOperation(); // 如果是异步压缩,通过线程池执行doCompaction方法 if (asyncCompaction) { // executes the compaction task asynchronously to not block the checkpoint barrier propagate. executor.execute( () -> doCompaction(instantTime, compactionOperation, collector), "Execute compaction for instant %s from task %d", instantTime, taskID); } else { // executes the compaction task synchronously for batch mode. LOG.info("Execute compaction for instant {} from task {}", instantTime, taskID); doCompaction(instantTime, compactionOperation, collector); } } doCompaction方法: private void doCompaction(String instantTime, CompactionOperation compactionOperation, Collector collector) throws IOException { // 通过FlinkCompactHelpers执行数据压缩操作 List writeStatuses = FlinkCompactHelpers.compact(writeClient, instantTime, compactionOperation); // 收集数据压缩结果到下游 collector.collect(new CompactionCommitEvent(instantTime, writeStatuses, taskID)); } 到此为止,Flink写入Hudi表的流程已分析完毕。 本博客为作者原创,欢迎大家参与讨论和批评指正。如需转载请注明出处。 你可能感兴趣的:(Flink Hudi 源码之HoodieTableSink) 力扣每日一练之字符串Day6 京与旧铺 LeetCode刷起来leetcodejava算法 力扣每日一练之字符串Day6前面的话大家好!本篇文章将介绍2周搞定数据结构的题,本文将以三道题作为背景,介绍经典的数独以及排序算法,展示语言为java(博主学习语言为java)。今天呢,是博主开始刷力扣的第五天,如果有想要开始准备自己的算法面试的同学,可以跟着我的脚步一起,共同进步。大家都是并肩作战的伙伴,一起努力奋力前行,路漫漫其修远兮,吾将上下而求索,相信我们一定都可以拿到自己期望的offer 详解多模态(红外-可见光图像)目标检测模型SuperYOLO源码,真正搞清代码逻辑! 弗兰随风小欢 目标检测实验系列深度学习目标检测YOLO计算机视觉多模态目标检测视觉检测人工智能 目录1.文章主要内容2.相关说明3.基于SuperYOLO的多模态目标检测3.1详解代码流程(重点)3.1.1train.py文件(入口)3.1.2SRyolo.py文件3.1.3datasets.py文件3.1.4再次回到train.py文件3.1.5再次回到SRyolo.py文件3.总结1.文章主要内容本文主要是详细分析SuperYOLO多模态源代码,包括如何启动,以及详细代码部分如何改进,从 50道题快速复习MySQL之准备篇 比奇堡的天没有云 速通MySQLmysqloracle数据库 文章目录1.创建数据库表2.创建表数据本文旨在帮助大家快速复习MySQL,共有4张表,50道题.本篇文章在做讲解50道题目之前,先将数据库表以及表中的数据创建好。1.创建数据库表建表语句如下,分别是学生表,课程表,教师表和成绩表。#–1.学生表#Student(s_id,s_name,s_birth,s_sex)–学生编号,学生姓名,出生年月,学生性别CREATETABLE`Student`(`s 观望=没有! 郭顺发_ 博客经验分享 “兄弟,Java现在学还有前途吗?”“前端是不是饱和了?”——每天打开私信,这类问题能占大半。我的回复永远只有一句:“如果你非要等我说‘行’才敢行动,那答案已经不重要了。”#技术人总在纠结“能不能”一直在等权威认证有人私信问“学Java还能找到工作吗”,自己却从来没打开过招聘网站——实际上,2024年Java岗位仍占后端需求的百分之四五十。考虑技术风向新手在Vue/React/Svelte之间反复 InfiniBand包头与ibverbs接口实现(一)—— RDMA WRITE分析 网络编程code数据数据库 InfiniBand是一种高性能网络技术,其数据包格式设计对实现高效可靠的网络传输至关重要。本文将详细介绍InfiniBand数据包的头部结构,分析它们在实际应用中的作用和实现机制。并且我们会讨论可靠连接(RC)服务类型下的传输头格式,以及RDMAWRITE等典型操作场景,以及它们在ibverbs接口中的对应关系。TableofContents:两类包头格式RoutingHeaders2.1.源码 【HarmonyOS NEXT】异步编程的神器之Promise androidios前端 1.背景异步编程对ArkTS这门语言来说实在太重要。因为ArkTS是单线程模型【单线程模型指的是,JavaScript只在一个线程上运行。也就是说,JavaScript同时只能执行一个任务,其他任务都必须在后面排队等待。】如果没有异步编程,很容易就写出回调地狱般的屎山代码。现在在ArkTS中要实现异步并发任务时,最合适的就是使用Promise和async/await【在此之前JS异步的发展历程是c 「QT」布局类 之 QGridLayout 网格布局类 何曾参静谧 「QT」QT5程序设计qt开发语言 ✨博客主页何曾参静谧的博客(✅关注、点赞、⭐收藏、转发)文章专栏「QT」QT5程序设计全部专栏(专栏会有变化,以最新发布为准)「Win」Windows程序设计「IDE」集成开发环境「UG/NX」BlockUI集合「C/C++」C/C++程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「UG/NX」NX定制开发「Py」Python程序设计「Ma 面试题之Vuex,sessionStorage,localStorage的区别 阿丽塔~ 前端javascript开发语言 Vuex、localStorage和sessionStorage都是用于存储数据的技术,但它们在存储范围、存储方式、应用场景等方面存在显著区别。以下是它们的详细对比:1.存储范围Vuex:是Vue.js的状态管理库,用于存储全局状态。数据存储在内存中,页面刷新后数据会丢失。只在当前应用实例的生命周期内有效。localStorage:是浏览器提供的WebStorageAPI的一部分。数据存储在浏览器 感应式门铃的设计(源码+万字报告+实物) 炳烛之明科技 数据挖掘人工智能 摘要:31绪论31.1国内外研究现状41.2课题研究的背景及意义42.系统设计总述42.1系统设计方案与原理42.2AT89C51单片机的引出及特点52.2.1AT89C51D的功能62.3热释电红外探测器72.3.1HN911红外探测器原理及特点72.3.2HN911红外探测器的特点补充72.4ADC0809简介83.系统硬件及电路83.1键盘输入电路83.2复位93.3显示电路93.4扬声器驱 Go语言标准库之regexp aaronthon Golang regexp是go支持正则表达式的相关内置模块。一、引入import"regexp"二、使用2.1regexp.MatchString使用正则表达式匹配字符串match,_:=regexp.MatchString("H(.*)!","Helloworld!")fmt.Println(match)//true2.2regexp.Match使用正则表达式匹配字符串match,_:=regexp.Mat 云原生架构师2024 theo.wu 云原生架构师2024云原生 1-Linux操作系统-CSDN博客├──1-Linux操作系统|├──1-项目部署之-Linux操作系统||├──1-Linux概述与安装||├──2-Linux基本操作||└──3-Linux软件安装与配置|└──2-Shell编程||└──1-Shell编程2-计算机网络基础-CSDN博客├──2-计算机网络基础|└──1-计算机网络基础||├──1-前言||├──2-计算机网络概述||├─ C++效率掌握之STL库:vector底层剖析 DARLING Zero two♡ C++初阶c++开发语言stlvector 文章目录1.学习vector底层的必要性2.vector类对象基本函数实现3.vector类对象的遍历4.vector类对象的扩容追加5.string类对象的插入、删除6.vector类对象的其余操作7.使用memcpy拷贝问题希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力!了解完vector函数的主要用法,很有必要对vector进行深层次的剖析,进一步了解其运作原理,深化理解的同 Mac环境jenkins多渠道配置打包Flutter、Android应用 Super-Bin Flutterandroidflutterjenkins移动端 这里写目录标题前言Jenkins安装启动安装插件项目配置General配置源码管理构建构建后操作执行解决问题找不到./gradlew命令解决方法1解决方法2只显示代码,没有正常显示二维码局域网ip无法访问jenkins修改配置可能与nginx的端口占用有关参考前言关于Flutter、Android多渠道配置打包,源码Jenkins安装两种安装方式:各平台官网安装Jenkins步骤mac官网安装je Ubuntu 下 nginx-1.24.0 源码分析 - ngx_localtime 函数 若云止水 nginx运维 ngx_localtime函数声明在src\os\unix\ngx_time.h中:voidngx_localtime(time_ts,ngx_tm_t*tm);定义在src/os/unix/ngx_time.c中voidngx_localtime(time_ts,ngx_tm_t*tm){#if(NGX_HAVE_LOCALTIME_R)(void)localtime_r(&s,tm);#els android+8.0对应版本,Android各版本适配之8.0 chinhoyoo android+8.0对应版本 Android8.0透明Activity报错"Onlyfullscreenactivitiescanrequestorientation"1、分析问题首先,我的代码是这样的:style.xmltruetrueadjustPanfalse@color/app_transparent_colortrue//透明true//悬浮AndroidManifest.xml从上面可以看出,我的activity是 强势破局:基于Java的开源能源管理系统源码+能碳管理系统+能源管理系统+能源管理平台:智碳能源管理系统,我为地球降一度!开启智慧节能新纪元,让能源管理“碳”索未来!可在线体验 智碳未来科技有限公司 能源开源javaspringboot物联网 先上干货!墙内仓库地址(码云):https://gitee.com/ustcyc/zhitan-ems已同步更新到github仓库:https://github.com/Andy-Yin/zhitan-ems在当今社会,能源管理已经成为一个不可忽视的重要议题。随着科技的不断发展,人们对能源的需求也日益增长,而能源的消耗对地球环境造成的影响也日益显现。为了更好地管理和利用能源资源,推动绿色可持续发展 Ubuntu 下 nginx-1.24.0 源码分析 - ngx_pnalloc函数 若云止水 nginx运维 ngx_pnalloc声明在src\core\ngx_palloc.hvoid*ngx_pnalloc(ngx_pool_t*pool,size_tsize);定义在src\core\ngx_palloc.cvoid*ngx_pnalloc(ngx_pool_t*pool,size_tsize){#if!(NGX_DEBUG_PALLOC)if(sizemax){returnngx_palloc_ Android15音频进阶之焦点仲裁矩阵(一百零七) Android系统攻城狮 AndroidAudio工程师进阶系列音视频矩阵python 简介:CSDN博客专家、《Android系统多媒体进阶实战》一书作者新书发布:《Android系统多媒体进阶实战》优质专栏:Audio工程师进阶系列【原创干货持续更新中……】优质专栏:多媒体系统工程师系列【原创干货持续更新中……】优质视频课程:AAOS车载系统+AOSP14系统攻城狮入门视频实战课 Arthas使用使用方法 小秋蜀黍 大容量性能问题分析性能优化 一、Arthas简介可以用来快速定位java程序使用中的问题,查看程序运行过程中的各种信息。相对于之前jvm命令方式的定位方式,主要有以下特性:1)对源程序无侵入性,不需要重启或修改源码2)交互式命令行操作方式,方便使用3)功能丰富,对jvm各种性能,class信息等都能做到分析二、安装启动1、将下载的软件传入需要分析的服务容器中解压,然后执行java-jararthas-boot.jar命令运行 redis基础篇——redis常用的数据类型 石灰聪 redisredis 数据模型Redis的存储我们叫做key-value存储,或者叫做字典结构。key的最大长度限制是512M,值的限制不同,有的是用长度限制的,有的是用个数限制的。Redis是KV的数据库,Key-Value我们一般会用什么数据结构来存储它?哈希表。Redis的最外层确实是通过hashtable实现的,在Redis里面,这个哈希表怎么实现呢?我们看一下C语言的源码每个键值对都是一个dictEntry, 数位dp(算法篇) Moon2144 数据结构与算法算法 算法篇之数位dp数位dp概念:数位dp是一种计数用的dp,一般是要统计一个区级[l,r]内满足一些条件的数的个数所谓数位dp,就是对数位进行dp,也就是个位、十位等相对于普通的暴力枚举,数位dp快就快在它的记忆化,也就是说后面可能会利用到前面已经计算好的东西题型往往是:给定一个闭区间[L,R],求这个区间中满足"某种条件"的数的总数量题型特征:要求统计满足一定条件的数的数量(即,最终目的为计数); C++ 中的 std::timed_mutex 和 std::recursive_timed_mutex 哎呦,帅小伙哦 C++c++ 1、背景在多线程编程中,互斥锁(Mutex)是用于保护共享资源的重要工具。C++标准库提供了多种互斥锁类型,其中std::timed_mutex和std::recursive_timed_mutex是两种支持超时功能的互斥锁。在阅读FastDDS源码时,发现了这两种类型,以前没有使用过,顺便补盲记录下。2、std::timed_mutexstd::timed_mutex是C++11引入的一种互斥锁 小狐狸3.1.2版本源码,新增deepseek接口 CSDN专家-微编程 PHP源码PHP人工智能 小狐狸3.1.2版本源码,新增deepseek接口文件夹说明:1、后端:文件夹是后台文件2、.sql文件是数据库文件后台安装步骤:1、在宝塔新建个站点,php版本使用7.4,将“后端”文件夹里的文件上传到站点根目录,运行目录设置为/public2、导入数据库文件,数据库文件是/db.sql3、修改数据库连接配置,配置文件是/.env4、正式使用时,请把调试模式关闭:/.env文件第一行,true改 LeetCode详解之如何一步步优化到最佳解法:9. 回文数 杰瑞学AI LeetCode职业发展代码优化leetcode算法职场和发展面试改行学it数据结构学习方法 LeetCode详解系列的上一题链接:LeetCode详解之如何一步步优化到最佳解法:1.两数之和9.回文数本题题目链接:9.回文数-力扣(LeetCode)本题的目标不将整数转为字符串就可以解决这道题。解法1:暴力解法代码:classSolution:defisPalindrome(self,x:int)->bool:corresponding_str=str(x)length=len(corr vue集成codemirror代码编辑器 NMGWAP vue.js 点击上方“青年码农”关注回复“特效源码”可获取各种资料CodeMirror是一个用JavaScript为浏览器实现的通用文本编辑器。它专门用于编辑代码,并带有多种语言模式和插件,可实现更高级的编辑功能。本教程是基于vue2实现集成,使用vue-codemirror插件1.安装# npmnpm install vue-codemirror -S# y 温度传感器的工作原理 JZMSYYQ 温度传感器功能测试 温度是一个基本的物理量,自然界中的一切过程无不与温度密切相关。温度传感器是最早开发,应用zui广的一类传感器。温度传感器的shi场份额大大超过了其他的传感器。从17世纪初人们开始利用温度进行测量。在半导体技术的支持下,本世纪相继开发了半导体热电偶传感器、PN结温度传感器和集成温度传感器。与之相应,根据波与物质的相互作用规律,相继开发了声学温度传感器、红外传感器和微波传感器。两种不同材质的导体,如在 Java多线程【4】interrupt打断线程、两阶段终止模式 王乐乐君 Javajavajvm开发语言 系列文章目录Java多线程【1】synchronized对象锁、内置锁使用Java多线程【2】Javawait/notify的使用于同步模式保护性暂停Java多线程【3】同步模式之保护性暂停案例相亲问题Java多线程【4】interrupt线程的打断机制、两阶段终止模式Java多线程【5】异步模式之生产者消费者Java多线程【6】LockSupportpark/unpark原理和使用以及于wait ESP32-C3实现多个命名空间实现非易失存储(Arduino IDE) 知更鸟_z ESP32单片机 源码#includevoidsetup(){Serial.begin(115200);Serial.println();delay(2000);//在命名空间"userprefs"中保存用户数据PreferencesuserPrefs;userPrefs.begin("userprefs");intuserSetting=userPrefs.getInt("setting",0);//获取用户设置 Bug处理之执行自己编写代码中出现的AttributeError: ‘XXX’ object has no attribute’xxx’ NormanG 量化编程基础 操作系统Windows10.0;PythonIDE:Pycharm2018.02Python版本:python3.6(anaconda平台)自己编写脚本搭建股票数据库问题描述:自己编写python脚本解决一些数据库搭建过程中的繁琐过程,方便以后重复调用;编写的类在导入应用时报错AttributeError:‘XXX’objecthasnoattribute’xxx’,之前未出现过,因为是重新修改代 Pytorch实现之粒子群优化算法在GAN中的应用 这张生成的图像能检测吗 优质GAN模型训练自己的数据集生成对抗网络人工智能神经网络pytorch算法深度学习计算机视觉 简介简介:主要是采用了粒子群优化(PSO)算法来优化GAN的一个训练。PSO是一种是一种基于种群的随机优化技术。这种优化技术是通过粒子群进行的,粒子群在每次迭代中都会更新自己。对于给定的目标函数,这种方法利用一个搜索空间,在那里粒子群移动,找到所需的全局最小值。这些粒子与它们当前的环境局部相互作用,也与彼此相互作用,具有可接受的随机性质。通过合并粒子的当前速度,探索粒子的历史和粒子的邻居,可以知道 ASM系列四 利用Method 组件动态注入方法逻辑 lijingyao8206 字节码技术jvmAOP动态代理ASM 这篇继续结合例子来深入了解下Method组件动态变更方法字节码的实现。通过前面一篇,知道ClassVisitor 的visitMethod()方法可以返回一个MethodVisitor的实例。那么我们也基本可以知道,同ClassVisitor改变类成员一样,MethodVIsistor如果需要改变方法成员,注入逻辑,也可以 java编程思想 --内部类 百合不是茶 java内部类匿名内部类 内部类;了解外部类 并能与之通信 内部类写出来的代码更加整洁与优雅 1,内部类的创建 内部类是创建在类中的 package com.wj.InsideClass; /* * 内部类的创建 */ public class CreateInsideClass { public CreateInsideClass( web.xml报错 crabdave web.xml web.xml报错 The content of element type "web-app" must match "(icon?,display- name?,description?,distributable?,context-param*,filter*,filter-mapping*,listener*,servlet*,s 泛型类的自定义 麦田的设计者 javaandroid泛型 为什么要定义泛型类,当类中要操作的引用数据类型不确定的时候。 采用泛型类,完成扩展。 例如有一个学生类 Student{ Student(){ System.out.println("I'm a student....."); } } 有一个老师类 CSS清除浮动的4中方法 IT独行者 JavaScriptUIcss 清除浮动这个问题,做前端的应该再熟悉不过了,咱是个新人,所以还是记个笔记,做个积累,努力学习向大神靠近。CSS清除浮动的方法网上一搜,大概有N多种,用过几种,说下个人感受。 1、结尾处加空div标签 clear:both 1 2 3 4 .div 1 { background : #000080 ; border : 1px s Cygwin使用windows的jdk 配置方法 _wy_ jdkwindowscygwin 1.[vim /etc/profile] JAVA_HOME="/cgydrive/d/Java/jdk1.6.0_43" (windows下jdk路径为D:\Java\jdk1.6.0_43) PATH="$JAVA_HOME/bin:${PATH}" CLAS linux下安装maven 无量 mavenlinux安装 Linux下安装maven(转) 1.首先到Maven官网 下载安装文件,目前最新版本为3.0.3,下载文件为 apache-maven-3.0.3-bin.tar.gz,下载可以使用wget命令; 2.进入下载文件夹,找到下载的文件,运行如下命令解压 tar -xvf apache-maven-2.2.1-bin.tar.gz 解压后的文件夹 tomcat的https 配置,syslog-ng配置 aichenglong tomcathttp跳转到httpssyslong-ng配置syslog配置 1) tomcat配置https,以及http自动跳转到https的配置 1)TOMCAT_HOME目录下生成密钥(keytool是jdk中的命令) keytool -genkey -alias tomcat -keyalg RSA -keypass changeit -storepass changeit 关于领号活动总结 alafqq 活动 关于某彩票活动的总结 具体需求,每个用户进活动页面,领取一个号码,1000中的一个; 活动要求 1,随机性,一定要有随机性; 2,最少中奖概率,如果注数为3200注,则最多中4注 3,效率问题,(不能每个人来都产生一个随机数,这样效率不高); 4,支持断电(仍然从下一个开始),重启服务;(存数据库有点大材小用,因此不能存放在数据库) 解决方案 1,事先产生随机数1000个,并打 java数据结构 冒泡排序的遍历与排序 百合不是茶 java java的冒泡排序是一种简单的排序规则 冒泡排序的原理: 比较两个相邻的数,首先将最大的排在第一个,第二次比较第二个 ,此后一样; 针对所有的元素重复以上的步骤,除了最后一个 例题;将int array[] JS检查输入框输入的是否是数字的一种校验方法 bijian1013 js 如下是JS检查输入框输入的是否是数字的一种校验方法: <form method=post target="_blank"> 数字:<input type="text" name=num onkeypress="checkNum(this.form)"><br> </form> Test注解的两个属性:expected和timeout bijian1013 javaJUnitexpectedtimeout JUnit4:Test文档中的解释: The Test annotation supports two optional parameters. The first, expected, declares that a test method should throw an exception. If it doesn't throw an exception or if it [Gson二]继承关系的POJO的反序列化 bit1129 POJO 父类 package inheritance.test2; import java.util.Map; public class Model { private String field1; private String field2; private Map<String, String> infoMap 【Spark八十四】Spark零碎知识点记录 bit1129 spark 1. ShuffleMapTask的shuffle数据在什么地方记录到MapOutputTracker中的 ShuffleMapTask的runTask方法负责写数据到shuffle map文件中。当任务执行完成成功,DAGScheduler会收到通知,在DAGScheduler的handleTaskCompletion方法中完成记录到MapOutputTracker中 WAS各种脚本作用大全 ronin47 WAS 脚本 http://www.ibm.com/developerworks/cn/websphere/library/samples/SampleScripts.html 无意中,在WAS官网上发现的各种脚本作用,感觉很有作用,先与各位分享一下 获取下载 这些示例 jacl 和 Jython 脚本可用于在 WebSphere Application Server 的不同版本中自 java-12.求 1+2+3+..n不能使用乘除法、 for 、 while 、 if 、 else 、 switch 、 case 等关键字以及条件判断语句 bylijinnan switch 借鉴网上的思路,用java实现: public class NoIfWhile { /** * @param args * * find x=1+2+3+....n */ public static void main(String[] args) { int n=10; int re=find(n); System.o Netty源码学习-ObjectEncoder和ObjectDecoder bylijinnan javanetty Netty中传递对象的思路很直观: Netty中数据的传递是基于ChannelBuffer(也就是byte[]); 那把对象序列化为字节流,就可以在Netty中传递对象了 相应的从ChannelBuffer恢复对象,就是反序列化的过程 Netty已经封装好ObjectEncoder和ObjectDecoder 先看ObjectEncoder ObjectEncoder是往外发送 spring 定时任务中cronExpression表达式含义 chicony cronExpression 一个cron表达式有6个必选的元素和一个可选的元素,各个元素之间是以空格分隔的,从左至右,这些元素的含义如下表所示: 代表含义 是否必须 允许的取值范围 &nb Nutz配置Jndi ctrain JNDI 1、使用JNDI获取指定资源: var ioc = { dao : { type :"org.nutz.dao.impl.NutDao", args : [ {jndi :"jdbc/dataSource"} ] } } 以上方法,仅需要在容器中配置好数据源,注入到NutDao即可. 解决 /bin/sh^M: bad interpreter: No such file or directory daizj shell 在Linux中执行.sh脚本,异常/bin/sh^M: bad interpreter: No such file or directory。 分析:这是不同系统编码格式引起的:在windows系统中编辑的.sh文件可能有不可见字符,所以在Linux系统下执行会报以上异常信息。 解决: 1)在windows下转换: 利用一些编辑器如UltraEdit或EditPlus等工具 [转]for 循环为何可恨? dcj3sjt126com 程序员读书 Java的闭包(Closure)特征最近成为了一个热门话题。 一些精英正在起草一份议案,要在Java将来的版本中加入闭包特征。 然而,提议中的闭包语法以及语言上的这种扩充受到了众多Java程序员的猛烈抨击。 不久前,出版过数十本编程书籍的大作家Elliotte Rusty Harold发表了对Java中闭包的价值的质疑。 尤其是他问道“for 循环为何可恨?”[http://ju Android实用小技巧 dcj3sjt126com android 1、去掉所有Activity界面的标题栏 修改AndroidManifest.xml 在application 标签中添加android:theme="@android:style/Theme.NoTitleBar" 2、去掉所有Activity界面的TitleBar 和StatusBar 修改AndroidManifes Oracle 复习笔记之序列 eksliang Oracle 序列sequenceOracle sequence 转载请出自出处:http://eksliang.iteye.com/blog/2098859 1.序列的作用 序列是用于生成唯一、连续序号的对象 一般用序列来充当数据库表的主键值 2.创建序列语法如下: create sequence s_emp start with 1 --开始值 increment by 1 --増长值 maxval 有“品”的程序员 gongmeitao 工作 完美程序员的10种品质 完美程序员的每种品质都有一个范围,这个范围取决于具体的问题和背景。没有能解决所有问题的 完美程序员(至少在我们这个星球上),并且对于特定问题,完美程序员应该具有以下品质: 1. 才智非凡- 能够理解问题、能够用清晰可读的代码翻译并表达想法、善于分析并且逻辑思维能力强 (范围:用简单方式解决复杂问题) 使用KeleyiSQLHelper类进行分页查询 hvt sql.netC#asp.nethovertree 本文适用于sql server单主键表或者视图进行分页查询,支持多字段排序。KeleyiSQLHelper类的最新代码请到http://hovertree.codeplex.com/SourceControl/latest下载整个解决方案源代码查看。或者直接在线查看类的代码:http://hovertree.codeplex.com/SourceControl/latest#HoverTree.D SVG 教程 (三)圆形,椭圆,直线 天梯梦 svg SVG <circle> SVG 圆形 - <circle> <circle> 标签可用来创建一个圆: 下面是SVG代码: <svg xmlns="http://www.w3.org/2000/svg" version="1.1"> <circle cx="100" c 链表栈 luyulong java数据结构 public class Node { private Object object; private Node next; public Node() { this.next = null; this.object = null; } public Object getObject() { return object; } public 基础数据结构和算法十:2-3 search tree sunwinner Algorithm2-3 search tree Binary search tree works well for a wide variety of applications, but they have poor worst-case performance. Now we introduce a type of binary search tree where costs are guaranteed to be loga spring配置定时任务 stunizhengjia springtimer 最近因工作的需要,用到了spring的定时任务的功能,觉得spring还是很智能化的,只需要配置一下配置文件就可以了,在此记录一下,以便以后用到: //------------------------定时任务调用的方法------------------------------ /** * 存储过程定时器 */ publi ITeye 8月技术图书有奖试读获奖名单公布 ITeye管理员 活动 ITeye携手博文视点举办的8月技术图书有奖试读活动已圆满结束,非常感谢广大用户对本次活动的关注与参与。 8月试读活动回顾: http://webmaster.iteye.com/blog/2102830 本次技术图书试读活动的优秀奖获奖名单及相应作品如下(优秀文章有很多,但名额有限,没获奖并不代表不优秀): 《跨终端Web》 gleams:http 按字母分类: ABCDEFGHIJKLMNOPQRSTUVWXYZ其他