diff --git a/Alibaba_Druid/README.md b/Alibaba_Druid/README.md index 425e004..12dc801 100644 --- a/Alibaba_Druid/README.md +++ b/Alibaba_Druid/README.md @@ -1,4 +1,4 @@ -###Alibaba Druid数据源监控 +### Alibaba Druid数据源监控 在[阿里巴巴Druid](https://github.com/Alibaba/Druid)数据库连接池上写着这一句话,"为监控而生的数据库连接池",确实,Druid在数据库连接池监控上做得非常完善。 diff --git a/AngularJS_String_SubString/README.md b/AngularJS_String_SubString/README.md new file mode 100644 index 0000000..913246d --- /dev/null +++ b/AngularJS_String_SubString/README.md @@ -0,0 +1,36 @@ +### angularjs 字符串截取 filter,用于截取字符串 + +```JavaScript + +angular.module('ng').filter('cut', function () { + return function (value, wordwise, max, tail) { + if (!value) return ''; + + max = parseInt(max, 10); + if (!max) return value; + if (value.length <= max) return value; + + value = value.substr(0, max); + if (wordwise) { + var lastspace = value.lastIndexOf(' '); + if (lastspace != -1) { + value = value.substr(0, lastspace); + } + } + + return value + (tail || ' …'); + }; + }); +``` + +### Usage: + +{{some_text | cut:true:100:' ...'}} + +### Options: + +* wordwise (boolean) - if true, cut only by words bounds, + +* max (integer) - max length of the text, cut to this number of chars, + +* tail (string, default: ' …') - add this string to the input string if the string was cut. \ No newline at end of file diff --git a/Big_Data_ETL/README.md b/Big_Data_ETL/README.md index 7c661ca..4a9c324 100644 --- a/Big_Data_ETL/README.md +++ b/Big_Data_ETL/README.md @@ -1,4 +1,4 @@ -###ETL +### ETL ETL,是英文 Extract-Transform-Load 的缩写,用来描述将数据从来源端经过抽取(extract)、转换 (transform)、加载(load)至目的端的过程。ETL一词较常用在数据仓库,但其对象并不限于数据仓库。 @@ -6,7 +6,7 @@ ETL是构建数据仓库的重要一环,用户从数据源抽取出所需的 信息是现代企业的重要资源,是企业运用科学管理、决策分析的基础。目前,大多数企业花费大量的资金和时间来构建联机事务处理OLTP的业务系统和办公自动化系统,用来记录事务处理的各种相关数据。据统计,数据量每2~3年时间就会成倍增长,这些数据蕴含着巨大的商业价值,而企业所关注的通常只占在总数据量的2%~4%左右。因此,企业仍然没有最大化地利用已存在的数据资源,以致于浪费了更多的时间和资金,也失去制定关键商业决策的最佳契机。于是,企业如何通过各种技术手段,并把数据转换为信息、知识,已经成了提高其核心竞争力的主要瓶颈。而ETL则是主要的一个技术手段。 -####主要特点 +#### 主要特点 * ETL负责将分布的、异构数据源中的数据如关系数据、平面数据文件等抽取到临时中间层后进行清洗、转换、集成,最后加载到数据仓库或数据集市中,成为联机分析处理、数据挖掘的基础。 * ETL一词较常出现在数据仓库,但其对象并不局限于数据仓库。 @@ -18,22 +18,22 @@ ETL是构建数据仓库的重要一环,用户从数据源抽取出所需的 ETL过程在很大程度上受企业对源数据的理解程度的影响,也就是说从业务的角度看数据集成非常重要。一个优秀的ETL设计应该具有如下功能: -#####1、管理简单 +##### 1、管理简单 采用元数据方法,集中进行管理;接口、数据格式、传输有严格的规范;尽量不在外部数据源安装软件;数据抽取系统流程自动化,并有自动调度功能;抽取的数据及时、准确、完整;可以提供同各种数据系统的接口,系统适应性强;提供软件框架系统,系统功能改变时,应用程序很少改变便可适应变化;可扩展性强。 -#####2、标准定义数据 +##### 2、标准定义数据 合理的业务模型设计对ETL至关重要。数据仓库是企业唯一、真实、可靠的综合数据平台。数据仓库的设计建模一般都依照三范式、星型模型、雪花模型,无论哪种设计思想,都应该最大化地涵盖关键业务数据,把运营环境中杂乱无序的数据结构统一成为合理的、关联的、分析型的新结构,而ETL则会依照模型的定义去提取数据源,进行转换、清洗,并最终加载到目标数据仓库中。 模型的重要之处在于对数据做标准化定义,实现统一的编码、统一的分类和组织。标准化定义的内容包括:标准代码统一、业务术语统一。ETL依照模型进行初始加载、增量加载、缓慢增长维、慢速变化维、事实表加载等数据集成,并根据业务需求制定相应的加载策略、刷新策略、汇总策略、维护策略。 -#####3、拓展新型应用 +##### 3、拓展新型应用 对业务数据本身及其运行环境的描述与定义的数据,称之为元数据(metadata)。元数据是描述数据的数据。从某种意义上说,业务数据主要用于支持业务系统应用的数据,而元数据则是企业信息门户、客户关系管理、数据仓库、决策支持和B2B等新型应用所不可或缺的内容。 元数据的典型表现为对象的描述,即对数据库、表、列、列属性(类型、格式、约束等)以及主键/外部键关联等等的描述。特别是现行应用的异构性与分布性越来越普遍的情况下,统一的元数据就愈发重要了。“信息孤岛”曾经是很多企业对其应用现状的一种抱怨和概括,而合理的元数据则会有效地描绘出信息的关联性。 而元数据对于ETL的集中表现为:定义数据源的位置及数据源的属性、确定从源数据到目标数据的对应规则、确定相关的业务逻辑、在数据实际加载前的其他必要的准备工作,等等,它一般贯穿整个数据仓库项目,而ETL的所有过程必须最大化地参照元数据,这样才能快速实现ETL。 -####其他资料 +#### 其他资料 ETL(Extract-Transform-Load的缩写,即数据抽取、转换、装载的过程)作为BI/DW(Business Intelligence)的核心和灵魂,能够按照统一的规则集成并提高数据的价值,是负责完成数据从数据源向目标数据仓库转化的过程,是实施数据仓库的重要步骤。如果说数据仓库的模型设计是一座大厦的设计蓝图,数据是砖瓦的话,那么ETL就是建设大厦的过程。在整个项目中最难部分是用户需求分析和模型设计,而ETL规则设计和实施则是工作量最大的,约占整个项目的60%~80%,这是国内外从众多实践中得到的普遍共识。 @@ -41,7 +41,7 @@ ETL是数据抽取(Extract)、清洗(Cleaning)、转换(Transform) 信息是现代企业的重要资源,是企业运用科学管理、决策分析的基础。目前,大多数企业花费大量的资金和时间来构建联机事务处理OLTP的业务系统和办公自动化系统,用来记录事务处理的各种相关数据。据统计,数据量每2~3年时间就会成倍增长,这些数据蕴含着巨大的商业价值,而企业所关注的通常只占在总数据量的2%~4%左右。因此,企业仍然没有最大化地利用已存在的数据资源,以致于浪费了更多的时间和资金,也失去制定关键商业决策的最佳契机。于是,企业如何通过各种技术手段,并把数据转换为信息、知识,已经成了提高其核心竞争力的主要瓶颈。而ETL则是主要的一个技术手段。 -####ETL工具应用 +#### ETL工具应用 目前,ETL工具的典型代表有:Informatica、Datastage、OWB、微软DTS、Beeload、Kettle…… 开源的工具有eclipse的etl插件。cloveretl. diff --git a/C#/HTML2Word/README.md b/C#/HTML2Word/README.md new file mode 100644 index 0000000..d0f7973 --- /dev/null +++ b/C#/HTML2Word/README.md @@ -0,0 +1,113 @@ +### HTML转出到Word中 ### +通过系统剪切板把HTML复制到Word中,并且保留原来的格式 + +```C# + + class HTMLUtils + { + // 将HTML代码复制到Windows剪贴板,并保证中 + [DllImport("user32.dll")] + static extern bool OpenClipboard(IntPtr hWndNewOwner); + [DllImport("user32.dll")] + static extern bool EmptyClipboard(); + [DllImport("user32.dll")] + static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem); + [DllImport("user32.dll")] + static extern bool CloseClipboard(); + [DllImport("user32.dll", SetLastError = true)] + static extern uint RegisterClipboardFormatA(string lpszFormat); + + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GlobalLock(IntPtr hMem); + [DllImport("kernel32.dll", SetLastError = true)] + static extern uint GlobalSize(IntPtr hMem); + [DllImport("kernel32.dll", SetLastError = true)] + static extern IntPtr GlobalUnlock(IntPtr hMem); + + /// + /// copy the html into clipboard + /// + /// + /// + static public bool CopyHTMLToClipboard(string html) + { + uint CF_HTML = RegisterClipboardFormatA("HTML Format"); + bool bResult = false; + if (OpenClipboard(IntPtr.Zero)) + { + if (EmptyClipboard()) + { + byte[] bs = System.Text.Encoding.UTF8.GetBytes(html); + + int size = Marshal.SizeOf(typeof(byte)) * bs.Length; + + IntPtr ptr = Marshal.AllocHGlobal(size); + Marshal.Copy(bs, 0, ptr, bs.Length); + + IntPtr hRes = SetClipboardData(CF_HTML, ptr); + CloseClipboard(); + } + } + return bResult; + } + + //将HTML代码按照Windows剪贴板格进行格式化 + public static string HtmlClipboardData(string html) + { + StringBuilder sb = new StringBuilder(); + Encoding encoding = Encoding.UTF8; //Encoding.GetEncoding(936); + string Header = @"Version: 1.0 + StartHTML: {0:000000} + EndHTML: {1:000000} + StartFragment: {2:000000} + EndFragment: {3:000000} + "; + string HtmlPrefix = @" + + + + + + + "; + HtmlPrefix = string.Format(HtmlPrefix, "gb2312"); + + string HtmlSuffix = @""; + + // Get lengths of chunks + int HeaderLength = encoding.GetByteCount(Header); + HeaderLength -= 16; // extra formatting characters {0:000000} + int PrefixLength = encoding.GetByteCount(HtmlPrefix); + int HtmlLength = encoding.GetByteCount(html); + int SuffixLength = encoding.GetByteCount(HtmlSuffix); + + // Determine locations of chunks + int StartHtml = HeaderLength; + int StartFragment = StartHtml + PrefixLength; + int EndFragment = StartFragment + HtmlLength; + int EndHtml = EndFragment + SuffixLength; + + // Build the data + sb.AppendFormat(Header, StartHtml, EndHtml, StartFragment, EndFragment); + sb.Append(HtmlPrefix); + sb.Append(html); + sb.Append(HtmlSuffix); + + //Console.WriteLine(sb.ToString()); + return sb.ToString(); + } + } +``` + +调用: + +```C# + string html = "

abc

"; + html = HTMLUtils.HtmlClipboardData(html); + HTMLUtils.CopyHTMLToClipboard(html); + //then you can paste into word using WdPasteDataType.wdPasteHTML parameter + object dataType = Word.WdPasteDataType.wdPasteHTML; + newapp.Selection.PasteSpecial(ref nothing, ref nothing, ref nothing, ref nothing, ref dataType, ref nothing, ref nothing); ; +``` +具体的实现请看[https://github.com/scalad/MathML2MathTypeEquation](https://github.com/scalad/MathML2MathTypeEquation) + diff --git a/C#/WordObjectModel/README.md b/C#/WordObjectModel/README.md new file mode 100644 index 0000000..3c3021b --- /dev/null +++ b/C#/WordObjectModel/README.md @@ -0,0 +1,47 @@ +### Word对象模型 ### + +#### 一、开发环境布置 #### +C#中添加对Word的支持,只需添加对Microsoft.Office.Interop.Word的命名空间,如下图所示,右键点击“引用”,在弹出的“添加引用”对话框中选中COM标签页,找到“Microsoft Word 12.0 Object Library”。 + +![](https://github.com/scalad/Note/blob/master/C%23/WordObjectModel/image/20161109112442139.png) + +点击确定按钮后,可在引用中添加显示名称为Microsoft.Office.Interop.Word的引用: + +![](https://github.com/scalad/Note/blob/master/C%23/WordObjectModel/image/20161109112637339.png) + +#### 二、Word的对象模型介绍 #### +Word中共有5种常用的对象模型:应用程序对象Application、文档对象Document、Selection对象、Range对象和Bookmark对象。 +下图显示了 Word 对象模型层次结构中这些对象的一个视图。 + +![](https://github.com/scalad/Note/blob/master/C%23/WordObjectModel/image/20161109112800937.png) + +初看起来,对象似乎重叠在一起。 例如,Document 和 Selection 对象都是 Application 对象的成员,但 Document 对象也是 Selection 对象的成员。 Document 和 Selection 对象都包含 Bookmark 和 Range 对象。 因为有多种方法可以访问相同类型的对象,所以存在重叠。 例如,你将格式设置应用于 Range 对象;但你可能想要访问当前选定内容、某一特定段落,某一节或整个文档的范围。 + +下面分别介绍五种模型对象的含义和作用。 + +#### 2.1 Applicatin对象。 #### +Application 对象表示 Word 应用程序,并且是所有其他对象的父级。 其成员通常作为一个整体应用于 Word。 你可以使用其属性和方法来控制 Word 环境。 + +在文档级项目中,可以通过使用 ThisDocument 类的 Application 属性来访问 Application 对象。 + +#### 2.2 Document对象 #### +`Microsoft.Office.Interop.Word.Document` 对象是 Word 编程的中心。 它表示一个文档及其所有内容。 当你打开文档或创建新文档时,将创建新的 `Microsoft.Office.Interop.Word.Document` 对象,并将其添加到 Application 对象的 T:`Microsoft.Office.Interop.Word.Documents` 集合。 具有焦点的文档被称为活动文档。 它由 Application 对象的 P:`Microsoft.Office.Interop.Word._Application.ActiveDocument` 属性表示。 + +#### 2.3 Selection对象 #### +Selection 对象表示当前所选的区域。 在 Word 用户界面中执行操作(如文本加粗)时,可以选择或突出显示文本,然后应用格式设置。 文档中始终存在 Selection 对象。 如果未选中任何内容,则它表示插入点。 此外,选定内容可包含多个不相邻的文本块。 + +#### 2.4 Range对象 #### +Range 对象表示文档中的相邻区域,并由起始字符位置和结束字符位置进行定义。 并不仅限于单个 Range 对象。 你可以在同一文档中定义多个 Range 对象。 Range 对象具有以下特性: + +* 它可以只包含单独的插入点,也可包含一个文本范围或整个文档。 +* 它包括非打印字符,如空格、制表符和段落标记。 +* 它可以是当前选定内容所表示的区域,也可以表示不同于此内容的区域。 +* 它在文档中不可见,这与选定内容不同,后者总是可见。 +* 它不随文档一起保存,且仅在代码运行时才存在。 +* 当在某个范围的末尾插入文本时,Word 会自动扩展该范围以包括插入的文本。 + +#### 2.5 Bookmark对象 #### +Microsoft.Office.Interop.Word.Bookmark 对象表示文档中的相邻区域,同时具有起始位置和结束位置。 你可以使用书签标记文档中的某个位置,也可将其作为文档中文本的容器。 Microsoft.Office.Interop.Word.Bookmark 对象可以包含插入点,也可以与整个文档一样大。Microsoft.Office.Interop.Word.Bookmark 具有下列特征,以将其与 Range 对象区别开来: +* 你可以在设计时命名书签。 +* Microsoft.Office.Interop.Word.Bookmark 对象随文档一起保存,因此在代码停止运行或文档关闭时不会被删除。 +* 通过将 T:Microsoft.Office.Interop.Word.View 对象的 P:Microsoft.Office.Interop.Word.View.ShowBookmarks 属性设置为 false 或 true,可以隐藏或显示书签。 \ No newline at end of file diff --git a/C#/WordObjectModel/image/20161109112442139.png b/C#/WordObjectModel/image/20161109112442139.png new file mode 100644 index 0000000..8be72e5 Binary files /dev/null and b/C#/WordObjectModel/image/20161109112442139.png differ diff --git a/C#/WordObjectModel/image/20161109112637339.png b/C#/WordObjectModel/image/20161109112637339.png new file mode 100644 index 0000000..625ec10 Binary files /dev/null and b/C#/WordObjectModel/image/20161109112637339.png differ diff --git a/C#/WordObjectModel/image/20161109112800937.png b/C#/WordObjectModel/image/20161109112800937.png new file mode 100644 index 0000000..feeca01 Binary files /dev/null and b/C#/WordObjectModel/image/20161109112800937.png differ diff --git a/CROS/README.md b/CROS/README.md new file mode 100644 index 0000000..aadf9a1 --- /dev/null +++ b/CROS/README.md @@ -0,0 +1,77 @@ +## CROS实现跨域时授权问题 + + Response to preflight request doesn't pass access control check: + No 'Access-Control-Allow-Origin' header is present on the requested resource. + Origin 'null' is therefore not allowed access. The response had HTTP status code 403 + +### 问题的提出 +如果我们访问的资源是不需要授权的,也就是在HTTP请求头中不包含authentication头那么以上做法就足够了。但是如果该资源是需要权限验证的,那么这个时候跨域请求的预检测option请求,由于不会携带身份信息而被拒绝。浏览器会报出401错误。错误信息如下: + + Failed to load resource: + the server responded with a status of 401 (Unauthorized) + XMLHttpRequest cannot load http://localhost/api/test. + Response for preflight has invalid HTTP status code 401 + +既然知道了问题的原因,答案也就很容易得出:对需要进行跨域请求的资源(api),当服务端检测到是OPTONS请求时候统统放行,给出HTTP.OK(200)的状态和必要的响应头,哪怕它是不带身份信息的。 +这个问题既可以通过编写对应的后端代码实现,也可以通过设置服务器配置文件实现。也就是如何设置响应头和返回200状态码的办法了。 + +### Spring+Shrio的解决方案 +shiro中可以在自己实现的身份验证filter中加入以下代码: + +```Java +@Override protected boolean preHandle(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; if(request.getMethod().equals(RequestMethod.OPTIONS.name())) { response.setStatus(HttpStatus.OK.value()); return false; } return super.preHandle(request, response); } +``` + +shiro中AccessControlFilter提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等,也是我们一般自定义权限验证时候的一个父类,我们通过重写他的onPreHandle方法判断是否是option请求,如果是则设置相应状态,(响应头已经在之前文章中通过filter配置过了)返回false表示该拦截器实例已经处理了,将直接返回即可。 + +### Tomcat配置 +需要修改tomcat的全局web.xml文件在CATALINA_HOME/conf下,加入以下配置。 + +```Xml + + CorsFilter + org.apache.catalina.filters.CorsFilter + + + CorsFilter + /* + +``` + +### Nginx配置 + + add_header 'Access-Control-Allow-Methods' 'GET,OPTIONS,PUT,DELETE' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Origin' '$http_origin' always; + add_header 'Access-Control-Allow-Headers' 'Authorization,DNT,User-Agent, + Keep-Alive,Content-Type,accept,origin,X-Requested-With' always; + + if ($request_method = OPTIONS ) { + return 200; + } + +### Apache配置 + + Header always set Access-Control-Allow-Origin "http://waffle" + Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS" + Header always set Access-Control-Allow-Credentials "true" + Header always set Access-Control-Allow-Headers "Authorization,DNT,User-Agent,Keep-Alive,Content-Type,accept,origin,X-Requested-With" + + RewriteCond %{REQUEST_METHOD} OPTIONS + RewriteRule ^(.*)$ $1 [R=200,L] + +### js请求示例 +请求时候需要加上Authorization和Content-Type头。 + + $http({ + method: 'POST', + url: scope.webdav.url, + withCredentials: true, + headers: { + Authorization: 'Basic ' + btoa(user + ':' + password), + 'Content-Type': 'application/vnd.google-earth.kml+xml; charset=utf-8' + }, + data: getKml() + }) + +参考文章:[http://www.jujens.eu/posts/en/2015/Jun/27/webdav-options/](http://www.jujens.eu/posts/en/2015/Jun/27/webdav-options/) \ No newline at end of file diff --git a/CrossOrigin_Request/README.md b/CrossOrigin_Request/README.md new file mode 100644 index 0000000..3d65af3 --- /dev/null +++ b/CrossOrigin_Request/README.md @@ -0,0 +1,79 @@ +[关于使用JS解决跨域问题](http://www.cnblogs.com/dojo-lzz/p/4265637.html) + +服务器端解决跨域请求问题,拦截请求并重新设置响应头 + +服务器端拦截器 + +```Java + + package com.silence.util; + + import java.io.IOException; + import java.text.SimpleDateFormat; + import java.util.Date; + + import javax.servlet.Filter; + import javax.servlet.FilterChain; + import javax.servlet.FilterConfig; + import javax.servlet.ServletException; + import javax.servlet.ServletRequest; + import javax.servlet.ServletResponse; + import javax.servlet.http.HttpServletResponse; + + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + + public class CrossOriginFilter implements Filter { + + private String allowDomain = ""; + private static final Logger logger = LoggerFactory.getLogger(CrossOriginFilter.class); + + public void init(FilterConfig filterConfig) throws ServletException { + allowDomain = filterConfig.getInitParameter("domain"); + } + + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + logger.info("CrossOriginFilter 跨域请求拦截 " + new SimpleDateFormat("YYYY-DD-MM").format(new Date())); + HttpServletResponse httpResponse = (HttpServletResponse) response; + setAccessControl(httpResponse); + chain.doFilter(request, response); + } + + public void destroy() { + + } + /** + *在某域名下使用Ajax向另一个域名下的页面请求数据,会遇到跨域问题。另一个域名必须在response中添加 + *Access-Control-Allow-Origin 的header,才能让前者成功拿到数据。 + *只有当目标页面的response中,包含了 Access-Control-Allow-Origin 这个header,并且它的值里有我们自己的域名时, + *浏览器才允许我们拿到它页面的数据进行下一步处理。 + *如果它的值设为 * ,则表示谁都可以用 + */ + private void setAccessControl(HttpServletResponse response) { + response.setHeader("Access-Control-Allow-Origin", allowDomain); + response.setHeader("Access-Control-Allow-Credentials", "true"); + String headers = "Origin, Accept-Language, Accept-Encoding,X-Forwarded-For, Connection, Accept, User-Agent, Host, Referer,Cookie, Content-Type, Cache-Control"; + response.setHeader("Access-Control-Allow-Headers", headers); + response.setHeader("Access-Control-Request-Method", "GET,POST"); + } + + } +``` + +在web.xml中配置 + + + CrossOriginFilter + com.silence.util.CrossOriginFilter + + domain + * + + + + CrossOriginFilter + /* + + +默认拦截所有的请求,允许来自任何链接的请求 \ No newline at end of file diff --git a/Eclipse_Referenced_File_Contains_Errors/README.md b/Eclipse_Referenced_File_Contains_Errors/README.md new file mode 100644 index 0000000..47d0288 --- /dev/null +++ b/Eclipse_Referenced_File_Contains_Errors/README.md @@ -0,0 +1,35 @@ +### Eclipse Xml编译错误Referenced file contains errors - spring-beans-4.0.xsd + +在eclipse中,有时候在xml文件中,特别是于spring相关的配置文件中,会出现一些不影响程序正常运行的编译错误,如: + +Referenced file contains errors (http://www.springframework.org/schema/beans/spring-beans-4.0.xsd). + +可通过如下步骤解决这个编译错误: + +1. Spring的版本变更了,但是Eclipse的编译器还是使用之前缓存的spring-beans-**.xsd文件。其原因是 + +对于Eclipse编译器来说有个缓存会缓存这些配置文件,这样验证的时候会告诉你版本不统一。 + +解决办法是清空这些文件并强制eclipse重新加载这些文件。 + +> 1) Preferences -> General -> Network Connections -> Cache + +选择响应的文件病点击删除或者直接点击删除全部。 + +> 2) 如果是Maven工程,右击工程,并选择Maven,选择Update Project. + +> 3)如果以上两步都不行,则可关闭project并重新打开强制eclipse进行编译。 + + +2 . 当前使用的spring版本和配置文件中配置的不相同,导致xsd等文件不会被正确加载,改成当前版本即可。 + +如果不成功则重复上面的2)3)两步即可。 + +3 . 在使用spring时,使用多个配置文件,那么头里面的配置一定要统一。 + +如果不成功则重复上面的2)3)两步即可。 + + +从网上http://stackoverflow.com/questions/7267341/validation-error-of-spring-beans-schema-inside-application-context看到可能又另外一种情况: + +就是你在spring的配置文件中混合使用了不同版本的xsd文件,你可以把这些xsd文件的版本设置为一样,然后重新更新下项目 \ No newline at end of file diff --git a/Git_Language_Show/README.md b/Git_Language_Show/README.md new file mode 100644 index 0000000..d22ce8d --- /dev/null +++ b/Git_Language_Show/README.md @@ -0,0 +1,42 @@ +### 修改Github上项目语言显示问题 + +#### 问题 +最近将自己写的博客放到github上了。由于使用了富文本编辑器、jQuery、Bootstrap等第三方插件,导致js、css等代码远远超过你自己写的代码 + +于是也就成这样了 + +![](https://github.com/scalad/Note/blob/master/Git_Language_Show/image/vQz6FjB.png) + +而且这里也显示JavaScript + +![](https://github.com/scalad/Note/blob/master/Git_Language_Show/image/EBFZJrr.png) + +这样的情况很不能忍,尤其对于强迫症来说。而且github也没有bitbucket项目语言的设置 + +搜索了一下发现github是使用 Linguist 来detect所使用的语言。 Linguist 是什么鬼我也不了解,大致就是通过统计哪种语言代码数量最多的作为当前项目主语言。这样很不公平有木有,像Scala这种支持函数式编程而且语法简洁的语言,代码量完全拼不过其他语言 + +#### 解决 + +解决起来也简单,有2种方法 + +#### 1、使用外链接 + +将项目中的静态文件如jQuery、Bootstrap等放到别处用连接导入即可 + +#### 2、使用 .gitattributes 配置文件 + +具体就是在项目根目录添加文件名为.gitattributes的文本文件,写入 + + *.js linguist-language=Scala + *.css linguist-language=Scala + *.html linguist-language=Scala + +意思就是将.js、css、html当作Scala语言来统计 + +另外,说一下,在windows系统中并不好直接创建名为 .gitattributes 的文件,会提示: + +![](https://github.com/scalad/Note/blob/master/Git_Language_Show/image/u2UVna.png) + +那么只需要用命令行创建就行了,或者使用VI也是可以的: + +touch .gitattributes \ No newline at end of file diff --git a/Git_Language_Show/image/EBFZJrr.png b/Git_Language_Show/image/EBFZJrr.png new file mode 100644 index 0000000..9e24002 Binary files /dev/null and b/Git_Language_Show/image/EBFZJrr.png differ diff --git a/Git_Language_Show/image/u2UVna.png b/Git_Language_Show/image/u2UVna.png new file mode 100644 index 0000000..d96ed0f Binary files /dev/null and b/Git_Language_Show/image/u2UVna.png differ diff --git a/Git_Language_Show/image/vQz6FjB.png b/Git_Language_Show/image/vQz6FjB.png new file mode 100644 index 0000000..fe23448 Binary files /dev/null and b/Git_Language_Show/image/vQz6FjB.png differ diff --git a/Gradle_Dependencies/README.md b/Gradle_Denpendencies_Exclude/README.md similarity index 95% rename from Gradle_Dependencies/README.md rename to Gradle_Denpendencies_Exclude/README.md index 111af6a..1098ae8 100644 --- a/Gradle_Dependencies/README.md +++ b/Gradle_Denpendencies_Exclude/README.md @@ -1,4 +1,4 @@ -##Gradle排除依赖关系 +## Gradle排除依赖关系 在IDE中发现了C3P0的依赖,但是在build.gradle并没有手动导入,所以说某个jar包依赖了,在STS中没有像Maven可以直接查看依赖的窗口 diff --git a/Gradle_Dependencies/image/1.png b/Gradle_Denpendencies_Exclude/image/1.png similarity index 100% rename from Gradle_Dependencies/image/1.png rename to Gradle_Denpendencies_Exclude/image/1.png diff --git a/Gradle_Denpendency_Management/README.md b/Gradle_Denpendency_Management/README.md new file mode 100644 index 0000000..b464a1f --- /dev/null +++ b/Gradle_Denpendency_Management/README.md @@ -0,0 +1,234 @@ +### Gradle系列之依赖管理 +Gradle依赖管理看起来很容易,但是当出现依赖解析冲突时就会很棘手, 复杂的依赖关系可能导致构建中依赖一个库的多个版本。Gradle通过分析依赖树得到依赖报告,你将很容易找到一个指定的依赖的来源. + +Gradle有自己的依赖管理实现,除了支持ant和Maven的特性外,Gradle关心的是性能、可靠性和复用性. + +#### 简要概述依赖管理 +几乎所有基于JVM的项目都会或多或少依赖其他库,假设你在开发一个基于web的项目,你很可能会依赖很受欢迎的开源框架比如Spring MVC来提高效率。Java的第三方库一般以JAR文件的形式存在,一般用库名加版本号来标识。随着开发的进行依赖的第三方库增多小的项目变的越来越大, 组织和管理你的JAR文件就很关键. + +#### 不算完美的依赖管理技术 +由于Java语言并没提供依赖管理的工具,所以你的团队需要自己开发一套存储和检索依赖的想法。你可能会采取以下几种常见的方法: + +* 手动复制JAR文件到目标机器,这是最原始的很容易出错的方法 + +* 使用一个共享的存储介质来存储JAR文件(比如共享的网盘),你可以加载网络硬盘或者通过FTP检索二进制文件。这种方法需要开发者事先建立好与仓库的连接,手动添加新的依赖到仓库中 + +* 把依赖的JAR文件同源代码都添加到版本控制系统中。这种方法不需要任何额外的步骤,你的同伴在拷贝仓库的时候就能检索依赖的改变。另一方面,这些JAR文件占用了不必要的空间,当你的项目存在相互之间依赖的时候你需要频繁的check-in的检查源代码是否发生了改变. + +#### 自动管理依赖的重要性 +尽管上面的方法都能用,但是这距离理想的解决方案差远了,因为他们没有提供一个标准化的方法来命名和管理JAR文件。至少你得需要开发库的准确版本和它依赖的库(传递依赖),这个为什么这么重要? + +#### 准确知道依赖的版本 +如果在项目中你没有准确声明依赖的版本这将会是一个噩梦,如果没有文档你根本无法知道这个库支持哪些特性,是否升级一个库到新的版本就变成了一个猜谜游戏因为你不知道你的当前版本. + +#### 管理传递依赖 +在项目的早期开发阶段传递依赖就会是一个隐患,这些库是第一层的依赖需要的,比如一个比较常见的开发方案是将Spring和Hibernate结合起来这会引入超过20个其他的开发库,一个库需要很多其他库来正常工作。下图展示了Hibernate核心库的依赖图: + +![](https://github.com/silence940109/Java/blob/master/Gradle_Denpendency_Management/image/20150512084408_658.png) + +如果没有正确的管理依赖,你可以会遇到没想到过的编译期错误和运行期类加载问题。我们可以总结到我们需要一个更好的方式来管理依赖,一般来讲你想在 项目元数据中声明你的依赖和它的版本号。作为一个项目自动化的过程,这个版本的库会自动从中央仓库下载、安装到你的项目中,我们来看几个现有的开源解决方 案。 + +#### 使用自动化的依赖管理 +在Java领域里支持声明的自动依赖管理的有两个项目:Apache Ivy(Ant项目用的比较多的依赖管理器)和Maven(在构建框架中包含一个依赖管理器),我不再详细介绍这两个的细节而是解释自动依赖管理的概念和机制 + +Ivy和Maven是通过XML描述文件来表达依赖配置,配置包含两部分:依赖的标识加版本号和中央仓库的位置(可以是一个HTTP链接),依赖管 理器根据这个信息自动定位到需要下载的仓库然后下载到你的机器中。库可以定义传递依赖,依赖管理器足够聪明分析这个信息然后解析下载传递依赖。如果出现了 依赖冲突比如上面的Hibernate core的例子,依赖管理器会试着解决。库一旦被下载就会存储在本地的缓存中,构建系统先检查本地缓存中是否存在需要的库然后再从远程仓库中下载。下图显 示了依赖管理的关键元素: + +![](https://github.com/silence940109/Java/blob/master/Gradle_Denpendency_Management/image/20150512084409_161.png) + +Gradle通过DSL来描述依赖配置,实现了上面描述的架构 + +#### 自动依赖管理面临的挑战 +虽然依赖管理器简化了手工的操作,但有时也会遇到问题。你会发现你的依赖图中会依赖同个库的不同版本,使用日志框架经常会遇到这个问题,依赖管理器 基于一个特定的解决方案只选择其中一个版本来避免版本冲突。如果你想知道某个库引入了什么版本的传递依赖,Gradle提供了一个非常有用的依赖报告来回 答这个问题。下一节我会通过一个例子来讲解 + +#### 声明依赖 +DSL配置block dependencies用来给配置添加一个或多个依赖,你的项目不仅可以添加外部依赖,下面这张表显示了Gradle支持的各种不同类型的依赖. + +![](https://github.com/silence940109/Java/blob/master/Gradle_Denpendency_Management/image/20150512084409_541.png) + +这一章直接扫外部模块依赖和文件依赖,我们来看看Gradle APi是怎么表示依赖的 + +#### 理解依赖的API表示 + +每个Gradle项目都有一个DependencyHandler的实例,你可以通过getDependencies()方法来获取依赖处理器的引 用,上表中每一种依赖类型在依赖处理器中都有一个相对应的方法。每一个依赖都是Dependency的一个实例,group, name, version, 和classifier这几个属性用来标识一个依赖,下图清晰的表示了项目(Project)、依赖处理器(DependencyHandler)和依赖 三者之间的关系: + +![](https://github.com/silence940109/Java/blob/master/Gradle_Denpendency_Management/image/20150512084410_595.png) + +#### 外部模块依赖 +在Gradle的术语里,外部库通常是以JAR文件的形式存在,称之为外部模块依赖,代表项目层次外的一个模块,这种类型的依赖是通过属性来唯一的标识,接下来我们来介绍每个属性的作用 + +#### 依赖属性 + +当依赖管理器从仓库中查找依赖时,需要通过属性的结合来定位,最少需要提供一个name。 + +* group: 这个属性用来标识一个组织、公司或者项目,可以用点号分隔,Hibernate的group是org.hibernate。 + +* name: name属性唯一的描述了这个依赖,hibernate的核心库名称是hibernate-core。 + +* version: 一个库可以有很多个版本,通常会包含一个主版本号和次版本号,比如Hibernate核心库3.6.3-Final。 + +* classifier: 有时候需要另外一个属性来进一步的说明,比如说明运行时的环境,Hibernate核心库没有提供classifier。 + +#### 依赖的写法 + +你可以使用下面的语法在项目中声明依赖: + + dependencies { + configurationName dependencyNotation1, dependencyNotation2, ... + } + +你先声明你要给哪个配置添加依赖,然后添加依赖列表,你可以用map的形式来注明,你也可以直接用冒号来分隔属性,比如这样的: + +![](https://github.com/silence940109/Java/blob/master/Gradle_Denpendency_Management/image/20150512084410_977.png) + + //声明外部属性 + ext.cargoGroup = 'org.codehaus.cargo' + ext.cargoVersion = '1.3.1' + + dependencies { + //使用映射声明依赖 + compile group: cargoGroup, name: 'cargo-core-uberjar',version: cargoVersion + //用快捷方式来声明,引用了前面定义的外部属性 + cargo "$cargoGroup:cargo-ant:$cargoVersion" + } + +如果你项目中依赖比较多,你把一些共同的依赖属性定义成外部属性可以简化build脚本. + +Gradle没有给项目选择默认的仓库,当你没有配置仓库的时候运行deployTOLocalTomcat任务的时候回出现如下的错误: + + $ gradle deployToLocalTomcat + :deployToLocalTomcat FAILED + FAILURE: Build failed with an exception. + + Where: Build file '/Users/benjamin/gradle-in-action/code/chapter5/cargo-configuration/build.gradle' line: 10 + + What went wrong: + Execution failed for task ':deployToLocalTomcat'. + > Could not resolve all dependencies for configuration ':cargo'. + > Could not find group:org.codehaus.cargo, module:cargo-core-uberjar, version:1.3.1. + Required by: + :cargo-configuration:unspecified + > Could not find group:org.codehaus.cargo, module:cargo-ant,version:1.3.1. + Required by: + :cargo-configuration:unspecified + +到目前为止还没讲到怎么配置不同类型的仓库,比如你想使用MavenCentral仓库,添加下面的配置代码到你的build脚本中: + + repositories { + mavenCentral() + } + +#### 检查依赖报告 + +当你运行dependencies任务时,这个依赖树会打印出来,依赖树显示了你build脚本声明的顶级依赖和它们的传递依赖: + +![](https://github.com/silence940109/Java/blob/master/Gradle_Denpendency_Management/image/20150512084410_225.png) + +仔细观察你会发现有些传递依赖标注了*号,表示这个依赖被忽略了,这是因为其他顶级依赖中也依赖了这个传递的依赖,Gradle会自动分析下载最合适的依赖. + +#### 排除传递依赖 + +Gradle允许你完全控制传递依赖,你可以选择排除全部的传递依赖也可以排除指定的依赖,假设你不想使用UberJar传递的xml-api的版本而想声明一个不同版本,你可以使用exclude方法来排除它: + + dependencies { + cargo('org.codehaus.cargo:cargo-ant:1.3.1') { + exclude group: 'xml-apis', module: 'xml-apis' + } + cargo 'xml-apis:xml-apis:2.0.2' + } + +exclude属性值和正常的依赖声明不太一样,你只需要声明group和(或)module,Gradle不允许你只排除指定版本的依赖. + +有时候仓库中找不到项目依赖的传递依赖,这会导致构建失败,Gradle允许你使用transitive属性来排除所有的传递依赖: + + dependencies { + cargo('org.codehaus.cargo:cargo-ant:1.3.1') { + transitive = false + } + // 选择性的声明一些需要的库 + } + +#### 动态版本声明 + +如果你想使用一个依赖的最新版本,你可以使用latest.integration,比如声明 Cargo Ant tasks的最新版本,你可以这样写org.codehaus .cargo:cargo-ant:latest-integration,你也可以用一个+号来动态的声明: + + dependencies { + //依赖最新的1.x版本 + cargo 'org.codehaus.cargo:cargo-ant:1.+' + } + +Gradle的dependencies任务可以清晰的看到选择了哪个版本,这里选择了1.3.1版本: + + $ gradle –q dependencies + ------------------------------------------------------------ + Root project + ------------------------------------------------------------ + Listing 5.4 Excluding a single dependency + Listing 5.5 Excluding all transitive dependencies + Listing 5.6 Declaring a dependency on the latest Cargo 1.x version + Exclusions can be + declared in a shortcut + or map notation. + 120 CHAPTER 5 Dependency management + cargo - Classpath for Cargo Ant tasks. + \--- org.codehaus.cargo:cargo-ant:1.+ -> 1.3.1 + \--- ... + +#### 文件依赖 +如果你没有使用自动的依赖管理工具,你可能会把外部库作为源代码的一部分或者保存在本地文件系统中,当你想把项目迁移到Gradle的时候,你不想去重构,Gradle很简单就能配置文件依赖。下面这段代码复制从Maven中央仓库解析的依赖到libs/cargo目录。 + + task copyDependenciesToLocalDir(type: Copy) { + //Gradle提供的语法糖 + from configurations.cargo.asFileTree + into "${System.properties['user.home']}/libs/cargo" + } + +运行这个任务之后你就可以在依赖中声明Cargo库了,下面这段代码展示了怎么给cargo配置添加JAR文件依赖: + + dependencies { + cargo fileTree(dir: "${System.properties['user.home']}/libs/cargo",include: '*.jar') + } + +#### 配置远程仓库 +Gradle支持下面三种不同类型的仓库: + +![](https://github.com/silence940109/Java/blob/master/Gradle_Denpendency_Management/image/20150512084411_208.png) + +下图是配置不同仓库对应的Gradle API: + +![](https://github.com/silence940109/Java/blob/master/Gradle_Denpendency_Management/image/20150512084411_990.png) + +下面以Maven仓库来介绍,Maven仓库是Java项目中使用最为广泛的一个仓库,库文件一般是以JAR文件的形式存在,用XML(POM文 件)来来描述库的元数据和它的传递依赖。所有的库文件都存储在仓库的指定位置,当你在构建脚本中声明了依赖时,这些属性用来找到库文件在仓库中的准确位 置。group属性标识了Maven仓库中的一个子目录,下图展示了Cargo依赖属性是怎么对应到仓库中的文件的: + +![](https://github.com/silence940109/Java/blob/master/Gradle_Denpendency_Management/image/20150512084412_417.png) + +RepositoryHandler接口提供了两个方法来定义Maven仓库,mavenCentral方法添加一个指向仓库列表的引用,mavenLocal方法引用你文件系统中的本地Maven仓库. + +#### 添加Maven仓库 + +要使用Maven仓库你只需要调用mavenCentral方法,如下所示: + + repositories { + mavenCentral() + } + +添加本地仓库 + +本地仓库默认在 /.m2/repository目录下,只需要添加如下脚本来引用它: + + repositories { + mavenLocal() + } + +#### 添加自定义Maven仓库 + +如果指定的依赖不存在与Maven仓库或者你想通过建立自己的企业仓库来确保可靠性,你可以使用自定义的仓库。仓库管理器允许你使用Maven布局 来配置一个仓库,这意味着你要遵守artifact的存储模式。你也可以添加验证凭证来提供访问权限,Gradle的API提供两种方法配置自定义的仓 库:maven()和mavenRepo()。下面这段代码添加了一个自定义的仓库,如果Maven仓库中不存在相应的库会从自定义仓库中查找: + + repositories { + mavenCentral() + maven { + name 'Custom Maven Repository', + url 'http://repository.forge.cloudbees.com/release/') + } + } + +来自:[http://coolshell.info/blog/2015/05/gradle-dependency-management.html](http://coolshell.info/blog/2015/05/gradle-dependency-management.html) \ No newline at end of file diff --git a/Gradle_Denpendency_Management/image/20150512084408_658.png b/Gradle_Denpendency_Management/image/20150512084408_658.png new file mode 100644 index 0000000..6f3e3f5 Binary files /dev/null and b/Gradle_Denpendency_Management/image/20150512084408_658.png differ diff --git a/Gradle_Denpendency_Management/image/20150512084409_161.png b/Gradle_Denpendency_Management/image/20150512084409_161.png new file mode 100644 index 0000000..dc23031 Binary files /dev/null and b/Gradle_Denpendency_Management/image/20150512084409_161.png differ diff --git a/Gradle_Denpendency_Management/image/20150512084409_541.png b/Gradle_Denpendency_Management/image/20150512084409_541.png new file mode 100644 index 0000000..0140a86 Binary files /dev/null and b/Gradle_Denpendency_Management/image/20150512084409_541.png differ diff --git a/Gradle_Denpendency_Management/image/20150512084410_225.png b/Gradle_Denpendency_Management/image/20150512084410_225.png new file mode 100644 index 0000000..3935455 Binary files /dev/null and b/Gradle_Denpendency_Management/image/20150512084410_225.png differ diff --git a/Gradle_Denpendency_Management/image/20150512084410_595.png b/Gradle_Denpendency_Management/image/20150512084410_595.png new file mode 100644 index 0000000..cfb5f65 Binary files /dev/null and b/Gradle_Denpendency_Management/image/20150512084410_595.png differ diff --git a/Gradle_Denpendency_Management/image/20150512084410_977.png b/Gradle_Denpendency_Management/image/20150512084410_977.png new file mode 100644 index 0000000..d5c908c Binary files /dev/null and b/Gradle_Denpendency_Management/image/20150512084410_977.png differ diff --git a/Gradle_Denpendency_Management/image/20150512084411_208.png b/Gradle_Denpendency_Management/image/20150512084411_208.png new file mode 100644 index 0000000..3f1b8dc Binary files /dev/null and b/Gradle_Denpendency_Management/image/20150512084411_208.png differ diff --git a/Gradle_Denpendency_Management/image/20150512084411_990.png b/Gradle_Denpendency_Management/image/20150512084411_990.png new file mode 100644 index 0000000..f0d4b8f Binary files /dev/null and b/Gradle_Denpendency_Management/image/20150512084411_990.png differ diff --git a/Gradle_Denpendency_Management/image/20150512084412_417.png b/Gradle_Denpendency_Management/image/20150512084412_417.png new file mode 100644 index 0000000..5debad0 Binary files /dev/null and b/Gradle_Denpendency_Management/image/20150512084412_417.png differ diff --git a/Gradle_Gretty/README.md b/Gradle_Gretty/README.md index a98a57d..9ea523a 100644 --- a/Gradle_Gretty/README.md +++ b/Gradle_Gretty/README.md @@ -1,4 +1,4 @@ -###Gretty插件实现Gradle Web项目热部署 +### Gretty插件实现Gradle Web项目热部署 在build.gradle配置文件中 buildscript { @@ -31,7 +31,7 @@ gradle jetty* / gradle tomcat* // serlvetContainer 支持 jetty7/8/9,tomcat7/8 // contextPath 设置根路径,默认为项目名称 port = 8080 - serlvetContainer = 'tomcat8' + servletContainer = 'tomcat8' contextPath = '/' } @@ -93,4 +93,4 @@ gradle buildProduct 项目启动,修改项目文件,自动编译部署 -![](https://github.com/silence940109/Java/blob/master/Gradle_Gretty/image/1.png) \ No newline at end of file +![](https://github.com/silence940109/Java/blob/master/Gradle_Gretty/image/1.png) diff --git a/Gradle_Linux_Install/README.md b/Gradle_Linux_Install/README.md new file mode 100644 index 0000000..4343e68 --- /dev/null +++ b/Gradle_Linux_Install/README.md @@ -0,0 +1,76 @@ +### Gradle Linux安装 +#### 使用SDKMAN安装 + +sdkman(The Software Development Kit Manager), 中文名为:软件开发工具管理器.这个工具的主要用途是用来解决在类unix操作系统(如mac, Linux等)中多种版本开发工具的切换, 安装和卸载的工作.对于windows系统的用户可以使用Powershell CLI来体验. + +例如: 项目A使用Jdk7中某些特性在后续版本中被移除(尽管这是不好的设计),项目B使用Jdk8,我们在切换开发这两个项目的时候,需要不断的切换系统中的JAVA_PATH,这样很不方便,如果存在很多个类似的版本依赖问题,就会给工作带来很多不必要的麻烦. +   +sdkman这个工具就可以很好的解决这类问题,它的工作原理是自己维护多个版本,当用户需要指定版本时,sdkman会查询自己所管理的多版本软件中对应的版本号,并将它所在的路径设置到系统PATH. + +直接打开终端,执行如下命令: + + curl -s http://get.sdkman.io | bash + +上面的命令的含义: 首先sdkman官网下载对应的安装shell script,然后调用bash解析器去执行. + +![](https://github.com/silence940109/Java/blob/master/Gradle_Linux_Install/image/sdk1.jpg) +![](https://github.com/silence940109/Java/blob/master/Gradle_Linux_Install/image/sdk2.jpg) + +可以通过输入sdk help或sdk version确认安装是否完成 + + root@iZ286714gzoZ:~# sdk version + SDKMAN 5.1.18+191 + + sroot@iZ286714gzoZ:~# sdk help + Usage: sdk [candidate] [version] + sdk offline + commands: + install or i [version] + uninstall or rm + list or ls [candidate] + use or u [version] + default or d [version] + current or c [candidate] + upgrade or ug [candidate] + version or v + broadcast or b + help or h + offline [enable|disable] + selfupdate [force] + flush + candidate : the SDK to install: groovy, scala, grails, gradle, kotlin, etc. + use list command for comprehensive list of candidates + eg: $ sdk list + version : where optional, defaults to latest stable if not provided + eg: $ sdk install groovy + + +#### 安装指定版本的gradle +打开一个新的终端执行以下命令安装指定版本的gradle + + $ sdk install gradle 3.3 + +![](https://github.com/silence940109/Java/blob/master/Gradle_Linux_Install/image/install.jpg) +#### 移除安装的gradle + + sdk uninstall gradle + or + sdk rm gradle + +#### 使用临时版本 + + sdk use gradle 3.0 + +#### 设置默认版本 + + sdk default gradle 3.0 + +#### 查看安装的sdk版本列表 + + sdk current gradle + +#### Grale其他地址 + +[Binary only distribution (no documentation or source code)](https://services.gradle.org/distributions/gradle-3.3-all.zip) + +[Gradle source code (just the Gradle source code; not a usable Gradle installation)](https://services.gradle.org/distributions/gradle-3.3-src.zip) \ No newline at end of file diff --git a/Gradle_Linux_Install/image/install.jpg b/Gradle_Linux_Install/image/install.jpg new file mode 100644 index 0000000..3f8a036 Binary files /dev/null and b/Gradle_Linux_Install/image/install.jpg differ diff --git a/Gradle_Linux_Install/image/sdk1.jpg b/Gradle_Linux_Install/image/sdk1.jpg new file mode 100644 index 0000000..127c818 Binary files /dev/null and b/Gradle_Linux_Install/image/sdk1.jpg differ diff --git a/Gradle_Linux_Install/image/sdk2.jpg b/Gradle_Linux_Install/image/sdk2.jpg new file mode 100644 index 0000000..01db707 Binary files /dev/null and b/Gradle_Linux_Install/image/sdk2.jpg differ diff --git a/Gradle_Test/README.md b/Gradle_Test/README.md new file mode 100644 index 0000000..c88275f --- /dev/null +++ b/Gradle_Test/README.md @@ -0,0 +1,158 @@ +### Gradle单元测试 + +我们可以通过在Gradle添加Java插件来执行单元测试的任务,默认的,在项目中所有的测试都会被执行,如果我们只想测试其中一个类,我们可以使用Java系统属性`test.single`作为测试的名字,事实上,这个系统属性的模式是`taskName.single`,其中`taskName`是我们工程中单元测试类型的名称。以下将会看到我们如何构建单元测试 + +1.创建Gradle工程,并在build.gradle配置文件中加入: +```Java + + // File: build.gradle + apply plugin: 'java' + repositories { + mavenCentral() + } + dependencies { + testCompile 'junit:junit:[4,)' + } + test { + testLogging { + // Show that tests are run in the command-line output + events 'started', 'passed' + } + } +``` +2.第二步,我们创建一个测试类,每个测试类一个测试方法,这样子让我们可以在后面单独的调用他们 + +```Java + + // File: src/test/java/com/mrhaki/gradle/SampleTest.java + package com.mrhaki.gradle; + + import static org.junit.Assert.*; + import org.junit.*; + + public class SampleTest { + + @Test public void sample() { + assertEquals("Gradle is gr8", "Gradle is gr8"); + } + + } + + // File: src/test/java/com/mrhaki/gradle/AnotherSampleTest.java + package com.mrhaki.gradle; + + import static org.junit.Assert.*; + import org.junit.*; + + public class AnotherSampleTest { + + @Test public void anotherSample() { + assertEquals("Gradle is great", "Gradle is great"); + } + } + +``` + +3.为了只执行SampleTest类中的测试方法,我们必须从命令行中以Java系统属性`-Dtest.single=Sample`来执行单元测试 + + $ gradle -Dtest.single=Sample test + :compileJava UP-TO-DATE + :processResources UP-TO-DATE + :classes UP-TO-DATE + :compileTestJava + :processTestResources UP-TO-DATE + :testClasses + :test + + com.mrhaki.gradle.SampleTest > sample STARTED + + com.mrhaki.gradle.SampleTest > sample PASSED + + BUILD SUCCESSFUL + + Total time: 11.404 secs + +4.注意,现在只有一个测试类被执行,Gradle将会使用查询以`**/*`模式的类中查询出测试方法,因此我们不必要写该测试类的全类名,为了只执行AnotherSampleTest测试类我们也是如此: + + $ gradle -Dtest.single=AnotherSample test + :compileJava UP-TO-DATE + :processResources UP-TO-DATE + :classes UP-TO-DATE + :compileTestJava + :processTestResources UP-TO-DATE + :testClasses UP-TO-DATE + :test + + com.mrhaki.gradle.AnotherSampleTest > anotherSample STARTED + + com.mrhaki.gradle.AnotherSampleTest > anotherSample PASSED + + BUILD SUCCESSFUL + + Total time: 5.62 secs + +5.我们也可以使用Java系统属性的模式来匹配多个测试类来一次性执行多个测试方法,。例如,我们可以使用`*Sample`来一次性执行SampleTest和AnotherSampleTest类的单元测试。 + + $ gradle -Dtest.single=*Sample test + :compileJava UP-TO-DATE + :processResources UP-TO-DATE + :classes UP-TO-DATE + :compileTestJava + :processTestResources UP-TO-DATE + :testClasses UP-TO-DATE + :test + + com.mrhaki.gradle.AnotherSampleTest > anotherSample STARTED + + com.mrhaki.gradle.AnotherSampleTest > anotherSample PASSED + + com.mrhaki.gradle.SampleTest > sample STARTED + + com.mrhaki.gradle.SampleTest > sample PASSED + + BUILD SUCCESSFUL + + Total time: 5.605 secs + +6.为了证明Java的系统属性同样对其他测试类型起作用,我们在buile.gradle配置文件中加入了新的task,我们把它叫做sampleTest并且包含了我们的测试文件,我们同样的使用了testLogging方便看测试结果在控制台的输出 + + // File: build.gradle + apply plugin: 'java' + + repositories { + mavenCentral() + } + + dependencies { + testCompile 'junit:junit:[4,)' + } + + task sampleTest(type: Test, dependsOn: testClasses) { + include '**/*Sample*' + } + + tasks.withType(Test) { + testLogging { + events 'started', 'passed' + } + } + +7.下一步我们想运行SampleTest类的单元测试,现在我们可以使用`-DsampleTest.single=S*`来作为运行参数 + + $ gradle -DsampleTest.single=S* sampleTest + :compileJava UP-TO-DATE + :processResources UP-TO-DATE + :classes UP-TO-DATE + :compileTestJava UP-TO-DATE + :processTestResources UP-TO-DATE + :testClasses UP-TO-DATE + :sampleTest + + com.mrhaki.gradle.SampleTest > sample STARTED + + com.mrhaki.gradle.SampleTest > sample PASSED + + BUILD SUCCESSFUL + + Total time: 10.677 secs + Code written with Gradle 1.6 \ No newline at end of file diff --git a/HashSet_VS_TreeSet/README.md b/HashSet_VS_TreeSet/README.md index c78e342..9de61e9 100644 --- a/HashSet_VS_TreeSet/README.md +++ b/HashSet_VS_TreeSet/README.md @@ -1,6 +1,6 @@ -####HashSet And TreeSet +### HashSet And TreeSet -####Set接口 +#### Set接口 Set不允许包含相同的元素,如果试图把两个相同元素加入同一个集合中,add方法返回false。 Set判断两个对象相同不是使用==运算符,而是根据equals方法。也就是说,只要两个对象用equals方法比较返回true,Set就不 会接受这两个对象,并且最多包含一个 null 元素。 @@ -11,7 +11,7 @@ HashSet与TreeSet都是基于Set接口的实现类。其中TreeSet是Set的子 Set接口——|——HashSet实现类 |——LinkedHashSet实现类 -####HashSet +#### HashSet * 不能保证元素的排列顺序,顺序有可能发生变化 * 不是同步的 * 集合元素可以是null,但只能放入一个null @@ -65,7 +65,7 @@ HashSet与TreeSet都是基于Set接口的实现类。其中TreeSet是Set的子 } # -####TreeSet类 +#### TreeSet类 TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。 TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0 @@ -162,6 +162,6 @@ obj1大于obj2,如果是 负数,则表明obj1小于obj2。 8、SortedSet tailSet(fromElement):返回此Set的子集,由大于等于fromElement的元素组成。 -####LinkedHashSet +#### LinkedHashSet HashSet还有一个子类LinkedHashSet,其集合也是根据元素hashCode值来决定元素的存储位置,但它同时用链表来维护元素的次序,这样使得元素看起来是以插入的顺序保存的,也就是说,当遍历LinkedHashSet集合元素时,它将会按元素的添加顺序来访问集合里的元素。所以LinkedHashSet的性能略低于HashSet,但在迭代访问全部元素时将有很好的性能,因为它以链表来维护内部顺序。 diff --git a/Http_Code/README.md b/Http_Code/README.md index dad1e9a..24d6c4c 100644 --- a/Http_Code/README.md +++ b/Http_Code/README.md @@ -1,4 +1,4 @@ -###HTTP状态码 +### HTTP状态码 * 100 请求者应继续进行请求。服务器返回此代码以表示,服务器已收到某项请求的第一部分,正等待接收剩余部分。 * 101 请求者已要求服务器切换协议,服务器已确认并准备切换。 diff --git a/Java_ArrayList_Vector/README.md b/Java_ArrayList_Vector/README.md index 663c153..b04214a 100644 --- a/Java_ArrayList_Vector/README.md +++ b/Java_ArrayList_Vector/README.md @@ -1,4 +1,4 @@ -###ArrayList And Vector +### ArrayList And Vector List,就如图名字所示一样,是元素的有序列表。当我们讨论List时,将其与Set作对比是一个很好的办法,Set集合中的元素是无序且唯一的。 diff --git a/Java_Bubble_Sort/README.md b/Java_Bubble_Sort/README.md index 5b1c4ce..ec732da 100644 --- a/Java_Bubble_Sort/README.md +++ b/Java_Bubble_Sort/README.md @@ -1,5 +1,5 @@ -###Java冒泡排序 -####排序算法概述 +### Java冒泡排序 +#### 排序算法概述 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。 **稳定性**:一个排序算法是稳定的,就是当有两个相等记录的关键字R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前。 diff --git a/Java_Copy_On_Write/README.md b/Java_Copy_On_Write/README.md index e295636..18ae060 100644 --- a/Java_Copy_On_Write/README.md +++ b/Java_Copy_On_Write/README.md @@ -1,4 +1,4 @@ -###Java CopyOnWrite容器 +### Java CopyOnWrite容器 Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。 **什么是CopyOnWrite容器** diff --git a/Java_Exception/README.md b/Java_Exception/README.md index 77948b3..a45a2e1 100644 --- a/Java_Exception/README.md +++ b/Java_Exception/README.md @@ -1,6 +1,6 @@ -###Java异常处理机制 +### Java异常处理机制 -####1. 引子 +#### 1. 引子 try…catch…finally恐怕是大家再熟悉不过的语句了,而且感觉用起来也是很简单,逻辑上似乎也是很容易理解。不过,我亲自体验的“教训”告诉我,这个东西可不是想象中的那么简单、听话。不信?那你看看下面的代码,“猜猜”它执行后的结果会是什么?不要往后看答案、也不许执行代码看真正答案哦。如果你的答案是正确,那么这篇文章你就不用浪费时间看啦。 @@ -97,7 +97,7 @@ finally语句块不应该出现 应该出现return。上面的return ret最好是其他语句来处理相关逻辑。 -####2.JAVA异常 +#### 2.JAVA异常 异常指不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java通 过API中Throwable类的众多子类描述各种不同的异常。因而,Java异常都是对象,是Throwable子类的实例,描述了出现在一段编码中的 错误条件。当条件生成时,错误将引发异常。 diff --git a/Java_ExecutorService/README.md b/Java_ExecutorService/README.md new file mode 100644 index 0000000..1a53169 --- /dev/null +++ b/Java_ExecutorService/README.md @@ -0,0 +1,165 @@ +### ExecutorService 的理解与使用 ### +接口 java.util.concurrent.ExecutorService 表述了异步执行的机制,并且可以让任务在后台执行。一个 ExecutorService 实例因此特别像一个线程池。事实上,在 java.util.concurrent 包中的 ExecutorService 的实现就是一个线程池的实现。 + +#### ExecutorService 样例 #### +```java + ExecutorService executorService = Executors.newFixedThreadPool(10); + + executorService.execute(new Runnable() { + public void run() { + System.out.println("Asynchronous task"); + } + }); + + executorService.shutdown(); +``` + +首先使用 newFixedThreadPool() 工厂方法创建壹個 ExecutorService ,上述代码创建了壹個可以容纳10個线程任务的线程池。其次,向 execute() 方法中传递壹個异步的 Runnable 接口的实现,这样做会让 ExecutorService 中的某個线程执行这個 Runnable 线程。 + +#### 任务的委托(Task Delegation) #### +下方展示了一个线程的把任务委托异步执行的ExecutorService的示意图。 + +![](https://github.com/scalad/Note/blob/master/Java_ExecutorService/image/39824293_1.png) + +壹旦线程把任务委托给 ExecutorService,该线程就会继续执行与运行任务无关的其它任务。 + +#### ExecutorService 的实现 #### +由于 ExecutorService 只是壹個接口,你壹量需要使用它,那麽就需要提供壹個该接口的实现。ExecutorService 接口在 java.util.concurrent 包中有如下实现类: + +* ThreadPoolExecutor +* ScheduledThreadPoolExecutor + +#### 创建壹個 ExecutorService #### +你可以根据自己的需要来创建壹個 ExecutorService ,也可以使用 Executors 工厂方法来创建壹個 ExecutorService 实例。这里有几個创建 ExecutorService 的例子: + +```java +ExecutorService executorService1 = Executors.newSingleThreadExecutor(); +ExecutorService executorService2 = Executors.newFixedThreadPool(10); +ExecutorService executorService3 = Executors.newScheduledThreadPool(10); +``` +#### ExecutorService 使用方法 #### +这里有几种不同的方式让你将任务委托给壹個 ExecutorService: + +* execute(Runnable) +* submit(Runnable) +* submit(Callable) +* invokeAny(...) +* invokeAll(...) + +我会在接下来的内容里把每個方法都看壹遍。 +#### execute(Runnable) #### +方法 execute(Runnable) 接收壹個 java.lang.Runnable 对象作为参数,并且以异步的方式执行它。如下是壹個使用 ExecutorService 执行 Runnable 的例子: +```java + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + executorService.execute(new Runnable() { + public void run() { + System.out.println("Asynchronous task"); + } + }); + + executorService.shutdown(); +``` + +使用这种方式没有办法获取执行 Runnable 之后的结果,如果你希望获取运行之后的返回值,就必须使用 接收 Callable 参数的 execute() 方法,后者将会在下文中提到。 + +#### submit(Runnable) #### +方法 submit(Runnable) 同样接收壹個 Runnable 的实现作为参数,但是会返回壹個 Future 对象。这個 Future 对象可以用于判断 Runnable 是否结束执行。如下是壹個 ExecutorService 的 submit() 方法的例子: + +```java + Future future = executorService.submit(new Runnable() { + public void run() { + System.out.println("Asynchronous task"); + } + }); + //如果任务结束执行则返回 null + System.out.println("future.get()=" + future.get()); +``` +上述样例代码会输出如下结果: + +1 Asynchronous Callable + +2 future.get() = Callable Result + +#### inVokeAny() #### +方法 invokeAny() 接收壹個包含 Callable 对象的集合作为参数。调用该方法不会返回 Future 对象,而是返回集合中某壹個 Callable 对象的结果,而且无法保证调用之后返回的结果是哪壹個 Callable,只知道它是这些 Callable 中壹個执行结束的 Callable 对象。 +如果壹個任务运行完毕或者抛出异常,方法会取消其它的 Callable 的执行。 + +以下是壹個样例: + +```java + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + Set> callables = new HashSet>(); + + callables.add(new Callable() { + public String call() throws Exception { + return "Task 1"; + } + }); + callables.add(new Callable() { + public String call() throws Exception { + return "Task 2"; + } + }); + callables.add(new Callable() { + public String call() throws Exception { + return "Task 3"; + } + }); + + String result = executorService.invokeAny(callables); + + System.out.println("result = " + result); + + executorService.shutdown(); +``` + +以上样例代码会打印出在给定的集合中的某壹個 Callable 的返回结果。我尝试运行了几次,结果都在改变。有时候返回结果是"Task 1",有时候是"Task 2",等等。 + +#### invokeAll() #### +方法 invokeAll() 会调用存在于参数集合中的所有 Callable 对象,并且返回壹個包含 Future 对象的集合,你可以通过这個返回的集合来管理每個 Callable 的执行结果。 +需要注意的是,任务有可能因为异常而导致运行结束,所以它可能并不是真的成功运行了。但是我们没有办法通过 Future 对象来了解到这個差异。 + +以下是壹個代码样例: + +```java + ExecutorService executorService = Executors.newSingleThreadExecutor(); + + Set> callables = new HashSet>(); + + callables.add(new Callable() { + public String call() throws Exception { + return "Task 1"; + } + }); + callables.add(new Callable() { + public String call() throws Exception { + return "Task 2"; + } + }); + callables.add(new Callable() { + public String call() throws Exception { + return "Task 3"; + } + }); + + List> futures = executorService.invokeAll(callables); + + for(Future future : futures){ + System.out.println("future.get = " + future.get()); + } + + executorService.shutdown(); +``` + +#### ExecuteService 服务的关闭 #### +当使用 ExecutorService 完毕之后,我们应该关闭它,这样才能保证线程不会继续保持运行状态。 + +举例来说,如果你的程序通过 main() 方法启动,并且主线程退出了你的程序,如果你还有壹個活动的 ExecutorService 存在于你的程序中,那么程序将会继续保持运行状态。存在于 ExecutorService 中的活动线程会阻止Java虚拟机关闭。 + +为了关闭在 ExecutorService 中的线程,你需要调用 shutdown() 方法。ExecutorService 并不会马上关闭,而是不再接收新的任务,壹但所有的线程结束执行当前任务,ExecutorServie 才会真的关闭。所有在调用 shutdown() 方法之前提交到 ExecutorService 的任务都会执行。 + +如果你希望立即关闭 ExecutorService,你可以调用 shutdownNow() 方法。这個方法会尝试马上关闭所有正在执行的任务,并且跳过所有已经提交但是还没有运行的任务。但是对于正在执行的任务,是否能够成功关闭它是无法保证 的,有可能他们真的被关闭掉了,也有可能它会壹直执行到任务结束。这是壹個最好的尝试。 + +本文英文原文链接:http://tutorials.jenkov.com/java-util-concurrent/executorservice.html#executorservice-example ,中文译文首发开源中国社区 http://my.oschina.net/bairrfhoinn/blog/177639,转载请注明原始出处。 \ No newline at end of file diff --git a/Java_ExecutorService/image/39824293_1.png b/Java_ExecutorService/image/39824293_1.png new file mode 100644 index 0000000..7c317a6 Binary files /dev/null and b/Java_ExecutorService/image/39824293_1.png differ diff --git a/Java_IO/README.md b/Java_IO/README.md new file mode 100644 index 0000000..8b87158 --- /dev/null +++ b/Java_IO/README.md @@ -0,0 +1,38 @@ +### JAVA IO 流 ### + +#### 一、Java IO 体系结构 #### + +#### (1). 流的概念 #### +流是对数据传输的总称或抽象,流的本质是数据传输,是数据的有序排列,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。 + +![](https://github.com/scalad/Note/blob/master/Java_IO/image/io_stream.jpg) + +在C++中,我们将数据从一个对象到另一个对象的流动抽象为"流"。Java继承C++的流机制,不过在具体实现上有别,Java中的"流"就是指把数据从一个对象移动到另一个对象的流动模式的抽象。(专业术语就是拿来装逼的)![](https://github.com/scalad/Note/blob/master/Java_IO/image/emoji1.png) + +James Gosling的Java流模式图与水流模式图概念映射。数据源(data source)即水库,数据目的地(data destination)就是脸盆,数据(data)就是水,流(stream)实例化就是在管子中流动的水流。输入流(input stream)就是用水泵从水库中抽出来要到水管中的水,输出流(output stream)经过水龙头将要达到脸盆中的水,计算机内存(memory)就是上图中的水流管道,关闭输入流(close input stream)就是关闭水泵开关,关闭输出流(close output stream)就是关闭关闭水龙头开关. + +![](https://github.com/scalad/Note/blob/master/Java_IO/image/io_stream1.png) + +#### (2). IO 流的分类 #### +* 根据处理数据类型的不同分为:[(3). 字符流和字节流的区别](#字符流和字节流的区别) +* 根据数据流向不同分为:[输入流和输出流](https://github.com/scalad/Note/tree/master/Java_IO/inputOutputStream) +* 根据流的功能来分:[节点流(又称低级流)、过滤流(又称高级流、处理流、包装流)](https://github.com/scalad/Note/tree/master/Java_IO/functionStream) + +![](https://github.com/scalad/Note/blob/master/Java_IO/image/Java_IO.png) + +#### (3). 字符流和字节流的区别 #### + +* 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读多个字节,在Java流的处理上,字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组 +* 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据 +* 字节流默认是不带缓冲区的,而字符流默认是带缓冲区的 +* 字节流是底层数据流,是数据有意义的最小单位。字符流是字节流的包装,底层实现是字节流 + +> 提醒:如果只是处理纯文本数据的话优先使用字符流,其他的使用字符流 + +字节流和字符流在物理层面的实现都是比特流,二进制数据流可以认为是字节流,而字符流是遵循unicode编码规则的字节流。因此计算机中的"流"概念实际上就是指字节数据(bytes data)从源对象对按顺序流向目标对象的一种流动形式 + +![](https://github.com/scalad/Note/blob/master/Java_IO/image/Java_IO_Detail.png) + +![](https://github.com/scalad/Note/blob/master/Java_IO/image/input.png) + +![](https://github.com/scalad/Note/blob/master/Java_IO/image/output.png) \ No newline at end of file diff --git a/Java_IO/functionStream/README.md b/Java_IO/functionStream/README.md new file mode 100644 index 0000000..a71496c --- /dev/null +++ b/Java_IO/functionStream/README.md @@ -0,0 +1,10 @@ +### 节点流和过滤流 ### +#### 节点流 #### +节点流(Node Stream)是流管道两端直接连接data source和data destination上的,即为取放数据的真实载体,在流通道本身不对数据做任何加工,因而也被称为低级流。 + +![](https://github.com/scalad/Note/blob/master/Java_IO/functionStream/image/node_stream.png) + +#### 过滤流 #### +过滤流(Filter Stream)是套在节点流或过滤上的,而且过滤流是不能够脱离节点流(低级流)存在的,因此称为高级流。过滤流的流管道本身封装方法,能够对低级流数据进行处理,因此也被称为处理流。究其本质,过滤流就是把不符合通过管道条件的数据过滤掉,而让满足条件的数据通过过滤流管道。 + +![](https://github.com/scalad/Note/blob/master/Java_IO/functionStream/image/filter_stream.png) \ No newline at end of file diff --git a/Java_IO/functionStream/image/filter_stream.png b/Java_IO/functionStream/image/filter_stream.png new file mode 100644 index 0000000..a05a102 Binary files /dev/null and b/Java_IO/functionStream/image/filter_stream.png differ diff --git a/Java_IO/functionStream/image/node_stream.png b/Java_IO/functionStream/image/node_stream.png new file mode 100644 index 0000000..cfa15bb Binary files /dev/null and b/Java_IO/functionStream/image/node_stream.png differ diff --git a/Java_IO/image/Java_IO.png b/Java_IO/image/Java_IO.png new file mode 100644 index 0000000..cdaf083 Binary files /dev/null and b/Java_IO/image/Java_IO.png differ diff --git a/Java_IO/image/Java_IO_Detail.png b/Java_IO/image/Java_IO_Detail.png new file mode 100644 index 0000000..6a9b96e Binary files /dev/null and b/Java_IO/image/Java_IO_Detail.png differ diff --git a/Java_IO/image/emoji1.png b/Java_IO/image/emoji1.png new file mode 100644 index 0000000..39d88eb Binary files /dev/null and b/Java_IO/image/emoji1.png differ diff --git a/Java_IO/image/input.png b/Java_IO/image/input.png new file mode 100644 index 0000000..b22b25f Binary files /dev/null and b/Java_IO/image/input.png differ diff --git a/Java_IO/image/io_stream.jpg b/Java_IO/image/io_stream.jpg new file mode 100644 index 0000000..70ad9c9 Binary files /dev/null and b/Java_IO/image/io_stream.jpg differ diff --git a/Java_IO/image/io_stream1.png b/Java_IO/image/io_stream1.png new file mode 100644 index 0000000..b29bc0b Binary files /dev/null and b/Java_IO/image/io_stream1.png differ diff --git a/Java_IO/image/output.png b/Java_IO/image/output.png new file mode 100644 index 0000000..43c89c0 Binary files /dev/null and b/Java_IO/image/output.png differ diff --git a/Java_IO/inputOutputStream/README.md b/Java_IO/inputOutputStream/README.md new file mode 100644 index 0000000..5fa58e0 --- /dev/null +++ b/Java_IO/inputOutputStream/README.md @@ -0,0 +1,7 @@ +### 输入流、输出流 ### + +判断当前流是输入流还是输出流的依据是二进制数据相对于计算机内存的位置,输入流是输入计算机内存是二进制数据,输出流是从计算机内存输出的二进制数据。而计算机程序在运行期间会储存在到计算机内存中,因此总的来说就是数据的来源、取向是相对程序而言的。比如键盘键入数据属于输入流,内存数据持久化到磁盘中属于输出流。 + +![](https://github.com/scalad/Note/blob/master/Java_IO/inputOutputStream/image/inputoutput.png) + +> 说明:本图片取自互联网,纠正补充为流的来源有网络连接、内存块、磁盘(文件)、键盘等;流的去向也基本是这些。 \ No newline at end of file diff --git a/Java_IO/inputOutputStream/image/inputoutput.png b/Java_IO/inputOutputStream/image/inputoutput.png new file mode 100644 index 0000000..57e4da4 Binary files /dev/null and b/Java_IO/inputOutputStream/image/inputoutput.png differ diff --git a/Java_JVM_Monitor/README.md b/Java_JVM_Monitor/README.md new file mode 100644 index 0000000..ced634c --- /dev/null +++ b/Java_JVM_Monitor/README.md @@ -0,0 +1,90 @@ +## JVM监控和诊断概述jps、jstak、jstack、jinfo、jmap ## +程序运行中经常会遇到各种问题,定位问题时通常需要综合各种信息,如系统日志、堆dump文件、线程dump文件、GC日志等。通过虚拟机监控和诊断工具可以帮忙我们快速获取、分析需要的数据,进而提高问题解决速度。可以使用图形化工具如:jconsole,jvisualvm,或者命令进行监控和诊断命令。 本文将介绍虚拟机常用监控和问题诊断命令工具的使用方法,主要包含以下工具: + + jps 显示系统中所有Hotspot虚拟机进程 + jstat 收集Hotspot虚拟机各方面运行数据 + jstack 显示虚拟机的线程栈信息 + jinfo 显示虚拟机的配置信息 + jmap 用于生成虚拟机的内存快照信息 + +### 命令介绍 ### + +#### 1).jps 命令 #### +JVM Process Status Tool,该命令用于列出正在运行的虚拟机进程,显示main类的名称和虚拟机进程id。该命令受当前用户的访问权限影响,比如linux下非root用户只列出当前用户启动的虚拟机进程。 + +命令格式: + + jps [options] [hostid] + + 常用参数: + -l 输出主类全名 + -v 输出虚拟机进程启动的jvm参数 + -m 输出启动时传递给main函数的参数 + +#### 2).jstack 命令 #### +Stack Trace for Java,用于生成虚拟机当前的线程快照信息,包含每一条线程的堆栈信息。该命令通常用于定位线程停顿原因,当出现线程停顿时,可通过stack查看每个线程的堆栈信息,进而分析停顿原因。 + +命令格式: + jstack [ option ] pid + + 常用参数: + -l 除堆栈外,显示锁的附加信息 + -F 当请求不被响应时,强制输出线程堆栈 + -m 混合模式,打印java和本地C++调用的堆栈信息 + +#### 3) jstat命令 #### +JVM Statistics Monitoring Tool,用于监控各种运行状态信息的命令。在只有文本控制台的环境中(如企业中的生产环境),该工具非常有用。 可以用来显示系统中类装载、垃圾回收、运行期编译状况等运行数据。 + +命令格式: + jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ] + vmid表示虚拟机唯一标识符,如果是本地虚拟机进程,与LVMID一致,通常为本地虚拟机进程号。 + interval表示查询间隔时间,count表示查询次数。如果省略interval和count参数,表示查询一次。 + + 常用参数: + class 类装载相关信息. + compiler JIT编译器编译过的方法、耗时等. + gc java堆信息和垃圾回收状况. + gccapacity 关注java堆各个区的最大和最小空间. + gccause 类似gcutil,额外输出导致上一次gc的原因. + gcnew 新生代gc状况. + gcnewcapacity 关注新生代gc使用的最大和最小空间. + gcold 老年代gc状况. + gcoldcapacity 关注老年代gc使用的最大和最小空间. + gcpermcapacity 关注持久代gc使用的最大和最小空间. + gcutil 关注已使用空间占总空间比例. + printcompilation 输出已经被JIT编译的方法. + +#### 4) jinfo命令 #### +Configuration Info for Java,用于查看和修改虚拟机的各项参数信息。 + +命令格式: + + jinfo [ option ] pid + 常用参数: + -flag name 打印虚拟机该参数对应的值. + -flag [+\-]name 使该参数生效或失效. + -flag name=value 修改相应参数的值. + -flags 打印传给jvm的参数值. + -sysprops 打印System.getProperties()信息. + +#### 5) jmap命令 #### +Memory Map for Java,可以产生堆dump文件,查询堆和持久代的详细信息等。 + +命令格式: + + jmap [ option ] pid + 常用参数: + -dump 生成堆dump文件,格式为: -dump:[live,]format=b,file= + -heap 显示java堆的详细信息,包括垃圾回收期、堆配置和分代信息等 + -histo 显示堆中对象的统计信息,包括类名称,对应的实例数量和总容量 + -permstat 统计持久代中各ClassLoader的统计信息。 + +#### 6) jhatk命令 #### +Jvm Heap Analysis Tool, 与jmap一起使用 + +示例: + + jhat dump.tmp + Reading from dump.tmp... + Dump file created Tue Jun 28 13:55:09 CST 2016 + diff --git a/Java_Jstat/README.md b/Java_Jstat/README.md new file mode 100644 index 0000000..606f835 --- /dev/null +++ b/Java_Jstat/README.md @@ -0,0 +1,109 @@ +## JVM自带命令jstat命令 ## +Jstat是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,它位于Java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。可见,Jstat是轻量级的、专门针对JVM的工具,非常适用。 + +jstat工具特别强大,有众多的可选项,详细查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id,和所选参数。 + +参考格式如下:jstat -options + +可以列出当前JVM版本支持的选项,常见的有 + + class (类加载器) + compiler (JIT) + gc (GC堆状态) + gccapacity (各区大小) + gccause (最近一次GC统计和原因) + gcnew (新区统计) + gcnewcapacity (新区大小) + gcold (老区统计) + gcoldcapacity (老区大小) + gcpermcapacity (永久区大小) + gcutil (GC统计汇总) + printcompilation (HotSpot编译统计) + +### 1、jstat –class : 显示加载class的数量,及所占空间等信息。 ### + + + 显示列名 具体描述 + Loaded 装载的类的数量 + Bytes 装载类所占用的字节数 + Unloaded 卸载类的数量 + Bytes 卸载类的字节数 + Time 装载和卸载类所花费的时间 + +### 2、jstat -compiler 显示VM实时编译的数量等信息。 ### + + + 显示列名 具体描述 + Compiled 编译任务执行数量 + Failed 编译任务执行失败数量 + Invalid 编译任务执行失效数量 + Time 编译任务消耗时间 + FailedType 最后一个编译失败任务的类型 + FailedMethod 最后一个编译失败任务所在的类及方法 + +### 3、jstat -gc : 可以显示gc的信息,查看gc的次数,及时间。 ### + + 显示列名 具体描述 + S0C 年轻代中第一个survivor(幸存区)的容量 (字节) + S1C 年轻代中第二个survivor(幸存区)的容量 (字节) + S0U 年轻代中第一个survivor(幸存区)目前已使用空间 (字节) + S1U 年轻代中第二个survivor(幸存区)目前已使用空间 (字节) + EC 年轻代中Eden(伊甸园)的容量 (字节) + EU 年轻代中Eden(伊甸园)目前已使用空间 (字节) + OC Old代的容量 (字节) + OU Old代目前已使用空间 (字节) + PC Perm(持久代)的容量 (字节) + PU Perm(持久代)目前已使用空间 (字节) + YGC 从应用程序启动到采样时年轻代中gc次数 + YGCT 从应用程序启动到采样时年轻代中gc所用时间(s) + FGC 从应用程序启动到采样时old代(全gc)gc次数 + FGCT 从应用程序启动到采样时old代(全gc)gc所用时间(s) + GCT 从应用程序启动到采样时gc用的总时间(s) + +### 4、jstat -gccapacity :可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小 ### + + 显示列名 具体描述 + NGCMN 年轻代(young)中初始化(最小)的大小(字节) + NGCMX 年轻代(young)的最大容量 (字节) + NGC 年轻代(young)中当前的容量 (字节) + S0C 年轻代中第一个survivor(幸存区)的容量 (字节) + S1C 年轻代中第二个survivor(幸存区)的容量 (字节) + EC 年轻代中Eden(伊甸园)的容量 (字节) + OGCMN old代中初始化(最小)的大小 (字节) + OGCMX old代的最大容量(字节) + OGC old代当前新生成的容量 (字节) + OC Old代的容量 (字节) + PGCMN perm代中初始化(最小)的大小 (字节) + PGCMX perm代的最大容量 (字节) + PGC perm代当前新生成的容量 (字节) + PC Perm(持久代)的容量 (字节) + YGC 从应用程序启动到采样时年轻代中gc次数 + FGC 从应用程序启动到采样时old代(全gc)gc次数 + +### 5、jstat -gcutil :统计gc信息 ### + +![](https://github.com/scalad/Note/blob/master/Java_Jstat/image/5.png) + +### 6、jstat -gcnew :年轻代对象的信息。 ### + +![](https://github.com/scalad/Note/blob/master/Java_Jstat/image/6.png) + +### 7、jstat -gcnewcapacity: 年轻代对象的信息及其占用量。 ### + +![](https://github.com/scalad/Note/blob/master/Java_Jstat/image/7.png) + +### 8、jstat -gcold :old代对象的信息。 ### + +![](https://github.com/scalad/Note/blob/master/Java_Jstat/image/8.png) + +### 9、stat -gcoldcapacity : old代对象的信息及其占用量。 ### + +![](https://github.com/scalad/Note/blob/master/Java_Jstat/image/9.png) + +### 10、jstat -gcpermcapacity: perm对象的信息及其占用量。 ### + +![](https://github.com/scalad/Note/blob/master/Java_Jstat/image/10.png) + +### 11、jstat -printcompilation :当前VM执行的信息。 ### + +![](https://github.com/scalad/Note/blob/master/Java_Jstat/image/11.png) \ No newline at end of file diff --git a/Java_Jstat/image/10.png b/Java_Jstat/image/10.png new file mode 100644 index 0000000..544d199 Binary files /dev/null and b/Java_Jstat/image/10.png differ diff --git a/Java_Jstat/image/11.png b/Java_Jstat/image/11.png new file mode 100644 index 0000000..d00b0aa Binary files /dev/null and b/Java_Jstat/image/11.png differ diff --git a/Java_Jstat/image/5.png b/Java_Jstat/image/5.png new file mode 100644 index 0000000..4925f55 Binary files /dev/null and b/Java_Jstat/image/5.png differ diff --git a/Java_Jstat/image/6.png b/Java_Jstat/image/6.png new file mode 100644 index 0000000..6b9ba14 Binary files /dev/null and b/Java_Jstat/image/6.png differ diff --git a/Java_Jstat/image/7.png b/Java_Jstat/image/7.png new file mode 100644 index 0000000..95cb9e9 Binary files /dev/null and b/Java_Jstat/image/7.png differ diff --git a/Java_Jstat/image/8.png b/Java_Jstat/image/8.png new file mode 100644 index 0000000..14b4df9 Binary files /dev/null and b/Java_Jstat/image/8.png differ diff --git a/Java_Jstat/image/9.png b/Java_Jstat/image/9.png new file mode 100644 index 0000000..e35ef4d Binary files /dev/null and b/Java_Jstat/image/9.png differ diff --git a/Java_Lambda/README.md b/Java_Lambda/README.md index ca50818..02d6cbc 100644 --- a/Java_Lambda/README.md +++ b/Java_Lambda/README.md @@ -1,4 +1,4 @@ -###Java8 Lambda表达式 +### Java8 Lambda表达式 **1. 什么是λ表达式** λ表达式本质上是一个匿名方法。让我们来看下面这个例子: diff --git a/Java_Memory/README.md b/Java_Memory/README.md index 9f41ef6..ee090a1 100644 --- a/Java_Memory/README.md +++ b/Java_Memory/README.md @@ -1,4 +1,4 @@ -###关于Java内存泄漏 +### 关于Java内存泄漏 **Java是如何管理内存** diff --git a/Java_Node/README.md b/Java_Node/README.md index 081714a..c0b40e7 100644 --- a/Java_Node/README.md +++ b/Java_Node/README.md @@ -1,4 +1,4 @@ -###二叉树 +### 二叉树 public class Node { public int value; diff --git a/Java_Photo_Base64AndZip/README.md b/Java_Photo_Base64AndZip/README.md new file mode 100644 index 0000000..4c49dd0 --- /dev/null +++ b/Java_Photo_Base64AndZip/README.md @@ -0,0 +1,156 @@ +### 图片转Base64并压缩 + +首先需要Apache下的两个jar包 + + + + commons-codec + commons-codec + 1.10 + + + + commons-io + commons-io + 2.4 + +```Java + package com.silene.base64; + + import java.io.ByteArrayInputStream; + import java.io.ByteArrayOutputStream; + import java.io.File; + import java.io.FileInputStream; + import java.io.IOException; + import java.util.zip.GZIPInputStream; + import java.util.zip.GZIPOutputStream; + + import org.apache.commons.codec.binary.Base64; + import org.apache.commons.io.FileUtils; + + public class TestBase64Zip { + + public static void main(String[] args) { + + String base64 = base64("e:\\question.jpg"); + System.out.println("经过base64转码和压缩" + base64); + + decode(base64, "test.jpg", "e:\\"); + } + + /** + * 把经过压缩过的base64串解码解压并写入打磁盘中 + * @param base64 压缩过的base64串 + * @param fileName 文件名 + * @param path 路径地址 + */ + public static void decode(String base64, String fileName, String path) { + //解码 + byte[] data = Base64.decodeBase64(base64); + data = unGZip(data); + writeFile(data, fileName, path); + } + + /** + * 二进制文件写入文件 + * @param data 二进制数据 + * @param fileName 文件名 + * @param path 路径地址 + */ + public static void writeFile(byte[] data, String fileName, String path) { + try + { + String url = path + "//" + fileName; + FileUtils.writeByteArrayToFile(new File(url), data); + } + catch (IOException e) + { + System.out.println("写文件出错" + e); + } + } + + /** + * 解壓Gzip + * @param data + * @return + */ + public static byte[] unGZip(byte[] data){ + byte[] b = null; + try{ + ByteArrayInputStream bis = new ByteArrayInputStream(data); + GZIPInputStream gzip = new GZIPInputStream(bis); + byte[] buf = new byte[1024]; + int num = -1; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while ((num = gzip.read(buf, 0, buf.length)) != -1) + { + baos.write(buf, 0, num); + } + b = baos.toByteArray(); + baos.flush(); + baos.close(); + gzip.close(); + bis.close(); + } + catch (Exception ex){ + System.out.println("解压数据流出错!!" + ex); + } + return b; + } + + /** + * 读取文件并压缩数据然后转Base64编码 + * @param pathName 图片的绝对路径地址 + * @return + */ + public static String base64(String pathName) { + byte[] data = getPicData(pathName); + if (data == null) { + return null; + } + byte[] zipData = gZip(data); + return Base64.encodeBase64String(zipData); + } + + /** + * @description 获取图片的二进制数据 + * @param pathName 图片的绝对路径地址 + * @return + */ + public static byte[] getPicData(String pathName) { + byte[] data = null; + try { + FileInputStream fi = new FileInputStream(pathName); + int length = fi.available(); + data = new byte[length]; + fi.read(data); + fi.close(); + } catch (Exception e) { + System.out.println(e); + } + return data; + } + + /*** + * @description 压缩GZip + * @param data 要压缩的二进制数据 + * @return + */ + public static byte[] gZip(byte[] data) { + byte[] b = null; + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(bos); + gzip.write(data); + gzip.finish(); + gzip.close(); + b = bos.toByteArray(); + bos.close(); + } catch (Exception ex) { + ex.printStackTrace(); + } + return b; + } + + } +``` \ No newline at end of file diff --git a/Java_Request_GetIP/README.md b/Java_Request_GetIP/README.md new file mode 100644 index 0000000..efb6752 --- /dev/null +++ b/Java_Request_GetIP/README.md @@ -0,0 +1,47 @@ +### 获取客户端真实的IP地址 + +```Java + + /** + * + * @description:获取客户端真实IP + * @param request + * @return + */ + public static String getIpAddr(HttpServletRequest request) + { + + if (request == null) + { + return ""; + } + String ip = request.getHeader("x-forwarded-for"); + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getRemoteAddr(); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("http_client_ip"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + // 如果是多级代理,那么取第一个ip为客户ip + if (ip != null && ip.indexOf(",") != -1) + { + ip = ip.substring(ip.lastIndexOf(",") + 1, ip.length()).trim(); + } + return ip; + } +``` \ No newline at end of file diff --git a/Java_Shiro_Session/README.md b/Java_Shiro_Session/README.md new file mode 100644 index 0000000..8cf09df --- /dev/null +++ b/Java_Shiro_Session/README.md @@ -0,0 +1,497 @@ +### Shiro Session +session管理可以说是Shiro的一大卖点 + +Shiro可以为任何应用(从简单的命令行程序还是手机应用再到大型企业应用)提供会话解决方案。 + +在Shiro出现之前,如果我们想让你的应用支持session,我们通常会依赖web容器或者使用EJB的Session Bean。 + +Shiro对session的支持更加易用,而且他可以在任何应用、任何容器中使用。 + +即便我们使用Servlet或者EJB也并不代表我们必须使用容器的session,Shiro提供的一些特性足以让我们用Shiro session替代他们。 + +* 基于POJO + +* 易定制session持久化 + +* 容器无关的session集群 + +* 支持多种客户端访问 + +* 会话事件监听 + +* 对失效session的延长 + +* 对Web的透明支持 + +* 支持SSO + +使用Shiro session时,无论是在JavaSE还是web,方法都是一样的。 + +```Java +public static void main(String[] args) { + Factory factory = new IniSecurityManagerFactory("classpath:shiro/shiro.ini"); + + SecurityUtils.setSecurityManager(factory.getInstance()); + Subject currentUser = SecurityUtils.getSubject(); + UsernamePasswordToken token = new UsernamePasswordToken("king","t;stmdtkg"); + currentUser.login(token); + + Session session = currentUser.getSession(); + System.out.println(session.getHost()); + System.out.println(session.getId()); + + System.out.println(session.getStartTimestamp()); + System.out.println(session.getLastAccessTime()); + + session.touch(); + User u = new User(); + session.setAttribute(u, "King."); + Iterator keyItr = session.getAttributeKeys().iterator(); + while(keyItr.hasNext()){ + System.out.println(session.getAttribute(keyItr.next())); + } +} +``` + +无论是什么环境,只需要调用Subject的getSession()即可。 +另外Subject还提供了一个... + + Session getSession(boolean create); + +即,当前Subject的session不存在时是否创建并返回新的session。 + +即,当前Subject的session不存在时是否创建并返回新的session。 + + +以DelegatingSubject为例: +(注意!从Shiro 1.2开始多了一个isSessionCreationEnabled属性,其默认值为true。) + +```Java +public Session getSession() { + return getSession(true); +} + +public Session getSession(boolean create) { + if (log.isTraceEnabled()) { + log.trace("attempting to get session; create = " + create + + "; session is null = " + (this.session == null) + + "; session has id = " + (this.session != null && session.getId() != null)); + } + + if (this.session == null && create) { + + //added in 1.2: + if (!isSessionCreationEnabled()) { + String msg = "Session creation has been disabled for the current subject. This exception indicates " + + "that there is either a programming error (using a session when it should never be " + + "used) or that Shiro's configuration needs to be adjusted to allow Sessions to be created " + + "for the current Subject. See the " + DisabledSessionException.class.getName() + " JavaDoc " + + "for more."; + throw new DisabledSessionException(msg); + } + + log.trace("Starting session for host {}", getHost()); + SessionContext sessionContext = createSessionContext(); + Session session = this.securityManager.start(sessionContext); + this.session = decorate(session); + } + return this.session; +} +``` + +### SessionManager + +正如其名,sessionManager用于为应用中的Subject管理session,比如创建、删除、失效或者验证等。 +和Shiro中的其他核心组件一样,他由SecurityManager维护。 +> (注意:public interface SecurityManager extends Authenticator, Authorizer, SessionManager)。 + +```Java +public interface SessionManager { + Session start(SessionContext context); + Session getSession(SessionKey key) throws SessionException; +} +``` + +Shiro为SessionManager提供了3个实现类(顺便也整理一下与SecurityManager实现类的关系)。 + +![](https://github.com/scalad/Note/blob/master/Java_Shiro_Session/image/012002107171657.jpg) + +* DefaultSessionManager + +* DefaultWebSessionManager + +* ServletContainerSessionManager + +其中ServletContainerSessionManager只适用于servlet容器中,如果需要支持多种客户端访问,则应该使用DefaultWebSessionManager。 + +默认情况下,sessionManager的实现类的超时设为30分钟。 + +见AbstractSessionManager: + + public static final long DEFAULT_GLOBAL_SESSION_TIMEOUT = 30 * MILLIS_PER_MINUTE; + private long globalSessionTimeout = DEFAULT_GLOBAL_SESSION_TIMEOUT; + + +当然,我们也可以直接设置AbstractSessionManager的globalSessionTimeout。 + +比如在.ini中: + + securityManager.sessionManager.globalSessionTimeout = 3600000 + +注意!如果使用的SessionManager是ServletContainerSessionManager(没有继AbstractSessionManager),超时设置则依赖于Servlet容器的设置。 +见: [https://issues.apache.org/jira/browse/SHIRO-240](https://issues.apache.org/jira/browse/SHIRO-240) + +session过期的验证方法可以参考SimpleSession: + +```Java +protected boolean isTimedOut() { + + if (isExpired()) { + return true; + } + + long timeout = getTimeout(); + + if (timeout >= 0l) { + + Date lastAccessTime = getLastAccessTime(); + + if (lastAccessTime == null) { + String msg = "session.lastAccessTime for session with id [" + + getId() + "] is null. This value must be set at " + + "least once, preferably at least upon instantiation. Please check the " + + getClass().getName() + " implementation and ensure " + + "this value will be set (perhaps in the constructor?)"; + throw new IllegalStateException(msg); + } + + // Calculate at what time a session would have been last accessed + // for it to be expired at this point. In other words, subtract + // from the current time the amount of time that a session can + // be inactive before expiring. If the session was last accessed + // before this time, it is expired. + long expireTimeMillis = System.currentTimeMillis() - timeout; + Date expireTime = new Date(expireTimeMillis); + return lastAccessTime.before(expireTime); + } else { + if (log.isTraceEnabled()) { + log.trace("No timeout for session with id [" + getId() + + "]. Session is not considered expired."); + } + } + + return false; +} +``` + +试着从SecurityUtils.getSubject()一步步detect,感受一下session是如何设置到subject中的。 +判断线程context中是否存在Subject后,若不存在,我们使用Subject的内部类Builder进行buildSubject(); + +```Java +public static Subject getSubject() { + Subject subject = ThreadContext.getSubject(); + if (subject == null) { + subject = (new Subject.Builder()).buildSubject(); + ThreadContext.bind(subject); + } + return subject; +} +``` + +buildSubject()将建立Subject的工作委托给securityManager.createSubject(subjectContext) +createSubject会调用resolveSession处理session。 + +```Java +protected SubjectContext resolveSession(SubjectContext context) { + if (context.resolveSession() != null) { + log.debug("Context already contains a session. Returning."); + return context; + } + try { + //Context couldn't resolve it directly, let's see if we can since we have direct access to + //the session manager: + Session session = resolveContextSession(context); + if (session != null) { + context.setSession(session); + } + } catch (InvalidSessionException e) { + log.debug("Resolved SubjectContext context session is invalid. Ignoring and creating an anonymous " + + "(session-less) Subject instance.", e); + } + return context; +} +``` + +resolveSession(subjectContext),首先尝试从context(MapContext)中获取session,如果无法直接获取则改为获取subject,再调用其getSession(false)。 + +如果仍不存在则调用resolveContextSession(subjectContext),试着从MapContext中获取sessionId。 + +根据sessionId实例化一个SessionKey对象,并通过SessionKey实例获取session。 + +getSession(key)的任务直接交给sessionManager来执行。 + +```Java +public Session getSession(SessionKey key) throws SessionException { + return this.sessionManager.getSession(key); +} +``` + +sessionManager.getSession(key)方法在AbstractNativeSessionManager中定义,该方法调用lookupSession(key), + +lookupSession调用doGetSession(key),doGetSession(key)是个protected abstract,实现由子类AbstractValidatingSessionManager提供。 + +doGetSession调用retrieveSession(key),该方法尝试通过sessionDAO获得session信息。 + +最后,判断session是否为空后对其进行验证(参考SimpleSession.validate())。 + +```Java +protected final Session doGetSession(final SessionKey key) throws InvalidSessionException { + enableSessionValidationIfNecessary(); + + log.trace("Attempting to retrieve session with key {}", key); + + Session s = retrieveSession(key); + if (s != null) { + validate(s, key); + } + return s; +} +``` + +Session Listener + +我们可以通过SessionListener接口或者SessionListenerAdapter来进行session监听,在session创建、停止、过期时按需进行操作。 + +```Java +public interface SessionListener { + + void onStart(Session session); + + void onStop(Session session); + + void onExpiration(Session session); +} +``` + +我只需要定义一个Listener并将它注入到sessionManager中。 + +```Java +package pac.testcase.shiro.listener; + +import org.apache.shiro.session.Session; +import org.apache.shiro.session.SessionListener; + +public class MySessionListener implements SessionListener { + + public void onStart(Session session) { + System.out.println(session.getId()+" start..."); + } + + public void onStop(Session session) { + System.out.println(session.getId()+" stop..."); + } + + public void onExpiration(Session session) { + System.out.println(session.getId()+" expired..."); + } + +} +``` + + [main] + realm0=pac.testcase.shiro.realm.MyRealm0 + realm1=pac.testcase.shiro.realm.MyRealm1 + + + authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy + sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager + #sessionManager = org.apache.shiro.web.session.mgt.ServletContainerSessionManager + + sessionListener = pac.testcase.shiro.listener.MySessionListener + securityManager.realms=$realm1 + securityManager.authenticator.authenticationStrategy = $authcStrategy + securityManager.sessionManager=$sessionManager + #sessionManager.sessionListeners =$sessionListener + securityManager.sessionManager.sessionListeners=$sessionListener + +###SessionDAO + +SessionManager将session CRUD的工作委托给SessionDAO。 + +我们可以用特定的数据源API实现SessionDAO,以将session存储于任何一种数据源中。 + +```Java +public interface SessionDAO { + + Serializable create(Session session); + + Session readSession(Serializable sessionId) throws UnknownSessionException; + + void update(Session session) throws UnknownSessionException; + + void delete(Session session); + + Collection getActiveSessions(); +} +``` + +![](https://github.com/scalad/Note/blob/master/Java_Shiro_Session/image/012002553111967.jpg) + +* AbstractSessionDAO:在create和read时对session做验证,保证session可用,并提供了sessionId的生成方法。 + +* CachingSessionDAO:为session存储提供透明的缓存支持,使用CacheManager维护缓存。 + +* EnterpriseCacheSessionDAO:通过匿名内部类重写了AbstractCacheManager的createCache,返回MapCache对象。 + +* MemorySessionDAO:基于内存的实现,所有会话放在内存中。 + +![](https://github.com/scalad/Note/blob/master/Java_Shiro_Session/image/012003031238347.jpg) + +默认使用MemorySessionDAO(注意!DefaultWebSessionManager extends DefaultSessionManager) + + + +当然,我们也可以试着使用缓存。 +Shiro没有默认启用EHCache,但是为了保证session不会在运行时莫名其妙地丢失,建议启用EHCache优化session管理。 +启用EHCache为session持久化服务非常简单,首先我们需要添加一个denpendency。 + +```Xml + + org.apache.shiro + shiro-ehcache + ${shiro.version} + +``` + +接着只需要配置一下,以.ini配置为例: + + [main] + realm0=pac.testcase.shiro.realm.MyRealm0 + realm1=pac.testcase.shiro.realm.MyRealm1 + + authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy + sessionManager = org.apache.shiro.web.session.mgt.DefaultWebSessionManager + cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager + sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO + #sessionManager = org.apache.shiro.web.session.mgt.ServletContainerSessionManager + + sessionListener = pac.testcase.shiro.listener.MySessionListener + securityManager.realms=$realm1 + securityManager.authenticator.authenticationStrategy = $authcStrategy + securityManager.sessionManager=$sessionManager + sessionManager.sessionListeners =$sessionListener + sessionDAO.cacheManager=$cacheManager + securityManager.sessionManager.sessionDAO=$sessionDAO + securityManager.sessionManager.sessionListeners=$sessionListener + +此处主要是cacheManager的定义和引用。 + +另外,此处使用的sessionDAO为EnterpriseCacheSessionDAO。 + +前面说过EnterpriseCacheSessionDAO使用的CacheManager是基于MapCache的。 + +其实这样设置并不会影响,因为EnterpriseCacheSessionDAO继承CachingSessionDAO,CachingSessionDAO实现CacheManagerAware。 + +注意!只有在使用SessionManager的实现类时才有sessionDAO属性。 + +(事实上他们把sessionDAO定义在DefaultSessionManager中了,但似乎有将sessionDAO放到AbstractValidatingSessionManager的打算。) + +如果你在web应用中配置Shiro,启动后你会惊讶地发现securityManger的sessionManager属性居然是ServletContainerSessionManager。 + +看一下上面的层次图发现ServletContainerSessionManager和DefaultSessionManager没有关系。 +也就是说ServletContainerSessionManager不支持SessionDAO(cacheManger属性定义在CachingSessionDAO)。 + +此时需要显示指定sessionManager为DefaultWebSessionManager。 + +关于EhCache的配置,默认情况下EhCacheManager使用指定的配置文件,即: + + private String cacheManagerConfigFile = "classpath:org/apache/shiro/cache/ehcache/ehcache.xml"; + +来看一下他的配置: + +```Xml + + + + + + + + + +``` + +如果打算改变该原有设置,其中有两个属性需要特别注意: + +* overflowToDisk="true":保证session不会丢失。 + +* eternal="true":保证session缓存不会被自动失效,将其设为false可能会和session validation的逻辑不符。 + +另外,name默认使用"shiro-activeSessionCache" + +public static final String ACTIVESESSIONCACHE_NAME = "shiro-activeSessionCache"; + +如果打算使用其他名字,只要在CachingSessionDAO或其子类设置activeSessionsCacheName即可。 + +当创建一个新的session时,SessionDAO的实现类使用SessionIdGenerator来为session生成ID。 +默认使用的SessionIdGenerator是JavaUuidSessionIdGenerator,其实现为: + +```Java +public Serializable generateId(Session session) { + return UUID.randomUUID().toString(); +} +``` + +当然,我们也可以自己定制实现SessionIdGenerator。 + +### Session Validation & Scheduling + +比如说用户在浏览器上使用web应用时session被创建并缓存什么的都没有什么问题,只是用户退出的时候可以直接关掉浏览器、关掉电源、停电或者其他天灾什么的。 + +然后session的状态就不得而知了(it is orphaned)。 +为了防止垃圾被一点点堆积起来,我们需要周期性地检查session并在必要时删除session。 +于是我们有SessionValidationScheduler: + +```Java +public interface SessionValidationScheduler { + + boolean isEnabled(); + void enableSessionValidation(); + void disableSessionValidation(); + +} +``` + +Shiro只提供了一个实现,ExecutorServiceSessionValidationScheduler。 默认情况下,验证周期为60分钟。 + +当然,我们也可以通过修改他的interval属性改变验证周期(单位为毫秒),比如这样: + + sessionValidationScheduler = org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler + sessionValidationScheduler.interval = 3600000 + securityManager.sessionManager.sessionValidationScheduler = $sessionValidationScheduler + +如果打算禁用按周期验证session(比如我们在Shiro外做了一些工作),则可以设置 + + securityManager.sessionManager.sessionValidationSchedulerEnabled = false + +如果不打算删除失效的session(比如我们要做点统计之类的),则可以设置 + + securityManager.sessionManager.deleteInvalidSessions = false \ No newline at end of file diff --git a/Java_Shiro_Session/image/012002107171657.jpg b/Java_Shiro_Session/image/012002107171657.jpg new file mode 100644 index 0000000..7629dd1 Binary files /dev/null and b/Java_Shiro_Session/image/012002107171657.jpg differ diff --git a/Java_Shiro_Session/image/012002553111967.jpg b/Java_Shiro_Session/image/012002553111967.jpg new file mode 100644 index 0000000..98e546e Binary files /dev/null and b/Java_Shiro_Session/image/012002553111967.jpg differ diff --git a/Java_Shiro_Session/image/012003031238347.jpg b/Java_Shiro_Session/image/012003031238347.jpg new file mode 100644 index 0000000..bf99b51 Binary files /dev/null and b/Java_Shiro_Session/image/012003031238347.jpg differ diff --git a/Java_Singleton/README.md b/Java_Singleton/README.md index d5fd7ae..212fbf1 100644 --- a/Java_Singleton/README.md +++ b/Java_Singleton/README.md @@ -1,4 +1,4 @@ -###Java:单例模式的七种写法 +### Java:单例模式的七种写法 **第一种(懒汉,线程不安全):** public class Singleton { diff --git a/Java_Transient/README.md b/Java_Transient/README.md index 72cae4c..62dbdfb 100644 --- a/Java_Transient/README.md +++ b/Java_Transient/README.md @@ -1,7 +1,7 @@ -####Java中Transient关键字 +#### Java中Transient关键字 虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transient关键字的使用,涨下姿势~~~好了,废话不多说,下面开始: -####1. transient的作用及使用方法 +#### 1. transient的作用及使用方法 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。 然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。 @@ -101,7 +101,7 @@ 密码字段为null,说明反序列化时根本没有从文件中获取到信息。 -####2. transient使用小结 +#### 2. transient使用小结 1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。 @@ -205,7 +205,7 @@ 这说明反序列化后类中static型变量username的值为当前JVM中对应static变量的值,为修改后jmwang,而不是序列化时的值Alexia。 -####3. transient使用细节——被transient关键字修饰的变量真的不能被序列化吗? +#### 3. transient使用细节——被transient关键字修饰的变量真的不能被序列化吗? 思考下面的例子: @@ -262,7 +262,7 @@ content变量会被序列化吗?好吧,我把答案都输出来了,是的 我们知道在Java中,对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。因此第二个例子输出的是变量content初始化的内容,而不是null。 -####关于java.io.Serializable序列化 +#### 关于java.io.Serializable序列化 Java API中java.io.Serializable接口源码: public interface Serializable { diff --git a/Java_URLConnection_File_Download/README.md b/Java_URLConnection_File_Download/README.md index f79ef55..76615e0 100644 --- a/Java_URLConnection_File_Download/README.md +++ b/Java_URLConnection_File_Download/README.md @@ -1,4 +1,4 @@ -###JAVA URLConnection在线文件的下载 +### JAVA URLConnection在线文件的下载 # /* diff --git a/Java_Volatile/README.md b/Java_Volatile/README.md index bce07b3..d5968e7 100644 --- a/Java_Volatile/README.md +++ b/Java_Volatile/README.md @@ -1,4 +1,4 @@ -###Java Volatile关键字 +### Java Volatile关键字 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。 Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制。 diff --git a/Java_jmap_histo_pid/README.md b/Java_jmap_histo_pid/README.md new file mode 100644 index 0000000..ac43e3f --- /dev/null +++ b/Java_jmap_histo_pid/README.md @@ -0,0 +1,73 @@ +### jmap -histo pid 输出结果样式 + +因为在Linux环境,用jdk自带的jmap工具(Linux/Unix环境特有的),可以对进程中的内存对象监视,然后就运行命令jmap -histo [pid],找内存中的对象数目变化,如下图所示: + + num #instances #bytes class name + ---------------------------------------------- + 1: 1169837 131659368 [C + 2: 25945 38337824 [I + 3: 31548 29407968 [B + 4: 1164546 27949104 java.lang.String + 6: 91313 12829072 + 7: 12395 12404880 [S + 8: 91313 11700288 + 9: 7525 9303112 + 10: 7525 5606808 + 11: 6043 5028288 + 12: 10048 2007888 [Ljava.lang.Object; + 14: 3507 1707048 + 15: 8132 980616 java.lang.Class + 16: 26854 859328 java.util.HashMap$Entry + 17: 12368 699296 [[I + 18: 14135 452320 java.util.concurrent.ConcurrentHashMap$HashEntry + 19: 20883 334128 java.lang.Object + 20: 590 316240 + 21: 1757 305904 [Ljava.util.HashMap$Entry; + 22: 2809 224720 net.sf.ehcache.Element + 23: 1992 223104 java.net.SocksSocketImpl + 24: 2668 213440 java.lang.reflect.Method + 26: 5932 183928 [Ljava.lang.String; + 27: 7588 182112 java.util.concurrent.ConcurrentSkipListMap$Node + 28: 7317 175608 java.lang.Long + 29: 5303 169696 java.util.Hashtable$Entry + 30: 6778 162672 java.util.ArrayList + 31: 3931 157240 java.lang.ref.SoftReference + 32: 2972 118880 java.util.LinkedHashMap$Entry + 33: 1565 112680 org.apache.commons.pool2.impl.DefaultPooledObject + 34: 2817 112680 net.sf.ehcache.store.chm.SelectableConcurrentHashMap$HashEntry + 35: 2243 107664 java.util.HashMap + 36: 2592 103680 java.util.TreeMap$Entry + 37: 3214 102848 java.lang.ref.WeakReference + 38: 1565 100160 redis.clients.jedis.Client + 39: 4155 99720 java.util.LinkedList$Node + 40: 1986 95328 java.net.SocketInputStream + 41: 414 92952 [Ljava.util.concurrent.ConcurrentHashMap$HashEntry; + 42: 2275 91000 java.lang.ref.Finalizer + 43: 1161 83592 java.lang.reflect.Constructor + 44: 757 78728 java.io.ObjectStreamClass + 45: 1587 76176 java.net.SocketOutputStream + 46: 1189 66584 java.beans.MethodDescriptor + 47: 2770 66480 org.apache.commons.pool2.impl.LinkedBlockingDeque$Node + 48: 388 66368 [Ljava.util.Hashtable$Entry; + 49: 1989 63648 java.net.Socket + 50: 749 53928 java.lang.reflect.Field + ... + ... + 2947: 1 16 sun.misc.Launcher + 2948: 1 16 org.codehaus.jackson.map.ser.std.DateSerializer + 2949: 1 16 org.apache.phoenix.schema.types.PDataType$2 + 2950: 1 16 org.springframework.data.redis.connection.convert.StringToRedisClientInfoConverter + Total 3090439 316004152 + +#### 输出结果说明 + + [C is a char[] + [S is a short[] + [I is a int[] + [B is a byte[] + [[I is a int[][] + +上面的输出中[C对象占用Heap这么多,往往跟String有关,String其内部使用final char[]数组来保存数据的 + +constMethodKlass/ methodKlass/ constantPoolKlass/ constantPoolCacheKlass/instanceKlassKlass/ methodDataKlass与Classloader相关,常驻与Perm区。其中最后一行(total行),分别记录了实例总数、程序占用总内存数,本例显示的程序总占用内存约300M + diff --git a/Java_jmeter_server_polling_pressure_test/README.md b/Java_jmeter_server_polling_pressure_test/README.md new file mode 100644 index 0000000..9564116 --- /dev/null +++ b/Java_jmeter_server_polling_pressure_test/README.md @@ -0,0 +1,229 @@ +### jmeter性能测试之windows篇 + +#### 一、jmeter在windows中的安装 + +jmeter就是传说中的绿色软件,无需安装,只需要在官网下载zip包后,解压即可。 + +#### 二、jmeter的运行 + +jmeter需要依托jdk,首先你的系统中得有jdk。在windows下,进入jmeter解压后的主目录(如apache-jmeter-2.13),进入bin目录,双击运行jmeter.bat即可启动jmeter。如果打开时cmd窗口报错,一般是给jmeter分配的空间太小,需要调整一些参数,我们可以在编辑器中打开jmeter.bat,调整两个参数的值,一个是HEAP,一个是NEW。我的jmeter中这两个值配置如下: + + set HEAP=-Xms512m -Xmx512m + set NEW=-XX:NewSize=128m -XX:MaxNewSize=128m + +目前运行正常,如果您的设置和我的设置相同但无法启动,可以再调整一下,这两个参数的设置和你电脑本身的内存大小有关。 + +如果正常运行,会打开jmeter的主界面,如下图: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/2564647465.png) + +#### 三、压测一个普通的get接口 + +下面我们来进行一个普通的get接口压测。接口是我自己写的,部署在自己本地机器,你们是访问不了的^_^。 +接口地址:http://127.0.0.1:5000/api/search/user +请求参数:term=xiaoming&type=1 + +好,下面我们打开jmeter的主界面,先在左侧测试计划上右键添加一个线程组,如下: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/702867952.png) + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/1806796478.png) + +下面对几个重要的地方进行说明: + +1.线程数 +jmeter中的线程数相当于loadrunner中的虚拟用户(vuser),即你想要并发多少用户,我们这里并发10个用户,就是说10个用户同时不断请求这个接口 + +2.Ramp-Up Period +这个参数指你想要多长时间启动这10个线程。我们这边在1秒内启动,如果设置为0,则是瞬间启动10个线程;如果设置为5,则是大约每秒启动两个线程。 + +3.循环次数 +指的是每个线程请求多少次,我们这里设置为10,即每个线程发送10个请求,那么10个线程就会总共发出100个接口请求。如果勾选了永远,则表示一直发送请求。 + +4.Delay Thread creation until needed +该参数勾选表示延迟创建线程,我一般不勾选。 + +5.调度器 +我这里没有勾选,若勾选,则会出现下面的设置文本框: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/3549944133.png) + +这个调度器常用于设置该次压测持续多长时间,比如,若持续时间填写为1800,则表示该次压测持续半小时后就会自动停止。 + +> Tips:如果设置了持续时间,则启动时间和结束时间的设置都将无效。并且如果你一旦勾选调度器,那么上面的循环次数最好勾选为永远。 + +刚才我们添加了线程组,现在让我们右键点击左侧的线程组,添加一个HTTP请求: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/324644707.png) + +这个时候就要我们去设置要压测的url以及参数等等,我们已经设置好了,如下: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/1923593825.png) + +关键位置已经用红框标注出来了,我们的服务器在本地,为127.0.0.1,我们的端口为5000,如果是http默认端口则不用填写。get请求的路径也已经填写进去了,还有我们的两个参数都添加进去了。接下来我们再添加一个查看结果数,以便我们先看下请求的结果是否正确。 + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/3957689577.png) + +Tips:当测试通过,正式进行性能测试时,一定记得把这个查看结果数去掉,好几次,就因为这个查看结果数的存在,把jmeter搞崩了。 + +目前我们左侧的目录树是这样的: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/2376186870.png) + +好了,我们初步设置完了,下面保存一下这个测试计划(jmeter的测试计划后缀是jmx),然后执行一下看看: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/622651851.png) + +没错,执行按钮就是这个绿色小三角。还记得我们刚才添加线程组的时候,起了10个线程,每个线程循环10次,那么查看结果数下面就是100个请求,我们随便点击一个请求看下: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/481954290.png) + +没错,是预期的请求url,响应的json数据也正常。 + +##### 性能测试的目的是什么? +做一次性能测试,我们的目的肯定是要得到某个接口的性能数据,进而评估这个接口是否满足并发要求、时间要求、以及其他要求,如果不满足这些要求,那就要分析这个接口的性能瓶颈在哪,怎样优化等等。常见的性能数据是QPS以及响应时间。 + +QPS:指该接口在1s内能处理多少个请求 +响应时间:该接口响应一次请求需要多少时间。 + +那么问题来了,我们怎么通过jmeter来查看这些数据呢?jmeter默认提供了一些图表,其中信息量最大的可能要数Aggregate Graph,下面我们添加一个看看: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/2914601014.png) + +添加了聚合报告后,我们再跑一轮,看看这聚合报告里面有些什么数据。 + + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/1725278346.png) + +聚合报告的表格中有很多字段,我们一个一个解读一下: + + Samples:表示总共请求了几次接口 + Average:平均响应时间,单位ms + Median:50%的请求的响应时间小于该值 + 90% Line:90%的请求的响应时间小于该值 + 95% Line:95%的请求的响应时间小于该值 + 99% Line:99%的请求的响应时间小于该值 + Min:最小的响应时间 + Max:最大的响应时间 + Error %:错误率 + Throughput:吞吐率,即QPS(TPS) + +> Tips:关于Median、90% Line等的含义,很多同学都有误解,以为是各个百分比请求数下的平均响应时间,其实不然,拿90%Line来说,他指的是90%请求的最大响应时间,即按照响应时间从小到大排序,有90%的请求的响应时间是在该值之下的。 + + +从聚合报告我们可以看出我们想要的QPS和各个响应时间。 + +有同学可能会说,如果我想知道QPS和响应时间在我压测的过程中随着时间的变化曲线,这个需求怎么满足呢? +jmeter默认自带的图表包含的东西并不多,不过我们可以在jmeter的官网上找到这些图表的插件。我们可以下载JMeterPlugins-Extras-1.3.1.zip,解压后在解压包的lib/ext目录得到JMeterPlugins-Extras.jar,把这个jar包复制到你的jmeter的安装目录的相同位置,如:apache-jmeter-2.13\lib\ext\ 目录下,并重启jmeter。 +重启后,我们添加一个QPS图表和一个响应时间图表,如下: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/3732705026.png) + + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/3514939857.png) + +上图的Transactions Per Second就是QPS(TPS),下图就是接口响应时间随时间变化图。 +现在我们左侧的目录树是这样的: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/2414477759.png) + +我们再来进行一轮压测,观察下这两个图表: +先看QPS: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/167337359.png) + +从上图我们可以看到目前我们的接口qps大约在430左右。 + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/2937255878.png) + +从响应时间图我们可以看到响应时间大概是11ms左右。大家可以对照聚合报告中的数据合并这连个图进行对比,看下是否一致。 + +#### 四、设置请求header + +看到这里,您对基本的get接口压测就有一定了解了。有时候,我们的请求是需要带header的,对于这种情况该怎么办呢?jmeter照样提供了相应的方法,我们可以添加一个HTTP信息头管理器,如下: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/1358110718.png) + +在信息头中,我们也可以设置Cookie,比如如下: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/4086235082.png) + +这样的设置就可以满足需要cookie验证的接口。 + +更多的设置就等您来慢慢发掘吧!jmeter已经足够强大。 + +#### 五、压测一个post接口 + +对于post接口的压测,其实是完全一样的,只有在设置HTTP请求的时候,设置为POST方法,并且请求body放入“Body Data”就可以,如下: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/1973724565.png) + +#### 六、参数化 + +以上我们讲到的接口测试,参数都是固定的,那如果我们有一个测试集,要在这次性能测试中对同一个接口发送不同的参数该怎么办呢?这种需求很多,比如要进行一个线上真实流量的的测试,我们需要把nginx日志拿到,并且把这些真实的参数作为压测的输入,就会涉及到参数化。常用的参数化方法大概分为两种,下面分别介绍。 + +我们先看下需求,还是刚才的接口:http://127.0.0.1:5000/api/search/user + +现在我们不传固定的参数,我要从文件读取参数,文件格式如下: + + xiaoming,1 + xiaohong,1 + xiaolong,2 + +也就是说,我们想要每次请求的时候,把当前读取到的行的第一列赋值给term,第二列赋值给type,即每次请求的url如下: + + http://127.0.0.1:5000/api/search/user?term=xiaoming&type=1 + http://127.0.0.1:5000/api/search/user?term=xiaohong&type=1 + http://127.0.0.1:5000/api/search/user?term=xiaolong&type=2 + +当文件的三行取完后,再次从头开始依次取值。 +我们先看第一种参数化方法。 + +##### 1.函数助手 + +在jmeter菜单点击选项->函数助手对话框,弹出如下: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/3214965067.png) + +在标1处,我们选择__CSVRead,然后在标2处写入我们的参数文件的路径:E:\data.txt + +> 此处与格式无关,且每列以半角逗号分隔 + +然后在文件列号中写0(此处为固定值,表示我们的参数文件第一列序号从0开始)。然后在3处点击生成,把4处的函数字符串复制,然后开始在HTTP请求中设置参数,,如下: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/592142899.png) + +上图中的名称从上到下,依次就是参数文件中从左到右的每列,注意到在值的那列,我们的函数从上到下只有序号不同,依次从0往下排。 + +运行一下,并在查看结果树中查看每次请求的是否是不同的参数。 + +##### 2.CSV Data Set Config +这种方法下,我们需要添加一个配置原件,就叫CSV Data Set Config: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/115424753.png) + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/429298423.png) + +每个字段含义如下: + +* Filename:指定文件路径 +* File encoding:UTF-8 + +> Tips:保存参数文件的时候,注意要以utf-8格式保存 + +* Variable Names:设置参数文件中每列的参数名 +* Delimiter:分隔符,这里是半角逗号 +* Recycle on EOF:为True,参数文件取值到结尾时再次从头取 + +设置了CSV Data Set Config后,我们还需要返回HTTP请求,去设置参数: + +![](https://github.com/scalad/Note/blob/master/Java_jmeter_server_polling_pressure_test/image/4183093774.png) + +注意这里使用“$”符号,来取参数文件的每列。 + +> Tips:这里的参数名一定要和CSV Data Set Config中设置的Variable Names相同。 + +设置完这些,就可以检验成果了。 + +经过实测,第一种参数化方法取值有时会发生行错乱。如果要精确取值的话,最好还是采用第二种参数化方法。 + diff --git a/Java_jmeter_server_polling_pressure_test/image/115424753.png b/Java_jmeter_server_polling_pressure_test/image/115424753.png new file mode 100644 index 0000000..7f76bb8 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/115424753.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/1358110718.png b/Java_jmeter_server_polling_pressure_test/image/1358110718.png new file mode 100644 index 0000000..a52d0e5 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/1358110718.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/167337359.png b/Java_jmeter_server_polling_pressure_test/image/167337359.png new file mode 100644 index 0000000..769b5ac Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/167337359.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/1725278346.png b/Java_jmeter_server_polling_pressure_test/image/1725278346.png new file mode 100644 index 0000000..15b97aa Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/1725278346.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/1806796478.png b/Java_jmeter_server_polling_pressure_test/image/1806796478.png new file mode 100644 index 0000000..872d783 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/1806796478.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/1923593825.png b/Java_jmeter_server_polling_pressure_test/image/1923593825.png new file mode 100644 index 0000000..a6a7656 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/1923593825.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/1973724565.png b/Java_jmeter_server_polling_pressure_test/image/1973724565.png new file mode 100644 index 0000000..1873c19 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/1973724565.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/2376186870.png b/Java_jmeter_server_polling_pressure_test/image/2376186870.png new file mode 100644 index 0000000..8b85dc1 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/2376186870.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/2414477759.png b/Java_jmeter_server_polling_pressure_test/image/2414477759.png new file mode 100644 index 0000000..9471feb Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/2414477759.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/2564647465.png b/Java_jmeter_server_polling_pressure_test/image/2564647465.png new file mode 100644 index 0000000..f9846f1 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/2564647465.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/2914601014.png b/Java_jmeter_server_polling_pressure_test/image/2914601014.png new file mode 100644 index 0000000..72cce4d Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/2914601014.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/2937255878.png b/Java_jmeter_server_polling_pressure_test/image/2937255878.png new file mode 100644 index 0000000..ba519e0 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/2937255878.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/3214965067.png b/Java_jmeter_server_polling_pressure_test/image/3214965067.png new file mode 100644 index 0000000..fcbbc3e Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/3214965067.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/324644707.png b/Java_jmeter_server_polling_pressure_test/image/324644707.png new file mode 100644 index 0000000..fab4b5d Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/324644707.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/3514939857.png b/Java_jmeter_server_polling_pressure_test/image/3514939857.png new file mode 100644 index 0000000..f31bd25 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/3514939857.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/3549944133.png b/Java_jmeter_server_polling_pressure_test/image/3549944133.png new file mode 100644 index 0000000..080d066 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/3549944133.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/3732705026.png b/Java_jmeter_server_polling_pressure_test/image/3732705026.png new file mode 100644 index 0000000..653f7af Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/3732705026.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/3957689577.png b/Java_jmeter_server_polling_pressure_test/image/3957689577.png new file mode 100644 index 0000000..d1ad165 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/3957689577.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/4086235082.png b/Java_jmeter_server_polling_pressure_test/image/4086235082.png new file mode 100644 index 0000000..78d6e43 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/4086235082.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/4183093774.png b/Java_jmeter_server_polling_pressure_test/image/4183093774.png new file mode 100644 index 0000000..c90c98e Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/4183093774.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/429298423.png b/Java_jmeter_server_polling_pressure_test/image/429298423.png new file mode 100644 index 0000000..29ec0d6 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/429298423.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/481954290.png b/Java_jmeter_server_polling_pressure_test/image/481954290.png new file mode 100644 index 0000000..dc10b31 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/481954290.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/592142899.png b/Java_jmeter_server_polling_pressure_test/image/592142899.png new file mode 100644 index 0000000..8be73c8 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/592142899.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/622651851.png b/Java_jmeter_server_polling_pressure_test/image/622651851.png new file mode 100644 index 0000000..1e5f7bd Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/622651851.png differ diff --git a/Java_jmeter_server_polling_pressure_test/image/702867952.png b/Java_jmeter_server_polling_pressure_test/image/702867952.png new file mode 100644 index 0000000..4b26282 Binary files /dev/null and b/Java_jmeter_server_polling_pressure_test/image/702867952.png differ diff --git a/Java_jstack/README.md b/Java_jstack/README.md new file mode 100644 index 0000000..014bc5e --- /dev/null +++ b/Java_jstack/README.md @@ -0,0 +1,512 @@ +### JDK自带命令jstack通过JVM堆栈分析出现大量线程的原因 ### +最近服务器内存总是耗光,于是想要分析下问题的原因: + +首先找到JVM进程 + + [root@10-13-40-54 social-search]# ps -aux | grep tomcat + root 7437 5.8 8.0 10049732 1303772 pts/0 Sl 09:31 + 1:12 /usr/bin/java -Djava.util.logging.config.file=/usr/local/5080SocialSearchTomcat/conf/ + logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.endorsed.dirs= + /usr/local/5080SocialSearchTomcat/endorsed -classpath /usr/local/5080SocialSearchTomcat/bin/bootstrap.jar: + /usr/local/5080SocialSearchTomcat/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/5080SocialSearchTomcat + -Dcatalina.home=/usr/local/5080SocialSearchTomcat -Djava.io.tmpdir=/usr/local/5080SocialSearchTomcat/ + temp/org.apache.catalina.startup.Bootstrap start + +得到JVM的tomcat进程是7437,然后把堆栈导出,下载到本地: + + jstack -l 7437 > log.txt + +下载后,发现线程堆栈中,有大量的这样的日志: + + "pool-2918-thread-2" #14663 prio=5 os_prio=0 tid=0x00007fdc81759000 nid=0x157c + waiting on condition [0x00007fd8821ef000] + java.lang.Thread.State: WAITING (parking) + at sun.misc.Unsafe.park(Native Method) + - parking to wait for <0x000000079329da40 + - > - (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067) + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127) + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) + at java.lang.Thread.run(Thread.java:745) + + "pool-2918-thread-1" #14662 prio=5 os_prio=0 tid=0x00007fdc81757000 nid=0x157b + waiting on condition [0x00007fd8822f0000] + java.lang.Thread.State: WAITING (parking) + at sun.misc.Unsafe.park(Native Method) + - parking to wait for <0x000000079329da40> + - (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) + at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1067) + at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127) + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) + at java.lang.Thread.run(Thread.java:745) + +可以看到,线程处于WAITING状态,阻塞在试图从任务队列中取任务(LinkedBlockingQueue.take),这个任务队列指的是ThreadPoolExecutor的线程池启动的线程任务队列; + +也就是说,这些线程都是空闲状态,在等着任务的到来呢! + +补充下LinkedBlockingQueue的知识: + + >并发库中的BlockingQueue是一个比较好玩的类,顾名思义,就是阻塞队列。该类主要提供了两个方法put()和take(), + >前者将一个对象放到队列尾部, + >如果队列已经满了,就等待直到有空闲节点;后者从head取一个对象,如果没有对象,就等待直到有可取的对象。 + +定位到问题就简单了,查找代码,发现有个位置启动了线程池提交了任务,但是任务执行完返回后,线程池没有关闭导致的; + +问题总结: + +1、使用ExecutorService提交的线程任务,也要记得关闭; + +2、启动新线程的时候,最好给线程起个名字,这样线程堆栈的问题排查更加容易; + +### 关于Jstack ### +#### 一、介绍 #### +jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式: + + jstack [-l] pid + +jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。 + +So,jstack命令主要用来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁) + +#### 线程状态 #### +想要通过jstack命令来分析线程的情况的话,首先要知道线程都有哪些状态,下面这些状态是我们使用jstack命令查看线程堆栈信息时可能会看到的线程的几种状态: + +* NEW,未启动的。不会出现在Dump中。 +* RUNNABLE,在虚拟机内执行的。 +* BLOCKED,受阻塞并等待监视器锁。 +* WATING,无限期等待另一个线程执行特定操作。 +* TIMED_WATING,有时限的等待另一个线程的特定操作。 +* TERMINATED,已退出的。 + +#### Monitor #### +在多线程的 JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。下 面这个图,描述了线程和 Monitor之间关系,以 及线程的状态转换图: + +![](https://github.com/scalad/Note/blob/master/Java_jstack/image/thread.png) + +进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁。如果对象未被锁住,则迚入拥有者;否则则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。 + +拥有者(The Owner):表示某一线程成功竞争到对象锁。 + +等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。 + +从图中可以看出,一个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在“Wait Set”中等待的线程状态是 “in Object.wait()”。 先看 “Entry Set”里面的线程。我们称被 synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。对应的 code就像: + +```java + synchronized(obj) { + ... + } +``` + +#### 调用修饰 #### + +* locked <地址> 目标:使用synchronized申请对象锁成功,监视器的拥有者。 +* waiting to lock <地址> 目标:使用synchronized申请对象锁未成功,在迚入区等待。 +* waiting on <地址> 目标:使用synchronized申请对象锁成功后,释放锁幵在等待区等待。 +* parking to wait for <地址> 目标 + +#### 1、locked #### + + at oracle.jdbc.driver.PhysicalConnection.prepareStatement + - locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection) + at oracle.jdbc.driver.PhysicalConnection.prepareStatement + - locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection) + at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement + +通过synchronized关键字,成功获取到了对象的锁,成为监视器的拥有者,在临界区内操作。对象锁是可以线程重入的。 + +#### 2、waiting to lock #### + + at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165) + - waiting to lock <0x0000000097ba9aa8> (a CacheHolder) + at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder + at com.jiuqi.dna.core.impl.ContextImpl.find + at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo + +通过synchronized关键字,没有获取到了对象的锁,线程在监视器的进入区等待。在调用栈顶出现,线程状态为Blocked。 + +#### 3、waiting on #### + at java.lang.Object.wait(Native Method) + - waiting on <0x00000000da2defb0> (a WorkingThread) + at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo + - locked <0x00000000da2defb0> (a WorkingThread) + at com.jiuqi.dna.core.impl.WorkingThread.run + +通过synchronized关键字,成功获取到了对象的锁后,调用了wait方法,进入对象的等待区等待。在调用栈顶出现,线程状态为WAITING或TIMED_WATING。 + +#### 4、parking to wait for #### +park是基本的线程阻塞原语,不通过监视器在对象上阻塞。随concurrent包会出现的新的机制,不synchronized体系不同。 + +#### 线程动作 #### +线程状态产生的原因: + +* runnable:状态一般为RUNNABLE。 +* in Object.wait():等待区等待,状态为WAITING或TIMED_WAITING。 +* waiting for monitor entry:进入区等待,状态为BLOCKED。 +* waiting on condition:等待区等待、被park。 +* sleeping:休眠的线程,调用了Thread.sleep()。 + +Wait on condition 该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stacktrace来分析。 最常见的情况就是线程处于sleep状态,等待被唤醒。 常见的情况还有等待网络IO:在java引入nio之前,对于每个网络连接,都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪费,而且给操作系统的线程调度也带来压力。在 NewIO里采用了新的机制,编写的服务器程序的性能和可扩展性都得到提高。 正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几 乎消耗了所有的带宽,仍然有大量数据等待网络读 写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。所以要结合系统的一些性能观察工具来综合分析,比如 netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制 ; 观察 cpu的利用率,如果系统态的 CPU时间,相对于用户态的 CPU时间比例较高;如果程序运行在 Solaris 10平台上,可以用 dtrace工具看系统调用的情况,如果观察到 read/write的系统调用的次数或者运行时间遥遥领先;这些都指向由于网络带宽所限导致的网络瓶颈。(来自[http://www.blogjava.net/jzone/articles/303979.html](http://www.blogjava.net/jzone/articles/303979.html)) + +### 二、命令格式 ### + + jstack [ option ] pid + jstack [ option ] executable core + jstack [ option ] [server-id@]remote-hostname-or-IP + +常用参数说明 + +1)options: + +executable Java executable from which the core dump was produced.(可能是产生core dump的java可执行程序) + +core 将被打印信息的core dump文件 + +remote-hostname-or-IP 远程debug服务的主机名或ip + +server-id 唯一id,假如一台主机上多个远程debug服务 + +2)基本参数: + +-F 当’jstack [-l] pid’没有相应的时候强制打印栈信息 + +-l 长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表. + +-m 打印java和native c/c++框架的所有栈信息. + +-h | -help打印帮助信息 + +pid 需要被打印配置信息的java进程id,可以用jps查询. + +### 三、使用实例 ### +1、jstack pid + + ~$ jps -ml + org.apache.catalina.startup.Bootstrap + ~$ jstack 5661 + 2013-04-16 21:09:27 + Full thread dump Java HotSpot(TM) Server VM (20.10-b01 mixed mode): + + "Attach Listener" daemon prio=10 tid=0x70e95400 nid=0x2265 waiting on condition [0x00000000] + java.lang.Thread.State: RUNNABLE + + "http-bio-8080-exec-20" daemon prio=10 tid=0x08a35800 nid=0x1d42 waiting on condition [0x70997000] + java.lang.Thread.State: WAITING (parking) + at sun.misc.Unsafe.park(Native Method) + - parking to wait for <0x766a27b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) + at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156) + at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987) + at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:399) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104) + at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32) + at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:947) + at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907) + at java.lang.Thread.run(Thread.java:662) + ........ + +一般情况下,通过jstack输出的线程信息主要包括:jvm自身线程、用户线程等。其中jvm线程会在jvm启动时就会存在。对于用户线程则是在用户访问时才会生成。 + +2、jstack 查看线程具体在做什么,可看出哪些线程在长时间占用CPU,尽快定位问题和解决问题 + +[http://www.iteye.com/topic/1114219](http://www.iteye.com/topic/1114219) + +1.top查找出哪个进程消耗的cpu高。执行top命令,默认是进程视图,其中PID是进程号 + + 21125 co_ad2 18 0 1817m 776m 9712 S 3.3 4.9 12:03.24 java + 5284 co_ad 21 0 3028m 2.5g 9432 S 1.0 16.3 6629:44 ja + +这里我们分析21125这个java进程 + +2.top中shift+h 或“H”查找出哪个线程消耗的cpu高 + +先输入top,然后再按shift+h 或“H”,此时打开的是线程视图,pid为线程号 + + 21233 co_ad2 15 0 1807m 630m 9492 S 1.3 4.0 0:05.12 java + 20503 co_ad2_s 15 0 1360m 560m 9176 S 0.3 3.6 0:46.72 java + +这里我们分析21233这个线程,并且注意的是,这个线程是属于21125这个进程的。 + +3.使用jstack命令输出这一时刻的线程栈,保存到文件,命名为jstack.log。注意:输出线程栈和保存top命令快照尽量同时进行。 + +由于jstack.log文件记录的线程ID是16进制,需要将top命令展示的线程号转换为16进制。 + +4. jstack查找这个线程的信息 +jstack [进程]|grep -A 10 [线程的16进制] + + 即: jstack 21125|grep -A 10 52f1 + +-A 10表示查找到所在行的后10行。21233用计算器转换为16进制52f1,注意字母是小写。 结果: + + "http-8081-11" daemon prio=10 tid=0x00002aab049a1800 nid=0x52bb in Object.wait() [0x0000000042c75000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait(Native Method) + at java.lang.Object.wait(Object.java:485) + at org.apache.tomcat.util.net.JIoEndpoint$Worker.await(JIoEndpoint.java:416) + +在结果中查找52f1,可看到当前线程在做什么。 + +3、代码示例 + +```java +/** + * @author hollis + */ +public class JStackDemo1 { + public static void main(String[] args) { + while (true) { + //Do Nothing + } + } +} +``` + +先是有jps查看进程号: + + hollis@hos:~$ jps + 29788 JStackDemo1 + 29834 Jps + 22385 org.eclipse.equinox.launcher_1.3.0.v20130327-1440.jar + +然后使用jstack 查看堆栈信息: + + hollis@hos:~$ jstack 29788 + 2015-04-17 23:47:31 + ...此处省略若干内容... + "main" prio=10 tid=0x00007f197800a000 nid=0x7462 runnable [0x00007f197f7e1000] + java.lang.Thread.State: RUNNABLE + at javaCommand.JStackDemo1.main(JStackDemo1.java:7) + +我们可以从这段堆栈信息中看出什么来呢?我们可以看到,当前一共有一条用户级别线程,线程处于runnable状态,执行到JStackDemo1.java的第七行。 看下面代码: + +```java +/** + * @author hollis + */ +public class JStackDemo1 { + public static void main(String[] args) { + Thread thread = new Thread(new Thread1()); + thread.start(); + } +} +class Thread1 implements Runnable{ + @Override + public void run() { + while(true){ + System.out.println(1); + } + } +} +``` + +线程堆栈信息如下: + + "Reference Handler" daemon prio=10 tid=0x00007fbbcc06e000 nid=0x286c in Object.wait() [0x00007fbbc8dfc000] + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait(Native Method) + - waiting on <0x0000000783e066e0> (a java.lang.ref.Reference$Lock) + at java.lang.Object.wait(Object.java:503) + at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133) + - locked <0x0000000783e066e0> (a java.lang.ref.Reference$Lock) + +我们能看到: + +> 线程的状态: WAITING 线程的调用栈 线程的当前锁住的资源: <0x0000000783e066e0> 线程当前等待的资源:<0x0000000783e066e0> + +为什么同时锁住的等待同一个资源: +> 线程的执行中,先获得了这个对象的 Monitor(对应于 locked <0x0000000783e066e0>)。当执行到 obj.wait(), 线程即放弃了 Monitor的所有权,进入 “wait set”队列(对应于 waiting on <0x0000000783e066e0> )。 + +### 四、如何分析 ### +#### 1、线程Dump的分析 #### +原则 +> 结合代码阅读的推理。需要线程Dump和源码的相互推导和印证。造成Bug的根源往往丌会在调用栈上直接体现,一定格外注意线程当前调用之前的所有调用。 + +入手点 + +进入区等待 + + "d&a-3588" daemon waiting for monitor entry [0x000000006e5d5000] + java.lang.Thread.State: BLOCKED (on object monitor) + at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle() + - waiting to lock <0x0000000602f38e90> (a java.lang.Object) + at com.jiuqi.dna.bap.authority.service.UserService$LoginHandler.handle() + +线程状态BLOCKED,线程动作wait on monitor entry,调用修饰waiting to lock总是一起出现。表示在代码级别已经存在冲突的调用。必然有问题的代码,需要尽可能减少其发生。 + +同步块阻塞 + +一个线程锁住某对象,大量其他线程在该对象上等待。 + + "blocker" runnable + java.lang.Thread.State: RUNNABLE + at com.jiuqi.hcl.javadump.Blocker$1.run(Blocker.java:23) + - locked <0x00000000eb8eff68> (a java.lang.Object) + "blockee-11" waiting for monitor entry + java.lang.Thread.State: BLOCKED (on object monitor) + at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41) + - waiting to lock <0x00000000eb8eff68> (a java.lang.Object) + "blockee-86" waiting for monitor entry + java.lang.Thread.State: BLOCKED (on object monitor) + at com.jiuqi.hcl.javadump.Blocker$2.run(Blocker.java:41) + - waiting to lock <0x00000000eb8eff68> (a java.lang.Object) + +持续运行的IO IO操作是可以以RUNNABLE状态达成阻塞。例如:数据库死锁、网络读写。 格外注意对IO线程的真实状态的分析。 一般来说,被捕捉到RUNNABLE的IO调用,都是有问题的。 + +以下堆栈显示: 线程状态为RUNNABLE。 调用栈在SocketInputStream或SocketImpl上,socketRead0等方法。 调用栈包含了jdbc相关的包。很可能发生了数据库死锁 + + "d&a-614" daemon prio=6 tid=0x0000000022f1f000 nid=0x37c8 runnable + [0x0000000027cbd000] + java.lang.Thread.State: RUNNABLE + at java.net.SocketInputStream.socketRead0(Native Method) + at java.net.SocketInputStream.read(Unknown Source) + at oracle.net.ns.Packet.receive(Packet.java:240) + at oracle.net.ns.DataPacket.receive(DataPacket.java:92) + at oracle.net.ns.NetInputStream.getNextPacket(NetInputStream.java:172) + at oracle.net.ns.NetInputStream.read(NetInputStream.java:117) + at oracle.jdbc.driver.T4CMAREngine.unmarshalUB1(T4CMAREngine.java:1034) + at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:588) + +分线程调度的休眠 + +正常的线程池等待 + + "d&a-131" in Object.wait() + java.lang.Thread.State: TIMED_WAITING (on object monitor) + at java.lang.Object.wait(Native Method) + at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo(WorkingManager.java:322) + - locked <0x0000000313f656f8> (a com.jiuqi.dna.core.impl.WorkingThread) + at com.jiuqi.dna.core.impl.WorkingThread.run(WorkingThread.java:40) + +可疑的线程等待 + + "d&a-121" in Object.wait() + java.lang.Thread.State: WAITING (on object monitor) + at java.lang.Object.wait(Native Method) + at java.lang.Object.wait(Object.java:485) + at com.jiuqi.dna.core.impl.AcquirableAccessor.exclusive() + - locked <0x00000003011678d8> (a com.jiuqi.dna.core.impl.CacheGroup) + at com.jiuqi.dna.core.impl.Transaction.lock() + +入手点总结 + +wait on monitor entry: 被阻塞的,肯定有问题 + +runnable : 注意IO线程 + +in Object.wait(): 注意非线程池等待 + +#### 2、死锁分析 #### +的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。 说白了,我现在想吃鸡蛋灌饼,桌子上放着鸡蛋和饼,但是我和我的朋友同时分别拿起了鸡蛋和病,我手里拿着鸡蛋,但是我需要他手里的饼。他手里拿着饼,但是他想要我手里的鸡蛋。就这样,如果不能同时拿到鸡蛋和饼,那我们就不能继续做后面的工作(做鸡蛋灌饼)。所以,这就造成了死锁。 看一段死锁的程序: + +```java +package javaCommand; +/** + * @author hollis + */ +public class JStackDemo { + public static void main(String[] args) { + Thread t1 = new Thread(new DeadLockclass(true));//建立一个线程 + Thread t2 = new Thread(new DeadLockclass(false));//建立另一个线程 + t1.start();//启动一个线程 + t2.start();//启动另一个线程 + } +} +class DeadLockclass implements Runnable { + public boolean falg;// 控制线程 + DeadLockclass(boolean falg) { + this.falg = falg; + } + public void run() { + /** + * 如果falg的值为true则调用t1线程 + */ + if (falg) { + while (true) { + synchronized (Suo.o1) { + System.out.println("o1 " + Thread.currentThread().getName()); + synchronized (Suo.o2) { + System.out.println("o2 " + Thread.currentThread().getName()); + } + } + } + } + /** + * 如果falg的值为false则调用t2线程 + */ + else { + while (true) { + synchronized (Suo.o2) { + System.out.println("o2 " + Thread.currentThread().getName()); + synchronized (Suo.o1) { + System.out.println("o1 " + Thread.currentThread().getName()); + } + } + } + } + } +} + +class Suo { + static Object o1 = new Object(); + static Object o2 = new Object(); +} +``` + +当我启动该程序时,我们发现,程序只输出了两行内容,然后程序就不再打印其它的东西了,但是程序并没有停止。这样就产生了死锁。 当线程1使用synchronized锁住了o1的同时,线程2也是用synchronized锁住了o2。当两个线程都执行完第一个打印任务的时候,线程1想锁住o2,线程2想锁住o1。但是,线程1当前锁着o1,线程2锁着o2。所以两个想成都无法继续执行下去,就造成了死锁。 + +然后,我们使用jstack来看一下线程堆栈信息: + + Found one Java-level deadlock: + ============================= + "Thread-1": + waiting to lock monitor 0x00007f0134003ae8 (object 0x00000007d6aa2c98, a java.lang.Object), + which is held by "Thread-0" + "Thread-0": + waiting to lock monitor 0x00007f0134006168 (object 0x00000007d6aa2ca8, a java.lang.Object), + which is held by "Thread-1" + + Java stack information for the threads listed above: + =================================================== + "Thread-1": + at javaCommand.DeadLockclass.run(JStackDemo.java:40) + - waiting to lock <0x00000007d6aa2c98> (a java.lang.Object) + - locked <0x00000007d6aa2ca8> (a java.lang.Object) + at java.lang.Thread.run(Thread.java:745) + "Thread-0": + at javaCommand.DeadLockclass.run(JStackDemo.java:27) + - waiting to lock <0x00000007d6aa2ca8> (a java.lang.Object) + - locked <0x00000007d6aa2c98> (a java.lang.Object) + at java.lang.Thread.run(Thread.java:745) + + Found 1 deadlock. + +哈哈,堆栈写的很明显,它告诉我们 Found one Java-level deadlock,然后指出造成死锁的两个线程的内容。然后,又通过 Java stack information for the threads listed above来显示更详细的死锁的信息。 他说: +> Thread-1在想要执行第40行的时候,当前锁住了资源<0x00000007d6aa2ca8>,但是他在等待资源<0x00000007d6aa2c98>Thread-0在想要执行第27行的时候,当前锁住了资源<0x00000007d6aa2c98>,但是他在等待资源<0x00000007d6aa2ca8> 由于这两个线程都持有资源,并且都需要对方的资源,所以造成了死锁。 原因我们找到了,就可以具体问题具体分析,解决这个死锁了。 + +#### 其他 #### + +虚拟机执行Full GC时,会阻塞所有的用户线程。因此,即时获取到同步锁的线程也有可能被阻塞。 在查看线程Dump时,首先查看内存使用情况。 + +#### 频繁GC问题或内存溢出问题 #### + +一、使用jps查看线程ID + +二、使用jstat -gc 3331 250 20 查看gc情况,一般比较关注PERM区的情况,查看GC的增长情况。 + +三、使用jstat -gccause:额外输出上次GC原因 + +四、使用jmap -dump:format=b,file=heapDump 3331生成堆转储文件 + +五、使用jhat或者可视化工具(Eclipse Memory Analyzer 、IBM HeapAnalyzer)分析堆情况。 + +六、结合代码解决内存溢出或泄露问题。 + +#### 死锁问题 #### + +一、使用jps查看线程ID + +二、使用jstack 3331:查看线程情况 \ No newline at end of file diff --git a/Java_jstack/image/thread.png b/Java_jstack/image/thread.png new file mode 100644 index 0000000..83b8149 Binary files /dev/null and b/Java_jstack/image/thread.png differ diff --git a/Linux_Copy_On_Write/README.md b/Linux_Copy_On_Write/README.md index 500f401..2879859 100644 --- a/Linux_Copy_On_Write/README.md +++ b/Linux_Copy_On_Write/README.md @@ -1,4 +1,4 @@ -###Linux写时拷贝技术(copy-on-write) +### Linux写时拷贝技术(copy-on-write) 源于网上资料 **COW技术初窥:** diff --git a/Linux_Hydra/README.md b/Linux_Hydra/README.md index a051eef..7272376 100644 --- a/Linux_Hydra/README.md +++ b/Linux_Hydra/README.md @@ -1,4 +1,4 @@ -###在线破解工具Hydra爆破3389服务器 +### 在线破解工具Hydra爆破3389服务器 hydra是一款全能的暴力破解工具,功能强大,几乎支持所有的协议,是著名黑客组织thc开发的。 在Kali Linux下已经是默认安装的,于是测试爆破一下自己的一台VM虚拟机服务器。hydra还支持GUI图形界面(xhydra),不过习惯还是命令好用。 @@ -38,7 +38,7 @@ hydra是一款全能的暴力破解工具,功能强大,几乎支持所有的 teamspeak sip vmauthd firebird ncp afp等等。 # -####爆破过程: +#### 爆破过程: ![](https://github.com/silence940109/Java/blob/master/Linux_Hydra/image/777998-20160129150436380-748025426.jpg) 其中命令:hydra 192.168.1.12 rdp -L users.txt -P pass.txt -V diff --git a/Linux_Pip/README.md b/Linux_Pip/README.md new file mode 100644 index 0000000..2aa6d7f --- /dev/null +++ b/Linux_Pip/README.md @@ -0,0 +1,85 @@ +### Linux安装Python包管理工具——Pip + +pip 是一个常用的Python包管理工具,主要是用于安装 PyPI 上的软件包,可以替代 easy_install 工具。 + +* GitHub: https://github.com/pypa/pip + +* Doc: https://pip.pypa.io/en/latest/ + +在Centos 7中安装Python包管理工具—Pip时,使用 sudo yum install python-pip 时无法安装,是由于Centos发行版的源内容更新的比较滞后,导致找不到 python-pip 这个包管理工具。 + +这里解决的是无法安装pip 时问题,或者说是如何在Centos 7中安装Python包管理工具Pip 。 + +#### 一、脚本安装pip + +首先下载get-pip.py脚本,然后运行即可安装: + + $ curl -O https://bootstrap.pypa.io/get-pip.py + + $ python get-pip.py + +### 二、使用包管理软件安装 + +1、首先安装epel扩展源: + + $ sudo yum -y install epel-release + + +2、然后安装python-pip + + $ sudo yum -y install python-pip + +#### 三、其他linux中安装 Pip +1、Debian/Ubuntu + + sudo apt-get install python-pip + +2、Fedora + +* Fedora 21: + + Python 2: + + sudo yum upgrade python-setuptools + sudo yum install python-pip python-wheel + + Python 3: + + sudo yum install python3 python3-wheel + +* Fedora 22: + + Python 2: + + sudo dnf upgrade python-setuptools + sudo dnf install python-pip python-wheel + + Python 3: + + sudo dnf install python3 python3-wheel + +3、openSUSE + + Python 2: + + sudo zypper install python-pip python-setuptools python-wheel + + Python 3: + + sudo zypper install python3-pip python3-setuptools python3-wheel + + +4、Arch Linux + + Python 2: + + sudo pacman -S python2-pip + + Python 3: + + sudo pacman -S python-pip + + +参考文档: + +Installing pip/setuptools/wheel with Linux Package Managers \ No newline at end of file diff --git a/Linux_Redis_Make/README.md b/Linux_Redis_Make/README.md index 95d79ee..94778a2 100644 --- a/Linux_Redis_Make/README.md +++ b/Linux_Redis_Make/README.md @@ -1,7 +1,7 @@ -###Linux Redis Make and Install +### Linux Redis Make and Install redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统。和Memcached类似,但很大程度补偿了memcached的不足,它支持存储的value类型相对更多,包括string、list、set、zset和hash。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作。在此基础上,redis支持各种不同方式的排序。Redis数据都是缓存在计算机内存中,并且会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。 - -####下载编译 + +#### 下载编译 从[http://download.redis.io/releases/](http://download.redis.io/releases/)下载相应的版本 wget http://download.redis.io/releases/redis-3.0.5.tar.gz @@ -11,7 +11,7 @@ redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系 ![](https://github.com/silence940109/Java/blob/master/Linux_Redis_Make/image/redis_make.png) -####拷贝文件 +#### 拷贝文件 编译完成后,在Src目录下,有四个可执行文件redis-server、redis-benchmark、redis-cli,在上一级上找到redis.conf,拷贝到新建的目录下 ![](https://github.com/silence940109/Java/blob/master/Linux_Redis_Make/image/redis.png) diff --git a/Linux_Shell_GetAndPost/README.md b/Linux_Shell_GetAndPost/README.md new file mode 100644 index 0000000..5ad16d1 --- /dev/null +++ b/Linux_Shell_GetAndPost/README.md @@ -0,0 +1,26 @@ +### Linux系统模拟Http的get或post请求 + +Http请求指的是客户端向服务器的请求消息,Http请求主要分为get或post两种,在Linux系统下可以用curl和wget命令来模拟Http的请求。下面就来介绍一下Linux系统如何模拟Http的get或post请求。 + +#### 一、get请求: + + 1、使用curl命令: + + curl “http://www.baidu.com” 如果这里的URL指向的是一个文件或者一幅图都可以直接下载到本地 + curl -i “http://www.baidu.com” 显示全部信息 + curl -l “http://www.baidu.com” 只显示头部信息 + curl -v “http://www.baidu.com” 显示get请求全过程解析 + + 2、使用wget命令: + wget “http://www.baidu.com”也可以 + +#### 二、post请求 + + 1、使用curl命令(通过-d参数,把访问参数放在里面): + + curl -d “param1=value1¶m2=value2” “http://www.baidu.com” + + 2、使用wget命令:(--post-data参数来实现) + wget --post-data ‘user=foo&password=bar’ http://www.baidu.com + +以上就是Linux模拟Http的get或post请求的方法了,这样一来Linux系统也能向远程服务器发送消息了。 \ No newline at end of file diff --git a/Linux_Vim/README.md b/Linux_Vim/README.md index a49e7ed..76fbdd2 100644 --- a/Linux_Vim/README.md +++ b/Linux_Vim/README.md @@ -1,4 +1,4 @@ -####VI查找和替换 +#### VI查找和替换 Linux VI提供了文件中字符串的查找和全局替换的方法。在命令模式下输入/或?可进入查找模式/输入“/searchstring”,然后回车,VI光标从光标位置开始出现第一次出现的地方。输入n跳到该串的下一个出现处,输入N跳到该串上一次出现的位置。 在替换时,可指定替换的范围(1,n),当n为$时指定为最后一行。s是替换命令,g代表全部替换。 diff --git a/Linux_XShell_File_Upload/README.md b/Linux_XShell_File_Upload/README.md new file mode 100644 index 0000000..018f037 --- /dev/null +++ b/Linux_XShell_File_Upload/README.md @@ -0,0 +1,48 @@ +### Xshell实现Windows上传文件到Linux主机 +经常有这样的需求,我们在Windows下载的软件包,如何上传到远程Linux主机上?还有如何从Linux主机下载软件包到Windows下;之前我的做法现在看来好笨好繁琐,不过也达到了目的,笨人有本方法嘛; + +我是怎么操作的: + +1、打开一台本地Linux虚拟机,使用mount 挂载Windows的共享文件夹到Linux上,然后拷贝数据到Linux虚拟机里面;(经常第一步都不顺利,无法挂载Windows的文件夹) + +2、在本地Linux虚拟机使用rsync同步拷贝的数据到远程Linux主机上,需要双方都要安装rsync包、openssh-clients包;遇到大一点的文件拷贝很费时间; + +3、还有一种方法就是直接使用wget直接下载,提前是有下载的网址;大部分都是下载到Windows本地然后上传到远程Linux主机; + +下面介绍一个简单的方法,方便上传Windows的文件到Linux上,也可以从Linux下载到Windows本地; + +1、使用我们常用的Xshell登录工具,新建立一个远程会话,填写ip地址及用户名密码后,选择最下面的ZMODEM,填写下载的路径,加载的路径;2个路径可以一样也可以不一样; + +![](https://github.com/silence940109/Java/blob/master/Linux_XShell_File_Upload/image/1.jpg) + +2、在Linux主机上,安装上传下载工具包rz及sz + +如果不知道你要安装包的具体名称,可以使用yum provides */name 进行查找系统自带软件包的信息; + +[root@localhost src]# yum provides */rz + +lrzsz-0.12.20-27.1.el6.i686 : The lrz and lsz modem communications programs + +Repo : base + +Filename : /usr/bin/rz + +一般会列出软件包的名称及版本,还有安装路径;查询到软件包名后,使用yum install -y 包名 进行安装。 + +lrzsz包安装完成后包括上传rz、下载sz命令;只需要安装这个包即可。 + +[root@localhost src]# yum install -y lrzsz + +3、从Windows上传文件,上传命令为rz;在Linux命令行下输入rz,上传的文件在当前命令行的目录下; + +[root@localhost src]# rz + +输入rz命令后,会弹出对话框,选择你要上传的文件,选择打开就上传到Linux主机。上传完可以使用ls 查看; + +![](https://github.com/silence940109/Java/blob/master/Linux_XShell_File_Upload/image/2.jpg) + +4、从Linux主机下载文件,下载命令为sz ,后面跟要下载的文件名;可以选择下载的保存文件夹; + +[root@localhost src]# sz nginx-1.6.2.tar.gz + +![](https://github.com/silence940109/Java/blob/master/Linux_XShell_File_Upload/image/3.jpg) diff --git a/Linux_XShell_File_Upload/image/1.jpg b/Linux_XShell_File_Upload/image/1.jpg new file mode 100644 index 0000000..34f019d Binary files /dev/null and b/Linux_XShell_File_Upload/image/1.jpg differ diff --git a/Linux_XShell_File_Upload/image/2.jpg b/Linux_XShell_File_Upload/image/2.jpg new file mode 100644 index 0000000..86dbea8 Binary files /dev/null and b/Linux_XShell_File_Upload/image/2.jpg differ diff --git a/Linux_XShell_File_Upload/image/3.jpg b/Linux_XShell_File_Upload/image/3.jpg new file mode 100644 index 0000000..91adb26 Binary files /dev/null and b/Linux_XShell_File_Upload/image/3.jpg differ diff --git a/Lucene_Introduce/README.md b/Lucene_Introduce/README.md index 7d60a1d..7a9106f 100644 --- a/Lucene_Introduce/README.md +++ b/Lucene_Introduce/README.md @@ -1,5 +1,5 @@ -##Lucene -###Lucene 简介 +## Lucene +### Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能。Lucene 目前是 Apache Jakarta 家族中的一个开源项目。也是目前最为流行的基于 Java 开源全文检索工具包。 目前已经有很多应用程序的搜索功能是基于 Lucene 的,比如 Eclipse 的帮助系统的搜索功能。Lucene 能够为文本类型的数据建立索引,所以你只要能把你要索引的数据格式转化的文本的,Lucene 就能对你的文档进行索引和搜索。比如你要对一些 HTML 文档,PDF 文档进行索引的话你就首先需要把 HTML 文档和 PDF 文档转化成文本格式的,然后将转化后的内容交给 Lucene 进行索引,然后把创建好的索引文件保存到磁盘或者内存中,最后根据用户输入的查询条件在索引文件上进行查询。不指定要索引的文档的格式也使 Lucene 能够几乎适用于所有的搜索应用程序。 @@ -8,12 +8,12 @@ Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完 ![](https://github.com/silence940109/Java/blob/master/Lucene_Introduce/image/fig001.jpg) -###索引和搜索 +### 索引和搜索 索引是现代搜索引擎的核心,建立索引的过程就是把源数据处理成非常方便查询的索引文件的过程。为什么索引这么重要呢,试想你现在要在大量的文档中搜索含有某个关键词的文档,那么如果不建立索引的话你就需要把这些文档顺序的读入内存,然后检查这个文章中是不是含有要查找的关键词,这样的话就会耗费非常多的时间,想想搜索引擎可是在毫秒级的时间内查找出要搜索的结果的。这就是由于建立了索引的原因,你可以把索引想象成这样一种数据结构,他能够使你快速的随机访问存储在索引中的关键词,进而找到该关键词所关联的文档。Lucene 采用的是一种称为反向索引(inverted index)的机制。反向索引就是说我们维护了一个词 / 短语表,对于这个表中的每个词 / 短语,都有一个链表描述了有哪些文档包含了这个词 / 短语。这样在用户输入查询条件的时候,就能非常快的得到搜索结果。我们将在本系列文章的第二部分详细介绍 Lucene 的索引机制,由于 Lucene 提供了简单易用的 API,所以即使读者刚开始对全文本进行索引的机制并不太了解,也可以非常容易的使用 Lucene 对你的文档实现索引。 对文档建立好索引后,就可以在这些索引上面进行搜索了。搜索引擎首先会对搜索的关键词进行解析,然后再在建立好的索引上面进行查找,最终返回和用户输入的关键词相关联的文档。 -###Lucene 软件包分析 +### Lucene 软件包分析 Lucene 软件包的发布形式是一个 JAR 文件,下面我们分析一下这个 JAR 文件里面的主要的 JAVA 包,使读者对之有个初步的了解。 @@ -26,10 +26,10 @@ Lucene 软件包的发布形式是一个 JAR 文件,下面我们分析一下 * Package: org.apache.lucene.search 这个包提供了对在建立好的索引上进行搜索所需要的类。比如 IndexSearcher 和 Hits, IndexSearcher 定义了在指定的索引上进行搜索的方法,Hits 用来保存搜索得到的结果。 -###一个简单的搜索应用程序 +### 一个简单的搜索应用程序 假设我们的电脑的目录中含有很多文本文档,我们需要查找哪些文档含有某个关键词。为了实现这种功能,我们首先利用 Lucene 对这个目录中的文档建立索引,然后在建立好的索引中搜索我们所要查找的文档。通过这个例子读者会对如何利用 Lucene 构建自己的搜索应用程序有个比较清楚的认识。 -###建立索引 +### 建立索引 为了对文档进行索引,Lucene 提供了五个基础的类,他们分别是 Document, Field, IndexWriter, Analyzer, Directory。下面我们分别介绍一下这五个类的用途: * Document @@ -87,7 +87,7 @@ IndexWriter 是 Lucene 用来创建索引的一个核心的类,他的作用是 在清单 1 中,我们注意到类 IndexWriter 的构造函数需要三个参数,第一个参数指定了所创建的索引要存放的位置,他可以是一个 File 对象,也可以是一个 FSDirectory 对象或者 RAMDirectory 对象。第二个参数指定了 Analyzer 类的一个实现,也就是指定这个索引是用哪个分词器对文挡内容进行分词。第三个参数是一个布尔型的变量,如果为 true 的话就代表创建一个新的索引,为 false 的话就代表在原来索引的基础上进行操作。接着程序遍历了目录下面的所有文本文档,并为每一个文本文档创建了一个 Document 对象。然后把文本文档的两个属性:路径和内容加入到了两个 Field 对象中,接着在把这两个 Field 对象加入到 Document 对象中,最后把这个文档用 IndexWriter 类的 add 方法加入到索引中去。这样我们便完成了索引的创建。接下来我们进入在建立好的索引上进行搜索的部分。 -###搜索文档 +### 搜索文档 利用 Lucene 进行搜索就像建立索引一样也是非常方便的。在上面一部分中,我们已经为一个目录下的文本文档建立好了索引,现在我们就要在这个索引上进行搜索以找到包含某个关键词或短语的文档。Lucene 提供了几个基础的类来完成这个过程,它们分别是呢 IndexSearcher, Term, Query, TermQuery, Hits. 下面我们分别介绍这几个类的功能。 * Query diff --git a/Maven_Package_Jar/README.md b/Maven_Package_Jar/README.md new file mode 100644 index 0000000..f04973e --- /dev/null +++ b/Maven_Package_Jar/README.md @@ -0,0 +1,37 @@ +### Maven中把依赖的JAR包一起打包 + +这里所用到的MAVEN-PLUGIN是MAVNE-ASSEMBLY-PLUGIN +官方网站是:[http://maven.apache.org/plugins/maven-assembly-plugin/usage.html](http://maven.apache.org/plugins/maven-assembly-plugin/usage.html) + +1. 添加此PLUGIN到项目的POM.XML中 +```java + + + + maven-assembly-plugin + + + + com.allen.capturewebdata.Main + + + + jar-with-dependencies + + + + + +``` + +如果出现CLASS重名的情况,这时候就要把最新的版本号添加进去即可. + +2, 在当前项目下执行mvn assembly:assembly, 执行成功后会在target文件夹下多出一个以-jar-with-dependencies结尾的JAR包. 这个JAR包就包含了项目所依赖的所有JAR的CLASS. + +3.如果不希望依赖的JAR包变成CLASS的话,可以修改ASSEMBLY插件. + +3.1 找到assembly在本地的地址,一般是c:/users/${your_login_name}/.m2/\org\apache\maven\plugins\maven-assembly-plugin\2.4 + +3.2 用WINZIP或解压工具打开此目录下的maven-assembly-plugin-2.4.jar, 找到assemblies\jar-with-dependencies.xml + +3.3 把里面的UNPACK改成FALSE即可 \ No newline at end of file diff --git a/Message_Queue/README.md b/Message_Queue/README.md new file mode 100644 index 0000000..2e9ddc6 --- /dev/null +++ b/Message_Queue/README.md @@ -0,0 +1,225 @@ +### 关于消息队列的使用 + +#### 一、消息队列概述 +消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ + +#### 二、消息队列应用场景 +以下介绍消息队列在实际应用中常用的使用场景。异步处理,应用解耦,流量削锋和消息通讯四个场景。 + +##### 2.1异步处理 +景说明:用户注册后,需要发注册邮件和注册短信。传统的做法有两种 1.串行的方式;2.并行方式 + +a、串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端。 + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/1.png) + +b、并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间 + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/2.png) + +假设三个业务节点每个使用50毫秒钟,不考虑网络等其他开销,则串行方式的时间是150毫秒,并行的时间可能是100毫秒。 + +因为CPU在单位时间内处理的请求数是一定的,假设CPU1秒内吞吐量是100次。则串行方式1秒内CPU可处理的请求量是7次(1000/150)。并行方式处理的请求量是10次(1000/100) + +小结:如以上案例描述,传统的方式系统的性能(并发量,吞吐量,响应时间)会有瓶颈。如何解决这个问题呢? + +引入消息队列,将不是必须的业务逻辑,异步处理。改造后的架构如下: + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/3.png) + +按照以上约定,用户的响应时间相当于是注册信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍。 + +##### 2.2应用解耦 +场景说明:用户下单后,订单系统需要通知库存系统。传统的做法是,订单系统调用库存系统的接口。如下图: + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/4.png) + +传统模式的缺点:假如库存系统无法访问,则订单减库存将失败,从而导致订单失败,订单系统与库存系统耦合 + +如何解决以上问题呢?引入应用消息队列后的方案,如下图: + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/5.png) + +订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功 + +库存系统:订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操作 + +假如:在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦 + +##### 2.3流量削锋 + +流量削锋也是消息队列中的常用场景,一般在秒杀或团抢活动中使用广泛。 + +应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。 + +* a、可以控制活动的人数 + +* b、可以缓解短时间内高流量压垮应用 + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/6.png) + +用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。 +秒杀业务根据消息队列中的请求信息,再做后续处理 + +##### 2.4日志处理 +日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。架构简化如下 + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/7.png) + +日志采集客户端,负责日志数据采集,定时写受写入Kafka队列 + +Kafka消息队列,负责日志数据的接收,存储和转发 + +日志处理应用:订阅并消费kafka队列中的日志数据 + +##### 2.5消息通讯 +消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点消息队列,或者聊天室等 + +点对点通讯: + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/8.png) + +客户端A和客户端B使用同一队列,进行消息通讯。 + +聊天室通讯: + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/9.png) + +客户端A,客户端B,客户端N订阅同一主题,进行消息发布和接收。实现类似聊天室效果。 + +以上实际是消息队列的两种消息模式,点对点或发布订阅模式。模型为示意图,供参考。 + +#### 三、消息中间件示例 +##### 3.1电商系统 + +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/10.png) + +消息队列采用高可用,可持久化的消息中间件。比如Active MQ,Rabbit MQ,Rocket Mq。 + +(1)应用将主干逻辑处理完成后,写入消息队列。消息发送是否成功可以开启消息的确认模式。(消息队列返回消息接收成功状态后,应用再返回,这样保障消息的完整性) + +(2)扩展流程(发短信,配送处理)订阅队列消息。采用推或拉的方式获取消息并处理。 + +(3)消息将应用解耦的同时,带来了数据一致性问题,可以采用最终一致性方式解决。比如主数据写入数据库,扩展应用根据消息队列,并结合数据库方式实现基于消息队列的后续处理。 + +##### 3.2日志收集系统 +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/11.png) + +### 四、JMS消息服务 +讲消息队列就不得不提JMS 。JMS(JAVA Message Service,java消息服务)API是一个消息服务的标准/规范,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。 + +在EJB架构中,有消息bean可以无缝的与JM消息服务集成。在J2EE架构模式中,有消息服务者模式,用于实现消息与应用直接的解耦。 + +##### 4.1消息模型 +在JMS标准中,有两种消息模型P2P(Point to Point),Publish/Subscribe(Pub/Sub)。 + +##### 4.1.1 P2P模式 +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/12.png) + +P2P模式包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。 + +P2P的特点 +每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中) + +发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列 + +接收者在成功接收消息之后需向队列应答成功 + +如果希望发送的每个消息都会被成功处理的话,那么需要P2P模式。 + +##### 4.1.2 Pub/Sub模式 +![](https://github.com/silence940109/Java/blob/master/Message_Queue/image/13.png) + +包含三个角色主题(Topic),发布者(Publisher),订阅者(Subscriber) 多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。 + +Pub/Sub的特点 + +每个消息可以有多个消费者 + +发布者和订阅者之间有时间上的依赖性。针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息 + +为了消费消息,订阅者必须保持运行的状态 + +为了缓和这样严格的时间相关性,JMS允许订阅者创建一个可持久化的订阅。这样,即使订阅者没有被激活(运行),它也能接收到发布者的消息。 + +如果希望发送的消息可以不被做任何处理、或者只被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用Pub/Sub模型。 + +##### 4.2消息消费 +在JMS中,消息的产生和消费都是异步的。对于消费来说,JMS的消息者可以通过两种方式来消费消息。 + +(1)同步 + +订阅者或接收者通过receive方法来接收消息,receive方法在接收到消息之前(或超时之前)将一直阻塞; + +(2)异步 + +订阅者或接收者可以注册为一个消息监听器。当消息到达之后,系统自动调用监听器的onMessage方法。 + +JNDI:Java命名和目录接口,是一种标准的Java命名系统接口。可以在网络上查找和访问服务。通过指定一个资源名称,该名称对应于数据库或命名服务中的一个记录,同时返回资源连接建立所必须的信息。 +JNDI在JMS中起到查找和访问发送目标或消息来源的作用。 + +#### 五、常用消息队列 +一般商用的容器,比如WebLogic,JBoss,都支持JMS标准,开发上很方便。但免费的比如Tomcat,Jetty等则需要使用第三方的消息中间件。本部 +分内容介绍常用的消息中间件(Active MQ,Rabbit MQ,Zero MQ,Kafka)以及他们的特点。 +##### 5.1 ActiveMQ +ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。 + +ActiveMQ特性如下: + +⒈ 多种语言和协议编写客户端。语言: Java,C,C++,C#,Ruby,Perl,Python,PHP。应用协议: OpenWire,Stomp REST,WS Notification,XMPP,AMQP + +⒉ 完全支持JMS1.1和J2EE 1.4规范 (持久化,XA消息,事务) + +⒊ 对Spring的支持,ActiveMQ可以很容易内嵌到使用Spring的系统里面去,而且也支持Spring2.0的特性 + +⒋ 通过了常见J2EE服务器(如 Geronimo,JBoss 4,GlassFish,WebLogic)的测试,其中通过JCA 1.5 resource adaptors的配置,可以让ActiveMQ可以自动的部署到任何兼容J2EE 1.4 商业服务器上 + +⒌ 支持多种传送协议:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA + +⒍ 支持通过JDBC和journal提供高速的消息持久化 + +⒎ 从设计上保证了高性能的集群,客户端-服务器,点对点 + +⒏ 支持Ajax + +⒐ 支持与Axis的整合 + +⒑ 可以很容易得调用内嵌JMS provider,进行测试 + +##### 5.2 Kafka + +Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群机来提供实时的消费。 + +Kafka是一种高吞吐量的分布式发布订阅消息系统,有如下特性: + +通过O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。(文件追加的方式写入数据,过期的数据定期删除) + +支持通过Kafka服务器和消费机集群来分区消息 + +支持Hadoop并行数据加载 + +Kafka相关概念 + +Broker + +Kafka集群包含一个或多个服务器,这种服务器被称为broker[5] + +Topic +每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处) + +Partition + +Parition是物理上的概念,每个Topic包含一个或多个Partition. + +Producer + +负责发布消息到Kafka broker + +Consumer + +消息消费者,向Kafka broker读取消息的客户端。 + +Consumer Group + +每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。 +一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用。 \ No newline at end of file diff --git a/Message_Queue/image/1.png b/Message_Queue/image/1.png new file mode 100644 index 0000000..020ca0a Binary files /dev/null and b/Message_Queue/image/1.png differ diff --git a/Message_Queue/image/10.png b/Message_Queue/image/10.png new file mode 100644 index 0000000..7b72072 Binary files /dev/null and b/Message_Queue/image/10.png differ diff --git a/Message_Queue/image/11.png b/Message_Queue/image/11.png new file mode 100644 index 0000000..81bff7a Binary files /dev/null and b/Message_Queue/image/11.png differ diff --git a/Message_Queue/image/12.png b/Message_Queue/image/12.png new file mode 100644 index 0000000..5b85741 Binary files /dev/null and b/Message_Queue/image/12.png differ diff --git a/Message_Queue/image/13.png b/Message_Queue/image/13.png new file mode 100644 index 0000000..82ca1b7 Binary files /dev/null and b/Message_Queue/image/13.png differ diff --git a/Message_Queue/image/2.png b/Message_Queue/image/2.png new file mode 100644 index 0000000..4a7cbec Binary files /dev/null and b/Message_Queue/image/2.png differ diff --git a/Message_Queue/image/3.png b/Message_Queue/image/3.png new file mode 100644 index 0000000..585a2eb Binary files /dev/null and b/Message_Queue/image/3.png differ diff --git a/Message_Queue/image/4.png b/Message_Queue/image/4.png new file mode 100644 index 0000000..625820b Binary files /dev/null and b/Message_Queue/image/4.png differ diff --git a/Message_Queue/image/5.png b/Message_Queue/image/5.png new file mode 100644 index 0000000..d34b379 Binary files /dev/null and b/Message_Queue/image/5.png differ diff --git a/Message_Queue/image/6.png b/Message_Queue/image/6.png new file mode 100644 index 0000000..413cbf8 Binary files /dev/null and b/Message_Queue/image/6.png differ diff --git a/Message_Queue/image/7.png b/Message_Queue/image/7.png new file mode 100644 index 0000000..35b23fe Binary files /dev/null and b/Message_Queue/image/7.png differ diff --git a/Message_Queue/image/8.png b/Message_Queue/image/8.png new file mode 100644 index 0000000..28e3e6f Binary files /dev/null and b/Message_Queue/image/8.png differ diff --git a/Message_Queue/image/9.png b/Message_Queue/image/9.png new file mode 100644 index 0000000..328daf4 Binary files /dev/null and b/Message_Queue/image/9.png differ diff --git a/Mysql_Query_Optimization/README.md b/Mysql_Query_Optimization/README.md new file mode 100644 index 0000000..6795a8a --- /dev/null +++ b/Mysql_Query_Optimization/README.md @@ -0,0 +1,73 @@ +### 提高mysql千万级大数据SQL查询优化30条经验(Mysql索引优化注意) + +1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 + +2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num is null可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=0 + +3.应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。 + +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.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 + +6.下面的查询也将导致全表扫描:select id from t where name like '李%'若要提高效率,可以考虑全文检索。 + +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 name like 'abc%' + +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.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。 \ No newline at end of file diff --git a/Mysql_Sharding/README.md b/Mysql_Sharding/README.md new file mode 100644 index 0000000..585ee00 --- /dev/null +++ b/Mysql_Sharding/README.md @@ -0,0 +1,143 @@ +## 数据库分库分表(sharding)系列拆分实施策略和示例 ## + +> 本文图片摘自《MySQL性能调优与架构设计》一书 + +### 一、基本思想 ### +Sharding的基本思想就要把一个数据库切分成多个部分放到不同的数据库(server)上,从而缓解单一数据库的性能问题。不太严格的讲,对于海量数据的数据库,如果是因为表多而数据多,这时候适合使用垂直切分,即把关系紧密(比如同一模块)的表切分出来放在一个server上。如果表并不多,但每张表的数据非常多,这时候适合水平切分,即把表的数据按某种规则(比如按ID散列)切分到多个数据库(server)上。当然,现实中更多是这两种情况混杂在一起,这时候需要根据实际情况做出选择,也可能会综合使用垂直与水平切分,从而将原有数据库切分成类似矩阵一样可以无限扩充的数据库(server)阵列。下面分别详细地介绍一下垂直切分和水平切分. + +垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非常低,相互影响很小,业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业务模块所使用的表分拆到不同的数据库中。根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较简单清晰。(这也就是所谓的”share nothing”)。 + +![](https://github.com/scalad/Note/blob/master/Mysql_Sharding/image/0_12958577041KqK.gif) + +水平切分于垂直切分相比,相对来说稍微复杂一些。因为要将同一个表中的不同数据拆分到不同的数据库中,对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂,后期的数据维护也会更为复杂一些。 + +![](https://github.com/scalad/Note/blob/master/Mysql_Sharding/image/0_1295857710BUth.gif) + +让我们从普遍的情况来考虑数据的切分:一方面,一个库的所有表通常不可能由某一张表全部串联起来,这句话暗含的意思是,水平切分几乎都是针对一小搓一小搓(实际上就是垂直切分出来的块)关系紧密的表进行的,而不可能是针对所有表进行的。另一方面,一些负载非常高的系统,即使仅仅只是单个表都无法通过单台数据库主机来承担其负载,这意味着单单是垂直切分也不能完全解决问明。因此多数系统会将垂直切分和水平切分联合使用,先对系统做垂直切分,再针对每一小搓表的情况选择性地做水平切分。从而将整个数据库切分成一个分布式矩阵。 + +![](https://github.com/scalad/Note/blob/master/Mysql_Sharding/image/0_1295857852VJcX.gif) + +### 二、切分策略 ### +如前面所提到的,切分是按先垂直切分再水平切分的步骤进行的。垂直切分的结果正好为水平切分做好了铺垫。垂直切分的思路就是分析表间的聚合关系,把关系紧密的表放在一起。多数情况下可能是同一个模块,或者是同一“聚集”。这里的“聚集”正是领域驱动设计里所说的聚集。在垂直切分出的表聚集内,找出“根元素”(这里的“根元素”就是领域驱动设计里的“聚合根”),按“根元素”进行水平切分,也就是从“根元素”开始,把所有和它直接与间接关联的数据放入一个shard里。这样出现跨shard关联的可能性就非常的小。应用程序就不必打断既有的表间关联。比如:对于社交网站,几乎所有数据最终都会关联到某个用户上,基于用户进行切分就是最好的选择。再比如论坛系统,用户和论坛两个模块应该在垂直切分时被分在了两个shard里,对于论坛模块来说,Forum显然是聚合根,因此按Forum进行水平切分,把Forum里所有的帖子和回帖都随Forum放在一个shard里是很自然的。 + +对于共享数据数据,如果是只读的字典表,每个shard里维护一份应该是一个不错的选择,这样不必打断关联关系。如果是一般数据间的跨节点的关联,就必须打断。 + +需要特别说明的是:当同时进行垂直和水平切分时,切分策略会发生一些微妙的变化。比如:在只考虑垂直切分的时候,被划分到一起的表之间可以保持任意的关联关系,因此你可以按“功能模块”划分表格,但是一旦引入水平切分之后,表间关联关系就会受到很大的制约,通常只能允许一个主表(以该表ID进行散列的表)和其多个次表之间保留关联关系,也就是说:当同时进行垂直和水平切分时,在垂直方向上的切分将不再以“功能模块”进行划分,而是需要更加细粒度的垂直切分,而这个粒度与领域驱动设计中的“聚合”概念不谋而合,甚至可以说是完全一致,每个shard的主表正是一个聚合中的聚合根!这样切分下来你会发现数据库分被切分地过于分散了(shard的数量会比较多,但是shard里的表却不多),为了避免管理过多的数据源,充分利用每一个数据库服务器的资源,可以考虑将业务上相近,并且具有相近数据增长速率(主表数据量在同一数量级上)的两个或多个shard放到同一个数据源里,每个shard依然是独立的,它们有各自的主表,并使用各自主表ID进行散列,不同的只是它们的散列取模(即节点数量)必需是一致的。 + +#### 1.事务问题: #### +解决事务问题目前有两种可行的方案:分布式事务和通过应用程序与数据库共同控制实现事务下面对两套方案进行一个简单的对比。 + + 方案一:使用分布式事务 + + 优点:交由数据库管理,简单有效 + 缺点:性能代价高,特别是shard越来越多时 + + 方案二:由应用程序和数据库共同控制 + 原理:将一个跨多个数据库的分布式事务分拆成多个仅处 + 于单个数据库上面的小事务,并通过应用程序来总控 + 各个小事务。 + 优点:性能上有优势 + 缺点:需要应用程序在事务控制上做灵活设计。如果使用 + 了spring的事务管理,改动起来会面临一定的困难。 + +#### 2.跨节点Join的问题 #### +只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。 + +#### 3.跨节点的count,order by,group by以及聚合函数问题 #### +这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。 + +### 三、实施 ### +#### 第一部分:实施策略 #### +#### 1.准备阶段 #### +![](https://github.com/scalad/Note/blob/master/Mysql_Sharding/image/67a6a651gw1ducq4lmzyzj.jpg) + +对数据库进行分库分表(Sharding化)前,需要开发人员充分了解系统业务逻辑和数据库schema.一个好的建议是绘制一张数据库ER图或领域模型图,以这类图为基础划分shard,直观易行,可以确保开发人员始终保持清醒思路。对于是选择数据库ER图还是领域模型图要根据项目自身情况进行选择。如果项目使用数据驱动的开发方式,团队以数据库ER图作为业务交流的基础,则自然会选择数据库ER图,如果项目使用的是领域驱动的开发方式,并通过OR-Mapping构建了一个良好的领域模型,那么领域模型图无疑是最好的选择。就我个人来说,更加倾向使用领域模型图,因为进行切分时更多的是以业务为依据进行分析判断,领域模型无疑更加清晰和直观。 + +#### 2.分析阶段 #### + +1. 垂直切分 + +垂直切分的依据原则是:将业务紧密,表间关联密切的表划分在一起,例如同一模块的表。结合已经准备好的数据库ER图或领域模型图,仿照活动图中的泳道概念,一个泳道代表一个shard,把所有表格划分到不同的泳道中。下面的分析示例会展示这种做法。当然,你也可以在打印出的ER图或模型图上直接用铅笔圈,一切取决于你自己的喜好。 + +2. 水平切分 + +垂直切分后,需要对shard内表格的数据量和增速进一步分析,以确定是否需要进行水平切分。 + +2.1 若划分到一起的表格数据增长缓慢,在产品上线后可遇见的足够长的时期内均可以由单一数据库承载,则不需要进行水平切分,所有表格驻留同一shard,所有表间关联关系会得到最大限度的保留,同时保证了书写SQL的自由度,不易受join、group by、order by等子句限制。 + +2.2 若划分到一起的表格数据量巨大,增速迅猛,需要进一步进行水平分割。进一步的水平分割就这样进行: + +2.2.1.结合业务逻辑和表间关系,将当前shard划分成多个更小的shard,通常情况下,这些更小的shard每一个都只包含一个主表(将以该表ID进行散列的表)和多个与其关联或间接关联的次表。这种一个shard一张主表多张次表的状况是水平切分的必然结果。这样切分下来,shard数量就会迅速增多。如果每一个shard代表一个独立的数据库,那么管理和维护数据库将会非常麻烦,而且这些小shard往往只有两三张表,为此而建立一个新库,利用率并不高,因此,在水平切分完成后可再进行一次“反向的Merge”,即:将业务上相近,并且具有相近数据增长速率(主表数据量在同一数量级上)的两个或多个shard放到同一个数据库上,在逻辑上它们依然是独立的shard,有各自的主表,并依据各自主表的ID进行散列,不同的只是它们的散列取模(即节点数量)必需是一致的。这样,每个数据库结点上的表格数量就相对平均了。 + +2.2.2. 所有表格均划分到合适的shard之后,所有跨越shard的表间关联都必须打断,在书写sql时,跨shard的join、group by、order by都将被禁止,需要在应用程序层面协调解决这些问题。 + +特别想提一点:经水平切分后,shard的粒度往往要比只做垂直切割的粒度要小,原单一垂直shard会被细分为一到多个以一个主表为中心关联或间接关联多个次表的shard,此时的shard粒度与领域驱动设计中的“聚合”概念不谋而合,甚至可以说是完全一致,每个shard的主表正是一个聚合中的聚合根! + +#### 3.实施阶段 #### +如果项目在开发伊始就决定进行分库分表,则严格按照分析设计方案推进即可。如果是在中期架构演进中实施,除搭建实现sharding逻辑的基础设施外(关于该话题会在下篇文章中进行阐述),还需要对原有SQL逐一过滤分析,修改那些因为sharding而受到影响的sql. + +#### 第二部分:示例演示 #### +本文选择一个人尽皆知的应用:jpetstore来演示如何进行分库分表(sharding)在分析阶段的工作。由于一些个人原因,演示使用的jpetstore来自原ibatis官方的一个Demo版本,SVN地址为:http://mybatis.googlecode.com/svn/tags/java_release_2.3.4-726/jpetstore-5。关于jpetstore的业务逻辑这里不再介绍,这是一个非常简单的电商系统原型,其领域模型如下图: + +![](https://github.com/scalad/Note/blob/master/Mysql_Sharding/image/67a6a651tw1dv5vr1tskuj.jpg) + +由于系统较简单,我们很容易从模型上看出,其主要由三个模块组成:用户,产品和订单。那么垂直切分的方案也就出来了。接下来看水平切分,如果我们从一个实际的宠物店出发考虑,可能出现数据激增的单表应该是Account和Order,因此这两张表需要进行水平切分。对于Product模块来说,如果是一个实际的系统,Product和Item的数量都不会很大,因此只做垂直切分就足够了,也就是(Product,Category,Item,Iventory,Supplier)五张表在一个数据库结点上(没有水平切分,不会存在两个以上的数据库结点)。但是作为一个演示,我们假设产品模块也有大量的数据需要我们做水平切分,那么分析来看,这个模块要拆分出两个shard:一个是(Product(主),Category),另一个是(Item(主),Iventory,Supplier),同时,我们认为:这两个shard在数据增速上应该是相近的,且在业务上也很紧密,那么我们可以把这两个shard放在同一个数据库节点上,Item和Product数据在散列时取一样的模。根据前文介绍的图纸绘制方法,我们得到下面这张sharding示意图: + +![](https://github.com/scalad/Note/blob/master/Mysql_Sharding/image/67a6a651tw1dv5vpue9s3j.jpg) + +对于这张图再说明几点: + +1.使用泳道表示物理shard(一个数据库结点) + +2.若垂直切分出的shard进行了进一步的水平切分,但公用一个物理shard的话,则用虚线框住,表示其在逻辑上是一个独立的shard。 + +3.深色实体表示主表 + +4.X表示需要打断的表间关联 + +### 主键生成策略 ### +#### 第一部分:一些常见的主键生成策略 #### +一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由。目前几种可行的主键生成策略有: + +1. UUID:使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。 +2. 结合数据库维护一个Sequence表:此方案的思路也很简单,在数据库中建立一个Sequence表,表的结构类似于: + + CREATE TABLE `SEQUENCE` ( + `tablename` varchar(30) NOT NULL, + `nextid` bigint(20) NOT NULL, + PRIMARY KEY (`tablename`) + ) ENGINE=InnoDB + +每当需要为某个表的新纪录生成ID时就从Sequence表中取出对应表的nextid,并将nextid的值加1后更新到数据库中以备下次使用。此方案也较简单,但缺点同样明显:由于所有插入任何都需要访问该表,该表很容易成为系统性能瓶颈,同时它也存在单点问题,一旦该表数据库失效,整个应用程序将无法工作。有人提出使用Master-Slave进行主从同步,但这也只能解决单点问题,并不能解决读写比为1:1的访问压力问题。 + +除此之外,还有一些方案,像对每个数据库结点分区段划分ID,以及网上的一些ID生成算法,因为缺少可操作性和实践检验,本文并不推荐。实际上,接下来,我们要介绍的是Fickr使用的一种主键生成方案,这个方案是目前我所知道的最优秀的一个方案,并且经受了实践的检验,可以为大多数应用系统所借鉴。 + +#### 第二部分:一种极为优秀的主键生成策略 #### + +flickr开发团队在2010年撰文介绍了flickr使用的一种主键生成测策略,同时表示该方案在flickr上的实际运行效果也非常令人满意,原文连接:[Ticket Servers: Distributed Unique Primary Keys on the Cheap](http://code.flickr.com/blog/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/) 这个方案是我目前知道的最好的方案,它与一般Sequence表方案有些类似,但却很好地解决了性能瓶颈和单点问题,是一种非常可靠而高效的全局主键生成方案。 + +![](https://github.com/scalad/Note/blob/master/Mysql_Sharding/image/67a6a651gw1dujgqcx9ncj.jpg) + +flickr这一方案的整体思想是:建立两台以上的数据库ID生成服务器,每个服务器都有一张记录各表当前ID的Sequence表,但是Sequence中ID增长的步长是服务器的数量,起始值依次错开,这样相当于把ID的生成散列到了每个服务器节点上。例如:如果我们设置两台数据库ID生成服务器,那么就让一台的Sequence表的ID起始值为1,每次增长步长为2,另一台的Sequence表的ID起始值为2,每次增长步长也为2,那么结果就是奇数的ID都将从第一台服务器上生成,偶数的ID都从第二台服务器上生成,这样就将生成ID的压力均匀分散到两台服务器上,同时配合应用程序的控制,当一个服务器失效后,系统能自动切换到另一个服务器上获取ID,从而保证了系统的容错。 + +关于这个方案,有几点细节这里再说明一下: +1. flickr的数据库ID生成服务器是专用服务器,服务器上只有一个数据库,数据库中表都是用于生成Sequence的,这也是因为auto-increment-offset和auto-increment-increment这两个数据库变量是数据库实例级别的变量。 + +2. flickr的方案中表格中的stub字段只是一个char(1) NOT NULL存根字段,并非表名,因此,一般来说,一个Sequence表只有一条纪录,可以同时为多张表生成ID,如果需要表的ID是有连续的,需要为该表单独建立Sequence表。 + +3. 方案使用了MySQL的LAST_INSERT_ID()函数,这也决定了Sequence表只能有一条记录。 + +4. 使用REPLACE INTO插入数据,这是很讨巧的作法,主要是希望利用mysql自身的机制生成ID,不仅是因为这样简单,更是因为我们需要ID按照我们设定的方式(初值和步长)来生成。 + +5. SELECT LAST_INSERT_ID()必须要于REPLACE INTO语句在同一个数据库连接下才能得到刚刚插入的新ID,否则返回的值总是0 + +6. 该方案中Sequence表使用的是MyISAM引擎,以获取更高的性能,注意:MyISAM引擎使用的是表级别的锁,MyISAM对表的读写是串行的,因此不必担心在并发时两次读取会得到同一个ID(另外,应该程序也不需要同步,每个请求的线程都会得到一个新的connection,不存在需要同步的共享资源)。经过实际对比测试,使用一样的Sequence表进行ID生成,MyISAM引擎要比InnoDB表现高出很多! + +7. 可使用纯JDBC实现对Sequence表的操作,以便获得更高的效率,实验表明,即使只使用spring JDBC性能也不及纯JDBC来得快! + +实现该方案,应用程序同样需要做一些处理,主要是两方面的工作: + +1. 自动均衡数据库ID生成服务器的访问 + +2. 确保在某个数据库ID生成服务器失效的情况下,能将请求转发到其他服务器上执行。 + diff --git a/Mysql_Sharding/image/0_12958577041KqK.gif b/Mysql_Sharding/image/0_12958577041KqK.gif new file mode 100644 index 0000000..cc294cc Binary files /dev/null and b/Mysql_Sharding/image/0_12958577041KqK.gif differ diff --git a/Mysql_Sharding/image/0_1295857710BUth.gif b/Mysql_Sharding/image/0_1295857710BUth.gif new file mode 100644 index 0000000..c530e15 Binary files /dev/null and b/Mysql_Sharding/image/0_1295857710BUth.gif differ diff --git a/Mysql_Sharding/image/0_1295857852VJcX.gif b/Mysql_Sharding/image/0_1295857852VJcX.gif new file mode 100644 index 0000000..6076fb0 Binary files /dev/null and b/Mysql_Sharding/image/0_1295857852VJcX.gif differ diff --git a/Mysql_Sharding/image/67a6a651gw1ducq4lmzyzj.jpg b/Mysql_Sharding/image/67a6a651gw1ducq4lmzyzj.jpg new file mode 100644 index 0000000..09da13b Binary files /dev/null and b/Mysql_Sharding/image/67a6a651gw1ducq4lmzyzj.jpg differ diff --git a/Mysql_Sharding/image/67a6a651gw1dujgqcx9ncj.jpg b/Mysql_Sharding/image/67a6a651gw1dujgqcx9ncj.jpg new file mode 100644 index 0000000..a527e87 Binary files /dev/null and b/Mysql_Sharding/image/67a6a651gw1dujgqcx9ncj.jpg differ diff --git a/Mysql_Sharding/image/67a6a651tw1dv5vpue9s3j.jpg b/Mysql_Sharding/image/67a6a651tw1dv5vpue9s3j.jpg new file mode 100644 index 0000000..a7df494 Binary files /dev/null and b/Mysql_Sharding/image/67a6a651tw1dv5vpue9s3j.jpg differ diff --git a/Mysql_Sharding/image/67a6a651tw1dv5vr1tskuj.jpg b/Mysql_Sharding/image/67a6a651tw1dv5vr1tskuj.jpg new file mode 100644 index 0000000..66d3c18 Binary files /dev/null and b/Mysql_Sharding/image/67a6a651tw1dv5vr1tskuj.jpg differ diff --git a/Nginx_Load_Balancing/README.md b/Nginx_Load_Balancing/README.md new file mode 100644 index 0000000..793b920 --- /dev/null +++ b/Nginx_Load_Balancing/README.md @@ -0,0 +1,185 @@ +### Nginx负载均衡配置实例详解 ### +[导读] 负载均衡是我们大流量网站要做的一个东西,下面我来给大家介绍在Nginx服务器上进行负载均衡配置方法,希望对有需要的同学有所帮助哦。负载均衡先来简单了解一下什么是负载均衡,单从字面上的意思来理解就可以解 + +负载均衡是我们大流量网站要做的一个东西,下面我来给大家介绍在Nginx服务器上进行负载均衡配置方法,希望对有需要的同学有所帮助哦。 +负载均衡 + +先来简单了解一下什么是负载均衡,单从字面上的意思来理解就可以解释N台服务器平均分担负载,不会因为某台服务器负载高宕机而某台服务器闲置的情况。那么负载均衡的前提就是要有多台服务器才能实现,也就是两台以上即可。 + +测试环境 +由于没有服务器,所以本次测试直接host指定域名,然后在VMware里安装了三台CentOS。 + + 测试域名 :a.com + + A服务器IP :192.168.5.149 (主) + + B服务器IP :192.168.5.27 + + C服务器IP :192.168.5.126 + +部署思路 +A服务器做为主服务器,域名直接解析到A服务器(192.168.5.149)上,由A服务器负载均衡到B服务器(192.168.5.27)与C服务器(192.168.5.126)上。 + +#### 域名解析 #### + +由于不是真实环境,域名就随便使用一个a.com用作测试,所以a.com的解析只能在hosts文件设置。 + +打开:C:WindowsSystem32driversetchosts + +在末尾添加 + +192.168.5.149 a.com + +保存退出,然后启动命令模式ping下看看是否已设置成功 + +从截图上看已成功将a.com解析到192.168.5.149IP + +A服务器nginx.conf设置 +打开nginx.conf,文件位置在nginx安装目录的conf目录下。 + +在http段加入以下代码 + +```groovy +upstream a.com { + server 192.168.5.126:80; + server 192.168.5.27:80; +} + +server{ + listen 80; + server_name a.com; + location / { + proxy_pass http://a.com; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` +保存重启nginx + +B、C服务器nginx.conf设置 +打开nginx.confi,在http段加入以下代码 + +```groovy +server{ + listen 80; + server_name a.com; + index index.html; + root /data0/htdocs/www; +} +``` +保存重启nginx + +测试 +当访问a.com的时候,为了区分是转向哪台服务器处理我分别在B、C服务器下写一个不同内容的index.html文件,以作区分。 + +打开浏览器访问a.com结果,刷新会发现所有的请求均分别被主服务器(192.168.5.149)分配到B服务器(192.168.5.27)与C服务器(192.168.5.126)上,实现了负载均衡效果。 + +假如其中一台服务器宕机会怎样? +当某台服务器宕机了,是否会影响访问呢? + +我们先来看看实例,根据以上例子,假设C服务器192.168.5.126这台机子宕机了(由于无法模拟宕机,所以我就把C服务器关机)然后再来访问看看。 + +访问结果: + + + +我们发现,虽然C服务器(192.168.5.126)宕机了,但不影响网站访问。这样,就不会担心在负载均衡模式下因为某台机子宕机而拖累整个站点了。 + +如果b.com也要设置负载均衡怎么办? +很简单,跟a.com设置一样。如下: + +假设b.com的主服务器IP是192.168.5.149,负载均衡到192.168.5.150和192.168.5.151机器上 + +现将域名b.com解析到192.168.5.149IP上。 + +在主服务器(192.168.5.149)的nginx.conf加入以下代码: + +```groovy +upstream b.com { + server 192.168.5.150:80; + server 192.168.5.151:80; +} + +server{ + listen 80; + server_name b.com; + location / { + proxy_pass http://b.com; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +保存重启nginx + +在192.168.5.150与192.168.5.151机器上设置nginx,打开nginx.conf在末尾添加以下代码: + +server{ + listen 80; + server_name b.com; + index index.html; + root /data0/htdocs/www; +} +``` + +保存重启nginx + +完成以后步骤后即可实现b.com的负载均衡配置。 + +#### 主服务器不能提供服务吗? #### +以上例子中,我们都是应用到了主服务器负载均衡到其它服务器上,那么主服务器本身能不能也加在服务器列表中,这样就不会白白浪费拿一台服务器纯当做转发功能,而是也参与到提供服务中来。 + +如以上案例三台服务器: + +A服务器IP :192.168.5.149 (主) + +B服务器IP :192.168.5.27 + +C服务器IP :192.168.5.126 + +我们把域名解析到A服务器,然后由A服务器转发到B服务器与C服务器,那么A服务器只做一个转发功能,现在我们让A服务器也提供站点服务。 + +我们先来分析一下,如果添加主服务器到upstream中,那么可能会有以下两种情况发生: + +1、主服务器转发到了其它IP上,其它IP服务器正常处理; + +2、主服务器转发到了自己IP上,然后又进到主服务器分配IP那里,假如一直分配到本机,则会造成一个死循环。 + +怎么解决这个问题呢?因为80端口已经用来监听负载均衡的处理,那么本服务器上就不能再使用80端口来处理a.com的访问请求,得用一个新的。于是我们把主服务器的nginx.conf加入以下一段代码: + + server{ + listen 8080; + server_name a.com; + index index.html; + root /data0/htdocs/www; + } + +重启nginx,在浏览器输入a.com:8080试试看能不能访问。结果可以正常访问 + +既然能正常访问,那么我们就可以把主服务器添加到upstream中,但是端口要改一下,如下代码: + +```groovy +upstream a.com { + server 192.168.5.126:80; + server 192.168.5.27:80; + server 127.0.0.1:8080; +} +``` + +由于这里可以添加主服务器IP192.168.5.149或者127.0.0.1均可以,都表示访问自己。 + +重启Nginx,然后再来访问a.com看看会不会分配到主服务器上。 + +主服务器也能正常加入服务了。 + +#### 最后 #### + +一、负载均衡不是nginx独有,著名鼎鼎的apache也有,但性能可能不如nginx。 + +二、多台服务器提供服务,但域名只解析到主服务器,而真正的服务器IP不会被ping下即可获得,增加一定安全性。 + +三、upstream里的IP不一定是内网,外网IP也可以。不过经典的案例是,局域网中某台IP暴露在外网下,域名直接解析到此IP。然后又这台主服务器转发到内网服务器IP中。 + +四、某台服务器宕机、不会影响网站正常运行,Nginx不会把请求转发到已宕机的IP上 \ No newline at end of file diff --git a/Nginx_WebSocket/README.md b/Nginx_WebSocket/README.md new file mode 100644 index 0000000..7282184 --- /dev/null +++ b/Nginx_WebSocket/README.md @@ -0,0 +1,45 @@ +### Nginx反向代理WebSocket ### + + worker_processes 1; + events { + worker_connections 1024; + } + + http { + include mime.types; + default_type application/octet-stream; + sendfile on; + autoindex on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + server { + listen 80; + server_name example.com; + + #charset koi8-r; + + #access_log logs/host.access.log main; + + location / { + proxy_pass http://120.27.114.229/; + proxy_redirect off; + + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_set_header X-NginX-Proxy true; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + } + } + diff --git a/Nginx_Window_Register_Service/README.md b/Nginx_Window_Register_Service/README.md new file mode 100644 index 0000000..9cb3450 --- /dev/null +++ b/Nginx_Window_Register_Service/README.md @@ -0,0 +1,63 @@ +### Window平台下Nginx注册为后台服务 ### +Nginx 不是为 Windows 而写。Nginx 是用在软件的工作环境中的。但软件开发环境一般都是 Windows,有时调试的需要也要装 Nginx,但 Nginx 并没给 Windows 提供服务支持。如何把 Nginx 创建为 Windows 的一个服务呢? + +把 Nginx 创建为 Windows 的一个服务,比较流行的一个做法就是用微软提供的 instsrv/servany。本文没有使用这种做法,并说明理由。 + +#### 引言 #### + +Nginx 是一个 web 服务器。它类似于 Lighttpd,作为轻量级的 web server,可以替代重量级的 Apache/IIS。Nginx 专为性能优化而开发,是一个快速且能经受高负载考验的 web server。它来自于 Linux 世界但同样可以运行在 Windows 上面(由本地语言构建)。唯一的问题就是它不支持 Windows Services。 + +尽管 Nginx 以快速和提供高性能而具有很大的声誉,但并非是在 Windows 平台上。访问官方网址 [http://nginx.org/en/docs/windows.html](http://nginx.org/en/docs/windows.html),你会发现,对于 Windows 平台的支持被认为是测试版,根据 Nginx 的实现来看它并不提供(和 Linux 平台)相同的性能水平。 + +也许你都没有意识到,WordPress 就是一个 Nginx 的用户,使用它提供大量的静态内容服务,并负载平衡请求到其他服务器。如果你想了解更多关于 Nginx 的内容请看本文底部的链接。 + +#### Nginx 对比 Windows 服务 #### +Nginx 是绿色免安装的。这里我不去介绍它的管理配置,官方已经提供了一个很棒的 wiki,上面有丰富的服务器相关信息(参考文后链接)。可以使用默认的配置,它会使用 Nginx 目录下的 html 文件夹服务于端口 80。 + +只需简单地执行 nginx.exe 即可启动 Nginx。但你想要停止它的时候问题来了,你需要执行以下命令: + + nginx.exe -s stop + +虽然这很简单,但是如果它能够像 apache 或 IIS 那样作为一个服务工作的话会更漂亮。那样的话,我们就可以设置机器启动时 Nginx 自动启动,还可以方便地启动、停止或者重启服务,设置恢复选项、依赖的服务,等等。 + +为什么不使用 instsrv/servany、FireDaemon 或者其他办法呢? + +已经有介绍如何通过 FireDaemon 使用 Nginx,但它有一个很重要的问题。Nginx 启动以后,它会创建一个次级进程。所以会有两个 nginx.exe 在运行。对于这个官方可能有一个很合理的理由,但你最好到 Nginx 论坛去问为什么:-) + +通过 instsrv/srvany(微软官方创建服务的方法) 或者 FireDaemon 的方式(来创建 Nginx 为服务),只是启动进程,当你想要停止它时,将关闭这个进程。但这些方式都无法关闭多余的那个 nginx.exe 进程。所以每次你停止/启动/重启服务都会产生一个多余的 nginx.exe 进程。不怎么好! + +把 Nginx 创建为 Windows 的一个服务(一个较好的做法) + + 多亏了一个叫做 ["Windows Service Wrapper"](http://projectkenai.com/projects/winsw) 的小项目,我们有了一个办法来恰当地启动和停止 Nginx。首先从[https://github.com/scalad/Note/blob/master/Nginx_Window_Register_Service/winsw-1.8-bin.zip](https://github.com/scalad/Note/blob/master/Nginx_Window_Register_Service/winsw-1.8-bin.zip)下载winsw工具包 + +得到该程序后,将其放在 Nginx 安装目录下,并重命名为 myapp.exe。 + +然后是告诉 WinSw 我们想要它做什么。这将使用一个 XML 配置文件,我们将在文件中指出 Nginx 需要一个 shutdown 命令。 + +(在 Nginx 安装目录下)新建一个名为 myapp.xml 的文件,编辑其内容如下: + +```xml + + nginx + nginx + nginx + c:\nginx\nginx.exe + c:\nginx\ + roll + + -p c:\nginx + -p c:\nginx -s stop + +``` + +很明显,你应该稍微更改文件,这取决于你自己的文件路径。对于有更多技术需求的朋友,你也可以在该文件中设置 Nginx 依赖的服务。最后,我们要安装服务了。只需要简单地执行以下语句,你将在你的服务列表 +里找到 "Nginx" 服务: + + c:\nginx\myapp.exe install + +就这些! + +结束语 + +根据我的经验,到目前为止这种做法的效果很完美。你得到了 Windows 服务的支持,而且在服务重启时没有遗留孤立的 "nginx.exe"。两全其美。 +如果 Nginx 自己可以做到这样的话会更好,但 Nginx 的作者当下正在专注于其他更重要的开发。我敢肯定还有其他人有足够的编程知识来贡献这块所需的代码,所以,如果你是这样的一个人,请尽力来帮助大家。 \ No newline at end of file diff --git a/Nginx_Window_Register_Service/winsw-1.8-bin.zip b/Nginx_Window_Register_Service/winsw-1.8-bin.zip new file mode 100644 index 0000000..5e1c6d4 Binary files /dev/null and b/Nginx_Window_Register_Service/winsw-1.8-bin.zip differ diff --git a/RESTFUL_architecture/README.md b/RESTFUL_architecture/README.md index 5370636..ff97139 100644 --- a/RESTFUL_architecture/README.md +++ b/RESTFUL_architecture/README.md @@ -1,4 +1,4 @@ -###RESTFUL架构 +### RESTFUL架构 越来越多的人开始意识到,网站即软件,而且是一种新型的软件。 diff --git a/RabbitMQ/README.md b/RabbitMQ/README.md new file mode 100644 index 0000000..bab0362 --- /dev/null +++ b/RabbitMQ/README.md @@ -0,0 +1,188 @@ +### [Rabbit+Python经典例子] 兔子和兔子窝 ### +[RabbitMQ](http://www.rabbitmq.com/)作为一个工业级的消息队列服务器,在其[客户端手册列表](http://www.rabbitmq.com/how.html#clients)的[Python](http://lib.csdn.net/base/python)段当中推荐了一篇blog,作为RabbitMQ+Python的入门手册再合适不过了。不过,正如其标题Rabbit and Warrens(兔子和养兔场)一样,这篇英文写的相当俏皮,以至于对于我等非英文读者来说不像一般的技术文档那么好懂,所以,翻译一下吧。翻译过了,希望其他人可以少用一些时间。翻译水平有限,不可能像原文一样俏皮,部分地方可能就意译了,希望以容易懂为准。想看看老外的幽默的,推荐去看原文,其实,也不是那么难理解…… + +#### 兔子和兔子窝 #### +当时我们的动机很简单:从生产环境的电子邮件处理流程当中分支出一个特定的离线分析流程。我们开始用的MySQL,将要处理的东西放在表里面,另一个程序从中取。不过很快,这种设计的丑陋之处就显现出来了…… 你想要多个程序从一个队列当中取数据来处理?没问题,我们硬编码程序的个数好了……什么?还要能够允许程序动态地增加和减少的时候动态进行压力分配? + +是的,当年我们想的简单的东西(做一个分支处理)逐渐变成了一个棘手的问题。以前拿着锤子(MySQL)看所有东西都是钉子(表)的年代是多么美好…… + +在搜索了一下之后,我们走进了消息队列(message queue)的大门。不不,我们当然知道消息队列是什么,我们可是以做电子邮件程序谋生的。我们实现过各种各样的专业的,高速的内存队列用来做电子邮件处理。我们不知道的是那一大类现成的、通用的消息队列(MQ)服务器——无论是用什么语言写出的,不需要复杂的装配的,可以自然的在网络上的应用程序之间传送数据的一类程序。不用我们自己写?看看再说。 + +#### 让大家看看你们的Queue吧…… #### + +过去的4年里,人们写了有好多好多的开源的MQ服务器啊。其中大多数都是某公司例如LiveJournal写出来用来解决特定问题的。它们的确不关心上面跑的是什么类型的消息,不过他们的设计思想通常是和创建者息息相关的(消息的持久化,崩溃恢复等通常不在他们考虑范围内)。不过,有三个专门设计用来做及其灵活的消息队列的程序值得关注: + +* [Apache ActiveMQ](http://activemq.apache.org/) +* [ZeroMQ](http://www.zeromq.org/) +* [RabbitMQ](http://www.rabbitmq.com/) + +Apache ActiveMQ 曝光率最高,不过看起来它有些问题,可能会造成丢消息。不可接受,下一个。 + +ZeroMQ 和 RabbitMQ 都支持一个开源的消息协议,成为AMQP。AMQP的一个优点是它是一个灵活和开放的协议,以便和另外两个商业化的Message Queue (IBM和Tibco)竞争,很好。不过ZeroMQ不支持消息持久化和崩溃恢复,不太好。剩下的只有RabbitMQ了。如果你不在意消息持久化和崩溃恢复,试试ZeroMQ吧,延迟很低,而且支持灵活的拓扑。 + +#### 剩下的只有这个吃胡萝卜的家伙了…… #### +![](https://github.com/scalad/Note/blob/master/RabbitMQ/image/0_127963627592fa.gif) + +当我读到它是用Erlang写的时候,RabbitMQ震了我一下。[Erlang](http://en.wikipedia.org/wiki/Erlang_%28programming_language%29)是爱立信开发的高度并行的语言,用来跑在电话交换机上。是的,那些要求6个9的在线时间的东西。在Erlang当中,充斥着大量轻量进程,它们之间用消息传递来通信。听起来思路和我们用消息队列的思路是一样的,不是么? +而且,RabbitMQ支持持久化。是的,如果RabbitMQ死掉了,消息并不会丢失,当队列重启,一切都会回来。而且,正如在DigiTar(注:原文作者的公司)做事情期望的那样,它[可以和Python无缝结合](http://barryp.org/software/py-amqplib/)。除此之外,RabbitMQ的文档相当的……恐怖。如果你懂AMQP,这些文档还好,但是有多少人懂AMQP?这些文档就像MySQL的文档假设你已经懂了SQL一样……不过没关系啦。 + +好了,废话少说。这里是花了一周时间阅读关于AMQP和关于它如何在RabbitMQ上工作的文档之后的一个总结,还有,怎么在Python当中使用。 + +#### 开始吧 #### +AMQP当中有四个概念非常重要:虚拟主机(virtual host),交换机(exchange),队列(queue)和绑定(binding)。一个虚拟主机持有一组交换机、队列和绑定。为什么需要多个虚拟主机呢?很简单,RabbitMQ当中,用户只能在虚拟主机的粒度进行权限控制。因此,如果需要禁止A组访问B组的交换机/队列/绑定,必须为A和B分别创建一个虚拟主机。每一个RabbitMQ服务器都有一个默认的虚拟主机“/”。如果这就够了,那现在就可以开始了。 + +#### 交换机,队列,还有绑定……天哪! #### +刚开始我思维的列车就是在这里脱轨的…… 这些鬼东西怎么结合起来的? + +队列(Queues)是你的消息(messages)的终点,可以理解成装消息的容器。消息就一直在里面,直到有客户端(也就是消费者,Consumer)连接到这个队列并且将其取走为止。不过。你可以将一个队列配置成这样的:一旦消息进入这个队列,biu~,它就烟消云散了。这个有点跑题了…… + +需要记住的是,队列是由消费者(Consumer)通过程序建立的,不是通过配置文件或者命令行工具。这没什么问题,如果一个消费者试图创建一个已经存在的队列,RabbitMQ就会起来拍拍他的脑袋,笑一笑,然后忽略这个请求。因此你可以将消息队列的配置写在应用程序的代码里面。这个概念不错。 + +OK,你已经创建并且连接到了你的队列,你的消费者程序正在百无聊赖的敲着手指等待消息的到来,敲啊,敲啊…… 没有消息。发生了什么?你当然需要先把一个消息放进队列才行。不过要做这个,你需要一个交换机(Exchange)…… + +交换机可以理解成具有路由表的路由程序,仅此而已。每个消息都有一个称为路由键(routing key)的属性,就是一个简单的字符串。交换机当中有一系列的绑定(binding),即路由规则(routes),例如,指明具有路由键 “X” 的消息要到名为timbuku的队列当中去。先不讨论这个,我们有点超前了。 + +你的消费者程序要负责创建你的交换机们(复数)。啥?你是说你可以有多个交换机?是的,这个可以有,不过为啥?很简单,每个交换机在自己独立的进程当中执行,因此增加多个交换机就是增加多个进程,可以充分利用服务器上的CPU核以便达到更高的效率。例如,在一个8核的服务器上,可以创建5个交换机来用5个核,另外3个核留下来做消息处理。类似的,在RabbitMQ的集群当中,你可以用类似的思路来扩展交换机一边获取更高的吞吐量。 + +OK,你已经创建了一个交换机。但是他并不知道要把消息送到哪个队列。你需要路由规则,即绑定(binding)。一个绑定就是一个类似这样的规则:将交换机“desert(沙漠)”当中具有路由键“阿里巴巴”的消息送到队列“hideout(山洞)”里面去。换句话说,一个绑定就是一个基于路由键将交换机和队列连接起来的路由规则。例如,具有路由键“audit”的消息需要被送到两个队列,“log-forever”和“alert-the-big-dude”。要做到这个,就需要创建两个绑定,每个都连接一个交换机和一个队列,两者都是由“audit”路由键触发。在这种情况下,交换机会复制一份消息并且把它们分别发送到两个队列当中。交换机不过就是一个由绑定构成的路由表。 + +现在复杂的东西来了:交换机有多种类型。他们都是做路由的,不过接受不同类型的绑定。为什么不创建一种交换机来处理所有类型的路由规则呢?因为每种规则用来做匹配分子的CPU开销是不同的。例如,一个“topic”类型的交换机试图将消息的路由键与类似“dogs.*”的模式进行匹配。匹配这种末端的通配符比直接将路由键与“dogs”比较(“direct”类型的交换机)要消耗更多的CPU。如果你不需要“topic”类型的交换机带来的灵活性,你可以通过使用“direct”类型的交换机获取更高的处理效率。那么有哪些类型,他们又是怎么处理的呢? + +Fanout Exchange – 不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。 + +Direct Exchange – 处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog”的消息才被转发,不会转发dog.puppy,也不会转发dog.guard,只会转发dog。 + +Topic Exchange – 将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate”,但是“audit.*” 只会匹配到“audit.irs”。我在RedHat的朋友做了一张不错的图,来表明topic交换机是如何工作的: + +![](https://github.com/scalad/Note/blob/master/RabbitMQ/image/0_1279636289h7H9.gif) + +#### 持久化这些小东西们 #### +你花了大量的时间来创建队列、交换机和绑定,然后,砰~服务器程序挂了。你的队列、交换机和绑定怎么样了?还有,放在队列里面但是尚未处理的消息们呢? + +放松~如果你是用默认参数构造的这一切的话,那么,他们,都,biu~,灰飞烟灭了。是的,RabbitMQ重启之后会干净的像个新生儿。你必须重做所有的一切,亡羊补牢,如何避免将来再度发生此类杯具? + +队列和交换机有一个创建时候指定的标志durable,直译叫做坚固的。durable的唯一含义就是具有这个标志的队列和交换机会在重启之后重新建立,它不表示说在队列当中的消息会在重启后恢复。那么如何才能做到不只是队列和交换机,还有消息都是持久的呢? + +但是首先一个问题是,你真的需要消息是持久的吗?对于一个需要在重启之后回复的消息来说,它需要被写入到磁盘上,而即使是最简单的磁盘操作也是要消耗时间的。如果和消息的内容相比,你更看重的是消息处理的速度,那么不要使用持久化的消息。不过对于我们@DigiTar来说,持久化很重要。 + +当你将消息发布到交换机的时候,可以指定一个标志“Delivery Mode”(投递模式)。根据你使用的AMQP的库不同,指定这个标志的方法可能不太一样(我们后面会讨论如何用Python搞定)。简单的说,就是将Delivery Mode设置成2,也就是持久的(persistent)即可。一般的AMQP库都是将Delivery Mode设置成1,也就是非持久的。所以要持久化消息的步骤如下: + +1. 将交换机设成 durable。 +2. 将队列设成 durable。 +3. 将消息的 Delivery Mode 设置成2 。 + +就这样,不是很复杂,起码没有造火箭复杂,不过也有可能犯点小错误。 + +下面还要罗嗦一个东西……绑定(Bindings)怎么办?我们无法在创建绑定的时候设置成durable。没问题,如果你绑定了一个durable的队列和一个durable的交换机,RabbitMQ会自动保留这个绑定。类似的,如果删除了某个队列或交换机(无论是不是durable),依赖它的绑定都会自动删除。 +注意两点: + +* RabbitMQ 不允许你绑定一个非坚固(non-durable)的交换机和一个durable的队列。反之亦然。要想成功必须队列和交换机都是durable的。 +* 一旦创建了队列和交换机,就不能修改其标志了。例如,如果创建了一个non-durable的队列,然后想把它改变成durable的,唯一的办法就是删除这个队列然后重现创建。因此,最好仔细检查创建的标志。 + +#### 开始喂蛇了~ #### +【译注】说喂蛇是因为Python的图标是条蛇。 + +AMQP的一个空白地带是如何在Python当中使用。对于其他语言有一大坨材料。 + +* Java – [http://www.rabbitmq.com/java-client.html](http://www.rabbitmq.com/java-client.html) +* .NET – [http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.5.0/rabbitmq-dotnet-client-1.5.0-user-guide.pdf](http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.5.0/rabbitmq-dotnet-client-1.5.0-user-guide.pdf) +* Ruby – [http://somic.org/2008/06/24/ruby-amqp-rabbitmq-example/](http://somic.org/2008/06/24/ruby-amqp-rabbitmq-example/) + +但是对Python老兄来说,你需要花点时间来挖掘一下。所以我写了这个,这样别的家伙们就不需要经历我这种抓狂的过程了。 + +首先,我们需要一个Python的AMQP库。有两个可选: + +* [py-amqplib](http://barryp.org/software/py-amqplib/) – 通用的AMQP +* [txAMQP](https://launchpad.net/txamqp) – 使用 [Twisted](http://www.twistedmatrix.com/)框架的AMQP库,因此允许异步I/O。 + +根据你的需求,py-amqplib或者txAMQP都是可以的。因为是基于Twisted的,txAMQP可以保证用异步IO构建超高性能的AMQP程序。但是Twisted编程本身就是一个很大的主题……因此清晰起见,我们打算用 py-amqplib。更新:请参见Esteve Fernandez关于txAMQP的使用和代码样例的回复。 +AMQP支持在一个TCP连接上启用多个MQ通信channel,每个channel都可以被应用作为通信流。每个AMQP程序至少要有一个连接和一个channel。 + +```python +from amqplib import client_0_8 as amqp +conn = amqp.Connection(host="localhost:5672 ", userid="guest", +password="guest", virtual_host="/", insist=False) +chan = conn.channel() +``` + +每个channel都被分配了一个整数标识,自动由Connection()类的.channel()方法维护。或者,你可以使用.channel(x)来指定channel标识,其中x是你想要使用的channel标识。通常情况下,推荐使用.channel()方法来自动分配channel标识,以便防止冲突。 + +现在我们已经有了一个可以用的连接和channel。现在,我们的代码将分成两个应用,生产者(producer)和消费者(consumer)。我们先创建一个消费者程序,他会创建一个叫做“po_box”的队列和一个叫“sorting_room”的交换机: + +```python +chan.queue_declare(queue="po_box", durable=True, +exclusive=False, auto_delete=False) +chan.exchange_declare(exchange="sorting_room", type="direct", durable=True, +auto_delete=False,) +``` + +这段代码干了啥?首先,它创建了一个名叫“po_box”的队列,它是durable的(重启之后会重新建立),并且最后一个消费者断开的时候不会自动删除(auto_delete=False)。在创建durable的队列(或者交换机)的时候,将auto_delete设置成false是很重要的,否则队列将会在最后一个消费者断开的时候消失,与durable与否无关。如果将durable和auto_delete都设置成True,只有尚有消费者活动的队列可以在RabbitMQ意外崩溃的时候自动恢复。 +(你可以注意到了另一个标志,称为“exclusive”。如果设置成True,只有创建这个队列的消费者程序才允许连接到该队列。这种队列对于这个消费者程序是私有的)。 + +还有另一个交换机声明,创建了一个名字叫“sorting_room”的交换机。auto_delete和durable的含义和队列是一样的。但是,.excange_declare() 还有另外一个参数叫做type,用来指定要创建的交换机的类型(如前面列出的): fanout, direct 和 topic. + +到此为止,你已经有了一个可以接收消息的队列和一个可以发送消息的交换机。不过我们需要创建一个绑定,把它们连接起来。 + + chan.queue_bind(queue=”po_box”, exchange=”sorting_room”, + routing_key=”jason”) + +这个绑定的过程非常直接。任何送到交换机“sorting_room”的具有路由键“jason” 的消息都被路由到名为“po_box” 的队列。 + +现在,你有两种方法从队列当中取出消息。第一个是调用chan.basic_get(),主动从队列当中拉出下一个消息(如果队列当中没有消息,chan.basic_get()会返回None, 因此下面代码当中print msg.body 会在没有消息的时候崩掉): + +```python +msg = chan.basic_get("po_box") +print msg.body +chan.basic_ack(msg.delivery_tag) +``` + +但是如果你想要应用程序在消息到达的时候立即得到通知怎么办?这种情况下不能使用chan.basic_get(),你需要用chan.basic_consume()注册一个新消息到达的回调。 + +```python +def recv_callback(msg): + print 'Received: ' + msg.body +chan.basic_consume(queue='po_box', no_ack=True, +callback=recv_callback, consumer_tag="testtag") +while True: + chan.wait() +chan.basic_cancel("testtag") +``` + +chan.wait() 放在一个无限循环里面,这个函数会等待在队列上,直到下一个消息到达队列。chan.basic_cancel() 用来注销该回调函数。参数consumer_tag 当中指定的字符串和chan.basic_consume() 注册的一直。在这个例子当中chan.basic_cancel() 不会被调用到,因为上面是个无限循环…… 不过你需要知道这个调用,所以我把它放在了代码里。 + +需要注意的另一个东西是no_ack参数。这个参数可以传给chan.basic_get()和chan.basic_consume(),默认是false。当从队列当中取出一个消息的时候,RabbitMQ需要应用显式地回馈说已经获取到了该消息。如果一段时间内不回馈,RabbitMQ会将该消息重新分配给另外一个绑定在该队列上的消费者。另一种情况是消费者断开连接,但是获取到的消息没有回馈,则RabbitMQ同样重新分配。如果将no_ack 参数设置为true,则py-amqplib会为下一个AMQP请求添加一个no_ack属性,告诉AMQP服务器不需要等待回馈。但是,大多数时候,你也许想要自己手工发送回馈,例如,需要在回馈之前将消息存入数据库。回馈通常是通过调用chan.basic_ack()方法,使用消息的delivery_tag属性作为参数。参见chan.basic_get() 的实例代码。 + +好了,这就是消费者的全部代码。(下载:amqp_consumer.py) + +不过没有人发送消息的话,要消费者何用?所以需要一个生产者。下面的代码示例表明如何将一个简单消息发送到交换区“sorting_room”,并且标记为路由键“jason” : + +```python +msg = amqp.Message("Test message!") +msg.properties["delivery_mode"] = 2 +chan.basic_publish(msg,exchange="sorting_room",routing_key="jason") +``` + +你也许注意到我们设置消息的delivery_mode属性为2,因为队列和交换机都设置为durable的,这个设置将保证消息能够持久化,也就是说,当它还没有送达消费者之前如果RabbitMQ重启则它能够被恢复。 + +剩下的最后一件事情(生产者和消费者都需要调用的)是关闭channel和连接: + + chan.close() + conn.close() + +很简单吧。([下载:amqp_publisher.py](http://blogs.digitar.com/jjww/code-samples/amqp_publisher.py)) + +#### 来真实地跑一下吧…… #### + +现在我们已经写好了生产者和消费者,让他们跑起来吧。假设你的RabbitMQ在localhost上安装并且运行。 +打开一个终端,执行python ./amqp_consumer.py让消费者运行,并且创建队列、交换机和绑定。 +然后在另一个终端运行python ./amqp_publisher.py “AMQP rocks.” 。如果一切良好,你应该能够在第一个终端看到输出的消息。 + +#### 付诸使用吧 #### +我知道这个教程是非常粗浅的关于AMQP/RabbitMQ和如何使用Python访问的教程。希望这个可以说 +明所有的概念如何在Python当中被组合起来。如果你发现任何错误,请联系原作者(williamsjj@digitar.com) 【译注:如果是翻译问题请联系译者】。同时,我很高兴回答我知道的问题。【译注:译者也是一样的】。接下来是,集群化(clustering)!不过我需要先把它弄懂再说。 + +注:关于RabbitMQ的知识我主要来自这些来源,推荐阅读: + +[zeromq:消息中间件分析](http://blog.csdn.net/linvo/article/details/undefined) +[RabbitMQ .NET客户端库用户手册](http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.5.0/rabbitmq-dotnet-client-1.5.0-user-guide.pdf) +[高级消息队列协议(Advanced Message Queuing Protocol):协议规约0.8 版本](http://jira.amqp.org/confluence/download/attachments/720900/amqp0-8.pdf?version=1) \ No newline at end of file diff --git a/RabbitMQ/image/0_127963627592fa.gif b/RabbitMQ/image/0_127963627592fa.gif new file mode 100644 index 0000000..c5690ec Binary files /dev/null and b/RabbitMQ/image/0_127963627592fa.gif differ diff --git a/RabbitMQ/image/0_1279636289h7H9.gif b/RabbitMQ/image/0_1279636289h7H9.gif new file mode 100644 index 0000000..983233d Binary files /dev/null and b/RabbitMQ/image/0_1279636289h7H9.gif differ diff --git a/RabbitMQ_User_Manager/README.MD b/RabbitMQ_User_Manager/README.MD new file mode 100644 index 0000000..007cad3 --- /dev/null +++ b/RabbitMQ_User_Manager/README.MD @@ -0,0 +1,104 @@ +### rabbitmq——用户管理 +安装最新版本的rabbitmq(3.3.1),并启用management plugin后,使用默认的账号guest登陆管理控制台,却提示登陆失败。 + +翻看官方的release文档后,得知由于账号guest具有所有的操作权限,并且又是默认账号,出于安全因素的考虑,guest用户只能通过localhost登陆使用,并建议修改guest用户的密码以及新建其他账号管理使用rabbitmq(该功能是在3.3.0版本引入的)。 + +虽然可以以比较猥琐的方式:将ebin目录下rabbit.app中loopback_users里的<<"guest">>删除,或者在配置文件rabbitmq.config中对该项进行配置 + +![](https://github.com/silence940109/RabbitMQ/blob/master/user_configuration/image/132319_nDll_184909.jpg) + +并重启rabbitmq,可通过任意IP使用guest账号登陆管理控制台,但始终是违背了设计者的初衷,再加上以前对这一块了解也不多,因此有必要总结一下。 + +1. 用户管理 + +用户管理包括增加用户,删除用户,查看用户列表,修改用户密码。 + +相应的命令 + +(1) 新增一个用户 + +rabbitmqctl add_user Username Password + +(2) 删除一个用户 + +rabbitmqctl delete_user Username + +(3) 修改用户的密码 + +rabbitmqctl change_password Username Newpassword + +(4) 查看当前用户列表 + +rabbitmqctl list_users + +2. 用户角色 + +按照个人理解,用户角色可分为五类,超级管理员, 监控者, 策略制定者, 普通管理者以及其他。 + +(1) 超级管理员(administrator) + +可登陆管理控制台(启用management plugin的情况下),可查看所有的信息,并且可以对用户,策略(policy)进行操作。 + +(2) 监控者(monitoring) + +可登陆管理控制台(启用management plugin的情况下),同时可以查看rabbitmq节点的相关信息(进程数,内存使用情况,磁盘使用情况等) + +![](https://github.com/silence940109/RabbitMQ/blob/master/user_configuration/image/124711_CcDj_184909.jpg) + +(3) 策略制定者(policymaker) + +可登陆管理控制台(启用management plugin的情况下), 同时可以对policy进行管理。但无法查看节点的相关信息(上图红框标识的部分)。 + +![](https://github.com/silence940109/RabbitMQ/blob/master/user_configuration/image/125058_doRy_184909.jpg) + +与administrator的对比,administrator能看到这些内容 + +![](https://github.com/silence940109/RabbitMQ/blob/master/user_configuration/image/125325_bR7x_184909.jpg) + +(4) 普通管理者(management) + +仅可登陆管理控制台(启用management plugin的情况下),无法看到节点信息,也无法对策略进行管理。 + +(5) 其他 + +无法登陆管理控制台,通常就是普通的生产者和消费者。 + +了解了这些后,就可以根据需要给不同的用户设置不同的角色,以便按需管理。 + +设置用户角色的命令为: + +rabbitmqctl set_user_tags User Tag + +User为用户名, Tag为角色名(对应于上面的administrator,monitoring,policymaker,management,或其他自定义名称)。 + +也可以给同一用户设置多个角色,例如 + +rabbitmqctl set_user_tags hncscwc monitoring policymaker + +3. 用户权限 + +用户权限指的是用户对exchange,queue的操作权限,包括配置权限,读写权限。配置权限会影响到exchange,queue的声明和删除。读写权限影响到从queue里取消息,向exchange发送消息以及queue和exchange的绑定(bind)操作。 + +例如: 将queue绑定到某exchange上,需要具有queue的可写权限,以及exchange的可读权限;向exchange发送消息需要具有exchange的可写权限;从queue里取数据需要具有queue的可读权限。详细请参考官方文档中"How permissions work"部分。 + +相关命令为: + +(1) 设置用户权限 + +rabbitmqctl set_permissions -p VHostPath User ConfP WriteP ReadP + +(2) 查看(指定hostpath)所有用户的权限信息 + +rabbitmqctl list_permissions [-p VHostPath] + +(3) 查看指定用户的权限信息 + +rabbitmqctl list_user_permissions User + +(4) 清除用户的权限信息 + +rabbitmqctl clear_permissions [-p VHostPath] User + +=============================== + +命令详细参考官方文档:[rabbitmqctl](http://www.rabbitmq.com/man/rabbitmqctl.1.man.html) \ No newline at end of file diff --git a/RabbitMQ_User_Manager/image/124711_CcDj_184909.jpg b/RabbitMQ_User_Manager/image/124711_CcDj_184909.jpg new file mode 100644 index 0000000..be080c1 Binary files /dev/null and b/RabbitMQ_User_Manager/image/124711_CcDj_184909.jpg differ diff --git a/RabbitMQ_User_Manager/image/125058_doRy_184909.jpg b/RabbitMQ_User_Manager/image/125058_doRy_184909.jpg new file mode 100644 index 0000000..8e385c9 Binary files /dev/null and b/RabbitMQ_User_Manager/image/125058_doRy_184909.jpg differ diff --git a/RabbitMQ_User_Manager/image/125325_bR7x_184909.jpg b/RabbitMQ_User_Manager/image/125325_bR7x_184909.jpg new file mode 100644 index 0000000..daf9b79 Binary files /dev/null and b/RabbitMQ_User_Manager/image/125325_bR7x_184909.jpg differ diff --git a/RabbitMQ_User_Manager/image/132319_nDll_184909.jpg b/RabbitMQ_User_Manager/image/132319_nDll_184909.jpg new file mode 100644 index 0000000..339743c Binary files /dev/null and b/RabbitMQ_User_Manager/image/132319_nDll_184909.jpg differ diff --git a/Redis_Config/README.md b/Redis_Config/README.md new file mode 100644 index 0000000..a18b69c --- /dev/null +++ b/Redis_Config/README.md @@ -0,0 +1,481 @@ +### Redis安装与配置 ### +Redis is an open source (BSD licensed), in-memory data structure store, used as database, cache and message broker.It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs and geospatial indexes with radius queries. + +Redis作为如今比较火热的NoSQL数据库,在数据的热数据存储和查询方面有着不错的应用,这篇文章将介绍Redis的安装和配置信息。 + +#### 下载、安装Redis #### +1. [Redis 官方下载](http://redis.io/download) + + wget http://download.redis.io/releases/redis-3.0.7.tar.gz + +2. 解压 + + tar xzf redis-3.0.7.tar.gz + +3. 赋予权限 + + udo chmod -R 775 redis-3.0.7/ + +4. make + + cd redis-3.0.7/ + make + +5. install tcl + + sudo apt-get install tcl + +6. make test + + make test + +7. make install + + make PREFIX=/home/server/software/redisInstall install + +#### 配置与启动 #### +1. 进入目录 + + cd /home/server/software/redisInstall/bin + +2. 文件介绍 + + + + + + + + + + + + + + + + + + + + + + + + + + +
文件名介绍
redis-benchmarkredis性能测试工具
redis-check-aofaof日志检查工具
redis-check-dump rdb日志检查工具
redis-cliredis客户端连接
redis-serverredis服务进程
+ +3. 复制配置文件 + + cp ../../redis-3.0.7/redis.conf ../ + +4. 启动redis服务 + + cd .. + ./bin/redis-server ./redis.conf + +# + 26049:M 24 Mar 16:59:56.768 # You requested maxclients of 10000 requiring at + least 10032 max file descriptors. + 26049:M 24 Mar 16:59:56.768 # Redis can't set maximum open files to 10032 + because of OS error: Operation not permitted. + 26049:M 24 Mar 16:59:56.768 # Current maximum open files is 4096. maxclients + has been reduced to 4064 to compensate for low ulimit. If you need higher + maxclients increase 'ulimit -n'. + _._ + _.-``__ ''-._ + _.-`` `. `_. ''-._ Redis 3.0.7 (00000000/0) 64 bit + .-`` .-```. ```\/ _.,_ ''-._ + ( ' , .-` | `, ) Running in standalone mode + |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 + | `-._ `._ / _.-' | PID: 26049 + `-._ `-._ `-./ _.-' _.-' + |`-._`-._ `-.__.-' _.-'_.-'| + | `-._`-._ _.-'_.-' | http://redis.io + `-._ `-._`-.__.-'_.-' _.-' + |`-._`-._ `-.__.-' _.-'_.-'| + | `-._`-._ _.-'_.-' | + `-._ `-._`-.__.-'_.-' _.-' + `-._ `-.__.-' _.-' + `-._ _.-' + `-.__.-' + +# + +5. 开启后台进程配置 + + vim redis.conf + + # 修改如下字段,重启server + daemonize yes + +6. 客户端测试连接 + + ./bin/redis-cli + + set test "This is a redis test" + get test + +#### redis.conf 配置详解 #### + + ######################### 通用 ######################### + + # 启动后台进程 + daemonize yes + + # 后台进程的pid文件存储位置 + pidfile /var/run/redis.pid + + # 默认监听端口 + port 6379 + + # 在高并发的环境中,为避免慢客户端的连接问题,需要设置一个高速后台日志 + tcp-backlog 511 + + # 只接受以下绑定的IP请求 + # Examples: + # bind 192.168.1.100 10.0.0.1 + bind 127.0.0.1 + + # 设置unix监听,默认为空 + # unixsocket /tmp/redis.sock + # unixsocketperm 700 + + #客户端空闲多长时间,关闭链接,0表示不关闭 + timeout 0 + + # TCP keepalive. + # 如果是非零值,当失去链接时,会使用SO_KEEPALIVE发送TCP ACKs 到客户端。 + # 这个参数有两个作用: + # 1.检测断点。 + # 2.从网络中间设备来看,就是保持链接 + # 在Linux上,设定的时间就是发送ACKs的周期。 + # 注意:达到双倍的设定时间才会关闭链接。在其他内核上,周期依赖于内核设置。 + # 一个比较合理的值为60s + tcp-keepalive 0 + + # 指定日志级别,以下记录信息依次递减 + # debug用于开发/测试 + # verbose没debug那么详细 + # notice适用于生产线 + # warning只记录非常重要的信息 + loglevel notice + + #日志文件名称,如果为stdout则输出到标准输出端,如果是以后台进程运行则不产生日志 + logfile "" + + # 要想启用系统日志记录器,设置一下选项为yes + # syslog-enabled no + + # 指明syslog身份 + # syslog-ident redis + + # 指明syslog设备。必须是一个用户或者是local0 ~ local7之一 + # syslog-facility local0 + + #设置数据库数目,第一个数据库编号为:0 + databases 16 + + ######################### 快照 ######################### + + # 在什么条件下保存数据库到磁盘,条件可以有很多个,满足任何一个条件都会进行快照存储 + # 在900秒之内有一次key的变化 + save 900 1 + # 在300秒之内,有10个key的变化 + save 300 10 + # 在60秒之内有10000个key变化 + save 60 10000 + + # 当持久化失败的时候,是否继续提供服务 + stop-writes-on-bgsave-error yes + + # 当写入磁盘时,是否使用LZF算法压缩数据,默认为yes + rdbcompression yes + + # 是否添加CRC64校验到每个文件末尾--花费时间保证安全 + rdbchecksum yes + + # 磁盘上数据库的保存名称 + dbfilename dump.rdb + + # Redis工作目录,以上数据库保存文件和AOF日志都会写入此目录 + dir ./ + + ######################### 主从同步 ######################### + + # 主从复制,当本机是slave时配置 + # slaveof + + # 当主机需要密码验证时候配置 + # masterauth + + # 当slave和master丢失链接,或正处于同步过程中。是否响应客户端请求 + # 设置为yes表示响应 + # 设置为no,直接返回"SYNC with master in progress"(正在和主服务器同步中) + slave-serve-stale-data yes + + # 设置slave是否为只读。 + # 注意:即使slave设置为只读,也不能令其暴露在不受信任的网络环境中 + slave-read-only yes + + # 无硬盘复制功能 + repl-diskless-sync no + + # 等待多个slave一起来请求之间的间隔时间 + repl-diskless-sync-delay 5 + + # 设置slave给master发送ping的时间间隔 + # repl-ping-slave-period 10 + + # 设置数据传输I/O,主机数据、ping响应超时时间,默认60s + # 这个时间一定要比repl-ping-slave-period大,否则会不断检测到超时 + # repl-timeout 60 + + # 是否在SYNC后slave socket上禁用TCP_NODELAY? + # 如果你设置为yes,Redis会使用少量TCP报文和少量带宽发送数据给slave。 + # 但是这样会在slave端出现延迟。如果使用Linux内核的默认设置,大概40毫秒。 + # 如果你设置为no,那么在slave端研究就会减少但是同步带宽要增加。 + # 默认我们是为低延迟优化的。 + # 但是如果流量特别大或者主从服务器相距比较远,设置为yes比较合理。 + repl-disable-tcp-nodelay no + + # 设置复制的后台日志大小。 + # 复制的后台日志越大, slave 断开连接及后来可能执行部分复制花的时间就越长。 + # 后台日志在至少有一个 slave 连接时,仅仅分配一次。 + # repl-backlog-size 1mb + + # 在 master 不再连接 slave 后,后台日志将被释放。下面的配置定义从最后一个 slave 断开连接后需要释放的时间(秒)。 + # 0 意味着从不释放后台日志 + # repl-backlog-ttl 3600 + + # 设置slave优先级,默认为100 + # 当主服务器不能正确工作的时候,数字低的首先被提升为主服务器,但是0是禁用选择 + slave-priority 100 + + # 如果少于 N 个 slave 连接,且延迟时间 <=M 秒,则 master 可配置停止接受写操作。 + # 例如需要至少 3 个 slave 连接,且延迟 <=10 秒的配置: + # min-slaves-to-write 3 + # min-slaves-max-lag 10 + # 设置 0 为禁用 + # 默认 min-slaves-to-write 为 0 (禁用), min-slaves-max-lag 为 10 + + ######################### 安全 ######################### + + # 设置客户端连接密码,因为Redis响应速度可以达到每秒100w次,所以密码要特别复杂 + # requirepass 1413 + + # 命令重新命名,或者禁用。 + # 重命名命令为空字符串可以禁用一些危险命令比如:FLUSHALL删除所有数据 + # 需要注意的是,写入AOF文件或传送给slave的命令别名也许会引起一些问题 + # rename-command CONFIG "" + + + # 设置客户端连接密码,因为Redis响应速度可以达到每秒100w次,所以密码要特别复杂 + requirepass 1413 + + # 命令重新命名,或者禁用。 + # 重命名命令为空字符串可以禁用一些危险命令比如:FLUSHALL删除所有数据 + # 需要注意的是,写入AOF文件或传送给slave的命令别名也许会引起一些问题 + # rename-command CONFIG "" + + ######################### 限制 ######################### + + # 设置最多链接客户端数量,默认为10000。 + # 实际可以接受的请求数目为设置值减去32,这32是Redis为内部文件描述符保留的 + # maxclients 10000 + + # 设置最多链接客户端数量,默认为10000。 + # 实际可以接受的请求数目为设置值减去32,这32是Redis为内部文件描述符保留的 + # maxclients 10000 + # 设置最大使用内存数量,在把Redis当作LRU缓存时特别有用。 + # 设置的值要比系统能使用的值要小 + # 因为当启用删除算法时,slave输出缓存也要占用内存 + # maxmemory + + #达到最大内存限制时,使用何种删除算法 + # volatile-lru 使用LRU算法移除带有过期标致的key + # allkeys-lru -> 使用LRU算法移除任何key + # volatile-random -> 随机移除一个带有过期标致的key + # allkeys-random -> 随机移除一个key + # volatile-ttl -> 移除最近要过期的key + # noeviction -> 不删除key,当有写请求时,返回错误 + #默认设置为volatile-lru + # maxmemory-policy noeviction + + # LRU和最小TTL算法没有精确的实现 + # 为了节省内存只在一个样本范围内选择一个最近最少使用的key,可以设置这个样本大小 + # maxmemory-samples 5 + + ######################### AO模式 ######################### + + # AOF和RDB持久化可以同时启用 + # Redis启动时候会读取AOF文件,AOF文件有更好的持久化保证 + appendonly no + + # AOF的保存名称,默认为appendonly.aof + appendfilename "appendonly.aof" + + # 设置何时写入追加日志,又三种模式 + # no:表示由操作系统决定何时写入。性能最好,但可靠性最低 + # everysec:表示每秒执行一次写入。折中方案,推荐 + # always:表示每次都写入磁盘。性能最差,比上面的安全一些 + # appendfsync always + appendfsync everysec + # appendfsync no + + # 当AOF同步策略设定为alway或everysec + # 当后台存储进程(后台存储或者AOF日志后台写入)会产生很多磁盘开销 + # 某些Linux配置会使Redis因为fsync()调用产生阻塞很久 + # 现在还没有修复补丁,甚至使用不同线程进行fsync都会阻塞我们的同步write(2)调用。 + # 为了缓解这个问题,使用以下选项在一个BGSAVE或BGREWRITEAOF运行的时候 + # 可以阻止fsync()在主程序中被调用, + no-appendfsync-on-rewrite no + + # AOF自动重写(合并命令,减少日志大小) + # 当AOF日志大小增加到一个特定比率,Redis调用BGREWRITEAOF自动重写日志文件 + # 原理:Redis 会记录上次重写后AOF文件的文件大小。 + # 如果刚启动,则记录启动时AOF大小 + # 这个基本大小会用来和当前大小比较。如果当前大小比特定比率大,就会触发重写。 + # 你也需要指定一个AOF需要被重写的最小值,这样会避免达到了比率。 + # 但是AOF文件还很小的情况下重写AOF文件。 + # 设置为0禁用自动重写 + auto-aof-rewrite-percentage 100 + auto-aof-rewrite-min-size 64mb + + #redis在启动时可以加载被截断的AOF文件,而不需要先执行 redis-check-aof 工具 + aof-load-truncated yes + + ######################### LUA脚本 ######################### + + # Lua脚本的最大执行时间,单位毫秒 + # 超时后会报错,并且计入日志 + # 当一个脚本运行时间超过了最大执行时间 + # 只有SCRIPT KILL和 SHUTDOWN NOSAVE两个命令可以使用。 + # SCRIPT KILL用于停止没有调用写命令的脚本。 + # SHUTDOWN NOSAVE是唯一的一个,在脚本的写命令正在执行 + # 用户又不想等待脚本的正常结束的情况下,关闭服务器的方法。 + # 以下选项设置为0或负数就会取消脚本执行时间限制 + lua-time-limit 5000 + + ####################### redis集群 ######################## + + # 是否启用集群 + # cluster-enabled yes + + # 集群配置文件 + # 集群配置变更后会自动写入改文件 + # cluster-config-file nodes-6379.conf + + # 节点互连超时的阀值 + # 节点超时时间,超过该时间无法连接主要Master节点后,会停止接受查询服务 + # cluster-node-timeout 15000 + + # 控制从节点FailOver相关的设置,设为0,从节点会一直尝试启动FailOver. + # 设为正数,失联大于一定时间(factor*节点TimeOut),不再进行FailOver + # cluster-slave-validity-factor 10 + + # 最小从节点连接数 + # cluster-migration-barrier 1 + + # 默认为Yes,丢失一定比例Key后(可能Node无法连接或者挂掉),集群停止接受写操作 + # 设置为No,集群丢失Key的情况下仍提供查询服务 + # cluster-require-full-coverage yes + + ######################### 慢查询 ######################### + + # Redis慢查询日志记录超过设定时间的查询,且只记录执行命令的时间 + # 不记录I/O操作,比如:和客户端交互,发送回复等。 + # 时间单位为微妙,1000000微妙 = 1 秒 + # 设置为负数会禁用慢查询日志,设置为0会记录所有查询命令 + slowlog-log-slower-than 10000 + + # 日志长度没有限制,但是会消耗内存。超过日志长度后,最旧的记录会被移除 + # 使用SLOWLOG RESET命令可以回收内存 + slowlog-max-len 128 + + ######################### 延迟监测 ######################### + + # 系统只记录超过设定值的操作,单位是毫秒,0表示禁用该功能 + # 可以通过命令“CONFIG SET latency-monitor-threshold ” 直接设置而不需要重启redis + latency-monitor-threshold 0 + + ######################### 事件通知 ######################### + # 当事件发生时, Redis 可以通知 Pub/Sub 客户端。 + # 可以在下表中选择 Redis 要通知的事件类型。事件类型由单个字符来标识: + # K Keyspace 事件,以 _keyspace@_ 的前缀方式发布 + # E Keyevent 事件,以 _keysevent@_ 的前缀方式发布 + # g 通用事件(不指定类型),像 DEL, EXPIRE, RENAME, … + # $ String 命令 + # s Set 命令 + # h Hash 命令 + # z 有序集合命令 + # x 过期事件(每次 key 过期时生成) + # e 清除事件(当 key 在内存被清除时生成) + # A g$lshzxe 的别称,因此 ”AKE” 意味着所有的事件 + # notify-keyspace-events 带一个由 0 到多个字符组成的字符串参数。空字符串意思是通知被禁用。 + # 例子:启用 list 和通用事件: + # notify-keyspace-events Elg + # 默认所用的通知被禁用,因为用户通常不需要改特性,并且该特性会有性能损耗。 + # 注意如果你不指定至少 K 或 E 之一,不会发送任何事件。 + notify-keyspace-events "" + #notify-keyspace-events AKE + + ######################### 高级设置 ######################### + + # 当有少量条目的时候,哈希使用高效内存数据结构。最大的条目也不能超过设定的阈值。# “少量”定义如下: + hash-max-ziplist-entries 512 + hash-max-ziplist-value 64 + + # 和哈希编码一样,少量列表也以特殊方式编码节省内存。“少量”设定如下: + list-max-ziplist-entries 512 + list-max-ziplist-value 64 + + # 集合只在以下情况下使用特殊编码来节省内存 + # -->集合全部由64位带符号10进制整数构成的字符串组成 + # 下面的选项设置这个特殊集合的大小。 + set-max-intset-entries 512 + + # 当有序集合的长度和元素设定为以下数字时,又特殊编码节省内存 + zset-max-ziplist-entries 128 + zset-max-ziplist-value 64 + + + # HyperLogLog 稀疏表示字节限制 + # 这个限制包含了16个字节的头部,当一个HyperLogLog使用sparse representation + # 超过了这个显示,它就会转换到dense representation上 + hll-sparse-max-bytes 3000 + + # 哈希刷新使用每100个CPU毫秒中的1毫秒来帮助刷新主哈希表(顶级键值映射表)。 + # Redis哈希表使用延迟刷新机制,越多操作,越多刷新。 + # 如果服务器空闲,刷新操作就不会进行,更多内存会被哈希表占用 + # 默认每秒进行10次主字典刷新,释放内存。 + # 如果你有硬性延迟需求,偶尔2毫秒的延迟无法忍受的话。设置为no + # 否则设置为yes + activerehashing yes + + # 客户端输出缓存限制强迫断开读取速度比较慢的客户端 + # 有三种类型的限制 + # normal -> 正常 + # slave -> slave和 MONITOR + # pubsub -> 客户端至少订阅了一个频道或者模式 + # 客户端输出缓存限制语法如下(时间单位:秒) + # client-output-buffer-limit <类别> <强制限制> <软性限制> <软性时间> + # 达到强制限制缓存大小,立刻断开链接。 + # 达到软性限制,仍然会有软性时间大小的链接时间 + # 默认正常客户端无限制,只有请求后,异步客户端数据请求速度快于它能读取数据的速度 + # 订阅模式和主从客户端又默认限制,因为它们都接受推送。 + # 强制限制和软性限制都可以设置为0来禁用这个特性 + client-output-buffer-limit normal 0 0 0 + client-output-buffer-limit slave 256mb 64mb 60 + client-output-buffer-limit pubsub 32mb 8mb 60 + + # 设置Redis后台任务执行频率,比如清除过期键任务。 + # 设置范围为1到500,默认为10.越大CPU消耗越大,延迟越小。 + # 建议不要超过100 + hz 10 + + # 当子进程重写AOF文件,以下选项开启时,AOF文件会每产生32M数据同步一次。 + # 这有助于更快写入文件到磁盘避免延迟 + aof-rewrite-incremental-fsync yes \ No newline at end of file diff --git a/Redis_Monitor/README.md b/Redis_Monitor/README.md new file mode 100644 index 0000000..9ab354f --- /dev/null +++ b/Redis_Monitor/README.md @@ -0,0 +1,30 @@ +### Redis Monitor配置安装 + +#### required + +* [python 2.7+](https://www.python.org/ftp/python/2.7.12/python-2.7.12.amd64.msi) +* [redis-monitor](https://github.com/NetEaseGame/redis-monitor) + +#### install + +1.先[下载](https://www.python.org/ftp/python/2.7.12/python-2.7.12.amd64.msi)Window64位版本的Python,安装,python命令会自动加入到系统环境中 + +2.打开一个命令行窗口 + +* `pip install redis-monitor` + +* 系统可能会提醒你pip的版本太低,执行`python -m pip install --upgrade pip` + +* 再执行`pip install redis-monitor` + +* `redis-monitor init` + +* `redis-monitor start` + +![](https://github.com/silence940109/Java/blob/master/Redis_Monitor/image/pip.jpg) + +ok启动完毕 + +![](https://github.com/silence940109/Java/blob/master/Redis_Monitor/image/start.jpg) + + diff --git a/Redis_Monitor/image/pip.jpg b/Redis_Monitor/image/pip.jpg new file mode 100644 index 0000000..4e8ff5c Binary files /dev/null and b/Redis_Monitor/image/pip.jpg differ diff --git a/Redis_Monitor/image/start.jpg b/Redis_Monitor/image/start.jpg new file mode 100644 index 0000000..b3bde59 Binary files /dev/null and b/Redis_Monitor/image/start.jpg differ diff --git a/Redis_State_Live/README.md b/Redis_State_Live/README.md new file mode 100644 index 0000000..6f1af30 --- /dev/null +++ b/Redis_State_Live/README.md @@ -0,0 +1,168 @@ +### Redis监控工具-Redis-Stat、RedisLive ### + +Redis-stat(Ruby)和Redis Live(python)是两款Redis监控工具,下面将介绍如何安装部署这两个工具,监控Redis运行情况 + +#### 测试环境: #### + + Ubuntu 14.04 LTS x64 + Redis redis-3.0.7.tar.gz + Ruby ruby 1.9.3 + Python 2.7.6 + +#### Redis 安装 #### +Redis安装请参照:[Redis安装与配置](https://github.com/scalad/Note/tree/master/Redis_Config) + +#### Redis-stat 安装部署 #### +redis-stat is a simple Redis monitoring tool written in Ruby. + +It is based on INFO command of Redis, and thus generally won’t affect the performance of the Redis instance unlike the other monitoring tools based on MONITOR command. + +redis-stat allows you to monitor Redis instances + +* either with vmstat-like output from the terminal +* or with the dashboard page served by its embedded web server. +* +通常来说,不会像基于MONITOR命令的监控工具一样,对Redis本身有性能上的影响 + +[Github地址](https://github.com/junegunn/redis-stat) + +#### 卸载原有Ruby #### + sudo apt-get autoremove --purge ruby* + +#### 安装Ruby #### + sudo apt-get install ruby-full + +#### 安装Redis-stat #### + gem install redis-stat + +#### 基本使用 #### +1. redis-stat命令参数 + + usage: redis-stat [HOST[:PORT] ...] [INTERVAL [COUNT]] + + -a, --auth=PASSWORD 设置密码 + -v, --verbose 显示更多信息 + --style=STYLE 输出编码类型: unicode|ascii + --no-color 取消ANSI颜色编码 + --csv=OUTPUT_CSV_FILE_PATH 以CSV格式存储结果 + --es=ELASTICSEARCH_URL 把结果发送到 ElasticSearch: [http://]HOST[:PORT][/INDEX] + + --server[=PORT] 运行redis-stat的web server (默认端口号: 63790) + --daemon 使得redis-stat成为进程。必须使用 --server 选项 + + --version 显示版本号 + --help 显示帮助信息 + +2. redis-stat运行命令行监控 + redis-stat + redis-stat 1 + redis-stat 1 10 + redis-stat --verbose + redis-stat localhost:6380 1 10 + redis-stat localhost localhost:6380 localhost:6381 5 + redis-stat localhost localhost:6380 1 10 --csv=/tmp/outpu.csv --verbose + +3. Server端运行界面 + +![](https://github.com/scalad/Note/blob/master/Redis_State_Live/image/redis-stat-0.3.0.png) + +4. Web界面中的redis-stat + +当设置–server选项之后,redis-stat会在后台启动一个嵌入式的web server(默认端口号:63790),可以让你在浏览器中监控Redis + + redis-stat --server + redis-stat --verbose --server=8080 5 + + # redis-stat server can be daemonized + redis-stat --server --daemon + + # Kill the daemon + killall -9 redis-stat-daemon + +5. Web端运行界面 + +然后在你的浏览器中输入: + + http://你的Redis IP:63790 + +![](https://github.com/scalad/Note/blob/master/Redis_State_Live/image/redis-stat-web.png) + +### RedisLive 安装部署 ### + +Redis Live is a dashboard application with a number of useful widgets. At it’s heart is a monitoring script that periodically issues INFO and MONITOR command to the redis instances and stores the data for analytics. + +长时间运行对Redis性能有所影响 + +[Github地址](https://github.com/nkrode/RedisLive) + +[Real time dashboard for redis](http://www.nkrode.com/article/real-time-dashboard-for-redis) + +#### 安装运行依赖 #### +1. tornado + pip install tornado + +2. redis.py + pip install redis + +3. python-dateutil + pip install python-dateutil + +#### 下载RedisLive #### + git clone https://github.com/kumarnitin/RedisLive.git + +#### conf配置 #### +进入src目录 + + cp redis-live.conf.example ./redis-live.conf + + vim redis-live.conf + + { + "RedisServers": + [ + { + "server" : "你的Redis IP地址", + "port" : 6379 + } + ........ + 可以多个 + ], + + + "DataStoreType" : "redis", + + "RedisStatsServer": + { + "server" : "你的Redis 监控IP地址", + "port" : 6379 + }, + + "SqliteStatsStore" : + { + "path": "to your sql lite file" + } + } + +其中RedisServers为你要监控的redis实例,可以添加多个,RedisStatsServer是存储RedisLive监控数据的实例,如果redis有密码,可以在实例配置中加入password选项;如果没有存储RedisLive数据的实例,需要将DataStoreType改成”DataStoreType” : “sqlite”这种设置 + +#### 启动RedisLive #### +1. 启动监控脚本,监控120秒,duration参数是以秒为单位 + + sudo ./redis-monitor.py --duration=120 + +2. 启动webserver。 + +RedisLive使用tornado作为web服务器,所以不需要单独安装服务器 + +Tornado web server 是使用Python编写出來的一个极轻量级、高可伸缩性和非阻塞IO的Web服务器软件 + + sudo ./redis-live.py + +#### Web运行界面 #### +然后在你的浏览器中输入: + + http://你的Redis IP:8888/index.html + +![](https://github.com/scalad/Note/blob/master/Redis_State_Live/image/redis-live.png) + +本文来自[http://wxmimperio.tk/2016/02/25/Redis-Monitor-Tools/](http://wxmimperio.tk/2016/02/25/Redis-Monitor-Tools/) \ No newline at end of file diff --git a/Redis_State_Live/image/redis-live.png b/Redis_State_Live/image/redis-live.png new file mode 100644 index 0000000..1769e3f Binary files /dev/null and b/Redis_State_Live/image/redis-live.png differ diff --git a/Redis_State_Live/image/redis-stat-0.3.0.png b/Redis_State_Live/image/redis-stat-0.3.0.png new file mode 100644 index 0000000..75be9bd Binary files /dev/null and b/Redis_State_Live/image/redis-stat-0.3.0.png differ diff --git a/Redis_State_Live/image/redis-stat-web.png b/Redis_State_Live/image/redis-stat-web.png new file mode 100644 index 0000000..00e9746 Binary files /dev/null and b/Redis_State_Live/image/redis-stat-web.png differ diff --git a/Scala_Java_Override_Variable_Parameter/README.md b/Scala_Java_Override_Variable_Parameter/README.md new file mode 100644 index 0000000..f74b273 --- /dev/null +++ b/Scala_Java_Override_Variable_Parameter/README.md @@ -0,0 +1,48 @@ +### Scala重写Java可变参数方法问题 ### + +```Java +public interface KeyGenerator { + /** + * Generate a key for the given method and its parameters. + * @param target the target instance + * @param method the method being called + * @param params the method parameters (with any var-args expanded) + * @return a generated key + */ + Object generate(Object target, Method method, Object... params); +} +``` + +如以上的Java接口,Java中的可变参数是以...出现,而Scala则不同,Scala的可变参数是以*出现的,如下: + +```Scala +def generate(target:Object, method: Method, params: Object*): Object = { + //省略 +} +``` + +如果你使用IDE自动生成的方法参数如下: + +```Scala +def generate(target:Any, method: Method, params: Array[AnyRef]): Object = { + //省略 +} +``` + +在使用Scala重写了KeyGenerator接口的generate方法时编译器总是无法通过,即在Scala中使用params: Object*或者是Array[AnyRef]是无法替代Object... params的,注意Scala中的Any和AnyRef都是Object类型,classOf[Any]和classOf[AnyRef]都是Object类型,解决办法是你只需要将Array[AnyRef]修改成AnyRef*就可以编译通过了。 + +```Scala +def wiselyKeyGenerator(): KeyGenerator = { + new KeyGenerator() { + override protected def generate(target: Any, method: Method, params: AnyRef*): Object = { + var sb = new StringBuilder() + sb.append(target.getClass().getName()) + sb.append(method.getName()) + for(param <- params) { + sb.append(param.toString()) + } + sb.toString() + } + }; +} +``` \ No newline at end of file diff --git a/Scala_Map_List_Array/README.md b/Scala_Map_List_Array/README.md new file mode 100644 index 0000000..056570b --- /dev/null +++ b/Scala_Map_List_Array/README.md @@ -0,0 +1,246 @@ +### scala map/list/array/的常用内置遍历操作总结 + +Scala 是面向函数的,所以在集合函数里,它很轻易地提供了非常丰富遍历操作,数组变换操作。这对于我们数据挖掘,爬虫,文本处理等都非常有帮助。有了这些内置的遍历变换操作,我们再也不用像java那样写一个笨笨的for循环来迭代,然后还要定义一些规范的函数来迎合需求。而scala不同,随时随地就可以写一个你自己想要的函数,而不需要严格地定义它,规范它。(注意,scala里一切事物皆函数,一切函数皆对象) + +下面将提供一些集合内置遍历方法用法,熟练运用,常常使得代码精简整洁。 + +### 一、常用遍历变换操作 +#### 1.map 遍历 +map[B](f: (A) ⇒ B): List[B] + +定义一个变换,把该变换应用到列表的每个元素中,原列表不变,返回一个新的列表数据 + +Example1 平方变换 + val nums = List(1,2,3) + val square = (x: Int) => x*x + val squareNums1 = nums.map(num => num*num) //List(1,4,9) + val squareNums2 = nums.map(math.pow(_,2)) //List(1,4,9) + val squareNums3 = nums.map(square) //List(1,4,9) + +Example2 保存文本数据中的某几列 + + val text = List("Homeway,25,Male","XSDYM,23,Female") + val usersList = text.map(_.split(",")(0)) + val usersWithAgeList = text.map(line => { + val fields = line.split(",") + val user = fields(0) + val age = fields(1).toInt + (user,age) + }) + +#### 2.flatMap,flatten扁平化 +flatten: flatten[B]: List[B] 对列表的列表进行平坦化操作 flatMap: flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): List[B] map之后对结果进行flatten + +定义一个变换f, 把f应用列表的每个元素中,每个f返回一个列表,最终把所有列表连结起来。 + + val text = List("A,B,C","D,E,F") + val textMapped = text.map(_.split(",").toList) // List(List("A","B","C"),List("D","E","F")) + val textFlattened = textMapped.flatten // List("A","B","C","D","E","F") + val textFlatMapped = text.flatMap(_.split(",").toList) // List("A","B","C","D","E","F") + +#### 3.reduce遍历简化 +定义一个变换f, f把两个列表的元素合成一个,遍历列表,最终把列表合并成单一元素 + +Example 列表求和 + + val nums = List(1,2,3) + val sum1 = nums.reduce((a,b) => a+b) //6 + val sum2 = nums.reduce(_+_) //6 + val sum3 = nums.sum //6 + + reduceLeft: reduceLeft[B >: A](f: (B, A) ⇒ B): B + + reduceRight: reduceRight[B >: A](op: (A, B) ⇒ B): B + +reduceLeft从列表的左边往右边应用reduce函数,reduceRight从列表的右边往左边应用reduce函数 + +Example + + val nums = List(2.0,2.0,3.0) + val resultLeftReduce = nums.reduceLeft(math.pow) // = pow( pow(2.0,2.0) , 3.0) = 64.0 + val resultRightReduce = nums.reduceRight(math.pow) // = pow(2.0, pow(2.0,3.0)) = 256.0 + +#### 4.Fold折叠 +fold: fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1 带有初始值的reduce,从一个初始值开始,从左向右将两个元素合并成一个,最终把列表合并成单一元素。 + +foldLeft: foldLeft[B](z: B)(f: (B, A) ⇒ B): B 带有初始值的reduceLeft + +foldRight: foldRight[B](z: B)(op: (A, B) ⇒ B): B 带有初始值的reduceRight + + val nums = List(2,3,4) + val sum = nums.fold(1)(_+_) // = 1+2+3+4 = 9 + + val nums = List(2.0,3.0) + val result1 = nums.foldLeft(4.0)(math.pow) // = pow(pow(4.0,2.0),3.0) = 4096 + val result2 = nums.foldRight(1.0)(math.pow) // = pow(1.0,pow(2.0,3.0)) = 8.0 + +#### 5.sort排序 +sortBy: sortBy[B](f: (A) ⇒ B)(implicit ord: math.Ordering[B]): List[A] 按照应用函数f之后产生的元素进行排序 + +sorted: sorted[B >: A](implicit ord: math.Ordering[B]): List[A] 按照元素自身进行排序 + +sortWith: sortWith(lt: (A, A) ⇒ Boolean): List[A] 使用自定义的比较函数进行排序 + + val nums = List(1,3,2,4) + val sorted = nums.sorted //List(1,2,3,4) + + val users = List(("HomeWay",25),("XSDYM",23)) + val sortedByAge = users.sortBy{case(user,age) => age} //List(("XSDYM",23),("HomeWay",25)) + val sortedWith = users.sortWith{case(user1,user2) => user1._2 < user2._2} //List(("XSDYM",23),("HomeWay",25)) + +#### 6.filter过滤 +count(p: (A) ⇒ Boolean): Int + +计算列表中所有满足条件p的元素的个数,等价于 filter(p).length + +val nums = List(-1,-2,0,1,2) val plusCnt1 = nums.count( > 0) val plusCnt2 = nums.filter( > 0).length + +#### 8.diff,union,intersect两个集合的交集联结 + +diff:diff(that: collection.Seq[A]): List[A] 保存列表中那些不在另外一个列表中的元素,即从集合中减去与另外一个集合的交集 + +union : union(that: collection.Seq[A]): List[A] 与另外一个列表进行连结 + +intersect: intersect(that: collection.Seq[A]): List[A] 与另外一个集合的交集 + + val nums1 = List(1,2,3) + val nums2 = List(2,3,4) + val diff1 = nums1 diff nums2 // List(1) + val diff2 = nums2.diff(num1) // List(4) + val union1 = nums1 union nums2 // List(1,2,3,2,3,4) + val union2 = nums2 ++ nums1 // List(2,3,4,1,2,3) + val intersection = nums1 intersect nums2 //List(2,3) + +#### 9.distinct去重 + +distinct: List[A] 保留列表中非重复的元素,相同的元素只会被保留一次 + + val list = List("A","B","C","A","B") val distincted = list.distinct // List("A","B","C") + +#### 10.group分组 + +groupBy : groupBy[K](f: (A) ⇒ K): Map[K, List[A]] 将列表进行分组,分组的依据是应用f在元素上后产生的新元素 +grouped: grouped(size: Int): Iterator[List[A]] 按列表按照固定的大小进行分组 + + val data = List(("HomeWay","Male"),("XSDYM","Femail"),("Mr.Wang","Male")) + val group1 = data.groupBy(_._2) // = Map("Male" -> List(("HomeWay","Male"),("Mr.Wang","Male")),"Female" -> List(("XSDYM","Femail"))) + val group2 = data.groupBy{case (name,sex) => sex} // = Map("Male" -> List(("HomeWay","Male"),("Mr.Wang","Male")),"Female" -> List(("XSDYM","Femail"))) + val fixSizeGroup = data.grouped(2).toList // = Map("Male" -> List(("HomeWay","Male"),("XSDYM","Femail")),"Female" -> List(("Mr.Wang","Male"))) + +#### 11.scan扫描 + +scan[B >: A, That](z: B)(op: (B, B) ⇒ B)(implicit cbf: CanBuildFrom[List[A], B, That]): That + +由一个初始值开始,从左向右,进行积累的op操作,这个比较难解释,具体的看例子吧。 + + val nums = List(1,2,3) + val result = nums.scan(10)(_+_) // List(10,10+1,10+1+2,10+1+2+3) = List(10,11,12,13) + +scanLeft: scanLeft[B, That](z: B)(op: (B, A) ⇒ B)(implicit bf: CanBuildFrom[List[A], B, That]): That + +scanRight: scanRight[B, That](z: B)(op: (A, B) ⇒ B)(implicit bf: CanBuildFrom[List[A], B, That]): That + +scanLeft: 从左向右进行scan函数的操作,scanRight:从右向左进行scan函数的操作 + + val nums = List(1.0,2.0,3.0) + val result = nums.scanLeft(2.0)(math.pow) // List(2.0,pow(2.0,1.0), pow(pow(2.0,1.0),2.0),pow(pow(pow(2.0,1.0),2.0),3.0) = List(2.0,2.0,4.0,64.0) + val result = nums.scanRight(2.0)(math.pow) // List(2.0,pow(3.0,2.0), pow(2.0,pow(3.0,2.0)), pow(1.0,pow(2.0,pow(3.0,2.0))) = List(1.0,512.0,9.0,2.0) + +#### 12.take截取 + +take : takeRight(n: Int): List[A] 提取列表的前n个元素 takeRight: takeRight(n: Int): List[A] 提取列表的最后n个元素 takeWhile: takeWhile(p: (A) ⇒ Boolean): List[A] 从左向右提取列表的元素,直到条件p不成立 + + val nums = List(1,1,1,1,4,4,4,4) + val left = nums.take(4) // List(1,1,1,1) + val right = nums.takeRight(4) // List(4,4,4,4) + val headNums = nums.takeWhile( _ == nums.head) // List(1,1,1,1) + +#### 13.drop丢弃 + +drop: drop(n: Int): List[A] 丢弃前n个元素,返回剩下的元素 dropRight: dropRight(n: Int): List[A] 丢弃最后n个元素,返回剩下的元素 dropWhile: dropWhile(p: (A) ⇒ Boolean): List[A] 从左向右丢弃元素,直到条件p不成立 + + val nums = List(1,1,1,1,4,4,4,4) + val left = nums.drop(4) // List(4,4,4,4) + val right = nums.dropRight(4) // List(1,1,1,1) + val tailNums = nums.dropWhile( _ == nums.head) // List(4,4,4,4) + +#### 14.span,spliAt,partition拆分 + +span : span(p: (A) ⇒ Boolean): (List[A], List[A]) 从左向右应用条件p进行判断,直到条件p不成立,此时将列表分为两个列表 + +splitAt: splitAt(n: Int): (List[A], List[A]) 将列表分为前n个,与,剩下的部分 + +partition: partition(p: (A) ⇒ Boolean): (List[A], List[A]) 将列表分为两部分,第一部分为满足条件p的元素,第二部分为不满足条件p的元素 + + val nums = List(1,1,1,2,3,2,1) + val (prefix,suffix) = nums.span( _ == 1) // prefix = List(1,1,1), suffix = List(2,3,2,1) + val (prefix,suffix) = nums.splitAt(3) // prefix = List(1,1,1), suffix = List(2,3,2,1) + val (prefix,suffix) = nums.partition( _ == 1) // prefix = List(1,1,1,1), suffix = List(2,3,2 + +#### 15.padTo批量扩展 + +padTo(len: Int, elem: A): List[A] + +将列表扩展到指定长度,长度不够的时候,使用elem进行填充,否则不做任何操作。 + + val nums = List(1,1,1) + val padded = nums.padTo(6,2) // List(1,1,1,2,2,2) + +#### 16.combinations,permutations随机组合 + +combinations: combinations(n: Int): Iterator[List[A]] 取列表中的n个元素进行组合,返回不重复的组合列表,结果一个迭代器 + +permutations: permutations: Iterator[List[A]] 对列表中的元素进行排列,返回不重得的排列列表,结果是一个迭代器 + + val nums = List(1,1,3) + val combinations = nums.combinations(2).toList //List(List(1,1),List(1,3)) + val permutations = nums.permutations.toList // List(List(1,1,3),List(1,3,1),List(3,1,1)) + +#### 17.zip打包 + +zip: zip[B](that: GenIterable[B]): List[(A, B)] 与另外一个列表进行拉链操作,将对应位置的元素组成一个pair,返回的列表长度为两个列表中短的那个 + +zipAll: zipAll[B](that: collection.Iterable[B], thisElem: A, thatElem: B): List[(A, B)] 与另外一个列表进行拉链操作,将对应位置的元素组成一个pair,若列表长度不一致,自身列表比较短的话使用thisElem进行填充,对方列表较短的话使用thatElem进行填充 + +zipWithIndex:zipWithIndex: List[(A, Int)] 将列表元素与其索引进行拉链操作,组成一个pair + +unzip: unzip[A1, A2](implicit asPair: (A) ⇒ (A1, A2)): (List[A1], List[A2]) 解开拉链操作 + +unzip3: unzip3[A1, A2, A3](implicit asTriple: (A) ⇒ (A1, A2, A3)): (List[A1], List[A2], List[A3]) 3个元素的解拉链操作 + + val alphabet = List("A",B","C") + val nums = List(1,2) + val zipped = alphabet zip nums // List(("A",1),("B",2)) + val zippedAll = alphabet.zipAll(nums,"*",-1) // List(("A",1),("B",2),("C",-1)) + val zippedIndex = alphabet.zipWithIndex // List(("A",0),("B",1),("C",3)) + val (list1,list2) = zipped.unzip // list1 = List("A","B"), list2 = List(1,2) + val (l1,l2,l3) = List((1, "one", '1'),(2, "two", '2'),(3, "three", '3')).unzip3 // l1=List(1,2,3),l2=List("one","two","three"),l3=List('1','2','3') + +#### 18.slice提取 + +slice(from: Int, until: Int): List[A] 提取列表中从位置from到位置until(不含该位置)的元素列表 + + val nums = List(1,2,3,4,5) + val sliced = nums.slice(2,4) //List(3,4) + +#### 19.sliding按步长分组(不同于group) + +sliding(size: Int, step: Int): Iterator[List[A]] 将列表按照固定大小size进行分组,步进为step,step默认为1,返回结果为迭代器 + + val nums = List(1,1,2,2,3,3,4,4) + val groupStep2 = nums.sliding(2,2).toList //List(List(1,1),List(2,2),List(3,3),List(4,4)) + val groupStep1 = nums.sliding(2).toList //List(List(1,1),List(1,2),List(2,2),List(2,3),List(3,3),List(3,4),List(4,4)) + +#### 20.updte更新(对于List产生新对象) + +updated(index: Int, elem: A): List[A] 对列表中的某个元素进行更新操作 + + val nums = List(1,2,3,3) + val fixed = nums.updated(3,4) // List(1,2,3,4) + +#### 21.contains,exits包含存在 + + List(1,2,3) contains 1 + Set(1,2,3).exists(x=>x==1); + + diff --git a/Security_Camera_Invade/README.md b/Security_Camera_Invade/README.md index f77788b..1e4a062 100644 --- a/Security_Camera_Invade/README.md +++ b/Security_Camera_Invade/README.md @@ -1,6 +1,6 @@ -###弱密码摄像头批量入侵 +### 弱密码摄像头批量入侵 -#####弱密码摄像头批量入侵,端口37777,8000,默认密码账号admin +##### 弱密码摄像头批量入侵,端口37777,8000,默认密码账号admin 这个一个非常简单易懂,没有什么太多技术含量的,默认弱密码摄像头入侵。 首先,使用Scanport 批量扫描你所选IP的此类型号摄像头的端口,37777,8000,也可以指定IP或者扫自己周围IP的,这样比较快,如果说范围太大可能会很慢。 diff --git a/SpringBoot-RabbitMQ/README.md b/SpringBoot-RabbitMQ/README.md new file mode 100644 index 0000000..125228d --- /dev/null +++ b/SpringBoot-RabbitMQ/README.md @@ -0,0 +1,382 @@ +# SpringBoot-RabbitMQ 消息队列 + +这个指南将引导你建立一个RabbitMQ AMQP服务器发布和订阅消息的过程。 + +### 声明 +可以使用本人阿里云安装好的RabbitMQ服务器 + + host:http://120.27.114.229 + username:root + password:root + web management: http://120.27.114.229:15672 + +### 构建 +你会使用 Spring AMQP的 RabbitTemplate构建应用系统来发布消息并且使用一个MessageListenerAdapter POJO来订阅消息 + +### 需要 +* 15分钟 +* 一款文本编辑器或者IDE +* [JDK 1.8+](http://www.oracle.com/technetwork/java/javase/downloads/index.html) +* [Gradle2.3+](http://www.gradle.org/downloads) 或者[Maven3.0+](http://maven.apache.org/download.cgi) +* 你也可以从这个项目中导入代码或者可以在导入[Spring Tool Suite(STS)](https://spring.io/guides/gs/sts)(个人非常喜欢的一款eclipse的IDE)中查看 +* RabbitMQ服务器 + +### 如何完成 +像许多的Spring [Getting Started guides](https://spring.io/guides)项目,你可以从头开始并完成每一步,或者你可以绕过你已经熟悉的一些步骤,无论是哪种步骤,你最终可以完成代码 + +从头开始的话,请去看[使用Gradle构建](https://spring.io/guides/gs/messaging-rabbitmq/#scratch) + +如果要绕过你熟悉的,按照以下构建: + +* [下载](https://github.com/spring-guides/gs-messaging-rabbitmq/archive/master.zip)并解压得到源代码或者从[Git](https://spring.io/understanding/Git): + >git clone https://github.com/spring-guides/gs-messaging-rabbitmq.git +* 进入`gs-messaging-rabbitmq/initial`目录 +* 跳过[创建RabbitMQ消息接收]() + +当你完成时,你可以对比在`gs-messaging-rabbitmq/complete.`目录中的结果和你的结果 + +### 使用Gradle构建 +第一步你需要建立一个基本的脚本,当你构建APP应用时你可以任何你喜欢的构建系统,但这些代码你必须要使用到[Gradle](http://gradle.org/)和[Maven](https://maven.apache.org/),如果你对这两个不熟悉,你可以参考[Building Java Projects with Gradle](https://spring.io/guides/gs/gradle)和[Building Java Projects with Maven](https://spring.io/guides/gs/maven) + +#### 1.创建目录结构 +在你项目的文件夹中创建如下的子目录结构,例如,在*nix系统中使用命令创建`mkdir -p src/main/java/hello` + + └── src + └── main + └── java + └── hello + +#### 2.创建Gradle配置文件build.gradle +以下来自[初始化Gradle配置文件](https://github.com/silence940109/SpringBoot-RabbitMQ/blob/master/build.gradle) + +`build.gradle` + + buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:1.4.3.RELEASE") + } + } + + apply plugin: 'java' + apply plugin: 'eclipse' + apply plugin: 'idea' + apply plugin: 'org.springframework.boot' + + jar { + baseName = 'gs-messaging-rabbitmq' + version = '0.1.0' + } + + repositories { + mavenCentral() + } + + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + + dependencies { + compile("org.springframework.boot:spring-boot-starter-amqp") + testCompile("junit:junit") + } + +[Spring Boot gradle plugin](https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-gradle-plugin)提供了很多方便的特性: + +* 它集成了所有在类路径下的jar包并构建成单独的jar包,可执行的`über-jar`使它可以更加方便的执行和在你的服务中进行传输 +* 它为`public static void main()`方法寻找可执行的类作为标志 +* 它提供了一个内置的依赖解析器来匹配[Spring Boot Dependencies](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-dependencies/pom.xml)依赖版本号,你可以重写任何你希望的版本,但它默认启动时选择的版本集合 + +### 使用Maven构建 +第一步你需要建立一个基本的脚本,当你构建APP应用时你可以任何你喜欢的构建系统,但这些代码你必须要使用到[Maven](https://maven.apache.org/),如果你对Maven不熟悉,你可以参考[Building Java Projects with Maven](https://spring.io/guides/gs/maven) + +#### 1.创建目录结 构 +在你项目的文件夹中创建如下的子目录结构,例如,在*nix系统中使用命令创建`mkdir -p src/main/java/hello` + + └── src + └── main + └── java + └── hello + +`pom.xml` + +```xml + + + 4.0.0 + org.springframework + gs-messaging-rabbitmq + 0.1.0 + + org.springframework.boot + spring-boot-starter-parent + 1.4.3.RELEASE + + + 1.8 + + + + org.springframework.boot + spring-boot-starter-amqp + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + +``` +[Spring Boot gradle plugin](https://github.com/spring-projects/spring-boot/tree/master/spring-boot-tools/spring-boot-gradle-plugin)提供了很多方便的特性: + +* 它集成了所有在类路径下的jar包并构建成单独的jar包,可执行的`über-jar`使它可以更加方便的执行和在你的服务中进行传输 +* 它为`public static void main()`方法寻找可执行的类作为标志 +* 它提供了一个内置的依赖解析器来匹配[Spring Boot Dependencies](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-dependencies/pom.xml)依赖版本号,你可以重写任何你希望的版本,但它默认启动时选择的版本集合 + +### 使用IDE编译 +#### 1.建立RabbitMQ沙箱 +在你可以构建你的消息应用前,你需要建发布和订阅消息的服务器 + +RabbitMQ是一个AMQP(Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计)服务器,这个服务器是免费的,你可以在[http://www.rabbitmq.com/download.html](http://www.rabbitmq.com/download.html),你可以手动的下载,或者如果你使用的Mac可以自己制作 + + brew install rabbitmq + +打开服务器位置并使用默认的配置进行启动 + + rabbitmq-server + +你可以看到如下的一些输出信息: +# + RabbitMQ 3.1.3. Copyright (C) 2007-2013 VMware, Inc. + ## ## Licensed under the MPL. See http://www.rabbitmq.com/ + ## ## + ########## Logs: /usr/local/var/log/rabbitmq/rabbit@localhost.log + ###### ## /usr/local/var/log/rabbitmq/rabbit@localhost-sasl.log + ########## + Starting broker... completed with 6 plugins. + +# +如果你有运行在本地的docker 你也可以使用[Docker Compose](https://docs.docker.com/compose/)(一个部署多个容器的简单但是非常必要的工具)来快速的启动RabbitMQ服务器,在这个项目的根目录中有一个`docker-compose.yml`,它非常简单: + +`docker-compose.yml` + + rabbitmq: + image: rabbitmq:management + ports: + - "5672:5672" + - "15672:15672" + +如果这个文件在你的当前目录中你可以运行`docker-compose up`来是RabbitMQ运行在容器中 + +#### 2.创建RabbitMQ消息订阅 +任何基于消息的应用程序你都需要创建一个消息订阅来响应消息的发布 + +`src/main/java/hello/Receiver.java` + +```Java +package hello; + +import java.util.concurrent.CountDownLatch; +import org.springframework.stereotype.Component; + +@Component +public class Receiver { + + private CountDownLatch latch = new CountDownLatch(1); + + public void receiveMessage(String message) { + System.out.println("Received <" + message + ">"); + latch.countDown(); + } + + public CountDownLatch getLatch() { + return latch; + } + +} +``` + +定义一个简单的`Receiver`类,类中receiveMessage方法用来接收消息,当你注册接受消息时,你可以随意命名它 + +>为了方便,这个POJO类有一个`CountDownLatch`类的属性,它允许当消息接收到时给它一个信号量,这是你在生产环境中你不太可能实现的 + +#### 3.注册监听并发布消息 +Spring AMQP的`RabbitTemplate`提供了你使用RabbitMQ发布和订阅消息所需要的一切,特别的,你需要如下配置: + +* 一个消息监听的容器 +* 声明队列,交换空间,并且绑定他们 +* 一个发送一些信息用来测试监听的组件 + +>Spring Boot会自动创建连接工场和RabbitTemplate,以便减少你需要编写的代码量 + +你将会使用`RabbitTemplate`来发送消息,并且你要使用消息监听的容器注册一个`Receiver`来接收消息。连接工场驱动使它们可以连接到RabbitMQ服务器上 + +`src/main/java/hello/Application.java` + +```Java +package hello; + +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.core.TopicExchange; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; +import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; + +@SpringBootApplication +public class Application { + + final static String queueName = "spring-boot"; + + final static String HOST = "120.27.114.229"; + + final static String USERNAME = "root"; + + final static String PASSWORD = "root"; + + final static int PORT = 15672; + + @Bean + Queue queue() { + return new Queue(queueName, false); + } + + @Bean + TopicExchange exchange() { + return new TopicExchange("spring-boot-exchange"); + } + + @Bean + Binding binding(Queue queue, TopicExchange exchange) { + return BindingBuilder.bind(queue).to(exchange).with(queueName); + } + + @Bean + public ConnectionFactory connectionFactory() { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + connectionFactory.setHost(HOST); + connectionFactory.setPort(PORT); + connectionFactory.setUsername(USERNAME); + connectionFactory.setPassword(PASSWORD); + connectionFactory.setVirtualHost("/"); + //必须要设置,消息的回掉 + connectionFactory.setPublisherConfirms(true); + return connectionFactory; + } + + @Bean + SimpleMessageListenerContainer container(ConnectionFactory connectionFactory, + MessageListenerAdapter listenerAdapter) { + SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + container.setQueueNames(queueName); + container.setMessageListener(listenerAdapter); + return container; + } + + @Bean + MessageListenerAdapter listenerAdapter(Receiver receiver) { + return new MessageListenerAdapter(receiver, "receiveMessage"); + } + + public static void main(String[] args) throws InterruptedException { + SpringApplication.run(Application.class, args); + } + +} +``` + +`@SpringBootApplication`是一个非常方便的注解,它添加了所有如下的东西: + +* `@Configuration`标志着这个类作为资源被这个应用程序而定义的一个bean +* `@EnableAutoConfiguration`告诉Spring Boot启动自动添加bean是基于类路径配置,其他Beans以及各种属性的设置 +* 通常你会为Spring MVC的应用程序添加`@EnableWebMvc`注解,但是当Spring Boot看见Spring-webmvc在它的类路径下时它会自动添加,这个标志着这个应用程序是一个web应用程序,并且自动激活并配置例如`DispatcherServlet`的配置 +* `@ComponentScan`告诉Spring去寻找其他的组件,配置以及在hello包中的其他services,并且允许它使用controllers组件 + +`main()`方法是用了Spring Boot的`SpringAPplication.run()`方法来启动一个应用程序,你注意到这里没有使用XML了吗?同样没有web.xml配置文件。这个web应用程序时纯粹的Java开发并且你不必处理任何配置信息 + +定义在`listenerAdapter()`bean方法在定义`container()`容器时注册成为一个消息监听器,它会为"spring-boot"的消息队列进行监听。因为`Receiver`是一个POJO,在你指定它被`receiveMessage`调用时,它需要被包装到`MessageListenerAdapter`适配器中 + +>JMS队列和AMQP队列有一些语义上的不同。例如,JMS向队列发送消息时只有一个消费者,然而AMQP队列做同样的事情,它虽然模仿JMS主题的概念,但AMQP生产者并不向队列直接发送消息,反而消息发送给的是交换空间,所以AMQP的消息它可以放到一个队列中,或者展开多个队列中。[更多](https://spring.io/understanding/AMQP) + +消息监听容器和接收都是你为了监听到消息所必需的,为了发布一个消息,你也需要一个Rabbit模板 + +`queue()`方法创建了一个AMQP的队列,`exchang()`方法创建了exchange,`binding()`方法把他们两个绑定在了一起,并且定义了当RabbitTemplate发布给exchange时发生的动作 + +>Spring AMQP要求`Queue`,`TopicExchang`,`Binding`被Spring按照顺序定义为定理的bean + +#### 4.发送文本消息 +测试消息是通过`CommandLineRunner`发送的,它也可以等待并锁定接受者并且关闭应用程序: + +`src/main/java/hello/Runner.java` + +```Java +package hello; + +import java.util.concurrent.TimeUnit; + +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.stereotype.Component; + +@Component +public class Runner implements CommandLineRunner { + + private final RabbitTemplate rabbitTemplate; + private final Receiver receiver; + private final ConfigurableApplicationContext context; + + public Runner(Receiver receiver, RabbitTemplate rabbitTemplate, + ConfigurableApplicationContext context) { + this.receiver = receiver; + this.rabbitTemplate = rabbitTemplate; + this.context = context; + } + + @Override + public void run(String... args) throws Exception { + System.out.println("Sending message..."); + rabbitTemplate.convertAndSend(Application.queueName, "Hello from RabbitMQ!"); + receiver.getLatch().await(10000, TimeUnit.MILLISECONDS); + context.close(); + } + +} +``` + +runner可以在测试中进行模拟,以此,reveive可以单独的进行测试 + +#### 5.启动应用 +`main()`方法通过创建Spring应用环境来启动进程。这个进程启动了消息监听容器,它会开始监听消息.`Runner`bean会自动执行:它从应用上下文中检索`RabbitTemplate`并且往"sping-boot"队列中发送一个`Hello from RabbitMQ!`的消息,最后,它关闭Spring应用程序,程序结束 + +#### 6.编译可执行的JAR包 +你可以使用Gradle或者Maven从命令行运行程序,或者你可以编译成一个包含了所有的依赖,类和资源的可执行的JAR文件,然后就可以直接运行。这使它在不同的环境和在整个应用程序的开发声明周期的部署中变得非常容易 + +如果你使用的时Gradle,你需要使用`./gradlew bootRun`来运行应用程序,或者你可以使用`./gradlew build`编译成JAR文件,然后你就可以运行JAR文件了 + + java -jar build/libs/gs-messaging-rabbitmq-0.1.0.jar + +如果你使用的时Maven,你需要使用`./mvnw spring-boot:run`来运行应用程序,或者你可以使用`./mvnw clean package`编译成JAR文件,然后你就可以运行JAR文件了 + + java -jar target/gs-messaging-rabbitmq-0.1.0.jar + +>上面的结果中会创建一个可执行的JAR文件,你也可以选择[构建一个典型的war文件](https://spring.io/guides/gs/convert-jar-to-war/) + +然后你就可以看到如下的输出: + + Sending message... + Received + + +![](https://github.com/silence940109/Java/blob/master/SpringBoot-RabbitMQ/image/run.jpg) \ No newline at end of file diff --git a/SpringBoot-RabbitMQ/image/run.jpg b/SpringBoot-RabbitMQ/image/run.jpg new file mode 100644 index 0000000..d3a7e70 Binary files /dev/null and b/SpringBoot-RabbitMQ/image/run.jpg differ diff --git a/SpringBoot-Scala/README.md b/SpringBoot-Scala/README.md new file mode 100644 index 0000000..10e90a1 --- /dev/null +++ b/SpringBoot-Scala/README.md @@ -0,0 +1,324 @@ +### SpringBoot Scala + +可以说近几年Spark的流行带动了Scala的发展,它集成了面向对象编程和函数式编程的各种特性,Scala具有更纯Lambda表粹的函数式业务逻辑解决方案,其语法比Java8后Lambda更加简洁方便,SpringBoot为spring提供了一种更加方便快捷的方式,不再要求写大量的配置文件,作为一名Scala爱好者,使用SpringBoot结合Scala将大大节省我们开发的时间以及代码量,让程序员可以花更多的时间关注业务逻辑。[项目源代码所在Github地址](https://github.com/silence940109/SpringBoot-Scala) +本文将使用SpringBoot+Scala+Gradle部署Web应用,如果你对Gradle不熟悉,或者IDE中没有Gradle,你可以使用Maven进行管理,原理上是一样的 + +1、首先是build.gradle文件 + + buildscript { + ext { + springBootVersion = '1.4.3.RELEASE' + } + repositories { + mavenCentral() + } + dependencies { + classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") + } + } + apply plugin: 'java' + apply plugin: 'scala' + apply plugin: 'eclipse' + apply plugin: 'application' + apply plugin: 'org.springframework.boot' + + ext { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + } + jar { + baseName = 'spring-boot-scala' + version = '0.1.0' + } + + repositories { + mavenCentral() + maven { url "https://repo.spring.io/snapshot" } + maven { url "https://repo.spring.io/milestone" } + } + + dependencies { + compile("org.springframework.boot:spring-boot-starter-web") + compile("org.springframework.boot:spring-boot-starter-actuator") + compile("org.springframework.boot:spring-boot-starter-data-jpa") + compile "org.scala-lang:scala-library:2.11.7" + compile "org.scala-lang:scala-compiler:2.11.7" + compile "org.scala-lang:scala-reflect:2.11.7" + testCompile("org.springframework.boot:spring-boot-starter-test") + //mysql driver + compile ("mysql:mysql-connector-java:5.1.38") + testCompile("junit:junit:4.12") + } + + task wrapper(type: Wrapper) { + gradleVersion = '2.9' + } + + bootRepackage.enabled = false + +这里面引入了spring-boot-starter-data-jpa,它依赖于hibernate-core和spring-data-jpa,两者的结合可以更好的帮我们处理数据库层的操作。 + +2、springboot的配置文件src/main/resources/application.properties + + #spring.datasource.url: jdbc:hsqldb:mem:scratchdb + #logging.file: /tmp/logs/app.log + + #datasource + spring.datasource.url = jdbc:mysql://localhost:3306/sss + spring.datasource.username = root + spring.datasource.password = root + spring.datasource.driverClassName = com.mysql.jdbc.Driver + + # Specify the DBMS + spring.jpa.database = MYSQL + # Show or not log for each sql query + spring.jpa.show-sql = true + # Hibernate ddl auto (create, create-drop, update) + spring.jpa.hibernate.ddl-auto = update + # stripped before adding them to the entity manager) + spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect + + # Server port + server.port=8080 + +3、dao层代码src/main/scala/com/silence/repository/UserRepository.scala + + package com.silence.repository + + import com.silence.enties.User + import org.springframework.data.jpa.repository.JpaRepository + import java.lang.Long + + trait UserRepository extends JpaRepository[User, Long] + +Scala并没有Java概念中的Interface,不过Scala使用的trait特质来声明,注意,JpaRepository中的Long对象是来自java.lang.Long,而不是Scala特有的Long类型。 + +4、service层/src/main/scala/com/silence/service/BaseService.scala + + +```Scala +package com.silence.service + +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.beans.factory.annotation.Autowired +import scala.reflect.ClassTag +import java.lang.Long +import org.springframework.data.domain.Page +import java.util.List +import org.springframework.context.annotation.ComponentScan +import org.springframework.stereotype.Service +import org.springframework.context.annotation.Bean +import javax.transaction.Transactional +import java.lang.Boolean +import org.springframework.data.domain.PageRequest + +@Service +abstract class BaseService[T: ClassTag] { + + /** spring data jpa dao*/ + @Autowired val jpaRepository: JpaRepository[T, Long] = null + + /** + * @description 添加记录 + * @param S <: T + * @return T + */ + def save[S <: T](s: S) : T = jpaRepository.save(s) + + /** + * @description 根据Id删除数据 + * @param id 数据Id + * @return Unit + */ + @Transactional + def delete(id: Long): Unit = jpaRepository.delete(id) + + /** + * @description 实体批量删除 + * @param List[T] + * @return Unit + */ + @Transactional + def delete(lists: List[T]) : Unit = jpaRepository.delete(lists); + + /** + * @description 根据Id更新数据 + * @param S <: T + * @return T + */ + @Transactional + def update[S <: T](s: S) : T = jpaRepository.save(s) + + /** + * @description 根据Id查询 + * @param id 数据Id + * @return T + */ + def find[S <: T](id: Long) : T = jpaRepository.findOne(id) + + /** + * @description 查询所有数据 + * @return List[T] + */ + def findAll[S <: T]: List[T] = jpaRepository.findAll + + /** + * @description 集合Id查询数据 + * @return List[T] + */ + def findAll[S <: T](ids: List[Long]): List[T] = jpaRepository.findAll(ids) + + /** + * @description 统计大小 + * @return Long + */ + def count : Long = jpaRepository.count + + /** + * @description 判断数据是否存在 + * @param id 数据Id + * @return Boolean + */ + def exist(id: Long) : Boolean = jpaRepository.exists(id) + + /** + * @description 查询分页 + * @param page 起始页 + * @param pageSize 每页大小 + * @return Page[T] + */ + def page[S <: T](page: Int, pageSize: Int): Page[T] = { + var rpage = if (page < 1) 1 else page; + var rpageSize = if (pageSize < 1) 5 else pageSize; + jpaRepository.findAll(new PageRequest(rpage - 1, pageSize)) + } + +} + +``` +该类声明了针对DAO层的一系列操作,包括分页 + +5、UserService.scala + + +```Scala +package com.silence.service + +import com.silence.enties.User +import org.springframework.beans.factory.annotation.Autowired +import com.silence.repository.UserRepository +import org.springframework.stereotype.Service + +@Service +class UserService extends BaseService[User] { + + @Autowired val userRepository: UserRepository = null + +} + +``` +UserRepository是继承了JpaRepository接口的trait + +6、src/main/scala/com/silence/controller/UserController.scala + +```Scala + +package com.silence.controller + +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestMethod +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.web.bind.annotation.ResponseBody +import org.springframework.beans.factory.annotation.Autowired +import com.silence.repository.UserRepository +import org.springframework.web.servlet.ModelAndView +import com.silence.enties.User +import java.util.List +import org.springframework.web.bind.annotation.PathVariable +import javax.validation.Valid +import org.springframework.validation.BindingResult +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.data.domain.Pageable +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Page +import com.silence.service.UserService + +@ComponentScan +@Controller +@ResponseBody +class UserController @Autowired()(private val userService : UserService){ + + @RequestMapping(value = Array("/list"), method = Array(RequestMethod.GET)) + def list() : List[User] = { + userService.findAll + } + + @RequestMapping(value = Array("save"), method = Array(RequestMethod.POST)) + def save(@Valid user : User) : User = { + userService.save(user) + } + + @RequestMapping(value = Array("/find/{id}"), method = Array(RequestMethod.GET)) + def find(@PathVariable(value = "id") id: Long) : User = { + userService.find(id) + } + + @RequestMapping(value = Array("delete/{id}"), method = Array(RequestMethod.POST)) + def delete(@PathVariable(value = "id") id: Long) : Unit = { + userService.delete(id) + } + + @RequestMapping(value = Array("update"), method = Array(RequestMethod.POST)) + def update(@Valid user : User, bindingResult : BindingResult) : User = { + userService.update(user) + } + + @RequestMapping(value = Array("page"), method = Array(RequestMethod.GET)) + def page(@RequestParam("page") page : Int, @RequestParam("pageSize") pageSize : Int) : Page[User] = { + userService.page(page, pageSize) + } + +} + +``` + +7、SpringBoot启动src/main/scala/com/silence/SpringBootScalaApplication.scala + +```Scala +package com.silence + +import org.springframework.context.annotation.Configuration +import org.springframework.boot.autoconfigure.EnableAutoConfiguration +import org.springframework.context.annotation.ComponentScan +import org.springframework.boot.SpringApplication +import org.springframework.boot.autoconfigure.SpringBootApplication + +@Configuration +@EnableAutoConfiguration +@ComponentScan +@SpringBootApplication +class Config + + +object SpringBootScalaApplication extends App { + + SpringApplication.run(classOf[Config]) + +} +``` + +8、运行,如果是使用IDE,可以直接在SpringBootScalaApplication中Run As -> Scala Application,或者在项目的根目录下使用命令行gradle bootRun + +9、测试,可以使用浏览器打开测试,Linux可以使用crul + + curl http://localhost:8080/list + curl http://localhost:8080/find/2 + curl -d "name=silence&telephone=15345678960&birthday=1994-06-24" http://localhost:8080/save + curl -d null http://localhost:8080/delete/505 + curl http://localhost:8080/page?pageSize=6&page=5 + +![](https://github.com/silence940109/Java/blob/master/SpringBoot-Scala/image/springboot-scala.jpg) + +项目地址[github.com/silence940109/SpringBoot-Scala](github.com/silence940109/SpringBoot-Scala) \ No newline at end of file diff --git a/SpringBoot-Scala/image/springboot-scala.jpg b/SpringBoot-Scala/image/springboot-scala.jpg new file mode 100644 index 0000000..277cf14 Binary files /dev/null and b/SpringBoot-Scala/image/springboot-scala.jpg differ diff --git a/SpringBoot-Swagger/README.md b/SpringBoot-Swagger/README.md new file mode 100644 index 0000000..259235e --- /dev/null +++ b/SpringBoot-Swagger/README.md @@ -0,0 +1,134 @@ +### SpringBoot-Scala-Swagger + +swagger用于定义API文档。 + +好处: + +* 前后端分离开发 +* API文档非常明确 +* 测试的时候不需要再使用URL输入浏览器的方式来访问Controller +* 传统的输入URL的测试方式对于post请求的传参比较麻烦(当然,可以使用postman这样的浏览器插件) +* spring-boot与swagger的集成简单的一逼 + +Maven项目 + +```Xml + + io.springfox + springfox-swagger2 + 2.2.2 + + + io.springfox + springfox-swagger-ui + 2.2.2 + +``` + +Gradle项目 + + compile ("io.springfox:springfox-swagger2:2.2.2") + compile ("io.springfox:springfox-swagger-ui:2.2.2") + +SpringBoot启动应用时加上@EnableSwagger2注解 + +```Java + +@Configuration +@EnableAutoConfiguration +@ComponentScan +@SpringBootApplication +//启动swagger注解 +@EnableSwagger2 +class Config + +object StartSpringBootApplication extends App { + + SpringApplication.run(classOf[Config]) + +} + +``` + +在controller层加入swagger api注解 + +Scala模式 + +```Java + +@ComponentScan +@Controller +@ResponseBody +@Api(value = "用户相关操作") +class UserController @Autowired()(private val userService : UserService){ + + @ApiOperation("查询用户信息") + @ApiImplicitParams(Array(new ApiImplicitParam(paramType="header",name="id",dataType="Integer",required=true,value="用户的编号",defaultValue="1"))) + @ApiResponses(Array(new ApiResponse(code=400,message="请求参数没填好"),new ApiResponse(code=404,message="请求路径没有或页面跳转路径不对"))) + @RequestMapping(value = Array("/find/{id}"), method = Array(RequestMethod.GET)) + def find(@PathVariable(value = "id") id: Long) : User = { + userService.find(id) + } + +} +``` + +Java模式 + +```Java +@RestController +@RequestMapping("/user") +@Api("userController相关api") +public class UserController { + + @Autowired + private UserService userService; + + @ApiOperation("获取用户信息") + @ApiImplicitParams({ + @ApiImplicitParam(paramType="header",name="username",dataType="String",required=true,value="用户的姓名",defaultValue="zhaojigang"), + @ApiImplicitParam(paramType="query",name="password",dataType="String",required=true,value="用户的密码",defaultValue="wangna") + }) + @ApiResponses({ + @ApiResponse(code=400,message="请求参数没填好"), + @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对") + }) + @RequestMapping(value="/getUser",method=RequestMethod.GET) + public User getUser(@RequestHeader("username") String username, @RequestParam("password") String password) { + return userService.getUser(username,password); + } +} +``` + +说明: + +* @Api:用在类上,说明该类的作用 +* @ApiOperation:用在方法上,说明方法的作用 +* @ApiImplicitParams:用在方法上包含一组参数说明 +* @ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面 + + - paramType:参数放在哪个地方 + - header-->请求参数的获取:@RequestHeader + - query-->请求参数的获取:@RequestParam + - path(用于restful接口)-->请求参数的获取:@PathVariable + - body(不常用) + - form(不常用) + - name:参数名 + - dataType:参数类型 + - required:参数是否必须传 + - value:参数的意思 + - defaultValue:参数的默认值 + +* @ApiResponses:用于表示一组响应 +* @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息 + + - code:数字,例如400 + - message:信息,例如"请求参数没填好" + - response:抛出异常的类 + +* @ApiModel:描述一个Model的信息(这种一般用在post创建的时候,使用@RequestBody这样的场景,请求参数无法使用@ApiImplicitParam注解进行描述的时候) +* @ApiModelProperty:描述一个model的属性 + +以上这些就是最常用的几个注解了。 + +[项目地址](https://github.com/silence940109/WebSocket) \ No newline at end of file diff --git a/SpringBoot/README.md b/SpringBoot/README.md new file mode 100644 index 0000000..2d29dce --- /dev/null +++ b/SpringBoot/README.md @@ -0,0 +1,218 @@ +Spring 框架作为目前非常流行的一个 Java 应用开发框架,它所包含的内容是非常繁多的。Spring 框架包含几十个不同的子项目,涵盖应用开发的不同方面。要在这些子项目之间进行选择,并快速搭建一个可以运行的应用是比较困难的事情。Spring Boot 的目的在于快速创建可以独立运行的 Spring 应用。通过 Spring Boot 可以根据相应的模板快速创建应用并运行。Spring Boot 可以自动配置 Spring 的各种组件,并不依赖代码生成和 XML 配置文件。Spring Boot 可以大大提升使用 Spring 框架时的开发效率。 + +Spring 框架对于很多 Java 开发人员来说都不陌生。自从 2002 年发布以来,Spring 框架已经成为企业应用开发领域非常流行的基础框架。有大量的企业应用基于 Spring 框架来开发。Spring 框架包含几十个不同的子项目,涵盖应用开发的不同方面。如此多的子项目和组件,一方面方便了开发人员的使用,另外一个方面也带来了使用方面的问题。每个子项目都有一定的学习曲线。开发人员需要了解这些子项目和组件的具体细节,才能知道如何把这些子项目整合起来形成一个完整的解决方案。在如何使用这些组件上,并没有相关的最佳实践提供指导。对于新接触 Spring 框架的开发人员来说,并不知道如何更好的使用这些组件。Spring 框架的另外一个常见问题是要快速创建一个可以运行的应用比较麻烦。Spring Boot 是 Spring 框架的一个新的子项目,用于创建 Spring 4.0 项目。它的开发始于 2013 年。2014 年 4 月发布 1.0.0 版本。它可以自动配置 Spring 的各种组件,并不依赖代码生成和 XML 配置文件。Spring Boot 也提供了对于常见场景的推荐组件配置。Spring Boot 可以大大提升使用 Spring 框架时的开发效率。本文将对 Spring Boot 进行详细的介绍。 + +### 简介 +从 Spring Boot 项目名称中的 Boot 可以看出来,Spring Boot 的作用在于创建和启动新的基于 Spring 框架的项目。它的目的是帮助开发人员很容易的创建出独立运行和产品级别的基于 Spring 框架的应用。Spring Boot 会选择最适合的 Spring 子项目和第三方开源库进行整合。大部分 Spring Boot 应用只需要非常少的配置就可以快速运行起来。 + +Spring Boot 包含的特性如下: + +* 创建可以独立运行的 Spring 应用。 +* 直接嵌入 Tomcat 或 Jetty 服务器,不需要部署 WAR 文件. +* 提供推荐的基础 POM 文件来简化 Apache Maven 配置。 +* 尽可能的根据项目依赖来自动配置 Spring 框架。 +* 提供可以直接在生产环境中使用的功能,如性能指标、应用信息和应用健康检查。 +* 没有代码生成,也没有 XML 配置文件。 + +通过 Spring Boot,创建新的 Spring 应用变得非常容易,而且创建出的 Spring 应用符合通用的最佳实践。只需要简单的几个步骤就可以创建出一个 Web 应用。下面介绍使用 Maven 作为构建工具创建的 Spring Boot 应用。代码清单 1 给出了该应用的 POM 文件。 + +`清单 1. Spring Boot 示例应用的 POM 文件` + +```Xml + + +4.0.0 +com.midgetontoes +spring-boot-simple +1.0-SNAPSHOT + + 1.1.4.RELEASE + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + + + org.springframework.boot + spring-boot-maven-plugin +${spring.boot.version} + + + + repackage + + + + + + + +``` + +从代码清单 1 中的 POM 文件中可以看到,应用所声明的依赖很少,只有一个“org.springframework.boot:spring-boot-starter-web”,而不是像其他 Spring 项目一样需要声明很多的依赖。当使用 Maven 命令“mvn dependency:tree”来查看项目实际的依赖时,会发现其中包含了 Spring MVC 框架、SLF4J、Jackson、Hibernate Validator 和 Tomcat 等依赖。这实际上 Spring 推荐的 Web 应用中使用的开源库的组合。代码清单 2 中给出了示例应用的 Java 代码。 + +`清单 2. Spring Boot 示例应用的 Java 代码` + +```Java +@RestController +@EnableAutoConfiguration +public class Application { + @RequestMapping("/") + String home() { + return "Hello World!"; + } + public static void main(String[] args) throws Exception { + SpringApplication.run(Application.class, args); + } +} +``` + +代码清单 2 中的 Java 类 Application 是一个简单的可以独立运行的 Web 应用。直接运行该 Java 类会启动一个内嵌的 Tomcat 服务器运行在 8080 端口。访问“http://localhost:8080”可以看到页面上显示“Hello World!”。也就是说,只需要简单的 2 个文件就可以启动一个独立运行的 Web 应用。并不需要额外安装 Tomcat 这样的应用服务器,也不需要打包成 WAR 文件。可以通过“mvn spring-boot:run”在命令行启动该应用。在代码清单 1 中的 POM 文件中添加了“org.springframework.boot:spring-boot-maven-plugin”插件。在添加了该插件之后,当运行“mvn package”进行打包时,会打包成一个可以直接运行的 JAR 文件,使用“java -jar”命令就可以直接运行。这在很大程度上简化了应用的部署,只需要安装了 JRE 就可以运行。 + +代码清单 2 中的“@EnableAutoConfiguration”注解的作用在于让 Spring Boot 根据应用所声明的依赖来对 Spring 框架进行自动配置,这就减少了开发人员的工作量。注解“@RestController”和”@RequestMapping”由 Spring MVC 提供,用来创建 REST 服务。这两个注解和 Spring Boot 本身并没有关系。 + +### Spring Boot 推荐的基础 POM 文件 +上一节的代码清单 1 中给出的“org.springframework.boot:spring-boot-starter-web”是 Spring Boot 所提供的推荐的基础 POM 文件之一,用来提供创建基于 Spring MVC 的 Web 应用所需的第三方库依赖。除了这个 POM 文件之外,Spring Boot 还提供了其他类似的 POM 文件。所有这些基础 POM 依赖都在“org.springframework.boot”组中。一些重要 POM 文件的具体说明见表 1。 + +`表 1. Spring Boot 推荐的基础 POM 文件` + + + 名称 说明 + spring-boot-starter 核心 POM,包含自动配置支持、日志库和对 YAML 配置文件的支持。 + spring-boot-starter-amqp 通过 spring-rabbit 支持 AMQP。 + spring-boot-starter-aop 包含 spring-aop 和 AspectJ 来支持面向切面编程(AOP)。 + spring-boot-starter-batch 支持 Spring Batch,包含 HSQLDB。 + spring-boot-starter-data-jpa 包含 spring-data-jpa、spring-orm 和 Hibernate 来支持 JPA。 + spring-boot-starter-data-mongodb 包含 spring-data-mongodb 来支持 MongoDB。 + spring-boot-starter-data-rest 通过 spring-data-rest-webmvc 支持以 REST 方式暴露 Spring Data 仓库。 + spring-boot-starter-jdbc 支持使用 JDBC 访问数据库。 + spring-boot-starter-security 包含 spring-security。 + spring-boot-starter-test 包含常用的测试所需的依赖,如 JUnit、Hamcrest、Mockito 和 spring-test 等。 + spring-boot-starter-velocity 支持使用 Velocity 作为模板引擎。 + spring-boot-starter-web 支持 Web 应用开发,包含 Tomcat 和 spring-mvc。 + spring-boot-starter-websocket 支持使用 Tomcat 开发 WebSocket 应用。 + spring-boot-starter-ws 支持 Spring Web Services。 + spring-boot-starter-actuator 添加适用于生产环境的功能,如性能指标和监测等功能。 + spring-boot-starter-remote-shell 添加远程 SSH 支持。 + spring-boot-starter-jetty 使用 Jetty 而不是默认的 Tomcat 作为应用服务器。 + spring-boot-starter-log4j 添加 Log4j 的支持。 + spring-boot-starter-logging 使用 Spring Boot 默认的日志框架 Logback。 + spring-boot-starter-tomcat 使用 Spring Boot 默认的 Tomcat 作为应用服务器。 + +所有这些 POM 依赖的好处在于为开发 Spring 应用提供了一个良好的基础。Spring Boot 所选择的第三方库是经过考虑的,是比较适合产品开发的选择。但是 Spring Boot 也提供了不同的选项,比如日志框架可以用 Logback 或 Log4j,应用服务器可以用 Tomcat 或 Jetty。 + +### 自动配置 +Spring Boot 对于开发人员最大的好处在于可以对 Spring 应用进行自动配置。Spring Boot 会根据应用中声明的第三方依赖来自动配置 Spring 框架,而不需要进行显式的声明。比如当声明了对 HSQLDB 的依赖时,Spring Boot 会自动配置成使用 HSQLDB 进行数据库操作。 + +Spring Boot 推荐采用基于 Java 注解的配置方式,而不是传统的 XML。只需要在主配置 Java 类上添加“@EnableAutoConfiguration”注解就可以启用自动配置。Spring Boot 的自动配置功能是没有侵入性的,只是作为一种基本的默认实现。开发人员可以通过定义其他 bean 来替代自动配置所提供的功能。比如当应用中定义了自己的数据源 bean 时,自动配置所提供的 HSQLDB 就不会生效。这给予了开发人员很大的灵活性。既可以快速的创建一个可以立即运行的原型应用,又可以不断的修改和调整以适应应用开发在不同阶段的需要。可能在应用最开始的时候,嵌入式的内存数据库(如 HSQLDB)就足够了,在后期则需要换成 MySQL 等数据库。Spring Boot 使得这样的切换变得很简单。 + +### 外部化的配置 +在应用中管理配置并不是一个容易的任务,尤其是在应用需要部署到多个环境中时。通常会需要为每个环境提供一个对应的属性文件,用来配置各自的数据库连接信息、服务器信息和第三方服务账号等。通常的应用部署会包含开发、测试和生产等若干个环境。不同的环境之间的配置存在覆盖关系。测试环境中的配置会覆盖开发环境,而生产环境中的配置会覆盖测试环境。Spring 框架本身提供了多种的方式来管理配置属性文件。Spring 3.1 之前可以使用 PropertyPlaceholderConfigurer。Spring 3.1 引入了新的环境(Environment)和概要信息(Profile)API,是一种更加灵活的处理不同环境和配置文件的方式。不过 Spring 这些配置管理方式的问题在于选择太多,让开发人员无所适从。Spring Boot 提供了一种统一的方式来管理应用的配置,允许开发人员使用属性文件、YAML 文件、环境变量和命令行参数来定义优先级不同的配置值。 + +Spring Boot 所提供的配置优先级顺序比较复杂。按照优先级从高到低的顺序,具体的列表如下所示。 +* 命令行参数。 + +* 通过 System.getProperties() 获取的 Java 系统参数。 + +* 操作系统环境变量。 + +* 从 java:comp/env 得到的 JNDI 属性。 + +* 通过 RandomValuePropertySource 生成的“random.*”属性。 + +* 应用 Jar 文件之外的属性文件。 + +* 应用 Jar 文件内部的属性文件。 + +* 在应用配置 Java 类(包含“@Configuration”注解的 Java 类)中通过“@PropertySource”注解声明的属性文件。 + +* 通过“SpringApplication.setDefaultProperties”声明的默认属性。 + +Spring Boot 的这个配置优先级看似复杂,其实是很合理的。比如命令行参数的优先级被设置为最高。这样的好处是可以在测试或生产环境中快速地修改配置参数值,而不需要重新打包和部署应用。 + +SpringApplication 类默认会把以“--”开头的命令行参数转化成应用中可以使用的配置参数,如 “--name=Alex” 会设置配置参数 “name” 的值为 “Alex”。如果不需要这个功能,可以通过 “SpringApplication.setAddCommandLineProperties(false)” 禁用解析命令行参数。 + +RandomValuePropertySource 可以用来生成测试所需要的各种不同类型的随机值,从而免去了在代码中生成的麻烦。RandomValuePropertySource 可以生成数字和字符串。数字的类型包含 int 和 long,可以限定数字的大小范围。以“random.”作为前缀的配置属性名称由 RandomValuePropertySource 来生成,如代码清单 3 所示。 + +清单 3. 使用 RandomValuePropertySource 生成的配置属性 + + user.id=${random.value} + user.count=${random.int} + user.max=${random.long} + user.number=${random.int(100)} + user.range=${random.int[100, 1000]} + +#### 属性文件 +属性文件是最常见的管理配置属性的方式。Spring Boot 提供的 SpringApplication 类会搜索并加载 application.properties 文件来获取配置属性值。SpringApplication 类会在下面位置搜索该文件。 + +* 当前目录的“/config”子目录。 + +* 当前目录。 + +* classpath 中的“/config”包。 + +* classpath + +上面的顺序也表示了该位置上包含的属性文件的优先级。优先级按照从高到低的顺序排列。可以通过“spring.config.name”配置属性来指定不同的属性文件名称。也可以通过“spring.config.location”来添加额外的属性文件的搜索路径。如果应用中包含多个 profile,可以为每个 profile 定义各自的属性文件,按照“application-{profile}”来命名。 + +对于配置属性,可以在代码中通过“@Value”来使用,如代码清单 4 所示。 +清单 4. 通过“@Value”来使用配置属性 + + @RestController + @EnableAutoConfiguration + public class Application { + @Value("${name}") + private String name; + @RequestMapping("/") + String home() { + return String.format("Hello %s!", name); + } + } + +#### YAML +相对于属性文件来说,YAML 是一个更好的配置文件格式。YAML 在 Ruby on Rails 中得到了很好的应用。SpringApplication 类也提供了对 YAML 配置文件的支持,只需要添加对 SnakeYAML 的依赖即可。代码清单 5 给出了 application.yml 文件的示例。 + +清单 5. 使用 YAML 表示的配置属性 + + spring: + profiles: development + db: + url: jdbc:hsqldb:file:testdb + username: sa + password: + --- + spring: + profiles: test + db: + url: jdbc:mysql://localhost/test + username: test + password: test + + +代码清单 5 中的 YAML 文件同时给出了 development 和 test 两个不同的 profile 的配置信息,这也是 YAML 文件相对于属性文件的优势之一。除了使用“@Value”注解绑定配置属性值之外,还可以使用更加灵活的方式。代码清单 6 给出的是使用代码清单 5 中的 YAML 文件的 Java 类。通过“@ConfigurationProperties(prefix="db")”注解,配置属性中以“db”为前缀的属性值会被自动绑定到 Java 类中同名的域上,如 url 域的值会对应属性“db.url”的值。只需要在应用的配置类中添加“@EnableConfigurationProperties”注解就可以启用该自动绑定功能。 + +清单 6. 使用 YAML 文件的 Java 类 + + @Component + @ConfigurationProperties(prefix="db") + public class DBSettings { + private String url; + private String username; + private String password; + } + +### 开发 Web 应用 +Spring Boot 非常适合于开发基于 Spring MVC 的 Web 应用。通过内嵌的 Tomcat 或 Jetty 服务器,可以简化对 Web 应用的部署。Spring Boot 通过自动配置功能对 Spring MVC 应用做了一些基本的配置,使其更加适合一般 Web 应用的开发要求。 + +#### HttpMessageConverter +Spring MVC 中使用 HttpMessageConverter 接口来在 HTTP 请求和响应之间进行消息格式的转换。默认情况下已经通过 Jackson 支持 JSON 和通过 JAXB 支持 XML 格式。可以通过创建自定义 HttpMessageConverters 的方式来添加其他的消息格式转换实现。 + +#### 静态文件 +默认情况下,Spring Boot 可以对 “/static”、“/public”、“/resources” 或 “/META-INF/resources” 目录下的静态文件提供支持。同时 Spring Boot 还支持 Webjars。路径“/webjars/**”下的内容会由 webjar 格式的 Jar 包来提供。 diff --git a/SpringBoot_ApplicationContext/README.md b/SpringBoot_ApplicationContext/README.md new file mode 100644 index 0000000..4029d10 --- /dev/null +++ b/SpringBoot_ApplicationContext/README.md @@ -0,0 +1,93 @@ +### SpringBoot 获取应上下文 ApplicationContent ### +#### 1、定义上下文工具类: #### +```java +package com.alimama.config; + +import org.springframework.context.ApplicationContext; +/** + * 上下文获取工具类 + * @author mengfeiyang + * + */ +public class SpringContextUtil { + private static ApplicationContext applicationContext; + + public static void setApplicationContext(ApplicationContext context) { + applicationContext = context; + } + + public static Object getBean(String beanId) { + return applicationContext.getBean(beanId); + } +} +``` + +#### 2、在启动入口类中注入applicationContext #### +```java +package com.alimama; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; +import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ComponentScan; + +import com.alimama.config.SbootConfig; +import com.alimama.config.SpringContextUtil; +import com.alimama.config.ZKConfig; +import com.alimama.quartz.InitTask; + +/** + * spring boot启动入口类 + * @author mengfeiyang + * + */ +@ComponentScan +@SpringBootApplication +@EnableConfigurationProperties({ZKConfig.class,SbootConfig.class}) +public class SbootApplication implements EmbeddedServletContainerCustomizer{ + + public static void main(String[] args) { + ApplicationContext applicationContext = SpringApplication.run(SbootApplication.class, args); + SpringContextUtil.setApplicationContext(applicationContext); + } + + @Override + public void customize(ConfigurableEmbeddedServletContainer container) { + + } +} +``` + +#### 3、调用方法 #### +```java +package com.alimama.quartz; + +import java.io.IOException; + +import org.phoenix.api.action.IInterfaceAPI; +import org.phoenix.api.action.InterfaceAPI; +import org.quartz.Job; +import org.springframework.beans.factory.annotation.Autowired; + +import com.alimama.config.SpringContextUtil; +import com.alimama.dto.TaskBean; +import com.alimama.service.IConfigService; +import com.alimama.service.impl.ConfigService; +/** + * 任务执行者 + * @author mengfeiyang + * + */ +public class TaskHandler implements Job{ + private ConfigService configService = (ConfigService) SpringContextUtil.getBean("configService"); + private IInterfaceAPI interf = new InterfaceAPI(); + @Override + public void execute(JobExecutionContext arg0){ + String watchDogServer = configService.getwatchDogServer(); + System.out.println(watchDogServer); + } +} +``` diff --git a/SpringBoot_DevTools/README.md b/SpringBoot_DevTools/README.md new file mode 100644 index 0000000..ba15bdb --- /dev/null +++ b/SpringBoot_DevTools/README.md @@ -0,0 +1,156 @@ +### DevTools in Spring Boot 热部署 ### + +本文主要了解Spring Boot 1.3.0新添加的spring-boot-devtools模块的使用,该模块主要是为了提高开发者开发Spring Boot应用的用户体验。 + +要想使用该模块需要在Maven中添加: + +```xml + + + org.springframework.boot + spring-boot-devtools + + +``` + +或者在Gradle配置文件中添加: + +```groovy +dependencies { + compile("org.springframework.boot:spring-boot-devtools") +} +``` + +#### 1、默认属性 #### + +在Spring Boot集成Thymeleaf时,`spring.thymeleaf.cache`属性设置为false可以禁用模板引擎编译的缓存结果。现在,devtools会自动帮你做到这些,禁用所有模板的缓存,包括Thymeleaf, Freemarker, Groovy Templates, Velocity, Mustache等。更多的属性,请参考[DevToolsPropertyDefaultsPostProcessor。](http://github.com/spring-projects/spring-boot/tree/v1.3.2.RELEASE/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsPropertyDefaultsPostProcessor.java) + +#### 2、自动重启 #### + +你可能使用过 JRebel 或者 Spring Loaded来自动重启应用,现在只需要引入devtools就可以了,当代码变动时,它会自动进行重启应用。当然,你也可以使用插件来实现,例如在maven中配置插件: + +```xml + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + +``` + +```groovy +bootRun { + addResources = true +} +``` + +DevTools在重启过程中依赖于application context的shutdown hook,如果设置`SpringApplication.setRegisterShutdownHook(false)`,则自动重启将不起作用。 + +#### 排除静态资源文件 #### +静态资源文件在改变之后有时候没必要触发应用程序重启,例如,Thymeleaf的模板会被变量替代。默认的,/META-INF/maven、/META-INF/resources、/resources、/static、/public或者/templates这些目录下的文件修改之后不会触发重启但是会触发LiveReload。可以通过`spring.devtools.restart.exclude`属性来修改默认值 + +如果你想保留默认配置,并添加一些额外的路径,可以使用`spring.devtools.restart.additional-exclude`属性 + +#### 观察额外的路径 #### +如果你想观察不在classpath中的路径的文件变化并触发重启,则可以配置 spring.devtools.restart.additional-paths 属性 + +#### 关闭自动重启 #### +设置 spring.devtools.restart.enabled 属性为false,可以关闭该特性。可以在application.properties中设置,也可以通过设置环境变量的方式 + +```java +public static void main(String[] args) { + System.setProperty("spring.devtools.restart.enabled", "false"); + SpringApplication.run(MyApp.class, args); +} +``` + +#### 使用一个触发文件 #### +通过设置`spring.devtools.restart.trigger-file`属性指定一个文件,当该文件被修改时,则触发自动重启 + +#### 自定义自动重启类加载器 #### +Spring Boot自动重启使用的是两个类加载器,大多数情况下工作良好,有时候会出现问题。 + +默认的,IDE中打开的项目会使用 restart 类加载器进行加载,而任何其他的 .jar 文件会使用 base 类加载器进行加载。如果你使用的是多模块的项目,并且有些模块没有被导入到IDE,你需要创建并编辑`META-INF/spring-devtools.properties`文件来自定义一些配置 + +`spring-devtools.properties`文件包含有 `restart.exclude`. 和 `restart.include`. 前缀的属性。include属性文件的都会被加入到restart类加载器,exclude属性文件的都会被加入到base类加载器,他们的值是正则表达式,所有的属性值必须是唯一的 + +例如: + + restart.include.companycommonlibs=/mycorp-common-[\\w-]+\.jar + restart.include.projectcommon=/mycorp-myproj-[\\w-]+\.jar + +> classpath中的所有META-INF/spring-devtools.properties文件都会被加载。 + +#### 已知的限制 #### + +如果对象是使用ObjectInputStream进行反序列化,则自动重启将不可用。如果你需要反序列化对象,则你需要使用spring的`ConfigurableObjectInputStream`并配合`Thread.currentThread().getContextClassLoader()`进行反序列化 + +#### 3、LiveReload #### + +在浏览器方面,DevTools内置了一个LiveReload服务,可以自动刷新浏览器。如果你使用JRebel,则自动重启将会失效,取而代之的是使用动态加载类文件。当然,其他的DevTools(例如LiveReload和属性覆盖)特性还能使用。 + +该特性可以通过`spring.devtools.livereload.enabled`属性来设置是否开启 + +#### 4、全局设置 #### +可以在 $HOME 目录下创建一个`.spring-boot-devtools.properties`文件来设置全局的配置。 + +例如,设置一个触发文件类触发重启: + + spring.devtools.reload.trigger-file=.reloadtrigger + +#### 5、远程应用 #### +DevTools不仅可以用于本地应用,也可以用于远程应用。通过设置 spring.devtools.remote.secret 属性可以开启远程应用。 + +远程DevTools支持包括两个部分,服务端应用和你IDE中运行的本地客户端应用。当设置spring.devtools.remote.secret属性之后,服务端应用自动开启DevTools特性,客户端程序需要手动启动。 + +#### 运行远程客户端应用 #### + +远程客户端应用被设计来运行在你的IDE中。你需要使用和你连接的远程应用相同的classpath来运行org.springframework.boot.devtools.RemoteSpringApplication类,传递给该类的必选参数是你连接的应用的url。 + +例如,如果你在使用Eclipse或者STS,并且你有一个 my-app 应用部署在Cloud Foundry,你可以按照以下步骤操作: + +* 从Run菜单运行Run Configurations…` +* 创建一个Java Application的启动配置 +* 浏览my-app项目 +* 使用org.springframework.boot.devtools.RemoteSpringApplication作为main类 +* 添加https://myapp.cfapps.io到the Program arguments + +一个运行中的远程客户端将会是这样子: + + . ____ _ __ _ _ + /\\ / ___'_ __ _ _(_)_ __ __ _ ___ _ \ \ \ \ + ( ( )\___ | '_ | '_| | '_ \/ _` | | _ \___ _ __ ___| |_ ___ \ \ \ \ + \\/ ___)| |_)| | | | | || (_| []::::::[] / -_) ' \/ _ \ _/ -_) ) ) ) ) + ' |____| .__|_| |_|_| |_\__, | |_|_\___|_|_|_\___/\__\___|/ / / / + =========|_|==============|___/===================================/_/_/_/ + :: Spring Boot Remote :: 1.3.2.RELEASE + + 2015-06-10 18:25:06.632 INFO 14938 --- [ main] o.s.b.devtools.RemoteSpringApplication : Starting RemoteSpringApplication on pwmbp with PID 14938 (/Users/pwebb/projects/spring-boot/code/spring-boot-devtools/target/classes started by pwebb in /Users/pwebb/projects/spring-boot/code/spring-boot-samples/spring-boot-sample-devtools) + 2015-06-10 18:25:06.671 INFO 14938 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@2a17b7b6: startup date [Wed Jun 10 18:25:06 PDT 2015]; root of context hierarchy + 2015-06-10 18:25:07.043 WARN 14938 --- [ main] o.s.b.d.r.c.RemoteClientConfiguration : The connection to http://localhost:8080 is insecure. You should use a URL starting with 'https://'. + 2015-06-10 18:25:07.074 INFO 14938 --- [ main] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729 + 2015-06-10 18:25:07.130 INFO 14938 --- [ main] o.s.b.devtools.RemoteSpringApplication : Started RemoteSpringApplication in 0.74 seconds (JVM running for 1.105) + +说明: + +* 因为远程客户端和远程应用使用的是相同的classpath,所以远程客户端可以直接读取应用的配置文件。所以spring.devtools.remote.secret属性能够被读取到并传递给服务端进行校验。 +* 远程url建议使用更加安全的https://协议 +* 如果需要使用代理,则需要设置`spring.devtools.remote.proxy.host`和`spring.devtools.remote.proxy.port`属性。 + +#### 远程更新 #### + +远程客户端会监控你的应用的classpath的改变和本地重启的方式一样。任何更新的资源将会被推送到远程应用并且(如果有必要)触发一个重启。这在你集成一个使用云服务的本地不存在的特性时会是非常有用的。通常远程的更新和重启比一个完整的重新编译和部署周期会快的多。 + +> 只有在远程客户端运行的过程中,文件才会被监控。如果你在启动远程客户端之前,修改一个文件,其将不会被推送到远程应用。 + +#### 远程调试 #### + +远程调试默认使用的端口是8000,你可以通过spring.devtools.remote.debug.local-port来修改。 + +你可以通过查看JAVA_OPTS来看远程调试是否被启用,主要是观察是否有-Xdebug -Xrunjdwp:server=y,transport=dt_socket,suspend=n参数 + diff --git a/SpringBoot_Junit_Test/README.md b/SpringBoot_Junit_Test/README.md new file mode 100644 index 0000000..8b6c359 --- /dev/null +++ b/SpringBoot_Junit_Test/README.md @@ -0,0 +1,25 @@ +### SpringBoot Junit单元测试 + +如果是使用spring-boot 1.4以下的版本,使用@SpringApplicationConfiguration注解 + +```Java +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = 启动类.class) +public class ApplicationTest { + + //代码省略 +} +``` + +如果spring-boot是1.4以上的版本,使用@SpringBootTest注解 + +```Java +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = 启动类.class) +public class ApplicationTest { + + //代码省略 +} +``` + +> 注意,1.4版本支持 @SpringBootTest注解,或者直接使用 @SpringBootContextLoader注解 \ No newline at end of file diff --git a/SpringBoot_Log_Management/README.md b/SpringBoot_Log_Management/README.md new file mode 100644 index 0000000..d5fbc8c --- /dev/null +++ b/SpringBoot_Log_Management/README.md @@ -0,0 +1,84 @@ +### Spring Boot日志管理 ### + +Spring Boot在所有内部日志中使用[Commons Logging](http://commons.apache.org/proper/commons-logging/),但是默认配置也提供了对常用日志的支持,如:[Java Util Logging](http://docs.oracle.com/javase/7/docs/api/java/util/logging/package-summary.html),[Log4J](http://logging.apache.org/log4j/), [Log4J2](http://logging.apache.org/log4j/)和[Logback](http://logback.qos.ch/)。每种Logger都可以通过配置使用控制台或者文件输出日志内容。 + +#### 格式化日志 #### + +默认的日志输出如下: + + 2017-04-04 09:58:22.233 INFO 5972 --- [ main] com.silence.Application$ : Starting Application. on silence with PID 5972 (E:\github\LayIM\bin started by asus in E:\github\LayIM) + 2017-04-04 09:58:22.241 INFO 5972 --- [ main] com.silence.Application$ : No active profile set, falling back to default profiles: default + 2017-04-04 09:58:22.490 INFO 5972 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@74235045: startup date [Tue Apr 04 09:58:22 CST 2017]; root of context hierarchy + + +输出内容元素具体如下: + +* 时间日期 — 精确到毫秒 +* 日志级别 — ERROR, WARN, INFO, DEBUG or TRACE +* 进程ID +* 分隔符 — --- 标识实际日志的开始 +* 线程名 — 方括号括起来(可能会截断控制台输出) +* Logger名 — 通常使用源代码的类名 +* 日志内容 + +#### 控制台输出 #### + +在Spring Boot中默认配置了ERROR、WARN和INFO级别的日志输出到控制台。 + +我们可以通过两种方式切换至DEBUG级别: + +* 在运行命令后加入--debug标志,如:$ java -jar myapp.jar --debug +* 在application.properties中配置debug=true,该属性置为true的时候,核心Logger(包含嵌入式容器、hibernate、spring)会输出更多内容,但是你自己应用的日志并不会输出为DEBUG级别。 + +#### 多彩输出 #### + +如果你的终端支持ANSI,设置彩色输出会让日志更具可读性。通过在`application.properties`中设置`spring.output.ansi.enabled`参数来支持。 + +* NEVER:禁用ANSI-colored输出(默认项) +* DETECT:会检查终端是否支持ANSI,是的话就采用彩色输出(推荐项) +* ALWAYS:总是使用ANSI-colored格式输出,若终端不支持的时候,会有很多干扰信息,不推荐使用 + +#### 文件输出 #### + +Spring Boot默认配置只会输出到控制台,并不会记录到文件中,但是我们通常生产环境使用时都需要以文件方式记录 + +若要增加文件输出,需要在`application.properties`中配置`logging.file`或`logging.path`属性 + +* logging.file,设置文件,可以是绝对路径,也可以是相对路径。如:logging.file=my.log +* logging.path,设置目录,会在该目录下创建spring.log文件,并写入日志内容,如:logging.path=/var/log + +日志文件会在10Mb大小的时候被截断,产生新的日志文件,默认级别为:ERROR、WARN、INFO + +#### 级别控制 #### + +在Spring Boot中只需要在application.properties中进行配置完成日志记录的级别控制。 + +配置格式:logging.level.*=LEVEL + +* logging.level:日志级别控制前缀,*为包名或Logger名 +* LEVEL:选项TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF + +举例: + + logging.level.com.didispace=DEBUG:com.didispace包下所有class以DEBUG级别输出 + logging.level.root=WARN:root日志以WARN级别输出 + +> 注意,如果想要输出Mybatis的SQL语句可以logging.level.com.silence.mapper=debug + +#### 自定义日志配置 #### +由于日志服务一般都在ApplicationContext创建前就初始化了,它并不是必须通过Spring的配置文件控制。因此通过系统属性和传统的Spring Boot外部配置文件依然可以很好的支持日志控制和管理。 + +根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载: + +* Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy +* Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml +* Log4j2:log4j2-spring.xml, log4j2.xml +* JDK (Java Util Logging):logging.properties + +Spring Boot官方推荐优先使用带有-spring的文件名作为你的日志配置(如使用logback-spring.xml,而不是logback.xml) + +#### 自定义输出格式 #### +在Spring Boot中可以通过在application.properties配置如下参数控制输出格式: + +* logging.pattern.console:定义输出到控制台的样式(不支持JDK Logger) +* logging.pattern.file:定义输出到文件的样式(不支持JDK Logger) \ No newline at end of file diff --git a/SpringBoot_Mail/README.md b/SpringBoot_Mail/README.md new file mode 100644 index 0000000..796ec92 --- /dev/null +++ b/SpringBoot_Mail/README.md @@ -0,0 +1,276 @@ +### Spring Boot中使用JavaMailSender发送邮件 ### +相信使用过Spring的众多开发者都知道Spring提供了非常好用的JavaMailSender接口实现邮件发送。在Spring Boot的Starter模块中也为此提供了自动化配置。下面通过实例看看如何在Spring Boot中使用JavaMailSender发送邮件。 + +#### 快速入门 #### +在Spring Boot的工程中的pom.xml中引入spring-boot-starter-mail依赖: + +```xml + + org.springframework.boot + spring-boot-starter-mail + +``` + +如其他自动化配置模块一样,在完成了依赖引入之后,只需要在application.properties中配置相应的属性内容。 + +下面我们以QQ邮箱为例,在application.properties中加入如下配置(注意替换自己的用户名和密码): + + spring.mail.host=smtp.qq.com + spring.mail.username=用户名 + spring.mail.password=密码 + spring.mail.properties.mail.smtp.auth=true + spring.mail.properties.mail.smtp.starttls.enable=true + spring.mail.properties.mail.smtp.starttls.required=true + +用户名是你的qq邮箱,密码是生成的授权码,具体如下: + +![](https://github.com/scalad/Note/blob/master/SpringBoot_Mail/image/qqMail1.png) + +![](https://github.com/scalad/Note/blob/master/SpringBoot_Mail/image/qqMail2.png) + +通过单元测试来实现一封简单邮件的发送: + +```java +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest +public class ApplicationTests { + @Autowired + private JavaMailSender mailSender; + @Test + public void sendSimpleMail() throws Exception { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom("dyc87112@qq.com"); + message.setTo("dyc87112@qq.com"); + message.setSubject("主题:简单邮件"); + message.setText("测试邮件内容"); + mailSender.send(message); + } +} +``` + +到这里,一个简单的邮件发送就完成了,运行一下该单元测试,看看效果如何? + +由于Spring Boot的starter模块提供了自动化配置,所以在引入了spring-boot-starter-mail依赖之后,会根据配置文件中的内容去创建JavaMailSender实例,因此我们可以直接在需要使用的地方直接@Autowired来引入邮件发送对象。 + +#### 进阶使用 #### +在上例中,我们通过使用SimpleMailMessage实现了简单的邮件发送,但是实际使用过程中,我们还可能会带上附件、或是使用邮件模块等。这个时候我们就需要使用MimeMessage来设置复杂一些的邮件内容,下面我们就来依次实现一下。 + +#### 发送附件 #### + +在上面单元测试中加入如下测试用例(通过MimeMessageHelper来发送一封带有附件的邮件): + +```Java +@Test +public void sendAttachmentsMail() throws Exception { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); + helper.setFrom("dyc87112@qq.com"); + helper.setTo("dyc87112@qq.com"); + helper.setSubject("主题:有附件"); + helper.setText("有附件的邮件"); + FileSystemResource file = new FileSystemResource(new File("weixin.jpg")); + helper.addAttachment("附件-1.jpg", file); + helper.addAttachment("附件-2.jpg", file); + mailSender.send(mimeMessage); +} +``` + +#### 嵌入静态资源 #### +除了发送附件之外,我们在邮件内容中可能希望通过嵌入图片等静态资源,让邮件获得更好的阅读体验,而不是从附件中查看具体图片,下面的测试用例演示了如何通过MimeMessageHelper实现在邮件正文中嵌入静态资源。 + +```Java +@Test +public void sendInlineMail() throws Exception { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); + helper.setFrom("dyc87112@qq.com"); + helper.setTo("dyc87112@qq.com"); + helper.setSubject("主题:嵌入静态资源"); + helper.setText("", true); + FileSystemResource file = new FileSystemResource(new File("weixin.jpg")); + helper.addInline("weixin", file); + mailSender.send(mimeMessage); +} +``` + +这里需要注意的是addInline函数中资源名称weixin需要与正文中cid:weixin对应起来 + +#### 模板邮件 #### + +通常我们使用邮件发送服务的时候,都会有一些固定的场景,比如重置密码、注册确认等,给每个用户发送的内容可能只有小部分是变化的。所以,很多时候我们会使用模板引擎来为各类邮件设置成模板,这样我们只需要在发送时去替换变化部分的参数即可。 + +在Spring Boot中使用模板引擎来实现模板化的邮件发送也是非常容易的,下面我们以velocity为例实现一下。 + +引入velocity模块的依赖: + +```xml + + org.springframework.boot + spring-boot-starter-velocity + +``` + +在resources/templates/下,创建一个模板页面template.vm: + +```html + + + +

你好, ${username}, 这是一封模板邮件!

+ + +``` + +我们之前在Spring Boot中开发Web应用时,提到过在Spring Boot的自动化配置下,模板默认位于resources/templates/目录下最后,我们在单元测试 +中加入发送模板邮件的测试用例,具体如下: + +最后,我们在单元测试中加入发送模板邮件的测试用例,具体如下: + +```java +@Test +public void sendTemplateMail() throws Exception { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); + helper.setFrom("dyc87112@qq.com"); + helper.setTo("dyc87112@qq.com"); + helper.setSubject("主题:模板邮件"); + Map model = new HashedMap(); + model.put("username", "didi"); + String text = VelocityEngineUtils.mergeTemplateIntoString( + velocityEngine, "template.vm", "UTF-8", model); + helper.setText(text, true); + mailSender.send(mimeMessage); +} +``` + +尝试运行一下,就可以收到内容为你好, didi, 这是一封模板邮件!的邮件。这里,我们通过传入username的参数,在邮件内容中替换了模板中的${username}变量 + +#### 常用的邮件发送工具 #### + +```Scala +import org.springframework.stereotype.Service +import org.springframework.mail.javamail.JavaMailSender +import org.springframework.beans.factory.annotation.Autowired +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Value +import org.springframework.mail.SimpleMailMessage +import org.springframework.mail.javamail.MimeMessageHelper +import org.springframework.core.io.FileSystemResource +import java.io.File +import javax.mail.MessagingException + +/** + * @description 邮件发送相关服务 + * @date 2017-04-04 + * @author silence + */ +@Service +class MailService { + + private final val LOGGER: Logger = LoggerFactory.getLogger(classOf[MailService]) + + @Autowired private var sender: JavaMailSender = _ + + @Value("${spring.mail.username}") private var username: String = _ + + /** + * @description 发送纯文本的简单邮件 + * @param to + * @param subject + * @param content + */ + def sendSimpleMail(to: String, subject: String, content: String) = { + val message = new SimpleMailMessage + message.setFrom(username) + message.setTo(to) + message.setSubject(subject) + message.setText(content) + try { + sender.send(message) + LOGGER.info("to + " + to + "邮件发送成功") + } catch { + case ex: Exception => { + LOGGER.info("to " + to + "邮件发送失败!" + ex.getMessage) + } + } + } + + /** + * @description 发送html格式的邮件 + * @param to + * @param subject + * @param content + */ + def sendHtmlMail(to: String, subject: String,content: String) = { + val message = sender.createMimeMessage() + val helper = new MimeMessageHelper(message, true) + helper.setFrom(username) + helper.setTo(to) + helper.setSubject(subject) + helper.setText(content, true) + try { + sender.send(message) + LOGGER.info("to + " + to + "html格式的邮件发送成功") + } catch { + case ex: MessagingException => { + LOGGER.info("to " + to + "html格式的邮件发送失败!" + ex.getMessage) + } + } + } + + /** + * @description 发送带附件的邮件 + * @param to + * @param subject + * @param content + * @param filePath + */ + def sendAttachmentsMail(to: String,subject: String, content: String, filePath: String) = { + val message = sender.createMimeMessage() + val helper = new MimeMessageHelper(message, true) + helper.setFrom(username) + helper.setTo(to) + helper.setSubject(subject) + helper.setText(content, true) + val file = new FileSystemResource(new File(filePath)) + val fileName = filePath.substring(filePath.lastIndexOf(File.separator)) + helper.addAttachment(fileName, file) + try { + sender.send(message) + LOGGER.info("to + " + to + "带附件邮件发送成功") + } catch { + case ex: MessagingException => { + LOGGER.info("to " + to + "带附件邮件发送失败!" + ex.getMessage) + } + } + } + + /** + * @description 发送嵌入静态资源(一般是图片)的邮件 + * @param to + * @param subject + * @param content 邮件内容,需要包括一个静态资源的id,比如: + * @param rscPath 静态资源路径和文件名 + * @param rscId 静态资源id + */ + def sendInlineResourceMail(to: String, subject: String, content: String, rscPath: String, rscId: String) = { + val message = sender.createMimeMessage() + val helper = new MimeMessageHelper(message, true) + helper.setFrom(username) + helper.setTo(to) + helper.setSubject(subject) + helper.setText(content, true) + val res = new FileSystemResource(new File(rscPath)) + helper.addInline(rscId, res) + try { + sender.send(message) + LOGGER.info("to + " + to + "嵌入静态资源的邮件发送成功") + } catch { + case ex: MessagingException => { + LOGGER.info("to " + to + "嵌入静态资源的邮件发送失败!" + ex.getMessage) + } + } + } +} +``` \ No newline at end of file diff --git a/SpringBoot_Mail/image/qqMail1.png b/SpringBoot_Mail/image/qqMail1.png new file mode 100644 index 0000000..3afd865 Binary files /dev/null and b/SpringBoot_Mail/image/qqMail1.png differ diff --git a/SpringBoot_Mail/image/qqMail2.png b/SpringBoot_Mail/image/qqMail2.png new file mode 100644 index 0000000..8ab2f03 Binary files /dev/null and b/SpringBoot_Mail/image/qqMail2.png differ diff --git a/SpringBoot_Redis/README.md b/SpringBoot_Redis/README.md new file mode 100644 index 0000000..9bbf602 --- /dev/null +++ b/SpringBoot_Redis/README.md @@ -0,0 +1,194 @@ +### SpringBoot Redis配置以及使用Redis作缓存 ### + +Spring Boot是为了简化Spring开发而生,从Spring 3.x开始,Spring社区的发展方向就是弱化xml配置文件而加大注解的戏份。最近召开的SpringOne2GX2015大会上显示:Spring Boot已经是Spring社区中增长最迅速的框架,前三名是:Spring Framework,Spring Boot和Spring Security,这个应该是未来的趋势。 + +我学习Spring Boot,是因为通过cli工具,spring boot开始往flask(python)、express(nodejs)等web框架发展和靠近,并且Spring Boot几乎不需要写xml配置文件。感兴趣的同学可以根据spring boot quick start这篇文章中的例子尝试下。 + +学习新的技术最佳途径是看官方文档,现在Spring boot的release版本是1.5.2-RELEASE,相应的参考文档是[Spring Boot Reference Guide(1.5.2-REALEASE)](http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/),如果有绝对英文比较吃力的同学,可以参考中文版Spring Boot参考指南。在前段时间阅读一篇技术文章,介绍如何阅读ios技术文档,我从中也有所收获,那就是我们应该重视spring.io上的guides部分——Getting Started Guides,这部分都是一些针对特定问题的demo,值得学习。 + +#### Spring Boot的项目结构 #### + + com + +- example + +- myproject + +- Application.java + | + +- domain + | +- Customer.java + | +- CustomerRepository.java + | + +- service + | +- CustomerService.java + | + +- web + +- CustomerController.java + +如上所示,Spring boot项目的结构划分为web->service->domain,其中domain文件夹可类比与业务模型和数据存储,即xxxBean和Dao层;service层是业务逻辑层,web是控制器。比较特别的是,这种类型的项目有自己的入口,即主类,一般命名为Application.java。Application.java不仅提供入口功能,还提供一些底层服务,例如缓存、项目配置等等。 + +#### 1. 自定义配置 #### +Spring Boot允许外化配置,这样你可以在不同的环境下使用相同的代码。你可以使用properties文件、yaml文件,环境变量和命令行参数来外化配置。使用@Value注解,可以直接将属性值注入到你的beans中。 + +Spring Boot使用一个非常特别的PropertySource来允许对值进行合理的覆盖,按照优先考虑的顺序排位如下: + +1. 命令行参数 +2. 来自java:comp/env的JNDI属性 +3. Java系统属性(System.getProperties()) +4. 操作系统环境变量 +5. 只有在random.*里包含的属性会产生一个RandomValuePropertySource +6. 在打包的jar外的应用程序配置文件(application.properties,包含YAML和profile变量) +7. 在打包的jar内的应用程序配置文件(application.properties,包含YAML和profile变量) +8. 在@Configuration类上的@PropertySource注解 +9. 默认属性(使用SpringApplication.setDefaultProperties指定) + +使用场景:可以将一个application.properties打包在Jar内,用来提供一个合理的默认name值;当运行在生产环境时,可以在Jar外提供一个application.properties文件来覆盖name属性;对于一次性的测试,可以使用特病的命令行开关启动,而不需要重复打包jar包。 + +具体的例子操作过程如下: + +* 新建配置文件(application.properties) + + spring.redis.database=0 + spring.redis.host=localhost + spring.redis.password= # Login password of the redis server. + spring.redis.pool.max-active=8 + spring.redis.pool.max-idle=8 + spring.redis.pool.max-wait=-1 + spring.redis.pool.min-idle=0 + spring.redis.port=6379 + spring.redis.sentinel.master= # Name of Redis server. + spring.redis.sentinel.nodes= # Comma-separated list of host:port pairs. + spring.redis.timeout=0 + +* 使用@PropertySource引入配置文件 + +```Java +@Configuration +@PropertySource(value = "classpath:/redis.properties") +@EnableCaching +public class CacheConfig extends CachingConfigurerSupport { + ...... +} +``` + +* 使用@Value引用属性值 + +```Java +@Configuration +@PropertySource(value = "classpath:/redis.properties") +@EnableCaching +public class CacheConfig extends CachingConfigurerSupport { + @Value("${spring.redis.host}") + private String host; + @Value("${spring.redis.port}") + private int port; + @Value("${spring.redis.timeout}") + private int timeout; + ...... +} +``` + +#### 2. redis使用 #### +* 添加pom配置 + +```xml + + org.springframework.boot + spring-boot-starter-redis + +``` + +* 编写CacheConfig + +```Java +@Configuration +@PropertySource(value = "classpath:/redis.properties") +@EnableCaching +public class CacheConfig extends CachingConfigurerSupport { + @Value("${spring.redis.host}") + private String host; + @Value("${spring.redis.port}") + private int port; + @Value("${spring.redis.timeout}") + private int timeout; + @Bean + public KeyGenerator wiselyKeyGenerator(){ + return new KeyGenerator() { + @Override + public Object generate(Object target, Method method, Object... params) { + StringBuilder sb = new StringBuilder(); + sb.append(target.getClass().getName()); + sb.append(method.getName()); + for (Object obj : params) { + sb.append(obj.toString()); + } + return sb.toString(); + } + }; + } + @Bean + public JedisConnectionFactory redisConnectionFactory() { + JedisConnectionFactory factory = new JedisConnectionFactory(); + factory.setHostName(host); + factory.setPort(port); + factory.setTimeout(timeout); //设置连接超时时间 + return factory; + } + @Bean + public CacheManager cacheManager(RedisTemplate redisTemplate) { + RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate); + // Number of seconds before expiration. Defaults to unlimited (0) + cacheManager.setDefaultExpiration(10); //设置key-value超时时间 + return cacheManager; + } + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + StringRedisTemplate template = new StringRedisTemplate(factory); + setSerializer(template); //设置序列化工具,这样ReportBean不需要实现Serializable接口 + template.afterPropertiesSet(); + return template; + } + private void setSerializer(StringRedisTemplate template) { + Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); + ObjectMapper om = new ObjectMapper(); + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + jackson2JsonRedisSerializer.setObjectMapper(om); + template.setValueSerializer(jackson2JsonRedisSerializer); + } +} +``` + +* 启动缓存,使用@Cacheable注解在需要缓存的接口上即可 + +```Java +@Service +public class ReportService { + @Cacheable(value = "reportcache", keyGenerator = "wiselyKeyGenerator") + public ReportBean getReport(Long id, String date, String content, String title) { + System.out.println("无缓存的时候调用这里---数据库查询"); + return new ReportBean(id, date, content, title); + } +} +``` + +* 测试验证 + * 运行方法如下: + * mvn clean package + * java -jar target/dailyReport-1.0-SNAPSHOT.jar + * 验证缓存起作用: + * 访问:http://localhost:8080/report/test + * 访问:http://localhost:8080/report/test2 + * 验证缓存失效(10s+后执行): + * 访问:http://localhost:8080/report/test2 + +#### 清楚缓存 #### +清除缓存是为了保持数据的一致性,CRUD (Create 创建,Retrieve 读取,Update 更新,Delete 删除) 操作中,除了 R 具备幂等性,其他三个发生的时候都可能会造成缓存结果和数据库不一致。为了保证缓存数据的一致性,在进行 CUD 操作的时候我们需要对可能影响到的缓存进行更新或者清除 + +```java + //清除缓存 + @CacheEvict(value = Array("findUsers" ), allEntries = true) + def saveUser(user: User): Int = { + userMapper.saveUser(user) + } +``` + +本示例用的都是 @CacheEvict 清除缓存。如果你的 CUD 能够返回 City 实例,也可以使用 @CachePut 更新缓存策略。笔者推荐能用 @CachePut 的地方就不要用 @CacheEvict,因为后者将所有相关方法的缓存都清理掉,比如上面三个方法中的任意一个被调用了的话,provinceCities 方法的所有缓存将被清除。 \ No newline at end of file diff --git a/SpringBoot_Run_In_Background/README.md b/SpringBoot_Run_In_Background/README.md new file mode 100644 index 0000000..395ae89 --- /dev/null +++ b/SpringBoot_Run_In_Background/README.md @@ -0,0 +1,99 @@ +### Spring Boot应用的后台运行配置 ### + +酱油一篇,整理一下关于Spring Boot后台运行的一些配置方式。在介绍后台运行配置之前,我们先回顾一下Spring Boot应用的几种运行方式: + +* 运行Spring Boot的应用主类 +* 使用Maven的Spring Boot插件mvn spring-boot:run来运行 +* 打成jar包后,使用java -jar运行 + +我们在开发的时候,通常会使用前两种,而在部署的时候往往会使用第三种。但是,我们在使用java -jar来运行的时候,并非后台运行。下面我们分别针对Windows和Linux/Unix两种环境,整理一下如何配置后台运行的方法。 + +#### Windows #### +Windows下比较简单,我们可以直接使用这款软件:AlwaysUp。如下图所示,简单、暴力、好用。 + +![](https://github.com/scalad/Note/blob/master/SpringBoot_Run_In_Background/image/13213400_9s9B.png) + +配置方式很简单,我们只需要把Spring Boot应用通过mvn install打成jar包,然后编写一个java -jar yourapp.jar的bat文件。再打开AlwaysUp,点击工具栏的第一个按钮,如下图所示,选择上面编写的bat文件,并填写服务名称。 + +![](https://github.com/scalad/Note/blob/master/SpringBoot_Run_In_Background/image/13213417_TI8z.png) + +完成了创建之后,在列表中可以看到我们配置的服务,通过右键选择Start xxx就能在后台将该应用启动起来了 + +#### Linux/Unix #### +下面我们来说说服务器上该如何来配置。实际上,实现的方法有很多种,这里就列两种还比较好用的方式: + +nohup和Shell + +该方法主要通过使用nohup命令来实现,该命令的详细介绍如下: + +nohup 命令 + + 用途:不挂断地运行命令。 + + 语法:nohup Command [ Arg … ][ & ] + + 描述:nohup 命令运行由 Command 参数和任何相关的 Arg 参数指定的命令,忽略所有挂断(SIGHUP)信号。在注销后使用 nohup 命令运行后台中的程序。要运行后台中的 nohup 命令,添加 &到命令的尾部。 + +所以,我们只需要使用nohup java -jar yourapp.jar &命令,就能让yourapp.jar在后台运行了。但是,为了方便管理,我们还可以通过Shell来编写一些用于启动应用的脚本,比如下面几个: + +* 关闭应用的脚本:stop.sh + +```shell +#!/bin/bash +PID=$(ps -ef | grep yourapp.jar | grep -v grep | awk '{ print $2 }') +if [ -z "$PID" ] +then + echo Application is already stopped +else + echo kill $PID + kill $PID +fi +``` + +* 启动应用的脚本:start.sh + +```shell +#!/bin/bash +nohup java -jar yourapp.jar --server.port=8888 & +``` + +* 整合了关闭和启动的脚本:run.sh,由于会先执行关闭应用,然后再启动应用,这样不会引起端口冲突等问题,适合在持续集成系统中进行反复调用。 + +```shell +#!/bin/bash +echo stop application +source stop.sh +echo start application +source start.sh +``` + +#### 系统服务 #### +在Spring Boot的Maven插件中,还提供了构建完整可执行程序的功能,什么意思呢?就是说,我们可以不用java -jar,而是直接运行jar来执行程序。这样我们就可以方便的将其创建成系统服务在后台运行了。主要步骤如下: + +* 在pom.xml中添加Spring Boot的插件,并注意设置executable配置 + +```xml + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + +``` + +* 在完成上述配置后,使用mvn install进行打包,构建一个可执行的jar包 + +* 创建软连接到/etc/init.d/目录下 + + sudo ln -s /var/yourapp/yourapp.jar /etc/init.d/yourapp + +* 在完成软连接创建之后,我们就可以通过如下命令对yourapp.jar应用来控制启动、停止、重启操作了 + + /etc/init.d/yourapp start|stop|restart + +原文地址:[http://blog.didispace.com/spring-boot-run-backend/](http://blog.didispace.com/spring-boot-run-backend/) diff --git a/SpringBoot_Run_In_Background/image/13213400_9s9B.png b/SpringBoot_Run_In_Background/image/13213400_9s9B.png new file mode 100644 index 0000000..576a341 Binary files /dev/null and b/SpringBoot_Run_In_Background/image/13213400_9s9B.png differ diff --git a/SpringBoot_Run_In_Background/image/13213417_TI8z.png b/SpringBoot_Run_In_Background/image/13213417_TI8z.png new file mode 100644 index 0000000..c9f8d36 Binary files /dev/null and b/SpringBoot_Run_In_Background/image/13213417_TI8z.png differ diff --git a/SpringBoot_Session_Share/README.md b/SpringBoot_Session_Share/README.md new file mode 100644 index 0000000..8ca981a --- /dev/null +++ b/SpringBoot_Session_Share/README.md @@ -0,0 +1,108 @@ +### Spring Boot Session共享 ### +分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多;还有一种是利用一些tomcat上的插件,修改tomcat配置文件,让tomcat自己去把Session放到Redis/Memcached/DB中去。这两种各有优缺,也都能解决问题 + +但是现在项目全线Spring Boot,并不自己维护Tomcat,而是由Spring去启动Tomcat。这样就会有一个问题:在服务器上并不存在一个持久存在的Tomcat程序,这样也无从去修改Tomcat的配置文件了。经过了一番搜索,发现Spring果然对这个问题有自己的解决方案,那就是Spring-Session + +Spring-Session是通过过滤器实现的session共享,具体原理可以自己去官网查,这里只说一下如何配置。整个项目基于Spring Boot,如果不是Boot项目就需要自己去调整了。 + +项目需要先准备一个Redis服务,在本地启动一个即可。还需要有一个已经使用session但是未做session共享的Spring Boot项目,下面我就讲述一下如何给这个项目加上基于redis的session共享。 + +#### 引入依赖 #### +首先,要在maven中加入以下依赖: + +```xml + + + org.springframework.session + spring-session + 1.2.2.RELEASE + + + org.springframework.boot + spring-boot-starter-redis + +     + org.springframework.session + spring-session-data-redis + 1.2.2.RELEASE + pom + + +``` + +#### 配置Redis #### +在项目目前在使用的properties文件中,加入如下配置: + + spring.redis.host=localhost + spring.redis.password=secret + spring.redis.port=6379 + +#### Spring配置 #### +在项目的目录中,创建一个Config.java文件(名称随意) + +```Java +@Configuration +@EnableRedisHttpSession +public class Config { + + @Bean + public JedisConnectionFactory connectionFactory() { + return new JedisConnectionFactory(); + } +} +``` + +@EnableRedisHttpSession这个注解就是最重要的东西,加了它之后,spring生产一个新的拦截器,用来实现Session共享的操作,具体实现这里暂不展开。而配置的这个Bean,则是让Spring根据配置文件中的配置连到Redis。 + +#### 测试 #### +首先我们开启两个tomcat服务,端口分别为8080和9090,在application.properties中进行设置 + + server.port=8080 + +接下来定义一个Controller: + +```Java +@RestController +@RequestMapping(value = "/admin/v1") +public class QuickRun { + @RequestMapping(value = "/first", method = RequestMethod.GET) + public Map firstResp (HttpServletRequest request){ + Map map = new HashMap<>(); + request.getSession().setAttribute("request Url", request.getRequestURL()); + map.put("request Url", request.getRequestURL()); + return map; + } + + @RequestMapping(value = "/sessions", method = RequestMethod.GET) + public Object sessions (HttpServletRequest request){ + Map map = new HashMap<>(); + map.put("sessionId", request.getSession().getId()); + map.put("message", request.getSession().getAttribute("map")); + return map; + } +} +``` + +启动之后进行访问测试,首先访问8080端口的tomcat,返回 + +> {"request Url":"http://localhost:8080/admin/v1/first"} + +接着,我们访问8080端口的sessions,返回: + +> {"sessionId":"efcc85c0-9ad2-49a6-a38f-9004403776b5","message":"http://localhost:8080/admin/v1/first"} + +最后,再访问9090端口的sessions,返回: + +> {"sessionId":"efcc85c0-9ad2-49a6-a38f-9004403776b5","message":"http://localhost:8080/admin/v1/first"} + +可见,8080与9090两个服务器返回结果一样,实现了session的共享 + +如果此时再访问9090端口的first的话,首先返回: + +> {"request Url":"http://localhost:9090/admin/v1/first"} + +而两个服务器的sessions都是返回: + +> {"sessionId":"efcc85c0-9ad2-49a6-a38f-9004403776b5","message":"http://localhost:9090/admin/v1/first"} + + \ No newline at end of file diff --git a/SpringMVC_RequestBody/README.md b/SpringMVC_RequestBody/README.md new file mode 100644 index 0000000..a9bc273 --- /dev/null +++ b/SpringMVC_RequestBody/README.md @@ -0,0 +1,31 @@ +### SpringMVC @RequestBody接收JSON参数 + +以前,一直以为在SpringMVC环境中,@RequestBody接收的是一个Json对象,一直在调试代码都没有成功,后来发现,其实 @RequestBody接收的是一个Json对象的字符串,而不是一个Json对象。然而在ajax请求往往传的都是Json对象,后来发现用 JSON.stringify(data)的方式就能将对象变成字符串。同时ajax请求的时候也要指定dataType: "json",contentType:"application/json" 这样就可以轻易的将一个对象或者List传到Java端,使用@RequestBody即可绑定对象或者List. + +```javascript +$(document).ready(function(){ + var saveDataAry=[]; + var data1={"userName":"test","address":"gz"}; + var data2={"userName":"ququ","address":"gr"}; + saveDataAry.push(data1); + saveDataAry.push(data2); + $.ajax({ + type:"POST", + url:"user/saveUser", + dataType:"json", + contentType:"application/json", + data:JSON.stringify(saveData), + success:function(data){ + + } + }); +}); +``` + +```Java +@RequestMapping(value = "saveUser", method = {RequestMethod.POST }}) +@ResponseBody +public void saveUser(@RequestBody List users) { + userService.batchSave(users); +} +``` \ No newline at end of file diff --git a/SpringMVC_Scala/README.md b/SpringMVC_Scala/README.md index 21b19c4..c8d55da 100644 --- a/SpringMVC_Scala/README.md +++ b/SpringMVC_Scala/README.md @@ -1,4 +1,4 @@ -###Scala和Spring集成(Scala和Java完成共同编译) +### Scala和Spring集成(Scala和Java完成共同编译) Scala看起来像是一种纯粹的面向对象编程语言,而又无缝地结合了命令式和函数式的编程风格。前日,Groovy创始人撰博文称Scala将取代Java。他说,如果他在2003年看过《Programming Scala》的话,那可能就不会有Groovy了。 diff --git a/WebCollector/image/webcollector.png b/WebCollector/image/webcollector.png new file mode 100644 index 0000000..1291907 Binary files /dev/null and b/WebCollector/image/webcollector.png differ diff --git a/Web_Notification/README.md b/Web_Notification/README.md new file mode 100644 index 0000000..6d9b3cd --- /dev/null +++ b/Web_Notification/README.md @@ -0,0 +1,119 @@ +### Web Notification API消息通知接口 + +#### 1、概述 + +Notification API是浏览器的通知接口,用于在用户的桌面(而不是网页上)显示通知信息,桌面电脑和手机都适用,比如通知用户收到了一封Email。具体的实现形式由浏览器自行部署,对于手机来说,一般显示在顶部的通知栏。 + +如果网页代码调用这个API,浏览器会询问用户是否接受。只有在用户同意的情况下,通知信息才会显示。 + +下面的代码用于检查浏览器是否支持这个API。 + +```javascript +if (window.Notification) { + // 支持 +} else { + // 不支持 +} +``` + +目前,Chrome和Firefox在桌面端部署了这个API,Firefox和Blackberry在手机端部署了这个API。 + +```javascript +if(window.Notification && Notification.permission !== "denied") { + Notification.requestPermission(function(status) { + var n = new Notification('通知标题', { body: '这里是通知内容!' }); + }); +} +``` + +上面代码检查当前浏览器是否支持Notification对象,并且当前用户准许使用该对象,然后调用Notification.requestPermission方法,向用户弹出一条通知。 + +#### 2、Notification对象属性和方法 + +##### 2.1、Notification.permission属性,用于读取用户给予的权限,它是一个只读属性,它有三种状态 + +* default:用户还没有做出任何许可,因此不会弹出通知 +* granted:用户明确同意接收通知 +* denied:用户明确拒绝接收通知 + +##### 2.2Notification.requestPermission() +Notification.requestPermission方法用于让用户做出选择,到底是否接收通知。它的参数是一个回调函数,该函数可以接收用户授权状态作为参数。 + +```javascript +Notification.requestPermission(function (status) { + if (status === "granted") { + var n = new Notification("Hi!"); + } else { + alert("Hi!"); + } +}); +``` + +上面代码表示,如果用户拒绝接收通知,可以用alert方法代替。 + +#### 3、Notification实例对象 +##### 3.1、Notification构造函数 +Notification对象作为构造函数使用时,用来生成一条通知 + +```javascript +var notification = new Notification(title, options); +``` + +Notification构造函数的title属性是必须的,用来指定通知的标题,格式为字符串。options属性是可选的,格式为一个对象,用来设定各种设置。该对象的属性如下: + +* dir:文字方向,可能的值为auto、ltr(从左到右)和rtl(从右到左),一般是继承浏览器的设置 +* lang:使用的语种,比如en-US、zh-CN +* body:通知内容,格式为字符串,用来进一步说明通知的目的 +* tag:通知的ID,格式为字符串。一组相同tag的通知,不会同时显示,只会在用户关闭前一个通知后,在原位置显示 +* icon:图表的URL,用来显示在通知上 + +上面这些属性,都是可读写的 + +下面是一个生成Notification实例对象的例子 + +```javascript +var notification = new Notification('收到新邮件', { + body: '您总共有3封未读邮件。' +}); + +notification.title // "收到新邮件" +notification.body // "您总共有3封未读邮件。" +``` + +##### 3.2、实例对象的事件 +Notification实例会触发以下事件 + +* show:通知显示给用户时触发 +* click:用户点击通知时触发 +* close:用户关闭通知时触发 +* error:通知出错时触发(大多数发生在通知无法正确显示时) + +这些事件有对应的onshow、onclick、onclose、onerror方法,用来指定相应的回调函数。addEventListener方法也可以用来为这些事件指定回调函数 + +```javascript +notification.onshow = function() { + console.log('Notification shown'); +}; +``` + +##### 3.3、close方法 +Notification实例的close方法用于关闭通知 + +```javascript +var n = new Notification("Hi!"); + +// 手动关闭 +n.close(); + +// 自动关闭 +n.onshow = function () { + setTimeout(n.close.bind(n), 5000); +} +``` + +上面代码说明,并不能从通知的close事件,判断它是否为用户手动关闭 + +### 4、浏览器兼容 +桌面版chrome实现的最为完善,相当于一个附属app了,chrome把消息系统独立了出来,点击桌面工具栏的提醒图标可以打开消息中心查看所有的消息,一次最多可以显示三条消息,firefox一次只能显示一条消息,循环发出多条消息的话firefox居然一条也不显示。。。手机上目前只有firefox支持,其他的浏览器都不支持,安卓上的chrome提醒应该在开发中(PS:没测过safari,其他的浏览器都是使用的最新版的)。 + +详细的例子可以看这里[https://scalad.github.io/FrontJS/Web_Notification/index.html](https://scalad.github.io/FrontJS/Web_Notification/index.html) \ No newline at end of file diff --git "a/algorithm/\351\207\221\351\242\235\350\275\254\346\215\242/Change.java" "b/algorithm/\351\207\221\351\242\235\350\275\254\346\215\242/Change.java" deleted file mode 100644 index c2aa96f..0000000 --- "a/algorithm/\351\207\221\351\242\235\350\275\254\346\215\242/Change.java" +++ /dev/null @@ -1,22 +0,0 @@ -public class Change { - - private static final char[] data = new char[] { '零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖' }; - - private static final char[] units = new char[] { '元', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿' }; - - public static void main(String[] args) { - System.out.println(convert(135689003)); - } - - public static String convert(int money) { - StringBuffer sbf = new StringBuffer(); - int unit = 0; - while (money != 0) { - sbf.insert(0, units[unit++]); - int number = money % 10; - sbf.insert(0, data[number]); - money /= 10; - } - return sbf.toString(); - } -} diff --git "a/algorithm/\351\207\221\351\242\235\350\275\254\346\215\242/README.md" "b/algorithm/\351\207\221\351\242\235\350\275\254\346\215\242/README.md" deleted file mode 100644 index 8536f53..0000000 --- "a/algorithm/\351\207\221\351\242\235\350\275\254\346\215\242/README.md" +++ /dev/null @@ -1,24 +0,0 @@ -金额转换,阿拉伯数字的金额转换成中国传统的形式如:(¥1011)->(一千零一拾一元整)输出。 - - public class Change { - - private static final char[] data = new char[] { '零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖' }; - - private static final char[] units = new char[] { '元', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿' }; - - public static void main(String[] args) { - System.out.println(convert(135689003)); - } - - public static String convert(int money) { - StringBuffer sbf = new StringBuffer(); - int unit = 0; - while (money != 0) { - sbf.insert(0, units[unit++]); - int number = money % 10; - sbf.insert(0, data[number]); - money /= 10; - } - return sbf.toString(); - } - } diff --git a/chrome_postman/README.md b/chrome_postman/README.md index d12c46c..84ba7d9 100644 --- a/chrome_postman/README.md +++ b/chrome_postman/README.md @@ -1,4 +1,4 @@ -###Chrome Postman进行rest请求模拟 +### Chrome Postman进行rest请求模拟 在web和移动端开发时,常常会调用服务器端的restful接口进行数据请求,为了调试,一般会先用工具进行测试,通过测试后才开始在开发中使用。这里介绍一下如何在chrome浏览器利用postman应用进行restful api接口请求测试。 diff --git a/comparable_vs_comparator/README.md b/comparable_vs_comparator/README.md index 1462fa5..4c36164 100644 --- a/comparable_vs_comparator/README.md +++ b/comparable_vs_comparator/README.md @@ -1,6 +1,6 @@ -####Java中Comparable和Comparator比较 +#### Java中Comparable和Comparator比较 -####Comparable 简介 +#### Comparable 简介 Comparable 是排序接口。 @@ -22,7 +22,7 @@ Comparable 接口仅仅只包括一个函数,它的定义如下: 说明: 假设我们通过 x.compareTo(y) 来“比较x和y的大小”。若返回“负数”,意味着“x比y小”;返回“零”,意味着“x等于y”;返回“正数”,意味着“x大于y”。 -####Comparator 简介 +#### Comparator 简介 Comparator 是比较器接口。 @@ -51,7 +51,7 @@ Comparator 接口仅仅只包括两个个函数,它的定义如下: (02) int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。 -####Comparator 和 Comparable 比较 +#### Comparator 和 Comparable 比较 Comparable是排序接口;若一个类实现了Comparable接口,就意味着“该类支持排序”。 而Comparator是比较器;我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。 @@ -291,7 +291,7 @@ f) 运行结果 Desc(age) sort, list:[ddd - 40, AAA - 30, ccc - 20, bbb - 10] eee - 100 EQUAL eee - 100 -###Difference between Comparator and Comparable in Java +### Difference between Comparator and Comparable in Java 以下根据[https://www.javacodegeeks.com/2013/03/difference-between-comparator-and-comparable-in-java.html](https://www.javacodegeeks.com/2013/03/difference-between-comparator-and-comparable-in-java.html)翻译而来 @@ -338,7 +338,7 @@ Comparator接口:实现可排序的类的对象不需要实现该接口,其 } }); -###Comparator vs Comparable +### Comparator vs Comparable ![](https://github.com/silence940109/Java/blob/master/image/comparableAndcomparator.png) diff --git a/hashMap_vs_hashTable/README.md b/hashMap_vs_hashTable/README.md index 8507963..c69ecfe 100644 --- a/hashMap_vs_hashTable/README.md +++ b/hashMap_vs_hashTable/README.md @@ -1,4 +1,4 @@ -###HashMap And HashTable +### HashMap And HashTable HashMap和Hashtable两个类都实现了Map接口,二者保存K-V对(key-value对);HashSet则实现了Set接口,性质类似于集合。Hashtable的应用非常广泛,HashMap是新框架中用来代替Hashtable的类,也就是说建议使用HashMap,不要使用Hashtable。可能你觉得Hashtable很好用,为什么不用呢?这里简单分析他们的区别。 一、继承的父类不同 @@ -150,12 +150,12 @@ Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩 Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。 -###我们能否让HashMap同步? +### 我们能否让HashMap同步? HashMap可以通过下面的语句进行同步: Map m = Collections.synchronizeMap(hashMap); -###关于ConcurrentHashMap +### 关于ConcurrentHashMap public class ConcurrentHashMap extends AbstractMap implements ConcurrentMap, Serializable { diff --git a/mysql_replace_into/README.md b/mysql_replace_into/README.md new file mode 100644 index 0000000..a0e7045 --- /dev/null +++ b/mysql_replace_into/README.md @@ -0,0 +1,34 @@ +### Mysql Replace Into + +replace into 跟 insert 功能类似, + +不同点在于:replace into 首先尝试插入数据到表中 + +1. 如果发现表中已经有此行数据(根据主键或者唯一索引判断)则先删除此行数据,然后插入新的数据。 + +2. 否则,直接插入新数据。 + +> 要注意的是:插入数据的表必须有主键或者是唯一索引!否则的话,replace into 会直接插入数据,这将导致表中出现重复的数据。 + +replace into 有三种形式: + + replace into tbl_name(col_name, ...) values(...) + replace into tbl_name(col_name, ...) select ... + replace into tbl_name set col_name=value, ... + +前两种形式用的多些。其中 “into” 关键字可以省略,不过最好加上 “into”,这样意思更加直观。另外,对于那些没有给予值的列,MySQL 将自动为这些列赋上默认值。 + +#### Insert update +INSERT 中 ON DUPLICATE KEY UPDATE的使用 + +如果您指定了ON DUPLICATE KEY UPDATE,并且insert行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则执行旧行UPDATE。例如,如果列a被定义为UNIQUE,并且包含值1,则以下两个语句具有相同的效果: + + mysql> INSERT INTO table (a,b,c) VALUES (1,2,3) ON DUPLICATE KEY UPDATE c=c+1; + mysql> UPDATE table SET c=c+1 WHERE a=1; + +总之 +如果表中不存在主键记录,replace和insert*update都与insert是一样的特点。 + +如果表中存在主键记录,replace相当于执行delete 和 insert两条操作,而insert*update的相当于执行if exist do update else do insert操作。 + +因此,如果replace填充的字段不全,则会导致未被更新的字段都会修改为默认值,并且如果有自增id的话,自增id会变化为最新的值(这样如果是以自增id为标志的话可能导致记录丢失);而insert*update只是更新部分字段,对于未被更新的字段不会变化(不会强制修改为默认值)。 \ No newline at end of file diff --git a/resume/grade.docx b/resume/grade.docx deleted file mode 100644 index eeca5e5..0000000 Binary files a/resume/grade.docx and /dev/null differ diff --git a/resume/grade/README.md b/resume/grade/README.md deleted file mode 100644 index 55ebcc3..0000000 --- a/resume/grade/README.md +++ /dev/null @@ -1,44 +0,0 @@ - - 学生成绩明细表 - - 长沙理工大学学生成绩明细(有效) - 院(系)/部:计算机与通信工程学院 行政班级:软件13-1 平均成绩:82.65 - 学 号:201316080135 姓 名:王培坤 打印时间:2016-09-10 - - -学年学期:2013-2014学年第一学期 - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/1.png) - -学年学期:2013-2014学年第二学期 - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/2.png) - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/3.png) - -学年学期:2014-2015学年第一学期 - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/4.png) - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/5.png) - -学年学期:2014-2015学年第二学期 - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/6.png) - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/7.png) - -学年学期:2015-2016学年第一学期 - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/8.png) - -学年学期:2015-2016学年第二学期 - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/9.png) - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/10.png) - -总评 - -![](https://github.com/silence940109/Java/blob/master/resume/grade/images/11.png) - diff --git a/resume/grade/images/1.png b/resume/grade/images/1.png deleted file mode 100644 index c7455a2..0000000 Binary files a/resume/grade/images/1.png and /dev/null differ diff --git a/resume/grade/images/10.png b/resume/grade/images/10.png deleted file mode 100644 index 5cbea67..0000000 Binary files a/resume/grade/images/10.png and /dev/null differ diff --git a/resume/grade/images/11.png b/resume/grade/images/11.png deleted file mode 100644 index 53bd948..0000000 Binary files a/resume/grade/images/11.png and /dev/null differ diff --git a/resume/grade/images/2.png b/resume/grade/images/2.png deleted file mode 100644 index 30e113b..0000000 Binary files a/resume/grade/images/2.png and /dev/null differ diff --git a/resume/grade/images/3.png b/resume/grade/images/3.png deleted file mode 100644 index 5d9708c..0000000 Binary files a/resume/grade/images/3.png and /dev/null differ diff --git a/resume/grade/images/4.png b/resume/grade/images/4.png deleted file mode 100644 index a156f18..0000000 Binary files a/resume/grade/images/4.png and /dev/null differ diff --git a/resume/grade/images/5.png b/resume/grade/images/5.png deleted file mode 100644 index b8ab82d..0000000 Binary files a/resume/grade/images/5.png and /dev/null differ diff --git a/resume/grade/images/6.png b/resume/grade/images/6.png deleted file mode 100644 index 34f3df7..0000000 Binary files a/resume/grade/images/6.png and /dev/null differ diff --git a/resume/grade/images/7.png b/resume/grade/images/7.png deleted file mode 100644 index dd9ad2c..0000000 Binary files a/resume/grade/images/7.png and /dev/null differ diff --git a/resume/grade/images/8.png b/resume/grade/images/8.png deleted file mode 100644 index a5fd8b1..0000000 Binary files a/resume/grade/images/8.png and /dev/null differ diff --git a/resume/grade/images/9.png b/resume/grade/images/9.png deleted file mode 100644 index 734b686..0000000 Binary files a/resume/grade/images/9.png and /dev/null differ diff --git a/resume/silence.jpg b/resume/silence.jpg deleted file mode 100644 index 4315aa7..0000000 Binary files a/resume/silence.jpg and /dev/null differ diff --git "a/resume/\344\270\252\344\272\272\347\256\200\345\216\206-2016-09.pdf" "b/resume/\344\270\252\344\272\272\347\256\200\345\216\206-2016-09.pdf" deleted file mode 100644 index a59f4f7..0000000 Binary files "a/resume/\344\270\252\344\272\272\347\256\200\345\216\206-2016-09.pdf" and /dev/null differ diff --git "a/resume/\344\270\252\344\272\272\347\256\200\345\216\2062016-2.pdf" "b/resume/\344\270\252\344\272\272\347\256\200\345\216\2062016-2.pdf" deleted file mode 100644 index 87e4adf..0000000 Binary files "a/resume/\344\270\252\344\272\272\347\256\200\345\216\2062016-2.pdf" and /dev/null differ diff --git "a/resume/\351\230\277\351\207\214\344\272\221.txt" "b/resume/\351\230\277\351\207\214\344\272\221.txt" deleted file mode 100644 index 96ab41e..0000000 --- "a/resume/\351\230\277\351\207\214\344\272\221.txt" +++ /dev/null @@ -1,9 +0,0 @@ -ϵͳ(123456) -http://120.27.114.229/PrescriptionTrackSystem/login.html - - -̳ǣδҼ -http://120.27.114.229/Shop/ - -WebSocket -http://120.27.114.229/WebSocketChat/index.html \ No newline at end of file diff --git a/springSecurity/README.md b/springSecurity/README.md index 065ff4e..1a1cf21 100644 --- a/springSecurity/README.md +++ b/springSecurity/README.md @@ -1,4 +1,4 @@ -####Spring Security 3.1 中功能强大的加密工具 PasswordEncoder +#### Spring Security 3.1 中功能强大的加密工具 PasswordEncoder 去年发生的密码泄漏事件,我们也对密码加密做了重新研究。 在筛选加密方法的过程中,发现了Spring Security 3.1.0版本中提供了新的PasswordEncoder,它的加密方法非常给力!虽然ns同学曾经说过“你的网站看起来很安全, 只是因为人家没精力或者没兴趣搞你...”,但是找到一个好的加密方法,无疑还是会有很大帮助的,至少会延迟破解的时间。 diff --git a/springmvc_HandlerInterceptorAdapter/README.md b/springmvc_HandlerInterceptorAdapter/README.md index 918a259..c89379b 100644 --- a/springmvc_HandlerInterceptorAdapter/README.md +++ b/springmvc_HandlerInterceptorAdapter/README.md @@ -1,4 +1,4 @@ -###Spring MVC 中 HandlerInterceptorAdapter的使用 +### Spring MVC 中 HandlerInterceptorAdapter的使用 一般情况下,对来自浏览器的请求的拦截,是利用Filter实现的,这种方式可以实现Bean预处理、后处理。 Spring MVC的拦截器不仅可实现Filter的所有功能,还可以更精确的控制拦截精度。 diff --git a/swagger/README.md b/swagger/README.md index 5fe483a..8e07ab2 100644 --- a/swagger/README.md +++ b/swagger/README.md @@ -1,4 +1,4 @@ -###SpringMVC Swagger Swagger UI +### SpringMVC Swagger Swagger UI 按照现在的趋势,前后端分离几乎已经是业界对开发和部署方式所达成的一种共识。所谓的前后端分离,并不是传统行业中的按部门划分,一部分人只做前端(HTML/CSS/JavaScript等等),另一部分人只做后端(或者叫服务端),因为这种方式是不工作的:比如很多团队采取了后端的模板技术(JSP, FreeMarker, ERB等等),前端的开发和调试需要一个后台Web容器的支持,从而无法将前后端开发和部署做到真正的分离。 @@ -8,7 +8,7 @@ 不过,仅仅靠纪律是不够的,还需要通过工具的辅助来提高效率。下面,我们就来看一下,一个API设计工具——Swagger,将如何帮助我们更好的实现“前后端分离”。 -####Swagger +#### Swagger Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法,参数和模型紧密集成到服务器端的代码,允许API来始终保持同步。Swagger 让部署管理和使用功能强大的API从未如此简单。 @@ -74,7 +74,7 @@ Swagger 是一个规范和完整的框架,用于生成、描述、调用和可 该swagger作为一个bean被spring所管理 -####配置Swagger UI +#### 配置Swagger UI 到[Swagger UI](https://github.com/swagger-api/swagger-ui)Github主页下载 diff --git "a/\347\201\260\345\272\246\345\217\221\345\270\203/README.md" "b/\347\201\260\345\272\246\345\217\221\345\270\203/README.md" index e71ff03..0f4e470 100644 --- "a/\347\201\260\345\272\246\345\217\221\345\270\203/README.md" +++ "b/\347\201\260\345\272\246\345\217\221\345\270\203/README.md" @@ -1,4 +1,4 @@ -###灰度发布 +### 灰度发布 灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。 @@ -6,8 +6,5 @@ ![](https://github.com/silence940109/Java/blob/master/%E7%81%B0%E5%BA%A6%E5%8F%91%E5%B8%83/image/1.png) -####作用 -及早获得用户的意见反馈,完善产品功能,提升产品质量 让用户参与产品测试,加强与用户互动 降低产品升级所影响的用户范围 - - - +#### 作用 +及早获得用户的意见反馈,完善产品功能,提升产品质量 让用户参与产品测试,加强与用户互动 降低产品升级所影响的用户范围 \ No newline at end of file