首页 文章 精选 留言 我的

精选列表

搜索[分区技术],共10000篇文章
优秀的个人博客,低调大师

Xamarin 技术全解析

Xamarin 是一套基于C#语言的跨平台移动应用开发工具,今年2月份微软宣布收购Xamarin,而后在4月份进行的Build大会上微软宣布将会在各个版本的Visual Studio中免费提供Xamarin,并且宣布Xamarin SDK开源。 本文主要阐述Xamarin是什么,它能做什么以及它是如何跨平台的。 1. 什么是Xamarin Xamarin 是一个跨平台的移动开发工具,由 Mono 发展而来。开发人员可以使用 C# 为iOS,Android, Mac以及Windows Phone开发原生应用。 Xamarin 的跨平台开发思路是:使用 C# 来完成所有平台共用的,和平台无关的 app 逻辑部分;由于各个平台的 UI 和交互不同,再使用由 Xamarin 封装好的 C# API 来访问和操控 native 的控件,分别进行不同平台的 UI 开发。 如下图: 另外Xamarin还提供了Xamarin.Forms UI工具包,Xamarin.Forms可以帮助开发人员快速的构建跨平台的UI,通过一次编码,生成多个平台的原生UI界面,稍后本文会讲述Xamarin.Forms的使用方法以及实现原理。 2. Xamarin能做什么 Xamarin主要由Xamarin.iOS,Xamarin.Android以及Xamarin.Forms组成,主要功能也有着三部分组成: 2.1 使用Xamarin.iOS来构建iOS原生应用 下面会使用Mac OS X上的Xamarin Studio来演示如何构建iOS应用: - 打开Xamarin Studio - 新建一个项目,选择iOS - App - Single View App - 点击下一步,输入App 名称, 例如:FirstXamariniOS,一路点击下一步,工程创建完成。 下面是生成的iOS工程结构截图: 如果有Xcode使用经验的话会发现,这个Xamarin iOS工程的项目结构与Xcode的结构很类似,都包含了AppDelegate类,默认的ViewController以及Main StoryBoard文件,基本的类名称都是一致的。 打开Main.storyboard 文件,可以从Toolbox上拖拽一些原生控件到View Controller上,与Xcode中使用方式一致,但是有一些功能没有Xcode 强大,比如设置View的Auto layout等等,如下图: 运行上面的工程,就可以在模拟器中查看效果了。 从上面来看来说使用Xamarin进行iOS编程需要有一定的iOS App开发知识,需要熟悉iOS UI框架(Cocoa Touch)等等,即便使用Xamarin开发应用,也绕不过原生底层的这些东西。 2.2 使用Xamarin.Android来构建Android原生应用 下面会使用Mac OS X上的Xamarin Studio来演示如何构建iOS应用: - 打开Xamarin Studio - 新建一个项目,选择Android - App - Android App - 点击下一步,输入App 名称, 例如:FirstXamarinAndroid,一路点击下一步,工程创建完成。 下面是生成的Android工程结构截图: 如果有Eclipse进行Android编程经验的话会发现,这个Xamarin Android工程的项目结构与Eclipse的结构很类似,都包含了默认的MainActivity以及布局文件,基本的类名称都是一致的。 打开Main.axml文件,可以从Toolbox上拖拽一些原生控件到View Controller上,与Eclipse的体验类似,也可以通过编辑XML的方式更改界面。 同样从上面来看来说使用Xamarin进行Android编程需要有一定的Android App开发知识,需要熟悉Android UI框架等等,原生底层的东西还是需要熟悉的。 2.3 使用Xamarin.Forms来构建跨平台的应用 Xamarin.Forms 是一个创建跨平台用户界面的库,通过Xamarin.Forms 可以一次编码生成基于各个移动平台(iOS, Android, Windows Phone)的应用界面。 Xamarin.Forms提供了更高层次的一层UI组件抽象,这些组件在进行最终呈现的时候,会以原生控件的方式表现出来,也就是说每一个Xmarin.Forms的控件最终会有多个平台的原生呈现逻辑,如下图中,Xamarin.Forms的Entry控件,对应的原生呈现为: 使用Xamarin.Forms构建跨平台应用的一个缺陷就是只能使用Xamarin.Forms包中的控件,会有一些限制。 如果先了解更多关于如何使用Xamarin.Forms构建跨平台应用,请参见文章:Xamarin.Forms入门-使用 Xamarin.Forms 来创建跨平台的用户界面。 3. Xamarin实现原理 3.1 Xamarin.Android 实现原理 在讲述Xamarin.Android架构之前,需要先了解一些Android应用程序的背景知识: - Android应用程序试运行在Dalvik虚拟机中的,每一个应用程序对应一个单独的虚拟机实例,其代码在虚拟机的解释下得以执行。 -Dalvik主要是完成对象生命周期管理,堆栈管理,线程管理,安全和异常管理,以及垃圾回收等等重要功能。 -不同于Java虚拟机运行java字节码,Dalvik虚拟机运行的是其专有的文件格式 Xamarin.Android架构图(ART 是Android 虚拟机Dalvik): Android Callable Wrappers(ACW) 使用C#开发的Android应用程序在运行的时候,C#代码是在Mono虚拟机中执行的,而Mono虚拟机是寄宿在Dalvik虚拟机中运行的,所有的C#代码都通过ACW的方式被调用。 由于需要打包Mono环境,使用C#开发的Android应用的APK文件会比原生开发的大,执行效率也会差一些。 Managed Callable Wrapper(MCW) 如果需要在C#中调用一些系统的功能或者Java实现的类库,该如何调用那? 答案就是MCW,MCW就是一个JNI桥梁,可以使用托管代码调用Android的代码。MCW将整个Android.* 以及相关的命名空间通过jar绑定的方式暴露出来,是的C#可以调用。 3.2 Xamarin.iOS 实现原理 对于开发者来说,Xamarin.IOS相对于Xamarin.Android就要简单很多了,我们用C#开发的iOS应用程序在被编译成IL代码之后,然后转交给Apple complier直接编译成iOS的本地机器码,也就是说C#写的iOS应用程序和Objective-C 写的是一样的。 透过 Ahead-of-Time (AOT) 编译程序,直接将Xamarin.iOS程序编译为ARM的执行档。编译封装完成的应用程序被直接编译为原生的二进制执行文件。 3.3 Xamarin.Forms实现原理 在Xamarin Studio中构建Xamarin.Forms跨平台的应用的时候,会生成Android以及iOS单独的项目工程,两者共享业务逻辑以及一些UI界面,在打包生成App的时候,是分开进行的,两者互不影响。每个平台的实现原理与上面讲的是一样的。 本文转自 powertoolsteam 51CTO博客,原文链接:http://blog.51cto.com/powertoolsteam/1782877,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android热修复技术

更新版本一直以来是移动端的一大痛点,各大公司也推出了相应的解决方案。 1)AndFix(阿里巴巴):兼容性不太好,亲试过,上线反馈崩溃问题特别严重。 2)Tinker(微信):集成起来是相当的麻烦 看完http://blog.csdn.net/u010983881/article/details/53196574这个链接,基本就能接入了。 但是还有一些需要补充的 1》Android的一些编译打包命令:http://blog.csdn.net/dakaring/article/details/44944139 2》怎么保留自己的Application:http://blog.csdn.net/cao126197103/article/details/54693760 3》Tinker id is not set错误:(将tinker id写死,如果没有使用git管理代码就会出现此问题) 4》加载补丁包失败,通过tinker过滤日志,查看失败原因。有时也要结合源码 5》Tinker多渠道打包:https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97#%E5%A4%9Aflavor%E6%89%93%E5%8C%85 6》常见问题:http://blog.csdn.net/tyk9999tyk/article/details/53391519 7》TinkerInstaller.install(this) Tinker instance is already set的问题 http://www.jianshu.com/p/9680a58e67fe 8》使用了Tinker的项目,在运行的时候无法使用Instant-Run。 线上应用的问题: 1)app如果打了一次补丁,再从市场上更新app,点击桌面应用图标,app启动不起来。(巨坑!!) 后来给Tinker项目提了个issue,才得知我把tinkerid写死了。tinkerid应该每个版本都不一样 比如3.3.7更新了补丁,到了3.3.8版本,然后从市场更新到了3.3.9版本。这3个版本的tinkerid要两两互不一样。否则就会出现以上问题。 2) 本文转自屠夫章哥 51CTO博客,原文链接:http://blog.51cto.com/4259297/1929242,如需转载请自行联系原作者

优秀的个人博客,低调大师

《Spring技术内幕》——导读

目 录 前言第1章 Spring的设计理念和整体架构1.1 Spring的各个子项目1.2 Spring的设计目标1.3 Spring的整体架构1.4 Spring的应用场景1.5 小结第一部分 Spring核心实现篇第2章 Spring Framework的核心:IoC容器的实现 2.1 Spring IoC容器概述 2.2 IoC容器系列的设计与实现:BeanFactory和ApplicationContext2.3 IC容器的初始化过程2.4 IoC容器的依赖注入2.5 容器其他相关特性的设计与实现2.6 小结第3章 Spring AOP的实现 3.1 Spring AOP概述3.2 Spring AOP的设计与实现

优秀的个人博客,低调大师

Apache Storm技术实战

WordCountTopology “源码走读系列”从代码层面分析了storm的具体实现,接下来通过具体的实例来说明storm的使用。因为目前storm已经正式迁移到Apache,文章系列也由twitter storm转为apache storm. WordCountTopology 使用storm来统计文件中的每个单词的出现次数。 通过该例子来说明tuple发送时的几个要素 source component 发送源 destination component 接收者 stream 消息通道 tuple 消息本身 本文涉及到的开发环境搭建可以参考前面的两篇博文。 arch linux简明安装指南 在archlinux上搭建storm cluster awk实现 其实对文件中的单词进行统计是Linux下一个很常见的任务,用awk就可以轻松的解决(如果文件不是太大的话),下面是进行word counting的awk脚本,将其保存为名为wordcount.awk文件。 wordcount.awk { for (i = 1; i<=NF; i++) freq[$i]++ } END{ for (word in freq) printf "%s\t%d\n",word,freq[word] } 运行该脚本,对文件中的单词进行统计 gawk -f wordcount.awk filename 原始版本 从github上复制内容 git clone https://github.com/nathanmarz/storm-starter.git 编译运行 lein deps lein compile java -cp $(lein classpath) WordCountTopology main函数 main函数的主要内容 TopologyBuilder builder = new TopologyBuilder(); builder.setSpout("spout", new RandomSentenceSpout(), 5); builder.setBolt("split", new SplitSentence(), 8).shuffleGrouping("spout"); builder.setBolt("count", new WordCount(), 12).fieldsGrouping("split", new Fields("word")); 注意:grouping操作的时候,如果没有显示指定stream id,则使用的是default stream. 如shuffleGrouping("spout")表示从名为spout的component中接收从default stream发送过来的tuple. 改进版本 在原始版本中,spout不停的向split bolt随机发送句子,Count bolt统计每个单词出现的次数。 那么能不能让Spout在读取完文件之后,通知下游的bolt显示最柊的统计结果呢? 要想达到上述的改进目标,采用如上图所示的结构即可。改变的地方如下, 在Spout中添加一个SUCCESS_STREAM 添加只有一个运行实例的statistics bolt 当spout读取完文件内容之后,通过SUCCESS_STREAM告诉statistics bolt,文件已经处理完毕,可以打印当前的统计结果 RandomSentenceSpout.java declareOutputFields 添加SUCCESS_STREAM @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(new Fields("word")); declarer.declareStream("SUCCESS_STREAM",new Fields("word")); } nextTuple 使用SUCCESS_STREAM通知下游,文件处理完毕 @Override public void nextTuple() { Utils.sleep(100); String[] sentences = new String[]{ "the cow jumped over the moon", "an apple a day keeps the doctor away", "four score and seven years ago", "snow white and the seven dwarfs", "i am at two with nature" }; if ( count == sentences.length ) { System.out.println(count+" try to emit tuple by success_stream"); _collector.emit("SUCCESS_STREAM",new Values(sentences[0])); count++; }else if ( count < sentences.length ){ _collector.emit(new Values(sentences[count])); count++; } } WordCountTopology.java 添加静态类WordCount2 public static class WordCount2 extends BaseBasicBolt { Map<String, Integer> counts = new HashMap<String, Integer>(); @Override public void execute(Tuple tuple, BasicOutputCollector collector) { if ( tuple.getSourceStreamId() == "SUCCESS_STREAM" ) { System.out.println("prepare to print the statistics"); for (String key : counts.keySet()) { System.out.println(key+"\t"+counts.get(key)); } System.out.println("finish printing"); }else { String word = tuple.getString(0); Integer count = counts.get(word); if (count == null) count = 0; count++; counts.put(word, count); } } main函数 将spout的并行数由5改为1 builder.setSpout("spout", new RandomSentenceSpout(), 1); 在原有的Topology中添加WordCount2 Bolt builder.setBolt("count2", new WordCount2(), 1).globalGrouping("count").globalGrouping("spout","SUCCESS_STREAM"); WordCount2 Bolt会接收从Count Bolt通过default stream发送的tuple,同时接收Spout通过SUCCESS_STREAM发送的tuple,也就是说wordcount2会接收从两个stream来的数据。 编译 编译修改后的源文件 cd $STROM_STARTER lein compile storm.starter 可能会出现以下异常信息,该异常可以忽略。 Exception in thread "main" java.io.FileNotFoundException: Could not locate storm/starter/WordCountTopology__init.class or storm/starter/WordCountTopology.clj on classpath: 运行 在local模式下运行修改后的WordCountTopology java -cp $(lein classpath) storm.starter.WordCountTopology 如果一切正常,日志如下所示,线程的名字可能会有所不同。 moon 1 score 1 cow 1 doctor 1 over 1 nature 1 snow 1 four 1 keeps 1 with 1 a 1 white 1 dwarfs 1 at 1 the 4 and 2 i 1 two 1 away 1 seven 2 apple 1 am 1 an 1 jumped 1 day 1 years 1 ago 1 结果验证 可以将WordCountTopology的运行结果和awk脚本的运行结果相比对,结果应该是一致的。 小技巧 awk脚本的执行结果存为一个文件result1.log, WordCountTopology的输出中单词统计部分存为result2.log 用vim打开result1.log,进行sorting,保存结果;用vim打开result2.log,进行sorting,保存。 然后用vimdiff来进行比较 vimdiff result1.log result2.log BasicDRPCTopology 本文通过BasicDRPCTopology的实例来分析DRPCTopology在提交的时候, Topology中究竟含有哪些内容? BasicDRPCTopology main函数 DRPC 分布式远程调用(这个说法有意思,远程调用本来就是分布的,何须再加个D, <头文字D>看多了, :) public static void main(String[] args) throws Exception { LinearDRPCTopologyBuilder builder = new LinearDRPCTopologyBuilder("exclamation"); builder.addBolt(new ExclaimBolt(), 3); Config conf = new Config(); if (args == null || args.length == 0) { LocalDRPC drpc = new LocalDRPC(); LocalCluster cluster = new LocalCluster(); cluster.submitTopology("drpc-demo", conf, builder.createLocalTopology(drpc)); for (String word : new String[]{ "hello", "goodbye" }) { System.out.println("Result for \"" + word + "\": " + drpc.execute("exclamation", word)); } cluster.shutdown(); drpc.shutdown(); } else { conf.setNumWorkers(3); StormSubmitter.submitTopology(args[0], conf, builder.createRemoteTopology()); } } 问题: 上面的代码中只是添加了一个bolt,并没有设定Spout. 我们知道一个topology中最起码得有一个Spout,那么这里的Spout又隐身于何处呢? 关键的地方就在builder.createLocalTopology,调用关系如下 LinearDRPCTopologyBuilder::createLocalTopology LinearDRPCTopologyBuilder::createTopology() LinearDRPCTopologyBuilder::createTopology(new DRPCSpout(_function)) 原来DRPCTopology中使用的Spout是DRPCSpout. LinearDRPCTopology::createTopology 既然代码已经读到此处,何不再进一步看看createTopology的实现. 简要说明一下该段代码的处理逻辑: 设置DRPCSpout 以bolt为入参,创建CoordinatedBolt 添加JoinResult Bolt 添加ReturnResult Bolt: ReturnResultBolt连接到DRPCServer,并返回结果 private StormTopology createTopology(DRPCSpout spout) { final String SPOUT_ID = "spout"; final String PREPARE_ID = "prepare-request"; TopologyBuilder builder = new TopologyBuilder(); builder.setSpout(SPOUT_ID, spout); builder.setBolt(PREPARE_ID, new PrepareRequest()) .noneGrouping(SPOUT_ID); int i=0; for(; i<_components.size();i++) { Component component = _components.get(i); Map<String, SourceArgs> source = new HashMap<String, SourceArgs>(); if (i==1) { source.put(boltId(i-1), SourceArgs.single()); } else if (i>=2) { source.put(boltId(i-1), SourceArgs.all()); } IdStreamSpec idSpec = null; if(i==_components.size()-1 && component.bolt instanceof FinishedCallback) { idSpec = IdStreamSpec.makeDetectSpec(PREPARE_ID, PrepareRequest.ID_STREAM); } BoltDeclarer declarer = builder.setBolt( boltId(i), new CoordinatedBolt(component.bolt, source, idSpec), component.parallelism); for(Map conf: component.componentConfs) { declarer.addConfigurations(conf); } if(idSpec!=null) { declarer.fieldsGrouping(idSpec.getGlobalStreamId().get_componentId(), PrepareRequest.ID_STREAM, new Fields("request")); } if(i==0 && component.declarations.isEmpty()) { declarer.noneGrouping(PREPARE_ID, PrepareRequest.ARGS_STREAM); } else { String prevId; if(i==0) { prevId = PREPARE_ID; } else { prevId = boltId(i-1); } for(InputDeclaration declaration: component.declarations) { declaration.declare(prevId, declarer); } } if(i>0) { declarer.directGrouping(boltId(i-1), Constants.COORDINATED_STREAM_ID); } } IRichBolt lastBolt = _components.get(_components.size()-1).bolt; OutputFieldsGetter getter = new OutputFieldsGetter(); lastBolt.declareOutputFields(getter); Map<String, StreamInfo> streams = getter.getFieldsDeclaration(); if(streams.size()!=1) { throw new RuntimeException("Must declare exactly one stream from last bolt in LinearDRPCTopology"); } String outputStream = streams.keySet().iterator().next(); List<String> fields = streams.get(outputStream).get_output_fields(); if(fields.size()!=2) { throw new RuntimeException("Output stream of last component in LinearDRPCTopology must contain exactly two fields. The first should be the request id, and the second should be the result."); } builder.setBolt(boltId(i), new JoinResult(PREPARE_ID)) .fieldsGrouping(boltId(i-1), outputStream, new Fields(fields.get(0))) .fieldsGrouping(PREPARE_ID, PrepareRequest.RETURN_STREAM, new Fields("request")); i++; builder.setBolt(boltId(i), new ReturnResults()) .noneGrouping(boltId(i-1)); return builder.createTopology(); } Bolt 处理逻辑: 在接收到的每一个单词后面添加'!'. public static class ExclaimBolt extends BaseBasicBolt { @Override public void execute(Tuple tuple, BasicOutputCollector collector) { String input = tuple.getString(1); collector.emit(new Values(tuple.getValue(0), input + "!")); } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(new Fields("id", "result")); } } 运行 java -cp $(lein classpath) storm.starter.BasicDRPCTopology TridentWordCount 介绍TridentTopology的使用,重点分析newDRPCStream和stateQuery的实现机理。 使用TridentTopology进行数据处理的时候,经常会使用State来保存一些状态,这些保存起来的State通过stateQuery来进行查询。问题恰恰在这里产生,即对state进行更新的Stream和尔后进行stateQuery的Stream并非同一个,那么它们之间是如何关联起来的呢。 在TridentTopology中,有一些Processor可能会同处于一个Bolt中,这些Processor形成一个processing chain, 那么Tuple又是如何在这些Processor之间进行传递的呢。 TridentWordCount 编译和运行 lein compile storm.starter.trident.TridentWordCount java -cp $(lein classpath) storm.starter.trident.TridentWordCount main函数 public static void main(String[] args) throws Exception { Config conf = new Config(); conf.setMaxSpoutPending(20); if (args.length == 0) { LocalDRPC drpc = new LocalDRPC(); LocalCluster cluster = new LocalCluster(); cluster.submitTopology("wordCounter", conf, buildTopology(drpc)); for (int i = 0; i < 100; i++) { System.out.println("DRPC RESULT: " + drpc.execute("words", "cat the dog jumped")); Thread.sleep(1000); } } else { conf.setNumWorkers(3); StormSubmitter.submitTopology(args[0], conf, buildTopology(null)); } } buildTopology public static StormTopology buildTopology(LocalDRPC drpc) { FixedBatchSpout spout = new FixedBatchSpout(new Fields("sentence"), 3, new Values("the cow jumped over the moon"), new Values("the man went to the store and bought some candy"), new Values("four score and seven years ago"), new Values("how many apples can you eat"), new Values("to be or not to be the person")); spout.setCycle(true); TridentTopology topology = new TridentTopology(); TridentState wordCounts = topology.newStream("spout1", spout).parallelismHint(16).each(new Fields("sentence"), new Split(), new Fields("word")).groupBy(new Fields("word")).persistentAggregate(new MemoryMapState.Factory(), new Count(), new Fields("count")).parallelismHint(16); topology.newDRPCStream("words", drpc).each(new Fields("args"), new Split(), new Fields("word")).groupBy(new Fields( "word")).stateQuery(wordCounts, new Fields("word"), new MapGet(), new Fields("count")).each(new Fields("count"), new FilterNull()).aggregate(new Fields("count"), new Sum(), new Fields("sum")); return topology.build(); } 示意图 在整个topology中,有两个不同的spout。 运行结果该如何理解 此图有好几个问题 PartitionPersistProcessor和StateQueryProcessor同处于一个bolt,该bolt为SubtopologyBolt SubtopologyBolt有来自多个不同Stream的输入,根据不同的Streamid找到对应的InitialReceiver drpcspout在执行的时候,是一直不停的emit消息到SubtopologyBolt,还是发送完一次消息就停止发送 不同的tuple,其sourcestream不一样,根据SourceStream,找到对应的InitialReceiver Map<String, InitialReceiver> _roots = new HashMap(); 状态更新 进行状态更新的Processor名为PartitionPersistProcessor execute 记录哪些tuple需要进行状态更新 finishBatch 状态真正更新是发生在finishBatch阶段 persistentAggregate PartitionPersistProcessor SubtopologyBolt::execute PartitionPersistProcessor::finishBatch _updater::updateState Snapshottable::update 当状态更新的时候,状态查询是否会发生? 状态查询 进行状态查询的Processor名为StateQueryProcessor execute finishBatch 查询的时候,首先调用batchRetrieive来获得最新的状态更新结果,再对每个最新的结果使用_function来进行处理。 调用层次 SubtopologyBolt::finishBatch StateQueryProcessor::finishBatch _function.batchRetrieve _function.execute 将处理过的结果发送给下一跳进行处理 消息的传递 TridentTuple 如何决定bolt内部的哪个processor来处理接收到的消息,这个是根据不同的Stream来判断InitialReceiver完成。 当SubtopologyBolt接收到最原始的tuple时,根据streamid找到InitialReceiver后,InitialReceiver在receive函数中作的第一件事情就是根据tuple来创建一个tridenttuple,tridenttuple会被处在同一个SubtopologyBolt中的processor一一处理,处理的结果是保存在tridenttuple和processorcontext中。 ProcessorContext ProcessorContext记录两个重要的信息,即当前的batchId和batchState. public class ProcessorContext { public Object batchId; public Object[] state; public ProcessorContext(Object batchId, Object[] state) { this.batchId = batchId; this.state = state; } } TridentCollector tridentcollector在emit的时候将消息由各个TupleReceiver进行处理。目前仅有BridgeReceiver实现了该接口。 BridgeReceiver负责将消息发送给另外的Bolt进行处理。这里说的“另外的Bolt”是指Vanilla Topology中的Bolt.

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册