Java启动命令是所有java应用程序的入口,通过它来启动Java运行时环境,并加载相关的class。不过由于IDE的盛行,我们Java程序员中的大多数并不是非常的了解Java启动命令。本文希望做一个Java启动命令的汇总,和各位同道分享,也便于日后作为自己的参考。
Java启动命令有两个,java
以及javaw
,它们的唯一区别是javaw
不会启动一个控制台窗口(启动失败的时候会弹出附有错误信息的窗口),而java
会。不希望有控制台的时候用javaw
,其它时候用java
。它们的语法结构都一样,只需要记住其中一个即可。调用程序有两种方式,指定Class以及指定jar文件,具体的语法如下:
其中[options]表示Java运行环境的可选配置信息,会影响java运行环境,是性能调优的关键所在,可以传多个可选项。class表示的是包含main函数的class名称(含包名)。-jar和file.jar配对使用,-jar表示用jar方式启动,而file.jar表示的是jar文件的名称,替换为自己的jar文件名字即可。[arguments]表示的是程序自身的参数,会被传到main函数的参数数组里面,为程序自己所使用。
Java启动命令可选项(options)大致可分为标准和非标准两种,非标准的可选项不保证在所有平台上都实现,并且未来的版本中可能会被修改且不告知,总之就是不稳定(Unstable)。不过有的非标准可选项还是非常有用的,后面我们会谈到。
-client 与 -server
以clien或者server模式启动,二选一,Java运行时环境会依场景来优化自己的配置策略。值得指出的是64位的Java不支持client模式,默认就是server。不同平台的默认配置可以参考Server-Class Machine Detection
-cp 和 -classpath
最重要的可选项之一,指定Java运行时环境搜索class的路径。-cp只是-classpath的简写,做相同的事情,用其中之一即可。多个路径用英文里面的分号分隔。如果-cp和-classpath都没有使用,CLASSPATH环境变量也没用设定,那么就是当前路径(.)。
-Dproperty=value
设定系统属性值,比如编码-Dfile.encoding=UTF-8。可以设定一些系统使用的属性,也可以用来向程序传递值,比如程序的根目录。 可以通过System.getProperty("keyname")
来获取属性。
-help与-?
都是要求显示帮助信息
disableassertions[:packagename…|:classname]与da[:packagename…|:classname]
禁用断言,可以指定包名(注意包后面的三个点)或者class的名称。默认就是禁用的。da只是disableassertions的缩写,使用方法完全一样。 禁用包: da:org.slf4j...
禁用类: da:org.slf4j.LoggerFactory
enableassertions[:packagename…|:classname]与ea[:packagename…|:classname]
启用断言,可以指定包名(注意包后面的三个点)或者class的名称。ea只是enableassertions的缩写,使用方法完全一样。 启用包: ea:org.slf4j...
启用类: ea:org.slf4j.LoggerFactory
disablesystemassertions与dsa
禁用系统类(syetem class)断言
enablesystemassertions与esa
启用系统类(syetem class)断言
-version 与 -showversion
这两个都显示Java的版本信息,不过后者会把help的信息也显示出来。
verbose:class, verbose:gc, “verbose:jni”
verbose:class显示class加载信息,verbose:gc显示Java垃圾回收的信息。verbose:jni显示JNI(Java Native Interface)信息。
-agentlib:agentlibname[=options]与-agentpath:pathtoagent[=options]
都是加载本地代理库(Native Agent Library),-agentlib只需要library的名称,且会根据不同的操作系统转为对应的文件(Windows为DLL)。 -agentpath指定library的绝对路径。
非标准的可选项都以-X开始,其中的部分选项如下:
-X
显示所有非标准选项的信息。看看都有些什么非标准选项吧。java -X
。
-Xms
设置Java堆的初始化大小。例如 -Xms1024m,Java堆的初始化大小就设置为1G。
-Xmx
设置Java堆的最大值。例如 -Xmx3072m,Java堆的最大值就设置为3G。
-Xss
设置Java线程栈的值。例如 -Xss128m,Java线程栈的值就设置为128兆。
-Xnoclassgc
禁用Java垃圾回收器。
-Xincgc
启用Java增量式垃圾回收器。
-Xmixed
解释模式和编译模式混合执行
-Xint
仅解释模式执行
-Xshare:on,-Xshare:off,-Xshare:auto 设定类数据共享CDS(Class data sharing)是否开启。-Xshare:on要求一定要用共享的类数据;-Xshare:auto表示让系统自己判断 是否有类共享数据,有就用,没有不用;-Xshare:off禁用共享类数据。
-Xcheck:jni
针对JNI函数做额外的检查
不稳定的选项当然是非标准的,都以-XX开头。这一类参数有很多,暂时只列其中的几个:
-XX:PermSize<=size>和-XX:MaxPermSize<=size>
设置Java永久保存区域(Permanent Generation Space)的大小。PermSize设置初始值,MaxPermSize设置最大值。 该区域主要存储class的信息,且不会被 垃圾回收器回收,如果加载的class过多,就会报错:java.lang.OutOfMemoryError: PermGen space。 -XX:PermSize=64m -XX:MaxPermSize=128m,表示Java永久保存区域大小初始化问为64兆,最大为128兆。
-XX:+UseParallelGC
开启并行Java回收器
-XX:+PrintGCDetails
显示比-verbose:gc
更多更准确的垃圾回收信息
-XX:+TraceClassLoading
显示类加载以及卸载的信息
class或者jar文件之后的字符串会传给main函数的String[] args
参数,多个参数用空格隔开。如果某一个参数包含多个单词,那么需要用引号包起来。当有很多程序参数,且有的必填,有的可选的时候,如果不设计好,顺序是很头疼的问题。一种解法是传键值对(key=value),完全不用在意参数的顺序。在main函数里面把键值对解析成Map,然后再校验以及使用。简单的示例代码如下:
1 | public static void main(String[] args) { |
一个对象的一生:我是一个普通的Java对象,我出生在Eden区,在Eden区我还看到和我长的很像的小兄弟,我们在Eden区中玩了挺长时间。有一天Eden区中的人实在是太多了,我就被迫去了Survivor区的“From”区,自从去了Survivor区,我就开始漂了,有时候在Survivor的“From”区,有时候在Survivor的“To”区,居无定所。直到我18岁的时候,爸爸说我成人了,该去社会上闯闯了。于是我就去了年老代那边,年老代里,人很多,并且年龄都挺大的,我在这里也认识了很多人。在年老代里,我生活了20年(每次GC加一岁),然后被回收。
JVM堆内存分为2块:Permanent Space 和 Heap Space。
所有新生成的对象首先都是放在年轻代。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代一般分3个区,1个Eden区,2个Survivor区(from 和 to)。
大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当一个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当另一个Survivor区也满了的时候,从前一个Survivor区复制过来的并且此时还存活的对象,将可能被复制到年老代。
2个Survivor区是对称的,没有先后关系,所以同一个Survivor区中可能同时存在从Eden区复制过来对象,和从另一个Survivor区复制过来的对象;而复制到年老区的只有从另一个Survivor区过来的对象。而且,因为需要交换的原因,Survivor区至少有一个是空的。特殊的情况下,根据程序需要,Survivor区是可以配置为多个的(多于2个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
针对年轻代的垃圾回收即 Young GC。
在年轻代中经历了N次(可配置)垃圾回收后仍然存活的对象,就会被复制到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
针对年老代的垃圾回收即 Full GC。
用于存放静态类型数据,如 Java Class, Method 等。持久代对垃圾回收没有显著影响。但是有些应用可能动态生成或调用一些Class,例如 Hibernate CGLib 等,在这种时候往往需要设置一个比较大的持久代空间来存放这些运行过程中动态增加的类型。
当一组对象生成时,内存申请过程如下:
这是最常见的情况,产生的原因可能是:设置的内存参数Xmx过小或程序的内存泄露及使用不当问题。
例如循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存。还有的时候虽然不会报内存溢出,却会使系统不间断的垃圾回收,也无法处理其它请求。这种情况下除了检查程序、打印堆内存等方法排查,还可以借助一些内存分析工具,比如MAT就很不错。
通常由于持久代设置过小,动态加载了大量Java类而导致溢出,解决办法唯有将参数 -XX:MaxPermSize 调大(一般256m能满足绝大多数应用程序需求)。将部分Java类放到容器共享区(例如Tomcat share lib)去加载的办法也是一个思路,但前提是容器里部署了多个应用,且这些应用有大量的共享类库。
-XX:NewSize=1024m:设置年轻代初始值为1024M。
-XX:MaxNewSize=1024m:设置年轻代最大值为1024M。
-XX:PermSize=256m:设置持久代初始值为256M。
-Xmn,-XX:NewSize/-XX:MaxNewSize,-XX:NewRatio 3组参数都可以影响年轻代的大小,混合使用的情况下,优先级是什么?
如下:
推荐使用-Xmn参数,原因是这个参数简洁,相当于一次设定 NewSize/MaxNewSIze,而且两者相等,适用于生产环境。-Xmn 配合 -Xms/-Xmx,即可将堆内存布局完成。
-Xmn参数是在JDK 1.4 开始支持。
JVM给出了3种选择:串行收集器、并行收集器、并发收集器。串行收集器只适用于小数据量的情况,所以生产环境的选择主要是并行收集器和并发收集器。
默认情况下JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0以后,JVM会根据当前系统配置进行智能判断。
-XX:+UseParNewGC:设置年轻代为并发收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此参数。
-XX:CMSFullGCsBeforeCompaction=0:由于并发收集器不对内存空间进行压缩和整理,所以运行一段时间并行收集以后会产生内存碎片,内存使用效率降低。此参数设置运行0次Full GC后对内存空间进行压缩和整理,即每次Full GC后立刻开始压缩和整理内存。
java.lang.Thread.setPriority()
生效,不启用则无效。承受海量访问的动态Web应用
服务器配置:8 CPU, 8G MEM, JDK 1.6.X
参数方案:
-server -Xmx3550m -Xms3550m -Xmn1256m -Xss128k -XX:SurvivorRatio=6 -XX:MaxPermSize=256m -XX:ParallelGCThreads=8 -XX:MaxTenuringThreshold=0 -XX:+UseConcMarkSweepGC
调优说明:
-XX:ParallelGCThreads=8 配置并行收集器的线程数,即同时8个线程一起进行垃圾回收。此值一般配置为与CPU数目相等。
-XX:MaxTenuringThreshold=0 设置垃圾最大年龄(在年轻代的存活次数)。如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。对于年老代比较多的应用,可以提高效率;如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。根据被海量访问的动态Web应用之特点,其内存要么被缓存起来以减少直接访问DB,要么被快速回收以支持高并发海量请求,因此其内存对象在年轻代存活多次意义不大,可以直接进入年老代,根据实际应用效果,在这里设置此值为0。
高性能数据处理的工具应用
服务器配置:1 CPU, 4G MEM, JDK 1.6.X
参数方案:
-server -XX:PermSize=196m -XX:MaxPermSize=196m -Xmn320m -Xms768m -Xmx1024m
调优说明:
-XX:PermSize=196m -XX:MaxPermSize=196m 根据集成构建的特点,大规模的系统编译可能需要加载大量的Java类到内存中,所以预先分配好大量的持久代内存是高效和必要的。
-Xmn320m 遵循年轻代大小为整个堆的3/8原则。
-Xms768m -Xmx1024m 根据系统大致能够承受的堆内存大小设置即可。
在64位服务器上运行应用程序,构建执行时,用 jmap -heap 11540 命令观察JVM堆内存状况如下:
1 | Attaching to process ID 11540, please wait... |
结果是比较健康的。
本文属于转载:原文(JVM 堆内存设置原理)
]]>1、无限循环的while会导致CPU使用率飙升吗?
2、经常使用Young GC会导致CPU占用率飙升吗?
3、具有大量线程的应用程序的CPU使用率是否较高?
4、CPU使用率高的应用程序的线程数是多少?
5、处于BLOCKED状态的线程会导致CPU使用率飙升吗?
6、分时操作系统中的CPU是消耗 us
还是 sy
?
CPU%= 1 - idleTime / sysTime * 100
idleTime:CPU空闲的时间
sysTime:CPU处于用户模式和内核模式的时间总和
人们常说,计算密集型程序的CPU密集程度更高。
那么,JAVA应用程序中的哪些操作更加CPU密集?
以下列出了常见的CPU密集型操作:
1、频繁的GC; 如果访问量很高,可能会导致频繁的GC甚至FGC。当调用量很大时,内存分配将如此之快以至于GC线程将连续执行,这将导致CPU飙升。
2、序列化和反序列化。稍后将给出一个示例:当程序执行xml解析时,调用量会增加,从而导致CPU变满。
3、序列化和反序列化;
4、正则表达式。我遇到了正则表达式使CPU充满的情况; 原因可能是Java正则表达式使用的引擎实现是NFA自动机,它将在字符匹配期间执行回溯。
5、线程上下文切换; 有许多已启动的线程,这些线程的状态在Blocked(锁定等待,IO等待等)和Running之间发生变化。当锁争用激烈时,这种情况很容易发生。
6、有些线程正在执行非阻塞操作,例如 while(true)
语句。如果在程序中计算需要很长时间,则可以使线程休眠。
现在,分时操作系统使用循环方式为进程调度分配时间片。如果进程正在等待或阻塞,那么它将不会使用CPU资源。线程称为轻量级进程,并共享进程资源。因此,线程调度在CPU中也是分时的。但在Java中,我们使用JVM进行线程调度。因此,通常,线程调度有两种模式:时间共享调度和抢占式调度。
是。
首先,无限循环将调用CPU寄存器进行计数,此操作将占用CPU资源。那么,如果线程始终处于无限循环状态,CPU是否会切换线程?
除非操作系统时间片到期,否则无限循环不会放弃占用的CPU资源,并且无限循环将继续向系统请求时间片,直到系统没有空闲时间来执行任何其他操作。
stackoverflow中也提出了这个问题:为什么无意的无限循环增加了CPU的使用?
是。
Young GC本身就是JVM用于垃圾收集的操作,它需要计算内存和调用寄存器。因此,频繁的Young GC必须占用CPU资源。
让我们来看一个现实世界的案例。for循环从数据库中查询数据集合,然后再次封装新的数据集合。如果内存不足以存储,JVM将回收不再使用的数据。因此,如果所需的存储空间很大,您可能会收到CPU使用率警报。
不是。
如果通过jstack检查系统线程状态时线程总数很大,但处于Runnable和Running状态的线程数不多,则CPU使用率不一定很高。
我遇到过这样一种情况:系统线程的数量是1000+,其中超过900个线程处于BLOCKED和WAITING状态。该线程占用很少的CPU。
但是大多数情况下,如果线程数很大,那么常见的原因是大量线程处于BLOCKED和WAITING状态。
不是。
高CPU使用率的关键因素是计算密集型操作。如果一个线程中有大量计算,则CPU使用率也可能很高。这也是数据脚本任务需要在大规模集群上运行的原因。
不会。
CPU使用率的飙升更多是由于上下文切换或过多的可运行状态线程。处于阻塞状态的线程不一定会导致CPU使用率上升。
us
或 sy
值很高,这意味着什么?您可以使用命令查找CPU的值 us
和 sy
值 top
,如以下示例所示:
us
:用户空间占用CPU的百分比。简单来说,高我们是由程序引起的。通过分析线程堆栈很容易找到有问题的线程。
sy
:内核空间占用CPU的百分比。当sy为高时,如果它是由程序引起的,那么它基本上是由于线程上下文切换。
如何找出CPU使用率高的原因?下面简要描述分析过程。
如果发现应用程序服务器的CPU使用率很高,请首先检查线程数,JVM,系统负载等参数,然后使用这些参数来证明问题的原因。其次,使用jstack打印堆栈信息并使用工具分析线程使用情况(建议使用fastThread,一个在线线程分析工具)。
以下是一个真实案例:
一天晚上,我突然收到一条消息,说CPU使用率达到了100%。然后我用jstack导出了线程栈信息。
进一步检查日志:
1 | onsumer_ODC_L_nn_jmq919_1543834242875 - priority:10- threadid:0x00007fbf7011e000- nativeid:0x2f093- state:RUNNABLE |
现在通过这个日志找到了问题:用于反序列化MQ消息实体的方法导致CPU使用率飙升。
数字签名、信息加密 是前后端开发都经常需要使用到的技术,应用场景包括了用户登入、交易、信息通讯、oauth
等等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的 签名加密算法 来达到业务目标。这里简单的给大家介绍几种常见的签名加密算法和一些典型场景下的应用。
数字签名,简单来说就是通过提供 可鉴别 的 数字信息 验证 自身身份 的一种方式。一套 数字签名 通常定义两种 互补 的运算,一个用于 签名,另一个用于 验证。分别由 发送者 持有能够 代表自己身份 的 私钥 (私钥不可泄露),由 接受者 持有与私钥对应的 公钥 ,能够在 接受 到来自发送者信息时用于 验证 其身份。
注意:图中 加密过程 有别于 公钥加密,更多 介绍戳这里。签名 最根本的用途是要能够唯一 证明发送方的身份,防止 中间人攻击、
CSRF
跨域身份伪造。基于这一点在诸如 设备认证、用户认证、第三方认证 等认证体系中都会使用到 签名算法 (彼此的实现方式可能会有差异)。
数据加密 的基本过程,就是对原来为 明文 的文件或数据按 某种算法 进行处理,使其成为 不可读 的一段代码,通常称为 “密文”。通过这样的途径,来达到 保护数据 不被 非法人窃取、阅读的目的。
加密 的 逆过程 为 解密,即将该 编码信息 转化为其 原来数据 的过程。
加密算法分 对称加密 和 非对称加密,其中对称加密算法的加密与解密 密钥相同,非对称加密算法的加密密钥与解密 密钥不同,此外,还有一类 不需要密钥 的 散列算法。
常见的 对称加密 算法主要有
DES
、3DES
、AES
等,常见的 非对称算法 主要有RSA
、DSA
等,散列算法 主要有SHA-1
、MD5
等。
对称加密算法 是应用较早的加密算法,又称为 共享密钥加密算法。在 对称加密算法 中,使用的密钥只有一个,发送 和 接收 双方都使用这个密钥对数据进行 加密 和 解密。这就要求加密和解密方事先都必须知道加密的密钥。
数据加密过程:在对称加密算法中,数据发送方 将 明文 (原始数据) 和 加密密钥 一起经过特殊 加密处理,生成复杂的 加密密文 进行发送。
数据解密过程:数据接收方 收到密文后,若想读取原数据,则需要使用 加密使用的密钥 及相同算法的 逆算法 对加密的密文进行解密,才能使其恢复成 可读明文。
非对称加密算法,又称为 公开密钥加密算法。它需要两个密钥,一个称为 公开密钥 (public key
),即 公钥,另一个称为 私有密钥 (private key
),即 私钥。
因为 加密 和 解密 使用的是两个不同的密钥,所以这种算法称为 非对称加密算法。
如果使用 公钥 对数据 进行加密,只有用对应的 私钥 才能 进行解密。
如果使用 私钥 对数据 进行加密,只有用对应的 公钥 才能 进行解密。
例子:甲方生成 一对密钥 并将其中的一把作为 公钥 向其它人公开,得到该公钥的 乙方 使用该密钥对机密信息 进行加密 后再发送给甲方,甲方再使用自己保存的另一把 专用密钥 (私钥),对 加密 后的信息 进行解密。
MD5
用的是 哈希函数,它的典型应用是对一段信息产生 信息摘要,以 防止被篡改。严格来说,MD5
不是一种 加密算法 而是 摘要算法。无论是多长的输入,MD5
都会输出长度为 128bits
的一个串 (通常用 16
进制 表示为 32
个字符)。
1 | public static final byte[] computeMD5(byte[] content) { |
SHA1
是和 MD5
一样流行的 消息摘要算法,然而 SHA1
比 MD5
的 安全性更强。对于长度小于 2 ^ 64
位的消息,SHA1
会产生一个 160
位的 消息摘要。基于 MD5
、SHA1
的信息摘要特性以及 不可逆 (一般而言),可以被应用在检查 文件完整性 以及 数字签名 等场景。
1 | public static byte[] computeSHA1(byte[] content) { |
HMAC
是密钥相关的 哈希运算消息认证码(Hash-based Message Authentication Code),HMAC
运算利用 哈希算法 (MD5
、SHA1
等),以 一个密钥 和 一个消息 为输入,生成一个 消息摘要 作为 输出。
HMAC
发送方 和 接收方 都有的 key
进行计算,而没有这把 key
的第三方,则是 无法计算 出正确的 散列值的,这样就可以 防止数据被篡改。
1 | package net.pocrd.util; |
测试结论:
HMAC
算法实例在 多线程环境 下是 不安全的。但是需要在 多线程访问 时,进行同步的辅助类,使用ThreadLocal
为 每个线程缓存 一个实例可以避免进行锁操作。
AES
、DES
、3DES
都是 对称 的 块加密算法,加解密 的过程是 可逆的。常用的有 AES128
、AES192
、AES256
(默认安装的 JDK
尚不支持 AES256
,需要安装对应的 jce
补丁进行升级 jce1.7
,jce1.8
)。
DES
加密算法是一种 分组密码,以 64
位为 分组对数据 加密,它的 密钥长度 是 56
位,加密解密 用 同一算法。
DES
加密算法是对 密钥 进行保密,而 公开算法,包括加密和解密算法。这样,只有掌握了和发送方 相同密钥 的人才能解读由 DES
加密算法加密的密文数据。因此,破译 DES
加密算法实际上就是 搜索密钥的编码。对于 56
位长度的 密钥 来说,如果用 穷举法 来进行搜索的话,其运算次数为 2 ^ 56
次。
是基于 DES
的 对称算法,对 一块数据 用 三个不同的密钥 进行 三次加密,强度更高。
AES
加密算法是密码学中的 高级加密标准,该加密算法采用 对称分组密码体制,密钥长度的最少支持为 128
位、 192
位、256
位,分组长度 128
位,算法应易于各种硬件和软件实现。这种加密算法是美国联邦政府采用的 区块加密标准。
AES
本身就是为了取代 DES
的,AES
具有更好的 安全性、效率 和 灵活性。
1 | import net.pocrd.annotation.NotThreadSafe; |
RSA
加密算法是目前最有影响力的 公钥加密算法,并且被普遍认为是目前 最优秀的公钥方案 之一。RSA
是第一个能同时用于 加密 和 数字签名 的算法,它能够 抵抗 到目前为止已知的 所有密码攻击,已被 ISO
推荐为公钥数据加密标准。
RSA
加密算法 基于一个十分简单的数论事实:将两个大 素数 相乘十分容易,但想要对其乘积进行 因式分解 却极其困难,因此可以将 乘积 公开作为 加密密钥。
1 | import net.pocrd.annotation.NotThreadSafe; |
ECC
也是一种 非对称加密算法,主要优势是在某些情况下,它比其他的方法使用 更小的密钥,比如 RSA
加密算法,提供 相当的或更高等级 的安全级别。不过一个缺点是 加密和解密操作 的实现比其他机制 时间长 (相比 RSA
算法,该算法对 CPU
消耗严重)。
1 | import net.pocrd.annotation.NotThreadSafe; |
名称 | 安全性 | 速度 |
---|---|---|
SHA-1 | 高 | 慢 |
MD5 | 中 | 快 |
名称 | 密钥名称 | 运行速度 | 安全性 | 资源消耗 |
---|---|---|---|---|
DES | 56位 | 较快 | 低 | 中 |
3DES | 112、168位 | 慢 | 中 | 高 |
AES | 128、192、256位 | 快 | 高 | 低 |
名称 | 成熟的 | 安全性 | 运算速度 | 资源消耗 |
---|---|---|---|---|
RSA | 高 | 高 | 中 | 中 |
ECC | 高 | 高 | 慢 | 高 |
密钥管理:比较难,不适合互联网,一般用于内部系统
安全性:中
加密速度:快好 几个数量级 (软件加解密速度至少快 100
倍,每秒可以加解密数 M
比特 数据),适合大数据量的加解密处理
密钥管理:密钥容易管理
安全性:高
加密速度:比较慢,适合 小数据量 加解密或数据签名
本文介绍了 数字签名,加密和解密,对称加密和非对称加密,然后详细介绍了 MD5
,SHA-1
,HMAC
,DES/AES
,RSA
和 ECC
这几种加密算法和代码示例。
本文属于转载:原文(浅谈常见的七种加密算法及实现)
]]>阿里开源的一个数据库中间件,专门为大数据量的项目做分库分表用的。有如下特点:
一个彻底开源的,面向企业应用开发的大数据库集群
支持事务、ACID、可以替代MySQL的加强版数据库
一个可以视为MySQL集群的企业级数据库,用来替代昂贵的Oracle集群
一个融合内存缓存技术、NoSQL技术、HDFS大数据的新型SQL Server
结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品
一个新颖的数据库中间件产品
官网地址: http://www.mycat.org.cn/
mycat权威指南: http://www.mycat.org.cn/document/mycat-definitive-guide.pdf
话不多说,一张mycat架构图就能让你知道mycat的角色以及作用。
mycat1.6版本架构如下:
由图可知,mycat位于数据库和应用层(APP)之间,它的角色就是帮我们管理数据库集群,而提供应用统一访问数据库接口。
垂直分库通俗来说就是按照功能划分,将不同的数据放在不同的数据库中。
水平分表通俗来说就是某表数据量太大了,那么我们很自然地想到是加多一个表来存储数据,这样查询速度才会更快。那么水平分表就是表结构是一样的,只不过存的数据不一样而已。
逻辑库:通常对实际应用来说,并不需要知道中间件的存在,业务开发人员只需要知道数据库的概念,所以数据库中间件可以被看做是一个或多个数据库集群构成的逻辑库。
逻辑表:分布式数据库中,对应用来说,读写数据的表就是逻辑表。逻辑表,可以是数据切分后,分布在一个或多个分片库中,也可以不做数据切分,不分片,只有一个表构成。
当我们数据量特别大的时候需要分库分表的时候,那么我们可以考虑使用mycat。那么mycat具体如何做到分库分表的呢?接下来就要学习mycat的三大配置文件进行灵活配置就可以实现分库分表啦啦啦。。。
mycat主要的三大配置文件: server.xml、schema.xml、rule.xml
这三个配置文件位于mycat的安装目录的conf目录中。
该文件几乎包含了所有 mycat 需要的系统配置信息。其中包括对外(应用)访问端口,编码,连接超时时间,最大连接数,事务级别,用户密码以及逻辑库等信息。
详细可以阅读下面的配置:
1 | <!DOCTYPE mycat:server SYSTEM "server.dtd"> |
更多关于server.xml的配置可参考以下文章:
http://suo.im/6lJTHF http://suo.im/6lJTK5 http://suo.im/6613kS
作为MyCat中重要的配置文件之一,它主要管理着MyCat的逻辑库、表、分片规则、DataNode以及DataHost。弄懂这些配置,是正确使用MyCat的前提。这里就一层层对该文件进行解析。
标签解析:
schema:配置逻辑库,与server.xml中的逻辑库要对应。
table:配置逻辑表,包含主键,自增在,位于哪个节点等。
dataNode :配置逻辑库与物理库的对应关系
dataHost :配置连接数据库主机信息。
更多详细可以参考配置文件说明:
1 | <!DOCTYPE mycat:schema SYSTEM "schema.dtd"> |
关于更多ER图关系配置和其他参数意义。可参考以下文件或者《mycat权威指南》
它的功能主要体现在以下两个方面:
1、配置水平分片的分片规则
2、配置分片规则所对应的分片函数
1 | <!DOCTYPE mycat:rule SYSTEM "rule.dtd"> |
其中的function的name必须是唯一的,以上就是hash取模4的分片算法。
常见的分片算法:
本文属于转载:原文(一篇秒懂mycat)
]]>本文总结了一些常见的线上应急现象和对应排查步骤和工具。分享的主要目的是想让对线上问题接触少的同学有个预先认知,免得在遇到实际问题时手忙脚乱。毕竟作者自己也是从手忙脚乱时走过来的。
只不过这里先提示一下。在线上应急过程中要记住,只有一个总体目标:尽快恢复服务,消除影响。 不管处于应急的哪个阶段,我们首先必须想到的是恢复问题,恢复问题不一定能够定位问题,也不一定有完美的解决方案,也许是通过经验判断,也许是预设开关等,但都可能让我们达到快速恢复的目的,然后保留部分现场,再去定位问题、解决问题和复盘。
在大多数情况下,我们都是先优先恢复服务,保留下当时的异常信息(内存dump、线程dump、gc log等等,在紧急情况下甚至可以不用保留,等到事后去复现),等到服务正常,再去复盘问题。
好,现在让我们进入正题吧。
场景预设:
监控系统突然告警,提示服务器负载异常。
预先说明:
CPU飙升只是一种现象,其中具体的问题可能有很多种,这里只是借这个现象切入。
注:CPU使用率是衡量系统繁忙程度的重要指标。但是CPU使用率的安全阈值是相对的,取决于你的系统的IO密集型还是计算密集型。一般计算密集型应用CPU使用率偏高load偏低,IO密集型相反。
常见原因:
这里为了演示,用一个最简单的死循环来模拟CPU飙升的场景,下面是模拟代码,
在一个最简单的SpringBoot Web 项目中增加CpuReaper这个类,
1 | /** |
打包成jar之后,在服务器上运行。java -jar cpu-reaper.jar &
top 定位CPU 最高的进程
执行top命令,查看所有进程占系统CPU的排序,定位是哪个进程搞的鬼。在本例中就是咱们的java进程。PID那一列就是进程号。(对指示符含义不清楚的见【附录】)
printf ‘0x%x’ tid 线程 id 转化 16 进制
printf ‘0x%x’ 12817
0x3211
jstack pid | grep tid 找到线程堆栈
jstack 12816 | grep 0x3211 -A 30
这个脚本来自于github上一个开源项目,项目提供了很多有用的脚本,show-busy-java-threads就是其中的一个。使用这个脚本,可以直接简化方法A中的繁琐步骤。如下,
wget –no-check-certificate https://raw.github.com/oldratlee/useful-scripts/release-2.x/bin/show-busy-java-threads
chmod +x show-busy-java-threads
./show-busy-java-threads
1 | show-busy-java-threads |
阿里开源的arthas现在已经几乎包揽了我们线上排查问题的工作,提供了一个很完整的工具集。在这个场景中,也只需要一个thread -n命令即可。
wget https://arthas.gitee.io/arthas-boot.jar # 下载
chmod 755 ./arthas-boot.jar # 授权
java -jar arthas-boot.jar # 运行并选择java进程
thread # 列出线程
thread 1001 # 查看线程1001
要注意的是,arthas的cpu占比,和前面两种cpu占比统计方式不同。前面两种针对的是Java进程启动开始到现在的cpu占比情况,arthas这种是一段采样间隔内,当前JVM里各个线程所占用的cpu时间占总cpu时间的百分比。
通过第一步,找出有问题的代码之后,观察到线程栈之后。我们就要根据具体问题来具体分析。这里举几个例子。
1 | GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fd99001f800 nid=0x779 runnable |
gc 排查的内容较多,所以我决定在后面单独列一节讲述。
jstack -l pid | grep BLOCKED
查看阻塞态线程堆栈thread -b
,可以找出当前阻塞其他线程的线程。针对 synchronized 情况在了解下面内容之前,请先花点时间回顾一下GC的整个流程。
接前面的内容,这个情况下,我们自然而然想到去查看gc 的具体情况。
jstat -gcutil 进程号 统计间隔毫秒 统计次数
(缺省代表一致统计)这里对开启 gc log 进行补充说明。一个常常被讨论的问题(惯性思维)是在生产环境中GC日志是否应该开启。因为它所产生的开销通常都非常有限,因此我的答案是需要开启。但并不一定在启动JVM时就必须指定GC日志参数。
HotSpot JVM有一类特别的参数叫做可管理的参数。对于这些参数,可以在运行时修改他们的值。我们这里所讨论的所有参数以及以“PrintGC”开头的参数都是可管理的参数。这样在任何时候我们都可以开启或是关闭GC日志。比如我们可以使用JDK自带的jinfo工具来设置这些参数,或者是通过JMX客户端调用
HotSpotDiagnostic MXBean
的setVMOption方法来设置这些参数。这里再次大赞arthas❤️,它提供的vmoption命令可以直接查看,更新VM诊断相关的参数。
获取到gc日志之后,可以上传到GC easy帮助分析,得到可视化的图表分析结果。
prommotion failed
从S区晋升的对象在老年代也放不下导致 FullGC(fgc 回收无效则抛 OOM)。
可能原因:
survivor 区太小,对象过早进入老年代
查看 SurvivorRatio 参数
大对象分配,没有足够的内存
dump 堆,profiler/MAT 分析对象占用情况
old 区存在大量对象
dump 堆,profiler/MAT 分析对象占用情况
你也可以从full GC 的效果来推断问题,正常情况下,一次full GC应该会回收大量内存,所以 正常的堆内存曲线应该是呈锯齿形。如果你发现full gc 之后堆内存几乎没有下降,那么可以推断: 堆中有大量不能回收的对象且在不停膨胀,使堆的使用占比超过full GC的触发阈值,但又回收不掉,导致full GC一直执行。换句话来说,可能是内存泄露了。
一般来说,GC相关的异常推断都需要涉及到内存分析,使用jmap
之类的工具dump出内存快照(或者 Arthas的heapdump
)命令,然后使用MAT、JProfiler、JVisualVM等可视化内存分析工具。
至于内存分析之后的步骤,就需要小伙伴们根据具体问题具体分析啦。
场景预设:
业务监控突然告警,或者外部反馈提示大量请求执行失败。
异常说明:
Java 线程池以有界队列的线程池为例,当新任务提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求。如果正在运行的线程数等于 corePoolSize 时,则新任务被添加到队列中,直到队列满。当队列满了后,会继续开辟新线程来处理任务,但不超过 maximumPoolSize。当任务队列满了并且已开辟了最大线程数,此时又来了新任务,ThreadPoolExecutor 会拒绝服务。
这种线程池异常,一般可以通过开发查看日志查出原因,有以下几种原因:
下游服务 响应时间(RT)过长
这种情况有可能是因为下游服务异常导致的,作为消费者我们要设置合适的超时时间和熔断降级机制。
另外针对这种情况,一般都要有对应的监控机制:比如日志监控、metrics监控告警等,不要等到目标用户感觉到异常,从外部反映进来问题才去看日志查。
数据库慢 sql 或者数据库死锁
查看日志中相关的关键词。
Java 代码死锁
jstack –l pid | grep -i –E 'BLOCKED | deadlock'
这一部分内容参考自此篇文章
对于上文提到的一些问题,这里总结了一些恢复的方法。
这里还是想单独用一节安利一下Arthas这个工具。
Arthas 是阿里巴巴开源的Java 诊断工具,基于 Java Agent 方式,使用 Instrumentation 方式修改字节码方式进行 Java 应用诊断。
dashboard :系统实时数据面板, 可查看线程,内存,gc 等信息
thread :查看当前线程信息,查看线程的堆栈,如查看最繁忙的前 n 线程
getstatic:获取静态属性值,如getstatic className attrName
可用于查看线上开关真实值
sc:查看 jvm 已加载类信息,可用于排查 jar 包冲突
sm:查看 jvm 已加载类的方法信息
jad:反编译 jvm 加载类信息,排查代码逻辑没执行原因
logger:查看logger信息,更新logger level
watch:观测方法执行数据,包含出参、入参、异常等
trace:方法内部调用时长,并输出每个节点的耗时,用于性能分析
tt:用于记录方法,并做回放
以上内容节选自Arthas官方文档。
另外,Arthas里的 还集成了 ognl 这个轻量级的表达式引擎,通过ognl,你可以用arthas 实现很多的“骚”操作。
其他的这里就不多说了,感兴趣的可以去看看arthas的官方文档、github issue。
再说下一些工具。
我知道我这篇文章对于线上异常的归纳并不全面,还有网络(超时、TCP队列溢出…)、堆外内存等很多的异常场景没有涉及。主要是因为自己接触很少,没有深刻体会研究过,强行写出来免不得会差点意思,更怕的是误了别人😅。
还有想说的就是,Java 应用线上排查实际非常考究一个人基础是否扎实、解决问题能力是否过关。比如线程池运行机制、gc分析、Java 内存分析等等,如果基础不扎实,看了更多的是一头雾水。另外就是,多看看网上一些有实际场景的关于异常排查的经验文章,学习他们解决排查问题的思路和工具。这样即使自己暂时遇不到,但是会在脑海里面慢慢总结出一套解决类似问题的结构框架,到时候真的遇到了,也就是触类旁通的事情罢了。
如果本文有帮助到你,希望能点个赞,这是对我的最大动力🤝🤝🤗🤗。
指示符 | 含义 |
---|---|
PID | 进程id |
USER | 进程所有者 |
PR | 进程优先级 |
NI | nice值。负值表示高优先级,正值表示低优先级 |
VIRT | 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES |
RES | 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA |
SHR | 共享内存大小,单位kb |
S | 进程状态。D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程 |
%CPU | 上次更新到现在的CPU时间占用百分比 |
%MEM | 进程使用的物理内存百分比 |
TIME+ | 进程使用的CPU时间总计,单位1/100秒 |
COMMAND | 进程名称(命令名/命令行) |
本文属于转载:原文(Java 应用线上问题排查思路、工具小结)
]]>下面是线上机器的cpu使用率,可以看到从4月8日开始,随着时间cpu使用率在逐步增高,最终使用率达到100%导致线上服务不可用,后面重启了机器后恢复。
简单分析下可能出问题的地方,分为5个方向:
系统本身代码问题
内部下游系统的问题导致的雪崩效应
上游系统调用量突增
http请求第三方的问题
机器本身的问题
查看日志,没有发现集中的错误日志,初步排除代码逻辑处理错误。
首先联系了内部下游系统观察了他们的监控,发现一起正常。可以排除下游系统故障对我们的影响。
查看provider接口的调用量,对比7天没有突增,排除业务方调用量的问题。
查看tcp监控,TCP状态正常,可以排除是http请求第三方超时带来的问题。
查看机器监控,6台机器cpu都在上升,每个机器情况一样。排除机器故障问题。即通过上述方法没有直接定位到问题。
1、重启了6台中问题比较严重的5台机器,先恢复业务。保留一台现场,用来分析问题。
2、查看当前的tomcat线程pid。
3、查看该pid下线程对应的系统占用情况。top -Hp 384
4、发现pid 4430
4431
4432
4433
线程分别占用了约40%的cpu
5、将这几个pid转为16进制,分别为114e
114f
1150
1151
6、下载当前的java线程栈 sudo -u tomcat jstack -l 384>/1.txt
7、查询5中对应的线程情况,发现都是gc线程导致的
8、dump java堆数据
sudo -u tomcat jmap -dump:live,format=b,file=/dump201612271310.dat 384
9、使用MAT加载堆文件,可以看到javax.crypto.JceSecurity对象占用了95%的内存空间,初步定位到问题。
MAT下载地址:
10、查看类的引用树,看到BouncyCastleProvider
对象持有过多。即我们代码中对该对象的处理方式是错误的,定位到问题。
我们代码中有一块是这样写的
这是加解密的功能,每次运行加解密都会new一个BouncyCastleProvider对象,放倒Cipher.getInstance()方法中。
看下Cipher.getInstance()的实现,这是jdk的底层代码实现,追踪到JceSecurity类中
verifyingProviders每次put后都会remove,verificationResults只会put,不会remove.
看到verificationResults是一个static的map,即属于JceSecurity类的。所以每次运行到加解密都会向这个map put一个对象,而这个map属于类的维度,所以不会被GC回收。这就导致了大量的new的对象不被回收。
将有问题的对象置为static,每个类持有一个,不会多次新建。
遇到线上问题不要慌,首先确认排查问题的思路:
查看日志
查看CPU情况
查看TCP情况
查看java线程,jstack
查看java堆,jmap
通过MAT分析堆文件,寻找无法被回收的对象
本文属于转载:原文(通过jstack与jmap分析一次线上故障)
]]>char是一种固定长度的类型,varchar则是一种可变长度的类型,它们的区别是: char(M)类型的数据列里,每个值都占用M个字节,如果某个长度小于M,MySQL就会在它的右边用空格字符补足.(在检索操作中那些填补出来的空格字符将被去掉)在varchar(M)类型的数据列里,每个值只占用刚好够用的字节再加上一个用来记录其长度的字节(即总长度为L+1字节).
在MySQL中用来判断是否需要进行对据列类型转换的规则
ps :被问到一个问题:MySQL中varchar最大长度是多少?这不是一个固定的数字。本文简要说明一下限制规则。
字段的限制在字段定义的时候有以下规则:
varchar最多能存储65535个字节的数据。varchar 的最大长度受限于最大行长度(max row size,65535bytes)。65535并不是一个很精确的上限,可以继续缩小这个上限。65535个字节包括所有字段的长度,变长字段的长度标识(每个变长字段额外使用1或者2个字节记录实际数据长度)、NULL标识位的累计。
NULL标识位,如果varchar字段定义中带有default null允许列空,则需要1bit来标识,每8个bit的标识组成一个字段。一张表中存在N个varchar字段,那么需要(N+7)/8 (取整)bytes存储所有的NULL标识位。
如果数据表只有一个varchar字段且该字段DEFAULT NULL,那么该varchar字段的最大长度为65532个字节,即65535-2-1=65532 byte。
1 | mysql\> create table t1 ( name varchar(65532) default null)charset\=latin1; |
1 | mysql\> create table t2 ( name varchar(65533) default null)charset\=latin1; |
可以看见当设置长度为65533时,已经超过行最大长度,我们可以计算一下,行最大长度是65535字节。上面t2表name字段使用varchar(65533),字符集是latin1,占用1个字节。还有默认为空,那么还有null标识位,( 1 + 7 ) / 8 =1,所以null标识位占用1个字节。现在我们来看看,65533 + 1 + 2=65536字节,已经大于行最大长度。这里2字节怎么来的???因为varchar类型存储变长字段的字符类型,与char类型不同的是,其存储时需要在前缀长度列表加上实际存储的字符,当存储的字符串长度小于255字节时,其需要1字节的空间,当大于255字节时,需要2字节的空间。
如果数据表只有一个varchar字段且该字段NOT NULL,那么该varchar字段的最大长度为65533个字节,即65535-2=65533byte
1 | mysql\> create table t2 ( name varchar(65533) not null) charset\=latin1; |
1 | mysql\> create table t3 ( name varchar(65534) not null) charset\=latin1; |
导致实际应用中varchar长度限制的是一个行定义的长度。 MySQL要求一个行的定义长度不能超过65535。若定义的表长度超过这个值,则提示
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs。
举两个例说明一下实际长度的计算。
1 | create table t4(c varchar(N)) charset=gbk; |
则此处N的最大值为(65535-1-2)/2= 32766。
1 | create table t4(c int, c2 char(30), c3 varchar(N)) charset=utf8; |
则此处N的最大值为 (65535-1-2-4-303)/3=21812
如果被varchar超过上述的b规则,被强转成text类型,则每个字段占用定义长度为11字节,当然这已经不是varchar了。则此处N的最大值为 (65535-1-2-4-30*3)/3=21812,例子如下:
1 | mysql\> create table t4(c int, c2 char(30), c3 varchar(21812)) charset\=utf8; |
最后让我们来看一个例子
1 | CREATE TABLE t6 ( |
那么上面这条语句中的varchar(N)的最大值是多少呢?
每个NULL字段用1bit标识,10个字段都是default null,那么需要用(10+7)/8bit = 2 bytes存储NULL标识位。int占用4个 byte。
(65535 - 1 - 2 8 - 4 - 100 3 * 8 - 2) / 3=21037
1 | mysql\> CREATE TABLE t6 ( id int, a VARCHAR(100) DEFAULT NULL, b VARCHAR(100) DEFAULT NULL, c VARCHAR(100) DEFAULT NULL, d VARCHAR(100) DEFAULT NULL, e VARCHAR(100) DEFAULT NULL, f VARCHAR(100) DEFAULT NULL, g VARCHAR(100) DEFAULT NULL, h VARCHAR(100) DEFAULT NULL, i VARCHAR(21037) DEFAULT NULL ) CHARSET\=utf8; |
可以看见多一个字符都报错了。
varchar到底能存多少个字符?这与使用的字符集相关,latin1、gbk、utf8编码存放一个字符分别需要占1、2、3个字节。
在物理存储上,varchar使用1到2个额外的字节表示实际存储的字符串长度(bytes)。如果列的最大长度小于256个字节,用一个字节表示(标识)。如果最大长度大于等于256,使用两个字节。
varchar对于不同的RDBMS引擎,有不通的物理存储方式,虽然有统一的逻辑意义。对于mysql的不同存储引擎,其实现方法与数据的物理存放方式也不同。
InnoDB中varchar的物理存储方式与InnoDB使用的innodb_file_format有关。早期的innodb_file_forma使用的Antelope文件格式,支持redundant和compact两种row_format。从5.5开始或者InnoDB1.1,可以使用一种新的file format,Barracuda。Barracuda兼容Redundant,另外还支持dynamic和compressed两种row_format.
当innodb_file_format=Antelope,ROW_FORMAT=REDUNDANT 或者COMPACT。
innodb的聚集索引(cluster index)仅仅存储varchar、text、blob字段的前768个字节,多余的字节存储在一个独立的overflow page中,这个列也被称作off-page。768个字节前缀后面紧跟着20字节指针,指向overflow pages的位置。
另外,在innodb_file_format=Antelope情况下,InnoDB中最多能存储10个大字段(需要使用off-page存储)。innodbd的默认page size为16KB,InnoDB单行的长度不能超过16k/2=8k个字节,(768+20)* 10 < 8k。
当innodb_file_format=Barracuda, ROW_FORMAT=DYNAMIC 或者 COMPRESSED
innodb中所有的varchar、text、blob字段数据是否完全off-page存储,根据该字段的长度和整行的总长度而定。对off-page存储的列,cluster index中仅仅存储20字节的指针,指向实际的overflow page存储位置。如果单行的长度太大而不能完全适配cluster index page,innodb将会选择最长的列作为off-page存储,直到行的长度能够适配cluster index page。
对于MyISAM引擎,varchar字段所有数据存储在数据行内(in-line)。myisam表的row_format也影响到varchar的物理存储行为。
MyISAM的row_format可以通过create或者alter sql语句设为fixed和dynamic。另外可以通过myisampack生成row_format=compresse的存储格式。
当myisam表中不存在text或者blob类型的字段,那么可以把row_format设置为fixed(也可以为dynamic),否则只能为dynamic。
当表中存在varchar字段的时候,row_format可以设定为fixed或者dynamic。使用row_format=fixed存储varchar字段数据,浪费存储空间,varchar此时会定长存储。row_format为fixed和dynamic,varchar的物理实现方式也不同(可以查看源代码文件field.h和field.cc),因而myisam的row_format在fixed和dynamic之间发生转换的时候,varchar字段的物理存储方式也将会发生变化。
本文属于转载:原文(MySQL中varchar最大长度是多少?)
]]>进行分布式服务开发, 各服务均划分了各自的消息协议项目/应用服务项目, 这些项目骨架相同, 仅仅是项目名和包名有个别字的区别; 为此, 笔者经过调研后选择使用giter8
来创建项目骨架模板, 后期项目均使用该模板生成项目.
笔者仅介绍OSX的安装方法, 其它平台的安装详见:
1 | brew install giter8 |
创建模板的规则和最佳实践
.g8
结尾src/main/g8
目录下, 若该目录不存在, 使用项目根目录取而代之(不推荐)default.properties
定义待输入的变量和默认值name
熟悉兼做项目创建时的目录名package
属性用在路径上时会进行目录展开description
定义的字符串会首先被g8
显示出来git
无法提交空目录, 针对有目录为空的目录, 在其内添加文件.gitkeep
g8 file:///Users/arccode/workspace/github/rest-api-protocol-template.g8
g8 arccode/rest-api-protocol-template.g8
开发分布式服务, 各服务会根据命名规范定义各自的消息协议, 某一服务A调用指定服务B时, 服务A需要依赖服务B的消息协议, 使用构建工具(maven/sbt/gradle)+nexus私服可以满足多服务并行开发时, 服务间消息协议构件(artifact)最新版本的发布和获取.
详见参考文章.
-SNAPSHOT
标识.-SNAPSHOT
标识 创建类型为group
的仓库(命名为xxx-central), 同时把需要的远程仓库纳入其中, 目前纳入的仓库有maven-central
, maven-releases
, maven-snapshots
在pom.xml
文件的project
节点下加入如下内容,
1 | <!-- nexus仓库 --> |
在build.sbt
文件末尾增加如下内容,
1 | // xxx-central为创建的类型为group的组仓库 |
maven-releases
仓库中maven-snapshots
仓库中发布构件需要进行认证, 在maven
的setting.xml文件中servers
节点下增加如下配置
1 | <server> |
在pom.xml
文件的project
节点下加入如下内容,
1 | <!-- 发布jar到nexus --> |
maven clean deploy
在build.sbt
中增加如下配置,
1 | publishTo := { |
在build.sbt
中增加如下配置,
1 | credentials += Credentials( |
新建sonatype.sbt
文件, 并将其放置于sbt
目录下, 内容如下
1 | credentials += Credentials( |
sbt publish
sbt publishSigned
以上配置针对nexus 3.x.x
, 2.x.x
和3.x.x
差异如下,
object
中, 例如: DeviceProtocolprotocol/api
中protocol/service
中时刻与时间的区别: 时刻指某一个点(比如: 2010-01-01 10:00:00), 时间指两个时刻间的差值(比如: 2分钟, 5小时)
部分文档消息内部包装类: XXXWrapper, 例如: BindDeviceWrapper
不允许使用ask
模式, 根据需要选择使用tell
, forward
, pipeTo
发起调用时可以使用ask
模式
仅针对单表进行映射(ORM), 多表关联产生的数据使用元组进行接收; 减少domain的无限膨胀.
业务表使用复数, 例如: activites; 关联表使用单数, 例如: userActivity;
域名.module.模块名.*
根据目前项目的需求, 使用单容错结构, 减少调用方解析成本; 以后有足以平衡调用成本的需求, 再平滑升级到多容错结构;
code
分类如下:3
将收到此code检测到输入或业务调用异常, 直接返回, 不再对其它resource
或field
进行检测;
1 | { |
检测到输入或业务调用异常, 继续对其它resource
或field
进行检测, 封装组合后再返回;
1 | { |
error
?如果只使用message
, 只能人类看懂, 机器很难进行分类解析.
场景: 绑定设备, 假设提供的userId
和chId
在库中均不存在, 如果只返回message的话, 将返回用户不存在
或设备不存在
; 人能看懂, 但代码无法进行分类; 假如增加error
的话, 调用方就可根据resource
和field
进行自定义提示, 而不依赖于后端提供的message
;
如下:
1 | { |
1 | { |
传统编程模型认为写一个变量就是直接写了相应的内存地址,但是在现代计算机架构,简单点说CPU写的是cache lines而不是直接写内存,这些缓存大部分是L1一级缓存,也就是说CPU的一个核写的内容另外的核看不见,为了把变化广播给别的核(这样才能同样广播到别的线程),需要(做额外动作)把cache line的变化同步到其他核的cache.
JVM运行环境下,要做到上述的线程间共享,需要显式地给变量(memory locations)打上volatile 标记 或者使用Atomic原子包装数据结构,否则,一般变量值的变化不保证能实时同步给其它线程可见,要么就使用锁,你可能会问为什么不标记所有变量都是volatile的呢?因为在CPU的多个核之间同步cache lines是很昂贵的操作!会拖慢CPU内核速度并且导致cache coherence protocol (the protocol CPUs use to transfer cache lines between main memory and other CPUs) 瓶颈,结果就是明显的拖慢运行.
当你看源码时你以为的方法调用
运行时一条线程执行你的代码时走的方法调用
两条线程的时候
多条线程、多个对象、多种方法
曾经如日中天上有图灵奖背书下有Spring鼎鼎大名的OO编程,实际上对运行时环境是没有多大卵用的,OO多半只是源码这种文本的组织方式,类似书要分章节写作、货架要分品种摆放,到了编译之后的运行时二进制的世界里O只是一些方法和数据的包.
即使是对知晓上述情况的开发者,决定哪些memory locations应该用volatile标记或atomic结构,仍然也算是黑科技(框架或库的使用问题你可以google,但是并发问题都有各自的场景,加之往往很难再现,google不到标准答案,意指这些技能经验难以积累共享)
总之:
1、从来就没有什么真正共享的内存.一个CPU的多个内核之间传递数据 (cache lines)就跟不同计算机通过网络传递数据一个熊样!CPU间的通讯和网络通讯的共同点比大部分人所了解的要更多. Passing messages is the norm now be it across CPUs or networked computers.
2、把变量标记为共享(volatile)或使用atomic数据结构只是隐藏了消息传递而已(在多核之间或多个CPU之间传递消息).有节操有原则的姿势应该是把本地状态封装在并发实体当中,然后在并发实体之间传播数据或者事件——通过消息.
我们总是想当然认为方法调用自有其固定的调用栈,但是调用栈是上一个时代的产物,那个时代并发编程还不是主流,因为多CPU架构的系统不通用、不常见,调用栈Call stacks不会跨线程、无法对异步调用链建模.当一条线程要代理一项任务去后台运行,问题就暴露了,这不是一个简单的方法/函数调用,此时一个“caller”线程把一个对象放到一个内存位置,然后共享给另一条“callee”线程,callee线程接收到对象、会去执行某种事件循环也就是执行任务,caller线程也可以去干自己的事了.
第一个问题: caller线程怎么知道任务干完了?这还不算最严重的问题,更严重的是如果任务执行失败了呢?任务抛了个异常,但是是在 callee线程环境中抛的,callee线程该拿这个异常肿么办?没法办,这个异常只会传递给callee线程自己的exception handler异常处理器,因为它找不回当初的caller线程:
Main是caller线程;Worker是callee线程;caller调用了callee之后就一拍两散了,我不知你你不知我
只能用一些土办法,比如信号量之类的,但是如果信号迟迟不来,caller也毫无办法.实际上,在异常情况下任务就丢了、丢了?丢了!forever!这和网络通讯时messages/requests发生lost/fail丢失/失败情况是何其惊人的相似: 你得不到任何通知,没人会给你一个说法.In fact, due to the exception reaching to the top, unwinding all of the call stack, the task state is fully lost ! We have lost a message even though this is local communication with no networking involved (where message losses are to be expected).
在高并发系统当中,必然大量发生线程间代理/委托任务,而且这些代理必然也应该是无阻塞的(否则那不叫高并发),线程间所有协作都应该设置response deadlines也就是timeouts,和网络或分布式系统一样!
总之,对象和线程是两个维度的事情,OO没有理清它们,二者纠缠不清,这种OO是反人类的(你不觉得Gof设计模式大部分都怪怪的吗?),actor才符合我们的心智模型.
需要的话ActorSystem会使用大量线程,所以对每一个逻辑概念上的应用只要创建一个就好了,一个ActorSystem容纳几百万actor没问题.ActorSystem也管理着共享基础设施如调度服务、配置、日志等等,若干不同配置的ActorSystem可以共存在一个JVM里,AKKA本身并不存在全局共享状态(也就是不存在跨ActorSystem的共享),ActorSystem之间的actor还可以透明通讯,因此,ActorSystem本身(也像actor一样)可以用作系统构造块building blocks.
一个actor应该把大块的功能分解成更小的任务,交付给下属actor去并行完成,自己则专注于监管下级actor.所有actor都只有一个监管者,也就是创建它的那一个.actor系统最典型的行为特征就是任务分解、并行执行.设计这样一个系统的难点在于决定谁应该监管什么,一些指导原则:
1、任务的管理者应该监管任务的执行者,因为管理者知道这个任务可能出现什么样的失败以及如何处理.
2、如果一个actor携带有很重要的状态数据,那么它应该把一些危险任务委托给下级子actor去做,比如说为每个请求创建一个子actor(类似每请求每线程),这样也会简化结果收集状态管理,在ErLang中这就是著名的Error Kernel Pattern模式.
3、如果actor A需要依赖actor B分担它的职责,A应该观察(Watch)B的存活状态并对B的终止提醒消息进行响应,观察并不会干扰监管,这是另一套方法.functional dependency alone is not a criterion for deciding where to place a certain child actor in the hierarchy. 这句话意思应该是只有功能性依赖的话(也就是职责分担),是不能作为把某个actor放到监管层次某一个位置上的标准.总体来说,就是对于职责的分担而不是任务的分解可以用Watch、和监管无关的观察.
顶级actor筑成整个系统容错能力的内核Error Kernel,所以不可滥用,创建要节制、谨慎,更推荐真正层次化的系统,这样有利于故障处理/隔离(both considering the granularity of config and performance),同时还可以降低guardian actor监管actor的压力,当过载或使用不当时,guardian actor会形成单点争用瓶颈 —— 意思是应该创建少数顶级actor,一般也就1~3个左右,而且他们要慎用,不要去做有风险或阻塞的事情,只要做好它的角色就好,因为它们是当系统出现大范围故障时的最后火种,要仰仗它们恢复整个系统,要尽量多去分层,每层都有监管actor,把风险用多层的结构化解掉.
Erlang程序员说: 提高他们的程序的运行速度的技巧就是找出代码中需要顺序执行的部分.而对于任何对于其他编写顺序执行程序的程序员来说,提高他们程序速度的方法是找出他们程序中可以并行执行的部分.恩~,Akka’s Actors的API类似于scala Actor,然后它们又都是抄的ErLang: )
supervision监管描述了一种actor之间的依赖关系,是一种递归的错误处理结构.监管者actor会把任务委托给下属actor去干,所以(和人类社会一样,下面人搞事儿那么监管者就得担事儿也就是要对下属的失败负责),当下属发生失败也就是抛出异常那么它会立即挂起suspend(不再处理消息,直到被恢复)自己以及它的所有下属,然后向监管者上报一个消息: 我搞不定了,老大您看咋办?老大也就是监管者有四个选项:
任何一个actor都必然属于某一个监管体系(actor不能独立存在只能存在于ActorSystem),也就是说所有actor都监管其他actor同时也被其他actor监管着(除了大boss根actor),这样一个体系下: 让下属继续/恢复工作也就是让下属的下属全部恢复工作、重启下属也就是让下属的下属全部重启;终止下属也就是让下属的下属全部终止——这便是递归的含义;actor的preRestart钩子方法默认就是终止所有下属(但该方法可以被重写),该方法执行完了才会递归重启所有下属.每个监管者都配置了一个翻译函数,函数将失败对应到上述某一个处理方式.但是这个函数没有接收actor ID参数,也就是说无法针对不同actor采取不同的策略,但是如果在一层想做的太多,是不利于reason about的,因此,更好的做法是添加一个监管层,也就是说实现层层监管,每一层的策略都是一致的.
AKKA的实现称做“强制父监管”,actor只能是由另一个actor所创建,顶级大Boss actor由库创建,其他所有actor都是由其创建者监管着的,无一例外.也就是说actor不可能没有监管者,也绝不可能被外界改变其监管者,这共同构筑了干净的actor监管逻辑树体系,actor监管树类似于操作系统管理进程构建的进程树类.注意: 监管相关消息使用独立邮箱,不会和常规业务消息混杂.actor逻辑树如下图示:
An actor system will during its creation start at least three actors
这个名为”/user”的守护者,是所有用户创建的顶级actor的父actor,可能是需要打交道最多的.使用system.actorOf()创建的actor都是其子actor,也是顶级actor.这意味着,当该守护者终止时,系统中所有的普通actor(也就是我们的业务actor)都将被关闭.同时也意味着,该守护者的监管策略决定了普通的顶级actor是如何被监督的.自Akka 2.1起就可以使用这个设定akka.actor.guardian-supervisor-strategy,以一个SupervisorStrategyConfigurator的完整类名进行配置.当这个守护者上报escalates一个失败,根守护者的响应是终止该守护者,从而关闭整个actor系统.
这个特殊的守护者是为了保障正确的关闭顺序,即日志(logging)要保持可用直到所有普通actor终止,即使日志本身也是用actor实现的.其实现方法是: 系统守护者会一直观察(Watch)user守护者,直到收到Terminated消息,之后它开始初始化它自己的关闭过程.顶级的系统actor被监管的策略是,对收到的除ActorInitializationException 和 ActorKilledException之外的所有Exception无限地执行重启,这也将终止其所有子actor.所有其他Throwable被上报,然后将导致整个actor系统的关闭.
根守护者是所有所谓顶级actor的祖父,它监督所有在Actor路径的顶级作用域中定义的顶级actor,使用发现任何Exception就终止子actor的SupervisorStrategy.stoppingStrategy策略.其他所有Throwable都会被上报……但是上报给谁?所有的真实actor都有一个监管者,但是根守护者没有父actor,因为它就是整个树结构的根.因此这里使用一个虚拟的ActorRef,上报给它(类似JVM的classLoader体系),在发现问题后立即停掉其子actor,并在根守护者完全终止之后(所有子actor递归停止),立即把actor系统的isTerminated置为true
根守护者是祖父节点、守护者和系统守护者是父节点,下面才是所谓的顶级actor包括你在守护者下面用ActorSystem.actorOf创建的actor,这里才是你的世界,人外有人,你的世界之上还有两层呢.系统守护者下面是系统创建的actor包括日志监听、配置的系统启动自动创建的actor;其他的父节点还有: 死信节点(deadLetters): 接收所有死信dead letter的actor;临时节点(temp)监管所有系统创建的临时actor比如ActorRef.ask创建的;远程(remote),它下面是所有监管者是远程actor的actor.
The ability to do this is one of the reasons for encapsulating actors within special references. The new actor then resumes processing its mailbox, meaning that the restart is not visible outside of the actor itself with the notable exception that the message during which the failure occurred is not re-processed.
当一个actor失败重启了,则它的所有ActorRef也会被更新,这种能力是设计ActorRef的重要原因(参考远程Rremoting的定义要求,AKKA要求P2P对等通讯,似乎也有这方面的原因,因为actor和actorRef必须能够做到双向主动通讯,这和传统OO的对象与对象引用之间的关系本质上不同,可以理解为actor和actorRef实际上是对等的可以互相通讯的独立对象),新创建的actor接着就会重新开始处理它自己的邮箱(这里也有重点,就是原actor尚未来得及处理的消息是否能得到继续处理呢?下面解释),这个过程意味着重启在actor外部是不可见的、完全透明的,外部是看不到这个actor重启了的,因为外部没感觉到消息得不到处理的异常情况.可以明确: 原actor的消息会得到继续处理、不会丢失,邮箱是属于ActorSystem而非actor的,赞!
如上所述,监管是父子间的特殊关系,除此以外,任意一个actor都可以观察(Watch)任意一个actor,Watch的含义是: 由于actor从创建开始就是活跃的,而且其重启除了直接监管者,在外部都是不可见的,所以所谓的观察(Watch)就只能监视一件事: 死亡、或者说从活跃到死亡的状态转移.Watch就是绑定了两个actor,被监视者的死亡会给监视者发出Terminated消息通知,Terminated消息定义在Actor库内,不可转发而且属于本地(同一个JVM)消息,对应的远程消息是DeathWatchNotification.
这种机制又叫Death Watch——死亡观察,估计是因为只能监视死亡.调用ActorContext.watch(targetActorRef)开始观察, I Watch your Death.
Flyway是一款开源的数据库版本管理工具,它更倾向于规约优于配置的方式。
Flyway可以独立于应用实现管理并跟踪数据库变更,支持数据库版本自动升级,并且有一套默认的规约,不需要复杂的配置,Migrations可以写成SQL脚本,也可以写在Java代码中,不仅支持Command Line和Java API,还支持Build构建工具和Spring Boot等,同时在分布式环境下能够安全可靠地升级数据库,同时也支持失败恢复等。
https://flywaydb.org/documentation/migrations
版本号__描述.sql
V: 版本控制
U:撤销
R:可重复执行
V{version}_{date}_{num}__{type}_{description}_{Author}.sql
例如: V5_1_0_20180914_1__DDL_alter_table_medicinenames_qiaotiansheng
详细说明:
version : 需求版本号 (2.3.3、2.4.0)
date: 提交日期20180507
num: 开发人员自由命名,格式必须为数字
type: sql文件类型 DML 数据更新(插入、更新、删除); DDL 结构更新; DCL 权限控制;
description: 文件描述
Author: 开发人员姓名
插入新数据,建议使用Replace替代Insert,但需要保证插入的数据是完整且包含主键,否则未包含的列自动置为空值
创建视图建议使用Create OR Replace,可多次执行不报错
批量插入数据,建议关闭外键检查和自动提交,如下SQL:
1 | -- 关闭外键检查 |
SQL文件已经提交之后,不得修改,否则会导致校验失败
添加外键的SQL,一定要加上外键名称, 没有名称系统会默认生成,会导致各环境外键名不一致,就无法删除外键了。添加外键
alter table locstock add foreign key locstock_ibfk2(stockid) references product(stockid)
locstock 为表名, locstock_ibfk2 为外键名 第一个括号里填写外键列名, product为表名,第二个括号里是写外键关联的列名
word作为office的一部分,是微软提供的办公文档写作软件.除了文字编辑的功能之外,它还包含很多提高写作效率的自动化功能,目前已成为办公文档、专业论文写作等必不可少的利器.本文内容涵盖了word自动化列表、段落文字样式批量设置、图表名称和编号设置、目录设置、制表位等等功能,并一步步教大家打造一个word常规模板,阅读完本文后,大家可以自己配置专属的文本模板,并应用到以后的工作中,提高文档写作、格式调整等工作的效率.
我尽可能的做到对每一个操作都详细的解说,但是难免有遗漏之处,还请大家批评指正,谢谢.文章稍微有点长,但请细心读下去,我相信会对你的工作带来一定的帮助.
本文操作采用 word 2013 版本, 同时提供可应用于word 2007 的样式模板和导入方法.
参考我的模板文件,可以更好的理解本文的内容,当然你也可以直接在模板的基础上进行修改,达到自己的目的.但在修改之前,请务必对本文讲解的原理有一个基础的把握.(模板文件里面除了“自动图文集”功能可能不能使用而外,其他是一样的.“自动图文集”功能建议拓展阅读 word 的“building blocks”相关内容.)
打开 word 软件,先不要着急写文字,先将所有格式标记打开,有助于直接看到页面的排版控制符号.
选择“文件”–“选项”,在选项面板下,选择“显示”–“显示所有格式标记”:
这部分包含几个常用样式的设计,包括“标题”、“多级列表”等.
注意:这里不涉只讲阿拉伯数字编号,中文章节号的设置问题见文末.
点击“开始”菜单面板下面的“样式”组右下角的按钮,会弹出样式管理器界面,在这个界面下,统一设置样式会方便很多.
然后先来设置“标题1”样式,点击“标题1”样式后面的下拉菜单按钮,选择“修改”,打开“修改样式”面板.
这里就可以按照不同文档的要求,进行一级标题的字体、段落、行间距等等设置.需要说明的是,这里建议将“样式基准”修改为“无样式”,因为正文的行间距、缩进等等要求往往与标题不一样,所以这里将它们区分开,避免以后更改了正文的样式导致标题样式出现问题.
至于,每个标题文字的格式设置,我就不细说了,大家自己按照要求修改好就行.
OK,按照上面的步骤,分别设置好 1~4 级标题样式(也可以设置到5级、6级,自己开心就好).
设置好了标题样式,多级列表就是直接应用在标题上面的,如“1.1”、“1.1.1”等等这种.
千万不要手动把这些编号打上去,否则自动编号功能就会出错,一旦编号过多,人工修改很麻烦还容易出问题!
打开“开始”菜单下面的“段落”组,选择多级列表下拉菜单,然后选择“定义新的多级列表”.
打开“定义新多级列表”面板后,先选择左下角的“更多”按钮,显示面板的全貌.
一般来说,我们的文档标题列表是没有缩进的,但是 word 默认却是一级级缩进的,所以我们先把这些缩进干掉.(当然,你也可以保留缩进,你开心就好)
选择列表1,将文本的缩进位置改为 0, 并将列表1链接到“标题1”样式.还有一个可选的设置,就是编号之后插入“制表符”还是“空格”,这个一般没有做要求,但是我建议还是都设置成“空格”吧.
这样就设置好了一级标题的编号,二级、三级标题也是同样,但可以快速设置所有文本的缩进位置.点击“文本缩进位置”右边的“设置所有级别按钮”,在弹出的对话框中,将“每一级的附加缩进量”设置为 0.
单击确定之后,你就会看到,所有列表的缩进都没有了.
别忘了设置二级、三级、四级列表的链接样式以及库中显示级别.下面只给出二级列表的截图,其余类似.
设置好以上的步骤,我们在文档中敲入标题名称,然后应用相应的样式,就会得到如下的结果.
不要再用那么多空格来排版了!
做好了上面的操作,我们先不着急敲入内容,先把封面设置好.
封面的设置,一定不要用空格或者回车来强行排版,难看不说,可能以后修改格式之后会发生排版的问题.所以,建议用表格来设置封面.
选择“插入表格”命令,我这里只插入 4 行,作为示范,具体情况根据你的封面来设置.
把页面视图缩小一点,能够看到整张纸,然后把表格每一行的大小调整一下.
在表格后面空行,插入一个“分节符”,将封面与文档正文断开.
在表格的每一行输入封面的内容,然后调整一下文字在表格中的位置.最后差不多是这样.
为什么没有先设置目录、页眉、页脚什么的,就来说正文了.因为有了一部分正文基础,设置上面的那些部分会更直观.
先打字一部分正文内容上去,然后设置“正文”样式,这个跟前面设置标题的样式是一样的,根据自己正文内容的字体、字号等要求设置就行了.然后设置一下每个“标题1”样式单独占一页.设置“标题1”单独分页有两种方式,一种是设置“标题1”样式中的“格式”–“换行与分页”–“段前分页”,或者是手动的在每个章节的后面插入一个“分节符”,你知道,我当然还是推荐第一种方式,所以我在这里单独再说一下.
打上正文之后,文档现在是这个样子.
关于正文的“首行缩进”,有同学喜欢设置在“正文”的样式中,这样会自动缩进,但是要注意,如果设置了“正文”样式的“首行缩进”,那么其他的内容诸如“列表”、“引用”等等这些样式就要小心,因为它们默认都是基于“正文”样式的,都会带上缩进.我其实比较喜欢在每段正文的前面打上一个“Tab”键来手动缩进,这样虽然看起来麻烦了,但是可以帮助我整理文档的时候对全文不符合样式格式的一些问题做检查,同时也不会影响到其他的不想缩进的样式内容.这里我就采用手动缩进了.
在封面后面插入一个新页,然后选择“引用”–“目录”–“自定义目录”.
在弹出的面板里面可以看到,目录的预览也是每一级标题有一个缩进的,这个可以从“修改”里面设置.目录显示的大纲级别也是可以根据需要设置的.这里我们只是提一下,常规的文档好像也是要求缩进的,所以我们这里不做设置.如果你要修改,可以按照下图的方式定义显示级别的样式.
最后,单击“确定”按钮,我们的目录就生成好了.但是你会发现,这里显示的页码不正确,这个问题,我们会在页码设置好以后再来解决,也方便大家理解页码和目录背后的关联关系.
页脚大多数时候也就是设置个页码,所以我们这里也就只将页码的设置.
选择“插入”–“页码”,选择合适的页码格式,这里我们选择居中显示的页码.
这里很关键了,当点击插入页码之后,会出现到“设计”视图里面,这里,我们对页脚做一些相应的格式设置.
首先,选择“首页不同”,这样首页就没有页码了.
然后切换到“目录”所在的页面的“后一页”,注意是“后一页”,选择这一页的页码(如果你跟着我设置,这里的页码显示的是“3”),然后在“设计”视图上,把“链接到前一条页眉”关掉.
OK,这样,正文的页码就和前面断开了,然后右键这个页码,选择“设置页码格式”
设置“起始页码”为“1”
然后,我们再回到“目录”所在页面,这个时候它的页码没有与任何页面的页码有关联,所以直接删掉就行了.插一句,有些文档要求目录的页码单独设置成罗马数字或者是什么的,现在直接设置就可以了,因为目录页码已经和后面的链接断开了,设置不会影响后面的正文.
设置好了以后,我们选择目录项,右键,点击“更新域”,在弹出的对话框选择“只更新页码”.
这个时候,我们的页码就正常了.
页眉和页脚(页码)的设置是类似的,只需要注意断开“链接到前一条页眉”就好了.也可以根据需要设置“奇偶页不同”.这里每一节的关键就在于先前插入的那个“分节符”,就将封面、目录、正文分成了不同的节.
图、表的编号一定要用题注,切勿手动编号!
题注的编号作用非常重要,尤其是在大文档的写作当中.如果手动编号,一旦中间的图、表有增减,那么后面的编号就全都不对了.
选择一张图片,插入到正文中.再插入一个新表格到正文中.然后光标定位到图的下一行、表的上一行位置选择“引用”菜单下的“插入题注”功能.
这里可以设置图、表的编号包含章节号的功能,也就是如“图1-1”或者“表1-1”这种样式,第一个编号一般引用一级标题的编号.如果没有相应名称的标签,就需要新建一个.操作见下面两幅图.
这里最好新建 4 个新样式,分别命名为“图”、“图名”、“表”、“表名”.“图”样式是为了控制插入的图所在的段落格式,“表”样式则是方便单独控制表中的文字字体、行距、表格间距等格式.“图名”和“表名”则是为了单独控制图题注和表题注的字体、段落格式.
首先选择“图”样式,在“修改样式”面板里面,先将“样式基准”和“居中对齐”设置好,然后选择“格式”–“段落”–“换行和分页”,勾选“与下段同页”.这个功能是为了避免出现,图和图名出现断页的情况,当然你也可以根据情况设置图段落的其他段落格式等.
对“图名”样式,设置字体样式、段落格式.
对“表”样式,就单独设置单元格间距、字体大小等样式就可以了.
就不截图了,根据要求设置就行了.
对“表名”样式,则要在在“修改样式”面板里面,选择“格式”–“段落”–“换行和分页”,勾选“与下段同页”.同样,这个是为了避免表名和表出现断页的情况.
设置好了之后,下次插入图或者表,就只需要再次选择“插入题注”就好了.不要忘了对新插入的图和表以及他们的名称分别应用“图”、“图名”、“表”、“表名”4 种样式.
公式也可以直接插入题注,但是往往我们要求公式和公式的名称在同一行当中,这就比较麻烦了.如果按照图和表一样的操作,那么公式本身的字体样式就没有办法独立于公式名称样式,因为这两者是在同一个段落里面的.于是这里就要引入 3 个新的功能–“样式分隔符”、“制表位”和“自动图文集”.这里对公式的段落设置就稍显复杂,但是理解了工作机制之后,也就很容易了.
“样式分隔符”,顾名思义,就是在同一个段落里面将不同的部分,分隔成不同的样式.插入样式分隔符需要用到“ctrl+alt+回车”快捷键.“样式分隔符”只分隔样式,而不重新分段,我们需要在公式和公式名称中间插入一个样式分隔符,显示符号如下图.
“制表位”是一个非常有用的高级排版功能,符号显示在标尺上.其实质就是通过“Tab”键来指定缩进的位置.添加制表位需要在标尺上单击添加,或者选择所在定位到所在行,右键,选择“段落”,然后选择左下角的“制表位”按钮.其实直接单击标尺添加是最方便的,不过要理解“制表位”的符号含义.如下图所示的例子,分别插入了 3 个制表位,通过在段落内容中间插入 2 个“Tab”键来把这 3 部分内容隔开并对齐到相应的位置.
对于公式,我们在段落中插入 2 个制表位.第一个制表位让公式居于行正中,第二个制表位指定右对齐,然后在公式前面、公式后面(“样式分隔符”之前)分别插入“Tab”键,如下图,就达到了我们公式排版的目的.为了显示,我一般还会在公式名之前(“样式分隔符”之后)添加一个“空格”.
当然,为了方便,把公式选中,然后选择“创建新样式”,命名为“公式”.把公式名称选中,选择“创建新样式”,命名为“公式名”.这样就可以单独控制公式和公式名的字体样式了.
如果创建样式的时候格式出现缩进不正确的问题,请检查段落里面的“首行缩进”,以及将“公式”和“公式名”的样式基准都改为“无样式”,与其他样式断开依赖.
有了以上的设置之后,新插入公式还需要重复上面的步骤.当然有一个简便的方法,那就是选中公式段落,然后选择“插入”–“文档部件”–“自动图文集”–“将所选内容保存到自动图文集库”,取名为“公式段落”,这样,以后插入公式,只要在“文档部件”里面,选择这个“公式段落”自动图文集,然后把公式内容替换掉就可以了.
至此,一篇文档模板就做好了,以后只需要基于这个模板做更改,写入文字内容就好了.
但是有的文档要求一级标题的编号,必须采用中文,例如“第一章”,这种格式要求,就会使得前面的图、表以及公式的编号不起作用.因此,有必要针对这种要求单独说明一下解决方案.
设置中文章节号,主要在于对“多级列表”的设置上.
首先,选择“多级列表”下拉菜单的“定义新的多级列表”,这个我们在上文中讲过了.
选择一级列表,如下图设置.
对于二级、三级、四级等列表,设置如下图,注意勾选“正规形式编号”.
然后,再来设置一下“标题1”样式的内容“居中对齐”就好了.
这个时候,如果你的文档中有题注等“域”内容编号引用到了“标题1”样式,右键选择“更新域”之后,就会发现问题了.
这里,编号自动引用了“标题1”中的中文编号.
目前来说,word 并没有一个特别好的官方功能来解决这个问题(至少我确实不知道),但是网上流行一个间接的办法,那就是更改“域代码”.“域”这个功能在 word 里面属于高级功能,相信大家平时的工作中很少会用到,但是其实 word 里面很多编辑功能背后都是基于“域”的.感兴趣的同学可以自己去网上搜一下,了解一下“域”的工作机制,这里就只讲我们需要用到的.
中文编号问题主要出现在“图”、“表”以及“公式”的编号上面,我们选择这些编号,“右键”–“切换域代码”.
会发现这些编号显示成了下图这样.(突然,程序员感觉自己又有用武之地了~~开个玩笑)
我们的解决办法就是,把“STYLEREF 1 \s”这行代码更改为 QUOTE “一九一一年一月{ STYLEREF 1 \s }日” \@”D”.注意这里的左右花括号不是常规的花括号,而是“域代码”的容器.
替换了这部分“域代码”之后,我们再用同样的方法,选择“切换域代码”将代码转换为文本显示.然后“右键”–“更新域”,看看是不是编码就正确了.
到这里我猜大家已经想到了,加入了中文章节号,那么我们的“插入题注”功能是不能再用了,否则每次都要手动切换域代码,更改这部分代码,还是挺麻烦的.不过不要忘了,我们刚刚上文中“公式”的部分,讲到了一个叫“自动图文集”的功能.没错,这里我们按照与“公式”同样的处理方法,分别将“图名”、“表名”保存为“自动图文集”,以后就直接插入“自动图文集”就好了.不过还是不要忘记分别对它们应用各自的“图名”、“表名”样式.
做好了模板文件,可以将文件保存为 *.dotx 格式,然后放到 word 安装目录的模板文件夹(或者自定义的某个路径)下,以后打开 word 就可以像其他官方模板那样在首页看到自己的模板文件了.在“文件”菜单下,点击“选项”,自定义路径设置如下图.
把模板文件拷贝到相应路径中,然后在新建文件的时候就可以看到如下图所示的模板自定义模板了.
“开始” → “更改样式” → “样式集” → “另存为快速样式集”, 其模板格式为”.dotx”
设置启用”开发工具”才可启用导入模板功能
“Word 选项” → “常用” → “在在功能区显示 ‘开发工具’ 选项卡(勾选)”
工具栏中, “开发工具” → “文档模板” → “选用” → “自动更新文档样式(勾选)”
差不多了,应该比较详细了,希望我没有遗漏什么重要的操作.给坚持读完本文的同学说一声感谢,也祝愿我的这些文字能够对你带来帮助.
最后,如果是协同写作文档,要提高工作的效率,光一个人会操作是不够的,需要大家都理解这背后的原理机制,在写作时遵从一定的原则,尽量减少给别人工作带来的不必要的麻烦.
本文若有纰漏,请不吝指正,谢谢!
本文属于转载: 原文(一劳永逸,打造自己的word常规模板)
]]>Git目前已成为比较流行的版本管理工具, 该文档记录使用Git过程中碰到的相关问题和解决方案.
1 | # --hard 参数会抛弃当前工作区的修改 |
1 | # 进入命令行, 切换至项目根目录(工作区) |
1 | # 删除git branch -a 中不存在的远程分支 |
1 | # 删除本地分支(merge了的分支) |
1 | # 新建标签 |
1 | # 下载远程仓库最新内容,不做合并 |
1 | git commit --amend --author="userName <userEmail>" |
1 | git filter-branch --env-filter ' |
1 | # 合并 branch_2的某个 commit 至 branch_1 |
分别修改AUTHOR_NAME和COMMITTER_NAME,不太清楚二者的区别。
好像GIT_AUTHOR是用来在push时验证用户信息的,所以可以只修改第一个。
其中ref是起始commit的sha-1的简写,指定生效范围为此commit(exclude)到HEAD(include),避免对整个git的提交历史的修改,可以缩短运行时间。
这一方法不知会不会影响已push过的commit,不过一般都是因为用户名没有push权限,才需要纠正commit的用户名,所以无需在意。
1 | git fetch --all |
1 | Cannot create a new backup. |
添加-f
即可,即git filter-branch -f --env-filter
1 | error: 推送一些引用到 'git@github.com:arccode/wechat-pay-sdk.git' 失败 |
确认没问题,直接强制提交,即git push -f
1 | git config --global core.quotepath false |
开发过程中, 我们会经常混迹与不同的代码仓库,时常不同仓库会有作者信息验证。比如公司内建的gitlab一般会要求统一使用公司内部的域账号签名;github要求使用github账号签名等。因此,很容易犯在不同库中提交代码发现默认配置(全局)的author
信息没有变更,结果push
被拒绝或泄漏了姓名。
下面介绍几种常用的解决方式,当然最终还是需要养成切换代码库检查author信息的习惯,主动配置
1 | // 设置全局 |
如果只需要最近一次提交,那么很简单直接使用git commit --amend
就可以搞定
1 | git commit --amend --author="NewAuthor <NewEmail@address.com>" |
如果是多个修改,那么就需要使用到git filter-branch
这个工具来做批量修改为了方便大家使用,封装了一个简单的shell脚本,直接修改[XXX]
中的变量为对应的值即可
1 | #!/bin/sh |
changGitInfo.sh
sh changGitInfo.sh
git push --force
本文接上文发布构件至Maven中央库, 详细讲解SBT
如何发构件至本地, 如何发布构件至Maven中央库
.
该项目的项目目录结构, build.sbt相关配置符合发布要求, 且已发布成功, 并会进行不定期更新, 首次发布的同学可参考对比.
略.
详见: 发布构件至Maven中央库
修改build.sbt
文件, 增加三行配置即可
1 | // 本地maven仓库位置 |
完整配置如下:
1 | name := "rest-api-protocol" |
1 | sbt publish |
配置plugins.sbt
,
1 | logLevel := Level.Info |
配置build.sbt
,
1 | name := "rest-api-protocol" |
进入项目根目录, 输入sbt
指令进入交互模式,
1 | > compile // 编译项目 |
新建文件: ~/.sbt/0.13/sonatype.sbt
, 内容为:
1 | credentials += Credentials( |
笔者重新定义过相关参数(在repositories中),
1 | # Path to global settings/plugins directory (default: ~/.sbt) |
因此需要在该目录下新建上述授权文件.
1 | > publishSigned |
略
详见: 发布构件至Maven中央库
在 Java 8 之前,我们最常见的时间与日期处理相关的类就是 Date、Calendar 以及 SimpleDateFormatter 等等。不过 java.util.Date
也是被诟病已久,它包含了日期、时间、毫秒数等众多繁杂的信息,其内部利用午夜 12 点来区分日期,利用 1970-01-01 来计算时间;并且其月份从 0 开始计数,而且用于获得年、月、日等信息的接口也是太不直观。除此之外,java.util.Date
与 SimpleDateFormatter
都不是类型安全的,而 JSR-310 中的 LocalDate
与 LocalTime
等则是不变类型,更加适合于并发编程。JSR 310 实际上有两个日期概念。第一个是 Instant,它大致对应于 java.util.Date
类,因为它代表了一个确定的时间点,即相对于标准 Java 纪元(1970年1月1日)的偏移量;但与 java.util.Date
类不同的是其精确到了纳秒级别。另一个则是 LocalDate、LocalTime 以及 LocalDateTime 这样代表了一般时区概念、易于理解的对象。
Java8中, java.time
包下包含下面几个主要的类:
1 | Instant:时间戳 |
Class / Type | Description |
---|---|
Year | Represents a year. |
YearMonth | A month within a specific year. |
LocalDate | A date without an explicitly specified time zone. |
LocalTime | A time without an explicitly specified time zone. |
LocalDateTime | A combination date and time without an explicitly specified time zone. |
最新 JDBC 映射将把数据库的日期类型和 Java 8 的新类型关联起来:
SQL | Java |
---|---|
date | LocalDate |
time | LocalTime |
timestamp | LocalDateTime |
datetime | LocalDateTime |
GMT 即「格林威治标准时间」( Greenwich Mean Time,简称 G.M.T. ),指位于英国伦敦郊区的皇家格林威治天文台的标准时间,因为本初子午线被定义为通过那里的经线。然而由于地球的不规则自转,导致 GMT 时间有误差,因此目前已不被当作标准时间使用。UTC 是最主要的世界时间标准,是经过平均太阳时(以格林威治时间 GMT 为准)、地轴运动修正后的新时标以及以「秒」为单位的国际原子时所综合精算而成的时间。UTC 比 GMT 来得更加精准。其误差值必须保持在 0.9 秒以内,若大于 0.9 秒则由位于巴黎的国际地球自转事务中央局发布闰秒,使 UTC 与地球自转周期一致。不过日常使用中,GMT 与 UTC 的功能与精确度是没有差别的。协调世界时区会使用 “Z” 来表示。而在航空上,所有使用的时间划一规定是协调世界时。而且 Z 在无线电中应读作 “Zulu”(可参见北约音标字母),协调世界时也会被称为 “Zulu time”。
人们经常会把时区与 UTC 偏移量搞混,UTC 偏移量代表了某个具体的时间值与 UTC 时间之间的差异,通常用 HH:mm 形式表述。而 TimeZone 则表示某个地理区域,某个 TimeZone 中往往会包含多个偏移量,而多个时区可能在一年的某些时间有相同的偏移量。譬如 America/Chicago, America/Denver, 以及 America/Belize 在一年中不同的时间都会包含 -06:00 这个偏移。
Unix 时间戳表示当前时间到 1970 年 1 月 1 日 00:00:00 UTC 对应的秒数。注意,JavaScript 内的时间戳指的是当前时间到 1970 年 1 月 1 日 00:00:00 UTC 对应的毫秒数,和 Unix 时间戳不是一个概念,后者表示秒数,差了 1000 倍。
1 | YYYY/MM/DD HH:MM:SS ± timezone(时区用4位数字表示) |
国际标准化组织的国际标准 ISO 8601 是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。目前最新为第三版 ISO8601:2004,第一版为 ISO8601:1988,第二版为 ISO8601:2000。年由 4 位数组成,以公历公元 1 年为 0001 年,以公元前 1 年为 0000 年,公元前 2 年为 -0001 年,其他以此类推。应用其他纪年法要换算成公历,但如果发送和接受信息的双方有共同一致同意的其他纪年法,可以自行应用。
1 | YYYY-MM-DDThh:mm:ss ± timezone(时区用HH:MM表示) |
在 Java 8 之前,我们使用 java.sql.Timestamp
来表示时间戳对象,可以通过以下方式创建与获取对象:
1 | // 利用系统标准时间创建 |
在 Java 8 中,即可以使用 java.time.Instant
来表示自从 1970-01-01T00:00:00Z 之后经过的标准时间:
1 | // 基于静态函数创建 |
Clock 方便我们去读取当前的日期与时间。Clock 可以根据不同的时区来进行创建,并且可以作为System.currentTimeMillis()
的替代。这种指向时间轴的对象即是Instant
类。Instants 可以被用于创建java.util.Date
对象。
1 | Clock clock = Clock.systemDefaultZone(); |
1 | // 默认创建 |
基于 Date 的日期比较常常使用以下方式:
new Date(99, 2, 12).before(new Date (99, 2, 18))
返回true。compareTo()
方法,它是由 Comparable 接口定义的,Date 类实现了这个接口。Date 用于记录某一个含日期的、精确到毫秒的时间。重点在代表一刹那的时间本身。 Calendar 用于将某一日期放到历法中的互动——时间和年、月、日、星期、上午、下午、夏令时等这些历法规定互相作用关系和互动。我们可以通过 Calendar 内置的构造器来创建实例:
1 | Calendar.Builder builder =new Calendar.Builder(); |
在 Calendar 中我们则能够获得较为直观的年月日信息:
1 | // 2017,不再是 2017 - 1900 = 117 |
除此之外,Calendar 还提供了一系列 set 方法来允许我们动态设置时间,还可以使用 add 等方法进行日期的加减。
1 | // 取当前日期: |
1 | // 取本月第1天 |
1 | // 获取其他时区下时间 |
1 | // 通过时间戳创建 |
1 | LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); |
1 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); |
1 | localDateTime.plusDays(1); |
Timezones 以 ZoneId
来区分。可以通过静态构造方法很容易的创建,Timezones 定义了 Instants 与 Local Dates 之间的转化关系:
1 | System.out.println(ZoneId.getAvailableZoneIds()); |
1 | LocalDateTime ldt = ... |
1 | ZoneId losAngeles = ZoneId.of("America/Los_Angeles"); |
Period 类以年月日来表示日期差,而 Duration 以秒与毫秒来表示时间差;Duration 适用于处理 Instant 与机器时间。
1 | // periods |
1 | // 2000-2-10 11:10:10 |
1 | LocalDateTime ldt = LocalDateTime.now() |
1 | // 2000-2-10 11:10:10 |
当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。
HTTP状态码的英文为HTTP Status Code。
表示临时响应并需要请求者继续执行操作的状态代码。
这一类型的状态码,代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束。由于 HTTP/1.0 协议中没有定义任何 1xx 状态码,所以除非在某些试验条件下,服务器禁止向此类客户端发送 1xx 响应。
状态:继续
说明:请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。
客户端应当继续发送请求。这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应。
状态:切换协议
说明:请求者已要求服务器切换协议,服务器已确认并准备切换。
服务器已经理解了客户端的请求,并将通过Upgrade 消息头通知客户端采用不同的协议来完成这个请求。在发送完这个响应最后的空行后,服务器将会切换到在Upgrade 消息头中定义的那些协议。
只有在切换新的协议更有好处的时候才应该采取类似措施。例如,切换到新的HTTP 版本比旧版本更有优势,或者切换到一个实时且同步的协议以传送利用此类特性的资源。
状态:继续执行
说明:由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行。
表示临时响应并需要请求者继续执行操作的状态代码。
这一类型的状态码,代表请求已成功被服务器接收、理解、并接受。
状态:成功
说明:服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。
请求已成功,请求所希望的响应头或数据体将随此响应返回。出现此状态码是表示正常状态。
状态:已创建
说明:请求成功并且服务器创建了新的资源。
请求已经被实现,而且有一个新的资源已经依据请求的需要而建立,且其 URI 已经随Location 头信息返回。假如需要的资源无法及时建立的话,应当返回 ‘202 Accepted’。
状态:已接受
说明:服务器已接受请求,但尚未处理。
服务器已接受请求,但尚未处理。正如它可能被拒绝一样,最终该请求可能会也可能不会被执行。在异步操作的场合下,没有比发送这个状态码更方便的做法了。
返回202状态码的响应的目的是允许服务器接受其他过程的请求(例如某个每天只执行一次的基于批处理的操作),而不必让客户端一直保持与服务器的连接直到批处理操作全部完成。在接受请求处理并返回202状态码的响应应当在返回的实体中包含一些指示处理当前状态的信息,以及指向处理状态监视器或状态预测的指针,以便用户能够估计操作是否已经完成。
状态:非授权信息
说明:服务器已成功处理了请求,但返回的信息可能来自另一来源。
服务器已成功处理了请求,但返回的实体头部元信息不是在原始服务器上有效的确定集合,而是来自本地或者第三方的拷贝。当前的信息可能是原始版本的子集或者超集。例如,包含资源的元数据可能导致原始服务器知道元信息的超集。使用此状态码不是必须的,而且只有在响应不使用此状态码便会返回200 OK的情况下才是合适的。
状态:无内容
说明:服务器成功处理了请求,但没有返回任何内容。
服务器成功处理了请求,但不需要返回任何实体内容,并且希望返回更新了的元信息。响应可能通过实体头部的形式,返回新的或更新后的元信息。如果存在这些头部信息,则应当与所请求的变量相呼应。
如果客户端是浏览器的话,那么用户浏览器应保留发送了该请求的页面,而不产生任何文档视图上的变化,即使按照规范新的或更新后的元信息应当被应用到用户浏览器活动视图中的文档。
由于204响应被禁止包含任何消息体,因此它始终以消息头后的第一个空行结尾。
状态:重置内容
说明:服务器成功处理了请求,但没有返回任何内容。
服务器成功处理了请求,且没有返回任何内容。但是与204响应不同,返回此状态码的响应要求请求者重置文档视图。该响应主要是被用于接受用户输入后,立即重置表单,以便用户能够轻松地开始另一次输入。
与204响应一样,该响应也被禁止包含任何消息体,且以消息头后的第一个空行结束。
状态:部分内容
说明:服务器成功处理了部分 GET 请求。
服务器已经成功处理了部分 GET 请求。类似于 FlashGet 或者迅雷这类的 HTTP下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
该请求必须包含 Range 头信息来指示客户端希望得到的内容范围,并且可能包含 If-Range 来作为请求条件。
响应必须包含如下的头部域:
Content-Range 用以指示本次响应中返回的内容的范围;如果是 Content-Type 为 multipart/byteranges 的多段下载,则每一 multipart 段中都应包含 Content-Range 域用以指示本段的内容范围。假如响应中包含 Content-Length,那么它的数值必须匹配它返回的内容范围的真实字节数。Date
ETag 和/或 Content-Location,假如同样的请求本应该返回200响应。
Expires, Cache-Control,和/或 Vary,假如其值可能与之前相同变量的其他响应对应的值不同的话。
假如本响应请求使用了 If-Range 强缓存验证,那么本次响应不应该包含其他实体头;假如本响应的请求使用了 If-Range 弱缓存验证,那么本次响应禁止包含其他实体头;这避免了缓存的实体内容和更新了的实体头信息之间的不一致。否则,本响应就应当包含所有本应该返回200响应中应当返回的所有实体头部域。
假如 ETag 或 Last-Modified 头部不能精确匹配的话,则客户端缓存应禁止将206响应返回的内容与之前任何缓存过的内容组合在一起。
状态:多种状态
说明:由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码。
表示临时响应并需要请求者继续执行操作的状态代码。
这类状态码代表需要客户端采取进一步的操作才能完成请求。通常,这些状态码用来重定向,后续的请求地址(重定向目标)在本次响应的 Location 域中指明。
当且仅当后续的请求所使用的方法是 GET 或者 HEAD 时,用户浏览器才可以在没有用户介入的情况下自动提交所需要的后续请求。客户端应当自动监测无限循环重定向(例如:A->A,或者A->B->C->A),因为这会导致服务器和客户端大量不必要的资源消耗。按照 HTTP/1.0 版规范的建议,浏览器不应自动访问超过5次的重定向。
状态:多种选择
说明:针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。
被请求的资源有一系列可供选择的回馈信息,每个都有自己特定的地址和浏览器驱动的商议信息。用户或浏览器能够自行选择一个首选的地址进行重定向。
除非这是一个 HEAD 请求,否则该响应应当包括一个资源特性及地址的列表的实体,以便用户或浏览器从中选择最合适的重定向地址。这个实体的格式由 Content-Type 定义的格式所决定。浏览器可能根据响应的格式以及浏览器自身能力,自动作出最合适的选择。当然,RFC 2616规范并没有规定这样的自动选择该如何进行。
如果服务器本身已经有了首选的回馈选择,那么在 Location 中应当指明这个回馈的 URI;浏览器可能会将这个 Location 值作为自动重定向的地址。此外,除非额外指定,否则这个响应也是可缓存的。
状态:永久移动
说明:请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。
新的永久性的URI 应当在响应的 Location 域中返回。除非这是一个 HEAD 请求,否则响应的实体中应当包含指向新的 URI 的超链接及简短说明。
如果这不是一个 GET 或者 HEAD 请求,因此浏览器禁止自动进行重定向,除非得到用户的确认,因为请求的条件可能因此发生变化。注意:对于某些使用 HTTP/1.0 协议的浏览器,当它们发送的 POST 请求得到了一个301响应的话,接下来的重定向请求将会变成 GET 方式。
状态:临时移动
说明:服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
请求的资源临时从不同的 URI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
上文有提及。
如果这不是一个 GET 或者 HEAD 请求,那么浏览器禁止自动进行重定向,除非得到用户的确认,因为请求的条件可能因此发生变化。
注意:虽然RFC 1945和RFC 2068规范不允许客户端在重定向时改变请求的方法,但是很多现存的浏览器将302响应视作为303响应,并且使用 GET 方式访问在 Location 中规定的 URI,而无视原先请求的方法。状态码303和307被添加了进来,用以明确服务器期待客户端进行何种反应。
状态:查看其他位置
说明:请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
对应当前请求的响应可以在另一个 URI 上被找到,而且客户端应当采用 GET 的方式访问那个资源。这个方法的存在主要是为了允许由脚本激活的POST请求输出重定向到一个新的资源。这个新的 URI 不是原始资源的替代引用。同时,303响应禁止被缓存。当然,第二个请求(重定向)可能被缓存。
注意:许多 HTTP/1.1 版以前的浏览器不能正确理解303状态。如果需要考虑与这些浏览器之间的互动,302状态码应该可以胜任,因为大多数的浏览器处理302响应时的方式恰恰就是上述规范要求客户端处理303响应时应当做的。
状态:未修改
说明:自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。304响应禁止包含消息体,因此始终以消息头后的第一个空行结尾。
该响应必须包含以下的头信息:
Date,除非这个服务器没有时钟。假如没有时钟的服务器也遵守这些规则,那么代理服务器以及客户端可以自行将 Date 字段添加到接收到的响应头中去(正如RFC 2068中规定的一样),缓存机制将会正常工作。
ETag 和/或 Content-Location,假如同样的请求本应返回200响应。
Expires, Cache-Control,和/或Vary,假如其值可能与之前相同变量的其他响应对应的值不同的话。
假如本响应请求使用了强缓存验证,那么本次响应不应该包含其他实体头;否则(例如,某个带条件的 GET 请求使用了弱缓存验证),本次响应禁止包含其他实体头;这避免了缓存了的实体内容和更新了的实体头信息之间的不一致。
假如某个304响应指明了当前某个实体没有缓存,那么缓存系统必须忽视这个响应,并且重复发送不包含限制条件的请求。
假如接收到一个要求更新某个缓存条目的304响应,那么缓存系统必须更新整个条目以反映所有在响应中被更新的字段的值。
状态:使用代理
说明:请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。
被请求的资源必须通过指定的代理才能被访问。Location 域中将给出指定的代理所在的 URI 信息,接收者需要重复发送一个单独的请求,通过这个代理才能访问相应资源。只有原始服务器才能建立305响应。
注意:RFC 2068中没有明确305响应是为了重定向一个单独的请求,而且只能被原始服务器建立。忽视这些限制可能导致严重的安全后果。
状态:
说明:在最新版的规范中,306状态码已经不再被使用。
最新版中已经被弃用
状态:临时重定向
说明:服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
请求的资源临时从不同的URI 响应请求。
新的临时性的URI 应当在响应的 Location 域中返回。除非这是一个HEAD 请求,否则响应的实体中应当包含指向新的URI 的超链接及简短说明。因为部分浏览器不能识别307响应,因此需要添加上述必要信息以便用户能够理解并向新的 URI 发出访问请求。
如果这不是一个GET 或者 HEAD 请求,那么浏览器禁止自动进行重定向,除非得到用户的确认,因为请求的条件可能因此发生变化。
表示临时响应并需要请求者继续执行操作的状态代码。
这类的状态码代表了客户端看起来可能发生了错误,妨碍了服务器的处理。除非响应的是一个 HEAD 请求,否则服务器就应该返回一个解释当前错误状况的实体,以及这是临时的还是永久性的状况。这些状态码适用于任何请求方法。浏览器应当向用户显示任何包含在此类错误响应中的实体内容。
如果错误发生时客户端正在传送数据,那么使用TCP的服务器实现应当仔细确保在关闭客户端与服务器之间的连接之前,客户端已经收到了包含错误信息的数据包。如果客户端在收到错误信息后继续向服务器发送数据,服务器的TCP栈将向客户端发送一个重置数据包,以清除该客户端所有还未识别的输入缓冲,以免这些数据被服务器上的应用程序读取并干扰后者。
状态:错误请求
说明:服务器不理解请求的语法。
- 语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。
- 请求参数有误。
状态:未授权
说明:请求要求身份验证。对于需要登录的网页,服务器可能返回此响应。
当前请求需要用户验证。该响应必须包含一个适用于被请求资源的 WWW-Authenticate 信息头用以询问用户信息。客户端可以重复提交一个包含恰当的 Authorization 头信息的请求。如果当前请求已经包含了 Authorization 证书,那么401响应代表着服务器验证已经拒绝了那些证书。如果401响应包含了与前一个响应相同的身份验证询问,且浏览器已经至少尝试了一次验证,那么浏览器应当向用户展示响应中包含的实体信息,因为这个实体信息中可能包含了相关诊断信息。参见RFC 2617。
状态:
说明:该状态码是为了将来可能的需求而预留的。
状态:禁止
说明:服务器拒绝请求。
服务器已经理解请求,但是拒绝执行它。与401响应不同的是,身份验证并不能提供任何帮助,而且这个请求也不应该被重复提交。如果这不是一个 HEAD 请求,而且服务器希望能够讲清楚为何请求不能被执行,那么就应该在实体内描述拒绝的原因。当然服务器也可以返回一个404响应,假如它不希望让客户端获得任何信息。
状态:未找到
说明:服务器找不到请求的网页。
请求失败,请求所希望得到的资源未被在服务器上发现。没有信息能够告诉用户这个状况到底是暂时的还是永久的。假如服务器知道情况的话,应当使用410状态码来告知旧资源因为某些内部的配置机制问题,已经永久的不可用,而且没有任何可以跳转的地址。404这个状态码被广泛应用于当服务器不想揭示到底为何请求被拒绝或者没有其他适合的响应可用的情况下。出现这个错误的最有可能的原因是服务器端没有这个页面。
状态:方法禁用
说明:禁用请求中指定的方法。
请求行中指定的请求方法不能被用于请求相应的资源。该响应必须返回一个Allow 头信息用以表示出当前资源能够接受的请求方法的列表。
鉴于 PUT,DELETE 方法会对服务器上的资源进行写操作,因而绝大部分的网页服务器都不支持或者在默认配置下不允许上述请求方法,对于此类请求均会返回405错误。
状态:不接受
说明:无法使用请求的内容特性响应请求的网页。
请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体。
除非这是一个 HEAD 请求,否则该响应就应当返回一个包含可以让用户或者浏览器从中选择最合适的实体特性以及地址列表的实体。实体的格式由 Content-Type 头中定义的媒体类型决定。浏览器可以根据格式及自身能力自行作出最佳选择。但是,规范中并没有定义任何作出此类自动选择的标准。
状态:需要代理授权
说明:此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。
与401响应类似,只不过客户端必须在代理服务器上进行身份验证。代理服务器必须返回一个 Proxy-Authenticate 用以进行身份询问。客户端可以返回一个 Proxy-Authorization 信息头用以验证。参见RFC 2617。
状态:请求超时
说明:服务器等候请求时发生超时。
请求超时。客户端没有在服务器预备等待的时间内完成一个请求的发送。客户端可以随时再次提交这一请求而无需进行任何更改。
状态:冲突
说明:服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。
由于和被请求的资源的当前状态之间存在冲突,请求无法完成。这个代码只允许用在这样的情况下才能被使用:用户被认为能够解决冲突,并且会重新提交新的请求。该响应应当包含足够的信息以便用户发现冲突的源头。
冲突通常发生于对 PUT 请求的处理中。例如,在采用版本检查的环境下,某次 PUT 提交的对特定资源的修改请求所附带的版本信息与之前的某个(第三方)请求向冲突,那么此时服务器就应该返回一个409错误,告知用户请求无法完成。此时,响应实体中很可能会包含两个冲突版本之间的差异比较,以便用户重新提交归并以后的新版本。
状态:已删除
说明:如果请求的资源已永久删除或过期不可用,服务器就会返回此响应。
被请求的资源在服务器上已经不再可用,而且没有任何已知的转发地址。这样的状况应当被认为是永久性的。如果可能,拥有链接编辑功能的客户端应当在获得用户许可后删除所有指向这个地址的引用。如果服务器不知道或者无法确定这个状况是否是永久的,那么就应该使用404状态码。除非额外说明,否则这个响应是可缓存的。
410响应的目的主要是帮助网站管理员维护网站,通知用户该资源已经不再可用,并且服务器拥有者希望所有指向这个资源的远端连接也被删除。这类事件在限时、增值服务中很普遍。同样,410响应也被用于通知客户端在当前服务器站点上,原本属于某个个人的资源已经不再可用。当然,是否需要把所有永久不可用的资源标记为’410 Gone’,以及是否需要保持此标记多长时间,完全取决于服务器拥有者。
状态:需要有效长度
说明:服务器不接受不含有效内容长度标头字段的请求。
服务器拒绝在没有定义 Content-Length 头的情况下接受请求。在添加了表明请求消息体长度的有效 Content-Length 头之后,客户端可以再次提交该请求。
状态:未满足前提条件
说明:服务器未满足请求者在请求中设置的其中一个前提条件。
服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。这个状态码允许客户端在获取资源时在请求的元信息(请求头字段数据)中设置先决条件,以此避免该请求方法被应用到其希望的内容以外的资源上。
状态:请求实体过大
说明:服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围。此种情况下,服务器可以关闭连接以免客户端继续发送此请求。
如果这个状况是临时的,服务器应当返回一个 Retry-After 的响应头,以告知客户端可以在多少时间以后重新尝试。
状态:请求的 URI 过长
说明:请求的 URI(通常为网址)过长,服务器无法处理。
请求的URI 长度超过了服务器能够解释的长度,因此服务器拒绝对该请求提供服务。这比较少见,通常的情况包括:
本应使用POST方法的表单提交变成了GET方法,导致查询字符串(Query String)过长。
重定向URI “黑洞”,例如每次重定向把旧的 URI 作为新的 URI 的一部分,导致在若干次重定向后 URI 超长。
客户端正在尝试利用某些服务器中存在的安全漏洞攻击服务器。这类服务器使用固定长度的缓冲读取或操作请求的 URI,当 GET 后的参数超过某个数值后,可能会产生缓冲区溢出,导致任意代码被执行[1]。没有此类漏洞的服务器,应当返回414状态码。
状态:不支持的媒体类型
说明:请求的格式不受请求页面的支持。
对于当前请求的方法和所请求的资源,请求中提交的实体并不是服务器中所支持的格式,因此请求被拒绝。
状态:请求范围不符合要求
说明:如果页面无法提供请求的范围,则服务器会返回此状态代码。
如果请求中包含了 Range 请求头,并且 Range 中指定的任何数据范围都与当前资源的可用范围不重合,同时请求中又没有定义 If-Range 请求头,那么服务器就应当返回416状态码。
假如 Range 使用的是字节范围,那么这种情况就是指请求指定的所有数据范围的首字节位置都超过了当前资源的长度。服务器也应当在返回416状态码的同时,包含一个 Content-Range 实体头,用以指明当前资源的长度。这个响应也被禁止使用 multipart/byteranges 作为其 Content-Type。
状态:未满足期望值
说明:服务器未满足”期望”请求标头字段的要求。
在请求头 Expect 中指定的预期内容无法被服务器满足,或者这个服务器是一个代理服务器,它有明显的证据证明在当前路由的下一个节点上,Expect 的内容无法被满足。
状态:连接过多
说明:从当前客户端所在的iP地址到服务器的连接数超过了服务器许可的最大范围。通常,这里的iP地址指的是从服务器上看到的客户端地址(比如用户的网关或者代理服务器地址)。在这种情况下,连接数的计算可能涉及到不止一个终端用户。
There are too many connections from your internet address
译:从您的互联网地址有太多的连接从当前客户端所在的IP地址到服务器的连接数超过了服务器许可的最大范围。通常,这里的IP地址指的是从服务器上看到的客户端地址(比如用户的网关或者代理服务器地址)。在这种情况下,连接数的计算可能涉及到不止一个终端用户。
状态:错误实体
说明:请求格式正确,但是由于含有语义错误,无法响应。(RFC 4918 WebDAV)
状态:锁定
说明:当前资源被锁定。(RFC 4918 WebDAV)
状态:错误接洽关系
说明:由于之前的某个请求发生的错误,导致当前请求失败,例如 PROPPATCH。(RFC 4918 WebDAV)
状态:无序集合
说明:在WebDav Advanced Collections 草案中定义,但是未出现在《WebDAV 顺序集协议》(RFC 3658)中。
状态:进级请求
说明:客户端应当切换到TLS/1.0。(RFC 2817)
状态:要求先决条件
说明:先决条件是客户端发送 HTTP 请求时,如果想要请求能成功必须满足一些预设的条件。
先决条件是客户端发送 HTTP 请求时,必须要满足的一些预设条件。一个好的例子就是 If-None-Match 头,经常用在 GET 请求中。如果指定了 If-None-Match ,那么客户端只在响应中的 ETag 改变后才会重新接收回应。
先决条件的另外一个例子是 If-Match 头,一般用在 PUT 请求上,用于指示只更新但没有被改变的资源。这在多个客户端使用 HTTP 服务时用来防止彼此间覆盖相同内容的情况。
当服务器端使用 428 Precondition Required 状态码时,表示客户端必须发送上述的请求头才能执行该请求操作。这个方法为服务器提供一种有效的方法来阻止 “lost update”问题的出现。
状态:太多请求
说明:当你需要限制客户端请求某个服务数量时,该状态码就很有用,也就是请求速度限制。
当你需要限制客户端请求某个服务的数量,也就是限制请求速度时,该状态码就会非常有用。在此之前,有一些类似的状态码。例如”509 Bandwidth Limit Exceeded”。
如果你希望限制客户端对服务的请求数,可使用 429 状态码,同时包含一个 Retry-After 响应头用于告诉客户端多长时间后可以再次请求服务。
状态:请求头字段太大
说明:某些情况下,客户端发送 HTTP 请求头会变得很大,那么服务器可发送 431 Request Header Fields Too Large 来指明该问题。
某些情况下,客户端发送 HTTP 请求头会变得很大,那么服务器可发送 431 Request Header Fields Too Large 来指明该问题。
我不太清楚为什么没有 430 状态码,而是直接从 429 跳到 431,我尝试搜索但没有结果。唯一的猜测是 430 Forbidden 跟 403 Forbidden 太像了,为了避免混淆才这么做的,天知道!
状态:
说明:由微软扩展,代表请求应当在执行完适当的操作后进行重试。
状态:因法律原因而被官方审查
说明:由于法律原因产生的后果而被官方拒绝访问(RFC 7725)
表示临时响应并需要请求者继续执行操作的状态代码。
这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生,也有可能是服务器意识到以当前的软硬件资源无法完成对请求的处理。除非这是一个HEAD 请求,否则服务器应当包含一个解释当前错误状态以及这个状况是临时的还是永久的解释信息实体。浏览器应当向用户展示任何在当前响应中被包含的实体。
这些状态码适用于任何响应方法。
状态:服务器内部错误
说明:服务器遇到错误,无法完成请求。
服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现。
状态:尚未实施
说明:服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。
服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的方法,并且无法支持其对任何资源的请求。
状态:错误网关
说明:服务器作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
状态:服务不可用
说明:服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。
由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。如果能够预计延迟时间,那么响应中可以包含一个 Retry-After 头用以标明这个延迟时间。如果没有给出这个 Retry-After 信息,那么客户端应当以处理500响应的方式处理它。
注:503状态码的存在并不意味着服务器在过载的时候必须使用它。某些服务器只不过是希望拒绝客户端的连接。
状态:网关超时
说明:服务器作为网关或代理,但是没有及时从上游服务器收到请求。
作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应。
注:某些代理服务器在DNS查询超时时会返回400或者500错误
状态:HTTP 版本不受支持
说明:服务器不支持请求中所用的 HTTP 协议版本。
服务器不支持,或者拒绝支持在请求中使用的 HTTP 版本。这暗示着服务器不能或不愿使用与客户端相同的版本。响应中应当包含一个描述了为何版本不被支持以及服务器支持哪些协议的实体。
状态:变体协商
说明:由《透明内容协商协议》(RFC 2295)扩展,代表服务器存在内部配置错误:被请求的协商变元资源被配置为在透明内容协商中使用自己,因此在一个协商处理中不是一个合适的重点。
状态:存储不足
说明:服务器无法存储完成请求所必须的内容。这个状况被认为是临时的。WebDAV (RFC 4918)
状态:超过带宽限制
说明:服务器达到带宽限制。这不是一个官方的状态码,但是仍被广泛使用。
状态:不可拓展
说明:获取资源所需要的策略并没有满足。(RFC 2774)
状态:要求网络认证
说明:如果你频繁使用笔记本和智能手机,你可能会注意到大量的公用 WIFI 服务要求你必须接受一些协议或者必须登录后才能使用。
对我来说这个状态码很有趣,如果你在开发一个 HTTP 服务器,你不一定需要处理该状态码,但如果你在编写 HTTP 客户端,那这个状态码就非常重要。
如果你频繁使用笔记本和智能手机,你可能会注意到大量的公用 Wifi 服务要求你必须接受一些协议或者必须登录后才能使用,这是通过拦截HTTP流量实现的。当用户试图访问网络返回一个重定向和登录,这很讨厌,但是实际情况就是这样的。
使用这些”拦截”客户端,会有一些讨厌的副作用。在 RFC 中提到以下这两个的例子:
- 如果你在登录Wifi前访问某个网站,网络设备将会拦截首个请求,这些设备往往也有自己的网站图标”favicon.ico”。登录后你会发现,有一段时间内你访问的网站图标一直是Wifi登录网站的图标。
- 如果客户端使用HTTP请求来查找文档,网络将会响应一个登录页,这样你的客户端就会解析错误并导致客户端运行异常,在现实中这种问题非常常见。
而 511 状态码的提出就是为了解决这个问题。因此,如果你正在编写 HTTP 的客户端,你最好还是检查 511 状态码以确认是否需要认证后才能访问。
详细说明看 5xx
状态:没有返回响应头部
说明:源站没有返回响应头部,只返回实体内容
本文属于转载:原文(HTTP协议状态码)
]]>以前mysql的相关问题较散, 自此以后小问题均聚合到该文进行记录.
升级mysql后, 之前的sql语句报错, 报错信息如下:
1 | ERROR 1055 (42000): Expression #1 of SELECT name is not in GROUP BY clause and contains nonaggregated column 'database.user.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by |
经查询是由于5.7.5后的版本, sql_mode默认增加了ONLY_FULL_GROUP_BY
, 该模式的作用如下:
对于GROUP BY聚合操作,如果在SELECT中的列,没有在GROUP BY中出现,那么这个SQL是不合法的,因为列不在GROUP BY从句中,也就是说查出来的列必须在group by后面出现否则就会报错,或者这个字段出现在聚合函数里面。
set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
1 | vim my.cnf |
mysql的当前连接数超过了允许的最大连接数.
/etc/my.conf
文件, 在节点[mysqld]
下增加如下内容max_connections = 500
max_user_connections = 500
当前操作数据库的用户和创建该数据库的用户不匹配.
/var/lib/mysql
mysql
启动)chown -R mysql user_database
chgrp -R mysql user_database
MySQL的my.cnf文件(解决5.7.18下没有my-default.cnf)
由于频繁操作表,其索引或文件出现异常
REPAIR TABLE `<table name>`;
硬件设备产生大量传感数据, 需要根据这些原始数据拟合出人能看懂并应用于生活的数据; 公司一直使用Matlab进行数据分析, 本文将介绍如何搭建环境, 并在Akka中调用相关库进行分析.
Matlab 编写的文件后缀为m
(如: filterF.m), 但可以使用Matlab将其打包生成jar
包, 提供给Java生态进行调用.
导出过程很简单, 命令行键入 deploytool → Library Compiler → Java Package → 选择m文件(可以多个) → 填写jar名称, 类名 → Package
如下几点需要特别关注下:
crack
吧.该运行环境免费, 可以到官网进行下载; 但必须下载你Matlab对应的版本和环境(x86/x64).
地址: https://cn.mathworks.com/products/compiler/matlab-runtime.html
直接安装, 环境变量都会由其自动添加; 俗称一键解决.
安装完成后, 会出现successful字样, 之后还会提醒去添加环境变量; 放在/etc/profile
或~/.bash_profile
; 根据情况而定
变量如下:
1 | MCR_ROOT=你的安装目录 |
如此配置后, 发现yum
无法使用, 异常报python版本问题, 解决方案如下:
1 | # 修改环境变量 |
拟合算法入参: Double[n][m]
Scala中二维数组和Java中的二维数组签名并不一致, 我用了个适配方案:
因此, 项目中src
结构如下, 相关文件按类型进行存放
1 | src |
jar包使用需要注意如下几点:
classpath
下, 即: 该包需要在项目启动时, load进JVM中; 后续会发博文详解使用SBT
进行不同环境打包及如何使用本地仓库.MCR
运行包, 其位置在<mcr_root>*\toolbox\javabuilder\jar\glnxa64\javabuilder.jar
, 根据不同环境进行选择1 | double[][] array = new double[m][n]; |
以上示例主要说明, 如何调用方法, xxxFun(1, a)有两个参数, 当然支持多个参数; 第一个1
代表返回值有1个; 第二个参数就是实践需要传入的实参(本例为一个二维数组).
java.lang.LinkageError: libXmu.so.6: cannot open shared object file: No such file or directory
直接安装就ok,
yum -y install libXmu.{i686,x86_64}
总结: 专包专用
各平台的javabuilder.jar
和导出的*.jar
不可跨平台使用.
让使用微信支付的朋友最快速度接入微信支付.
两行代码解决微信支付提供的各种服务, 开箱即用, 可扩展性超强(只需根据服务的上下行协议定义协议类后, 放入工厂即可获取调用结果).
1 | <dependency> |
所有服务在单元测试类(WXPayClientTest.java)中均已测试通过, 下行参数response.isSuccess == true
表示服务调用成功.
官方文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
1 | String nonceStr = SDKUtils.genRandomStringByLength(32); |
官方文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
1 | String nonceStr = SDKUtils.genRandomStringByLength(32); |
官方文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
1 | String nonceStr = SDKUtils.genRandomStringByLength(32); |
官方文档详见: https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
1 | String nonceStr = SDKUtils.genRandomStringByLength(32); |
官方文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_4&index=6
1 | String nonceStr = SDKUtils.genRandomStringByLength(32); |
官方文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7
1 | String notifyTxt = "<xml>\n" + |
官方文档详见: https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
目前未使用, 待续……
该SDK设计了一个服务工厂, 该工厂中包含HTTP执行器/返回数据解析方式(json/xml)/入参数据格式(json/xml)构造等, 开发人员需要增加服务仅需要根据服务协议文档编写上下行协议, 并在协议中指明API接口和返回数据类型, 再将上行协议放入工厂中执行即可; 可参考已完成的服务协议进行扩展编写.
1 | FDFS_STORAGE_STATUS:INIT :初始化,尚未得到同步已有数据的源服务器 |
fdfs_monitor /etc/fdfs/client.conf
无法上传文件, 查看Storage server状态如下:
1 | Storage 2: |
从集群中删除节点及文件, 重启服务
1 | fdfs_monitor /etc/fdfs/client.conf delete group1 192.168.10.202 |
重新查询状态
1 | Storage 2: |
工欲善其事, 必先利其器; 笔者习惯将所有用到的东西掌控在自己可控的范围内, 清晰每个流程, 以便发生问题的时候有思路并能快速响应得出解决方案.
比如默认安装的scala和SBT, 执行sbt
指令时, 默认会在用户目录下生成.ivy
文件, 该文件夹中存储从远程仓库中下载的构件; 笔者不喜欢该方式, 需要控制配置,启动参数和相关仓库地址.
前提条件: 必须安装JDK, 若scala>=2.12.0, 则JDK>=1.8
JDK
simple build tool
, 对于初次接触的同学来说, 其实并不简单; 一种构建工具, 类比于Maven
根据操作系统环境选择合适的开发包.
将开发包下载至自己钟爱的目录并解压,
1 | cd ~/devTools/ |
将开发包下载至自己钟爱的目录并解压,
1 | cd ~/devTools/ |
配置CLASS_PATH
1 | vim ~/.bash_profile |
配置CLASS_PATH
1 | vim ~/.bash_profile |
构建自己依赖库保存目录
1 | cd ~ |
重点: 合理启用关闭其内的地址可大大减少等待时间.
1 | cd ~/sbt-repo/sbt |
内部配置参考如下:
1 | [repositories] |
如上配置了各种仓库, 中央库被注释, 若阿里云仓库
中没找到需要的构件时, 请开启中央库; 否则下载构件时会一直报错, 且编译无法通过.
进入sbt安装目录下的conf目录, 编辑sbtconfig.txt
文件, 如下:
1 | # Set the java args to high |
1 | cd ~/devTools/sbt/conf |
按如下配置进行修改:
1 | # ------------------------------------------------ # |
完成本地仓库和远程仓库配置后, 执行sbt
命令后:
repositories
文件中配置的仓库地址尝试下载(找到一个可用的地址, 若所有地址都不存在需要的构件则报错)sbtopts
指定的本地仓库中, 在用户目录下不会再生成.ivy
文件夹打开terminal
, 输入scala
将进入REPL
1 | AC-PRO:devTools ac$ scala |
打开terminal
, 输入sbt
, 发现会开始下载一些构件, 观察本地仓库, 发现内部多了些目录和文件.
1 | AC-PRO:sbt ac$ sbt |
首次配置完scala和SBT后, 启Intellij IDEA, 进入Plugins
安装如下两个插件:
新建sbt项目, 发现正在下载一些构件(以上不是在执行sbt
指令时就下载至本地仓库了?), 这是因为Intellij IDEA未配置远程仓库和本地仓库, 采用了默认配置, 同时可发现用户目录下出现了.ivy
文件夹.
修改配置如下:
配置路径: Build, Execution, Deployment → Build Tools → SBT
编辑VM parameters
如下:
1 | -XX:MaxPermSize=384M |
配置路径: Build, Execution, Deployment → Build Tools → SBT
编辑VM parameters
如下:
1 | -XX:MaxPermSize=384M |
删除用户目录下.ivy
文件夹, 重启Intellij IDEA, 构件sbt项目, 秒成, 同时用户目录下不再生成.ivy
文件夹.
上一篇 搭建FastDFS集群-一 描述了如何从源码构建FastDFS
服务, 并通过脚本进行文件的 上传/下载/删除; 本文将详细描述如何安装nginx扩展模块
,并使用http协议
下载文件; 同时使用Java Client
方式执行文件的 上传/下载/删除/获取文件详情等.
全新安装nginx或在已安装的nginx上添加模块请看文章 源码安装nginx ,笔者已安装nginx, 就按照添加模块方式进行安装.
查看当前已安装模块
1 | [root@Node nginx-1.12.1]# nginx -V |
1 | cd ~/workspaces/github |
注: 切记不要执行make install
指令, 否则将覆盖之前的配置文件
接下来切换到nginx
安装目录, 备份可执行的二进制文件, 然后复制源码下./objs
目录下的对应文件到安装目录即可, 如下:
1 | cd /root/devTools/nginx-1.12.1/ |
查看
1 | [root@Node nginx-1.12.1]# nginx -V |
安装完成
1 | cd ~/workspaces/github/fastdfs-nginx-module/src |
按照如下进行配置,
1 | # FastDFS tracker_server can ocur more than once, and tracker_server format is |
复制其余配置,
1 | cd ~/workspaces/github/fastdfs/conf |
1 | vim ~/devTools/nginx-1.12.1/conf/nginx.conf |
在server
节点中增加如下location
配置,
1 | location /group1/M00/ { |
在浏览器中输入 http://192.168.10.201 , 能看到 Welcome to nginx!
, 说明nginx工作正常.
部分情况可能会出现403 Forbidden
, 可能是如下原因:
nginx.conf
首行user nobody;
为所属用户, 例如: user root;
按照上一篇 搭建FastDFS集群-一 介绍的内容, 上传文件; 可获得fileId
(group, visual disk, data directory, file name), 例如:
group1/M00/00/00/wKgKyVnmAaOAMTPXAABDgcE2i8g51.jpeg
拼接上schema, host, port即可构成一个url; 例如:
http://192.168.10.201/group1/M00/00/00/wKgKyVnmAaOAMTPXAABDgcE2i8g51.jpeg
在浏览器中访问该url可获得相关信息
至此, fastdfs-nginx-module
扩展模块安装完成; 接下来将介绍如何用java
代码操纵fastDFS
中的文件.
导入依赖库:
1 | <dependency> |
fdfs_client.conf 配置如下:
1 | connect_timeout = 30 |
1 | package com.service_im; |
fastdfs-nginx-module 高可用方案配置.
已解决, 解决方案如下:
nginx中server节点中的location的配置修改如下:
1 | location /group1/M00/ { |
如果还是没有生效, 可尝试如下方法, 定能解决:
nginx
错误日志, 根据提示进行修改nginx
; 示例如下1 | ./configure --prefix=/home/devTools/nginx-1.12.2 --with-pcre=/home/installPackage/pcre-8.41 --with-zlib=/home/installPackage/zlib-1.2.8 --add-module=/home/workspaces/github/fastdfs-nginx-module/src |
之后把objs/nginx
复制到nginx的安装目录进行覆盖.
rsync
指令同步各物理机间的文件我司目前仅仅存储一些小文件, 经过调研, 选择简单实用的FastDFS
.
按VMware克隆多台实例构建分布式系统的方案制作三台虚拟机M_21(192.168.10.201), M_22(192.168.10.202), M_23(192.168.10.203).
最佳实践: 建议首先将M_21
进行配置, 之后使用VMware的克隆
功能复制出剩余的两台; 然后对相关配置文件修改IP
和Port
.
本节内容详细介绍依赖库和FastDFS的安装, 完全无坑, 可放心按步骤操作.
在集群的机器上均执行安装步骤, 或制作安装脚本.
1 | yum -y install gcc automake autoconf libtool libevent |
1 | cd ~ |
看到如下信息表示安装成功
1 | install -m 644 common_define.h hash.h chain.h logger.h base64.h shared_func.h pthread_func.h ini_file_reader.h _os_define.h sockopt.h sched_thread.h http_func.h md5.h local_ip_func.h avl_tree.h ioevent.h ioevent_loop.h fast_task_queue.h fast_timer.h process_ctrl.h fast_mblock.h connection_pool.h fast_mpool.h fast_allocator.h fast_buffer.h skiplist.h multi_skiplist.h flat_skiplist.h skiplist_common.h system_info.h fast_blocked_queue.h php7_ext_wrapper.h id_generator.h char_converter.h char_convert_loader.h /usr/include/fastcommon |
安装和配置git
详见 一台电脑上同时使用github和gitlab
1 | cd ~/workspaces/github |
到此, fastDFS安装完毕, 接下来进行配置.
配置文件目录: /etc/fdfs
1 | #创建fastDFS 文件存储目录 |
https://github.com/happyfish100/fastdfs/blob/master/INSTALL
1 | cd /etc/fdfs |
对照着如下配置进行修改,
1 | # the base path to store data and log files |
注: tracker server的默认监听端口22122
, 如不冲突, 不建议修改
1 | cd /etc/fdfs |
对照如下配置进行修改,
1 | # the base path to store data and log files |
注: storage server的默认监听端口23000
, 如不冲突, 不建议修改
1 | cd /etc/fdfs |
对照如下配置进行修改,
1 | # the base path to store log files |
使用软链接
简化指令
1 | [root@Node fastdfs]# netstat -unltp|grep fdfs |
tracker Server正常运行.
/usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf start
ps -ef|grep fdfs
service
方式启动tracker Server: service fdfs_trackerd start
service fdfs_storaged start
/usr/bin/restart.sh /etc/fdfs/tracker.conf
ps -ef|grep fdfs
service
方式重启tracker Server: service fdfs_trackerd restart
service fdfs_storaged restart
/usr/bin/stop.sh /etc/fdfs/tracker.conf
ps -ef|grep fdfs
service
方式停止tracker Server: service fdfs_trackerd stop
service fdfs_storaged stop
tracker和storage服务均已启动, 且端口正常监听, 如何判断他们是否在通信呢?
使用如下指令:
/usr/bin/fdfs_monitor /etc/fdfs/storage.conf
集群:
1 | [root@Node ~]# /usr/bin/fdfs_monitor /etc/fdfs/storage.conf |
1 | cd /etc/fdfs |
1 | cd ~/temp |
1 | [root@Node temp]# /usr/bin/fdfs_test /etc/fdfs/client.conf upload ~/temp/ops.txt |
1 | [root@Node temp]# /usr/bin/fdfs_test /etc/fdfs/client.conf download group1 M00/00/00/wKgKyVnl_VmAahy0AAAAD1HyOTc464_big.txt |
1 | [root@Node temp]# /usr/bin/fdfs_test /etc/fdfs/client.conf delete group1 M00/00/00/wKgKyVnl_VmAahy0AAAAD1HyOTc464_big.txt |
yum
安装软件报错1 | [root@Node github]# yum -y install git |
解决办法:
1 | vim /etc/yum/pluginconf.d/fastestmirror.conf |
1 | vim /etc/yum.conf |
开始把各种服务的文件分得很细, 亦每个服务对应各自的目录; 其实没必要, track和storage服务产生的文件并不会产生冲突, 可共存于一个目录下.
FastDFS是一款类Google FS的开源分布式文件系统,它用纯C语言实现,支持Linux、FreeBSD、AIX等UNIX系统。它只能通过专有API对文件进行存取访问,不支持POSIX接口方式,不能mount使用
。准确地讲,Google FS以及FastDFS、mogileFS、HDFS、TFS等类Google FS都不是系统级
的分布式文件系统,而是应用级
的分布式文件存储服务。
客户端和Storage server主动连接Tracker server。Storage server主动向Tracker server报告其状态信息,包括磁盘剩余空间、文件同步状况、文件上传下载次数等统计信息。Storage server会连接集群中所有的Tracker server,向他们报告自己的状态。Storage server启动一个单独的线程来完成对一台Tracker server的连接和定时报告。需要说明的是,一个组包含的Storage server不是通过配置文件设定的,而是通过Tracker server获取到的。
不同组的Storage server之间不会相互通信,同组内的Storage server之间会相互连接进行文件同步。
Storage server采用binlog文件记录文件上传、删除等更新操作。binlog中只记录文件名,不记录文件内容。
文件同步只在同组内的Storage server之间进行,采用push方式,即源头服务器同步给目标服务器。只有源头数据才需要同步,备份数据并不需要再次同步,否则就构成环路了。有个例外,就是新增加一台Storage server时,由已有的一台Storage server将已有的所有数据(包括源头数据和备份数据)同步给该新增服务器。
Storage server中由专门的线程根据binlog进行文件同步。为了最大程度地避免相互影响以及出于系统简洁性考虑,Storage server对组内除自己以外的每台服务器都会启动一个线程来进行文件同步。
文件同步采用增量同步方式,系统记录已同步的位置(binlog文件偏移量)到标识文件中。标识文件名格式:{dest storage IP}_{port}.mark,例如:192.168.1.14_23000.mark。
FastDFS是为互联网应用量身定做的分布式文件系统,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标。和现有的类Google FS分布式文件系统相比,FastDFS的架构和设计理念有其独到之处,主要体现在轻量级、分组方式和对等结构三个方面。
FastDFS只有两个角色:Tracker server和Storage server。Tracker server作为中心结点,其主要作用是负载均衡和调度。Tracker server在内存中记录分组和Storage server的状态等信息,不记录文件索引信息,占用的内存量很少。另外,客户端(应用)和Storage server访问Tracker server时,Tracker server扫描内存中的分组和Storage server信息,然后给出应答。由此可以看出Tracker server非常轻量化,不会成为系统瓶颈。
FastDFS中的Storage server在其他文件系统中通常称作Trunk server或Data server。Storage server直接利用OS的文件系统存储文件。FastDFS不会对文件进行分块存储,客户端上传的文件和Storage server上的文件一一对应。
众所周知,大多数网站都需要存储用户上传的文件,如图片、视频、电子文档等。出于降低带宽和存储成本的考虑,网站通常都会限制用户上传的文件大小,例如图片文件不能超过5MB、视频文件不能超过100MB等。我认为,对于互联网应用,文件分块存储没有多大的必要。它既没有带来多大的好处,又增加了系统的复杂性。FastDFS不对文件进行分块存储,与支持文件分块存储的DFS相比,更加简洁高效,并且完全能满足绝大多数互联网应用的实际需要。
在FastDFS中,客户端上传文件时,文件ID不是由客户端指定,而是由Storage server生成后返回给客户端的。文件ID中包含了组名、文件相对路径和文件名,Storage server可以根据文件ID直接定位到文件。因此FastDFS集群中根本不需要存储文件索引信息,这是FastDFS比较轻量级的一个例证。而其他文件系统则需要存储文件索引信息,这样的角色通常称作NameServer。其中mogileFS采用MySQL数据库来存储文件索引以及系统相关的信息,其局限性显而易见,MySQL将成为整个系统的瓶颈。
FastDFS轻量级的另外一个体现是代码量较小, 代码行数不到5.2万行。
类Google FS都支持文件冗余备份,例如Google FS、TFS的备份数是3。一个文件存储到哪几个存储结点,通常采用动态分配的方式。采用这种方式,一个文件存储到的结点是不确定的。举例说明,文件备份数是3,集群中有A、B、C、D四个存储结点。文件1可能存储在A、B、C三个结点,文件2可能存储在B、C、D三个结点,文件3可能存储在A、B、D三个结点。
FastDFS采用了分组存储方式。集群由一个或多个组构成,集群存储总容量为集群中所有组的存储容量之和。一个组由一台或多台存储服务器组成,同组内的多台Storage server之间是互备关系,同组存储服务器上的文件是完全一致的。文件上传、下载、删除等操作可以在组内任意一台Storage server上进行。类似木桶短板效应,一个组的存储容量为该组内存储服务器容量最小的那个,由此可见组内存储服务器的软硬件配置最好是一致的。
采用分组存储方式的好处是灵活、可控性较强。比如上传文件时,可以由客户端直接指定上传到的组。一个分组的存储服务器访问压力较大时,可以在该组增加存储服务器来扩充服务能力(纵向扩容)。当系统容量不足时,可以增加组来扩充存储容量(横向扩容)。采用这样的分组存储方式,可以使用FastDFS对文件进行管理,使用主流的Web server如Apache、nginx等进行文件下载。
FastDFS集群中的Tracker server也可以有多台,Tracker server和Storage server均不存在单点问题。Tracker server之间是对等关系,组内的Storage server之间也是对等关系。传统的Master-Slave结构中的Master是单点,写操作仅针对Master。如果Master失效,需要将Slave提升为Master,实现逻辑会比较复杂。和Master-Slave结构相比,对等结构中所有结点的地位是相同的,每个结点都是Master,不存在单点问题。
fileId即文件路径
文件路径:
group1/M00/00/00/wKgKyVnmAaOAMTPXAABDgcE2i8g51.jpeg
Storage server会连接集群中所有的Tracker server,定时向他们报告自己的状态,包括磁盘剩余空间、文件同步状况、文件上传下载次数等统计信息。
tracker根据请求的fileId来快速定义文件。
1.通过组名tracker能够很快的定位到客户端需要访问的存储服务器组是group1,并选择合适的存储服务器提供客户端访问。
2.存储服务器根据“文件存储虚拟磁盘路径”和“数据文件两级目录”可以很快定位到文件所在目录,并根据文件名找到客户端需要访问的文件。
做了一些开源项目, 可以开箱即用, 为了能让用户快速体验, 最佳方案就是把其构件发布至中央库, 用户按文档导入该依赖, 拷贝文档的测试用例代码, 运行即可看到结果; 为用户调研权衡节省时间.
下文将介绍如何把源码成功发布至Maven中央库的全过程.
该项目的项目目录结构, pom.xml相关配置符合发布要求, 且已发布成功, 并会进行不定期更新, 首次发布的同学可参考对比.
该站点用于提交构件发布的申请及相关问题.
注: 请记住注册的用户名和密码, 在以后会经常用到.
根据提示填写相关信息, 关键信息介绍如下:
其他的就没有什么了,提交之后就等工作人员离开确认吧,有时候工作人员会问你些你没有明确的内容,只需要回答就好.需要注意的是,这个系统的工作人员是在美国上班的,经过我的等待和观察,他们会在北京时间的22:00开始上班、处理issue,所以在这个时间之前就不要去查询状态了.
等到工作人员在你创建的issue下面回复你说“配置已经修改……”(还有几个链接)的时候就说明审批已经通过了,你就可以进行构件的上传了.
在上传构件之前,需要准备GPG以便对发布的文件进行签名.
1.查看是否安装成功
gpg --version
能够显示 GPG 的版本信息,说明安装成功了.
2.生成密钥对
gpg --gen-key
此时需要输入姓名、邮箱等字段,其它字段可使用默认值,此外,还需要输入一个Passphase,相当于一个密钥库的密码,一定不要忘了,也不要告诉别人,最好记下来,因为后面会用到.
3.查看公钥
gpg --list-keys
输出如下信息:
1 | pub rsa2048 2017-10-13 [SC] [有效至:2019-10-13] |
可见这里的公钥的ID是:28FB909499763F5AC432159E4ADB67A5CD37AB08,稍后就会用到.
4.将公钥发布到 PGP 密钥服务器
gpg --keyserver hkp://pool.sks-keyservers.net --send-keys 28FB909499763F5AC432159E4ADB67A5CD37AB08
此后,可使用本地的私钥来对上传构件进行数字签名,而下载该构件的用户可通过上传的公钥来验证签名,也就是说,大家可以验证这个构件是否由本人上传的,因为有可能该构件被坏人给篡改了.
5.查询公钥是否发布成功
gpg --keyserver hkp://pool.sks-keyservers.net --recv-keys 28FB909499763F5AC432159E4ADB67A5CD37AB08
实际上就是从key server上通过公钥ID来接收公钥,此外,也可以到sks-keyservers.net上通过公钥ID去查询.
文件位置: MAVEN_HOME/conf/setting.xml
1 | <settings> |
使用自己注册的Sonatype账号的用户名与密码来配置以上server信息.
1 | <plugin> |
1 | <name>LittleBird MyBatis Generator Core</name> |
1 | <distributionManagement> |
注: 以上pom.xml必须包括:name、description、url、licenses、developers、scm等基本信息; 此外,snapshotRepository与repository中的id一定要与setting.xml中server的id保持一致.
mvn clean deploy
当执行以上Maven命令时,会自动弹出一个对话框,需要输入上面提到的Passphase,它就是通过GPG密钥对的密码,只有自己才知道.随后会看到大量的upload信息,而且速度比较慢,经常会timeout,需要反复尝试.
注意:此时上传的构件并未正式发布到中央仓库中,只是部署到OSS中了,下面才是真正的发布.
若多次发布不成功可使用另一个指令
mvn clean deploy -Dgpg.passphrase=密码
用户名和密码为第一步注册的Sonatype账号
在OSS中,使用自己的Sonatype账号登录后,可在Staging Repositories中查看刚才已上传的构件,这些构件目前是放在Staging仓库中,可进行模糊查询,快速定位到自己的构件.此时,该构件的状态为Open,需要勾选它,然后点击Close按钮.接下来系统会自动验证该构件是否满足指定要求,当验证完毕后,状态会变为Closed,最后,点击Release按钮来发布该构件.
需要在曾经创建的 Issue 下面回复一条“构件已成功发布”的评论,这是为了通知 Sonatype 的工作人员为需要发布的构件做审批,发布后会关闭该 Issue.
没错,还是要等,也许又是 1 ~ 2 天.同样,当审批通过后,将会收到邮件通知.
过一段时间可以在另一些仓库中搜索到, 例如 http://mvnrepository.com/
第一次发布很痛苦, 但只要这次发布成功了, 以后发布就很简单了.
该问题表示对内存不足, 要么将jdk版本升级至jdk1.7及更高, 要么增加堆内存大小; 为了兼容性笔者选择增加对内存大小.
找到文件%M2_HOME%\bin\mvn.bat ,这就是启动Maven的脚本文件,在该文件中你能看到有一行注释为:
@REM set MAVEN_OPTS=-Xdebug -Xnoagent -Djava.compiler=NONE…
它的意思是你可以设置一些Maven参数,我们就在注释下面加入一行:
set MAVEN_OPTS= -Xms128m -Xmx512m
编辑文件/etc/profile
export MAVEN_OPTS=-Xmx512m
编辑文件~/.bash_profile
export MAVEN_OPTS=-Xmx512m
1 | ... |
经查得知,在JDK 8中,Javadoc中添加了doclint,而这个工具的主要目的是旨在获得符合W3C HTML 4.01标准规范的HTML文档,在JDK 8中,已经无法获取如下的Javadoc,除非它满足doclint:
<br/>
或者<a id="x"/>
<ul>
而没有</ul>
</br>
<h3>
而不是<h4>
List<String>
需要用<>
对应的实体符号@link references
@param references
,它们必须匹配实际的参数名称@throws references
,第一个词必须是一个类名称注意违反这些规则的话,将不会得到Javadoc的输出。
在profile
中禁用doclint
,
1 | <profiles> |
在maven-javadoc-plugin
中禁用doclint
1 | <plugin> |
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
本pom.xml中配置了assembly
, 将把文档/jar包/readme等(具体看需求而定)打包成一个bundle.zip
文件, 因此需要配置src.xml
; 其配置如下:
文件位置:
${project.basedir}/src/main/assembly/src.xml
1 | <!-- |
经实践证明, 此后的发布超级简单,
mvn clean deploy
;开始使用多种分布式系统, 搭建本地开发环境.
略…
注: 安装完毕后记得将该实例作为基础实例进行备份, 以后需要添加实例的时候直接克隆
该实例即可.
笔者虚拟机实例网络模式使用桥接模式
, 此模式可使实例获得独立IP, 并能与所在网段的其它机器互相通讯.
1 | ifconfig |
如果启用了的话, 会显示相关信息;
若未启用, 修改网卡配置并使用service network restart
重启eth0即可.
1 | vim /etc/sysconfig/network-scripts/ifcfg-eth0 |
添加如下内容, 如果原来有照着修改即可:
1 | ONBOOT=yes |
以上配置说明:
1 | # 重启网卡即可重用设置 |
注: 填写IPADDR时, 先ping
下所选ip是否被其它机器占用
虚拟机 → 安装VMware Tools
1 | cd |
一路回车, 看见如下内容, 表示安装成功.
1 | To enable advanced X features (e.g., guest resolution fit, drag and drop, and |
将基础实例命名为M_BAK
.
选中实例(右键) → 管理 → 克隆 → … → 创建完整克隆
克隆产生的实例网卡信息会产生变化, 需要进行修改.
ifconfig
1 | [root@Node ~]# ifconfig |
此时看不到eth0网卡的信息
1 | vim /etc/udev/rules.d/70-persistent-net.rules |
1 | # This file was automatically generated by the /lib/udev/write_net_rules |
将上述配置的第一个设备注释掉, 并且将第二个设备信息修改为NAME="eth0"
, 如下
1 | # PCI device 0x8086:0x100f (e1000) |
记住此时的MAC ATTR已变更, 00:0c:29:de:b1:fa
1 | vim /etc/sysconfig/network-scripts/ifcfg-eth0 |
修改IPADDR
和HWADDR
, 如下:
1 | HWADDR=00:0c:29:de:b1:fa |
1 | service network restart |
此处可能会遇到错误,
1 | Shutting down loopback insterface: [ OK ] |
重启(reboot)实例可以解决该问题, 重启后输入指令ifconfig
1 | [root@Node ~]# ifconfig |
原因是什么呢?
因为在虚拟机(Vmware)中移动了Centos系统对应的文件,导致重新配置时,网卡的MAC地址变了,输入ifconfig -a,找不到eth0; 重启后, 将重新加载相关信息.
]]>注: windows的命令行不太好用, 建议用Windows的朋友安装gitbash
ssh钥匙文件默认存储于操作系统当前用户目录的.ssh
子目录下
为了生成自命名的钥匙, 切换到.ssh
作为当前目录
1 | $ cd ~ |
1 | $ ssh-keygen -t rsa -C "arccode@163.com" |
1 | $ ssh-keygen -t rsa -C "username@gmail.com" |
1 | $ ssh-keygen -t rsa -C "username@company.com" |
之后在~/.ssh
目录下将新增6个文件
打开github和gitlab网页, 将github_id_rsa.pub
,gitlab_id_rsa.pub
,gitlab_gyenno_id_rsa.pub
分别配置到对应的sshkey
中
注: 此处不上传公钥(.pub)的话, 连接时将提示Permission denied (publickey).
1 | $ cd ~ |
config 内容如下
1 | # github |
1 | $ ssh -T github |
这里的github
和gitlab
是config中配置的host; 根据此host,git可以找到配置对应的地址
笔者配置如下:
~/workspace/github
~/workspace/gitlab
~/workspace/gitlab_gyenno
检查当前配置
git config --list
1 | $ cd ~/workspace/github |
1 | $ cd ~/workspace/gitlab |
1 | $ cd ~/workspace/gitlab_gyenno |
1 | $ git clone git@github:arccode/littlebird-mybatis-generator-core.git |
原本从仓库clone项目的指令是, git clone git@github.com:arccode/littlebird-mybatis-generator-core.git
因为配置了config
, 所以使用git会使用host
自动查找到git@github.com
1 | $ git clone git@gitlab:arccode/arccode.net.git |
1 | $ git clone git@gitlab_gyenno:backend/GyennoMedicalCloud.git |
指令修改原因同上
]]>安装MySql后, 未及时修改安装时的root默认密码, 密码过期; 需要重置密码.
系统偏好设置 → MySql → Stop MySql Server
注: 如果无法关闭服务, 可在活动监视器中找到mysqld进程, 直接杀死即可.
以上操作适用于MySql v5.7.9及之后版本, 之前版本使用如下命令:
UPDATE mysql.user SET Password=PASSWORD(‘新密码’) WHERE User=’root’;
]]>Nginx是一款轻量级的网页服务器、反向代理服务器.相较于Apache、lighttpd具有占有内存少,稳定性高等优势.它最常的用途是提供反向代理服务.
yum -y install gcc gcc-c++ autoconf automake make
nginx 中gzip模块需要 zlib 库,rewrite模块需要 pcre 库,ssl 功能需要openssl库
1 | cd ~/source-module |
1 | cd ~/source-module |
1 | cd /usr/local |
1 | wget https://nginx.org/download/nginx-1.12.1.tar.gz |
注: 该部分文章, 与上面文章不在一台机器上操作, 注意路径即可.
下面以添加fastdfs-nginx-module
模块为例进行操作.
查看当前已安装模块
1 | [root@Node nginx-1.12.1]# nginx -V |
1 | cd ~/workspaces/github |
注: 切记不要执行make install
指令, 否则将覆盖之前的配置文件
接下来切换到nginx
安装目录, 备份可执行的二进制文件, 复制./objs
目录下的对应文件到安装目录即可, 如下:
1 | cd /root/devTools/nginx-1.12.1/ |
查看
1 | [root@Node nginx-1.12.1]# nginx -V |
将安装后的目录加入PATH中, 方便调用指令
编辑.bash_profile
文件
1 | export NGINX_HOME=/Users/ac/devTools/nginx-1.12.1 |
更新profile文件, source ~/.bash_profile
注: 若提示Permission denied, 请在命令前添加sudo或切换到root用户(使用su命令)
配置文件位置
1 | /Users/ac/devTools/nginx-1.12.1/conf/nginx.conf |
Nginx配置文件常见结构的从外到内依次是「http」「server」「location」等等,缺省的继承关系是从外到内,也就是说内层块会自动获取外层块的值作为缺省值.
1 | server { |
建议有多个server时, 将各server独立放入一个*.conf
文件中(例如: gyenno.conf), 并在nginx.conf文件内容末尾加入如下语句include conf.d/*.conf;
每个 url 请求都会对应的一个服务,nginx 进行处理转发或者是本地的一个文件路径,或者是其他服务器的一个服务路径.而这个路径的匹配是通过 location 来进行的.我们可以将 server 当做对应一个域名进行的配置,而 location 是在一个域名下对更精细的路径进行配置.
以上面的例子,可以将root和index指令放到一个location中,那么只有在匹配到这个location时才会访问root后的内容:
1 | location / { |
location 匹配规则
1 | ~ 波浪线表示执行一个正则匹配,区分大小写 |
匹配示例:
1 | location = / { |
root
和alias
两个指令指定文件目录
autoindex on: 列出指定目录下的文件
1 | location /b/ { |
文件所在目录: root + location
例如: http://localhost/b/index.html, index.html所在目录为/a/b/
注: root后的/
可以省略
1 | location /b/ { |
文件所在目录: alias
例如: http://localhost/b/index.html, index.html所在目录为/a/
注: root后的/
不可省略
1 | location / { |
所有访问ts.gyenno.com
的请求都转发到本地机器的8080
端口.
1 | upstream myserver { |
我们在 upstream 中指定了一组机器,并将这个组命名为 myserver,这样在 proxypass 中只要将请求转移到 myserver 这个 upstream 中我们就实现了在四台机器的反向代理加负载均衡.其中的 ip_hash 指明了我们均衡的方式是按照用户的 ip 地址进行分配.另外还有轮询、指定权重轮询、fair、url_hash几种调度算法.
以上是最简单的通过 nginx 实现静态文件转发、反向代理和负载均衡的配置.在 nginx 中所有的功能都是通过模块来实现的,比如当我们配置 upstream 时是用 upstream 模块,而 server 和 location 是在 http core 模块,其他的还有流控的 limt 模块,邮件的 mail 模块,https 的 ssl 模块.他们的配置都是类似的可以再 nginx的模块文档中找到详细的配置说明.
]]>看了腾讯动漫各位导演的演讲, 好有感触, 特意找了一部提及的动漫(妖怪名单)进行观看, 总共18集; 看了2集觉得不错, 想直接看完, 但每次都有60s广告时间, 烦人. 速度浏览了下该站点前端代码, 哈哈, 很好解决这问题, 接下来详细讲解各种手法.
以下手法经测试, 可以秒杀
目前流行的各大视频平台.
chrome浏览器
针对不同的用户群体, 给出相适应的避广告
手法.
右键–>在新标签页中打开链接
右键–>检查–>toggle device mode–>Ctrl + F5(强制刷新页面)
注: 有些视频网站提示报错, 可以切换Device, 通常使用Apple iPhone和Google Nexus即可解决.
右键–>检查–>Network–>Type(用于排序, 方便查找)–>复制类型未media的地址
直接获取地址:
间接获取地址, 该方法适用于那些地址隐藏较深的站点:
通过该手法, 结合爬虫
技术, 那想象空间真的很大……….(此处省略3万字)
该博文仅作为技术交流, 切勿用于商业目的.
]]>用过微信网页版的人应该都清楚网页登陆的流程,大致描述一下这个过程:
这个过程的交互方式和一般的WEB应用不太一样,步骤4
网页自动跳转,明显是由服务端主动推送了内容给网页端,网页端收到跳转确认后才触发的,这里就引出了今天要讨论的问题:服务端推送技术
.服务端推送又称为Comet,服务端异步处理等.很早以前就出现了,但一直没有一个统一的标准,存在着不少Comet技术框架,各个Web容器也各自实现了自己的Comet支持.最近公司的产品也出现了和微信网页版登陆类似的场景,需要用到Comet技术,我简单的研究了下,写下来记录一下.
omcat 内置支持,需要实现CometProcessor接口.但是应用就依赖Tomcat容器了, 由于依赖性过大, 暂不考虑.
Servlet3提供一套完整的异步处理API,包括AsyncContext,AsyncLiseter,AsyncEvent. 要求Tomcat7.0++.
SpringMVC3.2 在Servlet3的基础上做了进一步的封装,编码更为简单,提供Callable,WebAsyncTask,DeferredResult三种方式进行异步编程支持,非常方便.
扫描动态二维码关注微信公众账号.
配置web.xml的命名空间
1 | <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
配置tomcat
1 | <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" |
1 | @WebServlet(value = "/scan/*",asyncSupported = true) |
1 | public class ScanRetain { |
1 | Long senceId = 0L; |
Spring的代码实现简单很多,把Servlet方案中的第二步和第三步合成了一步,但是也不那么直观,不利于理解.
1 | // 上下文容器 |
1 | @RequestMapping(value="/newScan/{key}",produces = "text/plain;charset=utf-8;") |
异步操作比较适用于一些比较耗时的操作(如大数据计算,文件处理),它们的响应一般不存在其他的触发点,就是取决于Callable内部代码块的执行结束.
Callable:
1 | @ResponseBody |
WebAsyncTask:
1 | @ResponseBody |
如果在web.xml中配置了其它的servlet或filter, 必须在他们的根标签中增加支持异步操作标签<async-supported>true</async-supported>
.
综上,我们大致可以总结出异步处理的两种应用场景:
多点操作,单点的响应往往依赖于其他点的触发,最典型的就是微信扫描登录了.这个基本的编码思路应该是这样的:
单点操作,但是操作往往非常耗时,不能及时响应.这种场景一般会把耗时操作全部抽离到Callable代码段,响应的触发点就是Callable代码的结束处.
最近有个服务运行几天就会莫名其妙的无法调用, 但使用jps -m|grep 服务名称
指令查看后发现服务进程仍然存在; 因此怀疑可能是有内存泄漏导致进程编程”僵尸”进程.
为此决定使用jconsole来对指定进程的JVM进行监控, 下面将详细介绍如何配置Linux JRE来监控内存.
在启动脚本中增加启动参数, 启动脚本可以是自定义的shell脚本或tomcat自带的脚本等等.
1 | JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=192.168.10.89 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=39700 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=true" |
1 | # 切换到jdk的子目录 |
启动程序脚本;
启动jconsole, 如下图;
]]>server.port=9001 设置无效
VM options
(-Dserver.port=9001)lombok不生效
lombok
并安装、重启Enable annotation processing
前的复选框选中
代码修改后,运行时发现并未生效
out
目录中的对应文件out
目录,并重新编译mysql安装后, 插入的数据位乱码, 经检查为默认字符集是latin1
, 而程序使用UTF-8
.
1.使用ssh终端连接mysql
1 | mysql -h 127.0.0.1 -u root -p |
2.查看字符集
1 | show variables like 'character%'; |
显示如下
1 | +--------------------------+----------------------------+ |
character_set_database和character_set_server的默认字符集还是latin1.
修改mysql的my.cnf
文件中的字符集键值, my.cnf
一般位于/etc
目录下, 没找到的话可以使用find / -name my.cnf
查找.
1.在[client]字段里加入default-character-set=utf8
1 | [client] |
2.在[mysqld]字段里加入character-set-server=utf8
1 | [mysqld] |
3.在[mysql]字段里加入default-character-set=utf8
1 | [mysql] |
修改完成后,service mysql restart重启mysql服务就生效.
注意: [mysqld]字段与[mysql]字段是有区别的.
4.再次查看字符集
1 | show variables like 'character%'; |
如下所示:
1 | +--------------------------+----------------------------+ |
5.如果上面的都修改了还乱码,那剩下问题就一定在connection连接层上.
1 | SET NAMES 'utf8'; |
该语句相当于
1 | SET character_set_client = utf8; |
开发第三方支付, 需要接收第三方回调(即在外网上暴露api), 发现ngrok可以完成该业务, 下面具体介绍其使用.
1 |
|
可以通过http://127.0.0.1:4040
登录控制台查看配置等参数.
ngrok自动映射的subDomain
是随机的, 关闭重启将改变, 可升级为付费用户自定义subDomain
, 详情见高级特性.
详见搭建自己的ngrok服务.
使用步骤:
1.7版客户端分流:Linux版本,Mac OSX版本,32Bit Win版本,64Bit Win版本
运行客户端时,请添加-config以载入配置文件。
1 | 例如 ngrok -config ngrok.cfg -subdomain example 8080 |
基于总线的设计,借鉴了计算机内部硬件组成的设计思想(通过总线传输数据).在分布式系统中,不同子系统之间需要实现相互通信和远程调用,比较直接的方式就是“点对点”的通信方式,但是这样会暴露出一些很明显的问题:系统之间紧密耦合、配置和引用混乱、服务调用关系错综复杂、难以统一管理、异构系统之间存在不兼容等.而基于总线的设计,正是为了解决上述问题.总线则作为中枢系统,提供统一的服务入口,并实现了服务统一管理、服务路由、协议转换、数据格式转换等功能.这样能够将不同系统有效地连接起来,并大大降低了连接数(每个子系统只需要和总线建立连接)和系统间连接拓扑的复杂度.
如图所示:
基于服务总线的设计,往往需要ESB(Enterprise Service Bus,企业服务总线)产品来充当基础设施.ESB采用了“总线”这样一种模式来管理和简化应用之间的集成拓扑结构,以广为接受的开放标准为基础来支持应用之间在消息、事件和服务的级别上动态的互连互通. ESB是一种在松散耦合的服务和应用之间标准的集成方式.
在其内部设计和实现中,通常会应用到一些经典的架构模式,例如:Broker模式、消息总线模式、管道过滤器模式、发布订阅模式等.这里,我们将重点介绍这几种核心的架构模式.
Broker可以看作是服务总线中的一部分,通常应用于同步调用的场景(调用服务后阻塞,等待远程服务响应完成后再返回结果).Broker可以看作是代理、分发,意味着对服务的调用,可以直接通过Broker来完成.在软件架构设计中,向已经存在(引用或者调用)关系的两者中间加入“第三者”是一种常见的解耦方式,显然,Broker也不例外.
如下图所示:
为了进一步加深读者对Broker模式的理解,这里通过举例来说明:
在现实生活中,我们需要租房子(可以看作是一种服务调用),可以通过几种途径来完成.第一,我们可以先在网上了解,然后跑到目标小区去询问和看房,最后再找房东签合同等;第二,也可以直接找附近的地产中介,说出我们的要求和预算,请中介直接帮我们搞定一切.很明显,第一种方式,需要自己对目标有足够的了解而且还要亲自去找房东签合同(依赖具体,可以看作是紧密耦合),而第二种方式,仅仅需要告知中介需要什么即可完成租房,甚至都不需要知道哪个小区有房子、房东到底是谁等这些信息(依赖抽象,并通过第三者来实现解耦).当然,找中介虽然省事,也会产生额外的经济开销.同理,通过Broker来调用服务也可能会产生一定的开销.
SOA系统有三种基础组件:消息总线、信息转换/处理引擎和存储库.一般来说,这些组件会集成到ESB中,而在这些基础组件中,消息总线是最重要的.消息总线主要应用于异步通信场景(投递消息后立刻返回结果,而不用等待远程系统返回执行成功),可以大大提升响应速度和系统吞吐量.当然,消息总线也同时支持同步通信模式.基于消息总线模式的设计中,消息中间件屏蔽了系统间底层通信的细节,是比较典型的(异步)松耦合的架构.
在企业中,随着业务的逐渐发展,各大系统之间的调用交互变得非常频繁,关系错综复杂.
想象如果有几十或者上百个系统(在保险、金融领域很常见),将变得难以维护.通过引入消息中间件,便能有效的解决这些问题.不同系统之间,只需要连接到消息总线,能保证成功投递/接收消息即可.对于消息投递者(生产者)而言,根本不用关心消息的接收者(消费者)到底有哪些,也不用关心消费者接收到消息之后该如何处理.对于接收者(消费者)而言,只需要关注与自己有关的消息,接收到消息后并做出相应的处理即可.
如下图所示:
比较典型的应用场景:张三在CRM系统中录入了一条客户签约订单的信息并审核通过后,CRM系统会向消息总线投递一条消息(消息中包含必要信息,例如员工ID,订单编号,产品编号和交易金额等,而CRM系统不用关心有哪些系统会接受到该消息).员工绩效奖金管理系统订阅了该消息主题,收到消息通知后开始处理(给张三执行加奖金操作),同时,进销存系统收到消息通知后也会即时地更新商品库存的数量.这样,便实现异构系统之间的松耦合、异步通信.当然,真实场景可能比这里更复杂,但是实现思路上都大同小异.
在理解了上述实例之后,有必要了解下Java EE中被广泛应用和实现的JMS(Java消息服务).
JMS是一种关于消息的规范,业界流行的ActiveMQ则是实现了JMS规范的开源消息总线.
JMS支持两种模式:发布/订阅模式和队列模式(点对点模式).其中,发布/订阅模式借鉴了现实生活中的出版社(发布图书)和读者(订阅图书),消息的消费者(读者)只需要订阅自己感兴趣的消息(图书)即可,消息生产者(出版社)生产(出版)了消费者(读者)感兴趣的新消息(新书)后,会通知消费者(读者)接受处理.在JMS发布/订阅模式中,通常以Topic(主题)来标识消息,是一对多的模式,意味着同一个主题可以同时被多个消费者来订阅和消费.而在JMS 队列模型中,通常以Queue name来标识消息,是一对一的模型,意味着同一条消息只能被一个消费者接收并消费(且只能被消费一次).在生产者和消费者都是集群的环境中,通常需要将这两种模式结合起来使用,情况会复杂很多,而且需要考虑容错性、负载均衡、消息一致性、消息优先级等复杂的问题.
业界主流的消息总线(消息中间件)产品,普遍支持消息过滤、自动重试、分布式事务、持久化、消息优先级、消息回溯、(生产者/消费者/中间件自身)集群、故障转移等高级特性.
基于总线架构的主要优势在于:
可扩展性
使用消息架构,可以在不影响现有应用的情况下增加或移除应用.
低复杂度
每个应用只需要和总线通信,只有1个连接点,降低了应用程序集成的复杂度.
灵活性
对于构成复杂处理的应用程序通信组来说,只需要改变配置和控制路由参数就能满足业务逻辑或者需求变更,而不需要更改服务本身.
松耦合
应用程序直接和消息总线通信,不依赖其它应用程序,因此可以替换和修改.
可扩展性
可以把多个应用程序附加到总线上,进行并发处理,达到负载均衡的目的.
基于总线的模型,可以面向同构和异构系统(跨平台).当前主要应用于传统企业应用的整合与系统集成中(例如:电信、保险、金融等行业).由于基于总线的模型是一种集中式的架构,总线自身容易形成性能瓶颈,但可以通过横向扩展, 然后在其上层增加LVS进行负载均衡, 实现高并发,高可用.
]]>在足球比赛里,一个球员在一场比赛中进三个球,称之为帽子戏法(Hat-trick).在分布式数据系统中,也有一个帽子原理(CAP Theorem),不过此帽子非彼帽子.CAP原理中,有三个要素:
CAP原理指的是,这三个要素最多只能同时实现两点,不可能三者兼顾.因此在进行分布式架构设计时,必须做出取舍.而对于分布式数据系统,分区容忍性是基本要求,否则就失去了价值.因此设计分布式数据系统,就是在一致性和可用性之间取一个平衡.对于大多数web应用,其实并不需要强一致性,因此牺牲一致性而换取高可用性,是目前多数分布式数据库产品的方向.
当然,牺牲一致性,并不是完全不管数据的一致性,否则数据是混乱的,那么系统可用性再高分布式再好也没有了价值.牺牲一致性,只是不再要求关系型数据库中的强一致性,而是只要系统能达到最终一致性即可,考虑到客户体验,这个最终一致的时间窗口,要尽可能的对用户透明,也就是需要保障“用户感知到的一致性”.通常是通过数据的多份异步复制来实现系统的高可用和数据的最终一致性的,“用户感知到的一致性”的时间窗口则取决于数据复制到一致状态的时间.
对于一致性,可以分为从客户端和服务端两个不同的视角.从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题.从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致.一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景.
从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性.对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性.如果能容忍后续的部分或者全部访问不到,则是弱一致性.如果经过一段时间后要求能访问到更新后的数据,则是最终一致性.
最终一致性根据更新数据后各进程访问到数据的时间和方式的不同,又可以区分为:
因果一致性.如果进程A通知进程B它已更新了一个数据项,那么进程B的后续访问将返回更新后的值,且一次写入将保证取代前一次写入.与进程A无因果关系的进程C的访问遵守一般的最终一致性规则.
“读己之所写(read-your-writes)”一致性.当进程A自己更新一个数据项之后,它总是访问到更新过的值,绝不会看到旧值.这是因果一致性模型的一个特例.
会话(Session)一致性.这是上一个模型的实用版本,它把访问存储系统的进程放到会话的上下文中.只要会话还存在,系统就保证“读己之所写”一致性.如果由于某些失败情形令会话终止,就要建立新的会话,而且系统的保证不会延续到新的会话.
单调(Monotonic)读一致性.如果进程已经看到过数据对象的某个值,那么任何后续访问都不会返回在那个值之前的值.
单调写一致性.系统保证来自同一个进程的写操作顺序执行.要是系统不能保证这种程度的一致性,就非常难以编程了.
上述最终一致性的不同方式可以进行组合,例如单调读一致性和读己之所写一致性就可以组合实现.并且从实践的角度来看,这两者的组合,读取自己更新的数据,和一旦读取到最新的版本不会再读取旧版本,对于此架构上的程序开发来说,会少很多额外的烦恼.
从服务端角度,如何尽快将更新后的数据分布到整个系统,降低达到最终一致性的时间窗口,是提高系统的可用度和用户体验非常重要的方面.对于分布式数据系统:
如果W+R>N,写的节点和读的节点重叠,则是强一致性.例如对于典型的一主一备同步复制的关系型数据库,N=2,W=2,R=1,则不管读的是主库还是备库的数据,都是一致的.
如果W+R<=N,则是弱一致性.例如对于一主一备异步复制的关系型数据库,N=2,W=1,R=1,则如果读的是备库,就可能无法读取主库已经更新过的数据,所以是弱一致性.
对于分布式系统,为了保证高可用性,一般设置N>=3.不同的N,W,R组合,是在可用性和一致性之间取一个平衡,以适应不同的应用场景.
如果N=W,R=1,任何一个写节点失效,都会导致写失败,因此可用性会降低,但是由于数据分布的N个节点是同步写入的,因此可以保证强一致性.* 如果N=R,W=1,只需要一个节点写入成功即可,写性能和可用性都比较高.但是读取其他节点的进程可能不能获取更新后的数据,因此是弱一致性.这种情况下,如果W<(N+1)/2,并且写入的节点不重叠的话,则会存在写冲突
]]>曾今SOA的概念犹如今日“云计算、大数据”一样,被炒得火热,不少企业便纷纷响应,并宣称会拥抱和实施SOA.而事实上,业界出现了两种极端:一种是由于各类文章和书籍关于SOA的描述往往太过抽象,再加上各大厂商的呼吁,使得SOA往往显得“高大上”,令不少企业和架构师们望而却步.第二种恰好相反,有部分人却认为SOA无非是“新瓶装旧酒”.
个人理解,SOA在宏观上确实太复杂,因为它涉及到的不仅仅是技术和架构本身.而从技术的视角来看,并非难以落地.
SOA全称“面向服务架构”,它提供的是一种架构风格和理念,而并非是一种技术或者产品.并不是说项目中用了WebService、WCF、Hessian、RMI之类的就是SOA了.
通俗点来讲,SOA提倡将不同应用程序的业务功能封装成“服务”并宿主起来,通常以接口和契约的形式暴露并提供给外界应用访问(通过交换消息),达到不同系统可重用的目的.
流行的WebService等可以看作是实现SOA基础设施的技术方法.当然,实践SOA不仅需要解决服务调用的问题,还包括服务编排、服务治理、服务路由、服务监控等一系列的问题.在大型分布式系统中,SOA被广泛实践,但是在不同的应用场景中,设计方法也大不相同.
SOA是一个组件模型,它能将不同的服务通过定义良好的接口和契约联系起来.服务是SOA的基石,在开始服务设计和SOA实践之前,有必要先了解服务的概念以及服务的常见特性.
服务的概念非常宽泛,在宏观上,服务的理解是“为他人做事,满足他人需要,而且通常是不以实物形式提供劳动的…”.在SOA系统中,服务指的是应用程序的功能单元,它通常体现了业务功能.服务是一种抽象,它向服务使用者隐藏了服务内部的实现细节.根据服务设计的基本原则,服务可能会具有以下特性:
自治(理)性
服务应该是独立部署和运行存在的,且边界清晰,应尽量减少对外部的引用和依赖.
粗粒度
服务调用是需要开销的,这也是实现松耦合的分布式系统必须付出的代价.因此,应尽量通过一次服务调用传输所有需要的数据,而不是分多次去调用服务和组装数据.
可见性
服务是对外提供的,必须在某公共的地方可搜寻和发现,且服务要有必要的描述.
无状态
服务不应该依赖于其他服务的上下文、会话等,尽量减少不必要的状态管理流程所带来的资源消耗.但是,对于业务流程服务而言,状态数据是不可避免的.
幂等性
当消费者调用服务后,服务调用可能会有“成功、失败、超时”这三种状态,当服务并没有最终响应完成时,消费者可以尝试反复地调用服务,这样仍不会影响到最终结果.
可重用性
服务应该是可以被重用的,相同功能应可以调用相同的服务,这也是软件设计的原则.
可组合
服务是可以被当作成一个步骤的,服务也可以调用其它的服务.这样能够灵活的组合.
有关服务的“粗粒度、无状态、幂等性”等特性,一直是饱受争议的话题,可谓见仁见智.这里有必要说明下,这些特性并不是服务不可或缺的,应当在实践中根据需求来取舍.
SOA架构将公共的业务拆分出来,形成可共用的服务,最大程度的保障了代码和逻辑的复用,避免了系统的重复建设,并且让应用程序的部署找到了一种持续可扩展的方案,给应用抗负载能力带来了质的飞跃.
SOA架构所面临的一大问题就是如何解决集成服务应用普遍存在的一致性问题,举例来说,同时调用多个服务,当其中一个服务调用失败时,其他服务已经处理执行的结果该如何进行回滚,这在单机本地调用的情况下使用事务比较好处理,而分布式环境下的事务将问题复杂化,并且性能开销难以承受,因此,只有在极端情况下才会考虑强一致性,一般情况下更多的关注最终一致性.
面向企业的平台级的SOA架构,需要对参数传递、响应内容以及各种用户私有信息的交互,有着更严格的且特殊的安全需求,如何构建一个安全的SOA架构体系,也给技术人员带来了很大的挑战.
]]>以前编程时也碰到过Stub
这个名词, 但没关注, 今天研究Paypal Rest API
又碰到到这个词, 决定探查个究竟.
Stub 跟 Proxy 是一对,俗称代理-桩
,一般用在远程方法调用.
Proxy 相当于是拿在手里的遥控器,而 Stub 相当于长在电视机里的遥控接收器,它们有着一一对应的接口方法.
Proxy 的接口供客户端程序调用,然后它内部会把信息包装好,以某种方式(比如 RMI)传递给 Stub,而后者通过对应的接口作用于服务端系统,从而完成了远程调用
.
Dubbo是阿里巴巴内部的SOA服务化治理方案的核心框架,每天为2000+ 个服务提供3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。Dubbo自2011年开源后,已被许多非阿里系公司使用。
项目主页:http://dubbo.io/Home-zh.htm
我叫梁飞,花名虚极,之前负责Dubbo服务框架,现已调到天猫。
Dubbo是一个分布式服务框架,以及SOA治理方案。其功能主要包括:高性能NIO通讯及多协议集成,服务动态寻址与路由,软负载均衡与容错,依赖分析与降级等。
可参见:http://alibaba.github.io/dubbo-doc-static/Home-zh.htm
当网站变大后,不可避免的需要拆分应用进行服务化,以提高开发效率,调优性能,节省关键竞争资源等。
当服务越来越多时,服务的URL地址信息就会爆炸式增长,配置管理变得非常困难,F5硬件负载均衡器的单点压力也越来越大。
当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。
接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?等等……
在遇到这些问题时,都可以用Dubbo来解决。
可参见:Dubbo的背景及需求
该框架具有极高的扩展性,采用微核+插件体系,并且文档齐全,很方便二次开发,适应性极强。
可参见:开发者指南 - 框架设计
Dubbo运行JDK1.5之上,缺省依赖javassist、netty、spring等包,但不是必须依赖,通过配置Dubbo可不依赖任何三方库运行。
可参见:用户指南 - 依赖
Dubbo通过长连接减少握手,通过NIO及线程池在单连接上并发拼包处理消息,通过二进制流压缩数据,比常规HTTP等短连接协议更快。在阿里巴巴内部,每天支撑2000多个服务,30多亿访问量,最大单机支撑每天近1亿访问量。
可参见:Dubbo性能测试报告
1. Dubbo比HSF的部署方式更轻量,HSF要求使用指定的JBoss等容器,还需要在JBoss等容器中加入sar包扩展,对用户运行环境的侵入性大,如果你要运行在Weblogic或Websphere等其它容器上,需要自行扩展容器以兼容HSF的ClassLoader加载,而Dubbo没有任何要求,可运行在任何Java环境中。
2. Dubbo比HSF的扩展性更好,很方便二次开发,一个框架不可能覆盖所有需求,Dubbo始终保持平等对待第三方理念,即所有功能,都可以在不修改Dubbo原生代码的情况下,在外围扩展,包括Dubbo自己内置的功能,也和第三方一样,是通过扩展的方式实现的,而HSF如果你要加功能或替换某部分实现是很困难的,比如支付宝和淘宝用的就是不同的HSF分支,因为加功能时改了核心代码,不得不拷一个分支单独发展,HSF现阶段就算开源出来,也很难复用,除非对架构重写。
3. HSF依赖比较多内部系统,比如配置中心,通知中心,监控中心,单点登录等等,如果要开源还需要做很多剥离工作,而Dubbo为每个系统的集成都留出了扩展点,并已梳理干清所有依赖,同时为开源社区提供了替代方案,用户可以直接使用。
4. Dubbo比HSF的功能更多,除了ClassLoader隔离,Dubbo基本上是HSF的超集,Dubbo也支持更多协议,更多注册中心的集成,以适应更多的网站架构。
Dubbo主要针对内部服务,对外的服务,阿里有开放平台来处理安全和流控,所以Dubbo在安全方面实现的功能较少,基本上只防君子不防小人,只防止误调用。
Dubbo通过Token令牌防止用户绕过注册中心直连,然后在注册中心上管理授权。Dubbo还提供服务黑白名单,来控制服务所允许的调用方。
可参见:Dubbo的令牌验证
在阿里内部,除淘系以外的其它阿里子公司,都在使用Dubbo,包括:中文主站,国际主站,AliExpress,阿里云,阿里金融,阿里学院,良无限,来往等等。
开源后,已被:去哪儿,京东,吉利汽车,方正证劵,海尔,焦点科技,中润四方,华新水泥,海康威视,等公司广泛使用,并不停的有新公司加入,社区讨论及贡献活跃,得到用户很高的评价。
可参见:Dubbo的已知用户
分布式事务可能暂不会支持,因为如果只是支持简单的XA/JTA两阶段提交事务,实用性并不强。用户可以自行实现业务补偿的事件,或更复杂的分布式事务,Dubbo有很多扩展点可以集成。
在多语言方面,Dubbo实现了C++版本,但在内部使用面极窄,没有得到很强的验证,并且C++开发资源紧张,没有精力准备C++开源事项。
Dubbo采用Apache License 2.0开源协议,它是一个商业友好的协议,你可以免费用于非开源的商业软件中。
你可以对它进行改造和二次发布,只要求保留阿里的著作权,并在再发布时保留原始许可声明。
可参见:Dubbo的开源许可证
Dubbo共有六个开发人员参与开发和测试,每一个开发人员都是很有经验,团队合作很默契,开发过程也很有节奏,有完善质量保障流程。团队组成:
梁飞 (开发人员/产品管理)
刘昊旻 (开发人员/过程管理)
刘超 (开发人员/用户支持)
李鼎 (开发人员/用户支持)
陈雷 (开发人员/质量保障)
闾刚 (开发人员/开源运维)
从左至右:刘超,梁飞,闾刚,陈雷,刘昊旻,李鼎
可参见:Dubbo的团队成员
开发者可以在Github上fork分支,然后将修改push过来,我们审核并测试后,会合并到主干中。
Github地址:https://github.com/alibaba/dubbo
开发者可以在JIRA上认领小的BUG修复,也可以在开发者指南页面领取大的功能模块。
JIRA:http://code.alibabatech.com/jira/browse/DUBBO(暂不可用)
开发者指南:http://alibaba.github.io/dubbo-doc-static/Developer+Guide-zh.htm
Dubbo的RPC框架已基本稳定,未来的重心会放在服务治理上,包括架构分析、监控统计、降级控制、流程协作等等。
可参见:http://alibaba.github.io/dubbo-doc-static/Roadmap-zh.htm
API就是开发者使用的界面。我的目标不仅是能用,而且好用, 跨平台(PC, Android, IOS, etc…)使用; 本文将详细介绍API的设计及异常处理, 并将异常信息进行封装友好地反馈给前端.
上篇文章前后端完全分离初探只是讲了些宽泛的概念, 接下来的文章将直接上干货, 干货的源码会挂在github
上.
前后端完全分离后, 前端和后端如何交互?
答: 通过双方协商好的API.
接下来我分享我自己设计的API接口, 欢迎各位朋友指教.
id
访问资源, 在url
上做文章, 以后再也不用为url
起名字而苦恼了.HTTP
动词对资源进行CRUD
(增删改查); get->查, post->增, put->改, delete->删.-
)连接.目前我API的设计只涉及这两点, 至于第三点HATEOAS
(Hypermedia As The Engine Of Application State)那就由读者自己去选择了.
本文中只涉及了设计的理念, 具体的实现请下载源码rest-api, 项目内写了比较详细的注释.
实战将从业务场景出发, 详细介绍如何使用HTTP verb对资源进行操作(状态转移
), 使用JSON返回结果(资源表述
), 并定义JSON的基础结构.
requestParams:
1 | { |
responseBody:
1 | { |
meta
中封装操作成功或失败的消息, data
中封装返回的具体数据.
当新建商品或更新产品时, 相关属性封装在JSON中, 通过POST或PUT发送,
1 | { |
当用户对商品进行操作后, 将得到响应结果,
GET, POST, PUT操作成功, 返回如下结果
1 | { |
DELETE操作成功, 返回如下结果
1 | { |
电商网站的管理员对商品进行新增,编辑,删除,浏览的操作; 暂时不考虑认证授权, 只关注对商品的操作.
为了以后便于做分布式, 所有资源id(表主键)均采用uuid.
1, url: /api/product
2, method: POST
3, requestParams:
1 | { |
4, responseBody
1 | { |
1, url: /api/products/{id}
2, method: PUT
3, requestParams:
1 | { |
4, responseBody
1 | { |
1, url: /api/products/{id}
2, method: DELETE
3, responseBody
1 | { |
1, url: /api/products/{id}
2, method: GET
3, responseBody
删除前
1 | { |
删除后
1 | { |
1, url: /api/products
2, method: GET
3, responseBody
1 | { |
业务场景一
中只涉及了单个资源的操作, 但实际场景中还有些关联操作; 如用户去电商网站浏览商品, 并收藏了一些商品, 之后又取消收藏了部分商品.
暂时不考虑用户认证授权, 以后加了token
后, 用户信息可以从中获取.
1, url: /api/products/{id}/star
2, method: PUT
3, responseBody
1 | { |
1, url: /api/products/{id}/star
2, method: DELETE
3, responseBody
1 | { |
所有自定义异常继承RuntimeException, 在业务层抛出, 统一在Controller层进行处理.
异常分为全局异常和局部异常, 例如http method unsupported(405), unauthorized(401), accessDenied(403), not found(404)等属于全局异常; 针对对独立业务的一些异常属于局部异常, 例如产品编辑出错;
异常在Controller中进行处理, 并封装成json返回给前端, 封装后的数据如下, 相关实现见源码;
1 | { |
1 | { |
使用Spring的AntPathMatch对RESTful的URL进行匹配.
ANT方式的通配符有三种:
? 匹配任何单字符
* 匹配0或者任意数量的字符
** 匹配0或者更多的目录
注: 所有的通配符均不包含路径分隔符/
.
1 | import org.junit.Test; |
后端负责业务逻辑处理, 前端负责展示逻辑的处理.
2013年3月,首次接触了underscore.js这个区区45k大小的js库, 在使用其提供的简单模板完成表格异步分页后,从此我开始慢慢践行前后端完全分离的架构.
2013年7月,我接触到了REST这种重用HTTP应用协议的架构, 更坚定了我践行前后端分离的决心.
2014年3月,国内刮起了nodeJS的风暴, 此时我采用国外的一个开源项目MEAN开发了一套完整的CMS系统, 此次开发学习到了API接口如何标准化, API具体的设计参考了github API, Instagram API.
2015年1月, 公司开发新产品, 需要新开发一套支持平台及数据下发平台, 在该产品中我主要参与API的设计, 并主导开发支撑平台, 下面我就介绍下这套支撑平台.
工欲善其事, 必先利其器, 先介绍下自己使用的工具
强大的Java集成开发工具, 没有之一.
强大的HTML,CSS,Javascript集成开发工具, 没有之一.
强大的文本编辑工具, 目前我主要用于临时文件查看, 临时json数据格式化查看, 集成了plugin后, 它的功能会吓到人.
小巧强大的关系数据库建模工具, 相当易用.
强大的版本控制客户端.
一款简洁强大的MarkDown可视化编辑工具.
一款强大的Linux SSH客户端工具.
一款强大的Linux SFTP客户端可视化工具.
强大的VPN, 连接稳定, 速度快, 价格实惠, 可做全局代理, 包括terminal.
一个用于创建web交互界面的库, 主要用于数据双向绑定.
一个非常方便的客户端请求代理模块,可方便的使用get,post,put,delete,head动词, 异步回调中封装了http状态码和业务数据等等.
一个强大的文件上传库, 可获取文件mime, 文件大小等; 针对图片可生成缩略图, 获取图片宽度,高度.
一个强大的表格组件.
一个强大的前端路由库, 通过#
符号进行路径组织, 结合vue
的component
可进行单页的局部模块刷新.
spring 超文本驱动库, 可根据需求返回不同的httpStatus及links.
对架构进行分层, 目前分三层
一个半自动ORM(对象关系映射库)库, 简单灵活, 使用原生sql, 可完成较复杂的查询.
mybatis-generator-maven-plugin
mybatis插件, 可根据数据库中的表生成JavaBean,Dao接口,Dao的xml文件, 本人修改了部分源码, 可在JavaBean中自动添加注释, 详见另一篇博文MyBatis-Generator最佳实践.
完成用户的认证和授权.
thumbnailator(注: 需翻墙)
一个Java端的图片处理库, 可完成图片压缩, 裁剪, 水印等功能.
如何提高使用者的工作效率?
如何提高开发人员的开发效率?
目前, 后端中将数据访问层和业务逻辑层中常用的方法封装成了泛型接口, 并使用抽象类来实现最基础的逻辑, 开发人员如果觉得指定方法无法满足需求, 可重写指定方法或使用新方法. 具体封装如下:
1 |
|
1 | /** |
1 | /** |
熟练使用该模式, 开发一套业务逻辑的增删改查, 那速度是相当快.
针对前段也做了一些组件式的封装, 表格采用backgrid.js
完成展示及分页操作, 只需要在html文件指定位置写入两个带id的html元素(一个用于展示表格, 一个用于分页), 之后copy写好的js模板, 在js中修改属性便可快速完成表格异步分页.
接下来将陆续发布实战干货.
1 | <html> |
IE5.5冒泡顺序如下:
(1)<div>
(2)<body>
(3)<document>
为什么称作冒泡、因为事件按照DOM的层次结构像水泡一样不断上升。让我想起了一首歌:”吹泡泡、吹泡泡、泡泡飞啊飞得高、飞到天空中、问声太阳好”。
IE6呢、稍微修改了冒泡型事件、这样<html>元素也可以接收冒泡的事件、还是上面的代码。
IE6的冒泡顺序如下:
(1)<div>
(2)<body>
(3)<html>
(4)<document>
Mozilla1.0及更高版本也支持冒泡事件但到达了另一层次。类似IE6.0,它也支持<html>元素级别的事件、不过,事件”起泡”一直上升到Windows窗口对象。继续前面的代码、点击<div>元素将造成下图所示的事件冒泡:
捕获型事件:
IE使用冒泡型事件、相对的、Netscape使用了另一种称为捕获型事件(eventcapturing)的解决方案、事件的捕获和冒泡刚好相反的两种 过程——捕获型事件中、事件从最不精确的对象(document对象)开始触发、然后到最精确(也可以在窗口级别捕获事件,不过必寻由开发人员特别指 定)。Netscape不会将页面上的很多元素暴露给事件。继续使用前面的代码示例、事件按照下面的路径传播:
(1)document
(2)<div>
有些人也称之为自顶向下的事件模型,因为它是从DOM层次的顶端开始向下延伸的:
DOM事件流:
DOM(文档对象模型)结构是一个树型结构,当一个HTML元素产生一个事件时,该事件会在元素结点与根节点之间按特定的顺序传播,路径所经过的节点都会收到该事件,这个传播过程可称为DOM事件流。事件顺序有两种类型:事件捕捉和事件冒泡。
DOM标准的事件模型
我们已经对上面两个不同的事件模型进行了解释和对比。DOM标准同时支持两种事件模型,即捕获型事件与冒泡型事件,但是,捕获型事件先发生。两种事件流都 会触发DOM中的所有对象,从document对象开始,也在document对象结束(大部分兼容标准的浏览器会继续将事件是捕捉/冒泡延续到 window对象)。继续使用前面的例子、在与DOM兼容的浏览器中点击<div>元素时、事件流的进行如下图:
注意因为事件的目标(<div>元素)是最精确的元素(于是,在DOM树中最深),实际上它会接收两次事件,一次在捕获过程中,另一次在冒泡 过程中。DOM事件模型的最独特的性质是,文本节点也触发事件(在IE不会)。所以如果点击示例中的<div>点 击</div>、实际的事件流应该是:
参考资料
]]>以前我们嚷着不想上课,以后,我们再也不用上课了……
该文章属于转载,原文(致我们即将逝去的高中–终生难忘的回忆!)
]]>创建新文件夹,打开,然后执行如下命令创建新的git仓库。
1 | git init |
执行如下命令以创建一个本地仓库的克隆
1 | git clone /path/to/repository |
如果是远端服务器上的仓库,你的命令会是这个
1 | git clone username@host:/path/to/repository |
你的本地仓库由 git 维护的三棵“树”组成。第一个是你的 工作目录
,它持有实际文件;第二个是 暂存区(Index)
,它像个缓存区域,临时保存你的改动;最后是 HEAD
,它指向你最后一次提交的结果.
你可以提出更改(把它们添加到暂存区),使用如下
1 | git add <filename> |
这是 git 基本工作流程的第一步;使用如下命令以实际提交改动:
1 | git commit -m "代码提交信息" |
现在,你的改动已经提交到了 HEAD,但是还没到你的远端仓库。
你的改动现在已经在本地仓库的 HEAD 中了。执行如下命令以将这些改动提交到远端
1 | git push origin master |
可以把 master 换成你想要推送的任何分支。
如果你还没有克隆现有仓库,并欲将你的仓库连接到某个远程服务器,你可以使用如下命令添加:
1 | git remote add origin <server> |
如此你就能够将你的改动推送到所添加的服务器上去了。
分支是用来将特性开发绝缘开来的。在你创建仓库的时候,master 是“默认的”分支。在其他分支上进行开发,完成后再将它们合并到主分支上。
创建一个叫做“feature_x”的分支,并切换过去:
1 | git checkout -b feature_x |
切换回主分支:
1 | git checkout master |
再把新建的分支删掉:
1 | git branch -d feature_x |
除非你将分支推送到远端仓库,不然该分支就是 不为他人所见的:
git push origin <branch>
要更新你的本地仓库至最新改动,执行:
1 | git pull |
可在你的工作目录中 获取(fetch) 并 合并(merge) 远端的改动。
要合并其他分支到你的当前分支(例如 master),执行:
1 | git merge <branch> |
在这两种情况下,git 都会尝试去自动合并改动。遗憾的是,这可能并非每次都成功,并可能出现冲突(conflicts)。这时候就需要你修改这些文件来手动合并这些冲突(conflicts)。改完之后,你需要执行如下命令以将它们标记为合并成功:
1 | git add <filename> |
在合并改动之前,你可以使用如下命令预览差异:
1 | git diff <source_branch> <target_branch> |
为软件发布创建标签是推荐的。这个概念早已存在,在 SVN 中也有。你可以执行如下命令创建一个叫做1.0.0 的标签:
1 | git tag 1.0.0 1b2e1d63ff |
1b2e1d63ff 是你想要标记的提交 ID 的前 10 位字符。可以使用下列命令获取提交 ID:
1 | git log |
你也可以使用少一点的提交 ID 前几位,只要它的指向具有唯一性。
假如你操作失误(当然,这最好永远不要发生),你可以使用如下命令替换掉本地改动:
1 | git checkout -- <filename> |
此命令会使用 HEAD 中的最新内容替换掉你的工作目录中的文件。已添加到暂存区的改动以及新文件都不会受到影响。
假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它:
1 | git fetch origin |
内建的图形化 git:
1 | gitk |
彩色的 git 输出:
1 | git config color.ui true |
显示历史记录时,每个提交的信息只显示一行:
1 | git config format.pretty oneline |
交互式添加文件到暂存区:
1 | git add -i |
.ssh
目录下配置了config
文件, 在终端中使用命令无任何问题, 使用IDEA进行push/pull
报错误Could not read from remote repository.
Preferences
→ Version Control
→ Git
, SSH executable选择Native
之前一直使用Apache做静态资源私服及反向代理, 配置稍显复杂, 今天使用nginx来替换Apache, 在此小记一下探索的过程及碰到的问题.
本人使用Mac系统, 偷懒的使用了homebrew, 一键完成了安装. 对于其它OS
使用解压zip
的方式.
下面从安装–>配置(静态资源私服 php 反向代理)–>问题展开吧
1 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" |
安装
1 | brew install nginx |
启动
1 | sudo nginx |
停止
1 | sudo nginx -s stop |
nginx安装文件目录
1 | /usr/local/Cellar/nginx |
nginx配置文件目录
1 | /usr/local/etc/nginx |
系统hosts位置
1 | /private/etc/hosts |
使用多个配置来管理nginx下的多个websites
1 | cd /usr/local/etc/nginx |
在nginx.conf中进行修改, 大致配置可如下, 详细配置见官方文档
1 | user your_username staff; |
注意修改该配置中的your_username
为你的Mac用户名.
到此父级配置已配置完成, 接下来配置用户希望的功能, 例如静态资源私服或反向代理.
配置自己的静态资源服务器(username.conf), 如下
1 | server { |
配置反向代理, 如下
1 | server { |
安装php-fpm
Mac OSX 10.9的系统自带了PHP、php-fpm,省去了安装php-fpm的麻烦。 这里需要简单地修改下php-fpm的配置,否则运行php-fpm会报错。
1 | sudo cp /private/etc/php-fpm.conf.default /private/etc/php-fpm.conf |
修改php-fpm.conf文件中的error_log项,默认该项被注释掉,这里需要去注释并且修改为error_log = /usr/local/var/log/php-fpm.log。如果不修改该值,运行php-fpm的时候会提示log文件输出路径不存在的错误。
1 | vim /private/etc/hosts |
在末尾加入如下代码, 该域名是nginx
中server
指定的server_name
1 | # 针对静态资源私服 |
配置完毕, 在浏览器中键入http://resource.com/
可以对指定目录的文件进行浏览,下载; 键入http://app.com/resource-app
或http://app.com/actor-app
可分别进入不同的应用.
在root
根目录中创建文件index.php, 内容如下,
1 | <?php phpinfo(); ?> |
启动nginx
1 | sudo nginx |
重启nginx
1 | sudo nginx -s reload |
停止nginx
1 | sudo nginx -s stop |
启动php
1 | sudo php-fpm |
在浏览器中输入http://resource.com/index.php
即可看到php的详细信息.
1.键入http://resource.com/
, 服务器返回403
界面
该问题是因为访问权限不够造成, 在nginx.conf
首行配置即可,
1 | user your_username staff; |
2.目录未显示, 只显示index.html页面
该问题是指定站点(website)未启用目录浏览功能, 在指定server
的location
节点启用目录浏览功能,如下,
1 | autoindex on; |
此文为实践总结,是自己在实践过程中积累的经验和”哲学”。部分内容参考相关资料,参考内容请看尾页。建议对RESTful有一定了解者阅读!
示例: 指向ID为yanbo.ai
的Account
对象
1 | GET http://~/$version/accounts/yanbo.ai |
示例: 隐式地指向trades list 集合
1 | GET http://~/$version/trades/(list) |
示例: Profile
是User
的聚合资源,User
有一个唯一且私有的Profile
资源,只能通过User
操作Profile
。
1 | 更新user_id为123456的Profile资源 |
示例: 一个系统里面包含多个 applications,一个 application 又包含多个 users。那获取 user 资源的路径应该是怎样的?
看一个路径嵌套的例子:
1 | GET http://~/$version/systems/:systemId/applications/:applicationId/users/:userId |
这样做是不合理的,它会让你的接口变得越来越混乱和缺少灵活性。正确的做法是:
1 | GET http://~/$version/systems/:systemId |
HTTP Operation | Description |
---|---|
GET | 获取,查找 |
POST | 新增创建 |
PUT | 更新 |
PATCH | 部分更新 |
DELETE | 删除 |
1 | GET https://github.com/v1/trades |
为什么需要版本?
当服务被更多其他系统使用的时候,服务的可用性和上下兼容变得至关重要。被外部系统依赖的服务在升级时是一个非常麻烦的事情,既要发布新的接口,又要保留旧的接口留出时间让调用者去升级。在URL中加入Version
标示能很好地解决上下兼容(新老版本共存)问题。
示例1: URL中新增了Path parameter
v1版本
1 | GET http://~/v1/trades?user_id=123456 |
v2版本
1 | GET http://~/v2/:user_id/trades |
示例1中的user_id
参数在v2版本被加入到path parameter中,使用$version
保证了v1
和v2
接口的共存。
示例2: 数据接口发生变化
v1版本
1 | GET http://~/v1/accounts/yanbo.ai |
v2版本
1 | GET http://~/v2/accounts/yanbo.ai |
示例2中的接口返回数据结构已经发生了变化。使用$version
保证了v1
和v2
接口的共存。
-
代替下划线_
1 | GET http://~/$version/trades 获取trades列表 |
使用services
标识,根据服务的属性选择http方法。
1 | http://~/services/$version/server-name |
使用settings
标识,根据服务的属性选择http方法。
1 | http://~/settings/$version/server-name |
示例1: 搜索
1 | GET http://~/services/$version/search?q=filter?category=file |
示例2: 任务队列操作
1 | PUT http://~/services/$version/queued/jobs 往任务队列里面添加一个新的任务 |
示例3: 更改界面语言环境
1 | PUT http://~/settings/$version/gui/lang |
为什么需要区分?
Microservices
是一个全新的概念,它主要的观点是将一个大型的服务系统分解成多个微型系统。每个微型系统都能独立工作,并且提供各种不同的服务。独立运行的特点使微型系统之间不会产生相互影响,其中的一个微型系统宕机并不会牵连到其他的微型系统。这种架构使分布式系统的节点数量大大提升。因为RESTful服务是无状态的,所以这种分解并不会带来状态共享的问题。
2.路由规则(逻辑)
当我们需要对不同属性的接口做路由规则的时候,按功能划分接口是一个很好的方案。例如:我们要对系统设置接口设置增加更严格的调用限制。
网络接口相对于堆栈接口来说数据传输极其不稳定,尽可能地减少数据传输不仅能控制这种风险还能减少流量。使用缓存还能有效地提高后台的吞吐量。
后台在响应请求时使用响应头E-Tag
或Last-Modified
来标记数据的版本,前台在发送请求时将数据版本通过请求头If-Match
帮助后台判断缓存的使用。
Request Header
1 | If-Match: 2390239059405940 |
Response Header
1 | E-Tag: 2390239059405940 |
在实际的环境中,有大量的查询需求是相同的。将这些搜索需求标签化能降低使用难度也可以达到重用的目的。
示例1: 查找状态为关闭的订单
普通方式
1 | GET http://~/$version/trades?status=closed&sorting=-created_at |
Bookmarker
1 | GET http://~/$version/trades#recently_closed |
或
1 | GET http://~/$version/trades/recently_closed |
HATEOAS通过Web Linking的方式来描述程序的状态信息
Link 主要包含以下属性:
Property | Description |
---|---|
rel | 关联内容 |
href | URL |
type | 媒体类型 |
method | Http Method |
title | 标题 |
arguments | 参数列表 |
value | 返回值 |
Rel
可能为以下值:
Value | Description |
---|---|
next | 下一步 |
prev | 上一步 |
first | 第一步,最前 |
last | 最后一步,最后 |
source | 来源 |
self | 资源自身,相对于this |
Web Linking 可以通过两种方式传递至客户端:
Http Header
1 | Link: <http://~/$version/trades?page_no=10>; rel="next", <http://~/$version/trades?page_no=19>; rel="last" |
Http JSON Body
1 | { |
示例1: 用户注册业务
Register Request
1 | POST http://~/$version/accounts |
Register Response
1 | Headers: |
Profile Request
1 | POST http://~/$version/accounts/yanbo.ai/profiles |
Profile Response
1 | Headers: |
示例2: 请看下节<分页>
HATEOAS在解决什么问题?
HATEOAS是Hypermedia as the Engine of Application State的缩写形式,中文意思为:超媒体应用状态引擎。它的核心思想是使用超媒体表达应用状态,与hypertext-driven思想是一致的。在此之前,我们大多数的程序业务控制在前台完成。例如:我们会在前台做注册流程,我们在前台判定下一步应该做什么,可以做什么。当使用HATEOAS时,这些状态流程控制都在应用程序的后台完成。我们使用超媒体来表达前台做完某一步骤之后可以做哪些? 这样一来,前台的任务就变得相当简单了,前台需要处理的是理解状态表述,数据收集和结果显示。
思考
HATEOAS会带来怎样的改变? 使用它的意义在哪?
Request
1 | GET http://~/$version/trades?page=10&pre_page=100 |
Response
Link Header
1 | Link: <http://~/$version/trades?page=11&pre_page=100>; rel="next", <http://~/$version/trades?page=19&pre_page=100>; rel="last" |
JSON Body
1 | { |
为保证服务的可用性应对服务进行调用过载保护
Response Headers
1 | X-RateLimit-Limit: 3000 调用量的最大限制 |
RESTful服务使用Oauth2的方式进行调用授权,使用http请求头Authorization
设置授权码; 必须使用User-Agent
设置客户端信息, 无User-Agent
请求头的请求应该被拒绝访问。
Request Header
1 | User-Agent: Data-Server-Client |
为什么建议使用Oauth2授权?
Oauth2的参与者为:客户端,资源所有者,授权服务器,资源服务器。客户端先从资源所有者得到授权码之后使用授权码从授权服务器得到token
,再使用token
调用资源服务器获取经过资源所有者授权使用的资源。这种授权方式的特点有:
资源所有者可以随时撤销授权许可
可以通过撤销token
拒绝客户端的调用
资源服务器可以拒绝客户端的调用
通过这三种方式可以做到对资源的严格保护。资源的访问权限也把握在资源所有者的手中,而不是资源服务器。
当然,Oauth2授权框架也允许受信任的客户端直接使用token
调用资源服务器获取资源。这种灵活性完全取决于客户端类型和对资源的保护程度。
为什么授权码要放在Http Header中?
token
(授权码)包含在URL中就有很大的安全风险。token
串可能被进行重定向传递。通过这两种方式入侵者可以不通过授权而使用泄漏的授权码访问那些受保护的数据,会造成数据泄漏的风险。以Tomcat为例,访问日志为:
1 | 127.0.0.1 - - [24/Jun/2014:14:38:04 +0800] "GET /v1/accounts/yanbo.ai?token=dgdreLJLJLER798989erJKJK HTTPS/1.1" 200 343 |
通过对访问日志的提取,很容易得到token
信息。
X-Result-Fields
。2014-04-05T14:30Z
。使用JSON格式传输数据,在http请求头和响应头申明Content-Type
。返回的数据结构应该做到尽可能简单,不要过于包装。响应状态应该包含在响应头中!
Request
1 | Accept: application/json |
Response
1 | Content-Type: application/json;charset=UTF-8 |
错误的做法
1 | { |
正确的做法
1 | Response Headers: |
示例1: 创建User
对象
1 | POST http://~/$version/users |
为什么是JSON?
JSON
是一种可以跨平台高扩展的轻量级的数据交换格式。易于人阅读和编写,同时也易于机器解析和生成。
无状态服务器应该允许客户端对数据按需提取。在请求头使用X-Result-Fields
指定数据返回的字段集合。
例如:trade 有trade_id
, trade_name
, created_at
三个属性,客户端只需其中的trade_id
与trade_name
属性。
Request Header
1 | X-Result-Fields: trade_id,trade_name |
数据里面的子对象使用URI描述不应该被提取,除非用户指定需要提取子对象
示例: trade
里面的order
对象
错误的做法
1 | { |
正确的做法
1 | { |
应用指定提取子对象,需要在请求头声明X-Expansion-Fields
Request
1 | X-Expansion-Fields: true |
为什么要客户端指定提取子对象时才提取?
懒模式服务能够最大程度地节省运算资源。虽然与客户端交互的次数有所增加,但是能做到按需提取,按需响应,这也是响应式设计的一大特点。客户端的用户行为模式无法真实地模拟,也就无法确定哪些资源需要做到一次性推送,让客户端按需使用是一个不错的方式。
关于空字段
应该在返回结果里面剔除空字段,因为null值传输到客户端并没有实际的含义,反而增加了占用空间。
Tips
使用HTTP Header时,优先使用合适的标准头属性。用X-
作为前缀自定义一个头属性,例如: X-Result-Fields
。
Code | HTTP Operation Body | Contents | Description |
---|---|---|---|
200 | GET,PUT | 资源 | 操作成功 |
201 | POST | 资源,元数据 | 对象创建成功 |
202 | POST,PUT,DELETE,PATCH | N/A | 请求已经被接受 |
204 | DELETE,PUT,PATCH | N/A | 操作已经执行成功,但是没有返回数据 |
301 | GET | link | 资源已被移除 |
303 | GET | link | 重定向 |
304 | GET | N/A | 资源没有被修改 |
400 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 参数列表错误(缺少,格式不匹配) |
401 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 未授权 |
403 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 访问受限,授权过期 |
404 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 资源,服务未找到 |
405 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 不允许的http方法 |
409 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 资源冲突,或者资源被锁定 |
415 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 不支持的数据(媒体)类型 |
429 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 请求过多被限制 |
500 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 系统内部错误 |
501 | GET,PSOT,PUT,DELETE,PATCH | 错误提示(消息) | 接口未实现 |
容器状态码是指http容器的状态码,应用不应该使用或限制使用
Code | HTTP Operation | Body Contents | Description |
---|---|---|---|
303 | GET | link | 静态资源被移除,应用限制使用 |
503 | GET,PSOT,PUT,DELETE,PATCH | text body | 服务器宕机 |
Tips
4开头的错误用来表达来自于客户端的错误,例如: 未授权,参数缺失。5开头的错误用来表达服务端的错误,例如: 在连接外部系统(DB)发生的IO错误。
错误信息应该包含下列内容:
message
, 必须 error code
, 必须 error message
, 必须 resource
, 可选 field
, 可选 document
, 可选Tips
Error Code
尽可能做到简洁明了,提取异常的关键字并且使用下划线_把它们连接起来。
示例: 调用频率超过限制,Response:
1 | Headers: |
Server
头X-Powered-By
Response Headers
1 | X-Pretty-Print: true |
本文属于转载:原文(RESTful Best Practices)
]]>在移动互联网、云计算迅猛发展的今天,作为一名Web开发者,如果您还没听说过“REST”这个buzzword,显然已经落伍了。夸张点说,甚至“出了门都不好意思跟别人打招呼”。尽管如此,对于REST这个泊来品的理解,大多数人(包括一些资深的架构师)仍然停留在“盲人摸象”的阶段。常常听到各种各样关于REST的说法,例如:有人说:“我们这套新的API决定不用Web Service(SOAP+WSDL),而是直接使用HTTP+JSON,也就是用RESTful的方式来开发。” 不用SOAP,甚至也不用XML,就自动变成了RESTful了。还有人认为:REST与传统的 Service其实没有本质区别,只是对于URI的构造方式提出了更多要求,而这些要求Web Service完全都可以实现。潜台词是:既生瑜,何生亮。Web Service已经足够好了,干嘛还要再折腾什么REST。这些对于REST的不同说法,果真如此吗?REST究竟是什么?是一种新的技术、一种新的架构、还是一种新的规范?
对于这些问题笔者先不解答,为了深入理解REST是什么,我们需要回顾一下Web发展的最初年代,从源头上讲讲REST是怎么得来的。
Web(万维网World Wide Web的简称)是个包罗万象的万花筒,不同的人从不同的角度观察,对于Web究竟是什么会得出大不相同的观点。作为Web开发者,我们需要从技术上来理解Web。从技术架构层面上看,Web的技术架构包括了四个基石:
这四个基石相互支撑,促使Web这座宏伟的大厦以几何级数的速度发展了起来。在这四个基石之上,Web开发技术的发展可以粗略划分成以下几个阶段:
从上述Web开发技术的发展过程看,Web从最初其设计者所构思的主要支持静态文档的阶段,逐渐变得越来越动态化。Web应用的交互模式,变得越来越复杂:从静态文档发展到以内容为主的门户网站、电子商务网站、搜索引擎、社交网站,再到以娱乐为主的大型多人在线游戏、手机游戏。
在互联网行业,实践总是走在理论的前面。Web发展到了1995年,在CGI、ASP等技术出现之后,沿用了多年、主要面向静态文档的HTTP/1.0协议已经无法满足Web应用的开发需求,因此需要设计新版本的HTTP协议。在HTTP/1.0协议专家组之中,有一位年轻人脱颖而出,显示出了不凡的洞察力,后来他成为了HTTP/1.1协议专家组的负责人。这位年轻人就是Apache HTTP服务器的核心开发者Roy Fielding,他还是Apache软件基金会的合作创始人。
Roy Fielding和他的同事们在HTTP/1.1协议的设计工作中,对于Web之所以取得巨大成功,在技术架构方面的因素做了一番深入的总结。Fielding将这些总结纳入到了一套理论框架之中,然后使用这套理论框架中的指导原则,来指导HTTP/1.1协议的设计方向。HTTP/1.1协议的第一个草稿是在1996年1月发布的,经过了三年多时间的修订,于1999年6月成为了IETF的正式规范(包括了RFC 2616以及用于对客户端做身份认证的RFC 2617)。HTTP/1.1协议设计的极为成功,以至于发布之后整整10年时间里,都没有多少人认为有修订的必要。用来指导HTTP/1.1协议设计的这套理论框架,最初是以备忘录的形式在专家组成员之间交流,除了IETF/W3C的专家圈子,并没有在外界广泛流传。Fielding在完成HTTP/1.1协议的设计工作之后,回到了加州大学欧文分校继续攻读自己的博士学位。第二年(2000年)在他的博士学位论文Architectural Styles and the Design of Network-based Software Architectures中,Fielding更为系统、严谨地阐述了这套理论框架,并且使用这套理论框架推导出了一种新的架构风格,并且为这种架构风格取了一个令人轻松愉快的名字“REST”——Representational State Transfer(表述性状态转移)的缩写。
在笔者看来,Fielding这篇博士论文在Web发展史上的价值,不亚于Web之父Tim Berners-Lee关于超文本的那篇经典论文。然而遗憾的是,这篇博士论文在诞生之后的将近5年时间里,一直没有得到足够的重视。例如Web Service相关规范SOAP/WSDL的设计者们,显然不大理解REST是什么,HTTP/1.1究竟是一个什么样的协议、为何要设计成这个样子。
这种情况在2005年之后有了很大的改善,随着Ajax、Ruby on Rails等新的Web开发技术的兴起,在Web开发技术社区掀起了一场重归Web架构设计本源的运动,REST架构风格得到了越来越多的关注。在2007年1月,支持REST开发的Ruby on Rails 1.2版正式发布,并且将支持REST开发作为Rails未来发展中的优先内容。Ruby on Rails的创始人DHH做了一个名为“World of Resources”的精彩演讲,DHH在Web开发技术社区中的强大影响力,使得REST一下子处在Web开发技术舞台的聚光灯之下。
今天,各种流行的Web开发框架,几乎没有不支持REST开发的了。大多数Web开发者都是通过阅读某种REST开发框架的文档,以及通过一些例子代码来学习REST开发的。然而,通过例子代码来学习REST有非常大的局限性。因为REST并不是一种具体的技术,也不是一种具体的规范,REST其实是一种内涵非常丰富的架构风格。通过例子代码来学习REST,除了学习到一种有趣的Web开发技术之外,并不能全面深入的理解REST究竟是什么。甚至还会误以为这些简单的例子代码就是REST本身,REST不过是一种简单的Web开发技术而已。就像盲人摸象一样,有的人摸到了象鼻子、有的人摸到了象耳朵、有的人摸到了象腿、有的人摸到了象尾巴。他们都坚信自己感觉到的大象,才是最真实的大象,而其他人的感觉都是错误的。
对于不理解REST的Web开发者,人们习惯于展示一些例子代码来让他们理解REST,笔者不赞同上述做法。如果Web开发者想要深入理解REST是什么,就很难避开Fielding的这篇博士论文。笔者在本文中对于REST是什么的介绍,也是基于Fielding的博士论文的。尽管如此,笔者强烈建议本文的读者亲自去通读一下Fielding的博士论文,就像想要了解孔子的思想应该直接去读《论语》等著作,而不是首先去读其他人的转述一样。笔者在本文中也仅仅是努力不做一个把经书念错了的歪嘴和尚而已。那么,下面我们言归正传。
在Fielding的这篇名为Architectural Styles and the Design of Network-based Software Architectures的博士论文(中文版名为《架构风格与基于网络的软件架构设计》)中,提出了一整套基于网络的软件(即所谓的“分布式应用”)的设计方法,值得所有分布式应用的开发者仔细阅读、深入体会。
在论文的前三章中,Fielding在批判性继承前人研究成果的基础上,建立起来一整套研究和评价软件架构的方法论。这套方法论的核心是“架构风格”这个概念。架构风格是一种研究和评价软件架构设计的方法,它是比架构更加抽象的概念。一种架构风格是由一组相互协作的架构约束来定义的。架构约束是指软件的运行环境施加在架构设计之上的约束。
在论文的第四章中,Fielding研究了Web这样一个分布式系统对于软件架构设计提出了哪些需求。在第五章中,Fielding将第四章Web提出的需求具体化为一些架构约束,通过逐步添加各种架构约束,推导出来了REST这种新的架构风格。
REST架构风格的推导过程如下图所示:
图1:REST所继承的架构风格约束
在图1中,每一个椭圆形里面的缩写词代表了一种架构风格,而每一个箭头边的单词代表了一种架构约束。
REST架构风格最重要的架构约束有6个:
通信只能由客户端单方面发起,表现为请求-响应的形式。
通信的会话状态(Session State)应该全部由客户端负责维护。
响应内容可以在通信链的某处被缓存,以改善网络效率。
通信链的组件之间通过统一的接口相互通信,以提高交互的可见性。
通过限制组件的行为(即,每个组件只能“看到”与其交互的紧邻层),将架构分解为若干等级的层。
支持通过下载并执行一些代码(例如Java Applet、Flash或JavaScript),对客户端的功能进行扩展。
在论文中推导出的REST架构风格如下图所示:
图2:REST架构风格
而HTTP/1.1协议作为一种REST架构风格的架构实例,其架构如下图所示:
图3:一个基于REST的架构的过程视图
用户代理处在三个并行交互(a、b和c)的中间。用户代理的客户端连接器缓存无法满足请求,因此它根据每个资源标识符的属性和客户端连接器的配置,将每个请求路由到资源的来源。请求(a)被发送到一个本地代理,代理随后访问一个通过DNS查找发现的缓存网关,该网关将这个请求转发到一个能够满足该请求的来源服务器,服务器的内部资源由一个封装过的对象请求代理(object request broker)架构来定义。请求(b)直接发送到一个来源服务器,它能够通过自己的缓存来满足这个请求。请求(c)被发送到一个代理,它能够直接访问WAIS(一种与Web架构分离的信息服务),并将WAIS的响应翻译为一种通用的连接器接口能够识别的格式。每一个组件只知道与它们自己的客户端或服务器连接器的交互;整个过程拓扑是我们的视图的产物。
通过比较图2和图3,读者不难发现这两张图中的架构是高度一致的。对于HTTP/1.1协议为何要设计成这个样子,读者想必已经有所领悟。
在论文的第六章中,Fielding对于到2000年为止在Web基础架构协议的设计和开发方面的一些经验教训进行了深入的分析。其中,“HTTP不是RPC”、“HTTP不是一种传输协议”两部分值得读者反复阅读。时至13年之后的今日,对于HTTP协议的误解仍然广泛存在。
以上简要介绍了Fielding博士论文中的内容。为了帮助读者仔细阅读Fielding的博士论文,笔者整理了一套Fielding博士论文的导读,将在本专栏后续文章中载出。
REST究竟是什么?因为REST的内涵非常丰富,所以很难用一两句话解释清楚这个问题。
首先,REST是Web自身的架构风格。REST也是Web之所以取得成功的技术架构方面因素的总结。REST是世界上最成功的分布式应用架构风格(成功案例:Web,还不够吗?)。它是为 运行在互联网环境 的 分布式 超媒体系统量身定制的。互联网环境与企业内网环境有非常大的差别,最主要的差别是两个方面:
可伸缩性需求无法控制:并发访问量可能会暴涨,也可能会暴跌。
安全性需求无法控制:无法控制客户端发来的请求的格式,很可能会是恶意的请求。
而所谓的“超媒体系统”,即,使用了超文本的系统。可以把“超媒体”理解为超文本+媒体内容。
REST是HTTP/1.1协议等Web规范的设计指导原则,HTTP/1.1协议正是为实现REST风格的架构而设计的。新的Web规范,其设计必须符合REST的要求,否则整个Web的体系架构会因为引入严重矛盾而崩溃。这句话不是危言耸听,做个类比,假如苏州市政府同意在市区著名园林的附近大型土木,建造大量具有后现代风格的摩天大楼,那么不久之后世界闻名的苏州园林美景将不复存在。
上述这些关于“REST是什么”的描述,可以总结为一句话:REST是所有Web应用都应该遵守的架构设计指导原则。当然,REST并不是法律,违反了REST的指导原则,仍然能够实现应用的功能。但是违反了REST的指导原则,会付出很多代价,特别是对于大流量的网站而言。
要深入理解REST,需要理解REST的五个关键词:
什么是资源?
资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI来标识。URI既是资源的名称,也是资源在Web上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互。
什么是资源的表述?
资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)。资源的表述可以有多种格式,例如HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式。
什么是状态转移?
状态转移(state transfer)与状态机中的状态迁移(state transition)的含义是不同的。状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。
什么是统一接口?
REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。以HTTP/1.1协议为例,HTTP/1.1协议定义了一个操作资源的统一接口,主要包括以下内容:
7个HTTP方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS
HTTP头信息(可自定义)
HTTP响应状态代码(可自定义)
一套标准的内容协商机制
一套标准的缓存机制
一套标准的客户端身份认证机制
REST还要求,对于资源执行的操作,其操作语义必须由HTTP消息体之前的部分完全表达,不能将操作语义封装在HTTP消息体内部。这样做是为了提高交互的可见性,以便于通信链的中间组件实现缓存、安全审计等等功能。
什么是超文本驱动?
“超文本驱动”又名“将超媒体作为应用状态的引擎”(Hypermedia As The Engine Of Application State,来自Fielding博士论文中的一句话,缩写为HATEOAS)。将Web应用看作是一个由很多状态(应用状态)组成的有限状态机。资源之间通过超链接相互关联,超链接既代表资源之间的关系,也代表可执行的状态迁移。在超媒体之中不仅仅包含数据,还包含了状态迁移的语义。以超媒体作为引擎,驱动Web应用的状态迁移。通过超媒体暴露出服务器所提供的资源,服务器提供了哪些资源是在运行时通过解析超媒体发现的,而不是事先定义的。从面向服务的角度看,超媒体定义了服务器所提供服务的协议。客户端应该依赖的是超媒体的状态迁移语义,而不应该对于是否存在某个URI或URI的某种特殊构造方式作出假设。一切都有可能变化,只有超媒体的状态迁移语义能够长期保持稳定。
一旦读者理解了上述REST的五个关键词,就很容易理解REST风格的架构所具有的6个的主要特征:
面向资源(Resource Oriented)
可寻址(Addressability)
连通性(Connectedness)
无状态(Statelessness)
统一接口(Uniform Interface)
超文本驱动(Hypertext Driven)
这6个特征是REST架构设计优秀程度的判断标准。其中,面向资源是REST最明显的特征,即,REST架构设计是以资源抽象为核心展开的。可寻址说的是:每一个资源在Web之上都有自己的地址。连通性说的是:应该尽量避免设计孤立的资源,除了设计资源本身,还需要设计资源之间的关联关系,并且通过超链接将资源关联起来。无状态、统一接口是REST的两种架构约束,超文本驱动是REST的一个关键词,在前面都已经解释过,就不再赘述了。
从架构风格的抽象高度来看,常见的分布式应用架构风格有三种:
架构实例有CORBA/RMI/EJB/DCOM/.NET Remoting等等
架构实例有SOAP/XML-RPC/Hessian/Flash AMF/DWR等等
架构实例有HTTP/WebDAV
DO和RPC这两种架构风格在企业应用中非常普遍,而REST则是Web应用的架构风格,它们之间有非常大的差别。
REST与DO的差别在于:
REST支持抽象(即建模)的工具是资源,DO支持抽象的工具是对象。在不同的编程语言中,对象的定义有很大差别,所以DO风格的架构通常都是与某种编程语言绑定的。跨语言交互即使能实现,实现起来也会非常复杂。而REST中的资源,则完全中立于开发平台和编程语言,可以使用任何编程语言来实现。
DO中没有统一接口的概念。不同的API,接口设计风格可以完全不同。DO也不支持操作语义对于中间组件的可见性。
DO中没有使用超文本,响应的内容中只包含对象本身。REST使用了超文本,可以实现更大粒度的交互,交互的效率比DO更高。
REST支持数据流和管道,DO不支持数据流和管道。
DO风格通常会带来客户端与服务器端的紧耦合。在三种架构风格之中,DO风格的耦合度是最大的,而REST的风格耦合度是最小的。REST松耦合的源泉来自于统一接口+超文本驱动。
REST与RPC的差别在于:
REST支持抽象的工具是资源,RPC支持抽象的工具是过程。REST风格的架构建模是以名词为核心的,RPC风格的架构建模是以动词为核心的。简单类比一下,REST是面向对象编程,RPC则是面向过程编程。
RPC中没有统一接口的概念。不同的API,接口设计风格可以完全不同。RPC也不支持操作语义对于中间组件的可见性。
RPC中没有使用超文本,响应的内容中只包含消息本身。REST使用了超文本,可以实现更大粒度的交互,交互的效率比RPC更高。
REST支持数据流和管道,RPC不支持数据流和管道。
因为使用了平台中立的消息,RPC风格的耦合度比DO风格要小一些,但是RPC风格也常常会带来客户端与服务器端的紧耦合。支持统一接口+超文本驱动的REST风格,可以达到最小的耦合度。
比较了三种架构风格之间的差别之后,从面向实用的角度来看,REST架构风格可以为Web开发者带来三方面的利益:
采用REST架构风格,对于开发、测试、运维人员来说,都会更简单。可以充分利用大量HTTP服务器端和客户端开发库、Web功能测试/性能测试工具、HTTP缓存、HTTP代理服务器、防火墙。这些开发库和基础设施早已成为了日常用品,不需要什么火箭科技(例如神奇昂贵的应用服务器、中间件)就能解决大多数可伸缩性方面的问题。
充分利用好通信链各个位置的HTTP缓存组件,可以带来更好的可伸缩性。其实很多时候,在Web前端做性能优化,产生的效果不亚于仅仅在服务器端做性能优化,但是HTTP协议层面的缓存常常被一些资深的架构师完全忽略掉。
统一接口+超文本驱动,带来了最大限度的松耦合。允许服务器端和客户端程序在很大范围内,相对独立地进化。对于设计面向企业内网的API来说,松耦合并不是一个很重要的设计关注点。但是对于设计面向互联网的API来说,松耦合变成了一个必选项,不仅在设计时应该关注,而且应该放在最优先位置。
有的读者可能会问:“你说了这么多,REST难道就没有任何缺点了吗?”当然不是,正如Fielding在博士论文中阐述的那样,评价一种软件架构的优劣,不能脱离开软件的具体运行环境。永远不存在适用于任何运行环境的、包治百病的银弹式架构。笔者在前面强调过REST是一种为运行在互联网环境中的Web应用量身定制的架构风格。REST在互联网这个运行环境之中已经占据了统治地位,然而,在企业内网运行环境之中,REST还会面临DO、RPC的巨大挑战。特别是一些对实时性要求很高的应用,REST的表现不如DO和RPC。所以需要针对具体的运行环境来具体问题具体分析。但是,REST可以带来的上述三方面的利益即使在开发企业应用时,仍然是非常有价值的。所以REST在企业应用开发,特别是在SOA架构的开发中,已经得到了越来越大的重视。本专栏将有一篇文章专门介绍REST在企业级应用中与SOA的结合。
到了这里,“REST究竟是什么”这个问题笔者就解答完了。本文开头那些说法是否正确,笔者还是笑而不语,读者此时应该已经有了自己的判断。在接下来的REST系列文章中,我将会为读者澄清一些关于HTTP协议和REST的常见误解。
本文属于转载:原文(理解本真的REST架构风格)
]]>最近使用MyBatis开发项目,为了快速开发,发现了一个可快速生成mapper类和mapper配置文件及Model的插件-MyBatis-Generator
,总结下该插件的使用及最佳实践.
1> 建表-teacher
1 | CREATE TABLE `test`.`teacher` ( |
2> 配置properties常量
1 | # 数据库驱动jar 路径 |
3> 配置文件-generatorConfig.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
4> 运行maven - Run As Maven build
Goals 参数 : mybatis-generator:generate -Dmybatis.generator.overwrite=true
修改配置文件-generatorConfig.xml, 将table标签修改如下
1 | <table tableName="teacher" enableCountByExample="false" |
修改mybatis-generator源码
位置: mybatis-generator-core/src/main/java/org/mybatis/generator/internal/DefaultCommentGenerator.java
修改该类的方法: addFieldComment
1 | public void addFieldComment(Field field, |
如果不想修改源码, 可以下载mybatis生成中文注释项目, maven本地安装后在pom中配置version即可.
使用git克隆github项目(mybatis-generator源项目)
1 | // 克隆 parent |
碰到的问题:
平台: Mac
Jdk: 自带的jdk_1.6.0
解决办法: 安装jdk1.7, 该版本带有tools.jar
在以上三点需求完成后, 还可做以下修改使得开发更快更敏捷
1 | <commentGenerator> |
1 |
|
为Maven指定tools.jar ,解决Missing artifact com.sun:tools:jar:1.5.0错误
]]>感谢MarshalChen开源了UltimateAndroid这个快速开发框架, 在此我把各个demo的效果图进行展示, 刚开始学习Android, 不对的解释请留言纠正, 欢迎各位极客加入到这个框架的开发中来.
由于图片太多, 所有把文档分为五部分:
在测试过程中有三个Demo在小米3
机型上出现程序崩溃的情况:
1 | HomeActivity |
调色板,按钮样式及点击效果,checkbox及switch开关,进度条显示底部snackBar, dialog
拖动item
上划按钮消失
地球根据手势移动
图片缓慢拉近拉远
listview中的item图文混排, 点击提示, 手势左右滑动移除item
导航栏图标动画效果
图片拉近拉远效果
带数值的直线进度条
点击按钮效果-波纹效果
直线滚动的进度条
签名板, 画图
Activity到Activity过渡的动画效果
多种Activity到Activity过渡的动画效果
多种字体特效
Listview中item view Hover效果, 相当赞
日历, 每天0~23时段事件
闪屏图
父子菜单
文本自适应
弹出对话框后, 背景毛玻璃效果
左侧导航栏划出后, 背景毛玻璃效果
日历范围选择
多种日历选择方式
ViewPaper左右滑动效果
圆圈进度条带数值
圆圈循环效果
多种按钮点击效果-圆圈进度条, 矩形等
分段计时, 带提示音
滚动文字
裁剪图片
裁剪图片
下拉刷新
图片形状
图标按钮及点击效果
滚动条-跳到某一页
上划图文飞入
手机QQ小红点
添加gridview, 超出的放在第二页, 可以设计成桌面图标摆放框架
画图
下拉加载, 上拉加载
grid布局
多种平面直角坐标画曲线的效果
多种翻页效果
多种view的边缘效果
可扩充的圆圈进度条
面部裁剪
多种图片旋转木马
动态增加viewItem
图片上下翻页
浮动按钮, 带子按钮
上划按钮消失
浮动的Label
嵌在图片边缘的流动文本
图片翻页效果
图片及左侧菜单折叠效果
图片流加载
变换的网格布局, 可滚动指定位置
谷歌进度条
注: 该Demo测试失败
测试机型: 小米3
图层过滤
图层处理
播放图片
图片翻转效果
字体跳动效果
卡片加载, 网格图片, listview文本加载效果, viewItem点击效果
分割列表流动
列表过滤
文字选取框
字体动画效果
输入框样式
定制圆圈进度条
按钮波纹效果
图标tab页
文本tab页
浮动按钮, 带子按钮
多选列表
多种对话框效果
多种视图滚动效果
文本tab页, 可自定义tab颜色
左右摇动图片
多种list滚动, 有叠加效果
注: 该Demo测试失败
测试机型: 小米3
拍照, 选取本地图片, 可选择多张
图片自动播放, 可放大
圆圈滚动条, 可选择转动方式
页面加载圆圈进度条
滚动view, item点击事件
上滑进入下一页, 可做成锁屏首页
滚动放大图片
注: 该Demo测试失败
测试机型: 小米3
回弹图片效果
回弹图片效果Demo
动态增加item效果, 可从不同方向滑入
Item点击效果
RecyclerView
StickyHeader折叠
侧边菜单
波纹背景动画效果
点击item出现波纹效果
数字增加效果
圆角进度条
圆角图片
图片形状
按钮显示,隐藏, 下一步效果
单选列表, item可拖动
list列表, 点击进入详情, 右滑返回列表
轮播图片带文字, 多种轮播效果可选
多种GridView, ListView
GridView分类Item
下拉刷新ListView GridView
左滑右滑浏览图片
左滑,右滑,上滑,下滑 ListView Item, 带ActionBar
Swipe下拉刷新
在view中加载图片布局的动画效果
开关按钮切换
多种webView效果, 音频, 视频, 文字排版
数字过渡效果
图片移动动画效果
三角框架
ListView Item点击效果, 显示位置
ListView下拉刷新
图片view下拉刷新
图片view下拉刷新
右滑Item展开图片详情
简单文本
文字tab页, 可换颜色, actionBar可点击
多种ViewPager过渡效果
动态新建Activity
波浪状view, 可调节位置
柱状图, 直角坐标图
]]>由于图片太多, 所有把文档分为五部分:
在测试过程中有三个Demo在小米3
机型上出现程序崩溃的情况:
1 | HomeActivity |
调色板,按钮样式及点击效果,checkbox及switch开关,进度条显示底部snackBar, dialog
拖动item
上划按钮消失
地球根据手势移动
图片缓慢拉近拉远
listview中的item图文混排, 点击提示, 手势左右滑动移除item
导航栏图标动画效果
图片拉近拉远效果
带数值的直线进度条
点击按钮效果-波纹效果
直线滚动的进度条
签名板, 画图
Activity到Activity过渡的动画效果
多种Activity到Activity过渡的动画效果
多种字体特效
Listview中item view Hover效果, 相当赞
日历, 每天0~23时段事件
闪屏图
父子菜单
文本自适应
弹出对话框后, 背景毛玻璃效果
左侧导航栏划出后, 背景毛玻璃效果
日历范围选择
多种日历选择方式
ViewPaper左右滑动效果
圆圈进度条带数值
圆圈循环效果
多种按钮点击效果-圆圈进度条, 矩形等
分段计时, 带提示音
滚动文字
裁剪图片
裁剪图片
下拉刷新
图片形状
图标按钮及点击效果
滚动条-跳到某一页
上划图文飞入
手机QQ小红点
添加gridview, 超出的放在第二页, 可以设计成桌面图标摆放框架
画图
下拉加载, 上拉加载
grid布局
多种平面直角坐标画曲线的效果
多种翻页效果
多种view的边缘效果
可扩充的圆圈进度条
面部裁剪
多种图片旋转木马
动态增加viewItem
图片上下翻页
浮动按钮, 带子按钮
上划按钮消失
浮动的Label
嵌在图片边缘的流动文本
图片翻页效果
图片及左侧菜单折叠效果
图片流加载
变换的网格布局, 可滚动指定位置
谷歌进度条
注: 该Demo测试失败
测试机型: 小米3
图层过滤
图层处理
播放图片
图片翻转效果
字体跳动效果
卡片加载, 网格图片, listview文本加载效果, viewItem点击效果
分割列表流动
列表过滤
文字选取框
字体动画效果
输入框样式
定制圆圈进度条
按钮波纹效果
图标tab页
文本tab页
浮动按钮, 带子按钮
多选列表
多种对话框效果
多种视图滚动效果
文本tab页, 可自定义tab颜色
左右摇动图片
多种list滚动, 有叠加效果
注: 该Demo测试失败
测试机型: 小米3
拍照, 选取本地图片, 可选择多张
图片自动播放, 可放大
圆圈滚动条, 可选择转动方式
页面加载圆圈进度条
滚动view, item点击事件
上滑进入下一页, 可做成锁屏首页
滚动放大图片
注: 该Demo测试失败
测试机型: 小米3
回弹图片效果
回弹图片效果Demo
动态增加item效果, 可从不同方向滑入
Item点击效果
RecyclerView
StickyHeader折叠
侧边菜单
波纹背景动画效果
点击item出现波纹效果
数字增加效果
圆角进度条
圆角图片
图片形状
按钮显示,隐藏, 下一步效果
单选列表, item可拖动
list列表, 点击进入详情, 右滑返回列表
轮播图片带文字, 多种轮播效果可选
多种GridView, ListView
GridView分类Item
下拉刷新ListView GridView
左滑右滑浏览图片
左滑,右滑,上滑,下滑 ListView Item, 带ActionBar
Swipe下拉刷新
在view中加载图片布局的动画效果
开关按钮切换
多种webView效果, 音频, 视频, 文字排版
数字过渡效果
图片移动动画效果
三角框架
ListView Item点击效果, 显示位置
ListView下拉刷新
图片view下拉刷新
图片view下拉刷新
右滑Item展开图片详情
简单文本
文字tab页, 可换颜色, actionBar可点击
多种ViewPager过渡效果
动态新建Activity
波浪状view, 可调节位置
柱状图, 直角坐标图
准备开发一个小型APP, 经过调研发现组合一些开源库即可实现, 尝试组合了一些开源库, 调用API没问题, 但为了满足需求做扩展就有点困难, 为此准备系统学习Android, 参考的资料, 碰到的问题,使用的开源库, 开发的Demo均在该博客中进行记录.
ASimpleCache - a simple cache for android and java
KJFrameForAndroid - android的ioc,orm框架
Android中的组件需要用一个int类型的值来表示,这个值也就是组件标签中的id属性值;id属性只能接受资源类型的值,也就是必须以@开头的值,例如,@id/abc、@+id/xyz等。
如果在@后面使用“+”,表示当修改完某个布局文件并保存后,系统会自动在R.java文件中生成相应的int类型变量。变量名就是“/”后面的值,例如,@+id/xyz会在R.java文件中生成int xyz = value,其中value是一个十六进制的数。如果xyz在R.java中已经存在同名的变量,就不再生成新的变量,而该组件会使用这个已存在的变量的值。
也就是说,如果使用@+id/name形式,当R.java中存在名为name变量时,则该组件会使用该变量的值作为标识。如果不存在该变量,则添加一个新的变量,并为该变量赋相应的值(不会重复)。
既然组件的id属性是一个资源id就可以,那么自然可以设置任何已经存在的资源id值,例如,@drawable/icon、@string/ok、@+string/you等。当然,也可以设置android系统中已存在的资源id,例如,可以设置id属性值为@id/android:message。
1 | <ListView android:id="@+id/android:message" android:layout_width="wrap_content" android:layout_height="wrap_content"/> |
还有一种方法定义资源文件ids, 若在ids.xml中定义了ID,则在layout中可如下定义@id/price_edit,
否则@+id/price_edit。
例如,在ids.xml中我们可以如下定义:
1 | <?xml version="1.0" encoding="utf-8"?> |
在布局文件中使用:
这样有利于统一管理资源文件。
apkTool
: https://android-apktool.googlecode.com/files/apktool1.5.2.tar.bz2
;dex2jar
: https://dex2jar.googlecode.com/files/dex2jar-0.0.9.15.zip
;jd-gui
: http://jd.benow.ca/jd-gui/downloads/jd-gui-0.3.5.osx.i686.dmg
;1 | revcompile/ |
以前听说过Gradle,也查阅过相关资料,自定义生命周期等等,比Maven牛X, 但Maven在项目中已经够用, 所以一直未做构建迁移.
今天业余搞点Android小应用, 经过对比, 最终选择了Android Studio(1.0.3), 该软件默认使用Gradle构建, 在此记录下使用历程及碰到的问题.
笔者操作系统为Mac, 以下目录采用Mac目录
1 | cd /usr/bin |
使用Android Studio按照提示完成HelloWorld App的建立.
尝试使用gradle来构建App.
gradle clean
报错, 1 | FAILURE: Build failed with an exception. |
老版本bug, 使用gradle-2.2.1
无此问题.
gradle build
后生成的apk包(汗, 😓),项目的根目录下有build
目录, 但gradle构建生成的目录默认放在app/build
, 所有生成的apk包也在app/build
目录下.
ZooKeeper是一个为分布式应用所设计的分布的、开源的协调服务,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,简化分布式应用协调及其管理的难度,提供高性能的分布式服务。ZooKeeper本身可以以Standalone模式安装运行,不过它的长处在于通过分布式ZooKeeper集群(一个Leader,多个Follower),基于一定的策略来保证ZooKeeper集群的稳定性和可用性,从而实现分布式应用的可靠性。
ZooKeeper是作为分布式协调服务,是不需要依赖于Hadoop的环境,也可以为其他的分布式环境提供服务。
1 | # 可能是受Windows影响,首次接触Mac时创建了这个目录用于存放开源框架 |
1 | # zookeeper 可执行文件目录 |
至此单节点配置,启动完成.
1 | └── nodes |
不解释, 直接上配置文件(/Users/arccode/ProgramFiles/zookeeper346/conf/zk1.cfg)
1 | dataDir=/Users/arccode/data/zk/nodes/zk1/data |
根据dataDir,dataLogDir创建相应的目录并在data目录执行如下命令
1 | echo 1 > myid |
不解释, 直接上配置文件(/Users/arccode/ProgramFiles/zookeeper346/conf/zk2.cfg)
1 | dataDir=/Users/arccode/data/zk/nodes/zk2/data |
根据dataDir,dataLogDir创建相应的目录并在data目录执行如下命令
1 | echo 2 > myid |
不解释, 直接上配置文件(/Users/arccode/ProgramFiles/zookeeper346/conf/zk3.cfg)
1 | dataDir=/Users/arccode/data/zk/nodes/zk3/data |
根据dataDir,dataLogDir创建相应的目录并在data目录执行如下命令
1 | echo 3 > myid |
1 | # zookeeper可执行文件目录 |
1 | ./zkServer.sh status zk1.cfg |
注: 可以考虑多种启动或停止zookeeper的情况进行状态查看, 可发现zookeeper的高可用(HA);
使用命令行client连接zookeeper并操作API
1 | # 连接到任意节点 |
1 | package com.ts.zookeeper; |
1 | Event emit -> None |
Mongoose是一个针对MongoDB的ODM框架(技术名词, 类似ORM的思想).
Schema: 一种以文件形式存储的数据库模型骨架(定义数据结构及相关算法),不具备数据库的操作能力
Model: 由Schema发布生成的模型,可对集合(collectin)进行操作
Entity: 由Model创建的实体,他的操作也会影响数据库
指定的Collection名是Person
, 经过查询实际存储的Collection名是People
, Why??
1 | // 创建Schema |
注: 请将上面代码中的Person
改为Anything
, 对于初次接触MongoDB且没看参考资料2
的工程师更容易理解, 因为MongoDB复数化Person
时会将其变成people
.
查看mongoose文档, 发现可以设置Collection的名称,如下
1 | // 创建Schema |
看了参考资料2
你将恍然大悟, 并且学会如何将获得的单词变为复数.
该博客框架解析Markdown的table不是太好, 以下采用列举方式进行对比
System.out.println('hello');
java.util.logging.Logger
slf4j(定义接口), log4j(实现类)
console.log('hello');
util.log
log4js
笔者经过测试, 选择第三方的日志库. Java Log4j配置详见我的另一篇博客 http://www.arccode.net/log4j-config.html.
在开发代码中加入如下代码 其中’…..’表示笔者自己的业务代码
1 | ..... |
log4js.json配置
1 | { |
更详细的配置信息详见Log4JS文档
目前公司从开发到发布一个版本的流程是本地开发环境(自测)->测试环境(测试人员)->灰度发布环境(测试人员+开发人员)->上线, 不同的环境配置的属性不同(例如: 数据库连接url, 用户名, 密码), 以下为我经历的阶段.
1 | ##-------------开发------------------------- |
以上配置在打包时根据不同的环境手动注释和反注释相关代码.
配置文件为db-dev.properties, db-test.properties, db-prepub.properties, db-pub.properties, 利用maven进行配置, 在打包的时候指定要打的包或把所有包打出来
1 | <profiles> |
打包与配置的繁琐事情解决了, 在需要打包的时候指定参数就可以打出自己所需环境的包, 但经过一段时间的开发, 新的问题出现了, 当需要在配置文件中添加或修改属性时, 得同时修改三个文件, 其次线上环境那些敏感的数据被暴露给开发人员了. 经过思考, 打算采用配置服务器对配置进行管理.
在各个环境中准备好配套的配置(例如: /data/configs/proj_name/db.properties)
在spring中配置读取文件properties的位置, 如下:
1 | <bean id="commonProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> |
该方案解决了敏感数据暴露给开发人员的问题, 但维护配置还是繁琐;接下来计划开发一个配置服务器,对各个项目提供配置信息.
待续.
1 | <?xml version="1.0" encoding="UTF-8"?> |
1 | org.apache.log4j.RollingFileAppender(滚动文件,自动记录最新日志) |
1 | OFF ,FATAL ,ERROR ,WARN ,INFO ,DEBUG |
1 | # 格式说明layout中的参数都以%开始,后面不同的参数代表不同的格式化信息(参数按字母表顺序列出): |
Mac自带svnserver, 输入svnserve –version
1 | svnserve, version 1.7.10 (r1485443) |
创建目录
创建仓库
查看: ls -al /data/svn/repositories/local
1 | total 16 |
cd /data/svn/repositories/local/conf
1 | anon-access = none |
1 | # 在[users] 下添加用户及密码 |
1 | admin = arccode |
sudo svnserve -d -r /data/svn/repositories --log-file=/var/log/svn.log
1 | # 连接, svnserve默认端口3690 |
1 | sudo svn import -m 'init repo' SvnTest/ svn://localhost/local |
配置url为 svn://localhost/local
1 | # 编辑/etc/rc.local(Mac默认无此文件,需要手动创建), 添加 |
文中提到切换用户
: 表明未切换的状态下是root用户
1 | # 添加组 |
1 | # 上传文件到/usr/local/src |
1 | # 下载安装包 |
1 | # 列出存在的Topic |
1 | # console |
1 | # console |
1 | # 编辑配置文件 |
1 | ./kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic three-replication-topicTest |
1 | # 查看 |
1 | ./kafka-console-producer.sh --broker-list localhost:9092 --topic three-replication-topicTest |
1 | ./kafka-console-consumer.sh --zookeeper localhost:2181 --topic three-replication-topicTest --from-beginning |
1 | # 查看进程 |
发送消息时报错, 正在查找原因中……..
找到原因: 生产者连接的broker被终止导致发送消息出错(连接异常)
采用四个broker及四个replication-factor
1 | Topic:topicRepl PartitionCount:1 ReplicationFactor:4 Configs: |
逐个删除leader
解决方案:
1 | vim /etc/hosts |
命令行执行mvn clean package
–> 编码 EUC_CN 的不可映射字符,在Myeclipse下执行命令无此问题
GBK
,而代码文件使用UTF-8
编码解决方案:
UTF-8
UTF-8
进行文件拷贝,编译例如:
1 | <properties> |
或1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.5</source>
<target>1.5</target>
<encoding>UTF-8</encoding><!-- 指定编码格式,否则在DOS下运行mvn compile命令时会出现莫名的错误,因为系统默认使用GBK编码 -->
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<configuration>
<encoding>UTF-8</encoding><!-- 指定编码格式,否则在DOS下运行mvn命令时当发生文件资源copy时将使用系统默认使用GBK编码 -->
</configuration>
</plugin>
或1
mvn clean package -Dencoding=utf8
]]>看个人爱好,本人购买阿里云主机,最低配置55元/月
zoc6
连接服务器更新软件 yum -y update
安装开发工具 yum -y groupinstall "Development Tools"
/usr/local/bin/node
参考资料
重新ssh登录,生效;
执行 /usr/local/mongodb/bin/mongod –dbpath=/var/local/mongodb/data –logpath=/var/local/mongodb/logs/log.log 报错 -bash: ./mongod: cannot execute binary file
原因: mongodb版本与centos版本不匹配,本人安装了32位的centos,但mongodb的版本为64位
解决方案: 下载32位的mongodb进行安装
1 | Unclean shutdown detected. |
原因: mongodb意外退出导致数据文件被锁, 本人采用kill -9 pid
导致此问题
解决方案:
## 删除dbpath下的mongod.lock文件rm /usr/local/mongodb/data/mongod.lock## 修复数据文件mongod --dbpath=/usr/local/mongodb/data --repair## 重启mongodb/usr/local/mongodb/bin/mongod --dbpath=/var/local/mongodb/data --logpath=/var/local/mongodb/logs/log.log
总结: 错误的关闭方式导致的问题,以后应该避免kill -9 pid
这种暴力的方式, 可采用kill -2 pid
或切换到mongodb的admin数据库下执行db.shutdownServer()
Description | instruct | Note | ||
---|---|---|---|---|
修改日期 | date -s “2014-01-01 00:00:00” | yyyy-MM-dd HH:mm:ss | ||
同步指定服务器日期 | ntpdate 3.cn.pool.ntp.org | - | ||
查看文件编码 | set fileencoding | - | ||
查看端口占用情况 | netstat -an grep 3306 | netstat命令 | ||
查看文件使用情况 | lsof -i:80 | -i参数表示网络链接,:80 指明端口号,该命令会同时列出PID,方便kill | ||
查看端口使用 | lsof -w -n -i tcp:8080 | - | ||
进程查看 | ps -ef\ | grep gyenno-service\ | cut -c 1-200 | 截断指定长度 |
进程查看 | ps -aux | 不带 “-”为类BSD风格指令,带“-”为UNIX风格指令 START 是命令启动的时间,24小时内格式:HH:mm(例如,11:20) 24小时外格式:Mmmdd(例如,Dec11) TIME 是累积的 CPU 时间(user+system),显示格式通常是”mm:ss”(例如:58:18) | ||
显示文件大小 | ll -h |
Description | instruct | Note |
---|---|---|
复制一行 | yy | - |
粘贴 | p | - |
删除一行 | dd | - |
文章底部 | shift + g | - |
文章首部 | gg | - |
行首 | 0 | - |
行末 | shift + $ | - |
下一页 | control + f | - |
上一页 | control + b | - |
##Less
Description | instruct | Note |
---|---|---|
文章底部 | shift + g | |
文章首部 | gg | |
向下搜索 | n | |
向上搜索 | b | |
下一页 | control + f | |
上一页 | control + b |
Description | instruct | Note |
---|---|---|
目录下文件总大小 | du -h | - |
目录下各文件大小 | ll -h | - |
Description | instruct | Note |
---|---|---|
压缩文件 | xz -z 文件 | - |
解压文件 | xz -d 文件 | - |
Description | instruct | Note | |
---|---|---|---|
获取关键字所在行 | grep 【关键字】 | - | |
排除关键字所在行 | grep -v 【关键字】 | - | |
批量排除关键字所在行 | grep -v 【关键字1】 \ | grep -v 【关键字1】 | - |
批量排除关键字所在行 | grep -v 【关键字1】 \\ | 【关键字1】 | - |
或操作 | grep -E ‘123\ | abc’ filename | 找出文件(filename)中包含123或者包含abc的行 |
egrep ‘123\ | abc’ filename | 用egrep同样可以实现 | |
awk ‘/123\ | abc/‘ filename | awk 的实现方式 | |
与操作 | grep pattern1 files \ | grep pattern2 | 显示既匹配 pattern1 又匹配 pattern2 的行 |
其他操作 | grep -i pattern files | 不区分大小写地搜索。默认情况区分大小写 | |
grep -l pattern files | 只列出匹配的文件名 | ||
grep -L pattern files | 列出不匹配的文件名 | ||
grep -w pattern files | 只匹配整个单词,而不是字符串的一部分(如匹配‘magic’,而不是‘magical’) | ||
grep -C number pattern files | 匹配的上下文分别显示[number]行 |
1 | # 显示表头 |
redis-cli KEYS "user*" | xargs redis-cli DEL
flushdb
flushall
mvn clean
mvn compile
mvn package
mvn install
mvn clean -Dmaven.test.skip=true
mvn dependency:copy-dependencies -DoutputDirectory=lib
mvn dependency:tree
http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz
1 | wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz sudo tar xzvf tcl8.6.1-src.tar.gz -C /usr/local/cd /usr/local/tcl8.6.1/unix/sudo ./configuresudo makesudo make install |
tar -xzf libevent-2.0.21-stable.tar.gz
cd libevent-2.0.21-stable
sudo ./configure --prefix=/usr
sudo make
sudo make install
memcached-1.4.20
cd memcached-1.4.20
sudo ./configure --prefix=/usr
sudo make
sudo make install
1 | # 以调试模式启动 |
1 | 启动时相关参数如下: |
1 | 后台进程启动的一些指令 |
1 | # 在终端中使用`telnet`进行测试 |
1 | # cli语法,输入value时必须换行 |
关于memcached的详细用法请关注下篇博文。
]]>1 | git clone git@github.com:nicolasff/phpredis.git |
vim /etc/apache2/httpd.conf
#LoadModule php5_module libexec/apache2/libphp5.so
,将#
删除vim /etc/php.ini
extension=
,在其下面一行增加extension=redis.so
1 | # 将phpRedisAdmin克隆到apache应用目录 |
redis-server redis.conf
1 | cd ~/ProgramFiles |
1 | # 解压 |
1 | vim ~/ProgramFiles/redis/redis.conf |
1 | # 服务器端 |
1 | sudo /Users/用户名/ProgramFiles/redis/redis-server /Users/用户名/ProgramFiles/redis/redis.conf |
1 | # 连接到Redis Server |
1 | # 查找进程id |
可以在操作的过程中观察日志的输出1
tail -f /Users/用户名/ProgramFiles/redis/log-redis.log
之后想把apk包导出到本地
使用命令keytool -genkey -alias android.keystore -keyalg RSA -validity 100000 -keystore android.keystore
生成keystore时终端出现乱码
经过思考,应该是jdkkeytool
指令编码与系统编码不一致,点击终端
–>偏好设置
–>设置
,可以看见“字符编码”为Unicode(UTF-8)
,将该设置改为GB2312,重启终端,输入keytool
中文显示正常,之后输入构建keystore的指令,按照提示便可以正常生成keystore
注:该设置在某些使用UTF-8的指令会产生乱码,注意根据情况进行配置,可以配置描述文件进行快速切换
]]>path/to/eclipse
path/to/android-sdk-macosx
android-sdk-macosx
目录下的tools
目录添加到环境变量中,方便命令行调用Android指令android
,显示 Android SDK Manager图形管理工具,根据需要选择要下载的tool和sdkHelp
–> Install New Software...
–>add
,根据提示安装完毕可以与代码库断开,具体操作 右击|小组|断开连接…,在操作过程中可以删除svn相关管理文件,也可保留。
可以从代码库上把工程导到本地,个体操作 文件|导入…|SVN|从 SVN 检出项目。注:在此操作过程中有一个“选择检出的方式”选项,有两种,第一种是“做为新项目检出,并使用新建项目向导进行配置(仅当资源库中不存在.project工程文件时才可用,意思是如果代码库中有了这个工程文件,那么它就认为这是一个信息完整的工程,在导入的过程中就不需要再创建工程——选这选那的,因为.project工程文件中已包含了这一些列的信息,所以只有在代码库中不存在.project工程文件时才可选择)”;第二种是“做为工作空间中的项目检出”,这一选项不管代码库中是否有.project文件都是可以选择的,如果代码库中没有.project文件时,选择此种方式,会创建一个简单的工程,那不是Java工程,或其他代码工程,这种工程无不进行相应手工设置是不能进行相应开发的,所以在代码库上无.project工程文件导入时,最好选择前一选项,根据新建项目向导来进行相应设置并自动生成.project工程文件。
如果将文件添加到项目中,它不会自动成为版本控制的一部分 —— 您需要明确将其添加到特定的存储库中。右击新文件,然后选择 Team > Add to Version Control。就是这样!下一次将此项目中的变更提交给存储库时,新文件也会检入。
如果项目生成文件,或者包括您不想检入的 Subversion 存储库文件,则可以通知 Subclipse 忽略它们。右击您要在版本控制中排除的文件或目录,然后选择 Team > Add to svn:ignore 即可显示Add to svn:ignore 对话框。
不要把class目录上传到服务器上,起初我没有删掉它,更改提交没有问题,但在更新时总是报错:……Working copy not locked; this is probably a bug, please report svn:……,提示就是class目录下的.svn有毛病。原因可能是在Eclipse编译时,将src目录下的svn配置一起编译到了class文件中,并且在编译时先会删除class中的内容,再编译,这就导致了本地配置与配置库上不一致。需要注意的是,请不要将动态生成的目录添加到SVN,否则其他人Check out的之后,再Update容易出错,例如class目录,不需要提交上去
可以用来在文件(例如,对二进制文件非常有用)上强加排斥锁。一个定义了svn:need-lock属性的文件一次只能被一个人修改。当该文件被检出时,它是只读的。如果你想修改该文件,你需要首先使用”Team>Lock”菜单选项。之后,使用”Team> Unlock”释放该文件,或仅提交你的变化,也会释放锁,但别人的文件还是只读的,只有别人在获取了锁之后,再能修改该文件。其实该属性以一种强迫的属形势让用户在修改前一定要选获取锁,再能修改该文件,这样就不需要等到提交时再发现早已被别人锁住了。
在Subversion中,很容易创建新的tag和branch。你可以使用tag来标识一个特定的版本(使用一种可读的名字,例如”Release 1.0”);而一个branch用于新的开发工作而不影响主源码基(称作trunk)。在一个branch上的开发仍会继续进行,直到开发者已经为把变化集成回主trunk作好准备。
在Subversion中,branch和tag都是通过制作给定修订的一个虚拟副本(以另一个名字和/或另一个目录)创建的。在常规情况下,branch存储在branches目录下,tag位于tags目录下,尽管在实践中为了满足你的工程你可以使用自己的任何定制。
从Eclipse中,”Team>Branch/Tag”菜单能够使你创建branch和tag。其中,Browse按钮提供了一种方便的方法来查看有哪些branch和tag存在于仓库中。
当你使用”Team>Switch”创建成功一个新的branch或tag时,你可以非常容易地在branches之间进行切换。无论何时你切换到一个不同的branch(或返回到trunk),Subversion将仅更新文件(它需要保持你的当前工作的副本与目的branch之间的同步)。
如果别人和自己更改的是同一个文件,那么update时会自动进行合并,但如果修改的是同一行,那么合并时会产生冲突。
更新时注意所更新文件的列表,如果提交过程中产生了更新,则也是需要重新编译并且完成自己的一些必要测试,再进行提交。
每次提交以几小时为单位。在开发功能模块的时候,可以每完成一个小细节功能的测试,就提交一次,在修改bug的时候,每修改掉一个bug并且确认修改了这个bug,也就提交一次。
代码在提交之前,首先要确认自己能够在本地编译。如果在代码中使用了第三方类库,要考虑到项目组成员中有些成员可能没有安装相应的第三方类库。项目经理在准备项目工作区域的时候,需要考虑到这样的情况,确保开发小组成员在签出代码之后能够在统一的环境中进行编译。
明晰的标注要能够概要的描述所提交文件的信息,让项目组其他成员在看到标注后不用详细看代码就能了解你所做的修改。
例如eclipse中的.classpath文件,Windows生成的缩略图Thumbs.db,项目编译生成的临时文件.obj, .class等等。如果项目中没有进行这方面的配置来强行禁止提交这样的文件,请自觉不要提交这样的文件。提交了这样的文件后,别人在更新后就可能与本地的环境冲突从而影响大家的工作。
代码在提交入SVN之后,你的代码将被项目成员所分享。如果提交了你不明白的代码,你看不懂,别人也看不懂,如果在以后出现了问题将会成为项目质量的隐患。因此在引入任何第三方代码之前,确保你对这个代码有一个很清晰的了解。
在项目中要慎用锁定的功能,在你锁定了一个文件之后别人就无法继续修改提交该文件,虽然可以减少冲突的发生率,但是可能会影响项目组中其他人员的工作。平时只有在编辑那些无法合并的文件(例如图片文件,flash文件等)时,才适当的采用锁定操作。
该文章转自Iteye—svn的基本使用
安装JDK,安装成功后在终端中输入java -version
,显示如下
1 | java version "1.7.0_51" |
下载Hadoop,将该文件解压至/usr/local
目录下
1 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.7.0_51.jdk/Contents/Home |
输入ssh localhost
显示
1 | ssh: connect to host localhost port 22: Connection refused |
表示ssh未配置,需要进行配置;
“系统偏好设置”–>”共享”–>”远程登录”;
再次输入ssh localhost
,输入密码后可以看到连接成功的信息1
Last login: Mon Jul 14 23:41:32 2014 from localhost
ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa
cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
###
HBase – Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。
HBase是Google Bigtable的开源实现,类似Google Bigtable利用GFS作为其文件存储系统,HBase利用Hadoop HDFS作为其文件存储系统;Google运行MapReduce来处理Bigtable中的海量数据,HBase同样利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable利用 Chubby作为
协同服务,HBase利用Zookeeper作为对应。
上图描述了Hadoop EcoSystem中的各层系统,其中HBase位于结构化存储层,Hadoop HDFS为HBase提供了高可靠性的底层存储支持,Hadoop MapReduce为HBase提供了高性能的计算能力,Zookeeper为HBase提供了稳定服务和failover机制。
此外,Pig和Hive还为HBase提供了高层语言支持,使得在HBase上进行数据统计处理变的非常简单。 Sqoop则为HBase提供了方便的RDBMS数据导入功能,使得传统数据库数据向HBase中迁移变的非常方便。
Native Java API,最常规和高效的访问方式,适合Hadoop MapReduce Job并行批处理HBase表数据;
HBase Shell,HBase的命令行工具,最简单的接口,适合HBase管理使用;
Thrift Gateway,利用Thrift序列化技术,支持C++,PHP,Python等多种语言,适合其他异构系统在线访问HBase表数据;
REST Gateway,支持REST 风格的Http API访问HBase, 解除了语言限制;
Pig,可以使用Pig Latin流式编程语言来操作HBase中的数据,和Hive类似,本质最终也是编译成MapReduce Job来处理HBase表数据,适合做数据统计;
Hive,当前Hive的Release版本尚没有加入对HBase的支持,但在下一个版本Hive 0.7.0中将会支持HBase,可以使用类似SQL语言来访问HBase;
Ø Row Key: 行键,Table的主键,Table中的记录按照Row Key排序
Ø Timestamp: 时间戳,每次数据操作对应的时间戳,可以看作是数据的version number
Ø Column Family:列簇,Table在水平方向有一个或者多个Column Family组成,一个Column Family中可以由任意多个Column组成,即Column Family支持动态扩展,无需预先定义Column的数量以及类型,所有Column均以二进制格式存储,用户需要自行进行类型转换。
当Table随着记录数不断增加而变大后,会逐渐分裂成多份splits,成为regions,一个region由[startkey,endkey)表示,不同的region会被Master分配给相应的RegionServer进行管理:
HBase中有两张特殊的Table,-ROOT-和.META.
Ø .META.:记录了用户表的Region信息,.META.可以有多个regoin
Ø -ROOT-:记录了.META.表的Region信息,-ROOT-只有一个region
Ø Zookeeper中记录了-ROOT-表的location
Client访问用户数据之前需要首先访问zookeeper,然后访问-ROOT-表,接着访问.META.表,最后才能找到用户数据的位置去访问,中间需要多次网络操作,不过client端会做cache缓存。
在HBase系统上运行批处理运算,最方便和实用的模型依然是MapReduce,如下图:
HBase Table和Region的关系,比较类似HDFS File和Block的关系,HBase提供了配套的TableInputFormat和TableOutputFormat API,可以方便的将HBase Table作为Hadoop MapReduce的Source和Sink,对于MapReduce Job应用开发人员来说,基本不需要关注HBase系统自身的细节。
HBase Client使用HBase的RPC机制与HMaster和HRegionServer进行通信,对于管理类操作,Client与HMaster进行RPC;对于数据读写类操作,Client与HRegionServer进行RPC
Zookeeper Quorum中除了存储了-ROOT-表的地址和HMaster的地址,HRegionServer也会把自己以Ephemeral方式注册到Zookeeper中,使得HMaster可以随时感知到各个HRegionServer的健康状态。此外,Zookeeper也避免了HMaster的单点问题,见下文描述
HMaster没有单点问题,HBase中可以启动多个HMaster,通过Zookeeper的Master Election机制保证总有一个Master运行,HMaster在功能上主要负责Table和Region的管理工作:
管理用户对Table的增、删、改、查操作
管理HRegionServer的负载均衡,调整Region分布
在Region Split后,负责新Region的分配
在HRegionServer停机后,负责失效HRegionServer 上的Regions迁移
HRegionServer主要负责响应用户I/O请求,向HDFS文件系统中读写数据,是HBase中最核心的模块。
HRegionServer内部管理了一系列HRegion对象,每个HRegion对应了Table中的一个Region,HRegion中由多个HStore组成。每个HStore对应了Table中的一个Column Family的存储,可以看出每个Column Family其实就是一个集中的存储单元,因此最好将具备共同IO特性的column放在一个Column Family中,这样最高效。
HStore存储是HBase存储的核心了,其中由两部分组成,一部分是MemStore,一部分是StoreFiles。MemStore是Sorted Memory Buffer,用户写入的数据首先会放入MemStore,当MemStore满了以后会Flush成一个StoreFile(底层实现是HFile),当StoreFile文件数量增长到一定阈值,会触发Compact合并操作,将多个StoreFiles合并成一个StoreFile,合并过程中会进行版本合并和数据删除,因此可以看出HBase其实只有增加数据,所有的更新和删除操作都是在后续的compact过程中进行的,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O的高性能。当StoreFiles Compact后,会逐步形成越来越大的StoreFile,当单个StoreFile大小超过一定阈值后,会触发Split操作,同时把当前Region Split成2个Region,父Region会下线,新Split出的2个孩子Region会被HMaster分配到相应的HRegionServer上,使得原先1个Region的压力得以分流到2个Region上。下图描述了Compaction和Split的过程:
在理解了上述HStore的基本原理后,还必须了解一下HLog的功能,因为上述的HStore在系统正常工作的前提下是没有问题的,但是在分布式系统环境中,无法避免系统出错或者宕机,因此一旦HRegionServer意外退出,MemStore中的内存数据将会丢失,这就需要引入HLog了。每个HRegionServer中都有一个HLog对象,HLog是一个实现Write Ahead Log的类,在每次用户操作写入MemStore的同时,也会写一份数据到HLog文件中(HLog文件格式见后续),HLog文件定期会滚动出新的,并删除旧的文件(已持久化到StoreFile中的数据)。当HRegionServer意外终止后,HMaster会通过Zookeeper感知到,HMaster首先会处理遗留的 HLog文件,将其中不同Region的Log数据进行拆分,分别放到相应region的目录下,然后再将失效的region重新分配,领取 到这些region的HRegionServer在Load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到MemStore中,然后flush到StoreFiles,完成数据恢复。
HBase中的所有数据文件都存储在Hadoop HDFS文件系统上,主要包括上述提出的两种文件类型:
HFile, HBase中KeyValue数据的存储格式,HFile是Hadoop的二进制格式文件,实际上StoreFile就是对HFile做了轻量级包装,即StoreFile底层就是HFile;
HLog File,HBase中WAL(Write Ahead Log) 的存储格式,物理上是Hadoop的Sequence File;
下图是HFile的存储格式:
首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。正如图中所示的,Trailer中有指针指向其他数据块的起始点。File Info中记录了文件的一些Meta信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等。Data Index和Meta Index块记录了每个Data块和Meta块的起始点。
Data Block是HBase I/O的基本单元,为了提高效率,HRegionServer中有基于LRU的Block Cache机制。每个Data块的大小可以在创建一个Table的时候通过参数指定,大号的Block有利于顺序Scan,小号Block利于随机查询。每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成, Magic内容就是一些随机数字,目的是防止数据损坏。后面会详细介绍每个KeyValue对的内部构造。
HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构。我们来看看里面的具体结构:
开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定长度的数值,表示RowKey的长度,紧接着是RowKey,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。
上图中示意了HLog文件的结构,其实HLog文件就是一个普通的Hadoop Sequence File,Sequence File 的Key是HLogKey对象,HLogKey中记录了写入数据的归属信息,除了table和region名字外,同时还包括 sequence number和timestamp,timestamp是“写入时间”,sequence number的起始值为0,或者是最近一次存入文件系统中sequence number。
HLog Sequece File的Value是HBase的KeyValue对象,即对应HFile中的KeyValue,可参见上文描述。
本文对HBase技术在功能和设计上进行了大致的介绍,由于篇幅有限,本文没有过多深入地描述HBase的一些细节技术。目前一淘的存储系统就是基于HBase技术搭建的,后续将介绍“一淘分布式存储系统”,通过实际案例来更多的介绍HBase应用。
技术人员怎么也讲不清楚的云计算,咱们的高级总监曾经就是这样和政府领导普及云计算和智慧城市知识的,却都听懂了:
你娶了一个老婆,这叫传统IT架构。
你觉得一个老婆不够,这叫传统企业CIO的困境。
你又娶了一个老婆,这叫双活数据中心。
你在外地又娶了一个小老婆,这叫两地三中心容灾。
你娶了很多风格气质各异的小老婆,以至于形成了后宫,这叫私有云。你的后宫就叫计算资源池。
你从后宫里选出懂事有能的管理其他小老婆,这叫私有云管理方案。管事的那个就是HYPERV或VMWARE。
你不娶小老婆,改成包养很多情人,这叫托管云。 你是穷人,没有钱包养任何人也没钱娶小老婆,你选择去洗浴中心解决问题,这叫面向中小企业的公有云服务。 你在享受公有云服务的同时还得交公粮,这叫中小企业的混合IT架构。
你是富人,但也喜欢去高级夜总会,这叫面向大企业用户的公有云服务。
你有钱,同时包养小老婆和情人,还去洗浴中心,这叫混合云。但是在混合云里,最关键的业务还是会谨慎地采用传统IT架构。
你有钱,包养小老婆,但有一天所有小老婆都来大姨妈,你还得去洗浴中心,这叫CLOUD BURST。
洗浴中心就是云服务提供商。 本地最大的洗浴中心是AWS。 高端那个比如****是18M。 打出商务**的名头的是SALESFORCE。 在**上发帖“我们的技师服务态度超过对面家9倍”的是O记。
不开洗浴中心,但是专门卖水床卖情趣床的是等灯等灯。 不开洗浴中心,但是做陌陌类应用开发运营的是C记。 本地的洗浴中心都是两个有背景的大老板开的,他们是OPENSTACK和CLOUDSTACK。
场子小且只用本地或附近技师的是本土中小云服务商。 如果上头没人罩,本土中小云服务商很难开得长。 开洗浴中心的老板有政府背景,这是城市云。 开洗浴中心的老板有黑社会背景,这是电信云。 洗浴中心要VIP卡才让进,这是行业云。 洗浴中心只面向本小区业主服务,这是园区云。
你的小老婆们和情人们可能因为矛盾而让你的经济问题败露,这叫私有云安全问题。 你去洗浴中心可能染上病,这是公有云安全问题。 由此可见公有云和私有云的安全问题是两种不同性质的问题。 只提供场所,需要你自己去找技师的洗浴中心叫IAAS。 提供场所和技师的洗浴中心叫PAAS。 做到东莞ISO的程度就叫SAAS。 下载AV录像自己打飞机叫VAAS。
一个有很多人分享心得的洗浴中心信息网站,这是开放数据中心联盟。 洗浴中心的行业协会叫CLOUD BUILDER。 下榻一家五星级宾馆,你习惯性地用你用微信陌陌定个位,这叫CLOUD FINDER。 良家妇女下海,这叫传统IT应用的云化。
你去洗浴中心的经验很多,看见门脸就知道洗浴中心提不提供服务,看见新技师就能推测出服务质量,这叫数据分析和挖掘。 你把这些信息和心得有偿分享给其他人,你是大数据服务商。 一个地方不严打洗浴中心,这是智慧城市。
该文章转载自Iteye-别样解释云计算,太TM天才跨界了
大数据,官方定义是指那些数据量特别大、数据类别特别复杂的数据集,这种数据集无法用传统的数据库进行存储,管理和处理。大数据的主要特点为数据量大(Volume),数据类别复杂(Variety),数据处理速度快(Velocity)和数据真实性高(Veracity),合起来被称为4V。
大数据中的数据量非常巨大,达到了PB级别。而且这庞大的数据之中,不仅仅包括结构化数据(如数字、符号等数据),还包括非结构化数据(如文本、图像、声音、视频等数据)。这使得大数据的存储,管理和处理很难利用传统的关系型数据库去完成。在大数据之中,有价值的信息往往深藏其中。这就需要对大数据的处理速度要非常快,才能短时间之内就能从大量的复杂数据之中获取到有价值的信息。在大数据的大量复杂的数据之中,通常不仅仅包含真实的数据,一些虚假的数据也混杂其中。这就需要在大数据的处理中将虚假的数据剔除,利用真实的数据来分析得出真实的结果。
大数据,表面上看就是大量复杂的数据,这些数据本身的价值并不高,但是对这些大量复杂的数据进行分析处理后,却能从中提炼出很有价值的信息。对大数据的分析,主要分为五个方面:可视化分析(Analytic Visualization)、数据挖掘算法(Date Mining Algorithms)、预测性分析能力(Predictive Analytic Capabilities)、语义引擎(Semantic Engines)和数据质量管理(Data Quality Management)。
可视化分析是普通消费者常常可以见到的一种大数据分析结果的表现形式,比如说百度制作的“百度地图春节人口迁徙大数据”就是典型的案例之一。可视化分析将大量复杂的数据自动转化成直观形象的图表,使其能够更加容易的被普通消费者所接受和理解。
数据挖掘算法是大数据分析的理论核心,其本质是一组根据算法事先定义好的数学公式,将收集到的数据作为参数变量带入其中,从而能够从大量复杂的数据中提取到有价值的信息。著名的“啤酒和尿布”的故事就是数据挖掘算法的经典案例。沃尔玛通过对啤酒和尿布购买数据的分析,挖掘出以前未知的两者间的联系,并利用这种联系,提升了商品的销量。亚马逊的推荐引擎和谷歌的广告系统都大量使用了数据挖掘算法。
预测性分析能力是大数据分析最重要的应用领域。从大量复杂的数据中挖掘出规律,建立起科学的事件模型,通过将新的数据带入模型,就可以预测未来的事件走向。预测性分析能力常常被应用在金融分析和科学研究领域,用于股票预测或气象预测等。
语义引擎是机器学习的成果之一。过去,计算机对用户输入内容的理解仅仅停留在字符阶段,不能很好的理解输入内容的意思,因此常常不能准确的了解用户的需求。通过对大量复杂的数据进行分析,让计算机从中自我学习,可以使计算机能够尽量精确的了解用户输入内容的意思,从而把握住用户的需求,提供更好的用户体验。苹果的Siri和谷歌的Google Now都采用了语义引擎。
数据质量管理是大数据在企业领域的重要应用。为了保证大数据分析结果的准确性,需要将大数据中不真实的数据剔除掉,保留最准确的数据。这就需要建立有效的数据质量管理系统,分析收集到的大量复杂的数据,挑选出真实有效的数据。
对于如何处理大数据,计算机科学界有两大方向:第一个方向是集中式计算,就是通过不断增加处理器的数量来增强单个计算机的计算能力,从而提高处理数据的速度。第二个方向是分布式计算,就是把一组计算机通过网络相互连接组成分散系统,然后将需要处理的大量数据分散成多个部分,交由分散系统内的计算机组同时计算,最后将这些计算结果合并得到最终的结果。尽管分散系统内的单个计算机的计算能力不强,但是由于每个计算机只计算一部分数据,而且是多台计算机同时计算,所以就分散系统而言,处理数据的速度会远高于单个计算机。
过去,分布式计算理论比较复杂,技术实现比较困难,因此在处理大数据方面,集中式计算一直是主流解决方案。IBM的大型机就是集中式计算的典型硬件,很多银行和政府机构都用它处理大数据。不过,对于当时的互联网公司来说,IBM的大型机的价格过于昂贵。因此,互联网公司的把研究方向放在了可以使用在廉价计算机上的分布式计算上。
服务器集群是一种提升服务器整体计算能力的解决方案。它是由互相连接在一起的服务器群所组成的一个并行式或分布式系统。服务器集群中的服务器运行同一个计算任务。因此,从外部看,这群服务器表现为一台虚拟的服务器,对外提供统一的服务。
尽管单台服务器的运算能力有限,但是将成百上千的服务器组成服务器集群后,整个系统就具备了强大的运算能力,可以支持大数据分析的运算负荷。Google,Amazon,阿里巴巴的计算中心里的服务器集群都达到了5000台服务器的规模。
大数据的技术基础:MapReduce、Google File System和BigTable
2003年到2004年间,Google发表了MapReduce、GFS(Google File System)和BigTable三篇技术论文,提出了一套全新的分布式计算理论。
MapReduce是分布式计算框架,GFS(Google File System)是分布式文件系统,BigTable是基于Google File System的数据存储系统,这三大组件组成了Google的分布式计算模型。
Google的分布式计算模型相比于传统的分布式计算模型有三大优势:首先,它简化了传统的分布式计算理论,降低了技术实现的难度,可以进行实际的应用。其次,它可以应用在廉价的计算设备上,只需增加计算设备的数量就可以提升整体的计算能力,应用成本十分低廉。最后,它被Google应用在Google的计算中心,取得了很好的效果,有了实际应用的证明。
后来,各家互联网公司开始利用Google的分布式计算模型搭建自己的分布式计算系统,Google的这三篇论文也就成为了大数据时代的技术核心。
主流的三大分布式计算系统:Hadoop,Spark和Storm
由于Google没有开源Google分布式计算模型的技术实现,所以其他互联网公司只能根据Google三篇技术论文中的相关原理,搭建自己的分布式计算系统。
Yahoo的工程师Doug Cutting和Mike Cafarella在2005年合作开发了分布式计算系统Hadoop。后来,Hadoop被贡献给了Apache基金会,成为了Apache基金会的开源项目。Doug Cutting也成为Apache基金会的主席,主持Hadoop的开发工作。
Hadoop采用MapReduce分布式计算框架,并根据GFS开发了HDFS分布式文件系统,根据BigTable开发了HBase数据存储系统。尽管和Google内部使用的分布式计算系统原理相同,但是Hadoop在运算速度上依然达不到Google论文中的标准。
不过,Hadoop的开源特性使其成为分布式计算系统的事实上的国际标准。Yahoo,Facebook,Amazon以及国内的百度,阿里巴巴等众多互联网公司都以Hadoop为基础搭建自己的分布式计算系统。
Spark也是Apache基金会的开源项目,它由加州大学伯克利分校的实验室开发,是另外一种重要的分布式计算系统。它在Hadoop的基础上进行了一些架构上的改良。Spark与Hadoop最大的不同点在于,Hadoop使用硬盘来存储数据,而Spark使用内存来存储数据,因此Spark可以提供超过Hadoop100倍的运算速度。但是,由于内存断电后会丢失数据,Spark不能用于处理需要长期保存的数据。
Storm是Twitter主推的分布式计算系统,它由BackType团队开发,是Apache基金会的孵化项目。它在Hadoop的基础上提供了实时运算的特性,可以实时的处理大数据流。不同于Hadoop和Spark,Storm不进行数据的收集和存储工作,它直接通过网络实时的接受数据并且实时的处理数据,然后直接通过网络实时的传回结果。
Hadoop,Spark和Storm是目前最重要的三大分布式计算系统,Hadoop常用于离线的复杂的大数据处理,Spark常用于离线的快速的大数据处理,而Storm常用于在线的实时的大数据处理。
该文章转载自虎嗅网-【专治不明觉厉】之“大数据”
1 | tar {-c | -r | -t | -x | -f} {fileName} [fileList] |
创建test.tar
文件,内部包含1.txt、2.txt文件
tar -cf test.tar 1.txt 2.txt
向test.tar
文件中继续添加文件(3.txt)
tar -rf test.tar 3.txt
列出test.tar
文件中文件列表
tar -tf test.tar
提取test.tar
文件中的文件到当前目录
tar -xf test.tar
压缩test.tar
文件,产生test.tar.gz
文件
gzip test.tar
解压test.tar.gz
文件中的文件到当前目录
tar -xzf test.tar.gz
man tar
点击modern.IE,选择要下载的版本,点击Grab them all with cURL
获取下载的URL,例如
1 | curl -O -L "https://www.modern.ie/vmdownload?platform=mac&virtPlatform=virtualbox&browserOS=IE10-Win8.1&parts=5&filename=VMBuild_20131127/VirtualBox/IE10_Win8/Mac/IE10.Win8.For.MacVirtualBox.part{1.sfx,2.rar,3.rar,4.rar,5.rar}" |
$ cd ~/Downloads/VisualBox_IE
,切换到保存文件的目录;
执行获取的URL
1 | curl -O -L "https://www.modern.ie/vmdownload?platform=mac&virtPlatform=virtualbox&browserOS=IE10-Win8.1&parts=5&filename=VMBuild_20131127/VirtualBox/IE10_Win8/Mac/IE10.Win8.For.MacVirtualBox.part{1.sfx,2.rar,3.rar,4.rar,5.rar}" |
$ chmod +x ./IE11.Win7.For.MacVirtualBox.part1.sfx
$ ./chmod +x ./IE11.Win7.For.MacVirtualBox.part1.sfx
IE11 - Win7.ova
,并配置内存另一种方式:Mac安装IE 方法二,使用到googlecode需要使用代理。
结构
主要指HTML/XHTML标准,包括页面渲染、标签大小写、属性大小写、标签嵌套、闭合标签等等;表现
主要指CSS标准,包括页面布局、CSS选择器、盒子模型、盒子样式、盒子内容样式、媒体查询(CSS3);行为
主要指Javascript标准,包括对DOM及BOM的操作;由于IE6、IE7、IE8未完全按照W3C标准去实现其浏览器的功能,而是自定义了一套IE的标准,所以在开发的过程中需要特意针对IE6~IE8开发相对应的CSS及Javascript。
CSS兼容:
使用hack;Javascript兼容:
IE事件绑定采用attachEvent,现代浏览器采用addEventListener;并发执行的程序在执行过程中对资源(CPU、寄存器、IO、程序数据)分配和管理的基本单位;
注: 进程状态对就绪态、运行态、阻塞态进行了细分,其目的均是为了更好的控制程序,但这是以增加系统复杂性和系统开销为代价;
轻量级进程,创建及销毁开销小。
问:为什么要搭建博客?
答:记录自己在IT这个行业中的点滴,同时把自己的一些东西分享给各位朋友。
问:为什么不选择已有的博客平台,而要自己搭建?
答:想让事情变得简单点,有想法或解决了一些问题的时候,用Markdown记录一下。
问:……?
答:…………。
问: 你的声音太小,我没听清楚呀。
答: 噢,我给你留言并发送邮件吧,我的Email:arccode@163.com。
]]>