0%

Centos SSH免密码登陆

1.在客户机创建一对密钥文件,包括公钥文件(/.ssh/id_rsa.pub),私钥文件(/.ssh/id_rsa)

2.把公钥放到服务器上(~/.ssh/authorized_keys),在使用ssh登录时,ssh程序会发送私钥去和服务器上的公钥做匹配。如果匹配成功就可以自动登录了。

服务器配置

  1. 修改sshd配置文件(/etc/ssh/sshd_config)

    1
    PubkeyAuthentication yes
  2. 配置authorized_keys文件
    ~/.ssh/authorized_keys 不存在,则建立.ssh文件夹和authorized_keys文件.

  3. 重启sshd

    1
    $ /etc/init.d/sshd restart

注意

  1. .ssh目录的权限必须是700
  2. .ssh/authorized_keys文件权限必须是600

Redis 版本号约定

安装Redis需要知道自己需要哪个版本,有针对性的安装,比如如果需要redis GEO这个地理集合的特性,那么redis版本就不能低于3.2版本,由于这个特性是3.2版本才有的。另外需要注意的是,Redis约定次版本号(即第一个小数点后的数字)为偶数的版本是稳定版(如2.8版、3.0版),奇数版本是非稳定版(如2.7版、2.9版),生产环境下一般需要使用稳定版本。

下载安装包

1
wget http://download.redis.io/releases/redis-4.0.2.tar.gz

可从http://download.redis.io/releases/查询自己所需要的版本。

解压安装包并下载

1
2
3
4
tar -zxvf redis-4.0.2.tar.gz
cd redis-4.0.2
make
make install

Redis 启动

1
./redis-server ./redis.conf 

通过参数daemonize设置redis后台启动

Redis 关闭

1
redis-cli SHUTDOWN

或者

1
kill -9 进程号

引入jar

1
2
3
4
5
6
7
8
9
10
11
12
13
 <!-- 阿波罗 -->
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>${apollo.version}</version>
</dependency>

<!-- 阿波罗依赖guava包,注意guava包的版本 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>

启动类添加Apollo注解

1
2
3
4
5
6
7
@EnableApolloConfig
@SpringBootApplication
public class App{
public static void main( String[] args ){
new SpringApplicationBuilder(App.class).web(true).run(args);
}
}

新增一个类监听Apollo参数变更

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
@Slf4j
public class ApolloConfigRefresher implements ApplicationContextAware {

@Autowired
private ApplicationContext applicationContext;

@ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) {
// TODO 在此处监听发生改变的参数,若为连接池的参数需要重新加载链接池

this.applicationContext.publishEvent(new EnvironmentChangeEvent(changeEvent.changedKeys()));
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

}

Apollo Demo提供的参考方法

  1. https://github.com/ctripcorp/apollo/blob/master/apollo-demo/src/main/java/com/ctrip/framework/apollo/demo/spring/springBootDemo/SpringBootSampleApplication.java

  2. apollo-use-cases

应用配置信息

1
2
3
4
5
6
7
app:
id: vota_business_mgmt_test

apollo:
autoUpdateInjectedSpringProperties: true
bootstrap:
enabled: true

Apollo 环境变量

文件目录

  • Window C:\opt\settings\server.properties
  • Liunx /opt/settings/server.properties

server.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#env=LOCAL

#dev
#env=DEV
#apollo.meta=http://apollo-dev.qa.xxxx.com

# 测试环境
#env=FAT / UAT
#apollo.meta=http://apollo.qa.xxxx.com/
#apollo.meta=http://apollo-pre.qa.xxxx.com/

# 生产
env=PRO
apollo.meta=http://apollo.ngi.xxxx.com/

env参数说明:

1
env=LOCAL  不会从服务器拉去最新的配置文件,只会使用当前的配置文件

最近再做根据 Cmpp 协议对接一个短信网关接口。
考虑到需要频繁的与网关服务器进行数据交互,因此使用长连接。

如何区分长连接,短连接 ?

所谓短连接就是建立一次tcp握手成功后进行数据交互,交互完成主动或者关闭tcp。 主动关闭 一般就是关闭socket或者关闭socket的 in ou流。被动关闭会受限操作系统的配置。比如建立tcp成功之后的一段时间若无数据交互,则操作系统会主动关闭这次tcp连接。

长连接,其实就是就是在建立一次tcp成功之后,通过一定的心跳机制来保证tcp链路一直建立而不释放。具体的长连接也需要Server的支持。

Java Socket 长连接

  1. 创建一个Socket
    1
    2
    3
    4
    5
    Socket msgSocket = new Socket();
    SocketAddress socAddress = new InetSocketAddress(msgConfig.getIsmgIp(), msgConfig.getIsmgPort());
    SocketAddress locAddress = new InetSocketAddress(InetAddress.getLocalHost(), msgConfig.getIsLocalPort());
    msgSocket.bind(locAddress);
    msgSocket.connect(socAddress, SOCKET_TIME_OUT);
    创建客户端socket 有多种方式,最常用的方式为Socket msgSocket = new Socket(msgConfig.getIsmgIp(), msgConfig.getIsmgPort(), InetAddress.getLocalHost(), msgConfig.getIsLocalPort());,但通过这个方式我发现 Socket 会自动关闭。 后来尝试使用上面的方法创建一个Socket 并通过 connect 设置超时时间,然后在超时时间内进行一次心跳交互,可以达到http长连接。

如何判断是否为长连接

若http协议可以根据http head 中的 keep-alive 判断是否为长连接。
对于tcp或者socket,我们再与服务器建立长连接时会开辟一个端口,数据会通过这个端口与服务器进行数据交互,因此我们可以监控这个端口,查看这个端口的状态。

1
2
3
lsof -i: port

java 2240 root 154u IPv4 205709 0t0 TCP xxx:port-> remote_ip:remote_ip (ESTABLISHED)

TCP 端口说明

TCP 协议规定,对于已经建立的连接,网络双方要进行四次握手才能断开成功,如果缺少了其中某个步骤,将会使连接处于假死状态,连接本身占用的资源也不会被释放。**CLOSE_WAIT 和 TIME_WAIT ** 这两个TCP状态值得我们关注一下

  1. LISTENING 状态
    FTP 服务启动后会处于侦听(LISTENING) 状态

  2. ESTABLISTENED 状态
    ESTABLISTENED的意思是建立连接,表示两台机器正在通信

  3. CLOSE_WAIT
    对方主动关闭连接或者网络异常导致连接中断,这是我方的状态会变成CLOSE_WAIT,此时我方要调用 close() 来使得连接正确关闭。

  4. TIME_WAIT
    我方主动调用 close() 断开连接,收到对方确认后状态会变为 TIME_WAIT。TCP 协议规定TIME_WAIT状态会一直持续2MSL(即两倍的分段最大生存期),以此来确保旧的连接状态不会对新连接产生影响。处于TIME_WAIT 状态的连接占用资源不会被内存释放,所以作为服务器,再可能的情下,尽量不要主动断开连接,减少TIME_WAIT状态造成的资源浪费。

  5. SYN_SENT 状态
    SYN_SENT 状态表示请求连接,当你要访问其他的计算机的服务时首先要发个同步信号给该端口,此时的状态为SYN_SENT,如果连接成功了就变成 ESTABLISTENED,此时SYN_SENT状态非常短暂。

根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态,TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒,TIME_WAIT状态下的socket不能被回收使用. 具体现象是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的socket, 甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket,停止服务. TIME_WAIT是TCP协议用以保证被重新分配的socket不会受到之前残留的延迟重发报文影响的机制,是必要的逻辑保证.

常用tcp命令

1
2
3
netstat 

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

Log4j MDC

问题

在项目中需要分类收集处理日志信息,使用log4j的MDC线程中添加分类信息。不过最近却出现信息记录错误的情况,具体来说就是会出现本来属于下一个分类的一部分信息莫名的记录到上一个分类的日志文件中了。

问题原因分析

MDC 需要在完成后被重置,或者说清空。因为MDC是绑定线程的,所以,在大多数简单的使用的情况下,例如在服务器端接受请求的入口处,设置一个MDC信息,如果服务器这个程序任务的程序结束后会被丢弃,那么也不会出现什么问题,但是如果是一个线程池,池的线程会被重复利用的情况下,如果你没有结束后清楚MDC的信息,那么在下次设置之前,上次设置以后的这段日志,就会出现数据错乱的情况。

解决方法

在log4j 1.2.16以后的版本,MDC直接提供了clear() 方法即:

1
MDC.clear()

或者手动清理MDC

1
2
MDC.getContext().clear();
// 需要校验一下MDC.getContext() 不为null

tip

当我们在项目中使用 ThreadLocal 时,在一个线程结束时也需要清空 ThreadLocal中的数据,否则也可能会出现上述情况。

MySQL 安装

Windons 下MySQL的安装方式一般分为两种,一种基于 Exe 可执行文件的安装,另外一种是解压安装。

解压安装

将下载下来的mysql进行zip解压。

复制 my-default.ini 并改名为 my.ini,新建 data 和 log 两个文件夹看一个保存数据,一个保存log。

修改 my.ini 下的文件内容

执行 mysqld.exe install mysql 将mysql注册到window service 中

执行 mysqld --initialize --console 初始化mysql data,并在控制台输出默认密码

执行 net start mysql 启动mysql

使用初始密码进入mysql

执行 set password = PASSWorD("Admin123456") 修改 mysql root 用户的密码

GC 优化

JVM监控可以使用以下衡量标准

  1. 总内存使用情况(MB):即JVM使用的总内存。如果JVM使用了所有可用内存,这项指标可以衡量底层操作系统的整体性能。
  2. 堆内存使用(MB):即JVM为运行的Java应用所使用的对象分配的所有内存。不使用的对象通常会被垃圾回收器从堆中移除。所以,如果这个指数增大,表示你的应用没有把不使用的对象移除或者你需要更好的配置垃圾回收器的参数。
  3. 非堆内存的使用(MB):即为方法区和代码缓存分配的所有内存。方法区是用于存储被加载的类的引用,如果这些引用没有被适当的清理,永生代池会在每次应用被重新部署的时候都会增大,导致非堆的内存泄露。这个指标也可能指示了线程创建的泄露。
  4. 池内总内存(MB):即JVM所分配的所有变量内存池的内存和(即除了代码缓存区外的所有内存和)。这个指标能够让你明确你的应用在JVM过载前所能使用的总内存。
  5. 线程:即所有有效线程数。举个例子,在Tomcat服务器中每个请求都是一个独立的线程来处理,所以这个衡量指标可以表示当前有多少个请求数,是否影响到了后台低权限的线程的运行。
  6. 类:即所有被加载的类的总数。如果你的应用动态的创建很多类,这可能是内存泄露的一个原因。

GC 优化的目的:

  1. 将老年代的对象数量将至最低
  2. 减少Full GC 的执行时间

老年代GC相对于新生代GC更耗时

Full GC 的执行时间币 Minor GC 要长的多

老年代空间过小会导致 Full GC 频率增加或者 内存溢出
老年代空间过大会导致 Full GC 时间过长

GC 监控工具 jstat 和 HPJMeter

1
jstat -gcutil
1
2
3
4
jstat -gccapacity # 检查内存用量情况

NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
212992.0 212992.0 212992.0 21248.0 21248.0 170496.0 1884160.0 1884160.0 1884160.0 1884160.0 262144.0 262144.0 262144.0 262144.0 54 5

堆内存分配

1
-Xms -Xmx

非堆内存分配
永久保存的区域, 用于存放Class和Meta信息,Class在被Load的时候被放入该区域。

三种内存溢出异常

  1. OutOfMemoryError: Java heap space 堆溢出
    内存溢出主要存在的问题就是出现在这个情况中。当在JVM中如果98%的时间是用于GS切可以用的Heap size 不足2%的时候将抛出此异常信息
  2. OutOfMemoryError: PermGen space 非堆溢出(永久保存区域溢出)
    这种错误常见在web服务器对jsp进行 pre compile 的时候。如果你的WEB APP 下毒用了大量第三方jar,其大小超过了jvm默认的大小(4M) 那么就会产生此错误信息。 如果web app 用了大量的第三方jar或者应用有太多的class文件而恰好MaxPermSize设置较小,超出了也会导致这块内存的占用过多造成溢出,或者 tomcat 热部署时不清除前面加载的环境,只会将 context 更改为新部署的,非堆存的内容就越来越多。
  3. OutOfMemoryError: unable to create new native thread 无法创建新的线程
    这种现象比较少见,也比较奇怪,主要和jvm与系统内存的比例有关,这种怪事是因为jvm已经被系统分配了大量的内存,并且它至少要占用可用内存的一半。

Java Heap 分为3个区:

  1. Young
  2. Old
  3. Permanent

Jvm 有2个GC线程:
第一个线程负责回收Heap的Young区
第二个线程在Heap不足时,遍历Heap,将Young区升级为Older区,Older区的大小等于 -Xmx 减去 -Xmn,不能将-Xms的值设的过大,因为第二个线程被迫运行会降低 JVM 的性能

GC 优化的经验之谈

  1. JVM 最好将 -Xms 和 -Xmx 设为相同的值。为了优化GC,最好让 -Xmn值约等于-Xmx 的 1/3 .
  2. 一个 GUI 程序最好是每 10到20秒间执行一个GC,每次在半秒之内完成

GC 分析

1
2
3
4
5
6
7
8

ps -ef|grep java #获取进程号

jstat -gc 进程号

# 间隔固定时间打印
jstat -gc 进程号 2000 20 # 每隔2000ms输出该进程的gc情况,一共输出20此

GC 参数

  • -XX:+PrintGC 输出GC日志
  • -XX:+PrintGCDetails 输出GC的详细日志
  • -XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
  • -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如年月日)
  • -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
  • -Xloggc:../logs/gc.log 日志文件的输出路径

Tomcat 设置示例

1
2
3
4
5
6
JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4
-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log
-Djava.awt.headless=true
-XX:+PrintGCTimeStamps -XX:+PrintGCDetails
-Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000
-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"

一个 Linux 文件就是一个 m个字节的序列。
所有的I/O设备(例如网络、磁盘和终端)都被模型化为文件,而所有的输入和输出都被当作对相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Liunx内核引出一个简单、低级的应用接口,称为 Unix I/O ,这使得所有的输入和输出都能以一种统一且一致的方式来执行:

  • 打开文件。一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个I/O设备。内核返回一个小的非负整数,叫做描述符,它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需要记住这个描述符。
  • Liunx shell 创建的每个进程开始时都有三个打开的文件:标准输入(描述符为0)、标准输出(描述符为1)和标准错误(描述符为2)。
  • 改变当前文件的文件位置。对于每个打开的文件,内核保持这一个文件位置k,初始为0.这个文件位置是从文件开头开始的字节偏移量。应用程序能够通过执行 seek操作,显式地设置文件的当前位置为k。
  • 读写文件。一个读操作就是从文件复制 n>0 个字节到内存。从当前文件位置k开始,然后将k添加到k+n。给定一个大小为m字节的文件,当k>=m 时执行行读写操作会触发一个称为 end-of-file(EOF)的条件,应用程序能检测到这个条件。在文件结尾处并没有明确的”EOF符号”。
    类似地,写操作就是从内存复制 n>0 个字节到一个文件,从当前文件位置k开始,然后更新k。
  • 关闭文件。当应用完成了对文件的访问之后,它就通知内核关闭这个文件。作为响应,内核释放文件打开时创建的数据结构,并将这个描述符恢复到可用的描述符池中。无论一个进程因为何中原因终止时,内核都会关闭所有打开的文件并释放它们的内存资源。

每个Liunx文件都有一个类型(type)来表明它在系统中的角色:

  • 普通文件
  • 目录
  • 套接字(socket) 是用来与另外一个进行跨网络通信的文件。
  • 命名通道
  • 符号链接
  • 字符
  • 块设备

存储单位

二进制序列用以表示计算机、电子信息数据容量的量纲,基本单位为字节B,字节向上分别为KB、MB、GB、TB,每级为前一级的1024倍,比如1KB=1024B,1M=1024KB.
位 bit(比特):存放一个二进制数,即0或1,最小的存储单位。英文缩写:b(固定小写)
字节byte:8个二进制位为一个字节(B),最常用的单位。

Nginx负载均衡策略

轮询(默认)

每个web请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。

1
2
3
4
upstream nginxDemo{
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}

最少链接

web 请求会被转发到连接数最少的服务器上。

1
2
3
least_conn;
server 127.0.0.1:8081;
server 127.0.0.1:8082;

weight 权重

指定轮询几率,weight和访问比率成正比,weight默认是1。
服务器A和服务器B的访问比例为:2-1,比如有3个请求,前两个会访问A,第三个访问B,其他的规则和轮询一样。

1
2
3
4
upstream nginxDemo{
server 127.0.0.1:8081 weight=2;
server 127.0.0.1:8082;
}

ip_hash

每个请求按访问ip的hash值分配,这样客户端连续的Web请求都会被分配到同一服务器进行处理,可以解决session的问题。当后台服务器宕机时,会自动跳转到其他服务器。

1
2
3
4
5
upstream nginxDemo{
ip_hash;
server 127.0.0.1:8081 weight=2;
server 127.0.0.1:8082 ;
}

基于weight的负载均衡和基于 ip_hash 的负载均衡器可以组合在一起使用。

url_hash(第三方)

url_hash 是nginx的第三方模块,nginx本身不支持,需要引入第三方组件。
nginx 按访问 url 的hash 结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存服务器,文件服务器,静态资源服务器时比较有效。
缺点:当后端服务器宕机时,url_hash 不会自动跳转到其他缓存服务器,而返回给用于一个503错误。

1
2
3
4
5
upstream nginxDemo{
server 127.0.0.1:8081;
server 127.0.0.1:8082;
hash $request_url
}

fair(第三方)

按后端服务器的响应时间来分配请求,响应时间短的优先分配

1
2
3
4
5
upstream nginxDemo{
server 127.0.0.1:8081;
server 127.0.0.1:8082;
fair;
}

负载均衡后 Session 管理

  1. tomcat
    tomcat 本身已支持该功能。但这种处理方式,不建议在大的集群中使用。
    tomcat 的会话复制为两种:

    1
    2
    3
    <Manager className="org.apache.catalina.ha.session.BackupManager" expireSessionsOnshutdown="false" notifyListenersOnReplication="true" mapSendOptions="6">

    <Manager className="org.apache.catalina.ha.session.DeltaManager" expireSessionsOnshutdown="false" notifyListenersOnReplication="true" mapSendOptions="6">

    全局复制(DeltaManager):复制会话中的变更信息到集群中的所有其他节点。
    非全局复制(BackupManger):它会吧session复制给一个指定的备份节点。

  2. Session 共享
    session共享的实现方式有很多种,比人们 memcached,Redis,DB等。其核心思想是修改 tomcat 的session存储机制,使之能够session序列化,然后存放到memcached中。
    实现方式: Tomcat+Nginx+MSM+memcached

    1. 相关jar包放到tomcat/lib目录下:
      Java memcached客户端:spymemcached.jar
      MSM:

      1. 核心包,memcached-session-manager-{version}.jar
      2. Tomcat版本对应的jar包:memcached-session-manager-tc{tomcat-version}-{version}.jar
        序列化工具包:可选kryo(据说效率比较快),javolution,xstream等,不设置时使用jdk默认序列化。
    2. Tomcat文件tomcatconfcontext.xml添加:

      • 黏性处理方式:

        1
        2
        3
        <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:192.168.2.61:11211,n2:192.168.2.66:11211" requestUriIgnorePattern=".*.(ico|png|gif|jpg|css|js)$"
        transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
        />
      • 非黏性处理方式:

        1
        <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:192.168.2.61:11211,n2:192.168.2.66:11211" sticky="false" lockingMode="auto" requestUriIgnorePattern=".*.(ico|png|gif|jpg|css|js)$" sessionBackupAsync= "false" sessionBackupTimeout= "100" transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"/>

参考资料

Nginx+Tomcat 配置负载均衡集群