首页 文章 精选 留言 我的

精选列表

搜索[告别],共769篇文章
优秀的个人博客,低调大师

告别 2023,迎接 2024

顺便带一下开放签开源电子签章上线两周的小结,本周关键字“惊喜”。官网访问量稳定在200左右/天,github/gitee:start总计130。5个企业版意向客户,以上便是开放签上线两周的运营数据。 用“坚持”“艰辛”来总结2023年,这是我们真实的写照。团队这几年确实很辛苦,经历了创业的艰辛,失去了几载青春、大把的票子,用很低的收入维持生活,这是我们的现状。但是这些依然打不倒,也压不跨我们,对我们来说也不是什么”苦“。因为我们早已看淡了这些,我们相信我们这些年的积累、经验总有一天有用武之地。因为我们有梦想(说梦想可能会有人笑话吧),我们可以把开放签做好。其实毫不夸张的说,直至开放签上线后,才体会到十年磨一剑的感受。不瞒大家,从开始做电子签直至开放签这个项目上线,真的已经十年了,期间被家人和朋友调侃十年才玩明白这点事儿。调侃归调侃,真正想做好一个产品,我们觉得十年只是一个开始,没有这十来年足够的客户、技术、服务等方方面面积累,以及我们对开放签的坚持,估计开放签上线后也是个没血肉、立不住的产品吧。对开放签来说,十年只是一个开始,我们深知没有任何一件事情可以随便、容易的完成的,接下来的几年、几十年,我们将持续的、顽强的深耕我们的产品、服务,让电子签章更简单不是说说而已。 用“惊喜”“憧憬”来展望2024年,惊喜发生在开放签上线后,而且是持续的。惊喜的是没想到有特别多的朋友在关注、关心我们,给我们提了不少战略级别的建议,真的感谢你们。在此特别感谢@jack,感谢你给予我们的指导和建议。你给我们带来了更多的正能量,我们更加坚定了能做好开放签的信心。只要用心做好产品,我们相信24年会有更多的惊喜。24年用“憧憬”一词来展望新年,代表了我们对新的一年美好的期冀。我们期待有更多用户可以低门槛的应用电子签章,电子签章可以更加简单的得到普及。新的一年,我们更加期待开放签可以更多的参与到公益和对社会有益的事业中,让开源、开放的意义更大。 写在最后,我们还很年轻,还有很多不足,肯定在发展的道路上会犯很多错误,但是我们是开放的,有激情的,我们愿意接受各种批评和质疑,最后还是用一句我们自己坚信的话来总结我们“我们相信开源开放会为产品与用户之间带来更多信任“,这就是开放签的价值观,是我们坚定走下去的信念。 祝各位在新的一年顺遂、如意。新年好!

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

告别“一页障目”

没有宏观概念,上来通过撸代码来理解简直就是耍流氓,效率极低。为了更有效的理解内存管理的来龙去脉很有必要先了解一些基础概念,然后再去撸代码。来,先一起看看那些内存里的各种页的含义和应用场景。 用户进程的内存页分为两种: file-backed pages(文件背景页) anonymous pages(匿名页) 比如进程的代码段、映射的文件都是file-backed,而进程的堆、栈都是不与文件相对应的、就属于匿名页。 file-backed pages在内存不足的时候可以直接写回对应的硬盘文件里,称为page-out,不需要用到交换区(swap);而anonymous pages在内存不足时就只能写到硬盘上的交换区(swap)里,称为swap-out。 file-backed pages(文件背景页) 对于有文件背景的页面,程序去读文件时,可以通过read也可以通过mmap去读。当你通过任何一种方式从磁盘读文件时,内核都会给你申请一个page cache,来缓存硬盘上的内容。这样的话,读过一遍的数据,本进程或其他进程下次再读的时候就直接从page cache里去拿,就很快了,提升系统的整体性能。因此用户的read/write实际上是跟page cache的相互拷贝。 而用户的mmap则会将一段虚拟地址(3G)以下映射到page cache上,这样的话,用户就可以通过读写这段虚拟地址来修改文件内容,省去了内核和用户之间的拷贝。 所以文件对于用户程序来讲其实只是内存,page cache就是磁盘中文件的一个副本。可以通过 “echo 3 > /proc/sys/vm/drop_cache” 来清cache。清掉之后,进程第一次读文件就会变慢。 通过free命令可以看到当前page cache占用内存的大小,free命令中会打印buffers和cached。通过文件系统来访问文件(挂载文件系统,通过文件名打开文件)产生的缓存就由cached记录,而直接操作裸盘(打开/dev/sda设备去读写)产生的缓存就由buffers记录。 实际上文件系统本身再读写文件就是操作裸分区的方式,用户态也可以直接操作裸盘,像dd命令操作一个设备名也是直接访问裸分区。那么,通过文件系统读写的时候,就会既有cached又有buffers。从图中可以看到,文件名等元数据和文件系统相关,是进cached,实际的数据缓存还是在buffers。例如,read一个文件(如ext4文件系统)的时候,如果文件cache命中了,就不用走到ext4层,从vfs层就返回了。 当然,还可以在open的时候加上O_DIRECT标记,做直接IO,就连buffers都不进了,直接读写磁盘。 anonymous pages(匿名页) 没有文件背景的页面,即匿名页(anonymous page),如堆,栈,数据段等,不是以文件形式存在,因此无法和磁盘文件交换,但可以通过硬盘上划分额外的swap分区或使用swap文件进行交换。swap分区可以将不活跃的页交换到硬盘中,缓解内存紧张。swap分区可以当做针对匿名页伪造的文件背景。 页面回收(reclaim) 有文件背景的数据实际上就是page cache,但page cache不能无限增加,不能说慢慢的所有文件都缓存到内存了。肯定要有一个机制,让不常用的文件数据从page cache刷出去。内核中有一个水位控制的机制,在系统内存不够用的时候,会触发页面回收。 对于没有文件背景的页面即匿名页,比如堆、栈、数据段,如果没有swap分区,不能与磁盘交换,就要常驻内存了。但是常驻内存的话,就会吃内存,可以通过给硬盘搞一个swap分区或硬盘中创建一个swap文件让匿名页也能交换到磁盘上。可认为是为匿名页伪造的文件背景。swap分区或swap文件实际上最终是到达了增大内存的效果。当然,如果频繁交换的话,被交换出去的数据的访问就会慢一些,因为要有IO操作了。 1. 水位(watermark)控制: 内核中有三个水位: min:如果剩余内存减少到触及这个水位,可认为内存严重不足,当前进程就会被堵住,kernel会直接在这个进程的进程上下文里面做内存回收(direct reclaim)。 low:当剩余内存慢慢减少,触到这个水位时,就会触发kswapd线程的内存回收。 high: 进行内存回收时,内存慢慢增加,触到这个水位时,就停止回收。 由于每个ZONE是分别管理各自内存的,因此每个ZONE都有这三个水位 2. swapness: 回收的时候,是回收有文件背景的页还是匿名页还是都会回收呢,可通过/proc/sys/vm/swapness来控制让谁回收多一点点。swappiness越大,越倾向于回收匿名页;swappiness越小,越倾向于回收file-backed的页面。当然,它们的回收方法都是一样的LRU算法,即最近最少使用的页会被回收。 3. 如何计算水位: /proc/sys/vm/min_free_kbytes是一个用户可配置的值,默认值是min_free_kbytes = 4 * sqrt(lowmem_kbytes)。然后根据min算出来low和high水位的值:low=5/4*min,high=6/4*min。 脏页的写回 sync是用来回写脏页的,脏页不能在内存中呆的太久,因为如果突然断电没有写到硬盘的话脏数据就丢了,另一方面如果攒了很多一起写回也会明显占用CPU时间。 那么脏页时候写回呢?脏页回写的时机由时间和空间两方面共同控制: 时间: dirty_expire_centisecs: 脏页的到期时间,或理解为老化时间,单位是1/100s,内核中的flusher thread会检查驻留内存的时间超过dirty_expire_centisecs的脏页,超过的就回写。 dirty_writeback_centisecs:内核的flusher thread周期性被唤醒(wakeup_flusher_threads())的时间间隔,每次被唤醒都会去检查是否有脏页老化了。如果将这个值置为0,则flusher线程就完全不会被唤醒了。 空间: dirty_ratio: 一个写磁盘的进程所产生的脏页到达这个比例时,这个进程自己就会去回写脏页。 dirty_background_ratio: 如果脏页的数量超过这个比例时,flusher线程就会启动脏页回写。 所以: 即使只有一个脏页,那如果它超时了,也会被写回。防止脏页在内存驻留太久。dirty_expire_centisecs这个值默认是3000,即30s,可以将其设置得短一些,这样掉电后丢失的数据会更少,但磁盘写操作也更密集。 不能有太多的脏页,否则会给磁盘IO造成很大压力,例如在内存不够做内存回收时,还要先回写脏页,也会明显耗时。 需要注意的是,在达到dirty_background_ratio后,flusher线程(名为“[flush-devname]”)开始回写,但由于写磁盘速度慢,如果此时应用进程还在不停地写磁盘,flusher线程回写没那么快,那么就会导致进程的脏页达到dirty_ratio,这时这个进程就会去回写脏页而导致write被堵住。也就是说dirty_background_ratio通常是比dirty_ratio小的。 脏页都是指有文件背景的页面,匿名页不会存在脏页。从/proc/meminfo的’Dirty’一行可以看到当前系统的脏页有多少,用sync命令可以刷掉。 zRAM机制 不用swap分区,也可以用zRAM机制来缓解内存紧张:从内存里拿出一段内存空间(compressed block),作为交换空间模拟硬盘的交换分区,用来交换匿名页,并且让kernel看到的物理内存大小不包括这段内存。而这段交换空间自带透明压缩功能,即交换到这块zRAM分区时,Linux会自动将这块匿名页压缩存放。系统访问这块页面的内容时,产生page fault后从交换分区去拿,这时Linux给你透明解压再交换出来。 使用zRAM的好处,就是访存比访问硬盘或flash的速度提高很多,且不用考虑寿命问题,并且由于这段内存是压缩后存储的,因此可以存更多的数据,虽然占用了一段内存,但实际可以存更多的数据,也达到了增加内存的效果。缺点就是压缩要占用CPU时间。 Android里面普遍使用了zRAM技术,由于zRAM牺牲了CPU时间,所以交换次数还是越少越好。像Android和windows,内存越大越好,因为发生交换的几率就小。这样两个进程相互切换(如微博和微信)时就会变得流畅,因为内存足够的话,后台进程无需被换进swap分区或被OOM杀掉。当然如果你只打打电话,就没必要大内存啦。 【推荐阅读】 CPU是如何访问内存的? 物理地址和虚拟地址的分布 Linux内核内存管理算法Buddy和Slab Linux用户态进程的内存管理 Linux 内存相关问题汇总 添加极客助手微信,加入技术交流群 长按,扫码,关注公众号 本文分享自微信公众号 - 人人都是极客(rrgeek)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

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

.NET多层架构告别大学青春

相隔上次的asp.net三层架构解析,不知道大家有木有去试试,做出一两个自己的功能模块,其实都很简单,只是在摸索的路上有些难而已。加油啊!未来不等人。 接着上次的教程继续说,还在学校的时候真的见识太少,对于现在公司的要求都是可望不可即,也就见怪不怪应届生的竞争力是多么不值得一提了,所以提前给自己找个好公司,去帮公司解决问题,才是学习和进步的正确方向,学校的生活只是一个儿时的梦想实现的倒影,不要过于迷恋了。 好了,又说教了,话说自己马上大四毕业,对于一些小学弟总是想多唠叨几句,嘿嘿为了你们好,要知道,社会不会袒护和关心任何人。学的东西都是自己的,下面来看看多层架构的企业网站级别是什么样子的。 这里由于版权之类的问题,我就不截图了,直接给大家看看做的代码截图,如图, 可以看到这才是社会需要的标准,我们还是太年轻了,因为这些东西还只是最基本的,不过给大家展示的这个企业后台客户关系管理系统的权限业务逻辑算是郑州的最高水准了,不信的可以慢慢看下面的演示,耐心。。 这个后台的复杂程度是和北京Lenovo研发部门持平的,PS:因为就是从那里传出来的,架构师说的。我用了大概九天才学会了这个系统的权限管理,因为里面可以控制权限到每个页面的标签,写的非常强大。 开始的接触的时候我是从PD入手的,看到了传说中的云图,公司规定这个不能带出,所以就没法给图了,不过这个图大概有30多个表,我也记不了啊。。不过为了给大家演示效果,我凭着印象大概记下了一些关键的业务逻辑构思的方法,试了画了一个,大家可以从图中清楚的分析出来这块的权限管理方法,如下图,PS:不知道这算不算给公司泄密。。我想应该不会有公司的人看的博客吧,我人气很低的。。看不到看不到。。毕竟公司这么框架,这个九牛一毛啊。好东西就是要分享学习嘛。 这样就熟悉了权限管理的业务逻辑实现方法,接下来就是对于使用什么样的架构来写这么庞大的一个系统,选择三层显然已经满足不了,所以就开始学习了传说中的多层架构 - 工厂模式来开发这个后台。一般的小公司会选择用动软来构架好一些基本的类库和方法,这样来一步步的修改功能,不过大公司的会考虑效率问题,最后我所在的公司介绍了一种代码生成器SocanCode,用这个的好处是可以定制属于自己的代码类库,非常巧妙的使用JS编写了一套替换代码的生成方法,大家可以试SocanCode,如图,PS:右侧的模版就是修改定制自己想要的代码规范,这里就不详述了,因为网上的教程很多,下面来介绍生成后的类库使用方法。 这样生成了我们要写的业务,三层架构的模版文件,如图, 然后把各种页面的四操作写出来,对于大家来说应该不难吧,我在上期写过一个例子,这里就不多说了。写好后就添加类库到本项目中,如图, 这样订制的代码有很高的质量,但是没什么成就感。给大家一个四操作的例子吧,省得有人说我不给demo, TVisitBLL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 using System; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Web; using System.Web.Caching; namespace BLL { /// <summary> /// 业务逻辑类 TVisitBLL /// </summary> public class TVisitBLL: BLHelper { private readonly DAL.TVisitDAL dal = new DAL.TVisitDAL(); public TVisitBLL() : base ( "_TVisit_" ) { } /// <summary> /// 增加一条数据 /// </summary> public void Add(Model.TVisitModel model) { dal.Add(model); } /// <summary> /// 更新一条数据 /// </summary> public void Update(Model.TVisitModel model) { int count = dal.Update(model); if (EnableCache && count > 0) { RemoveModelCache(model.VisitId); } } /// <summary> /// 删除一条数据 /// </summary> public void Delete( int ? VisitId) { int count = dal.Delete(VisitId); if (EnableCache && count > 0) { RemoveModelCache(VisitId.ToString()); } } /// <summary> /// 是否存在该记录 /// </summary> public bool Exists( int ? VisitId) { bool bln = dal.Exists(VisitId); return bln; } /// <summary> /// 得到一个对象实体 /// </summary> public Model.TVisitModel GetModel( int ? VisitId) { Model.TVisitModel model = null ; if (!EnableCache) { model = dal.GetModel(VisitId); } else { string key = VisitId.ToString(); if (GetModelCache(key) != null ) { model = (Model.TVisitModel)GetModelCache(key); } else { model = dal.GetModel(VisitId); TryAddModelCache(key, model, null , Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Normal, null ); } } return model; } /// <summary> /// 获得泛型数据列表 /// </summary> public List<Model.TVisitModel> GetList() { List<Model.TVisitModel> lst = dal.GetList(); return lst; } /// <summary> /// 分页获取泛型数据列表 /// </summary> public PageList<Model.TVisitModel> GetPageList(PageInfo pi) { PageList<Model.TVisitModel> pl = dal.GetPageList(pi); return pl; } } } TVisitDAL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Text; namespace DAL { /// <summary> /// 数据访问类 TVisitDAL /// <summary> public partial class TVisitDAL : DALHelper { /// <summary> /// 增加一条数据 /// </summary> public int Add(Model.TVisitModel model) { IDbDataParameter[] parms4TVisit = PrepareAddParameters(model); return dbHelper.ExecuteNonQuery(CommandType.StoredProcedure, COMMAND_ADD, parms4TVisit); } /// <summary> /// 更新一条数据 /// </summary> public int Update(Model.TVisitModel model) { IDbDataParameter[] parms4TVisit = PrepareUpdateParameters(model); return dbHelper.ExecuteNonQuery(CommandType.StoredProcedure, COMMAND_UPDATE, parms4TVisit); } /// <summary> /// 删除一条数据 /// </summary> public int Delete( int ? VisitId) { IDbDataParameter[] parms4TVisit = PrepareDeleteParameters(VisitId); return dbHelper.ExecuteNonQuery(CommandType.StoredProcedure, COMMAND_DELETE, parms4TVisit); } /// <summary> /// 得到一个对象实体 /// </summary> public Model.TVisitModel GetModel( int ? VisitId) { IDbDataParameter[] parms4TVisit = PrepareGetModelParameters(VisitId); using (IDataReader dr = dbHelper.ExecuteReader(CommandType.StoredProcedure, COMMAND_GETMODEL, parms4TVisit)) { if (dr.Read()) return GetModel(dr); return null ; } } /// <summary> /// 是否存在该记录 /// </summary> public bool Exists( int ? VisitId) { IDbDataParameter[] parms4TVisit = PrepareExistParameters(VisitId); object obj = dbHelper.ExecuteScalar(CommandType.StoredProcedure, COMMAND_EXISTS, parms4TVisit); return int .Parse(obj.ToString()) > 0; } /// <summary> /// 获取数量 /// </summary> public int GetCount() { object obj = dbHelper.ExecuteScalar(CommandType.StoredProcedure, COMMAND_GETCOUNT, null ); return int .Parse(obj.ToString()); } /// <summary> /// 获取泛型数据列表 /// </summary> public List<Model.TVisitModel> GetList() { using (IDataReader dr = dbHelper.ExecuteReader(CommandType.StoredProcedure, COMMAND_GETLIST, null )) { List<Model.TVisitModel> lst = new List<Model.TVisitModel>(); ExecuteReaderAction(dr, r => lst.Add(GetModel(r))); return lst; } } /// <summary> /// 分页获取泛型数据列表 /// </summary> public PageList<Model.TVisitModel> GetPageList(PageInfo pi) { pi.RecordCount = GetCount(); pi.Compute(); PageList<Model.TVisitModel> pl = new PageList<Model.TVisitModel>(pi); using (IDataReader dr = dbHelper.ExecuteReader(CommandType.Text, COMMAND_GETLIST, null )) { pl.List = new List<Model.TVisitModel>(); ExecuteReaderAction(dr, pi.FirstIndex, pi.PageSize, r => pl.List.Add(GetModel(r))); } return pl; } /// <summary> /// 由一行数据得到一个实体 /// </summary> private Model.TVisitModel GetModel(IDataReader dr) { Model.TVisitModel model = new Model.TVisitModel(); PrepareModel(model, dr); return model; } } } TVisitDAL: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Text; using DBUtility; namespace DAL { /// <summary> /// 数据访问类 TVisit ,此类请勿动,以方便表字段更改时重新生成覆盖 /// </summary> public partial class TVisitDAL { internal const string COMMAND_ADD = "sp_TVisit_Add" ; internal const string COMMAND_UPDATE = "sp_TVisit_Update" ; internal const string COMMAND_DELETE = "sp_TVisit_Delete" ; internal const string COMMAND_EXISTS = "sp_TVisit_Exists" ; internal const string COMMAND_GETMODEL = "sp_TVisit_GetModel" ; internal const string COMMAND_GETCOUNT = "sp_TVisit_GetCount" ; internal const string COMMAND_GETLIST = "sp_TVisit_GetAllList" ; internal const string PARM_VISITID = "@in_VisitId" ; internal const string PARM_VISITTIME = "@in_VisitTime" ; internal const string PARM_VISITCUSTOMER = "@in_VisitCustomer" ; internal const string PARM_VISITWAY = "@in_VisitWay" ; internal const string PARM_VISITCONTENT = "@in_VisitContent" ; internal const string PARM_VISITOR = "@in_Visitor" ; internal const string PARM_FREMARK = "@in_FRemark" ; internal const string PARM_FSORT = "@in_FSort" ; internal const string PARM_FISDELETE = "@in_FIsDelete" ; internal const string PARM_FCREATOR = "@in_FCreator" ; internal const string PARM_FCREATEDATE = "@in_FCreateDate" ; internal const string PARM_FMODIFY = "@in_FModify" ; internal const string PARM_FMODIFYDATE = "@in_FModifyDate" ; internal const string PARM_FGUID = "@in_FGUID" ; internal const string PARM_FISSYNCHRONOUS = "@in_FIsSynchronous" ; /// <summary> /// 为新增一条数据准备参数 /// </summary> internal static IDbDataParameter[] PrepareAddParameters(Model.TVisitModel model) { IDbDataParameter[] parms = DbParameterCache.GetCachedParameterSet(dbHelper.ConnectionString, COMMAND_ADD); if (parms == null ) { parms = new IDbDataParameter[]{ dbHelper.CreateDbParameter(PARM_VISITTIME, DbType.DateTime, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_VISITCUSTOMER, DbType.Int32, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_VISITWAY, DbType.Int32, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_VISITCONTENT, DbType.AnsiString, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_VISITOR, DbType.Int32, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FREMARK, DbType.String, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FSORT, DbType.Int32, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FISDELETE, DbType.Boolean, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FCREATOR, DbType.AnsiString, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FCREATEDATE, DbType.DateTime, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FMODIFY, DbType.AnsiString, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FMODIFYDATE, DbType.DateTime, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FGUID, DbType.AnsiString, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FISSYNCHRONOUS, DbType.Boolean, ParameterDirection.Input)}; DbParameterCache.CacheParameterSet(dbHelper.ConnectionString, COMMAND_ADD, parms); } parms[0].Value = model.VisitTime; parms[1].Value = model.VisitCustomer; parms[2].Value = model.VisitWay; parms[3].Value = model.VisitContent; parms[4].Value = model.Visitor; parms[5].Value = model.FRemark; parms[6].Value = model.FSort; parms[7].Value = model.FIsDelete; parms[8].Value = model.FCreator; parms[9].Value = model.FCreateDate; parms[10].Value = model.FModify; parms[11].Value = model.FModifyDate; parms[12].Value = model.FGUID; parms[13].Value = model.FIsSynchronous; return parms; } /// <summary> /// 为更新一条数据准备参数 /// </summary> internal static IDbDataParameter[] PrepareUpdateParameters(Model.TVisitModel model) { IDbDataParameter[] parms = DbParameterCache.GetCachedParameterSet(dbHelper.ConnectionString, COMMAND_UPDATE); if (parms == null ) { parms = new IDbDataParameter[]{ dbHelper.CreateDbParameter(PARM_VISITTIME, DbType.DateTime, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_VISITCUSTOMER, DbType.Int32, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_VISITWAY, DbType.Int32, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_VISITCONTENT, DbType.AnsiString, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_VISITOR, DbType.Int32, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FREMARK, DbType.String, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FSORT, DbType.Int32, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FISDELETE, DbType.Boolean, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FCREATOR, DbType.AnsiString, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FCREATEDATE, DbType.DateTime, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FMODIFY, DbType.AnsiString, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FMODIFYDATE, DbType.DateTime, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FGUID, DbType.AnsiString, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_FISSYNCHRONOUS, DbType.Boolean, ParameterDirection.Input), dbHelper.CreateDbParameter(PARM_VISITID, DbType.Int32, ParameterDirection.Input)}; DbParameterCache.CacheParameterSet(dbHelper.ConnectionString, COMMAND_UPDATE, parms); } parms[0].Value = model.VisitTime; parms[1].Value = model.VisitCustomer; parms[2].Value = model.VisitWay; parms[3].Value = model.VisitContent; parms[4].Value = model.Visitor; parms[5].Value = model.FRemark; parms[6].Value = model.FSort; parms[7].Value = model.FIsDelete; parms[8].Value = model.FCreator; parms[9].Value = model.FCreateDate; parms[10].Value = model.FModify; parms[11].Value = model.FModifyDate; parms[12].Value = model.FGUID; parms[13].Value = model.FIsSynchronous; parms[14].Value = model.VisitId; return parms; } /// <summary> /// 为删除一条数据准备参数 /// </summary> internal static IDbDataParameter[] PrepareDeleteParameters( int ? VisitId) { IDbDataParameter[] parms = DbParameterCache.GetCachedParameterSet(dbHelper.ConnectionString, COMMAND_DELETE); if (parms == null ) { parms = new IDbDataParameter[]{ dbHelper.CreateDbParameter(PARM_VISITID, DbType.Int32, ParameterDirection.Input)}; DbParameterCache.CacheParameterSet(dbHelper.ConnectionString, COMMAND_EXISTS, parms); } parms[0].Value = VisitId; return parms; } /// <summary> /// 为查询是否存在一条数据准备参数 /// </summary> internal static IDbDataParameter[] PrepareExistParameters( int ? VisitId) { IDbDataParameter[] parms = DbParameterCache.GetCachedParameterSet(dbHelper.ConnectionString, COMMAND_EXISTS); if (parms == null ) { parms = new IDbDataParameter[]{ dbHelper.CreateDbParameter(PARM_VISITID, DbType.Int32, ParameterDirection.Input)}; DbParameterCache.CacheParameterSet(dbHelper.ConnectionString, COMMAND_EXISTS, parms); } parms[0].Value = VisitId; return parms; } /// <summary> /// 为获取一条数据准备参数 /// </summary> internal static IDbDataParameter[] PrepareGetModelParameters( int ? VisitId) { IDbDataParameter[] parms = DbParameterCache.GetCachedParameterSet(dbHelper.ConnectionString, COMMAND_GETMODEL); if (parms == null ) { parms = new IDbDataParameter[]{ dbHelper.CreateDbParameter(PARM_VISITID, DbType.Int32, ParameterDirection.Input)}; DbParameterCache.CacheParameterSet(dbHelper.ConnectionString, COMMAND_EXISTS, parms); } parms[0].Value = VisitId; return parms; } /// <summary> /// 由一行数据得到一个实体 /// </summary> internal static void PrepareModel(Model.TVisitModel model, IDataReader dr) { model.VisitId = DbValue.GetInt(dr[ "VisitId" ]); model.VisitTime = DbValue.GetDateTime(dr[ "VisitTime" ]); model.VisitCustomer = DbValue.GetInt(dr[ "VisitCustomer" ]); model.VisitWay = DbValue.GetInt(dr[ "VisitWay" ]); model.VisitContent = DbValue.GetString(dr[ "VisitContent" ]); model.Visitor = DbValue.GetInt(dr[ "Visitor" ]); model.FRemark = DbValue.GetString(dr[ "FRemark" ]); model.FSort = DbValue.GetInt(dr[ "FSort" ]); model.FIsDelete = DbValue.GetBool(dr[ "FIsDelete" ]); model.FCreator = DbValue.GetString(dr[ "FCreator" ]); model.FCreateDate = DbValue.GetDateTime(dr[ "FCreateDate" ]); model.FModify = DbValue.GetString(dr[ "FModify" ]); model.FModifyDate = DbValue.GetDateTime(dr[ "FModifyDate" ]); model.FGUID = DbValue.GetString(dr[ "FGUID" ]); model.FIsSynchronous = DbValue.GetBool(dr[ "FIsSynchronous" ]); } } } TVisitModel: 1 2 3 4 5 6 7 8 9 10 using System; using System.Collections.Generic; namespace Model { /// <summary> /// 实体类 TVisitModel /// </summary> public partial class TVisitModel { } } TVisitModel: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 using System; namespace Model { /// <summary> /// 实体类 TVisitModel, 此类请勿动,以方便表字段更改时重新生成覆盖 /// </summary> [Serializable] public partial class TVisitModel : ICloneable { public TVisitModel() { } /// <summary> /// 构造函数 TVisitModel /// </summary> /// <param name="visitid">拜访编号</param> /// <param name="visittime">拜访时间</param> /// <param name="visitcustomer">拜访对象编号</param> /// <param name="visitway">拜访方式</param> /// <param name="visitcontent">拜访内容</param> /// <param name="visitor">拜访人员</param> /// <param name="fremark">备注</param> /// <param name="fsort">显示顺序</param> /// <param name="fisdelete">是否被删除:默认0</param> /// <param name="fcreator">创建者</param> /// <param name="fcreatedate">创建日期</param> /// <param name="fmodify">修改者</param> /// <param name="fmodifydate">修改日期</param> /// <param name="fguid">唯一标识</param> /// <param name="fissynchronous">是否已同步</param> public TVisitModel( int ? visitid, DateTime? visittime, int ? visitcustomer, int ? visitway, string visitcontent, int ? visitor, string fremark, int ? fsort, bool ? fisdelete, string fcreator, DateTime? fcreatedate, string fmodify, DateTime? fmodifydate, string fguid, bool ? fissynchronous) { this .VisitId = visitid; this .VisitTime = visittime; this .VisitCustomer = visitcustomer; this .VisitWay = visitway; this .VisitContent = visitcontent; this .Visitor = visitor; this .FRemark = fremark; this .FSort = fsort; this .FIsDelete = fisdelete; this .FCreator = fcreator; this .FCreateDate = fcreatedate; this .FModify = fmodify; this .FModifyDate = fmodifydate; this .FGUID = fguid; this .FIsSynchronous = fissynchronous; } #region 实体属性 /// <summary> /// 拜访编号 /// </summary> public int ? VisitId { get ; set ; } /// <summary> /// 拜访时间 /// </summary> public DateTime? VisitTime { get ; set ; } /// <summary> /// 拜访对象编号 /// </summary> public int ? VisitCustomer { get ; set ; } /// <summary> /// 拜访方式 /// </summary> public int ? VisitWay { get ; set ; } /// <summary> /// 拜访内容 /// </summary> public string VisitContent { get ; set ; } /// <summary> /// 拜访人员 /// </summary> public int ? Visitor { get ; set ; } /// <summary> /// 备注 /// </summary> public string FRemark { get ; set ; } /// <summary> /// 显示顺序 /// </summary> public int ? FSort { get ; set ; } /// <summary> /// 是否被删除:默认0 /// </summary> public bool ? FIsDelete { get ; set ; } /// <summary> /// 创建者 /// </summary> public string FCreator { get ; set ; } /// <summary> /// 创建日期 /// </summary> public DateTime? FCreateDate { get ; set ; } /// <summary> /// 修改者 /// </summary> public string FModify { get ; set ; } /// <summary> /// 修改日期 /// </summary> public DateTime? FModifyDate { get ; set ; } /// <summary> /// 唯一标识 /// </summary> public string FGUID { get ; set ; } /// <summary> /// 是否已同步 /// </summary> public bool ? FIsSynchronous { get ; set ; } #endregion #region ICloneable 成员 public object Clone() { return this .MemberwiseClone(); } #endregion public override bool Equals( object obj) { Model.TVisitModel model = obj as Model.TVisitModel; if (model != null && model.VisitId == this .VisitId) return true ; return false ; } public override int GetHashCode() { return VisitId.GetHashCode(); } } } 总算写完了这个业务的功能模块代码,呃。。。累死了,继续说,下面就可以基于其他的模块进行系统的四操作开发了,比如,在web里面写自己的页面,提示:这里大家可以定义一个topmasterSite1.Master放置自己的title功能显示,这样就避免了很多繁杂的多余模块的产生,PS:这个是架构师教我的,嘿嘿。。。 好了到这里就给大家简单的介绍完了多层架构的基本使用方法了,工厂的核心在于接口的调用,其实也就多了接口的实例化引用对象,可以方便的调用到各种XXOODAL,所以大家如果想用好多层就要学会接口的方法使用。大部分的网站都可以用三层解决的话,是不建议用工厂的,所以掌握住基本的才是王道。 这次的东西有点多,大家慢慢理解,我会再用一期的时间把这个项目简单的介绍完,让大家知道其中的一些核心写法,具体的细节需要大家自己去写代码去修改和优化。 本文转自 吴雨声 51CTO博客,原文链接:http://blog.51cto.com/liangxiao/1281748,如需转载请自行联系原作者

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

告别“救火”式运维

一早刚上班,就听到小李抱怨:昨晚凌晨2点多,收到监控告警,磁盘空间满了;刚清理完成磁盘,又发现文件系统只读了;整整折腾了一晚上,才修复完成。运维太苦X了! 你也跟小李一样? 一会上线出问题;一会磁盘故障;一会硬件告警;一会应用性能不足。客户怨声载道,运维叫苦连天。每天忙忙碌碌,但是没有任何工作成果,还不断遭受老板、上司的批,苦X的运维,背锅侠之类的话也成了我们的口头禅。 给你句实话,这就是传统运维的真实写照! 1.传统运维的困境 在传统运维中,部门在制订IT设备和信息化系统管理目标时,关注的是一台台IT设备的故障率和一套套应用系统的可用性。在基础设施、数据库、中间件、灾备、存储等环节通常大量采用商业闭源的软硬件产品及其解决方案。设备的开放性差、标准也不统一,喜欢采用两地三中心这种典型的重量级、集中式运维管理方式。 随着IT规模越来越大、系统越来越复杂,运维保障工作由最初的硬件运维不断细分,网络工程师、系统运维工程师、DBA、安全工程师等岗位加入到运维体系中。 当业务系统发生故障时,IT主管首先召集自扫门前雪的各个运维岗位进行自检,查看各自负责的设备、应用组件、系统是否运行正常。所以,传统运维部门常常被称为“救火”队员,依靠人工巡检的工作方式,不但工作被动,而且效率低下。 2.自动化运维的到来 基于以上原因,自动化运维应运而生。自动化运维的作用就像工业革命时,织布机代替了大量的织布工人,给企业带来了利益;自动化运维就像织布机;他的核心诉求是提升效率;自动化运维是运维演进历程中的一环;从使用脚本的半自动化运维发展到集成到平台的自动化运维,未来也会发展为大数据运维。自动化运维的基本目标解决的是“能程序完成的事情尽量不要用人去干”,具体来说就是把周期性、重复性、规律性的工作都交给工具去做,最终达到提升运维效率的目的。这件事情,说起来容易,但做起来却一点不容易,完成自动化运维,首先需要实现标准化、流程化。 其中所谓流程化,就是将自动化的工作串起来,实现有序的协作,例如代码发布,通过制定流程,然后使用持续集成工具如jekins实现流程化发布。这种流程化的发布,可以实现文件的上传、分发、版本管理、回滚等各种操作。此外,对于其它运维工作,也可以实现流程化,制定运维流程规范、故障处理规范、故障告警规范等,通过多个流程规范可以实现运维工作的秩序化,合理化,从而提高运维效率。 ▼免费直播扫码报名 3.智能运维AIOps 自动化运维虽然提升了效率,解决了一部分问题,但也遇到了新的难题,比如面对繁多的报警信息,运维人员应该如何处理?故障发生时,又如何能够迅速定位问题? 这就是未来智能运维AIOps,它主要解决的是复杂运维环境下问题的快速发现甚至提前预判,以及出现问题后如何在复杂的告警、报错和日志中快速进行根因分析。甚至实现某些故障的自愈功能。 AI和Ops要解决的还是两个层面的问题,可以类比到人。AI相当于人的大脑,我们手脚和躯干是执行系统,大脑负责决策判断,手脚躯干负责完成大脑下发的动作指令。 对应到运维上面,AI 要解决的是怎么快速发现问题和判断根因,而问题一旦找到,就需要靠我们高度完善的自动化体系去执行对应的运维操作,比如容量不够就扩容、流量过大就应该触发限流和降级等等。然后是Ops,从Ops的角度,涉及的主要是运维自动化相关的技术,也就是说AIOps一定是建立在高度完善的运维自动化基础之上的,只有AI没有Ops,是谈不上AIOps。 报名《深入浅出解析大数据平台》免费直播,实操上手! ❥ 直播作者:运维专家·高俊峰❥ 直播时间:11.02(今天)晚8:00❥ 直播群:成功报名后,还可领取直播PPT+作者互动哦~ ▼免费直播扫码报名

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

告别“臃肿”,选择微服务(文末福利)

点击标题下「异步社区」可快速关注 参与文末话题讨论,每周赠送异步图书 ——异步小编 一直以来,系统的架构设计是IT领域经久不衰的话题,也是构建每一个系统最核心且重要的部分之一。它决定了系统能否满足业务、技术、组织、灵活、可扩展性等多种要求,同时肩负起了解放程序员生产力的作用。 2016年底,由于业务的不断发展,我所在公司维护的项目也越来越“臃肿”。随着无数个版本的迭代,以及开发人员的不断增加,开发效率越来越低,每次投产的人力成本和时间成本都逐渐增加,我们一直在思索如何能破局。评估了各种方案后,最终微服务进入了我们的视野。 谈到微服务,大家众说纷纭,但却很难有一个清晰的概念来描述。微服务不是“银弹”,我理解的微服务是一种文化,而我们要做的就是将微服务的理念运用到实际开发中。经过一系列的技术选型,最终Spring Cloud凭借其成熟的组件、完善的一站式解决方案,最终成为了我们落地微服务的选择。 Spring Cloud简介 Spring Cloud作为Java语言的微服务框架,它依赖于Spring Boot,有快速开发、持续交付和容易部署等特点。Spring Cloud的组件非常多,涉及微服务的方方面面,并在开源社区Spring和Netflix、Pivotal两大公司的推动下越来越完善。本文主要介绍Spring Cloud,将从以下方面来讲解。 微服务应该具备的功能。 Spring Cloud介绍。 Dubbo介绍。 Kubernetes介绍。 Spring Cloud与Dubbo比较。 Spring Cloud与Kubernetes比较。 1.1 微服务应该具备的功能 微服务,可以拆分为“微”和“服务”二字。“微”即小的意思,那到底多小才算“微”呢?可能不同的团队有不同的答案。从参与微服务的人数来讲,单个微服务从架构设计、代码开发、测试、运维的人数加起来是8~10人才算“微”。那么何为“服务”呢?按照“微服务”概念提出者Martin Fowler给出的定义:“服务”是一个独立运行的单元组件,每个单元组件运行在独立的进程中,组件与组件之间通常使用HTTP这种轻量级的通信机制进行通信。 微服务具有以下的特点。 按照业务来划分服务,单个服务代码量小,业务单一,易于维护。 每个微服务都有自己独立的基础组件,例如数据库、缓存等,且运行在独立的进程中。 微服务之间的通信是通过HTTP协议或者消息组件,且具有容错能力。 微服务有一套服务治理的解决方案,服务之间不耦合,可以随时加入和剔除服务。 单个微服务能够集群化部署,并且有负载均衡的能力。 整个微服务系统应该有一个完整的安全机制,包括用户验证、权限验证、资源保护等。 整个微服务系统有链路追踪的能力。 有一套完整的实时日志系统。 微服务具有以上这些特点,那么微服务需要具备一些什么样的功能呢?微服务的功能主要体现在以下几个方面。 服务的注册和发现。 服务的负载均衡。 服务的容错。 服务网关。 服务配置的统一管理。 链路追踪。 实时日志。 1.1.1 服务的注册与发现 微服务系统由很多个单一职责的服务单元组成,例如Netflix公司的系统是由600多个微服务构成的,而每一个微服务又有众多实例。由于该系统的服务粒度较小,服务数量众多,服务之间的相互依赖成网状,所以该系统需要服务注册中心来统一管理微服务实例,方便查看每一个微服务实例的健康状态。 服务注册是指向服务注册中心注册一个服务实例,服务提供者将自己的服务信息(如服务名、IP地址等)告知服务注册中心。服务发现是指当服务消费者需要消费另外一个服务时,服务注册中心能够告知服务消费者它所要消费服务的实例信息(如服务名、IP地址等)。通常情况下,一个服务既是服务提供者,也是服务消费者。服务消费者一般使用HTTP协议或者消息组件这种轻量级的通信机制来进行服务消费。服务的注册与发现如图1-1所示。 ​ ▲图1-1 服务的治理—服务的注册和发现 服务注册中心会提供服务的健康检查方案,检查被注册的服务是否可用。通常一个服务实例注册后,会定时向服务注册中心提供“心跳”,以表明自己还处于可用的状态。当一个服务实例停止向服务注册中心提供心跳一段时间后,服务注册中心会认为该服务实例不可用,会将该服务实例从服务注册列表中剔除。如果这个被剔除掉的服务实例过一段时间后继续向注册中心提供心跳,那么服务注册中心会将该服务实例重新加入服务注册中心的列表中。另外,微服务的服务注册组件都会提供服务的健康状况查看的UI界面,开发人员或者运维人员只需要登录相关的界面就可以知道服务的健康状态。 1.1.2 服务的负载均衡 在微服务架构中,服务之间的相互调用一般是通过HTTP通信协议来实现的。网络往往具有不可靠性,为了保证服务的高可用(High Availability),服务单元往往是集群化部署。例如将服务提供者进行集群化部署,那么服务消费者该调用哪个服务提供者的实例呢?这就涉及到了服务的负载均衡。 服务的负载均衡一般最流行的做法如图1-2所示,所有的服务都向服务注册中心注册,服务注册中心持有每个服务的应用名和IP地址等信息,同时每个服务也会获取所有服务注册列表信息。服务消费者集成负载均衡组件,该组件会向服务消费者获取服务注册列表信息,并每隔一段时间重新刷新获取该列表。当服务消费者消费服务时,负载均衡组件获取服务提供者所有实例的注册信息,并通过一定的负载均衡策略(开发者可以配置),选择一个服务提供者的实例,向该实例进行服务消费,这样就实现了负载均衡。 ​ ▲图1-2 服务的负载均衡 服务注册中心不但需要定时接收每个服务的心跳(用来检查服务是否可用),而且每个服务会定期获取服务注册列表的信息,当服务实例数量很多时,服务注册中心承担了非常大的负载。由于服务注册中心在微服务系统中起到了至关重要的作用,所以必须实现高可用。一般的做法是将服务注册中心集群化,每个服务注册中心的数据实时同步,如图1-3所示。 ▲图1-3 将服务注册中心高可用 1.1.3 服务的容错 微服务落地到实际项目中,服务的数量往往非常多,服务之间的相互依赖性也是错综复杂的,一个网络请求通常需要调用多个服务才能完成。如果一个服务不可用,例如网络延迟或故障,会影响到依赖于这个不可用的服务的其他服务。如图1-4所示,一个微服务系统有很多个服务,当服务F因某些原因导致了服务的不可用,来自于用户的网络请求需要调用服务F。由于服务F无响应,用户的请求都处于阻塞状态,在高并发的场景下,短时间内会导致服务器的线程资源消耗殆尽。另外,依赖于服务F的其他的服务,例如图中的服务E、服务G、服务J,也会等待服务F的响应,处于阻塞状态,导致这些服务的线程资源消耗殆尽,进而导致它们的不可用,以及依赖于它们的服务的不可用,最后导致整个系统处于瘫痪的状态也就是1.2.1节中提到的雪崩效应。 ▲图1-4 服务的依赖性 为了解决分布式系统的雪崩效应,分布式系统引进了熔断器机制。熔断器(Circuit Breaker)一词来源于物理学中的电路知识,它的作用是当电路中出现故障时迅速切断电路,起到保护电路的作用,熔断器机制如图1-5所示。当一个服务的处理用户请求的失败次数在一定时间内小于设定的阀值时,熔断器处于关闭状态,服务正常;当服务处理用户请求的失败次数大于设定的阀值时,说明服务出现了故障,打开熔断器,这时所有的请求会执行快速失败,不执行业务逻辑。当处于打开状态的熔断器时,一段时间后会处于半打开状态,并执行一定数量的请求,剩余的请求会执行快速失败,若执行的请求失败了,则继续打开熔断器;若成功了,则将熔断器关闭。 ​▲图1-5 熔断器机制 这种机制有着非常重要的意义,它不仅能够防止系统的“雪崩”效应,还具有以下作用。 将资源进行隔离,如果某个服务里的某个API接口出现了故障,只会隔离该API接口。不会影响到其他API接口。被隔离的API接口会执行快速失败的逻辑,不会等待,请求不会阻塞。如果不进行这种隔离,请求会一直处于阻塞状态,直到超时。若有大量的请求同时涌入,都处于阻塞的状态,服务器的线程资源,迅速被消耗完。 服务降级的功能。当服务处于正常的状态时,大量的请求在短时间内同时涌入,超过了服务的处理能力,这时熔断器会被打开,将服务降级,以免服务器因负载过高而出现故障。 自我修复能力。当因某个微小的故障,例如网络服务商的问题,导致网络在短时间内不可用,熔断器被打开。如果不能自我监控、自我检测和自我修复,那么需要开发人员手动地去关闭熔断器,无疑会增加开发人员的工作量。 Netflix的Hystrix熔断器开源组件功能非常强大,不仅有熔断器的功能,还有熔断器的状态监测,并提供界面友好的UI,开发人员或者运维人员通过UI界面能够直观地看到熔断器的状态和各种性能指标。 1.1.4 服务网关 微服务系统通过将资源以API接口的形式暴露给外界来提供服务。在微服务系统中,API接口资源通常是由服务网关(也称API网关)统一暴露,内部服务不直接对外提供API资源的暴露。这样做的好处是将内部服务隐藏起来,外界还以为是一个服务在提供服务,在一定程度上保护了微服务系统的安全。API网关通常有请求转发的作用,另外它可能需要负责一定的安全验证,例如判断某个请求是否合法,该请求对某一个资源是否具有操作权限等。通常情况下,网关层以集群的形式存在。在服务网关层之前,有可能需要加上负载均衡层,通常为Nginx双机热备,通过一定的路由策略,将请求转发到网关层。到达网关层后,经过一系列的用户身份验证、权限判断,最终转发到具体的服务。具体的服务经过一系列的逻辑运算和数据操作,最终将结果返回给用户,此时的架构如图1-6所示。 网关层具有很重要的意义,具体体现在以下方面。 网关将所有服务的API接口资源统一聚合,对外统一暴露,外界系统调用的API接口都是网关对外暴露的API接口。外界系统不需要知道微服务架构中各服务相互调用的复杂性,微服务系统也保护了其内部微服务单元的API接口,防止被外界直接调用以及服务的敏感信息对外暴露。 网关可以做一些用户身份认证、权限认证,防止非法请求操作API接口,对内部服务起到保护作用。 网关可以实现监控功能,实时日志输出,对请求进行记录。 网关可以用来做流量监控,在高流量的情况下,对服务进行降级。 API接口从内部服务分离出来,方便做测试。 当然,网关实现这些功能,需要做高可用,否则网关很可能成为架构中的瓶颈。最常用的网关组件有Zuul、Nginx等。 ​▲图1-6 服务网关架构图 1.1.5 服务配置的统一管理 在实际开发过程中,每个服务都有大量的配置文件,例如数据库的配置、日志输出级别的配置等,而往往这些配置在不同的环境中也是不一样的。随着服务数量的增加,配置文件的管理也是一件非常复杂的事。 在微服务架构中,需要有统一管理配置文件的组件,例如Spring Cloud 的Spring Cloud Config组件、阿里的Diamond、百度的Disconf、携程的Apollo等。这些配置组件所实现的功能大体相同,但是又有些差别,下面以Spring Cloud Config为例来阐述服务配置的统一管理。 如图1-7所示,大致过程如下。 首先,Config Server(配置服务)读取配置文件仓库的配置信息,其中配置文件仓库可以存放在配置服务的本地仓库,也可以放在远程的Git仓库(例如GitHub、Coding等)。 配置服务启动后,读取配置文件信息,读取完成的配置信息存放在配置服务的内存中。 当启动服务A、B时,由于服务A、B指定了向配置服务读取配置信息,服务A、B向配置服务读取配置信息。 当服务的配置信息需要修改且修改完成后,向配置服务发送Post请求进行刷新,这时服务A、B会向配置服务重写读取配置文件 ​▲图1-7 服务配置统一管理 对于集群化的服务,可以通过使用消息总线来刷新多个服务实例。如果服务数量较多,对配置中心需要考虑集群化部署,从而使配置中心高可用,做分布式集群。 1.1.6 服务链路追踪 微服务系统是一个分布式架构的系统,微服务系统按业务划分服务单元,一个微服务系统往往有很多个服务单元。由于服务单元数量很多且业务复杂,服务与服务之间的调用有可能非常复杂,一旦出现了异常和错误,就会很难去定位。所以在微服务架构中,必须实现分布式链路追踪,去跟进一个请求到底有哪些服务参与,参与的顺序又是怎样的,从而使每个请求链路清晰可见,出了问题很快就能定位。 举个例子,如图1-8所示,在微服务系统中,一个来自用户的请求先达到前端A(如前端界面),然后通过远程调用,达到系统的中间件B、C(如负载均衡、网关等),最后达到后端服务D、E。后端经过一系列的业务逻辑计算,最后将数据返回给用户。对于这样一个请求,经历了这么多服务,怎么样将它的请求过程的数据记录下来呢?这就需要用到服务链路追踪。 ​ ▲图1-8 请求通过A、B、C、D、E Google开源了链路追踪组件Dapper,并在2010年发表了论文《Dapper, a Large-Scale Distributed Systems Tracing Infrastructure》,这篇文章是业内实现链路追踪的标杆和理论基础,具有非常高的参考价值。 目前,常见的链路追踪组件有Google的Dapper、Twitter 的Zipkin,以及阿里的Eagleeye (鹰眼)等,都是非常优秀的链路追踪开源组件。 1.2 Spring Cloud 1.2.1 简介 Spring Cloud是基于Spring Boot的。Spring Boot是由Pivotal团队提供的全新Web框架,它主要的特点就是简化了开发和部署的过程,简化了Spring 复杂的配置和依赖管理,通过起步依赖和内置Servlet容器能够使开发者迅速搭起一个Web工程。所以Spring Cloud在开发部署上继承了Spring Boot的一些优点,提高其在开发和部署上的效率。 Spring Cloud的首要目标就是通过提供一系列开发组件和框架,帮助开发者迅速搭建一个分布式的微服务系统。Spring Cloud是通过包装其他技术框架来实现的,例如包装开源的Netflix OSS?组件,实现了一套通过基于注解、Java配置和基于模版开发的微服务框架。Spring Cloud框架来自于Spring Resouces社区,由Pivotal和Netflix两大公司和一些其他的开发者提供技术上的更新迭代。Spring Cloud提供了开发分布式微服务系统的一些常用组件,例如服务注册和发现、配置中心、熔断器、智能路由、微代理、控制总线、全局锁、分布式会话等。 1.2.2 常用组件 (1)服务注册和发现组件Eureka 利用Eureka组件可以很轻松地实现服务的注册和发现的功能。Eureka组件提供了服务的健康监测,以及界面友好的UI。通过Eureka组件提供的UI,Eureka组件可以让开发人员随时了解服务单元的运行情况。另外Spring Cloud也支持Consul和Zookeeper,用于注册和发现服务。 (2)熔断组件Hystrix Hystrix是一个熔断组件,它除了有一些基本的熔断器功能外,还能够实现服务降级、服务限流的功能。另外Hystrix提供了熔断器的健康监测,以及熔断器健康数据的API接口。Hystrix Dashboard组件提供了单个服务熔断器的健康状态数据的界面展示功能,Hystrix Turbine组件提供了多个服务的熔断器的健康状态数据的界面展示功能。 (3)负载均衡组件Ribbon Ribbon是一个负载均衡组件,它通常和Eureka、Zuul、RestTemplate、Feign配合使用。Ribbon和Zuul配合,很容易做到负载均衡,将请求根据负载均衡策略分配到不同的服务实例中。Ribbon和RestTemplate、Feign配合,在消费服务时能够做到负载均衡。 (4)路由网关Zuul 路由网关Zuul有智能路由和过滤的功能。内部服务的API接口通过Zuul网关统一对外暴露,内部服务的API接口不直接暴露,防止了内部服务敏感信息对外暴露。在默认的情况下,Zuul和Ribbon相结合,能够做到负载均衡、智能路由。Zuul的过滤功能是通过拦截请求来实现的,可以对一些用户的角色和权限进行判断,起到安全验证的作用,同时也可以用于输出实时的请求日志。 上述的4个组件都来自于Netflix的公司,统一称为Spring Cloud Netflix。 (5)Spring Cloud Config Spring Cloud Config组件提供了配置文件统一管理的功能。Spring Cloud Config包括Server端和Client端,Server 端读取本地仓库或者远程仓库的配置文件,所有的Client向Server读取配置信息,从而达到配置文件统一管理的目的。通常情况下,Spring Cloud Config和Spring Cloud Bus相互配合刷新指定Client或所有Client的配置文件。 (6)Spring Cloud Security Spring Cloud Security是对Spring Security组件的封装,Spring Cloud Security向服务单元提供了用户验证和权限认证。一般来说,单独在微服务系统中使用Spring Cloud Security是很少见的,一般它会配合?Spring Security OAuth2?组件一起使用,通过搭建授权服务,验证Token或者JWT这种形式对整个微服务系统进行安全验证。 (7)Spring Cloud Sleuth Spring Cloud Sleuth是一个分布式链路追踪组件,它封装了Dapper、Zipkin和Kibana等组件,通过它可以知道服务之间的相互依赖关系,并实时观察链路的调用情况。 (8)Spring Cloud Stream Spring Cloud Stream是Spring Cloud框架的数据流操作包,可以封装RabbitMq、ActiveMq、Kafka、Redis等消息组件,利用Spring Cloud Stream可以实现消息的接收和发送。 上述列举了一些常用的Spring Cloud组件。一个简单的由Spring Cloud构建的微服务系统,通常由服务注册中心Eureka、网关Zuul、配置中心Config和授权服务Auth构成,架构如图1-9所示。 字 ​▲图1-9 一个简单的由Spring Cloud构建的微服务系统 1.2.3 项目一览表 Spring Cloud Config:服务配置中心,将所有的服务的配置文件放到本地仓库或者远程仓库,配置中心负责读取仓库的配置文件,其他服务向配置中心读取配置。Spring Cloud Config使得服务的配置统一管理,并可以在不人为重启服务的情况下进行配置文件的刷新。 Spring Cloud Netflix:它是通过包装了Netflix公司的微服务组件实现的,也是Spring Cloud核心的核心组件,包括Eureka、Hystrix、Zuul、Archaius等。 Eureka:服务注册和发现组件。 Hystrix:熔断器组件。Hystrix通过控制服务的API接口的熔断来转移故障,防止微服务系统发生雪崩效应。另外,Hystrix能够起到服务限流和服务降级的作用。使用Hystrix Dashboard组件监控单个服务的熔断器的状态,使用Turbine组件可以聚合多个服务的熔断器的状态。 Zuul:智能路由网关组件。Netflix Zuul能够起到智能路由和请求过滤的作用,是服务接口统一暴露的关键模块,也是安全验证、权限控制的一道门。 Feign:声明式远程调度组件。 Ribbon:负载均衡组件。 Archaius:配置管理API的组件,一个基于Java的配置管理库,主要用于多配置的动态获取。 Spring Cloud Bus:消息总线组件,常和Spring Cloud Config配合使用,用于动态刷新服务的配置。 Spring Cloud Sleuth:服务链路追踪组件,封装了Dapper、Zipkin、Kibina等组件,可以实时监控服务的链路调用情况。 Spring Cloud Data Flow:大数据操作组件,Spring Cloud Data Flow是Spring XD的替代品,也是一个混合计算的模型,可以通过命令行的方式操作数据流。 Spring Cloud Security:安全模块组件,是对Spring Security的封装,通常配合OAuth2使用来保护微服务系统的安全。 Spring Cloud Consul:该组件是Spring Cloud对Consul的封装,和Eureka类似,它是另一个服务注册和发现组件。 Spring Cloud Zookeeper:该组件是Spring Cloud对Zookeeper的封装,和Eureka、Consul类似,用于服务的注册和发现。 Spring Cloud Stream:数据流操作组件,可以封装Redis、RabbitMQ、Kafka等组件,实现发送和接收消息等。 Spring Cloud CLI:该组件是Spring Cloud对Spring Boot CLI的封装,可以让用户以命令行方式快速运行和搭建容器。 Spring Cloud Task:该组件基于Spring Task,提供了任务调度和任务管理的功能。 Spring Cloud Connectors:用于Paas云平台连接到后端。 1.3 Dubbo简介 Dubbo是阿里巴巴开源的一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务冶理方案。Dubbo广泛用于阿里巴巴的各大站点,有很多互联网公司也在使用这个框架,它包含如下核心内容。 RPC远程调用:封装了长连接NIO框架,如Netty、Mina等,采用的是多线程模式。 集群容错:提供了基于接口方法的远程调用的功能,并实现了负载均衡策略、失败容错等功能。 服务发现:集成了Apache的Zookeeper组件,用于服务的注册和发现。 Dubbo框架的架构图如图1-10所示。 ​ ▲图1-10 Dubbo架构图 Dubbo架构的流程如下。 (1)服务提供者向服务中心注册服务。 (2)服务消费者订阅服务。 (3)服务消费者发现服务。 (4)服务消费者远程调度服务提供者进行服务消费,在调度过程中,使用了负载均衡策略、失败容错的功能。 (5)服务消费者和提供者,在内存中记录服务的调用次数和调用时间,并定时每分钟发送一次统计数据到监控中心。 Dubbo是一个非常优秀的服务治理框架,在国内互联网公司应用广泛。它具有以下特性。 连通性:注册中心负责服务的注册;监控中心负责收集调用次数、调用时间;注册中心、服务提供者、服务消费者为长连接。 健壮性:监控中心宕机不影响其他服务的使用;注册中心集群,任意一个实例宕机自动切换到另一个注册中心实例;服务实例集群,任意一个实例宕机,自动切换到另外一个可用的实例。 伸缩性:可以动态增减注册中心和服务的实例数量。 升级性:服务集群升级,不会对现有架构造成压力。 1.4 Spring Cloud与Dubbo比较 首先从微服务关注点来比较Spring Cloud和Dubbo两大服务框架,如表1-1所示。 表1-1 从微服务关注点比较Spring Cloud和Dubbo ​Spring Cloud拥有很多的项目模块,包含了微服务系统的方方面面。Dubbo是一个非常优秀的服务治理和服务调用框架,但缺少很多功能模块,例如网关、链路追踪等。在项目模块上,Spring Cloud占据着更大的优势。 Spring Cloud的更新速度非常块,Camden.SR5版本发布于2017年2月6日,Camden.SR6版本发布于2017年3月10日,Dalston版本发布于2017年4月12日,基本每个月会发一次版本的迭代。从GitHub的代码仓库来看,Spring Cloud几乎每天都有更新。阿里巴巴于2011年10月开源了Dubbo,开源后的Dubbo发展迅速,大概每2~3个月有一次版本更新。然而,从在2013年3月开始,Dubbo暂停了版本更新,并只在2014年10月发布了一个小版本,修复了一个bug,之后长期处于版本停止更新的状态。直到2017年9月,阿里巴巴中间件部门重新组建了Dubbo团队,把Dubbo列为重点开源项目,并在2017年9~11月期间,一直保持每月一次版本更新的频率。 从学习成本上考虑,Dubbo的版本趋于稳定,文档完善,可以即学即用,没有太大难度。Spring Cloud 基于Spring Boot开发,需要开发者先学会Spring Boot。另外,Spring Cloud版本迭代快,需要快速跟进学习。Spring Cloud文档大多是英文的,要求学习者有一定的英文阅读能力。此外,Spring Cloud文档很多,不容易快速找到相应的文档。 从开发风格上来讲,Dubbo 更倾向于Spring Xml的配置方式,Dubbo官方也推荐这种方式。Spring Cloud基于Spring Boot,Spring Boot采用的是基于注解和JavaBean配置方式的敏捷开发。从开发速度上讲,Spring Cloud具有更高的开发和部署速度。 最后,Spring Cloud 的通信方式大多数是基于HTTP Restful风格的,服务与服务之间完全无关、无耦合。由于采用的是HTTP Rest,因此服务无关乎语言和平台,只需要提供相应API接口,就可以相互调用。Dubbo 的通信方式基于远程调用,对接口、平台和语言有强依赖性。如果需要实现跨平台调用服务,需要写额外的中间件,这也是Dubbo存在的原因。 Dubbo和Spring Cloud拥有各自的优缺点。Dubbo更易上手,并且广泛使用于阿里巴巴的各大站点,经历了“双11”期间高并发、大流量的检验,Dubbo框架非常成熟和稳定。Spring Cloud服务框架严格遵守Martin Fowler 提出的微服务规范,社区异常活跃,它很可能成为微服务架构的标准。 1.5 Kubernetes简介 Kubernetes是一个容器集群管理系统,为容器化的应用程序提供部署运行、维护、扩展、资源调度、服务发现等功能。 Kubernetes是Google运行Borg大规模系统达15年之久的一个经验总结。Kubernetes结合了社区的最佳创意和实践,旨在帮助开发人员将容器打包、动态编排,同时帮助各大公司向微服务方向进行技术演进。 它具有以下特点。 Planet Scale(大容量):使用Kubernetes的各大公司(包括Google)每周运行了数十亿个容器,这些容器的平台采用同样的设计原则。这些平台在不增加DevOps团队成员的情况下,可以让容器数量增加,节省了人力成本,达到了复用性。 Never Outgrow(永不过时):无论容器是运行在一个小公司的测试环境中,还是运行在一个全球化企业的大型系统里,Kubernetes都能灵活地满足复杂的需求。同时,无论业务多么复杂,Kubernetes都能稳定地提供服务。 Run Anywhere(随时随地运行):Kubernetes是开源的,可以自由地利用内部、混合或公共云的基础组件进行部署,让开发者可以将更多的时间和精力投入在业务上,而不是服务部署上。 Kubernetes开源免费,是Google在过去15年时间里部署、管理微服务的经验结晶,所以目前Kubernetes在技术社区也是十分火热。下面来看它提供的功能。 Automatic Binpacking(自动包装):根据程序自身的资源需求和一些其他方面的需求自动配置容器。Kubernetes能够最大化地利用机器的工作负载,提高资源的利用率。 Self-healing(自我修复):容器失败自动重启,当节点处于“死机”的状态时,它会被替代并重新编排;当容器达到用户设定的无响应的阀值时,它会被剔除,并且不让其他容器调用它,直到它恢复服务。 Horizontal scaling(横向扩展):可以根据机器的CPU的使用率来调整容器的数量,只需开发人员在管理界面上输入几个命令即可。 Service discovery and load balancing(服务发现和负载均衡):在不需要修改现有的应用程序代码的情况下,便可使用服务的发现机制。Kubernetes为容器提供了一个虚拟网络环境,每个容器拥有独立的IP地址和DNS名称,容器之间实现了负载均衡。 Automated rollouts and rollbacks(自动部署或回滚):Kubernetes支撑滚动更新模式,能逐步替换掉当前环境的应用程序和配置,同时监视应用程序运行状况,以确保不会同时杀死所有实例。如果出现问题,Kubernetes支持回滚更改。 Secret and configuration management(配置管理):部署和更新应用程序的配置,不需要重新打镜像,并且不需要在堆栈中暴露配置。 Storage orchestration(存储编排):自动安装所选择的存储系统,无论是本地存储、公共云提供商(如GCP或AWS),还是网络存储系统(如NFS、iSCSI、Gluster、Ceph、Cinder或Flocker)。 Batch execution(批量处理):除了服务之外,Kubernetes还可以管理批量处理和CI的工作负载,如果需要,可以替换容器,如NFS、iSCSI、Gluster、Ceph、Cinder或Flocker等。 从Kubernetes提供的功能来看,Kubernetes完全可以成为构建和部署微服务的一个工具,它是从服务编排上实现的,而不是代码实现的。目前国外有很多知名的公司在使用Kubernetes,如Google、eBay、Pearson等。由于它的开源免费,Microsoft、VMWare、Red Hat、CoreOS等公司纷纷加入并贡献代码。Kubernetes技术吸引了一大批公司和技术爱好者,它已经成为容器管理的领导者。 1.6 Spring Could与Kubernetes比较 Spring Cloud是一个构建微服务的框架,而Kubernetes是通过对运行的容器的编排来实现构建微服务的。两者从构建微服务的角度和实现方式有很大的不同,但它们提供了构建微服务所需的全部功能。从提供的微服务所需的功能上看,两者不分上下,如表1-2所示。 表1-2 Spring Cloud与Kubernetes比较 ​Spring Cloud通过众多的类库来实现微服务系统所需的各个组件,同时不断集成优秀的组件,所以Spring Cloud组件是非常完善的。Spring Cloud基于Spring Boot框架,有快速开发、快速部署的优点。对于Java开发者来说,学习Spring Cloud的成本不高。 Kubernetes在编排上解决微服务的各个功能,例如服务发现、配置管理、负载均衡、容错等。Kubernetes不局限于Java平台,也不局限于语言,开发者可以自由选择开发语言进行项目开发。 与Kubernetes相比,Spring Cloud具有以下优点。 采用Java语言开发,基于Spring平台,继承了Spring Boot快速开发的优势,是Java程序员实现微服务的最佳实践。 Spring Cloud有大量的类库和资源,基本上能解决所有可能出现的问题。 与Kubernetes比较,Spring Cloud具有以下缺点。 依赖于Java语言,不支持跨语言。 Spring Cloud 需要在代码中关注微服务的功能点,例如服务发现、负载均衡等。Kubernetes则不需要关注这些。 下面介绍Kubernetes的优点和缺点,优点如下。 Kubernetes支持多种语言,并且是一个容器管理平台。Kubernetes使程序容器化,并在容器管理上提供了微服务的功能,例如配置管理、服务发现、负载均衡等。Kubernetes能够被应用于多种场合,例如程序开发、测试环境、创建环境等。 Kubernetes除了提供基本的构建微服务的功能外,还提供了环境、资源限制、管理应用程序的生命周期的功能。Kubernetes更像是一个平台,而Spring Cloud 是一个框架。 Kubernetes的缺点如下。 Kubernetes面向DevOps人员,普通的开发人员需要学习很多这方面的知识,学习成本非常高。 Kubernetes仍然是一个相对较新的平台,发展十分迅速。新特性更新得快,所以需要DevOps人员跟进,不断地学习。 Spring Cloud尝试从Java类库来实现微服务的所有功能,而Kubernetes尝试从容器编排上实现所有的微服务功能,两者的实现角度和方式不一样。个人觉得,两者最终的实现功能和效果上不分胜负,但从实现的方式上来讲,Kubernetes略胜一筹。Kubernetes面向DevOps人员,学习成本高。Spring Cloud有很多的类库,以Spring为基础,继承了Spring Boot快速开发的优点,为Java程序员开发微服务提供了很好的体验,学习成本也较低。所以二者比较,各有优势。没有最好的框架,也没有最好的工具,关键是要适合业务需求和满足业务场景。 1.7 总结 本文首先介绍了微服务应该具备的功能,然后介绍了Spring Cloud和Spring Cloud的基本组件,最后介绍了Spring Cloud与Dubbo、Kubernetes之间的比较,以及它们的优缺点。Spring Cloud作为Java语言的微服务落地框架,有很多的微服务组件。为了循序渐进地学习这些组件,以后章节将介绍构建微服务前的准备工作,这是学习Spring Cloud组件的基本前提。 ​ ​ 本文摘自​《深入理解Spring Cloud与微服务构建》 ​《深入理解Spring Cloud与微服务构建》 方志朋著 点击封面购买纸书 谈到微服务,大家众说纷纭,但却很难有一个清晰的概念来描述。微服务不是“银弹”,我理解的微服务是一种文化,而我们要做的就是将微服务的理念运用到实际开发中。经过一系列的技术选型,最终Spring Cloud凭借其成熟的组件、完善的一站式解决方案,最终成为了我们落地微服务的选择。 此时的Spring Cloud相关资料在国内还是凤毛麟角,没有完整的中文书籍和教程可以参考,只有官方的英文文档以及网上零零散散的教程可以阅读。就是在这种情况下,本书的作者方志朋在公司技术选型以及后续的微服务落地过程中,逐渐有了自己的积累和理解,同时在博客中连载了“史上最简单的Spring Cloud教程”。此教程一出,就受到广大程序员的欢迎,因此最终整理为此书。 纵览全书,文字清晰明了,通过理论结合实践的方式介绍了Spring Cloud的每一个组件的实践,并解读了部分源代码。图文并茂,语言朴实,不愧为“简单”之名。本书融合了作者实施微服务的一线经 小福利 关注【异步社区】服务号,转发本文至朋友圈或 50 人以上微信群,截图发送至异步社区微信公众号号后台,并在文章底下留言你的开发经验,或者试读本书感受,我们将选出3名读者赠送《深入理解Spring Cloud与微服务构建》1本,赶快积极参与吧! 活动截止时间:2018 年3月29日 ​在“异步社区”后台回复“关注”,即可免费获得2000门在线视频课程;推荐朋友关注根据提示获取赠书链接,免费得异步图书一本。赶紧来参加哦! 扫一扫上方二维码,回复“关注”参与活动! 阅读原文,购买《深入理解Spring Cloud与微服务构建》 阅读原文 ​

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

告别内存OOM,解决MySQL内存增长问题

本文分享自华为云社区《【华为云MySQL技术专栏】MySQL内存增长问题分析案例》,作者:GaussDB 数据库。 前言 在现网环境中,偶尔会遇到客户实例内存OOM(Out Of Memory,即内存耗尽或溢出)的情况。MySQL数据库作为一款面向高并发应用场景的系统软件,因其应用场景复杂且函数调用链极长,导致分析内存异常问题变得非常困难。 MySQL提供了Performance Schema功能,用于跟踪其性能指标,包括内存占用情况。但该工具是有一定的局限性,只能监控通过MySQL提供的内存分配接口分配的内存,不能监控直接使用malloc或者new函数分配的内存。这种情况我们就可以借助jeprof工具(jeprof是jemalloc配套的内存分析工具)来定位,以下是借助jeprof定位MySQL内存问题的分析过程。 问题描述 在生产环境中,某客户的实例频繁出现OOM,通过排查客户业务,发现是大事务导致的内存异常增长,该实例的MySQL版本为8.0.22(MySQL 8.0.25以后的版本解决了内存异常增长问题),我们通过如下方式在本地进行复现: 1. 采用sysbench往一张表中压入100000000条数据,如下: sysbench --mysql-host=xxx --mysql-port=xxx --mysql-user=xxx --mysql-password=xxx \/usr/share/sysbench/oltp_read_write.lua --tables=1 \--table_size=100000000 prepare 2. 随后开启一个大事务,比如更新某一列数据,如下: begin; update sbtest1 set k=k+1; 该语句执行前,通过top命令查看到的内存使用情况如下: 语句执行过程中,内存持续增长,执行完毕时,其内存占用如下: 执行过程中,物理内存增长了近2.6g,显然占用异常,若并发量较大的情况下,势必会OOM。 定位过程 1. 查看performance_schema相关的内存监控数据 遇到MySQL的内存问题,首先想到的是打开performance_schema开启MySQL自带的内存监控,这些监控在相当一部分场景下的确发挥了重要作用。 例如,可以使用如下的命令查看内存情况: select event_name, current_alloc from sys.memory_global_by_current_bytes limit 20; 但performance_schema监控数据也有一些局限性。比如当前的这个场景,MySQL自带的内存监控并没有采样到任何的内存变化。这是因为自带的内存监控只能监控到通过MySQL内存分配函数分配的内存,比如以mem_block_info_t为单位的MySQL内存分配函数等。所以,直接通过malloc或者new分配的函数,自带内存监控是无法监控到的。 2. 通过jeprof等工具采样内存 jeprof可以捕获所有的malloc和free函数的调用,不过由于glibc默认的分配器是ptmalloc,因此,需要一些配置将默认的ptmalloc替换成jemalloc。 下载jemalloc源码,启用--enable-prof,编译出对应的libjemalloc.so.2,jeprof工具即可,具体如下: 步骤一:下载jemalloc源码,进入解压后的目录,执行如下命令编译出对应的libjemalloc.so.2,jeprof工具; cd {jemalloc解压后的目录} ./autogen.sh --enable-prof ./configure --enable-prof 步骤二:配置jeprof内存采样,配置相关的选项参数, 具体的参数配置参考;http://jemalloc.net/jemalloc.3.html。 export MALLOC_CONF="background_thread:true,narenas:4,dirty_decay_ms:5000,prof:true,prof_prefix:/opt/workdir/logs/log,lg_prof_interval:30,lg_prof_sample:19" 步骤三:将ptmalloc替换为jemalloc,若通过ldd命令能看到如下结果则证明配置成功。 export LD_PRELOAD='/jemalloc/libjemalloc.so.2' // 步骤一编译出来的libjemalloc.so.2路径 步骤四:启动进程,随后就可以在配置的prof_prefix路径下查看到生成的内存采样数据。 结果分析 关于jeprof结果解析的命令,这里不再赘述,具体可以通过jeprof -h查看。 就本文的问题而言,实际上只需要关心从语句执行开始到语句执行结束内存的变化部分就可。那么,就可以通过如下命令对比第一次生成的profing数据以及最后一次的profling数据: /jemalloc/jeprof --pdf ./mysqld --base=log.start.heap log.end.heap > ../xxx.pdf 这里取部分结果的截图做分析: 采样出来的数据是非常直观的,3072M的内存全部是在Rpl_transaction_write_set_ctx add_write_set函数中分配的,通过查看Rpl_transaction_write_set_ctx add_write_set函数的实现如下: void Rpl_transaction_write_set_ctx::add_write_set(uint64 hash) { DBUG_TRACE; DBUG_EXECUTE_IF("add_write_set_crash_no_memory", throw std::bad_alloc();); write_set.push_back(hash); } 参考《STL源码剖析》一书, vector的底层数据结构其实就是一段连续的线性空间,以start指针和fininsh指针分别指向已申请的连续空间中目前已被使用的范围,以end_of_storage指针指向连续空间的尾端,当原空间使用完,也即fininsh==end_of_storage时,vector会执行的动态扩容,也就是_M_realloc_insert过程, 其步骤如下: 1) 以原空间大小的2倍申请一块新的空间; 2) 将原空间的内容拷贝到新空间; 3) 释放原空间。 而write_set恰是一个vector, 因此可以确定write_set占用了3072M内存从而导致内存的异常增长。这其实也是vector错误使用的一个典型案例,对于这种大量的push_back场景,由于vector的2倍扩容,不仅会导致内存占用过多,扩容的过程中反复的申请新内存、释放旧内存也会导致性能问题。若仅考虑当前的问题场景,显然list是更优的选择。 注意事项 1. 编译jemalloc时,注意./autogen.sh --enable-prof 以及./configure --enable-prof 都需要加上--enable-prof选项,若仅在./autogen.sh是加上--enable-prof参数,这种情况下你需要在启动的时候以如下方式启动mysqld, 否则无法生成profiling文件: LD_PRELOAD='/jemalloc/libjemalloc.so.2' bin/mysqld & 2. 注意MALLOC_CONF的参数中lg_prof_interval参数。该参数设置过小,会严重影响mysqld的性能。当执行性能下降后,某些场景可能就不会复现。比如本文所涉及的问题场景,lg_prof_interval设置的过小,就几乎观察不到明显的内存变化。 3. 通过jeprof采样到的数据没有捕获到buffer pool的内存分配,这是因为jeprof是通过在jemalloc中设置采样点来采集数据的,只有应用程序通过malloc, free分配释放内存才会被采集到,而MySQL的buffer pool内存是直接通过mmap系统调用分配的,不经过jemalloc, 可以参考MySQL的large_page_aligned_alloc函数,所有的大内存均是通过该函数分配的。 inline void *large_page_aligned_alloc(size_t n_bytes) { int mmap_flags = MAP_PRIVATE * MAP_ANON; void *ptr = mmap(nullptr, n_bytes, PROT_READ * PROT_WRITE, mmap_flags, -1, 0); return (ptr != (void *)-1) ? ptr : nullptr; } 总结 上述客户业务中出现的问题,归根结底是代码中未对vector的内存进行限制,才有了大事务场景下内存无限增长最终导致OOM发生。华为云RDS for MySQL和GaussDB(for MySQL)完全兼容MySQL,华为云数据库在产品中对该问题进行了提前修复,后来开源MySQL在高版本中也对该问题也进行了修复。因此,MySQL内存问题可以结合MySQL提供的performance schema内存表和jeprof工具来辅助定位。 点击关注,第一时间了解华为云新鲜技术~

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

告别DNS劫持,一文读懂DoH

如果评选一个差评服务器榜单,除去育碧高居榜首外,一定也少不了 Nintendo Switch 让人头秃的联网服务。尽管任天堂已经架设了香港 CDN 服务器用于加速,但是更新安装的速度也没有什么大幅改变。一般这种时候大家都会选择更改 DNS 来提高 NS 下载速度。 DNS(域名系统)是工作生活中很常见的名词,用户只需要在浏览器中输入一个可识别的网址,系统便会在很短的时间内找到相应的 IP 地址。在解析过程中,DNS 会访问各种名称服务器,从这些名称服务器中获取存储着的与 URL 对应的数字地址。截止到现在,DNS 已经发展了几十年,虽然使用广泛,却很少引起人们对其安全性的关注。 从安全角度来看,请求传输时通常不进行任何加密,任何人都可以读取的 DNS 其实是不安全的。这意味着网络罪犯可以很容易地使用自己的服务器拦截受害者的 DNS,将用户的请求跳转到钓鱼网站上,这些网站发布恶意软件,或在正常网站上投放大量广告吸引用户,这种行为我们称之为 DNS 劫持。为了减少这类情况的发生,业界专家目前在挣扎讨论基于 HTTPS 的 DNS(DoH)的可行性选择。那么什么是通过 HTTPS 的 DNS,它可以使 Internet 更安全吗?我们一起来看看吧。 为什么需要通过 HTTPS 的 DNS? 在日常上网中,如果用户输入无法解析的网址(例如,由于输入错误),则某些 Internet 提供商(ISP)会故意使用 DNS 劫持技术来提供错误消息。一旦 ISP 拦截了此内容,就会将用户定向到自己的网站,在该网站宣传自己或第三方的产品。虽然这并不违法,也不会直接损害用户,但是该类重定向仍会让用户反感。因此,单独使用 DNS 协议并不是非常可靠的。 而 DoH (DNS over HTTPS)即使用安全的 HTTPS 协议运行 DNS ,主要目的是增强用户的安全性和隐私性。通过使用加密的 HTTPS 连接,第三方将不再影响或监视解析过程。因此,欺诈者将无法查看请求的 URL 并对其进行更改。如果使用了基于 HTTPS 的 DNS ,数据在传输过程中发生丢失时,DoH 中的传输控制协议(TCP)会做出更快的反应。 目前,DoH 尚未成为 Internet 上的全球标准,大多数连接仍依赖基本的 DNS。到目前为止,仅 Google 和 Mozilla 两家公司涉足了这一领域。Google 现正在与部分用户一起测试该功能。此外,还有用于移动设备的应用程序,这些应用程序也可以通过 DoH 进行网上冲浪。Android Pie 也提供了通过网络设置启用基于 HTTPS 的 DNS 选项。 通过 HTTPS 的 DNS 如何工作? 通常一些域名解析会直接从用户的客户端进行,相应的域名信息被保存在浏览器或路由器的缓存中。而期间传输的所有内容都需要通过 UDP 连接,因为这样可以更快速地交换信息。但是我们都知道,UDP 既不安全也不可靠。使用该协议时,数据包可能会随时丢失,因为没有任何机制可以保证传输的可靠性。 而 DoH 依赖于 HTTPS,因此也依赖于 TCP,一种在 Internet 上使用频率更高的协议。这样既可以对连接进行加密, TCP 协议也可以确保完整的数据传输。另外,使用了基于 HTTPS 的 DNS,通信始终通过 443 端口进行,并在 443 端口传输实际的网络流量(例如,访问网站)。因此,外人无法区分 DNS 请求和其他通信,这也保障了更高级别的用户隐私。 DoH 的优点和缺点 DoH 的优点是显而易见的,该技术提高了安全性并保护了用户隐私。与传统的 DNS 相比,DoH 提供了加密措施。它利用 HTTPS 这种行业通用的安全协议,将 DNS 请求发往 DNS 服务器,这样运营商或第三方在整个传输过程中,只能知道发起者和目的地,除此以外别的什么都知道,甚至都不知道我们发起了 DNS 请求。 DoH 的加密措施可防止窃听或拦截 DNS 查询,但这也会带来了一些潜在的风险。多年以来实施的一些互联网安全措施都要求 DNS 请求过程可见。例如,家长控制需要依靠运营商为一些用户阻止访问某些域名。执法部门可能希望通过 DNS 数据来跟踪罪犯,并且许多组织都会使用安全系统来保护其网络,这些安全系统也会使用 DNS 信息来阻止已知的恶意站点。引入 DoH 可能会严重影响上述这些情况。因此,目前 DoH 还处于自主配置的时期。用户需要清楚谁可以看到数据,谁可以访问数据以及在什么情况下可以访问。 DoH 与 DoT 除了基于 HTTPS 的 DNS 外,目前还有另一种用于保护域名系统的技术:基于 TLS 的 DNS(DoT)。这两个协议看起来很相似,它们也都承诺了更高的用户安全性和隐私性。但是这两项标准都是单独开发的,并且各有各的 RFC 文档。DoT 使用了安全协议 TLS,在用于 DNS 查询的用户数据报协议(UDP)的基础上添加了 TLS 加密。DoT 使用 853 端口,DoH 则使用 HTTPS 的 443 端口。 由于 DoT 具有专用端口,因此即使请求和响应本身都已加密,但具有网络可见性的任何人都可以发现来回的 DoT 流量。DoH 则相反,DNS 查询和响应在某种程度上伪装在其他 HTTPS 流量中,因为它们都是从同一端口进出的。 关于 DoT 和 DoH 究竟哪个更好?这个还有待商榷。不过从网络安全的角度来看,DoT 可以说是更好的。它使网络管理员能够监视和阻止 DNS 查询,这对于识别和阻止恶意流量非常重要。另一方面,DoH 查询隐藏在常规 HTTPS 流量中。这意味着,若不阻止所有其他的 HTTPS 流量,就很难阻止它们。 推荐阅读 最容易被盗的密码,你中了么? 聊聊 DNS 的那些小知识

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

企业研发效能月刊:告别996,走向“211”!

云效功能 项目管理 >看板: 云效全面支持看板方法的实践,包含:更好地可视化端到端价值流动,确保产品、开发、测试等职能的前后拉通;支持任务按所属模块展开为子列,确保不同模块与所属的开发任务对齐;明确定义各列的流转规则,确保在交付过程中内建质量;限制各列工作项的并行数目,促进需求的快速持续交付;凸显交付过程中的问题、瓶颈和阻碍,让团队聚焦应该关注的问题等。同时针对需求的创建、分拆以及每日站会等主要场景优化了操作体验。 验入口:项目 > 需求 > “看板”视图(如下图所示) >文档: 文档打通了在线文档编辑与工作流的协同办公,企业除了使用云效文档管理日常项目资料、沉淀团队知识外,还可以用它进行会议纪要管理。云效文档打破了传统文档的记录模式,支持在编辑会议纪要的同时,一键生成需求、任务,并定向指派到人,无需额外再在任务管理里

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

告别 ORM、贫血模型、阻抗失配 | Wow 2.8.6 发布

基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架 领域驱动|事件驱动|测试驱动|声明式设计|响应式编程|命令查询职责分离|事件溯源 🎉 更新内容 🎉 新增10 秒钟快速构建基于 Wow 框架的 DDD 项目模板 特性: 新增AggregateRouteSpecFactoryProviderAPI 特性: 增强自定义RouteHandlerFunctionFactoryAPI 依赖: 更新gradle版本v8.5 依赖: 更新插件detekt版本v1.23.4 依赖: 更新插件ksp版本v1.9.21-1.0.15 重构: 使用 CosId 的HostAddressSupplier替换InetUtils,移除Spring-Cloud-Commons依赖 案例: 新增解冻账户(UnfreezeAccount)命令 (经典 DDD 银行转账案例(JAVA)) 修复:AggregateTracingHandlerFunction路由不匹配的问题 特性: 新增10 秒钟快速构建基于 Wow 框架的 DDD 项目模板 模块 说明 api API 层,定义聚合命令(Command)、领域事件(Domain Event)以及查询视图模型(Query View Model)。充当各个模块之间通信的“发布语言”,同时提供详细的 API 文档,助力开发者理解和使用接口。 domain 领域层,包含聚合根和业务约束的实现。聚合根充当领域模型的入口点,负责协调领域对象的操作,确保业务规则的正确执行。业务约束包括领域对象的验证规则、领域事件的处理等。模块内附有详细的领域模型文档,助力团队深入了解业务逻辑。 server 宿主服务,应用程序的启动点。负责整合其他模块,并提供应用程序的入口。涉及配置依赖项、连接数据库、启动 API 服务等任务。此外,server 模块提供了容器化部署的支持,包括 Docker 构建镜像和 Kubernetes 部署文件,简化了部署过程。 code-coverage-report 测试覆盖率,用于生成详细的测试覆盖率报告,以及验证覆盖率是否符合要求。帮助开发团队了解项目测试的全面性和质量。 dependencies 依赖项管理,这个模块负责管理项目的依赖关系,确保各个模块能够正确地引用和使用所需的外部库和工具。 bom 项目的 BOM(Bill of Materials) libs.versions.toml 依赖版本配置文件,明确了项目中各个库的版本,方便团队协作和保持版本的一致性。 deploy Kubernetes 部署文件,提供了在 Kubernetes 上部署应用程序所需的配置文件,简化了部署过程。 Dockerfile server Docker 构建镜像,通过 Dockerfile 文件定义了应用程序的容器化构建步骤,方便部署和扩展。 document 项目文档,包括 UML 图和上下文映射图,为团队成员提供了对整个项目结构和业务逻辑的清晰理解。 架构图 性能测试 (Example) 测试代码:Example 测试场景:加入购物车、下单 命令发送等待模式(WaitStrategy):SENT、PROCESSED 部署 Redis MongoDB Kafka Application-Config Application-Deployment 测试报告 加入购物车 请求 详细报告 (PDF)-SENT 详细报告 (PDF)-PROCESSED WaitStrategy:SENT WaitStrategy:PROCESSED 下单 请求 详细报告 (PDF)-SENT 详细报告 (PDF)-PROCESSED WaitStrategy:SENT WaitStrategy:PROCESSED 事件源 可观测性 OpenAPI (Spring WebFlux 集成) 自动注册命令路由处理函数 (HandlerFunction) ,开发人员仅需编写领域模型,即可完成服务开发。 测试套件:80%+ 的测试覆盖率轻而易举 Given -> When -> Expect . 前置条件 理解领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》 理解命令查询职责分离(CQRS) 理解事件源架构 理解响应式编程 特性 Aggregate Modeling Single Class Inheritance Pattern Aggregation Pattern Saga Modeling StatelessSaga Test Suite 兼容性测试规范(TCK) AggregateVerifier SagaVerifier EventSourcing EventStore MongoDB (Recommend) R2dbc Database Sharding Table Sharding Redis Snapshot MongoDB R2dbc Database Sharding Table Sharding ElasticSearch Redis (Recommend) 命令等待策略(WaitStrategy) SENT: 命令发送成功后发送完成信号 PROCESSED: 命令处理完成后发送完成信号 SNAPSHOT: 快照生成完成后发送完成信号 PROJECTED: 命令产生的事件被投影后发送完成信号 CommandBus InMemoryCommandBus KafkaCommandBus(Recommend) RedisCommandBus LocalFirstCommandBus DomainEventBus InMemoryDomainEventBus KafkaDomainEventBus(Recommend) RedisDomainEventBus LocalFirstDomainEventBus StateEventBus InMemoryStateEventBus KafkaStateEventBus(Recommend) RedisStateEventBus LocalFirstStateEventBus Spring 集成 Spring Boot Auto Configuration Automatically registerCommandAggregatetoRouterFunction 可观测性 OpenTelemetry OpenAPI WowMetadataGenerator wow-compiler Example Example 单元测试套件 80%+ 的测试覆盖率轻而易举。 Given -> When -> Expect . Aggregate Unit Test (AggregateVerifier) Aggregate Test internal class OrderTest { private fun mockCreateOrder(): VerifiedStage<OrderState> { val tenantId = GlobalIdGenerator.generateAsString() val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } return aggregateVerifier<Order, OrderState>(tenantId = tenantId) .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) .expectEventCount(1) .expectEventType(OrderCreated::class.java) .expectStateAggregate { assertThat(it.aggregateId.tenantId, equalTo(tenantId)) } .expectState { assertThat(it.id, notNullValue()) assertThat(it.customerId, equalTo(customerId)) assertThat(it.address, equalTo(SHIPPING_ADDRESS)) assertThat(it.items, equalTo(orderItems)) assertThat(it.status, equalTo(OrderStatus.CREATED)) } .verify() } /** * 创建订单 */ @Test fun createOrder() { mockCreateOrder() } @Test fun createOrderGivenEmptyItems() { val customerId = GlobalIdGenerator.generateAsString() aggregateVerifier<Order, OrderState>() .inject(mockk<CreateOrderSpec>(), "createOrderSpec") .given() .`when`(CreateOrder(customerId, listOf(), SHIPPING_ADDRESS, false)) .expectErrorType(IllegalArgumentException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-库存不足 */ @Test fun createOrderWhenInventoryShortage() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId } /* * 模拟库存不足 */ .map { it.quantity - 1 }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:库存不足异常. */ .expectErrorType(InventoryShortageException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-下单价格与当前价格不一致 */ @Test fun createOrderWhenPriceInconsistency() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId } /* * 模拟下单价格、商品定价不一致 */ .map { it.price.plus(BigDecimal.valueOf(1)) }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:价格不一致异常. */ .expectErrorType(PriceInconsistencyException::class.java).verify() } } Saga Unit Test (SagaVerifier) Saga Test class CartSagaTest { @Test fun onOrderCreated() { val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) sagaVerifier<CartSaga>() .`when`( mockk<OrderCreated> { every { customerId } returns "customerId" every { items } returns listOf(orderItem) every { fromCart } returns true }, ) .expectCommandBody<RemoveCartItem> { assertThat(it.id, equalTo("customerId")) assertThat(it.productIds, hasSize(1)) assertThat(it.productIds.first(), equalTo(orderItem.productId)) } .verify() } } 设计 聚合建模 Single Class Inheritance Pattern Aggregation Pattern 加载聚合 聚合状态流 发送命令 命令与事件流 Saga - OrderProcessManager (Demo)

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

告别 ORM、贫血模型、阻抗失配 | Wow 2.9.2 发布

基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架 领域驱动|事件驱动|测试驱动|声明式设计|响应式编程|命令查询职责分离|事件溯源 更新内容 🎉 🎉 🎉 适配Spring-Boot 3.2.0 依赖: 更新org.springframework.boot:spring-boot-dependencies版本v3.2.0 依赖: 更新me.ahoo.cosid:cosid-bom版本v2.6.0 重构: 使用 CosId 的HostAddressSupplier替换InetUtils,移除Spring-Cloud-Commons依赖 案例: 新增解锁金额(UnfreezeAccount)命令 (经典 DDD 银行转账案例(JAVA)) 架构图 性能测试 (Example) 测试代码:Example 测试场景:加入购物车、下单 命令发送等待模式(WaitStrategy):SENT、PROCESSED 部署 Redis MongoDB Kafka Application-Config Application-Deployment 测试报告 加入购物车 请求 详细报告 (PDF)-SENT 详细报告 (PDF)-PROCESSED WaitStrategy:SENT WaitStrategy:PROCESSED 下单 请求 详细报告 (PDF)-SENT 详细报告 (PDF)-PROCESSED WaitStrategy:SENT WaitStrategy:PROCESSED 事件源 可观测性 OpenAPI (Spring WebFlux 集成) 自动注册命令路由处理函数 (HandlerFunction) ,开发人员仅需编写领域模型,即可完成服务开发。 测试套件:80%+ 的测试覆盖率轻而易举 Given -> When -> Expect . 前置条件 理解领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》 理解命令查询职责分离(CQRS) 理解事件源架构 理解响应式编程 特性 Aggregate Modeling Single Class Inheritance Pattern Aggregation Pattern Saga Modeling StatelessSaga Test Suite 兼容性测试规范(TCK) AggregateVerifier SagaVerifier EventSourcing EventStore MongoDB (Recommend) R2dbc Database Sharding Table Sharding Redis Snapshot MongoDB R2dbc Database Sharding Table Sharding ElasticSearch Redis (Recommend) 命令等待策略(WaitStrategy) SENT: 命令发送成功后发送完成信号 PROCESSED: 命令处理完成后发送完成信号 SNAPSHOT: 快照生成完成后发送完成信号 PROJECTED: 命令产生的事件被投影后发送完成信号 CommandBus InMemoryCommandBus KafkaCommandBus(Recommend) RedisCommandBus LocalFirstCommandBus DomainEventBus InMemoryDomainEventBus KafkaDomainEventBus(Recommend) RedisDomainEventBus LocalFirstDomainEventBus StateEventBus InMemoryStateEventBus KafkaStateEventBus(Recommend) RedisStateEventBus LocalFirstStateEventBus Spring 集成 Spring Boot Auto Configuration Automatically registerCommandAggregatetoRouterFunction 可观测性 OpenTelemetry OpenAPI WowMetadataGenerator wow-compiler Example Example 单元测试套件 80%+ 的测试覆盖率轻而易举。 Given -> When -> Expect . Aggregate Unit Test (AggregateVerifier) Aggregate Test internal class OrderTest { private fun mockCreateOrder(): VerifiedStage<OrderState> { val tenantId = GlobalIdGenerator.generateAsString() val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } return aggregateVerifier<Order, OrderState>(tenantId = tenantId) .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) .expectEventCount(1) .expectEventType(OrderCreated::class.java) .expectStateAggregate { assertThat(it.aggregateId.tenantId, equalTo(tenantId)) } .expectState { assertThat(it.id, notNullValue()) assertThat(it.customerId, equalTo(customerId)) assertThat(it.address, equalTo(SHIPPING_ADDRESS)) assertThat(it.items, equalTo(orderItems)) assertThat(it.status, equalTo(OrderStatus.CREATED)) } .verify() } /** * 创建订单 */ @Test fun createOrder() { mockCreateOrder() } @Test fun createOrderGivenEmptyItems() { val customerId = GlobalIdGenerator.generateAsString() aggregateVerifier<Order, OrderState>() .inject(mockk<CreateOrderSpec>(), "createOrderSpec") .given() .`when`(CreateOrder(customerId, listOf(), SHIPPING_ADDRESS, false)) .expectErrorType(IllegalArgumentException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-库存不足 */ @Test fun createOrderWhenInventoryShortage() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId } /* * 模拟库存不足 */ .map { it.quantity - 1 }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:库存不足异常. */ .expectErrorType(InventoryShortageException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-下单价格与当前价格不一致 */ @Test fun createOrderWhenPriceInconsistency() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId } /* * 模拟下单价格、商品定价不一致 */ .map { it.price.plus(BigDecimal.valueOf(1)) }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:价格不一致异常. */ .expectErrorType(PriceInconsistencyException::class.java).verify() } } Saga Unit Test (SagaVerifier) Saga Test class CartSagaTest { @Test fun onOrderCreated() { val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) sagaVerifier<CartSaga>() .`when`( mockk<OrderCreated> { every { customerId } returns "customerId" every { items } returns listOf(orderItem) every { fromCart } returns true }, ) .expectCommandBody<RemoveCartItem> { assertThat(it.id, equalTo("customerId")) assertThat(it.productIds, hasSize(1)) assertThat(it.productIds.first(), equalTo(orderItem.productId)) } .verify() } } 设计 聚合建模 Single Class Inheritance Pattern Aggregation Pattern 加载聚合 聚合状态流 发送命令 命令与事件流 Saga - OrderProcessManager (Demo)

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

Wow 2.6.8 发布,告别 ORM、贫血模型、阻抗失配

基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架 领域驱动|事件驱动|测试驱动|声明式设计|响应式编程|命令查询职责分离|事件溯源 更新内容 🎉 🎉 🎉 特性(RouteSpec): 命令路由(@CommandRoute)路径参数支持未绑定命令体字段 特性: 支持使用租户ID(TenantId)参数构造状态聚合 依赖: 更新org.testcontainers:testcontainers-bom版本v1.19.1 依赖: 更新io.opentelemetry:opentelemetry-bom版本v1.31.0 依赖: 更新io.mockk:mockk版本v1.13.8 架构图 事件源 可观测性 Spring WebFlux 集成 自动注册命令路由处理函数 (HandlerFunction) ,开发人员仅需编写领域模型,即可完成服务开发。 测试套件:80%+ 的测试覆盖率轻而易举 Given -> When -> Expect . 前置条件 理解领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》 理解命令查询职责分离(CQRS) 理解事件溯源架构 理解响应式编程 特性 Aggregate Modeling Single Class Inheritance Pattern Aggregation Pattern Saga Modeling StatelessSaga Test Suite 兼容性测试规范(TCK) AggregateVerifier SagaVerifier EventSourcing EventStore MongoDB (Recommend) R2dbc Database Sharding Table Sharding Redis Snapshot MongoDB R2dbc Database Sharding Table Sharding ElasticSearch Redis (Recommend) 命令等待策略(WaitStrategy) SENT: 命令发送成功后发送完成信号 PROCESSED: 命令处理完成后发送完成信号 SNAPSHOT: 快照生成完成后发送完成信号 PROJECTED: 命令产生的事件被投影后发送完成信号 CommandBus InMemoryCommandBus KafkaCommandBus(Recommend) RedisCommandBus LocalFirstCommandBus DomainEventBus InMemoryDomainEventBus KafkaDomainEventBus(Recommend) RedisDomainEventBus LocalFirstDomainEventBus StateEventBus InMemoryStateEventBus KafkaStateEventBus(Recommend) RedisStateEventBus LocalFirstStateEventBus Spring 集成 Spring Boot Auto Configuration Automatically registerCommandAggregatetoRouterFunction 可观测性 OpenTelemetry OpenAPI WowMetadataGenerator wow-compiler Example Example 单元测试套件 80%+ 的测试覆盖率轻而易举。 Given -> When -> Expect . Aggregate Unit Test (AggregateVerifier) Aggregate Test internal class OrderTest { private fun mockCreateOrder(): VerifiedStage<OrderState> { val tenantId = GlobalIdGenerator.generateAsString() val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } return aggregateVerifier<Order, OrderState>(tenantId = tenantId) .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) .expectEventCount(1) .expectEventType(OrderCreated::class.java) .expectStateAggregate { assertThat(it.aggregateId.tenantId, equalTo(tenantId)) } .expectState { assertThat(it.id, notNullValue()) assertThat(it.customerId, equalTo(customerId)) assertThat(it.address, equalTo(SHIPPING_ADDRESS)) assertThat(it.items, equalTo(orderItems)) assertThat(it.status, equalTo(OrderStatus.CREATED)) } .verify() } /** * 创建订单 */ @Test fun createOrder() { mockCreateOrder() } @Test fun createOrderGivenEmptyItems() { val customerId = GlobalIdGenerator.generateAsString() aggregateVerifier<Order, OrderState>() .inject(mockk<CreateOrderSpec>(), "createOrderSpec") .given() .`when`(CreateOrder(customerId, listOf(), SHIPPING_ADDRESS, false)) .expectErrorType(IllegalArgumentException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-库存不足 */ @Test fun createOrderWhenInventoryShortage() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId } /* * 模拟库存不足 */ .map { it.quantity - 1 }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:库存不足异常. */ .expectErrorType(InventoryShortageException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-下单价格与当前价格不一致 */ @Test fun createOrderWhenPriceInconsistency() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId } /* * 模拟下单价格、商品定价不一致 */ .map { it.price.plus(BigDecimal.valueOf(1)) }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:价格不一致异常. */ .expectErrorType(PriceInconsistencyException::class.java).verify() } } Saga Unit Test (SagaVerifier) Saga Test class CartSagaTest { @Test fun onOrderCreated() { val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) sagaVerifier<CartSaga>() .`when`( mockk<OrderCreated> { every { customerId } returns "customerId" every { items } returns listOf(orderItem) every { fromCart } returns true }, ) .expectCommandBody<RemoveCartItem> { assertThat(it.id, equalTo("customerId")) assertThat(it.productIds, hasSize(1)) assertThat(it.productIds.first(), equalTo(orderItem.productId)) } .verify() } } 设计 聚合建模 Single Class Inheritance Pattern Aggregation Pattern 加载聚合 聚合状态流 发送命令 命令与事件流 Saga - OrderProcessManager (Demo)

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

Wow 2.5.6 发布,告别 ORM、贫血模型、阻抗失配

基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架 领域驱动|事件驱动|测试驱动|声明式设计|响应式编程|命令查询职责分离|事件溯源 更新内容 🎉 🎉 🎉 特性:OpenAPI模块支持ErrorInfoSchema 特性:GivenStage支持when函数 特性: 新增上下文别名前缀到OpenAPI Schema以防止命名冲突 修复: 丢失requestBodyType定义 依赖: 更新CosId版本v2.5.2 依赖: 更新springdoc版本v2.2.0 依赖: 更新kotlin版本v1.9.10 依赖: 更新org.springframework.boot:spring-boot-dependencies版本v3.1.3 依赖: 更新org.testcontainers:testcontainers-bom版本v1.19.0 依赖: 更新io.mockk:mockk版本v1.13.7 依赖: 更新io.opentelemetry:opentelemetry-bom版本v1.9.0 依赖: 更新org.jetbrains.dokka版本v1.29.0 架构图 事件源 可观测性 Spring WebFlux 集成 自动注册命令路由处理函数 (HandlerFunction) ,开发人员仅需编写领域模型,即可完成服务开发。 测试套件:80%+ 的测试覆盖率轻而易举 Given -> When -> Expect . 前置条件 理解领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》 理解命令查询职责分离(CQRS) 理解事件溯源架构 理解响应式编程 特性 Aggregate Modeling Single Class Inheritance Pattern Aggregation Pattern Saga Modeling StatelessSaga Test Suite 兼容性测试规范(TCK) AggregateVerifier SagaVerifier EventSourcing EventStore MongoDB (Recommend) R2dbc Database Sharding Table Sharding Redis Snapshot MongoDB R2dbc Database Sharding Table Sharding ElasticSearch Redis (Recommend) 命令等待策略(WaitStrategy) SENT: 命令发送成功后发送完成信号 PROCESSED: 命令处理完成后发送完成信号 SNAPSHOT: 快照生成完成后发送完成信号 PROJECTED: 命令产生的事件被投影后发送完成信号 CommandBus InMemoryCommandBus KafkaCommandBus(Recommend) RedisCommandBus LocalFirstCommandBus DomainEventBus InMemoryDomainEventBus KafkaDomainEventBus(Recommend) RedisDomainEventBus LocalFirstDomainEventBus StateEventBus InMemoryStateEventBus KafkaStateEventBus(Recommend) RedisStateEventBus LocalFirstStateEventBus Spring 集成 Spring Boot Auto Configuration Automatically registerCommandAggregatetoRouterFunction 可观测性 OpenTelemetry OpenAPI WowMetadataGenerator wow-compiler Example Example 单元测试套件 80%+ 的测试覆盖率轻而易举。 Given -> When -> Expect . Aggregate Unit Test (AggregateVerifier) Aggregate Test internal class OrderTest { private fun mockCreateOrder(): VerifiedStage<OrderState> { val tenantId = GlobalIdGenerator.generateAsString() val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } return aggregateVerifier<Order, OrderState>(tenantId = tenantId) .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) .expectEventCount(1) .expectEventType(OrderCreated::class.java) .expectStateAggregate { assertThat(it.aggregateId.tenantId, equalTo(tenantId)) } .expectState { assertThat(it.id, notNullValue()) assertThat(it.customerId, equalTo(customerId)) assertThat(it.address, equalTo(SHIPPING_ADDRESS)) assertThat(it.items, equalTo(orderItems)) assertThat(it.status, equalTo(OrderStatus.CREATED)) } .verify() } /** * 创建订单 */ @Test fun createOrder() { mockCreateOrder() } @Test fun createOrderGivenEmptyItems() { val customerId = GlobalIdGenerator.generateAsString() aggregateVerifier<Order, OrderState>() .inject(mockk<CreateOrderSpec>(), "createOrderSpec") .given() .`when`(CreateOrder(customerId, listOf(), SHIPPING_ADDRESS, false)) .expectErrorType(IllegalArgumentException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-库存不足 */ @Test fun createOrderWhenInventoryShortage() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId } /* * 模拟库存不足 */ .map { it.quantity - 1 }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:库存不足异常. */ .expectErrorType(InventoryShortageException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-下单价格与当前价格不一致 */ @Test fun createOrderWhenPriceInconsistency() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId } /* * 模拟下单价格、商品定价不一致 */ .map { it.price.plus(BigDecimal.valueOf(1)) }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:价格不一致异常. */ .expectErrorType(PriceInconsistencyException::class.java).verify() } } Saga Unit Test (SagaVerifier) Saga Test class CartSagaTest { @Test fun onOrderCreated() { val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) sagaVerifier<CartSaga>() .`when`( mockk<OrderCreated> { every { customerId } returns "customerId" every { items } returns listOf(orderItem) every { fromCart } returns true }, ) .expectCommandBody<RemoveCartItem> { assertThat(it.id, equalTo("customerId")) assertThat(it.productIds, hasSize(1)) assertThat(it.productIds.first(), equalTo(orderItem.productId)) } .verify() } } 设计 聚合建模 Single Class Inheritance Pattern Aggregation Pattern 加载聚合 聚合状态流 发送命令 命令与事件流 Saga - OrderProcessManager (Demo)

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

Wow 2.5.0 发布,告别 ORM、贫血模型、阻抗失配

基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架 领域驱动|事件驱动|测试驱动|声明式设计|响应式编程|命令查询职责分离|事件溯源 更新内容 🎉 🎉 🎉 支持标记忽略事件溯源接口(IgnoreSourcing) 依赖: 更新CosId版本v2.4.0 依赖: 更新io.gitlab.arturbosch.detekt版本v1.23.1 特性: 增强Spring-Boot配置提示 特性: 增强单元测试套件断言失败提示 依赖: 更新com.google.guava:guava版本v32.1.2-jre 特性: 支持标记忽略事件溯源接口(IgnoreSourcing) 应用场景:执行聚合根命令时,业务校验失败需要生成失败事件以便下游订阅者处理。并且不需要溯源领域事件。 触发条件: 领域事件标记ErrorInfo,标记该事件为失败事件 领域事件标记IgnoreSourcing 领域事件版本=1 影响: 忽略该领域事件的溯源,且不会变更聚合版本。 将忽略未初始化的状态聚合发送到状态事件总线。 聚合快照处理器将无法接受到该状态事件,即不会存储到快照仓库。 架构图 事件源 可观测性 Spring WebFlux 集成 自动注册命令路由处理函数 (HandlerFunction) ,开发人员仅需编写领域模型,即可完成服务开发。 测试套件:80%+ 的测试覆盖率轻而易举 Given -> When -> Expect . 前置条件 理解领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》 理解命令查询职责分离(CQRS) 理解事件溯源架构 理解响应式编程 特性 Aggregate Modeling Single Class Inheritance Pattern Aggregation Pattern Saga Modeling StatelessSaga Test Suite 兼容性测试规范(TCK) AggregateVerifier SagaVerifier EventSourcing EventStore MongoDB (Recommend) R2dbc Database Sharding Table Sharding Redis Snapshot MongoDB R2dbc Database Sharding Table Sharding ElasticSearch Redis (Recommend) 命令等待策略(WaitStrategy) SENT: 命令发送成功后发送完成信号 PROCESSED: 命令处理完成后发送完成信号 SNAPSHOT: 快照生成完成后发送完成信号 PROJECTED: 命令产生的事件被投影后发送完成信号 CommandBus InMemoryCommandBus KafkaCommandBus(Recommend) RedisCommandBus LocalFirstCommandBus DomainEventBus InMemoryDomainEventBus KafkaDomainEventBus(Recommend) RedisDomainEventBus LocalFirstDomainEventBus StateEventBus InMemoryStateEventBus KafkaStateEventBus(Recommend) RedisStateEventBus LocalFirstStateEventBus Spring 集成 Spring Boot Auto Configuration Automatically registerCommandAggregatetoRouterFunction 可观测性 OpenTelemetry OpenAPI WowMetadataGenerator wow-compiler Example Example 单元测试套件 80%+ 的测试覆盖率轻而易举。 Given -> When -> Expect . Aggregate Unit Test (AggregateVerifier) Aggregate Test internal class OrderTest { private fun mockCreateOrder(): VerifiedStage<OrderState> { val tenantId = GlobalIdGenerator.generateAsString() val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } return aggregateVerifier<Order, OrderState>(tenantId = tenantId) .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) .expectEventCount(1) .expectEventType(OrderCreated::class.java) .expectStateAggregate { assertThat(it.aggregateId.tenantId, equalTo(tenantId)) } .expectState { assertThat(it.id, notNullValue()) assertThat(it.customerId, equalTo(customerId)) assertThat(it.address, equalTo(SHIPPING_ADDRESS)) assertThat(it.items, equalTo(orderItems)) assertThat(it.status, equalTo(OrderStatus.CREATED)) } .verify() } /** * 创建订单 */ @Test fun createOrder() { mockCreateOrder() } @Test fun createOrderGivenEmptyItems() { val customerId = GlobalIdGenerator.generateAsString() aggregateVerifier<Order, OrderState>() .inject(mockk<CreateOrderSpec>(), "createOrderSpec") .given() .`when`(CreateOrder(customerId, listOf(), SHIPPING_ADDRESS, false)) .expectErrorType(IllegalArgumentException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-库存不足 */ @Test fun createOrderWhenInventoryShortage() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId } /* * 模拟库存不足 */ .map { it.quantity - 1 }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:库存不足异常. */ .expectErrorType(InventoryShortageException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-下单价格与当前价格不一致 */ @Test fun createOrderWhenPriceInconsistency() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId } /* * 模拟下单价格、商品定价不一致 */ .map { it.price.plus(BigDecimal.valueOf(1)) }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:价格不一致异常. */ .expectErrorType(PriceInconsistencyException::class.java).verify() } } Saga Unit Test (SagaVerifier) Saga Test class CartSagaTest { @Test fun onOrderCreated() { val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) sagaVerifier<CartSaga>() .`when`( mockk<OrderCreated> { every { customerId } returns "customerId" every { items } returns listOf(orderItem) every { fromCart } returns true }, ) .expectCommandBody<RemoveCartItem> { assertThat(it.id, equalTo("customerId")) assertThat(it.productIds, hasSize(1)) assertThat(it.productIds.first(), equalTo(orderItem.productId)) } .verify() } } 设计 聚合建模 Single Class Inheritance Pattern Aggregation Pattern 加载聚合 聚合状态流 发送命令 命令与事件流 Saga - OrderProcessManager (Demo)

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

Wow 2.4.5 发布,告别贫血模型、ORM、阻抗失配

基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架 领域驱动|事件驱动|测试驱动|声明式设计|响应式编程|命令查询职责分离|事件源 更新内容 🎉 🎉 🎉 依赖: 更新CosId版本v2.2.6 修复: 限界上下文别名(NamedBoundedContext.alias)兼容性问题 重构: 限界上下文别名冲突检测跳过空别名 依赖: 更新org.springframework.boot:spring-boot-dependencies版本v3.1.2 架构图 事件源 可观测性 Spring WebFlux 集成 自动注册命令路由处理函数 (HandlerFunction) ,开发人员仅需编写领域模型,即可完成服务开发。 测试套件:80%+ 的测试覆盖率轻而易举 Given -> When -> Expect . 前置条件 理解领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》 理解命令查询职责分离(CQRS) 理解事件源架构 理解响应式编程 特性 Aggregate Modeling Single Class Inheritance Pattern Aggregation Pattern Saga Modeling StatelessSaga Test Suite 兼容性测试规范(TCK) AggregateVerifier SagaVerifier EventSourcing EventStore MongoDB (Recommend) R2dbc Database Sharding Table Sharding Redis Snapshot MongoDB R2dbc Database Sharding Table Sharding ElasticSearch Redis (Recommend) 命令等待策略(WaitStrategy) SENT: 命令发送成功后发送完成信号 PROCESSED: 命令处理完成后发送完成信号 SNAPSHOT: 快照生成完成后发送完成信号 PROJECTED: 命令产生的事件被投影后发送完成信号 CommandBus InMemoryCommandBus KafkaCommandBus(Recommend) RedisCommandBus LocalFirstCommandBus DomainEventBus InMemoryDomainEventBus KafkaDomainEventBus(Recommend) RedisDomainEventBus LocalFirstDomainEventBus StateEventBus InMemoryStateEventBus KafkaStateEventBus(Recommend) RedisStateEventBus LocalFirstStateEventBus Spring 集成 Spring Boot Auto Configuration Automatically registerCommandAggregatetoRouterFunction 可观测性 OpenTelemetry OpenAPI WowMetadataGenerator wow-compiler Example Example 单元测试套件 80%+ 的测试覆盖率轻而易举。 Given -> When -> Expect . Aggregate Unit Test (AggregateVerifier) Aggregate Test internal class OrderTest { private fun mockCreateOrder(): VerifiedStage<OrderState> { val tenantId = GlobalIdGenerator.generateAsString() val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } return aggregateVerifier<Order, OrderState>(tenantId = tenantId) .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) .expectEventCount(1) .expectEventType(OrderCreated::class.java) .expectStateAggregate { assertThat(it.aggregateId.tenantId, equalTo(tenantId)) } .expectState { assertThat(it.id, notNullValue()) assertThat(it.customerId, equalTo(customerId)) assertThat(it.address, equalTo(SHIPPING_ADDRESS)) assertThat(it.items, equalTo(orderItems)) assertThat(it.status, equalTo(OrderStatus.CREATED)) } .verify() } /** * 创建订单 */ @Test fun createOrder() { mockCreateOrder() } @Test fun createOrderGivenEmptyItems() { val customerId = GlobalIdGenerator.generateAsString() aggregateVerifier<Order, OrderState>() .inject(mockk<CreateOrderSpec>(), "createOrderSpec") .given() .`when`(CreateOrder(customerId, listOf(), SHIPPING_ADDRESS, false)) .expectErrorType(IllegalArgumentException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-库存不足 */ @Test fun createOrderWhenInventoryShortage() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId } /* * 模拟库存不足 */ .map { it.quantity - 1 }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:库存不足异常. */ .expectErrorType(InventoryShortageException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-下单价格与当前价格不一致 */ @Test fun createOrderWhenPriceInconsistency() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId } /* * 模拟下单价格、商品定价不一致 */ .map { it.price.plus(BigDecimal.valueOf(1)) }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:价格不一致异常. */ .expectErrorType(PriceInconsistencyException::class.java).verify() } } Saga Unit Test (SagaVerifier) Saga Test class CartSagaTest { @Test fun onOrderCreated() { val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) sagaVerifier<CartSaga>() .`when`( mockk<OrderCreated> { every { customerId } returns "customerId" every { items } returns listOf(orderItem) every { fromCart } returns true }, ) .expectCommandBody<RemoveCartItem> { assertThat(it.id, equalTo("customerId")) assertThat(it.productIds, hasSize(1)) assertThat(it.productIds.first(), equalTo(orderItem.productId)) } .verify() } } 设计 聚合建模 Single Class Inheritance Pattern Aggregation Pattern 加载聚合 聚合状态流 发送命令 命令与事件流 Saga - OrderProcessManager (Demo)

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

iPadOS系统发布:桌面级体验 告别大号iPhone

【大咖・来了 第7期】10月24日晚8点观看《智能导购对话机器人实践》 除了watchOS、tvOS、iOS、macOS四大系统的常规升级,苹果今天还带来了全新的iPadOS,也就是iPad平板机专用系统,特别针对iPad的大屏应用做了优化,号称可以带来桌面级的体验,拒绝继续做“大号iPhone”。 iPadOS的桌面和应用布局更有点macOS的味道,无论是自带应用还是第三方应用都全面支持多窗口分屏,比如两个Word窗口并排,支持Files应用新视图、元数据可视化、原生预览图、SD卡/U盘、支持SMB文件分享、压缩解压。 加入新的触控功能,比如选择文字、翻页、复制粘贴,尤其是支持大量的三指手势。 Safari浏览器上网时会默认显示桌面版布局而非移动版,支持独立下载管理器、丰富快捷键、自定义字体。 Apple Pencil触控笔也特别优化,延迟从20毫秒降到9毫秒,还有更小的单侧键盘模式。 不过,苹果并未公布iPadOS支持的设备和上线时间。

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

赛门铁克推生物特征识别科技 告别密码

赛门铁克信息保护的ANZ业务经理Nick Savvides表示,“密码是个太弱的烂东西。” Savvides近日接受媒体采访时表示,密码不管多么复杂都不是企业用来对付网络威胁和保护自己的理想方式,因为网络犯罪分子的手法已越来越精密。 他还特别提到,由于物联网、云计算和移动互联网的兴起,企业需要实施新的安全措施,采用诸如语音、面部,或指纹等生物识别技术以及利用关键动态元素和地理位置信息,以确保自己得到保护。 Savvides表示,赛门铁克将在未来的12个月里发布多种利用这些新元素的功能。 Savvides给出了一些预览功能,他透露,赛门铁克将推出VIP Everywhere(VIP处处太平),以加强旗下赛门铁克验证和标识保护(VIP)服务的功能。VIP Everywhere预定将于2016年下半年推出。据Savvides介绍,VIP Everywhere旨在提供终端到终端的认证,将见证“安全性和可用性首次成为少见的朋友”。VIP Everywhere会被嵌入到Norton软件里,使得计算机具备一个唯一的赛门铁克标识符,可以识别特定计算机用户。 他表示,“我们现在通过VIP认证用户。我们现在引进的这项功能可以利用行为分析进行连续的认证;可以利用来自应用程序在使用过程中的遥测数据;可以进行机器学习做出决定,确定是否需要停止用户操作,或是需要重新进行身份验证,以确保该设备没有被劫持。” 此外,赛门铁克会将重点放在“移动强化”上,赛门铁克将推出一个名为移动应用保护服务的新科技,简称MAPS。Savvides表示,MAPS产品的设计考虑的对象是移动应用程序开发者,使得他们可以专注于应用程序的功能,而不是安全。 他表示,“安全性对应用程序开发人员来说是件难事。为什么呢?因为移动应用程序开发者是些25岁的大胡子小伙子,他们不知道如何编安全应用程序;他们知道如何编移动应用程序。” Savvides表示,“他们关心的是程序的功能,他们不关心程序的安全性,所以我们所做的就是,为他们提供一个简化保护应用程序的做法,我们为他们提供一个黑盒子,我们对他们说:”把这个加到移动应用程序里,赛门铁克会将移动应用程序包起来,我们会确保用的是最好的加密技术,我们向你保证会实施钉扎(Pinning)机制,我们保证会实施编纂(Codification),我们保证你可以检测到在装置上运行的恶意软件。'” 作者:杨昀煦/编译 来源:51CTO

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

告别 GPU 焦虑,玩转极致性价比的 CPU 文生图

作者:壮怀、竹刚 AIGC 中的 Stable Diffusion 文生图模型是开源流行的跨模态生成模型,用于生成给定文本对应的图像。但由于众所周知的原因,GPU 资源出现了一卡难求的现状,如何通过云计算快速提升业务规模,降低文生图的计算成本,以及更好的保护自定义的扩展模型?针对文生图模型特性和规模化场景,本文提供了一种新的思路,通过云原生部署方式提供推理服务 API,使用 CPU 矩阵计算能力针对模型进行无侵入优化,以及机密计算的环境的无缝切换,可以有效的替代部分 GPU 推理需求,提供稳定、高效、高性价比且安全的文生图服务。 通过在 Kubernetes 集群内添加阿里云第八代企业级 CPU 实例 g8i,不修改模型本身,通过云原生化的部署和推理优化,在 CPU 节点上实现秒级响应的成本低廉的文生图服务。本文介绍如何在 ACK 集群中快速部署一个使用 CPU 加速的 Stable Diffusion 文生图示例服务,并且您还可以将这个示例服务无缝迁移到机密虚拟机节点池中,为您的推理服务提供数据安全保护。 准备环境 在 ACK 集群内创建一个使用阿里云第八代企业级实例 g8i 的节点池,确保实例规格的 CPU 大于或等于 16vCPU,可以使用 ecs.g8i.4xlarge,ecs.g8i.8xlarge 或 ecs.g8i.12xlarge 实例规格。(如果你还没有一个阿里云 Kubernetes 集群,请参见创建 Kubernetes 托管版集群 [ 1] 。) 一步生成文生图服务 使用默认参数执行以下命令,在集群内部署一个使用的 Stable Diffusion XL Turbo 模型。 helm install stable-diffusion-ipex https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/pre/charts-incubator/stable-diffusion-ipex-0.1.9.tgz 等待约 10 分钟,然后执行以下命令检查 Pod 状态,确保运行正常。 kubectl get pod |grep stable-diffusion-ipex 预期输出: stable-diffusion-ipex-577674874c-lhnlc 1/1 Running 0 11m stable-diffusion-ipex-webui-85d949d9bd-mcwln 1/1 Running 0 11m 服务部署完成后,对外提供了一个文生图 API 和 web UI。 测试文生图服务 执行以下命令,将 Stable Diffusion XL Turbo 模型服务提供的 web UI 转发到本地。 kubectl port-forward svc/stable-diffusion-ipex-webui 5001:5001 预期输出: Forwarding from 127.0.0.1:5001 -> 5001 Forwarding from [::1]:5001 -> 5001 在浏览器中打开http://127.0.0.1:5001/ ,访问 web UI 页面。 在这个 web UI 页面中,您可以点击【生成图片】按钮使用输入的提示词生成图片。 测试提示词: "A panda listening to music with headphones. highly detailed, 8k." "A dog listening to music with headphones. highly detailed, 8k." “A robot cat eating spaghetti, digital art, 8k.” “A large blob of exploding splashing rainbow paint, with an apple emerging, 8k.” “An astronaut riding a galloping horse, 8k.” “A family of raccoons living in a small cabin, tilt shift, arc shot, 8k.” “A tree walking through the forest, tilt shift, 8k.” “An octopus attacks New York, 8k.” “Motorcyclist on a racing track, highly detailed, 8k.” “Humans building a highway on Mars, cinematic, 8k.” 性能测试 使用不同 ECS g8i 实例规格在 Stable Diffusion XL Turbo 模型中生成 512x512、1024x1024 图片的耗时信息(单个 batch)。下表结果数据仅为实验参考,实际数据可能会因您的操作环境而发生变化。 实例规格 Pod Request/Limit 参数 单次平均耗时 (512x512) 单次平均耗时 (1024x1024) ecs.g8i.4xlarge(16 vCPU 64 GiB) 14/16 batch: 1step: 4 2.2s 8.8s ecs.g8i.8xlarge(32 vCPU 128 GiB) 24/32 batch: 1step: 4 1.3s 4.7s ecs.g8i.12xlarge(48 vCPU 192 GiB) 32/32 batch: 1step: 4 1.1s 3.9s 从推理速度的绝对性能来看,在多 batch 和多步的推理场景下,CPU 的推理速度仍然与 A10 的 GPU 实例有所差距。采用 ecs.g8i.8xlarge 的 CPU 机型、step 为 30、batch 为 16 时,图片生成速度为 0.14 images/s;采用 A10 的 GPU 实例、step 为 30,batch 为 16 时,图片生成速度为 0.4 images/s。但从最佳图像生成质量的推理性能来看,采用 ecs.g8i.8xlarge 的 CPU 机型、step 为 4、batch 为 16 时,图像生成速度为 1.2 images/s,仍可实现秒级出图性能。 因此,通过合理运用 CPU 加速策略和文生图模型推理的最佳实践,ECS g8i 等第八代 CPU 实例可用于替代 GPU 推理实例,提供稳定、高效、高性价比且安全机密的文生图服务。 在追求性价比、模型安全 TEE 和大规模资源供给的文生图推理场景下,建议采用 ecs.g8i.4xlarge 机型运行 stabilityai/sdxl-turbo 及相关的微调模型,以最优性价比的方式提供高质量的文生图服务。 使用 ecs.g8i.8xlarge 实例代替 ecs.gn7i-c8g1.2xlarge 时,可有效节省约 9% 的成本,并依然保持 1.2 images/s 的图像生成速度。 使用 ecs.g8i.4xlarge 实例替代 ecs.gn7i-c8g1.2xlarge 时,图像生成速度降为 0.5 images/s,但可有效节省超过 53% 的成本。 One morethings,更安全的机密推理 使用 ACK 集群的 TDX 机密虚拟机节点池,通过采用 ECS g8i 实例并结合 AMX + IPEX 技术,能够有效加速文生图模型的推理速度,并可以开启TEE实现对模型推理的安全保护。通过简单的打标和更新模型部署,可以把模型部署迁移进入内存加密的机密虚拟机环境。 在 ACK 集群内创建一个机密计算节点池,参考创建机密虚拟机节点池 [ 2] 。 将以下内容保存为 tdx_values.yaml。说明此处以为 TDX 机密虚拟机节点池配置了标签 nodepool-label=tdx-vm-pool 为例,如果您配置了其他标签,需替换 nodeSelector 中 nodepool-label 取值。 nodeSelector: nodepool-label: tdx-vm-pool 执行以下命令,将部署的 Stable Diffusion 示例模型迁移到 TDX 机密计算节点池。 helm upgrade stable-diffusion-ipex https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/pre/charts-incubator/stable-diffusion-ipex-0.1.9.tgz -f tdx_values.yaml 结论 在追求性价比、模型安全和大规模资源供给的文生图推理场景下,建议采用 ecs.g8i.4xlarge, ecs.g8i.8xlarge,ecs.g8i.12xlarge 机型补充,代替部分 GPU 实例运行 stabilityai/sdxl-turbo 及相关的微调模型,通过合理运用节点池管理,应用和 API 的部署模版 Helm Chart,Intel CPU 加速扩展,以最优性价比的方式提供稳定、高效、安全机密的文生图服务。 使用 ecs.g8i.8xlarge 实例代替 ecs.gn7i-c8g1.2xlarge 时,可有效节省约 9% 的成本,并依然保持 1.2 images/s 的图像生成速度。 使用 ecs.g8i.4xlarge 实例替代 ecs.gn7i-c8g1.2xlarge 时,图像生成速度降为 0.5 images/s,但可有效节省超过 53% 的成本。 更详细参考:https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/using-tee-cpu-to-accelerate-text2image-inference 相关链接: [1]创建 Kubernetes 托管版集群 https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/create-an-ack-managed-cluster-2#task-skz-qwk-qfb [2]创建机密虚拟机节点池https://help.aliyun.com/zh/ack/ack-managed-and-ack-dedicated/user-guide/create-tdx-confidential-vm-node-pools

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

【建议使用】告别if,Java超好用参数校验工具类

一、前言 今天和小伙伴们分享一个常用的工具类,共计四个方法,使用场景比较广泛,有用于校验某个对象或对象中指定属性为空值时,直接返回异常,常用语校验前端请求参数;也有当值不为空时,执行指定动作,可减少大量的if条件,如:mybatis请求参数设置;还有用于判断当值不为空时,替代为新值,完成后续动作。 这样描述可能不够清晰,这里我列举了几个使用场景,更多的场景需要小伙伴们根据自己业务需求合理使用。 //场景一,点击登录时,后端校验用户名 if(StringUtils.isEmpty(name)){ throw new Exception("登录用户名不能为空"); } ​ //场景二:属性内容转换为新值 String address = "浙江省杭州市"; if(StringUtils.isNotEmpty(address)){ address ="地址:"+address; } ​ //场景三:替代过多的if条件 SysUserDto userDto = new SysUserDto();//前端参数 ​ SysUser user = new SysUser();//mybatis参数 if(StringUtils.isEmpty(userDto.getName())){ user.setUserName(userDto.getName()) } if(StringUtils.isEmpty(userDto.getPwd())){ user.setPassword(userDto.getPwd()) } 复制代码 \ 二、正文 首先创建一个测试实体类: import lombok.Data; ​ @Data public class SysUser{ private String name; private String password; } 复制代码 2.1 检查多个对象不能为空 测试范例 SysUser user = new SysUser(); SysUser user2 = null; Args.notEmptys(user,user2); 复制代码 方法内容 public static void notEmptys(Object... objects) { for (Object obj : objects) { if (obj == null) { throw new BusinessException("属性不能为空"); } if (obj.toString().trim().isEmpty()) { throw new BusinessException("属性不能为空"); } } } 复制代码 测试结果 Exception in thread "main" 属性不能为空 at com.basic.business.utils.Args.notEmptys(Args.java:27) at com.basic.business.demo.DemoController.main(DemoController.java:43) 复制代码 2.2 校验对象属性不能为空 若将参数【Boolean isAll】设置为true,则会校验全部属性;若设置为false,就需要将检测字段放入【propertys】中。 测试范例 SysUser user = new SysUser(); //用法一:校验全部参数 Args.checkField(user,true,""); //用法二:只校验password参数 Args.checkField(user,false,"password"); 复制代码 方法内容 /** * 对象多字段判空检查 * @param obj 被检查的对象 * @param isAll 是否检查全部参数 * @param propertys 被检查对象中的字段 可多个 */ public static void checkField(Object obj, Boolean isAll, String... propertys) { if (obj != null) { Class<? extends Object> clazz = obj.getClass(); if (isAll) { PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(clazz); for (int p = 0; p < propertyDescriptors.length; p++) { checkEachField(obj, propertyDescriptors[p]); } } else { if (propertys != null && propertys.length > 0) { //遍历所有属性 for (int i = 0; i < propertys.length; i++) { String property = propertys[i]; //获取属性信息 BeanUtils.getPropertyDescriptors(clazz); PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(clazz, property); checkEachField(obj, pd); } } } } } ​ private static void checkEachField(Object obj, PropertyDescriptor pd) { Class<? extends Object> clazz = obj.getClass(); String property = pd.getName(); if (pd != null) { //获取当前字段的javabean读方法 Method readMethod = pd.getReadMethod(); if (readMethod != null) { ​ Object invoke = null; try { invoke = readMethod.invoke(obj); } catch (Exception e) { throw new BusinessException("方法 " + readMethod.getName() + "无法执行"); } ​ if (invoke != null) { //String类型单独处理 Class<?> propertyType = pd.getPropertyType(); if ("java.lang.String".equals(propertyType.getName())) { if (StringUtils.isBlank((String) invoke)) { throw new BusinessException("错误 : [ " + property + " ] 不能为空!"); } } else if ("java.util.List".equals(propertyType.getName())) { List list = (List) invoke; if (list.size() == 0) { throw new BusinessException("错误 : [ " + property + " ] 不能为空!"); } } } else { throw new BusinessException("错误 : [ " + property + " ] 不能为空!"); } } else { throw new BusinessException("在 " + clazz + "中 找不到" + "[ " + property + " ] 的 读方法"); } } else { throw new BusinessException("在 " + clazz + "中 找不到" + "[ " + property + " ] 属性"); } } 复制代码 测试结果 用法一结果: Exception in thread "main" 错误 : [ name ] 不能为空! at com.basic.business.utils.Args.checkEachField(Args.java:116) at com.basic.business.utils.Args.checkField(Args.java:77) 用法二结果: Exception in thread "main" 错误 : [ password ] 不能为空! at com.basic.business.utils.Args.checkEachField(Args.java:116) at com.basic.business.utils.Args.checkField(Args.java:77) 复制代码 2.3 参数不为空时执行指定动作 我们经常会遇到这种场景,根据前端参数来组装数据库查询条件,如果不为空我们就将前端值设置到是实体类中,或者如下面这个例子中,当name不为空时,加入到list当中。 测试范例 List list = Lists.newArrayList(); ​ SysUser user = new SysUser(); user.setName("huage"); Args.doIfNotEmpty(user.getName(),list::add); System.out.println(list); 复制代码 方法内容 public static <R, T> R doIfNotNull(T t, Callback<T, R> callback) { try { return t == null ? null : callback.call(t); } catch (Exception e) { throw new BusinessException("[doIfNotNull error]", e); } } 复制代码 测试结果 [huage] 复制代码 2.4 参数为空时使用新值,并完成指定动作 本方法和2.较为类似,只是当目标值不为空时,使用新值进行后续的操作,如下面这个例子中,name初始值为【test】,执行完doIfNotNullNewValue后会将新值【huage】添加到list中,。 测试范例 List list = Lists.newArrayList(); ​ SysUser user = new SysUser(); user.setName("test"); Args.doIfNotNullNewValue(user.getName(),"huage",list::add); System.out.println(list); 复制代码 方法内容 public static <R, T> R doIfNotNullNewValue(T t,T newt, Callback<T, R> callback) { try { return t == null ? null : callback.call(newt); } catch (Exception e) { throw new BusinessException("[doIfNotNull error]", e); } } 复制代码 测试结果 [huage] 最后 如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163相互学习,我们会有专业的技术答疑解惑 如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star:https://gitee.com/ZhongBangKeJi/crmeb_java不胜感激!

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

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

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

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

用户登录
用户注册