Coder Social home page Coder Social logo

johnnian / blog Goto Github PK

View Code? Open in Web Editor NEW
208.0 208.0 59.0 19.81 MB

MyBlog

License: Mozilla Public License 2.0

Java 0.42% Objective-C 8.16% C++ 91.28% HTML 0.01% C 0.01% Python 0.12%
docker java java-web mongodb nginx redis-cluster spring-boot spring-cloud web

blog's Introduction

如果您Fork本仓库,请先阅读:如何基于Github Issues与Github Actions写技术博客?

词云
AI [1篇]
Apps [1篇]
Blog [1篇]
Databases [6篇]
Docker [6篇]
Guitar [1篇]
Java Web [11篇]
keras [1篇]
Linux [13篇]
MongoDB [1篇]
MySQL [1篇]
Python [4篇]
RabbitMQ [1篇]
Redis [1篇]
SpringBoot [3篇]
Storm [3篇]
Tools [17篇]
区块链 [1篇]
大数据 [4篇]
机器学习 [1篇]
读书笔记 [1篇]

blog's People

Contributors

johnnian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

技术关键字(大数据、架构、网络相关)

平时在流浪大数据以及产品架构的时候,出现很多技术关键字,先记录下:

未分类的关键字

  • elastic
  • zeppelin
  • kylin
  • canal
  • codis
  • S3
  • Apache Beam
  • Avro 格式
  • Kanban
  • Google Cloud Data Flow
  • StreamSets
  • Apache NiFi
  • Druid
  • LinkedIn WhereHows
  • Microsoft Cognitive Services
  • Thirft
  • gRpc
  • protobuf
  • jenkins

1、计算框架

实时计算

  • Storm
  • JStorm
  • Spark Streaming
  • Flink
  • Samza
  • S4
  • Heron

批处理/离线计算

  • Hadoop
  • Spark

2、大数据工具

  • MapReduce
  • Pig
  • Sqoop
  • ZooKeeper

3、消息队列

  • Kafka
  • RabbitMQ
  • ActiveMQ

4、日志采集

  • Flume-NG

5、 存储

  • Hive
  • HBase
  • HDFS
  • ES
  • Redis
  • MongoDB
  • MySQL

6、虚拟化

  • Docker
  • Kubernetes

7、SQL 引擎

  • Streaming CQL

8、发行版本

  • CDH(Cloudera)

9、负载均衡

  • Nginx
  • Nginx + Lua
  • LVS
  • Keepalived
  • haproxy

10、运维工具

  • puppet
  • ansible
  • monit

11、人工智能

  • TensorFlow

Storm实战—基本概念

基础概念

storm-flow

1). Topologies
Storm 运行任务的逻辑单元,由 spouts、bolts 构成的有向图。

2). Tuples
Storm中的基础数据结构,可以包含下面数据结构:integers, longs, shorts, bytes, strings, doubles, floats, booleans, and byte arrays, 此外,可以通过序列化(serializers )实现自定义类型的支持;

3). Spouts
数据源,从外部读取数据,传递到 Topologies 内部;

4). Bolts
数据处理单元,Topologies中所有处理都在此进行;

5). Streams
由一组 tuples 构成,Storm的数据流

6). Stream groupings
决定Stream如何分发到bolts,每一种组别对应一种数据传递的策略,目前,Storm中内置了 8 种分组策略:

  • Shuffle grouping: 随机均分Steam,各个 bolts 得到相同数额的数据流;
  • Fields grouping:根据指定的 Fileds 进行定向分发;
  • Partial Key grouping: 和 Fields grouping 类似,在下发的bolts中间进行负载均衡;
  • All grouping: Stream 会逐一复制到下发的 bolts 中进行处理;
  • Global grouping: 所有的Stream向同一个 bolts 传递;
  • None grouping: 目前和Shuffle grouping类似;
  • Direct grouping: Stream传递给指定的bolts;
  • Local or shuffle grouping: 优先传递给正在运行中的bolts,如果没有正在运行的bolts,则按照随机的方式分发;

7). Reliability

Storm保证每个数据流在 topology 中会被完全的传递和处理;

8). Tasks & Workers

Topologies 运行多个工作线程,所有的有作线程均分执行task。

参考

Zookeeper安装部署(单点/集群)

Zookeeper 是个分布式开源框架,之前在做分布式日志收集的时候,就使用到,Zookeeper搭建比较简单。

下载

目前最新稳定版本:3.4.10, 下载地址

进入 stable 目录下载:

1

下载后解压:

[root@1c271ed316ca ~]#  tar zxvf zookeeper-3.4.10.tar.gz 

单点模式启动

使用默认配置,启动监听端口:2181

[root@1c271ed316ca ~]#  cd zookeeper-3.4.10
[root@1c271ed316ca zookeeper-3.4.10 ]#  cp conf/zoo_sample.cfg conf/zoo.cfg
[root@1c271ed316ca zookeeper-3.4.10 ]#  bin/zkServer.sh start

默认情况下,zkServer.sh 加载 ../conf/zoo.cfg 配置文件,配置文件主要有下面内容:

可以修改 dataDir 的目录,Zookeeper暴露给客户端的端口。

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/tmp/zookeeper
# the port at which the clients will connect
clientPort=2181

集群模式

步骤1:配置文件增加节点信息
分别在不同的节点A、B、C机器上下载Zookeeper,并且解压,在默认配置文件基础上,添加下面的节点信息:

server.1=xx.xx.xx.xx:2888:3888 # A节点IP
server.2=xx.xx.xx.xx:2888:3888 # B节点IP
server.2=xx.xx.xx.xx:2888:3888 # C节点IP

说明:

  1. 2888 端口: Zookeeper 各个节点之间的通信端口;
  2. 3888端口: Zookeeper 选择Leader的端口;
  3. 这些端口可以自行修改,需要指定好 当前节点的ID(myid)即可,Zookeeper在启动服务后,会开启当前节点的端口配置;

步骤2:配置当前节点的ID

[root@1c271ed316ca ~]# echo "X"  > /tmp/zookeeper/myid

注意:

  1. "X" 改为当前节点的编号,对应于配置文件中 server.X
  2. /tmp/zookeeper 改为实际 Zookeeper 配置文件中的 dataDir 路径;

Zookeeper 服务启动的时候,会在 Zookeeper DataDir 目录查找 myid文件,里面的数字即表示当前的节点是 server.X.

步骤3:各个节点分别启动服务

[root@A节点 zookeeper-3.4.10 ]#  bin/zkServer.sh start
[root@B节点 zookeeper-3.4.10 ]#  bin/zkServer.sh start
[root@C节点 zookeeper-3.4.10 ]#  bin/zkServer.sh start

参考链接

CentOS开机启动脚本

CentOS下,开机启动脚本,可以通过配置/etc/rc.d/rc.local来设定

要配置开机自启动,先执行:

chmod +x /etc/rc.d/rc.local

1. 配置:/etc/rc.d/rc.local

[root@localhost ~]$ vi /etc/rc.d/rc.local 
source /etc/bashrc
<你的初始化脚本>

如果需要以普通用户的权限运行脚本,这个时候,可以这样配置(示例),可以替换成具体的用户:

[root@localhost ~]$ vi /etc/rc.d/rc.local 
source /etc/bashrc

#需要先切换到对应目录,否则脚本在写日志的情况下,会报错:permission deny
cd /home/johnnian/demo  #替换成实际的脚本路径目录
su johnnian -c "/home/johnnian/demo/start.sh"  #替换成实际的用户、脚本路径

2. 开机启动脚本的调试

如果发现开机启动脚本有异常,或是服务没有正常启动,可以打开调试的开关、日志:

[root@localhost ~]$ vi /etc/rc.d/rc.local 
#在 #!/bin/sh 后面加上下面的配置

# send stderr from rc.local to a log file
exec 2> /tmp/rc.local.log      

# send stdout to the same log file
exec 1>&2

# tell sh to display commands before execution
set -x            

备注:

  1. 如果发现还是不会执行/etc/rc.d/rc.local, 请确认下该脚本是否有执行的权限;
  2. 如果没有该脚本,可以自己创建一个,或者安装ssh客户端,安装完成后会自动创建,yum -y install openssh-clients;

附录

生产环境部署Redis Cluster集群

软件版本: redis版本3.2.9(官方稳定版本)

说明:redis对应的包,是我在CentOS 6.8上编译过,并且编写了一些脚本,方便快速配置Redis Cluster。
该包已经开源,感兴趣的可以点击前往:EasyRedisCluster

在生产的服务器上,使用3台服务器,每台服务器开启两个Redis 实例,组成Redis Cluster:

qq20170707-211234 2x

下面是 Redis Cluster 的安装步骤:

步骤1:拷贝安装包到 home 目录

1、拷贝 redis_cluster.tar.gz 到 home目录;

2、解压到 home 目录:

[root@ec7e56056c01 ~]# tar -zxvf redis_cluster.tar.gz

3、添加到环境变量

[root@ec7e56056c01 bin]# vi /etc/bashrc

#Redis Cluster config
REDIS_CLUSTER_HOME=/root/redis_cluster
PATH=$REDIS_CLUSTER_HOME/bin:$PATH
export PATH REDIS_CLUSTER_HOME

[root@ec7e56056c01 bin]# source /etc/bashrc

步骤2:安装 Ruby 环境

[root@ec7e56056c01 ~]# yum -y install ruby
[root@ec7e56056c01 ~]# yum -y install rubygems
[root@ec7e56056c01 ~]# gem install redis  #这一步安装会慢一些

步骤3:配置集群

1、修改每个节点的配置信息
注:假设需要在同一台机子上部署更多节点,可以复制一份为nodeX,修改对应的端口,nodes.conf 位置。

[root@ec7e56056c01 ~]# vi redis_cluster/node1/redis.conf
port 7000
cluster-enabled yes
cluster-config-file /root/redis_cluster/node1/nodes.conf
cluster-node-timeout 5000
appendonly yes
bind <内网IP>

[root@ec7e56056c01 ~]# vi redis_cluster/node2/redis.conf
port 7001
cluster-enabled yes
cluster-config-file /root/redis_cluster/node2/nodes.conf
cluster-node-timeout 5000
appendonly yes
bind <内网IP>

配置文件的 bind 默认是 bind 本机,因为在局域网部署集群,设置成 局域网的本机IP,如 172.17.0.1

2、设置服务器的防火墙端口

在生产情况下,服务器的防火墙是开着的,因此需要设置防火墙端口,让集群中的节点间可以彼此通讯。

上面的配置中,把redis的端口设置成 7000,因此防火墙需要开通 7000 对外端口。

但是在实际部署的过程中发现,单单开 7000 端口,无法顺利搭建集群,发现集群脚本一直处于等待状态...

组建集群报错

看到这个,想到平时在自己本地用Docker搭建,都是秒速创建完成,于是推测: 应该是防火墙没开某个端口。

查了一些资料,发现, Redis 集群的搭建,默认情况下会另外开一个端口用于节点之间通讯( 新端口号 = 开通的端口 +10000,即 我原先的端口是7000, 节点之间通讯的端口就是 17000),开通之后,集群就搭建成功了~

下面是配置防火墙的命令:

[root@c43bfab6f744 ~] vi /etc/sysconfig/iptables
# 在 -A INPUT -i lo -j ACCEPT 后添加
-A INPUT -m state --state NEW -m tcp -p tcp --dport 7000 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 7001 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 17000 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 17001 -j ACCEPT

[root@c43bfab6f744 ~] service iptables restart

3、分别启动每个节点的Redis
注:请根据实际的节点,修改下 start.sh 的内容

[root@ec7e56056c01 ~]# ./redis_cluster/start.sh

4、选择任意一个节点,配置集群启动脚本信息,并且执行脚本
注:将下面的IP改为真实的内网IP地址

[root@ec7e56056c01 ~]# vi redis_cluster/first_init_cluster.sh
#!/bin/sh
redis-trib.rb create --replicas 1 172.17.0.1:7000 172.17.0.1:7001 172.17.0.2:7002 172.17.0.2:7003 172.17.0.3:7004 172.17.0.3:7005

[root@ec7e56056c01 ~]# ./redis_cluster/first_init_cluster.sh

Docker固定IP设置

经常用Docker模拟项目在生产环境中的部署,往往需要同时开好几台Docker容器,而且有时安装的软件需要绑定Docker局域网中的其他容器,如 MongoDB 副本集部署的时候,就需要绑定其他容器的内网IP。

但是,Docker 每次重启后,容器的IP地址会变化,查询了资料,Docker是支持设置固定IP的。

Docker 默认网络

Docker安装后,默认会创建下面三种网络类型:

$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
9781b1f585ae        bridge              bridge              local
1252da701e55        host                host                local
237ea3d5cfbf        none                null                local

启动 Docker的时候,用 --network 参数,可以指定网络类型,如:

➜  ~ docker run -itd --name test1 --network bridge --ip 172.17.0.10 centos:latest /bin/bash

bridge:桥接网络

默认情况下启动的Docker容器,都是使用 bridge,Docker安装时创建的桥接网络,每次Docker容器重启时,会按照顺序获取对应的IP地址,这个就导致重启下,Docker的IP地址就变了

none:无指定网络

使用 --network=none ,docker 容器就不会分配局域网的IP

host: 主机网络

使用 --network=host,此时,Docker 容器的网络会附属在主机上,两者是互通的。
例如,在容器中运行一个Web服务,监听8080端口,则主机的8080端口就会自动映射到容器中。


创建自定义网络:(设置固定IP)

启动Docker容器的时候,使用默认的网络是不支持指派固定IP的,如下:

➜  ~ docker run -itd  --network bridge --ip 172.17.0.10 centos:latest /bin/bash
6eb1f228cf308d1c60db30093c126acbfd0cb21d76cb448c678bab0f1a7c0df6
docker: Error response from daemon: User specified IP address is supported on user defined networks only.

因此,需要创建自定义网络,下面是具体的步骤:

步骤1: 创建自定义网络

创建自定义网络,并且指定网段:172.18.0.0/16

➜  ~ docker network create --subnet=172.18.0.0/16 mynetwork
➜  ~ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
9781b1f585ae        bridge              bridge              local
1252da701e55        host                host                local
4f11ae9c85de        mynetwork           bridge              local
237ea3d5cfbf        none                null                local

步骤2: 创建Docker容器

➜  ~ docker run -itd --name networkTest1 --network mynetwork --ip 172.18.0.2 centos:latest /bin/bash 

这个时候,创建的Docker容器就会持有 172.18.0.2 这个IP.

[root@ec8e31938fe7 /]# ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:12:00:02
          inet addr:172.18.0.2  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::42:acff:fe12:2/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:88 errors:0 dropped:0 overruns:0 frame:0
          TX packets:14 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:4056 (3.9 KiB)  TX bytes:1068 (1.0 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

链接

Supervisor 安装使用

之前在生产环境部署Web服务,使用 nohup来启动服务,但是nohup在异常崩溃的情况下,无法重启服务,找到 Supervisor , 可以实现后台守护进程的方式运行服务。

一、介绍

Supervisor是比较常用的进程管理工具,支持 Linux/MacOS平台,可以用来控制一组Linux/Unix进程(启动、重启、kill)等,使用Supervisor管理的进程,可以做到以守护进程的方式运行,服务异常关闭后可以自动重启。

Supervisor 有下面几个组件:

  • supervisord: Supervisor的服务端程序,使用前,需要先启动该组件;
  • supervisorctl: Supervisor的客户端程序,用来实际控制子进程(自定义的服务、程序);

Supervisor 通过配置文件,还可以启动Web控制台,通过Web页面来管理子进程;

二、安装

环境:
CentOS : 6/7
Python : 2.X

安装方法1:使用Python的Setuptools 软件包来安装:

[root@4fff02d62bba ~]# yum -y install python-setuptools
[root@4fff02d62bba ~]# easy_install supervisor
[root@4fff02d62bba ~]# supervisord -v
3.3.3

如果需要使用非ROOT用户安装,则参考:

export PYTHONPATH=$PYTHONPATH:/home/yoursiteuser/bin
easy_install --install-dir=/home/yoursiteuser/bin supervisor

安装方法2:使用yum安装

yum install epel-release
yum install -y supervisor
systemctl enable supervisord # 开机自启动
systemctl start supervisord # 启动supervisord服务

三、配置 & 启动服务

拷贝默认配置文件

[root@4fff02d62bba ~]# mkdir /etc/supervisor
[root@4fff02d62bba ~]# echo_supervisord_conf > /etc/supervisord.conf
[root@4fff02d62bba ~]# vi /etc/supervisord.conf
;修改下面路径
[unix_http_server]
file=/etc/supervisor/supervisor.sock   ; the path to the socket file

;修改下面路径
[supervisord]
logfile=/etc/supervisor/supervisord.log ; main log file; default $CWD/supervisord.log
pidfile=/etc/supervisor/supervisord.pid ; supervisord pidfile; default supervisord.pid

;修改下面路径
[supervisorctl]
serverurl=unix:///etc/supervisor/supervisor.sock ; use a unix:// URL  for a unix socket

;修改include配置,去除注释
[include]
files = /etc/supervisor/*.conf

[root@4fff02d62bba ~]# supervisord -c /etc/supervisord.conf
[root@4fff02d62bba ~]# ps -ef | grep supervisord
root       202     0  0 03:24 ?        00:00:00 /usr/bin/python /usr/bin/supervisord -c /etc/supervisord.conf

四、配置应用或服务器进程

编写应用启动脚本:启动脚本还有其他的选项配置,点击查看官网文档

[root@4fff02d62bba ~]# vi /etc/supervisor/storm.conf
[program:storm]
directory=/root
command=storm supervisor
autostart=true
autorestart=true

配置文件立即生效:

#(修改的配置文件生效,设置`autostart=true`的程序,会自动启动)
[root@4fff02d62bba ~]# supervisorctl update
Restarted supervisord

启动或关闭应用:

#启动全部应用
[root@4fff02d62bba ~]# supervisorctl start all
#关闭全部应用
[root@4fff02d62bba ~]# supervisorctl stop all
#重启全部应用
[root@4fff02d62bba ~]# supervisorctl restart all

启动多个同一进程:

PS: 同一个应用程序,启动多个进程,默认情况下,这些进程都归属于同一个组别,例如下面,test应用启动了2个进程,则这两个进程就归属于 test

[root@ 4fff02d62bba ~]# vi /etc/supervisor/test.conf
[program:test]
directory=/root
command=/root/test
numprocs=2
process_name=%(program_name)s_%(process_num)s
autostart=true
autorestart=true

#查看后台进程:
[root@ 4fff02d62bba ~]# supervisorctl status
test:test_0                        RUNNING   pid 81, uptime 0:00:02
test:test_1                        RUNNING   pid 80, uptime 0:00:02

启动或者关闭一组进程:

[root@ 4fff02d62bba ~]# vi /etc/supervisor/test.conf
[group:foo]
programs=bar,baz
priority=999

#启动组别进程的命令
[root@75d336b8c2cf log]# supervisorctl help start
start <name>		Start a process
start <gname>:*		Start all processes in a group
start <name> <name>	Start multiple processes or groups
start all		Start all processes

[root@75d336b8c2cf log]# supervisorctl start foo:
fazoo:bar: started
foo:b: started

#关闭组别进程的命令
[root@75d336b8c2cf log]# supervisorctl help stop
stop <name>		Stop a process
stop <gname>:*		Stop all processes in a group
stop <name> <name>	Stop multiple processes or groups
stop all		Stop all processes

[root@75d336b8c2cf ~]# supervisorctl stop foo:
fazoo:bar: stopped
foo:b: stopped

以其他用户角色启动进程:

[root@ 4fff02d62bba ~]# vi /etc/supervisor/test.conf
[program:test]
user=test
...

备注:

  • supervisorctl 还有其他命令,点击这里查看官网文档
  • supervisorctl 命令的默认配置文件是:/etc/supervisord.conf
  • 除了上述的配置外,还可以设置输出的日志信息等,具体,可以参考官网。

五、常见问题

1、使用普通用户控制supervisor

假设普通用户名为:testuser

  • 步骤1:使用root用户安装supervisor
  • 步骤2:创建普通用户组
groupadd supervisor
usermod -a testuser -G supervisor
  • 步骤3:修改以下配置文件
[testuser@4fff02d62bba ~]# vi /path/to/supervisord.conf
;修改下面路径
[unix_http_server]
file=/etc/supervisor/supervisor.sock   ; the path to the socket file
chmod=0766                 ; socket file mode (default 0700)
chown= testuser:supervisor     ; socket file uid:gid owner

删除默认路径下的: /etc/supervisord.conf

  • 步骤4:使用普通用户启动supervisord
supervisord -c  /path/to/supervisord.conf

参考链接

MongoDB 副本集部署-3.6版本

操作系统: CentOS 6.9
安装软件包:mongodb-linux-x86_64-rhel62-3.6.2.tgz
MongoDB版本:3.6

安装流程思路

目前最小节点是3个,本文档暂且按照3个节点来配置:A、B、C
1、在集群每个节点上安装一份MongoDB;
2、配置副本集;
3、配置副本集的用户、密码;
4、配置副本集的KeyFile安全鉴权;
5、配置开机自启动;

每个节点IP:

  • A节点: 172.17.0.3
  • B节点: 172.17.0.4
  • C节点: 172.17.0.5

步骤1: 每个节点安装MongoDB

1、下载MongoDB安装包:mongodb-linux-x86_64-3.4.1.tgz

[root@a6a766e6204a ~]# wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel62-3.6.2.tgz

2、解压安装包, 创建目录,拷贝配置文件:

[root@a6a766e6204a ~]# tar -zxvf mongodb-linux-x86_64-rhel62-3.6.2.tgz
[root@a6a766e6204a ~]# mv mongodb-linux-x86_64-rhel62-3.6.2 mongodb
[root@a6a766e6204a ~]# mv mongo.conf mongodb/
[root@a6a766e6204a ~]# mkdir mongodb/data mongodb/keyfile mongodb/logs

创建完成后,目录的结构:
mongodb
├── bin #可执行文件
├── data #存放数据库文件
├── keyfile #存放Keyfile
├── logs #存放系统日志
├── mongo.conf #配置文件

3、添加到环境变量

[root@a6a766e6204a ~]# vi /etc/bashrc
#MongoDB config
MONGO_HOME=/root/mongodb
PATH=$MONGO_HOME/bin:$PATH
export PATH MONGO_HOME

[root@a6a766e6204a ~]# source /etc/bashrc

4、创建 mongo.conf 配置项,包括端口、路径等, 例如,配置后的文件:

[root@a6a766e6204a ~]# vi mongodb/mongo.conf

#日志文件位置:改为实际路径
logpath=/root/mongodb/logs/mongo.log
#以追加的方式写日志
logappend=true
#端口
port=27018
#是否以守护进程的方式运行
fork=true
#数据库存储位置:改为实际路径
dbpath=/root/mongodb/data/
#是否以安装认证方式运行
#auth=true
#副本集名字
replSet=replSet
#KeyFile鉴权文件:改为实际路径
#keyFile=/root/mongodb/keyfile
#最大缓存大小,根据实际情况而定
wiredTigerCacheSizeGB=8

谈到配置文件,MongoDB有下面两种配置:

下面的配置与上面INI形式配置是一样的:

systemLog:
   destination: file
   path: "/root/mongodb/logs/mongo.log"
   logAppend: true
storage:
   dbPath:"/root/mongodb/data/"
   journal:
      enabled: true
   wiredTiger:
      engineConfig:
         cacheSizeGB: 6
replication:
    replSetName: "replSet"
processManagement:
   fork: true
net:
   bindIp: 0.0.0.0
   port: 27018
setParameter:
   enableLocalhostAuthBypass: false

5、分别启动每个节点:A、B、C节点

[root@a6a766e6204a ~]# mongod -f mongodb/mongo.conf
about to fork child process, waiting until server is ready for connections.
forked process: 1289
child process started successfully, parent exiting

步骤2: 组建副本集

1、使用mongo 客户端 连接任意节点(假设 目前创建的节点有 A、B、C 三台)

[root@c43bfab6f744 ~]# mongo --port 27018

连接成功后,开始配置副本集:

> config = {_id: 'replSet', members: [{_id: 0, host: '172.17.0.3:27018'},{_id: 1, host: '172.17.0.4:27018'},{_id: 2, host:'172.17.0.5:27018'}]}
{
	"_id" : "replSet",
	"members" : [
		{
			"_id" : 0,
			"host" : "172.17.0.3:27018"
		},
		{
			"_id" : 1,
			"host" : "172.17.0.4:27018"
		},
		{
			"_id" : 2,
			"host" : "172.17.0.5:27018"
		}
	]
}

> rs.initiate(config)   #初始化副本集
{ "ok" : 1 }

> rs.status()           #查看副本集状态,找到private节点的IP

步骤3:创建帐户密码

副本集搭建成功后,需要给整个副本集创建帐户、密码

1、在主节点上,用客户端连接,创建用户权限(主节点,可以用 rs.status() 查看)

[root@c43bfab6f744 ~]# mongo --port 27018
replSet:PRIMARY> use admin
switched to db admin

#创建分配用户权限的帐户:admin
replSet:PRIMARY> db.createUser({user:"admin", pwd:"admin", roles:[{role: "userAdminAnyDatabase", db:"admin" }]})
Successfully added user: {
	"user" : "admin",
	"roles" : [
		{
			"role" : "userAdminAnyDatabase",
			"db" : "admin"
		}
	]
}

#创建普通数据库、用户
replSet:PRIMARY> db.auth("admin","admin")
1

replSet:PRIMARY> use mytest  #创建mytest数据库
switched to db mytest

replSet:PRIMARY> db.createUser({user:"mytest",pwd:"mytest",roles:[{role:"dbOwner",db:"mytest"}]})
Successfully added user: {
	"user" : "mytest",
	"roles" : [
		{
			"role" : "dbOwner",
			"db" : "mytest"
		}
	]
}

这样,就创建数据库:mytest, 数据库用户:mytest / mytest, 生产部署的时候,改成对应的数据库名、用户名

可以通过连接验证下:

[root@c43bfab6f744 ~]# mongo 172.17.0.3:27018/mytest -u mytest -p mytest

步骤4:创建副本集认证key文件

1、创建key文件: 注意,三个节点必须要用同一份keyfile,在一台机器生成,拷贝到另外两台,并且修改成 600 的文件属性

[root@c43bfab6f744 ~]# openssl rand -base64 90 -out ./keyfile
[root@c43bfab6f744 ~]# cp keyfile mongodb/keyfile/
[root@c43bfab6f744 ~]# chmod 600 mongodb/keyfile/keyfile

备注:keyfile的属性必须更改,否则会报错:

2017-06-30T07:01:28.950+0000 I CONTROL  [main] ***** SERVER RESTARTED *****
2017-06-30T07:01:28.954+0000 I ACCESS   [main] permissions on /root/mongodb/keyfile/keyfile are too open

并且把这份keyfile同步到其他两个节点的 ~/mongodb/keyfile/ 文件夹中;

2、关闭副本集:分别关闭每个节点的mongod

[root@c43bfab6f744 ~]# mongo --port 27018
replSet:PRIMARY> use admin
replSet:PRIMARY> db.shutdownServer()

3、修改每个节点的配置文件mongo.conf中的下面项:

[root@c43bfab6f744 ~]# vi  ~/mongodb/mongo.conf

...
...
#是否以安装认证方式运行
auth=true
#KeyFile鉴权文件:改为实际路径
keyFile=/root/mongodb/keyfile/keyfile

4、重新启动副本集

[root@c43bfab6f744 ~]# mongod -f mongodb/mongo.conf #机器A
[root@c43bfab6f744 ~]# mongod -f mongodb/mongo.conf #机器B
[root@c43bfab6f744 ~]# mongod -f mongodb/mongo.conf #机器C

步骤5:设置MongoDB开机自启动

[root@c43bfab6f744 ~]# vi /etc/rc.d/rc.local #加入下面的自启动脚本 
/root/mongodb/bin/mongod -f /root/mongodb/mongo.conf 

步骤6: 设置MongoDB自动备份

注意:

  1. 需要连接到MongoDB的主节点;
  2. 命令中输出文件夹,% 号需要专一成 \%
[root@017d14b36363 ~]# crontab  -e
0 7 * * * mongodump --host 172.17.0.4 --port 27018 -u mytest -p mytest -d mytest -o /data/mongodata/$(date +\%Y\%m\%d)

附录

1、创建数据库的用户角色:

role角色
  • 数据库用户角色:read、readWrite;
  • 数据库管理角色:dbAdmin、dbOwner、userAdmin;
  • 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager;
  • 备份恢复角色:backup、restore;
  • 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase
  • 超级用户角色:root
  • 内部角色:__system
角色说明
  • read:允许用户读取指定数据库
  • readWrite:允许用户读写指定数据库
  • dbAdmin:允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
  • userAdmin:允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
  • clusterAdmin:只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
  • readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限
  • readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限
  • userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
  • dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
  • root:只在admin数据库中可用。超级账号,超级权限
  • dbOwner: readWrite + dbAdmin + dbAdmin

2、Java客户端连接配置

Spring XML配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mongo="http://www.springframework.org/schema/data/mongo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/data/mongo     
        http://www.springframework.org/schema/data/mongo/spring-mongo.xsd 
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- credentials="用户名:密码@用户归属数据库" -->
	<mongo:mongo-client replica-set="172.17.0.3:27018,172.17.0.4:27018, 172.17.0.5:27018" credentials="mytest:mytest@mytest"  id="mongo">
        <mongo:client-options 
            connections-per-host="20"
	        threads-allowed-to-block-for-connection-multiplier="10" 
	        connect-timeout="120000"
	        max-wait-time="120000"
	        socket-keep-alive="true"
	        socket-timeout="150000"
	         />	
	</mongo:mongo-client>

	<mongo:db-factory dbname="数据库名" mongo-ref="mongo" />

	<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
		<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
	</bean>
</beans>
Spring Boot配置
spring.data.mongodb.uri=mongodb://user:pwd@ip1:port1,ip2:port2/database

Kubernetes—简介以及部署方案

谈到容器化管理解决方案,Kubernetes 目前应该是比较成熟的,有比较多的落地实施案例。

一、Kubernetes 简介

Kubernetes 是容器自动部署、扩展与管理的开源平台,具体的介绍,可以参考链接: What is Kubernetes?

目前,Kubernetes有比较多的落地实施案例,例如 京东、SAE、网宿科技、时速云、灵雀云、网易蜂巢等等。

参考链接:

二、Kubernetes基本概念

Kubernetes 集群的节点,按照功能,可以划分为 Master Node(控制集群),Worker Node(实际工作)。

Master Node上运行的组件: Master Components + Node components
Worker Node上运行的组件: Node components

2.1 Components(组件)

Master Components

  • kube-apiserver:配置API 对象(pods, services, replicationcontrollers等),并且放出REST 接口,供其他组件进行交互
  • etcd: Kubernetes用于存储API对象的Key-Value存储中心
  • kube-controller-manager/cloud-controller-manager: 处理集群中的运行时任务,正常情况下是通过这些控制器来控制管理集群的
  • kube-scheduler:为Pod分配运行节点
  • addons(DNS、Dashboard、Monitoring、Logging等)

Node Components

  • kubelet: 节点的守护进程,最终工作的执行者
  • kube-proxy: Kubernetes集群的网络代理
  • docker / rkt: 容器
  • supervisord:轻量级的守护进程
  • fluentd: 日志收集

Master node 组件结构
master-node

Worker node 组件结构

worker-node

参考链接:

2.2 API Objects(API 对象)

Kubernetes使用 持久化的Objects对象来展现集群的状态,这些对象存储在 Kubernetes的 Etcd KeyValue存储中。

使用 kubectl命令行客户端来操作 API 对象。

API Object的文档说明,请看这里

通过创建 .yaml格式的文件,使用kubectl 命令行创建 API对象, .yaml文件的格式大致是:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

可以分为:

  • apiVersion: 需要使用哪个版本的Kubernetes来创建 API对象
  • kind: 需要创建那种类型的 API对象
  • metadata: API对象的一些标志属性,如,name, UID, namespace等
  • spec: API对象的具体属性配置

对于API对象的类型,划分:

  • Pod: Pod可以理解为应用实例特定的逻辑主机,表示一个或多个容器组和这些容器的共享资源,包共用卷、唯一的集群IP和容器运行的信息,如端口等;
  • Controller:Controller可以根据对应的策略创建或者管理Pod,如,Deployment,DaemonSet,StatefulSet等;
  • Service: Service是一组相同逻辑的pods和一个访问它们的策略;
    a. ClusterIP(默认): 只有集群内部访问;
    b. NodePort: 使用NAT方式,在集群中每个选定的节点的同一端口上暴露服务。可以在集群外部访问服务。
    c. LoadBalancer:创建外部负载均衡。
    d. ExternalName:使用任意名称显示服务。
  • Namespace: 命名空间;
  • Node: kubernetes的工作机器(物理机或虚拟机),Node由master管理,可以在一个node上部署多个pod;

2.3 Controller(控制器)

不同类型的控制器,管理Pod的策略是不同的,Controller主要分为下面几种类型:

  • Deployment: Deployments控制器是最常用的,可以用来管理和更新Pods 、ReplicaSets
  • Replication Controller
  • ReplicaSets(Replication Controller的下一个升级版本)
  • DaemonSet
  • Job
  • Cron Job
  • StatefulSets[beta]
  • Garbage collection[beta]

参考链接:

2.4 Service(服务)

Service 可以说是连接 Pods 与外界的桥梁,Service可以建立起 内部 Pod 容器的端口 <--> 外部端口 的映射关系,通过Visual IP,客户端直接连接Service的IP、端口,而无需关注后端的Pods IP地址的变化,这个概念有点儿像微服务。

Service 有下面几种类型:

  • ClusterIP(默认): 仅限于集群内部使用的服务,外部无法访问;
  • NodePort: 支持外部访问的服务,可以通过 [NodeIP]:[NodePort] 来访问对应的服务;
  • LoadBalancer:需要云服务商支持
  • ExternalName:通过域名来访问

每个Node 都会运行 kube-proxy 组件,kube-proxy 组件为Service创建虚拟IP(Visual IP),下面是整个网络代理的拓扑图:

Kubernetes V1.2版本前,网络代理的拓扑图:
qq20170905-152148 2x

Kubernetes V1.2版本后,网络代理的拓扑图:
qq20170905-152210 2x

详细说明,参考:Service

2.5 网络拓扑

Kubernetes 高可用集群拓扑图
default

三、常见的部署方案以及示例

目前市面上有很多部署和实施Kubernete的解决方案,点击这里查看, 总的来说,大致分为下面的几种:

  • Hosted Solutions:托管部署方案,例如直接用谷歌的Google Container Engine
  • Local-machine Solutions: 本地部署方案,一般用于开发测试使用,例如,使用 minikube 部署;
  • Custom Solutions: 自行定制的方案,例如,使用 kubeadm部署;

3.1、本地部署-minikube

minikube安装倒是挺简单的,就是下载镜像的时候会有问题,可以考虑:使用阿里云镜像,或者用Docker Hub 作为中转。

部署示例

操作说明

3.2 集群部署-kubeadm

参考链接

Linux Socket Exception

一、遇到的问题 & 解决方法

在项目中,出现下面的几个问题:

1、java.net.SocketException: Too many open files

Caused by: java.net.SocketException: Too many open files
	at java.net.Socket.createImpl(Socket.java:460)
	at java.net.Socket.getImpl(Socket.java:520)
	at java.net.Socket.setSoTimeout(Socket.java:1141)

排查了下,是因为系统的 "Open File" 参数比较低,默认是 1024:

[root@bb7122794dd0 ~]# ulimit -n
1024

「Open File」 这个参数,限定了 每个进程可以打开的最大文件描述符数(File Descriptors), 创建 Socket、打开文件等操作,都会影响文件描述符的值。

因此,简单的解决方法是,提升操作系统的文件描述符限值:

修改操作系统允许打开的最大 文件描述符 数:

[root@bb7122794dd0 ~]# vi /etc/sysctl.conf
#添加或修改
fs.file-max = 6553560

修改单个进程允许打开的最大 文件描述符 数:

永久改变

[root@bb7122794dd0 ~]# vi /etc/security/limits.conf
* hard nofile 65535
* soft nofile 65535

仅当前登录shell生效,退出后就失效

[root@bb7122794dd0 ~]# ulimit -n 4096

配置完成后,重启服务器即可生效。

2、java.net.SocketException: Connection reset

java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(SocketInputStream.java:209)
	at java.net.SocketInputStream.read(SocketInputStream.java:141)

原因: Client 或者 Server, 一方已经关闭Socket连接,另一方还在写数据,就会抛出改异常。也就是说,这个异常是因为Socket连接断开后的读和写操作引起的。

解决方法:目测是因为“Open File”参数低导致,也需要排查下程序,确保在异常的情况下,所有的Socket连接都要断开。

3、java.net.SocketException: Broken pipe

Caused by: java.net.SocketException: Broken pipe
	at java.net.SocketOutputStream.socketWrite0(Native Method)
	at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:109)
	at java.net.SocketOutputStream.write(SocketOutputStream.java:153)

原因:如果程序在报 java.net.SocketException: Connection reset 后,还继续读写,则抛出改异常,解决方法同上。

4、出现大量的 TIME_WAIT & CLOSE_WAIT

  • TIME_WAIT: 主动关闭连接的一方保持的状态, 通常是维护人员手动Kill掉服务器进程导致,可以通过系统参数调优,缩短等待时间;
  • CLOSE_WAIT: 由于一方在通讯异常等情况下,没有对建立的Socket进行关闭操作,就会出现CLOSE_WAIT , 需要检查对应的代码。

解决方法:

  • TIME_WAIT 优化: 参考文章最后面的链接方法;
  • CLOSE_WAIT: 检查程序代码的问题,比如出现异常的情况下,是否有关闭连接的处理等;

二、基本概念

1、File Descriptors

维基百科

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。

Linux 限制每个进程能够打开的文件描述符的数,默认是 1024,文件操作、Socket通讯、TCP通讯操作都会影响Open Files的值。

三、常用命令

  • ulimit -n : 查看每个进程的最大 Open File数
  • lsof | wc -l : 统计当前系统所有进程已经用的文件描述符数
  • lsof [PID] | wc -l : 统计具体进程已经用的文件描述符数
  • cat /proc/sys/fs/file-max : 获取系统允许打开的最大描述符数

四、参考链接

CentOS 6 配置:Keepalived + Nginx

操作系统:centos 6
服务器两台:A、B
目的:A、B 上都安装Nginx, 想要使用 keepalived 做HA

下面是具体的步骤:

步骤1: 安装Keepalived

[root@localhost ~]# yum groupinstall 'Development Tools' -y
[root@localhost ~]# yum install openssl-devel libnl-devel libnfnetlink-devel iptables-devel  -y
[root@localhost ~]# wget http://www.keepalived.org/software/keepalived-1.2.23.tar.gz
[root@localhost ~]# tar zxf keepalived-1.2.23.tar.gz && cd keepalived-1.2.23
[root@localhost ~]# ./configure --prefix=/usr --sysconfdir=/etc
[root@localhost ~]# make && make install

步骤2: 配置Keepalived

1. 查看网络信息:本机IP、网卡名字

1

现在的网络情况:

A服务器:

IP: 10.177.101.114, 网卡: eth4

B服务器:

IP: 10.177.101.115, 网卡: eth6

虚拟IP: 只要配置好Keepalived,启动后,就会自动在当前网卡上创建一个虚拟IP

IP: 10.177.101.117

2. 配置Keepalived

原理是:

A、B 服务器在Keepalived上配置同一个虚拟IP:10.177.101.117, 分别启动两台服务器的 keepalived时,会自动在对应的网卡上创建同一个虚拟IP。

服务器 IP地址 虚拟IP 权重 级别
A服务器 10.177.101.114 10.177.101.117 101 MASTER
B服务器 10.177.101.115 10.177.101.117 100 BACKUP

Keepalived 会创建路由表,根据A、B服务器的级别、权重,进行请求转发;

按照下面配置两台机器:

A机器的配置:

[root@7fde0e436c09 ~]#  vi /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
   router_id LVS_DEVEL
}

vrrp_instance VI_1 {
    state MASTER #状态设置成MASTER
    interface eth4 #A服务器网卡
    virtual_router_id 51
    priority 101  #Master设置高一点的权重
    advert_int 1
    mcast_src_ip 10.177.101.114 #这个是服务器的内网地址
    virtual_ipaddress {
        10.177.101.117 #虚拟IP
    }
}

B机器的配置:

[root@7fde0e436c09 ~]#  vi /etc/keepalived/keepalived.conf

! Configuration File for keepalived

global_defs {
   router_id LVS_DEVEL
}

vrrp_instance VI_1 {
    state BACKUP #状态设置成BACKUP
    interface eth6 #B服务器网卡
    virtual_router_id 51
    priority 100 #BACKUP设置低一点的权重
    advert_int 1
    mcast_src_ip 10.177.101.115 #这个是服务器的内网地址
    virtual_ipaddress {
        10.177.101.117 #虚拟IP
    }
}

配置完成后,两台都重启 keepalived:

[root@7fde0e436c09 ~]# /etc/init.d/keepalived restart

完成启动后,看下A、B服务器的网络信息如下:

2

3

步骤4: 配置防火墙

Keepalived 默认使用的端口是:112, 需要开启防火墙的端口

[root@c43bfab6f744 ~] vi /etc/sysconfig/iptables
#添加下面端口
-A INPUT -m state --state NEW -m tcp -p tcp --dport 112 -j ACCEPT
[root@c43bfab6f744 ~] service iptables restart

Keepalived 默认使用的端口是:112, 需要开启防火墙的端口

步骤5: 配置开机自启动

[root@localhost ~]# vi /etc/rc.d/rc.local
#在末尾添加
/etc/init.d/keepalived restart

链接

Docker容器启动后自运行脚本的配置

使用Docker 的Centos镜像(官方版本),发现 Docker在启动的时候,不会运行 /etc/rc.d/rc.local 脚本。

原因是:

Docker 的上的操作系统镜像没有 init system,而普通的虚拟机有,因为带有完整的系统,在操作系统启动的过程中,会执行 boot 的所有初始化操作,但 Docker 则不是, Docker 只运行我们设置需要启动运行的脚本,否则不会自己运行。

如果需要Docker在启动后就自动运行 /etc/rc.d/rc.local,有下面的两种方式:

方法一:通过Docker命令

docker run image /bin/bash -c "/etc/rc.d/rc.local; <your command>"

eg.

docker run -itd myimage:test /bin/bash -c "/etc/rc.d/rc.local;/bin/bash"

方法二:通过DockerFile重新构建镜像,指定启动运行的服务

Dockerfile 示例如下:

FROM centos

MAINTAINER Johnnian<[email protected]>

RUN <填写构建Docker时需要运行的命令>

ENV  MYENV /XX/XX

CMD <容器每次启动时运行的命令, 只能有一个CMD>

编写完后,运行:

docker build -t 镜像名:标签 <Dockerfile所在的目录>
# docker build -t centos:johnnian .

docker run -itd 新镜像名

附:参考现成的Dockerfile,如mysql的Dockerfile

参考链接

iOS开发入门

引言

记录下自学iOS过程中的点滴。
转载请标明原地址:#4


开发环境

iOS开发环境,不用多说,直接用Apple的Xcode, 在Mac的App Store上直接下载安装即可~


类库管理工具-CocoaPods

CocoaPods是一个非常棒的类库管理工具,并且Github上大部分的iOS开源类库均支持COcoaPods的安装方式。

在实际的开发过程中,肯定会使用到很多开源类库,如 AFNetworkingFMDB等,而这些类库又往往会依赖其他类库,因此在使用这些开源库的时候,可能会耗费我们一些时间去下载其他依赖库。

CocoaPods可以帮我们简单地管理这些第三方类库,而且可以通过配置文件的形式来设置我们项目的依赖库,为我们大大地节省了时间~

安装方法

由于Mac自带了Ruby,因此直接使用Ruby的gem命令就可以直接安装,下面是具体的安装步骤:

#步骤1: 设置Ruby的软件源,切换为国内的淘宝镜像
gem sources --remove https://rubygems.org/ 
gem sources -a http://ruby.taobao.org/ 
gem sources -l

#如果出现下面的提示,则说明设置完成
*** CURRENT SOURCES ***
http://ruby.taobao.org/

#步骤2: 安装CocoaPods
sudo gem install cocoapods

#安装完成后,可以在命令行输入 `pod` 命令来验证是否安装成功~ 

使用方法

步骤1: 搜索需要使用的开源库

方法1: 直接在命令行输入 pod 命令来搜索关键字

pod search xxxx

方法2: 谷歌、百度搜索,通常在开源库的 Github 页面都会有相应的 CocoaPods 安装方法

步骤2: 配置Podfile

在项目更目录新建名为Podfile的文件,配置文件内容:示例如下

platform :ios, '7.0'
pod "AFNetworking", "~> 2.0"

各个开源库的具体配置,可以参考相应开源库主页~

步骤3:CocoaPods下载开源库

打开命令行工具,进入项目的更目录下,执行下面的脚本:

pod install

经过若干时间的等待,开源库就下载好了~ 这个时候,项目文件夹里面也将有一些改变,多了 .xcworkspace的工程文件,在接下来的项目开发中就直接用这个文件来打开项目文件。

如果要增加货删减项目依赖的第三方开源组件,则直接更改 Podfile 配置文件,然后执行下列命令即可完成一切的变更:

pod update

步骤4:项目使用开源库的方法

通过 CocoaPods 管理第三方开源库,在下载了开源库之后,在原先项目的Frameworks中生成一个静态库,Cocoapods会将我们所需用的所有第三方类库打包成一个静态库libPods.a, 我们只需要引入第三方开源库的头文件,即可正常使用了~

pic

参考链接


常见开源项目

参考链接


入门学习资料

视频教程

注: 斯坦福大学的iOS开发公开课,堪称入门经典,如果想练习英文,建议直接看iTunes-U中的版本,也不会太难。

书籍

  • 《Objective-C.Programming》: Big.Nerd 出品,很棒的一本入门教程
  • 《Effective Objective-C 2.0》: 好书一本,类似于 《Effective C++》这本书,对于有一定开发经验的童鞋来说,应该能有挺大的帮助的。

常用网站

  • code4app:里面有挺多源代码的,可以免费下载学习
  • v2ex:iOS开发人员论坛
  • cocoachina:苹果开发者社区,有较多干货~
  • 开发者头条: 对于iOS开发有很多干货,当然里面不仅仅包括iOS开发,还有其他语言的~

相关链接


相关链接

H2数据库安装与使用(单点)

H2数据库是一款 Java 嵌入式数据库,之前在搭建分布式定时任务(Spring+Quartz)时,需要选择一款关系型数据库作为任务协调。H2数据库够轻量(就一个 Jar 包),使用起来还不错。

下面是具体安装使用的一些备忘:

安装部署

H2数据库版本: 1.4.196

1、下载与解压

[root@localhost ~]# wget http://www.h2database.com/h2-2017-06-10.zip
[root@localhost ~]# unzip h2-2017-06-10.zip
[root@localhost ~]# chmod +x h2/bin/*.sh
[root@localhost ~]# vi /h2/bin/h2.sh
#!/bin/sh
# 查看其他命令选项:java -cp h2*.jar org.h2.tools.Server -? 
nohup java -cp /root/h2/bin/h2*.jar org.h2.tools.Server -webAllowOthers -tcpAllowOthers -pgAllowOthers &

[root@localhost ~]# ./h2/bin/h2.sh

修改 /h2/bin/h2.sh ,改成使用 nohup 后台启动,同时设置成允许局域网其他主机连接。

启动H2后,会默认启动Web控制台(8082端口),可以通过Web控制台登录管理数据库。

h2-console

在生产环境,其实直接用命令行管理数据库更加方便;

2、创建数据库

导入脚本的方式创建数据库

[root@localhost ~]# cd /h2/bin
[root@localhost ~]# java -cp h2*.jar org.h2.tools.RunScript -url jdbc:h2:tcp://127.0.0.1/~/h2test -user sa -password 123abc -script schema_h2.sql -showResults;

上面的命令,做了下面几件事情:

  • 创建了数据库文件:~/h2test.mv.db
  • 创建账户:sa / 123abc
  • 导入SQL脚本

普通连接创建

[root@localhost ~]# cd /h2/bin
[root@localhost ~]# java -cp h2*.jar org.h2.tools.Shell -url jdbc:h2:tcp://127.0.0.1/~/test -user sa -password 123abc;

注意:

  • H2数据库,账户体系只和创建的数据库文件挂钩;
  • 连接到数据库, jdbc:h2:tcp://127.0.0.1/~/test, 如果数据库不存在,则创建一个新的;如果存在,则连接到原先的数据库;

3、数据库脚本交互

[root@localhost ~]# cd /h2/bin
[root@localhost ~]# java -cp h2*.jar org.h2.tools.Shell -url jdbc:h2:tcp://127.0.0.1/~/h2test -user sa -password 123abc;

Welcome to H2 Shell 1.4.196 (2017-06-10)
Exit with Ctrl+C
Commands are case insensitive; SQL statements end with ';'
help or ?      Display this help
list           Toggle result list / stack trace mode
maxwidth       Set maximum column width (default is 100)
autocommit     Enable or disable autocommit
history        Show the last 20 statements
quit or exit   Close the connection and exit

sql> 

4、客户端连接设置(Java:SpringBoot)

spring.datasource.url=jdbc:h2:tcp://127.0.0.1/~/h2test;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=123abc
spring.datasource.driver-class-name=org.h2.Driver

注意,连接配置的时候需要加上;DB_CLOSE_ON_EXIT=FALSE,否则,连接的客户端断开的时候,会直接关闭掉H2数据库。

5、参考

Docker容器—HBase安装

最近在学习HBase,为了省事,直接使用Docker镜像进行安装。

参考Github:dajobe/hbase-docker

安装具体步骤

# 下载 Hbase 镜像
$ docker pull dajobe/hbase

# 进入任意目录,根据镜像创建容器
$ mkdir data  
$ docker run --name=hbase-docker -h hbase-docker -d -v $PWD/data:/data dajobe/hbase

# 进入容器进行操作
$ docker ps -l
CONTAINER ID        IMAGE               COMMAND               CREATED             STATUS              PORTS                                                         NAMES
4acfa1bd1092        dajobe/hbase        "/opt/hbase-server"   9 minutes ago       Up 9 minutes        2181/tcp, 8080/tcp, 8085/tcp, 9090/tcp, 9095/tcp, 16010/tcp   hbase-docker

$ docker exec -it 4acfa1bd1092 hbash shell

hbase dec 4acfa1bd1092 hbase shell
2017-10-10 01:55:10,594 WARN  [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 1.2.4, r67592f3d062743907f8c5ae00dbbe1ae4f69e5af, Tue Oct 25 18:10:20 CDT 2016

hbase(main):001:0> status
1 active master, 0 backup masters, 1 servers, 0 dead, 2.0000 average load

HBase Shell 命令

Storm实战—安装部署

Storm版本:1.1.0

在搭建Storm集群前,先看下下面的拓扑图,在1.1.0版本中,支持 Nimbus的HA模式,Nimbus、Supervisor,需要使用Zookeeper作为协同。

storm_cluster

一、相关依赖

  • Zookeeper(可以使用 3.4.10 版本)
  • JDK7+
  • Python 2.6.6+

注意,如果是在MacOS系统下,则还需要安装 ZeroMQ,否则启动Storm UI的时候,会报错:
pasted graphic

可以使用 HomeBrew快速安装:

➜  ~ brew install zeromq

二、安装包下载

进入下载页面下载, 或者直接点击这里

或者直接wget:

➜  ~ wget http://apache.claz.org/storm/apache-storm-1.1.0/apache-storm-1.1.0.tar.gz
➜  ~ tar -zxvf apache-storm-1.1.0.tar.gz

拷贝安装包到每个节点,目前各个节点的信息如下:

简称 IP 角色
A 172.18.0.30 Nimbus_1
B 172.18.0.33 Supervisor_1
C 172.18.0.34 Supervisor_1

注意:

  1. 在安装配置Storm集群之前,需要先启动Zookeeper(单点或者集群)。
  2. 默认的配置文件,可以参考Storm Github上的内容,点击这里前往查看

三、Nimbus安装配置

Nimbus可以配置多台,实现HA,这里暂时就配置一台

需要对外开放的端口:

  • 6627: Nimbus 默认端口
  • 3772: DRPC 默认端口
  • 8080: Storm UI Web端口

1、配置文件

建议: Nimbus、DRPC的端口直接用默认的,无需更改

➜  ~ vi apache-storm-1.1.0/conf/storm.yaml

#配置Zookeeper
storm.zookeeper.servers:
    - "172.18.0.21"
storm.zookeeper.port: 2181
storm.zookeeper.root: "/storm"

#配置数据存储路径
storm.local.dir: "/root/apache-storm-1.1.0/data"

#配置节点健康检测
storm.health.check.dir: "healthchecks"
storm.health.check.timeout.ms: 5000

#配置Nimbus节点,如果有多个,可以填写,用逗号隔开
nimbus.seeds: ["172.18.0.30"]

#配置Storm UI
ui.port: 8080

## 配置DRPC服务
drpc.servers:
    - "172.18.0.30"

2、启动程序

➜  ~ ./apache-storm-1.1.0/bin/storm nimbus &
➜  ~ ./apache-storm-1.1.0/bin/storm ui &
➜  ~ ./apache-storm-1.1.0/bin/storm drpc &

在浏览器访问:http://172.18.0.30:8888, 查看Storm UI主页

四、Supervisor安装配置

Supervisor安装的时候,只需要指定Nimbus地址,就可以自动加入Storm集群,非常容易扩容。

1、配置文件

➜  ~ vi apache-storm-1.1.0/conf/storm.yaml

#配置Zookeeper
storm.zookeeper.servers:
    - "172.18.0.21"
storm.zookeeper.port: 2181
storm.zookeeper.root: "/storm"

#配置数据存储路径
storm.local.dir: "/root/apache-storm-1.1.0/data"

#配置节点健康检测
storm.health.check.dir: "healthchecks"
storm.health.check.timeout.ms: 5000

#配置Nimbus节点,如果有多个,可以填写,用逗号隔开
nimbus.seeds: ["172.18.0.30"]

#配置supervisor: 开启几个端口插槽,就开启几个对应的worker进程
supervisor.slots.ports:
    - 6700
    - 6701
    - 6702
    - 6703

2、启动程序

➜  ~ ./apache-storm-1.1.0/bin/storm supervisor &

四、问题

1、Storm UI 中显示的Supervisor 的个数与实际的不符:

我搭建了两个 Supervisor 节点,启动没有报错,但是Storm UI中显示的却是:

qq20170803-103823 2x

解决方法:

删除Supervisor中的 storm.local.dir 目录的数据,之前拷贝到各个节点的时候,把这个目录页拷贝过去了。如果 storm.local.dir的目录为空的话,每个Supervisor就会创建自己的ID

参考:storm ui显示supervisor个数与实际不符的解决

附录:参考

研发工具清单(持续更新)

1、文档绘图

  • showdoc: 一直在找合适的API文档编写工具(厌烦了用word),发现这款工具可以私有化部署,特别适合部署在公司内网中,还支持加密访问,可以用markdown写文档。

  • Gliffy: 画图利器,可以用来替代Visio,下载Chrome浏览器的Gliffy插件,可以离线使用,很方便。

  • Processon

  • Visio

  • Sketch

  • Pixelmator

  • Logoist

2、原型设计

  • Mockplus
  • Axure

3、思维导图

  • Xmind
  • MindNode

4、文本编辑器

  • Visual Studio Code
  • Mac Down

5、测试工具

  • JMeter: Apache的压力测试工具
  • Postman: Http测试工具
  • wrk: 类似于AB的压力测试工具,轻量级

6、Mac 软件

  • Caffine: 保持Mac不锁屏的一个工具
  • Pomorodo Timer: 番茄时钟

终端

  • Go2Shell
  • iTerm

Docker相关

  • Docker for Mac
  • Kitematic

Github相关

  • Lepton:Github的Gist管理第三方客户端,免费开源,挺好用的。在使用的时候,可以不用翻墙就能连得上Gist;

Java基础—NIO基础概念

一、I/O & NIO

Java的IO流,根据类型不同划分为(具体继承这些IO类的子类非常多)

  • 基于字节操作的 I/O 接口:InputStreamOutputStream
  • 基于字符操作的 I/O 接口:WriterReader
  • 基于磁盘操作的 I/O 接口:File
  • 基于网络操作的 I/O 接口:Socket

Java传统的IO类操作都是阻塞的,在高并发的场景下性能令人堪忧。

Java NIO(Non-blocking IO)是JDK1.4之后推出的一套新IO接口——非阻塞IO,与传统IO接口的主要区别在于:

I/O NIO
线程阻塞 线程非阻塞
用流(Stream)的方式处理数据 用缓冲区(Buffer)的方式处理数据

二、NIO基本概念

  • Channel(通道)
  • Buffer(缓冲区)
  • Selector(选择器)

2.1 Channel

1). Channel,相对于Stream,有几点区别:

Channel Stream
双向(可以读写) 单向(只能读或只能写)
非阻塞操作 阻塞操作
基于Buffer 基于流

2). NIO数据的流向总是:

Channel ----> Buffer
Channel <---- Buffer

3). Channel主要有下面几种类型:

类型 说明
FileChannel 文件的数据读写
DatagramChannel UDP的数据读写
SocketChannel TCP的数据读写
ServerSocketChannel 监听TCP链接请求,并且创建SocketChannel

4). 具体获取Channel的类

类型 说明
FileChannel FileInputStream.getChannel()
FileOutputStream.getChannel()
RandomAccessFile.getChannel()
DatagramChannel DatagramChannel.open()
SocketChannel SocketChannel.open()
ServerSocketChannel ServerSocketChannel.open()

2.2 Buffer

Buffer实际上就是一块内存区,用于和Channel交互,需要注意的是:

Buffer是非线程安全的,如果需要在多线程环境中使用,则需要有同步锁。

1). Buffer基本概念

要真正理解Buffer,有一张图比较经典:

  • capacity: buffer的容量大小,一般在创建之后就不会再改变;
  • limit: buffer内存区中还未被读写的第一个element位置索引;
  • position: 下一个等待要被读写的element位置索引;
  • mark: 当调用reset方法时,position会被重置的位置索引;

通常,0 <= mark <= position <= limit <= capacity

buffers-modes

2). Buffer类型

Buffer名字 对应基础类型
ByteBuffer byte
CharBuffer char
DoubleBuffer double
FloatBuffer float
IntBuffer int
LongBuffer long

3). Buffer常用操作

buffer操作 具体
clear() limit=capacity, position=0, mark忽略
flip() limit= position, position=0, mark忽略
rewind() position=0, mark忽略
compact()
(ByteBuffer才有)
将整块Buffer(从position~limit)移动到Buffer开始的位置(0~limit-position)

Buffer常用操作模式:

  • 把数据写入buffer;
  • 调用flip;
  • 从buffer中读取数据;
  • 调用buffer.clear()清空整个buffer 或者 buffer.compact()清空已经读取的buffer

2.3 Selector

Selector是NIO中的组件,检查一个或多个channel,实现一个线程管理多个网络链接。

overview-selectors

使用步骤:

  • 创建 selector
  • 注册channel到selector

Channel必须是非阻塞的。所以FileChannel不适用Selector,因为FileChannel不能切换为非阻塞模式。Socket channel可以正常使用。

参考链接

CentOS 7 安装Gitbook

安装步骤

步骤1: 安装NodeJS

[root@75d336b8c2cf ~]# yum install -y gcc make gcc-c++ openssl-devel wget
[root@75d336b8c2cf ~]# wget https://nodejs.org/dist/v8.9.4/node-v8.9.4.tar.gz
[root@75d336b8c2cf ~]# tar -zxvf node-v8.9.4.tar.gz
[root@75d336b8c2cf ~]# cd node-v8.9.4.tar.gz
[root@75d336b8c2cf node-v8.9.4]# ./configure
[root@75d336b8c2cf node-v8.9.4]# make && make install
[root@75d336b8c2cf node-v8.9.4]# node -v
v8.9.4
[root@75d336b8c2cf node-v8.9.4]# npm

Usage: npm <command>

步骤2: 安装Gitbook

[root@75d336b8c2cf ~]# npm install -g gitbook
[root@75d336b8c2cf ~]# npm install -g gitbook-cli
[root@75d336b8c2cf test]# gitbook
  Usage: gitbook [options] [command]

至此,Gitbook 安装完成。

可以通过Gitbook官网,安装一些实用插件。

常用命令

➜  gitbook git:(master) ✗ gitbook --help

  Usage: gitbook [options] [command]


  Options:

    -v, --gitbook [version]  specify GitBook version to use
    -d, --debug              enable verbose error
    -V, --version            Display running versions of gitbook and gitbook-cli
    -h, --help               output usage information


  Commands:

    ls                        List versions installed locally
    current                   Display currently activated version
    ls-remote                 List remote versions available for install
    fetch [version]           Download and install a <version>
    alias [folder] [version]  Set an alias named <version> pointing to <folder>
    uninstall [version]       Uninstall a version
    update [tag]              Update to the latest version of GitBook
    help                      List commands for GitBook
    *                         run a command with a specific gitbook version


➜  gitbook git:(master) ✗ gitbook help
    build [book] [output]       build a book
        --log                   Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled)
        --format                Format to build to (Default is website; Values are website, json, ebook)
        --[no-]timing           Print timing debug information (Default is false)

    serve [book] [output]       serve the book as a website for testing
        --port                  Port for server to listen on (Default is 4000)
        --lrport                Port for livereload server to listen on (Default is 35729)
        --[no-]watch            Enable file watcher and live reloading (Default is true)
        --[no-]live             Enable live reloading (Default is true)
        --[no-]open             Enable opening book in browser (Default is false)
        --browser               Specify browser for opening book (Default is )
        --log                   Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled)
        --format                Format to build to (Default is website; Values are website, json, ebook)

    install [book]              install all plugins dependencies
        --log                   Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled)

    parse [book]                parse and print debug information about a book
        --log                   Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled)

    init [book]                 setup and create files for chapters
        --log                   Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled)

    pdf [book] [output]         build a book into an ebook file
        --log                   Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled)

    epub [book] [output]        build a book into an ebook file
        --log                   Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled)

    mobi [book] [output]        build a book into an ebook file
        --log                   Minimum log level to display (Default is info; Values are debug, info, warn, error, disabled)

常见问题

1、Gitbook导出的HTML无法跳转

看了网上的介绍,可以安装下2.6.7版本解决这个问题, 具体步骤如下:

➜  gitbook git:(master) ✗ gitbook ls-remote
➜  gitbook git:(master) ✗ gitbook fetch 2.6.7
➜  gitbook git:(master) ✗ gitbook build --gitbook=2.6.7

2、报错:Cannot find module 'internal/fs'

这个是因为本机安装的NodeJS版本与NPM版本的问题,解决方法可以参考下面:

# 主要的思路是,安装正确版本的NodeJS和NPM
$ sudo n 6.9.1
$ sudo npm -g install npm@next
$ sudo n stable

参考链接

Redis安装、配置及使用(单机版)

简介

Redis 是一款 开源 & 高性能 的key-value数据库,可用于构建高性能,可扩展的Web应用程序。

主要特点

Redis的三个主要特点:

  • Redis数据库完全在内存中,使用磁盘仅用于持久性。
  • Redis支持较为丰富的数据类型:支持字符串(String)、列表(List),集合(Set),有序集合(Sorted Sets),散列数据类型(Hashes)
  • Redis可以将数据复制到任意数量的从服务器。

它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,可以很方便地使用。

适用场景

Redis使用最佳方式是全部数据in-memory。

具体,可参考下面链接进行了解:

相关网址


安装

Mac OSX

如果Mac上有安装 Homebrew, 可以直接下载安装:

sudo brew install redis

如果没有安装 Homebrew,则可以通过下面Linux安装步骤进行安装。

Linux

下载安装包(下载链接),解压,编译.

$ wget http://download.redis.io/releases/redis-3.0.5.tar.gz
$ tar xzf redis-3.0.5.tar.gz
$ cd redis-3.0.5
$ make

步骤2:拷贝可执行文件至自定义目录

编译完成后,在 redis-3.0.5/src 目录,有生成对应的可执行文件:redis-server、 redis-cli, 可以将其拷贝到自定义目录下。

$ cp src/redis-server /home/xxxx/redis/
$ cp src/redis-cli /home/xxxx/redis/
$ cp redis.conf /home/xxxx/redis/
$ cd /home/xxxx/redis/

$ ./redis-server redis.conf		#启动redis服务

如果想将Redis做成系统服务,可以参考这篇文章: Linux下redis的安装

Windows(32位、64位)

Redis Windows 版本由微软在维护,点击查看Github页面

不过,微软的Github上下载的仅支持64位操作系统的。下面的链接是

下载完成后,运行 redis-server.exe, redis-cli.exe 即可启动Redis服务以及客户端程序。


redis.config 配置

vi redis.config, 编辑下面的选项:

安全访问设置

bind <IP>                  #设置访问IP的白名单,只接受白名单发送过来的请求
requirepass <password>     #设置访问密码

数据库文件设置

dbfilename <DBname>        #设置数据库文件名称
dir <Path>                 #设置数据库文件存放的位置

主从数据库备份设置

slaveof <IP>               #设置主数据库的IP地址、端口
masterauth <master auth>   #主数据库访问的密码
slave-read-only no         #默认 ,slave 数据库是只读的,如果需要开启读写,可以更改选项为 yes

常用命令行

启动Redis服务

$ redis-server <配置文件路径>        #例如, `redis-server  redis.config`

client连接到Server

$ redis-cli -a <server密码>         #例如,`redis-cli -a admin`

关闭Server

$ redis-cli -a <server密码>         #例如,`redis-cli -a admin`
127.0.0.1:6379> SHUTDOWN

客户端使用(Java版本)

Redis拥有几乎所有主流编程语言的客户端,这里以 Java 客户端——Jedis为例。

下载Jedis

下载完,导入项目工程中,即可~

使用用例

Redis连接池代码

/**
*	Redis 连接池
*/
package com.test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public final class RedisUtil {
    
    //Redis服务器IP
    private static String ADDR = "127.0.0.1";	//设置
    
    //Redis的端口号
    private static int PORT = 6379;
    
    //访问密码
    private static String AUTH = "admin";	//Redis服务器访问密码
    
    //可用连接实例的最大数目,默认值为8;
    //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
    private static int MAX_ACTIVE = 1024;
    
    //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
    private static int MAX_IDLE = 200;
    
    //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
    private static int MAX_WAIT = 10000;
    
    private static int TIMEOUT = 10000;
    
    //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
    private static boolean TEST_ON_BORROW = true;
    
    private static JedisPool jedisPool = null;
    
    /**
     * 初始化Redis连接池
     */
    static {
        try {
         
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxActive(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWait(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 获取Jedis实例
     * @return
     */
    public synchronized static Jedis getJedis() {
        try {
            if (jedisPool != null) {
                Jedis resource = jedisPool.getResource();
                return resource;
            } else {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    /**
     * 释放jedis资源
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null) {
            jedisPool.returnResource(jedis);
        }
    }
}

下面是程序程序

/*
*	测试程序
*/
package com.test;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;

import redis.clients.jedis.Jedis;

public class TestRedis {
    private Jedis jedis; 
    
    @Before
    public void setup() {
        //连接redis服务器,192.168.0.100:6379
        jedis = new Jedis("127.0.0.1", 6379);
        //权限认证
        jedis.auth("admin");  
    }
    
    /**
     * redis存储字符串
     */
    @Test
    public void testString() {
        //-----添加数据----------  
        jedis.set("name","xinxin");//向key-->name中放入了value-->xinxin  
        System.out.println(jedis.get("name"));//执行结果:xinxin  
        
        jedis.append("name", " is my lover"); //拼接
        System.out.println(jedis.get("name")); 
        
        jedis.del("name");  //删除某个键
        System.out.println(jedis.get("name"));
        //设置多个键值对
        jedis.mset("name","liuling","age","23","qq","476777XXX");
        jedis.incr("age"); //进行加1操作
        System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" + jedis.get("qq"));
    }
    
    /**
     * redis操作Map
     */
    @Test
    public void testMap() {
        //-----添加数据----------  
        Map<String, String> map = new HashMap<String, String>();
        map.put("name", "xinxin");
        map.put("age", "22");
        map.put("qq", "123456");
        jedis.hmset("user",map);
        //取出user中的name,执行结果:[minxr]-->注意结果是一个泛型的List  
        //第一个参数是存入redis中map对象的key,后面跟的是放入map中的对象的key,后面的key可以跟多个,是可变参数  
        List<String> rsmap = jedis.hmget("user", "name", "age", "qq");
        System.out.println(rsmap);  
  
        //删除map中的某个键值  
        jedis.hdel("user","age");
        System.out.println(jedis.hmget("user", "age")); //因为删除了,所以返回的是null  
        System.out.println(jedis.hlen("user")); //返回key为user的键中存放的值的个数2 
        System.out.println(jedis.exists("user"));//是否存在key为user的记录 返回true  
        System.out.println(jedis.hkeys("user"));//返回map对象中的所有key  
        System.out.println(jedis.hvals("user"));//返回map对象中的所有value 
  
        Iterator<String> iter=jedis.hkeys("user").iterator();  
        while (iter.hasNext()){  
            String key = iter.next();  
            System.out.println(key+":"+jedis.hmget("user",key));  
        }  
    }
    
    /** 
     * jedis操作List 
     */  
    @Test  
    public void testList(){  
        //开始前,先移除所有的内容  
        jedis.del("java framework");  
        System.out.println(jedis.lrange("java framework",0,-1));  
        //先向key java framework中存放三条数据  
        jedis.lpush("java framework","spring");  
        jedis.lpush("java framework","struts");  
        jedis.lpush("java framework","hibernate");  
        //再取出所有数据jedis.lrange是按范围取出,  
        // 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有  
        System.out.println(jedis.lrange("java framework",0,-1));  
        
        jedis.del("java framework");
        jedis.rpush("java framework","spring");  
        jedis.rpush("java framework","struts");  
        jedis.rpush("java framework","hibernate"); 
        System.out.println(jedis.lrange("java framework",0,-1));
    }  
    
    /** 
     * jedis操作Set 
     */  
    @Test  
    public void testSet(){  
        //添加  
        jedis.sadd("user","liuling");  
        jedis.sadd("user","xinxin");  
        jedis.sadd("user","ling");  
        jedis.sadd("user","zhangxinxin");
        jedis.sadd("user","who");  
        //移除noname  
        jedis.srem("user","who");  
        System.out.println(jedis.smembers("user"));//获取所有加入的value  
        System.out.println(jedis.sismember("user", "who"));//判断 who 是否是user集合的元素  
        System.out.println(jedis.srandmember("user"));  
        System.out.println(jedis.scard("user"));//返回集合的元素个数  
    }  
  
    @Test  
    public void test() throws InterruptedException {  
        //jedis 排序  
        //注意,此处的rpush和lpush是List的操作。是一个双向链表(但从表现来看的)  
        jedis.del("a");//先清除数据,再加入数据进行测试  
        jedis.rpush("a", "1");  
        jedis.lpush("a","6");  
        jedis.lpush("a","3");  
        jedis.lpush("a","9");  
        System.out.println(jedis.lrange("a",0,-1));// [9, 3, 6, 1]  
        System.out.println(jedis.sort("a")); //[1, 3, 6, 9]  //输入排序后结果  
        System.out.println(jedis.lrange("a",0,-1));  
    }  
    
    @Test
    public void testRedisPool() {
        RedisUtil.getJedis().set("newname", "中文测试");
        System.out.println(RedisUtil.getJedis().get("newname"));
    }
}

相关链接

Redis常用

主从复制(Master-Slave)

Redis 支持主从备份,可以通过设置 redis.conf 文件,即可实现,具体可以参考下面的文章:

Struts2 文件下载

Struts2文件下载的相关配置如下:

Struts.xml 配置

<package name="defaultPackage" extends="struts-default">
		<!--配置下载Action入口-->
		<action name="download" class="com.johnnian.DownloadAction" >
			<!--stream 是文件下载的时候专用的-->
			<result name="success" type="stream">
				<!--文件下载的类型-->
				<param name="contentType">${contentType}</param>
				<!--文件下载方式分为:-->
				<param name="contentDisposition">inline;filename="${filename}"</param>
				<!--文件下载入口-->
				<param name="inputName">testDownload</param>
				<param name="bufferSize">1024</param>
			</result>
		</action>
	</package>

相关说明:

1、 contentType:
下载文件的类型,客户端向Tomcat请求静态资源的时候,Tomcat会自动在 Response Head 里面添加 “Content-Type” 属性,具体的属性列表配置,参考Tomcat下的 web.xml.

2、 contentDisposition:
这个属性配置下载文件的文件名等属性,其中文件类型划分为inline、attachment两种:

  • inline:浏览器尝试直接打开文件
  • atachment:浏览器直接下载为附件

这个差别还是有的,比如想要让下载的文件直接在浏览器打开,就需要设置成“inline”

3、 inputName:
配置下载请求的执行方法,例如,上述配置成 “testDownload”, 则在Action类中就需要实现 “getTestDownload” 方法。

4、配置文件中的 ${contentType}, {filename}, 需要在实现的接口上定义对应的属性,只要设置好其属性,就可以了~

Java后台

public class TicketDownloadAction extends ActionSupport {

	private String testParam; 
	
	private String filename;//文件名
	private String contentType;//文件类型
	

	/**
    *  下载处理方法: 
    */
	public InputStream getTestDownload() {
		
        // 直接获取参数
        String params = getTestParam();

		String path = "/Users/Johnnian/tmp/123.png";
		this.setFilename("123.png");
		this.setContentType("image/png");
		
		InputStream inputStream = null;
		try {
			inputStream = new FileInputStream(path);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	
		//可以应答任何继承 InputStream 的类实例
		return inputStream;
			
	}
	
	//---------------------------------
	//			Gettter & Setter
	//---------------------------------

	public String getTestParam() {
		return testParam;
	}

	public void setTestParam(String testParam) {
		this.testParam = testParam;
	}

	public String getFilename() {
		return filename;
	}

	public void setFilename(String filename) {
		this.filename = filename;
	}

	public String getContentType() {
		return contentType;
	} 
	
	public void setContentType(String contentType) {
		this.contentType = contentType;
	}

}

说明:

  1. Struts2中的参数是自动注入的,只需要get对应的属性就可以获取
  2. 需要实现“Struts.xml”中inputName对应的入口,应答一个InputStream文件流即可

Web前端

<a href="http://127.0.0.1:8080/posbox/downloadTicket.jhtml?testParam=12345678"> 点击下载 </a>

可以直接在 <a><img> 等标签中直接使用.

日志服务—使用Flume-NG/Kafaka搭建日志搜集服务

分布式日志收集系统(Flume-NG)

应用场景

在生产环境中,有若干台服务器,每台服务器都部署着一套生产版本系统,为了方便分析与查看日志,需要统一收集日志。

目前市面上,有不少开源的日志收集系统,如强大的ELK(ElasticSearch, Logstash, Kibana)、Flume-NG等。ELK提供日志检索的索引、强大的图形界面,而Flume-NG比较小巧,对于小项目用起来比较简单,所以这里选择用Flume-NG:

  • 日志采集客户端: Flume-NG
  • 消息传递: Kafka

网络拓扑图如下:

qq20170908-100544 2x

如图所示,服务器A、B 上的日志文件,被对应的Flume-NG客户端收集后,传递给 Kafka 消息队列,最终由Kafaka客户端消费(存储数据库,或者写入到本地文件中)

安装步骤

1、Kafaka安装与配置

步骤1: 下载Kafka安装包

点击下载Kafka

➜  wget http://mirrors.hust.edu.cn/apache/kafka/0.11.0.1/kafka_2.11-0.11.0.1.tgz

步骤2: 安装配置

这里的Zookeeper、Kafka暂时都是配置单点,如果想要配置集群,请参考:

##### 解压
➜  tar -zxvf kafka_2.11-0.11.0.1.tgz
➜  cd kafka_2.11-0.11.0.1

##### 配置启动Zookeeper, 包括端口、集群等, 默认端口2181
➜  vi config/zookeeper.properties
➜  ./bin/zookeeper-server-start.sh -daemon ./config/zookeeper.properties
➜  netstat -an | grep 2181
tcp46      0      0  *.2181                 *.*                    LISTEN

##### 配置启动Kafka,默认端口9092
➜  vi config/server.properties
# 因为部署Kafka单点,需要放开配置
advertised.listeners=PLAINTEXT://127.0.0.1:9092

##### 后台进程的形式启动kafka
➜  nohup ./bin/kafka-server-start.sh config/server.properties &

2、Flume-NG安装与配置

步骤1: 下载安装包

➜  wget http://mirrors.tuna.tsinghua.edu.cn/apache/flume/1.7.0/apache-flume-1.7.0-bin.tar.gz
➜  tar -zxvf apache-flume-1.7.0-bin.tar.gz
➜  cd apache-flume-1.7.0-bin

步骤2: 配置与启动

➜  cp conf/flume-conf.properties.template conf/flume-conf.properties
➜  vi conf/flume-conf.properties

下面是我的示例配置文件:具体的配置选项,可以参考官网的配置说明

agent.sources = s1 s2
agent.sinks = k1 k2
agent.channels = c1 c2

#配置source
agent.sources.s1.type = exec
agent.sources.s1.command = tail -F /Users/Johnnian/tmp/logs/test1.log
agent.sources.s1.channels = c1

agent.sources.s2.type = exec
agent.sources.s2.command = tail -F /Users/Johnnian/tmp/logs/test2.log
agent.sources.s2.channels = c2

#配置channel
agent.channels.c1.type = memory
agent.channels.c1.capacity = 1000000
agent.channels.c1.transactionCapacity = 100

agent.channels.c2.type = memory
agent.channels.c2.capacity = 1000000
agent.channels.c2.transactionCapacity = 100

#配置sinks
agent.sinks.k1.type = org.apache.flume.sink.kafka.KafkaSink
agent.sinks.k1.kafka.bootstrap.servers = 127.0.0.1:9092
agent.sinks.k1.kafka.topic = test1
agent.sinks.k1.channel = c1

agent.sinks.k2.type = org.apache.flume.sink.kafka.KafkaSink
agent.sinks.k2.kafka.bootstrap.servers = 127.0.0.1:9092
agent.sinks.k2.kafka.topic = test2
agent.sinks.k2.channel = c2

Flume-NG, 基本概念可以看下面的图:

每个JVM只能有一个 Flume-NG的Agent,每个agent由: sources、channels、sinks 三个模块组成;

1个source --> 1个channel --> N个sinks

userguide_image00

日志收集的思路是:

  • Source 用 Linux的 tail 命令抓取log的变化;
  • Sink 使用KafkaTopic,把log中变化的内容发送到Kafka;
  • 编写Kafka消费者代码,将日志写入到指定的位置;

步骤3: 启动 Flume-NG

➜  nohup ./bin/flume-ng agent -n agent -c conf -f conf/flume-conf.properties &

3、日志收集消费者安装与配置

日志收集客户端,把收集到的日志写入指定位置,如 使用logback每天滚动写入日志文件,或者写入 MongoDB、HBase等位置;

这里我使用 Springboot + logback + spring-kafka ,写入日志文件,具体Demo代码放在这里: 点击查看, 可以修改对应的Topic、日志路径等。

使用方法: 需要先安装Maven

➜  mvn clean install
➜  cd target
➜  java -jar flumelogger-1.0.0.jar

默认情况下,运行Springboot程序,在打包的时候会把所有的配置文件都打包到jar文件中,如果需要灵活更改配置,可以配置文件单独拷贝一份出来,放在jar包同一目录:

[root@0bf998fffd28 flumelogger]# tree
.
|-- application.properties
|-- flumelogger-1.0.0.jar
`-- logback-spring.xml

默认情况下,flumelogger-1.0.0.jar 会优先加载同目录下的application.properties,可以通过在 application.properties 文件中指定logback.xml的路径,就可以达到动态配置的效果:

[root@0bf998fffd28 flumelogger]# vi application.properties

logging.config=/root/flumelogger/logback-spring.xml

参考链接

SSH使用问题以及解决方案(expecting SSH2_MSG_KEX_ECDH_REPLY)

情景

公司内网的一台服务器(CentOS 7.4)无法通过SSH连接登录 阿里云服务器(CentOS7.4)

一开始以为是阿里云把内网服务器的请求IP过滤掉,然后又试了下其他服务商的服务器,有的时候可以SSH登录,又有的时候无法SSH登录。虽然SSH登录不上,但是远程服务器的IP还是可以PING通。

除了局域网这台服务器外,其他电脑都可以SSH到阿里云服务器,这就排出了阿里云过滤IP的问题(因为连的都是同一台局域网路由器,出口的IP地址都是一样的)。

这样,问题就定位到SSH的问题,打开SSH的调试,发现了现象:

SSH登录日志的最后,总是卡在:

debug1: expecting SSH2_MSG_KEX_ECDH_REPLY

完整的日志如下:

SSH Debug 日志

[root@localhost ~]# ssh -vvv [email protected]
debug1: key_load_public: No such file or directory
debug1: identity file /root/.ssh/id_ed25519-cert type -1
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_7.4
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.4
debug1: match: OpenSSH_7.4 pat OpenSSH* compat 0x04000000
debug2: fd 3 setting O_NONBLOCK
debug1: Authenticating to 47.100.31.179:22 as 'root'
debug3: hostkeys_foreach: reading file "/root/.ssh/known_hosts"
debug3: send packet: type 20
debug1: SSH2_MSG_KEXINIT sent
debug3: receive packet: type 20
debug1: SSH2_MSG_KEXINIT received
debug2: local client KEXINIT proposal
debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,ext-info-c
debug2: host key algorithms: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa,ssh-dss
debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc
debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc
debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: compression ctos: none,[email protected],zlib
debug2: compression stoc: none,[email protected],zlib
debug2: languages ctos:
debug2: languages stoc:
debug2: first_kex_follows 0
debug2: reserved 0
debug2: peer server KEXINIT proposal
debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1
debug2: host key algorithms: ssh-rsa,rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256,ssh-ed25519
debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc,blowfish-cbc,cast128-cbc,3des-cbc
debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected],aes128-cbc,aes192-cbc,aes256-cbc,blowfish-cbc,cast128-cbc,3des-cbc
debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: compression ctos: none,[email protected]
debug2: compression stoc: none,[email protected]
debug2: languages ctos:
debug2: languages stoc:
debug2: first_kex_follows 0
debug2: reserved 0
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: [email protected] MAC: <implicit> compression: none
debug1: kex: client->server cipher: [email protected] MAC: <implicit> compression: none
debug1: kex: curve25519-sha256 need=64 dh_need=64
debug1: kex: curve25519-sha256 need=64 dh_need=64
debug3: send packet: type 30
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
^@Connection closed by 123.123.123.123 port 22

一开始看日志,还以为是公私钥文件找不到:

debug1: key_load_public: No such file or directory

然后对比了正常SSH登录,也会提示这个问题,而且StackOverFlow上也有人说,这个不是问题:

debug1: key_load_public: No such file or directory is not an error. Just a debug message. The key is rejected on the server side and without information from the server log, it is not possible to help you

然后,问题就定位到最后一行的日志:

debug1: expecting SSH2_MSG_KEX_ECDH_REPLY

解决方法

下面方法可以解决:

设置网卡接口的MTU值,改成:1200,测试后,确实会生效。

临时生效的配置:

[root@localhost ~]# ifconfig
enp0s31f6: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.102  netmask 255.255.255.0  broadcast 192.168.0.255
        inet6 fe80::af7e:86da:a424:53fe  prefixlen 64  scopeid 0x20<link>
        ether d8:9e:f3:10:7a:11  txqueuelen 1000  (Ethernet)
        RX packets 814  bytes 92802 (90.6 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 561  bytes 159798 (156.0 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 16  memory 0xf7e80000-f7ea0000

[root@localhost ~]# ifconfig enp0s31f6 mtu 1200

永久生效的配置:

[root@localhost ~]# vi /etc/sysconfig/network-scripts/ifcfg-enp0s31f6
MTU=1200            #MTU设置

[root@localhost ~]# systemctl restart network

原理

MTU(Maximum Transmission Unit):最大传输单元,是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。最大传输单元这个参数通常与通信接口有关(网络接口卡、串口等).(摘自维基百科)

从维基百科种看到:

这里的MTU所指的是无需分段的情况下,可以传输的最大IP报文(包含IP头部,但不包含协议栈更下层的头部)。

下面是普通媒体的MTU表:

网络 MTU(Byte)
超通道 65535
16Mb/s令牌环 17914
4Mb/s令牌环 4464
FDDI 4352
以太网 1500
IEEE 802.3/802.2 1492
X.25 576
点对点(低时延) 296

对于使用AUTOSSH建立隧道:

  • 传输模式,MTU值最大是:1440
  • 隧道模式,MTU值最大是:1420

所以,出现这个现象的原因也就清楚了: 本身以太网的MTU是1500,而隧道的MTU值1400左右,比以太网的小,因此,以太网发出去的包就被拒绝了,最终导致无法建立SSH连接。

参考链接

Java基础—线程安全与锁

一、定义

“当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的”。

一个线程安全的代码,要有这样的特征:

代码本身封装了所有必要的正确性保障手段(如互斥同步等),令调用者无须关心多线程的问题,更无须自己实现任何措施来保证多线程的正确调用。

二、线程间共享数据类型

2.1 不可变

不可变的对象一定是线程安全的,如 使用 final 关键字修饰的变量、String类型对象、枚举对象等;

2.2 绝对线程安全

在Java中标注自己是线程安全的类,如Vector、HashTable等,大多数都不是绝对的线程安全,可以运行下面测试代码:

package com.johnnian.thread;

import java.util.Vector;

public class ThreadDemo {
	
	private static Vector< Integer> vector = new Vector< Integer>();
	public static void main(String[] args)  {
		 while (true) { 
			 try {
				 for (int i = 0; i < 100; i++) { 
			            vector. add( i); 
			        } 
			        Thread removeThread = new Thread( new Runnable() {
			            @Override 
			            public void run() { 
			                for (int i = 0; i < vector. size(); i++) { 
			                    vector. remove( i); 
			                } 
			            } 
			        });
			        Thread printThread = new Thread( new Runnable() { 
			            @Override 
			            public void run() { 
			                for (int i = 0; i < vector. size(); i++) { 
			                	Integer item = vector. get(i);
			                } 
			            } 
			        });
			        removeThread.start(); 
			        printThread.start(); 
				} catch (Exception e) {
					// TODO: handle exception
					System.out.println(e);
				}		
		    } 
	}   
}

运行结果:

Exception in thread "Thread-23" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 141
	at java.util.Vector.get(Vector.java:748)
	at com.johnnian.thread.ThreadDemo$2.run(ThreadDemo.java:30)
	at java.lang.Thread.run(Thread.java:745)

如果对vector对象进行同步操作,修改代码如下:

Thread removeThread = new Thread( new Runnable() {
    @Override 
    public void run() { 
    	synchronized (vector) {
    		for (int i = 0; i < vector. size(); i++) { 
                vector. remove( i); 
            } 
		}
    } 
});
    
Thread printThread = new Thread( new Runnable() { 
    @Override 
    public void run() { 
    	synchronized (vector) {
            for (int i = 0; i < vector. size(); i++) { 
            	Integer item = vector. get(i);
            } 
    	}
    } 
});

结果一切正常, :)

2.3 相对线程安全

相对的线程安全就是我们通常意义上所讲的线程安全,它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要做额外的保障措施,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。

2.4 线程兼容和线程对立

线程兼容指的是,原本不是线程安全的,例如 HashTable,通过一些同步方法(同步锁),保证线程安全。

线程对立指的是,无论如何都无法在多线程环境中使用,例如 Thread.suspend() & Thread.resume()方法。

三、线程安全的方法

3.1 同步互斥(阻塞同步)

方法一: 使用synchronized关键字

在Java里面,最基本的互斥同步手段就是synchronized关键字。

synchronized关键字,编译后,在同步块的前后生成 monitorentermonitorexit 这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。

monitorenter 指令(reference参数):锁计数器 +1,阻塞其他线程
代码块...
monitorexit 指令(reference参数):锁计数器 -1

reference参数:

  • synchronized指定对象, reference=对象
  • synchronized指定实例/类方法, reference=实例/类方法

方法二:ReentrantLock(重入锁)

可以用 java.util.concurrent 中的ReentrantLock实现, ReentrantLock是API级别的互斥锁,synchronized是系统级别(Java中重量级操作)。

ReentrantLock可以实现下面三种策略的锁:

名字 说明
等待可中断 持有锁的线程长时间不释放锁, 等待线程可以放弃等待,去做其他事情
公平锁 多个线程在申请锁的时候,按照申请的时间顺序依次获得锁
锁绑定多个条件 可以同时绑定多个Condition对象(可以绑定多个条件)
相比: synchronized只能绑定一个条件)

方法三: 使用第三方同步互斥锁(适用于分布式系统场景下)

可以使用Zookeeper、Redission分布式锁, 实现在分布式系统下的资源同步。

3.2 非阻塞同步

原理: 先进行正常操作,如果发现有线程操作冲突,则再进行处理。

可以通过 CPU的CAS指令(Compare-and-swap)实现(JDK1.5之后)。

四、锁优化

使用同步互斥锁,会阻塞等待中的线程(使其挂起),而挂起线程、恢复线程都算是重量级操作(这些操作需要转入内核进行),给操作系统的并发与性能带来不小的压力。

JDK1.6后,引入了一系列的锁优化技术,尽量减少线程直接的挂起,主要如下:

4.1 自旋锁 & 自适应自旋锁

自旋锁
qq20170930-092844 2x

可以从上图中看到:自旋锁,将原先使得线程挂起的操作 改为自循环,等到锁资源释放后,再继续。这种操作节省了线程挂起/恢复的开销,但是占用了处理器处理的时间。

JDK1.6后默认开启锁的自旋,当然,锁自旋是有限制的,如果超过自定次数的自旋后还没获得锁,就直接挂起线程(用 -XX: PreBlockPin 参数来配置自旋次数,默认10)

自适应自旋锁

在自旋锁基础上,自旋的时间不固定,而是由前一次同一个锁的自旋状态以及时间决定。

4.2 锁消除

JVM的JIT编译器在编译的时候,对于一些代码写着要同步锁,但是实际不存在数据资源竞争,JVM会消除这种锁。

4.3 锁粗化(扩大锁的范围)

正常情况下,我们总是尽量缩小锁的范围,但是对于一些频繁在同一个对象上加锁的操作,甚至在循环中没有竞争的情况下加锁,这个时候JVM会将锁的范围适当的扩大,节省加锁的次数。

4.4 轻量级锁

如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。

4.5 偏向锁

在无竞争的情况下,把整个同步操作都消除了。

SpringBoot系列之—日志打印的问题(经验总结)

问题复盘

在公司的一个项目中,有几个子系统用SpringBoot运行,期间出现了一个问题:

由于我们的服务器用的是自建的MongoDB副本集,而MongoDB是出了名的 吃内存,由于系统内存被耗尽,导致一些基础设施的无法正常运行。

然后我们的几个子系统就狂打错误日志,就直接吧磁盘给占满了。真是无语了~

3640765df92c24b526a47e5aec9c4ba8

解决方法

目前SpringBoot的日志,使用Logback,原先的配置如下:

 <appender name="ROLLING_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">  
    	<level>INFO</level>  
    </filter>
    <file>/Users/Johnnian/info.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- daily rollover -->
      <fileNamePattern>/Users/Johnnian/info.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>7</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%5p] [%t] [%c:%L] - %m%n</pattern>
    </encoder>
  </appender>

现在的处理:

  1. 日志设定当天最大存储大小;
  2. 设置所有日志总大小

Logback的rollingPolicy,除了ch.qos.logback.core.rolling.TimeBasedRollingPolicy 之外,还可以配置成ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy(大小限制+时间滚动处理)

新的配置如下:

 <appender name="ROLLING_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">  
    	<level>ERROR</level>  
    </filter>
    <file>/Users/Johnnian/info.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
      <!-- daily rollover -->
      <fileNamePattern>/Users/Johnnian/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
      <maxHistory>7</maxHistory>
      <maxFileSize>100MB</maxFileSize>
      <totalSizeCap>1GB</totalSizeCap>
    </rollingPolicy>
    <encoder>
      <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%5p] [%t] [%c:%L] - %m%n</pattern>
    </encoder>
  </appender>

注意:

fileNamePattern 字段配置: info.%d{yyyy-MM-dd}.%i.log, 需要添加 %i,否则启动报错。

参考链接

CentOS 安装 Ruby 2.3.0

Ruby 安装方法——RVM

操作系统: CentOS 6.5

说明:在CentOS上,直接用 yum 安装 Ruby的话,安装的版本比较低,所以,就直接用 RVM 进行安装。

步骤1: 安装依赖包

# yum install gcc-c++ patch readline readline-devel zlib zlib-devel -y
# yum install libyaml-devel libffi-devel openssl-devel make -y
# yum install bzip2 autoconf automake libtool bison iconv-devel sqlite-devel -y

步骤2: 安装 RVM

# curl -sSL https://rvm.io/mpapis.asc | gpg --import -
# curl -L get.rvm.io | bash -s stable
# source /etc/profile.d/rvm.sh
# rvm reload

步骤3: 验证环境依赖

# rvm requirements run

Checking requirements for centos.
Requirements installation successful.

步骤4: 安装 Ruby

直接从原始的 Ruby源下载,速度很慢,所以,需要切换成淘宝的镜像源:

# echo "ruby_url=https://cache.ruby-china.org/pub/ruby" > $rvm_path/config/db

执行安装:

# rvm requirements
# rvm install 2.3.0
# rvm use 2.3.0 --default --create

验证是否安装成功, 切换 gem源

# ruby -v
ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]

# gem -v
2.5.1

# gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/

https://gems.ruby-china.org/ added to sources
https://rubygems.org/ removed from sources

Kubernetes—使用Minikube搭建Nginx集群

目标

使用Minikube,在Kubernetes上搭建一个Nginx集群(3个节点)

备注:对于如何安装环境,请点击这里

开发环境

名称 版本号
Kubernetes V1.7
minikube v0.21.0
kubectl-Client v1.7.4
kubectl-Server v1.7.0
操作系统 MacOS
➜  ~ minikube version
minikube version: v0.21.0
➜  ~ kubectl version
Client Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.4", GitCommit:"793658f2d7ca7f064d2bdf606519f9fe1229c381", GitTreeState:"clean", BuildDate:"2017-08-17T17:03:51Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"7", GitVersion:"v1.7.0", GitCommit:"d3ada0119e776222f11ec7945e6d860061339aad", GitTreeState:"clean", BuildDate:"2017-07-26T00:12:31Z", GoVersion:"go1.8.3", Compiler:"gc", Platform:"linux/amd64"}

yaml配置文件

创建 nginx-controller.yaml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        name: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

创建 nginx-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    name: nginx-service
spec:
  type: NodePort
  ports:
  - port: 80
    nodePort: 30001
    targetPort: 80
  selector:
    name: nginx

创建步骤

➜  kubectl create -f nginx-controller.yaml
➜  kubectl get deployment
NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3         3         3            3           10m

➜  kubectl get pod
nginx-deployment-1969926507-cs4xb   1/1       Running   0          11m
nginx-deployment-1969926507-dc8nh   1/1       Running   0          11m
nginx-deployment-1969926507-nmtxp   1/1       Running   0          11m

➜  kubectl create -f nginx-service.yaml
➜  kubectl get service
NAME              CLUSTER-IP   EXTERNAL-IP   PORT(S)          AGE
nginx-service     10.0.0.104   <nodes>       80:30001/TCP     9m

备注: 这里的 80:30001, 对应的是:service端口:主机Node端口

验证

➜  minikube ip
192.168.99.100

➜  test curl 192.168.99.100:30001
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

网络拓扑图

qq20170905-195758 2x

网络映射关系:

主机Node-192.168.99.100:30001 <----> Service-10.0.0.104:80 <----> Deployment(3个Pod)-(端口:80; Label: name: nginx)

Service 通过 Label Select 找到 “name: nginx” 标签的Pod,通过内部的iptables进行路由映射,映射到对应Pod的80端口。

Oracle 命令行小结

登录指令

sqlplus                       /*运行SQLPLUS工具*/
sqlplus /nolog                /*直接进入SQLPLUS命令提示符*/
sqlplus / as sysdba           /*以OS身份连接*/ 
sqlplus scott/123456          /*普通用户登录*/
sqlplus sys/123456 as sysdba  /*以管理员登录*/
SQL>conn hr/123456            /*切换用户*/
quit                          /*断开连接*/

用户创建/删除

/*步骤1:删除 root 用户*/
drop user root cascade;		
/*步骤2:删除 TABLE_SPACE_TMP 临时表空间*/
drop tablespace TABLE_SPACE_TMP including CONTENTS and datafiles;
/*步骤3:删除 TABLE_SPACE 表空间*/
drop tablespace TABLE_SPACE including CONTENTS and datafiles;

/*步骤4:创建临时表空间*/
create temporary tablespace TABLE_SPACE_TMP  tempfile '/home/oracle/oradata/temp.dbf' size 128M autoextend on next 128M maxsize 512M extent management local;
/*步骤5:创建表空间*/
create tablespace TABLE_SPACE logging datafile '/home/oracle/oradata/tbs.dbf'  size 512M;
/*步骤6:创建 root 用户、密码 root, 表空间分配*/
create user root identified by root default tablespace TABLE_SPACE temporary tablespace TABLE_SPACE_TMP;
/*步骤7:创建表空间*/
grant connect,resource,dba to root;
/*步骤8:提交*/
commit;

常用指令

/*查看当前数据库名*/
SQL> select name from v$database;    
SQL> desc v$database;	--查看表结构

/*查看当前实例名*/
select instance_name from v$instance;
SQL> desc v$instance;	--查看表结构

/*运行SQL脚本*/
SQL> @<PATH>	--例如: @‘/home/oracle/test.sql’
/*获取表字段*/
select * 
from user_tab_columns 
where Table_Name='用户表' 
order by column_name

/*获取表注释*/
select * 
from user_tab_comments 
where Table_Name='用户表'
order by Table_Name

/*获取字段注释*/
select * 
from user_col_comments 
where Table_Name='用户表'
order by column_name

/* 获取表:*/
select table_name from user_tables; --当前用户的表,属性有 table_name,tablespace_name,last_analyzed等      
select table_name from all_tables; --所有用户的表,属性有 ower,table_name,tablespace_name,last_analyzed
select table_name from dba_tables; --包括系统表,属性有 ower,table_name,tablespace_name,last_analyzed
select table_name from dba_tables where owner='zfxfzb' 

/*  获取表字段:*/
select * from user_tab_columns where Table_Name='用户表';	--table_name,column_name,data_type,data_length,data_precision,data_scale,nullable,column_id
select * from all_tab_columns where Table_Name='用户表';     --ower,table_name,column_name,data_type,data_length,data_precision,data_scale,nullable,column_id
select * from dba_tab_columns where Table_Name='用户表';     --ower,table_name,column_name,data_type,data_length,data_precision,data_scale,nullable,column_id

/*  获取表注释:*/
select * from user_tab_comments   --table_name,table_type,comments

/* 获取字段注释:*/
select * from user_col_comments   --table_name,column_name,comments

/* 删除用户所有表 */
/*将会输出一批删除表的sql语句,这些SQL语句执行一下就可以了。*/
select 'drop table '||table_name||';' 
from cat 
where table_type='TABLE'

/* 查看表结构: 注意,表名需加引号*/
desc <table_name>  -- desc "test_table"

相关链接

  1. Oracle数据库,实例,表空间,用户,表之间的关系简析:链接

Tomcat安全加固

  1. 删除 webapps 下的所有内容
  2. 关闭 8009 端口
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
  1. 去掉关闭服务的端口
<Server port="8005" shutdown=SHUTDONWN">
改为
<Server>
  1. 隐藏Tomcat版本
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" server="1.0"/>

5、关闭war包自动解压,自动部署

<Host appBase="webapps" autoDeploy="false" name="localhost" unpackWARs="false" xmlNamespaceAware="false"
xmlValidation="false">

CentOS 7 更改网卡名

安装CentOS 7后,默认的网卡名字不是 「eth0」,由于在安装阿里云的Logtail客户端,其默认读取 「eth0」网卡的IP地址,因此需要更改下网卡名。

设置步骤

下面的具体步骤:(假设网卡名字是:eth123456)

步骤1: 配置创建并配置 ifcfg-eth0 文件

[root@localhost ~]# ifconfig
eth123456: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1280
        inet 192.168.0.102  netmask 255.255.255.0  broadcast 192.168.0.255
        inet6 fe80::77a1:9964:3a77:8ab9  prefixlen 64  scopeid 0x20<link>
        ether d8:9e:f3:10:7a:11  txqueuelen 1000  (Ethernet)
        RX packets 34180  bytes 4149267 (3.9 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 32703  bytes 20801400 (19.8 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
        device interrupt 16  memory 0xf7e80000-f7ea0000

[root@localhost ~]# cd /etc/sysconfig/network-scripts/
[root@localhost network-scripts]# mv ifcfg-eth123456 ifcfg-eth0
[root@localhost network-scripts]# vi ifcfg-eth0
#修改下面两个Key
NAME="eth0"
DEVICE="eth0"

步骤2: 修改grub配置并更新内核参数

#修改配置
[root@localhost ~]# vi /etc/sysconfig/grub
#修改这个Key:增加 net.ifnames=0 biosdevname=0
GRUB_CMDLINE_LINUX="crashkernel=auto rd.lvm.lv=centos/root net.ifnames=0 biosdevname=0 rd.lvm.lv=centos/swap rhgb quiet"

#更新内核参数
[root@localhost ~]# grub2-mkconfig -o /boot/grub2/grub.cfg

步骤3: 添加udev的规则

[root@localhost ~]# vi /usr/lib/udev/rules.d/60-net.rules
#将第一行注释掉
#ACTION=="add", SUBSYSTEM=="net", DRIVERS=="?*", ATTR{type}=="1", PROGRAM="/lib/udev/rename_device", RESULT=="?*", NAME="$result"
ACTION=="add", SUBSYSTEM=="net", DRIVERS=="?*", ATTR{type}=="1", ATTR{address}=="d8:9e:*:10:*11", NAME="eth0"

配置完成之后,重启服务器即可。

备注:

  • ATTR{address}=="网卡Mac地址"(可以通过 ifconfig命令查看 )
  • NAME="新网卡名字"

参考链接

CentOS6.8 安装 RebbitMQ

操作系统环境:CentOS 6.8
RabbitMQ版本:3.7.2

安装步骤:

1、安装 erlang 环境:

[root@a6a766e6204a ~]# wget https://github.com/rabbitmq/erlang-rpm/releases/download/v19.3.6.5/erlang-19.3.6.5-1.el6.x86_64.rpm
[root@a6a766e6204a ~]# rpm -ivh erlang-19.3.6.5-1.el6.x86_64.rpm

备注:如果下载失败,可以:

2、导入Rabbit签名文件:

[root@a6a766e6204a ~]# rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc

3、安装依赖:

[root@a6a766e6204a ~]# yum -y install initscripts
[root@a6a766e6204a ~]# yum -y install logrotate
#安装socat
[root@a6a766e6204a ~]#  wget http://mirror.centos.org/centos/6/os/x86_64/Packages/compat-readline5-5.2-17.1.el6.x86_64.rpm
[root@a6a766e6204a ~]# wget http://dl.fedoraproject.org/pub/epel/6/x86_64/Packages/s/socat-1.7.2.3-1.el6.x86_64.rpm
[root@ a6a766e6204a ~]# rpm -ivh compat-readline5-5.2-17.1.el6.x86_64.rpm
[root@ a6a766e6204a ~]# rpm -ivh socat-1.7.2.3-1.el6.x86_64.rpm

相关网址:

4、安装RabbitMQ:

[root@a6a766e6204a ~]# wget https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.2/rabbitmq-server-3.7.2-1.el6.noarch.rpm
[root@a6a766e6204a ~]# rpm -ivh rabbitmq-server-3.7.2-1.el6.noarch.rpm

5、运行RabbitMQ的命令:

#启动
service rabbitmq-server start
#停止
service rabbitmq-server stop 		
#重启
service rabbitmq-server restart

6、配置RabbitMQ的账户权限

A、配置管理员账号:

#创建管理员账号
rabbitmqctl add_user admin adminpasspord
rabbitmqctl set_user_tags admin administrator

#管理员账号分配权限
 rabbitmqctl list_users  #列出用户权限
 rabbitmqctl  set_permissions -p / admin '.*' '.*' '.*'  #使用户具有‘/’这个virtual host中所有资源的配置、写、读权限以便管理其中的资源

B、启动rabbitmq内置web插件, 管理rabbitmq账号等信息

rabbitmq-plugins enable rabbitmq_management

控制台的访问地址: http://IP地址:15672/

SpringBoot系列之—Web开发实战

前言

SpringBoot除了可以开发后台服务(Service),Web页面端也是可以的。在之前接触的项目中,主要使用JSP来开发Java Web应用,不过在SpringBoot中,默认推荐是使用 Thymeleaf 模版引擎。

本文主要是小结下SpringBoot的Web开发,初步搭建其开发环境(JSP & Thymeleaf),也对原理进行一些小结。

一、SpringBoot静态资源目录

SpringBoot使用标准的Maven目录结构:

qq20171104-223759 2x

在编译打包成Jar包之后,Jar包内的目录结构是:

qq20171104-224151 2x

编译后,源码工程与编译后的目录对照如下:

/src/main/java ----------BOOT-INF/classes/具体包名下
/src/main/resource -----BOOT-INF/classes/根目录下
pom.xml中的依赖包-----BOOT-INF/lib/
pom.xml----------------META-INF/maven/
SpringBoot工程启动依赖----------org/

SpringBoot默认的静态资源路径:

当我们在浏览器访问SpringBoot项目: http://127.0.0.1:8080/ 的时候,SpringBoot默认从下面的文件夹中加载静态资源:

  • /BOOT-INF/classes/static/
  • /BOOT-INF/classes/public/
  • /BOOT-INF/classes/resources/
  • /META-INF/resources/

二、SpringBoot支持的模板引擎

SpringBoot除了支持JSP外,还支持比较多的模版引擎:

  • Thymeleaf
  • FreeMarker
  • Groovy
  • Velocity

三、SpringBoot整合JSP

SpringBoot整合JSP,最终打包的时候,可以选择以 Jar包/War包的形式输出。

对于以war包输出,在外部tomcat运行,可以参考文尾的参考链接,官方的示例代码。

这里以jar包形式,便于独立运行,目录结构如下:

qq20171112-161912 2x

需要有下面几项配置:

1、配置POM文件

引入依赖包:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-test</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- JSP 依赖. -->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>
<dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <scope>provided</scope>
</dependency>

配置编译参数

上面提到SpringBoot的静态资源目录,编译的时候,将webapp目录拷贝到 META-INF/resources,同时JSP文件没有直接暴露出来,而是包装在 /WEB-INF/jsp 目录下,这样外部就无法直接访问JSP页面,而是要通过SpringMVC重新转向到JSP页面。

<build>
...
  <resources>
		<resource>
			<directory>src/main/webapp</directory>
			<targetPath>META-INF/resources</targetPath>
			<includes>
				<include>**/**</include>
			</includes>
		</resource>
		<resource>
			<directory>src/main/resources</directory>
			<includes>
				<include>**/**</include>
			</includes>
			<filtering>false</filtering>
		</resource>
	</resources>    
</build>

2、配置SpringMVC

编辑 application.properties

spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp

————————————————————————

配置完成后,运行SpringBoot:

访问JSP页面:

通过SpringMVC转向,可以访问JSP页面:

访问: http://127.0.0.1:2001/

qq20171112-161121 2x

JSP页面是无法直接访问的:

http://127.0.0.1:2002/WEB-INF/jsp/jspTest.jsp

qq20171112-163927 2x

访问静态资源

访问: http://127.0.0.1:2001/public.txt

qq20171112-163309 2x

具体工程代码,点击这里获取

四、SpringBoot整合ThymeLeaf

SpringBoot推荐的模版引擎是ThymeLeaf,SpringBoot配置ThymeLeaf比较简单,工程目录结构如下:

qq20171112-164602 2x

注意:

1、ThymeLeaf模版引擎,会从 resources/templates 目录下读取模版,因此可以在该目录存放业务模版页面;

2、在 resources/static 目录下放静态文件,css/images/js 文件。

配置如下:

1、配置POM

由于SpringBoot默认使用 ThymeLeaf2.X版本,要使用3.X版本,需要额外指定:

...
<groupId>com.johnnian</groupId>
<artifactId>springboot-thymeleaf</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
    <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
    <thymeleaf-layout-dialect.version>2.1.1</thymeleaf-layout-dialect.version>
</properties>
...

引入依赖包:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2、配置ThymeLeaf参数

spring.thymeleaf.cache: false
spring.thymeleaf.mode: html

3、运行结果

访问: http://127.0.0.1:2001/

qq20171112-165626 2x

具体工程代码,点击这里获取

参考链接

《微服务文集 (ThoughtWorks洞见)》

作者:TW洞见

感觉这本书上的几篇文章挺好的,对于微服务的特性、实施都有一定的描述。

目前手头刚好有一个项目,正在做架构上的优化与演进,虽然不完全用到微服务,但是也借用了一些框架与工具(Spring Cloud)。生产上的部署,目前还不敢直接用 Docker容器,不过自己在本地演练上线的时候,就用了 Docker容器模拟生产环境。

这本书更像是几篇文章的集合,读薄了,就下面的几点:

一、微服务的定义

@ James Lewis and Martin Fowler

微服务是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间互相协调、互相配合, 为用户提供最终价值。
   
每个服务运行在其独立的进程中,服务与服务间采用轻量级的通信机制互相沟通 (通常是基于HTTP协议的RESTful API)。
    
每个服务都围绕着具体业务进行构建,并且能够被独立地部 署到生产环境、类生产环境等。
    
另外,应当尽量避免统一的、集中式的服务管理机制,对具体的一个服 务而言,应根据业务上下文,选择合适的语言、工具对其进行构建。 
微服务架构通过将功能分解到多个独立的服务,以实现对解决方案或者复杂系统的解耦。

微服务的实施,并不是凑热闹,而是由于实际的需求导致的;

最终应当是需求、是人来决定 架构而非架构决定需求。

二、微服务的特点&好处

下面几点可以判断当前的系统是不是用到微服务:

  • 每个服务是不是跑在独立的进程 中?
  • 是不是采用轻量级的通讯机制? 不是用传统Java的RPC、RMI等重量级的协议,而是用HTTP等契约式的协议。
  • 是不是可以做到独立的部署?

微服务架构的好处:

  • 组件化
  • 弹性架构
  • 去中心化和快速响应
相对于单体架构和SOA,它的主要特点是组件化、松耦合、自治、去中心化.

1.一组小的服务 服务粒度要小,而每个服务是针对一个单一职责的业务能力的封装,专注做好一件事 情。
2.独立部署运行和扩展 每个服务能够独立被部署并运行在一个进程内。
3.独立团队和自治 团队对服务的整个生命周期负责,工作在独立的上下文中,自己决策自己治理,而不 需要统一的指挥中心。

三、微服务演进式的实践

1.架构演进的思路

实施微服务不是目的,而是应用在当前的业务场景下,单体无法灵活调度了,此时自然会想要对当前的架构进行解耦。

所以,在一个崭新的项目中,可以先使用单体结构(可以快速响应,部署、测试的时候也可以整体覆盖),随着业务、功能、并发的增多,使用演进式的架构,逐步肢解单体,使得架构越来越灵活。

这个就需要注意:使用单体结构,开发的时候最好可以对内部的代码层次划分好,这样也便于后面的拆分。例如,Web的话,划分: controller、service、dao、task;对于业务来说,根据接入端的不同也可以划分下;如果单体没有划分好,后面拆的时候非常困难。

引用书中的内容:

为了追求生产力的最大化,一开始我们可以选择从一个单体架构开始,然后争取在微服务架构生产力超 越单体架构的那个复杂度点时切换到微服务架构上来,这样才能实现生产力的最大化。
这就是Martin Fowler提出的单体应用优先原则(MonolithFirst),以单体架构开始,通过演进式设计逐渐重构到微服 务架构。
在马丁即将发表的一篇文章里提到,我们并不建议在一个崭新的项目中一开始就使用微服务进行开发。因为你很可能并不真的理解业务领域,也很难理解各个服务的边界。
因此在这种项目中,可以先做成单块的,进行适当的模块化,加深理解之后再考虑演进成微服务。
跟前面讲的一样,我们始终建议在完全透彻的理解业务背景的前提下再尝试使用微服务。
在一个崭新的项目上,始终建议从一个单块架构开始,因为你不可避免地会犯一些边界划分的错误,除非你真的是对于这个领域烂熟于胸。
就如庖丁解牛一样,拆分需要摸清内部的构造脉络,在筋骨缝隙处下刀。
(单体应用)拆分的关键在于正确理解业务,识别单体内部的业务领域及其边界,并按边界进行拆分。

系统可由单体结构开始,不断的演进。

2.挑战

这段时间在生产环境部署我们的项目(使用负载均衡、带有一些微服务),发现部署是在是复杂,花费两个人,用一天的时间才部署好。

架构越灵活,意味着运维需要做的事情也就更多,像基础设置环境的搭建,业务系统的配置等工作比较繁琐。

引用书中的内容:

微服务架构虽然看起来非常美好,但是也有很大的附加成本.
微服务相比于传统架构,增加了大量的运维负担。有更多的东西需要部署,有更多的地方需要监控,出错的地方自然也成倍增加,有更多的数据库,对应的需要更多的备份。

3.实施微服务后,需要同步考虑的内容:

  1. 分布式的日志收集
我们发现另外一种必要的监控是分布式的日志收集。这允许我们从不同的服务器收集日志并且可以做联
 合查询。

这样分布式的日志允许我们跟踪一个请求在系统不同服务中的跳转过程。
  1. 服务状态的监控
缺乏有效的监控和日志管理,产品环境定位困难。
  1. 自动化部署
    如果没有自动化部署,每次项目新上线,都需要花个一天多的时间来准备环境,这样效率很低。
通过自动化脚本将应用部署到生产环境。
采用部署流水线,由自动化脚本实现全环境的快速部署,包括配置管理、版本管理等。
  1. 接口文档自动化

这一点书中没讲, 但是微服务之间的调用,很多使用RESTFul API,对于接口文档的规范化 也是需要注意的。

需要考虑下使用第三方工具,自动生成接口文档;

Gson转换JSON的坑

在做微信小程序,需要生成子页面的二维码,并且在二维码中需要附带一些参数,例如:page/test/index?q=123

代码如下:

String path = "page/test/index?q=123";
String json = new Gson().toJson(path);

Gson 在转换成JSON的时候, = 号会变成 \u003d,导致生成的二维码不对,无法顺利跳转到具体页面。

出现这个的原因是: Gson会把html标签,转换为Unicode转义字符。

两种解决方法:

方法一:使用 JSONObject

String json = new JSONObject().fromObject(path).toString();

方法二:使用 GsonBuilder

String json = new GsonBuilder().disableHtmlEscaping().create().toJson(path);
;

SpringBoot系列之—Apache Shiro整合

Shiro基础概念

备注: 对于过滤器,user & authc 的区别如下:

user: 参考链接

Filter that allows access to resources if the accessor is a known user, which is defined as having a known principal. This means that any user who is authenticated or remembered via a 'remember me' feature will be allowed access from this filter.
If the accessor is not a known user, then they will be redirected to the loginUrl

authc: 参考链接

Requires the requesting user to be authenticated for the request to continue, and if they are not, forces the user to login via by redirecting them to the loginUrl you configure.

user过滤器,只要用户已经登录过,或者是通过rememberMe的方式登录的,都允许访问;

authc过滤器,要求用户必须授权,一些重要的接口,如支付等,可以设置使用该过滤器;

SpringBoot整合Shiro

1、基础整合

2、CacheManager: EhCache

Ehcache Java 本地缓存的加入,可以大大提高效率,不用每次授权都查询数据库;

3、SessionManager: Redis

PS: 使用Redis做Session管理,有个问题,就是刷新后台的一个页面,会来回调用很多次Redis,这个是有问题的,找到一些解决方法:

补充: 目前对于频繁update sesion的问题,我的处理方法如下:

1). 对于 RedisSessionDao中的doUpdate,不做任何处理;

    /**
     * 刷新session: 不做任何处理
     */
    @Override
    protected void doUpdate(Session session) {     
        super.doUpdate(session);
    }

2). 使用SpringBoot的拦截器,拦截所有请求,并且判断当前用户是否登录,如果已经登录,则手动刷新Session(Redis中对应的Key增加有效时间),这样就可以大大减少Shiro频繁更新session的问题了;

4、Shiro-Thymeleaf标签

5、自定义错误页面

整合Shiro之后,访问错误资源的时候,默认的错误页面如下:

qq20180305-154024 2x

SpringBoot可以自定义配置错误页面,错误页面目录结构需要符合下面结构:

src/
 +- main/
     +- java/
     |   + <source code>
     +- resources/
         +- public/
             +- error/
             |   +- 404.html
             +- <other public assets>

同时,SpringBoot需要配置下面的Bean:

	@Bean
	public EmbeddedServletContainerCustomizer containerCustomizer() {
	    return new EmbeddedServletContainerCustomizer() {
			@Override
			public void customize(ConfigurableEmbeddedServletContainer container) {
				 ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404.html");
				 ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500.html");
				 container.addErrorPages(error404Page);
				 container.addErrorPages(error500Page);
			}
	    };
	}

具体,参考下面链接:

Nginx配置多个HTTPS域名

最近在玩微信小程序,手头有:

  • 一台云服务器:CentOS 7.4
  • 多个一级域名

开发测试过程中,因为某些原因,想要让手头的A、B域名同时指向云服务器的443端口,支持HTTPS。

Nginx支持TLS协议的SNI扩展(同一个IP上可以支持多个不同证书的域名),只需要重新安装Nginx,使其支持TLS即可。

安装Nginx

[root@ localhost ~]#  wget https://www.openssl.org/source/openssl-1.0.2n.tar.gz
[root@ localhost ~]#  tar zxvf openssl-1.0.2n.tar.gz
[root@ localhost ~]# wget http://nginx.org/download/nginx-1.12.0.tar.gz
[root@ localhost ~]# tar zxvf nginx-1.12.0.tar.gz
[root@ localhost ~]# yum -y install zlib zlib-devel openssl openssl--devel pcre pcre-devel
[root@ localhost ~]# cd nginx-1.12.0
[root@ localhost nginx-1.12.0]# ./configure --prefix=/usr/local/nginx --with-http_ssl_module \
--with-openssl=../openssl-1.0.2n \
--with-openssl-opt="enable-tlsext"
[root@ localhost nginx-1.12.0]# make &&  make install

安装好的路径: /usr/local/nginx

备注:在安装的过程中发现,云服务器的环境中缺少一些库,下载后,重新执行Nginx的./configure指令,具体操作如下:

[root@ localhost ~]#   wget https://nchc.dl.sourceforge.net/project/pcre/pcre/8.35/pcre-8.35.tar.gz
[root@ localhost ~]#   tar zxvf pcre-8.35
[root@ localhost ~]#   yum -y install gcc
[root@ localhost ~]#   yum -y install gcc-c++
[root@ localhost ~]#   yum install -y zlib-devel

[root@ localhost ~]#   ./configure --prefix=/usr/local/nginx --with-http_ssl_module \
--with-openssl=../openssl-1.0.2n \
--with-openssl-opt="enable-tlsext" \
--with-pcre=../pcre-8.35
[root@ localhost nginx-1.12.0]# make &&  make install

配置Nginx

在购买域名的时候,如果域名提供商有免费的SSL证书,就直接用;如果没有的话,可以使用 Let's Encript 生成免费的CA证书。

打开Nginx的配置:

[root@ localhost ~]# vi /usr/local/nginx/nginx.conf
	...
	server {
		listen       443 ssl;
		#listen       [::]:443 ssl;
		server_name  abc.com;
		root         /usr/share/nginx/html;
		
		ssl_certificate "/root/keys/abc.com.pem";
		ssl_certificate_key "/root/keys/abc.com.private.pem";
		include /etc/nginx/default.d/*.conf;
		
		location / {
		}
		error_page 404 /404.html;
		    location = /40x.html {
		}
		error_page 500 502 503 504 /50x.html;
		    location = /50x.html {
		}
	}
	
	server {
		listen       443 ssl;
		#listen       [::]:443 ssl;
		server_name  def.com;
		root         /usr/share/nginx/html;
		
		ssl_certificate "/root/keys/def.com.pem";
		ssl_certificate_key "/root/keys/def.com.private.pem";
		include /etc/nginx/default.d/*.conf;
		
		location / {
		}
		error_page 404 /404.html;
		    location = /40x.html {
		}
		error_page 500 502 503 504 /50x.html;
		    location = /50x.html {
		}
	}
     
	

配置完成后,重新加载Ngixn:

[root@ localhost ~]#  nginx -s reload

申请免费的CA证书

对于没有SSL证书的情况,可以用下面的方法免费获得CA证书——Let's Encript。

步骤1: 安装 Let's Encrypt 官方客户端——CetBot

使用certbot-auto脚本安装

[root@ localhost ~]#  wget https://dl.eff.org/certbot-auto
[root@ localhost ~]#  chmod a+x certbot-auto

步骤2: 配置Nginx的配置文件,在 Server 模块(监听80端口的)添加下面配置:

CertBot在验证服务器域名的时候,会生成一个随机文件,然后CertBot的服务器会通过HTTP访问你的这个文件,因此要确保你的Nginx配置好,以便可以访问到这个文件。

server {
  	listen       80 default_server;
  	
  	...
  	
	location ^~ /.well-known/acme-challenge/ {   
		default_type "text/plain";   
		root     /usr/share/nginx/html;
	}
	
	location = /.well-known/acme-challenge/ {   
		return 404;
	}
}

重新加载Nginx: nginx -s reload

步骤3: 申请SSL证书

[root@ localhost~ ]# ./certbot-auto certonly --webroot -w /usr/share/nginx/html/ -d your.domain.com

安装过程中,会提示输入邮箱,用于更新CA证书的。

安装成功后,默认会在 /etc/letsencrypt/live/your.domain.com/ 会生成CA证书。

|-- fullchain.pem 
|-- privkey.pem

步骤4: 配置Nginx

server {
	listen       443 ssl;
	listen       [::]:443 ssl;
	server_name  def.com;
	root         /usr/share/nginx/html;
	
	ssl_certificate "/etc/letsencrypt/live/your.domain.com/fullchain.pem";
	ssl_certificate_key "/etc/letsencrypt/live/your.domain.com/privkey.pem";
	include /etc/nginx/default.d/*.conf;
	
	location / {
	}
	error_page 404 /404.html;
	    location = /40x.html {
	}
	error_page 500 502 503 504 /50x.html;
	    location = /50x.html {
	}
}

配置完,重新加载Nginx

步骤5: 自动更新证书

在命令行先进行模拟更新证书

./certbot-auto  renew --dry-run

如果模拟更新成功,则 使用 crontab -e 命令来启用自动更新任务:

[root]# crontab -e

30 2 * * 1 /root/certbot-auto renew  >> /var/log/le-renew.log

相关参考

Git 常用命令速查表

什么是 Git?

Git 是由 Linux 之父 Linus Tovalds 为了更好地管理linux内核开发而创立的分布式版本控制/软件配置管理软件。目前支持 Windows 、MacOSX 、Linux 等多种主流平台,特点为快速、高效及易于使用。

新手入门教程推荐


最常用的命令:

  • $ git add . #跟踪所有改动过的文件
  • $ git commit -m "Message" #提交所有更新过的文件
  • $ git push origin master #上传代码及快速合并
  • $ git pull origin master #下载代码及快速合并

创建版本库

  • $ git clone <url> #克隆远程版本库
  • $ git init #初始化本地版本库

修改和提交

  • $ git status #查看状态
  • $ git diff #查看变更内容
  • $ git add . #跟踪所有改动过的文件
  • $ git add <file> #跟踪指定的文件
  • $ git mv <old> <new> #文件改名
  • $ git rm <file> #删除文件
  • $ git rm --cached <file> #停止跟踪文件但不删除
  • $ git commit -m “commit message” #提交所有更新过的文件
  • $ git commit --amend #修改最后一次提交

查看提交历史

  • $ git log #查看提交历史

  • $ git log -p <file> #查看指定文件的提交历史

  • $ git blame <file> #以列表方式查看指定文件的提交历史

撤消

  • $ git reset --hard HEAD #撤消工作目录中所有未提交文件的修改内容
  • $ git checkout HEAD <file> #撤消指定的未提交文件的修改内容
  • $ git revert <commit> #撤消指定的提交

分支与标签

  • $ git branch #显示所有本地分支
  • $ git checkout <branch/tag> #切换到指定分支或标签
  • $ git branch <new-branch> #创建新分支
  • $ git branch -d <branch> #删除本地分支
  • $ git tag #列出所有本地标签
  • $ git tag <tagname> #基于最新提交创建标签
  • $ git tag -d <tagname> #删除标签

合并与衍合

  • $ git merge <branch> #合并指定分支到当前分支
  • $ git rebase <branch> #衍合指定分支到当前分支

远程操作

  • $ git remote -v #查看远程版本库信息
  • $ git remote show <remote> #查看指定远程版本库信息
  • $ git remote add <remote> <url> #添加远程版本库
  • $ git fetch <remote> #从远程库获取代码
  • $ git pull <remote> <branch> #下载代码及快速合并
  • $ git push <remote> <branch> #上传代码及快速合并
  • $ git push <remote> :<branch/tag-name> #删除远程分支或标签
  • $ git push --tags #上传所有标签

CentOS 7 搭建局域网VPN-Shadowsocks

前言

想要搭建一个VPN,方便在外网的时候远程连接公司局域网,可以使用Shadowsocks,很方便滴搭建,网络拓扑图如下:

qq20180212-101958 2x

需要的资源:

  • 公网服务器(带公网IP): 1台,用于作网络请求端口转发
  • 公司局域网服务: 1台,用于搭建Shadowsocks服务端

局域网服务器:安装配置Shadowsocks

步骤1: 安装Shadowsocks

#安装 python setup tools
[root@localhost ~]# yum install python-setuptools
#安装pip
[root@localhost ~]# easy_install pip
#升级 pip
[root@localhost ~]# pip install --upgrade pip
#安装 shadowsocks
[root@localhost ~]# pip install shadowsocks

步骤2: 创建Shadowsocks服务

[root@localhost ~]# vi /usr/lib/systemd/system/shadowsocks.service
[Unit]
Description=Shadowsocks Server
Documentation=https://github.com/shadowsocks/shadowsocks
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
#设置启动时的配置文件,根据自己的需求改.
ExecStart=/usr/bin/ssserver -c /etc/shadowsocks.json -d start
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/usr/bin/ssserver -d stop
[Install]
WantedBy=multi-user.target

步骤3: 创建Shadowsocks配置

[root@localhost ~]# vi /etc/shadowsocks.json
{
      "server":"0.0.0.0",
      "server_port":2082,
      "local_port":1080,
      "password":"LsY61dyK",
      "timeout":600,
      "method":"rc4-md5"
 }

备注:

  • server: 默认填写 0.0.0.0, 表示允许任何IP访问
  • server_port: shadowsocks 端口
  • password: 密码
  • method: 加密模式

步骤4: 启动服务

[root@localhost ~]#  systemctl enable shadowsocks
[root@localhost ~]#  systemctl start shadowsocks

这里需要注意:如果启动shadowsocks之后,访问还是有问题,可以确认下主机的防火墙是。

步骤5: 创建AUTOSSH反向代理

使用公网服务器作端口转发,公网服务器需要开启对应的端口,例如,这里使用 2082端口,则公网服务器需要开放2082端口。

#shadowsocks
[root@localhost ~]#  autossh -M 5678 -NR 2082:localhost:2082 -f [email protected]

备注: 对于如何使用AUTOSSH,请参考之前博文,autossh内外网穿透方法

客户端配置

qq20180212-103834 2x

客户端填写的配置:

  • 地址: 123.123.123.123(上面的公网IP)
  • 端口: 2082(公网IP作为映射的端口)
  • 加密方法: 填写RC4-MD5

参考链接

日志服务—阿里云日志服务使用记录

前言

之前写过关于搭建日志统一收集系统的方法,针对中小型系统,可以简单适用,参考这里:日志服务—使用Flume-NG/Kafaka搭建日志搜集服务

传统自建日志服务,有一些弊端:

  • 维护成本高
  • 经济成本高

试用了阿里云的日志服务,发现有几个亮点:

  • 支持ECS/非ECS服务的日志搜集:意味着如果想要汇总公司局域网的后台日志,也是可以滴;
  • 日志采集客户端可以云端配置:这个就非常方便了;
  • 强大的日志分析与处理;
  • 经济实惠;

过程记录

下面就只是记录下安装Logtail过程中的一点问题,具体使用以及原理,官网文档足够详细。

操作系统: CentOS 7.4

一、安装Logtail

在阿里云上创建日志服务的「项目」,需要注意的是,「项目」选择哪个区域,在官网的Logtail下载地址也需要选择对应链接,否则,Logtail客户端将无法和日志服务通信。

例如,创建 「华东2」区域的日志项目:

1、ECS下安装Logtail

对于ECS(VPC网络),选择「华东2」区域的下载:

[root@localhost ~]# wget http://logtail-release-sh.vpc100-oss-cn-shanghai.aliyuncs.com/linux64/logtail.sh; chmod 755 logtail.sh; sh logtail.sh install cn_shanghai_vpc

2、非ECS安装Logtail

步骤1: 下载Logtail

对于非ECS服务器,选择「华东2」区域的下载:

[root@localhost ~]# wget http://logtail-release.oss-cn-hangzhou.aliyuncs.com/linux64/logtail.sh; chmod 755 logtail.sh; sh logtail.sh install cn_shanghai_internet

步骤2: 配置用户标志

参考链接:非ECS(或线下机器)创建用户标志

步骤3: 重新启动Logtail

[root@izuf6dm5xm1ukjp6vlgmqzz ~]# /etc/init.d/ilogtaild stop
[root@izuf6dm5xm1ukjp6vlgmqzz ~]# /etc/init.d/ilogtaild start
[root@izuf6dm5xm1ukjp6vlgmqzz ~]# /etc/init.d/ilogtaild status

二、日志分段解析配置

目前服务器的日志,类似如下:

127.0.0.1|2018-02-10 14:22:24.542| INFO|http-nio-2003-exec-1|com.johnnian.Interceptor.InterceptorHandler:49|/query| - [HTTP请求]开始处理---------------
127.0.0.1|2018-02-10 14:22:24.543| INFO|http-nio-2003-exec-1|com.johnnian.Interceptor.InterceptorHandler:94|/query| - [请求信息]类型:JSON RAW 流

可以通过简单的配置规则,将log中的各个字段拆分出来,步骤如下:

qq20180210-143121 2x

qq20180210-143219 2x

qq20180210-143314 2x

应用到机器组中之后,新的日志上来,阿里的日志服务会自动将其分段,示例如下:

qq20180210-143526 2x

参考: 分隔符日志

附:遇到问题

可以通过Logtail日志来排查问题:

[root@localhost ~]# tail -100f /usr/local/ilogtail/ilogtail.LOG

1、Logtail心跳失败——无法读取本地IP地址,因为网卡名字的原因

在线下机器上安装Logtail,登录阿里云管理平台,发现Logtail客户端心跳异常,查看了下,原因是Logtail客户端无法读取本机IP地址导致。

默认情况下,Logtail 客户端会读取系统第一个网卡(eth0)的IP地址,而操作的CentOS默认的网卡名字不是 「eth0」,因此就一直读取不到IP地址。

可以从Logtail的应用配置文件看出问题:

[root@localhost ~]# cat /usr/local/ilogtail/app_info.json
{
   "UUID" : "01122243-****-4CBF-****-278ED0536481",
   "hostname" : "****",
   "instance_id" : "*",
   "ip" : "",
   "logtail_version" : "0.16.3",
   "os" : "Linux; 3.10.0-693.2.2.el7.x86_64; #1 SMP Tue Sep 12 22:26:13 UTC 2017; x86_64",
   "update_time" : "2018-02-09 15:56:53"
}
[root@izuf6dm5xm1ukjp6vlgmqzz ~]#

解决方案: 修改CentOS默认网卡名字为「eth0」,然后重启Logtail,即可,参考 CentOS 7 更改网卡名

2、心跳成功,但是日志上传失败, 查看Logtail日志,报错如下:

[2018-02-09 14:31:28.540046]    [ERROR] [2838]  [build/release64/sls/ilogtail/Sender.cpp:191]   send data to SLS fail:discard data      StatusCode:401  RequestId:5A7D40401BABB893FC91730A      ErrorCode:Unauthorized  ErrorMessage:no authority, denied by ACL        projectName:ticketcom   logstore:ticketcom_dev  RetryTimes:1    LogLines:389    bytes:15334     endpoint:http://cn-shanghai.log.aliyuncs.com
[2018-02-09 14:31:32.364462]    [WARNING]       [2838]  [build/release64/sls/ilogtail/ConfigManager.cpp:1860]   GetAccessKey:Fail       no GetAccessKey in response:{"Error":{"Code":"OLSParameterInvalid","Message":"The parameter is invalid : uuid=none AccessKeyId pair","RequestId":"5A7D40444F64FDCD6B8FD55E"}}

Deny By ACL, 原因是因为当前的登录账号没有配置 AccessKey,配置一下就可以了(配置主账号AccessKey,而不是子账号),参考:错误诊断:SEND_DATA_FAIL_ALARM

官方参考文档

1

1

autossh内外网穿透方法

场景

1、操作系统: CentOS 7.4

2、资源:

  • 内网服务器:1台
  • 阿里云服务器:1台
  • 公网IP:123.123.123.123

3、目标: 实现外网穿透到局域网的服务器,访问服务器的后台系统。

4、解决方法:内网服务器通过autossh,与公网服务器之间建立稳定的端口映射关系。

具体的步骤如下:

配置SSH免密登录

[root@localhost ~]# ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:0GkZLmelyX6WVKKF7k4eQ16FMK1G9G9m96NQJkG/y18 [email protected]
The key's randomart image is:
+---[RSA 2048]----+
|       .=++o.    |
|       +o%+oo    |
|      oo&.oo .   |
|       B=..o. .  |
|       =S.+.=+.  |
|        *o +=... |
|       + o . o .E|
|        o   . o o|
|             . . |
+----[SHA256]-----+

[root@localhost ~]# ssh-copy-id -i /root/.ssh/id_rsa.pub [email protected]

安装AUTOSSH

安装AUTOSSH

[root@localhost ~] wget http://www.harding.motd.ca/autossh/autossh-1.4e.tgz
[root@localhost ~] gunzip -c autossh-1.4e.tgz | tar xvf -
[root@localhost ~] cd autossh-1.4e
[root@localhost autossh-1.4e] ./configure
[root@localhost autossh-1.4e] make & make install

配置AUTOSSH

[root@localhost ~] autossh -M 5678 -NR 1234:localhost:8080 -f [email protected]

备注:

  • 5678 端口:负责通过这个端口监视连接状态,连接有问题时就会自动重连
  • 1234 端口:远程服务器的端口
  • localhost:8080: 本地或内网IP地址、端口
  • -f : 后台运行

如果想要断开AUTOSSH的隧道连接,只需要把 AUTOSSH监听端口的进程 kill 掉就可以了:

[root@localhost ~]# netstat -apn | grep 5678
tcp        0      0 127.0.0.1:5678          0.0.0.0:*               LISTEN      8843/ssh
tcp6       0      0 ::1:5678                :::*                    LISTEN      8843/ssh
[root@localhost ~]# kill -9 8843

如果想同时开多个隧道,则 AUTOSSH的监听端口必须也开多个,监听端口不能一样。

修改sshd配置

配置完AUTO SSH之后,发现还是无法穿透,解决方法是配置下ssh, 开启 GatewayPorts 参数即可。

#修改配置
[root@localhost ~] vi /etc/ssh/sshd_config
GatewayPorts yes
#重启SSHD
#CentOS 7
[root@localhost ~]  systemctl restart sshd.service
#CentOS 6
[root@localhost ~]  /etc/init.d/sshd restart

GatewayPorts原理:

当请求一个TCP端口的时候,默认情况下,SSH只监听本机地址,这就导致AUTOSSH虽然穿透到阿里云服务器,但是外网还是无法通过映射的端口 访问局域网资源。

When you forward a TCP port (either locally or remotely), by default SSH only listens for connections to the forwarded port on the loopback address (localhost, 127.0.0.1).

备注

SpringBoot系列之—瘦身部署

一、前言

SpringBoot部署起来虽然简单,如果服务器部署在公司内网,速度还行,但是如果部署在公网(阿里云等云服务器上),部署起来实在头疼: 编译出来的 Jar 包很大,如果工程引入了许多开源组件(SpringCloud等),那就更大了。

这个时候如果想要对线上运行工程有一些微调,则非常痛苦, :(

二、瘦身前的Jar包

Tomcat在部署Web工程的时候,可以进行增量更新,SpringBoot也是可以的~

SpringBoot编译出来的Jar包中,磁盘占用大的,是一些外部依赖库(jar包),例如:

进入项目工程根目录,执行 mvn clean install 命令,得到的Jar包,用压缩软件打开,目录结构如下:

qq20171112-191453 2x

整个Jar包 18.18 MB, 但是 BOOT-INF/lib 就占用了将近 18 MB:

qq20171112-192041 2x

三、解决方法

步骤1: 正常编译JAR包,解压出lib文件夹

POM文件如下:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
		        <mainClass>com.johnnian.App</mainClass>
		        <layout>ZIP</layout>
            </configuration>
            <executions>
		    <execution>
		         <goals>
		             <goal>repackage</goal>
		         </goals>
		     </execution>
           </executions>
        </plugin>
     <plugins>
<build>

进入项目根目录,执行命令: mvn clean install

将编译后的Jar包解压,拷贝 BOOT-INF 目录下的lib文件夹 到目标路径;

步骤2: 修改pom.xml配置,编译出不带 lib 文件夹的Jar包

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
		        <mainClass>com.johnnian.App</mainClass>
		        <layout>ZIP</layout>
		        <includes> 
			        <include>
			            <groupId>nothing</groupId>
			            <artifactId>nothing</artifactId>
			        </include>  
			    </includes>
		    </configuration>
		    <executions>
		        <execution>
		            <goals>
		                <goal>repackage</goal>
		            </goals>
		        </execution>
		    </executions>
        </plugin>
     <plugins>
<build>

配置完成后,再次执行编译:mvn clean install

生成的 Jar 包体积明显变小,如下所示, 外部的 jar 包已经不会被引入了:

qq20171112-193001 2x

步骤3: 运行编译后的Jar包

步骤1 解压出来的lib文件夹步骤2编译的jar包放在同一个目录, 运行下面命令:

java -Dloader.path=/path/to/lib -jar /path/to/springboot-jsp-0.0.1-SNAPSHOT.jar 

备注:

  • /path/to/改成实际的路径。
  • -Dloader.path=lib文件夹路径

最终目录文件结构是:

├── lib   #lib文件夹
└── springboot-jsp-0.0.1-SNAPSHOT.jar 

说明

1、通常,一个工程项目架构确定后,引入的jar包基本上不会变,改变的大部分是业务逻辑;

2、后面如果需要变更业务逻辑,只需要轻量地编译工程,大大提高项目部署的效率。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.