
关注.NetCore新趋向,分享.NetCore知识。
前言 许多个人计算机和工作站都有多个CPU核心,可以同时执行多个线程。利用硬件的特性,使用并行化代码以在多个处理器之间分配工作。 应用场景 文件批量上传 并行上传单个文件。也可以把一个文件拆成几段分开上传,加快上传速度。 数据分批计算 如几百万数据可以拆成许多无关联的部分,并行计算处理。最后聚合。 数据推送 也是需要将数据拆解后,并行推送。 任务并行库-数据并行 如果在一个循环内在每次迭代只执行少量工作或者它没有运行多次迭代,那么并行化的开销可能会导致代码运行的更慢。使用并行之前,应该对线程(锁,死锁,竞争条件)应该有基本的了解。 Parallel.For /// <summary> /// 正常循环 /// </summary> public void FormalDirRun() { long totalSize = 0; var dir = @"E:\LearnWall\orleans";//args[1]; String[] files = Directory.GetFiles(dir); stopwatch.Restart(); for (var i = 0; i < files.Length; i++) { FileInfo fi = new FileInfo(files[i]); long size = fi.Length; Interlocked.Add(ref totalSize, size); } stopwatch.Stop(); Console.WriteLine($"FormalDirRun------{files.Length} files, {totalSize} bytes,time:{stopwatch.ElapsedMilliseconds},Dir:{dir}"); } /// <summary> /// 并行循环 /// </summary> public void ParallelForDirRun() { long totalSize = 0; var dir = @"E:\LearnWall\orleans";//args[1]; String[] files = Directory.GetFiles(dir); stopwatch.Restart(); Parallel.For(0, files.Length, index => { FileInfo fi = new FileInfo(files[index]); long size = fi.Length; Interlocked.Add(ref totalSize, size); }); stopwatch.Stop(); Console.WriteLine($"ParallelForDirRun-{files.Length} files, {totalSize} bytes,time:{stopwatch.ElapsedMilliseconds},Dir:{dir}"); } 从下图对比接口可以看出当循环体内方法执行时间很短时,并行时间反而更长。这块会有更细致的补充。 FormalDirRun------20 files, 255618 bytes,time:0,Dir:E:\LearnWall\orleans ParallelForDirRun-20 files, 255618 bytes,time:6,Dir:E:\LearnWall\orleans 我们追加一些延时操作如Thread.Sleep,但这应该不是好好例子...但我只想演示效果就行了。 Thread.Sleep(1000); 查看结果得到,当方法内有阻塞延时一秒后,两者速度错了七倍。 FormalDirRun------20 files, 255618 bytes,time:20011,Dir:E:\LearnWall\orleans ParallelForDirRun-20 files, 255618 bytes,time:3007,Dir:E:\LearnWall\orleans 矩阵和秒表示例 Parallel.ForEach 为了并行速度的最大化,我们应该尽量减少在并行内对共享资源的访问,如Console.Write,文件日志等...但这里为了显示效果,就用了。 public void ParallelForEachDirRun() { long totalSize = 0; var dir = @"E:\LearnWall\orleans";//args[1]; String[] files = Directory.GetFiles(dir); stopwatch.Restart(); Parallel.ForEach(files, (current) => { FileInfo fi = new FileInfo(current); long size = fi.Length; Interlocked.Add(ref totalSize, size); Console.WriteLine($"name:{fi.Name}"); }); stopwatch.Stop(); Console.WriteLine($"ParallelForEachDirRun-{files.Length} files, {totalSize} bytes,Time:{stopwatch.ElapsedMilliseconds}"); } name:.gitignore name:build.sh . . . name:TestAll.cmd ParallelForEachDirRun-20 files, 255618 bytes,Time:17 Parallel.For 线程局部变量 public void ParallelForForThreadLocalVariables() { int[] nums = Enumerable.Range(0, 1000000).ToArray(); long total = 0; // Use type parameter to make subtotal a long, not an int Parallel.For<long>(0, nums.Length, () => 0, (j,loop, subtotal) => { subtotal += nums[j]; return subtotal; }, (x) => Interlocked.Add(ref total, x) ); Console.WriteLine("The total is {0:N0}", total); Console.WriteLine("Press any key to exit"); Console.ReadKey(); } 结果如下: The total is 499,999,509,000 每个For方法的前两个参数指定开始和结束迭代值。在此方法的重载中,第三个参数是初始化本地状态的位置。在此上下文中,本地状态表示一个变量,其生命周期从当前线程上的循环的第一次迭代之前延伸到最后一次迭代之后。 第三个参数的类型是Func ,其中TResult是将存储线程本地状态的变量的类型。它的类型由调用泛型For (Int32,Int32,Func ,Func ,Action )方法时提供的泛型类型参数定义,在这种情况下是Int64。type参数告诉编译器将用于存储线程局部状态的临时变量的类型。在此示例中,表达式() => 0(或Function() 0在Visual Basic中)将线程局部变量初始化为零。如果泛型类型参数是引用类型或用户定义的值类型,则表达式如下所示: () => new MyClass() 这块内容比较繁琐,一句话来说:前两个参数是开始和结束值,第三个是根据For泛型而初始化的值。我其实也没看太懂这块。.net Framework源码如下,.netcore的不知道: public static ParallelLoopResult For<TLocal>( int fromInclusive, int toExclusive, Func<TLocal> localInit, Func<int, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally) { if (body == null) { throw new ArgumentNullException("body"); } if (localInit == null) { throw new ArgumentNullException("localInit"); } if (localFinally == null) { throw new ArgumentNullException("localFinally"); } return ForWorker( fromInclusive, toExclusive, s_defaultParallelOptions, null, null, body, localInit, localFinally); } /// </summary> /// <typeparam name="TLocal">本地数据的类型.</typeparam> /// <param name="fromInclusive">循环开始数</param> /// <param name="toExclusive">循环结束数</param> /// <param name="parallelOptions">选项</param> /// <param name="body">循环执行体</param> /// <param name="bodyWithState">ParallelState的循环体重载。</param> /// <param name="bodyWithLocal">线程局部状态的循环体重载。</param> /// <param name="localInit">一个返回新线程本地状态的选择器函数。</param> /// <param name="localFinally">清理线程本地状态的清理函数。</param> /// <remarks>只能提供一个身体参数(即它们是独占的)。</remarks> /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult"/> structure.</returns> private static ParallelLoopResult ForWorker<TLocal>( int fromInclusive, int toExclusive, ParallelOptions parallelOptions, Action<int> body, Action<int, ParallelLoopState> bodyWithState, Func<int, ParallelLoopState, TLocal, TLocal> bodyWithLocal, Func<TLocal> localInit, Action<TLocal> localFinally) { . . . } Parallel.ForEach线程局部变量 /// <summary> /// /// </summary> public void ParallelForEachThreadLocalVariables() { int[] nums = Enumerable.Range(0, 1000000).ToArray(); long total = 0; // First type parameter is the type of the source elements // Second type parameter is the type of the thread-local variable (partition subtotal) Parallel.ForEach<int, long>(nums, // source collection () => 0, // method to initialize the local variable (j, loop, subtotal) => // method invoked by the loop on each iteration { subtotal += j; //modify local variable return subtotal; // value to be passed to next iteration }, // Method to be executed when each partition has completed. // finalResult is the final value of subtotal for a particular partition. (finalResult) => Interlocked.Add(ref total, finalResult) ); Console.WriteLine("The total from Parallel.ForEach is {0:N0}", total); } ForEach的源码如下 /// <summary> /// Executes a for each operation on an <see cref="T:System.Collections.IEnumerable{TSource}"/> /// in which iterations may run in parallel. /// </summary> /// <typeparam name="TSource">The type of the data in the source.</typeparam> /// <param name="source">An enumerable data source.</param> /// <param name="body">The delegate that is invoked once per iteration.</param> /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="source"/> /// argument is null.</exception> /// <exception cref="T:System.ArgumentNullException">The exception that is thrown when the <paramref name="body"/> /// argument is null.</exception> /// <exception cref="T:System.AggregateException">The exception that is thrown to contain an exception /// thrown from one of the specified delegates.</exception> /// <returns>A <see cref="T:System.Threading.Tasks.ParallelLoopResult">ParallelLoopResult</see> structure /// that contains information on what portion of the loop completed.</returns> /// <remarks> /// The <paramref name="body"/> delegate is invoked once for each element in the <paramref name="source"/> /// enumerable. It is provided with the current element as a parameter. /// </remarks> public static ParallelLoopResult ForEach<TSource>(IEnumerable<TSource> source, Action<TSource> body) { if (source == null) { throw new ArgumentNullException("source"); } if (body == null) { throw new ArgumentNullException("body"); } return ForEachWorker<TSource, object>( source, s_defaultParallelOptions, body, null, null, null, null, null, null); } 取消 Parallel.ForEach或Parallel.For 通过CancellationTokenSource来获取token CancellationTokenSource cts = new CancellationTokenSource(); 通过ParallelOptions.CancellationToken属性来控制取消状态。 ParallelOptions po = new ParallelOptions(); po.CancellationToken = cts.Token; 通过Parallel.For或Foreach的ParallelOptions值来控制并行内方法的取消。 代码如下: int[] nums = Enumerable.Range(0, 10000000).ToArray(); CancellationTokenSource cts = new CancellationTokenSource(); // Use ParallelOptions instance to store the CancellationToken ParallelOptions po = new ParallelOptions(); po.CancellationToken = cts.Token; po.MaxDegreeOfParallelism = System.Environment.ProcessorCount; Console.WriteLine("Press any key to start. Press 'c' to cancel."); Console.ReadKey(); // Run a task so that we can cancel from another thread. Task.Factory.StartNew(() => { var s = Console.ReadKey().KeyChar; if (s == 'c') cts.Cancel(); Console.WriteLine("press any key to exit111"); }); try { Parallel.ForEach(nums, po, (num) => { double d = Math.Sqrt(num); Console.WriteLine("{0} on {1}", d, Thread.CurrentThread.ManagedThreadId); po.CancellationToken.ThrowIfCancellationRequested(); }); } catch (OperationCanceledException e) { Console.WriteLine(e.Message); } finally { cts.Dispose(); } Console.ReadKey(); 运行结果如下,键盘输入c时,并行取消。 1937.41838537782 on 7 2739.95711645274 on 8 2501.40660429287 on 9 2958.47798707376 on 10 . . . press any key to exit111 The operation was canceled. 捕获并行体内的异常 示例方法采用ConcurrentQueue来接收异常集合,最后抛出一个聚合异常AggregateException。 var exceptions = new ConcurrentQueue(); exceptions.Enqueue(e); 外部调用AggregateException.Flatten方法获取异常信息。 这为我以后捕获异常提供了一个好思路。 /// <summary> /// 捕获并行体内的异常 /// </summary> public void HandleExceptionParallelLoop() { // Create some random data to process in parallel. // There is a good probability this data will cause some exceptions to be thrown. byte[] data = new byte[5000]; Random r = new Random(); r.NextBytes(data); try { ProcessDataInParallel(data); } catch (AggregateException ae) { var ignoredExceptions = new List<Exception>(); // This is where you can choose which exceptions to handle. foreach (var ex in ae.Flatten().InnerExceptions) { if (ex is ArgumentException) Console.WriteLine(ex.Message); else ignoredExceptions.Add(ex); } if (ignoredExceptions.Count > 0) throw new AggregateException(ignoredExceptions); } Console.WriteLine("Press any key to exit."); Console.ReadKey(); } private void ProcessDataInParallel(byte[] data) { // Use ConcurrentQueue to enable safe enqueueing from multiple threads. var exceptions = new ConcurrentQueue<Exception>(); // Execute the complete loop and capture all exceptions. Parallel.ForEach(data, d => { try { // Cause a few exceptions, but not too many. if (d < 3) throw new ArgumentException($"Value is {d}. Value must be greater than or equal to 3."); else Console.Write(d + " "); } // Store the exception and continue with the loop. catch (Exception e) { exceptions.Enqueue(e); } }); Console.WriteLine(); // Throw the exceptions here after the loop completes. if (exceptions.Count > 0) throw new AggregateException(exceptions); } 对微小执行体提速 当Parallel.For循环有一个很快的执行体,它可能比同等顺序循环执行更慢。较慢的性能是由分区数据所涉及的开销和每次循环迭代调用委托的成本引起的。为了解决这种情况,Partitioner类提供了Partitioner.Create方法,该方法使您能够为委托主体提供顺序循环,以便每个分区仅调用一次委托,而不是每次迭代调用一次。 var rangePartitioner = Partitioner.Create(0, source.Length); /// <summary> /// 提速 /// </summary> public void SpeedUpMicroParallelBody() { // Source must be array or IList. var source = Enumerable.Range(0, 100000).ToArray(); // Partition the entire source array. var rangePartitioner = Partitioner.Create(0, source.Length); double[] results = new double[source.Length]; // Loop over the partitions in parallel. Parallel.ForEach(rangePartitioner, (range, loopState) => { // Loop over each range element without a delegate invocation. for (int i = range.Item1; i < range.Item2; i++) { results[i] = source[i] * Math.PI; } }); Console.WriteLine("Operation complete. Print results? y/n"); char input = Console.ReadKey().KeyChar; if (input == 'y' || input == 'Y') { foreach (double d in results) { Console.Write("{0} ", d); } } } 源码地址 CsharpFanDemo 总结 本篇文章沿着微软官方文档步骤熟悉了第一部分数据并行的用法。 Parallel.For和Parallel.ForEach实现并行。 Parallel.For和Parallel.ForEach线程局部变量。 取消并行ParallelOptions.CancellationToken 捕捉异常ConcurrentQueue累加并行体内的异常,外部接收。 加速Partitioner.Create 感谢观看!
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
前言 说起支付平台,支付宝量级的支付平台和一个小型公司的支付不可同日耳语。一个初创或刚创业一两年的公司,一没人力,二没财力的情况下,如果也想对接支付那怎么办呢?感谢支付宝和微信支付,两大行业巨头提供了简单易用的方案,简化了对接流程,又能支持大部分银行。今天我们就来根据不同业务规模,设计一个能经受业务考验的支付平台。 第一阶段 举个例子,阿力空闲时间,接了个外包的分销系统。业务模型如:成为会员,可以自动带二维码的分销海报,扫描你二维码的人成为会员后,你获得提成。 这个例子有几个核心步骤: 申请会员,支付成为会员,自动生成海报, 计算分销提成。 有点小挑战的自动生成海报。这个可以参考微信参数二维码接口和GDI+绘制图片来搞定,利用html5的canvas也能搞定。 最核心的部分,当然是支付。 先来一张订单表流程图压压场面。 订单模型 前些天看领域驱动提到了核心域和子域,那么整个交易流程是是这个模型的核心域,订单表是交易流程的子域。 我大概说下这些字段,业务类型和业务id以及业务处理url实现了各个业务的解藕,各个业务线都有自己的限界上下文。它可以根据取消日期和取消地址完成订单的取消动作,可以根据支付平台交易id和支付平台查询对账。业务通知状态是用来综述通知业务处理是否成功。说完了订单,让我们来看下整体交易流程。 交易流程 订单有三个主流程,提交订单是用户主动触发,支付回调是属于支付平台触发,定时取消是后台任务根据设定的取消时间自动运行,小业务可以不考虑订单取消问题。 这样来说,第一版支付中心就完成了。由于刚上线,流量每天很少,平稳了运行一段时间后,也许会出现支付平台支付,但搭建的支付中心却未支付,只能手动修改数据库了,并触发业务回调了,这在最终一致性里,可以成为人工补偿。后来不厌其烦,加了个支付日志,记录任何与支付平台交互的信息,然后每隔一段时间扫描最近变更的日志表,并和订单表对比,发现不匹配的,修复为已支付,完美的解决了这个问题,这在最终一致性里,可概括为定时补偿。 交易日志表 老板缺少人手,业务量又上升,又对阿力解决问题的能力很欣赏,就直接把阿力工资翻倍从原公司挖了过来。(故事纯属虚构) 第二阶段 刚过来新公司不久,就接到了一笔融资,然后新公司扩招了很多同事,市场销售人一多,产品线更多,线上支付流量也加快起来。阿力信心满满,觉得很有干劲。得意不久,就遇到了服务线反馈的问题:有客户重复支付,需要退款。于是改订单,清理数据,财务退款,临时解决了问题。后来次数多了,手工处理及易出错,就查询支付宝和微信的自动退款接口,然后依赖日志表记录过支付成功对比判定重复支付,发起退款,引入了自动退款流程。 交易流程补充自动退款流程 然后又接到了一个线上客户需要抢购的需求,每月有一天集中一起抢,类似小米秒杀那样。然后到了激动的那天,系统撑过了三分钟,华丽丽的挂了!熬了二十分钟才恢复正常。 痛定思通,支付中心进入重构优化阶段。由于公司人员扩张,有时间和精力和能力去重购优化更健康的业务架构。 一,引入消息队列Rabbitmq支撑流量削峰。如支付回调先进消息队列,由消息队列去通知业务。大幅度缩短单次请求处理时间,提升并发能力。 二,全面引入Redis缓存,减小数据库访问压力,部分关键业务表启用HttpRuntime缓存,性能指数级提升。 三,引入专业调度工具quartz.net或hangfire。可以用来处理定时查询订单交易问题,及退费问题。 四,购买商业.net监控平台,如听云。检测程序性能。 阿力跟随新公司技术体系,也对支付中心实现了升级。 支付平台回调通知后,先转发到消息队列,由消息队列来通知业务处理,如失败后延时转发到消息队列继续执行,最高重试5次,然后发短信或邮件通知责任人。 针对之前线上支付平台和自建平台不一致问题,利用hangfire调度机制定时每天晚上拉取一周数据和支付平台核对。确保了两个异构系统的一致性。 为防止支付平台同时通知,造成两条支付日志,先更新订单成功后,在队列里,用redis的incr和decr原子性操作,来确保只能同时操作一个订单,另一个通知延迟处理。重复退款时,也要保证同时只能退款一个订单号。(同一时间可以操作不同的订单号,但同一个订单号必须强制避开并发) 数据库开启读写分离,部署集群。 经过阿力和同事们两个月的协力合作与加班加点,新系统终于在那个客户第三次线上抢购前一段时间上线。经过线上抢购验证,新的系统轻轻松松的抗过了抢购,大家一片欢声笑语。阿力看到十几分钟XX百万的交易额惊呆了!,这真是金牌客户啊! 到了年底,微信红包火热起来,许多客户申请开通微信红包,有家客户粉丝有二十多万,发的钱也特别多。当时一到点,十万人齐刷刷摇手机抢红包。最后,重启了几遍应用程序池也不顶用。针对如此的流量,我们应该怎么办呢?每秒万级的请求暂时就不是小公司处理的来的,况且这流量就过年才有,像级了春运。人有那么多,抢到红包的人是有限的。百分之九十五的人都是无效流量。那就取巧吧,随机抽取一部分人的数据进入服务器,其他的人就本地留存吧,通过这种思路减少了一大部分流量。 只考虑第一,第二阶段的话,上面关于支付中心的思考架构是完全可以满足交易量的。况且又有多少公司能迈向独角兽之路呢? 念天地之悠悠,独怆然而泪下! 第三阶段 上面那种方式虽然取巧,针对特定业务,本来就是抢红包,大部分人都是无效的,能说的过去,假如是主业务流程有万级每秒甚至百万千万级每秒的请求量应该怎么办呢?阿力陷入了迷茫。 听说过docker,kuberneters为代表的容器编排,听说过CI/CD自动部署,听说过微服务的强大,听说过负载均衡,仿佛都是方向。 大海跨不过陆地,台风却能轻易穿梭,大化为小,繁化为简,聚简成面,规模化微服务也许才是解决巨量请求之道!(故事纯属虚构,不要代号入座) 附录:最终一致性 说完了解决中小型流量的问题,我们来了解下一致性问题。 1、关系型数据库事务追求ACID: A: Atomicity,原子性 C: Consistency,一致性 I: Isolation,隔离性 D: Durability,持久性 2、CAP(帽子理论) C:Consistency,一致性, 数据一致更新,所有数据变动都是同步的 A:Availability,可用性, 好的响应性能,完全的可用性指的是在任何故障模型下,服务都会在有限的时间处理响应 P:Partition tolerance,分区容错性,可靠性 帽子理论证明,任何分布式系统只可同时满足二点,没法三者兼顾 3、Base模型: BA:Basically Available,基本可用 S:Soft State,软状态,状态可以有一段时间不同步 E:Eventually Consistent,最终一致,最终数据是一致的就可以了,而不是时时保持强一致 利用查询模式,补偿模式,异步确保模式,定时校对模式等可实现分布式系统最终一致性。 最终一致性更详细用法参考李艳鹏老师关于分布式一致性的讲解。https://www.jianshu.com/p/1156151e20c8?from=singlemessage&isappinstalled=0 后言 阿力解决了支付中心的稳定问题后,就买了许多书,看到了上面关于最终一致性的陈述时,心里想到,这些都是我已经实现了的,原来还有这么多头头道道?? 阿力又看到了领域驱动设计等书,感慨:支付领域模型真是学习领域驱动设计的最好实践。它具有独立的限界上下文,通过回调url和其他业务限界上下文沟通。 最后再用交易流程在做个总结吧! 交易流程 关键点: 1.回调部分,有消息队列通知,并支持失败重试。 2.每天晚上定时拉取支付平台对象记录核账,保证最终一致性。 3.支付平台回调时,根据支付日志判定是否重复支付,重复支付的发起自动退款。 源码 计划用.netcore按领域驱动的方式,完成以上设计。日期未定。 声明 全文除附录部分最终一致性外,均为原创。如文章能给你带来帮助,请点下推荐,感谢支持。
前言 从2018年7月份,我开始了写作博客之路。开始之前,我打算分享下之前的经历。去年初公司来了个架构师,内部分享过docker原理,TDD单元测试驱动,并发并行异步编程等内容,让我着实惊呆了,因为确实是干货。从那以后我买了快十本书了,为了补充我的基础知识,我觉得我对工作上的上层应用是足够完成工作了,但是沉淀到底层,沉淀到分布式,沉淀到大前端,我的知识栈脆弱的经不起推敲。计算机是一门高速发展的学科,跟不上时代就会被淘汰。没有背景的人唯一出路就是努力向前! 收获 线程安全集合类 在我7月份的一篇akka.net提到一个业务场景,就是异构系统数据同步,之前采用的是任何更新都直接推送队列,这无疑是很消耗性能的。我当时想到了加锁存httpruntime cache,定时推送。在研究了olreans的connect源码后,发现了大量的基于多线程的集合类,可以替代我之前的想法。就在昨天测试zipkintracer是又看到了那些多线程安全的集合。我就知道以后任何推送数据都可以采用这种方式,然后再异步落库。 关注到园区大牛 不断的坚持写了三个月博客后,部分文章也引起了一些关注,如《.net外国高质量博客分享》,《.net实用业务搭配技术栈》,《.net实践爬虫》《.net架构篇:实用中小型公司支付中心搭建》等。不同的文章能引起不同人的兴趣。让我有机会和周旭龙,圣杰,善友大神,倾竹大人,晓晨等园子里高质量博文大牛参与讨论学习,这其实很重要,空手交流和拿着东西交流感受是不一样的。 报错问题引发的挑战 我们在解决问题的过程中,会遇到各种各样的bug,在解决bug时,我最近习惯用bing搜索,可以支持国内搜索和国外搜索。搜索过程中,会加深一些知识应用。那些搜索的连接也是很有价值的,记录下来,总有收获。 专有问题关注专有人群 部分人可能博客更新率很低,但质量很高,你发布一篇博文话题时,有时会引起他们的兴趣和建议。然后根据他们的建议和博文记录,可以引发更大的世界。专人关注的时间更久,可能比你懂得更多。比如大数据,机器学习等领域,园子里都有对应的牛人。 黑夜给了我黑色的眼睛,我却用它来寻找光明。 如何写好一篇博客 刚开始我被移除首页的文章有很多,但我大多都赞同。一篇好的博客,应该做到下面几点: 一,排版整齐。 排版错乱的都没有兴趣看下去。 二,基本介绍 写一个东西前,要先把所写的东西做一个开篇介绍,让读者有个大概的熟悉。 三,故事讲清楚,讲完整,逻辑清晰 只有博文看的懂,或者让懂的人有复习或深入学习的兴趣。才算一篇好的博文。梳理,总结,提炼出精华内容,才是我辈追求的目标。 四,搭配源码 如果是实践型的项目,最好是能搭配源码,前两天看到三生石上的点推荐看源码连接的主意挺不错的,有兴趣的人自然会点击下载。软件是一项包含理论和实践的工程。实践也是相当重要的。 目前较好的博客平台 手机端支持最高的是简书,电脑端可选简书,CSDN,还有慕课网的手记,当然还有阿里云,腾讯云等。平台还是很多的。 博客园的编辑后台较老,不够现代化。我一般是有道云编辑好后,复制到博客园。 博客能带来盈利么 如果你的博客不是收费而是免费的,很难盈利。就像我老婆电商轻松刷一单就可以获得一件小礼物,如小电风扇,挡风被,鞋等各种各样。而我花费几个小时写一篇博文,也就收获几个赞,什么礼物也没有。 貌似投资完全不成正比。 但我们应该从另外一个方面思考问题。 如果你写的是系列文章,可以去慕课网等各大教育平台设置你的收费教程,这样肯定有收益。 如果达不到这个水平,就老老实实写博文积攒人气吧。张鑫旭十年博客,一朝图书出版,立刻进入css分类书籍排名前列,这都是十年积攒的人气。如果是一个默默无闻的人出个书,有谁买? 如果还没有达到出书的水平也不要紧,可以写一些入门教程,放个打赏分享。路过的看官,万一谁发善心打赏了点呢? 如果真的无人打赏,那么就看主战场的收获了。以解决实际工作为目标写博客,以提升现有工作性能为目标写博客,以重构老旧垃圾代码实现可维护性为目标写博客,以学习新技术为目标写博客,无论那一种,你都可以找到一样去选择。这么多进修了,面试或谈工资时,你的底气就不一样。 总结 不断的写博客,会激发你不断学习的激情。学习的激情又会发现无数不会的东西。不断的前进下去,博文会越来越多,学习的东西会越来越多。 这篇是属于鸡汤文,也是属于推广计划,欢迎大家查看推荐我的置顶博文。这是24小时内第四更了,约上万字数。一篇分布式监控实践,两篇看待外包,一篇推荐写博文。 本篇到此结束,感谢观看。
前言 从2018年7月份,我开始了写作博客之路。开始之前,我打算分享下之前的经历。去年初公司来了个架构师,内部分享过docker原理,TDD单元测试驱动,并发并行异步编程等内容,让我着实惊呆了,因为确实是干货。从那以后我买了快十本书了,为了补充我的基础知识,我觉得我对工作上的上层应用是足够完成工作了,但是沉淀到底层,沉淀到分布式,沉淀到大前端,我的知识栈脆弱的经不起推敲。计算机是一门高速发展的学科,跟不上时代就会被淘汰。没有背景的人唯一出路就是努力向前! 收获 线程安全集合类 在我7月份的一篇akka.net提到一个业务场景,就是异构系统数据同步,之前采用的是任何更新都直接推送队列,这无疑是很消耗性能的。我当时想到了加锁存httpruntime cache,定时推送。在研究了olreans的connect源码后,发现了大量的基于多线程的集合类,可以替代我之前的想法。就在昨天测试zipkintracer是又看到了那些多线程安全的集合。我就知道以后任何推送数据都可以采用这种方式,然后再异步落库。 关注到园区大牛 不断的坚持写了三个月博客后,部分文章也引起了一些关注,如《.net外国高质量博客分享》,《.net实用业务搭配技术栈》,《.net实践爬虫》《.net架构篇:实用中小型公司支付中心搭建》等。不同的文章能引起不同人的兴趣。让我有机会和周旭龙,圣杰,善友大神,倾竹大人,晓晨等园子里高质量博文大牛参与讨论学习,这其实很重要,空手交流和拿着东西交流感受是不一样的。 报错问题引发的挑战 我们在解决问题的过程中,会遇到各种各样的bug,在解决bug时,我最近习惯用bing搜索,可以支持国内搜索和国外搜索。搜索过程中,会加深一些知识应用。那些搜索的连接也是很有价值的,记录下来,总有收获。 专有问题关注专有人群 部分人可能博客更新率很低,但质量很高,你发布一篇博文话题时,有时会引起他们的兴趣和建议。然后根据他们的建议和博文记录,可以引发更大的世界。专人关注的时间更久,可能比你懂得更多。比如大数据,机器学习等领域,园子里都有对应的牛人。 黑夜给了我黑色的眼睛,我却用它来寻找光明。 如何写好一篇博客 刚开始我被移除首页的文章有很多,但我大多都赞同。一篇好的博客,应该做到下面几点: 一,排版整齐。 排版错乱的都没有兴趣看下去。 二,基本介绍 写一个东西前,要先把所写的东西做一个开篇介绍,让读者有个大概的熟悉。 三,故事讲清楚,讲完整,逻辑清晰 只有博文看的懂,或者让懂的人有复习或深入学习的兴趣。才算一篇好的博文。梳理,总结,提炼出精华内容,才是我辈追求的目标。 四,搭配源码 如果是实践型的项目,最好是能搭配源码,前两天看到三生石上的点推荐看源码连接的主意挺不错的,有兴趣的人自然会点击下载。软件是一项包含理论和实践的工程。实践也是相当重要的。 目前较好的博客平台 手机端支持最高的是简书,电脑端可选简书,CSDN,还有慕课网的手记,当然还有阿里云,腾讯云等。平台还是很多的。 博客园的编辑后台较老,不够现代化。我一般是有道云编辑好后,复制到博客园。 博客能带来盈利么 如果你的博客不是收费而是免费的,很难盈利。就像我老婆电商轻松刷一单就可以获得一件小礼物,如小电风扇,挡风被,鞋等各种各样。而我花费几个小时写一篇博文,也就收获几个赞,什么礼物也没有。 貌似投资完全不成正比。 但我们应该从另外一个方面思考问题。 如果你写的是系列文章,可以去慕课网等各大教育平台设置你的收费教程,这样肯定有收益。 如果达不到这个水平,就老老实实写博文积攒人气吧。张鑫旭十年博客,一朝图书出版,立刻进入css分类书籍排名前列,这都是十年积攒的人气。如果是一个默默无闻的人出个书,有谁买? 如果还没有达到出书的水平也不要紧,可以写一些入门教程,放个打赏分享。路过的看官,万一谁发善心打赏了点呢? 如果真的无人打赏,那么就看主战场的收获了。以解决实际工作为目标写博客,以提升现有工作性能为目标写博客,以重构老旧垃圾代码实现可维护性为目标写博客,以学习新技术为目标写博客,无论那一种,你都可以找到一样去选择。这么多进修了,面试或谈工资时,你的底气就不一样。 总结 不断的写博客,会激发你不断学习的激情。学习的激情又会发现无数不会的东西。不断的前进下去,博文会越来越多,学习的东西会越来越多。 这篇是属于鸡汤文,也是属于推广计划,欢迎大家查看推荐我的置顶博文。这是24小时内第四更了,约上万字数。一篇分布式监控实践,两篇看待外包,一篇推荐写博文。 本篇到此结束,感谢观看。 欢迎大家看我的NetCore实践篇:分布式监控客户端ZipkinTracer从入门到放弃之路 ,感谢支持。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
前言 本文紧接上篇.Net架构篇:思考如何设计一款实用的分布式监控系统?,上篇仅仅是个思考篇,跟本文没有太大的关系。但有思考,结合现有的开源组件,实践起来更易理解起来,所以看本文之前,应该先看下上篇博文。 Zipkin简介 Zipkin是一种分布式跟踪系统。它有助于收集解决微服务架构中的延迟问题所需的时序数据。它管理这些数据的收集和查找。Zipkin的设计基于Google Dapper 论文。 应用程序用于向Zipkin报告时序数据。Zipkin UI还提供了一个依赖关系图,显示了每个应用程序通过的跟踪请求数。如果要解决延迟问题或错误,可以根据应用程序,跟踪长度,注释或时间戳对所有跟踪进行筛选或排序。选择跟踪后,您可以看到每个跨度所需的总跟踪时间百分比,从而可以识别有问题的应用程序。 快速开始 启动的三种方式 Docker docker run -d -p 9411:9411 openzipkin/zipkin Java curl -sSL https://zipkin.io/quickstart.sh | bash -s java -jar zipkin.jar 源码启动 # get the latest source git clone https://github.com/openzipkin/zipkin cd zipkin # Build the server and also make its dependencies ./mvnw -DskipTests --also-make -pl zipkin-server clean install # Run the server java -jar ./zipkin-server/target/zipkin-server-*exec.jar 无论您以何种方式启动zikpin,请访问 http://your_host:9411以查询跟踪。 启动效果 架构 架构简述 应用程序中的监控器记录有关发生的操作的时间和元数据,并且对用户是透明的。如一个web监听服务记录了请求什么时候进来,什么时候离开。这个监控的数据叫做Span。 将数据发送到Zipkin的检测应用程序中的组件称为Reporter。 如图所示 示例流程 这是一个示例序列的http跟踪,其中用户代码调用资源/ foo。这个结果是单个Span,在用户代码收到http响应后异步发送到Zipkin。 ┌─────────────┐ ┌───────────────────────┐ ┌─────────────┐ ┌──────────────────┐ │ User Code │ │ Trace Instrumentation │ │ Http Client │ │ Zipkin Collector │ └─────────────┘ └───────────────────────┘ └─────────────┘ └──────────────────┘ │ │ │ │ ┌─────────┐ │ ──┤GET /foo ├─▶ │ ────┐ │ │ └─────────┘ │ record tags │ │ ◀───┘ │ │ ────┐ │ │ │ add trace headers │ │ ◀───┘ │ │ ────┐ │ │ │ record timestamp │ │ ◀───┘ │ │ ┌─────────────────┐ │ │ ──┤GET /foo ├─▶ │ │ │X-B3-TraceId: aa │ ────┐ │ │ │X-B3-SpanId: 6b │ │ │ │ └─────────────────┘ │ invoke │ │ │ │ request │ │ │ │ │ │ │ ┌────────┐ ◀───┘ │ │ ◀─────┤200 OK ├─────── │ │ ────┐ └────────┘ │ │ │ record duration │ │ ┌────────┐ ◀───┘ │ ◀──┤200 OK ├── │ │ │ └────────┘ ┌────────────────────────────────┐ │ │ ──┤ asynchronously report span ├────▶ │ │ │ │{ │ │ "traceId": "aa", │ │ "id": "6b", │ │ "name": "get", │ │ "timestamp": 1483945573944000,│ │ "duration": 386000, │ │ "annotations": [ │ │--snip-- │ └────────────────────────────────┘ 跟踪检测报告以异步方式跨越,以防止与跟踪系统相关的延迟或故障延迟或中断用户代码。 Transport。 由仪器库发送的span必须从跟踪到Zipkin收集器的服务传输。 主要支持三种传输: HTTP, Kafka 和 Scribe. 组件 Zipkin有4个组件: 收集器(collector) 存储(storage) 搜索(search) web UI Web UI 我们创建了一个GUI,它为查看跟踪提供了一个很好的界面。Web UI提供了一种基于服务,时间和注释查看跟踪的方法。注意:UI中没有内置身份验证! .NetCore使用zipkin 第一款ZipkinTracer 按照官方文档说明。 using ZipkinTracer.DependencyInjection; using ZipkinTracer.Owin; public class Startup { public void Configuration(IApplicationBuilder app) { app.UseZipkinTracer(); } public void ConfigureServices(IServiceCollection services) { var config = new ZipkinConfig(new Uri("http://XXX:9411"), request => new Uri("https://yourservice.com")) { Bypass = request => request.GetUri().AbsolutePath.StartsWith("/allowed"), SpanProcessorBatchSize = 10, SampleRate = 0.5 } services.AddZipkinTracer(config); } } //GetUri()方法报错。 改成如下方法: public void Configuration(IApplicationBuilder app) { app.UseZipkinTracer(); } public void ConfigureServices(IServiceCollection services) { var config = new ZipkinConfig(new Uri("http://weixinhe.cn:9411")); services.AddZipkinTracer(config); services.AddZipkinTracer(config); } 客户端调用 using ZipkinTracer.Http; public class HomeController : Controller { private readonly IZipkinTracer _tracer; public HomeController(IZipkinTracer tracer) { _tracer = tracer; } public async Task<ActionResult> Index() { using (var httpClient = new HttpClient(new ZipkinMessageHandler(_tracer)))) { var response = await httpClient.GetAsync("http://www.google.com"); if (response.IsSuccessStatusCode) { var content = await response.Content.ReadAsStringAsync(); } } return View(); } } 运行程序,报错! 让我们去官网看看问题,issue 里面有人提出了这个问题, Registering zipkin tracer throws an error #10 下面有人回复:意思是需要重写中间件。 I have the same issue. According to the documentation, the dependencies in the middlewares should be moved into the Invoke method. In this case, the ZipkinMiddleware has to be changed in my opinion. 年久失修,作者未回复。但是源码都放在那里了,难道任由其报错而无能为力么?这我不能忍受。所以,下载源码,引用源码项目。开启调试之路。 解决问题 右键查看属性,竟然看不到目标框架。应该是版本太低了。 查看依赖版本是framework4.6和.netstandard 1.3 那好办,我们新建一个.netcore2.1版本的项目,然后将tracer的代码都复制过去。 一些复制好并引用后,还是刚才那个错。调试也没有看到哪里报错,只是最终页面报错了。所以我们继续搜索。 Cannot consume scoped service 'XXXX' from singleton 'XXX'. 依赖注入的知识普及 .net核心依赖注入生命周期解释 无法从Singleton消耗Scoped服务 - ASP.net核心DI范围的一课 ASP.net核心中的第三方依赖注入 此网站已收集在.NetCore外国一些高质量博客分享,长期保持更新。 上面三篇文章普及了一些依赖注入的知识。sorry,这块我研究的很浅。。。这次顺带了解了不少,以后要抽空专门研究一下。 Single:单例是一个将持续应用程序整个生命周期的实例。在Web术语中,这意味着在服务的初始请求之后,每个后续请求将使用相同的实例。这也意味着它跨越Web请求(因此,如果两个不同的用户访问您的网站,代码仍然使用相同的实例)。考虑单例的最简单方法是,如果类中有静态变量,则它是跨多个实例的单个值。 Scoped:范围内的生命周期对象通常会简化为“每个Web请求一个实例”,但实际上它比实际上更加微妙。无可否认,在大多数情况下,您可以将每个Web请求视为范围对象。您可能会看到的常见问题是每个Web请求创建一次DBContext,或者创建一次NHibernate上下文,以便您可以将整个请求包含在事务中。作用域生存期对象的另一个非常常见的用途是当您要创建每个请求缓存时。 Scoped生命周期实际上意味着在创建的“范围”对象中将是同一个实例。它恰好发生在.net核心中,它在“范围”内包装请求,但您实际上可以手动创建范围 Transient:每次请求服务时,都会创建一个新实例。 关于上述的类似错误无法从单件服务 #2569中使用作用域服务'AutoMapper.IMapper',有用户评论: 这是一个基本的设计约束。你不能让单身人士(Single)依赖于瞬态(Scoped)或范围内(Transient)的物品。这不是容器的错,这些生命周期是不相容的。如果您需要兼容的生命周期,请选择不同的生命周期。 Singleton < - Singleton 良好 Singleton < - Scoped 糟糕 Singleton < - Transient 糟糕 Scoped < - Singleton 良好 Scoped < - Scoped 良好 Scoped < - Transient 良好 TRANSIENT < - Singleton 良好 Transient < - Scoped 良好在范围内,糟糕在范围外 TRANSIENT< - TRANSIENT 良好 所以真的只有两种情况“总是糟糕”,一种情况“有时候很糟糕”。 ASP.NET Core DI使这个非常明确,甚至将工厂方法传递给上下文对象,例如过滤器。过滤器是Singleton,因此您不能拥有构造函数依赖项。相反,您使用传递给过滤器方法的各种XyzContext对象来解析依赖项。 上述是十分有价值的评价,先收藏,以后细细品味。 有了这些基础知识和良好建议,我们再来回顾下代码。看到前面都是AddSingleton,后面是AddScoped,在上面的建议中是属于糟糕的。虽然不清楚作者的意图,但我们可以先把程序跑起来,以后用熟悉了再来仔细修复。 public static class ServiceCollectionExtensions { public static void AddZipkinTracer(this IServiceCollection services, ZipkinConfig config) { if (config == null) throw new ArgumentNullException(nameof(config)); var maxSize = config.MaxQueueSize <= 0 ? 100 : config.MaxQueueSize; services.AddSingleton(config); services.AddSingleton(new BlockingCollection<Span>(maxSize)); services.AddSingleton<IServiceEndpoint, ServiceEndpoint>(); services.AddSingleton<ISpanProcessorTask, SpanProcessorTask>(); services.AddSingleton<ISpanProcessor, SpanProcessor>(); services.AddSingleton<ITraceInfoAccessor, TraceInfoAccessor>(); services.AddScoped<ISpanCollector, SpanCollector>(); services.AddScoped<IZipkinTracer, ZipkinClient>(); services.AddScoped<ISpanTracer, SpanTracer>(); } } 好吧,临时将三个AddScoped 改为 AddSingleton(); 运行项目。又开始报错了。。。。说IZipkinTracer需要一个无参的构造函数。 InvalidOperationException: Could not create an instance of type 'ZipkinTracer.IZipkinTracer'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the 'tracer' parameter a non-null default value. 继续搜索新的这个错误。 模型绑定的知识普及 Model bound complex types must not be abstract or value types and must have a parameterless constructor asp.net mvc github的问题里面讨论的很激烈。我只能根据翻译大概猜测意思了。 模型通过DI #6014 绑定到接口 用于简单模型 - 视图模型 - 模型属性映射的基本对象映射器 未来投资:模型绑定 Asp.net Core中的自定义模型绑定,3:模型绑定接口 又get到一个网址:http://www.dotnet-programming.com,已更新到.NetCore外国一些高质量博客分享 为什么它需要默认构造函数而不是直接使用我的工厂方法? 精彩评论: 您正在混淆依赖注入和模型绑定。有一个很大的不同。请考虑执行以下操作。 注册IModelFactory为服务: public void ConfigureServices(IServiceCollection services) { services.AddSingleton<IModelFactory>(ModelFactory.Current); // Add framework services. services.AddMvc(); } 现在在您的控制器中,用于FromServices获取实例,并使用以下内容获取创建模型所需的值FromForm: [HttpPost] public IActionResult CreateTemplate([FromForm] string name, [FromServices] IModelFactory factory) { var item = factory.CreateTechnicalTaskTemplate(name); repo.Templates.Add(item); return View(nameof(TemplatesList)); } 您的工厂应该被视为一项服务。模型绑定需要POCO,而不是接口。 从入门到放弃 对不起,模型绑定这个错,我没看太懂,只能先放弃了。如果对着源码都找不到解决办法,我只能理解自己的知识太浅。。。时间也很晚了,程序员也是需要有业余生活的。关于zipkintracer的试用到此为止。下期使用官方推荐客户端zipkin4net。 看官们,你们也真的深入了解依赖注入和模型绑定么? 总结 本篇旅程虽然失败,但也了解了zipkin的相关介绍,原理,也在解决问题的过程中加深了依赖注入的理解。模型绑定的概念还不是太清楚,抽空我再看看。真可谓:无心栽花花不成,无心插柳柳成荫。来一句鸡汤:努力向前走,总会有意想不到的收获。 可参考资料 部署Zipkin分布式性能追踪日志系统的操作记录 微服务监控zipkin+asp.net core 各大厂分布式链路跟踪系统架构对比 Net和Java基于zipkin的全链路追踪 本篇到此结束,感谢观看。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
延续上篇文章.net外包篇:我是如何看待外包的。 从这家公司辞职以后,得益于我校园信息平台和高校信息管理的经验,我进入了一个互联网类型公司。以前的经历,环环相扣,步步提升。 互联网时代 第四家客户(未谈成) 这个是属于网上自己找的项目,和负责人探妥了条件后,就出发去他们公司了,里面已经有部分开发人员了。简单了过了下需求,发现让我做的,比负责人谈的要多很多。经过周末的试开发,发现他们提供的接口还有bug,由于时间和预期目标关系不符,所以选择了退出。 按道理,没谈成的项目不应该列出来,但这个还有后话。 收获 接一个项目,如果没有足够的时间,要有自己的预期,不符合预期就不要接下来,以防后期不好交付。更大的收获是和其中的几个开发人员互加了微信,后期为我带来了另外的一个项目。 第五个客户 这个就是程序媛妹妹登场的时候了,音乐响起来! 第四个客户过后一段时间,快过年了,一个叫静静的程序媛联系了我,说有个项目是否有兴趣,很融洽的商量了下来,功能模型类似我 实用中小型公司支付中心搭建那篇文章列举模型,会员分销提成。 这个项目我收获很大,但遗憾也不少。 由于是兼职,所以大多说都是晚上撸代码,也经常视频聊天沟通需求。大晚上有个美女视频着开发,感觉还是挺好的。然后就到了过年。因为我回老家,家里也没网,就牵了电信的网。那几天矛盾挺多的,静静一直很着急,但我那段时间准备结婚事宜和网络原因,没法敲代码,有些延误工期。等婚礼结束,网络连好后,就开始继续敲代码了,不过以后就再没视频过了,都是语音或微信沟通。 开年大吉,项目第一期完工后,项目老板举行一个简单的聚会,大家非常happy,老板说让我辞职过去,不过我找了个理由推辞了。后来的南京举行的发布会,我也没有去。我模糊的记得,静静好像说过:以前不是说好以后一起做项目的么,你就这样退出了?大概是这个意思。我觉得很愧疚,不好回答。 我为什么没有继续跟进了? 一个原因是人员,初创公司人员不齐全加上两个老板,三个兼职开发,一个更兼职的就五六个人。 第二个原因产品,第一版核心功能是会员分销拿提成,投资股权。我觉得这些有点虚,产品有传销风险。 第三个原因是我在现公司待的很好,同事们相处很融洽。我喜欢这个工作环境。 虽然没有跟进后续的项目,但也是结识了一些人,如果我有好的项目机会也会联系他们的。 收获 实践了微信支付,虽然在公司经常用支付,但还没真正从零接过微信支付。这次算弥补了愿望。 GDI+海报 利用gdi+实现了自动基于粉丝信息汇出分销海报。 参数二维码 熟悉了微信的参数二维码,利用参数二维码实现分销。 熟悉微信开发流程 从微信获取openid开始,到发模版图文消息等流程。对于微信不再是迷茫区。 第六个客户(失败) 这算是个失败的项目,因为从事过支付,微信和分销的项目,又接下了朋友的一个分销商品系统。我初期以为很快搞定,但搞了一个月后发现一个人搞不定了,界面说简单也简单,是一个完善带分销的完善电商系统,我想的太简单了。又是从头开发,没有适合的开源系统,注定不能快速的完成。 一个月后,我退钱结束项目。因为是朋友,他也没啥损失。以后再有人问我说电商之类系统,我都建议以阿里,有赞,微店看看是否适合。每个人都有特性的需求,完全开发,小店没有财力和时间去完成,只能以现成产品去用。 最近两年由于时间关系,再没接过外包了,耗时耗力。 大总结 我的外包故事到此结束,有成功有失败。酸甜苦辣,滋味不同。因为外包,我前些年有了全栈的开发能力,当然这在现在的大前端面前有点心虚。但当时从jquery到编码到数据库到部署,一系列流程都能搞定,也算是全栈吧?现在系统规模一大,队列,缓存,容器,大数据一来,我仿佛什么都不会了。 微软感受到了自己的落后而开源了.netcore,我也得补充自己的知识框架,我会的那些也许都过时了。竞争不过新人,就会被淘汰,这是真理。 虽然我断断续续接了些外包,但我也建议新人不要太过专注接外包。我上面所列的东西,你完全可以走另一条路,自研组件写博客,写书之路,这也许是一天更好的路。外包能影响你一份工作,但一本好书可以影响你的一生。 初生牛犊不怕虎,开源软件路先行。谨以此篇回忆文章纪念2011年在51aspx开源的XX远程控制系统。七年之内,却没有第二个开源出来。甚为遗憾。 此篇结束。 欢迎大家看我的NetCore实践篇:分布式监控客户端ZipkinTracer从入门到放弃之路 ,感谢支持。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
前言 无论从最早期的unix操作系统,还是曾经大行其道的单体式应用,还是现在日益流行的微服务架构,始终都离不开监控的身影。如windows的任务管理器,linux的top命令,都可以看作是监控的面板。 再联系起现实生活,无处不在的路网摄像头,为交通机关监控交通人流提供了方便。 系统规模越大,越离不开监控。缺少了监控,就像盲人摸象,窥不到全貌。 理想中的分布式监控 进入互联网时代,系统调用规模日益庞大,对监控的需求更是迫切。比如一个页面打开很慢,怎么分析哪里慢?是网站接受请求慢还是连接数据库慢,或者消息队列挂了,或者redis请求慢?我们需要监控系统能提供这些信息供我们追踪分析。 所以理想中的分布式监控应该记录从请求发起那一刻,所调用的公开方法,接触过的数据库,缓存,队列等步骤,以及每一步所消耗的时间。这些都需要大量的日志去记录。 第二点,理想中的分布式监控必须是对代码无侵入,应用程序员无需对每个方法去调用监控代码。这样完全解藕的监控系统,才更容易使用,加入每个方法,都要调一下监控接口,那不要累死人,代码也及其不友好。 第三点,理想中分布式监控应该对性能不造成损耗或者极小的损耗。如果流量一大,监控系统CPU飙生的话,那这个监控无疑是失败的。 第四点,许多方法有层级,方法内调用其他方法,应该能通过报表聚合查看,进入每个方法的时间以及调用耗时,调用方法的层级树。 第五点,分布式时代,一个调用请求会横跨很多站点,理想的分布式监控应该提供调用链上所有站点的聚合报表查看,要极力避免死循环,两个站点长官相互调用的情况下,应该用双箭头表明调用关系。 第六点,能提供接入监控的服务器cpu,内存,硬盘空间等指标,并根据警戒线发送通知。这个优先级可以降低,可以借助云服务器自身提供的监控,阿里云和Ucloud都有自己的服务器监控面板。 如何设计一款实用的监控 统一的调用链id 根据软件的调用链特性,从一个请求开始到最终的结束,应该具有一个统一的调用链id。 时间戳 调用各种方法的时间也应该是顺序的,需要一个精确的时间戳,来描述调用方法的进入与离开的时间。 异步传输 为了不影响性能,应该以异步传输,定时落库的方式。 延时聚合 如果能做到实时聚合更好,如果实现困难可以采用延时聚合报表,延时的时间应该小于一分钟。这个时间使用人群应该能接受,当然如果能缩短到几秒钟,那使用人群会更加高兴。聚合报表应首先提供最近时间内的耗时排序,可以查看调用的方法树,可以查看调用链的所有站点,其他需求可以后期开发,解决核心需求。 最后的难点 如果不追求无侵入,提供一个空接口。所有需要记录日志的实现接口,就已经达到了目标的一半。 为了完成对应用无侵入的目标,我们首先需要一款真正的aop,即静态编织Aop,这个只听说过postsharp,为什么只有它能实现?或者是类似fiddler之类的抓包工具。 本篇暂时写到这里结束。 可参考的如google dapper论文,zipkin,听云。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
前言 我之前看.netcore一些问题时候,用bing搜索工具搜到了一些外国人的博客。翻看以下,有学习的价值,就分享在这里了。 个人博客 andrewlock.net 最新几篇如下,一看标题就知道很有实用价值。 为什么我的ASP.NET Core特定于环境的配置没有加载? 使用Docker在CI中运行ASP.NET核心应用程序的冒烟测试 将Segment客户端分析添加到ASP.NET Core应用程序 在Dapper和PostgreSQL中使用蛇案例列名 将Serilog添加到ASP.NET核心通用主机 stephencleary-并行异步专家 举几篇例子: asp.net core 压缩流 使用Roslyn进行单元测试 Docker作为工具提供者 quickbird 这个博客的内容,可以看看,很不错的,但是需要FQ。博客举例: 在Docker和Kubernetes中调试.NetCore容器用于Arduino的EC和PH传感器 使用Kubernetes模拟数百个物联网设备 west-wind.com west-wind是作者的一个测试工具,作者也是一位微软MVP,可能有许多是他用自己的工具的软文,但整体还是很不错的。文章举例如下: 使用ASP.NET核心中间件创建通用Markdown页面处理程序 ASP.NET核心中的轻松配置绑定 - 重新审视 cecilphillip 这个网站有几篇内容挺好的博文,但今年没有新的更新,总体量很少,没兴趣的也可以不看。 使用Consul进行ASP.NET核心健康检查 使用Consul进行ASP.NET核心服务发现 使用Autofac Aggregate Services重构依赖关系 规模型博客 csharpcorner csharpcorner是个很出名的网站,我觉得它应该具有和csdn类似的地位。里面印度人挺多的,也有很多微软MVP。例如下面三位。 syed-shanu nitin-pandit MAHESH CHAND dzone.com dzone.com也许是infoq类型的开发者社区?有兴趣就看看,没兴趣也可以不看。 分为大数据/云/物联网/微服务/工作等二级类型。 微服务 你要询问的微服务概念,里面都能找到说明。 如: 微服务的模式语言 应用架构模式 分解 部署模式 横切关注点 沟通风格 外部API 事务消息传递 服务发现 可靠性 数据管理 测试 监控 UI模式 .net 微软官方站 这里是最权威的学习网站。英语不好的,部分子文档需要借助翻译 .net core 指南 c# 指南 .net framework指南 .netcore微服务微软官方文档 大家可以点击去查看学习。 MSDN杂志期刊 能入选MSDN杂志期刊的文章,还是有看头的。 数据点 - 深入了解 EF Core HasData 种子设定 领先技术 - 使用 ASP.NET Core SignalR 实现社交网络式通知 测试运行 - 使用 C# 执行 Q-Learning 入门 后语 英文网站看不懂的可以借助chrome的右键的翻译成中文,基本可以看懂了。。有些站点需要FQ,如果谁需要可以加入QQ群:823035027,共同交流学习。 评论补充 @Esofar Tahir Naushad's Blog - https://tahirnaushad.com Dot Net Core Tutorials - https://dotnetcoretutorials.comStrathWeb. A free flowing web tech monologue. - https://www.strathweb.comIndex - Joonas W's blog - https://joonasw.netTalking Dotnet - http://www.talkingdotnet.comSteve Gordon - Adventures in ASP.NET Core - https://www.stevejgordon.co.uk @五行缺猫 Marc Gravell - https://blog.marcgravell.com/Scott Hanselman - https://www.hanselman.com/blog/ @DJLNET http://www.entityframeworktutorial.net/efcore/entity-framework-core.aspx 2018-9-11日更新: https://www.dotnetperls.com https://www.dotnetcurry.com .NET Framework Tools 2018-9-12更新: http://www.dotnet-programming.com/ 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
前言 昨天有篇文章在讨论webform的设计思路,我已经四五年不用webform了,虽然它也提供了HttpModule和httphandle来处理请求,提供了一般处理程序ashx来简化处理流程,但依然会想起它的form runat=server,想起注册客户端脚本,想起那堆服务器控件,还有著名的GridView72变。但即使不用服务器控件,它也能提供很强大的功能。 后来微软提供了另一套解决方案asp.net mvc 。其实刚开始我并不习惯,一是因为思路转变,新知识需要学习。二是因为当时做报表用rdlc,但mvc不支持。rdlc对于数据的处理很强大,但浏览器兼容性太差,用于winform客户端效果还是很棒的。mvc用熟后,webform再也没用过了。我没再用过的东西多了,但不否认它们依旧强大,也足够支撑你庞大的业务。 C/S端的话,winform和wpf依旧应该很流行。这块我做的少,理念上大同小异。 无意浪费时间去争论谁好谁坏,能为你挣钱的都是好框架。闲话少说,书归正传。今天我根据以往接触或未来想要进修学习的业务规模,来总结下对应的技术栈,来为未来准备,面向未来思考。 第一种类型企业官网 性质:访问量少,网页功能相当简单。一般为首页,公司介绍,产品介绍,联系我们等。 对应解决方案: 这类可推荐一些来源CMS,无需关心后台,设计好页面,只修改前端页面就行。 如果打算自己开发,见几个表存取下完事。不要想太复杂的架构。简单来,快速交付。 数据库:可采用access,SQLite或sqlserver,mysql都行。 .net需要储备:了解ado.net,dapper,泛型,反射等。了解数据库操作常用语句。 第二种类型流量内容网站 整个网站属于新闻类型,有较多内容分类,日均访问量一万以下。每小时平均一千量。相对于iis每秒成百上千处理数,这点量丝毫不成压力。能日均一万访问量,那说明这个站在小圈子内有很大名气了。 对应解决方案: 仍然推荐开源或自研的cms,一般方案首页和分类定时静态化或启用缓存,明细页直读数据库或经过缓存。 数据库:sqlserver,mysql,mongodb .net储备: 定时任务如hangfire,quartz.net, Cache系列(页面缓存,httpruntime缓存等), 安装windows服务topshelf 常用的设计模式等。 数据库增量备份还原与全量备份还原。数据库索引优化。 uv和pv等了解。 搜索引擎优化知识必要了解。 第三种类型小流量业务管理系统 小流量业务管理系统的特点是业务较复杂,但用的人数可能控制在几人几十人。但业务单据一天天下来也非常多。有很多公司每年底或年初会清数据,汇集到数据仓库,新的一年新数据。我之前待过的一家集团超市公司有几个分店,业务系统基于powerbuilder开发,用的是db2数据库,每天营业额数据也挺大的。每天十点有日结,月底有月结。 对应解决方案: 这里一般是购买商业软件或者基于产品二次开发了,完全从头开发需要投入很多时间和费用。 数据库:sqlserver,mysql,postgresql .net储备: 数据库存储过程,函数,优化,关联表等。 数据库性能重中之重。 数据库每几分钟增量备份,一周全备份。读 写分离也可以在这时介入。 Redis可以考虑引入。 设计模式有需要的场景。 在计算报表和一些情况下,多线程,并行处理数据也会考虑引入。 Dapper,EF开始了用武之地,干起来! 依赖注入,aop 可以考虑引入。 可考虑引入前端vue,element。 以上考虑部分,根据业务规模和使用频数来决定是否引入。业务量太小时,引入多余架构反而有些笨重。 第四种类型百花齐放互联网和大数据量项目 性质:面向c端或B端。访问量大,业务多。虽然还有很多细分,但面向了互联网,我们就时刻准备着未来它能爆发式迎接大批量数据,我们的应用要高可用,健壮!时刻准备着,为未来!哪怕很多互联网公司生存周期只有几年! 数据库:Mongodb,mysql,postgresql .net储备: 数据库集群。 Mongodb集群,分片等。 必不可少,Redis缓存,缓解数据库压力。需要了解redis运行机制,缓存穿透和缓存雪崩等。 必不可少,消息队列rabbitmq或Kafka,多业务系统之间消息传递,解藕。需要了解rabbitmq运行机制和amqp协议。 很重要 Elastic-search,Es可以通过mongo-connector实现同步mongodb数据,是一个数据极快的搜索引擎。另外可以用ELK搭建日志分析系统,这块我还需要练练手,需要了解Es如何应用,部署,问题排查等。 理论积累之微服务:微服务api网关,监控,服务发现,熔断降级,限流等。可参考微软olreans,Akka.net,ocelot,appllo,前些天过千的surging。有源码的常分析借鉴,理解透。 理论积累之TDD:单元测试,集成测试,自动化测试。 理论积累之CI/CD:需要熟练掌握jenkins配置。熟悉docker生态工具用法。 理论积累之领域驱动:微服务怎么拆?怎么微?服务之间如何联系?领域驱动设计为你提供了大量的建议,虽然不会都完美,但可以为你提供思路。领域,子域和限界上下文,领域服务,事件驱动,CQRS责任分离,贫血富血模型。我现在正在抽时间看这个,期待能有所应用。 理论积累之.net: 并行编程,异步编程。多线程安全等。 网络编程socket,orleans的网关连接就是基于socket,在之上又包装了一层gateway连接。socket和tcp/ip通信息息相关,熟练了这个,其他语言同样思路都会了。很多RPC框架也是基于socket,是网络连接的源泉。 .netcore mvc的通用主机,内置kestrel,中间件等,单元测试,docker运行等需要深入了解。 理论积累之Linux: .netcore最重要特性是跨平台,以前对linux不熟悉的部分,需要尽快熟练,要变成一个老手。 业务演练之单点登录 业务演练之秒杀 业务演练之短信限发 业务演练之实现消息队列模型 业务演练之实现分布式通信模型 业务演练之搭建微服务框架模型。 后语 粗糙了列了一堆技术栈,好像都见过,深入一问总卡壳,我觉得还是写的文章太少。不写出来,印象就会不会太深刻。我最近已经开启了两日一更,不知道能坚持多久,而且太散。先继续积累吧,以后能不能写成系列性的文章再说。收拾心情再出发!
回顾 上篇文章NetCore实践爬虫系统(一)解析网页内容 我们讲了利用HtmlAgilityPack,输入XPath路径,识别网页节点,获取我们需要的内容。评论中也得到了大家的一些支持与建议。下面继续我们的爬虫系统实践之路。本篇文章不包含依赖注入/数据访问/UI界面等,只包含核心的爬虫相关知识,只能作为Demo使用,抛砖引玉,共同交流。 抽象规则 爬虫系统之所以重要,正是他能支持各种各样的数据。要支持识别数据,第一步就是要将规则剥离出来,支持用户自定义。 爬虫规则,实际上是跟商品有点类似,如动态属性,但也有它特殊的地方,如规则可以循环嵌套,递归,相互引用,链接可以无限下去抓取。更复杂的,就需要自然语言识别,语义分析等领域了。 我用PPT画了个演示图。用于演示支持分析文章,活动,天气等各种类型的规则。 编码实现 先来定义个采集规则接口,根据规则获取单个或一批内容。 /// <summary> /// 采集规则接口 /// </summary> public interface IDataSplider { /// <summary> /// 得到内容 /// </summary> /// <param name="rule"></param> /// <returns></returns> List<SpliderContent> GetByRule(SpliderRule rule); /// <summary> /// 得到属性信息 /// </summary> /// <param name="node"></param> /// <param name="rule"></param> /// <returns></returns> List<Field> GetFields(HtmlNode node, SpliderRule rule); } 必不可少的规则类,用来配置XPath根路径。 /// <summary> /// 采集规则-能满足列表页/详情页。 /// </summary> public class SpliderRule { public string Id { get; set; } public string Url { get; set; } /// <summary> /// 网页块 /// </summary> public string ContentXPath { get; set; } /// <summary> /// 支持列表式 /// </summary> public string EachXPath { get; set; } /// <summary> /// /// </summary> public List<RuleField> RuleFields { get; set; } } 然后就是属性字段的自定义设置,这里根据内容特性,加入了正则支持。例如评论数是数字,可用正则筛选出数字。还有Attribute字段,用来获取node的Attribute信息。 /// <summary> /// 自定义属性字段 /// </summary> public class RuleField { public string Id { get; set; } public string DisplayName { get; set; } /// <summary> /// 用于存储的别名 /// </summary> public string FieldName { get; set; } public string XPath { get; set; } public string Attribute { get; set; } /// <summary> /// 针对获取的HTml正则过滤 /// </summary> public string InnerHtmlRegex { get; set; } /// <summary> /// 针对获取的Text正则过滤 /// </summary> public string InnerTextRegex { get; set; } /// <summary> /// 是否优先取InnerText /// </summary> public bool IsFirstInnerText { get; set; } } 下面是根据文章爬虫规则的解析步骤,实现接口IDataSplider /// <summary> /// 支持列表和详情页 /// </summary> public class ArticleSplider : IDataSplider { /// <summary> /// 根据Rule /// </summary> /// <param name="rule"></param> /// <returns></returns> public List<SpliderContent> GetByRule(SpliderRule rule) { var url = rule.Url; HtmlWeb web = new HtmlWeb(); //1.支持从web或本地path加载html var htmlDoc = web.Load(url); var contentnode = htmlDoc.DocumentNode.SelectSingleNode(rule.ContentXPath); var list = new List<SpliderContent>(); //列表页 if (!string.IsNullOrWhiteSpace(rule.EachXPath)) { var itemsNodes = contentnode.SelectNodes(rule.EachXPath); foreach (var item in itemsNodes) { var fields = GetFields(item, rule); list.Add(new SpliderContent() { Fields = fields, SpliderRuleId = rule.Id }); } return list; } //详情页 var cfields = GetFields(contentnode, rule); list.Add(new SpliderContent() { Fields = cfields, SpliderRuleId = rule.Id }); return list; } public List<Field> GetFields(HtmlNode item, SpliderRule rule) { var fields = new List<Field>(); foreach (var rulefield in rule.RuleFields) { var field = new Field() { DisplayName = rulefield.DisplayName, FieldName = "" }; var fieldnode = item.SelectSingleNode(rulefield.XPath); if (fieldnode != null) { field.InnerHtml = fieldnode.InnerHtml; field.InnerText = fieldnode.InnerText; field.AfterRegexHtml = !string.IsNullOrWhiteSpace(rulefield.InnerHtmlRegex) ? Regex.Replace(fieldnode.InnerHtml, rulefield.InnerHtmlRegex, "") : fieldnode.InnerHtml; field.AfterRegexText = !string.IsNullOrWhiteSpace(rulefield.InnerTextRegex) ? Regex.Replace(fieldnode.InnerText, rulefield.InnerTextRegex, "") : fieldnode.InnerText; //field.AfterRegexHtml = Regex.Replace(fieldnode.InnerHtml, rulefield.InnerHtmlRegex, ""); //field.AfterRegexText = Regex.Replace(fieldnode.InnerText, rulefield.InnerTextRegex, ""); if (!string.IsNullOrWhiteSpace(rulefield.Attribute)) { field.Value = fieldnode.Attributes[rulefield.Attribute].Value; } else { field.Value = rulefield.IsFirstInnerText ? field.AfterRegexText : field.AfterRegexHtml; } } fields.Add(field); } return fields; } } 还是以博客园为例,配置内容和属性的自定义规则 /// <summary> /// /// </summary> public void RunArticleRule() { var postitembodyXPath = "div[@class='post_item_body']//"; var postitembodyFootXPath = postitembodyXPath+ "div[@class='post_item_foot']//"; var rule = new SpliderRule() { ContentXPath = "//div[@id='post_list']", EachXPath = "div[@class='post_item']", Url = "https://www.cnblogs.com", RuleFields = new List<RuleField>() { new RuleField(){ DisplayName="推荐", XPath="*//span[@class='diggnum']", IsFirstInnerText=true }, new RuleField(){ DisplayName="标题",XPath=postitembodyXPath+"a[@class='titlelnk']", IsFirstInnerText=true }, new RuleField(){ DisplayName="URL",XPath=postitembodyXPath+"a[@class='titlelnk']",Attribute="href", IsFirstInnerText=true }, new RuleField(){ DisplayName="简要",XPath=postitembodyXPath+"p[@class='post_item_summary']", IsFirstInnerText=true }, new RuleField(){ DisplayName="作者",XPath=postitembodyFootXPath+"a[@class='lightblue']", IsFirstInnerText=true }, new RuleField(){ DisplayName="作者URL",XPath=postitembodyFootXPath+"a[@class='lightblue']",Attribute="href", IsFirstInnerText=true }, new RuleField(){ DisplayName="讨论数", XPath="span[@class='article_comment']",IsFirstInnerText=true, InnerTextRegex=@"[^0-9]+" }, new RuleField(){ DisplayName="阅读数", XPath=postitembodyFootXPath+"span[@class='article_view']",IsFirstInnerText=true, InnerTextRegex=@"[^0-9]+" }, } }; var splider = new ArticleSplider(); var list = splider.GetByRule(rule); foreach (var item in list) { var msg = string.Empty; item.Fields.ForEach(M => { if (M.DisplayName != "简要" && !M.DisplayName.Contains("URL")) { msg += $"{M.DisplayName}:{M.Value}"; } }); Console.WriteLine(msg); } } 运行效果 效果完美! 经过简单的重构,我们已经达到了上篇的效果。 常用规则模型和自定义规则模型 写到这里,我想到了一般UML图工具或Axsure原型等,都会内置各种常用组件,那么文章爬虫模型也是我们内置的一种常用组件了。后续我们完全可以按照上面的套路支持其他模型。除了常用模型之外,在网页或客户端上,高级的爬虫工具会支持用户自定义配置,根据配置来获取内容。 上面的SpliderRule已经能支持大部分内容管理系统单页面抓取。但无法支持规则相互引用,然后根据抓取的内容引用配置规则继续抓取。(这里也许有什么专门的名词来描述:递归爬虫?)。 今天主要是在上篇文章的基础上重构而来,支持了规则配置。为了有点新意,就多提供两个配置例子吧。 例子1:文章详情 我们以上篇文章为例,获取文章详情。 主要结点是标题,内容。其他额外属性暂不处理。 编码实现 /// <summary> /// 详情 /// </summary> public void RunArticleDetail() { var rule = new SpliderRule() { ContentXPath = "//div[@id='post_detail']", EachXPath = "", Url = " https://www.cnblogs.com/fancunwei/p/9581168.html", RuleFields = new List<RuleField>() { new RuleField(){ DisplayName="标题",XPath="*//div[@class='post']//a[@id='cb_post_title_url']", IsFirstInnerText=true }, new RuleField(){ DisplayName="详情",XPath="*//div[@class='postBody']//div[@class='blogpost-body']",Attribute="", IsFirstInnerText=false } } }; var splider = new ArticleSplider(); var list = splider.GetByRule(rule); foreach (var item in list) { var msg = string.Empty; item.Fields.ForEach(M => { Console.WriteLine($"{M.DisplayName}:{M.Value}"); }); Console.WriteLine(msg); } } 运行效果 效果同样完美! 例子2:天气预报 天气预报的例子,我们就以上海8-15天预报为例。 分析结构 点击链接,我们发现 今天/7天/8-15天/40天分别是不同的路由页面,那就简单了,我们只考虑当前页面就行。还有个问题,那个晴天雨天的图片,是按样式显示的。我们虽然能抓到html,但样式还未考虑,,HtmlAgilityPack应该有个从WebBrowser获取网页的,似乎能支持样式。本篇文章先跳过这个问题,以后再细究。 配置规则 根据网页结构,配置对应规则。 public void RunWeather() { var rule = new SpliderRule() { ContentXPath = "//div[@id='15d']", EachXPath = "*//li", Url = "http://www.weather.com.cn/weather15d/101020100.shtml", RuleFields = new List<RuleField>() { new RuleField(){ DisplayName="日期",XPath="span[@class='time']", IsFirstInnerText=true }, new RuleField(){ DisplayName="天气",XPath="span[@class='wea']",Attribute="", IsFirstInnerText=false }, new RuleField(){ DisplayName="区间",XPath="span[@class='tem']",Attribute="", IsFirstInnerText=false }, new RuleField(){ DisplayName="风向",XPath="span[@class='wind']",Attribute="", IsFirstInnerText=false }, new RuleField(){ DisplayName="风力",XPath="span[@class='wind1']",Attribute="", IsFirstInnerText=false }, } }; var splider = new ArticleSplider(); var list = splider.GetByRule(rule); foreach (var item in list) { var msg = string.Empty; item.Fields.ForEach(M => { msg += $"{M.DisplayName}:{M.Value} "; }); Console.WriteLine(msg); } } 运行效果 效果再次完美! 源码 上述代码已提交到GitHub 总结探讨 综上所述,我们实现单页面的自定义规则,但也遗留了一个小问题。天气预报晴天阴天效果图,原文是用样式展示的。针对这种不规则问题,如果代码定制当然很容易,但如果做成通用,有什么好办法呢?请提出你的建议!心情好的,顺便点个推荐... 下篇文章,继续探讨多页面/递归爬虫自定义规则的实现。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
爬虫系统的意义 爬虫的意义在于采集大批量数据,然后基于此进行加工/分析,做更有意义的事情。谷歌,百度,今日头条,天眼查都离不开爬虫。 今日目标 今天我们来实践一个最简单的爬虫系统。根据Url来识别网页内容。 网页内容识别利器:HtmlAgilityPack GitHub地址 HtmlAgilityPack官网 HtmlAgilityPack的stackoverflow地址 至今Nuget已有超过900多万的下载量,应用量十分庞大。它提供的文档教程也十分简单易用。 Parser解析器 HtmlParse可以让你解析HTML并返回HtmlDocument FromFile从文件读取 /// <summary> /// 从文件读取 /// </summary> public void FromFile() { var path = @"test.html"; var doc = new HtmlDocument(); doc.Load(path); var node = doc.DocumentNode.SelectSingleNode("//body"); Console.WriteLine(node.OuterHtml); } 从字符串加载 /// <summary> /// 从字符串读取 /// </summary> public void FromString() { var html = @"<!DOCTYPE html> <html> <body> <h1>This is <b>bold</b> heading</h1> <p>This is <u>underlined</u> paragraph</p> <h2>This is <i>italic</i> heading</h2> </body> </html> "; var htmlDoc = new HtmlDocument(); htmlDoc.LoadHtml(html); var htmlBody = htmlDoc.DocumentNode.SelectSingleNode("//body"); Console.WriteLine(htmlBody.OuterHtml); } 从网络加载 /// <summary> /// 从网络地址加载 /// </summary> public void FromWeb() { var html = @"https://www.cnblogs.com/"; HtmlWeb web = new HtmlWeb(); var htmlDoc = web.Load(html); var node = htmlDoc.DocumentNode.SelectSingleNode("//div[@id='post_list']"); Console.WriteLine("Node Name: " + node.Name + "\n" + node.OuterHtml); } Selectors选择器 选择器允许您从HtmlDocument中选择HTML节点。它提供了两个方法,可以用XPath表达式筛选节点。XPath教程 SelectNodes() 返回多个节点 SelectSingleNode(String) 返回单个节点 简介到此为止,更全的用法参考 http://html-agility-pack.net 查看网页结构 我们以博客园首页为示例。用chrome分析下网页结构,可采集出推荐数,标题,内容Url,内容简要,作者,评论数,阅读数。 编码实现 建立一个Article用来接收文章信息。 public class Article { /// <summary> /// /// </summary> public string Id { get; set; } /// <summary> /// 标题 /// </summary> public string Title { get; set; } /// <summary> /// 概要 /// </summary> public string Summary { get; set; } /// <summary> /// 文章链接 /// </summary> public string Url { get; set; } /// <summary> /// 推荐数 /// </summary> public long Diggit { get; set; } /// <summary> /// 评论数 /// </summary> public long Comment { get; set; } /// <summary> /// 阅读数 /// </summary> public long View { get; set; } /// <summary> ///明细 /// </summary> public string Detail { get; set; } /// <summary> ///作者 /// </summary> public string Author { get; set; } /// <summary> /// 作者链接 /// </summary> public string AuthorUrl { get; set; } } 然后根据网页结构,查看XPath路径,采集内容 /// <summary> /// 解析 /// </summary> /// <returns></returns> public List<Article> ParseCnBlogs() { var url = "https://www.cnblogs.com"; HtmlWeb web = new HtmlWeb(); //1.支持从web或本地path加载html var htmlDoc = web.Load(url); var post_listnode = htmlDoc.DocumentNode.SelectSingleNode("//div[@id='post_list']"); Console.WriteLine("Node Name: " + post_listnode.Name + "\n" + post_listnode.OuterHtml); var postitemsNodes = post_listnode.SelectNodes("//div[@class='post_item']"); var articles = new List<Article>(); var digitRegex = @"[^0-9]+"; foreach (var item in postitemsNodes) { var article = new Article(); var diggnumnode = item.SelectSingleNode("//span[@class='diggnum']"); //body var post_item_bodynode = item.SelectSingleNode("//div[@class='post_item_body']"); var titlenode = post_item_bodynode.SelectSingleNode("//a[@class='titlelnk']"); var summarynode = post_item_bodynode.SelectSingleNode("//p[@class='post_item_summary']"); //foot var footnode = item.SelectSingleNode("//div[@class='post_item_foot']"); var authornode = footnode.ChildNodes[1]; var commentnode = item.SelectSingleNode("//span[@class='article_comment']"); var viewnode = item.SelectSingleNode("//span[@class='article_view']"); article.Diggit = int.Parse(diggnumnode.InnerText); article.Title = titlenode.InnerText; article.Url = titlenode.Attributes["href"].Value; article.Summary = titlenode.InnerHtml; article.Author = authornode.InnerText; article.AuthorUrl = authornode.Attributes["href"].Value; article.Comment = int.Parse(Regex.Replace(commentnode.ChildNodes[0].InnerText, digitRegex, "")); article.View = int.Parse(Regex.Replace(viewnode.ChildNodes[0].InnerText, digitRegex, "")); articles.Add(article); } return articles; } 查看采集结果 看到结果就惊呆了,竟然全是重复的。难道是Xpath语法理解不对么? 重温下XPath语法 XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的 表达式 描述 nodename 选取此节点的所有子节点。 / 从根节点选取。 // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 . 选取当前节点。 .. 选取当前节点的父节点。 @ 选取属性。 XPath 通配符可用来选取未知的 XML 元素 通配符 描述 * 匹配任何元素节点。 @* 匹配任何属性节点。 node() 匹配任何类型的节点。 我测试了几个语法如: //例1,会返回20个 var titlenodes = post_item_bodynode.SelectNodes("//a[@class='titlelnk']"); //会报错,因为这个a并不直接在bodynode下面,而是在子级h3元素的子级。 var titlenodes = post_item_bodynode.SelectNodes("a[@class='titlelnk']"); 然后又实验了一种: //Bingo,这个可以,但是强烈指定了下级h3,这就稍微麻烦了点。 var titlenodes = post_item_bodynode.SelectNodes("h3//a[@class='titlelnk']"); 这里就引申出了一个小问题:如何定位子级的子级?用通配符*可以么? //返回1个。 var titlenodes= post_item_bodynode.SelectNodes("*//a[@class='titlelnk']") 能正确返回1,应该是可以了,我们改下代码看下效果。然后和博客园首页数据对比,结果吻合。所以我们可以得出结论: XPath搜索以//开头时,会匹配所有的项,并不是子项。 直属子级可以直接跟上 node名称。 只想查子级的子级,可以用*代替子级,实现模糊搜索。 改过后代码如下: public List<Article> ParseCnBlogs() { var url = "https://www.cnblogs.com"; HtmlWeb web = new HtmlWeb(); //1.支持从web或本地path加载html var htmlDoc = web.Load(url); var post_listnode = htmlDoc.DocumentNode.SelectSingleNode("//div[@id='post_list']"); //Console.WriteLine("Node Name: " + post_listnode.Name + "\n" + post_listnode.OuterHtml); var postitemsNodes = post_listnode.SelectNodes("div[@class='post_item']"); var articles = new List<Article>(); var digitRegex = @"[^0-9]+"; foreach (var item in postitemsNodes) { var article = new Article(); var diggnumnode = item.SelectSingleNode("*//span[@class='diggnum']"); //body var post_item_bodynode = item.SelectSingleNode("div[@class='post_item_body']"); var titlenode = post_item_bodynode.SelectSingleNode("*//a[@class='titlelnk']"); var summarynode = post_item_bodynode.SelectSingleNode("p[@class='post_item_summary']"); //foot var footnode = post_item_bodynode.SelectSingleNode("div[@class='post_item_foot']"); var authornode = footnode.ChildNodes[1]; var commentnode = footnode.SelectSingleNode("span[@class='article_comment']"); var viewnode = footnode.SelectSingleNode("span[@class='article_view']"); article.Diggit = int.Parse(diggnumnode.InnerText); article.Title = titlenode.InnerText; article.Url = titlenode.Attributes["href"].Value; article.Summary = titlenode.InnerHtml; article.Author = authornode.InnerText; article.AuthorUrl = authornode.Attributes["href"].Value; article.Comment = int.Parse(Regex.Replace(commentnode.ChildNodes[0].InnerText, digitRegex, "")); article.View = int.Parse(Regex.Replace(viewnode.ChildNodes[0].InnerText, digitRegex, "")); articles.Add(article); } return articles; } 源码 代码已上传至 GitHub 总结 demo到此结束。谢谢观看! 下篇继续构思如何构建自定义规则,让用户可以在页面自己填写规则去识别。
主要目标 在Asp.net Core控制器中,通过自定义格式化程序来映射自定义处理控制器中的“未知”内容。 简单案例 为了演示这个问题,我们用VS2017创建一个默认的Asp.net Core Web Api项目。 [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase{ [HttpGet] public ActionResult<string> Get() { return "ok"; } [HttpPost] [Route("PostX")] public ActionResult<string> Post([FromBody] string value) { return value; } } Json请求 我们从最常见的json输入请求开始。 User-Agent: Fiddler Host: localhost:5000 Content-Type: application/json Content-Length: 16 请求body: {"123456"} 通过后台调试和fiddler抓包,我们可以看到请求输入和返回。 注意!! 别忘了[FromBody],有时候会忘的。 后台action接收类型为string的时候,请求body只能是字符串,不能传json对象。我演示这个例子时,被这点坑了。如果接收对象是一个类的时候,才可以传json对象。 没有JSON 虽然传输json数据是最常用的,但有时候我们需要支持普通的文本或者二进制信息。我们将Content-Type改为text/plain User-Agent: Fiddler Host: localhost:5000 Content-Type:text/plain Content-Length: 16 请求body: {"123456"} 悲剧的事情来,报404! 事情到此就变得稍微复杂了一些,因为asp.netcore只处理它认识的类型,如json和formdata。默认情况下,原始数据不能直接映射到控制器参数。这是个小坑,不知你踩到过没有?仔细想想,这是有道理的。MVC具有特定内容类型的映射,如果您传递的数据不符合这些内容类型,则无法转换数据,因此它假定没有匹配的端点可以处理请求。那么怎么支持原始的请求映射呢? 支持原始正文请求 不幸的是,ASP.NET Core不允许您仅通过方法参数以任何有意义的方式捕获“原始”数据。无论如何,您需要对其进行一些自定义处理Request.Body以获取原始数据,然后对其进行反序列化。 您可以捕获原始数据Request.Body并从中直接读取原始缓冲区。 最简单,最不易侵入,但不那么明显的方法是使用一个方法接受没有参数的 POST或PUT数据,然后从Request.Body以下位置读取原始数据: 读取字符串缓冲区 [HttpPost] [Route("PostText")] public async Task<string> PostText() { using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) { return await reader.ReadToEndAsync(); } } 这适用于一下Http和文本 User-Agent: Fiddler Host: localhost:5000 Content-Type: text/plain Content-Length: 6 要读取二进制数据,你可以使用以下内容: 读取byte缓冲区 [HttpPost] [Route("PostBinary")] public async Task<byte[]> PostBinary() { using (var ms = new MemoryStream(2048)) { await Request.Body.CopyToAsync(ms); return ms.ToArray(); // returns base64 encoded string JSON result } } 查看执行结果 HttpRequest静态扩展 如果你为了方便,写了很多HttpRequest的扩展,接收参数时,可以看起来更简洁一些。 public static class HttpRequestExtension { /// <summary> /// /// </summary> /// <param name="httpRequest"></param> /// <param name="encoding"></param> /// <returns></returns> public static async Task<string> GetRawBodyStringFormater(this HttpRequest httpRequest, Encoding encoding) { if (encoding == null) { encoding = Encoding.UTF8; } using (StreamReader reader = new StreamReader(httpRequest.Body, encoding)) { return await reader.ReadToEndAsync(); } } /// <summary> /// 二进制 /// </summary> /// <param name="httpRequest"></param> /// <param name="encoding"></param> /// <returns></returns> public static async Task<byte[]> GetRawBodyBinaryFormater(this HttpRequest httpRequest, Encoding encoding) { if (encoding == null) { encoding = Encoding.UTF8; } using (StreamReader reader = new StreamReader(httpRequest.Body, encoding)) { using (var ms = new MemoryStream(2048)) { await httpRequest.Body.CopyToAsync(ms); return ms.ToArray(); // returns base64 encoded string JSON result } } } } [HttpPost] [Route("PostTextX")] public async Task<string> PostTextX() { return await Request.GetRawBodyStringAsyn(); } /// <summary> /// 接收 /// </summary> /// <returns></returns> [HttpPost] [Route("PostBinaryX")] public async Task<byte[]> PostBinaryX() { return await Request.GetRawBodyBinaryAsyn(); } 自动转换文本和二进制值 上面虽然解决了原始参数转换问题,但不够友好。如果你打算像原生MVC那样自动映射参数的话,你需要做一些自定义格式化适配。 创建一个Asp.net MVC InputFormatter ASP.NET Core使用一种干净且更通用的方式来处理内容的自定义格式InputFormatter。输入格式化程序挂钩到请求处理管道,让您查看特定类型的内容以确定是否要处理它。然后,您可以阅读请求正文并对入站内容执行自己的反序列化。InputFormatter有几个要求 您需要使用[FromBody]去获取 您必须能够查看请求并确定是否以及如何处理内容。 在这个例子中,对于“原始内容”,我想查看具有以下类型的请求: text/plain(文本) appliaction/octet-stream(byte[])没有内容类型(string) 要创建格式化程序,你可以实现IInputFormatter或者从InputFormatter继承。 public class RawRequestBodyFormatter : IInputFormatter { public RawRequestBodyFormatter() { } public bool CanRead(InputFormatterContext context) { if (context == null) throw new ArgumentNullException("argument is Null"); var contentType = context.HttpContext.Request.ContentType; if (string.IsNullOrEmpty(contentType) || contentType == "text/plain" || contentType == "application/octet-stream") return true; return false; } public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context) { var request = context.HttpContext.Request; var contentType = context.HttpContext.Request.ContentType; if (string.IsNullOrEmpty(contentType) || contentType.ToLower() == "text/plain") { using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8)) { var content = await reader.ReadToEndAsync(); return await InputFormatterResult.SuccessAsync(content); } } if (contentType == "application/octet-stream") { using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8)) { using (var ms = new MemoryStream(2048)) { await request.Body.CopyToAsync(ms); var content = ms.ToArray(); return await InputFormatterResult.SuccessAsync(content); } } } return await InputFormatterResult.FailureAsync(); } } 格式化程序用于CanRead()检查对内容类型的请求以支持,然后将ReadRequestBodyAsync()内容读取和反序列化为应在控制器方法的参数中返回的结果类型。 InputFormatter必须在ConfigureServices()启动代码中注册MVC : public void ConfigureServices(IServiceCollection services) { services.AddMvc(o=>o.InputFormatters.Insert(0,new RawRequestBodyFormatter())).SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } 接受原始输入 [HttpPost] [Route("PostTextPlus")] public string PostTextPlus([FromBody] string value) { return value; } 然后你就可以发送post请求,像这样: User-Agent: Fiddler Host: localhost:5000 Content-Length: 6 或者 User-Agent: Fiddler Host: localhost:5000 Content-Type:text/plain Content-Length: 6 请注意,您可以使用内容类型调用相同的控制器方法application/json并传递JSON字符串,这也将起作用。在RawRequestBodyFormatter 简单地增加它支持的附加内容类型的支持。 二进制数据 [HttpPost] [Route("PostBinaryPlus")] public byte[] PostBinaryPlus([FromBody] byte[] value) { return value; } 请求内容如下: User-Agent: Fiddler Host: localhost:5000 Content-Length: 6 Content-Type: application/octet-stream 源代码 示例代码已上传到 CsharpFanDemo 参考链接 本文包含翻译和自己实践。主要思路和代码来源于以下链接:Accepting Raw Request Body Content in ASP.NET Core API Controllers
消息队列现今的应用场景越来越大,常用的有RabbmitMQ和KafKa。我们用BlockingCollection来实现简单的消息队列。 实现消息队列 用Vs2017创建一个控制台应用程序。创建DemoQueueBlock类,封装一些常用判断。 HasEle,判断是否有元素 Add向队列中添加元素 Take从队列中取出元素 为了不把BlockingCollection直接暴漏给使用者,我们封装一个DemoQueueBlock类 /// <summary> /// BlockingCollection演示消息队列 /// </summary> /// <typeparam name="T"></typeparam> public class DemoQueueBlock<T> where T : class { private static BlockingCollection<T> Colls; public DemoQueueBlock() { } public static bool IsComleted() { if (Colls != null && Colls.IsCompleted) { return true; } return false; } public static bool HasEle() { if (Colls != null && Colls.Count>0) { return true; } return false; } public static bool Add(T msg) { if (Colls == null) { Colls = new BlockingCollection<T>(); } Colls.Add(msg); return true; } public static T Take() { if (Colls == null) { Colls = new BlockingCollection<T>(); } return Colls.Take(); } } /// <summary> /// 消息体 /// </summary> public class DemoMessage { public string BusinessType { get; set; } public string BusinessId { get; set; } public string Body { get; set; } } 添加元素进队列 通过控制台,添加元素 //添加元素 while (true) { Console.WriteLine("请输入队列"); var read = Console.ReadLine(); if (read == "exit") { return; } DemoQueueBlock<DemoMessage>.Add(new DemoMessage() { BusinessId = read }); } 消费队列 通过判断IsComleted,来确定是否获取队列 Task.Factory.StartNew(() => { //从队列中取元素。 while (!DemoQueueBlock<DemoMessage>.IsComleted()) { try { var m = DemoQueueBlock<DemoMessage>.Take(); Console.WriteLine("已消费:" + m.BusinessId); } catch (Exception ex) { Console.WriteLine(ex.Message); } } }); 查看运行结果 这样我们就实现了简易的消息队列。 示例源码 简易队列 参考链接 BlockingCollectionOrleans源码分析
主要目标 在Asp.net Core控制器中,通过自定义格式化程序来映射自定义处理控制器中的“未知”内容。 简单案例 为了演示这个问题,我们用VS2017创建一个默认的Asp.net Core Web Api项目。 [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase{ [HttpGet] public ActionResult<string> Get() { return "ok"; } [HttpPost] [Route("PostX")] public ActionResult<string> Post([FromBody] string value) { return value; } } Json请求 我们从最常见的json输入请求开始。 User-Agent: Fiddler Host: localhost:5000 Content-Type: application/json Content-Length: 16 请求body: {"123456"} 通过后台调试和fiddler抓包,我们可以看到请求输入和返回。 后台调试,查看请求输入结果 fiddler查看请求header fiddler查看返回结果 注意!! 别忘了[FromBody],有时候会忘的。 后台action接收类型为string的时候,请求body只能是字符串,不能传json对象。我演示这个例子时,被这点坑了。如果接收对象是一个类的时候,才可以传json对象。 没有JSON 虽然传输json数据是最常用的,但有时候我们需要支持普通的文本或者二进制信息。我们将Content-Type改为text/plain User-Agent: Fiddler Host: localhost:5000 Content-Type:text/plain Content-Length: 16 请求body: {"123456"} 悲剧的事情来,报404! 不支持text/plain 事情到此就变得稍微复杂了一些,因为asp.netcore只处理它认识的类型,如json和formdata。默认情况下,原始数据不能直接映射到控制器参数。这是个小坑,不知你踩到过没有?仔细想想,这是有道理的。MVC具有特定内容类型的映射,如果您传递的数据不符合这些内容类型,则无法转换数据,因此它假定没有匹配的端点可以处理请求。那么怎么支持原始的请求映射呢? 支持原始正文请求 不幸的是,ASP.NET Core不允许您仅通过方法参数以任何有意义的方式捕获“原始”数据。无论如何,您需要对其进行一些自定义处理Request.Body以获取原始数据,然后对其进行反序列化。 您可以捕获原始数据Request.Body并从中直接读取原始缓冲区。 最简单,最不易侵入,但不那么明显的方法是使用一个方法接受没有参数的 POST或PUT数据,然后从Request.Body以下位置读取原始数据: 读取字符串缓冲区 [HttpPost] [Route("PostText")] public async Task<string> PostText() { using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8)) { return await reader.ReadToEndAsync(); } } 这适用于一下Http和文本 User-Agent: Fiddler Host: localhost:5000 Content-Type: text/plain Content-Length: 6 要读取二进制数据,你可以使用以下内容: 读取byte缓冲区 [HttpPost] [Route("PostBinary")] public async Task<byte[]> PostBinary() { using (var ms = new MemoryStream(2048)) { await Request.Body.CopyToAsync(ms); return ms.ToArray(); // returns base64 encoded string JSON result } } 查看执行结果 接收文本内容 接收二进制数据 HttpRequest静态扩展 如果你为了方便,写了很多HttpRequest的扩展,接收参数时,可以看起来更简洁一些。 public static class HttpRequestExtension { /// <summary> /// /// </summary> /// <param name="httpRequest"></param> /// <param name="encoding"></param> /// <returns></returns> public static async Task<string> GetRawBodyStringFormater(this HttpRequest httpRequest, Encoding encoding) { if (encoding == null) { encoding = Encoding.UTF8; } using (StreamReader reader = new StreamReader(httpRequest.Body, encoding)) { return await reader.ReadToEndAsync(); } } /// <summary> /// 二进制 /// </summary> /// <param name="httpRequest"></param> /// <param name="encoding"></param> /// <returns></returns> public static async Task<byte[]> GetRawBodyBinaryFormater(this HttpRequest httpRequest, Encoding encoding) { if (encoding == null) { encoding = Encoding.UTF8; } using (StreamReader reader = new StreamReader(httpRequest.Body, encoding)) { using (var ms = new MemoryStream(2048)) { await httpRequest.Body.CopyToAsync(ms); return ms.ToArray(); // returns base64 encoded string JSON result } } } } [HttpPost] [Route("PostTextX")] public async Task<string> PostTextX() { return await Request.GetRawBodyStringAsyn(); } /// <summary> /// 接收 /// </summary> /// <returns></returns> [HttpPost] [Route("PostBinaryX")] public async Task<byte[]> PostBinaryX() { return await Request.GetRawBodyBinaryAsyn(); } 自动转换文本和二进制值 上面虽然解决了原始参数转换问题,但不够友好。如果你打算像原生MVC那样自动映射参数的话,你需要做一些自定义格式化适配。 创建一个Asp.net MVC InputFormatter ASP.NET Core使用一种干净且更通用的方式来处理内容的自定义格式InputFormatter。输入格式化程序挂钩到请求处理管道,让您查看特定类型的内容以确定是否要处理它。然后,您可以阅读请求正文并对入站内容执行自己的反序列化。InputFormatter有几个要求 您需要使用[FromBody]去获取 您必须能够查看请求并确定是否以及如何处理内容。 在这个例子中,对于“原始内容”,我想查看具有以下类型的请求: text/plain(文本) appliaction/octet-stream(byte[])没有内容类型(string) 要创建格式化程序,你可以实现IInputFormatter或者从InputFormatter继承。 public class RawRequestBodyFormatter : IInputFormatter { public RawRequestBodyFormatter() { } public bool CanRead(InputFormatterContext context) { if (context == null) throw new ArgumentNullException("argument is Null"); var contentType = context.HttpContext.Request.ContentType; if (string.IsNullOrEmpty(contentType) || contentType == "text/plain" || contentType == "application/octet-stream") return true; return false; } public async Task<InputFormatterResult> ReadAsync(InputFormatterContext context) { var request = context.HttpContext.Request; var contentType = context.HttpContext.Request.ContentType; if (string.IsNullOrEmpty(contentType) || contentType.ToLower() == "text/plain") { using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8)) { var content = await reader.ReadToEndAsync(); return await InputFormatterResult.SuccessAsync(content); } } if (contentType == "application/octet-stream") { using (StreamReader reader = new StreamReader(request.Body, Encoding.UTF8)) { using (var ms = new MemoryStream(2048)) { await request.Body.CopyToAsync(ms); var content = ms.ToArray(); return await InputFormatterResult.SuccessAsync(content); } } } return await InputFormatterResult.FailureAsync(); } } 格式化程序用于CanRead()检查对内容类型的请求以支持,然后将ReadRequestBodyAsync()内容读取和反序列化为应在控制器方法的参数中返回的结果类型。 InputFormatter必须在ConfigureServices()启动代码中注册MVC : public void ConfigureServices(IServiceCollection services) { services.AddMvc(o=>o.InputFormatters.Insert(0,new RawRequestBodyFormatter())).SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } 接受原始输入 [HttpPost] [Route("PostTextPlus")] public string PostTextPlus([FromBody] string value) { return value; } 然后你就可以发送post请求,像这样: User-Agent: Fiddler Host: localhost:5000 Content-Length: 6 或者 User-Agent: Fiddler Host: localhost:5000 Content-Type:text/plain Content-Length: 6 请注意,您可以使用内容类型调用相同的控制器方法application/json并传递JSON字符串,这也将起作用。在RawRequestBodyFormatter 简单地增加它支持的附加内容类型的支持。 二进制数据 [HttpPost] [Route("PostBinaryPlus")] public byte[] PostBinaryPlus([FromBody] byte[] value) { return value; } 请求内容如下: User-Agent: Fiddler Host: localhost:5000 Content-Length: 6 Content-Type: application/octet-stream 源代码 示例代码已上传到 CsharpFanDemo 参考链接 本文包含翻译和自己实践。主要思路和代码来源于以下链接:Accepting Raw Request Body Content in ASP.NET Core API Controllers 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
最近写的文字很多。但我读了后发现很多是在写自己。 一个不成功的自己有什么好写的? 以后每个月无营养的杂记不得超过五分之一。 也就是说,五篇带代码实践的文章之后才能写一篇杂记。 那些不成功的过去,就过去吧,不要在恬不知耻的回忆来回忆去。 这样还不如花时间去写小说! 多参考下好博文是怎么写的。连个案例都讲不清的文章徒然浪费时光,还不如去看小说! 此篇终! 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
回顾 十年前,还未踏入某校时,便听闻某学长一毕业就入职北京某公司,月薪过万。对于一个名不见经传的小学院,一毕业能拿到这个薪水还是非常厉害的。听闻他学生期间参与开发了一款股票软件,股票那时正迎来一波疯涨。时也运也。我那时心里就想,只会软件也行不通吧,至少要熟悉股票规则。在还未踏入编程大门时,我就清楚的认识了软件服务于业务的本质。 等刚开始工作时,从事些较简单的工作,也是需要和使用人员讨论需求,文档编写和开发实现。性质偏向于公司内部财务人员或业务人员管理用的子系统。也许厌烦了写的代码用的人太少,于是转移到了互联网类型的公司。在日益复杂的业务与软件规模下,以前用的熟练的三板斧渐渐适应不了,知识库需要更新了。结合以前的工作实践,按自己的理解,重新解读下领域驱动设计。 第一部分 运用领域模型 按照一个系统的开发步骤,除了前期招标,合同,预算,人员规划等其他项目管理的范畴外,真正执行到系统部分是从沟通业务需求开始的。 第一章 消化知识 几年前我们要做一个院系资产管理系统,最开始理解的有人申请,管理员审核购买,分发扣库存的逻辑。实际讨论下来之后,分为很多流程。如设备提申请,教务处审批,院系审批,提交财务核账。又涉及到固定资产折旧,退货流程,又要财务核对。又有家具的申请与退货等其他。还有定期的报表功能。基于我们开发人员和校方人员,都对资产审核,退货,对账流程都有一定熟悉度,所以沟通下来业务大框架还算顺利。我理解为我们在沟通业务的过程中,有了相似的认识,并在磨合过程中,修炼完善。DDD一书中,以PCB电路板软件工具为开篇,讲述了PCB专家和开发人员沟通中从最开始的很难沟通,到最后依据流程图及PCB元件执行逻辑完成了语言上的沟通统一。很幸运,我们大部分的业务并没有如文中跨度那么大。 在沟通的过程中,业务专家需要理解共同构建的业务模型,开发人员也要依据业务模型来勾思大概实现逻辑。就比如设备申请,家具申请,XX申请;设备退货,资产退货。这些有共同性,又有差异的流程,如何更好的抽象,来实现复用?如果单纯开发人员自己抽象得到概念有可能是很幼稚的,开发出来的软件只能做基本工作,无法充分反映领域专家的思考方式。 领域专家和开发人员共同参与,一起来丰富抽象的模型。提炼模型,对于领域专家来说也是升华自己思考完善自己理解的过程。会更加注重概念的严谨性。 模型在不断改进的同时,也称为组织项目信息的工具。模型聚焦于需求分析。它与编程和设计紧密交互。 知识丰富的设计 举一个判断是否合并账号的逻辑。一个请求中的手机,邮箱账号,根据账号的是否验证,以及数据库中手机号邮箱的是否存在是否验证来判断是否合并账号。产品列举了81条合并规则。 我最开始想到了策略设计模式。根据各种状态分析出主要的几个策略来实现判断。工作量相当复杂,而且易出错。同事建议了另外一种规则式的实践。对比新账号的状态和筛选中的存在账号状态,形成一个规则,看这个规则符合那81条规则的哪一种。这样代码量指数级下降,也通用。而且其他人也更容易根据产品的文档,直接看懂代码。模型与实现一致。 书中依据航线超卖为例,举了两个例子,一个是简单的if超卖判断,一个把超卖独立成一个策略类来判断超卖。并强调超卖在模型中不仅仅是一个简单的判断,而是一个让所有人看到代码都明白是一个独特的策略。 经过以上对比,你会发现设计模式有它自己的适用场景,不要随便套用。第二点设计的模型和代码实现一致。 深层模型 说到太极,外是软绵绵的一套动作。如果按软件直接开发,实现出来的是错的。因为陈家沟的领域专家们会告诉你太极每一招都是制人招。这个我信,如果有人喂招的话,分秒钟被干到地,对付普通人还是有效的。 这里说的后续的制人招是深层模型,我们看到的慢腾腾的动作是表层。这样说很容易理解。 第二章 交流与语言的使用 通用语言 领域专家和开发人员语言要一致。将模型作为语言的支柱。确保团队在内部的所有交流中以及代码中坚持使用这种语言。 书面设计文档 文档应作为代码和口头交流的补充 文档和图 用图来沟通交流,能促进头脑风暴。但模型不是图。 本篇文章主要是应用自己亲身经历的案例来重新解读领域驱动。 本篇结束,谢谢观看。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
最近一鼓作气买了两本久负盛名的书《领域驱动设计 软件核心复杂性应对之道 》和《实现领域驱动设计》。开卷有益,在学习的途中,做些笔记巩固下,并记录下感想。先来说下《实现领域驱动设计》,大致翻了翻,本书并不是《算法导论》那种很高端难懂的类型,是属于思考实践总结类型的。我相信,一章一章看下去,会有很多收获的。本篇文章是个开篇说明,并不深究具体细节,讲述如何使用《实现领域驱动设计》。 DDD总览 DDD的通用语言(Ubiquitous Language)作用于某个限界上下文(Bounded Context),它对于领域建模是非常重要的。 战略建模 限界上下文是一种概念上的边界,领域模型方便工作于其中。同时,限界上下文为通用语言提供了一套环境,项目成员通过通用语言来表达软件模型,如流程图: 架构 战术建模 站术设计的一个重要模式是聚合(Aggregate),聚合可以由单个实体(Entity)组成,也可以由一组实体和值对象(Value Object)组成,此时我们必须在聚合的整个生命周期保证事务上的一致性。 聚合实例通过资源库(Repository)进行持久化,另外对聚合的查找和获取也通过资源库完成。如图,两个聚合类型,它们拥有各自的事务一致性的边界。 在领域模型中,有些业务操作并不能自然的放在实体或值对象上,此时我们可以使用无状态的领域服务(Domain Service),领域服务执行特定与领域的操作,其中可能涉及多个领域对象 领域事件(Domain Event)表示领域模型中发生的重要事件。有多种方式可以对领域事件进行建模。在对聚合进行命令操作时,聚合本身将发布领域事件。 我们通常忽略了模块(Module),但是正确的设计模块同样重要,模块内包含的领域对象应该是内聚在一起的。 以上内容主要提到了通用语言,限界上下文,上下文映射图,聚合,实体,资源库,值对象,领域服务,领域事件,模块等概念。 书上总结内容完毕。 DDD不是无中生有,自动产生的。它来源于业务专家或软件开发师在沟通提炼业务的基础上归纳概括而来。所以在看书的过程中,会产生这个例子我见过,我用了相同的方法实现,感同身受的感受。也许当时你只是把问题解决了,并没有引起你多大的深思。但有些人,有些思想家遇到了很多的例子,成功或失败的例子,他们思考总结,写的多了,最后成了一本书。嘭的一声巨响,一个领域专家诞生了! 本篇完毕,谢谢观看。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
无限合并 最近工作上接到一个需求模块:关于账号自动合并的问题。简化来讲,手机1和邮箱1是一个账号,手机1和邮箱2请求过来创建账号时,由于手机号相同,自动合并为一个账号。手机3和邮箱2再过来请求创建账号,由于邮箱相同,自动合并为一个账号。手机3和邮箱4过来请求创建账号时,又因为手机号相同,再次合并为一个账号……假如是个访问量很大并且又这么巧的时候,就类似于无限合并了。实际上可能仅会出现几笔,不会这样无限循环下去。但我是一个容易多想的人。账号合并,又关联着和账号相关的数据的迁移,从我个人的角度来说,这样没有边界防御的需求,我内心是拒绝的,但还没想好更好的办法,暂且如此了。 这个话题联想到微服务,微服务能解决这种问题么?很遗憾,微服务并不是想象中的那么强大。账号合并本质上可以做成一个微服务,但微服务并不能解决这种业务问题。 我认识的微服务是为了方便水平扩展,方便使用体验异构技术,方便快速试错,方便部署,能最终实现高可用高并发。你了解再多的微服务知识,也不是用于解决此类业务问题。 对于此类业务问题,一般方式是判断需求是否合理?不合理的需求可以适当拒绝掉。 再着看是谁提的?如果是普通客户,尽量用其他更简便容易维护的方式代替。如果是金主或上层领导派发需求,那就只能在总结风险的基础上,一步一步往前看吧。 第三看需求是否通用,通用化的解决方案一般是更易理解,更适合推广。定制化得需求是耗时耗力的。 第四判断影响范围,如果是个高风险,又耗时又要牵扯很多旧业务,你敢动么?谨慎谨慎再谨慎。 需求判断阶段完毕,下面说说规避风险 第一,清晰的标注需求影响边界,改动后要及时单元测试或人工测试。你改的任何一行代码都有可能引发一个隐藏的碧游鸡。 第二,不要盲目动刀,一定要分析需求。对需求茫然的情况下,盲目码砖,你会很累的。更有甚者,需求本身只是显示了冰山一角,还有广大的未知冰山底层埋藏。如果不能提前发现,你的时间会越来越短,要做的事反而越来越多。这是一种失控。有时候失控是可预知但必须迎难而上的。有时候又可以轻易靠几句话轻易甩锅的。这本来也是一种修炼。 第三,人员分配。对合作的项目成员要有必要的了解,擅长的人做擅长的事。 第四,会议纪要。有时候频繁地会议是少不了的,有营养的思路应该记录下来方便实践。有疑问应及时去讨论,不要想当然。想当然是最浪费时间的事情。 这些事情和微服务无关。但这些功能最终可以称为一个微服务。这可以理解为微服务开发过程如何识别需求,开发需求的思路。 微服务来源于生活高于生活。 首发简书:https://www.jianshu.com/p/ab4f11fb4e77 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
电动车牌照 大上海生活不易,由于住的地方离工作地较远,买车开车消费较大,也相当堵车,做公交也是堵的很慢。所以买了个电动车,上班体验上升很多。昨天看天气还好,就请了个假去上车牌。到交警大队,先验车,然后复印电动车合格证,发票,身份证,居住证。然后排队领证。 以上事情完毕。重点说下回来途中,路经一个小广场,非机动车道一侧,一个绿化工人双手抬着那种带电机的修草机在修剪冬青。我看着有点危险有靠另一边过去。那家伙突然来个大回环修草机转了圈。把我吓得不轻,差点车倒。还好那里的非机动车宽一些,要不然新车或我的腿得开个口子了。真是气煞我也!停车后,我愤怒的瞪了他一眼,看他脸色也吓得不轻,我也没有继续深究。 以上事情完毕。 一条一条来总结 微服务授权验证 办证,办车牌,买房等生活事,在和谐的大中国以及外国,都需要各种证。相互依赖。 在微服务世界里,也应该提供类似的授权认证。这是一道安全防线。 微服务预警 路如果太窄,或微服务世界里的硬盘空间不足,网络访问量超限等风险情况,要提前预知,进行报警。现实生活里可以通过鸣笛来提醒对方有人来注意安全,或者行人自主根据安全来即时停下或绕开。这在微服务世界里,都要有对应的体现。要考虑内存,cpu,硬盘,网络访问量等各个维度的监控。根据预警情报自动或人工辅助调节。 微服务分布式监控 说起车牌,各种各样的车牌,行驶在大街小巷,在街道鹰眼的监控下,行驶轨迹一幕了然。证换来车牌,鹰眼监控车牌而不直接监控你的身份证。这类似于软件世界里的解藕。我们思路切换到微服务世界,某个关键间合参数生成唯一key,这个key在系统间的任何调用都做记录,形成监控。每个key每时每刻都记录成典,分散开来,无边无界,形成了微服务的分布式业务监控。 服务隔离 为了安全原因,出现了机动车,非机动车,人行步道。转换成软件思路,我们就需要根据安全,耗时,业务等原因,需要对部分业务进行隔离,不因某部分影响其他部分逻辑。 限界上下文 领域驱动设计里面常提到限界上下文,用来区分各业务线的边界。车行道,非机动车行道正是天然符合这个实践。又在不同的路口提供了交叉转移等。有界,又提供必要的互通。 微服务来源于生活,高于生活。 由于简书手机端操作方便,已首发简书:https://www.jianshu.com/p/2b681b1c7947 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
Bing.com在.NET Core 2.1上运行! 相关知识请参考.netCore开发团队博客(https://blogs.msdn.microsoft.com/dotnet/) Bing.com是一种云服务,运行在遍布全球许多数据中心的数千台服务器上。Bing服务器每秒处理来自全球消费者的数千个用户查询,通过他们的浏览器,使用Microsoft认知服务API的合作伙伴以及个人数字助理Cortana进行搜索。我们的用户要求这些结果具有相关性和速度,因此性能和可靠性是运行Bing等成功云服务的关键组件。 Bing的前端堆栈主要是以MVC模式分层的托管代码编写的。大多数业务逻辑代码都是用C#编写的数据模型,视图逻辑是用Razor编写的。该层负责将搜索结果数据(编码为Microsoft Bond)转换为HTML,然后将其压缩并发送到浏览器。作为Bing的前端平台的守门人,我们将开发人员的工作效率和功能敏捷性视为我们成功定义中的附加关键组件。数以百计的开发人员依靠这个平台将他们的功能投入生产,他们希望它能像钟表一样运行。 从一开始,Bing.com就在.NET Framework上运行,但它最近已转换为在.NET Core上运行。推动Bing.com采用.NET Core的主要原因是性能(即服务延迟),支持并行和应用程序本地安装,与机器范围的安装(或缺少安装)和ReadyToRun映像无关。为了实现这些改进,我们开始努力使代码在.NET实现中可移植,而不是依赖于仅在Windows上可用且仅与.NET Framework一起使用的库。团队开始使用.NET Standard 1.x,但是减少的API表面为我们的代码迁移带来了非常重要的复杂性。使用.NET Standard 2.0返回的20,000多个API,一切都改变了,我们能够迅速从代码修改转移到测试。在压缩了一些bug后,我们准备将.NET Core部署到生产环境中。 ReadyToRun图像 托管应用程序通常可能具有较差的启动性能,因为首先必须将JIT编译为机器代码。.NET Framework具有预编译技术NGEN。但是,NGEN需要在将执行代码的计算机上执行预编译步骤。对于Bing来说,这意味着NGENing成千上万的机器。随着应用程序在Web服务机器上进行预编译,这与积极的部署周期相结合将导致显着的服务容量减少。此外,运行NGEN需要管理权限,这些权限在数据中心设置中通常不可用或经过严格审查。在.NET Core上,crossgen 工具允许将代码预编译为预部署步骤,例如在构建实验室中,并且部署到生产的映像已准备好运行! 性能 .NET Core 2.1几乎在运行时和库的所有领域都进行了重大的性能改进; 博客上一篇文章中提供了一篇很好的论文。 我们的生产数据与.NET Core 2.1中的显着性能改进(与.NET Core 2.0和.NET Framework 4.7.2相比)产生了共鸣。下图跟踪了过去几个月内部服务器的延迟情况。Y轴是延迟(省略实际值),最后的急剧下降(6月2日)是.NET Core 2.1的部署!这一切都提高了34%,这要归功于.NET社区的辛勤工作! .NET Core 2.1中的以下更改是我们工作负载的显着改进的亮点。它们以降低的影响顺序呈现。 矢量化string.Equals(@jkotas)和string.IndexOf/LastIndexOf(@eerhardt) 无论您采用哪种方式切片,HTML渲染和操作都是字符串繁重的工作负载。字符串比较和索引操作是其中的主要组成部分。这些操作的矢量化是我们测量的性能改进的最大贡献者。 EqualityComparer<T>.Default(@AndyAyersMS)的虚拟化支持 我们的主要软件组件之一是重度用户Dictionary<int/long, V>,间接受益于JIT中为了Dictionary<K, V>优化而进行的内在识别工作(@benaadams) 软件写入监视并发GC(@ Maoni0和@kouvel) 这导致我们的应用程序中CPU使用率降低。在.NET Core 2.1之前,Windows x64(以及.NET Framework)上的写入监视是使用具有不同性能权衡的Windows API实现的。这个新实现依赖于JIT写屏障,它直观地增加了参考商店的成本,但是这个成本是摊销的,而且在我们的工作量中没有注意到。此改进现在也可以通过2018年5月的安全性和质量汇总在.NET Framework上获得 使用calli的方法现在可以内联(@AndyAyersMS和@mjsabby) 我们在代码的性能关键部分中使用ldftn+ calli代替委托(这会产生对象分配),其中需要间接调用托管方法。此更改允许具有calli指令的方法体具有内联条件。我们的依赖注入框架生成这样的方法 提高string.IndexOfAny的2&3 char搜索性能(@bbowyersmyth) 前端堆栈中的常见操作是在字符串中搜索“:”,“/”,“/”以分隔URL的各个部分。这种特殊的外壳改进在整个代码库中都是有益的。 除了运行时更改之外,.NET Core 2.1还为.NET库生态系统带来了Brotli支持。Bing.com使用此功能动态压缩内容并将其提供给支持的浏览器。 运行时敏捷 最后,在我们的应用程序中拥有运行时的xcopy版本的能力意味着我们能够以更快的速度采用更新版本的运行时。事实上,如果您查看上面的图表,我们将在6月2日(即发布后的两天)的常规应用程序部署中全球范围内进行.NET Core 2.1更新! 这是可能的,因为我们在.NET Core的每日CI构建测试功能和性能的整个版本中运行我们的持续集成(CI)管道。 我们对未来感到兴奋,并与.NET团队密切合作,帮助他们确定未来的更新资格!.NET Core团队很兴奋,因为我们提供了大量的功能测试和额外的大型代码库来衡量实际的性能改进,以及我们致力于为Bing.com用户提供快速结果以及我们自己的开发人员使用最新的软件和工具。 本篇文章原址:Bing.com runs on .NET Core 2.1! 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
Window下mysql环境配置问题整理 参考如下链接。 无需安装解压版mysql包 创建选项配置 首次启动服务 用mysqld初始化目录 安装后设置和测试 启动服务错误信息 管理员模式打开cmd mysqld install net start mysql MYSql服务正在启动 MYSql服务无法启动 解决办法 删除mysql安装主目录下的data目录下所有文件 mysqld --initialize --console C:\WINDOWS\system32> mysqld --initizlize --console mysqld: Can't change dir to 'D:\Program Files\mysql-8.0.12-winx64\data\' (OS errno 2 - No such file or directory) 2018-08-17T01:54:27.145772Z 0 [System] [MY-010116] [Server] D:\Program Files\mysql-8.0.12-winx64\bin\mysqld.exe (mysqld 8.0.12) starting as process 17876 2018-08-17T01:54:27.148482Z 0 [Warning] [MY-010091] [Server] Can't create test file D:\Program Files\mysql-8.0.12-winx64\data\DESKTOP-SET72RE.lower-test 2018-08-17T01:54:27.148571Z 0 [Warning] [MY-010091] [Server] Can't create test file D:\Program Files\mysql-8.0.12-winx64\data\DESKTOP-SET72RE.lower-test 2018-08-17T01:54:27.149798Z 0 [ERROR] [MY-010172] [Server] failed to set datadir to D:\Program Files\mysql-8.0.12-winx64\data\ 2018-08-17T01:54:27.169994Z 0 [ERROR] [MY-010119] [Server] Aborting 2018-08-17T01:54:27.172048Z 0 [System] [MY-010910] [Server] D:\Program Files\mysql-8.0.12-winx64\bin\mysqld.exe: Shutdown complete (mysqld 8.0.12) MySQL Community Server - GPL. 如上述报错的话,加上user,并记录初始密码 mysqld --initialize --user=root --console C:\WINDOWS\system32>mysqld --initialize --user=root --console 2018-08-17T03:06:51.636920Z 0 [System] [MY-013169] [Server] D:\Program Files\mysql-8.0.12-winx64\bin\mysqld.exe (mysqld 8.0.12) initializing of server in progress as process 7832 2018-08-17T03:06:54.218851Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: yacuOdqg/6Mn 2018-08-17T03:06:55.288246Z 0 [System] [MY-013170] [Server] D:\Program Files\mysql-8.0.12-winx64\bin\mysqld.exe (mysqld 8.0.12) initializing of server has completed 安装服务 mysqld install C:\WINDOWS\system32>mysqld install Service successfully installed. 启动服务 net start mysql C:\WINDOWS\system32>net start mysql MySQL 服务正在启动 . MySQL 服务已经启动成功。 登陆系统 如登陆不上,需要运行net stop mysql, 然后重新运行下mysqld --initialize --user=root --console mysql -u root -p C:\WINDOWS\system32>mysql -u root -p Enter password: ************ Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 8.0.12 Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql> 修改密码 ALTER USER 'root'@'localhost' IDENTIFIED BY 'fjdoe13232'; mysql执行语句一定要以;结尾。切记。 mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'fjdoe13232'; Query OK, 0 rows affected (0.11 sec) 常用命令 show databases; mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | +--------------------+ 4 rows in set (0.01 sec) mysql> create database 数据库; mysql> create database ustest; Query OK, 1 row affected (0.03 sec) mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | ustest | +--------------------+ 5 rows in set (0.00 sec) mysql> use ustest; Database changed 以上是简单环境配置。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
一天最惬意的时光莫过于晚上坐在阳台里,远望星空。虽然是极晚了,云朵仍然不忘旅行,逢着晴朗天气,还能遇到星星出没。 星空之下,小区的东边,有一条大道直通南北,大道两旁的照明灯远远看去,像是闪烁的星星。正如郭沫若所说:远远的街灯明了,好像闪着无数的明星。天上的明星现了,好像点着无数的街灯。 看到这迷幻的情景,我又想到凯文凯利的<<失控>>,里面以工蜂和蚂蚁的社群为例讲述了现在或未来社会无中心分布式得协同生活。我之前并未看完,但此本书面向未来,具有超前思想,很适合人工智能,分布式等扩展思维。 目前火热的云计算也是借鉴了自然。道法自然。生于斯,长于斯,成长于斯。就连最基础的软件工程,也是取决于建筑行业。 爱思考的人脑海总是天马星空,如没有记载,很快将会遗失到九宵云外。 任务调度 软件中的各种定时,延时作业是否类似于车来车往的十字路口,红绿灯的定时切换现象?扩展思维,车流人流类似任务,红绿灯像是个带状态的定时器,车道类似线程。 在同一个十字路口,车道的总数是固定的,他们是前进还是停止是观看对应的交通灯状态来自主决定。你如果不想出事故,一般都会遵守交通规约的。一般软件里面是时间轮询,主动去触发任务,和现实世界人流观看交通灯决策是相反的。这两种哪种更高呢?在设计任务调度时,是否可以考虑任务根据调度系统的时间状态来自行决定执行?这仿佛是一种待提炼的设计模式。有待开拓。 在交通灯故障或车流量过载时,一般需要交警或辅警进行交通指挥,这类似一种补偿机制。也类似是调度中心过载时备用方案。软件设计中,我们要思考这种备用么? 学而不思则罔,思而不学则殆。 高并发 大家对春运的人流量都有很深的印象。单个点要进站的有很多,同时进去不现实。这里就需要分批排队机制,延长护栏机制,茶水室优先进入机制。 分批排队机制确保按车次有序分批进入, 延长护栏机制,一到春运,护栏就格外的长,这样也有好处,不会把流量堵到一个点,分散延长。 茶水室类似为付钱或其他人士提供优先服务支持。 仔细想想,高并发,不就是负载均衡分流,验证码或拖拽验证等机制延长操作时间,某些关键业务保持优先级。其他业务可以异步。 设计思想来源于生活,高于生活。 微服务网关 说到网关对服务的监控,限流,熔断,异常重试等操作,你会不会想起十字路口的摄像头,大桥,保险丝等?服务的隔离,是不是想起了集装箱。最著名的容器docker更是以集装箱距离。我们离生活如此之近,离代码如此之远。 人法地,地法天,天法道,道法自然. 由于简书移动端的编辑支持良好,首发简书。 https://www.jianshu.com/p/e6f0525245de?utm_campaign=hugo&utm_medium=reader_share&utm_content=note 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
十年如歌,一曲终散 回顾十年之前,2008年8月8日,那是个值得我铭记的日子,我个人初次离开长大的地方去外省求学,伴随着失意与期望,到达了另外一个地方。那天晚上,在另一个省份从电视上见证了北京奥运会的开幕。 今天2018年8月8日,整整十年过去了,趁着夜幕降临,梳理下过去的种种,给未来做借鉴。 08-09年 08~09年的时候,社交网站51,开心网,人人网如火如荼,偷菜,农场等网页小游戏占据了庞大的流量,但最终都败给了腾讯,最后无人问津。 微软.net发布了重大的3.5版本,推出WCF,WPF,Silverlight,linq等一系列应用,现在Silverlight早已消亡,linq已经成为.net必不可少的东西了。 10-12年 10年iphone4s火爆全球,彻夜排队的盛况经历过的人总会有印象。移动互联网开始起苗头。 10~12年电商寒冬,白团大战,一片热潮一片寒冬。小米手机开始发力,微信也开始展露头脚。当然还有臭名昭著的3q大战。 13-18年 13年~18年微服务概念开始盛行,微软更换ceo全面拥抱云计算,人工智能。开源.netcore企图扭转颓势。java生态更加如日中天。 直播,3d打印,比特币,LBS风口一个接一个,消亡的一个比一个快。 以上简述仅凭记忆。 钻石恒久远,一颗永相传 人的精力是有限的,纵观历史,哪些知识是恒定稳健的呢?当然是与系统,网络相关的部分,如tcp,网络,线程进程等。如果盲目只追求最新框架,是会累死的。Silverlight,WPF我一点没看过,就是因为觉着用处太小。sharepoint之类的用户群也较小,也没有去熟悉。 虽然微软的东西淘汰了很多但.netcore我是完全看好的,因为它够颠覆,够轻量级。这个是肯定要学的。 除了这些编码类的,还有思想理论类的,如算法之类的,人工智能,机器学习的,未来二十年都不会过时。但这些需要大量的知识储备,不是简单看个demo就会的。 还有些敏捷开发,驱动设计,微服务架构,分布式之类,十年之内都会有它的应用价值。 说完了上面的虚的,来点实际的。.net或java各种各样的理念实践,最终都离不开落库的,所以保证消息队列,数据库(sqlserver,MySQL,mongodb),缓存redis,搜索引擎Elasticsearch等,加深这些知识的储备,也不会太过时。 每想起遥远的以前在搜索gridview七十二变就觉得好可笑。虽然aspx淘汰了,但不可否认现在流行的前端框架也都类似得影子,就只怪aspx放到了服务端了吧,如果当时是个前端框架,也不会这么衰。 除非是专门写中间件和框架的人,大部分其他人都是要写业务的。所以临时流行性的东西也不可错过,但你要大概要了解能流行多久。 以上讨论了学习的重点。下面来稍微讨论下学习的方法。 学习方法 一,理论修养 对于技术学习,一般来说,离不开理论支撑,没有完整理论支撑的东西就有些片面。君不见汇编语言,b语言,asp,foxpro,access等一代代流行,转瞬间芳华落尽,却看那人月神话,微软分布式框架基础actor更是1978年就发布了。2000年一篇rest架构的论文发表,现在风靡全球。谷歌分布式监控论文dapper发表以后,各种分布式插件盛况来临。理论为道,代码为术。 二,代码实践 陆游曾经说过:纸上得来终觉浅,绝之此事要躬行。这是大言。学习理论之后,时常进行代码实践。只有写过了的代码才是自己的,时常分享,参与讨论,关注最新动态,才能有所进步。说来可笑,我下载量最多的还是工作第一年2011写的一个远程控制软件。这么些年来竟然没有其他的来源代码可供下载分享,这是严重不对的。 三,积极分享 代码实践完成或理论看完,就会有思考,如果不及时写成日志记录下来,那是一种浪费,一种损失。博客园是个很好的分享地方。共同分享,共同进步。如果无人观看,无人评论,无人推荐,不要气馁,日志是对自己学习思考的沉淀。积累的多了,才会有进一步的提升。 就比如说善友大神的许多文章,单篇来看,并不是最好的。但能十几年如一日坚持下来,就成最好的了。 四,目标导向 以目标为导向,学习要出成果。以前我们的CEO发过一句话,我深有感触:任何三天两头式的学习都不如考个证来的实际。原句怎么说的我忘了,大概就是这意思。 所以如果你足够耐心,足够时间,就以成果为导向吧,如考证,出版书籍,如毅力不够,能出个学习系列也是退而求其次。 向先进的人学习 以上是我思考的四个方面,还有一种是紧随大流,看看先知在做什么,比你厉害的人在做什么。 十年练厨,十年悟厨。程序员就像厨子,为每个需求炒出不同的菜。每个人从事工作时间有长有短,但时间过的很快的,一眨眼我就来上海五年了。真的好快,如果时间可以倒流…… 扩充眼界 最后程序员不只是程序员,你还是一个独立的人,扩充眼界,不要局限于.net一亩三分地,现在python,golang,java如此火,都可以抽时间实践实践,至少增删查改,验证登录要熟悉完成。才不枉是中国之少年!……不谈政治。 废话说了那么多,过去的时光已经过去,未来十年怎样度过?面向未来,成为未来思想的自己。总不会太差。 让未来比今天好一点,让今天比昨天进步一点,如果没有就三省吾身吧!不进步就是退步。 写到这里,为十年前的2008做个总结,展望下一个十年2028。未来的自己是什么样? 最后,身体是革命的本钱,注意养生,不要猝死。 离开舒适区,成长性思维。结束,感谢观看! 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
简要说明 //连接代码。 using (var client = await StartClientWithRetries()) { } 从方法看,只是一个简单允许重试的启动客户端。追踪进去会发现关于重试逻辑的实践,Socket编程的实践,基于内存的消息队列的实践,依赖注入。再看源码的基础上,最好能配合一些理论书籍来看。理论指导实践,实践反馈理论,才是技术成长的步骤。 这篇文章只涉及Connect所引用方法的部分说明,一步一步来加深理解。 本来我是打算把orleans研究透之后再来写一篇,但看了一周之后,发下connect里面调用了很多类,每个类又有很多方法,这样下去没有尽头,到最终估计什么也写不成。 分析源码本来就是循环渐进的过程,也是一个熟悉框架/原理/实践的过程。直接跳过这个步骤,必然损失良多。所以这部分就叫开胃菜吧。在查看connect过程,会越来越接触到各种知识。 本篇暂不涉及数据持久化,主要依赖.netcore内置方法操纵内存实现。 您会接触到的扩展知识 扩展知识之Timer&TimerQueueTimer Timer 在设置的间隔后生成事件,并提供生成重复事件的选项 TimerQueue 时间队列 扩展知识之信号量SemaphoreSlimSemaphoreSlim 实现 //信号量 SemaphoreSlim 表示Semaphore的轻量级替代,它限制了可以同时访问资源或资源池的线程数 >>Release 释放 >> Wait 等待。 信号量有两种类型:本地信号量和命名系统信号量。前者是应用程序的本地。后者在整个操作系统中是可见的,并且适用于进程间同步。该SemaphoreSlim是一个轻量级替代信号量不使用Windows内核中的信号类。与Semaphore类不同,SemaphoreSlim类不支持命名系统信号量。您只能将其用作本地信号量。所述SemaphoreSlim类为单一的应用程序内的同步推荐的信号量。 扩展知识之BlockingCollectionBlockingCollection介绍利用BlockingCollection实现生产者和消费者队列 BlockingCollection 为实现 IProducerConsumerCollection<T> 的线程安全集合提供阻塞和限制功能。 >> Take >> Add 有这个类型, 扩展知识之InterlockedInterlocked Interlocked为多个线程共享的变量提供原子操作。 >>Add >>Decrement以原子操作的形式递减指定变量的值并存储结果。 >>Increment以原子操作的形式递增指定变量的值并存储结果 >>Exchange >>CompareExchange >>Read 个人想法:和Redis的Increment/Decrement类似,部分情况下可以取代Redis的increment/decrement,提高速度。 扩展知识之SpinWaitSpinWait两阶段提交Monitor SpinWait 为基于旋转的等待提供支持。 SpinWait是一种值类型,这意味着低级代码可以使用SpinWait而不必担心不必要的分配开销。SpinWait通常不适用于普通应用程序。在大多数情况下,您应该使用.NET Framework提供的同步类,例如Monitor >> SpinOnce 扩展知识之Queue&StackQueueStack Queue<T> 表示先进先出的对象集合,此类将通用队列实现为循环数组。存储在队列<T>中的对象在一端插入并从另一端移除。 >Enqueue >Dequeue >Peek Stack<T> 表示具有相同指定类型的实例的可变大小后进先出(LIFO)集合。 >Push >Pop >PeeK ConcurrentQueue <T> 表示线程安全的先进先出的对象集合 ConcurrentStack <T> 表示线程安全的后进先出(LIFO)集合 如果需要以与存储在集合中的顺序相同的顺序访问信息,请使用Queue <T>。如果需要以相反的顺序访问信息,请使用Stack <T>。使用ConcurrentQueue <T>或ConcurrentStack <T> 如果您需要同时从多个线程访问该集合。 扩展知识之TaskTaskCompletionSource基于Task的异步模式--全面介绍 TaskCompletionSource表示未绑定到委托的Task <TResult>的生产者端,通过Task属性提供对使用者端的访问。 扩展知识之线程安全的集合System.Collections.ConcurrentConcurrentDictionaryConcurrentDictionary 对决 Dictionary+Locking System.Collections.Concurrent提供了应在的地方对应的类型在使用几个线程安全的集合类System.Collections中和System.Collections.Generic命名空间,只要多线程并发访问的集合。 但是,通过当前集合实现的其中一个接口访问的成员(包括扩展方法)不保证是线程安全的,并且可能需要由调用者同步。 ConcurrentDictionary:表示可以由多个线程同时访问的键/值对的线程安全集合 对于ConcurrentDictionary <TKey,TValue>类上的所有其他操作,所有这些操作都是原子操作并且是线程安全的。唯一的例外是接受委托的方法,即AddOrUpdate和GetOrAdd。对于字典的修改和写入操作,ConcurrentDictionary <TKey,TValue>使用细粒度锁定来确保线程安全。(对字典的读取操作是以无锁方式执行的。)但是,这些方法的委托在锁外部调用,以避免在锁定下执行未知代码时可能出现的问题。因此,这些代理执行的代码不受操作的原子性影响。 扩展知识之网络编程Socket微软官方文档Socket博客园 Socket 类提供一组丰富的方法和属性进行网络通信 TCP协议 >BeginConnect >EndConnect >BeginSend >EndSend >BeginReceive >EndReceive >BeginAccept >EndAccept UDP协议 >BeginSendTo >EndSendTo >BeginReceiveFromandEndReceiveFrom 扩展知识之线程通知:AutoResetEventManualResetEventManualResetEventSlim AutoResetEvent允许线程通过信令相互通信。通常,当线程需要对资源的独占访问时,可以使用此类。 >Set释放线程 >WaitOne等待线程 ManualResetEvent 通知一个或多个等待线程发生了事件 ManualResetEventSlim 当等待时间预期非常短,并且事件未跨越进程边界时,您可以使用此类以获得比ManualResetEvent更好的性能 扩展知识之依赖注入:ActivatorUtilities扩展.net-使用.netcore进行依赖注入 服务可以通过两种机制来解析: IServiceProvider ActivatorUtilities – 允许在依赖关系注入容器中创建没有服务注册的对象。 ActivatorUtilities 用于面向用户的抽象,例如标记帮助器、MVC 控制器、SignalR 集线器和模型绑定器。 >ActivatorUtilities.CreateInstance >ActivatorUtilities.GetServiceOrCreateInstance Client连接代码。 //连接代码。 using (var client = await StartClientWithRetries()) { await DoClientWork(client); Console.ReadKey(); } 重点分析StartClientWithRetries UseLocalhostClustering 用来配置连接参数:端口/ClusterId/ServiceId等。 配置一个连接本地silo的客户端,也有其他类型的如: UseServiceProviderFactory,UseStaticClustering ConfigureLogging配置日志参数扩展阅读 Build用来注册默认服务和构建容器,扩展了解依赖注入知识。微软自带Microsoft.Extensions.DependencyInjection库 private static async Task<IClusterClient> StartClientWithRetries() { attempt = 0; IClusterClient client; client = new ClientBuilder() .UseLocalhostClustering() .Configure<ClusterOptions>(options => { options.ClusterId = "dev"; options.ServiceId = "HelloWorldApp"; }) .ConfigureLogging(logging => logging.AddConsole()) .Build(); await client.Connect(RetryFilter); Console.WriteLine("Client successfully connect to silo host"); return client; } 先来看下connect 这里的LockAsync,内部用了SemaphoreSlim.Wait需要扩展了解下。和lock的区别。信号量本地信号量和系统信号量。 这里用state来维护生命周期 public async Task Connect(Func<Exception, Task<bool>> retryFilter = null) { this.ThrowIfDisposedOrAlreadyInitialized(); using (await this.initLock.LockAsync().ConfigureAwait(false)) { this.ThrowIfDisposedOrAlreadyInitialized(); if (this.state == LifecycleState.Starting) { throw new InvalidOperationException("A prior connection attempt failed. This instance must be disposed."); } this.state = LifecycleState.Starting; if (this.runtimeClient is OutsideRuntimeClient orc) await orc.Start(retryFilter).ConfigureAwait(false); await this.clusterClientLifecycle.OnStart().ConfigureAwait(false); this.state = LifecycleState.Started; } } 看下orc.Start public async Task Start(Func<Exception, Task<bool>> retryFilter = null) { // Deliberately avoid capturing the current synchronization context during startup and execute on the default scheduler. // This helps to avoid any issues (such as deadlocks) caused by executing with the client's synchronization context/scheduler. await Task.Run(() => this.StartInternal(retryFilter)).ConfigureAwait(false); logger.Info(ErrorCode.ProxyClient_StartDone, "{0} Started OutsideRuntimeClient with Global Client ID: {1}", BARS, CurrentActivationAddress.ToString() + ", client GUID ID: " + handshakeClientId); } 重要的StartInternal gateways获取网关列表 transport用来维护客户端消息管理。 RunClientMessagePump用来处理接收分发消息。 private async Task StartInternal(Func<Exception, Task<bool>> retryFilter) { // Initialize the gateway list provider, since information from the cluster is required to successfully // initialize subsequent services. var initializedGatewayProvider = new[] {false}; await ExecuteWithRetries(async () => { if (!initializedGatewayProvider[0]) { await this.gatewayListProvider.InitializeGatewayListProvider(); initializedGatewayProvider[0] = true; } var gateways = await this.gatewayListProvider.GetGateways(); if (gateways.Count == 0) { var gatewayProviderType = this.gatewayListProvider.GetType().GetParseableName(); var err = $"Could not find any gateway in {gatewayProviderType}. Orleans client cannot initialize."; logger.Error(ErrorCode.GatewayManager_NoGateways, err); throw new SiloUnavailableException(err); } }, retryFilter); var generation = -SiloAddress.AllocateNewGeneration(); // Client generations are negative transport = ActivatorUtilities.CreateInstance<ClientMessageCenter>(this.ServiceProvider, localAddress, generation, handshakeClientId); transport.Start(); CurrentActivationAddress = ActivationAddress.NewActivationAddress(transport.MyAddress, handshakeClientId); listeningCts = new CancellationTokenSource(); var ct = listeningCts.Token; listenForMessages = true; // Keeping this thread handling it very simple for now. Just queue task on thread pool. Task.Run( () => { while (listenForMessages && !ct.IsCancellationRequested) { try { RunClientMessagePump(ct); } catch (Exception exc) { logger.Error(ErrorCode.Runtime_Error_100326, "RunClientMessagePump has thrown exception", exc); } } }, ct).Ignore(); await ExecuteWithRetries( async () => this.GrainTypeResolver = await transport.GetGrainTypeResolver(this.InternalGrainFactory), retryFilter); this.typeMapRefreshTimer = new AsyncTaskSafeTimer( this.logger, RefreshGrainTypeResolver, null, this.typeMapRefreshInterval, this.typeMapRefreshInterval); ClientStatistics.Start(transport, clientId); await ExecuteWithRetries(StreamingInitialize, retryFilter); async Task ExecuteWithRetries(Func<Task> task, Func<Exception, Task<bool>> shouldRetry) { while (true) { try { await task(); return; } catch (Exception exception) when (shouldRetry != null) { var retry = await shouldRetry(exception); if (!retry) throw; } } } } 重点关注下StartInternal里面ClientMessageCenter的初始化 用来处理消息分发等,也涉及网关部分调用。 public ClientMessageCenter( IOptions<GatewayOptions> gatewayOptions, IOptions<ClientMessagingOptions> clientMessagingOptions, IPAddress localAddress, int gen, GrainId clientId, IGatewayListProvider gatewayListProvider, SerializationManager serializationManager, IRuntimeClient runtimeClient, MessageFactory messageFactory, IClusterConnectionStatusListener connectionStatusListener, ExecutorService executorService, ILoggerFactory loggerFactory, IOptions<NetworkingOptions> networkingOptions, IOptions<StatisticsOptions> statisticsOptions) { this.loggerFactory = loggerFactory; this.openConnectionTimeout = networkingOptions.Value.OpenConnectionTimeout; this.SerializationManager = serializationManager; this.executorService = executorService; lockable = new object(); MyAddress = SiloAddress.New(new IPEndPoint(localAddress, 0), gen); ClientId = clientId; this.RuntimeClient = runtimeClient; this.messageFactory = messageFactory; this.connectionStatusListener = connectionStatusListener; Running = false; GatewayManager = new GatewayManager(gatewayOptions.Value, gatewayListProvider, loggerFactory); PendingInboundMessages = new BlockingCollection<Message>(); gatewayConnections = new Dictionary<Uri, GatewayConnection>(); numMessages = 0; grainBuckets = new WeakReference[clientMessagingOptions.Value.ClientSenderBuckets]; logger = loggerFactory.CreateLogger<ClientMessageCenter>(); if (logger.IsEnabled(LogLevel.Debug)) logger.Debug("Proxy grain client constructed"); IntValueStatistic.FindOrCreate( StatisticNames.CLIENT_CONNECTED_GATEWAY_COUNT, () => { lock (gatewayConnections) { return gatewayConnections.Values.Count(conn => conn.IsLive); } }); statisticsLevel = statisticsOptions.Value.CollectionLevel; if (statisticsLevel.CollectQueueStats()) { queueTracking = new QueueTrackingStatistic("ClientReceiver", statisticsOptions); } } 关注下StartInternal的RunClientMessagePump WaitMessage里面利用了BlockingCollection.Take private void RunClientMessagePump(CancellationToken ct) { incomingMessagesThreadTimeTracking?.OnStartExecution(); while (listenForMessages) { var message = transport.WaitMessage(Message.Categories.Application, ct); if (message == null) // if wait was cancelled break; // when we receive the first message, we update the // clientId for this client because it may have been modified to // include the cluster name if (!firstMessageReceived) { firstMessageReceived = true; if (!handshakeClientId.Equals(message.TargetGrain)) { clientId = message.TargetGrain; transport.UpdateClientId(clientId); CurrentActivationAddress = ActivationAddress.GetAddress(transport.MyAddress, clientId, CurrentActivationAddress.Activation); } else { clientId = handshakeClientId; } } switch (message.Direction) { case Message.Directions.Response: { ReceiveResponse(message); break; } case Message.Directions.OneWay: case Message.Directions.Request: { this.localObjects.Dispatch(message); break; } default: logger.Error(ErrorCode.Runtime_Error_100327, $"Message not supported: {message}."); break; } } incomingMessagesThreadTimeTracking?.OnStopExecution(); } RunClientMessagePump里面的ReceiveResponse 这里主要是对response做一些判断处理。 public void ReceiveResponse(Message response) { if (logger.IsEnabled(LogLevel.Trace)) logger.Trace("Received {0}", response); // ignore duplicate requests if (response.Result == Message.ResponseTypes.Rejection && response.RejectionType == Message.RejectionTypes.DuplicateRequest) return; CallbackData callbackData; var found = callbacks.TryGetValue(response.Id, out callbackData); if (found) { // We need to import the RequestContext here as well. // Unfortunately, it is not enough, since CallContext.LogicalGetData will not flow "up" from task completion source into the resolved task. // RequestContextExtensions.Import(response.RequestContextData); callbackData.DoCallback(response); } else { logger.Warn(ErrorCode.Runtime_Error_100011, "No callback for response message: " + response); } } //DoCallBack public void DoCallback(Message response) { if (this.IsCompleted) return; var requestStatistics = this.shared.RequestStatistics; lock (this) { if (this.IsCompleted) return; if (response.Result == Message.ResponseTypes.Rejection && response.RejectionType == Message.RejectionTypes.Transient) { if (this.shared.ShouldResend(this.Message)) { return; } } this.IsCompleted = true; if (requestStatistics.CollectApplicationRequestsStats) { this.stopwatch.Stop(); } this.shared.Unregister(this.Message); } if (requestStatistics.CollectApplicationRequestsStats) { requestStatistics.OnAppRequestsEnd(this.stopwatch.Elapsed); } // do callback outside the CallbackData lock. Just not a good practice to hold a lock for this unrelated operation. this.shared.ResponseCallback(response, this.context); } //this.shared.Unregister(this.Message); RunClientMessagePump里面的消息分发Dispatch(message) 这里面用ConcurrentDictionary<GuidId, LocalObjectData>来判断ObserverId是否存在,不存在移除。 如果存在,利用Queue 如果启动成功,异步调用LocalObjectMessagePumpAsync,然后利用Queue private async Task LocalObjectMessagePumpAsync(LocalObjectData objectData) { while (true) { try { Message message; lock (objectData.Messages) { if (objectData.Messages.Count == 0) { objectData.Running = false; break; } message = objectData.Messages.Dequeue(); } if (ExpireMessageIfExpired(message, MessagingStatisticsGroup.Phase.Invoke)) continue; RequestContextExtensions.Import(message.RequestContextData); var request = (InvokeMethodRequest)message.GetDeserializedBody(this.serializationManager); var targetOb = (IAddressable)objectData.LocalObject.Target; object resultObject = null; Exception caught = null; try { // exceptions thrown within this scope are not considered to be thrown from user code // and not from runtime code. var resultPromise = objectData.Invoker.Invoke(targetOb, request); if (resultPromise != null) // it will be null for one way messages { resultObject = await resultPromise; } } catch (Exception exc) { // the exception needs to be reported in the log or propagated back to the caller. caught = exc; } if (caught != null) this.ReportException(message, caught); else if (message.Direction != Message.Directions.OneWay) this.SendResponseAsync(message, resultObject); } catch (Exception) { // ignore, keep looping. } } } SendResponseAsync经过序列化,DeepCopy,赋值各种请求参数等各种操作以后,来到最关键的部分 transport.SendMessage 第一步先获取活动的网关(silo),如没有则建立GatewayConnection 第二步启动Connection Connect--调用socket创建连接 Start--GatewayClientReceiver间接调用Socket来接收消息, public void SendMessage(Message msg) { GatewayConnection gatewayConnection = null; bool startRequired = false; if (!Running) { this.logger.Error(ErrorCode.ProxyClient_MsgCtrNotRunning, $"Ignoring {msg} because the Client message center is not running"); return; } // If there's a specific gateway specified, use it if (msg.TargetSilo != null && GatewayManager.GetLiveGateways().Contains(msg.TargetSilo.ToGatewayUri())) { Uri addr = msg.TargetSilo.ToGatewayUri(); lock (lockable) { if (!gatewayConnections.TryGetValue(addr, out gatewayConnection) || !gatewayConnection.IsLive) { gatewayConnection = new GatewayConnection(addr, this, this.messageFactory, executorService, this.loggerFactory, this.openConnectionTimeout); gatewayConnections[addr] = gatewayConnection; if (logger.IsEnabled(LogLevel.Debug)) logger.Debug("Creating gateway to {0} for pre-addressed message", addr); startRequired = true; } } } // For untargeted messages to system targets, and for unordered messages, pick a next connection in round robin fashion. else if (msg.TargetGrain.IsSystemTarget || msg.IsUnordered) { // Get the cached list of live gateways. // Pick a next gateway name in a round robin fashion. // See if we have a live connection to it. // If Yes, use it. // If not, create a new GatewayConnection and start it. // If start fails, we will mark this connection as dead and remove it from the GetCachedLiveGatewayNames. lock (lockable) { int msgNumber = numMessages; numMessages = unchecked(numMessages + 1); IList<Uri> gatewayNames = GatewayManager.GetLiveGateways(); int numGateways = gatewayNames.Count; if (numGateways == 0) { RejectMessage(msg, "No gateways available"); logger.Warn(ErrorCode.ProxyClient_CannotSend, "Unable to send message {0}; gateway manager state is {1}", msg, GatewayManager); return; } Uri addr = gatewayNames[msgNumber % numGateways]; if (!gatewayConnections.TryGetValue(addr, out gatewayConnection) || !gatewayConnection.IsLive) { gatewayConnection = new GatewayConnection(addr, this, this.messageFactory, this.executorService, this.loggerFactory, this.openConnectionTimeout); gatewayConnections[addr] = gatewayConnection; if (logger.IsEnabled(LogLevel.Debug)) logger.Debug(ErrorCode.ProxyClient_CreatedGatewayUnordered, "Creating gateway to {0} for unordered message to grain {1}", addr, msg.TargetGrain); startRequired = true; } // else - Fast path - we've got a live gatewayConnection to use } } // Otherwise, use the buckets to ensure ordering. else { var index = msg.TargetGrain.GetHashCode_Modulo((uint)grainBuckets.Length); lock (lockable) { // Repeated from above, at the declaration of the grainBuckets array: // Requests are bucketed by GrainID, so that all requests to a grain get routed through the same bucket. // Each bucket holds a (possibly null) weak reference to a GatewayConnection object. That connection instance is used // if the WeakReference is non-null, is alive, and points to a live gateway connection. If any of these conditions is // false, then a new gateway is selected using the gateway manager, and a new connection established if necessary. var weakRef = grainBuckets[index]; if ((weakRef != null) && weakRef.IsAlive) { gatewayConnection = weakRef.Target as GatewayConnection; } if ((gatewayConnection == null) || !gatewayConnection.IsLive) { var addr = GatewayManager.GetLiveGateway(); if (addr == null) { RejectMessage(msg, "No gateways available"); logger.Warn(ErrorCode.ProxyClient_CannotSend_NoGateway, "Unable to send message {0}; gateway manager state is {1}", msg, GatewayManager); return; } if (logger.IsEnabled(LogLevel.Trace)) logger.Trace(ErrorCode.ProxyClient_NewBucketIndex, "Starting new bucket index {0} for ordered messages to grain {1}", index, msg.TargetGrain); if (!gatewayConnections.TryGetValue(addr, out gatewayConnection) || !gatewayConnection.IsLive) { gatewayConnection = new GatewayConnection(addr, this, this.messageFactory, this.executorService, this.loggerFactory, this.openConnectionTimeout); gatewayConnections[addr] = gatewayConnection; if (logger.IsEnabled(LogLevel.Debug)) logger.Debug(ErrorCode.ProxyClient_CreatedGatewayToGrain, "Creating gateway to {0} for message to grain {1}, bucket {2}, grain id hash code {3}X", addr, msg.TargetGrain, index, msg.TargetGrain.GetHashCode().ToString("x")); startRequired = true; } grainBuckets[index] = new WeakReference(gatewayConnection); } } } if (startRequired) { gatewayConnection.Start(); if (!gatewayConnection.IsLive) { // if failed to start Gateway connection (failed to connect), try sending this msg to another Gateway. RejectOrResend(msg); return; } } try { gatewayConnection.QueueRequest(msg); if (logger.IsEnabled(LogLevel.Trace)) logger.Trace(ErrorCode.ProxyClient_QueueRequest, "Sending message {0} via gateway {1}", msg, gatewayConnection.Address); } catch (InvalidOperationException) { // This exception can be thrown if the gateway connection we selected was closed since we checked (i.e., we lost the race) // If this happens, we reject if the message is targeted to a specific silo, or try again if not RejectOrResend(msg); } } public void Connect() { if (!MsgCenter.Running) { if (Log.IsEnabled(LogLevel.Debug)) Log.Debug(ErrorCode.ProxyClient_MsgCtrNotRunning, "Ignoring connection attempt to gateway {0} because the proxy message center is not running", Address); return; } // Yes, we take the lock around a Sleep. The point is to ensure that no more than one thread can try this at a time. // There's still a minor problem as written -- if the sending thread and receiving thread both get here, the first one // will try to reconnect. eventually do so, and then the other will try to reconnect even though it doesn't have to... // Hopefully the initial "if" statement will prevent that. lock (Lockable) { if (!IsLive) { if (Log.IsEnabled(LogLevel.Debug)) Log.Debug(ErrorCode.ProxyClient_DeadGateway, "Ignoring connection attempt to gateway {0} because this gateway connection is already marked as non live", Address); return; // if the connection is already marked as dead, don't try to reconnect. It has been doomed. } for (var i = 0; i < ClientMessageCenter.CONNECT_RETRY_COUNT; i++) { try { if (Socket != null) { if (Socket.Connected) return; MarkAsDisconnected(Socket); // clean up the socket before reconnecting. } if (lastConnect != new DateTime()) { // We already tried at least once in the past to connect to this GW. // If we are no longer connected to this GW and it is no longer in the list returned // from the GatewayProvider, consider directly this connection dead. if (!MsgCenter.GatewayManager.GetLiveGateways().Contains(Address)) break; // Wait at least ClientMessageCenter.MINIMUM_INTERCONNECT_DELAY before reconnection tries var millisecondsSinceLastAttempt = DateTime.UtcNow - lastConnect; if (millisecondsSinceLastAttempt < ClientMessageCenter.MINIMUM_INTERCONNECT_DELAY) { var wait = ClientMessageCenter.MINIMUM_INTERCONNECT_DELAY - millisecondsSinceLastAttempt; if (Log.IsEnabled(LogLevel.Debug)) Log.Debug(ErrorCode.ProxyClient_PauseBeforeRetry, "Pausing for {0} before trying to connect to gateway {1} on trial {2}", wait, Address, i); Thread.Sleep(wait); } } lastConnect = DateTime.UtcNow; Socket = new Socket(Silo.Endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); Socket.EnableFastpath(); SocketManager.Connect(Socket, Silo.Endpoint, this.openConnectionTimeout); NetworkingStatisticsGroup.OnOpenedGatewayDuplexSocket(); MsgCenter.OnGatewayConnectionOpen(); SocketManager.WriteConnectionPreamble(Socket, MsgCenter.ClientId); // Identifies this client Log.Info(ErrorCode.ProxyClient_Connected, "Connected to gateway at address {0} on trial {1}.", Address, i); return; } catch (Exception ex) { Log.Warn(ErrorCode.ProxyClient_CannotConnect, $"Unable to connect to gateway at address {Address} on trial {i} (Exception: {ex.Message})"); MarkAsDisconnected(Socket); } } // Failed too many times -- give up MarkAsDead(); } } GatewayConnection的Start会调用到GatewayClientReceiver的Run方法,利用BlockingCollection protected override void Run() { try { while (!Cts.IsCancellationRequested) { int bytesRead = FillBuffer(buffer.BuildReceiveBuffer()); if (bytesRead == 0) { continue; } buffer.UpdateReceivedData(bytesRead); Message msg; while (buffer.TryDecodeMessage(out msg)) { gatewayConnection.MsgCenter.QueueIncomingMessage(msg); if (Log.IsEnabled(LogLevel.Trace)) Log.Trace("Received a message from gateway {0}: {1}", gatewayConnection.Address, msg); } } } catch (Exception ex) { buffer.Reset(); Log.Warn(ErrorCode.ProxyClientUnhandledExceptionWhileReceiving, $"Unexpected/unhandled exception while receiving: {ex}. Restarting gateway receiver for {gatewayConnection.Address}.", ex); throw; } } 关注SafeTimerBase类 Orleans用于处理定时或延时回调作业。 总结 创建一个简单的connect,里面有这么多沟沟渠渠,但本质上来说,最底层是利用Socket套接字机制来实施机制。在Socket的基础之上,又封装维护了一层GatewayConnection和GatewayClientReceiver来实现网关(Silo)的操作,比如重试/监控/熔断等,再结合Timer,Queue 如果您完全熟悉异步编程,并行编程,Socket网络编程。又对分布式/微服务理论有较深的理解,那么orleans实现机制,对您来说可能是相对容易。 本期结束,下期更精彩! 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
Orleans之开胃小菜 SafeExecute安全执行 翻看Orleans源码时,发现一句SafeExecute,就以为是什么没接触过的很深的东西。那么SafeExecute到底是什么玩意呢? Utils.SafeExecute(() => { if (typeMapRefreshTimer != null) { typeMapRefreshTimer.Dispose(); typeMapRefreshTimer = null; } }, logger, "Client.typeMapRefreshTimer.Dispose"); 接下来追踪代码如下,原来主要逻辑就是加try,catch不引发异常。 public static void SafeExecute(Action action, ILogger logger, Func<string> callerGetter) { try { action(); } catch (Exception exc) { try { if (logger != null) { string caller = null; if (callerGetter != null) { try { caller = callerGetter(); }catch (Exception) { } } foreach (var e in exc.FlattenAggregate()) { logger.Warn(ErrorCode.Runtime_Error_100325, $"Ignoring {e.GetType().FullName} exception thrown from an action called by {caller ?? String.Empty}.", exc); } } } catch (Exception) { // now really, really ignore. } } } ExecuteWithRetries重试机制 还有例如以下代码,顾名思义就是支持重试执行 await ExecuteWithRetries( async () => this.GrainTypeResolver = await transport.GetGrainTypeResolver(this.InternalGrainFactory), retryFilter); 跟踪源码如下: 综合考虑最晚执行时间,成功执行次数,失败执行次数等因素,延迟回调. 利用do..while,Task,Func等知识来完成延时重试,并不算太复杂。 public static Task ExecuteWithRetries( Func<int, Task> action, int maxNumErrorTries, Func<Exception, int, bool> retryExceptionFilter, TimeSpan maxExecutionTime, IBackoffProvider onErrorBackOff) { Func<int, Task<bool>> function = async (int i) => { await action(i); return true; }; return ExecuteWithRetriesHelper<bool>( function, 0, 0, maxNumErrorTries, maxExecutionTime, DateTime.UtcNow, null, retryExceptionFilter, null, onErrorBackOff); } private static async Task<T> ExecuteWithRetriesHelper<T>( Func<int, Task<T>> function, int callCounter, int maxNumSuccessTries, int maxNumErrorTries, TimeSpan maxExecutionTime, DateTime startExecutionTime, Func<T, int, bool> retryValueFilter = null, Func<Exception, int, bool> retryExceptionFilter = null, IBackoffProvider onSuccessBackOff = null, IBackoffProvider onErrorBackOff = null) { T result = default(T); ExceptionDispatchInfo lastExceptionInfo = null; bool retry; do { retry = false; if (maxExecutionTime != Constants.INFINITE_TIMESPAN && maxExecutionTime != default(TimeSpan)) { DateTime now = DateTime.UtcNow; if (now - startExecutionTime > maxExecutionTime) { if (lastExceptionInfo == null) { throw new TimeoutException( $"ExecuteWithRetries has exceeded its max execution time of {maxExecutionTime}. Now is {LogFormatter.PrintDate(now)}, started at {LogFormatter.PrintDate(startExecutionTime)}, passed {now - startExecutionTime}"); } lastExceptionInfo.Throw(); } } int counter = callCounter; try { callCounter++; result = await function(counter); lastExceptionInfo = null; if (callCounter < maxNumSuccessTries || maxNumSuccessTries == INFINITE_RETRIES) // -1 for infinite retries { if (retryValueFilter != null) retry = retryValueFilter(result, counter); } if (retry) { TimeSpan? delay = onSuccessBackOff?.Next(counter); if (delay.HasValue) { await Task.Delay(delay.Value); } } } catch (Exception exc) { retry = false; if (callCounter < maxNumErrorTries || maxNumErrorTries == INFINITE_RETRIES) { if (retryExceptionFilter != null) retry = retryExceptionFilter(exc, counter); } if (!retry) { throw; } lastExceptionInfo = ExceptionDispatchInfo.Capture(exc); TimeSpan? delay = onErrorBackOff?.Next(counter); if (delay.HasValue) { await Task.Delay(delay.Value); } } } while (retry); return result; } } 耐心求索 很多基础性的实现并不完全依赖高深隐秘的技术,多思考,多实践。只要思路正确,慢慢修养,星星之火总能燎原。 众所周知,.net的应用层每隔几年总会变化,像aspx现在是完全放弃了的。.netcore现在日益火热,但不见的得持久恒定。在有限的时间内,为了不让知识贬值,除了快速学习新知识外,更要注意基础知识的沉淀。就像上面的两个例子,无论方法名,基础思想在那放着,不会太变质。 但这些是远远不够的。变化的永远是上层。沉淀到基础层次,如进程线程之类的,就比较稳定了。除了这些代码层次的,更重要的是思想的沉淀,如《人月神话》《失控》《领域驱动涉及》,敏捷开发等方法论,依然很有用,更加基础的冯诺依曼计算器体系结构更是奠基者。 理论才是跨各种语言平台的。只记得一些方法命名空间而根基不扎实的,是无根之木。 未来的日子,勉励自己继续扩充理论知识。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
业务简述 关键字段:邀请码最大使用次数UseMaxNumber和允许取消次数CancelUseMaxNumber,已使用次数UsedCount,已取消次数CancelUsedCount。 提交使用邀请码的订单,占用邀请码使用次数。 在允许取消次数内取消订单,退回邀请码使用次数。 超过允许取消次数取消订单,不退回邀请码使用次数。 注意点:临界值。 原核心代码(X.1版) public ResponseMessage<bool> 示例方法_ProcessCode(X used,YY invitecodedto) { var isoverinvite = false;//已经超过取消次数 var iswilloverinvite = false;//将要超出取消次数 long inviteNum = 0;//本次邀约使用次数 //判断是否已经超过取消次数,或者将要超出取消次数。 if (invitecodedto != null && invitecodedto.IsLimitCancelUse) { if (invitecodedto.CancelUsedCount > invitecodedto.CancelUseMaxNumber) { isoverinvite = true; } else if (invitecodedto.CancelUsedCount + used.InviteNum > invitecodedto.CancelUseMaxNumber) { iswilloverinvite = true; } } ResponseMessage<long> inviteuseres = null; //邀约码不为null,递增取消次数,扣减使用次数。 if (invitecodedto != null) { //递增已取消次数 var cancelcount = _codeService.IncCancelUseCount(invitecodedto.Id, (int)used.InviteNum); if (isoverinvite) { } else if (iswilloverinvite) { inviteNum = invitecodedto.CancelUseMaxNumber > cancelcount.Body ? invitecodedto.CancelUseMaxNumber - cancelcount.Body : cancelcount.Body - invitecodedto.CancelUseMaxNumber; //将要超出的,只退出部分。 inviteuseres = _codeService.IncUseCount(invitecodedto.Id, -(int)(inviteNum)); } else { inviteNum = used.InviteNum; //未超出取消次数的,全数退回。 inviteuseres = _codeService.IncUseCount(invitecodedto.Id, -(int)inviteNum); } } . . . //更新取消日志。 //更新码相关的各种状态。 } X.1版代码引起问题 使用次数为1,允许取消次数为1时,运行正确。 使用次数为1,允许取消次数为2时,结果错误。 >>测试流程目标:【每次报名都为1人】报名一次,取消一次,再报名一次,再取消一次后。再报名一次后,后续不能再报名。 >>实际效果:仍然还能报名一次。 >>原因分析:订单第二次取消后。已取消次数为2,允许取消次数为2,这个判断无法命中。 if (invitecodedto.CancelUsedCount > invitecodedto.CancelUseMaxNumber) { isoverinvite = true; } 优化后代码(X.2版) var isoverinvite = false;//已经超过取消次数 var iswilloverinvite = false;//将要超出取消次数 long inviteNum = 0;//本次邀约使用次数 if (invitecodedto != null && invitecodedto.IsLimitCancelUse) { //这里多加了个=号 if (invitecodedto.CancelUsedCount >= invitecodedto.CancelUseMaxNumber) { isoverinvite = true; }//这里也多加了个=号 else if (invitecodedto.CancelUsedCount + used.InviteNum >= invitecodedto.CancelUseMaxNumber) { iswilloverinvite = true; } } X.2版代码引起问题 X.2版修复了上个问题。但仍有场景覆盖不够。 使用次数为2,允许取消次数为2时,结果错误。 >>测试流程目标:报名一次(1人),取消,再报名一次(2人),再取消。预期仍可以继续报名1人。 >>实际效果:无法继续报名。 >>原因分析,第二次取消请求时: >>>根据判断 已取消次数加上邀约人数大于允许取消次数,1+2>2,所以是将要超出允许取消次数。 . . else if (invitecodedto.CancelUsedCount + used.InviteNum > invitecodedto.CancelUseMaxNumber) { iswilloverinvite = true; } . . >>>再来看下扣减使用次数的部分。CancelUseMaxNumber为2,cancelcount.Body为2, >>>所以结果是:2>2?(2-2):(2-2),返回0,意思是没有返回使用次数。 . . else if (iswilloverinvite) { inviteNum = invitecodedto.CancelUseMaxNumber > cancelcount.Body ? invitecodedto.CancelUseMaxNumber - cancelcount.Body : cancelcount.Body - invitecodedto.CancelUseMaxNumber; //将要超出的,只退出部分。 inviteuseres = _codeService.IncUseCount(invitecodedto.Id, -(int)(inviteNum)); } . . >>>正确结果应该是:因为已经取消过一次了,这次报名2人,如按正常应该是总取消3次,但允许取消次数是2次,所以使用次数只能返回一次。 >>>预期结果和实际结果不符。 思考 上面问题是由于退回使用次数计算不对引起的。 改动后验证流程是很繁琐的,要配置邀请码,要填写报名信息,要重复提交,重复取消订单好几次来验证逻辑。 组合条件是千变万化的。 这个业务重点是测试取消订单后对于使用次数和允许取消次数的正确性。如全流程走一下,是浪费时间的。 所以为保证正确性及方便,这个必须支持单元测试。单元测试才能快速试错。 影响单元测试的几点 业务耦合。这个取消邀请方法内有处理邀请码使用次数和取消次数的,也有处理取消记录,维护各个状态等。不符合单一功能原则。 数据库依赖,影响mock数据及执行后的结果对比。 重复执行后结果的积累。如订单取消后,邀请码的使用次数和允许取消次数都会变,作为下次单元测试的依据。 改进建议 对打算单元测试的代码,要保持功能单一,不耦合其他业务。 面向接口编程,依赖注入。与具体的实现解耦,方便单元测试。 方法体尽量移除仓储部分逻辑或者mock一个仓储对象替代。 必须方便批量单元测试。 单元测试前置--Nuget包依赖 Xunit:一个开发测试框架,它支持测试驱动开发,具有极其简单和与框架特征对齐的设计目标。 xunit.runner.visualstudio: 支持Vs调试,运行测试 NSubstitute :一个友好的.net单元测试隔离框架。 Autofac: Ioc容器 //单元测试部分 public class GetTicketDiscounts_Test { private IXTaDiscountService discountService = null; private IXTaCodeService codeSub = null; public GetTicketDiscounts_Test() { discountService = XTaContainer.Resolve<IXTaDiscountService>(); codeSub = NSubstitute.Substitute.For<IXTaCodeService>(); } } //注册部分 public static class XTaContainer { public readonly static IContainer _container; static XTaContainer() { // Create your builder. var builder = new ContainerBuilder(); //自动注册。 var baseType = typeof(IApplication); var assemblys = AppDomain.CurrentDomain.GetAssemblies().ToList(); builder.RegisterAssemblyTypes(assemblys.ToArray()) .Where(t => baseType.IsAssignableFrom(t) && t != baseType) .AsImplementedInterfaces() .InstancePerLifetimeScope(); //Redis builder.Register(n => Substitute.For<ICache>()) .As<ICache>().SingleInstance(); //mongodb builder.Register(n => Substitute.For<IMongoDbProvider>()) .As<IMongoDbProvider>().SingleInstance(); _container = builder.Build(); } public static T Resolve<T>() { return _container.Resolve<T>(); } } 支持单元测试的代码(X.3版-只粘贴相关代码) //接口 public interface IXTaService : IApplication{ ResponseMessage<long> GetReturnUseNum(long invitediscountNum, XTaCodeDto codedto); } //实现 public class XTaDiscountService : IXTaDiscountService { private readonly IXTaCodeService _codeService; public XTaDiscountService( IXTaCodeService codeService) { _codeService = codeService; } //将操作使用次数和取消次数的仓储部分挪出去,这里只计算需要退回的使用次数。 public ResponseMessage<long> GetReturnUseNum(long invitediscountNum, XTaCodeDto codedto) { //默认是全部退回使用次数。 long returnNum = invitediscountNum; if (codedto == null) { return ResponseMessage<long>.MakeSucc(0); } //不限制取消的的时候,退回全部使用次数。 if (!codedto.IsLimitCancelUse) { return ResponseMessage<long>.MakeSucc(returnNum); } //已超过的不处理。 if (codedto.CancelUsedCount >= codedto.CancelUseMaxNumber) { return ResponseMessage<long>.MakeSucc(0); } //将要超过的。 if (codedto.CancelUsedCount + invitediscountNum >= codedto.CancelUseMaxNumber) { returnNum = codedto.CancelUsedCount + invitediscountNum - codedto.CancelUseMaxNumber; return ResponseMessage<long>.MakeSucc(returnNum); } return ResponseMessage<long>.MakeSucc(returnNum); } } >初始化数据 private void 验证取消优惠_初始化数据(ref XTaCodeDto codeDto, int usemax = 0, int cancelmax = 0) { if (codeDto == null) { codeDto = new XTaCodeDto() { Id = "11111", CancelUsedCount = 0, UsedCount = 0, PrivateSetting = new PrivateSetting() { IsLimitCancelUse = true, IsCustomCancelUse = true, CancelUseMaxNumber = 1, IsLimitUse = true, IsCustomUse = true, UseMaxNumber = 1 } }; } if (cancelmax > 0) { codeDto.PrivateSetting.CancelUseMaxNumber = cancelmax; codeDto.CancelUsedCount = 0; } if (usemax > 0) { codeDto.PrivateSetting.UseMaxNumber = usemax; codeDto.UsedCount = 0; } } > 模拟报名使用邀请码,递增使用次数,方便批量测试。 private void 初始化数据_模拟报名使用邀请码_递增使用次数(int useNum, XTaCodeDto codeDto) { //mock模拟使用邀请码时,递增的邀请码使用次数返回使用次数。 var usercount = codeSub.IncUseCount(codeDto.Id, Arg.Any<int>()).Returns(x => new ResponseMessage<long>() { Body = (int)codeDto.UsedCount + x.Arg<int>() }); codeDto.UsedCount = codeSub.IncUseCount(codeDto.Id, useNum).Body; } > 模拟取消订单,退回使用次数 private void 验证取消优惠_退回使用次数_V1ForPrivate(long inviteDiscountNum, XTaCodeDto codeDto) { //计算退回使用次数。 var res = discountService.GetReturnUseNum(inviteDiscountNum, codeDto); codeDto.UsedCount -= res.Body; codeDto.CancelUsedCount += inviteDiscountNum; } >实际测试部分 [Fact] public void 验证取消优惠_退回使用次数_最大使用一次_允许取消一次() { XTaCodeDto codeDto = null; 验证取消优惠_初始化数据(ref codeDto, 1, 1); //第一次报名,取消 验证取消优惠_模拟报名使用邀请码_递增使用次数(1, codeDto); 验证取消优惠_退回使用次数_V1ForPrivate(1, codeDto); //第一次取消会退回使用次数。 Assert.True(codeDto.UsedCount == 0 && codeDto.CancelUsedCount == 1); //第二次报名,取消 验证取消优惠_模拟报名使用邀请码_递增使用次数(1, codeDto); 验证取消优惠_退回使用次数_V1ForPrivate(1, codeDto); //第二次取消后,超出允许取消次数限制,不会退回 Assert.True(codeDto.UsedCount == 1 && codeDto.CancelUsedCount == 2); } [Fact] public void 验证取消优惠_退回使用次数_最大使用2次_允许取消两次() { XTaCodeDto codeDto = null; 验证取消优惠_初始化数据(ref codeDto, 2, 2); 验证取消优惠_模拟报名使用邀请码_递增使用次数(1, codeDto); 验证取消优惠_退回使用次数_V1ForPrivate(1, codeDto); Assert.True(codeDto.UsedCount == 0 && codeDto.CancelUsedCount == 1); 验证取消优惠_模拟报名使用邀请码_递增使用次数(2, codeDto); 验证取消优惠_退回使用次数_V1ForPrivate(2, codeDto); Assert.True(codeDto.UsedCount == 1 && codeDto.CancelUsedCount == 3); 验证取消优惠_模拟报名使用邀请码_递增使用次数(1, codeDto); 验证取消优惠_退回使用次数_V1ForPrivate(1, codeDto); Assert.True(codeDto.UsedCount == 2 && codeDto.CancelUsedCount == 4); } 使用单元测试的好处 快速验证结果,不用依赖各种数据库/缓存等环境。 代码指责更单一。 减少bug 方便后期持续集成 可参考连接 使用 dotnet test 和 xUnit 在 .NET Core 中进行 C# 单元测试nsubstitute 介绍Autofac介绍单元测试的艺术 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
今天.NetCore2.1版本,建立Asp.net Core web应用程序项目时,报以下错误: 未能使用“Csc”任务的输入参数初始化该任务。 “Csc”任务不支持“SharedCompilationId”参数。请确认该参数存在于此任务中,并且是可设置的公共实例属性。 发现知乎有人问,无人回答:https://www.zhihu.com/question/284692910 然后去StackOverflow,正好有人问有人回答。 https://stackoverflow.com/questions/49173372/asp-net-core-2-1-preview-sharedcompilationid-parameter-is-not-supported-by-the 解决办法: Nuget上安装Microsoft.Net.Compilers -Version 2.8.2 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
今天是个不顺畅的一天! 早上出门时,公交做错了,半途转了另外一个去公司的公交,结果还是反方向的,等我发现这个问题时,感觉再去上班时间很晚了,于是决定请个假约了个面试。 每年抽空面试个两三家,我觉得是有必要的,提升下认知,补充下缺点。当然要以大幅度提高工资为目标。 前面整体还顺利,回答过程中结合Redis的业务场景描述多了些,然后问了我些Redis的hash用过么。我一听心中是茫然的,目前用的ServiceStackRedis C#客户端,公司又封装了下直接用,常用的set,get,delete,inc,decrement等,所以很自然说出set<泛型>之类的用法,关于redis存储类型争论了下。从我以这个组件的应用和他以原生Redis来聊,已经不太顺了。具体细节回忆不清了。 结果:面试是失败的。 我回来搜索下redis是支持hash的。所以反思下阶段目标:用的东西如队列,redis,mongodb等不要仅限于对工具的了解,而要扩展对它们本身的认识,毕竟你用的工具别人不一定用,就聊不到一起了。 暂时就说这么多吧!!!深以为耻!!!避免再犯!!!潜心了解下在用主要组件!!! 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
一、背景 最近半年或最近三个月来,公司在计划大刀阔斧的规划重构新的产品。按目前的计划和宣传还是很令人期待的。前端预计应用现在很流行的前端框架,有Vue、ElementUI等,后端宣传了很多微服务、持续集成、持续部署、单元测试,最终一致性等诸多理论概念的东西,总之是个我很感兴趣的事。最近半年来,我也看了不少微服务、分布式的书,但写成文档的却很少,时间一过,就淡忘了,相当于没有收获。趁着这个强烈学习的良机,我买了阿里云的一台Linux服务器,用于实践linux下.netcore的部署、运维等。部署过程中,遇到过各种各样的问题,但都已解决, 如果花了几天时间解决的问题,不做个记录,那不是对自己的浪费么?所以,才有了这篇博客。 二、windows构建 先来构建下windows下发布包,关于DotNetCore的创建方法我就不具体截图了,微软官方文档有更全的操作,可从网上查询KestrelHttp示例程序。对于 ASP.NET Core 2.1 版,Kestrel 默认传输不再基于 Libuv,而是基于托管的套接字。 2.1建好项目KestrelDemo运行: dotnet publish -c release -o "发布目录" cd "发布目录" dotnet KestrelDemo.dll 在window下查看运行效果,将发布包推到码云地址或github地址,便于linux下获取。 三、跨平台部署 我的阿里云linux发行版是CentOS7.4,.netcore版本2.0,接下来我们演示部署DotNet环境 3.1 安装.netCore运行时 注册Microsoft密钥 sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm 更新yum库,获取最新 sudo yum update sudo yum install aspnetcore-runtime-2.1 查看是否安装成功 dotnet --version 3.2安装git 因发布包是用git管理,所以需要centos上安装git。 yum install git 创建发布目录 mkdir /cusD/wwwroot/KesPublish 进入发布目录 cd /cusD/wwwroot/KesPublish 初始化git git init 拉取git代码 git pull 发布包地址 3.3启动 dotnet KestrelDemo.dll 查看发布后效果。正常情况下,简单的部署就完成了。 但是现在有个问题,当连接用户的shell断掉后,dotnet会自动关闭,达不到长期运行的效果。这时候就需要linux的守护进程了。下面我们继续讲述如何创建守护进程 四、守护进程 4.1 概念 Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等... 4.2创建服务文件 sudo nano /etc/systemd/system/KestrelDemoSer.service 4.3示例文件(要有足够权限) [Unit] Description=KestrelDemo running on CentOS [Service] WorkingDirectory=/cusD/wwwroot/KesPublish Type=simple User=root Group=root ExecStart=/usr/bin/dotnet /cusD/wwwroot/KesPublish/KestrelDemo.dll Restart=always # Restart service after 10 seconds if the dotnet service crashes: RestartSec=10 SyslogIdentifier=dotnet-example Environment=ASPNETCORE_ENVIRONMENT=Production Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false [Install] WantedBy=multi-user.target 按CTRL+O保存,CTRL+X退出。 Linux 具有区分大小写的文件系统。 将 ASPNETCORE_ENVIRONMENT 设置为“生产”会导致搜索配置文件 appsettings.Production.json,而不是 appsettings.production.json。 4.4保存文件并启用服务 systemctl enable KestrelDemoSer.service 4.5启用服务,并确认运行 systemctl start KestrelDemoSer.service systemctl status KestrelDemoSer.service 4.6查看日志 sudo journalctl -fu KestrelDemoSer.service reboot重启后,查询连接是否正常访问,初学者不熟悉权限问题,最好以root账号配置 五、扩展关注 Systemctl是一个systemd工具,主要负责控制systemd系统和服务管理器。 Systemd是一个系统管理守护进程、工具和库的集合,用于取代System V初始进程。Systemd的功能是用于集中管理和配置类UNIX系统。 5.1、查看是否安装 systemctl --version 5.2、查看安装目录 whereis systemd whereis systemctl 5.3、检测是否运行 ps -eaf | grep [s]ystemd 5.4、分析systemd启动进程 systemd-analyze 5.5、分析启动时各个进程花费时间 systemd-analyze blame 六、遇到问题及解决 6.1、如何选择linux发行版 这个我没有什么建议,但是在使用或部署的过程遇到了很多坑,如想安装服务器图形界面。我不断利用阿里云的更换系统盘功能试用了Debain,Ubuntu,CentOS等,最终就CentOS安装成功了,所以其他我就放弃了。等把Linux用熟了,我还是会用用其他的发行版的。 6.2、DotNetCore版本问题 如果不找到正确的途径,没有事情会是顺利的。我从网上找的教程,安装了.NetCore运行时,然后部署后运行 dotnet 你的dll名称 提示发布包是2.1版,系统是1.1版。这是个入门坑,找的教程太旧了,。后来尝试卸载也失败,就任性的又重新更换了系统盘。按照微软官方文档就三行命令解决,见3.1 6.3、Git注意 用mkdir创建好自己的发布目录后,要调用下 git init 如果不调用,会有报错提示 6.4、守护进程 这个类似windows的服务,node下有pm2支持,也有python写的的supervisor可以支持。 我按教程安装了supervisor,但配置好后,依然不能启动,有可能是配置不对等原因。 然后就开始查找微软官方文档看到了systemd,通过systemctl来配置启用服务,其实第一次用systemctl status 服务名称,提示状态失败的,后来又重新走了下步骤又好了。然后就是用户权限的问题,也注意下。 6.5、连接工具 手机上可以用阿里云字段的SSL工具,电脑端可以用XShell,都很方便。 6.6、常用命令 命令 说明 cd ~ 跳转到根目录 cd .. 跳转到当前目录 ls 当前目录下文件列表 mkdir 创建目录 rm 删除文件 users 显示当前登录用户 nano 字符终端文本编辑器,Ctrl+O保存,Ctrl+X退出 install 安装软件 tree 树状图列出目录的内容 whereis 查找二进制程序、代码等相关文件路径 vi 功能强大的文本编辑器,:wq保存退出。 systemctl 系统服务管理器指令 sudo 以其他身份来执行命令 find 用来在指定目录下查找文件 6.7、编辑器 上篇文章用的MarkDown编辑器,这次用秀米,放到博客园的编辑器后,效果惨不忍睹,最后又复制到有道云编辑下了,才看着正常了。 七、参考连接 重要提示:有官方资料的以官方最新为准! DotNetCore内置http服务器项目地址: https://github.com/aspnet/KestrelHttpServer dotnet命令行请参考: https://docs.microsoft.com/zh-cn/dotnet/core/tools/dotnet?tabs=netcore21 安装.netcore运行时参考连接: https://www.microsoft.com/net/download/linux-package-manager/centos/runtime-2.1.0 IIS配置DOtNetCore https://docs.microsoft.com/zh-cn/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-2.1&tabs=aspnetcore2x#monitoring-our-web-application systemctl参考连接: http://www.tecmint.com/manage-services-using-systemd-and-systemctl-in-linux/ https://linux.cn/article-5926-1.html linux命令参考连接: http://man.linuxde.net/ 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
一步一步来熟悉Akka.Net(一) 标签(空格分隔): .netcore 分布式 一、不利flag 好久没写过文章了,翻开前几年写的博客,看到有两个目标“代码生成器”和“文件隐身”。说起来并不是太难的东西,难产到现在还没有出来。还有之前定的学习计划--先学习spring boot,再熟悉spring cloud,再这些基础上,再去.net环境上去寻找完善更适合的解决方案。不过学完spring boot以后,由于一些原因cloud就没继续熟悉了。 所以,先有足够的时间和动力来实现执行力,再来说目标吧。 二、并行运算 近些日子,遇到一个工作中的场景:将A系统的一些表同步到B系统内。因涉及到数据整合,老版写的就是按部就班的分页查询,然后调用B系统的整合数据接口整合数据,整个同步速度是相当慢的。后来改成了分页查询的数据先丢到消息队列,然后多开几个消费端去并行处理,速度翻了几十倍甚至上百倍。当然B系统也优化去掉一些查询,把能提前的查询都放到了消息队列之前,这样能提高消费速度。 作为一个有追求的人,这样的结果是十分欣喜的。当然如果批量式的查询后,批量式的把数据整理后,批量式的提交数据库操作,速度会更加提升上百倍或千倍以上,但时间有限,牵扯部分太广,目前处理速度和业务量能应付的来,优化到此为止,思考却不会停止。 三、面向未来思考 此处说明下,A是业务系统,B是营销系统。A系统的任何更新都会通知到B系统,B系统聚合数据,然后做出营销动作,所以B系统的动作处理时间是比A系统的更新数据时间要长的,如果A系统有大批量导入或频繁更新,B系统是很难在短时间内处理掉的。所以思考方向是:A系统积累一批更新如一分钟内,再由消息队列通知B系统,B系统批量处理这些通知。 *旧版 A系统->B系统: A单条数据的任何修改通知B *思考版 A系统->B系统: A单条数据的修改积累到一批再通知B 以上思路,依赖HttpRuntime或Redis都能实现。但似乎要考虑加锁的问题,不太好。 上述废话那么多,是为了讲述一个问题点。有了这个点,在熟悉分布式框架的过程中,有参考匹配价值,不会过于迷茫。 来了解下Akka.Net Akka.Net是用于设计跨越处理器核心和网络的可扩展的弹性系统,它允许您专注于满足业务需求,而不是编写低级代码以提供可靠的行为,容错性和高性能。Akka.NET利用参与者模型来提供抽象级别,使得编写正确的并发,并行和分布式系统变得更加容易。Actor模型跨越了Akka.NET库的集合,为您提供了一个理解和使用它们的一致方式。 什么是Actor模型 Actor基本特征是他们将世界建模为通过显式消息传递相互通信的有状态实体。 作为计算实体,Actor有这些特征: 他们使用异步消息传递来取代方法调用他们能管理自己的状态 *当回复一个消息时,他们能: >创建其他的Actor >发送消息给其他的Actor >停止Actors或他们自己。 注意 由于Akka.NET强制实施家长监督,因此每位演员都受到监督,并且(可能)是其子女的监督人,因此建议您熟悉演员系统和监督与监督,并且还可以帮助阅读演员参考,路径和地址。 更详细的说明文档请跳转官方网站。 下面开始撸代码。 示例代码 *vs新建控制台项目 *Nuget安装 Akka *代码(截图太麻烦,都放到一个文件里去了) using Akka.Actor; using System; namespace DemoAkka.Simple { class Program { static void Main(string[] args) { var system = ActorSystem.Create("MySystem"); var greeter = system.ActorOf<GreeActor>("Student"); while (true) { var i = Math.Abs(Guid.NewGuid().GetHashCode()); greeter.Tell(new GreeMessage() { MsgId = i, RealName = $"Real{i}" }); var key = Console.ReadLine(); if (key == "exit") { break; } } } } /// <summary> /// Actor,可接收消息处理。 /// </summary> public class GreeActor : ReceiveActor { public GreeActor() { Receive<GreeMessage>(greet => { Console.WriteLine($"{DateTime.Now}---MsgId:{greet.MsgId},RealName:{greet.RealName}"); }); } } /// <summary> /// 用于传递消息的实体。 /// </summary> public class GreeMessage { public long MsgId { get; set; } public string RealName { get; set; } } } *运行效果(输入各种字符,消息传递到Actor) 以上代码能有什么用?就我目前体会到的,也就是解耦,方便扩展。当然也是Actor模型的一个特征,消息传递来代替方法调用。 我预想的中,应该先来个Akka.Net部分理论说明,再来说明代码部分的实现原理。可惜想的太远,实际时间太晚了。一步一步来吧。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
按正常的作息时间,现在已经睡着了,但今天加了点班,回到家又吃吃饭喝点啤酒,就不太困了。 既然无事,就写些闲话杂谈吧,反正也好久没更新过了。 刚才看博客园一篇文章《记一次面试经历》,里面谈到zookeeper分布式协调,很高深的样子,就百度科普了一下,原来是和大名鼎鼎的Hadoop有联系的。 此处也附带几个链接,为以后查看做个标记。 zookeeper原理 分布式服务框架 Zookeeper -- 管理分布式环境中的数据 ZooKeeper典型使用场景一览 就在上一年,这些分布式之类的概念都离我很远,只存在我的念想中。因为之前是做企业或学校内部管理系统的,对分布式的需求几乎没有,或者那时候我还没有意识到什么是分布式。但自从进了互联网公司之后,会越来越多的接触这些概念。公司在成长,数据在变大,个人也要跟着成长,要不然跟不上时代的脚步就被遗弃了。现在一些数据量大,耗时的动作,为防止网页超时,需要发送指令给后台处理,还要涉及不同系统之间的信息共享,又要防止因各种错误中断,不影响任务的继续执行。这些都是现在或以后需要考虑的事。 以前觉得自己的技术还行,除了设计无感,前后端都可以,接触了更多的同事之后,发现自己的js水平还很初级。去年刚来公司时,连chrome的js调试工具也不会用,以前完全即使alert或者IE的debugger调试的。chrome的这个F12 console调试更加方便。之前我也封装过各种弹窗,表格插件,提醒等,但和新同事的比起来,规范性和维护性都有些差距,认识到不足,才能提升。 以前做查询的时候,还是一个一个的拼接字符串,是多么的浪费时光。后来发现asp.net MVC自身的Model绑定,然后利用一些ORM或泛型或反射来处理好查询条件,节约了不少精力。这是对去年的自我检讨,今年已经再渐渐改正了,逐渐采用3.0,4.5以后的操作方式,lamuda表达式,List的查询,C#的表达式树等,来提高工作效率。新版本的特性也要更多的了解。 以前听说高内聚,低耦合,只知道模糊的概念,并不知道有什么好处,也没有具体的案例来分析。现在总算有一个大体的认识了(或许也很基础)。如果新建了一个项目,要引用老项目里面的一个小接口,却需要把老项目很多地方都引用进来,一层套一层,这就是比较坑的地方。老项目有些地方耦合太深,解耦任务经过不断的尝试和修改,已经有了很大的提升。说起这里,我想起过去一个项目的二次开发,那里面一个方法里面一层套一层,最后能套几十层,再加上里面一层层的接口实现,横跨大半个仙姑,最后都不能确定某个方法是谁调的了,维护性非常差。经历了这些坑,就能理解高内聚,低耦合的重要性了。高内聚就是要模块内的各个元素尽量紧密结合,低耦合就是要各个模块之间尽量减少依赖。 工作(生活)就是不断填坑(需求)的过程,有的人懂的多,已经预先填好了,走的路就顺畅些,有的人走一步填一步,没有规划,走过的路就坑坑洼洼的,一下雨就完全瘫痪。弥补这种情况的好方式,就是多学习,多思考,多看牛人的解决办法。 计算机界有个出名的摩尔定律:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。摩尔定律虽然不是万能的,但生活也应该像摩尔定律一样,在相同的工作时间,产出的效率和工资,约每隔18-24个月便会增加一倍。如果还没有做到,一定是还不够努力,努力学习新知识吧,在合适的情况下,多投投简历,试试自己的水平。比如金三银十阶段,即使不打算换工作,也可以试试水,毕竟如果一年半换一次工作的话,贸然提高一倍工资会心虚,但如果换成三个阶段,每半年广撒网一次,探探水,看看外界都是需要什么水平的,实时调整自己的方向,每次试投提价30%,一年半后就真的翻倍了。摩尔定律作为一种指导思想还是不错的。随着年龄的增大,步入中年期,就有瓶颈了,毕竟IT是个吃青春饭的活。 最近盛传36岁IT男加班猝死,园子里也多有文章辩论,所以大晚上的还是好好休息吧!。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
今天无意中用F12查看百度,看到console里面的百度招聘信息。虽然只是简单的console.log,但仍然眼前一亮的感觉,暂时称为百度的彩蛋吧。截图如下: 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
如没有感觉错误,现在应该是深秋的时节了。早上和晚上天气有点冷,中文还停留在夏天的温度,真有种"早穿棉袄午穿纱,围着火炉吃西瓜"的感觉。 身处IT界,相对于新公司几位创始人创业前10年以上不换工作的经历,我换工作的频率实在太高了。平均下来就是一年多一次。也许我们每到一个新地方刚开始会无所适从,渐渐融入,渐渐有些东西看着别扭,甚至厌烦,然后接着妥协。实在没兴趣妥协下去的时候,就该换工作了。新工作对于我们来说,是对旧工作的一种压力释放,开始重新有了奋斗的动力,加班的动力。 配备的电脑速度也挺快的,公司技术氛围都还可以,不懂得基本问了都会回答。技术机构用的也非常新。VS2013,mongoDb,git。我们所做的工作就是开发新的版本,重构原来的产品,添加新的功能。 对于IT人来说,能看到自己参与的产品上线,并且有很多人用,这是一件幸福的事,有这样的目标,所有做事也很有干劲。 遥想起当年初次工作,用的VS2005开发。电脑配置低的无话可说,调试一遍都得好久时间,后来申请了i5CPU,但显卡也跟不上,反正就是慢。很影响开发情绪。作为IT人,犹如剑客需要一把绝 世宝剑,我们也需要一个运行畅快的电脑和各种快捷的环境。那时也只是在信息部里给公司开发软件,是不专门配备笔记本的。接触的开发环境由VS2005到VS2008,数据库由SqlServer2000转向Sqlserver2005。团队管理用过Vss,SVN 。 再后来进了软件公司,配了个笔记本,也高兴了好多天。项目组地处N环外,在附近租房子,每天上下班自行车,也别有一番乐趣。一些琐事杂事暂且不表。开发环境涉及Vs2008,Vs2010,数据库Sqlserver2008。使用了MVC4,Linq,团队工具开始使用TFS。之前开发都是单人开发,这才真正走向多人协同开发的道路。单人开发的优点是大事小事一手抓,前台后台一手抓,能实现业务需求,但做的软件规模小。多人开发的优点,每个人做最擅长的东西,加快效率,开发的软件规模相对大点。 几经波折,摆脱了原来做管理软件的套路,进入平台/产品型的公司,这也是受互联网的影响。开发环境为VS2013,数据库MongoDb,团队工具使用Git。之前一直听闻NoSql,Git大名,现在终于能在新项目中使用了。Git用的还不太熟,经常出现冲突,比较冲突,极端情况下,也曾删除代码/撤消过,以后需要尽快熟悉Git的使用。习惯了TFS可设置文件只能单人编辑,自动获取最新版本的特性后,Git中要不断的Pull/push,总感觉很怪异。当然Git有那么大的用户量,这些都不是问题,思维转变过来就行了。Leader们讨论产品需求的时候,我有点跟不上思路。这或许是跟他们在讨论需求,我在考虑实现,导致思维不同步有关吧。 每一次工作,带来的是票子上的提升,技术上的提升,但同时也意味着你上一份工作的不如意,保持好初入公司的奋斗态度,努力! 另外一点闲话就是,虽然在公司里算个新人,但在IT界,五年怎么也得算个青年或中年人了吧。或许新的框架不太熟悉,但万变不离其宗,尽快理清头绪,庖丁解牛,加快工作效率。利用自己的创造力和复制粘贴力,在可访问的亿万网页里留下自己的手迹。 大话少说,实事求是,脚踏实地,同时拿出自己的一份力量去帮助别人。 If you are my colleague, If you always look cnblogs.com,you should know me.good night everyone.【词汇贫乏,错词莫怪】 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
一、前言 由于众所周知的原因,快播和百度影音已经被Out了,各种盗版电影也暂时的失去了两大传播介质。迅雷为躲避雷区,也紧急调整了高速通道和离线下载的战略。 这告诉我们一个道理,做人要低调,更别说做那些风口浪尖的事。 翻开你的电脑,是不是也有很多十分高调的文件?为了证明自己低调的活着,请各位阅者随我一起来体验今天突发奇想设计一款文件隐藏器---雨披风文件隐身衣。 有了它,你可以随心所欲加密或隐藏你的重要文件。 说起文件隐藏,在android系统上,我第一想到的是简易文件管理器、腾讯文件管理器、360手机卫士等(我已经2个月不用智能机了,刚换了诺基亚大板砖,如描述错误,请原谅)。 说实话,在windows系统上,我确实想不出有一个知名的用来隐藏文件的品牌。或许这功能非常容易,没有任何品牌价值? 说起电脑wifi共享,在很近的2010年,我已经知道使用方法,在cmd里,调用netsh wlan 相关命令可以实现,只需第一次稍微配置一下,以后点击写好的bat文件运行,也很容易。但直到2013年,我身边的程序员同事,也有一些人完全不知道这个事情。其实也就是几句代码的事,一说大家都会了。或许也是2013年,猎豹wifi横空出世,立刻横扫千军万马,气势如虹。我从来没想过要把那么简单的事,去写个wifi共享出来。或许很多人都没有想过去写,因为bat文件已经能实现了。但是猎豹写了,配合自身浏览器的宣传,吸引了大量客户。现在一提wifi共享,总会首先想到猎豹wifi共享。这就是品牌。 微信的广告说的很到位:“再小的个体,也有自己的品牌”。 基于以上原因,雨披风文件隐身衣的思路在今天无意间产生了----为了证明自己低调的活着。 顺便也为了祭奠昨天我的雨披风被光荣盗取,世风日下,让吾情何以堪! 二、实现功能 1、将重要文件或文件夹隐藏不显示。 2、程序应可以批量选择文件进行隐藏 3、程序应可以选择文件夹进行隐藏 4、程序应可以设置密码,输入正确的密码后才能访问。 5、程序应有忘记密码,恢复密码的功能。这一步可以考虑通过在线实现。 6、程序应有日志,用来查询隐藏记录、解除隐藏、设置密码、恢复密码、备份还原等记录。 三、实现原理 1、为了方面软件使用和移植,可采用单文件数据库sqllite/Access/MSDE 2000。 2、在点击隐藏时,可考虑用GUID为需要隐藏的文件名和后缀名全部改名,也可直接删除后缀名,并且调用attrib + s + t 变为系统隐藏,并在单文件数据库记录。 3、应由界面显示所有文件,单选或多选所有文件,解除隐藏,根据数据库记录,匹配GUID文件,和原文件名,调用attrib –s -t解除隐藏。并改回原来文件名。 4、将所有文件隐藏时都统一改为GUID,有利于文件的隐藏。更复杂的可考虑将文件压缩无数次,更改无数次后缀名,再统一改为guid,不过这非常影响效率。几种选择需要筛选取最优值。 5、或者在程序提供的窗口内,可查看隐藏文件,关掉程序,则文件不可见。这个实现起来相对复杂。 5、用户忘记密码,可留下网址接口,将用户名和密码提交到云服务器,由服务器处理。 四、代码实现 预计双休可以完成以上所有需求设定,并提供域名可供访问、宣传、下载。敬请期待。 五、总结 敬请期待。。亲们,晚安。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
一、前言 最近做的项目需要单据批量打印的功能,优先想到用RDLC来实现。经过Visual Studio几个版本的发展后,RDLC愈发成熟,操作方式也变得简洁,相比vs2005的版本,有质的提升,不过仍有一下几点缺憾: 1、内置函数不支持C#,只支持Visual Basic 2、不支持Asp.net MVC,支持webForm和winForm 3、VS2008及以下版本开发WebForm时,不建议使用RDLC,因为生成的报表样式不兼容chrome浏览器。 如果未来时间充裕,我将会把RDLC一系列应用实例分享出来。闲话少说,言归正传。 二、测试数据 --订单表 create table fcwOrder ( id int identity(1,1), ordercode varchar(50),--订单号 ordername varchar(50),--订单名字 opername varchar(50),--经手人 comname varchar(50),--客户名称 createtime datetime,--订单日期 barcode image--条形码,预留字段。 ) --订单明细 create table fcwOrderDetails ( id int identity(1,1), ordercode varchar(50),--订单号 procode varchar(50),--产品编号 proname varchar(50),--产品名称 promodel varchar(50),--产品型号 pronum int --订购数量 ) --测试数据 insert into fcwOrder(ordercode,ordername,opername,comname,createtime) values('order001','订单一','张三一','客户一','2014-07-01') insert into fcwOrder(ordercode,ordername,opername,comname,createtime) values('order002','订单二','张三二','客户二','2014-07-02') insert into fcwOrder(ordercode,ordername,opername,comname,createtime) values('order003','订单三','张三三','客户三','2014-07-03') insert into fcwOrderDetails(ordercode,procode,proname,promodel,pronum) values('order001','pro0011','产品一一','长1宽1',11) insert into fcwOrderDetails(ordercode,procode,proname,promodel,pronum) values('order001','pro0012','产品一二','长1宽2',12) insert into fcwOrderDetails(ordercode,procode,proname,promodel,pronum) values('order002','pro0021','产品二一','长2宽1',21) insert into fcwOrderDetails(ordercode,procode,proname,promodel,pronum) values('order002','pro0022','产品二二','长2宽2',22) insert into fcwOrderDetails(ordercode,procode,proname,promodel,pronum) values('order003','pro0031','产品三一','长3宽1',31) insert into fcwOrderDetails(ordercode,procode,proname,promodel,pronum) values('order003','pro0032','产品三二','长3宽2',32) insert into fcwOrderDetails(ordercode,procode,proname,promodel,pronum) values('order003','pro0033','产品三三','长3宽3',33) --统一订单明细 select o.id as orderid,o.ordercode,o.opername,o.ordername,o.comname,o.createtime,o.barcode, d.id as detailid,d.ordercode detailordercode,d.procode,d.proname,d.promodel,d.pronum from fcwOrder o join fcwOrderDetails d on d.ordercode=o.ordercode 三、编码实现 提示:本案例是属于winform程序,原理上操作RDLC部分后台代码也可用于webform 开发环境:Visual Studio 2012 , Sql server 2012 1、【创建项目】打开VS2012,新建项目Fcw.RDLC 2、【创建数据集】右击项目“Fcw.RDLC”,添加新建项,选择数据集,创建数据集Order.xsd 3、【配置数据集】将工具箱中的TableAdapter拖入到数据集设计器中,配置数据库连接后,并将以下语句装载到表中: select o.id as orderid,o.ordercode,o.opername,o.ordername,o.comname,o.createtime,o.barcode, d.id as detailid,d.ordercode detailordercode,d.procode,d.proname,d.promodel,d.pronum from fcwOrder o join fcwOrderDetails d on d.ordercode=o.ordercode 右键单击已经生成的TableAdapter,选择属性,将名称改为Order 4、【新建报表】,选择Fcw.RDLC,右键选择添加新建项,选择报表。创建order.rdlc 5、【配置报表数据源】双击打开Order.RDLC设计器,在报表数据源,点击新建---》数据集,选择已有的数据源或新建新数据源,名称也改成Order 6、【设计报表】将工具箱中的列表拖拽到rdlc界面设计器上,并指定列表的数据名称Order 6.1 选中列表,在行组中,用鼠标右键点击详细信息,添加组,父组。分组依据为ordercode (订单编号),右击ordercode分组,选中在组的各实例之间分页,作用是每个订单页显示。 6.2 删除自动添加的列ordercode及 详细信息组 6.3,依次拖入 表、文本框、图像,在报表数据源,新建参数。根据需求界面完成设计。 注:列表内默认包含矩形,也可以根据需要手动添加。矩形可实现循环数据内的排版。满足个性化需求。 7【winform代码】.winform 拖入ListBox,Button,ReportViewer等步骤,不再赘述。 ListBox的selectMode设为 MultiSimple, 为ReportView设定RDLC文件 重要代码: private void Form1_Load(object sender, EventArgs e) { //绑定list_order DataTable dtorder = SQLHelper.GetDataTable(@"select o.id as orderid,o.ordercode,o.opername,o.ordername,o.comname,o.createtime,o.barcode from fcwOrder o"); list_order.DataSource = dtorder; list_order.DisplayMember = "ordercode"; list_order.ValueMember = "ordername"; } private void button1_Click(object sender, EventArgs e) { StringBuilder sb = new StringBuilder(); //构造多选的order列表 foreach (object obj in list_order.SelectedItems) { DataRowView drv = (DataRowView)obj; if(drv!=null) { sb.Append("'" + drv.Row["ordercode"].ToString() + "',"); } } //为report绑定数据源 if (sb.ToString().Length > 2) { string orderwhere = sb.ToString().Substring(0, sb.ToString().Length - 1); string strsql = @"select o.id as orderid,o.ordercode,o.opername,o.ordername,o.comname,o.createtime,o.barcode, d.id as detailid,d.ordercode detailordercode,d.procode,d.proname,d.promodel,d.pronum from fcwOrder o join fcwOrderDetails d on d.ordercode=o.ordercode where o.ordercode in (" + orderwhere + ")"; DataTable dtorder2 = SQLHelper.GetDataTable(strsql); reportViewer1.LocalReport.DataSources.Clear(); reportViewer1.LocalReport.EnableExternalImages = true; reportViewer1.LocalReport.DataSources.Add(new ReportDataSource("Order",dtorder2)); reportViewer1.LocalReport.SetParameters(new ReportParameter("para_Total",dtorder2.Rows.Count.ToString())); reportViewer1.RefreshReport(); } } 8、【最终效果】 四、总结 本篇文章从基础开始搭建一个基本的单据批量打印应用,涉及数据集的使用,RLDC中列表、表、文本框、矩形、参数、组分页的使用方式。 RDLC中表用来实现类似表格数据之类的需求,包含分组统计汇总、分页等。 文本框用来实现单个字段的显示或者用来处理部分线条等。 参数用来接收固定值。 矩形用来方便排版,当其他方式设计和最终呈现出现布局不同时,可用矩形把布局错乱的部分包围起来。 列表,可以用来处理循环中的单条数据下的各种样式排版。实现自定义需求。 传送门:批量打印单据批量单据打印 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
今天暂时无事,风和日丽,万里无云。游山的、玩水的、遛麻雀的都闲的不亦乐乎,也忙的不亦乐乎。在这美好的季节,依旧躲在被窝或是电脑旁绞尽脑汁敲键盘的人们,也别有一番滋味。废话少说,言归正传。 赶上了一个最难就业季,总有很多不顺。前几天面试,被问了很多mvc和sqlserver索引的问题,借这个时候来温习一下这些知识。 有一个人事数据库hrmis,里面的用户表叫A01,共7000条数据。 为了测试需要把这些数据,复制到另外一个测试数据库里。语句如下: 测试数据库为:funcunweiTest use funcunweiTest select * into peoTest from hrmis..a01 –-输出结果(7311行受影响) 注:【SELECT INTO 和 INSERT INTO SELECT 两种表复制语句】 索引使用的目的,是在大数据量的情况下提高查询速度,几千条数据库是看不出来很大差别的。为了实现大数据,我们可以不断的执行下面这个语句(小心硬盘空间不足,): insert into peoTest select * from peoTest --【在耗费了3G的空间后,用count进行统计有将近375万条数据,可以满足我们的测试了。】 查询耗时我用的办法时,查询之前先声明一个时间,查询之后再声明一个时间,然后获得时间差。 datediff(millisecond,开始时间,结束时间) --millisecond 毫秒 【测试一】查看peoTest表只查询一个列耗时情况。 declare @start datetime,@end datetimeset @start=getdate()select A0188 from peotestset @end=getdate() select datediff(millisecond,@start,@end) --查询A0188耗时为:23706毫秒 A0188代表ID --查询A019Z列耗时:30960毫秒 A019Z代表地址 【测试二】查看peoTest表查询所有列耗时情况 declare @start datetime,@end datetime set @start=getdate() select * from peotest set @end=getdate() select datediff(millisecond,@start,@end) --查询所有列耗时为:201350毫秒 【测试三】利用分页存储过程,查看某一页单列及所有列耗时情况。 一个好用的分页存储过程 declare @start datetime,@end datetime set @start=getdate() exec selectbypage 'peoTest','*','A0188',20,10,0,0,'' set @end=getdate() select datediff(millisecond,@start,@end) --查询的所有列第10页,每页20条数据,耗时:22346毫秒 --只查询A0188列第10页,每页20条数据,耗时:12176毫秒 从以上三个测试例子,可以得出以下结论: 1、尽量少使用 * 号,应只查询需要的字段,能减少不必要的消耗。 2、多使用分页,单页数据量较少,也可以提高查询效率。先写到这里,外面这么好的天气,不去打打球,运动一下身体,真太可惜了。身体是本钱。身体是一切基础!。 现在是在窗户旁坐着,还是自然风吹的爽啊。 欲知后事如何,且听下回分解。。。。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
来到上海也有10天了吧,之前在各大网站上投了不少简历。因为目标想向项目管理层面发展,所以简历写得大部分是项目管理方面的东西,如需求分析、制定计划,分配成员任务,反而刻意避开了之前做的开发。因为之前做的东西,无论是公司内部的项目或者外部的项目,开发者大致都在一到二人,需求都是我和我们经理与其他人员讨论,具体功能设计文档也都是我写。所以也算写的都真实。 收到的面试是不少,每天基本都有两家面试。从这方面我还是有些满意的,毕竟面试也是他们给的一次机会。面试了十几家终于收到了一家公司的复试,其他公司或许是我所接触的知识与他们不符,或者是薪资方面过高,或者是资历与薪资不搭配,或者技术不达标,或者其他原因吧,都如石头落了水。 面试这么多家公司,问的MVC比较多,实际我自己很少用过MVC,也只是了解过一下。 今天下午遭遇了面试以来最囧的一次。面试官看到我的简历貌似心情很不爽,问了一些问题,,也把我的心情打落谷底。综合有以下几条 1)期望薪水与上一份工作薪水差距很大。当面试官问上一份工作薪水时,我刻意提高了一下,结果面试官一直在强调正式工作的薪水,我只好如实相告。 2)DB2,我之前所在第一家公司用的数据库是db2,我负责导出db2数据库到Excel,然后再导入到一个网上对账系统,所以对DB2有所接触,面试官问我用的哪个版本,额,其实有两年多了,我记不清是哪个版本了。 3)MVC出几个版本,这或许是基本知识吧,但MVC我了解不多,这个也没回答上来。 还有几个问题,反正我也回答的不怎么好,然后就听他一直讲人生大道理了,能记得的有一个个段子: 1)有的人耍棍棒很熟练,有的人虽然机关枪用的不熟练,但机关枪威力大,有的人是造原子弹,威力更大。耍棍棒的也想造原子弹,但是很少有机会见到原子弹,无从下手。 所以他的建议是先放低身段,先见识原子弹,去学会怎么造原子弹。 言者听者有益,不听也未必有害。 可以学习的有一条: 1)简历上写的东西一定要熟悉,像DB2,我只是学习了一下,不过时间久了忘却了,这样他一问,我回答不上来,有一种不诚实的感觉,虚夸的感觉。 一场人生道理课,记住的却不多。 或许人在前进的道路上,在不断的记忆与忘却着。 又想起之前一家面试,隔壁房间是一个销售主管在与销售人员说话。记住了几句: 1)”有感觉的人要先勾引,留下印象,然后再以其他方式体现自己的专业性“, 2)”现在是个个性张扬的时代,只有把自己销售出去,才能销售出你的产品“ 3)”每个产品功能对于客户都是基本一样,客户看的是你这个人,如果你这个人让客户放心,那客户就放心你的产品“ 哈,看来能说会道引领思想潮流的果然会有很大的吸引力,这几句话,我会好好记着的。 总结以下,除了一些基础知识,面试问到最多得是:MVC,数据库优化,WCF,数据安全,设计模式。 尽自己所能,尽自己所需,尽自己努力,在这上海滩闯出一片天空。 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
假设表game有一字段为gameYuiJian为bit字段(SQL SERVER 20005)和"是/否"字段(ACCSS数据库),在编写脚本文件时,如下才能正确执行 SQL strCmd = "Select Top 14 * From game Where gameTuiJian = 1 Order By Id Desc"; OleDb strCmd = "Select Top 14 * From game Where gameTuiJian = true Order By Id Desc"; 在时间的处理: sql server 语句"Select * From tt1 Where time1>'2009-12-01' " Access 语句" Select * From tt1 Where time1>#2009-12-01#"也可以用上面语句 Sql Server 语句"Select * From tt1 Where DateDiff(day,time1,getdate()) Access语句"Select * From tt1 Where DateDiff('d',time1,now) Access与SQL SERVER数据库转换 由于SQL2000里面没有"自动编号",所以你的以"自动编号"设置的字段都会变成非空的字段,这就必须手工修改这些字段,并把他的"标示"选择"是",种子为"1",增量为"1", 2,另外,ACCESS2000转换成SQL2000后,原来属性为"是/否"的字段将被转换成非空的"bit",这时候你必须修改成自己想要的属性了; ACCESS转SQL SERVER中的一些经验 1.ACCESS的数据库中的自动编号类型在转化时,sql server并没有将它设为自动编号型,我们需在SQL创建语句中加上identity,表示自动编号! 2.转化时,跟日期有关的字段,SQL SERVER默认为smalldatetime型,我们最好将它变为datetime型,因为datetime型的范围比smalldatetime型大。我遇见这种情况,用smalldatetime型时,转化失败,而用datetime型时,转化成功。 3.对此两种数据库进行操作的sql语句不全相同,例如:在对ACCESS数据库进行删除纪录时用:"delete * from user where id=10",而对SQL SERVER数据库进行删除是用:"delete user where id=10". 4.日期函数不相同,在对ACCESS数据库处理中,可用date()、time()等函数,但对 SQL SERVER数据库处理中,只能用datediff,dateadd等函数,而不能用date()、time()等函数。 5.在对ACCESS数据库处理中,sql语句中直接可以用一些VB的函数,像cstr()函数,而对SQL SERVER数据库处理中,却不能用。 1、必须先安装Microsoft Office Access 2003,和SQL Server2000。2、把旧的动网数据库备份,备份完成后,用Access 2003打开动网旧数据库,在打开时会出现一个警告,不要理会它(安全警告),按打开键,打开后按工具栏--数据库实用工具--转换数据库--转换为2002-2003格式,把数据库转换成2003格式。 2、转换完成后再用Access 2003打开,打开后按工具栏--数据库实用工具--升迁向导--新建数据库--填写SQL数据库登陆名称、密码和要新建的动网数据库(准备转成新的动网数据库),按下一步,按" 》"键,再按下一步,选取所有选项,再按下一步,选择"不对应用程序作任何改动",再按完成。 3、打开SQL企业管理器--数据库 吹礁詹判陆ǖ亩 菘饬税桑 慊髡飧鍪 菘庖幌拢 缓笤诠ぞ呃浮 ?/FONT>SQL脚本--常规--全部显示--编写全部对象脚本--确定(记住存放的位置)。 4、用记事本打开刚才生成的SQL脚本,在编辑栏--替换--查找内容为"smalldatetime"替换为"datetime"--替换全部;完成后再在编辑栏--替换--查找内容为"nvarchar"替换为"varcha"--替换全部,完成后保存退出。 5、打开SQL企业管理器--数据库--点击这个数据库一下新建的动网数据库,然后在工具栏--SQL查询分析器--文件--打开--"刚才生成的SQL脚本"--查询--执行,然后关闭窗口。 6、再回到SQL企业管理器--数据库--点击这个数据库一下新建的动网数据库,然后打开工具栏--数据库转换服务--导入数据--下一步--数据源"Microsoft Access"文件名"为旧的动网数据库"--下一步--再下一步--从源数据复制表和视图--下一步--全选--下一步--立即运行--下一步--完成。 7、修改动网文件夹两个文件conn.asp和inc\const.asp。 SQL是Structured Quevy Language(结构化查询语言)的缩写。SQL是专为数据库而建立的操作命令集,是一种功能齐全的数据库语言。在使用它时,只需要发出"做什么"的命令,"怎么做"是不用使用者考虑的。SQL功能强大、简单易学、使用方便,已经成为了数据库操作的基础,并且现在几乎所有的数据库均支持SQL。 <br> ##1 二、SQL数据库数据体系结构 <br> SQL数据库的数据体系结构基本上是三级结构,但使用术语与传统关系模型术语不同。在SQL中,关系模式(模式)称为"基本表"(base table);存储模式(内模式)称为"存储文件"(stored file);子模式(外模式)称为"视图"(view);元组称为"行"(row);属性称为"列"(column)。名称对称如^00100009a^: <br> ##1 三、SQL语言的组成 <br> 在正式学习SQL语言之前,首先让我们对SQL语言有一个基本认识,介绍一下SQL语言的组成: <br> 1.一个SQL数据库是表(Table)的集合,它由一个或多个SQL模式定义。 <br> 2.一个SQL表由行集构成,一行是列的序列(集合),每列与行对应一个数据项。 <br> 3.一个表或者是一个基本表或者是一个视图。基本表是实际存储在数据库的表,而视图是由若干基本表或其他视图构成的表的定义。 <br> 4.一个基本表可以跨一个或多个存储文件,一个存储文件也可存放一个或多个基本表。每个存储文件与外部存储上一个物理文件对应。 <br> 5.用户可以用SQL语句对视图和基本表进行查询等操作。在用户角度来看,视图和基本表是一样的,没有区别,都是关系(表格)。 <br> 6.SQL用户可以是应用程序,也可以是终端用户。SQL语句可嵌入在宿主语言的程序中使用,宿主语言有FORTRAN,COBOL,PASCAL,PL/I,C和Ada语言等。SQL用户也能作为独立的用户接口,供交互环境下的终端用户使用。 <br> ##1 四、对数据库进行操作 <br> SQL包括了所有对数据库的操作,主要是由4个部分组成: <br> 1.数据定义:这一部分又称为"SQL DDL",定义数据库的逻辑结构,包括定义数据库、基本表、视图和索引4部分。 <br> 2.数据操纵:这一部分又称为"SQL DML",其中包括数据查询和数据更新两大类操作,其中数据更新又包括插入、删除和更新三种操作。 <br> 3.数据控制:对用户访问数据的控制有基本表和视图的授权、完整性规则的描述,事务控制语句等。 <br> 4.嵌入式SQL语言的使用规定:规定SQL语句在宿主语言的程序中使用的规则。 <br> 下面我们将分别介绍: <br> ##2 (一)数据定义 <br> SQL数据定义功能包括定义数据库、基本表、索引和视图。 <br> 首先,让我们了解一下SQL所提供的基本数据类型:(如^00100009b^) <br> 1.数据库的建立与删除 <br> (1)建立数据库:数据库是一个包括了多个基本表的数据集,其语句格式为: <br> CREATE DATABASE <数据库名> 〔其它参数〕 <br> 其中,<数据库名>在系统中必须是唯一的,不能重复,不然将导致数据存取失误。〔其它参数〕因具体数据库实现系统不同而异。 <br> 例:要建立项目管理数据库(xmmanage),其语句应为: <br> CREATE DATABASE xmmanage <br> (2) 数据库的删除:将数据库及其全部内容从系统中删除。 <br> 其语句格式为:DROP DATABASE <数据库名> <br> 例:删除项目管理数据库(xmmanage),其语句应为: <br> DROP DATABASE xmmanage <br> 2.基本表的定义及变更 <br> 本身独立存在的表称为基本表,在SQL语言中一个关系唯一对应一个基本表。基本表的定义指建立基本关系模式,而变更则是指对数据库中已存在的基本表进行删除与修改。 <br> INSERT INTO mobile SELECT mobileID, ' ' '+mobilephone FROM OPENROWSET( 'Microsoft.Jet.OLEDB.4.0 ', 'Excel 8.0;Database=D:\Mobile.xls ', 'SELECT * FROM [Sheet1$] ') 转换时间 convert(char(6),getdate(),120) 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
* 说明:复制表(只复制结构,源表名:a 新表名:b) select * into b from a where 1<>1 * 说明:拷贝表(拷贝数据,源表名:a 目标表名:b) insert into b(a, b, c) select d,e,f from b; * 说明:显示文章、提交人和最后回复时间 select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b * 说明:外连接查询(表名1:a 表名2:b) select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c * 说明:日程安排提前五分钟提醒 select * from 日程安排 where datediff('minute',f开始时间,getdate())>5 * 说明:两张关联表,删除主表中已经在副表中没有的信息 delete from info where not exists ( select * from infobz where info.infid=infobz.infid ) * 说明:-- SQL: SELECT A.NUM, A.NAME, B.UPD_DATE, B.PREV_UPD_DATE FROM TABLE1, (SELECT X.NUM, X.UPD_DATE, Y.UPD_DATE PREV_UPD_DATE FROM (SELECT NUM, UPD_DATE, INBOUND_QTY, STOCK_ONHAND FROM TABLE2 WHERE TO_CHAR(UPD_DATE,'YYYY/MM') = TO_CHAR(SYSDATE, 'YYYY/MM')) X, (SELECT NUM, UPD_DATE, STOCK_ONHAND FROM TABLE2 WHERE TO_CHAR(UPD_DATE,'YYYY/MM') = TO_CHAR(TO_DATE(TO_CHAR(SYSDATE, 'YYYY/MM') || '/01','YYYY/MM/DD') - 1, 'YYYY/MM') ) Y, WHERE X.NUM = Y.NUM (+) AND X.INBOUND_QTY + NVL(Y.STOCK_ONHAND,0) <> X.STOCK_ONHAND ) B WHERE A.NUM = B.NUM * 说明:-- select * from studentinfo where not exists(select * from student where studentinfo.id=student.id) and 系名称='"&strdepartmentname&"' and 专业名称='"&strprofessionname&"' order by 性别,生源地,高考总成绩 * 从数据库中去一年的各单位电话费统计(电话费定额贺电化肥清单两个表来源) SELECT a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') AS telyear, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '01', a.factration)) AS JAN, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '02', a.factration)) AS FRI, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '03', a.factration)) AS MAR, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '04', a.factration)) AS APR, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '05', a.factration)) AS MAY, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '06', a.factration)) AS JUE, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '07', a.factration)) AS JUL, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '08', a.factration)) AS AGU, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '09', a.factration)) AS SEP, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '10', a.factration)) AS OCT, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '11', a.factration)) AS NOV, SUM(decode(TO_CHAR(a.telfeedate, 'mm'), '12', a.factration)) AS DEC FROM (SELECT a.userper, a.tel, a.standfee, b.telfeedate, b.factration FROM TELFEESTAND a, TELFEE b WHERE a.tel = b.telfax) a GROUP BY a.userper, a.tel, a.standfee, TO_CHAR(a.telfeedate, 'yyyy') * 说明:四表联查问题 select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where .. * 说明:得到表中最小的未使用的ID号 * SELECT (CASE WHEN EXISTS(SELECT * FROM Handle b WHERE b.HandleID = 1) THEN MIN(HandleID) + 1 ELSE 1 END) as HandleID FROM Handle WHERE NOT HandleID IN (SELECT a.HandleID - 1 FROM Handle a) * 一个SQL语句的问题:行列转换 select * from v_temp 上面的视图结果如下: user_name role_name ------------------------- 系统管理员 管理员 feng 管理员 feng 一般用户 test 一般用户 想把结果变成这样: user_name role_name --------------------------- 系统管理员 管理员 feng 管理员,一般用户 test 一般用户 =================== create table a_test(name varchar(20),role2 varchar(20)) insert into a_test values('李','管理员') insert into a_test values('张','管理员') insert into a_test values('张','一般用户') insert into a_test values('常','一般用户') create function join_str(@content varchar(100)) returns varchar(2000) as begin declare @str varchar(2000) set @str='' select @str=@str+','+rtrim(role2) from a_test where [name]=@content select @str=right(@str,len(@str)-1) return @str end go --调用: select [name],dbo.join_str([name]) role2 from a_test group by [name] --select distinct name,dbo.uf_test(name) from a_test * 快速比较结构相同的两表 结构相同的两表,一表有记录3万条左右,一表有记录2万条左右,我怎样快速查找两表的不同记录? ============================ 给你一个测试方法,从northwind中的orders表取数据。 select * into n1 from orders select * into n2 from orders select * from n1 select * from n2 --添加主键,然后修改n1中若干字段的若干条 alter table n1 add constraint pk_n1_id primary key (OrderID) alter table n2 add constraint pk_n2_id primary key (OrderID) select OrderID from (select * from n1 union select * from n2) a group by OrderID having count(*) > 1 应该可以,而且将不同的记录的ID显示出来。 下面的适用于双方记录一样的情况, select * from n1 where orderid in (select OrderID from (select * from n1 union select * from n2) a group by OrderID having count(*) > 1) 至于双方互不存在的记录是比较好处理的 --删除n1,n2中若干条记录 delete from n1 where orderID in ('10728','10730') delete from n2 where orderID in ('11000','11001') --************************************************************* -- 双方都有该记录却不完全相同 select * from n1 where orderid in(select OrderID from (select * from n1 union select * from n2) a group by OrderID having count(*) > 1) union --n2中存在但在n1中不存的在10728,10730 select * from n1 where OrderID not in (select OrderID from n2) union --n1中存在但在n2中不存的在11000,11001 select * from n2 where OrderID not in (select OrderID from n1) * 四种方法取表里n到m条纪录: 1. select top m * into 临时表(或表变量) from tablename order by columnname -- 将top m笔插入 set rowcount n select * from 表变量 order by columnname desc 2. select top n * from (select top m * from tablename order by columnname) a order by columnname desc 3.如果tablename里没有其他identity列,那么: select identity(int) id0,* into #temp from tablename 取n到m条的语句为: select * from #temp where id0 >=n and id0 <= m 如果你在执行select identity(int) id0,* into #temp from tablename这条语句的时候报错,那是因为你的DB中间的select into/bulkcopy属性没有打开要先执行: exec sp_dboption 你的DB名字,'select into/bulkcopy',true 4.如果表里有identity属性,那么简单: select * from tablename where identitycol between n and m * 如何删除一个表中重复的记录? create table a_dist(id int,name varchar(20)) insert into a_dist values(1,'abc') insert into a_dist values(1,'abc') insert into a_dist values(1,'abc') insert into a_dist values(1,'abc') exec up_distinct 'a_dist','id' select * from a_dist create procedure up_distinct(@t_name varchar(30),@f_key varchar(30)) --f_key表示是分组字段﹐即主键字段 as begin declare @max integer,@id varchar(30) ,@sql varchar(7999) ,@type integer select @sql = 'declare cur_rows cursor for select '+@f_key+' ,count(*) from ' +@t_name +' group by ' +@f_key +' having count(*) > 1' exec(@sql) open cur_rows fetch cur_rows into @id,@max while @@fetch_status=0 begin select @max = @max -1 set rowcount @max select @type = xtype from syscolumns where id=object_id(@t_name) and name=@f_key if @type=56 select @sql = 'delete from '+@t_name+' where ' + @f_key+' = '+ @id if @type=167 select @sql = 'delete from '+@t_name+' where ' + @f_key+' = '+''''+ @id +'''' exec(@sql) fetch cur_rows into @id,@max end close cur_rows deallocate cur_rows set rowcount 0 end select * from systypes select * from syscolumns where id = object_id('a_dist') * 查询数据的最大排序问题(只能用一条语句写) CREATE TABLE hard (qu char (11) ,co char (11) ,je numeric(3, 0)) insert into hard values ('A','1',3) insert into hard values ('A','2',4) insert into hard values ('A','4',2) insert into hard values ('A','6',9) insert into hard values ('B','1',4) insert into hard values ('B','2',5) insert into hard values ('B','3',6) insert into hard values ('C','3',4) insert into hard values ('C','6',7) insert into hard values ('C','2',3) 要求查询出来的结果如下: qu co je ----------- ----------- ----- A 6 9 A 2 4 B 3 6 B 2 5 C 6 7 C 3 4 就是要按qu分组,每组中取je最大的前2位!! 而且只能用一句sql语句!!! select * from hard a where je in (select top 2 je from hard b where a.qu=b.qu order by je) * 求删除重复记录的sql语句? 怎样把具有相同字段的纪录删除,只留下一条。 例如,表test里有id,name字段 如果有name相同的记录 只留下一条,其余的删除。 name的内容不定,相同的记录数不定。 有没有这样的sql语句? ============================== A:一个完整的解决方案: 将重复的记录记入temp1表: select [标志字段id],count(*) into temp1 from [表名] group by [标志字段id] having count(*)>1 2、将不重复的记录记入temp1表: insert temp1 select [标志字段id],count(*) from [表名] group by [标志字段id] having count(*)=1 3、作一个包含所有不重复记录的表: select * into temp2 from [表名] where 标志字段id in(select 标志字段id from temp1) 4、删除重复表: delete [表名] 5、恢复表: insert [表名] select * from temp2 6、删除临时表: drop table temp1 drop table temp2 ================================ B: create table a_dist(id int,name varchar(20)) insert into a_dist values(1,'abc') insert into a_dist values(1,'abc') insert into a_dist values(1,'abc') insert into a_dist values(1,'abc') exec up_distinct 'a_dist','id' select * from a_dist create procedure up_distinct(@t_name varchar(30),@f_key varchar(30)) --f_key表示是分组字段﹐即主键字段 as begin declare @max integer,@id varchar(30) ,@sql varchar(7999) ,@type integer select @sql = 'declare cur_rows cursor for select '+@f_key+' ,count(*) from ' +@t_name +' group by ' +@f_key +' having count(*) > 1' exec(@sql) open cur_rows fetch cur_rows into @id,@max while @@fetch_status=0 begin select @max = @max -1 set rowcount @max select @type = xtype from syscolumns where id=object_id(@t_name) and name=@f_key if @type=56 select @sql = 'delete from '+@t_name+' where ' + @f_key+' = '+ @id if @type=167 select @sql = 'delete from '+@t_name+' where ' + @f_key+' = '+''''+ @id +'''' exec(@sql) fetch cur_rows into @id,@max end close cur_rows deallocate cur_rows set rowcount 0 end select * from systypes select * from syscolumns where id = object_id('a_dist') * 行列转换--普通 假设有张学生成绩表(CJ)如下 Name Subject Result 张三 语文 80 张三 数学 90 张三 物理 85 李四 语文 85 李四 数学 92 李四 物理 82 想变成 姓名 语文 数学 物理 张三 80 90 85 李四 85 92 82 declare @sql varchar(4000) set @sql = 'select Name' select @sql = @sql + ',sum(case Subject when '''+Subject+''' then Result end) ['+Subject+']' from (select distinct Subject from CJ) as a select @sql = @sql+' from test group by name' exec(@sql) 行列转换--合并 有表A, id pid 1 1 1 2 1 3 2 1 2 2 3 1 如何化成表B: id pid 1 1,2,3 2 1,2 3 1 创建一个合并的函数 create function fmerg(@id int) returns varchar(8000) as begin declare @str varchar(8000) set @str='' select @str=@str+','+cast(pid as varchar) from 表A where id=@id set @str=right(@str,len(@str)-1) return(@str) End go --调用自定义函数得到结果 select distinct id,dbo.fmerg(id) from 表A * 如何取得一个数据表的所有列名 方法如下:先从SYSTEMOBJECT系统表中取得数据表的SYSTEMID,然后再SYSCOLUMN表中取得该数据表的所有列名。 SQL语句如下: declare @objid int,@objname char(40) set @objname = 'tablename' select @objid = id from sysobjects where id = object_id(@objname) select 'Column_name' = name from syscolumns where id = @objid order by colid 或 SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME ='users' * 通过SQL语句来更改用户的密码 修改别人的,需要sysadmin role EXEC sp_password NULL, 'newpassword', 'User' 如果帐号为SA执行EXEC sp_password NULL, 'newpassword', sa * 怎么判断出一个表的哪些字段不允许为空? select COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where IS_NULLABLE='NO' and TABLE_NAME=tablename * 如何在数据库里找到含有相同字段的表? a. 查已知列名的情况 SELECT b.name as TableName,a.name as columnname From syscolumns a INNER JOIN sysobjects b ON a.id=b.id AND b.type='U' AND a.name='你的字段名字' * 未知列名查所有在不同表出现过的列名 Select o.name As tablename,s1.name As columnname From syscolumns s1, sysobjects o Where s1.id = o.id And o.type = 'U' And Exists ( Select 1 From syscolumns s2 Where s1.name = s2.name And s1.id <> s2.id ) * 查询第xxx行数据 假设id是主键: select * from (select top xxx * from yourtable) aa where not exists(select 1 from (select top xxx-1 * from yourtable) bb where aa.id=bb.id) 如果使用游标也是可以的 fetch absolute [number] from [cursor_name] 行数为绝对行数 * SQL Server日期计算 a. 一个月的第一天 SELECT DATEADD(mm, DATEDIFF(mm,0,getdate()), 0) b. 本周的星期一 SELECT DATEADD(wk, DATEDIFF(wk,0,getdate()), 0) c. 一年的第一天 SELECT DATEADD(yy, DATEDIFF(yy,0,getdate()), 0) d. 季度的第一天 SELECT DATEADD(qq, DATEDIFF(qq,0,getdate()), 0) e. 上个月的最后一天 SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(mm,0,getdate()), 0)) f. 去年的最后一天 SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate()), 0)) g. 本月的最后一天 SELECT dateadd(ms,-3,DATEADD(mm, DATEDIFF(m,0,getdate())+1, 0)) h. 本月的第一个星期一 select DATEADD(wk, DATEDIFF(wk,0, dateadd(dd,6-datepart(day,getdate()),getdate()) ), 0) i. 本年的最后一天 SELECT dateadd(ms,-3,DATEADD(yy, DATEDIFF(yy,0,getdate())+1, 0))。 * 获取表结构[把 'sysobjects' 替换 成 'tablename' 即可] SELECT CASE IsNull(I.name, '') When '' Then '' Else '*' End as IsPK, Object_Name(A.id) as t_name, A.name as c_name, IsNull(SubString(M.text, 1, 254), '') as pbc_init, T.name as F_DataType, CASE IsNull(TYPEPROPERTY(T.name, 'Scale'), '') WHEN '' Then Cast(A.prec as varchar) ELSE Cast(A.prec as varchar) + ',' + Cast(A.scale as varchar) END as F_Scale, A.isnullable as F_isNullAble FROM Syscolumns as A JOIN Systypes as T ON (A.xType = T.xUserType AND A.Id = Object_id('sysobjects') ) LEFT JOIN ( SysIndexes as I JOIN Syscolumns as A1 ON ( I.id = A1.id and A1.id = object_id('sysobjects') and (I.status & 0x800) = 0x800 AND A1.colid <= I.keycnt) ) ON ( A.id = I.id AND A.name = index_col('sysobjects', I.indid, A1.colid) ) LEFT JOIN SysComments as M ON ( M.id = A.cdefault and ObjectProperty(A.cdefault, 'IsConstraint') = 1 ) ORDER BY A.Colid ASC * 提取数据库内所有表的字段详细说明的SQL语句 SELECT (case when a.colorder=1 then d.name else '' end) N'表名', a.colorder N'字段序号', a.name N'字段名', (case when COLUMNPROPERTY( a.id,a.name,'IsIdentity')=1 then '√'else '' end) N'标识', (case when (SELECT count(*) FROM sysobjects WHERE (name in (SELECT name FROM sysindexes WHERE (id = a.id) AND (indid in (SELECT indid FROM sysindexkeys WHERE (id = a.id) AND (colid in (SELECT colid FROM syscolumns WHERE (id = a.id) AND (name = a.name))))))) AND (xtype = 'PK'))>0 then '√' else '' end) N'主键', b.name N'类型', a.length N'占用字节数', COLUMNPROPERTY(a.id,a.name,'PRECISION') as N'长度', isnull(COLUMNPROPERTY(a.id,a.name,'Scale'),0) as N'小数位数', (case when a.isnullable=1 then '√'else '' end) N'允许空', isnull(e.text,'') N'默认值', isnull(g.[value],'') AS N'字段说明' FROM syscolumns a left join systypes b on a.xtype=b.xusertype inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties' left join syscomments e on a.cdefault=e.id left join sysproperties g on a.id=g.id AND a.colid = g.smallid order by object_name(a.id),a.colorder * 快速获取表test的记录总数[对大容量表非常有效] 快速获取表test的记录总数: select rows from sysindexes where id = object_id('test') and indid in (0,1) update 2 set KHXH=(ID+1)\2 2行递增编号 update [23] set id1 = 'No.'+right('00000000'+id,6) where id not like 'No%' //递增 update [23] set id1= 'No.'+right('00000000'+replace(id1,'No.',''),6) //补位递增 delete from [1] where (id%2)=1 奇数 * 替换表名字段 update [1] set domurl = replace(domurl,'Upload/Imgswf/','Upload/Photo/') where domurl like '%Upload/Imgswf/%' * 截位 SELECT LEFT(表名, 5) 熟悉SQL SERVER 2000的数据库管理员都知道,其DTS可以进行数据的导入导出,其实,我们也可以使用Transact-SQL语句进行导入导出操作。在 Transact-SQL语句中,我们主要使用OpenDataSource函数、OPENROWSET 函数,关于函数的详细说明,请参考SQL联机帮助。利用下述方法,可以十分容易地实现SQL SERVER、ACCESS、EXCEL数据转换,详细说明如下: 一、SQL SERVER 和ACCESS的数据导入导出 常规的数据导入导出:使用DTS向导迁移你的Access数据到SQL Server,你可以使用这些步骤: ○1在SQL SERVER企业管理器中的Tools(工具)菜单上,选择Data Transformation ○2Services(数据转换服务),然后选择 czdImport Data(导入数据)。 ○3在Choose a Data Source(选择数据源)对话框中选择Microsoft Access as the Source,然后键入你的.mdb数据库(.mdb文件扩展名)的文件名或通过浏览寻找该文件。 ○4在Choose a Destination(选择目标)对话框中,选择Microsoft OLE DB Prov ider for SQL Server,选择数据库服务器,然后单击必要的验证方式。 ○5在Specify Table Copy(指定表格复制)或Query(查询)对话框中,单击Copy tables(复制表格)。 ○6在Select Source Tables(选择源表格)对话框中,单击Select All(全部选定)。下一步,完成。 Transact-SQL语句进行导入导出: 1.在SQL SERVER里查询access数据: SELECT * FROM OpenDataSource( 'Microsoft.Jet.OLEDB.4.0','Data Source="c:\DB.mdb";User ID=Admin;Password=')表名 2.将access导入SQL server在SQL SERVER 里运行:SELECT * INTO newtable FROM OPENDATASOURCE ('Microsoft.Jet.OLEDB.4.0','Data Source="c:\DB.mdb";User ID=Admin;Password=' )...表名 3.将SQL SERVER表里的数据插入到Access表中在SQL SERVER 里运行:insert into OpenDataSource( 'Microsoft.Jet.OLEDB.4.0','Data Source=" c:\DB.mdb";User ID=Admin;Password=')...表名 (列名1,列名2) select 列名1,列名2 from sql表 实例:insert into OPENROWSET('Microsoft.Jet.OLEDB.4.0','C:\db.mdb';'admin';'', Test) select id,name from Test INSERT INTO OPENROWSET('Microsoft.Jet.OLEDB.4.0', 'c:\trade.mdb'; 'admin'; '', 表名) SELECT * FROM sqltablename 二、SQL SERVER 和EXCEL的数据导入导出 1、在SQL SERVER里查询Excel数据: SELECT * FROM OpenDataSource( 'Microsoft.Jet.OLEDB.4.0','Data Source="c:\book1.xls";User ID=Admin;Password=;Extended properties=Excel 5.0')[Sheet1$] 下面是个查询的示例,它通过用于 Jet 的 OLE DB 提供程序查询 Excel 电子表格。SELECT * FROM OpenDataSource ( 'Microsoft.Jet.OLEDB.4.0','Data Source="c:\Finance\account.xls";User ID=Admin;Password=;Extended properties=Excel 5.0')...xactions 2、将Excel的数据导入SQL server :SELECT * into newtable FROM OpenDataSource( 'Microsoft.Jet.OLEDB.4.0','Data Source="c:\book1.xls";User ID=Admin;Password=;Extended properties=Excel 5.0')...[Sheet1$] 实例:SELECT * into newtable FROM OpenDataSource( 'Microsoft.Jet.OLEDB.4.0','Data Source="c:\Finance\account.xls";User ID=Admin;Password=;Extended properties=Excel 5.0')...xactions 3、将SQL SERVER中查询到的数据导成一个Excel文件T-SQL代码:EXEC master..xp_cmdshell 'bcp 库名.dbo.表名out c:\Temp.xls -c -q -S"servername" -U"sa" -P""'参数:S 是SQL服务器名;U是用户;P是密码说明:还可以导出文本文件等多种格式 实例:EXEC master..xp_cmdshell 'bcp saletesttmp.dbo.CusAccount out c:\temp1.xls -c -q -S"pmserver" -U"sa" -P"sa"' EXEC master..xp_cmdshell 'bcp "SELECT au_fname, au_lname FROM pubs..authors ORDER BY au_lname" queryout C:\ authors.xls -c -Sservername -Usa -Ppassword' 在VB6中应用ADO导出EXCEL文件代码:Dim cn As New ADODB.Connectioncn.open "Driver={SQL Server};Server=WEBSVR;DataBase=WebMis;UID=sa;WD=123;"cn.execute "master..xp_cmdshell 'bcp "SELECT col1, col2 FROM 库名.dbo.表名" queryout E:\DT.xls -c -Sservername -Usa -Ppassword'" 4、在SQL SERVER里往Excel插入数据: insert into OpenDataSource( 'Microsoft.Jet.OLEDB.4.0','Data Source="c:\Temp.xls";User ID=Admin;Password=;Extended properties=Excel 5.0')...table1 (A1,A2,A3) values (1,2,3) 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
由于业务逻辑的多样性 经常得在sql server中查询不同数据库中数据 这就产生了分布式查询的需求 现我将开发中遇到的几种查询总结如下: 1.access版本 --建立连接服务器 exec sp_addlinkedserver --要创建的链接服务器名称 'ai', --产品名称 'access', --ole db 字符 'microsoft.jet.oledb.4.0', --数据源 --格式: -- 盘符:\路径\文件名 -- \\网络名\共享名\文件名 (网络版本) 'd:\testdb\db.mdb' go --创建链接服务器上远程登录之间的映射 --链接服务器默认设置为用登陆的上下文进行 --现在我们修改为连接链接服务器不需使用任何登录或密码 exec sp_addlinkedsrvlogin 'ai','false' go --查询数据 select * from ai...mytable go 2.excel版本 --建立连接服务器 exec sp_addlinkedserver --要创建的链接服务器名称 'ai_ex', --产品名称 'ex', --ole db 字符 'microsoft.jet.oledb.4.0', --数据源 --格式: -- 盘符:\路径\文件名 -- \\网络名\共享名\文件名 (网络版本) 'd:\testdb\mybook.xls' , null, --ole db 提供程序特定的连接字符串 'excel 5.0' go ----创建链接服务器上远程登录之间的映射 --链接服务器默认设置为用登陆的上下文进行 --现在我们修改为连接链接服务器不需使用任何登录或密码 exec sp_addlinkedsrvlogin 'ai_ex','false' go --查询数据 select * from ai_ex...sheet3$ go 3.ms sql版本 --建立连接服务器 exec sp_addlinkedserver --要创建的链接服务器名称 'ai_mssql', --产品名称 'ms', --ole db 字符 'sqloledb', --数据源 '218.204.111.111,3342' go --创建链接服务器上远程登录之间的映射 exec sp_addlinkedsrvlogin 'ai_mssql', 'false', null, --远程服务器的登陆用户名 'zhangzhe', --远程服务器的登陆密码 'fish2231' go --查询数据 select * from ai_mssql.pubs.dbo.jobs go --还有一个更简单的办法 --这种方式在链接服务器建立后,它是默认开放rpc的 --建立连接服务器 exec sp_addlinkedserver --要创建的链接服务器名称 --这里就用数据源作名称 '218.204.111.111,3342', 'sql server' go --创建链接服务器上远程登录之间的映射 exec sp_addlinkedsrvlogin '218.204.111.111,3342', 'false', null, --远程服务器的登陆用户名 'zhangzhe', --远程服务器的登陆密码 'fish2231' go --查询数据 select * from [218.204.253.131,3342].pubs.dbo.jobs go 4.oracle版本 --建立连接服务器 exec sp_addlinkedserver --要创建的链接服务器名称 'o', --产品名称 'oracle', --ole db 字符 'msdaora', --数据源 'acc' go --创建链接服务器上远程登录之间的映射 exec sp_addlinkedsrvlogin 'o', 'false', null, --oracle服务器的登陆用户名 'f02m185', --oracle服务器的登陆密码 'f02m185185' go --查询数据 --格式:linkserver..oracle用户名.表名 --注意用大写,因为在oracle的数据字典中为大写 select * from o..f02m185.ai go 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
最近把平时在项目中常用到的数据库分页sql总结了下。大家可以贴出分页更高效的sql语句。sqlserver分页 第一种分页方法 需用到的参数: pageSize 每页显示多少条数据 pageNumber 页数 从客户端传来 totalRecouds 表中的总记录数 select count (*) from 表名 totalPages 总页数 totalPages=totalRecouds%pageSize==0?totalRecouds/pageSize:totalRecouds/pageSize+1 pages 计算前pages 条数据 pages= pageSize*(pageNumber-1) SQL语句: select top pageSize * from 表名 where id not in (select top pages id from 表名 order by id) order by id 第二种分页方法 pageSize 每页显示多少条数据 pageNumber 页数 从客户端传来 pages=pageSize*(pageNumber-1)+1 select top pageSize * from 表名 where id>=(select max(id) from (select top pages id from 表名 order by id asc ) t )mysql分页 需用到的参数: pageSize 每页显示多少条数据 pageNumber 页数 从客户端传来 totalRecouds 表中的总记录数 select count (*) from 表名 totalPages 总页数 totalPages=totalRecouds%pageSize==0?totalRecouds/pageSize:totalRecouds/pageSize+1 pages 起始位置 pages= pageSize*(pageNumber-1) SQL语句: select * from 表名 limit pages, pageSize; mysql 分页依赖于关键字 limit 它需两个参数:起始位置和pageSize 起始位置=页大小*(页数-1) 起始位置=pageSize*(pageNumber -1) oracle分页 pageSize 每页显示多少条数据 pageNumber 页数 从客户端传来 totalRecouds 表中的总记录数 select count (*) from 表名 totalPages 总页数 totalPages=totalRecouds%pageSize==0?totalRecouds/pageSize:totalRecouds/pageSize+1 startPage 起始位置 startPage= pageSize*(pageNumber-1)+1 endPage=startPage+pageSize SQL语句 select a.* from ( select rownum num ,t.* from 表名 t where 某列=某值 order by id asc )a where a.num>=startPage and a.num<endPage db2分页 int startPage=1 //起始页 int endPage; //终止页 int pageSize=5; //页大小 int pageNumber=1 //请求页 startPage=(pageNumber-1)*pageSize+1 endPage=(startPage+pageSize); SQL语句 select * from (select 字段1,字段2,字段3,字段4,字段5,rownumber() over(order by 排序字段 asc ) as rowid from 表名 )as a where a.rowid >= startPage AND a.rowid <endPage access分页 pageSize 每页显示多少条数据 pageNumber 页数 从客户端传来 pages=pageSize*(pageNumber-1)+1 SQL语句 select top pageSize * from 表名 where id>=(select max(id) from (select top pages id from 表名 order by id asc ) t ) 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
DB2相关问题及解决方法: 一、DB2中的代码页(codepage)问题。 DB2备份时发生过代码页错误的问题,修改代码页后备份正常,但创建数据库时又发生代码页的错误。这是DB2服务器使用的代码页配置和客户端使用的代码页配置不同造成的(注:DB2服务器的代码页配置是独立的,用代码页不同的客户端操作服务器就会产生错误。本机操作服务器称为本地客户端,操作系统使用的代码页有可能和DB2服务器的配置不同,和远程客户端一样会产生上面的问题)。代码页和系统使用的字符集有关,这也是windows下的数据库备份和Linux下的数据库备份不能相互恢复的原因(Windows的codepage为819,一般的国标库/GBK为1386)。可用db2set命令对服务器的代码页进行设置。(具体设置见后文) locale命令查看本地字符集 二、TIANJIN数据库备份不能恢复的问题 TIANJIN数据库备份恢复时,在Linux系统下提示container被占用,这是由于TIANJIN数据库采用了系统以外的表空间引起的,不能采用常规方法进行恢复。 恢复步骤为: db2 create db targetdb using codeset GBK territory zh_CN (创建数据库供恢复) db2 connect to tianjin (连接到TIANJIN数据库) db2 list tablespaces (查看TIANJIN数据库使用的表空间) db2 list tablespace containers for 3 (查看表空间3使用的容器) ………… db2 list tablespace containers for 7 (查看表空间7使用的容器。) (TIANJIN数据库用到了7个表空间,其中1、2为系统默认,其他为自己创建,若已知道数据库表空间,以上步骤可省略) mkdir [directory] (创建表空间用到的容器所在目录。需要多少容器,创建多少个目录,这个目录必须是DB2用户有权限的目录) db2 connect reset (释放所有连接) db2 restore db sourcedb from /DB2Data/backupdata/ into targetdb redirect (恢复数据库) db2 "set tablespace containers for 3 using (path '/home/db2inst1/tt/1')" (设置表空间的容器,path后是容器存放的路径) db2 "set tablespace containers for 4 using (path '/home/db2inst1/tt/2',path '/home/db2inst1/tt/3')" (表空间用了多个路径的情况) ………… db2 "set tablespace containers for 7 using (path '/home/db2inst1/tt/6')" db2 restore db tianjin continue (完成数据库恢复) 连接数据库验证安装即可 三、不同操作系统中数据库的移动(db2move) 由于我们的系统中使用了多个用户、多个表空间,不能直接用db2move进行恢复,必须先生成相关表空间和表,再插入数据。 1、 生成ddl文件 db2look -d 数据库别名 -e -p -l -o 目标文件的名字-i 用户名 -w 密码 -d指定数据库,-o指定目标文件,-l表示生成表空间,-i指定用户名,-w指定密码。 如:db2look -d jsyrem -e -p -l -o jsyrem.ddl -i zgc3 -w zgc 注意:源数据库必须在本地客户端编目,生成的文件存放在当前目录下。 2、 生成db2move的导出文件 db2move数据库别名export -l 大对象存放目录(可省略) -u 用户名 -p 密码 如:db2move jsyrem export -l lob -u zgc3 -p zgc 注意:源数据库必须在本地客户端编目,大对象存放目录可以不用事先建立,由系统自动生成,生成的文件存放在当前目录下。 3、 新建目标数据库 4、 在目标数据库里创建表空间和表 db2 -tvf ddl文件名 例如:db2 -tvf jsyrem.ddl 注意: ①两个数据库里的代码页必须设置为一致 ②执行命令前必须先修改ddl文件,设置里面的connection连接至目标数据库(文件里可能会有多处需要对连接进行设置)。 ③执行命令前必须先修改ddl文件,设置表空间地址,为目标数据库建立表空间指定存放位置。这些目录可能需要事先建好。(目录1、2……n可以不用建,由系统自动生成) ④执行命令必须在生成的文件存放的目录下进行。 5、 导入数据 db2move 数据库名 import -io insert -l大对象存放目录 -io 指定导入方式,为create表示数据库中不存在该表时自动生成表,为replace表示替换原有内容,为insert表示仅仅插入数据;-l指定大对象存放目录。 db2move jsy2 import –u 用户名 –p 密码 注意:执行命令必须在生成的文件存放的目录下进行。 6、 其他:由于数据库表之间存在键关系,数据导入时可能会发生冲突,需要记下发生冲突的表,并修改db2move.lst文件,把这些表对应的行挪到文件的最后生成。 7、 可通过EXPORT文件和IMPORT文件查看数据导入导出时的系统信息,通过tablennn.msg文件查看某个表导入导出时的系统信息。 注意:以上操作针对的客户端是windows操作系统,linux系统下会发生错误。 四、联合数据对象的建立 联合数据对象提供将一个数据库里的用户和表映射到另一个数据库的功能。用户可通过对后者的访问达到访问前者的目的。 建立步骤: 1、 前期工作1:配置数据库实例名的参数,设置“管理”下的FEDERATED为“”;是 2、 前期工作2:源数据库编码到客户端 3、 在联合数据库对象中创建包装器。(DB2àDB2的映射包装器名选择DRDA,库名使用缺省的缺省db2drda.dll即可) 4、 创建服务器。(远程数据源的名称填写数据库编码的别名,DB2àDB2的映射服务器类型选择DB2/UDB,选择正确的版本号,用户标示和密码填写目标库的用户名密码,其余内容默认即可) 5、 建立用户映射。选择远程用户和本地用户进行映射。 6、 建立别名映射,即是表的映射。 五、DB2中的常用命令 1、 db2set命令 db2set:查看db2的常用设置 db2set –lr:查看db2的所有变量 db2set 变量名 = 变量值:设置db2中的变量。如:db2set db2codepage = GBK;db2set db2country = zh_CN。(这是最常用的两个设置,设置codepage为GBK国标库,country为zh_CN中国。设置后可解决数据库创建、备份时代码页错误的问题。设置完后用db2 terminate中止一下即可起作用) 2、 查看数据库配置 查看Database Manager配置:db2 get dbm cfg 查看某数据库配置: 两种方法: 第一、 db2 get db cfg for 数据库名。如db2 get db cfg for tianjin 第二、 先用connect命令连接上数据库,再用db2 get db cfg。如: connect to tianjin(或:connect to tianjin user zgc3 using zgc) db2 get db cfg 可以只查看其中某一项的配置,如查看territory db2 get db cfg for tianjin|grep terr 修改数据库配置:db2 update db cfg using 参数 参数值 3、 数据库备份、数据库创建、数据库恢复 数据库备份:db2 backup database 数据库名 to 备份位置(DB2用户必须对备份位置有权限)。如:db2 backup database tianjin to /home/db2inst1/backup。注意: 备份数据库时应用db2 connect reset将所有连接去掉。 数据库创建:db2 create database数据库名。如:db2 create database dbname 用特定的字符集创建数据库:db2 create database 数据库名 using codeset [codeset] territory [terriroty]。如:db2 create database dbname using codeset GBK territory CN 数据库恢复几点说明:若文件夹中只有一个备份文件,可以不用写taken at。若数据库恢复中产生错误,可用restore database dbname continue/abort来对恢复进行继续和取消。 或者用控制台创建数据库时,第六步region处选择PRC(People’s Republic Of China) 4、 其他常用命令 db2move:在不同操作系统中移植数据库。但因存在外键约束,应对文件进行编辑。 db2level;查看DB2的修订版本 db2look:导出ddl? db2 list table/tablespaces/db at……列出相应内容 (具体参看IBM红皮书) 5、 在客户端增加、查看结点和数据库编目 增加结点编目:db2 catalog tcpip node 结点名字 remote 结点所在ip地址 server 50000 查看结点编目:db2 list node directory 删除结点编目:db2 uncatalog node 结点别名 增加数据库编目:db2 catalog db 远程数据库名字 as 数据库别名 at node 结点名字 查看数据库编目:db2 list db directory 删除数据库编目:db2 uncatalog db数据库别名 6、 不同操作系统的倒库(db2move): export:db2move dbname export import:db2move dbname import(-io replace/create -u username –p password) load:db2move dbname load 注意:执行export命令,生成的文件存放在当前目录下,dbname是catalog上的别名。import也是从当前目录读取文件。在将库import入一个新库时,应该先建立一个库,然后db2move 新库名称 import……。-io参数表示导入的库里的表覆盖/新建到新库里,-u、-p表示用于建库的用户名和密码。 7、 远程操作数据库 db2 attach to 数据库别名 user 用户名 using 密码 即可在本地操作远程数据库(create db、drop db等操作) 注意:要操作的数据库必须在本地编目 要取消attach可用命令db2 detach,或attach到其他数据库。 8、 将数据库操作的控制台信息存入文档 要执行的命令名>控制台信息文件名 9、 DB2里的帮助 db2 ? 要查询的内容(如:db2 ? sql30082n) 或者直接输入命令(db2move)不带参数 10、 停止application 数据库操作过程中可能会发生错误,导致数据库不可用。此时可用force application命令停止这种操作。命令格式可以是db2 force application application号(停掉单个application)或db2 force application all(停掉所有application)两种形式。 11、 其他 六、对于系统优化的建议 1、 增加buffpage的配置 2、 增加sortheap的配置(一般4M以上) 3、 将锁定超时 locktimeout设置为on或yes 4、 恢复的日志保留 (具体设置参看IBM红皮书) 命令: db2 connect to tianjin db2 update db cfg using LOGRETAIN yes db2 backup db tianjin //设置完LOGRETAIN后应备份数据库 /home/db2inst1/sqllib/bin //进入此目录下 db2empfa tianjin; //Multi-page file allocation enabled db2 update db cfg for tianjin using BUFFPAGE 25000 // 或更多 db2 update db cfg for tianjin using LOCKLIST 1000; db2 update db cfg for tianjin using LOCKTIMEOUT 15 db2 update db cfg for tianjin using SORTHEAP 1000 //或更多 db2 update db cfg for tianjin using LOGFILSIZ 10000 db2 alter bufferpool ibmdefaultbp size -1 db2 update dbm cfg using SHEAPTHRES 25000 //Max to half of the total mem. 七、在root窗口下启动DB2控制台 1、 以db2inst1登录:su – db2inst1(-表示登录同时读取db2inst1的环境变量) 2、 export DISPLAY=127.0.0.1:0.0 3、 xhost + 4、 db2cc 八、数据导出 1、 EXPORT TO 'c:\UR_ENTERPRISE.del' OF DEL SELECT * FROM ZGC3.UR_ENTERPRISE 2、 EXPORT TO 'c:\UR_ENTERPRISE.ixf' OF ixf SELECT * FROM ZGC3.UR_ENTERPRISE 九、查看node 1、list node directory 2、删除node:? uncatalog 十、建序列 CREATE SEQUENCE "ZGC3 "."SP_SEQ_MO_wbk" AS INTEGER MINVALUE 0 MAXVALUE 2147483647 START WITH 21 INCREMENT BY 1 CACHE 20 NO CYCLE NO ORDER; 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/liu_xing_hui/archive/2008/11/20/3340522.aspx 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!
Db2命令大全 2007-05-12 10:43 连接数据库: connect to [数据库名] user [操作用户名] using [密码] 创建缓冲池(8K): create bufferpool ibmdefault8k IMMEDIATE SIZE 5000 PAGESIZE 8 K ; 创建缓冲池(16K)(OA_DIVERTASKRECORD): create bufferpool ibmdefault16k IMMEDIATE SIZE 5000 PAGESIZE 16 K ; 创建缓冲池(32K)(OA_TASK): create bufferpool ibmdefault32k IMMEDIATE SIZE 5000 PAGESIZE 32 K ; 创建表空间: CREATE TABLESPACE exoatbs IN DATABASE PARTITION GROUP IBMDEFAULTGROUP PAGESIZE 8K MANAGED BY SYSTEM USING ('/home/exoa2/exoacontainer') EXTENTSIZE 32 PREFETCHSIZE 16 BUFFERPOOL IBMDEFAULT8K OVERHEAD 24.10 TRANSFERRATE 0.90 DROPPED TABLE RECOVERY OFF; CREATE TABLESPACE exoatbs16k IN DATABASE PARTITION GROUP IBMDEFAULTGROUP PAGESIZE 16K MANAGED BY SYSTEM USING ('/home/exoa2/exoacontainer16k' ) EXTENTSIZE 32 PREFETCHSIZE 16 BUFFERPOOL IBMDEFAULT16K OVERHEAD 24.1 TRANSFERRATE 0.90 DROPPED TABLE RECOVERY OFF; CREATE TABLESPACE exoatbs32k IN DATABASE PARTITION GROUP IBMDEFAULTGROUP PAGESIZE 32K MANAGED BY SYSTEM USING ('/home/exoa2/exoacontainer32k' ) EXTENTSIZE 32 PREFETCHSIZE 16 BUFFERPOOL IBMDEFAULT32K OVERHEAD 24.1 TRANSFERRATE 0.90 DROPPED TABLE RECOVERY OFF; GRANT USE OF TABLESPACE exoatbs TO PUBLIC; GRANT USE OF TABLESPACE exoatbs16k TO PUBLIC; GRANT USE OF TABLESPACE exoatbs32k TO PUBLIC; 创建系统表空间: CREATE TEMPORARY TABLESPACE exoasystmp IN DATABASE PARTITION GROUP IBMTEMPGROUP PAGESIZE 8K MANAGED BY SYSTEM USING ('/home/exoa2/exoasystmp' ) EXTENTSIZE 32 PREFETCHSIZE 16 BUFFERPOOL IBMDEFAULT8K OVERHEAD 24.10 TRANSFERRATE 0.90 DROPPED TABLE RECOVERY OFF; CREATE TEMPORARY TABLESPACE exoasystmp16k IN DATABASE PARTITION GROUP IBMTEMPGROUP PAGESIZE 16K MANAGED BY SYSTEM USING ('/home/exoa2/exoasystmp16k' ) EXTENTSIZE 32 PREFETCHSIZE 16 BUFFERPOOL IBMDEFAULT16K OVERHEAD 24.10 TRANSFERRATE 0.90 DROPPED TABLE RECOVERY OFF; CREATE TEMPORARY TABLESPACE exoasystmp32k IN DATABASE PARTITION GROUP IBMTEMPGROUP PAGESIZE 32K MANAGED BY SYSTEM USING ('/home/exoa2/exoasystmp32k') EXTENTSIZE 32 PREFETCHSIZE 16 BUFFERPOOL IBMDEFAULT32K OVERHEAD 24.10 TRANSFERRATE 0.90 DROPPED TABLE RECOVERY OFF; 1. 启动实例(db2inst1): db2start 2. 停止实例(db2inst1): db2stop 3. 列出所有实例(db2inst1) db2ilist 5.列出当前实例: db2 get instance 4. 察看示例配置文件: db2 get dbm cfg|more 5. 更新数据库管理器参数信息: db2 update dbm cfg using para_name para_value 6. 创建数据库: db2 create db test 7. 察看数据库配置参数信息 db2 get db cfg for test|more 8. 更新数据库参数配置信息 db2 update db cfg for test using para_name para_value 10.删除数据库: db2 drop db test 11.连接数据库 db2 connect to test 12.列出所有表空间的详细信息。 db2 list tablespaces show detail 13.查询数据: db2 select * from tb1 14.删除数据: db2 delete from tb1 where id=1 15.创建索引: db2 create index idx1 on tb1(id); 16.创建视图: db2 create view view1 as select id from tb1 17.查询视图: db2 select * from view1 18.节点编目 db2 catalog tcp node node_name remote server_ip server server_port 19.察看端口号 db2 get dbm cfg|grep SVCENAME 20.测试节点的附接 db2 attach to node_name 21.察看本地节点 db2 list node direcotry 22.节点反编目 db2 uncatalog node node_name 23.数据库编目 db2 catalog db db_name as db_alias at node node_name 24.察看数据库的编目 db2 list db directory 25.连接数据库 db2 connect to db_alias user user_name using user_password 26.数据库反编目 db2 uncatalog db db_alias 27.导出数据 db2 export to myfile of ixf messages msg select * from tb1 28.导入数据 db2 import from myfile of ixf messages msg replace into tb1 29.导出数据库的所有表数据 db2move test export 30.生成数据库的定义 db2look -d db_alias -a -e -m -l -x -f -o db2look.sql 31.创建数据库 db2 create db test1 32.生成定义 db2 -tvf db2look.sql 33.导入数据库所有的数据 db2move db_alias import 34.重组检查 db2 reorgchk 35.重组表tb1 db2 reorg table tb1 36.更新统计信息 db2 runstats on table tb1 37.备份数据库test db2 backup db test 38.恢复数据库test db2 restore db test 399\.列出容器的信息 db2 list tablespace containers for tbs_id show detail 40.创建表: db2 ceate table tb1(id integer not null,name char(10)) 41.列出所有表 db2 list tables 42.插入数据: db2 insert into tb1 values(1,’sam’); db2 insert into tb2 values(2,’smitty’); . 建立数据库DB2_GCB CREATE DATABASE DB2_GCB ON G: ALIAS DB2_GCB USING CODESET GBK TERRITORY CN COLLATE USING SYSTEM DFT_EXTENT_SZ 32 2. 连接数据库 connect to sample1 user db2admin using 8301206 3. 建立别名 create alias db2admin.tables for sysstat.tables; CREATE ALIAS DB2ADMIN.VIEWS FOR SYSCAT.VIEWS create alias db2admin.columns for syscat.columns; create alias guest.columns for syscat.columns; 4. 建立表 create table zjt_tables as (select * from tables) definition only; create table zjt_views as (select * from views) definition only; 5. 插入记录 insert into zjt_tables select * from tables; insert into zjt_views select * from views; 6. 建立视图 create view V_zjt_tables as select tabschema,tabname from zjt_tables; 7. 建立触发器 CREATE TRIGGER zjt_tables_del AFTER DELETE ON zjt_tables REFERENCING OLD AS O FOR EACH ROW MODE DB2SQL Insert into zjt_tables1 values(substr(o.tabschema,1,8),substr(o.tabname,1,10)) 8. 建立唯一性索引 CREATE UNIQUE INDEX I_ztables_tabname [size=3]ON zjt_tables(tabname); 9. 查看表 select tabname from tables where tabname='ZJT_TABLES'; 10. 查看列 select SUBSTR(COLNAME,1,20) as 列名,TYPENAME as 类型,LENGTH as 长度 from columns where tabname='ZJT_TABLES'; 11. 查看表结构 db2 describe table user1.department db2 describe select * from user.tables 12. 查看表的索引 db2 describe indexes for table user1.department 13. 查看视图 select viewname from views where viewname='V_ZJT_TABLES'; 14. 查看索引 select indname from indexes where indname='I_ZTABLES_TABNAME'; 15. 查看存贮过程 SELECT SUBSTR(PROCSCHEMA,1,15),SUBSTR(PROCNAME,1,15) FROM SYSCAT.PROCEDURES; 16. 类型转换(cast) ip datatype:varchar select cast(ip as integer)+50 from log_comm_failed 17. 重新连接 connect reset 18. 中断数据库连接 disconnect db2_gcb 19. view application LIST APPLICATION; 20. kill application FORCE APPLICATION(0); db2 force applications all (强迫所有应用程序从数据库断开) 21. lock table lock table test in exclusive mode 22. 共享 lock table test in share mode 23. 显示当前用户所有表 list tables 24. 列出所有的系统表 list tables for system 25. 显示当前活动数据库 list active databases 26. 查看命令选项 list command options 27. 系统数据库目录 LIST DATABASE DIRECTORY 28. 表空间 list tablespaces 29. 表空间容器 LIST TABLESPACE CONTAINERS FOR Example: LIST TABLESPACE CONTAINERS FOR 1 30. 显示用户数据库的存取权限 GET AUTHORIZATIONS 31. 启动实例 DB2START 32. 停止实例 db2stop 33. 表或视图特权 grant select,delete,insert,update on tables to user grant all on tables to user WITH GRANT OPTION 34. 程序包特权 GRANT EXECUTE ON PACKAGE PACKAGE-name TO PUBLIC 35. 模式特权 GRANT CREATEIN ON SCHEMA SCHEMA-name TO USER 36. 数据库特权 grant connect,createtab,dbadm on database to user 37. 索引特权 grant control on index index-name to user 38. 信息帮助 (? XXXnnnnn ) 例:? SQL30081 39. SQL 帮助(说明 SQL 语句的语法) help statement 例如,help SELECT 40. SQLSTATE 帮助(说明 SQL 的状态和类别代码) ? sqlstate 或 ? class-code 41. 更改与"管理服务器"相关的口令 db2admin setid username password 42. 创建 SAMPLE 数据库 db2sampl db2sampl F:(指定安装盘) 43. 使用操作系统命令 ! dir 44. 转换数据类型 (cast) SELECT EMPNO, CAST(RESUME AS VARCHAR(370)) FROM EMP_RESUME WHERE RESUME_FORMAT = 'ascii' 45. UDF 要运行 DB2 Java 存储过程或 UDF,还需要更新服务器上的 DB2 数据库管理程序配置,以包括在该机器上安装 JDK 的路径 db2 update dbm cfg using JDK11_PATH d:sqllibjavajdk TERMINATE update dbm cfg using SPM_NAME sample 46. 检查 DB2 数据库管理程序配置 db2 get dbm cfg 47. 检索具有特权的所有授权名 SELECT DISTINCT GRANTEE, GRANTEETYPE, 'DATABASE' FROM SYSCAT.DBAUTH UNION SELECT DISTINCT GRANTEE, GRANTEETYPE, 'TABLE ' FROM SYSCAT.TABAUTH UNION SELECT DISTINCT GRANTEE, GRANTEETYPE, 'PACKAGE ' FROM SYSCAT.PACKAGEAUTH UNION SELECT DISTINCT GRANTEE, GRANTEETYPE, 'INDEX ' FROM SYSCAT.INDEXAUTH UNION SELECT DISTINCT GRANTEE, GRANTEETYPE, 'COLUMN ' FROM SYSCAT.COLAUTH UNION SELECT DISTINCT GRANTEE, GRANTEETYPE, 'SCHEMA ' FROM SYSCAT.SCHEMAAUTH UNION SELECT DISTINCT GRANTEE, GRANTEETYPE, 'SERVER ' FROM SYSCAT.PASSTHRUAUTH ORDER BY GRANTEE, GRANTEETYPE, 3 create table yhdab (id varchar(10), password varchar(10), ywlx varchar(10), kh varchar(10)); create table ywlbb (ywlbbh varchar(8), ywmc varchar(60)) 48. 修改表结构 alter table yhdab ALTER kh SET DATA TYPE varchar(13); alter table yhdab ALTER ID SET DATA TYPE varchar(13); alter table lst_bsi alter bsi_money set data type int; insert into yhdab values ('20000300001','123456','user01','20000300001'), ('20000300002','123456','user02','20000300002'); 49. 业务类型说明 insert into ywlbb values ('user01','业务申请'), ('user02','业务撤消'), ('user03','费用查询'), ('user04','费用自缴'), ('user05','费用预存'), ('user06','密码修改'), ('user07','发票打印'), ('gl01','改用户基本信息'), ('gl02','更改支付信息'), ('gl03','日统计功能'), ('gl04','冲帐功能'), ('gl05','对帐功能'), ('gl06','计费功能'), ('gl07','综合统计') 备份数据库: CONNECT TO EXOA; QUIESCE DATABASE IMMEDIATE FORCE CONNECTIONS; CONNECT RESET; BACKUP DATABASE EXOA TO "/home/exoa2/db2bak/" WITH 2 BUFFERS BUFFER 1024 PARALLELISM 1 WITHOUT PROMPTING; CONNECT TO EXOA; UNQUIESCE DATABASE; CONNECT RESET; 以下是小弟在使用db2move中的一些经验,希望对大家有所帮助。 db2 connect to YOURDB 连接数据库 db2look -d YOURDB -a -e -x -o creatab.sql 导出建库表的SQL db2move YOURDB export 用db2move将数据备份出来 vi creatab.sql 如要导入的数据库名与原数据库不同,要修改creatab.sql中CONNECT 项 如相同则不用更改 db2move NEWDB load 将数据导入新库中 在导入中可能因为种种原因发生中断,会使数据库暂挂 db2 list tablespaces show detail 如: 详细说明: 装入暂挂 总页数 = 1652 可用页数 = 1652 已用页数 = 1652 空闲页数 = 不适用 高水位标记(页) = 不适用 页大小(字节) = 4096 盘区大小(页) = 32 预读取大小(页) = 32 容器数 = 1 状态更改表空间标识 = 2 状态更改对象标识 = 59 db2 select tabname,tableid from syscat.tables where tableid=59 查看是哪张表挂起 表名知道后到db2move.lst(在db2move YOURDB export的目录中)中找到相应的.ixf文件 db2 load from tab11.ixf of ixf terminate into db2admin.xxxxxxxxx tab11.ixf对应的是xxxxxxxxx表 数据库会恢复正常,可再用db2 list tablespaces show detail查看 30.不能通过GRANT授权的权限有哪种? SYSAM SYSCTRL SYSMAINT 要更该述权限必须修改数据库管理器配置参数 31.表的类型有哪些? 永久表(基表) 临时表(说明表) 临时表(派生表) 32.如何知道一个用户有多少表? SELECT*FROMSYSIBM.SYSTABLESWHERECREATOR='USER' 33.如何知道用户下的函数? select*fromIWH.USERFUNCTION select*fromsysibm.SYSFUNCTIONS 34.如何知道用户下的VIEW数? select*fromsysibm.sysviewsWHERECREATOR='USER' 35.如何知道当前DB2的版本? select*fromsysibm.sysvERSIONS 36.如何知道用户下的TRIGGER数? select*fromsysibm.SYSTRIGGERSWHERESCHEMA='USER' 37.如何知道TABLESPACE的状况? select*fromsysibm.SYSTABLESPACES 38.如何知道SEQUENCE的状况? select*fromsysibm.SYSSEQUENCES 39.如何知道SCHEMA的状况? select*fromsysibm.SYSSCHEMATA 40.如何知道INDEX的状况? select*fromsysibm.SYSINDEXES 41.如何知道表的字段的状况? select*fromsysibm.SYSCOLUMNSWHERETBNAME='AAAA' 42.如何知道DB2的数据类型? select*fromsysibm.SYSDATATYPES 43.如何知道BUFFERPOOLS状况? select*fromsysibm.SYSBUFFERPOOLS 44.DB2表的字段的修改限制? 只能修改VARCHAR2类型的并且只能增加不能减少. 45.如何查看表的结构? DESCRIBLETABLETABLE_NAME OR DESCRIBLESELECT*FROMSCHEMA.TABLE_NAME 显示 测试区域代码 作者:从此启程/范存威 出处:http://www.cnblogs.com/fancunwei/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!