
暂无个人介绍
动力节点Java培训最新上线Java实验班,等你来测试自己适不适合学习Java编程哦! 随着互联网的发展,视频教程充斥着网络,很多人为了能够在视频教程中捞取一桶金,纷纷投入视频售卖的大军之中,其中不乏有一些劣质的视频课件让学员受害,今天我们就来看看在Java编程专业中,有什么好的Java自学教程视频,适合初学者的: 互联网上有很多Java视频教程,这里面鱼龙混杂,有的好有的差,有的讲解全面,有的讲解却是很片面。但是小编认为,一个好的Java视频应该具备以下几个条件: 第一,讲师的口齿清晰,声音洪亮,最主要的是普通话要好。试想如果讲师的声音不好,让人听起来就想睡觉的话,这样的视频教程谁能看得下去? 第二,视频录制清晰完整。 第三,课程内容讲解细致,条理清晰,部分内容有深入分析。我们在学习知识的时候,不但要知其然,更要知其所以然。这样才能够成长。 动力节点推出的一套301集Java入门视频教程,该视频专门针对零基础的学员录制,授课讲究通俗易懂,生动幽默。通过该视频的学习,相信你能够轻松地入门Java语言。注:掌握了该视频的知识,就可以具备报读Java就业班的条件。 第一阶段: 1、计算机基本原理,Java语言发展简史,Java开发环境的搭建,体验Java程序的开发,环境变量path和classpath的设置,Java程序的执行过程,Java反编译工具介绍。计算机常用进制二、八、十六的介绍,以及它们与十进制之间的相互转化,ASCII码。Java语法格式,常量和变量,变量的作用域,函数和函数的重载,运算符,程序流程控制,数组和操作数组的类。针对数组的常用查找、排序算法原理,以及其Java实现。 第二阶段: 2、对象的本质,理解面向对象,类与对象的关系,在程序中如何应用面向对象的思想解决问题。如何设计类,设计类的基本原则,类的实例化过程,类的细节:构造函数、this关键字、方法和方法的参数传递过程、static关键字、内部类,Java的垃极回收机制,Javadoc介绍。对象的三大特性:封装、继承和多态,以及相应的Java实现:子类对象的实例化过程、方法的覆盖、final关键字、抽象类、接口、继承的优点和缺点剖析。对象的多态性:子类和父类之间的转换、抽象类和接口在多态中的应用、多态带来的好处。Extensibility的理解、Extensibility的运用。常用设计模式:Singleton、Template、Strategy模式。 第三阶段: 3、JavaAPI介绍、Eclipse的安装和使用、String和StringBuffer、各种基本数据类型包装类,System和Runtime类,Date和DateFomat类等。 JavaCollectionsFramework:Collection、Set、List、ArrayList、Vector、LinkedList、Hashset、TreeSet、Map、HashMap、TreeMap、Iterator、Enumeration等常用集合类API。IO概念,File和FileRandomAccess类,字节流InputStream和OutputStream,字符流Reader和Writer,以及相应实现类,IO性能分析,字节和字符的转化流,包装流的概念,以及常用包装类,计算机编码。递归程序,Java的高级特性:反射、代理和泛型。 第四阶段: 4、多线程的概念,如何在程序中创建多线程(Thread、Runnable),线程安全问题,线程的同步,线程之间的通讯、死锁。Java图形用户介面编程(AWT、Swing),Java的事件处理机制,JavaApplet简介。 Java网络编程,网络通信底层协议TCP和UDP,以及其相应的Java实现:DatagramSocket,DatagramPacket,ServerSocket,Socket编程。网络通信常用应用层协议简介:HTTP、SMTP、POP3、MIME,以及WEB服务器的工作原理。编写网络聊天程序。 Java正则表达式API详解及其应用。 第五阶段: 5、JavaScript课程:HTML语言,HTML语言背景知识、HTML全局标签、格式标签、文件标签、超链接标签、图像标签、客户端图像地图、表格标签、帧标签、表单标签、头元素、分区标签。CSS介绍、CSS的设置方法、样式选择器、样式属性介绍,Dreamweaver的使用。 JavaScript编程,JavaScript语法、运算符、流程控制、函数、数组、对象、JavaScript的内部对象,JavaScript中专用于操作对象的语句。DHTML编程,理解DOM树,DOM对象,常用DOM对象的属性、方法和事件,编写事件处理程序、使用DOM操作HTML文档、遍历文档树上的节点、搜索文档中特定的元素、修改文档内容、往文档添加新内容、使用DOM操作XML文档。DHTML编程的实用案例:网页换肤、HTML表格排序等,以及JavaScript中的正则表达式及其应用。 Java自学需要严格的自律能力、良好的领悟能力以及优秀的教材,这不是一般人能够做到的。因此,要学Java,找个专业Java培训机构,有良师指导,学习更容易。通过专业的Java学校培训能更加系统的给你教学,而且在你遇到了困难也能及时解决。时间就是金钱。
动力节点Java培训最新上线Java实验班,等你来测试自己适不适合学习Java编程哦! 现在随着移动互联网时代和大数据时代的全面发展,Java软件开发又一次闪亮登场,吸引着人们的眼球,对Java软件开发好奇的人是越来越多,想要学Java软件开发的人也是越来越多,除了高校学习,以及自学Java软件开发外,目前最流行的还是各类IT培训机构了。那么,如何选择专业的Java软件工程师培训机构?在这里,小编给大家认真分析一下这个问题! IT培训机构的校区学习与生活环境如何? 来求学的学员们都是年轻人,向往美好舒适的学习与生活环境,这一点如果不行,基本上是可以否定的,想想,一个连基本的教学环境与生活环境都很差的培训机构,能指望有什么样的好老师在这里待吗?能指望学到什么好东东吗?所以说,当你要选择一家IT培训机构时,不光要看它的资质,更重要还是得看它的学习与生活环境如何! 学Java软件开发,专业的的Java软件教学名师是成功的关键; 有些IT培训机构聘请的教师在公司的工作中没有太多经验,或者在网上没有自己的教学工作。还有一些Java培训机构的教师水平不如一些基础学生。有些人甚至在没有任何工作经验的情况下训练自己然后训练在这种情况下,学员们应该擦亮眼睛; 一个专业的Java软件教学名师不光自己的Java编程基础扎实,同时,在引导学员学习的同时,更要注重培养学员自我的强大自学能力,因为在未来的工作当中,没有人会来教你,你所能做得事就是不断自学,从互联网中吸取能量。 学好Java软件开发,就业如何保障? 在所有的IT培训机构,关于就业是个永远摆脱不了的问题,很多中小机构往往都是靠忽悠,让学员参加几场面试会,就完事了。真正的坑学员。 真正好的IT培训机构,在就业保障,从两方面着手进行完美解决。 1、完面提升学员的软件开发Java实践项目能力,能独立完成企业指定项目的策划、开发、测试等等。让学员真正拥有面对企业面试的自信与从容。 2、与众多的招聘机构合作,与各大名企合作,给与学员众多的面试与选择机会。企业可以选学员,学员也可选自己喜欢的企业的。 看完小编给大家的分析,是不是完美的解决了你选择学习Java软件开发的IT培训机构大问题。动力节点做为国内的IT培训机构,在以上几方面都做得不错。优良的教学环境吸引着一批又一批学子来这里求学。而一批又一批学有所成的学员从这里出发,走向自己理想的人生之路。过自己想要的生活。
动力节点Java培训最新上线Java实验班,等你来测试自己适不适合学习Java编程哦!今天的主题我们来谈谈求职,每个程序员的生涯总有几次求职经历,对于求职者而言,在面对自己心仪的公司之前总要做足成分的准备,一份全面精细的面试题可以帮助我们减少很多麻烦,为此动力节点IT培训的小编特地做了Java面试题的文章,一方面可以帮助大家巩固基础,另一方面也希望帮助苦于面试的朋友。 Java中Runnable和Callable有什么不同? Runnable和Callable都代表那些要在不同的线程中执行的任务。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。它们的主要区别是Callable的call()方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。 接口:Collection Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。JavaSDK不提供直接继承自Collection的类,JavaSDK提供的类都是继承自Collection的“子接口”如List和Set。 所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。 主要的一个接口方法:booleanadd(Ojbectc) 虽然返回的是boolean,但不是表示添加成功与否,这个返回值表示的意义是add()执行后,集合的内容是否改变了(就是元素的数量、位置等有无变化)。类似的addAll,remove,removeAll,remainAll也是一样的。 用Iterator模式实现遍历集合 Collection有一个重要的方法:iterator(),返回一个Iterator(迭代器),用于遍历集合的所有元素。Iterator模式可以把访问逻辑从不同的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。典型的用法如下: 不需要维护遍历集合的“指针”,所有的内部状态都由Iterator来维护,而这个Iterator由集合类通过工厂方法生成。 每一种集合类返回的Iterator具体类型可能不同,但它们都实现了Iterator接口,因此,我们不需要关心到底是哪种Iterator,它只需要获得这个Iterator接口即可,这就是接口的好处,面向对象的威力。 要确保遍历过程顺利完成,必须保证遍历过程中不更改集合的内容(Iterator的remove()方法除外),所以,确保遍历可靠的原则是:只在一个线程中使用这个集合,或者在多线程中对遍历代码进行同步。 由Collection接口派生的两个接口是List和Set。 List接口 List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。和下面要提到的Set不同,List允许有相同的元素。 除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。 实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。 LinkedList类 LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。 注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List: Listlist=Collections.synchronizedList(newLinkedList(…)); ArrayList类 ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。 size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。 每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。 和LinkedList一样,ArrayList也是非同步的(unsynchronized)。 Vector类 Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。 Stack类 Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。 Set接口 Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。 很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。 请注意:必须小心操作可变对象(MutableObject)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。 Map接口 请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。 Hashtable类 Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。 添加数据使用put(key,value),取出数据使用get(key),这两个基本操作的时间开销为常数。 Hashtable通过initialcapacity和loadfactor两个参数调整性能。通常缺省的loadfactor0.75较好地实现了时间和空间的均衡。增大loadfactor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。 使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”: Hashtablenumbers=newHashtable(); numbers.put(“one”,newInteger(1)); numbers.put(“two”,newInteger(2)); numbers.put(“three”,newInteger(3)); 要取出一个数,比如2,用相应的key: Integern=(Integer)numbers.get(“two”); System.out.println(“two=”+n); 由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。 如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。 Hashtable是同步的。 HashMap类 HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即nullvalue和nullkey。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代器操作时间开销和HashMap的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者loadfactor过低。 WeakHashMap类 WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。 总结 如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。 如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。 要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。 尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。 今天就先说到这里吧,想要了解更多关于面试题的朋友,可以翻一翻小编之前的文章,新的面试题也还会不断更新,希望可以帮助到大家。
动力节点Java培训最新上线Java实验班,等你来测试自己适不适合学习Java编程哦! 很多的Java初学者从前辈的口中和各种资料中经常会听到一个词:Java框架,那么什么是“Java框架”?Java框架又包含哪些内容?今天小编为大家解答一下什么是Java的框架,主流的Java框架有哪些。 什么是Java框架 所谓的Java框架,简单理解是一个可复用的设计构件,它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程,表现为一组抽象类以及其实例之间协作的方法,它为构件复用提供了上下文(Context)关系。 常用的Java框架有哪些 Struts、Hibernate和Spring是我们Java开发中的常用框架,他们分别针对不同的应用场景给出最合适的解决方案。但你是否知道,这些知名框架最初是怎样产生的? 我们知道,传统的JavaWeb应用程序是采用JSP+Servlet+Javabean来实现的,这种模式实现了最基本的MVC分层,使的程序结构分为几层,有负责前台展示的JSP、负责流程逻辑控制的Servlet以及负责数据封装的Javabean。但是这种结构仍然存在问题:如JSP页面中需要使用符号嵌入很多的Java代码,造成页面结构混乱,Servlet和Javabean负责了大量的跳转和运算工作,耦合紧密,程序复用度低等等。 为了解决这些问题,出现了Struts框架,它是一个完美的MVC实现,它有一个中央控制类(一个Servlet),针对不同的业务,我们需要一个Action类负责页面跳转和后台逻辑运算,一个或几个JSP页面负责数据的输入和输出显示,还有一个Form类负责传递Action和JSP中间的数据。JSP中可以使用Struts框架提供的一组标签,就像使用HTML标签一样简单,但是可以完成非常复杂的逻辑。从此JSP页面中不需要出现一行包围的Java代码了。 如何熟练的使用这些Java框架 那么,作为一个Java开发者,如何熟练的使用这些框架呢,你应该掌握的Java技术有哪些? Java基础: Java原理和使用,包括基本的语言、语法知识和API JSP+Servlet,JavaWeb开发的基础 服务器: WebLogic的原理、使用和配置 Tomcat:轻量的JavaWeb容器,和WebLogic功能类似,使用简单、方便、免费、开源,但不支持EJB JBoss:类似于Tomcat,功能更强,支持EJB 这三种应用服务器至少掌握其中的一种是很必要的。 框架: Struts、Spring、Hibernate Tapestry:这是一个新的MVC框架,使用组件式开发,是显示层技术的发展趋势。 Flex和Laszlo:新的显示层技术,支持富客户端应用,是目前的较前沿的显示技术。 EJB:EJB的全称是EnterpriseJavaBeans,是Java中的商业应用组件技术。 JavaWeb开发周边技术: HTML、Javascript、CSS、XML、XSLT,这些是开发中经常使用到的,应该熟练掌握。 开发工具: Dreamweaver、Eclipse或Jbuilder、PL/SQL 数据库: Oracle必须会用、SQLServer掌握、MySQL可以在自己学习时使用。DB2那种东西就不要学了,国内太少使用了。 软件工程: 可以理解UML设计和使用UML进行简单设计。
在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是电子交易发展方面相关的,他们会问面试者很多令人混淆的Java线程问题,面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面,用于直接面向市场交易的高容量和低延时的电子交易系统在本质上是并发的。下面这些是我在不同时间不同地点喜欢问的Java线程问题,我没有提供答案,但只要可能我会给你线索,有些时候这些线索足够回答问题,现在引用Java5并发包关于并发工具和并发集合的问题正在增多。那些问题中ThreadLocal、BlockingQueue、CountingSemaphore和ConcurrentHashMap比较流行。 Java多线程面试题及回答(详细总结).jpg 15个Java多线程面试题及回答 1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。 2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。 3)在java中wait和sleep方法的不同? 通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。 4)用Java实现阻塞队列。 这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java5中的并发类来再写一次。 5)用Java写代码来解决生产者——消费者问题。 与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。 6)用Java编程一个会导致死锁的程序,你将怎么解决? 这是我最喜欢的Java线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlockfreecode(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免Java中的死锁来得到关于死锁的更多信息。 7)什么是原子操作,Java中的原子操作是什么? 非常简单的java线程面试问题,接下来的问题是你需要同步一个原子操作。 8)Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同? 自从Java5和Java内存模型改变以后,基于volatile关键字的线程问题越来越流行。应该准备好回答关于volatile变量怎样在并发环境中确保可见性、顺序性和一致性。 9)什么是竞争条件?你怎样发现和解决竞争? 这是一道出现在多线程面试的高级阶段的问题。大多数的面试官会问最近你遇到的竞争条件,以及你是怎么解决的。有些时间他们会写简单的代码,然后让你检测出代码的竞争条件。可以参考我之前发布的关于Java竞争条件的文章。在我看来这是最好的java线程面试问题之一,它可以确切的检测候选者解决竞争条件的经验,orwritingcodewhichisfreeofdataraceoranyotherracecondition。关于这方面最好的书是《ConcurrencypracticesinJava》。 10)你将如何使用threaddump?你将如何分析Threaddump? 在UNIX中你可以使用kill-3,然后threaddump将会打印日志,在windows中你可以使用”CTRL+Break”。非常简单和专业的线程面试问题,但是如果他问你怎样分析它,就会很棘手。 11)为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法? 这是另一个非常经典的java多线程面试问题。这也是我刚开始写线程程序时候的困惑。现在这个问题通常在电话面试或者是在初中级Java面试的第一轮被问到。这个问题的回答应该是这样的,当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。阅读我之前写的《start与run方法的区别》这篇文章来获得更多信息。 12)Java中你怎样唤醒一个阻塞的线程? 这是个关于线程和阻塞的棘手的问题,它有很多解决方法。如果线程遇到了IO阻塞,我并且不认为有一种方法可以中止线程。如果线程因为调用wait()、sleep()、或者join()方法而导致的阻塞,你可以中断线程,并且通过抛出InterruptedException来唤醒它。我之前写的《Howtodealwithblockingmethodsinjava》有很多关于处理线程阻塞的信息。 13)在Java中CycliBarriar和CountdownLatch有什么区别? 这个线程问题主要用来检测你是否熟悉JDK5中的并发包。这两个的区别是CyclicBarrier可以重复使用已经通过的障碍,而CountdownLatch不能重复使用。 14)什么是不可变对象,它对写并发应用有什么帮助? 另一个多线程经典面试问题,并不直接跟线程有关,但间接帮助很多。这个java面试问题可以变的非常棘手,如果他要求你写一个不可变对象,或者问你为什么String是不可变的。 15)你在多线程环境中遇到的共同的问题是什么?你是怎么解决它的? 多线程和并发程序中常遇到的有Memory-interface、竞争条件、死锁、活锁和饥饿。问题是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于实际应用的Java线程问题。
Remote Dictionary Server(Redis) 是一个开源的由Salvatore Sanfilippo使用ANSI C语言开发的key-value数据存储服务器。其值(value)可以是 字符串(String), 哈希(Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型,所以它通常也被称为数据结构服务器。 blob.png Redis特点 redis足够简单和稳定 支持丰富的数据结构 内存存储读写性能优秀 提供持久化的支持 支持事务操作 提供主从复制功能 Redis与memcache性能压力测试比较 blob.png Redis的典型应用场景: 一:缓存热点数据 热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存,redis的性能非常优秀。 二:计数器 诸如统计点击数、访问数、点赞数、评论数、浏览数等应用,由于单线程,可以避免并发问题,保证数据的正确性,并且100%毫秒级性能,同时开启Redis持久化,以便于持久化数据。 三:单线程机制 验证前端的重复请求,可以自由扩展类似情况),可以通过redis进行过滤,比如,每次请求将Request IP、参数、接口等hash作为key存储redis(幂等性请求),设置多长时间有效期,然后下次请求过来的时候先在redis中检索有没有这个key,进而验证是不是一定时间内过来的重复提交;再比如,限制用户登录的次数,比如一天错误登录次数10次等。 秒杀系统,基于redis是单线程特征,防止出现数据库超卖; 全局增量ID生成等; 四:排行榜 谁得分高谁排名在前,比如点击率最高、活跃度最高、销售数量最高、投票最高的前10名排行等等; 五:分布式锁 使用redis可以实现分布式锁,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件: 互斥性,在任意时刻,只有一个客户端能持有锁。 不会发生死锁,即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。 具有容错性,只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。 解铃还须系铃人,加锁和解锁必须是同一个客户端,客户端不能解他人加的锁。 六:Session存储 使用Redis的进行会话缓存(session cache)是非常常见的一种场景。用Redis缓存会话比其他存储(如Memcached)的优势在于:Redis提供持久化,目前大量的方案均采用了redis作为session的存储方案。
异常处理是Java软件开发中的一个重要部分,它是关乎每个应用的一个非功能性需求,是为了处理任何错误状况,比如资源不可访问,非法输入,空输入等等,Java提供了几个异常处理特性,以try,catch和finally关键字的形式内建于语言自身之中,Java编程语言也允许你创建新的异常,并通过使用throw和throws关键字抛出它们,事实上,在Java编程中,Java的异常处理不单单是知道语法这么简单,它必须遵循标准的JDK库,和几个处理错误和异常的开源代码,这里我们将讨论一些关于异常处理的Java最佳实践。 Java软件开发常出现哪些异常?要怎么处理?.jpg 为可恢复的错误使用检查型异常,为编程错误使用非检查型错误 选择检查型还是非检查型异常,对于Java编程人员来说,总是让人感到困惑。检查型异常保证你对错误条件提供异常处理代码,这是一种从语言到强制你编写健壮的代码的一种方式,但同时会引入大量杂乱的代码并导致其不可读。当然,如果你有替代品和恢复策略的话,捕捉异常并做些什么看起来似乎也在理,在Java编程中选择检查型异常还是运行时异常。 在finally程序块中关闭或者释放资源 这在Java编程中,是一个广为人知的最佳实践,在处理网络和IO类的时候,相当于一个标准。在finally块中关闭资源,在正常和异常执行的情况下,保证之前和稀缺资源的合理释放,这由finally块保证。从Java7开始,该语言有了一项更有趣的功能:资源管理自动化或者ARM块能实现这一功能。尽管如此,我们仍然要记住在finally块中关闭资源,这是对于释放像FileDescriptors这类,应用在socket和文件编程的情况下的有限资源很重要的。 使用标准异常 我们的第九条最佳实践建议使用标准和内置的Java异常。使用标准异常而不是每次创建我们自己的异常,对于维护性和一致性,不管是现在还是以后,都是最好的选择。重用标准异常使代码更具可读性,因为大部分Java开发人员对标准的像源自于JDK的RuntimeException异常,IllegalStateException异常,IllegalArgumentException异常或者NullPointerException异常,(开发者)他们能一眼就知道每种异常的目的,而不是在代码里查找或者在文档里查找用户定义的异常的目的。 记录任何方法抛出的异常 Java编程语言提供了throw和throws关键字来抛出异常,在javadoc中用@throw记录任何方法可能会抛出的异常。如果你编写API或者公共接口,这就变得非常重要。任何方法抛出的异常都有相应的文档记录,这样你就能下意识的提醒任何使用(该方法)的人。 这些就是所有在Java编程中在处理异常的时候需要遵循的最佳实践,让我们知道了什么是在Java编程中编写异常处理代码时需要遵循的实践。
如何准备转行学习Java,相信很多初学Java者都在考虑这个问题,如果你是在校学生,务必要在学好基础(比如计算机系统、算法、编译原理等等)的前提下,再考虑去进行下面的学习,第一部分:对于尚未做过Java工作的同学,包括一些在校生以及刚准备转行Java的同学。 一、Java基础首先去找一个Java基础教程学一下,这里推荐动力节点基础教学视频,学习Java基础的时候,应该尽量多动手,很多时候,你想当然的事情,等你写出来运行一下,你就会发现不是这么回事儿,不信你就试试,学完以上内容后,你应该对Java有一个基本的了解了,你可以用Java语言写出一些简单的程序,并且你用的是最简单的编辑器,比如记事本,这个时候,不要急于进入下一部分,留下几天好好写一些程序,尽可能熟悉这些基础内容。 二、Web开发等你写上几天程序以后,你往往会比较迷茫,因为你写的东西似乎看起来毫无用处,比如实现一个简单的计算器,读取一个文件等。这个时候你就应该去学着写一些让你觉得有意思的东西了,所以你应该学习更多的知识。这些内容主要是Web开发相关的内容,包括HTML/CSS/JS(前端页面)、Servlet/JSP(J2EE)以及Mysql(数据库)相关的知识。它们的学习顺序应该是从前到后,因此最先学习的应该是HTML/CSS/JS(前端页面) 你可以试着自己写一些页面,当然,你可以尽你最大的努力让它变得最漂亮。这部分内容对于后端Java来说,理论上不是特别重要,但至少要达到可以自己写出一些简单页面的水平。接下来,你需要学习的是Servlet/JSP(J2EE)部分,这部分是Java后端开发必须非常精通的部分,因此这部分是这三部分中最需要花精力的,而且这个时候,你要学会使用开发工具,而不能再使用记事本了,可以选择eclipse,当你下载安装好eclipse以后,看视频中的教程一步一步去学习,一定要多动手,关于Servlet/Jsp部分视频的选择,大家可以选择动力节点的。 最后一步,你需要学会使用数据库,mysql是个不错的入门选择,而且Java领域里主流的关系型数据库就是mysql。这部分一般在你学习Servlet/Jsp的时候,就会接触到的,其中的JDBC部分就是数据库相关的部分。你不仅要学会使用JDBC操作数据库,还要学会使用数据库客户端工具,比如navicat,sqlyog,二选一即可。 三、开发框架当你学会以上内容以后,这个时候你还不足以参加工作,你还需要继续深造。公司里为了提高开发的效率,会使用一些JavaWeb框架,因此你还需要学习一些开发框架。目前比较主流的是SSM框架,即spring、springmvc、mybatis。你需要学会这三个框架的搭建,并用它们做出一个简单的增删改查的Web项目。你可以不理解那些配置都是什么含义,以及为什么要这么做,这些留着后面你去了解。 但你一定要可以快速的利用它们三个搭建出一个Web框架,你可以记录下你第一次搭建的过程,相信自己,你一定会用到的。还要提一句的是,你在搭建SSM的过程中,可能会经常接触到一个叫maven的工具。这个工具也是你以后工作当中几乎是必须要使用的工具,所以你在搭建SSM的过程中,也可以顺便了解一下maven的知识。在你目前这个阶段,你只需要在网络上了解一下maven基本的使用方法即可,一些高端的用法随着你工作经验的增加,会逐渐接触到的。 四、当你完成开发框架的学习以后,你就该找工作了,在校的找实习,毕业的找全职。与此同时,在找工作的同时,你不应该停下你的学习,准确的说,是你在以后都不能停下学习,上面这些内容你只是囫囵吞枣的学会了使用,你可以逐步尝试着去了解更多的东西,网络是你最重要的老师,好了,说了这么多了,就到此为止吧,希望本文可以帮助到作为程序猿或即将成为程序猿的你。
在Java程序开发中的定制开发规范,想要把项目正规高效的跑起来。引入 Git 版本控制,Git-Flow 便成为了首选。今天动力节点Java学院来带你了解一下。一、为什么使用 git-flow当在团队开发中使用版本控制系统时,商定一个统一的工作流程是至关重要的。 Git 的确可以在各个方面做很多事情,然而,如果在你的团队中还没有能形成一个特定有效的工作流程,那么混乱就将是不可避免的。基本套路:你可以定义一个完全适合你自己项目的工作流程,或者使用一个别人定义好的。二、安装 git-flow我们使用 Homebrew 来安装 git-flow:1.brew install git-flow之后,通过 git-flow 来初始化项目:1.git flow init这时候就会有一些配置的操作,先默认操作:1.Initialized empty Git repository in /Users/jartto/Documents/git-flow-demo/.git/2.No branches exist yet. Base branches must be created now.3.Branch name for production releases: [master]4.Branch name for "next release" development: [develop]5.6.* Please tell me who you are.7.8.Run9. git config --global user.email "you@example.com" git config --global user.name "Your Name"12. 13.to set your account's default identity.14.Omit --global to set the identity only in this repository.15.16.fatal: unable to auto-detect email address (got 'jartto@bogon.(none)')17.fatal: Not a valid object name: 'master'.18.error: pathspec 'develop' did not match any file(s) known to git.19.20.How to name your supporting branch prefixes?21.Feature branches? [feature/]22.Release branches? [release/]23.Hotfix branches? [hotfix/]24.Support branches? [support/]25.Version tag prefix? []需要强调一点:git-flow 只是封装了 git 的命令。所以在我们初始化的时候,对仓库并没有其他改动,只是创建了几个分支。当然,如果你不想继续使用 git-flow ,那么只需要简单的停用 git-flow 的命令即可,不需要修改或者删除任何文件。为了验证一下,我们看下目前的分支,执行:1.git branch输出:1.* develop master很简单, develop 分支是我们日常开发的分支,会有很多改动。而 master 主要针对线上分支,下面会细说。 这里补充一点,我们可以通过 help 命令来查看用例。1.git flow help输出如下:1.usage: git flow 2.3.Available subcommands are: init Initialize a new git repo with support for the branching model. feature Manage your feature branches. release Manage your release branches. hotfix Manage your hotfix branches. support Manage your support branches. version Shows version information.10. 11.Try 'git flow help' for details.如果我要看 feature 相关命令呢?1.git flow feature help使用规范就会列出来:1.usage: git flow feature [list] [-v] git flow feature start [-F] [] git flow feature finish [-rFk] git flow feature publish git flow feature track git flow feature diff [] git flow feature rebase [-i] [] git flow feature checkout [] git flow feature pull []三、分支模式 git-flow 模式会预设两个主分支在仓库中: 1. master 只能用来包含产品代码 我们不能直接工作在这个 master 分支上,而是在其他指定的,独立的特性分支中。不直接提交改动到 master 分支上也是很多工作流程的一个共同的规则。2. develop 是你进行任何新的开发的基础分支 当你开始一个新的功能分支时,它将是开发的基础。另外,该分支也汇集所有已经完成的功能,并等待被整合到 master 分支中。 上面说到的这两个分支被称作为长期分支,它们会存活在项目的整个生命周期中。而其他的分支,例如针对功能的分支,针对发行的分支,仅仅只是临时存在的。它们是根据需要来创建的,当它们完成了自己的任务之后就会被删除掉。 四、明确分支功能1. master 分支 最为稳定功能比较完整的随时可发布的代码,即代码开发完成,经过测试,没有明显的 bug,才能合并到 master 中。请注意永远不要在 master 分支上直接开发和提交代码,以确保 master 上的代码一直可用;2. develop 分支 用作平时开发的主分支,并一直存在,永远是功能最新最全的分支,包含所有要发布 到下一个 release 的代码,主要用于合并其他分支,比如 feature 分支; 如果修改代码,新建 feature分支修改完再合并到 develop 分支。所有的 feature、 release 分支都是从 develop 分支上拉的。3. feature 分支 这个分支主要是用来开发新的功能,一旦开发完成,通过测试没问题,我们合并回 develop 分支进入下一个 release 。4. release 分支 用于发布准备的专门分支。当开发进行到一定程度,或者说快到了既定的发布日,可以发布时,建立一个 release 分支并指定版本号(可以在 finish 的时候添加)。开发人员可以对 release 分支上的代码进行集中测试和修改 bug。(这个测试,测试新功能与已有的功能是否有冲突,兼容性)全部完成经过测试没有问题后,将 release 分支上的代码合并到 master 分支和 develop 分支。5. hotfix 分支 用于修复线上代码的 bug 。从 master 分支上拉。完成 hotfix 后,打上 tag 我们合并回 master 和 develop 分支。需要注意:所有开发分支从 develop 分支拉。所有 hotfix 分支从 master 拉。所有在 master 上的提交都必要要有 tag,方便回滚。只要有合并到 master 分支的操作,都需要和 develop 分支合并下,保证同步。master 和 develop 分支是主要分支,主要分支每种类型只能有一个,派生分支每个类型可以同时存在多个。五、关于 Feature 分支在 Git-flow 中,通过使用 Feature 分支,使得我们在同一时间开发多个分支更加简单。 我们接到了一个 Test1 需求,使用 feature start 来启动:git flow feature start test1当我们开始一个新的 feature 开发后:1.Switched to a new branch 'feature/test1'2.3.Summary of actions:4.- A new branch 'feature/test1' was created, based on 'develop'5.- You are now on branch 'feature/test1'6.7.Now, start committing on your feature. When done, use:8. git flow feature finish test1我们自动切到了 feature/test1 分支下,正好开始我们的开发,建一个文件先: 1.vi main.js写入 console.log('hello jartto'); 接着提交我们的变更:1.git add .2.git commit -m 'feat: add console.log'好了,现在我们开发完了,结束 feature 开发:1.git flow feature finish test1控制台输出了:1.Switched to branch 'develop'2.Updating d975789..27e920c3.Fast-forward4.main.js | 1 +5.1 file changed, 1 insertion(+)6.create mode 100644 main.js7.Deleted branch feature/test1 (was 27e920c).8.9.Summary of actions:10.- The feature branch 'feature/test1' was merged into 'develop'11.- Feature branch 'feature/test1' has been removed12.- You are now on branch 'develop'这里做了几件事情: 1.将 feature/test1 分支合并到了 develop 分支; 2.删除了 feature/test1; 3.切换到 develop 分支;需要注意: git-flow 使用的命令是:1.git merge —no-ff feature/test1这样,在我们移除 feature 分支之前,是不会丢失任何历史记录的。如果你还不了解 --no-ff 相关知识,可以先看看:Git merge 的 –ff 和 –no-ff。接着,我们看一下变更记录:1.git log --oneline输出:1.27e920c (HEAD -> develop) feat: add console.log2.d975789 (master) Initial commit六、release 分支-版本发布当我们开发完毕,需要去发布新版本的时候,我们可以使用:1.git flow release start 0.1.0控制台会有一些提示:1.Switched to a new branch 'release/0.1.0'2.3.Summary of actions:4.- A new branch 'release/0.1.0' was created, based on 'develop'5.- You are now on branch 'release/0.1.0'6.7.Follow-up actions:8.- Bump the version number now!9.- Start committing last-minute fixes in preparing your release10.- When done, run:11. git flow release finish '0.1.0'很清晰,我们简单说一下: 1.基于 develop 分支新建了 release/0.1.0 分支; 2.切换至 release/0.1.0 分支; 又出现了新问题: 1.这是什么意思: Bumpthe version number now! 2. last-minute fixes 又是什么意思?那接下来我们要做什么呢?不着急,按照提示一步步来。我们修改了代码,进行add,和 commit 之后,执行:1.git flow release finish 0.1.0这个过程中间可能出现异常:1.fatal: no tag message?2.Tagging failed. Please run finish again to retry.输出:1.Switched to branch 'develop'2.Merge made by the 'recursive' strategy.3.test.js | 04.1 file changed, 0 insertions(+), 0 deletions(-)5.create mode 100644 test.js6.Deleted branch release/0.1.0 (was 0426707).7.8.Summary of actions:9.- Latest objects have been fetched from 'origin'10.- Release branch has been merged into 'master'11.- The release was tagged '0.1.0'12.- Release branch has been back-merged into 'develop'13.- Release branch 'release/0.1.0' has been deleted这里主要有几个操作: 1.首先, git-flow 会拉取远程仓库,以确保目前是最新的版本。 2.然后, release 的内容会被合并到 master 和 develop 两个分支中去,这样不仅产品代码为最新的版本,而且新的功能分支也将基于最新代码。 3.为便于识别和做历史参考, release 提交会被标记上这个 release 的 Tag。 4.清理操作,版本分支会被删除,并且回到 develop。七、Hotfix 线上代码如果线上代码有问题,这时候你需要紧急修复呢?我们可以使用 git flow hotfix :1.git flow hotfix start jartto看一下执行了什么:1.Switched to a new branch 'hotfix/jartto'2.3.Summary of actions:4.- A new branch 'hotfix/jartto' was created, based on 'master'5.- You are now on branch 'hotfix/jartto'6.7.Follow-up actions:8.- Bump the version number now!9.- Start committing your hot fixes10.- When done, run:11. git flow hotfix finish 'jartto'接着我们新建了目录: 1.mkdir assets放入一张图片,修改完毕,提交并结束 hotfix:1.git add .2.git commit -m 'fix: assets img'3.git flow hotfix finish 'jartto'看一下 git-flow 有哪些操作:1.Switched to branch 'master'2.Merge made by the 'recursive' strategy.3.assets/git-flow.png | Bin 0 -> 25839 bytes4.1 files changed, 0 insertions(+), 0 deletions(-)5.create mode 100644 assets/git-flow.png6.Switched to branch 'develop'7.Merge made by the 'recursive' strategy.8.assets/git-flow.png | Bin 0 -> 25839 bytes9.1 files changed, 0 insertions(+), 0 deletions(-)10.create mode 100644 assets/git-flow.png11.Deleted branch hotfix/jartto (was a734849).12.13.Summary of actions:14.- Latest objects have been fetched from 'origin'15.- Hotfix branch has been merged into 'master'16.- The hotfix was tagged 'jartto'17.- Hotfix branch has been back-merged into 'develop'18.- Hotfix branch 'hotfix/jartto' has been deleted查看一下目前的 Tag 情况:1.git tag正是我们上面添加的两个标签:1.0.1.02.jartto总结一下: 1.完成的改动会被合并到 master 中,同样也会合并到 develop 分支中,这样就可以确保这个错误不会再次出现在下一个 release 中。 2.这个 hotfix 程序将被标记起来以便于参考。 3.这个 hotfix 分支将被删除,然后切换到 develop 分支上去。八、git-flow 流程图示恭喜你,到这里你已经完成了 git-flow 的基本流程。为了更加整体的理解工作流,我们来看看下面这张流程图: Java清清楚楚,是不是和我们上面的过程一模一样。九、参考动力节点Java架构师班深度剖析Java底层原理,热门技术深入探讨,前沿技术深入解读,大项目实战重构,从0到1做架构,从全局思维出发,带你把控大型项目中别人忽略的重要细节节点,站在巨人肩膀上学习架构师,带你领会架构师不一样的视野
想要成为一名出色的Java架构师,必须要彻底了解Java的一个重要的特点那就JVM 动力节点Java学院寄语 前些天面试了阿里的实习生,问到关于Dalvik虚拟机能不能执行class文件,我当时的回答是不能,但是它执行的是class转换的dex文件。 当面试官继续问,为什么不能执行class文件时,我却只能回答Dalvik虚拟机内部的优化原因,却不能正确回答具体的原因。 其实周志明的这本书就有回答:Dakvik并不是一个Java虚拟机,它没有遵循Java虚拟机规范,不能执行Java的class文件,使用的是寄存器架构而不是JVM中常见的栈架构,但是它与Java又有着千丝万缕的关系,它执行的dex文件可以通过class文件转化而来。 其实在本科期间,就有接触过《深入理解Java虚拟机》,但是一直以来都没去仔细研读,现在回头想想实在是觉得可惜!研一期间花了不少时间研读,现在准备找工作了,发现好多内容看了又忘。索性写一篇文章,把这本书的知识点做一个总结。当然了,如果你想看比较详细的内容,可以翻看《深入理解Java虚拟机》。 JVM内存区域我们在编写程序时,经常会遇到OOM(out of Memory)以及内存泄漏等问题。为了避免出现这些问题,我们首先必须对JVM的内存划分有个具体的认识。JVM将内存主要划分为:方法区、虚拟机栈、本地方法栈、堆、程序计数器。JVM运行时数据区如下: 程序计数器程序计数器是线程私有的区域,很好理解嘛~,每个线程当然得有个计数器记录当前执行到那个指令。占用的内存空间小,可以把它看成是当前线程所执行的字节码的行号指示器。如果线程在执行Java方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果执行的是Native方法,这个计数器的值为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。Java虚拟机栈与程序计数器一样,Java虚拟机栈也是线程私有的。其生命周期与线程相同。如何理解虚拟机栈呢?本质上来讲,就是个栈。里面存放的元素叫栈帧,栈帧好像很复杂的样子,其实它很简单!它里面存放的是一个函数的上下文,具体存放的是执行的函数的一些数据。执行的函数需要的数据无非就是局部变量表(保存函数内部的变量)、操作数栈(执行引擎计算时需要),方法出口等等。执行引擎每调用一个函数时,就为这个函数创建一个栈帧,并加入虚拟机栈。换个角度理解,每个函数从调用到执行结束,其实是对应一个栈帧的入栈和出栈。注意这个区域可能出现的两种异常:一种是StackOverflowError,当前线程请求的栈深度大于虚拟机所允许的深度时,会抛出这个异常。制造这种异常很简单:将一个函数反复递归自己,最终会出现栈溢出错误(StackOverflowError)。另一种异常是OutOfMemoryError异常,当虚拟机栈可以动态扩展时(当前大部分虚拟机都可以),如果无法申请足够多的内存就会抛出OutOfMemoryError,如何制作虚拟机栈OOM呢,参考一下代码: 1.public void stackLeakByThread(){ 2.while(true){ 3.new Thread(){ 4.public void run(){ 5.while(true){ 6.} 7.} 8.}.start() 9.} 10.} ``这段代码有风险,可能会导致操作系统假死,请谨慎使用~~~本地方法栈本地方法栈与虚拟机栈所发挥的作用很相似,他们的区别在于虚拟机栈为执行Java代码方法服务,而本地方法栈是为Native方法服务。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。 Java堆Java堆可以说是虚拟机中最大一块内存了。它是所有线程所共享的内存区域,几乎所有的实例对象都是在这块区域中存放。当然,睡着JIT编译器的发展,所有对象在堆上分配渐渐变得不那么“绝对”了。Java堆是垃圾收集器管理的主要区域。由于现在的收集器基本上采用的都是分代收集算法,所有Java堆可以细分为:新生代和老年代。在细致分就是把新生代分为:Eden空间、From Survivor空间、To Survivor空间。当堆无法再扩展时,会抛出OutOfMemoryError异常。方法区方法区存放的是类信息、常量、静态变量等。方法区是各个线程共享区域,很容易理解,我们在写Java代码时,每个线程度可以访问同一个类的静态变量对象。由于使用反射机制的原因,虚拟机很难推测那个类信息不再使用,因此这块区域的回收很难。另外,对这块区域主要是针对常量池回收,值得注意的是JDK1.7已经把常量池转移到堆里面了。同样,当方法区无法满足内存分配需求时,会抛出OutOfMemoryError。制造方法区内存溢出,注意,必须在JDK1.6及之前版本才会导致方法区溢出,原因后面解释,执行之前,可以把虚拟机的参数-XXpermSize和-XX:MaxPermSize限制方法区大小。 1.List list =new ArrayList(); 2.int i =0; 3.while(true){ 4.list.add(String.valueOf(i).intern()); 5.} 运行后会抛出java.lang.OutOfMemoryError:PermGen space异常。解释一下,String的intern()函数作用是如果当前的字符串在常量池中不存在,则放入到常量池中。上面的代码不断将字符串添加到常量池,最终肯定会导致内存不足,抛出方法区的OOM。下面解释一下,为什么必须将上面的代码在JDK1.6之前运行。我们前面提到,JDK1.7后,把常量池放入到堆空间中,这导致intern()函数的功能不同,具体怎么个不同法,且看看下面代码: 1.String str1 =new StringBuilder("hua").append("chao").toString(); 2.System.out.println(str1.intern()==str1); 3.String str2=new StringBuilder("ja").append("va").toString(); 4.System.out.println(str2.intern()==str2); 这段代码在JDK1.6和JDK1.7运行的结果不同。JDK1.6结果是:false,false ,JDK1.7结果是true, false。原因是:JDK1.6中,intern()方法会吧首次遇到的字符串实例复制到常量池中,返回的也是常量池中的字符串的引用,而StringBuilder创建的字符串实例是在堆上面,所以必然不是同一个引用,返回false。在JDK1.7中,intern不再复制实例,常量池中只保存首次出现的实例的引用,因此intern()返回的引用和由StringBuilder创建的字符串实例是同一个。为什么对str2比较返回的是false呢?这是因为,JVM中内部在加载类的时候,就已经有"java"这个字符串,不符合“首次出现”的原则,因此返回false。垃圾回收(GC)JVM的垃圾回收机制中,判断一个对象是否死亡,并不是根据是否还有对象对其有引用,而是通过可达性分析。对象之间的引用可以抽象成树形结构,通过树根(GC Roots)作为起点,从这些树根往下搜索,搜索走过的链称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明这个对象是不可用的,该对象会被判定为可回收的对象。那么那些对象可作为GC Roots呢?主要有以下几种:1.虚拟机栈(栈帧中的本地变量表)中引用的对象。2.方法区中类静态属性引用的对象。3.方法区中常量引用的对象4.本地方法栈中JNI(即一般说的Native方法)引用的对象。另外,Java还提供了软引用和弱引用,这两个引用是可以随时被虚拟机回收的对象,我们将一些比较占内存但是又可能后面用的对象,比如Bitmap对象,可以声明为软引用货弱引用。但是注意一点,每次使用这个对象时候,需要显示判断一下是否为null,以免出错。 三种常见的垃圾收集算法1.标记-清除算法首先,通过可达性分析将可回收的对象进行标记,标记后再统一回收所有被标记的对象,标记过程其实就是可达性分析的过程。这种方法有2个不足点:效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量的不连续的内存碎片。2.复制算法为了解决效率问题,复制算法是将内存分为大小相同的两块,每次只使用其中一块。当这块内存用完了,就将还存活的对象复制到另一块内存上面。然后再把已经使用过的内存一次清理掉。这使得每次只对半个区域进行垃圾回收,内存分配时也不用考虑内存碎片情况。但是,这代价实在是让人无法接受,需要牺牲一般的内存空间。研究发现,大部分对象都是“朝生夕死”,所以不需要安装1:1比例划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和一块Survivor空间,默认比例为Eden:Survivor=8:1.新生代区域就是这么划分,每次实例在Eden和一块Survivor中分配,回收时,将存活的对象复制到剩下的另一块Survivor。这样只有10%的内存会被浪费,但是带来的效率却很高。当剩下的Survivor内存不足时,可以去老年代内存进行分配担保。如何理解分配担保呢,其实就是,内存不足时,去老年代内存空间分配,然后等新生代内存缓过来了之后,把内存归还给老年代,保持新生代中的Eden:Survivor=8:1.另外,两个Survivor分别有自己的名称:From Survivor、To Survivor。二者身份经常调换,即有时这块内存与Eden一起参与分配,有时是另一块。因为他们之间经常相互复制。3.标记-整理算法标记整理算法很简单,就是先标记需要回收的对象,然后把所有存活的对象移动到内存的一端。这样的好处是避免了内存碎片。类加载机制类从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。其中加载、验证、准备、初始化、和卸载这5个阶段的顺序是确定的。而解析阶段不一定:它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的运行时绑定。关于初始化:JVM规范明确规定,有且只有5中情况必须执行对类的初始化(加载、验证、准备自然再此之前要发生):1.遇到new、getstatic、putstatic、invokestatic,如果类没有初始化,则必须初始化,这几条指令分别是指:new新对象、读取静态变量、设置静态变量,调用静态函数。2.使用java.lang.reflect包的方法对类进行反射调用时,如果类没初始化,则需要初始化3.当初始化一个类时,如果发现父类没有初始化,则需要先触发父类初始化。4.当虚拟机启动时,用户需要制定一个执行的主类(包含main函数的类),虚拟机会先初始化这个类。5.但是用JDK1.7启的动态语言支持时,如果一个MethodHandle实例最后解析的结果是REF_getStatic、REF_putStatic、Ref_invokeStatic的方法句柄时,并且这个方法句柄所对应的类没有进行初始化,则要先触发其初始化。另外要注意的是:通过子类来引用父类的静态字段,不会导致子类初始化: 1.public class SuperClass{ 2.public static int value=123; 3.static{ 4.System.out.printLn("SuperClass init!"); 5.} 6.} 7.public class SubClass extends SuperClass{ 8.static{ 9.System.out.println("SubClass init!"); 10.} 11.} 12.public class Test{ 13.public static void main(String[] args){ 14.System.out.println(SubClass.value); 15.} 16.} 最后只会打印:SuperClass init!对应静态变量,只有直接定义这个字段的类才会被初始化,因此通过子类类引用父类中定义的静态变量只会触发父类初始化而不会触发子类初始化。通过数组定义来引用类,不会触发此类的初始化: 1.public class Test{ 2.public static void main(String[] args){ 3.SuperClass[] sca=new SuperClass[10]; 4.} 5.} 常量会在编译阶段存入调用者的常量池,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类初始化,示例代码如下: 1.public class ConstClass{ 2.public static final String HELLO_WORLD="hello world"; 3.static { 4.System.out.println("ConstClass init!"); 5.} 6.} 7.public class Test{ 8.public static void main(String[] args){ 9.System.out.print(ConstClass.HELLO_WORLD); 10.} 11.} 上面代码不会出现ConstClass init!加载加载过程主要做以下3件事1.通过一个类的全限定名称来获取此类的二进制流2.强这个字节流所代表的静态存储结构转化为方法区的运行时数据结构3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。验证这个阶段主要是为了确保Class文件字节流中包含信息符合当前虚拟机的要求,并且不会出现危害虚拟机自身的安全。准备准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都在方法区中分配。首先,这个时候分配内存仅仅包括类变量(被static修饰的变量),而不包括实例变量。实例变量会在对象实例化时随着对象一起分配在java堆中。其次这里所说的初始值“通常情况下”是数据类型的零值,假设一个类变量定义为 1.public static int value=123; 那变量value在准备阶段后的初始值是0,而不是123,因为还没有执行任何Java方法,而把value赋值为123是在程序编译后,存放在类构造函数()方法中。解析解析阶段是把虚拟机中常量池的符号引用替换为直接引用的过程。初始化类初始化时类加载的最后一步,前面类加载过程中,除了加载阶段用户可以通过自定义类加载器参与以外,其余动作都是虚拟机主导和控制。到了初始化阶段,才是真正执行类中定义Java程序代码。准备阶段中,变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划初始化类变量。初始化过程其实是执行类构造器()方法的过程。()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的。收集的顺序是按照语句在源文件中出现的顺序。静态语句块中只能访问定义在静态语句块之前的变量,定义在它之后的变量可以赋值,但不能访问。如下所示: 1.public class Test{ 2.static{ 3.i=0; 4.System.out.print(i); 5.} 6.static int i=1; 7.} ()方法与类构造函数(或者说实例构造器())不同,他不需要显式地调用父类构造器,虚拟机会保证子类的()方法执行之前,父类的()已经执行完毕。类加载器关于自定义类加载器,和双亲委派模型,这里不再提,写了几个小时了,该洗洗睡了~动力节点Java架构师班深度剖析Java底层原理,热门技术深入探讨,前沿技术深入解读,大项目实战重构,从0到1做架构,从全局思维出发,带你把控大型项目中别人忽略的重要细节节点,站在巨人肩膀上学习架构师,带你领会架构师不一样的视野
动力节点Java学院整理 1、java 中有几种类型的流?JDK 为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类? 字节流,字符流。字节流继承于 InputStream OutputStream,字符流继承于 Reader Writer。在 java.io 包中还有许多其他的流,低层流与调层流,高层流主要是为了提高性能和使用方便。 2、启动一个线程是用 run()还是 start()?启动一个线程是调用 start()方法,启动线程并调用 run 方法。 3、线程的基本概念、线程的基本状态以及状态之间的关系 线程是进程内的并发,没有自已内存空间,共享进程的,线程间的通信成本较低。Java 中的线程有四种状态分别是:运行、就绪、挂起、结束。 4、多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么? 用什么关键字修饰同步方法?stop()和 suspend()方法为何不推荐使用?Extends ThreadImplements Runnable同步Public synchronized aa(){}Public void cc(object aa){synchronized(aa){}}用 synchoronized 修饰同步方法。答:多线程有两种实现方法,分别是继承 Thread 类与实现 Runnable 接口同步的实现方面有两种,分别是 synchronized,wait 与 notify反对使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用 suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread 类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个 notify()重新启动线程。 5、集合框架有什么?Collection MapList set HashMapArrayList linkedList HashSet TreeSet 动力节点Java学院整理发布 转载请注明出处
平时阅读一些远吗分析类文章或是设计应用架构时没少与UML类图打交道。实际上,UML类图中最常用到的元素五分钟就能掌握,下面赶紧来一起认识一下它吧: 一、类的属性的表示方式 在UML类图中,类使用包含类名、属性(field) 和方法(method) 且带有分割线的矩形来表示,比如下图表示一个Employee类,它包含name,age和email这3个属性,以及modifyInfo()方法。 那么属性/方法名称前加的加号和减号是什么意思呢?它们表示了这个属性或方法的可见性,UML类图中表示可见性的符号有三种: · + :表示public · - :表示private · #:表示protected(friendly也归入这类) 因此,上图中的Employee类具有3个私有属性和一个公有方法。 实际上,属性的完整表示方式是这样的: 可见性 名称 :类型 [ = 缺省值] 中括号中的内容表示是可选的 二、类的方法的表示方式 上图中我们已经看到了方法的表示形式。实际上,方法的完整表示方式如下: 可见性 名称(参数列表) [ : 返回类型] 同样,中括号中的内容是可选的。 比如在下图的Demo类中,定义了3个方法: · public方法method1接收一个类型为Object的参数,返回值类型为void · protected方法method2无参数,返回值类型为String · private方法method3接收类型分别为int、int[]的参数,返回值类型为int 三、类与类之间关系的表示方式 1、关联关系 关联关系又可进一步分为单向关联、双向关联和自关联。 (1)单向关联 我们可以看到,在UML类图中单向关联用一个带箭头的直线表示。上图表示每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现。 (2)双向关联 从上图中我们很容易看出,所谓的双向关联就是双方各自持有对方类型的成员变量。在UML类图中,双向关联用一个不带箭头的直线表示。上图中在Customer类中维护一个Product[]数组,表示一个顾客购买了那些产品;在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买。 (3)自关联 自关联在UML类图中用一个带有箭头且指向自身的直线表示。上图的意思就是Node类包含类型为Node的成员变量,也就是“自己包含自己”。 2、聚合关系 上图中的Car类与Engine类就是聚合关系(Car类中包含一个Engine类型的成员变量)。由上图我们可以看到,UML中聚合关系用带空心菱形和箭头的直线表示。聚合关系强调是“整体”包含“部分”,但是“部分”可以脱离“整体”而单独存在。比如上图中汽车包含了发动机,而发动机脱离了汽车也能单独存在。 3、组合关系 组合关系与聚合关系见得最大不同在于:这里的“部分”脱离了“整体”便不复存在。比如下图: 显然,嘴是头的一部分且不能脱离了头而单独存在。在UML类图中,组合关系用一个带实心菱形和箭头的直线表示。 4、依赖关系 从上图我们可以看到,Driver的drive方法只有传入了一个Car对象才能发挥作用,因此我们说Driver类依赖于Car类。在UML类图中,依赖关系用一条带有箭头的虚线表示。 5、继承关系 继承关系对应的是extend关键字,在UML类图中用带空心三角形的直线表示,如下图所示中,Student类与Teacher类继承了Person类。 6、接口实现关系 这种关系对应implement关键字,在UML类图中用带空心三角形的虚线表示。如下图中,Car类与Ship类都实现了Vehicle接口。 到了这里,UML类图中最常见的表示方式我们就介绍完了,有了这些我们就能读懂常见的UML类图了,剩下的遇到时再查即可。 三、参考资料 http://www.uml.org.cn/oobject/201211231.asp
事务隔离级别: @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用 @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读) @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读) @Transactional(isolation = Isolation.SERIALIZABLE):串行化 1.READ UNCIMMITTED(未提交读) 事务中的修改,即使没有提交,其他事务也可以看得到,比如说上面的两步这种现象就叫做脏读,这种隔离级别会引起很多问题,如无必要,不要随便使用 例子:还是售票系统,小明和小花是售票员,他们分别是两个不同窗口的员工,现在售票系统只剩下3张票,此时A来小华这里买3张票,B来小明买票,小华查到余票还有就给接了订单,就要执行第三步的时候,小明接到B的请求查询有没有余票。看到小华卖出了3张票,于是拒绝卖票。但是小华系统出了问题,第三步执行失败,数据库为保证原子性,数据进行了回滚,也就是说一张票都没卖出去。 总结:这就是事务还没提交,而别的事务可以看到他其中修改的数据的后果,也就是脏读。 2.READ COMMITTED(提交读) 大多数数据库系统的默认隔离级别是READ COMMITTED,这种隔离级别就是一个事务的开始,只能看到已经完成的事务的结果,正在执行的,是无法被其他事务看到的。这种级别会出现读取旧数据的现象 例子:还是小明小华销售员,余票3张,A来小华那里请求3张订票单,小华接受订单,要卖出3张票,上面的销售步骤执行中的时候,B也来小明那里买票,由于小华的销售事务执行到一半,小明事务没有看到小华的事务执行,读到的票数是3,准备接受订单的时候,小华的销售事务完成了,此时小明的系统变成显示0张票,小明刚想按下鼠标点击接受订单的手又连忙缩了回去。 总结:这就是小华的事务执行到一半,而小明看不到他执行的操作,所以看到的是旧数据,也就是无效的数据 3.REPEATABLE READ(可重复读) REPEATABLE READ解决了脏读的问题,该级别保证了每行的记录的结果是一致的,也就是上面说的读了旧数据的问题,但是却无法解决另一个问题,幻行,顾名思义就是突然蹦出来的行数据。指的就是某个事务在读取某个范围的数据,但是另一个事务又向这个范围的数据去插入数据,导致多次读取的时候,数据的行数不一致。 例子:销售部门有规定,如果销售记录低于规定的值,要扣工资,此时经理在后端控制台查看了一下小明的销售记录,发现销售记录达不到规定的次数,心里暗喜,准备打印好销售清单,理直气壮和小明提出,没想到打印出来的时候发现销售清单里面销售数量增多了几条,刚刚好达到要求,气的经理撕了清单纸。原来是小明在就要打印的瞬间卖出了几张票,因此避过了减工资的血光之灾。 总结:虽然读取同一条数据可以保证一致性,但是却不能保证没有插入新的数据 4.SERIALIZABLE(可串行化) SERIALIZABLE是最高的隔离级别,它通过强制事务串行执行(注意是串行),避免了前面的幻读情况,由于他大量加上锁,导致大量的请求超时,因此性能会比较底下,再特别需要数据一致性且并发量不需要那么大的时候才可能考虑这个隔离级别 脏读 :所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。 不可重复读 :不可重复读字面含义已经很明了了,比如事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了,就是所谓的不可重复读了。 幻读 :小的时候数手指,第一次数十10个,第二次数是11个,怎么回事?产生幻觉了? 幻读也是这样子,事务A首先根据条件索引得到10条数据,然后事务B改变了数据库一条数据,导致也符合事务A当时的搜索条件,这样事务A再次搜索发现有11条数据了,就产生了幻读。
Java数据结构中常用的数据结构包含如下8种: 1:数组(Array) 2:栈(Stack) 3:队列(Queue) 4:链表(LinkedList) 5:树(Tree) 6:哈希表(Hash) 7:堆(Heap) 8:图(Graph) 这几个搞定对后期的发展非常有帮助,在此我用图给大家展示一下: 在此我也翻阅了很多的书籍查找了很多的资料,这几种数据结构的优缺点对比表,我感觉非常不错,在此奉献给大家: 希望对大家有所帮助,为了在以后的工作中写出大神之作,我工作之余经常研究数据结构和常用算法,希望在工作中能正确合理利用数据结构和常用算法来提高程序性能,学习有时候偶尔也会感觉枯燥,但是我会经常阿Q一下。
全文搜索属于最常见的需求,开源的 Elasticsearch (以下简称 Elastic)是目前全文搜索引擎的首选。 它可以快速地储存、搜索和分析海量数据。比如维基百科、Stack Overflow、Github 都采用它。 Elastic 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的接口。Elastic 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用,特别高效、方便。 在此我从零给大家 讲解如何使用 Elastic 搭建自己的全文搜索引擎。详细说明每一个步骤,大家跟着做就能学会。 一、安装 Elastic 需要 Java 8 环境。如果你的机器还没安装 Java,可以参考这篇文章,注意要保证环境变量JAVA_HOME正确设置。 安装完 Java,就可以跟着官方文档安装 Elastic。直接下载压缩包比较简单。 $ wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.5.1.zip $ unzip elasticsearch-5.5.1.zip $ cd elasticsearch-5.5.1/ 接着,进入解压后的目录,运行下面的命令,启动 Elastic。 $ ./bin/elasticsearch 如果这时报错"max virtual memory areas vm.maxmapcount [65530] is too low",要运行下面的命令。 $ sudo sysctl -w vm.max_map_count=262144 如果一切正常,Elastic 就会在默认的9200端口运行。这时,打开另一个命令行窗口,请求该端口,会得到说明信息。 $ curl localhost:9200 { "name" : "atntrTf", "cluster_name" : "elasticsearch", "cluster_uuid" : "tf9250XhQ6ee4h7YI11anA", "version" : { "number" : "5.5.1", "build_hash" : "19c13d0", "build_date" : "2017-07-18T20:44:24.823Z", "build_snapshot" : false, "lucene_version" : "6.6.0" }, "tagline" : "You Know, for Search" } 上面代码中,请求9200端口,Elastic 返回一个 JSON 对象,包含当前节点、集群、版本等信息。 按下 Ctrl + C,Elastic 就会停止运行。 默认情况下,Elastic 只允许本机访问,如果需要远程访问,可以修改 Elastic 安装目录的config/elasticsearch.yml文件,去掉network.host的注释,将它的值改成0.0.0.0,然后重新启动 Elastic。 network.host: 0.0.0.0 上面代码中,设成0.0.0.0让任何人都可以访问。线上服务不要这样设置,要设成具体的 IP。 二、基本概念 2.1 Node 与 Cluster Elastic 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elastic 实例。 单个 Elastic 实例称为一个节点(node)。一组节点构成一个集群(cluster)。 2.2 Index Elastic 会索引所有字段,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。 所以,Elastic 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。 下面的命令可以查看当前节点的所有 Index。 $ curl -X GET 'http://localhost:9200/_cat/indices?v' 2.3 Document Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。 Document 使用 JSON 格式表示,下面是一个例子。 { "user": "冷雨轩", "title": "java工程师", "desc": "java全栈工程师" } 同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。 2.4 Type Document 可以分组,比如weather这个 Index 里面,可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document。 不同的 Type 应该有相似的结构(schema),举例来说,id字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的一个区别。性质完全不同的数据(比如products和logs)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。 下面的命令可以列出每个 Index 所包含的 Type。 $ curl 'localhost:9200/_mapping?pretty=true' 根据规划,Elastic 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type。 三、新建和删除 Index 新建 Index,可以直接向 Elastic 服务器发出 PUT 请求。下面的例子是新建一个名叫weather的 Index。 $ curl -X PUT 'localhost:9200/weather' 服务器返回一个 JSON 对象,里面的acknowledged字段表示操作成功。 { "acknowledged":true, "shards_acknowledged":true } 然后,我们发出 DELETE 请求,删除这个 Index。 $ curl -X DELETE 'localhost:9200/weather' 四、中文分词设置 首先,安装中文分词插件。这里使用的是 ik,也可以考虑其他插件(比如 smartcn)。 $ ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.1/elasticsearch-analysis-ik-5.5.1.zip 上面代码安装的是5.5.1版的插件,与 Elastic 5.5.1 配合使用。 接着,重新启动 Elastic,就会自动加载这个新安装的插件。 然后,新建一个 Index,指定需要分词的字段。这一步根据数据结构而异,下面的命令只针对本文。基本上,凡是需要搜索的中文字段,都要单独设置一下。 $ curl -X PUT 'localhost:9200/accounts' -d ' { "mappings": { "person": { "properties": { "user": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" }, "title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" }, "desc": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" } } } } }' 上面代码中,首先新建一个名称为accounts的 Index,里面有一个名称为person的 Type。person有三个字段。 user title desc 这三个字段都是中文,而且类型都是文本(text),所以需要指定中文分词器,不能使用默认的英文分词器。 Elastic 的分词器称为 analyzer。我们对每个字段指定分词器。 "user": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" } 上面代码中,analyzer是字段文本的分词器,search_analyzer是搜索词的分词器。ik_max_word分词器是插件ik提供的,可以对文本进行最大数量的分词。 五、数据操作 5.1 新增记录 向指定的 /Index/Type 发送 PUT 请求,就可以在 Index 里面新增一条记录。比如,向/accounts/person发送请求,就可以新增一条人员记录。 $ curl -X PUT 'localhost:9200/accounts/person/1' -d ' { "user": "冷雨轩", "title": "java工程师", "desc": "java全栈工程师" }' 服务器返回的 JSON 对象,会给出 Index、Type、Id、Version 等信息。 { "_index":"accounts", "_type":"person", "_id":"1", "_version":1, "result":"created", "_shards":{"total":2,"successful":1,"failed":0}, "created":true } 如果你仔细看,会发现请求路径是/accounts/person/1,最后的1是该条记录的 Id。它不一定是数字,任意字符串(比如abc)都可以。 新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。 $ curl -X POST 'localhost:9200/accounts/person' -d ' { "user": "java老鸟", "title": "java工程师", "desc": "java骨灰级开发" }' 上面代码中,向/accounts/person发出一个 POST 请求,添加一个记录。这时,服务器返回的 JSON 对象里面,_id字段就是一个随机字符串。 { "_index":"accounts", "_type":"person", "_id":"AV3qGfrC6jMbsbXb6k1p", "_version":1, "result":"created", "_shards":{"total":2,"successful":1,"failed":0}, "created":true } 注意,如果没有先创建 Index(这个例子是accounts),直接执行上面的命令,Elastic 也不会报错,而是直接生成指定的 Index。所以,打字的时候要小心,不要写错 Index 的名称。 5.2 查看记录 向/Index/Type/Id发出 GET 请求,就可以查看这条记录。 $ curl 'localhost:9200/accounts/person/1?pretty=true' 上面代码请求查看/accounts/person/1这条记录,URL 的参数pretty=true表示以易读的格式返回。 返回的数据中,found字段表示查询成功,_source字段返回原始记录。 { "_index" : "accounts", "_type" : "person", "_id" : "1", "_version" : 1, "found" : true, "_source" : { "user" : "冷雨轩", "title" : "java工程师", "desc" : "java全栈工程师" } } 如果 Id 不正确,就查不到数据,found字段就是false。 $ curl 'localhost:9200/weather/beijing/abc?pretty=true' { "_index" : "accounts", "_type" : "person", "_id" : "abc", "found" : false } 5.3 删除记录 删除记录就是发出 DELETE 请求。 $ curl -X DELETE 'localhost:9200/accounts/person/1' 这里先不要删除这条记录,后面还要用到。 5.4 更新记录 更新记录就是使用 PUT 请求,重新发送一次数据。 $ curl -X PUT 'localhost:9200/accounts/person/1' -d ' { "user" : "冷雨轩", "title" : "java工程师", "desc" : "数据库管理,java软件开发" }' { "_index":"accounts", "_type":"person", "_id":"1", "_version":2, "result":"updated", "_shards":{"total":2,"successful":1,"failed":0}, "created":false } 上面代码中,我们将原始数据从"java全栈工程师"改成"数据库管理,java软件开发"。 返回结果里面,有几个字段发生了变化。 "_version" : 2, "result" : "updated", "created" : false 可以看到,记录的 Id 没变,但是版本(version)从1变成2,操作类型(result)从created变成updated,created字段变成false,因为这次不是新建记录。 六、数据查询 6.1 返回所有记录 使用 GET 方法,直接请求/Index/Type/_search,就会返回所有记录。 $ curl 'localhost:9200/accounts/person/_search' { "took":2, "timed_out":false, "_shards":{"total":5,"successful":5,"failed":0}, "hits":{ "total":2, "max_score":1.0, "hits":[ { "_index":"accounts", "_type":"person", "_id":"AV3qGfrC6jMbsbXb6k1p", "_score":1.0, "_source": { "user": "java老鸟", "title": "java工程师", "desc": "java骨灰级开发" } }, { "_index":"accounts", "_type":"person", "_id":"1", "_score":1.0, "_source": { "user" : "冷雨轩", "title" : "java工程师", "desc" : "数据库管理,java软件开发" } } ] } } 上面代码中,返回结果的 took字段表示该操作的耗时(单位为毫秒),timed_out字段表示是否超时,hits字段表示命中的记录,里面子字段的含义如下。 total:返回记录数,本例是2条。 max_score:最高的匹配程度,本例是1.0。 hits:返回的记录组成的数组。 返回的记录中,每条记录都有一个_score字段,表示匹配的程序,默认是按照这个字段降序排列。 6.2 全文搜索 Elastic 的查询非常特别,使用自己的查询语法,要求 GET 请求带有数据体。 $ curl 'localhost:9200/accounts/person/_search' -d ' { "query" : { "match" : { "desc" : "软件" }} }' 上面代码使用 Match 查询,指定的匹配条件是desc字段里面包含"软件"这个词。返回结果如下。 { "took":3, "timed_out":false, "_shards":{"total":5,"successful":5,"failed":0}, "hits":{ "total":1, "max_score":0.28582606, "hits":[ { "_index":"accounts", "_type":"person", "_id":"1", "_score":0.28582606, "_source": { "user" : "冷雨轩", "title" : "java工程师", "desc" : "数据库管理,java软件开发" } } ] } } Elastic 默认一次返回10条结果,可以通过size字段改变这个设置。 $ curl 'localhost:9200/accounts/person/_search' -d ' { "query" : { "match" : { "desc" : "管理" }}, "size": 1 }' 上面代码指定,每次只返回一条结果。 还可以通过from字段,指定位移。 $ curl 'localhost:9200/accounts/person/_search' -d ' { "query" : { "match" : { "desc" : "管理" }}, "from": 1, "size": 1 }' 上面代码指定,从位置1开始(默认是从位置0开始),只返回一条结果。 6.3 逻辑运算 如果有多个搜索关键字, Elastic 认为它们是or关系。 $ curl 'localhost:9200/accounts/person/_search' -d ' { "query" : { "match" : { "desc" : "软件 开发" }} }' 上面代码搜索的是软件 or 系统。 如果要执行多个关键词的and搜索,必须使用布尔查询。 $ curl 'localhost:9200/accounts/person/_search' -d ' { "query": { "bool": { "must": [ { "match": { "desc": "软件" } }, { "match": { "desc": "java" } } ] } } }' 七、参考链接 ElasticSearch 官方手册 A Practical Introduction to Elasticsearch (完)
在开发中我们可能会出现大量的公式计算,而这些公式可能并不确定。 比如用户今天说a=b+c 然而下次说公式不正确 应该是a=d+e 如果单纯的在代码中把这些公式写死 实现,后期修改维护工作量显然会增加好多。 下面就简单的介绍一种实现方法的思路: 我们知道js的eval()方法可以执行字符串的代码 而恰好jdk6增加了对脚本语言的支持 我们可以利用这个特性对计算实现简单化的处理 下面举个例子 例如有个公式 A+B*C 其中A=1,B=2,C=3 我们可以将公式的A B C替换成数字 转换为 1+2*3 最后就可以得到结果了 刚刚接触到ScriptEngine这个东西的时候仅仅了解其eval()方法 ,于是我利用了上述例子的思路去实现将字母替换成相应的数字去得到数字公式进而得到运算结果。 那么替换的方法当然是用正则去替换了,java中String对象有个replaceAll()方法可以实现。 当时想这个正则可是琢么了一会儿呢。然而我这种实现却白忙活了,因为后期发现没有必要这么麻烦 具体怎么实现大家可以想想看,我就不具体介绍了,有了思路比什么都重要。 然而过了一段时间,我在面试的时候和一个前辈聊天时,前辈告诉我不用自己写正则去替换,js中本来就有对象,也支持对象的运算,所以直接往里放对象就可以。 后来我自己查了查资料,发现果然可以: 为了可以实现打印出中间参与计算的变量,我利用反射实现了获取值的方法 下面就是代码的实现: public static void Calculation(){ Student stu=new Student(); stu.setAge(10); stu.setName("zhangsan"); stu.setSex(false); // 上边是student对象 Class claz=stu.getClass(); String className= claz.getSimpleName(); //String formula="Student.name+Student.age+10"; //1 String formula="function test(){ if(Student.age==10){ return 12;} }"; //2 System.out.println("the formula is:"+formula); //获取对象名称和值 for(Field field: claz.getDeclaredFields()){ try { //打开私有访问 //field.setAccessible(true); String fieldName = field.getName(); Method m = (Method) claz.getMethod("get" +getMethodName(fieldName)); System.out.println(getMethodName(fieldName)); System.out.println(field.getGenericType()+"-"+"fieldName:"+fieldName+"="+m.invoke(stu)); } catch (Exception e) { e.printStackTrace(); } } Object result=null; //计算结果 ScriptEngineManager manager = new ScriptEngineManager(); //创建一个ScriptEngineManager对象 ScriptEngine engine = manager.getEngineByName("js"); //通过ScriptEngineManager获得ScriptEngine对象 engine.put(className, stu); //将student对象放到ScriptEngine 中为计算变量提供值 try { //result =engine.eval(formula); //1 用ScriptEngine的eval方法执行脚本 String formula="Student.name+Student.age+10"; engine.eval(formula); //2 Invocable inv = (Invocable) engine; //2 result=inv.invokeFunction("test"); //2 执行字符串 js test() System.out.println("the result is :"+result.toString()); } catch (Exception e) { System.out.println("错误"); e.printStackTrace(); } } // 把一个字符串的第一个字母大写 private static String getMethodName(String fildeName) throws Exception{ byte[] items = fildeName.getBytes(); items[0] = (byte) ((char) items[0] - 'a' + 'A'); return new String(items); } 上述代码 还可注释2 打开注释1 试试 一样可以的 不过方式不同而已 有了这个思想 我们就可以把公式维护到数据库或文件中 方便我们后期对公式的维护修改 这样利用这个工具应该可以减少计算代码编写的复杂度 后期我也发现了好多表达式引擎如:Aviator、IKExpression等。
SpringBoot 有两个关键元素: @SpringBootApplication SpringApplication 以及 run() 方法 SpringApplication 这个类应该算是 Spring Boot 框架的“创新”产物了,原始的 Spring 中并没有这个类,SpringApplication 中封装了一套 Spring 应用的启动流程,然而这对用户完全透明,因此我们上手 Spring Boot 时感觉很简洁、轻量。 一般来说默认的 SpringApplication 执行流程已经可以满足大部分需求,但是若用户想干预这个过程,则可以通过 SpringApplication 在流程某些地方开启扩展点来完成对流程的扩展,典型的扩展方案那就是使用 set 方法。 比如,把我们天天司空见惯的 Spring Boot 应用的启动类来拆解一下写出来就是这样: @SpringBootApplication public class CodeSheepApplication { public static void main( String[] args ) { //SpringApplication.run( CodeSheepApplication.class args ); SpringApplication app = new SpringApplication( CodeSheepApplication.class ); app.setXXX( ... ); // 用户自定的扩展在此 !!! app.run( args ); } } 这样一拆解后我们发现,我们也需要先构造 SpringApplication 类对象,然后调用该对象的 run() 方法。那么接下来就讲讲 SpringApplication 的构造过程 以及其 run() 方法的流程,搞清楚了这个,那么也就搞清楚了SpringBoot应用是如何运行起来的! SpringApplication 实例的初始化 我们对照代码来看: 四个关键的步骤已标注在图中,分别解释如下: ① 推断应用的类型:创建的是 REACTIVE应用、SERVLET应用、NONE 三种中的某一种 ② 使用 SpringFactoriesLoader查找并加载 classpath下 META-INF/spring.factories文件中所有可用的 ApplicationContextInitializer ③ 使用 SpringFactoriesLoader查找并加载 classpath下 META-INF/spring.factories文件中的所有可用的 ApplicationListener ④ 推断并设置 main方法的定义类 SpringApplication 的run()方法探秘 先看看代码长啥样子: 各个主要步骤我已经标注在上图之中了,除此之外,我也按照自己的理解画了一个流程图如下所示,可以对照数字标示看一下: 我们将各步骤总结精炼如下: 1、通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件,获取并创建 SpringApplicationRunListener 对象 2、然后由 SpringApplicationRunListener 来发出 starting 消息 3、创建参数,并配置当前 SpringBoot 应用将要使用的 Environment 4、完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息 5、创建 ApplicationContext 6、初始化 ApplicationContext,并设置 Environment,加载相关配置等 7、由 SpringApplicationRunListener 来发出 contextPrepared 消息,告知SpringBoot 应用使用的 ApplicationContext 已准备OK 8、将各种 beans 装载入 ApplicationContext,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 SpringBoot 应用使用的 ApplicationContext 已装填OK 9、refresh ApplicationContext,完成IoC容器可用的最后一步 10、由 SpringApplicationRunListener 来发出 started 消息 11、完成最终的程序的启动 12、由 SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了 至此,全流程结束!
当要夸服务器访问数据库时,我们可以使用dblink建立连接服务器间的通道,本地创建了远程数据库的dblink后,访问远程服务器的库就像操作一个库一样了。 如果需要创建全局 DBLink,首先要确定用户有创建 dblink 的权限: 使用此语句查看:select * from user_sys_privs where privilege like upper('%DATABASE LINK%'); grant create database link to 用户名 //给本机用户分配创建link权限 CREATE DATABASE LINK linkname CONNECT TO 远程服务器用户名 IDENTIFIED BY 远程服务器密码 USING '(DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 远程服务器IP)(PORT = 远程端口号)) ) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = orcl) ) )'; linkname 创建通道的名称 SERVICE_NAME 可通过语句查看 select name,value from v$parameter where name='service_names' select * from tableName@linkname 建立链接之后 后续想怎么操作就看怎么用了
当你编写一个需要调用mybatis的dao层的类时,会先通过spring依赖注入该变量,但是由于你需要用到该变量在静态方法中,所以无法使用,此时你将该变量改为静态变量,发现无法注入了 解决方案: 需要一个私有静态类变量 DateUtils @PostConstruct 会在spring依赖注入后,自动执行,并且只执行一次,将当前类对象的地址付给了我们自己定义的静态变量 此时我们才可以获取到变量中的方法,否则dateDBMapper一直为null
水平居中 行内元素的水平居中 text-align:center(在父元素中设置) 只对内联元素或行内块元素有效 需要放置于父元素中 块级元素的水平居中 margin: 0 auto; 只对块级元素有效 auto指的是自适应宽度。实质就是均分了元素左右的剩余空间,所以元素会居中。 auto只有在块级元素设置了宽度width才有效(块级元素不设宽度默认就占整行了,所以是废话) auto无法实现块级元素的垂直居中,原因与CSS默认的高度计算规则有关,这里暂不深究。但margin:auto可以实现绝对定位元素的水平垂直居中,见下文。 垂直居中 行内元素的垂直居中 line-height: 父元素的高度;(在父元素中设置) 只对内联元素或行内块元素有效 需要知道父元素的高度 需要放置于父元素中 适用于垂直方向上只有一个需要居中的元素的情况(想同时垂直居中多个元素时,可以用padding) vertical-align: middle;(用于垂直对齐inline元素) 只对内联元素或行内块元素有效 主要用在文本和与文本相邻元素的垂直方向上的对齐问题(主要是对齐的作用,而不是居中的作用), 例如将一个icon与文字对齐。 使用vertical-align需要了解文字的baseline和line-box等知识 水平垂直居中 浮动元素 使用position:relative; 具体方法与绝对定位的第1个和第2个方法类似,只不过把absolute改为relative,并根据原float的方向稍作修改即可。 相对定位relative可以和float叠加,在float后的位置上再相对定位。 缺点就是float元素居中后仍会占据原来的位置。 第二种方式中,如果设定了浮动元素的高度,将会影响transform的具体值。 绝对定位元素 使用50%推进法则 position: absolute; left: 50%; top: 50%; margin-left: -该元素自身宽度的一半px; /*水平居中*/ margin-top: -该元素自身高度的一半px; /*垂直居中*/ 只对绝对定位的元素有效 需要知道绝对定位元素的宽高 兼容性很好,是一种主流用法 第一种方法的改进版,使用transform代替margin position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); /*水平垂直居中*/ 不需要知道绝对定位元素的宽高。(如果设置了高度,则有可能影响到transform的具体值) 兼容性一般,IE10+以及其他现代浏览器支持 使用margin:auto; position: absolute; left: 0; right: 0; /*水平居中*/ top: 0; bottom: 0; /*垂直居中*/ margin: auto; 只对绝对定位的元素有效 不必知道宽高,但需要是图片这种自身包含尺寸的元素 left与right必须配对出现,top与bottom必须配对出现 目前支持IE9+,及其他浏览器。 flex居中方式 display: flex; justify-content: center; /*水平居中*/ align-items: center; /*垂直居中*/ 块级元素设置display: flex;,内联元素设置display: inline-flex;。 需要注意的几个问题 元素浮动后仍可以设置margin属性,但auto不会起作用。 图片居中的问题 注意:如果图片的宽度大于父元素的宽度, 不能使用margin: 0 auto;或者text-align: center;让图片居中 如果图片的宽度大于父元素的宽度, 可以绝对定位中的居中方式让图片居中。但是定位流的弊端也比较明显。它必须知道图片宽度。 如果图片的宽度大于父元素的宽度, 也可以使用margin: 0 -100%; 注意: 父元素必须设置text-align: center;
数据库锁定机制简单来说就是数据库为了保证数据的一致性而使各种共享资源在被并发访问访问变得有序所设计的一种规则;对于任何一种数据库来说都需要有相应的锁定机制,Mysql也不例外。 Mysql几种锁定机制类型 MySQL 各存储引擎使用了三种类型(级别)的锁定机制:行级锁定,页级锁定和表级锁定。 1.行级锁定 锁定对象的颗粒度很小,只对当前行进行锁定,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力;弊端就是获取锁释放锁更加频繁,系统消耗更大,同时行级锁定也最容易发生死锁; 行级锁定的主要是Innodb存储引擎和NDB Cluster存储引擎; 2.页级锁定 锁定颗粒度介于行级锁定与表级锁之间,每页有多行数据,并发处理能力以及获取锁定所需要的资源开销在两者之间; 页级锁定主要是BerkeleyDB 存储引擎; 3.表级锁定 一次会将整张表锁定,该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小,而且可以避免死锁问题;弊端就是锁定资源争用的概率最高,并发处理能力最低; 使用表级锁定的主要是MyISAM,Memory,CSV等一些非事务性存储引擎。 本文重点介绍Innodb存储引擎使用的行级锁定; 两段锁协议(2PL) 两段锁协议规定所有的事务应遵守的规则: 1.在对任何数据进行读、写操作之前,首先要申请并获得对该数据的封锁; 2.在释放一个封锁之后,事务不再申请和获得其它任何封锁; 即事务的执行分为两个阶段: 第一阶段是获得封锁的阶段,称为扩展阶段;第二阶段是释放封锁的阶段,称为收缩阶段; begin; insert ... 加锁1 update ... 加锁2 commit; 事务提交时,释放锁1,锁2 如果在加锁2的时候,加锁不成功,则进入等待状态,直到加锁成功才继续执行; 如果有另外一个事务获取锁的时候顺序刚好相反,是有可能导致死锁的;为此有了一次性封锁法,要求事务必须一次性将所有要使用的数据全部加锁,否则就不能继续执行; 定理:若所有事务均遵守两段锁协议,则这些事务的所有交叉调度都是可串行化的(串行化很重要,尤其是在数据恢复和备份的时候); 行级锁定(悲观锁) 1.共享锁和排他锁 Innodb的行级锁定同样分为两种类型:共享锁和排他锁; 共享锁:当一个事务获得共享锁之后,它只可以进行读操作,所以共享锁也叫读锁,多个事务可以同时获得某一行数据的共享锁; 排他锁:而当一个事务获得一行数据的排他锁时,就可以对该行数据进行读和写操作,所以排他锁也叫写锁,排他锁与共享锁和其他的排他锁不兼容; 既然数据库提供了共享锁和排他锁,那具体用在什么地方: 1.1在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别,隔离级别就使用了锁机制; 1.2提供了相关的SQL,可以方便的在程序中使用; 2.事务隔离级别和锁的关系 数据库隔离级别:未提交读(Read uncommitted),已提交读(Read committed),可重复读(Repeatable read)和可串行化(Serializable); 未提交读(Read uncommitted):可能读取到其他会话中未提交事务修改的数据,会出现脏读(Dirty Read); 已提交读(Read committed):只能读取到已经提交的数据,会出现不可重复读(NonRepeatable Read); 可重复读(Repeatable read):InnoDB默认级别,不会出现不可重复读(NonRepeatable Read),但是会出现幻读(Phantom Read); 可串行化(Serializable):强制事务排序,使之不可能相互冲突,从而解决幻读问题,使用表级共享锁,读写相互都会阻塞; 常用的2种隔离级别是:已提交读(Read committed)和可重复读(Repeatable read); 3.已提交读 3.1准备测试表 CREATE TABLE `test_lock` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `type` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 mysql> insert into test_lock values(null,'zhaohui',1); mysql> insert into test_lock values(null,'zhaohui2',2); 3.2查看和设置隔离级别 mysql> SELECT @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set mysql> set session transaction isolation level read committed; Query OK, 0 rows affected mysql> SELECT @@tx_isolation; +----------------+ | @@tx_isolation | +----------------+ | READ-COMMITTED | +----------------+ 3.3模拟多个事务交叉执行 Session1执行查询 mysql> begin; Query OK, 0 rows affected mysql> select * from test_lock where id=1; +----+---------+------+ | id | name | type | +----+---------+------+ | 1 | zhaohui | 1 | +----+---------+------+ 1 row in set Session2更新数据 mysql> begin; Query OK, 0 rows affected mysql> update test_lock set name='zhaohui_new' where id=1; Query OK, 1 row affected Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; Query OK, 0 rows affected Session1执行查询 mysql> select * from test_lock where id=1; +----+-------------+------+ | id | name | type | +----+-------------+------+ | 1 | zhaohui_new | 1 | +----+-------------+------+ 1 row in set mysql> commit; Query OK, 0 rows affected Session1中出现了不可重复读(NonRepeatable Read),也就是在查询的时候没有锁住相关的数据,导致出现了不可重复读,但是写入、修改和删除数据还是加锁了,如下所示: Session1更新数据 mysql> begin; Query OK, 0 rows affected mysql> update test_lock set name='zhaohui_new2' where id=1; Query OK, 1 row affected Rows matched: 1 Changed: 1 Warnings: 0 Session2更新数据 mysql> begin; Query OK, 0 rows affected mysql> update test_lock set name='zhaohui_new3' where id=1; 1205 - Lock wait timeout exceeded; try restarting transaction Session2更新在更新同一条数据的时候超时了,在更新数据的时候添加了排他锁; 4.可重复读 4.1查看和设置隔离级别 mysql> set session transaction isolation level repeatable read; Query OK, 0 rows affected mysql> SELECT @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set 4.2模拟多个事务交叉执行 Session1执行查询 mysql> begin; Query OK, 0 rows affected mysql> select * from test_lock where type=2; +----+----------+------+ | id | name | type | +----+----------+------+ | 2 | zhaohui2 | 2 | +----+----------+------+ 1 row in set Session2更新数据 mysql> begin; Query OK, 0 rows affected mysql> update test_lock set name='zhaohui2_new' where type=2; Query OK, 1 row affected Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; Query OK, 0 rows affected Session1执行查询 mysql> select * from test_lock where type=2; +----+----------+------+ | id | name | type | +----+----------+------+ | 2 | zhaohui2 | 2 | +----+----------+------+ 1 row in set 可以发现2次查询的数据结果是一样的,实现了可重复读(Repeatable read),再来看一下是否有幻读(Phantom Read)的问题; Session3插入数据 mysql> begin; Query OK, 0 rows affected mysql> insert into test_lock values(null,'zhaohui3',2); Query OK, 1 row affected mysql> commit; Query OK, 0 rows affected Session1执行查询 mysql> select * from test_lock where type=2; +----+----------+------+ | id | name | type | +----+----------+------+ | 2 | zhaohui2 | 2 | +----+----------+------+ 1 row in set 可以发现可重复读(Repeatable read)隔离级别下,也不会出现幻读的现象; 分析一下原因:如何通过悲观锁的方式去实现可重复读和不出现幻读的现象,对读取的数据加共享锁,对同样的数据执行更新操作就只能等待,这样就可以保证可重复读,但是对于不出现幻读的现象无法通过锁定行数据来解决; 最终看到的现象是没有幻读的问题,同时如果对读取的数据加共享锁,更新相同数据应该会等待,上面的实例中并没有出现等待,所以mysql内部应该还有其他锁机制--MVCC机制; 5.悲观锁SQL使用 5.1共享锁使用(lock in share mode) Session1查询数据 mysql> begin; Query OK, 0 rows affected mysql> select * from test_lock where type=2 lock in share mode; +----+--------------+------+ | id | name | type | +----+--------------+------+ | 2 | zhaohui2_new | 2 | | 3 | zhaohui3 | 2 | +----+--------------+------+ 2 rows in set Session2查询数据 mysql> begin; Query OK, 0 rows affected mysql> select * from test_lock where type=2 lock in share mode; +----+--------------+------+ | id | name | type | +----+--------------+------+ | 2 | zhaohui2_new | 2 | | 3 | zhaohui3 | 2 | +----+--------------+------+ 2 rows in set Session3更新数据 mysql> begin; Query OK, 0 rows affected mysql> update test_lock set name='zhaohui3_new' where id=3; 1205 - Lock wait timeout exceeded; try restarting transaction Session1和Session2使用了共享锁,所以可以存在多个,并不冲突,但是Session3更新操作需要加上排他锁,和共享锁不能同时存在; 5.2排他锁使用(for update) Session1查询数据 mysql> begin; Query OK, 0 rows affected mysql> select * from test_lock where type=2 for update; +----+--------------+------+ | id | name | type | +----+--------------+------+ | 2 | zhaohui2_new | 2 | | 3 | zhaohui3 | 2 | +----+--------------+------+ 2 rows in set Session2查询数据 mysql> begin; Query OK, 0 rows affected mysql> select * from test_lock where type=2 for update; Empty set Session3更新数据 mysql> begin; Query OK, 0 rows affected mysql> update test_lock set name='zhaohui3_new' where id=3; 1205 - Lock wait timeout exceeded; try restarting transaction 排他锁只能有一个同时存在,所有Session2和Session3都将等等超时; 多版本并发控制MVCC 多版本并发控制(Multiversion Concurrency Control):每一个写操作都会创建一个新版本的数据,读操作会从有限多个版本的数据中挑选一个最合适的结果直接返回;读写操作之间的冲突就不再需要被关注,而管理和快速挑选数据的版本就成了MVCC需要解决的主要问题。 为什么要引入此机制,首先通过悲观锁来处理读请求是很耗性能的,其次数据库的事务大都是只读的,读请求是写请求的很多倍,最后如果没有并发控制机制,最坏的情况也是读请求读到了已经写入的数据,这对很多应用完全是可以接受的; 再来看一下可重复读(Repeatable read)现象,通过MVCC机制读操作只读该事务开始前的数据库的快照(snapshot), 这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读; 当然并不是说悲观锁就没有用了,在数据更新的时候数据库默认还是使用悲观锁的,所以MVCC是可以整合起来一起使用的(MVCC+2PL),用来解决读-写冲突的无锁并发控制; MVCC使用快照读的方式,解决了不可重复读和幻读的问题,如上面的实例所示:select查询的一直是快照信息,不需要添加任何锁; 以上实例中使用的select方式把它称为快照读(snapshot read),其实事务的隔离级别的读还有另一层含义:读取数据库当前版本数据–当前读(current read); 当前读和Gap锁 区别普通的select查询,当前读对应的sql包括: select ...for update, select ...lock in share mode, insert,update,delete; 以上sql本身会加悲观锁,所以不存在不可重复读的问题,剩下的就是幻读的问题; Session1执行当前读 mysql> select * from test_lock where type=2 for update; +----+----------------+------+ | id | name | type | +----+----------------+------+ | 2 | zhaohui2_new | 2 | | 3 | zhaohui3_new_1 | 2 | +----+----------------+------+ 2 rows in set Session2执行插入 mysql> begin; Query OK, 0 rows affected mysql> insert into test_lock values(null,'zhaohui_001',1); 1205 - Lock wait timeout exceeded; try restarting transaction 为什么明明锁住的是type=2的数据,当插入type=1也会锁等待,因为InnoDB对于行的查询都是采用了Next-Key锁,锁定的不是单个值,而是一个范围(GAP); 如果当前type类型包括:1,2,4,6,8,10锁住type=2,那么type=1,2,3会被锁住,后面的不会,锁住的是一个区间;这样也就保证了当前读也不会出现幻读的现象; 注:type字段添加了索引,如果没有添加索引,gap锁会锁住整张表; 乐观锁 乐观锁是一种思想,认为事务间争用没有那么多,和悲观锁是相对的,乐观锁在java的并发包中大量的使用;一般采用以下方式:使用版本号(version)机制来实现,版本号就是为数据添加一个版本标志,一般在表中添加一个version字段;当读取数据的时候把version也取出来,然后version+1,更新数据库的时候对比第一次取出来的version和数据库里面的version是否一致,如果一致则更新成功,否则失败进入重试,具体使用大致如下: begin; select id,name,version from test_lock where id=1; .... update test_lock set name='xxx',version=version+1 where id=1 and version=${version}; commit; 先查询后更新,需要保证原子性,要么使用悲观锁的方式,对整个事务加锁;要么使用乐观锁的方式,如果在读多写少的系统中,乐观锁性能更好; 总结 本文首先从Mysql的悲观锁出发,然后介绍了悲观锁和事务隔离级别之间的关系,并分析为什么没有使用悲观锁来实现隔离级别;然后从问题出发分别介绍了MVCC和Gap锁是如何解决了不可重复读的问题和幻读的问题;最后介绍了乐观锁经常被用在读数据远大于写数据的系统中。
一、基础规范 1) 使用InnoDB存储引擎 2) 数据库字符集使用UTF8,校对字符集使用utf8_general_ci 3) 所有表、字段都尽量添加注释 4) 库名、表名、字段名使用小写字母,禁止超过32个字符,须见名知意 5) 非唯一索引以 “idx_字段1_字段2” 命名,唯一索引必须以 “uniq_字段1_字段2” 命名 二、查询规范 1) SQL语句尽可能简单,大的SQL想办法拆分成小的SQL实现 2) 不要使用SELECT * ,查询具体要用到的字段 3) 禁止like做where条件(会全表扫描且不能用索引) 4) 除非必要,避免使用 != 等非等值操作符(会导致用不到索引) 5) Where条件里不要对列使用函数(不会引用索引) 6) 能确定返回结果只有一条时,使用limit 1(LIMIT分页注意效率,LIMIT越大,效率越低) 7) 少用子查询,改用JOIN(子查询要在内存里建临时表) 8) 多表JOIN的字段,区分度最大的字段放在前面 9) IN条件里的数据数量要尽量少,超过200个用EXIST代替IN 10) Where字句中同一个表的不同字段组合建议小于5组(否则考虑分表) 11) 禁止单条语句同时更新多个表 12) 事务要尽量简单,整个事务的时间长度不要太长 三、表设计规范 1) 用DECIMAL代替FLOAT和DOUBLE存储精确浮点数(精确数据) 2) 使用TINYINT代替ENUM类型(便于迁移时兼容) 3) 尽可能不使用TEXT、BLOB类型(该数据类型不能设置默认值、不便于排序、不便于建立索引) 4) 同一意义的字段设计定义必须相同(便于联表查询) 5) 所有字段均定义为NOT NULL(避免使用NULL字段,NULL字段很难查询优化,NULL字段的索引需要额外空间,NULL字段的复合索引无效) 6) 表必须有主键,不使用更新频繁的列做主键、尽量不使用字符串列做主键,尽量使用非空的唯一自增键做主键 四、索引设计规范 1) 单表索引数量不超过10个 2) 单个字段不要超过两个索引 3) 新建的唯一索引必须不能和主键重复 4) 避免冗余和重复索引 5) 尽量不要在频繁更新的列上建立索引 6) 不在低基数列上建立索引,例如状态、类型等 7) 不在索引列进行数学运算和函数运算(参与了运算的列不会引用索引) 8) 复合索引须符合最左前缀的特点建立索引(mysql使用复合索引时从左向右匹配) 9) 重要的SQL中where条件里的字段必须被索引 10) Where条件里的字段顺序与索引顺序无关,优化器会自动调整 11) 索引选择性= Cardinality / Total Rows,即基数 ÷ 数据行数,值越接近1说明使用索引的过滤效果越好 12) 建立索引时,务必先explain,查看索引使用情况
Zookeeper是什么 官方文档上这么解释zookeeper,它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。 上面的解释有点抽象,简单来说zookeeper=文件系统+监听通知机制。 1、 文件系统 Zookeeper维护一个类似文件系统的数据结构: 每个子目录项如 NameService 都被称作为 znode(目录节点),和文件系统一样,我们能够自由的增加、删除znode,在一个znode下增加、删除子znode,唯一的不同在于znode是可以存储数据的。 有四种类型的znode: PERSISTENT-持久化目录节点 客户端与zookeeper断开连接后,该节点依旧存在 PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点 客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号 EPHEMERAL-临时目录节点 客户端与zookeeper断开连接后,该节点被删除 EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点 客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号 2、 监听通知机制 客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)时,zookeeper会通知客户端。 就这么简单,下面我们看看Zookeeper能做点什么呢? Zookeeper能做什么 zookeeper功能非常强大,可以实现诸如分布式应用配置管理、统一命名服务、状态同步服务、集群管理等功能,我们这里拿比较简单的分布式应用配置管理为例来说明。 假设我们的程序是分布式部署在多台机器上,如果我们要改变程序的配置文件,需要逐台机器去修改,非常麻烦,现在把这些配置全部放到zookeeper上去,保存在 zookeeper 的某个目录节点中,然后所有相关应用程序对这个目录节点进行监听,一旦配置信息发生变化,每个应用程序就会收到 zookeeper 的通知,然后从 zookeeper 获取新的配置信息应用到系统中。 如上,你大致应该了解zookeeper是个什么东西,大概能做些什么了,我们马上来学习下zookeeper的安装及使用,并开发一个小程序来实现zookeeper这个分布式配置管理的功能。 Zookeeper单机模式安装 Step1:配置JAVA环境,检验环境:java -version Step2:下载并解压zookeeper # cd /usr/local # wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.12.tar.gz # tar -zxvf zookeeper-3.4.12.tar.gz # cd zookeeper-3.4.12 Step3:重命名配置文件zoo_sample.cfg # cp conf/zoo_sample.cfg conf/zoo.cfg Step4:启动zookeeper # bin/zkServer.sh start Step5:检测是否成功启动,用zookeeper客户端连接下服务端 # bin/zkCli.sh Zookeeper使用 使用客户端命令操作zookeeper 1、使用 ls 命令来查看当前 ZooKeeper 中所包含的内容 [zk: localhost:2181(CONNECTED) 1] ls / [dubbo, default, zookeeper] [zk: localhost:2181(CONNECTED) 2] 2、创建一个新的 znode ,使用 create /zkPro myData [zk: localhost:2181(CONNECTED) 2] create /zkPro myData Created /zkPro [zk: localhost:2181(CONNECTED) 3] 3、再次使用 ls 命令来查看现在 zookeeper 中所包含的内容: [zk: localhost:2181(CONNECTED) 3] ls / [dubbo, default, zookeeper, zkPro] [zk: localhost:2181(CONNECTED) 4] 4、下面我们运行 get 命令来确认第二步中所创建的 znode 是否包含我们所创建的字符串: [zk: localhost:2181(CONNECTED) 6] get /zkPro myData cZxid = 0x1146 ctime = Tue Sep 04 10:40:49 CST 2018 mZxid = 0x1146 mtime = Tue Sep 04 10:40:49 CST 2018 pZxid = 0x1146 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 6 numChildren = 0 [zk: localhost:2181(CONNECTED) 7] 5、下面我们通过 set 命令来对 zk 所关联的字符串进行设置: [zk: localhost:2181(CONNECTED) 7] set /zkPro myData123456 cZxid = 0x1146 ctime = Tue Sep 04 10:40:49 CST 2018 mZxid = 0x1147 mtime = Tue Sep 04 10:43:59 CST 2018 pZxid = 0x1146 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 12 numChildren = 0 [zk: localhost:2181(CONNECTED) 8] 6、下面我们将刚才创建的 znode 删除 [zk: localhost:2181(CONNECTED) 8] delete /zkPro [zk: localhost:2181(CONNECTED) 9] 使用Java API操作zookeeper 使用Java API操作zookeeper需要引用下面的包 com.101tec zkclient 0.10 下面我们来实现上面说的分布式配置中心: 1、在zookeeper里增加一个目录节点,并且把配置信息存储在里面 [zk: localhost:2181(CONNECTED) 9] create /username zhangsan Created /username [zk: localhost:2181(CONNECTED) 10] 2、启动两个zookeeper客户端程序,代码如下所示 import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.EventType; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; /** * 分布式配置中心demo * */ public class ZooKeeperProSync implements Watcher { private static CountDownLatch connectedSemaphore = new CountDownLatch(1); private static ZooKeeper zk = null; private static Stat stat = new Stat(); public static void main(String[] args) throws Exception { //zookeeper配置数据存放路径 String path = "/username"; //连接zookeeper并且注册一个默认的监听器 zk = new ZooKeeper("192.168.188.128:2181", 5000, // new ZooKeeperProSync()); //等待zk连接成功的通知 connectedSemaphore.await(); //获取path目录节点的配置数据,并注册默认的监听器 System.out.println(new String(zk.getData(path, true, stat))); Thread.sleep(Integer.MAX_VALUE); } public void process(WatchedEvent event) { if (KeeperState.SyncConnected == event.getState()) { //zk连接成功通知事件 if (EventType.None == event.getType() && null == event.getPath()) { connectedSemaphore.countDown(); } else if (event.getType() == EventType.NodeDataChanged) { //zk目录节点数据变化通知事件 try { System.out.println("配置已修改,新值为:" + new String(zk.getData(event.getPath(), true, stat))); } catch (Exception e) { } } } } } 两个程序启动后都正确的读取到了zookeeper的/username目录节点下的数据'zhangsan' 3、我们在zookeeper里修改下目录节点/username下的数据 [zk: localhost:2181(CONNECTED) 10] set /username zhangsan123456 cZxid = 0x1149 ctime = Tue Sep 04 10:49:11 CST 2018 mZxid = 0x114a mtime = Tue Sep 04 10:52:08 CST 2018 pZxid = 0x1149 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 14 numChildren = 0 [zk: localhost:2181(CONNECTED) 11] 修改完成后,我们看见两个程序后台都及时收到了他们监听的目录节点数据变更后的值,如下所示: zhangsan 配置已修改,新值为:zhangsan123456 Zookeeper集群模式安装 本例搭建的是伪集群模式,即一台机器上启动三个zookeeper实例组成集群,真正的集群模式无非就是实例IP地址不同,搭建方法没有区别 Step1:配置JAVA环境,检验环境:java -version Step2:下载并解压zookeeper # cd /usr/local # wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.12.tar.gz # tar -zxvf zookeeper-3.4.12.tar.gz # cd zookeeper-3.4.12 Step3:重命名 zoo_sample.cfg文件 # cp conf/zoo_sample.cfg conf/zoo-1.cfg Step4:修改配置文件zoo-1.cfg,原配置文件里有的,修改成下面的值,没有的则加上 # vim conf/zoo-1.cfg dataDir=/tmp/zookeeper-1 clientPort=2181 server.1=127.0.0.1:2888:3888 server.2=127.0.0.1:2889:3889 server.3=127.0.0.1:2890:3890 配置说明 tickTime:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。 initLimit:这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒 syncLimit:这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10秒 dataDir:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。 clientPort:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。 server.A=B:C:D:其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。 Step5:再从zoo-1.cfg复制两个配置文件zoo-2.cfg和zoo-3.cfg,只需修改dataDir和clientPort不同即可 # cp conf/zoo-1.cfg conf/zoo-2.cfg # cp conf/zoo-1.cfg conf/zoo-3.cfg # vim conf/zoo-2.cfg dataDir=/tmp/zookeeper-2 clientPort=2182 # vim conf/zoo-2.cfg dataDir=/tmp/zookeeper-3 clientPort=2183 Step6:标识Server ID 创建三个文件夹/tmp/zookeeper-1,/tmp/zookeeper-2,/tmp/zookeeper-2,在每个目录中创建文件myid 文件,写入当前实例的server id,即1.2.3 # cd /tmp/zookeeper-1 # vim myid 1 # cd /tmp/zookeeper-2 # vim myid 2 # cd /tmp/zookeeper-3 # vim myid 3 Step7:启动三个zookeeper实例 # bin/zkServer.sh start conf/zoo-1.cfg # bin/zkServer.sh start conf/zoo-2.cfg # bin/zkServer.sh start conf/zoo-3.cfg Step8:检测集群状态,也可以直接用命令“zkCli.sh -server IP:PORT”连接zookeeper服务端检测: 至此,我们对zookeeper就算有了一个入门的了解,当然zookeeper远比我们这里描述的功能多,比如用zookeeper实现集群管理,分布式锁,分布式队列,zookeeper集群leader选举等等。
从本地Linux复制文件到远程另一台Linux上: 比如将本地Linux的aa.txt文件复制到远程192.168.118.129机器的Linux的/home/www/file目录下 scp ./aa.txt root@192.168.118.129:/home/www/file 从远程Linux复制文件到本地Linux上: 比如将远程192.168.118.129机器的Linux的/home/www/file目录下的aa.txt文件或所有文件复制到本地Linux的/home/myfile/目录下 scp root@192.168.118.129:/home/www/file/aa.txt /home/myfile/ scp -r root@192.168.118.129:/home/www/file/ /home/myfile/
大多数互联网系统都是分布式部署的,分布式部署确实能带来性能和效率上的提升,但为此,我们就需要多解决一个分布式环境下,数据一致性的问题。 当某个资源在多系统之间,具有共享性的时候,为了保证大家访问这个资源数据是一致的,那么就必须要求在同一时刻只能被一个客户端处理,不能并发的执行,否者就会出现同一时刻有人写有人读,大家访问到的数据就不一致了。 一、我们为什么需要分布式锁? 在单机时代,虽然不需要分布式锁,但也面临过类似的问题,只不过在单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制,即当某个线程获取到这个资源后,就立即对这个资源进行加锁,当使用完资源之后,再解锁,其它线程就可以接着使用了。例如,在JAVA中,甚至专门提供了一些处理锁机制的一些API(synchronize/Lock等) 但是到了分布式系统的时代,这种线程之间的锁机制,就没作用了,系统可能会有多份并且部署在不同的机器上,这些资源已经不是在线程之间共享了,而是属于进程之间共享的资源。 因此,为了解决这个问题,我们就必须引入「分布式锁」。 分布式锁,是指在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问。 分布式锁要满足哪些要求呢? 排他性:在同一时间只会有一个客户端能获取到锁,其它客户端无法同时获取 避免死锁:这把锁在一段有限的时间之后,一定会被释放(正常释放或异常释放) 高可用:获取或释放锁的机制必须高可用且性能佳 讲完了背景和理论,那我们接下来再看一下分布式锁的具体分类和实际运用。 二、分布式锁的实现方式有哪些? 目前主流的有三种,从实现的复杂度上来看,从上往下难度依次增加: 基于数据库实现 基于Redis实现 基于ZooKeeper实现 无论哪种方式,其实都不完美,依旧要根据咱们业务的实际场景来选择。 基于数据库实现: 基于数据库来做分布式锁的话,通常有两种做法: 基于数据库的乐观锁 基于数据库的悲观锁 我们先来看一下如何基于「乐观锁」来实现: 乐观锁机制其实就是在数据库表中引入一个版本号(version)字段来实现的。 当我们要从数据库中读取数据的时候,同时把这个version字段也读出来,如果要对读出来的数据进行更新后写回数据库,则需要将version加1,同时将新的数据与新的version更新到数据表中,且必须在更新的时候同时检查目前数据库里version值是不是之前的那个version,如果是,则正常更新。如果不是,则更新失败,说明在这个过程中有其它的进程去更新过数据了。 下面找图举例, 如图,假设同一个账户,用户A和用户B都要去进行取款操作,账户的原始余额是2000,用户A要去取1500,用户B要去取1000,如果没有锁机制的话,在并发的情况下,可能会出现余额同时被扣1500和1000,导致最终余额的不正确甚至是负数。但如果这里用到乐观锁机制,当两个用户去数据库中读取余额的时候,除了读取到2000余额以外,还读取了当前的版本号version=1,等用户A或用户B去修改数据库余额的时候,无论谁先操作,都会将版本号加1,即version=2,那么另外一个用户去更新的时候就发现版本号不对,已经变成2了,不是当初读出来时候的1,那么本次更新失败,就得重新去读取最新的数据库余额。 通过上面这个例子可以看出来,使用「乐观锁」机制,必须得满足: (1)锁服务要有递增的版本号version (2)每次更新数据的时候都必须先判断版本号对不对,然后再写入新的版本号 我们再来看一下如何基于「悲观锁」来实现: 悲观锁也叫作排它锁,在Mysql中是基于 for update 来实现加锁的,例如: //锁定的方法-伪代码 public boolean lock(){ connection.setAutoCommit(false) for(){ result = select * from user where id = 100 for update; if(result){ //结果不为空, //则说明获取到了锁 return true; } //没有获取到锁,继续获取 sleep(1000); } return false; } //释放锁-伪代码 connection.commit(); 上面的示例中,user表中,id是主键,通过 for update 操作,数据库在查询的时候就会给这条记录加上排它锁。 (需要注意的是,在InnoDB中只有字段加了索引的,才会是行级锁,否者是表级锁,所以这个id字段要加索引) 当这条记录加上排它锁之后,其它线程是无法操作这条记录的。 那么,这样的话,我们就可以认为获得了排它锁的这个线程是拥有了分布式锁,然后就可以执行我们想要做的业务逻辑,当逻辑完成之后,再调用上述释放锁的语句即可。 基于Redis实现 基于Redis实现的锁机制,主要是依赖redis自身的原子操作,例如: SET user_key user_value NX PX 100 redis从2.6.12版本开始,SET命令才支持这些参数: NX:只在在键不存在时,才对键进行设置操作,SET key value NX 效果等同于 SETNX key value PX millisecond:设置键的过期时间为millisecond毫秒,当超过这个时间后,设置的键会自动失效 上述代码示例是指,当redis中不存在user_key这个键的时候,才会去设置一个user_key键,并且给这个键的值设置为 user_value,且这个键的存活时间为100ms 为什么这个命令可以帮我们实现锁机制呢? 因为这个命令是只有在某个key不存在的时候,才会执行成功。那么当多个进程同时并发的去设置同一个key的时候,就永远只会有一个进程成功。 当某个进程设置成功之后,就可以去执行业务逻辑了,等业务逻辑执行完毕之后,再去进行解锁。 解锁很简单,只需要删除这个key就可以了,不过删除之前需要判断,这个key对应的value是当初自己设置的那个。 另外,针对redis集群模式的分布式锁,可以采用redis的Redlock机制。 基于ZooKeeper实现 其实基于ZooKeeper,就是使用它的临时有序节点来实现的分布式锁。 原理就是:当某客户端要进行逻辑的加锁时,就在zookeeper上的某个指定节点的目录下,去生成一个唯一的临时有序节点, 然后判断自己是否是这些有序节点中序号最小的一个,如果是,则算是获取了锁。如果不是,则说明没有获取到锁,那么就需要在序列中找到比自己小的那个节点,并对其调用exist()方法,对其注册事件监听,当监听到这个节点被删除了,那就再去判断一次自己当初创建的节点是否变成了序列中最小的。如果是,则获取锁,如果不是,则重复上述步骤。 当释放锁的时候,只需将这个临时节点删除即可。 如图,locker是一个持久节点,node_1/node_2/…/node_n 就是上面说的临时节点,由客户端client去创建的。 client_1/client_2/…/clien_n 都是想去获取锁的客户端。以client_1为例,它想去获取分布式锁,则需要跑到locker下面去创建临时节点(假如是node_1)创建完毕后,看一下自己的节点序号是否是locker下面最小的,如果是,则获取了锁。如果不是,则去找到比自己小的那个节点(假如是node_2),找到后,就监听node_2,直到node_2被删除,那么就开始再次判断自己的node_1是不是序列中最小的,如果是,则获取锁,如果还不是,则继续找一下一个节点。 以上,就讲完了为什么我们需要分布式锁这个技术,以及分布式锁中常见的三种机制。
适配器模式是将一个类的接口变成客户端所期待的另一种接口,从而使原本接口不匹配而无法在一起工作的两个类能够在一起工作,适配器模式也称变压器模式。 适配器模式主要分为两种:类适配器 和 对象适配器。 类适配器是基于继承,对象适配器是基于组合,我们推荐多用组合少用继承。 我们举个例子: 如下图,现给手机充电,但墙上均为三脚插孔,而手机充电器为两脚插头,从而无法为手机充电,所以我们需要一个带有三脚插头的插线板,同时插线板上须有两脚插孔。 如此,手机-->手机充电器插头-->插线板-->墙上的三脚插孔,完成手机充电。 一个适配器模式主要有如下几个要素: 1、Source源角色(墙上的三脚插孔) 2.、Adapter适配器角色(插线板) 3.、Target目标角色(手机充电器插头) 4、Client客户端角色(手机) 下面我们来看一下如何用代码描述这个适配器的过程: /**源对象 (墙上的三脚插孔)*/ public class Source { public void method() { System.out.println("我是墙上的三脚插孔,我只接受三脚插头的"); } } /**适配器目标接口(手机充电器插头)*/ public interface Target { /**与源对象中的方法相同 */ public void method(); } /**类适配器方式,通过继承实现(插线板)*/ public class Adapter extends Source implements Target { public void method() { //调用父类的方法 super.method(); } } /**对象适配器方式,通过组合实现(插线板)*/ public class Adapter2 implements Target { /**对源对象的引用*/ private Source source; public Adapter2 (Source source) { this.source = source; } @Override public void method() { source.method(); } } /**客户端(手机)*/ public class Client { public static void main(String[] args) { //类适配器方式 Target target = new Adapter(); target.method(); //对象适配器方式 Source source = new Source(); Target target2 = new Adapter2(source); target2.method(); } } 以上就是一个适配器模式的典型代码模板,在Java IO中,字节流InputStream转换为字符流,中间即使用了适配器模式InputStreamReader,适配器模式可以让两个没有关系的类在一起运行。
AWK是Linux上卓越的文本处理工具,它具有非常简单的语法结构,拥有强大的文本处理能力。AWK 是一种解释执行的编程语言,AWK 的名称是由它们设计者的名字缩写而来 —— Afred Aho, Peter Weinberger 与 Brian Kernighan。 目前总共有如下几种不同的 AWK 版本。 AWK——这个版本是 AWK 最原初的版本,它由 AT&T 实验室开发。 NAWK ——NAWK(New AWK)是 AWK 的改进增强版本。 GAWK—— GAWK 即 GNU AWK,所有的 GNU/Linux 发行版都包括 GAWK,且 GAWK 完全兼容 AWK 与 NAWK。 AWK 可以做非常多的工作。 下面只是其中部分 AWK 的典型应用场景: 文本处理, 生成格式化的文本报告, 进行算术运算, 字符串操作,以及其它更多。 linux 默认安装了gawk,使用which gawk,如果输出/bin/gawk,说明已经安装了gawk,否则需要我们安装,可以使用 yum 包管理工具安装: [root]# yum install gawk 另外我们也可以通过源码编译的方式安装gawk: step 1——从可信的源下载源代码。可以在命令行使用 wget 命令下载。 [jerry]$ wget http://ftp.gnu.org/gnu/gawk/gawk-4.1.1.tar.xz step 2——解压并提取下载的源代码。 [jerry]$ tar xvf gawk-4.1.1.tar.xz step 3——切换至解压后的目录并运行 configure 命令 [jerry]$ ./configure step 4——configure 命令成功执行后会生成一个 Makefile 文件。 接下来使用 make 命令编译源代码。 [jerry]$ make step 5——你可以运行测试工具保证 build 是干净的。 这一步是可选的。 [jerry]$ make check step 6——最后一步,安装 AWK。 安装前请确认你有超级用户的权限。 [jerry]$ sudo make install 通过以上六个步骤,你就成功地编译并安装了 AWK。 你可以通过如下的命令来确认 awk 安装成功: gawk的版本通过: gawk --version 查看 [jerry]$ which awk 执行上面的命令,你将会得到如下的结果: /usr/bin/awk awk的工作流程: 读( Read )、执 行( Execute )与重复( Repeat ) 读入一行执行一行,直到文件末尾。 gawk的程序结构: 开始块(BEGIN block),以大写BEGIN开头,必须大写,这一部分是可选的,可有可无。如: BEGIN {awk-commands} 主体块(Body Block),如: /pattern/ {awk-commands} 结束块(END Block),以大写END结束,必须大写,这一部分是可选的,可有可无。如: END {awk-commands} awk的整体语法格式是: awk '/pattern/ {action}' file 其中单引号是为了和shell命令区分开; /pattern/ 是一个过滤器,匹配这个模式的行才会被action的命令处理; {}是一个命令组,action是具体执行的命令; file是要处理的文件 其中/pattern/ 和{action}必须要有一个, awk可以直接在命令行执行执行命令,也可以通过编写好脚本,然后执行脚本。 通过命令行的方式: 输出marks.txt文件的内容: [jerry]$ awk '{print}' marks.txt 再比如输出tomcat日志: gawk '{print}' /usr/local/apache-tomcat-8.0.30/logs/catalina.out 另外一种提供 AWK 命令的方式——通过脚本文件提供: awk [option] -f file .... 首先,创建一个文本文件 command.awk,在文件中输入如下 AWK 命令: {print} 现在,我们可以调用 AWK 从文本文件中读入命令并执行。这里,我们实现了与上面例子相同的效果: [jerry]$ awk -f command.awk marks.txt awk有一些标准选项: -v 选项 这个选项可以为变量赋值。它允许在程序执行之前为变量赋值。下面是一个 -v 选项使用的示例程序: [jerry]$ awk -v name=Jerry 'BEGIN{printf "Name = %s\n", name}' 执行上面的命令可以得到如下的结果: Name = Jerry --dump-variables[=file] 选项 此选项会将全局变量及相应值按序输出到指定文件中。默认的输出文件名是 awkvars.out。 [jerry]$ awk --dump-variables '' [jerry]$ cat awkvars.out awk的基本使用示例: 默认情况下,如果某行与模式串匹配,AWK 会将整行输出: [jerry]$ awk '/a/ {print}' marks.txt 这个输出marks.txt文件中匹配 a 字符的所在行。 输出某一列,比如输出第三列: [jerry]$ awk '/a/ {print $3}' marks.txt 统计模式串成功匹配的次数,并将该结果打印出来: [jerry]$ awk '/JVM/ {++count} END {print count }' catalina.out 输出字符数多于 18 的行: [jerry]$ awk 'length($0) > 18' marks.txt 查询文件中匹配的字符: awk '/uid=10001/' catalina.out 在catalina.out文件中找匹配uid=10001的行,和 grep类似的功能。 awk '/uid=10*/' catalina.out awk数组操作,比如删除数组的元素: [jerry]$ awk 'BEGIN { fruits["mango"]="yellow"; fruits["orange"]="orange"; delete fruits["orange"]; print fruits["orange"] }' awk的逻辑语言ifelse: [jerry]$ awk 'BEGIN { a=30; if (a==10) print "a = 10"; else if (a == 20) print "a = 20"; else if (a == 30) print "a = 30"; }' awk的循环: [jerry]$ awk 'BEGIN { for (i = 1; i <= 5; ++i) print i }' [jerry]$ awk 'BEGIN {i = 1; while (i < 6) { print i; ++i } }' awk里面有很多内置函数,数学函数,字符串函数,日期函数等。 还可以自定义函数。 awk也可以把输出重定向到文件: [jerry]$ awk 'BEGIN { print "Hello, World !!!" > "/tmp/message.txt" }' >> 双大于号是追加。 awk也可以使用管道: [jerry]$ awk 'BEGIN { print "hello, world !!!" | "tr [a-z] [A-Z]" }'
对于JavaWeb开发人员而言,Tomcat已成为默认的web服务器,但是在生产环境下使用Tomcat部署应用,我们如果采用Tomcat默认的配置,尤其是内存和线程的配置,其配置都很低,容易成为性能瓶颈,所以我们需要对Tomcat服务器进行优化,提升其运行性能,下面我们一起来看看Tomcat如何优化? 一、Tomcat内存优化,启动时告诉JVM需要多大内存(调优内存是最直接的方式) Windows 下的 catalina.bat Linux 下的 catalina.sh 在该文件中配置jvm的内存空间,如: JAVA_OPTS='-Xms256m -Xmx512m' -Xms<size> JVM初始化堆的大小 -Xmx<size> JVM堆的最大值,实际参数大小根据服务器配置或者项目具体设置; 二、Tomcat 线程优化 在server.xml中配置 比如: <Connector port="80" protocol="HTTP/1.1" maxThreads="600" minSpareThreads="100" maxSpareThreads="500" acceptCount="700" connectionTimeout="20000" /> maxThreads="X" 表示最多同时处理X个连接 minSpareThreads="X" 初始化X个连接 maxSpareThreads="X" 表示如果最多可以有X个线程,一旦超过X个,则会关闭不在需要的线程 acceptCount="X" 当同时连接的人数达到maxThreads时,还可以排队,队列大小为X.超过X就不处理 三、Tomcat IO 优化 1:同步阻塞IO(JAVA BIO) 同步并阻塞,服务器实现模式为一个连接一个线程(one connection one thread 想想都觉得恐怖,线程可是非常宝贵的资源),当然可以通过线程池机制改善. 2:JAVA NIO 又分为同步非阻塞IO,异步阻塞IO与BIO最大的区别one request one thread.可以复用同一个线程处理多个connection(多路复用). 3:异步非阻塞IO(Java NIO2又叫AIO) 主要与NIO的区别主要是操作系统的底层区别,可以做个比喻:比作快递,NIO就是网购后要自己到官网查下快递是否已经到了(可能是多次),然后自己去取快递;AIO就是快递员送货上门了(不用关注快递进度)。 BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解. NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持. AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持. 在server.xml中 <Connector port="80" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="20000" URIEncoding="UTF-8" useBodyEncodingForURI="true" enableLookups="false" redirectPort="8443" /> 实现对Tomcat的IO切换。 四、大杀器APR APR是从操作系统级别来解决异步的IO问题,大幅度的提高性能. (http://apr.apache.org/)。 APR(Apache Portable Runtime)是一个高可移植库,它是Apache HTTP Server 2.x 的核心,能更好地和其它本地web技术集成,总体上让Java更有效率作为一个高性能web服务器平台而不是简单作为后台容器; 在产品环境中,特别是直接使用Tomcat做WEB服务器的时候,应该使用Tomcat Native来提高其性能,如果不配APR,基本上300个线程狠快就会用满,以后的请求就只好等待.但是配上APR之后,并发的线程数量明显下降,从原来的300可能会马上下降到只有几十,新的请求会毫无阻塞的进来; 在局域网环境测,就算是400个并发,也是一瞬间就处理/传输完毕,但是在真实的Internet环境下,页面处理时间只占0.1%都不到,绝大部分时间都用来页面传输,如果不用APR,一个线程同一时间只能处理一个用户,势必会造成阻塞,所以生产环境下用apr是非常必要的. 安装Apache Tomcat Native Library,直接启动就支持apr(http://tomcat.apache.org/native-doc/)它本身是基于APR的,排除代码问题Tomcat优化到这个层次,可以应对大部分性能需求; 最后,优化的前提条件是良好的代码质量和设计。
什么是 REST ? REST架构风格描述了六个约束。应用于体系结构的这些约束最初由Roy Fielding在他的博士论文中提出(参见 https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm)该文是RESTful-style的基础 。 这六个约束是: Uniform Interface (统一接口) 统一接口约束定义了客户端和服务器之间的接口。它简化并解耦了架构,使每个部件都能独立演变。 统一接口的四个指导原则是: Resource-Based (基于资源的) 使用URI作为资源标识符在请求中标识各个资源。 资源本身在概念上与返回给客户端的表示(representations)分开。 例如,服务器不直接发送其数据库内容,而是发送一些表示某些数据库记录的HTML,XML或JSON,例如,用芬兰语表示并以UTF-8编码,具体取决于请求的详细信息和服务器实现。 Manipulation of Resources Through Representations 当客户端持有资源的表示(包括附加的任何元数据)时,它有足够的信息来修改或删除服务器上的资源,前提是它有权这样做。 Self-descriptive Messages (自描述信息) 每条消息都包含足够的信息来描述如何处理该消息。 例如,要调用的解析器可以由Internet媒体类型 media type(以前称为MIME类型)指定。 响应还明确指出了它们是否可以被缓存。 Hypermedia as the Engine of Application State (HATEOAS) (超媒体作为应用程序状态引擎) 客户端通过body 内容,查询字符串参数,请求header和请求的URI(资源名称)来提供状态。 服务通过正文内容,响应代码和响应标头向客户提供状态。 这在技术上被称为超媒体(或超文本中的超链接)。 除了上面的描述之外,HATEOS还意味着,在必要时,链接包含在返回的正文(或标题)中,以提供用于检索对象本身或相关对象的URI。 我们稍后会详细讨论这个问题。 任何REST服务必须提供的统一接口是其设计的基础 Stateless (无状态的) 由于REST是REpresentational State Transfer的首字母缩写,statelessness (无状态)是关键。 这意味着要处理请求的状态必须包含在请求本身中,无论是作为URI,查询字符串参数,正文还是标题的一部分。 URI唯一标识资源,body包含该资源的状态(或状态变化)。 然后,在服务器进行处理之后,通过headers,状态和响应主体将适当的状态或重要状态的片断传送回客户端。 我们大多数已经在业界工作了一段时间的人习惯于在container(不是docker中的) 内编程,这为我们提供了“session”的概念,它在多个HTTP请求中维护状态。 在REST中,客户端必须包含服务器的所有信息以完成请求,如果该状态必须跨越多个请求,则根据需要重新发送状态。 无状态可以实现更高的可伸缩性,因为服务器不必维护,更新或传递该会话状态。 此外,负载均衡器不必担心无状态系统的会话亲和性。 那么状态(state)和资源(resource)之间的区别是什么? 状态或应用程序状态是服务器关心的,以满足当前会话或请求所需的请求数据。 资源或资源状态是定义资源表示的数据 - 例如,存储在数据库中的数据。 将应用程序状态视为可能因客户端和每个请求而异的数据。 另一方面,资源状态在请求它的每个客户端都是不变的。 Cacheable 与万维网一样,客户端可以缓存响应。 因此,响应必须隐式或显式地将自身定义为可缓存或不可缓存,以防止客户端重用陈旧或不适当的数据以影响进一步的请求。 管理良好的缓存部分或完全消除了一些客户端 - 服务器交互,进一步提高了可伸缩性和性能。 Client-Server 统一接口将客户端与服务器分开。 这种关注点分离意味着,例如,客户端不关心数据存储,数据存储仍保留在每个服务器的内部,从而提高了客户端代码的可移植性。 服务器不关心用户界面或用户状态,因此服务器可以更简单,更具可伸缩性。 只要不改变接口,服务器和客户端也可以独立替换和开发。 Layered System (分层系统) 客户端通常无法判断它是直接连接到终端服务器,还是中间服务器。 中间服务器可以通过启用负载平衡和提供共享缓存来提高系统可伸缩性。 Layers 也可以实施安全策略。 Code on Demand (optional) 服务器能够通过向客户端传输可以执行的逻辑来临时扩展或自定义客户端的功能。 这样的示例可以包括编译的组件,例如Java applet和客户端脚本,例如JavaScript。 遵守这些约束,从而符合REST架构风格,将使任何类型的分布式超媒体系统具有理想的紧急(emergent)属性,例如性能,可伸缩性,简单性,可修改性,可见性,可移植性和可靠性。 注意:REST架构的唯一可选约束是code on demand。 如果服务违反任何其他约束,则严格来讲不能将其称为RESTful。 REST API Quick Tips 无论技术上是不是REST(根据前面提到的六个约束条件),这里有一些推荐的类似REST的概念。 这六个快速提示将带来更好,更实用的服务。 使用HTTP动词使你的请求带有含意 API使用者能够发送GET,POST,PUT和DELETE请求,这极大地增强了给定请求的清晰度。 通常,四个主要的HTTP动词使用如下: GET :读取特定资源(通过标识符)或资源集合。 PUT :更新特定资源(通过标识符)或资源集合。 如果资源标识符是事先已知的,也可以用于创建特定资源。 DELETE :通过标识符删除指定资源。 POST :创建一个新资源。 对于不适合其他类别的操作,也是一个万能的动词。 注意 :GET请求不得更改任何底层资源数据。 但可能会更新测量和跟踪数据,但URI标识的资源数据不应更改。 提供合理的资源名称 制作出色的API需要80%的艺术和20%的科学。 创建表示合理资源的URL层次结构是艺术部分。 拥有合理的资源名称(只是URL路径,例如/customers/12345/orders)可以提高给定请求的清晰度。 适当的资源名称为服务请求提供上下文,从而提高API的可理解性。 通过URI名称对资源进行分层查看,为消费者提供友好,易于理解的资源层次结构,以便在其应用程序中使用。 以下是一些URL路径(资源名称)设计的规则: 在你的网址中使用标识符,而不是在查询字符串中使用。 使用URL查询字符串参数非常适合过滤,但不适用于资源名称。 Good: /users/12345 Poor: /api?type=user&id=23 利用URL的分层特性来表示结构 为你的客户而不是数据设计 资源名称应为名词 避免使用动词作为资源名称,以提高清晰度。 使用HTTP methods 指定请求的动词部分。 在URL段中使用复数形式,使用集合使API URI在所有HTTP方法中保持一致。 Recommended: /customers/33245/orders/8769/lineitems/1 Not: /customer/33245/order/8769/lineitem/1 避免在URL中使用集合词 例如'customer_list'作为资源。 使用复数来隐含表示集合(例如,customers 代替customer_list)。 在URL段中使用小写,用下划线('_')或连字符(' - ')分隔单词 有些服务器会忽略大小写,所以最好清楚。 保持URL尽可能短,尽可能少的分段 使用HTTP响应代码指示状态 响应状态代码是HTTP规范的一部分。 它们中有很多可以gggg解决最常见的情况。 本着使RESTful服务包含HTTP规范的精神,我们的Web API应该返回相关的HTTP状态代码。 例如,当成功创建资源时(例如,来自POST请求),API应该返回HTTP状态代码201.这里有常用的HTTP状态代码列表,其列出了每个的详细描述。 十大HTTP响应状态代码的建议用法如下: 200 OK :通用成功状态代码。 这是最常见的代码。 用于表示成功。 201 CREATED :成功创建(通过POST或PUT)。 将Location header设置为包含指向新创建的资源的链接(在POST上)。 响应body 内容可能存在也可能不存在。 204 NO CONTENT :表示成功,但响应body中没有任何内容,通常用于DELETE和PUT操作。 400 BAD REQUEST :完成请求时的一般错误会导致无效状态。 例如域验证错误,缺少数据等。 401 UNAUTHORIZED :丢失或无效的身份验证令牌。 403 FORBIDDEN :当用户未被授权执行操作或资源由于某种原因(例如时间限 制等)不可用时的错误代码。 404 NOT FOUND :在找不到请求的资源时使用,不管是否不存在,或者是否是401或403,出于安全原因,服务需要屏蔽。 405 METHOD NOT ALLOWED :表示请求的URL存在,但请求的HTTP方法不对。 例如,POST /users/12345,其API不支持以这种方式创建资源(使用提供的ID)。 返回405时必须设置Allow header表明支持的HTTP方法。 例如:"Allow: GET, PUT, DELETE" 409 CONFLICT :请求导致资源冲突时。 重复条目,例如尝试创建具有相同信息的两个客户,以及在不支持级联删除时删除根对象。 500 INTERNAL SERVER ERROR :永远不要故意返回该状态码。 服务器端抛出异常时应使用catch-all捕捉。 仅将此用于客户端无法解决的错误(即服务器错误,client做啥也没有用,请联系后端人员解决)。 提供JSON和XML 一般只支持JSON就可以了,除非是高度标准化和受监管的行业,需要XML。 模式验证和命名空间,并提供JSON和XML,是非常高的。 理想情况下,让消费者使用HTTP Accept header在格式之间切换,或者只是在URL上将.xml的扩展名更改为.json。 请注意,一旦我们开始讨论XML支持,我们就会开始讨论用于验证,命名空间等的模式。除非你的行业需要,否则请尽量避免支持所有这些复杂性。 JSON旨在简化,简洁和实用。 如果可以的话,让你的XML看起来更简洁。 换句话说,使返回的XML更像JSON - 简单易读,不存在架构和命名空间细节,只有数据和链接。 如果它最终比这更复杂,那么XML的成本将是惊人的。 根据我的经验,过去几年没有人使用过XML响应,成本太昂贵了。 请注意,JSON-Schema提供了架构式验证功能。 创建细粒度资源 在开始时,最好创建模仿系统的底层应用程序域模型或数据库体系结构的API。 最终,聚合那些需要利用多个底层资源的服务来减少通信量。 但是,从单个资源创建更大的资源比从更大的聚合创建细粒度或单个资源要容易得多。 让自己轻松自如,从易于定义的小型资源开始,为这些资源提供CRUD功能。 你可以之后创建这些方面的用例,减少通信。 考虑连接性 REST的原则之一是连通性 - 通过超媒体链接(搜索HATEOAS)。 虽然没有超链接,服务仍然有用,但在响应中返回链接时,API会变得更具自我描述性和可发现性。 至少,“自我描述”的链接引用会告诉客户端如何检索数据。 此外,利用HTTP Location header 包含通过POST(或PUT)创建资源的链接。 对于在支持分页的响应中返回的集合,“first”,“last”,“next”和“prev”链接至少是非常有用的。 关于链接格式,有很多。 HTTP Web链接规范(RFC5988)解释了如下链接: 链接是由Internationalised Resource Identifiers (IRIs) [RFC3987]标识的两个资源之间的类型连接,包括: 上下文IRI, 链接关系类型 目标IRI,和 可选的目标属性。 链接可以被视为“{context IRI}在{target IRI}具有{relation type}资源的形式的声明,其具有{target attributes}。” 至少,按照规范中的建议放置HTTP链接头中的链接,或者在JSON表示中包含此HTTP链接样式的JSON表示(例如Atom样式链接,请参阅:RFC4287)。 之后,随着REST API变得更加成熟,你可以在更复杂的链接样式中进行分层,例如HAL + JSON,Siren,Collection + JSON 或 JSON-LD等。
什么是微服务架构 我们知道分布式强调系统的拆分,其实微服务也是强调系统的拆分,微服务架构属于分布式架构的范畴; 并且到目前为止,微服务并没有一个统一的标准的定义,那么微服务究竟是什么? 微服务一词源于Martin Fowler(马丁.福勒)的名为 Microservices 的博文, 可以在他的官方博客上找到这篇文章:http://martinfowler.com/articles/microservices.html 中文翻译版本:https://www.martinfowler.cn/articles/microservices.html 简单地说, 微服务是系统架构上的一种设计风格, 它的主旨是将一个原本独立的系统拆分成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP的RESTful API进行通信协作; 被拆分后的每一个小型服务都围绕着系统中的某一项业务功能进行构建, 并且每个服务都是一个独立的项目,可以进行独立的测试、开发和部署等; 由于各个独立的服务之间使用的是基于HTTP的JSON作为数据通信协作的基础,所以这些微服务可以使用不同的语言来开发; 微服务架构的优缺点 1、我们知道微服务架构是将系统中的不同功能模块拆分成多个不同的服务,这些服务进行独立地开发和部署,每个服务都运行在自己的进程内,这样每个服务的更新都不会影响其他服务的运行; 2、由于每个服务是独立部署的,所以我们可以更准确地监控每个服务的资源消耗情况,进行性能容量的评估,通过压力测试,也很容易发现各个服务间的性能瓶颈所在; 3、由于每个服务都是独立开发,项目的开发也比较方便,减少代码的冲突、代码的重复,逻辑处理流程也更加清晰,让后续的维护与扩展更加容易; 4、微服务可以使用不同的编程语言进行开发; 但是在系统架构领域关于微服务架构也有一些争论,有人倾向于在系统设计与开发中采用微服务架构实现软件系统的低耦合,被认为是系统架构的未来方向,Martin Fowler(马丁.福勒)也给微服务架构很高的评价; 同时,对微服务架构也有人持反对观点,他们表示: 1、微服务架构增加了系统维护、部署的难度,导致一些功能模块或代码无法复用; 2、随着系统规模的日渐增长,微服务在一定程度上也会导致系统变得越来越复杂,增加了集成测试的复杂度; 3、随着微服务的增多,数据的一致性问题,服务之间的通信成本等都凸显了出来; 所以在系统架构时也要提醒自己:不要为了微服务而微服务。 为什么选择Spring Cloud构建微服务 微服务一词是Martin Fowler(马丁.福勒)于2014年提出来的,近几年微服务架构的讨论非常火热,无数的架构师和开发者在实际项目中实践着微服务架构的设计理念,他们在微服务架构中针对不同应用场景出现的各种问题,也推出了很多解决方案和开源框架,其中我们国内的互联网企业也有一些著名的框架和方案; 整个微服务架构是由大量的技术框架和方案构成,比如: 服务基础开发 Spring MVC、Spring、SpringBoot 服务注册与发现 Netflix的Eureka、Apache的ZooKeeper等 服务调用 RPC调用有阿里巴巴的Dubbo,Rest方式调用有当当网Dubbo基础上扩展的Dubbox 分布式配置管理 百度的Disconf、360的QConf、淘宝的Diamond、Netflix的Archaius等 负载均衡 Ribbon 服务熔断 Hystrix API网关 Zuul 批量任务 当当网的Elastic-Job、Linkedln的Azkaban 服务跟踪 京东的Hydra、Twitter的Zipkin等 但是在微服务架构上,几乎大部分的开源组件都只能解决某一个场景下的问题,所以这些实施微服务架构的公司也是整合来自不同公司或组织的诸多开源框架,并加入针对自身业务的一些改进,没有一个统一的架构方案; 所以当我们准备实施微服务架构时,我们要整合各个公司或组织的开源软件,而且某些开源软件又有多种选择,这导致在做技术选型的初期,需要花费大量的时间进行预备研、分析和实验,这些方案的整合没有得到充分的测试,可能在实践中会遇到各种各样的问题; Spring Cloud的出现,可以说是为微服务架构迎来一缕曙光,有SpringCloud社区的巨大支持和技术保障,让我们实施微服务架构变得异常简单了起来,它不像我们之前所列举的框架那样,只是解决微服务中的某一个问题,而是一个解决微服务架构实施的综合性解决框架,它整合了诸多被广泛实践和证明有效的框架作为实施的基础组件,又在该体系基础上创建了一些非常优秀的边缘组件将它们很好地整合起来。 加之Spring Cloud 有其Spring 的强大技术背景,极高的社区活跃度,也许未来Spring Cloud会成为微服务的标准技术解决方案;
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。 3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0 4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num=10 or num=20 可以这样查询: select id from t where num=10 union all select id from t where num=20 5.下面的查询也将导致全表扫描: select id from t where name like '%abc%' 若要提高效率,可以考虑全文检索。 6.in 和 not in 也要慎用,否则会导致全表扫描,如: select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 3 7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描: select id from t where num=@num 可以改为强制查询使用索引: select id from t with(index(索引名)) where num=@num 8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where num/2=100 应改为: select id from t where num=100*2 9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where substring(name,1,3)='abc' --name以abc开头的id select id from t where datediff(day,createdate,'2005-11-30')=0 --'2005-11-30'生成的id 应改为: select id from t where name like 'abc%' select id from t where createdate>='2005-11-30' and createdate<'2005-12-1' 10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将不能正确使用索引。 11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。 12.不要写一些没有意义的查询,如需要生成一个空表结构: select col1,col2 into #t from t where 1=0 这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样: create table #t(...) 13.很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num) 14.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。 15.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。 16.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。 17.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。 18.尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。 19.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。 20.尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。 21.避免频繁创建和删除临时表,以减少系统表资源的消耗。 22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。 23.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。 24.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。 25.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。 26.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。 27.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。 28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。 29.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。 30.尽量避免大事务操作,提高系统并发能力。
我在给公司的一个H5页面添加搜索框,前端原先就用的mui,正常添加input框后,手机测试出现了问题: Android : input框有内容搜索跳转后,按返回键,input不能聚焦,键盘不弹出 IOS : 正常进入页面input框不能聚焦,键盘不弹出,多次点击可能会聚焦 代码如下: <div> <a class="mui-btn mui-btn-link" style="float: right;width: 14.5%;height: 34px;" onclick="searchInputWay()">搜索</a> <div class="mui-content-padded" style="margin: 5px;"> <div class="mui-input-row mui-search" style="width: 86%"> <input type="search" id="searchInput" class="muiSearchInput mui-input-clear" placeholder="请输入手机号码" onchange="searchInputWay()"> </div> </div> </div> CSS如下: .mui-search .mui-placeholder { font-size: 16px; line-height: 34px; position: absolute; z-index: 999; top: 0; right: 0; bottom: 0; left: 0; display: inline-block; height: 34px; text-align: center; color: #999; border: 0; border-radius: 6px; background: 0 0; } 研究了一会儿觉得可能是 mui 的一些问题,然后就去官网学习了一下 发现或许是mui的input框的事件穿透,可能会导致上面描述的一些问题 最后整理了一下解决方法 ( PS:我的是第2种情况 ) : 1.css里可能写了-webkit-user-select:none,并且作用域覆盖到了input框。 解决方法 : css样式修改为 -webkit-user-select:auto 2.在mui-search外面包含了mui-inner-wrap 。mui-placehold的绝对定位后在iOS端产生事件穿透。 解决方法 : 添加css样式,设置pointer-events属性。 <style> .mui-search .mui-placeholder { pointer-events: none; } </style> 3.input框没有添加 type 属性??? 这个...看情况添加一个吧,text或者search
MyBatis 四大核心主要包括(SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper)。 MyBatis 作为互联网数据库映射工具界的“上古神器”,训有四大“神兽”,谓之:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper。可以说,了解了这四大核心,便可知 MyBatis 八 九。 SqlSessionFactoryBuilder 从命名上可以看出,这个是一个 Builder 模式的,用于创建 SqlSessionFactory 的类。SqlSessionFactoryBuilder 根据配置来构造 SqlSessionFactory。 其中配置方式有两种 1. XML 文件方式 XML 文件方式是作为常用的一种方式: String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); mybatis-config.xml 就是我们的配置文件: PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> 2. Java Config 这是第二种配置方式,通过 Java 代码来配置: DataSource dataSource = BlogDataSourceFactory.getBlogDataSource(); TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("development", transactionFactory, dataSource); Configuration configuration = new Configuration(environment); configuration.addMapper(BlogMapper.class); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); Java Config 相比较 XML 文件的方式而言,会有一些限 制。比如修改了配置文件需要重新编译,注解方式没有 XML 配置项多等。所以,业界大多数情况下是选择 XML 文件的方式。但到底选择哪种方式,这个要取决与自己团队的需要。比如,项目的 SQL 语句不复杂,也不需要一些高级的 SQL 特性,那么 Java Config 则会更加简洁一点;反之,则可以选择 XML 文件的方式。 SqlSessionFactory SqlSessionFactory 顾名思义,是用于生产 SqlSession 的工厂。 通过如下的方式来获取 SqlSession 实例: SqlSession session = sqlSessionFactory.openSession(); SqlSession SqlSession 包含了执行 SQL 的所有的方法。以下是示例: SqlSession session = sqlSessionFactory.openSession(); try { Blog blog = session.selectOne( "org.mybatis.example.BlogMapper.selectBlog", 101); } finally { session.close(); } 当然,下面的方式可以做到类型安全: SqlSession session = sqlSessionFactory.openSession(); try { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(101); } finally { session.close(); } Mapper Mapper 顾名思义,是用做 Java 与 SQL 之间的映射的。包括了 Java 映射为 SQL 语句,以及 SQL 返回结果映射为 Java。 比如,下面是一个常见的 Mapper 接口映射文件: select * from Blog where id = #{id} 其中 "org.mybatis.example.BlogMapper" 就是我们要射射的接口,selectBlog 就是BlogMapper上的方法。而这个 selectBlog 具体就是要执行“select * from Blog where id = #{id}”这个 SQL 语句。 这样,我们就能通过 Blog blog = session.selectOne( "org.mybatis.example.BlogMapper.selectBlog", 101); 或者是 BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(101); 来获取到执行的结果。 当然,如果是采用注解的方式的话,可以省去 XML 文件: public interface BlogMapper { @Select("SELECT * FROM blog WHERE id = #{id}") Blog selectBlog(int id); }
1、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 2、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。 3、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0 4、应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num=10 or num=20 可以这样查询: select id from t where num=10 union all select id from t where num=20 5、下面的查询也将导致全表扫描: select id from t where name like '%abc%' 若要提高效率,可以考虑全文检索。 6、in 和 not in 也要慎用,否则会导致全表扫描,如: select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 3 7、如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描: select id from t where num=@num 可以改为强制查询使用索引: select id from t with(index(索引名)) where num=@num 8、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where num/2=100 应改为: select id from t where num=100*2 9、应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where substring(name,1,3)='abc'--name以abc开头的id select id from t where datediff(day,createdate,'2005-11-30')=0--'2005-11-30'生成的id 应改为: select id from t where name like 'abc%' select id from t where createdate>='2005-11-30' and createdate<'2005-12-1' 10、不要在where子句中的=左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。 11、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。 12、不要写一些没有意义的查询,如需要生成一个空表结构: select col1,col2 into #t from t where 1=0 这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样: create table #t(...) 13、很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num) 14、并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。 15、索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。 16、应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。 17、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。 18、尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。 19、任何地方都不要使用 select from t ,用具体的字段列表代替“”,不要返回用不到的任何字段。 20、尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。 21、避免频繁创建和删除临时表,以减少系统表资源的消耗。 22、临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。 23、在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。 24、如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。 25、尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。 26、使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。 27、与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。 28、在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。 29、尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。 30、尽量避免大事务操作,提高系统并发能力。
为什么需要限流 按照服务的调用方,可以分为以下几种类型服务 1、与用户打交道的服务 比如web服务、对外API,这种类型的服务有以下几种可能导致机器被拖垮: 用户增长过快(这是好事) 因为某个热点事件(微博热搜) 竞争对象爬虫 恶意的刷单 这些情况都是无法预知的,不知道什么时候会有10倍甚至20倍的流量进来,如果遇到此类情况,扩容是根本来不及的,弹性扩容也是来不及的; 2、对内的RPC服务 一个服务A的接口可能被BCDE多个服务进行调用,在B服务发生突发流量时,直接把A服务给调用挂了,导致A服务对CDE也无法提供服务。 这种情况时有发生,解决方案有两种: 1、每个调用方采用线程池进行资源隔离 2、使用限流手段对每个调用方进行限流 限流算法实现 常见的限流算法有:计数器、令牌桶、漏桶。 1、计数器算法 采用计数器实现限流有点简单粗暴,一般我们会限 制一秒钟的能够通过的请求数,比如限流qps为100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。 具体的实现可以是这样的:对于每次服务调用,可以通过 AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值,通过这个最新值和阈值进行比较。 这种实现方式,相信大家都知道有一个弊端:如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象” 2、漏桶算法 为了消除"突刺现象",可以采用漏桶算法实现限流,漏桶算法这个名字就很形象,算法内部有一个容器,类似生活用到的漏斗,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变。 不管服务调用方多么不稳定,通过漏桶算法进行限流,每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃。 在算法实现方面,可以准备一个队列,用来保存请求,另外通过一个线程池定期从队列中获取请求并执行,可以一次性获取多个并发执行。 这种算法,在使用过后也存在弊端:无法应对短时间的突发流量。 3、令牌桶算法 从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限 制请求调用的速率,而令牌桶算法能够在限 制调用的平均速率的同时还允许一定程度的突发调用。 在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。 放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。 实现思路:可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。 幸运的是,通过Google开源的guava包,我们可以很轻松的创建一个令牌桶算法的限流器。 com.google.guava guava 18.0 通过RateLimiter类的create方法,创建限流器。 public class RateLimiterMain { public static void main (String[] args) { RateLimiter rateLimiter = RateLimiter.create(10); for (int i=0; i<10; i++) { new Thread(new Runnable() { @Override public void run() { rateLimiter.acquire(); System.out.println("ok"); } }).start(); } } } 其实Guava提供了多种create方法,方便创建适合各种需求的限流器。在上述例子中,创建了一个每秒生成10个令牌的限流器,即100ms生成一个,并最多保存10个令牌,多余的会被丢弃。 rateLimiter提供了acquire()和tryAcquire()接口 1、使用acquire()方法,如果没有可用令牌,会一直阻塞直到有足够的令牌。 2、使用tryAcquire()方法,如果没有可用令牌,就直接返回false。 3、使用tryAcquire()带超时时间的方法,如果没有可用令牌,就会判断在超时时间内是否可以等到令牌,如果不能,就返回false,如果可以,就阻塞等待。 集群限流 前面讨论的几种算法都属于单机限流的范畴,但是业务需求五花八门,简单的单机限流,根本无法满足他们。 比如为了限 制某个资源被每个用户或者商户的访问次数,5s只能访问2次,或者一天只能调用1000次,这种需求,单机限流是无法实现的,这时就需要通过集群限流进行实现。 如何实现?为了控制访问次数,肯定需要一个计数器,而且这个计数器只能保存在第三方服务,比如redis。 大概思路:每次有相关操作的时候,就向redis服务器发送一个incr命令,比如需要限 制某个用户访问/index接口的次数,只需要拼接用户id和接口名生成redis的key,每次该用户访问此接口时,只需要对这个key执行incr命令,在这个key带上过期时间,就可以实现指定时间的访问频率。
1:Java中单例模式是一种常见的设计模式,单例模式有以下特点: 单例类只能有一个实例。 单例类必须自己创建自己的唯一实例。 单例类必须给所有其他对象提供这一实例。 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。 2:java中单例模式的写法也有很多种,我在这里列举几张常用的方式: 1、饿汉式,线程安全 但效率比较低: /** 单例模式的实现:饿汉式,线程安全 但效率比较低 */ public class SingletonTest { // 定义一个私有的构造方法 private SingletonTest() { } // 将自身的实例对象设置为一个属性,并加上static和final修饰符 private static final SingletonTest instance = new SingletonTest(); // 静态方法返回该类的实例 public static SingletonTest getInstancei() { return instance; } } 2、懒汉式,非线程安全: /** 单例模式的实现:懒汉式,非线程安全 */ public class SingletonTest { // 定义私有构造方法(防止通过 new SingletonTest()去实例化) private SingletonTest() { } // 定义一个SingletonTest类型的变量(不初始化,注意这里没有使用final关键字) private static SingletonTest instance; // 定义一个静态的方法(调用时再初始化SingletonTest,但是多线程访问时,可能造成重复初始化问题) public static SingletonTest getInstance() { if (instance == null) instance = new SingletonTest(); return instance; } } 3、懒汉式,线程安全简单实现 : /** 单例模式的实现:懒汉式,线程安全简单实现 */ public class SingletonTest { // 定义私有构造方法(防止通过 new SingletonTest()去实例化) private SingletonTest() { } // 定义一个SingletonTest类型的变量(不初始化,注意这里没有使用final关键字) private static SingletonTest instance; // 定义一个静态的方法(调用时再初始化SingletonTest,使用synchronized 避免多线程访问时,可能造成重的复初始化问题) public static synchronized SingletonTest getInstance() { if (instance == null) instance = new SingletonTest(); return instance; } } 4、线程安全 并且效率高 单例模式最优方案 /** 单例模式最优方案 线程安全 并且效率高 */ public class SingletonTest { // 定义一个私有构造方法 private SingletonTest() { } //定义一个静态私有变量(不初始化,不使用final关键字,使用volatile保证了多线程访问时instance变量的可见性,避免了instance初始化时其他变量属性还没赋值完时,被另外线程调用) private static volatile SingletonTest instance; //定义一个共有的静态方法,返回该类型实例 public static SingletonTest getIstance() { // 对象实例化时与否判断(不使用同步代码块,instance不等于null时,直接返回对象,提高运行效率) if (instance == null) { //同步代码块(对象未初始化时,使用同步代码块,保证多线程访问时对象在第一次创建后,不再重复被创建) synchronized (SingletonTest.class) { //未初始化,则初始instance变量 if (instance == null) { instance = new SingletonTest(); } } } return instance; } } 5、静态内部类方式 /** 静态内部类方式* */ public class Singleton { private static class SingletonTest { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonTest.INSTANCE; } }以上单例如设计模式即使有多重检查锁也可以通过反射破坏单例 6、目前最为安全的实现单例的方法是通过内部静态enum的方法来实现,因为JVM会保证enum不能被反射并且构造器方法只执行一次,事例如下: /** 使用枚举的单例模式* @author uu*/ public class EnumSingleton{ private EnumSingleton(){} public static EnumSingleton getInstance(){ return Singleton.INSTANCE.getInstance(); } private static enum Singleton{ INSTANCE; private EnumSingleton singleton; //JVM会保证此方法绝对只调用一次 private Singleton(){ singleton = new EnumSingleton(); } public EnumSingleton getInstance(){ return singleton; } } public static void main(String[] args) { EnumSingleton obj0 = EnumSingleton.getInstance(); EnumSingleton obj1 = EnumSingleton.getInstance(); //输出结果:obj0==obj1?true System.out.println("obj0==obj1?" + (obj0==obj1)); }} 在此浅谈一下个人理解,希望对大家有所帮助。
分布式文件系统 (Distributed File System) 是一个用来管理文件的软件或软件服务器,但这个软件所管理的文件通常不是在一个服务器节点上,而是在多个服务器节点上,这些服务器节点通过网络相连构成一个庞大的文件存储服务器集群,这些服务器都用于存储文件资源,通过分布式文件系统来管理这些服务器上的文件; 常见的分布式文件系统有:FastDFS、GFS、HDFS、Lustre 、Ceph 、GridFS 、mogileFS、TFS等; FastDFS是一个开源的轻量级分布式文件系统,为互联网应用量身定做,简单、灵活、高效,采用C语言开发,由阿里巴巴开发并开源; FastDFS对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载、文件删除)等,解决了大容量文件存储的问题,特别适合以文件为载体的在线服务,如相册网站、文档网站、图片网站等等; FastDFS充分考虑了冗余备份、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务; FastDFS发展历史 2008年4月项目启动,7月发布第一个版本V1.00,两年时间内持续升级到V1.29; 2010年8月推出V2.00; 2011年6月推出V3.00; 2012年10月推出V4.0.0; 2013年12月推出V5.0.0; 截止目前最新版是V5.11;(2017年6月发布) FastDFS系统架构从第一个版本发布后一直没有大的调整,高版本完全兼容低版本的数据,可以做到平滑升级,推荐更新升级到最新版本; FastDFS代码托管在github上:https://github.com/happyfish100/fastdfs FastDFS整体架构 FastDFS文件系统由两大部分构成,一个是客户端,一个是服务端; 客户端通常指我们的程序,比如我们的Java程序去连接FastDFS、操作FastDFS,那我们的Java程序就是一个客户端; FastDFS提供专有API访问,目前提供了 C、Java 和 PHP 几种编程语言的API,用来访问FastDFS文件系统; 服务端由两个部分构成:一个是跟踪器(tracker),一个是存储节点(storage); 跟踪器(tracker)主要做调度工作,在内存中记录集群中存储节点storage的状态信息,是前端Client和后端存储节点storage的枢纽; 因为相关信息全部在内存中,Tracker server的性能非常高,一个较大的集群(比如上百个group)中有3台就足够了; 存储节点(storage)用于存储文件,包括文件和文件属性(meta data)都保存到存储服务器磁盘上,完成文件管理的所有功能:文件存储、文件同步和提供文件访问等;