
我们也了解到,现在淘宝的整个的数据库团队在逐渐的把一些数据库从Oracle迁移到MySQL,然后呢,把一些服务器由小型机转到PC server,那你们整个转变的动机是什么?
主要是因为业务压力给了我们最大的动力。07年我来到淘宝的时候,当时只有三个主要的数据库,全部在小型机和存储上面。以当时的压力来看,它跑起来是非常顺利的,而且大家也知道小型机它从Unix *** 作系统到硬件,稳定性都会比PC server其实要高很多,当时的情况下淘宝用小型机是一个非常自然的选择。
从07年开始淘宝的业务量保持每年自然翻一番的增长,数据库质量感觉到非常大的压力。那么前端业务量增长一倍,在数据库上有可能增长是好几倍,它有一个放大效应在里边。当时我们第一步能够想到很自然的架构,就是把三个数据库拆成更多的数据库,或每一个数据库支持一个比较单一的业务。比如用户、商品和交易,都会分成独立的数据库,然后放到独立的小型计算中去,这是我们08年做的很大的事情就是垂直拆分,然后08年的业务我们就顶住了。
当时我们就预估09年、10年会有更大的压力增长,这个时候我们应该怎么办?当时我们从业界能看到很多的经验分享,包括eBay、亚马逊这些国外的大公司,他们的经验分享里面,水平拆分是我们数据库涨到一定程度后的架构选择。我们从Oracle到MySQL转移,主要是用水平拆分,这是我们未来的一个弱点,那水平拆分后机器、数据库的数量都会多很多,那Oracle它本身的成本也是我们考虑的一个重要因素,所以当时从成本考虑的话,那个时候我们自然会选择用MySQL数据库。
给我们再简单总结一下这几年,淘宝整个数据库的演变过程?
刚才说到08年我们做完垂直拆分以后,09年到今年我们主要做的工作其实就是水平拆分。今年在十月份之前我们全部完成了淘宝最核心的三个系统:交易数据库、商品数据库和用户数据库的水平拆分。所以到“双十一”之前,在我们内部采访中,我一直跟采访人员说,当时数据库情绪稳定。基本上我们没有做什么事情,只是在不停的看报表,看数据,然后很开心的看到交易曲线以超过45度的趋势往上涨。
那前期还是做了非常完善的准备。据我们了解在整个从小型机到PC server的迁移,包括从Oracle到MySQL数据库的迁移,你们在做这个事情的时候,都做过好几个月的压力测试。你讲讲这个背景和故事。
是这样的,今年我们年初决定,我们商品库从小型机迁到PC server上面去,这是淘宝压力最大的一个数据库,当时是用四台小型机加两个高端存储来支撑的。要把这么大一个数据库进行迁移,我们心里面也是没有底的,因为不知道要多少台PC server能够支撑,需要什么样的配置来支撑这个压力?当时我们能够想到一个很直观的想法就是模拟线上完全一样的压力,甚至加上几倍的压力来测它的极限值。
我们和开发团队、我们的性能测试团队,加上DBA团队和ops团队,成立了一个非常大的项目组,然后做了接近两个月的性能测试,在整个测试过程中发现了非常多的问题,包括我们给Oracle、MySQL等厂商都提交了很多Bug,有些Bug也得到厂商回应,进行修复。
那整体的转变的过程到现在进行到了什么样的程度?包括你在整个转变的过程中遇到哪些问题?
我们现在最核心的用户数据库今年已经彻底完成了从小型机、存储和Oracle切入到PC server加MySQL的架构。
我们内部有一个提法叫做去O、去I、去E,其实就是我们要从高端硬件Scale up模式到低端硬件的Scal out水平扩展的模式,这是淘宝内部最大最核心的系统,今年已经顺利完成了全部区的水平扩展。其他几个系统,比如说交易和商品已经完成了一部分,完成了水平拆分的一部分,但是没有达到我们希望的进度,这可能是明年我们需要做的事情。
在转型过程中主要遇到哪些问题?
让我们觉得比较大的问题就是我们从可靠的小型机迁移到大规模,大数据量的PC server上来,从架构上就对我们就是一个非常大的挑战。大家都知道,每一个PC server的稳定性肯定和单台小型机会有一定的差距,再加上我们一个机群有可能是32台或者64台PC server。每一台PC server即使有四个9的可用性,但如果我们整个系统合在一起,可能它最后的两个9的可用性都达不到。这就需要我们从软件层、架构层要做非常多的改进,能够要让单点的一些失效对整体的系统不造成任何影响,因为我们和架构部门、开发部门一起做了很多事情,才能保证我们的集群稳定上线。
其实“双十一”这个时间应该说是对过去的技术转变的检验,现在回头来看,这个检验的结果怎么样?
当时是有点提心吊胆的,之后又觉得相对来说今年我们做的很多事情还是非常成功的。但是现在再回头仔细想想还是有点后怕,“双十一”那天的凌晨零点不是有一次Ipad的秒杀吗,当天晚上我们都在线上观察数据,在零点的一瞬间,就看到所有数据库指标已经达到了以前正常时候最高峰的指标,有些甚至还超过了。
当天晚上睡觉的时候心里就有点在打鼓:才零点就这个样子了,明天下午明天晚上最高峰的时候我们应该怎么渡过?所以第二天早上八点多的时候我们一进到指挥部里面就看到所有的指标, 包括CDN的指标、各个业务线的指标、数据库的指标都是噌噌的往上涨,这时心里面其实是很忐忑不安的。
但是我们比较放心的是这三大核心系统,商品、用户和交易,在我们今年所有的水平扩展项目做完了以后,比如说商品功能做完了以后,从我们的机械压测里面它是有十倍的流量的,所以当天百分之一百,百分之两百的流量基本上对数据库没有造成太大的影响,所以当时还是很开心的看到这个指标快速的往上涨,希望交易能够通过10个亿、20个亿,我觉得都是能够承受的。
那对于整个数据库架构的演进下一步有什么打算?
下一步其实就是刚刚说的我们有几个核心系统还没有完全的做到这个水平扩展,加上“双十一”那天我们还是有一个小惊险:我们有一个数据库,跟交易核心有一点点联系的,但它还是放在小型机上面,当时已经提前为它准备了百分之一百的余量,就是说它可以承担平时最高压力的两倍。
但是那天已经达到平时最高压力的18倍左右的时候,把我们吓出了一身冷汗。如果当时淘宝的交易最高峰的流量再增长20%的话,有可能数据库就会到瓶颈了。所以我们明年是要把更多这种Scale up能够看到天花板的数据库全部要拆分成水平库存这种数据库。
那你刚才所提到的去Oracle,去小型机,去高端存储,这个“三去”的整体思路给淘宝网带来了哪些经济上的效应?
当时我们知道小型机和存储的价格是非常昂贵的,还是拿我们刚才说压力最大的商品数据库举个例子,当初我们数据库是用了四台高端的小型机,两套高端的存储,成本加起来起码都是三千万以上。那目前我们用的是32台PC server来搭建的一个机群,价格也就是300万~500万的级别。相对来说我们做完这个事情以后,解决了两三千万的硬件成本。
这样来讲,整体的经济效益还是非常不错的。但是其实刚才我们在前期沟通的时候也提到,你要从Oracle转到MySQL,包括从小型机转到PC server,其实里面还是会遇到蛮多问题的,包括它的不稳定性等等,那对于这一方面你有没有什么经验可谈?
在这一方面,我觉得有两个很重要的因素。第一个是我们需要和我们的开发前端应用架构部门能够紧密的合作,能够让我们的应用融入刚才说的整个机群的单点失效和容灾的问题。都需要我们和架构部门一起来考虑的;第二个比较大的经验就是目前我们在做的,深入研究MySQL的源代码。我们从研究和压力测试的过程中,发现MySQL它本身代码的一些缺陷,可能在高并发大压力下会有很多隐藏的Bug。
在我们最近的这次测试当中,我们还发现了Facebook发布的FlashCache二级缓存的软件,当时我们是测出它一个非常大的Bug:并发压力非常大的情况下,它会导致MySQL成为一个僵尸进程。我们发现了以后,很快反馈给Face book,然后Face book很快就修复了这个问题,这也是我们对使用开源软件带来更大的一个信心,就是开源能够在全球得到更多的支持,大家都能够从原代码层面来解决更深层次的一个问题。
我想这也可能是淘宝技术团队现在那么开放,那么注重开源的动力之一。那如果说想对MySQL的一些核心代码做编译,就需要对人才的储备,包括各方面资源整合的要求还是蛮大的,那你在这方面有没有什么感触?
说到人才这个话题,08年的时候,淘宝当时准备大规模的往MySQL方向上转,我们内部也是有一些置疑的声音。他们说淘宝DDA团队以前都是在Oracle方面比较专精,在业界来说,淘宝的DDA团队在Oracle方面更加有名气一些。所以我们内部有置疑的声音。就是说你们有MySQL专家吗,MySQL出问题了以后能很快的解决吗?所以从08年到现在,我们慢慢的一路走过来,内部培养了很多的MySQL的人才,包括这几年我们的应届生的成长,再加上我们从外部招到一些专家,我们对MySQL的理解已经越来越深。
刚才说到,我们已经能够给MySQL打Patch,已经能够给MySQL report这些Bug。到现在为止,我觉得MySQL的成长已经达到了非常高的一个程度,我们对MySQL已经越来越有信心,但是未来淘宝的MySQL肯定是要做得越来越大的,淘宝还有很多小型机上面扩展不太容易的系统需要迁移到可扩展的机群上面来,但我们也希望业界能够有更多的MySQL伙伴加入我们,和我们一起来做这么一件非常有意义的事情。
我想能够加入到淘宝的技术团队,去经历那么多有大交易量的技术实践还是非常宝贵的。另外一个问题就是虽然说现在我们用的越来越多的是MySQL,但是现在大家也知道MySQL已经被Oracle收购了,那对像淘宝这样的团队有什么影响呢?
大家都知道MySQL其实是基于GPL的协议来开源的软件,那淘宝在使用过程中,前期是已经考虑到一些风险。所以我们所有的MySQL都是自己来做编译做优化的,而且我想MySQL被Oracle收购了以后,现在看起来Oracle应该是给MySQL在开发这方面是提供了更大的帮助,像之前在Sun的时候,MySQL的版本相对来说是比较混乱的,包括我们现在在用的50和51的正式版本,最近还有包括开发方面就还有两个,一个60,一个54,这些特性会互相交织在一起,让我们选择的时候也有点不知道到底选哪个版本会更好一点。但现在Oracle收购MySQL以后,他把54跟60这些版本已经合成了一个比较规范的55的版本,并且为它制订了很好的一个milestone15:31,未来要怎么发展这个里程碑,M1、M2、M3、M4这种发展方向,而到现在为止这个55已经发展到56、57的版本,而且已经是IC版本了,很快就要GA了,那我想这对于MySQL来说应该是一个好消息。我们可以用到更多更稳定的新特性, 55版本里有几个新的特性是我们非常关注的,比如Google已经达到英文15:57这个pach,所以我们觉得对我们未来的这个MySQL这个系统非常有用的一个功能。那我们也等着Oracle的55这个版本能够尽快的GA出来。
市面上大多数的java课程内容其实都大同小异,理论知识讲解会在四个月左右。而接下来会进行长达一个月左右的实战演练环节。
理论课程上,最开始肯定是最基础的内容:JavaSE,这一阶段会接触到Java基础语法、面向对象编程思想、Java常用API、多线程并发编程、数据结构/集合框架、IO/网络编程/反射/设计模式。这一阶段往往也是最重要的,后面很多知识和框架的应用都是基于这一基础来的。学完这一部分,就能做一些简单的桌面应用程序设计。
之后,就是一些比较进阶的内容了,主流数据库管理系统、Java前端开发技术、流行框架应用,都是要慢慢深入的。
最后,也是最重要的,就是实战项目的训练,掌握了再多理论知识没有实战的支撑也没有用。而企业需要的也正是你的实战能力。
可能表面上看大多数的学校课程都很雷同,但其实也有要注意的点,就是看学校的课程有没有实时更新。随着时代发展,Java也融入了很多新元素。只有最新的课程才是企业真正所需要的。
1、Linux运维工程师
一般从企业入门到中级Linux运维工程师的工作大致有:挑选IDC机房及带宽、购买物理服务器或云服务、购买及使用CDN服务、搭建部署程序开发及用户的访问系统环境、对数据进行备份及恢复、处理网站运行中的各种故障、对网站的故障进行监控、解决网站运行的潜在安全问题、开发自动化脚本程序提高工作效率、规划网站架构、程序发布流程和规范,制定运维工作制度和规范、配合开发人员部署及调试产品研发需要的测试环境、代码发布等工作需求,公司如果较小可能还会兼职网管、网络工程师、数据库管理员、安全工程师、技术支持等职责。
2、Linux架构师
①自动化代码上线(SVN/GIT+Jenkins+MVN)解决方案;
②云计算部署架构及Docker微服务架构方案;
③服务自动化扩容方案(KVM/OpenStack/Docker+Ansible+Zabbix);
④10万并发的网站架构、秒杀系统的架构及解决方案;
⑤多IDC机房互联方案、全网数据备份解决方案、账号统一认证方案;
⑥数据库、存储及各重要服务节点的集群和高可用方案;
⑦各网络服务的极端优化方案、服务解耦/拆分;
⑧运维流程、制度、规范等的建设和推行;
⑨沟通能力、培训能力、项目管理、业务需求分析及落地执行力等。
3、数据库工程师
主要工作内容就是保证数据库数据的安全以及高效地为用户提供各种服务。工作内容主要有:数据库环境搭建、数据库优化、数据库备份恢复、数据库集群高可用、数据库数据统计分析、数据库数据可视化展示等。
涉及到的工具从早期传统的Oracle、Sql
Server,到当前互联网最火爆的MySQL,以及近年来崛起的NOSQL数据库Redis,Mongodb,Hbase,Cassandra等,对于高级数据库管理员、数据库架构师,还需要网络、系统、开发等能力。
4、运维开发工程师
运维开发工程师是介于运维工程师和开发工程师之间的岗位,简单地说就是开发和运维工作相关的工具、软件以及让运维数据自动化、智能化、可视化的平台产品。
5、运维经理
运维经理和运维总监是运维岗位的管理岗,和其他的部门领导一样,该岗位不但需要运维工程师、运维架构师的能力,同时还需要善于沟通、懂得团队激励、有培训能力、说服力。
这三个具体都有各自的有点和缺点,哪个好需要看使用在什么地方
1、什么时候要用 SQLite
a)、嵌入式应用 所有需要迁移性,不需要扩展的应用,例如,单用户的本地应用,移动应用和游戏
b)、代替磁盘访问 在很多情况下,需要频繁直接读/写磁盘文件的应用,都很适合转为使用 SQLite ,可以得益于 SQLite 使用 SQL 带来的功能性和简洁性
c)、测试 它能秒杀大部分专门针对应用业务逻辑(也就是应用的主要目的:能完成功能)的测试
2、什么时候不要用SQLite
a)、多用户应用 如果你在开发的应用需要被多用户访问,而且这些用户都用同一个数据库,那么相比 SQLite 最好还是选择一个功能完整的关系型数据库(例如 MySQL)
b)、需要大面积写入数据的应用 SQLite 的缺陷之一是它的写入 *** 作。这个数据库同一时间只允许一个写 *** 作,因此吞吐量有限
3、何时使用 MySQL?
a)、分布式 *** 作 当你需要的比SQLite可以提供的更多时,把MySQL包括进你的部署栈,就像任何一个独立的数据库服务器,会带来大量的 *** 作自由和一些先进的功能
b)、高安全性 MySQL的安全功能,用一种简单的方式为数据访问(和使用)提供了可靠的保护
c)、Web网站 和 Web应用 绝大多数的网站(和Web应用程序)可以忽视约束性地简单工作在MySQL上。这种灵活的和可扩展的工具是易于使用和易于管理的——这被证明非常有助于长期运行
d)、定制解决方案 如果你工作在一个高度量身定制的解决方案上,MySQL能够很容易地尾随和执行你的规则,这要感谢其丰富的配置设置和 *** 作模式
4、何时不用 MySQL?
a)、SQL 服从性 因为 MySQL 没有[想要]实现 SQL 的全部标准,所以这个工具不完全符合SQL。如果你需要对这样的关系数据库管理系统进行整合,从MySQL进行切换是不容易的
b)、并发 即使MySQL和一些存储引擎能够真地很好执行读取 *** 作,但并发读写还是有问题的
c)、缺乏特色 再次提及,根据数据库引擎的选择标准,MySQL会缺乏一定的特性,如全文搜索
5、何时使用PostgreSQL?
a)、数据完整性 当可靠性和数据完整性是绝对必要而无需理由时,PostgreSQL是更好的选择
b)、复杂的自定义过程 如果你需要你的数据库执行自定义过程,可扩展的PostgreSQL是更好的选择
c)、整合 在将来,如果可能要把整个数据库系统迁移到另一个适当的解决方案(例如Oracle)中,PostgreSQL对于这种切换将是最兼容和易于 *** 作的
d)、复杂的设计 相比其他的开源和免费的 RDBMS(关系数据库管理系统)实现来说,对于复杂的数据库设计,PostgreSQL提供了大部分的功能和可能性,同时并没放弃其他有价值的地方
6、何时不用 PostgreSQL?
a)、速度 如果你需要的只是快速的读取 *** 作, PostgreSQL 不是为此而准备的工具
b)、简化体制 除非你需要绝对的数据完整性,原子性,一致性,隔离性,耐久性,或复杂的设计,PostgreSQL 对简化体制来说是杀手
c)、复制 除非你愿意花不少时间,精力和资源,否则对于那些缺乏数据库和系统管理经验的人来说,实现与MySQL的(主从)复制可能不容易
ERP的英文全称是EnterpriseResourcePlanning,直译过来是企业资源计划。
ERP最初的功能是把企业所有可用资源(资金,库存,人力,供应商,客户等等)集中到计算机数据库中,通过合理的公司运作流程,最大程度的利用这些资源,为企业创造最大的价值。
但是随着科技的高速发展,目前ERP更像是一个代词,即企业信息化综合管理系统。功能是把企业日常经营所涉及到的全部内容数据化并集中到计算机数据库中,除了最大化利用企业资源外,同时利用计算的强大计算能力,结合不同功能模块,消灭大部分重复劳动,极大提升全员工作效率,为企业节约运营成本。比如移动办公功能让员工在路上也能工作,自动报表功能秒杀枯燥的统计工作,自动单据功能秒杀办公室大部分重复劳动等等。
开发语言选择很多,完全根据自己的实际情况来选择,Java,C,VB,HTML等等都可以开发。因为开发这种软件,编程主要是为了实现两个功能,第一是访问数据库,第二就是处理数据,上面说的任何一种语言都完全能够胜任这两个功能。我曾经还用过Excel给两个企业开发过管理系统。
对于管理系统来说,比开发编程更重要的是软件功能设计,就像一座大楼,首先要设计好,才能安全好用,否则程序编得再好,做出来对企业来说也是一场灾难。不但没有提升效率和利用好资源,反而可能浪费企业大量的时间和金钱。
管理系统在发达国家普及度很高,大多数公司都有各类型的管理系统辅助公司运营管理。但是在中国,管理系统普及度很低,实施成功率也很低。第一是因为我国企业对管理系统了解很局限,它能做什么?我们应该让它做什么?第二是因为我们属于发展中国家,国家和企业都在高速发展,那么管理系统也必须跟上节奏,不断进化,否则就会拖后腿。
最佳的方式就是企业自己要有技术团队来进行开发,在充分了解软件的能力和企业的需求的基础上设计开发出最适合企业的系统,同时保持系统的及时进步更新。
当然难题也来了,对于大型企业来说,养个技术团队不但不是问题,而且绝对必要。但对于中小企业,尤其是小微企业来说,成本就太高了,只能找软件外包公司来定制。不过外包定制费用也不低,而且每次升级开发都需要再花钱,像个无底洞。
/ Mainjava
Created on __DATE__, __TIME__
/
import javaxswingJOptionPane;
/
@author __USER__
/
public class Main extends javaxswingJFrame {
/ Creates new form Main /
public Main() {
initComponents();
}
/ This method is called from within the constructor to
initialize the form
WARNING: Do NOT modify this code The content of this method is
always regenerated by the Form Editor
/
//GEN-BEGIN:initComponents
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jPanel1 = new javaxswingJPanel(); //标签
jLabel1 = new javaxswingJLabel();
jLabel2 = new javaxswingJLabel();
jLabel3 = new javaxswingJLabel();
jButton1 = new javaxswingJButton(); //登录按钮
jButton2 = new javaxswingJButton(); //
jTextField1 = new javaxswingJTextField(); //文本框
jTextField2 = new javaxswingJTextField();
setDefaultCloseOperation(javaxswingWindowConstantsEXIT_ON_CLOSE); //关闭时的 *** 作,即退出程序
setTitle("登录界面"); //设置标题:欢迎使用股票管理系统
setResizable(false); //设置窗口不可调节大小
jLabel1setFont(new javaawtFont("微软雅黑", 0, 18)); //设置标签的字体
jLabel1
setText("您好,请输入信息");
jLabel2setText("用户名");
jLabel3setText("用户密码");
jButton1setText("登录");
jButton1addActionListener(new javaawteventActionListener() { //采用匿名内部类来实现按钮监听功能
public void actionPerformed(javaawteventActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
jButton2setText("退出");
jButton2addActionListener(new javaawteventActionListener() { //同上
public void actionPerformed(javaawteventActionEvent evt) {
jButton2ActionPerformed(evt);
}
});
javaxswingGroupLayout jPanel1Layout = new javaxswingGroupLayout( //层次布局管理器,下面n长的代码,就是设置位置,自己看吧,呵呵,这个必须的,就不多写了
jPanel1);
jPanel1setLayout(jPanel1Layout);
jPanel1Layout
setHorizontalGroup(jPanel1Layout
createParallelGroup(
javaxswingGroupLayoutAlignmentLEADING)
addGroup(
javaxswingGroupLayoutAlignmentTRAILING,
jPanel1LayoutcreateSequentialGroup()
addContainerGap(159, ShortMAX_VALUE)
addComponent(jLabel1)addGap(140, 140,
140))
addGroup(
jPanel1Layout
createSequentialGroup()
addGap(110, 110, 110)
addGroup(
jPanel1Layout
createParallelGroup(
javaxswingGroupLayoutAlignmentLEADING)
addComponent(jLabel2)
addComponent(jLabel3))
addGap(54, 54, 54)
addGroup(
jPanel1Layout
createParallelGroup(
javaxswingGroupLayoutAlignmentLEADING)
addGroup(
jPanel1Layout
createSequentialGroup()
addComponent(
jTextField2,
javaxswingGroupLayoutDEFAULT_SIZE,
191,
ShortMAX_VALUE)
addGap(
112,
112,
112))
addGroup(
jPanel1Layout
createSequentialGroup()
addComponent(
jTextField1,
javaxswingGroupLayoutPREFERRED_SIZE,
191,
javaxswingGroupLayoutPREFERRED_SIZE)
addContainerGap())))
addGroup(
jPanel1Layout
createSequentialGroup()
addGap(141, 141, 141)
addComponent(
jButton1,
javaxswingGroupLayoutPREFERRED_SIZE,
68,
javaxswingGroupLayoutPREFERRED_SIZE)
addGap(89, 89, 89)
addComponent(
jButton2,
javaxswingGroupLayoutPREFERRED_SIZE,
72,
javaxswingGroupLayoutPREFERRED_SIZE)
addContainerGap(145, ShortMAX_VALUE)));
jPanel1Layout
setVerticalGroup(jPanel1Layout
createParallelGroup(
javaxswingGroupLayoutAlignmentLEADING)
addGroup(
jPanel1Layout
createSequentialGroup()
addContainerGap()
addComponent(jLabel1)
addGap(55, 55, 55)
addGroup(
jPanel1Layout
createParallelGroup(
javaxswingGroupLayoutAlignmentLEADING)
addComponent(jLabel2)
addComponent(
jTextField1,
javaxswingGroupLayoutPREFERRED_SIZE,
javaxswingGroupLayoutDEFAULT_SIZE,
javaxswingGroupLayoutPREFERRED_SIZE))
addGap(41, 41, 41)
addGroup(
jPanel1Layout
createParallelGroup(
javaxswingGroupLayoutAlignmentBASELINE)
addComponent(jLabel3)
addComponent(
jTextField2,
javaxswingGroupLayoutPREFERRED_SIZE,
javaxswingGroupLayoutDEFAULT_SIZE,
javaxswingGroupLayoutPREFERRED_SIZE))
addGap(43, 43, 43)
addGroup(
jPanel1Layout
createParallelGroup(
javaxswingGroupLayoutAlignmentBASELINE)
addComponent(
jButton1,
javaxswingGroupLayoutPREFERRED_SIZE,
33,
javaxswingGroupLayoutPREFERRED_SIZE)
addComponent(
jButton2,
javaxswingGroupLayoutPREFERRED_SIZE,
33,
javaxswingGroupLayoutPREFERRED_SIZE))
addContainerGap(48, ShortMAX_VALUE)));
javaxswingGroupLayout layout = new javaxswingGroupLayout(
getContentPane());
getContentPane()setLayout(layout);
layoutsetHorizontalGroup(layoutcreateParallelGroup(
javaxswingGroupLayoutAlignmentLEADING)addGroup(
layoutcreateSequentialGroup()addContainerGap()addComponent(
jPanel1, javaxswingGroupLayoutDEFAULT_SIZE,
javaxswingGroupLayoutDEFAULT_SIZE, ShortMAX_VALUE)
addContainerGap()));
layoutsetVerticalGroup(layoutcreateParallelGroup(
javaxswingGroupLayoutAlignmentLEADING)addGroup(
layoutcreateSequentialGroup()addContainerGap()addComponent(
jPanel1, javaxswingGroupLayoutPREFERRED_SIZE,
javaxswingGroupLayoutDEFAULT_SIZE,
javaxswingGroupLayoutPREFERRED_SIZE)
addContainerGap(javaxswingGroupLayoutDEFAULT_SIZE,
ShortMAX_VALUE)));
pack();
setLocationRelativeTo(null); //居中
}// </editor-fold>
//GEN-END:initComponents
private void jButton1ActionPerformed(javaawteventActionEvent evt) { //事件处理,不建议这样做
// TODO add your handling code here:
String name = thisjTextField1getText()trim(); //获得用户名,去空格
String password = thisjTextField2getText()trim(); //获得密码,去空格
if (nameequals("admin") && passwordequals("123456")) { //如果符合条件其实这里应该去和数据库关联,这是死代码
// Showinformation sf=new Showinformation(); //我估计是另外一个框架,就是登录成功之后的框架
// sfsetVisible(true);
// sfshow(); //这个方法就直接秒杀,不用了,因为sfsetVisible(true)已经被调用了,这里就多次一举了
} else { //用户名或者密码验证失败
JOptionPaneshowMessageDialog(this, "用户名或者密码错误啦~", "提示", //提示框
JOptionPaneINFORMATION_MESSAGE);
thisjTextField1setText(""); //清空输入框
thisjTextField2setText("");
}
}
private void jButton2ActionPerformed(javaawteventActionEvent evt) { //事件处理,其实可以将jButton1ActionPerformed放在一起处理,只要获得按钮的标签内容 (JButton)(evtgetSuorse())getText()就做对比就ko了
// TODO add your handling code here:
Systemexit(0);
}
/
@param args the command line arguments
/
public static void main(String args[]) {
javaawtEventQueueinvokeLater(new Runnable() { //好方法,呵呵使用该方式的原因是:awt是单线程模式的,所有awt的组件只能在(推荐方式)事件处理线程中访问,从而保证组件状态的可确定性。java核心编程技术中每个实例基本都用到了
public void run() {
new Main()setVisible(true);
}
});
}
//GEN-BEGIN:variables
// Variables declaration - do not modify
private javaxswingJButton jButton1;
private javaxswingJButton jButton2;
private javaxswingJLabel jLabel1;
private javaxswingJLabel jLabel2;
private javaxswingJLabel jLabel3;
private javaxswingJPanel jPanel1;
private javaxswingJTextField jTextField1;
private javaxswingJTextField jTextField2;}
// End of variables declaration//GEN-END:variables
你修改一下吧,从记事本获得用户名判断一下就行了
对于互联网业务来说,传统的直接访问数据库方式,主要通过数据分片、一主多从等方式来扛住读写流量,但随着数据量的积累和流量的激增,仅依赖数据库来承接所有流量,不仅成本高、效率低、而且还伴随着稳定性降低的风险。
鉴于大部分业务通常是读多写少(读取频率远远高于更新频率),甚至存在读 *** 作数量高出写 *** 作多个数量级的情况。因此, 在架构设计中,常采用增加缓存层来提高系统的响应能力 ,提升数据读写性能、减少数据库访问压力,从而提升业务的稳定性和访问体验。
根据 CAP 原理,分布式系统在可用性、一致性和分区容错性上无法兼得,通常由于分区容错无法避免,所以一致性和可用性难以同时成立。对于缓存系统来说, 如何保证其数据一致性是一个在应用缓存的同时不得不解决的问题 。
需要明确的是,缓存系统的数据一致性通常包括持久化层和缓存层的一致性、以及多级缓存之间的一致性,这里我们仅讨论前者。持久化层和缓存层的一致性问题也通常被称为双写一致性问题,“双写”意为数据既在数据库中保存一份,也在缓存中保存一份。
对于一致性来说,包含强一致性和弱一致性 ,强一致性保证写入后立即可以读取,弱一致性则不保证立即可以读取写入后的值,而是尽可能的保证在经过一定时间后可以读取到,在弱一致性中应用最为广泛的模型则是最终一致性模型,即保证在一定时间之后写入和读取达到一致的状态。对于应用缓存的大部分场景来说,追求的则是最终一致性,少部分对数据一致性要求极高的场景则会追求强一致性。
为了达到最终一致性,针对不同的场景,业界逐步形成了下面这几种应用缓存的策略。
— 1 —
Cache-Aside
Cache-Aside 意为旁路缓存模式,是应用最为广泛的一种缓存策略。下面的图示展示了它的读写流程,来看看它是如何保证最终一致性的。在读请求中,首先请求缓存,若缓存命中(cache hit),则直接返回缓存中的数据;若缓存未命中(cache miss),则查询数据库并将查询结果更新至缓存,然后返回查询出的数据(demand-filled look-aside )。在写请求中,先更新数据库,再删除缓存(write-invalidate)。
1、为什么删除缓存,而不是更新缓存?
在 Cache-Aside 中,对于读请求的处理比较容易理解,但在写请求中,可能会有读者提出疑问,为什么要删除缓存,而不是更新缓存?站在符合直觉的角度来看,更新缓存是一个容易被理解的方案,但站在性能和安全的角度,更新缓存则可能会导致一些不好的后果。
首先是性能 ,当该缓存对应的结果需要消耗大量的计算过程才能得到时,比如需要访问多张数据库表并联合计算,那么在写 *** 作中更新缓存的动作将会是一笔不小的开销。同时,当写 *** 作较多时,可能也会存在刚更新的缓存还没有被读取到,又再次被更新的情况(这常被称为缓存扰动),显然,这样的更新是白白消耗机器性能的,会导致缓存利用率不高。
而等到读请求未命中缓存时再去更新,也符合懒加载的思路,需要时再进行计算。删除缓存的 *** 作不仅是幂等的,可以在发生异常时重试,而且写-删除和读-更新在语义上更加对称。
其次是安全 ,在并发场景下,在写请求中更新缓存可能会引发数据的不一致问题。参考下面的图示,若存在两个来自不同线程的写请求,首先来自线程 1 的写请求更新了数据库(step 1),接着来自线程 2 的写请求再次更新了数据库(step 3),但由于网络延迟等原因,线程 1 可能会晚于线程 2 更新缓存(step 4 晚于 step 3),那么这样便会导致最终写入数据库的结果是来自线程 2 的新值,写入缓存的结果是来自线程 1 的旧值,即缓存落后于数据库,此时再有读请求命中缓存(step 5),读取到的便是旧值。
2、为什么先更新数据库,而不是先删除缓存?
另外,有读者也会对更新数据库和删除缓存的时序产生疑问,那么为什么不先删除缓存,再更新数据库呢?在单线程下,这种方案看似具有一定合理性,这种合理性体现在删除缓存成功。
但更新数据库失败的场景下,尽管缓存被删除了,下次读 *** 作时,仍能将正确的数据写回缓存,相对于 Cache-Aside 中更新数据库成功,删除缓存失败的场景来说,先删除缓存的方案似乎更合理一些。那么,先删除缓存有什么问题呢?
问题仍然出现在并发场景下,首先来自线程 1 的写请求删除了缓存(step 1),接着来自线程 2 的读请求由于缓存的删除导致缓存未命中,根据 Cache-Aside 模式,线程 2 继而查询数据库(step 2),但由于写请求通常慢于读请求,线程 1 更新数据库的 *** 作可能会晚于线程 2 查询数据库后更新缓存的 *** 作(step 4 晚于 step 3),那么这样便会导致最终写入缓存的结果是来自线程 2 中查询到的旧值,而写入数据库的结果是来自线程 1 的新值,即缓存落后于数据库,此时再有读请求命中缓存( step 5 ),读取到的便是旧值。
另外,先删除缓存,由于缓存中数据缺失,加剧数据库的请求压力,可能会增大缓存穿透出现的概率。
3、如果选择先删除缓存,再更新数据库,那如何解决一致性问题呢?
为了避免“先删除缓存,再更新数据库”这一方案在读写并发时可能带来的缓存脏数据,业界又提出了延时双删的策略,即在更新数据库之后,延迟一段时间再次删除缓存,为了保证第二次删除缓存的时间点在读请求更新缓存之后,这个延迟时间的经验值通常应稍大于业务中读请求的耗时。
延迟的实现可以在代码中 sleep 或采用延迟队列。显而易见的是,无论这个值如何预估,都很难和读请求的完成时间点准确衔接,这也是延时双删被诟病的主要原因。
4、那么 Cache-Aside 存在数据不一致的可能吗?
在 Cache-Aside 中,也存在数据不一致的可能性。在下面的读写并发场景下,首先来自线程 1 的读请求在未命中缓存的情况下查询数据库(step 1),接着来自线程 2 的写请求更新数据库(step 2),但由于一些极端原因,线程 1 中读请求的更新缓存 *** 作晚于线程 2 中写请求的删除缓存的 *** 作(step 4 晚于 step 3),那么这样便会导致最终写入缓存中的是来自线程 1 的旧值,而写入数据库中的是来自线程 2 的新值,即缓存落后于数据库,此时再有读请求命中缓存(step 5),读取到的便是旧值。
这种场景的出现,不仅需要缓存失效且读写并发执行,而且还需要读请求查询数据库的执行早于写请求更新数据库,同时读请求的执行完成晚于写请求。足以见得,这种 不一致场景产生的条件非常严格,在实际的生产中出现的可能性较小 。
除此之外,在并发环境下,Cache-Aside 中也存在读请求命中缓存的时间点在写请求更新数据库之后,删除缓存之前,这样也会导致读请求查询到的缓存落后于数据库的情况。
虽然在下一次读请求中,缓存会被更新,但如果业务层面对这种情况的容忍度较低,那么可以采用加锁在写请求中保证“更新数据库&删除缓存”的串行执行为原子性 *** 作(同理也可对读请求中缓存的更新加锁)。 加锁势必会导致吞吐量的下降,故采取加锁的方案应该对性能的损耗有所预期。
— 2 —
补偿机制
我们在上面提到了,在 Cache-Aside 中可能存在更新数据库成功,但删除缓存失败的场景,如果发生这种情况,那么便会导致缓存中的数据落后于数据库,产生数据的不一致的问题。
其实,不仅 Cache-Aside 存在这样的问题,在延时双删等策略中也存在这样的问题。针对可能出现的删除失败问题,目前业界主要有以下几种补偿机制。
1、删除重试机制
由于同步重试删除在性能上会影响吞吐量,所以常通过引入消息队列,将删除失败的缓存对应的 key 放入消息队列中,在对应的消费者中获取删除失败的 key ,异步重试删除。这种方法在实现上相对简单,但由于删除失败后的逻辑需要基于业务代码的 trigger 来触发 ,对业务代码具有一定入侵性。
鉴于上述方案对业务代码具有一定入侵性,所以需要一种更加优雅的解决方案,让缓存删除失败的补偿机制运行在背后,尽量少的耦合于业务代码。一个简单的思路是通过后台任务使用更新时间戳或者版本作为对比获取数据库的增量数据更新至缓存中,这种方式在小规模数据的场景可以起到一定作用,但其扩展性、稳定性都有所欠缺。
一个相对成熟的方案是基于 MySQL 数据库增量日志进行解析和消费,这里较为流行的是阿里巴巴开源的作为 MySQL binlog 增量获取和解析的组件 canal(类似的开源组件还有 Maxwell、Databus 等)。
canal sever 模拟 MySQL slave 的交互协议,伪装为 MySQL slave,向 MySQL master 发送 dump 协议,MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal sever ),canal sever 解析 binary log 对象(原始为 byte 流),可由 canal client 拉取进行消费,同时 canal server 也默认支持将变更记录投递到 MQ 系统中,主动推送给其他系统进行消费。
在 ack 机制的加持下,不管是推送还是拉取,都可以有效的保证数据按照预期被消费。当前版本的 canal 支持的 MQ 有 Kafka 或者 RocketMQ。另外, canal 依赖 ZooKeeper 作为分布式协调组件来实现 HA ,canal 的 HA 分为两个部分:
那么,针对缓存的删除 *** 作便可以在 canal client 或 consumer 中编写相关业务代码来完成。这样,结合数据库日志增量解析消费的方案以及 Cache-Aside 模型,在读请求中未命中缓存时更新缓存(通常这里会涉及到复杂的业务逻辑),在写请求更新数据库后删除缓存,并基于日志增量解析来补偿数据库更新时可能的缓存删除失败问题,在绝大多数场景下,可以有效的保证缓存的最终一致性。
另外需要注意的是,还应该隔离事务与缓存,确保数据库入库后再进行缓存的删除 *** 作。 比如考虑到数据库的主从架构,主从同步及读从写主的场景下,可能会造成读取到从库的旧数据后便更新了缓存,导致缓存落后于数据库的问题,这就要求对缓存的删除应该确保在数据库 *** 作完成之后。所以,基于 binlog 增量日志进行数据同步的方案,可以通过选择解析从节点的 binlog,来避免主从同步下删除缓存过早的问题。
3、数据传输服务 DTS
— 3 —
Read-Through
Read-Through 意为读穿透模式,它的流程和 Cache-Aside 类似,不同点在于 Read-Through 中多了一个访问控制层,读请求只和该访问控制层进行交互,而背后缓存命中与否的逻辑则由访问控制层与数据源进行交互,业务层的实现会更加简洁,并且对于缓存层及持久化层交互的封装程度更高,更易于移植。
— 4 —
Write-Through
Write-Through 意为直写模式,对于 Write-Through 直写模式来说,它也增加了访问控制层来提供更高程度的封装。不同于 Cache-Aside 的是,Write-Through 直写模式在写请求更新数据库之后,并不会删除缓存,而是更新缓存。
这种方式的 优势在于读请求过程简单 ,不需要查询数据库更新缓存等 *** 作。但其劣势也非常明显,除了上面我们提到的更新数据库再更新缓存的弊端之外,这种方案还会造成更新效率低,并且两个写 *** 作任何一次写失败都会造成数据不一致。
如果要使用这种方案, 最好可以将这两个 *** 作作为事务处理,可以同时失败或者同时成功,支持回滚,并且防止并发环境下的不一致 。另外,为了防止缓存扰动的频发,也可以给缓存增加 TTL 来缓解。
站在可行性的角度,不管是 Write-Through 模式还是 Cache-Aside 模式,理想状况下都可以通过分布式事务保证缓存层数据与持久化层数据的一致性,但在实际项目中,大多都对一致性的要求存在一些宽容度,所以在方案上往往有所折衷。
Write-Through 直写模式适合写 *** 作较多,并且对一致性要求较高的场景,在应用 Write-Through 模式时,也需要通过一定的补偿机制来解决它的问题。首先,在并发环境下,我们前面提到了先更新数据库,再更新缓存会导致缓存和数据库的不一致,那么先更新缓存,再更新数据库呢?
这样的 *** 作时序仍然会导致下面这样线程 1 先更新缓存,最后更新数据库的情况,即由于线程 1 和 线程 2 的执行不确定性导致数据库和缓存的不一致。这种由于线程竞争导致的缓存不一致,可以通过分布式锁解决,保证对缓存和数据库的 *** 作仅能由同一个线程完成。对于没有拿到锁的线程,一是通过锁的 timeout 时间进行控制,二是将请求暂存在消息队列中顺序消费。
在下面这种并发执行场景下,来自线程 1 的写请求更新了数据库,接着来自线程 2 的读请求命中缓存,接着线程 1 才更新缓存,这样便会导致线程 2 读取到的缓存落后于数据库。同理,先更新缓存后更新数据库在写请求和读请求并发时,也会出现类似的问题。面对这种场景,我们也可以加锁解决。
另在,在 Write-Through 模式下,不管是先更新缓存还是先更新数据库,都存在更新缓存或者更新数据库失败的情况,上面提到的重试机制和补偿机制在这里也是奏效的。
— 5 —
Write-Behind
Write behind 意为异步回写模式,它也具有类似 Read-Through/Write-Through 的访问控制层,不同的是,Write behind 在处理写请求时,只更新缓存而不更新数据库,对于数据库的更新,则是通过批量异步更新的方式进行的,批量写入的时间点可以选在数据库负载较低的时间进行。
在 Write-Behind 模式下,写请求延迟较低,减轻了数据库的压力,具有较好的吞吐性。但数据库和缓存的一致性较弱,比如当更新的数据还未被写入数据库时,直接从数据库中查询数据是落后于缓存的。同时,缓存的负载较大,如果缓存宕机会导致数据丢失,所以需要做好缓存的高可用。显然,Write behind 模式下适合大量写 *** 作的场景,常用于电商秒杀场景中库存的扣减。
— 6 —
Write-Around
如果一些非核心业务,对一致性的要求较弱,可以选择在 cache aside 读模式下增加一个缓存过期时间,在写请求中仅仅更新数据库,不做任何删除或更新缓存的 *** 作,这样,缓存仅能通过过期时间失效。这种方案实现简单,但缓存中的数据和数据库数据一致性较差,往往会造成用户的体验较差,应慎重选择。
— 7 —
总结
在解决缓存一致性的过程中,有多种途径可以保证缓存的最终一致性,应该根据场景来设计合适的方案,读多写少的场景下,可以选择采用“Cache-Aside 结合消费数据库日志做补偿”的方案,写多的场景下,可以选择采用“Write-Through 结合分布式锁”的方案 ,写多的极端场景下,可以选择采用“Write-Behind”的方案。
以上就是关于淘宝的数据库怎么搭建全部的内容,包括:淘宝的数据库怎么搭建、java培训有哪些课程、Linux就业方向都有哪些等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)