使用Nginx+Lua实现Web项目的灰度发布

需求:

领导对时间要求紧迫、研发对现有系统摸不透、做到数据的兼容性,基于这样的要求就必须做到系统上线采用灰度的方式,指定忠实用户进行线上测试、选取有特征的群体进行线上测试和基于流量切换的方式进行线上测试等。

常规的部署做法:

常规的部署方式是采用Nginx的 upstream  配置来简单的实现新旧机器的切换, 在开发过程中,开发完成,完成测试阶段,修复bug后都要重启后台服务,测试又在测试,每次重启都要一两分钟,平凡的重启,测试不干了;所以想到就是部署两台服务器;用nginx upstream 模块实现 无感知部署,发现一个bug,修复;直接部署不会打断测试;常用的是一台部署完毕之后部署另外一台机器; 

灰度发布概述:

灰度发布,简单来说,就是根据各种条件,让一部分用户使用旧版本,另一部分用户使用新版本。

灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面 来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

灰度部署还可以根据设定的规则将请求路由到我们的灰度版本(灰度机器)上来。比如对于API来说,一般有如下几个需求:特定用户(比如测试帐号)、 特定的App(比如测试app或者合作App)、特定的模块、接口(只有某些接口需要灰度,这种一般是API Container的修改,拿一些不是很重要的API做灰度测试)、特定的机器(某些请求IP转发到灰度机)等。

本章只是简单的简述灰度部署的实现思路:

这里我们所做的灰度发布稍作改变:用1-2台机器作为B,B测试成功再部署A。用于WEB系统新代码的测试发布,让一部分(IP)用户访问新版本,一部分用户仍然访问正常版本,原理如图:

 image.png


执行过程:
1
、当用户请求到达前端web(代理)服务器Openresty,内嵌的lua模块解析Nginx配置文件中的lua脚本代码;
2
、Lua获取客户端IP地址,去查询Redis中是否有该键值,如果有返回值执行@clien2,否则执行@client1。
3
、Location @client2把请求转发给预发布服务器,location @client1把请求转发给生产服务器,服务器返回结果,整个过程完成。

Lua 的好处并不至于这个,可以使用LUA语言是实现一些业务上的负载,比如热点分离; 热点数据的自动降级机制;

  Lua教程: https://www.runoob.com/lua/lua-tutorial.html


案例实现:

1.       安装部署OpenResty:

OpenResty由Nginx核心加很多第三方模块组成,默认集成了Lua开发环境,使得Nginx可以作为一个Web Server使用。
     借助于Nginx的事件驱动模型和非阻塞IO,可以实现高性能的Web应用程序。
     而且OpenResty提供了大量组件如Mysql、Redis、Memcached等等,使在Nginx上开发Web应用更方便更简单。

1、部署第一个nginx,作为应用层nginx(192.168.1.104那个机器上)

1、  创建目录/usr/servers,以后我们把所有软件安装在此目录

mkdir -p /usr/servers 

cd /usr/servers/

2、  安装依赖(我的环境是centos,可以使用如下命令安装,其他的可以参考openresty安装步骤)

   yum install -y readline-devel pcre-devel openssl-devel gcc

 3、  下载ngx_openresty-xxx.tar.gz并解压(ngx_openresty-xxx/bundle目录里存nginx核心和很多第三方模块,比如有我们需要的Lua和LuaJIT。)

wget https://openresty.org/download/ngx_openresty-1.9.7.1.tar.gz

tar xvf ngx_openresty-1.9.7.1.tar.gz

 cd ngx_openresty-1.9.7.1

2. 安装LuaJIT

cd bundle/LuaJIT-2.1-20151219/

make clean && make && make install

 ln -sf luajit-2.1.0-alpha /usr/local/bin/luajit

 下载ngx_cache_purge模块,该模块用于清理nginx缓存

root@user:/usr/servers/ngx_openresty-1.9.7.1/bundle# wget https://github.com/FRiCKLE/ngx_cache_purge/archive/2.3.tar.gz
root@user:/usr/servers/ngx_openresty-1.9.7.1/bundle# tar -xvf 2.3.tar.gz

下载nginx_upstream_check_module模块,该模块用于ustream健康检查

             root@user:/usr/servers/ngx_openresty-1.9.7.1/bundle# 

                   wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/v0.3.0.tar.gz

root@user:/usr/servers/ngx_openresty-1.9.7.1/bundle# tar -xvf v0.3.0.tar.gz


  安装ngx_openresty

root@user:/usr/servers/ngx_openresty-1.9.7.1/bundle# cd .. 
root@user:/usr/servers/ngx_openresty-1.9.7.1# ./configure –prefix=/usr/servers –with-http_realip_module –with-pcre –with-luajit –add-module=./bundle/ngx_cache_purge-2.3/ –add-module=./bundle/nginx_upstream_check_module-0.3.0/ -j2 
root@user:/usr/servers/ngx_openresty-1.9.7.1# make && make install

参数说明:
–with***
安装一些内置/集成的模块
–with-http_realip_module
取用户真实ip模块
-with-pcre Perl
兼容的达式模块
–with-luajit
集成luajit模块
–add-module
添加自定义的第三方模块,如此次的ngx_che_purge

  到/usr/servers目录下用ll命令查看,会发现多出来了如下目录,说明安装成功

root@user:/usr/servers/ngx_openresty-1.9.7.1# cd /usr/servers/ 
root@user:/usr/servers# ll

image.png

说明:
/usr/servers/luajit
:luajit环境,luajit类似于java的jit,即即时编译,lua是一种解释语言,通过luajit可以即时编译lua代码到机器代码,得到很好的性能;
/usr/servers/lualib
:要使用的lua库,里边提供了一些默认的lua库,如redis,json库等,也可以把一些自己开发的或第三方的放在这;
/usr/servers/nginx
:安装的nginx,通过/usr/servers/nginx/sbin/nginx -V 查看nginx版本和安装的模块

启动nginx

root@user:/usr/servers# /usr/servers/nginx/sbin/nginx
检测配置是否正确(需要先切换到root用户):
root@user:/usr/servers# /usr/servers/nginx/sbin/nginx -t
重启nginx:
root@user:/usr/servers# /usr/servers/nginx/sbin/nginx -s reload


LUA环境测试:

1、              为了方便开发我们在/usr/servers/nginx/conf目录下创建一个lua.conf 
root@user:/home/user# cd /usr/servers/nginx/conf
root@user:/usr/servers/nginx/conf# vim lua.conf

server  {

       listen 80;

       server_name _;

       #HelloWorld

       location /lua {

              default_type 'text/html';

              content_by_lua 'ngx.say("hello world")';

       }

}

编辑nginx.conf配置文件 

vim /usr/servers/nginx/conf/nginx.conf 
在http部分添加如下配置 
lua_package_path "/usr/servers/lualib/?.lua;;"; #lua
模块 
lua_package_cpath "/usr/servers/lualib/?.so;;"; #c
模块 
include lua.conf; #
单独lua配置

重启nginx

  /usr/servers/nginx/sbin/nginx -s reload

 访问如http://192.168.1.104/lua(自己的机器根据实际情况换ip),可以看到如下内容 

hello world
说明配置成功。

灰度部署测试:

采用redis 方式;比如 192.168.0.101这个IP采用的是访问服务器的项目资源,其他IP是访问旧项目的资源;进行测试完毕并且完成之后,可以切换正式环境;

image.png

默认情况下lua_code_cache  是开启的,即缓存lua代码,即每次lua代码变更必须reload nginx才生效,如果在开发阶段可以通过lua_code_cache  off;关闭缓存,这样调试时每次修改lua代码不需要reload nginx;但是正式环境一定记得开启缓存

开启后reload nginx会看到如下报警
nginx: [alert] lua_code_cache is off; this will hurt performance ******;


配置文件:

Nginx.conf:

#user  nobody;
worker_processes  1;
 
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
 
#pid        logs/nginx.pid;
 
 
events {
    worker_connections  1024;
}
 
 
http {
    include       mime.types;
    default_type  application/octet-stream;
       
       lua_package_path "/usr/servers/lualib/?.lua;;"; #lua 模块 
       lua_package_cpath "/usr/servers/lualib/?.so;;"; #c模块 
       
 
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
 
    access_log  logs/access.log  main;
 
    sendfile        on;
    #tcp_nopush     on;
 
    #keepalive_timeout  0;
    keepalive_timeout  65;
 
    #gzip  on;
       
       proxy_buffering             off;
    proxy_set_header            Host $host;
    proxy_set_header            X-real-ip $remote_addr;
    proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
       
       
       upstream productServer {
         server 127.0.0.1:8080 weight=1 max_fails=3 fail_timeout=100s; #模拟生产服务器
       }
 
       upstream preServer {
         server 127.0.0.1:8081 weight=1 max_fails=3 fail_timeout=100s;  #模拟预发布服务器
       }
 
 
    server {
        listen       80;
        server_name  localhost;
 
        #charset koi8-r;
 
        #access_log  logs/host.access.log  main;
 
        location / {
                default_type 'text/html';  
          lua_code_cache off;  
          content_by_lua_file /usr/servers/nginx/conf/huidu.lua;
        }
 
        #error_page  404              /404.html;
        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
 
              location @productServer{
                proxy_pass http://productServer;
        }
              
        location @preServer{
                proxy_pass http://preServer;
        }
    
    }
 
 
 
}

huidu.lua:

local redis = require "resty.redis" 
local cache = redis.new() 
cache:set_timeout(60000)
 
local ok, err = cache.connect(cache, '127.0.0.1', 6379) 
if not ok then 
    ngx.say("failed to connect:", err) 
    return 
end 
 
--local red, err = cache:auth("foobared")
--if not red then
    --ngx.say("failed to authenticate: ", err)
    --return
--end
 
local local_ip = ngx.req.get_headers()["X-Real-IP"]
if local_ip == nil then
    local_ip = ngx.req.get_headers()["x_forwarded_for"]
end
 
if local_ip == nil then
    local_ip = ngx.var.remote_addr
end
--ngx.say("local_ip is : ", local_ip)
 
local intercept = cache:get(local_ip) 
 
 
if intercept == local_ip then
    ngx.exec("@preServer")
    return
end
 
ngx.exec("@productServer")
 
local ok, err = cache:close() 
 
if not ok then 
    ngx.say("failed to close:", err) 
    return 
end

上面的代码是简单的IP相等,可以采用IP段形式;


上面的例子测试:

image.png

image.png

image.png

额外参考资料:

https://my.oschina.net/izhangll/blog/884713

Redis Sentinel 容灾演练

本文主要介绍基于redis高可用的集群搭建,并做相应的高可用的容灾演练过程,熟悉一下redis高可用的方案配置;

简单的redis 系列教程可以查看之前的文章 http://www.dczou.com/viemall/tag/redis

首先的知道redis Sentinel 主从原理机制:

image.pngimage.png

image.pngimage.png


安装搭建Sentinel 集群:

分别有3个Sentinel节点,1个主节点,1个从节点组成一个简单的高可用方案;

role

IP

port

master

192.168.1.104

6379

slave

192.168.1.105

6379

Sentinel1

192.168.1.104

5000

Sentinel2

192.168.1.105

5000

Sentinel3

192.168.1.106

5000

安装redis 和部署redis 的主从方式,本章不阐述

配置sentinel.conf

哨兵默认用26379端口,默认不能跟其他机器在指定端口连通,只能在本地访问

192.168.1.104机器实例:

将sentinel.conf 复制 /usr/local/redis/etc

cp /opt/program/tools/redis-4.0.10/sentinel.conf  /usr/local/redis/etc

配置如下:

port 5000

bind 192.168.1.104

dir /tmp

sentinel monitor mymaster 192.168.1.104 6379 2

sentinel down-after-milliseconds mymaster 30000

sentinel failover-timeout mymaster 60000

sentinel parallel-syncs mymaster 1

daemonize yes

logfile /usr/local/redis/logs/sentinel_5000_log.log

192.168.1.105机器实例:

port 5000
bind 192.168.1.105
dir /tmp
sentinel monitor mymaster 192.168.1.104 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
daemonize yes
logfile /usr/local/redis/logs/sentinel_5000_log.log

192.168.1.106机器实例:

port 5000
bind 192.168.1.106
dir /tmp
sentinel monitor mymaster 192.168.1.104 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
daemonize yes
logfile /usr/local/redis/logs/sentinel_5000_log.log

Sentinel 配置文件解析:

http://www.mamicode.com/info-detail-1898706.html

检查设置:

启动哨兵进程

在192.168.1.104、192.168.1.105、192.168.1.106三台机器上,分别启动三个哨兵进程,组成一个集群,观察一下日志的输出

启动哨兵:

   方式一:redis-sentinel /path/to/sentinel.conf(推荐,这种方式启动和redis实例没有任何关系)

  方式二:redis-server /path/to/sentinel.conf –sentinel

  image.png

  日志里会显示出来,每个哨兵都能去监控到对应的redis master,并能够自动发现对应的slave

  哨兵之间,互相会自动进行发现,用的就是之前说的pub/sub,消息发布和订阅channel消息系统和机制


检查哨兵状态

redis-cli -h 192.168.1.104 -p 5000

sentinel master mymaster

SENTINEL slaves mymaster

SENTINEL sentinels mymaster

SENTINEL get-master-addr-by-name mymaster


检查各个redis状态查看

104、105主从状态查看命令

image.png

image.png


演练步骤:

哨兵节点的增加和删除:

    增加sentinal,会自动发现

   删除sentinal的步骤:

    (1)停止sentinal进程

    (2)SENTINEL RESET *,在所有sentinal上执行,清理所有的master状态

    (3)SENTINEL MASTER mymaster,在所有sentinal上执行,查看所有sentinal对数量是否达成了一致


slave的永久下线

   让master摘除某个已经下线的slave:SENTINEL RESET mastername,在所有的哨兵上面执行

    slave-priority,值越小优先级越高

容灾演练:

  master redis主节点宕机,看是否slave会选举成新的master节点;

  通过哨兵看一下当前的master:SENTINEL get-master-addr-by-name mymaster

  image.png

  image.png

   已经切换成功了;

    可以看到log信息

    image.png

    查看sentinal的日志,是否出现+sdown字样,识别出了master的宕机问题; 然后出现+odown字样,就是指定的quorum哨兵数量,都认为master宕机了

故障恢复

再将旧的master重新启动,查看是否被哨兵自动切换成slave节点

image.png

经过以上步骤,基本的sentinel下的高可用一主一从redis高可用就配置完成了。

java测试代码:

public static void main(String[] args) {

        Set<String> sentinels = new HashSet<String>();

        sentinels.add(new HostAndPort("192.168.1.104", 500).toString());

        sentinels.add(new HostAndPort("192.168.1.105", 500).toString());

        sentinels.add(new HostAndPort("192.168.1.106", 500).toString());

        JedisSentinelPool sentinelPool = new JedisSentinelPool("mymaster", sentinels);

        System.out.println("Current master: " + sentinelPool.getCurrentHostMaster().toString());

    }


构建java环境的自动化构建和部署系列四

 所有系列文章:

  <<构建java环境的自动化构建和部署系列一>>

  <<构建java环境的自动化构建和部署系列二>>

 <<构建java环境的自动化构建和部署系列三>>

 <<构建java环境的自动化构建和部署系列四>>

前面都介绍了整套的自动化部署环境的搭建,本章是介绍怎么基于docker快速启动一个jenkins容器,实现docker 的环境部署;

本章将基于 https://gitee.com/gz-tony/viemall-dubbo.git 开源代码来演练自动化部署的步骤;

通过docker file构建 jenkins镜像

#cd  /opt/program/tools/docker-file/jenkins

# docker build -t www.viemall.cn/viemall/jenkins:v1 .

这里构建镜像成功后,把镜像文件push公司的harbor镜像仓库中;

       #docker  push  www.viemall.cn/viemall/jenkins:v1

    docker-fil文件:

        https://gitee.com/gz-tony/viemall-dubbo/tree/master/viemall-docekr/dockerfile

启动jenkins镜像;

docker run -d –name jenkins -p 8080:8080 –restart=always –privileged=true \

-v /var/jenkins_home/:/var/jenkins_home  \

-v /usr/java/jdk1.8.0_131/:/usr/local/jdk \

-v /usr/local/apache-maven-3.3.9/:/usr/local/maven \

-v /var/run/docker.sock:/var/run/docker.sock \

-v $(which docker):/usr/bin/docker \

-v ~/.ssh:/root/.ssh \

-e JAVA_OPTS=-Dorg.apache.commons.jelly.tags.fmt.timeZone=Asia/Shanghai

www.viemall.cn/viemall/jenkins:v1

启动成功,访问:

 配置跟 <<构建java环境的自动化构建和部署系列三>> 基本一致;

唯一注意的配置的路径是配置您的docker mavean 路径

image.png
这里的mavean 就是配置您的docker mavean 路径

2. 构建Job任务:

 构建viemall-web服务:

 新建JOB

image.png

image.png

image.png

   image.png

   image.png

Source files(源目录文件)

viemall-service-web/target/viemall-service-web-0.0.1-SNAPSHOT.war

Remove prefix(删除的目录文件)

viemall-service-web/target

Remote directory(远程目录)

/opt/program/servers

 

Exec command(远程命令)

#!/bin/sh

echo '================开始本机执行 docker 部署任务================'

#相当于在构建完成后自动执行此脚本,这个脚本实现自动化发布工作

mv /opt/program/servers/viemall-service-web-0.0.1-SNAPSHOT.war /opt/program/servers/viemall-service-web.war

docker rm -f viemall-service-web-tomcat || true

docker run -d -p 8081:8080 –restart=always -v /opt/program/servers/viemall-service-web.war:/usr/local/tomcat/webapps/ROOT.war –name viemall-service-web-tomcat tomcat8:v1

echo '================结束 docker 部署任务================'

构建java环境的自动化构建和部署系列三

 所有系列文章:

  <<构建java环境的自动化构建和部署系列一>>

  <<构建java环境的自动化构建和部署系列二>>

 <<构建java环境的自动化构建和部署系列三>>

 <<构建java环境的自动化构建和部署系列四>>

前面已经讲述了SVN、mavean 、nexus的安装;本章将继续介绍jenkins安装与使用

一、环境 2

二、准备工作 2

三、Jenkins环境部署: 2

四、Jenkins系统配置 3

五、构建邮件配置 3

六. 配置SSH服务器 7

七、 Jenkins Job 9

为了方便运维人员的部署,采用Jenkins实现多机远程自动化部署方案;

自动化部署原理图示:

image.png

 Jenkins 是一个开源软件项目,旨在提供一个开放易用的软件平台,使软件的持续集成变得可能。现在软件开发追求的是效率以及质量,Jenkins使得自动化成为可能!

亮点

  采用shell自定义脚本,控制集成部署环境更加方便灵活

  精简war包中的lib包,常驻tomcat里,减少war包传输时间

  细粒度的用户权限管理,

  构建失败发邮件通知相关人员解决

  自动按天备份war包,Jenkins配置备份以及版本控制化

  可扩展性的插件管理

一、环境

  Linux(centos 7)、JDK8

二、准备工作

  SVN 版本控制服务器

  Tomcat发布服务器

  Jenkins服务器(提前安装好Maven,SVN,Jdk)

  Nexus 服务器

实验时可以在同一台机器配置,但是生产不建议,一台机器挂了,所有服务器都挂了。

机器

IP

Jenkins

192.168.0.144

SVN

192.168.1.110

Tomcat

192.168.0.146

Nexus

http://192.168.0.110:6161/nexus/

三、Jenkins环境部署:

1、wget https://pkg.jenkins.io/redhat-stable/jenkins-2.32.2-1.1.noarch.rpm

       rpm -ivh jenkins-2.32.2-1.1.noarch.rpm

安装完成之后: 

    /usr/lib/jenkins/jenkins.war    WAR包 

    /etc/sysconfig/jenkins       配置文件 

    /var/lib/jenkins/        默认的JENKINS_HOME目录 

    /var/log/jenkins/jenkins.log    Jenkins日志文件 

Jenkins 命令:

   service jenkins start –启动

   service jenkins restart –重启

   service jenkins stop –停止

2、配置Jenkins 的目录空间及端口

    vi /etc/sysconfig/jenkins

    JENKINS_HOME="/data/jenkins"

    JENKINS_PORT="6161"

3、访问链接: http://192.168.0.144:6161/

    第一次访问会初始化安装;

    在配置的时候需要注意用户权限问题:

    https://www.cnblogs.com/melody-emma/p/6026622.html


4、Jenkins 插件安装:

    Jenkins以其拥有大量的插件而著称,接下来需要安装常用的插件,本文需要确保已经安装了如下插件:

    Email Extension Plugin (邮件通知)

   Deploy to container Plugin (部署到Java容器中的插件)

   Publish Over SSH (远程Shell)

   Maven Integration plugin (Maven集成,否则新建的时候没有Maven Project)

   Monitoring (监控Jenkins所消耗的系统资源,如内存使用等)

    Docker Commons (运行docker 命令)

    MultiJob plugin(多项目构建的插件)


四、Jenkins系统配置

  1、配置JDK、Git、Maven、Ant:系统管理–Global Tool Configuration

image.png

 image.png

五、构建邮件配置

     1. 配置邮件服务器及邮件模板

     首先保证已经成功安装了Email Extension Plugin插件,然后依次点击左边的“Manage Jenkins” – “Configure System”,首先在Jenkins Location项目下面设置系统管理员的邮件地  址,这里填写的邮箱地址必须跟下面设置的发件人邮箱一致,否则不能成功发送邮件。

image.png

接下来在 Extended E-mail Notification 项下面设置SMTP服务器和邮件模板,这里推荐使用QQ邮箱:

image.png

image.png

Default Recipients填写默认的接收人邮箱地址,多个用英文逗号(,)进行分割,邮件模板可以使用下面参考资料里面提供的模板。

Default Content Type内容:

image.png

构建通知:$PROJECT_NAME – Build # $BUILD_NUMBER – $BUILD_STATUS!

Default Subject内容:

资料参考:

   http://blog.csdn.net/fullbug/article/details/53024562

   http://blog.csdn.net/songjiaping/article/details/51496977


. 配置SSH服务器

首先保证已经安装了Publish Over SSH插件,然后依次点击左边的“Manage Jenkins” – “Configure System”,在Publish over SSH项下面设置登录到远程服务器的配置信息,可以使用用户名/密码的方式,也可以使用证书的方式,这里建议使用后者。

 

1、首先配置免SSH免密码登录:

  确认用户

  确认当前用户是你需要的用户!

 $ whoami

  root   #root用户,根据你自己的需求选择用户。我这儿用root用户演示。不建议使用root

 生成key

 $ cd ~ #回到用户目录,不回去也没有关系

 $ ssh-keygen    

  #可以使用-t选项选择加密方式,包括 RSA 和 DSA 两种密钥

  #例如:$ssh-keygen -t dsa 或者ssh-keygen -t rsa

  #加密方式不同,key的名称不同,其他没有区别

  #如果没有指定密钥,默认RSA

 Generating public/private rsa key pair.

 Enter file in which to save the key (/home/froad/.ssh/id_rsa): #私钥存放的位置,默认会存放在用户目录的.ssh文件夹,直接回车

 Enter passphrase (empty for no passphrase): #默认,回车

 Enter same passphrase again: #默认,回车

 Your identification has been saved in /home/froad/.ssh/id_rsa.#私钥路径

 Your public key has been saved in /home/froad/.ssh/id_rsa.pub.#公钥路径

 The key fingerprint is:

  e8:b6:e6:xxxxxxxxxxxxxxxxx:ec:b5:d8 root@192.168.0.144;

 将本地公钥上传到远程主机:

 scp ~/.ssh/id_rsa.pub tomcatuser1@192.168.0.146:.ssh/id_rsa.pub 在远程主机(tomcat所在主机,如192.168.0.146):1,生成密钥文件:   touch /home/tomcatuser1/.ssh/authorized_keys

6.再转到第二台机器的.ssh目录下,会发现刚刚传输过来的文件-authorized_keys,然后执行命令,将第二台计算机的公钥也加进来,如:cat id-     rsa.pub >> authorized_keys.

2,修改权限:  chmod 600 ~/.ssh/authorized_keys

3,将控制主机上传的公钥复制:  cat /home/tomcatuser1/.ssh/id_rsa.pub  >> /home/tomcatuser1/.ssh/authorized_keys   将authorized_keys文件拷贝到需要被管理的电脑上。注意:放在用户目录下.ssh文件夹中。Linux用户会限制你的访问权限

4.验证

  ssh tomcatuser1@192.168.0.146

5、其他

   如果添加指纹的时候提示添加失败,是因为你以前添加过了这个ip的指纹。

   解决办法:将.ssh目录的known_hosts文件删除掉(好粗暴啊( ⊙ o ⊙ )啊!),也可以打开这个文件把对于ip的那条记录删除(这个就精细多了O(∩_∩)O哈哈~)

   如果操作步骤都正确,但是依然要求输入密码。一般是因为权限的问题。命令如下

   chmod 755 ~/.ssh/

   chmod 600 ~/.ssh/id_rsa  ~/.ssh/id_rsa.pub

   chmod 644 ~/.ssh/authorized_keys

二、 接下来就可以在Publish over SSH项设置Passphrase和Key,分别是私钥的密码和id_rsa文件的内容,点击下面的Add按钮增加SSH Servers,如果不使用上面公共的密钥设置或者使用用户名密码可以将 Use password authenticatio, or use a different key 选项勾选。

image.png

      设置完成之后可以点击下面的Test Configuration按钮,如果无误的话会出现Success的提示。

      PS:如果对远程服务器进行了安全设置,需要将 /etc/ssh/sshd_config 文件中的 PermitRootLogin 项目设置为 without-password,这样root账号不能用密码直接远程登录,但可以使用密钥对进行登录。

      构建完上述的配置后,就可以构建JOB服务了,提前说明一下,Publish over SSH是基于插件的形式进行远程登录,您可以不用这种方式,可以就简单的采用SHELL方式进行免登陆。

3. 构建完成之后使用SSH发布到演示服务器上

  在项目的配置项中的Post Steps项中选中 Send files or execute commands over SSH:

   image.png

接下来就可以设置执行的任务内容了:

   image.png

七、Jenkins Job

   方式一:基于SSH插件

直接点击Jenkins首页的新建按钮,创建一个Maven项目,配置根据大家需要,实际配置,供参考:

   image.png

接下来就可以设置执行的任务内容了:

  image.png

八、自动化部署docker的脚本

#!/bin/bash
#Time
app_name="viemall-web"
deploy_war="$app_name.war"
deploy_path="/opt/program/servers/$app_name"
deploy_path_war="$deploy_path/$deploy_war"
if [ ! -f $deploy_path_war ]; then
    echo "!WARN: No build jar file found in $deploy_path_war."
    echo "!WARN: Deployment is skipped."
    exit 0
fi
#备份log日志
echo "*************备份log日志****************"
if [ ! -d "$deploy_path/logs" ];then
mkdir -p $deploy_path/logs
else
   mv $deploy_path/logs $deploy_path/logs-$(date '+%m-%d')
   rm -rf $deploy_path/logs/*
fi
echo "************* 启动docker镜像 ****************"
docker rm -f $app_name || true
docker run -d -p 8080:8080 --restart=always -e TZ=Asia/Shanghai  -v $deploy_path_war:/usr/local/tomcat/webapps/ROOT.war -v $deploy_path/logs:/usr/local/tomcat/logs --name $app_name harbor.dczou.com/benniu/tomcat8-session-redis-test:v1
echo "************* 启动docker镜像成功 ****************"

构建java环境的自动化构建和部署系列二

 所有系列文章:

  <<构建java环境的自动化构建和部署系列一>>

  <<构建java环境的自动化构建和部署系列二>>

 <<构建java环境的自动化构建和部署系列三>>

 <<构建java环境的自动化构建和部署系列四>>

  

 前段为了方便公司管理项目的版本控制,和方便开发人员快速的部署项目(公司么有运维人员),搭建了搭建敏捷高效的持续集成管理平台,并且规范了安装部署的规范统一通过Shell来实现一键安装环境.(不过也要淘汰了,BOSS想通过Docker 来实现系统环境的部署)

持续集成管理平台的组成

    持续集成管理平台不只是CI服务器,是一系列软件开发管理工具的组合

1、源码版本管理:Subversion、 Git

2、项目构建工具:Maven、 Ant

3、代码质量管理:Sonar(Checkstyle、 PMD、 FindBugs……)

4、持续集成引擎: Jenkins、

5、应用持续部署:操作系统、 JDK、 Tomcat、 JBoss…

实施持续集成过程中要用到的其他各种工具、各种插件、和Shell脚本…

本系列的大概章目: 

   (1) Subversion源码版本控制系统的安装

    Subversion + Apache + jsvnadmin

    公司是使用Windows来搭建SVN的,这比Linux方便多了,偷懒了

     http://www.dczou.com/viemall/522.html

  (2) Maven私有库和本地库的安装与配置  Nexus + Maven

  (3) Sonar代码质量管控平台的安装与使用配置,

      这个比较坑,在安装的时候插件都不兼容;需要一个个找来组装

   (4) Hudson持续集成引擎的安装与配置

   (5) 自动化部署的实现

   (6) 自动化测试的实现

Maven私有库和本地库的安装与配置—-> Maven

    java开发环境(JDK)/ Eclipse IDE

    Maven3.0以上版本 下载地址:

       http://maven.apache.org/index.html

  1.本机机子环境:

      jdk1.7.0_55、apache-maven-3.3.9-bin.zip、myeclise10

 2.配置Mavean环境

       MAVEN_HOME: D:\web-server\apache-maven-3.2.3;

       path: ;%MAVEN_HOME%\bin;

 3.修改本地仓库位置

      Maven会将下载的类库(jar包)放置到本地的一个目录下,如果想重新定义这个目录的位置就需要修改Maven本地仓库的配置:

     D:\web-server\apache-maven-3.2.3\conf\settings.xml

 image.png

   依据该配置,Maven就会将下载的类库保存到固定文件中。

    通过mvn -V 检查mavean是否安装成功;

二、Maven基本介绍:

      Maven 是一个项目管理和构建自动化工具。但是对于我们程序员来说,我们最关心的是它的项目构建功能。所以这里我们介绍的就是怎样用 maven 来满足我们项目的日常需要。

      Maven 使用惯例优于配置的原则 。它要求在没有定制之前,所有的项目都有如下的结构:

 image.png

 Maven 常用命令:

mvn archetype:create 创建Maven项目

mvn compile 编译源代码

mvn deploy 发布项目

mvn test-compile 编译测试源代码

mvn test 运行应用程序中的单元测试

mvn site 生成项目相关信息的网站

mvn clean 清除项目目录中的生成结果

mvn package 根据项目生成的jar

mvn install 在本地Repository中安装jar

mvn eclipse:eclipse 生成eclipse项目文件;

清除eclipse的一些系统设置:mvn eclipse:clean 

mvnjetty:run 启动jetty服务

mvn tomcat:run 启动tomcat服务

mvn clean package -Dmaven.test.skip=true:清除以前的包后重新打包,跳过测试类

我常用的命令是:clean install package -Dmaven.test.skip -PDEV

使用-P参数显示的激活一个profile

    Maven简介(三)——profile介绍

如何将Jar包添加到本地maven仓库:

mvn install:install-file 

 -DgroupId=net.sf.json-lib

 -DartifactId=json-lib

 -Dversion=2.1

 -Dpackaging=jar

 -Dfile=json-lib-2.1-jdk15.jar

 

mvn deploy:deploy-file -DgroupId=org.springframework 

-DartifactId=org.springframework.web.servlet 

-Dversion=3.1.1.RELEASE 

-Dpackaging=jar -Dfile=org.springframework.web.servlet-3.1.1.RELEASE.jar

-Durl=http://192.168.0.66:8081/nexus/content/repositories/releases/    

-DrepositoryId=user-releases


mavean的生命周期及常用的插件介绍:

http://www.cnblogs.com/davidwang456/p/3915031.html


Mavean版本管理:

image.png

  http://hualom.iteye.com/blog/1387708


maven pom.xml文件教程详解:

http://www.zuidaima.com/share/1781583829978112.htm

http://yanguz123.iteye.com/blog/2211224


maven 多项目管理:

https://my.oschina.net/xuqiang/blog/99854

http://www.cnblogs.com/1995hxt/p/5254448.html 


基于mavean配置多个源目录环境(用于环境区分):

  http://casheen.iteye.com/blog/540385

   https://my.oschina.net/vernon/blog/271970

 资料参考:http://blog.csdn.net/liujiahan629629/article/details/39272321

Maven私有库和本地库的安装与配置—-> Nexus

        简介 :前边简单介绍了Maven,而Maven默认提供的中央仓库是在远程网络服务Appache提供的,这对于我们开发时不合理的。如果我们没网了或者什么情况,我们怎么办?也就是说我们队中央仓库的依赖性太大。而Nexus私服则可以解决我们这个问题。先看下这张图应该大家就非才明白了:

image.pngimage.png

      这样就相当于在我们本地的局域网搭建了一个类似中央仓库的服务器,我们开始将中央仓库的一些资料下载到私服务器上,然后平时我们的maven项目就是直接访问局域网内的私服即可,既节省了网络带宽也会加速项目搭建的进程,这样对我们开发来说,对公司来说都是非常好的选择。下边简单看一下Nexus私服的简单使用:

环境配置:

        本地环境: nexus-2.14.2-01-bundle.zip 

 1、Nexus下载

       下载地址:http://www.sonatype.org/nexus/go 

 2、Nexus启动

        我下载的是zip包,解压后进入\ nexus-2.14.2-01-bundle \nexus-2.14.2-01\bin\jsw\,根据操作系统类型选择文件夹,我选的是windows-x86-64文件夹,进入后可看到如下所示bat文件。

 8081为默认的端口号,要修改端口号可进入nexus-2.14.2-01-bundle\nexus-2.14.2-01\conf\打开nexus.properties文件,修改application-port属性值就可以了。

image.png

       双击console-nexus.bat运行。游览器中输入http://127.0.0.1:8081/nexus/,出现如下图所示就代表nexus已经启动成功。

image.png 

Nexus 的仓库分为这么几类:

   hosted 宿主仓库:主要用于部署无法从公共仓库获取的构件(如 oracle 的 JDBC 驱动)以及自己或第三方的项目构件;

   proxy 代理仓库:代理公共的远程仓库;

   virtual 虚拟仓库:用于适配 Maven 1;

   group 仓库组:Nexus 通过仓库组的概念统一管理多个仓库,这样我们在项目中直接请求仓库组即可请求到仓库组管理的多个仓库。

PublicRepositories:  仓库组:

     3rd party: 无法从公共仓库获得的第三方发布版本的构件仓库

     Apache Snapshots: 用了代理ApacheMaven仓库快照版本的构件仓库

     Central: 用来代理maven中央仓库中发布版本构件的仓库

    Central M1 shadow: 用于提供中央仓库中M1格式的发布版本的构件镜像仓库

     Codehaus Snapshots: 用来代理CodehausMaven 仓库的快照版本构件的仓库

     Releases: 用来部署管理内部的发布版本构件的宿主类型仓库

     Snapshots:用来部署管理内部的快照版本构件的宿主类型仓库

image.png

配置Maven使用Nexus私服

     让maven项目使用nexus作为远程仓库有两种方式,第一种是在项目的pom.xml中进行更改,让单个项目使用nexus仓库;另一种是通过修改maven的配置文件settings.xml进行更改,让所有项目都使用nexus仓库。

    pom配置:

<!-- 设定除中央仓库 (repo1.maven.org/maven2/)外的其他仓库,按设定顺序进行查询,
       如有Nexus私服, 取消注释并指向正确的服务器地址 -->
	<repositories>
		<repository>
			<id>nexus</id>
			<name>Team Nexus Repository</name>
			<url>http://192.168.0.110:6161/nexus/content/groups/public</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</repository>
	</repositories>

	<!-- 如有Nexus私服, 取消注释并指向正确的服务器地址 -->
	<pluginRepositories>
		<pluginRepository>
			<id>nexus</id>
			<name>Team Nexus Repository</name>
			<url>http://192.168.0.110:6161/nexus/content/groups/public</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>true</enabled>
			</snapshots>
		</pluginRepository>
	</pluginRepositories>

	<!-- 将项目发布到私服 -->
	<distributionManagement>
		<snapshotRepository>
			<uniqueVersion>false</uniqueVersion>
			<id>nexus-snapshots</id>  <!-- settings.xml id 对应 -->
			<name>User Porject Snapshot</name>
			<url>http://192.168.0.110:6161/nexus/content/repositories/snapshots/</url>
		</snapshotRepository>
		<repository>
			<id>nexus-releases</id>
			<name>User Porject Release</name>
			<url>http://192.168.0.110:6161/nexus/content/repositories/releases/</url>
		</repository>
	</distributionManagement>

基于Mavean将项目发布私服:

     1 . 修改私服中仓库的部署策略

        Release版本的项目应该发布到Releases仓库中,对应的,Snapshot版本应该发布到Snapshots仓库中。Maven根据pom.xml文件中版本号<version>节点的属性是否包含-SNAPSHOT,来判断该项目是否是snapshot版本。如果是snapshot版本,在执行mvn deploy部署命令时,maven会自动将项目发布到Snapshots仓库。要发布项目,

         首先需要将Releases仓库和Snapshots仓库的“Deployment Policy”设置为“Allow Redeploy”:

    奇葩问题:在mvn deploy jar是:Snapshot(快照)形式的项目打包后会自动增加时间戳的部分;

    问题:http://jira.codehaus.org/browse/MNG-4452

     image.png

  2 . 配置项目的部署仓库

      在pom.xml中分别对Release版本和Snapshot版本配置部署仓库,其中id唯一,url分别对应私服中Releases和Snapshots仓库的Repository Path:

     image.png

 配置如下:

    <!– 将项目发布到私服 –>        

 <distributionManagement>

              <snapshotRepository>

                     <uniqueVersion>false</uniqueVersion>

                     <id>nexus-snapshots</id>  <!– settings.xml id 对应 –>

                     <name>User Porject Snapshot</name>

                     <url>http://192.168.0.110:6161/nexus/content/repositories/snapshots/</url>

              </snapshotRepository>

              <repository>

                     <id>nexus-releases</id>

                     <name>User Porject Release</name>

                     <url>http://192.168.0.110:6161/nexus/content/repositories/releases/</url>

              </repository>

       </distributionManagement>


 3.在settings.xml中分别为上面配置的部署仓库配置server,其中id需要分别对应上面的部署仓库id:

  image.png

4 . 发布项目

   右键pom.xml – Run As – 2 Maven build…     clean deploy;


构建java环境的自动化构建和部署系列一

 所有系列文章:

  <<构建java环境的自动化构建和部署系列一>>

  <<构建java环境的自动化构建和部署系列二>>

 <<构建java环境的自动化构建和部署系列三>>

 <<构建java环境的自动化构建和部署系列四>>

        前段为了方便公司管理项目的版本控制,和方便开发人员快速的部署项目(公司么有运维人员),搭建了搭建敏捷高效的持续集成管理平台,并且规范了安装部署的规范统一通过Shell来实现一键安装环境.(不过也要淘汰了,BOSS想通过Docker 来实现系统环境的部署)

持续集成管理平台的组成

    持续集成管理平台不只是CI服务器,是一系列软件开发管理工具的组合

1、源码版本管理:Subversion、 Git

2、项目构建工具:Maven、 Ant

3、代码质量管理:Sonar(Checkstyle、 PMD、 FindBugs……)

4、持续集成引擎: Jenkins、

5、应用持续部署:操作系统、 JDK、 Tomcat、 JBoss…

实施持续集成过程中要用到的其他各种工具、各种插件、和Shell脚本…

    总体的平台流程图:下图网上找到,公司的部署架构跟这个类似,所以就没有自己去画了

image.png

本系列的大概章目: 

   (1) Subversion源码版本控制系统的安装

    Subversion + Apache + jsvnadmin

    公司是使用Windows来搭建SVN的,这比Linux方便多了,偷懒了

  (2) Maven私有库和本地库的安装与配置  Nexus + Maven

  (3) Sonar代码质量管控平台的安装与使用配置,

      这个比较坑,在安装的时候插件都不兼容;需要一个个找来组装

   (4) Hudson持续集成引擎的安装与配置

   (5) 自动化部署的实现

  (6) 自动化测试的实现

所有的前提准备:

    JDK 的环境安装(我是1.7的版本)

    Mysql 的安装.

系列一:Subversion + Apache + jsvnadmin

      公司是通过Window来搭建的SVN环境的,关于Windows的搭建,本章不去细说,本章通过Linux环境来搭建源码的控制。建议使用Windows来管理SVN,使用起来比较便捷方便,个人喜欢点;

安装步骤:

   #yum install -y httpd

  安装 apache

    # yum install httpd httpd-devel

    # service httpd start

    # chkconfig httpd on

我的机器有nginx 所以为了防止80端口冲突,改成了81。没有需要的可以不更改,也试过Nginx来实现,但是遇到一些奇葩问题,就采用Apatch了.

   # vi /etc/httpd/conf/httpd.conf

找到 ServerName 并修改成

    Listen 81

    ServerName localhost:81

防火墙中打开 81 端口:

    # vi /etc/sysconfig/iptables

    -A INPUT -m state –state NEW -m tcp -p tcp –dport 81 -j ACCEPT

    # service iptables restart

 访问:http://192.168.1.105:81/ 就可以知道有么有安装成功

 

安装 SVN 服务

   # yum install mod_dav_svn subversion

  必须安装 mod_dav_svn 模块

   安装完 svn 后要重启 apache

   # service httpd restart

查看测试是否安装 svn 模块

# ls /etc/httpd/modules/ | grep svn

# svn –version

创建 svn 库主目录(多库模式,一份配置文件管理多个库)

    # mkdir /svn/

    # vi /etc/httpd/conf.d/subversion.conf 添加以下内容

LoadModule dav_svn_module     modules/mod_dav_svn.so
LoadModule authz_svn_module   modules/mod_authz_svn.so
<Location /svn/>
         DAV svn
         SVNListParentPath on
         SVNParentPath /svn
         AuthType Basic
         AuthName "Subversion repositories"
         AuthUserFile /svn/passwd.http
         AuthzSVNAccessFile /svn/authz
         Require valid-user
</Location>
RedirectMatch ^(/svn)$ $1/

创建/svn/passwd.http 和/svn/authz

   # touch /svn/passwd.http

   # touch /svn/authz

重启 apache

    # service httpd restart

 

安装 jsvnadmin

   svnadmin 介绍

(在 Google Code 上,需要 FQ 才能下载。我们也会把最新版的 jsvnadmin 放到群共享中)

     https://code.google.com/p/jsvnadmin/

      https://jsvnadmin.googlecode.com/files/svnadmin-3.0.5.zip

 

将下载的jsvnadmin部署在tomcat;

修改svnadmin/WEB-INF 的jdbc.properties

内容改为如下

db=MySQL
#MySQL
MySQL.jdbc.driver=com.mysql.jdbc.Driver
MySQL.jdbc.url=jdbc:mysql://127.0.0.1:3306/svnadmin?characterEncoding=utf-8
MySQL.jdbc.username=root
MySQL.jdbc.password=123456

创建 svnadmin 数据库并导入相应数据( UTF-8 编码)

执行 db/mysql5.sql 和 db/lang/en.sql

 

启动tomcat:

浏览器中打开: http://192.168.1.105:9000/svnadmin/

第一次访问需要设置登录用户和密码,登录后的管理界面:

 image.png

创建库

 image.png

此时:

   /svn/目录下会创建一个viemall 的 SVN 库目录。通过下面就会有SVN项目的用户管理文件.

配置库目录权限

# cd /svn

# chown -R apache.apache viemall

# chmod -R 777 viemall

(如创建新库,需要对新建的库目录执行以上两步授权操作)

  否则在客户端提交代码的时候,会出现“SVN提交时:could not begin a transaction”;

关闭 SELinux( Linux 的访问控制)

修改/etc/selinux/config 文件

# vi /etc/selinux/config

将 SELINUX=enforcing 改为SELINUX=disabled

重启机器即可

# reboot

 

SVN 版本管理平台( Subversion+Apache+Jsvnadmin)的使用:

先安装 SVN 管理客户端 TortoiseSVN,方便对 SVN 库的操作和管理

    http://tortoisesvn.net/downloads.html 这个小海龟不多说都懂。

 

接下来可以对 wusc_edu 库进行相应的操作

(1) 用户组

(2) 用户

(3) 授权

(4) 导入项目

 image.png

     http://auskangaroo.blog.51cto.com/740826/1410549

聊聊互联网限流方案

   首先要知道为什么要进行限流,限流的方式有那些,那些应用需要限流。

    在业务场景中,我们会遇到有百万甚至更加大的用户请求流量来访问接口,或者在业务对接过程中,业务提供方的访问频率控制,等等我们的业务都需要做限流处理,

这个时候如果不做任何保护措施,服务器就会承受很大的处理压力,请求量很高,服务器负载也很高,并且当请求超过服务器承载极限的时候,系统就会崩溃,导致所有人都不能访问。为了应用服务的高可用,

一个常用的办法是对大流量的请求(秒杀/抢购)进行限流,拦截掉大部分请求,只允许一部分请求真正进入后端服务器,这样就可以防止大量请求造成系统压力过大导致的系统崩溃,从而保护服务正常可用。这里限流的常用算法有漏桶算法和令牌桶算法。

  额外插一句:处理高并发的三板斧: 缓存、降级和限流!

常见限流方案

 令牌桶(Token Bucket)、漏桶(leaky bucket)和计数器算法是最常用的三种限流的算法。

计数器方式:

    计数器限流算法也是比较常用的,主要用来限制总并发数,比如数据库连接池大小、线程池大小、程序访问并发数等都是使用计数器算法。也是最简单粗暴的算法。

  采用AtomicInteger:

     使用AomicInteger来进行统计当前正在并发执行的次数,如果超过域值就简单粗暴的直接响应给用户,说明系统繁忙,请稍后再试或其它跟业务相关的信息。

    弊端:使用 AomicInteger 简单粗暴超过域值就拒绝请求,可能只是瞬时的请求量高,也会拒绝请求。

 采用令牌Semaphore:

     使用Semaphore信号量来控制并发执行的次数,如果超过域值信号量,则进入阻塞队列中排队等待获取信号量进行执行。如果阻塞队列中排队的请求过多超出系统处理能力,则可以在拒绝请求。

    相对Atomic优点:如果是瞬时的高并发,可以使请求在阻塞队列中排队,而不是马上拒绝请求,从而达到一个流量削峰的目的。

 采用ThreadPoolExecutor java线程池:

    固定线程池大小,超出固定先线程池和最大的线程数,拒绝线程请求;

     https://www.cnblogs.com/clds/p/5850070.html

令牌桶方式:

 image.png 

   令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。先有一个木桶,系统按照固定速度,往桶里加入Token,如果桶已经满了就不再添加。当有请求到来时,会各自拿走一个Token,取到Token 才能继续进行请求处理,没有Token 就拒绝服务。

  这里如果一段时间没有请求时,桶内就会积累一些Token,下次一旦有突发流量,只要Token 足够,也能一次处理,所以令牌桶算法的特点是允许突发流量。

    我们看一个例子,看看令牌桶如何允许突发流量,假如令牌则按照每秒5 个的速度放入令牌桶,桶中最多存放20 个令牌,那系统可以支持两种类型的请求流量,一种是允许持续的每秒处理5 个请求,第二种是每隔4 秒,等桶中20 个令牌攒满后,就可以处理一次有20 个请求的突发情况。

令牌桶的方案设计:

  使用guava提供工具库里的RateLimiter类(内部采用令牌捅算法实现)进行限流

   平滑突发限流(SmoothBursty):

   image.png

     image.png

 

解析:

 RateLimiter.create(5) 表示桶容量为5且每秒新增5个令牌,即每隔200毫秒新增一个令牌;

 limiter.acquire()表示消费一个令牌,如果当前桶中有足够令牌则成功(返回值为0),如果桶中没有令牌则暂停一段时间,比如发令牌间隔是200毫秒,则等待200毫秒后再去消费令牌(如上测试用例返回的为0.198239,差不多等待了200毫秒桶中才有令牌可用),这种实现将突发请求速率平均为了固定请求速率。如果结构不想等待可以采用tryAcquire立刻返回!

RateLimiter的突发情况处理:

 image.png

     image.png 

limiter.acquire(5)表示桶的容量为5且每秒新增5个令牌,令牌桶算法允许一定程度的突发,所以可以一次性消费5个令牌,但接下来的limiter.acquire(1)将等待差不多1秒桶中才能有令牌,且接下来的请求也整形为固定速率了。

image.png

image.png

 

同上边的例子类似,第一秒突发了10个请求,令牌桶算法也允许了这种突发(允许消费未来的令牌),但接下来的limiter.acquire(1)将等待差不多2秒桶中才能有令牌,且接下来的请求也整形为固定速率了。


    因为SmoothBursty允许一定程度的突发,会有人担心如果允许这种突发,假设突然间来了很大的流量,那么系统很可能扛不住这种突发。因此需要一种平滑速率的限流工具,从而系统冷启动后慢慢的趋于平均固定速率(即刚开始速率小一些,然后慢慢趋于我们设置的固定速率)。Guava也提供了SmoothWarmingUp来实现这种需求类似漏桶算法;

 

漏桶算法:

        一个固定容量的漏桶,按照常量固定速率流出水滴;

      先想象有一个木桶,新请求就像水滴一样,不断地滴进来,水滴进来的速度是不确定的,有时会快一点,有时会慢一点,同时桶底下有个洞,可以按照固定的速度把水漏走,如果水进来的速度比漏走的快,桶就会满了,桶满了水就会漫出来,对应的就是拒绝请求。

  漏桶算法的主要特点是可以平滑网络上的突发流量,请求可以被整形成稳定的流量。
  image.png

C               // 水桶总容量

r               // 漏水速度

at              // 上一个请求时间

w               // 当前桶里面的水量
漏桶的方案设计:

    SmoothWarmingUp创建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit)

permitsPerSecond表示每秒新增的令牌数,warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔。

image.png

image.png

    速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节warmupPeriod参数实现一开始就是平滑固定速率。

以上的方式都是单应用上的请求限流,那么在分布式上,就不能这样全局的方式来实现了, 因此我们需要分布式限流和接入层限流来解决这个问题。

 

常用的分布方式的解决方式有:

 1.纯采用nginx 的IP方式限流,在一些大型的应用流量可以这样控制;

   https://www.cnblogs.com/biglittleant/p/8979915.html

 2.采用redis 的计时和计数方式,在规定的时间窗口期,允许通过的最大请求数量;

 3.采用redis lua或者 nginx lua方式实现限流处理;

   https://blog.csdn.net/fenglvming/article/details/51996406

一般都是采用应用层的限流,用户接入层都是采用负载均衡去分发流量;

 

额外资料参考:

http://m635674608.iteye.com/blog/2339587

http://rayleung.xyz/2016/06/23/rate-limit/

https://blog.52itstyle.com/archives/2982/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

 

开发上怎么提供数据库的安全?

开发过程中,了提升的安全,一般都采用一下方式提示据的安全性:

 1.码级别:

 2.的用户权级别:

码级别:

       如果接采用明文的方式,这样的安全性不是很高,以至于所有的技只要知道配置文件就可以知道登录数帐号和密了,

如:

image.png

的方式:通加密和解密

druid配置加密:

image.png

实践过程:

1.生成加密的密信息:

命令方式:

  入命令 java -cp druid-1.0.27.jar com.alibaba.druid.filter.config.ConfigTools your_password  

  image.png

方式生成:

public class DruidTest {
 
    public static void main(String[] args) throws Exception {
        // 密码明文
        String password = "viemall@123";
 
        System.out.println("密码[ " + password + " ]的加密信息如下:\n");
 
        String[] keyPair = ConfigTools.genKeyPair(512);
        // 私钥
        String privateKey = keyPair[0];
        // 公钥
        String publicKey = keyPair[1];
        // 用私钥加密后的密文
        password = ConfigTools.encrypt(privateKey, password);
 
        System.out.println("privateKey:" + privateKey);
        System.out.println("publicKey:" + publicKey);
        System.out.println("password:" + password);
        String decryptPassword = ConfigTools.decrypt(publicKey, password);
        System.out.println("decryptPassword:" + decryptPassword);
 
    }
 
}

image.png

配置Druid解密:

jdbc.type=mysql
 jdbc.driver=com.mysql.jdbc.Driver
 jdbc.url=jdbc:mysql://localhost:3306/viemall?useUnicode=true&characterEncoding=utf-8
 jdbc.username=root
 jdbc.password=IVpkS/WvZQKLcm4+f7xlLFo5FzxGIj3O1br9TcvLlq2a17mmt0SWe9Qq1hyVKsnbsRdU6FKTItc6vVIF9RRpTw==
 jdbc.publickey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKYBLQ067pCDwEfysD6rAIWZD4C2K7BO09NFYM
 mA+VD4i+28znGk9F3w3uCFp6vYf633rPJpx+hoHU/+9gBIewUCAwEAAQ==

配置Druid解密的主要新增的行配置。

1

<property name="connectionProperties"
value="config.decrypt=true;config.decrypt.key=${jdbc.publickey}"/>

2、<property name="filters" value="config" />   作用:提示Druid据源需要对数码进行解密  


的用户权级别:

除了代的密安全上限制,在用帐号权限上也要分配好相的用户权限,如:查询 只有查询数据的限;

GRANT SELECT ON viemall.* TO reader@"%"  IDENTIFIED BY "123456"
FLUSH PRIVILEGES;

这样行其他命令的提示限不足:

错误: 1142

DELETE command denied to user 'reader'@'127.0.0.1' for table 'viemall_sms_log'

的用命令管理:

1.新建用

  1.1 登MYSQL

  @>mysql -u root -p

  @>密

  1.2 建用

  mysql> insert into mysql.user(Host,User,Password) values("localhost","test",password("1234"));

  这样建了一:test 密码为:1234 的用

  注意:此的"localhost",是指只能在本地登,不能在另外一台机器上程登。如果想程登"localhost""%",表示在任何一台电脑上都可以登。也可以指定某台机器可以程登

  1.3 然后登一下:

  mysql>exit;

  @>mysql -u test -p

  @>入密

  mysql>登成功

2.

  授格式:grant 限 on .* to 名@登主机 identified by "密"; 

  2.1 登MYSQL(有ROOT限),里以ROOT身

  @>mysql -u root -p

  @>密

  2.2 首先户创建一个数(testDB)

  mysql>create database testDB;

  2.3 授test户拥有testDB的所有限(某个数的所有限):

   mysql>grant all privileges on testDB.* to test@localhost identified by '1234';

   mysql>flush privileges;//刷新系统权限表

  格式:grant 限 on .* to 名@登主机 identified by "密"; 

  2.4 如果想指定部分一用,可以这样来写:

  mysql>grant select,update on testDB.* to test@localhost identified by '1234';

  mysql>flush privileges; //刷新系统权限表

  2.5 授test户拥有所有的某些限:   

  mysql>grant select,delete,update,create,drop on *.* to test@"%" identified by "1234";

     //test户对所有都有select,delete,update,create,drop 限。

  //@"%" 表示所有非本地主机授,不包括localhost。(localhost地址设为127.0.0.1,如果设为真实的本地地址,不知道是否可以,验证。)

 //localhost:加上一句grant all privileges on testDB.* to test@localhost identified by '1234';即可。

3、除用

   @>mysql -u root -p

  @>密

   mysql>Delete FROM user Where User='test' and Host='localhost';

   mysql>flush privileges;

   mysql>drop database testDB; //除用

账户限:>drop user 用名@'%';

        >drop user 用名@ localhost; 

 

4. 修改指定用

    @>mysql -u root -p

    @>密

    mysql>update mysql.user set password=password('新密') where User="test" and Host="localhost";

    mysql>flush privileges;

 

5. 列出所有

  mysql>show database;

 

6. 换数

  mysql>use '名';

 

7. 列出所有表

  mysql>show tables;

 

8. 据表结构

  mysql>describe 表名;

 

9. 据表

  mysql>drop database 名;

  mysql>drop table 据表名;

redis的RDB和AOF 持久化方案

最近在弄docker的Redis部署,顺便去温习了一下redis 的持久化方案,整理了一下笔记:

 如果想基础学起可以看之前的文章: http://www.dczou.com/viemall/439.html

本章大纲:

  RDB和AOF两种持久化机制的介绍:

  RDB持久化机制的优点

  RDB持久化机制的缺点

  AOF持久化机制的优点

  AOF持久化机制的缺点

  RDB和AOF如何选择;

  RDB 实战:

  AOF 实战:


RDB和AOF两种持久化机制的介绍:

    RDB持久化机制,对redis中的数据执行周期性的持久化,是基于二进制文件形式保存

AOF机制对每条写入命令作为日志,以append-only的模式写入一个日志文件中,在redis重启的时候,可以通过回放AOF日志中的写入指令来重新构建整个数据集

   如果我们想要redis仅仅作为纯内存的缓存来用,那么可以禁止RDB和AOF所有的持久化机制;

  禁止:RDB   要设置 save "",还要把持久化的本地文件干掉!

  禁止: AOF   appendonly=no;

  通过RDB或AOF,都可以将redis内存中的数据给持久化到磁盘上面来.

  在生产环境中,我们可以定期将持久化文件同步到其他备份机器或者是阿里云服务;

 如果同时使用RDB和AOF两种持久化机制,那么在redis重启的时候,会使用AOF来重新构建数据,因为AOF中的数据更加完整

RDB持久化机制的优点

   (1)RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备,可以将这种完整的数据文件发送到一些远程的安全存储上去;

(2)RDB对redis对外提供的读写服务,影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可

(3)相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速

 

RDB持久化机制的缺点

   (1)如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据

(2)RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,或者甚至数秒

RDB 实战:

   配置RDB 持久化:

image.png

   每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中完整的数据快照,这个操作也被称之为snapshotting,快照

  客户端也可以使用 save或者 bgsave 命令通知 redis做一次快照持久化。save 操作是在 主线程中保存快照的, 由于 redis 是用一个主线程来处理所有客户端的请求, 这种方式会阻 塞所有客户端请求。 所以不推荐使用。 另一点需要注意的是, 每次快照持久化都是将内存数 据完整写入到磁盘一次, 并不是增量的只同步增量数据。 如果数据量大的话, 写操作会比较 多,必然会引起大量的磁盘 IO操作,可能会严重影响性能。
  注意: 由于快照方式是在一定间隔时间做一次的,所以如果 redis意外当机的话,就会丢失最后一次快照后的所有数据修改

工作机制:  

    (1)redis根据配置自己尝试去生成rdb快照文件

(2)fork一个子进程出来

(3)子进程尝试将数据dump到临时的rdb快照文件中

(4)完成rdb快照文件的生成之后,就替换之前的旧的快照文件

dump.rdb,每次生成一个新的快照,都会覆盖之前的老快照

实战测试:

 我们在redis 保存几条数据,然后shutdown 掉服务,然后重新启动服务,你会发现保存的数据都还存在, 通过redis-cli SHUTDOWN这种方式去停掉redis,其实是一种安全退出的模式,redis在退出的时候会将内存中的数据立即生成一份完整的rdb快照

     注:用kill -9粗暴杀死redis进程,模拟redis故障异常退出,你就会发现导致内存数据丢失的场景了,所以需要通过save 来设置一个时间的间隔和数量;

     如:配置成  save 5 1 ,你会发现,插入几条数据后,待5秒后kill服务,重新启动该保存的数据还会存在。


RDB 的数据导入:

redis中使用redis-dump导出、导入、还原数据实例: https://blog.csdn.net/dszgf5717/article/details/48002943

AOF持久化机制的优点: 

    (1)AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据 

   (2)AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,即使文件尾部破损,也很容易修复

   (3)AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。

  (4)AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据

AOF持久化机制的缺点

   (1)对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大

(2)AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,性能也还是很高的

AOF 实战:

   配置AOF持久化:

   AOF持久化,默认是关闭的,默认是打开RDB持久化 

   Appendonly=yes,可以打开AOF持久化机制,在生产环境里面,一般来说AOF都是要打开的,除非你说随便丢个几分钟的数据也无所谓, AOFRD同时开启的时候,rdb里也有部分数据,aof里也有部分数据,这个时候其实会发现,rdb的数据不会恢复到内存中


工作机制:

   打开AOF持久化机制之后,redis每次接收到一条写命令,就会写入日志文件中,当然是先写入os cache的,然后每隔一定时间再fsync一下,

而且即使AOF和RDB都开启了,redis重启的时候,也是优先通过AOF进行数据恢复的,因为aof数据比较完整


可以配置AOF的fsync策略,有三种策略可以选择,一种是每次写入一条数据就执行一次fsync; 一种是每隔一秒执行一次fsync; 一种是不主动执行fsync

 

always: 每次写入一条数据,立即将这个数据对应的写日志fsync到磁盘上去,性能非常非常差,吞吐量很低; 确保说redis里的数据一条都不丢,那就只能这样了

 

everysec: 每秒将os cache中的数据fsync到磁盘,这个最常用的,生产环境一般都这么配置,性能很高,QPS还是可以上万的

 

no: 仅仅redis负责将数据写入os cache就撒手不管了,然后后面os自己会时不时有自己的策略将数据刷入磁盘,不可控了

实战测试:

  重复RDB 实战测试的步骤,你可以发现数据被恢复回来了,就是从AOF文件中恢复回来的

  同时有RDB snapshot文件和AOF日志文件,那么redis重启的时候,会优先使用AOF进行数据恢复,因为其中的日志更完整。

AOF的 rewrite log 机制:

redis中的数据其实有限的,很多数据可能会自动过期,可能会被用户删除,可能会被redis用缓存清除的算法清理掉

redis中的数据会不断淘汰掉旧的,就一部分常用的数据会被自动保留在redis内存中

所以可能很多之前的已经被清理掉的数据,对应的写日志还停留在AOF中,AOF日志文件就一个,会不断的膨胀,到很大很大

所以AOF会自动在后台每隔一定时间做rewrite操作,比如日志里已经存放了针对100w数据的写日志了; redis内存只剩下10万; 基于内存中当前的10万数据构建一套最新的日志,到AOF中; 覆盖之前的老日志; 确保AOF日志文件不会过大,保持跟redis内存数据量一致

可以通过配置来配置rewrite策略:

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb

 

比如说上一次AOF rewrite之后,是128mb

然后就会接着128mb继续写AOF的日志,如果发现增长的比例,超过了之前的100%,256mb,就可能会去触发一次rewrite

但是此时还要去跟min-size,64mb去比较,256mb > 64mb,才会去触发rewrite

 

AOF破损文件的修复:

    如果redis在append数据到AOF文件时,机器宕机了,可能会导致AOF文件破损

 用redis-check-aof –fix命令来修复破损的AOF文件,用的很少;


RDB和AOF如何选择;

(1)不要仅仅使用RDB,因为那样会导致你丢失很多数据

(2)也不要仅仅使用AOF,因为那样有两个问题,第一,你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快; 第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug

(3)综合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复

深入浅出Docker技术- Disconf 分布式配置中心

 

本文主要介绍基于Disconf 搭建

环境准备:

   docker/centos7

Disconf 不懂的可以看我之前的系列文章:


   分布式配置中心 Disconf实践-概念片

   分布式配置中心 Disconf实践- 安装篇

   分布式配置中心 Disconf实践- 升级篇


image.png


 docker compose文件编写:

     https://gitee.com/gz-tony/viemall-dubbo/tree/master/viemall-docekr/compose/disconf

docker compose 服务启动;

    docker-compose up –d

docker ps –a : 服务列表

      image.png

所有容器启动正常以后,就可以通过 http://yourhost  访问Disconf-web服务了。Disconf的客户端需要访问zookeeper,所以在配置Disconf的客户端时请配置hosts文件,将主机名disconf-zoo映射到Docker所在的服务器上。

 image.png

   不过我用 admin admin 后台始终提示密码错误?????;

参考资料:

     https://github.com/friddle/docker-disconf