好库文摘 http://doc.okbase.net/ 袁创:如何成为黄金程序猿 http://doc.okbase.net/doclist/archive/259838.html doclist 2019/2/20 9:38:11

袁永福 2019-2-19
   
◆◆前言
   笔者袁永福快40岁了,南京东南大学毕业,非科班程序猿,创立了一个医疗软件公司:南京都昌信息科技有限公司,[袁永福原创]公司不大,但已经活跃了快7年的时间,公司发展稳扎稳打,现金流很好,而且远没达到天花板。
   笔者有着18年的职业软件开发经历,累计写下200万行代码,著书立作。南京雨花软件谷号称有12万程序猿,我也能在其中脱颖而出,自认为是一个黄金程序猿,一个程序猿中的王者。
   本文就我的经验谈谈如何成为黄金程序猿。这是一个很大的话题,得分成好多方面讲。
◆◆学习思考
   首先黄金程序猿能克制懒惰,勤于学习思考。
   学习及思考是反人性的,社会上有很多人[袁永福原创]宁愿忍受一辈子的生活艰辛而不愿意接受一时的学习思考而带来的痛苦。
   一些公司采用996的工作模式,个人觉得有点形式主义。但是公司中准点下班就停止学习思考的人,是不指望其有大能耐。
   因此学习思考是不限制时间场合的,它是[袁永福原创]游击战,在办公室、在高铁、在地铁等等都可以执行。比如此文主要就是在飞机上完成的。
   
◆◆意志坚定
   黄金程序猿应该是意志坚定耐磨的,而且能在坚定和变通中找到平衡。
   意志一直无条件的坚定就是性格固执,钻牛角尖,不少程序猿有这个毛病,需要改正。如果一直固执,则此生必然惨淡,蹉跎岁月,这方面我吃过大亏。
   现在的我可以在较长的时期内很耐心的用点点滴滴的细节累积出一座大厦。并经常校准做事方向。
   我认为我写的每一行代码都能微小的增强中国的国力,这么一想写代码就很认真了。
   
◆◆情商
   程序猿大多是工科男,高智商低情商。低情商不是多坏的事情。低情商就是简单,简单就是可靠,往这个方向想,就能转换为优点了。人简单可靠,能力强,也就容易博得部分客户的信任,生意也就好做一些。
   低情商有个不好的地方就是单纯幼稚,容易挨骗受欺负。我见过和听说过有高技术程序猿被所谓的兄弟情分牵绊而被利用欺负。
   对这方面就连我也是吃一堑长一智。在不断的经历中慢慢成长,至今还是有待提高。
   我得出的一个初步结论就是无论何时都要争取自己的合理权益,不必受兄弟情分牵绊,在商言商,不要在利益面前装高风亮节而退缩。而权益和自己的重要性及不可替代性关联。如果自己能力不行,价值小,就不能怪别人不出个好价格了。
   
◆◆止损原则
   另外要注意“止损”原则。比如新开发某商业产品,需要考虑最坏的情况,定下止损底线。触及底线原则上应立刻罢手撤出,不要拖沓。商业操作大多会失败,不要侥幸一定能成功而无限投入。避免因创业而返贫。
   
◆◆表达沟通能力
   情商不好的一个重要表现是表达能力及沟通能力不够。[袁永福原创]大量程序猿性格沉闷,不善言辞。给人榆木疙瘩的感觉。
   笔者当年也是如此,为此专门参加了保险公司三天的新员工封闭集训,用于增强表达沟通能力。而且对客户反复的讲解产品,这也逐渐锻炼出表达能力。
   
◆◆利益分析法
   情商不行还可以用智商补,使用严密的逻辑思维来补充情商的不足。这就采用利益分析法。
   大道至简,天下往来皆为利,这是千古真理。程序猿应该抓住这个真理对客户抽象分析,建立利益数据模型。
   在这个数据模型中,顶层是各种人员角色抽象的定义,然后是这些人各自的利益点,然后是利益点映射的实际问题,然后实际问题对应的解决方法,解决方法落实到功能模块,功能模块就包含着一行行代码。这种思维模式就是黄金程序猿的厉害之处。
   大量基层程序猿只能考虑到最后一层。不能突破思维层次就不能升级。
   
◆◆技术工具论
   程序猿们经常争论那种编程语言好。在我看来略显幼稚。我现在坚持[袁永福原创]技术工具论,所有的技术都是解决问题使用的工具。工具的使用是分场合的,锤子和锯子不能相互替代,也不会因为出现电动工具而淘汰。
   比如笔者擅长C#语言,js,sql,xml也会。不会写java程序,但能看懂。其他各种新潮技术在工作需要的时候就学习。
   学海无涯,人没有精力学习过多的知识,只能现学现用。
   一些人学习很多最新的技术框架,多是为了装逼满足虚荣心,实在不值得。技术是干活的工具,不是用来装逼的。咱们程序猿就是要实在的。
   
◆◆技术的价值
   技术工具论引申出技术的价值。也就是:“解决实际问题是检验技术价值的唯一标准”。这是“实践是检验真理的唯一标准”在技术领域中的体现。
   技术是解决客户问题的,因此黄金程序猿的技术价值观是朴实无华的,反对技术镀金,尽量简单,简单就是可靠,可靠就是能重复利用,重复利用就是省钱,就能创造效益[袁永福原创]。笔者曾经写过《打破牢笼,展望更高层次的世界》的文章(URL为https://www.cnblogs.com/xdesigner/p/break-self.html ),也提到不少相关思想。
   因此有价值的代码大多是简单的代码,能用上好几年而不淘汰。虽然简单的代码其行数会比复杂代码行数多,但可维护性好,总体拥有成本低。我从来反对复杂隐晦的代码,因为价值很低。
   
◆◆行业积累
   技术和特定行业结合起来才能发挥更多的价值。黄金程序猿必须知晓行业业务知识,至对行业发展有着独立的思考和实践。因此软件外包行业不可能出现黄金程序猿。
   比如[袁永福原创]笔者长期从事医院软件行业,对医院业务不是文盲,对局部领域很熟悉。对于不了解的区域稍微讲解一下就能有底。既有总体概念,又能把握局部。
   
◆◆代码规范
   如何写出简单的代码,也很简单,把代码书写规范背下来,牢记于心,简单的照着规范写代码就行了。
   大多数公司有代码规范,有很多共性,也有各自的特点。公司日常管理固然要检查规范执行情况。但黄金程序猿已经将代码规范融入骨髓中,其写出的代码就是规范。
   
◆◆文档
   代码之后就是写文档,[袁永福原创]写文档是很枯燥的工作,我也觉得难受,但没办法,要成为黄金程序猿总得闯过这关。
   写文档不是终点,之后就是写PPT了。用户不会看代码如何,而是看软件的运行效果。运行效果就要靠PPT讲。
   程序猿制作PPT的水平大多不行,我也一样,于是我发展了另外一种方式来发挥能力,那就是写文章公开发布。比如写博客。
   写文章是晋升黄金程序猿的必须关卡,写文章有几大好处
   第一,锻炼文笔。人升级必须要使用[袁永福原创]文字工具,写邮件,分析总结报告,替人做枪手等等。写文章就能锻炼文笔。
   第二,锻炼思维。公开发布的文章会受到很多陌生人的推敲,因此需要写得尽量滴水不漏。这样就能锻炼思维的连贯性和缜密性。思维缜密了,写出的代码也就滴水不漏了。
   第三,扩大影响力。文章可以反复观看和转发。引起部分读者的共鸣,共鸣就能产生影响力,逐渐把自己塑造成意见领袖。也能增加自己的人脉流量,并能导流到公司的销售部,提升在公司内外的影响力。
   第四,积累写书的素材。在中国写软件技术书不赚钱,但能赚个名声。而且作者亲笔签名的书籍是百发百中的糖衣炮弹,没有客户拒绝的。
   我以前在博客园发布了很多技术文章,于2008年成为微软mvp,写过c#编程书。
   近几年深耕医院软件行业,就选择在医院行业中坚持发布文章。经常发文讨论医院软件怎么来解决医院的问题,软件的大致架构和原理等等。文章不少,这些都逐渐产生了广泛的影响力,并为公司销售部导入很多流量。有力的支撑了公司发展。
   
◆◆超级黄金程序猿
   当然还有更罕见的超级黄金程序猿,拥有传说中的“红色物质”(源自电影《星际迷航》),具有创造“奇点”的能力。
   所谓奇点就是从0到1的过程。在市场中能[袁永福原创]无中生有的创造出一个平台,这是一个融合技术能力、市场环境、个人运气、团队搭配的过程。是个天时地利人和的事情,能完成的团队千里挑一。绝大多数奇点创造就湮灭。
   笔者就参与创造了一个奇点并维持了7年,侥幸,侥幸。
   
   通过以上内容就有可能成就黄金程序猿,可当公司CTO、首席架构师等技术高管。
   本文是我的经验之谈,是经过近二十年的积累而得,期间经历了很多汗水、泪水甚至人生的绝境,实属不易。
   说容易做难,长期坚持更难。黄金程序猿需要十多年的长期不懈的量变到质变,需要每天都在学习思考,每天都在进步。这是一个严重违反人性的过程,熬过来就有可能成为黄金程序猿。
   
◆◆小结
   黄金程序猿能完全把握自己的命运,有着卓越的技术,满满的自信,快速适应环境,写的每一行代码都是一首诗。所谓程序猿干不过35岁的规则对其无效。对时代的发展无所畏惧,甚至可以影响时代的发展。
   盛世也有衰败,乱世亦可崛起。但无论盛世乱世,真正的[袁永福原创]黄金程序猿必定能保持自身不败而持续崛起。希望中国能出现更多的黄金程序猿,促进中国软件行业实现价值最大化。
   --------------------------------
   最后打个广告,南京都昌公司招聘程序猿和销售猿。跟着黄金程序猿袁永福一起工作一起成长,创造美好未来。有意者请工作日时联系张小姐,手机:15380412021,QQ:2250624253。联系时请说明“黄金程序猿”。
   

]]>
揭秘第三方支付包含哪些业务 | 监管成长篇 http://doc.okbase.net/doclist/archive/259837.html doclist 2019/2/20 9:38:05

本篇文章给大家介绍第三方支付的行业监管、盈利模式和发展概况。

第三方支付行业监管

目前,我国第三方支付的监管机构是中国人民银行及其分支机构,按照“属地原则”进行监管。以《非金融机构支付服务管理办法》为政策核心,人民银行为主导,行业自律管理、商业银行监督为辅。由于第三方支付的迅猛发展,从2014年开始,央行对第三方支付出台了相关政策进行规范,如2015年12月出台的《非银行支付机构网络支付业务管理办法》,明确了第三方支付行业只能是中国支付体系的补充,作为非银支付机构,小额、便捷是其本质,需要做好客户信息安全、资金安全,以及风险防范。2016年开始的互联网金融整治,包含了第三方支付行业,央行出台了《非银行支付机构分类评级管理办法》。

从支付牌照的收紧,到96费改执行,再到二维码支付的开闸放水,监管部门出台的政策都是针对此前行业乱象的整顿,极大地促进了行业的良性发展。

备付金管理

我国相关监管制度规定,第三方支付机构只能在一家银行开立备付金专用存款账户,且其分支机构不得另外开设备付金账户。禁止第三方支付机构以任何形式挪用客户备付金,并要求其按照备付金专用账户的利息总额计提风险准备金。

为什么央行要建立第三方支付机构备付金存管制度呢?主要是保障资金的安全性、流动性,一方面保障客户资金安全,防止备付金被支付机构用于违规用途,比如被第三方支付机构擅自挪用、占用、借用客户备付金等;另一方面保证资金流动性的需求,防止支付机构因第三方支付 所产生的沉淀资金的 流动性风险。

统一清算

此前,第三方支付机构为绕开银联,与多个银行建立合作关系,逐步具备了跨行清算的功能,这加大了央行掌握资金流动性的难度,这一模式也成为洗钱、套现获利、诈骗盗取资金的温床,造成了支付和金融市场混乱。自2017开始,监管文件频发,“断直连”已成为板上钉钉的事情。

直连:指第三方支付直接与商业银行做对接,如果是本行交易直接完成,如果是跨行交易由商业银行在上送到银联进行交易。

间连:指第三方支付与银联系统相连接,当发生POS消费时,此交易信息先送至银联主机系统,由银联系统自动判断后直接送相关的发卡银行,然后信息再沿路返回。

创新业务

随着移动支付的快速发展,二维码支付、聚合支付这些新型支付方式的出现,在改变人们生活的同时,也隐藏着风险。2017年监管密集出台有关聚合支付、二维码的法律法规,引导行业的合理发展,防范创新业务带来的风险。

在所有互联网金融领域中,第三方支付是监管最早落地,牌照和管理体系最为健全的一个大分支。 2018年业内监管进一步趋严,在支付行业内监管目的更加明确,即:让更多的创新发生在走正道、合规且能承担社会责任的企业中。进而形成一种良币驱逐劣币的态势。

监管意图对第三方支付可能产生的影响

行业洗牌

已掌握市场优势资源的企业将在未来继续掌握更多资源,第三方支付将产生3-5家巨头企业引领市场,与其他体量较小的支付机构拉开差距,它们在支付链条上的强强联合又使得强者愈强,而体量较小的支付机构可能会专注于某一特定客户群体、特定行业的支付需求。

创新方式

未来在支付业务自身的创新层面上,将更加集中在已有支付方式的效能提高领域,集中到现有支付形式的开级上绕过各类监管红线,游走于灰色地带的创新将会受到一定抑制。

企业服务

无论是聚合支付公司还是第三方支付公司,将更加重视对企业客户的服务,将自身系统能力向外输出已经成为行业共识,尤其在监管强化支付牌照功能性的基础上,聚合支付公司将更加积极的向企业服务平台转型。

第三方支付的盈利模式

第三方支付参与者的盈利模式

产业链的不同环节赚取不同利润,第三方支付企业目前主要的盈利方式:电商平台支付解决方案;电商交易商户交易佣金;沉淀资金利息收入等。银行的核心业务是负债业务和资产业务,支付清算和电子银行业务只是银行中间业务中的一小部分,该类业务占银行业务收入的占比非常小。

银联:核心商业模式有银行卡收单跨行交易手续费分润;ATM 跨行取款收费;非金融机构支付清算;银行卡发行品牌服务(类似冠名)。

商业银行:银行卡交易发卡行手续费分润;银行卡交易收单行手续费分润;电子银行转账等手续费;快捷支付手续费分润支付。

第三方支付企业:电商交易商户交易佣金;电商平台支付解决方案;沉淀资金利息收入等。

第三方支付企业的盈利模式

支付清算是第三方支付的主要业务,是帮助实现资金转移支付的工具。银行对电子银行等业务的发展是为了更好的服务由资产业务和负债业务而产生的存量客户;电子支付作为第三方支付的根基,其要做到以支付产品去吸引用户,留住用户,所以在支付产品的开发、流程的优化的迫切性要远高于商业银行。资产负债类业务在银行电子支付业务之先,银行并非一定要通过支付数据的开发拓展新的业务模式,其对数据资源的重视和利用程度不如第三方支付。对于第三方支付企业,其盈利模式如下图所示:

2017年1月13日,央行下发通知,要求集中存管支付机构客户备付金。以后支付机构不能自由支配客户的备付金了,支付机构靠将备付金拿来投资获取收入的“吃利差”模式面临终结。

面对盈利难的现状,银行卡收单的第三方支付公司选择另外一个途径来盈利,那就是跳码,这个码是什么码呢?就是 MCC。

MCC 又称商户类别码,它由四位数字构成。由收单机构为特约商户设置,用语表明银联卡交易环境、所在商户的主营业务范围和行业归属、判断境内跨行交易商户结算手续费标准的主要依据。

MCC 由银联和发改委参照 ISO 国际标准《金融零售业商户类别码》发布, MCC 大类区别定价的状况一直未变,不同行业的刷卡费率从0.38%到1.25%不等,甚至还存在零费率的公益慈善类和执行封顶费率的房车批发类,行业价差相当可观。

根据 MCC 指定相关费率,餐饮娱乐类是1.25%,一般类是0.78%,民生类是0.38%,最低的0.38%和最高的1.25%费率相差好几倍,有的第三方支付公司为了赚取更加高额的收付费,就将餐饮类的交易上送到民生类,行业内俗称跳码,通过跳码第三方支付公司可以收取更多的手续费。

但跳码是银联禁止的,如果被监管方查到大规模的跳码交易,会对第三方支付进行处罚,严重者甚至会吊销第三方支付牌照,因此利润和风险共存。

这些年银/网连在推费率统一,如果真正实施了费率统一,依靠银行卡收单的第三方支付日子会越来越难过。

第三方支付发展概况

此段摘录于:http://www.chyxx.com/industry/201708/548783.html

国外第三方支付行业的发展概况

1996 年,全球第一家第三方支付公司在美国诞生,随后逐渐涌现出 Amazon Payments、 Yahoo!PayDirect、 PayPal 等一批第三方支付公司,其中以 PayPal 最为突出,其发展历程基本代表了北美第三方支付市场的发展缩影。

到 20 世纪 90 年代末,随着计算机网络技术、电子商务等行业的快速发展,完善的信用卡保障机制、金融支付系统、发达的物流体系极大促进了 B2B、 B2C、 C2C 等网上交易模式的发展。

21 世纪以来,美国电子商务的蓬勃发展进一步推动了第三方支付的兴起,比如知名的 eBay、 Amazon、谷歌等电子商务交易商,相应地也促进了 PayPal、Amazon Payment、 Google Checkout 等第三方支付机构的繁荣发展。

总体而言,国外第三方支付市场的发展历程可归纳为两个阶段:一是依托个人电子商务市场(C to C 市场)起源、壮大和成熟;二是向外部专业化、垂直化电子商务网站(B to C 市场)深入拓展。

国内第三方支付行业的发展概况

阶段一:网银发展促进行业生长(2005 年以前)

1991年,中国人民银行建成全国电子联行系统,至此,中国的支付体系才初步形成。支付服务出现的契机是工具的普及、 需求增长与银行落后的系统建设能力之间的矛盾。 2002 年之前,各大商业银行尚处于网银业务的发展完善期,向商家提供的支付接口没有统一的标准, 给商家和消费者造成诸多不便。

2002 年,中国银联的成立解决了多银行接口承接的问题。通过银行共同分担成本的方式,地方银联向商家提供多银行卡在线支付统一接口,使异地跨行网上支付成为可能;而金融网络与互联网的接口承接, 则由从电子商务发展而来的其他第三方支付机构承担。

该阶段,第三方支付机构提供的支付服务为支付网关模式,即具有较强银行接口专业技术的第三方支付公司作为中介方,分别连接银行和商家,从而帮助消费者和商家在网络交易支付过程中跳转到各家银行的网银界面进行支付操作。支付网关模式下,第三方支付机构业务自身附加值和增值空间均较小,收入主要来自银行端手续费的二次分润。

基于这项制约,第三方机构一方面不断发展壮大,以期获得规模效应;另一方面,不断寻求业务创新,以期获得新的利润增长点和竞争优势。至此,银行卡支付与互联网支付的商业合作模式初步形成。

阶段二:互联网浪潮推动爆炸式增长(2005-2012 年)

2005 年被称为以互联网支付为代表的第三方支付概念提出的一年,在这一年,第三方支付公司在专业化程度、市场规模和运营管理等方面均取得了较为显著的进步。

这一阶段,第三方支付机构在提供基础支付服务的同时,开始向用户提供各种类型的增值服务,例如缴费、转账、还款、授信等,第三方支付的概念逐渐被大众所认同。 2008—2010 年,中国第三方支付行业异军突起,交易规模连续三年持续增长;其中,互联网支付的发展尤其迅猛。

2011 年,以中国人民银行《非金融机构支付服务管理办法》正式发布及其后非金融支付机构《支付业务许可证》的颁发为标志,第三方支付行业的外延有了进一步延伸,即扩展为在收付款人之间作为中介机构提供网络支付、预付卡发行与受理、银行卡收单以及中国人民银行确定的其他支付服务的非金融机构,包括银联商务在内的 27 家企业获得了中国人民银行颁发的首批支付牌照。

至此,第三方支付牌照的发放标志着第三方支付行业合法地位的确立,第三方支付行业的涵义边界被正式定义,各子行业采用牌照监管,行业进入有序发展阶段。

阶段三:移动互联网浪潮酝酿重大变革(2012-2020 年)

2012 年是移动支付突破元年,基于智能手机的 SNS(社会性网络服务)、LBS(基于位置的服务)等应用都取得了较大突破,以智能终端和移动网络为依托的第三代支付风起云涌。同时,第三方支付与保险、信贷、证券等金融业务的新一轮互相渗透和融合正步入快车道,中国第三方支付将进入一个新技术、新金融、新体系、新格局不断涌现的重大变革阶段,并逐步走向成熟和完善。

参考:

]]>
Redis持久化方式的选择 http://doc.okbase.net/doclist/archive/259836.html doclist 2019/2/20 9:37:58

本文将介绍Redis持久化的两种方式:快照持久化和AOF持久化,并对两种方法进行分析和对比,方便在实际中做出选择。

持久化

什么是持久化

Redis所有数据保存在内存中,对数据的更新将异步地保存到磁盘上,使得数据在Redis重启之后仍然存在。这么做这有什么实际意义呢?将数据存储到硬盘是为了以后可以重用数据,将数据进行备份,可以在系统故障的时候从备份进行恢复。还有一点,存储在Redis里面的数据可能是经过复杂运算而得出的结果,把这些数据进行存储,方便后续的使用,以达到“空间换时间”的效果。

持久化的实现方式

Redis提供了两种不同的持久化方法将数据保存到硬盘里面。

快照持久化:将Redis某一时刻存在的所有数据都写入硬盘。

AOF持久化:AOF的全称叫append-only file,中文意思是只追加文件。当使用AOF持久化方式的时候,Redis执行写命令的时候,将被执行的写命令复制到硬盘里面,说的通俗一点就是写日志。

快照持久化

什么是快照持久化

Redis通过创建快照来获得存储在内存里面的数据在某个时间节点上的副本。

触发机制-主要三种方式

  1. save(同步)
  2. bgsave(异步)
  3. 自动
save命令:客户端向Redis发送save命令来创建一个快照文件。
执行save命令的时候,如果存在老的快照文件,新的将会替换老的。
bgsave命令:客户端向Redis发送bgsave命令,Redis调用fork创建一个子进程,然后子进程负责将快照写入硬盘,而父进程则继续处理命令请求。

save命令和bgsave命令对比:

命令
save
bgsave
IO类型
同步
异步
是否阻塞
复杂度
O(n)
O(n)
优点
不会消耗额外内存
不阻塞客户端命令
缺点
阻塞客户端命令
需要fork,消耗内存
自动生成:通过配置,满足任何一个条件就会创建快照文件。

快照持久化选项:

多久执行一次自动快照操作,60s之内有1000次操作写入时执行
save 60 1000
创建快照失败后是否仍然继续执行写命令
stop-writes-on-bgsave-error no
是否对快照文件进行压缩
rdbcompression yes
命名硬盘上的快照文件
dbfilename dump.rdb

最佳配置:

dbfilename dump-${port}.rdb
dir /bigdiskpath
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes

AOF持久化

快照持久化现存问题

耗时、耗性能:通过bgsave命令进行持久化的的时候,需要fork一个子进程,如果数据量很大的话,需要的内存也会相应的变大,内存的占用会导致Redis性能降低。

不可控、丢失数据:举个例子,上一次创建快照是11:00开始创建并创建成功。如果Redis在12:00开始创建新的快照,如果系统在未完成创建快照之前崩溃,11:00-12:00写入的数据将会丢失;如果系统在快照创建完成之后崩溃,那么12:00之后,创建快照的过程中的数据将会丢失。

什么是AOF

AOF持久化将被执行的写命令写到AOF文件的末尾,以达到记录数据的目的。Redis只要从头到尾重新执行一次AOF所有的命令就可以恢复数据。

AOF三种策略

always:每条Redis写命令都同步写入硬盘。
everysec:每秒执行一次同步,将多个命令写入硬盘。
no:由操作系统决定何时同步。

三种策略对比:生产环境中需要根据实际的需求进行选择。

命令 always everysec no
优点 不丢失数据 每秒一次fsync 不用管
缺点 IO开销较大,一般的SATA盘只有几百TPS 丢1s数据 不可控

AOF重写

随着Redis的运行,被执行的写命令不断同步到AOF文件中,AOF文件的体积越来越大,极端情况将会占满所有的硬盘空间。如果AOF文件体积过大,还原的过程也会相当耗时。为了解决AOF文件不断膨胀的问题,需要移除AOF文件中的冗余命令来重写AOF。

原生AOF AOF重写
set hello world
set hello java
set hello redis
incr counter
inct counter
rpush mylist a
rpush mylist b
rpush mylist c
过期数据
set hello redis
set counter 2
rpush mylist a b c
 
AOF重写的两种实现方式
  • bgrewriteaof命令

bgrewriteaof命令和bgsave命令的工作原理相似:Redis创建一个子进程,然后由子进程负责对AOF文件进行重写。

  • AOF重写配置

配置参数说明:

名称 含义
auto-aof-rewrite-min-size AOF文件重写需要的尺寸
auto-aof-rewrite-percentage AOF文件增长率
 

具体配置:

appendonly yes
appendfilename "appendonly-${port}.aof"
appendfsync everysc
dir /bigdiskpath
no-appendfsync-on-rwrite yes
auto-aof-rewrit-percentage 100
auto-aof-rewrite-min-size 64mb

快照持久化和AOF持久化的对比和选择

对比

命令 快照持久化 AOF持久化
启动优先级
体积
恢复速度
数据安全性 丢数据 根据策略决定
轻重

选择

在实际生产环境中,根据数据量、应用对数据的安全要求、预算限制等不同情况,会有各种各样的持久化策略;如完全不使用任何持久化、使用快照持久化或AOF持久化的一种,或同时开启快照持久化和AOF持久化等。此外,持久化的选择必须与Redis的主从策略一起考虑,因为主从复制与持久化同样具有数据备份的功能,而且主机master和从机slave可以独立的选择持久化方案。

(1)如果Redis中的数据完全丢弃也没有关系(如Redis完全用作DB层数据的cache),那么无论是单机,还是主从架构,都可以不进行任何持久化。

(2)在单机环境下(对于个人开发者,这种情况可能比较常见),如果可以接受十几分钟或更多的数据丢失,选择快照持久化对Redis的性能更加有利;如果只能接受秒级别的数据丢失,应该选择AOF。

(3)但在多数情况下,我们都会配置主从环境,slave的存在既可以实现数据的热备,也可以进行读写分离分担Redis读请求,以及在master宕掉后继续提供服务。在这种情况下,一种可行的做法是:master:完全关闭持久化,这样可以让master的性能达到最好slave:关闭快照持久化,开启AOF(如果对数据安全要求不高,开启快照持久化关闭AOF也可以),并定时对持久化文件进行备份(如备份到其他文件夹,并标记好备份的时间);然后关闭AOF的自动重写,然后添加定时任务,在每天Redis闲时(如凌晨12点)调用bgrewriteaof。

参考

[1] Josiah L.Carlson. Redis实战[M]. 陈健宏译. 北京:人民邮电出版社,2015.
 
]]>
使用ESMap的地图平台开发三维地图 http://doc.okbase.net/doclist/archive/259835.html doclist 2019/2/20 9:37:52

 

本文简单的介绍使用ESmap的SDK开发(DIY自己地图的)一个地图的过程。若有不足,欢迎指正。

一、创建地图

只需四步,从无到有,在浏览器中创建一个自己的三维地图,炫酷到爆!

第一步:引入ESMap 的SDK

ESMap家的SDK目前不支持用require js引用,只能使用<script src="lib/esmap.min.js"></script >引用

  <scriptsrc="../lib/esmap.min.js"></script>

第二部:创建一个地图容器

 <divid="map-container"></div>

第三部:拷贝地图数据和主题数据到自己创建的工程目录中

我拷贝的路径为:根目录>data文件夹>地图数据文件/主题文件夹>主题文件

 

第四部:配置初始化参数

var map =new esmap.ESMap({
container:container,
mapDataSrc: defaultOpt.mapDataUrl, //地图数据位置
mapThemeSrc: defaultOpt.mapThemeUrl, //主题数据位置
token:"escope"
});
//打开地图数据
map.openMapById(esmapId);

大功告成,显示创建的地图

 

二、ESMap地图支持的功能

  创建好三维地图后,根据需要添加功能到地图,可添加功能有:地图控件、地图标注、地图导航、地图数据信息检索、热力图绘制、地图路径规划等等。

  其中地图控件可为地图添加:楼层控制控件、放大缩小控制控件、显示地图比例尺、添加指北针、二三维切换开关。

  其中地图标注有9种类型可选择:图片标注、文字标注、线标注、多边形注、定位标注、气泡标注,还有最近新增的高级功能内嵌页面标注、三维模型标注和信息窗标注。

三、简单实现部分功能

  如下是创建了几个标注到我的地图上:

  创建标注需要注意的地方:由于数据加载、页面渲染的问题,创建标注代码要写在地图加载完成map.on(‘loadComplete’)代码的后面。

  1. 创建一个图片+文字标注:

  第一步:新建一个文字标注图层

var layer =new esmap.ESLayer(esmap.ESLayerType.TEXT_MARKER); //新建标注图层
//可以给图层添加自定义name
layer.name ='mylayer';

  第二部:创建一个文字图片标注实例,标注对象的值可以自定义;

var tm =new esmap.ESTextMarker({
x: gpos.x -30,
y: gpos.y -30,
id: 2019, //id,可自定义
image: 'image/user.png', //图片标注的图片地址
imageAlign: 'bottom', //图片在文字方位left,top,right,bottom
imageSize: 64, //图片大小
name: "图片文字标注", //文字名称
spritify: true, //跟随地图缩放变化大小,默认为true,可选参数
scale: 1, //文字等级缩放默认为1,可选参数,0.1表明缩小10倍
height: 1, //距离地面高度
showLevel: 20, //地图缩放等级达到多少时隐藏,可选参数
fillcolor: "255,0,0", //填充色
fontsize: "24", //字体大小
strokecolor: "255,255,0"//边框色
});

  第三部:把创建的标注添加到楼层对象中

var floorLayer = map.getFloor(1) //获取第一层的楼层对象
layer.addMarker(tm); //将标注添加到图层
floorLayer.addLayer(layer); //将图层添加到楼层对象

  一个图片+文字标注就这样完成啦!

 

如果不想要这个标注了,也可以删除掉标注

方法一:

//可以删除一整个标注图层
floorLayer.removeLayersByTypes(esmap.ESLayerType.IMAGE_MARKER);

方法二:

//也可以从标注图层中删除标注
layer.remove(tm); //删除某一个标注
layer.removeAll(); //删除所有标注

2.创建一个线标注:

第一步:确定两个坐标点(标注的起点和终点)

var center = map.center; //取地图的中心墨卡托坐标
//定义两个点
var v1 = {
x: center.x +20,
y: center.y +20,
fnum: 1, //楼层
offset: 10//偏移量
}
var v2 = {
x: center.x -10,
y: center.y -10,
fnum: 1,
offset: 10
}

第二步:创建线标注对象

//定义两个点集合
var points = [v1, v2];
//配置线标注样式 线标注的样式很多种:实线、普通箭头线、自定义虚线、导航线等,我选择的箭头线
var lineStyle = {
lineWidth: 6,
alpha: 0.8,
offsetHeight: 0,
lineType: esmap.ESLineType.ARROW
}
//创建线标注对象
var linemark =new esmap.ESLineMarker(1, points, lineStyle)

第三部:画线

//调用地图的画线方法 画标注线
map.drawLineMark(linemark);

so easy  一个线标注就这样完成啦!

 

同样,如果不想要这个标注,可以清除掉这个标注,方法如下:

可以根据设置的ID删除线标注

   map.clearLineMarkById(1)//我设置的id1

也可以直接删除地图所有的线标注

   map.clearAllLineMark();

3.创建一个气泡标注

第一步:创建气泡标注

var popMarker = new esmap.ESPopMarker({
mapCoord: {
x: gpos.x -20, //设置弹框的x轴
y: gpos.y -20, //设置弹框的y轴
height: 4, //控制信息窗的高度
fnum: 1//设置弹框位于的楼层
},
className: "myPopMarker", //自定义popMarker样式。在css里配置
width: 300, //设置弹框的宽度
height: 150, //设置弹框的高度
marginTop: 10, //弹框距离地面的高度
//设置弹框的内容
content: ' 我的popMarker< p >点击下方图片标注切换显示隐藏;长按图片标注可以拖动< /p >
< div class="myPopClose" >关闭< /div >',
//创建好气泡标注后 可以调用气泡标注相关方法
//popMarker.hide() //隐藏
//popMarker.show() //显示
//popMarker.toggle() //切换显示/隐藏
//popMarker.close() //删除标注
//下面我使用了气泡标注隐藏方法
created: function (e) {
$(".myPopClose").click(function () {
popMarker.hide(); //为关闭按钮绑定隐藏事件
})
}
});

我定义的气泡标注的css样式:

.myPopMarker {
padding: 0 20px;
background: rgb(28, 235, 190);
color: rgb(20, 21, 22);
border: 1 px solid#5e5e5e;
opacity: .7;
}
.myPopClose{
position: absolute;
top: 0;
right: 0;
width: 43px;
height: 25px;
text-align: center;
background-color: honeydew;
color: #000;
cursor: pointer;
}
 

一个气泡标注就这样完成啦!

 

以上就是我用ESMap的地图制作平台创建的地图,并简单实现了标注功能其中三种标注方法的过程,是不是觉得有点意思,感兴趣的小伙伴也去创建一个专属自己的地图吧!

Thank you for reading!

 

 

 

]]>
在ASP.NET Core中给上传图片功能添加水印 http://doc.okbase.net/doclist/archive/259834.html doclist 2019/2/20 9:37:45

在传统的.NET框架中,我们给图片添加水印有的是通过HttpModules或者是HttpHandler,然后可以通过以下代码添加水印:

var image = new WebImage(imageBytes);
image.AddTextWatermark(
    Settings.Instance.WatermarkText, "White", Settings.Instance.WatermarkFontSize,
    opacity: Settings.Instance.WatermarkTextOpacityPercentage
);

但是在.NET Core中不允许你这么干了,因为没有WebImage这个类型了。在现在的.NET Core中我们都是通过IFormFile来上传文件,这包含了很多格式,那我们试一下吧。

首先我们一定要知道,我们要把图片保存到什么地方,我们在.NET Core中获取项目目录需要最基本的构造函数,如以下定义:

public readonly ILogger<ImageController> Logger = null;
        private IHostingEnvironment hostingEnv;
        public ImageController(ILogger<ImageController> logger,IHostingEnvironment env)
        {
            Logger = logger;
            this.hostingEnv = env;
        }

由于可以扩展使用下简单的日志框架,我们也可以把微软那套的日志框架给构造进来。.NET Core添加水印的代码可以这么写。

// Add watermark
                            var watermarkedStream = new MemoryStream();
                            using (var img = Image.FromStream(stream))
                            {
                                using (var graphic = Graphics.FromImage(img))
                                {
                                    var font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Bold, GraphicsUnit.Pixel);
                                    var color = Color.FromArgb(128, 255, 255, 255);
                                    var brush = new SolidBrush(color);
                                    var point = new Point(img.Width - 120, img.Height - 30);

                                    graphic.DrawString("cnblogs.com/zaranet", font, brush, point);
                                    img.Save(watermarkedStream, ImageFormat.Png);
                                }
                                img.Save(hostingEnv.WebRootPath+"/"+name);
                            }

这里把传过来的内存流变成了Image也就是Bitmap,然后我们通过了graphic类的方法,变成了可修改的graphic类型,其中的方法大概有200多个。也就是画画~

 其中的完整代码如下:

        [HttpPost]
        public async Task<IActionResult> UploadImageAsync(IFormFile file)
        {
            try
            {
                if (null == file)
                {
                    Logger.LogError("file is null.");
                    return BadRequest();
                }

                if (file.Length > 0)
                {
                    var name = Path.GetFileName(file.FileName);
                    if (name != null)
                    {
                        using (var stream = new MemoryStream())
                        {
                            await file.CopyToAsync(stream);

                            // Add watermark
                            var watermarkedStream = new MemoryStream();
                            using (var img = Image.FromStream(stream))
                            {
                                using (var graphic = Graphics.FromImage(img))
                                {
                                    var font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Bold, GraphicsUnit.Pixel);
                                    var color = Color.FromArgb(128, 255, 255, 255);
                                    var brush = new SolidBrush(color);
                                    var point = new Point(img.Width - 120, img.Height - 30);

                                    graphic.DrawString("cnblogs.com/zaranet", font, brush, point);
                                    img.Save(watermarkedStream, ImageFormat.Png);
                                }
                                img.Save(hostingEnv.WebRootPath+"/"+name);

                            }
                            return StatusCode(StatusCodes.Status200OK);
                        }
                    }
                }
                return BadRequest();
            }
            catch (Exception e)
            {
                Logger.LogError(e, $"Error uploading image.");
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

最后,你可以使用PostMan进行测试。

Key要是你参数的名称,然后图片最后就成了这个样子。

 

 

]]>
java基础(六)-----String性质深入解析 http://doc.okbase.net/doclist/archive/259833.html doclist 2019/2/20 9:37:39

本文将讲解String的几个性质。

一、String的不可变性

  对于初学者来说,很容易误认为String对象是可以改变的,特别是+链接时,对象似乎真的改变了。然而,String对象一经创建就不可以修改。接下来,我们一步步 分析String是怎么维护其不可改变的性质

1. 手段一:final类 和 final的私有成员

我们先看一下String的部分源码:

1 public final class String
2     implements java.io.Serializable, Comparable<String>, CharSequence {
3     /** The value is used for character storage. */
4     private final char value[];
5     /** Cache the hash code for the string */
6     private int hash; // Default to 0
7     /** use serialVersionUID from JDK 1.0.2 for interoperability */
8     private static final long serialVersionUID = -6849794470754667710L;
9 }

  我们可以发现 String是一个final类,且3个成员都是私有的,这就意味着String是不能被继承的,这就防止出现:程序员通过继承重写String类的方法的手段来使得String类是“可变的”的情况。

  从源码发现,每个String对象维护着一个char数组 —— 私有成员value。数组value 是String的底层数组,用于存储字符串的内容,而且是 private final ,但是数组是引用类型,所以只能限制引用不改变而已,也就是说数组元素的值是可以改变的,而且String 有一个可以传入数组的构造方法,那么我们可不可以通过修改外部char数组元素的方式来“修改”String 的内容呢?

我们来做一个实验,如下:

1 public static void main(String[] args) {
2     char[] arr = new char[]{'a','b','c','d'};       
3     String str = new String(arr);       
4     arr[3]='e';     
5     System.out.println("str= "+str);
6     System.out.println("arr[]= "+Arrays.toString(arr));
7 }

运行结果

1 str= abcd
2 arr[]= [a, b, c, e]

结果与我们所想不一样。字符串str使用数组arr来构造一个对象,当数组arr修改其元素值后,字符串str并没有跟着改变。那就看一下这个构造方法是怎么处理的:

1 public String(char value[]) {
2     this.value = Arrays.copyOf(value, value.length);
3 }

原来 String在使用外部char数组构造对象时,是重新复制了一份外部char数组,从而不会让外部char数组的改变影响到String对象。

2. 手段二:改变即创建对象的方法

  从上面的分析我们知道,我们是无法从外部修改String对象的,那么可不可能使用String提供的方法,因为有不少方法看起来是可以改变String对象的,如replace()replaceAll()substring()等。我们以substring()为例,看一下源码:

1 public String substring(int beginIndex, int endIndex) {
2     //........
3     return ((beginIndex == 0) && (endIndex == value.length)) ? this
4             : new String(value, beginIndex, subLen);
5 }

从源码可以看出,如果不是切割整个字符串的话,就会新建一个对象。也就是说,只要与原字符串不相等,就会新建一个String对象

缓存Hashcode

  Java中经常会用到字符串的哈希码(hashcode)。例如,在HashMap中,字符串的不可变能保证其hashcode永远保持一致,这样就可以避免一些不必要的麻烦。这也就意味着每次在使用一个字符串的hashcode的时候不用重新计算一次,这样更加高效。

在String类中,有以下代码:

1 private int hash;//this is used to cache hash code.

以上代码中hash变量中就保存了一个String对象的hashcode,因为String类不可变,所以一旦对象被创建,该hash值也无法改变。所以,每次想要使用该对象的hashcode的时候,直接返回即可。

二、字符串拼接

其实,所有的所谓字符串拼接,都是重新生成了一个新的字符串。下面一段字符串拼接代码:

1 String s = "abcd";
2 s = s.concat("ef");

其实最后我们得到的s已经是一个新的字符串了。如下图

s中保存的是一个重新创建出来的String对象的引用.

那么,在Java中,到底如何进行字符串拼接呢?字符串拼接有很多种方式,这里简单介绍几种比较常用的

1、使用 + 拼接字符串

 我们先来看一个例子:

 1 public class MyTest {
 2     public static void main(String[] args) {
 3         String s = "Love You";      
 4         String s2 = "Love"+" You";
 5         String s3 = s2 + "";
 6         String s4 = new String("Love You");
 7         System.out.println("s == s2 "+(s==s2));
 8         System.out.println("s == s3 "+(s==s3));
 9         System.out.println("s == s4 "+(s==s4));
10     }
11 }

运行结果:

1 s == s2  true
2 s == s3  false
3 s == s4  false

  是不是对运行结果感觉很不解。别急,我们来慢慢理清楚。首先,我们要知道编译器有个优点:在编译期间会尽可能地优化代码,所以能由编译器完成的计算,就不会等到运行时计算,如常量表达式的计算就是在编译期间完成的。所以,s2 的结果其实在编译期间就已经计算出来了,与 s 的值是一样,所以两者相等,即都属于字面常量,在类加载时创建并维护在字符串常量池中。但 s3 的表达式中含有变量 s2 ,只能是运行时才能执行计算,也就是说,在运行时才计算结果,在堆中创建对象,自然与 s 不相等。而 s4 使用new直接在堆中创建对象,更不可能相等。

  那在运行期间,是如何完成String的+号链接操作的呢,要知道String对象可是不可改变的对象。我们反编译上面例子的calss文件,来看看究竟是怎么实现的:

 1 public class MyTest
 2 {
 3     public MyTest()
 4     {
 5     }
 6     public static void main(String args[])
 7     {
 8         String s = "Love You";
 9         String s2 = "Love You";//已经得到计算结果
10         String s3 = (new StringBuilder(String.valueOf(s2))).toString();
11         String s4 = new String("Love You");
12         System.out.println((new StringBuilder("s == s2 ")).append(s == s2).toString());
13         System.out.println((new StringBuilder("s == s3 ")).append(s == s3).toString());
14         System.out.println((new StringBuilder("s == s4 ")).append(s == s4).toString());
15     }
16 }

可以看出,编译器将 + 号处理成了StringBuilder.append()方法。也就是说,在运行期间,链接字符串的计算都是通过 创建StringBuilder对象,调用append()方法来完成的。

2、concat

除了使用+拼接字符串之外,还可以使用String类中的方法concat方法来拼接字符串。如:

1 String wechat = "ChenHao";
2 String introduce = "每日更新Java相关技术文章";
3 String hollis = wechat.concat(",").concat(introduce);

我们再来看一下concat方法的源代码,看一下这个方法又是如何实现的。

 1 public String concat(String str) {
 2    int otherLen = str.length();
 3    if (otherLen == 0) {
 4        return this;
 5    }
 6    int len = value.length;
 7    char buf[] = Arrays.copyOf(value, len + otherLen);
 8    str.getChars(buf, len);
 9    return new String(buf, true);
10 }

这段代码首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的String对象并返回。

通过源码我们也可以看到,经过concat方法,其实是new了一个新的String,这也就呼应到前面我们说的字符串的不变性问题上了。

三、StringBuffer和StringBuilder

接下来我们看看StringBufferStringBuilder的实现原理。和String类类似,StringBuilder类也封装了一个字符数组,定义如下:

1 char[] value;

String不同的是,它并不是final的,所以他是可以修改的。另外,与String不同,字符数组中不一定所有位置都已经被使用,它有一个实例变量,表示数组中已经使用的字符个数,定义如下:

1 int count;

其append源码如下:

1 public StringBuilder append(String str) {
2    super.append(str);
3    return this;
4 }

该类继承了AbstractStringBuilder类,看下其append方法:

1 public AbstractStringBuilder append(String str) {
2    if (str == null)
3        return appendNull();
4    int len = str.length();
5    ensureCapacityInternal(count + len);
6    str.getChars(0, len, value, count);
7    count += len;
8    return this;
9 }

append会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展。

 

StringBuffer和StringBuilder类似,最大的区别就是StringBuffer是线程安全的,看一下StringBufferappend方法。

1 public synchronized StringBuffer append(String str) {
2    toStringCache = null;
3    super.append(str);
4    return this;
5 }

该方法使用synchronized进行声明,说明是一个线程安全的方法。而StringBuilder则不是线程安全的。

效率比较

既然有这么多种字符串拼接的方法,那么到底哪一种效率最高呢?我们来简单对比一下。

 1 long t1 = System.currentTimeMillis();
 2 String str = "chenhao";
 3 //StringBuffer str = new StringBuffer("chenhao");
 4 for (int i = 0; i &lt; 50000; i++) {
 5    String s = String.valueOf(i);
 6    str += s;
 7    //str=str.concat(s);
 8    //str.append(s);
 9 }
10 long t2 = System.currentTimeMillis();
11 System.out.println("+ cost:" + (t2 - t1));

我们使用形如以上形式的代码,分别测试下五种字符串拼接代码的运行时间。得到结果如下:

1 + cost:5119
2 StringBuilder cost:3
3 StringBuffer cost:4
4 concat cost:3623

从结果可以看出,用时从短到长的对比是:

StringBuilder < StringBuffer < concat < + < StringUtils.join

StringBufferStringBuilder的基础上,做了同步处理,所以在耗时上会相对多一些,这个很好理解。

那么问题来了,前面我们分析过,其实使用+拼接字符串的实现原理也是使用的StringBuilder,那为什么结果相差这么多,高达1000多倍呢?

我们再把以下代码反编译下:

1 long t1 = System.currentTimeMillis();
2 String str = "chenhao";
3 for (int i = 0; i &lt; 50000; i++) {
4    String s = String.valueOf(i);
5    str += s;
6 }
7 long t2 = System.currentTimeMillis();
8 System.out.println("+ cost:" + (t2 - t1));

反编译后代码如下:

 1 long t1 = System.currentTimeMillis();
 2 String str = "chenhao";
 3 for(int i = 0; i &lt; 50000; i++)
 4 {
 5    String s = String.valueOf(i);
 6    str = (new StringBuilder()).append(str).append(s).toString();
 7 }
 8 
 9 long t2 = System.currentTimeMillis();
10 System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());

我们可以看到,反编译后的代码,在for循环中,每次都是new了一个StringBuilder,然后再把String转成StringBuilder,再进行append

而频繁的新建对象当然要耗费很多时间了,不仅仅会耗费时间,频繁的创建对象,还会造成内存资源的浪费。

所以:循环体内,字符串的连接方式,使用StringBuilder 的 append 方法进行扩展。而不要使用+

推荐博客

  https://www.cnblogs.com/chen-haozi/p/10227797.html

总结

本文介绍了什么是字符串拼接,虽然字符串是不可变的,但是还是可以通过新建字符串的方式来进行字符串的拼接。

常用的字符串拼接方式有五种,分别是使用+、使用concat、使用StringBuilder、使用StringBuffer以及使用StringUtils.join

由于字符串拼接过程中会创建新的对象,所以如果要在一个循环体中进行字符串拼接,就要考虑内存问题和效率问题。

因此,经过对比,我们发现,直接使用StringBuilder的方式是效率最高的。因为StringBuilder天生就是设计来定义可变字符串和字符串的变化操作的。

但是,还要强调的是:

1、如果不是在循环体中进行字符串拼接的话,直接使用+就好了。

2、如果在并发场景中进行字符串拼接的话,要使用StringBuffer来代替StringBuilder

 

]]>
设计模式总结 http://doc.okbase.net/doclist/archive/259832.html doclist 2019/2/20 9:37:32

一、设计模式的分类

  总体来说设计模式分为三类:

  1、创建型模式:工厂方法模式、抽象工厂模式、单利模式、建造者模式、原型模式

  2、结构性模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式

  3、行为模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式

  4、并发型模式和线程池模式

二、设计模式的六大原则

  1、开闭原则(Open Close Principle)

    就是说对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有的代码,实现一个热拔插的效果。所以,为了使程序的扩展性好,易于维护和升级,我们需要用到接口和抽象类

  2、里氏代换原则(Liskov Substiution Principle)

    面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。里氏是继承复用的基石,只有衍生类可以替换掉基类,软件单位的功能不受到影响,基类才能真正被复用,二衍生类也能够在基类的基础上增加新的行为。里氏是对“开闭”的补充。实现开闭原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是实现抽象化的具体步骤的规范

  3、依赖倒转原则(Dependence Inversion Principle)

    这个是开闭原则的基础。该原则规定:

    1)、高层次的模块不应该依赖于底层次的模块,两者都应该依赖于抽象接口

    2)、抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口

  4、接口隔离原则(Interface Segregation Principle)

    使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,可以看出,设计模式其实就是一种设计思想,就是为了软件升级和维护的方便

  5、迪米特原则(最少知道原则)(Demeter Principle)

    一个实体应当尽量少的与其他实体类之间发生相互作用,是的系统功能模块相对独立

  6、合成复用原则

    原则是尽量使用合成/聚合的方式,而不是使用继承

]]>
2018我像一条野狗 http://doc.okbase.net/doclist/archive/259831.html doclist 2019/2/20 9:37:25

2018这一年随着P2P爆雷潮、国内游戏行业大规模的倒闭潮及裁员、滴滴安全事件、华为5G渡劫等等大事件发生,这一年无论是企业还是个人都不好过,这一年寒风呼啸,大雪纷飞,而我像一条野狗。

阿里将员工分成三类:有能力没团队合作精神的,是“野狗”;和事佬、老好人,资质普通的,是“小白兔”;能力强有团队精神的,则是“猎犬”。

有人这样定义野狗式员工的特点:

个人能力强,在短期内快速给公司带来利益;

有个性有思想,但不服从公司管理;

个人利益大于团体的利益;

容易做出损害公司利益的事。

2018我像一条野狗

 

很多公司的现状是猎犬外出觅食创业,企业里小白兔当道,野狗频繁跳槽流窜。

2018年我就是这样的一条野狗,作为一个程序员在寒冬里频繁跳槽流窜了两次。第一家是在一家互联网创业公司,响应国家政策做的是大健康方面的互联网产品,在这家公司中工作了9个月,做项目像流水线一样快捷,9个月就做了很多项目,个人也得到了很好的成长,技术也有很大的提升,我也十分感恩公司给我这个成长的机会。

然而这一切在8月的时候一切都变了,公司在举办了一个关于互联网+大健康的融资大会后没有融到资。8月中旬的一个早上通知我们所有人说公司因为钱投到了其他项目没有回款同时也没有融到资,所以如果要留下来的员工后面可能会延期发工资,延期到什么时候未定。听到这个消息一下子心凉了,看来要跑路了,想着今年大环境不太好啊,工作不知道好不好找,然后一早上也没什么心情上班了。在中午的时候就和一个同事请假走了,然后和同事一起逛了一下午的公园,聊了很多,也想了很多,总之心情非常复杂难明,有对公司的不满,也有对公司领导的吐槽,也有对即将失业的迷茫。

2018我像一条野狗

 

生活就是这样,意外总是突如其来,无论怎么样还是要积极面对。在逛了公园后我就直接不去上班开始投简历找工作了,和其他同事聊,有一些同事也和我一样选择离开开始投简历,有一些同事则是对公司还有幻想,想着公司会撑过去的,选择留下来。那些选择离开的同事是像我一样的野狗,他们技术不差,不怕跳槽,不怕改变,不怕面对新的环境,那些选择留下来的大部分是因为生活所迫,房贷,家庭开支等压力大,怕跳槽一时找不到工作,一部分是一些“小白兔”,他们技术有些差,害怕跳槽,害怕改变,害怕面对新的环境,同时对公司还有幻想。

找工作出乎意料的顺利,投简历后也有很多家公司邀约去面试,好像并没有受什么大环境的影响,然后面试了第一家公司当场就顺利的被录用了,然后一个星期后就入职了新公司,工资待遇也涨了一截。

在新公司期间也和前同事们一起聊了很多,知道了大多数同事陆陆续续的找到了工作,只是有的没几天因为不适应又离职了,有的已经适应慢慢的进入忙碌的工作中了,有的则是吐槽新公司哪些地方比不上原公司,哪些地方比原公司好。

让我始料未及的是,我在新公司前2个月适应得很好,也做得不错,但是在第三个月来了新的项目经理后,因为开发思想还有技术见解方面和团队格格不入,同时也因为提了很多建议项目经理爱搭不理和项目经理有了争论,后面因为不按我的建议做项目确实比较难开展下去,项目经理才去执行我的一些建议。然后又因为其他多种原因,比如部门大佬请我去办公室喝茶说:“某某是我请来的项目经理,他是你领导,领导说怎么做就怎么做,你要服从,即使是错的你只要执行就可以了,有了锅也是领导背。”最主要的原因是被领导套路说我提离职了(事实上我没有提离职),最后刚好试用期结束时就离职了。

离职后休息调整了一个星期,到12月就开始投简历找工作,这次已经年底了工作机会真的少了很多。网上又经常传出各种消息说哪里哪里又裁员了,寒冬来临了,各大企业裁员过冬了,一股悲观的情绪蔓延开来,失业的我在这种情绪下感觉自己就像一条野狗在冰天雪地里到处觅食。还好12月中旬通过了一家公司的面试,然后12月底折腾了几天顺利入职了。

2018我像一条野狗

 

没有谁天生就想做“野狗”、“小白兔”,正如没有谁甘于平凡一样,我想如果有机会,有能力每一个人都想成为“猎犬”,想成为马云那样的人。但是该死的现实早就注定了大部分人只能成为“野狗”、“小白兔”,少部分人成为“猎犬”。

2018年的寒冬,如果把企业分为三类:创业公司是“野狗”;业绩不咸不淡的是“小白兔”;BAT等这些巨头,独角兽公司是“猎犬”。天地不仁以万物为刍狗,这句话我更喜欢直译,在天地大势面前,无论哪个人、哪个企业、哪个国家其实都是野狗。

狼行千里吃肉,狗行千里吃屎,2019,希望我们都能做最野的狼,赚更多的钱,喝最烈的酒,睡最爱的姑娘。

 

此文首发于今日头条互联网abc:https://www.toutiao.com/i6659156847225733636/

]]>
程序员过关斩将--数据库快速迁移10亿级数据 http://doc.okbase.net/doclist/archive/259830.html doclist 2019/2/20 9:15:30

菜菜呀,咱们业务BJKJ有个表数据需要做迁移

程序员主力 Y总

现在有多少数据?

菜菜

大约21亿吧,2017年以前的数据没有业务意义了,给你半天时间把这个事搞定,绩效给你A

程序员主力 Y总

有绩效奖金吗?

菜菜

钱的事你去问X总,我当家不管钱

程序员主力 Y总

...........

菜菜
问题分析

经过几分钟的排查,数据库情况如下:

1.  数据库采用Sqlserver 2008 R2,单表数据量21亿


2. 无水平或者垂直切分,但是采用了分区表。分区表策略是按时间降序分的区,将近30个分区。正因为分区表的原因,系统才保证了在性能不是太差的情况下坚持至今。

3. 此表除聚集索引之外,无其他索引,无主键(主键其实是利用索引来快速查重的)。所以在频繁插入新数据的情况下,索引调整所耗费的性能比较低。

至于聚集索引和非聚集索引等知识,请各位移步google或者百度。

        至于业务,不是太复杂。经过相关人员咨询,大约40%的请求为单条Insert,大约60%的请求为按class_id 和in_time(倒序)分页获取数据。Select请求全部命中聚集索引,所以性能非常高。这也是聚集索引之所以这样设计的目的。 

解决问题

        由于单表数据量已经超过21亿,并且2017年以前的数据几乎不影响业务,所以决定把2017年以前(不包括2017年)的数据迁移到新表,仅供以后特殊业务查询使用。经过查询大约有9亿数据量。

数据迁移工作包括三个个步骤:

1.  从源数据表查询出要迁移的数据

2.  把数据插入新表

3.  把旧表的数据删除

传统做法

        这里申明一点,就算是传统的做法也需要分页获取源数据,因为你的内存一次性装载不下9亿条数据。

1.  从源数据表分页获取数据,具体分页条数,太少则查询原表太频繁,太多则查询太慢。

SQL语句类似于

SELECT * FROM (
SELECT *,ROW_NUMBER() OVER(ORDER BY class_id,in_time) p FROM  tablexx WHERE in_time <'2017.1.1'  
) t WHERE t.p BETWEEN 1 AND 100


2.  把查询出来的数据插入目标数据表,这里强调一点,一定不要用单条插入策略,必须用批量插入。

3.  把数据删除,其实这里删除还是有一个小难点,表没有标示列。这里不展开,因为这不是菜菜要说的重点。

        如果你的数据量不大,以上方法完全没有问题,但是在9亿这个数字前面,以上方法显得心有余而力不足。一个字:慢,太慢,非常慢。

可以大体算一下,假如每秒可以迁移1000条数据,大约需要的时间为(单位:分)

900000000/1000/60=15000(分钟)

大约需要10天^ V ^

改进做法

以上的传统做法弊端在哪里呢?

1.  在9亿数据前查询必须命中索引,就算是非聚集索引菜菜也不推荐,首推聚集索引。

2.  如果你了解索引的原理,你应该明白,不停的插入新数据的时候,索引在不停的更新,调整,以保持树的平衡等特性。尤其是聚集索引影响甚大,因为还需要移动实际的数据。


提取以上两点共同的要素,那就是聚集索引。相应的解决方案也就应运而生:

1.  按照聚集索分页引查询数据

2 批量插入数据迎合聚集索引,即:按照聚集索引的顺序批量插入。

3. 按照聚集索引顺序批量删除

由于做了表分区,如果有一种方式把2017年以前的分区直接在磁盘物理层面从当前表剥离,然后挂载到另外一个表,可算是神级操作。有谁能指导一下菜菜,感激不尽

扩展阅读

1.  一个表的聚集索引的顺序就是实际数据文件的顺序,映射到磁盘上,本质上位于同一个磁道上,所以操作的时候磁盘的磁头不必跳跃着去操作。

2.  存储在硬盘中的每个文件都可分为两部分:文件头和存储数据的数据区。文件头用来记录文件名、文件属性、占用簇号等信息,文件头保存在一个簇并映射在FAT表(文件分配表)中。而真实的数据则是保存在数据区当中的。平常所做的删除,其实是修改文件头的前2个代码,这种修改映射在FAT表中,就为文件作了删除标记,并将文件所占簇号在FAT表中的登记项清零,表示释放空间,这也就是平常删除文件后,硬盘空间增大的原因。而真正的文件内容仍保存在数据区中,并未得以删除。要等到以后的数据写入,把此数据区覆盖掉,这样才算是彻底把原来的数据删除。如果不被后来保存的数据覆盖,它就不会从磁盘上抹掉。

NetCore 代码(实际运行代码)

1.  第一步:由于聚集索引需要class_id ,所以宁可花2-4秒时间把要操作的class_id查询出来(ORM为dapper),并且升序排列

   DateTime dtMax = DateTime.Parse("2017.1.1");
   var allClassId = DBProxy.GeSourcetLstClassId(dtMax)?.OrderBy(s=>s);

2.  按照第一步class_id 列表顺序查询数据,每个class_id 分页获取,然后插入目标表,全部完成然后删除源表相应class_id的数据。(全部命中聚集索引)

   D int pageIndex = 1//页码
            int pageCount = 20000;//每页的数据条数
            DataTable tempData =null;
            int successCount = 0;
            foreach (var classId in allClassId)
            {
                tempData = null;
                pageIndex = 1;
                while (true)
                {
                    int startIndex = (pageIndex - 1) * pageCount+1;
                    int endIndex = pageIndex * pageCount;

                    tempData = DBProxy.GetSourceDataByClassIdTable(dtMax, classId, startIndex, endIndex);
                    if (tempData == null || tempData.Rows.Count==0)
                    {
                        //最后一页无数据了,删除源数据源数据然后跳出
                         DBProxy.DeleteSourceClassData(dtMax, classId);
                        break;
                    }
                    else
                    {
                        DBProxy.AddTargetData(tempData);
                    }
                    pageIndex++;
                }
                successCount++;
                Console.WriteLine($"班级:{classId} 完成,已经完成:{successCount}个");
            }


DBProxy 完整代码:

class DBProxy
    {
        //获取要迁移的数据所有班级id
        public static IEnumerable<intGeSourcetLstClassId(DateTime dtMax)
        
{
            var connection = Config.GetConnection(Config.SourceDBStr);
            string Sql = @"SELECT class_id FROM  tablexx WHERE in_time <@dtMax GROUP BY class_id ";
            using (connection)
            {
                return connection.Query<int>(Sql, new { dtMax = dtMax }, commandType: System.Data.CommandType.Text);

            }
        }

        public static DataTable GetSourceDataByClassIdTable(DateTime dtMax, int classId, int startIndex, int endIndex)
        
{
            var connection = Config.GetConnection(Config.SourceDBStr);
            string Sql = @" SELECT * FROM (
                        SELECT *,ROW_NUMBER() OVER(ORDER BY in_time desc) p FROM  tablexx WHERE in_time <@dtMax  AND class_id=@classId
                        ) t WHERE t.p BETWEEN @startIndex AND @endIndex "
;
            using (connection)
            {
                DataTable table = new DataTable("MyTable");
                var reader = connection.ExecuteReader(Sql, new { dtMax = dtMax, classId = classId, startIndex = startIndex, endIndex = endIndex }, commandType: System.Data.CommandType.Text);
                table.Load(reader);
                reader.Dispose();
                return table;
            }
        }
         public static int DeleteSourceClassData(DateTime dtMax, int classId)
        
{
            var connection = Config.GetConnection(Config.SourceDBStr);
            string Sql = @" delete from  tablexx WHERE in_time <@dtMax  AND class_id=@classId ";
            using (connection)
            {
                return connection.Execute(Sql, new { dtMax = dtMax, classId = classId }, commandType: System.Data.CommandType.Text);

            }
        }
        //SqlBulkCopy 批量添加数据
        public static int AddTargetData(DataTable data)
        
{
            var connection = Config.GetConnection(Config.TargetDBStr);
            using (var sbc = new SqlBulkCopy(connection))
            {
                sbc.DestinationTableName = "tablexx_2017";               
                sbc.ColumnMappings.Add("class_id""class_id");
                sbc.ColumnMappings.Add("in_time""in_time");
                .
                .
                .
                using (connection)
                {
                    connection.Open();
                    sbc.WriteToServer(data);
                }               
            }
            return 1;
        }

    }

运行报告:

        程序本机运行,开vpn连接远程DB服务器,运行1分钟,迁移的数据数据量为 1915560,每秒约3万条数据

 1915560 / 60=31926 条/秒

 cpu情况(不高):


磁盘队列情况(不高):

写在最后

在以下情况下速度还将提高

 1. 源数据库和目标数据库硬盘为ssd,并且分别为不同的服务器

 2. 迁移程序和数据库在同一个局域网,保障数据传输时候带宽不会成为瓶颈

 3. 合理的设置SqlBulkCopy参数

 4. 菜菜的场景大多数场景下每次批量插入的数据量达不到设置的值,因为有的class_id 对应的数据量就几十条,甚至几条而已,打开关闭数据库连接也是需要耗时的

 5. 单纯的批量添加或者批量删除操作



]]>
何谓多租户模式 ? http://doc.okbase.net/doclist/archive/259829.html doclist 2019/2/20 8:53:48

一、什么是多租户模式

多租户模式的定义:单个产品实例为多个用户提供服务,同时用户可按需购买使用产品资源,用户数据相互隔离。

定义里的用户即租户,租户狭义上理解就是使用系统的人。广义上来说还应该包括创建的系统、数据等一切与当前租户有关的系统资源。

 

二、举个栗子

2.1 传统模式

假设我们有一个学生管理系统,有课程查询、成绩查询两个功能,每个学生都有账号可以登陆,使用系统中的这两个功能。然后我们把这个系统卖给很多个学校去使用,这时候需要给每个学校去部署一套系统。

 

2.2 多租户模式

还是上面的系统,结合第一章节的定义,我们看多租户模式下的系统架构,这时候我们只有一个学生管理系统实例,每个学校使用的时候首先以学校为单位进行租户创建,然后可以按需购买系统功能,比如只需要成绩查询,这里的每个学校就是一个租户。

 

三、几种数据隔离模式

3.1 共享数据表,租户ID隔离

这种模式也是我们平日里系统隔离用户使用的模式,既在表中加一个用户ID字段,多租户这里就是在表中多加一个租户ID字段进行数据区分,登陆哪个租户就通过ID查询到对应租户的数据。拿成绩表的查询SQL举例就是:

select * from t_score where tenantid = '租户的ID' 

 

3.2 共享数据库,Schema隔离

该模式就是共享一个数据库实例,然后根据租户建立多个Schema,以Mysql数据库为例,就是建立多个Mysql用户,每个Mysql用户下都有完整的系统表。这里在系统开发时就需要根据租户动态进行数据源切换,获取对应租户的Schema。

 

3.3 独立数据库

该模式就是完全独立数据库实例,还以Mysql数据库为例,就是装有很多个Mysql服务,每个租户对应一个实例 ,每个实例下具有相同的Schema和表 。这里也需要在系统开发时根据租户动态进行数据源切换,获取对应租户的数据库实例。

 

四、实现多租户系统的几个关键点

将一个传统模式的系统,改成多租户架构,大的方面来说,只需要做好这两点即可实现,也是最关键的两点。

4.1 租户管理

上面3.1 给了一个SQL,这个SQL在实际开发时,会发现是不可用的,满足不了需求,为什么呢?回到上面的学生管理系统,想想看,学校管理员购买了一个租户系统,而具体使用系统的人是学生,是学生在查成绩,先想一个问题,学生的数据从哪来?

 

(1)学校管理员添加

也就是学校管理员创建了租户系统后,在后台通过学生添加功能进行学生信息录入,这不就有了学生信息了,也容易关联上租户ID,看着好像没有问题,我们来捋一下执行过程。

学生访问系统 -> 输入账号密码,点登陆后 -> 3.1 模式因为数据源是一个,所以直接连上查t_student表 -> 对比用户名、密码 - > 找到且密码正确即认为登陆成功。

例外情况1:假如学生张三由学校A转到了学校B,这时候学校A的租户和学校B的租户都有了张三的信息,这时候有两条数据,怎么比对?这时候就不对了,租户ID还无法确定。这时候就需要下面的两种方案。

例外情况2:假如使用的是3.2 或 3.3 的数据隔离模式,相当于有多个数据源,这时候登陆时连接哪一个数据源查?这时候也就不对了。租户ID还无法确定。这时候同样需要下面的两种方案。

 

(2)学生注册为租户

方式一:学校想添加学生A,可以输入学生A的邮箱地址,发送邀请,学生A收到邮件邀请,登陆平台注册为租户(不创建系统),注册后自动进入发送邀请学校的学生库。

方式二:学生A直接登陆平台注册为租户,学生A将注册信息提供给学校管理员,学校管理员添加到学生库。

存储租户的租户表本身是共享的。然后需要 「租户学生表」 (创建系统的租户ID,学生租户ID)

这时候登陆并查询成绩流程就是:学生输入用户名、密码 -> 查询租户表 -> 登陆成功 -> 查询租户学生表 -> 选择对应的租户学校 -> 进入租户系统首页 -> 查询成绩

 

(3)特定登陆地址

即给每个租户购买系统后,生成特定独有的登陆地址,带有学校的租户ID,然后将登陆地址共享给学生即可。这也可以解决租户ID无法确定的问题。而且无需学生注册为租户。

 

4.2 数据路由

数据路由是数据隔离后的关键点,3.1 模式下即实现SQL的包装,做一层AOP,查询的时候在原来的SQL上附加

 where tenantid = '' and studentid = '' 

3.2、3.3 则需要根据租户ID动态切换数据源,代码层面即获取不同的Connection,实现细节限于篇幅,此处不展开了。 

 

五、总结

其实如果抛开商业模式来说,淘宝、京东包括我们常见的系统从技术架构上看都可以算是一种多租户系统,为什么?

因为首先是单实例可以支持多用户的,其次数据是可以根据用户ID隔离的(同第三章节「共享数据表,租户ID隔离」 模式)。

 

从整体架构上看又不是,为什么?首先不满足的是系统功能不是按需付费,像淘宝每个会员进去功能都是一样的,不能按需挑选功能,你说我不要订单功能,可能吗?显然不可能,你要不要订单功能就在那。另外功能免费。

还有就是商业模式的问题,不是将软件本身作为服务销售,还拿淘宝来说,它售卖的是淘宝上的商品,而不是淘宝软件本身的功能。所以模式上不是SAAS,严格上就不算多租户系统。

 

最后给大家一个公式,完整的多租户模式 = 多租户商业模式 + 多租户技术架构。

 

PS  :可关注「风象南讲全栈」公众号,有机会成为作者微信私人好友

]]>