Hexo


  • Home

  • Archives

1-redis高可用服务缓存架构

Posted on 2100-01-01 | In redis

1. 高并发、高可用复杂系统中的缓存架构有哪些东西?

  1. 复杂的缓存架构需要支撑高并发、高可用

2. 课程内容-商品详情页的架构?

  1. 商品详情页系统架构 -> 缓存架构 -> 高并发技术+解决方案+架构 -> 高可用技术+解决方案+架构

3. 小型电商网站的商品详情页的页面静态化架构及其缺陷?

  1. 商品详情页的系统架构 -> 缓存架构 -> 高并发 -> 高可用
    小型电商网站架构方案:页面静态化的方案
    html模板页面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <html>
    <title>
    <style css>
    <javascript>
    </title>
    <body>
    商品名称:#{productName}
    商品价格:#{productPrice}
    商品描述:#{productDesc}
    </body>
    </html>

    数据存在数据库中 -> 模板的渲染

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <html>
    <title>
    <style css>
    <javascript>
    </title>
    <body>
    商品名称:iphon7 plus(玫瑰)
    商品价格:5299.50
    商品描述:这是最好的手机,大降价了
    </body>
    </html>

    模板改变了,这个模板对应的所有数据,全部重新渲染一遍。小网站,页面很少,很实用,非常简单。
    大网站,模板上亿,重新渲染,不靠谱。

3-小型电商静态化架构

4. 大型电商网站的异步多级缓存构建+nginx数据本地化动态渲染的架构

4-大型电商网站的详情页架构

架构解析:

1. 用户访问页面

2. nignx服务器
    2.1 nignx服务器从本地获取数据,如果获取到,渲染html模板;
    没有,执行第3步;

3. redis集群
    3.1 redis集群从本地获取数据,如果获取到,返回给nignx服务器,nignx服务器将数据写入本地缓存;
    没有,执行第4步;

4. ehcache缓存
    4.1 ehcache缓存从本地获取数据,如果获取到,返回给redis集群,redis集群写入本地;
        redis集群将数据返回给nignx服务器,nignx服务器将数据写入本地缓存;
        没有,执行第5步;

5. 去源头获取数据,再一步一步往回执行,写入本地。

5. redis的重要性–首先要掌握redis

支撑高并发、高可用、海量数据、备份恢复的redis的重要性
1. redis架构支持高并发、高可用、海量数据、备份并随时可以恢复。

2. 解决各种各样高并发场景下的缓存面临的难题,缓存架构中不断的引入各种解决方案和技术,解决高并发的问题

3. 解决各种各样缓存架构本身面临的高可用的问题,缓存架构中引入各种解决方案和技术,解决高可用的问题

6. 准备好CentOS集群

7. 单机版redis的安装以及redis生产环境启动方案

8. redis持久化机对于生产环境中的灾难恢复的意义

8-redis持久化的意义

redis持久化的意义:在于数据备份,以便于故障恢复。
如果没有持久化的话,redis遇到灾难性故障的时候,就会丢失所有的数据。
如果通过持久化将数据备份在磁盘上,然后定期同步和备份到其它存储上,
那么就可以保证数据不全部丢失,还是可以恢复一部分数据回来的。

9. redis的RDB和AOF两种持久化机制的工作原理

持久化主要是做灾难恢复,数据恢复,可归类到高可用中。

数据恢复的意义:
    >假如redis集群宕机了,redis就不可用了,首要事情是让redis变得可用,尽快变得可用。
    >此时重启redis,尽快对外提供服务。
    >如果没做数据备份,redis启动了,没有可恢复的数据,redis无法对外提供服务。
    >此时大量的请求过来,缓存全部无法命中,请求全部查询数据库,造成数据库宕机,造成缓存雪崩问题。

redis持久化:RDB,AOF。
    >`RDB:Redis Database`
    >`AOF:Append Only File`

9-1-RDB和AOF的介绍

9-2-AOF-rewrite原理剖析

10. redis的RDB和AOF两种持久化机制的优劣势对比

10.1 RDB持久化机制的优点

  1. RDB会生成多个数据文件,每个数据文件代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备,
    可以将这些数据文件发送到一些远程的安全存储上去,以预定好的备份策略来定期备份redis中的数据。
    RDB也可以做冷备,生成多个文件,每个文件都代表了某一个时刻的完整的数据快照。
    AOF也可以做冷备,只有一个文件,但是你可以,每隔一定时间,去copy一份这个文件出来。
    RDB做冷备,优势在哪儿呢?由redis去控制固定时长生成快照文件的事情,比较方便;
    AOF,还需要自己写一些脚本去做这个事情,各种定时。
    RDB数据做冷备,在最坏的情况下,提供数据恢复的时候,速度比AOF快

  2. RDB对redis对外提供的读写服务,影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,
    让子进程执行磁盘IO操作来进行RDB持久化即可。
    RDB,每次写,都是直接写redis内存,只是在一定的时候,才会将数据写入磁盘中。
    AOF,每次都是要写文件的,虽然可以快速写入os cache中,但是还是有一定的时间开销的,速度肯定比RDB略慢一些。

  3. 相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速。
    AOF,存放的指令日志,做数据恢复的时候,其实是要回放和执行所有的指令日志,来恢复出来内存中的所有数据的。
    RDB,就是一份数据文件,恢复的时候,直接加载到内存中即可。

结合上述优点,RDB特别适合做冷备份。

10.2 RDB持久化机制的缺点

10-RDB丢失数据的问题

  1. 如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。一般来说,RDB数据快照文件,都是每隔5分钟,
    或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据。
    这个问题,也是rdb最大的缺点,就是不适合做第一优先的恢复方案,如果你依赖RDB做第一优先恢复方案,会导致数据丢失的比较多。

  2. RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒,
    或者甚至数秒。一般不要让RDB的间隔太长,否则每次生成的RDB文件太大了,对redis本身的性能可能会有影响的。

10.3 AOF持久化机制的优点

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

每隔1秒,就执行一次fsync操作,保证os cache中的数据写入磁盘中。

redis进程挂了,最多丢掉1秒钟的数据

  1. AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销,写入性能非常高,而且文件不容易破损,
    即使文件尾部破损,也很容易修复
  1. AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
    因为在rewrite log的时候,会对其中的指导进行压缩,创建出一份需要恢复数据的最小日志出来。
    再创建新日志文件的时候,老的日志文件还是照常写入。当新的merge后的日志文件ready的时候,再交换新老日志文件即可。

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

10.4 AOF持久化机制的缺点

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

  2. AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为AOF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync,
    性能也还是很高的。如果你要保证一条数据都不丢,也是可以的,AOF的fsync设置成没写入一条数据,
    fsync一次,那就完蛋了,redis的QPS大降

  3. 以前AOF发生过bug,就是通过AOF记录的日志,进行数据恢复的时候,
    没有恢复一模一样的数据出来。所以说,类似AOF这种较为复杂的基于命令日志/merge/回放的方式,
    比基于RDB每次持久化一份完整的数据快照文件的方式,更加脆弱一些,容易有bug。
    不过AOF就是为了避免rewrite过程导致的bug,因此每次rewrite并不是基于旧的指令日志进行merge的,
    而是基于当时内存中的数据进行指令的重新构建,这样健壮性会好很多。

  4. 唯一的比较大的缺点,其实就是做数据恢复的时候,会比较慢,还有做冷备,定期的备份,不太方便,
    可能要自己手写复杂的脚本去做,做冷备不太合适

10.5 RDB和AOF到底该如何选择

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

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

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

11. redis的RDB持久化配置以及数据恢复实验

2-redis高可用服务缓存架构-软件安装

Posted on 2100-01-01

7.1 安装单机版redis

7.1.1 安装单机版redis

  1. 更改/usr/local的group和user权限

    1
    sudo chown bigdata:bigdata /usr/local -R
  2. 安装tcl。本文安装tcl8.6.1版本

    1
    2
    3
    4
    5
    6
    7
    8

    cd /home/bigdata/software/
    wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz
    chmod 777 tcl8.6.1-src.tar.gz
    tar -zxvf /home/bigdata/software/tcl8.6.1-src.tar.gz -C /export/servers
    cd /export/servers/tcl8.6.1/unix/
    ./configure
    make && make install
  3. 安装redis。本文安装redis-3.2.8版本

    1
    2
    3
    4
    5
    6
    7
    8
    9

    cd /home/bigdata/software/
    wget http://download.redis.io/releases/redis-3.2.8.tar.gz
    chmod 777 redis-3.2.8.tar.gz
    tar -zxvf /home/bigdata/software/redis-3.2.8.tar.gz -C /export/servers
    cd /export/servers
    ln -s redis-3.2.8 redis
    cd /export/servers/redis
    make && make test && make install

7.1.2 redis的生产环境启动方案(非root用户启动)

  1. 将 /export/servers/redis/utils/redis_init_script脚本拷贝至/etc/init.d ,重命名为redis_6379,6379是redis实例监听的端口号。

    1
    2
    3
    4
    #root用户执行
    cp -r /export/servers/redis/utils/redis_init_script /etc/init.d
    cd /etc/init.d
    mv redis_init_script redis_6379
  2. 创建三个目录,一个存放redis配置文件,一个存放redis的持久化文件,一个存放redis日志

    • 创建存放redis的配置文件目录

      1
      2
      #非root用户执行
      mkdir -p /export/data/redis/conf
    • 创建存放redis的持久化文件

      1
      2
      #非root用户执行
      mkdir -p /export/data/redis/6379
    • 创建存放redis日志

      1
      2
      #非root用户执行
      mkdir -p /export/data/redis/logs
  3. 修改 /etc/init.d/redis_6379 脚本内容

    • 修改 /etc/init.d/redis_6379的REDISPORT为6379,默认是6379,可不修改
    • 设置 PIDFILE=/export/data/redis/redis_${REDISPORT}.pid
    • 设置 CONF="/export/data/redis/conf/${REDISPORT}.conf"
  1. 修改redis配置文件,并重命名
    将/export/servers/redis/redis.conf拷贝至/export/data/redis/conf,并重命名成6379.conf
    1
    2
    3
    4
    #非root用户执行
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf
    cd /export/data/redis/conf
    mv redis.conf 6379.conf

修改配置

配置项 配置值 说明
daemonize yes 让redis以daemon进程运行
pidfile /export/data/redis/redis_6379.pid 设置redis的pid文件位置
port 6379 设置redis的监听端口号
dir /export/data/redis/6379 设置持久化文件的存储位置
bind 127.0.0.1 192.168.33.61 设置绑定地址,使用redis-cli命令时, 使用-h参数时, 要指定ip地址
  1. 启动redis,关闭redis

    • root用户启动redis:

      1
      2
      3
      4
      #用root用户执行
      cd /etc/init.d
      chmod 777 redis_6379
      ./redis_6379 start
    • 非root用户启动redis:

      1
      /usr/local/bin/redis-server /export/data/redis/conf/6379.conf
    • 非root用户关闭redis:

      1
      /usr/local/bin/redis-cli -p 7200 shutdown
  2. 确认redis进程是否启动

    1
    ps -ef | grep redis
  3. 让redis跟随系统启动
    修改/etc/init.d/redis_6379脚本
    在/etc/init.d/redis_6379脚本中,最上面,加入下面两行注释

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #用root用户执行
    vi /etc/init.d/redis_6379

    # 添加下面两行注释
    # chkconfig: 2345 90 10
    # description: Redis is a persistent key-value database

    #执行以下命令,设置开机服务自动启动
    cd /etc/init.d/
    chkconfig redis_6379 on

    #如果要关闭自动启动,执行以下命令
    cd /etc/init.d/
    chkconfig redis_6379 off
  4. redis cli的使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #连接本机的6379端口停止redis进程
    redis-cli SHUTDOWN

    redis-cli -h 192.168.33.61 -p 6379

    #指定要连接的ip和端口号
    redis-cli -h 127.0.0.1 -p 6379 SHUTDOWN

    #ping redis的端口,看是否正常
    redis-cli PING

    redis-cli,进入交互式命令行

    #设置键值
    SET k1 v1

    #获取值
    GET k1

7.2 搭建redis-cluster集群

7.2.1 安装集群前的准备

*CentOS安装redis集群提示redis requires ruby version 2.2.2的解决方案*

  1. 安装RVM

    1
    2
    3
    4
    5
    #具体RVM安装命令地址,参考:http://rvm.io/
    gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB

    curl -sSL https://get.rvm.io | bash -s stable
    source /usr/local/rvm/scripts/rvm
  2. 查看rvm库中已知的ruby版本

    1
    rvm list known
  3. 安装一个ruby版本

    1
    rvm install 2.5.1
  4. 使用一个ruby版本

    1
    rvm use 2.5.1
  5. 设置默认版本

    1
    rvm use 2.5.1 --default
  6. 卸载一个已知版本

    1
    2
    3
    rvm remove 1.8.1
    rvm remove 2.3.4
    rvm remove 2.4.1
  7. 查看ruby版本

    1
    ruby --version
  8. 安装redis

    1
    gem install redis

7.2.2 安装redis集群

集群配置3台机器,一台机器配置一主一从。
redis的配置文件:/export/data/redis/conf/700*.conf
redis的持久化文件夹:/export/data/redis/700*

3台机器:

192.168.33.61
192.168.33.62
192.168.33.63

  1. 创建目录
    在3台机器上执行如下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    mkdir -p /export/data/redis-cluster
    mkdir -p /export/data/redis-cluster-log
    mkdir -p /export/data/redis/7001
    mkdir -p /export/data/redis/7002
    mkdir -p /export/data/redis/7003
    mkdir -p /export/data/redis/7004
    mkdir -p /export/data/redis/7005
    mkdir -p /export/data/redis/7006

    /export/data/redis-cluster为集群目录
    /export/data/redis-cluster-log为集群日志目录

  2. 准备配置文件
    在1台机器上执行如下命令,准备好配置文件

    1
    2
    3
    4
    5
    6
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7001.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7002.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7003.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7004.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7005.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7006.conf
  3. 修改配置


机器端口分配情况:

机器ip 分配的端口号
192.168.33.61 7001 ~ 7002
192.168.33.62 7003 ~ 7004
192.168.33.63 7005 ~ 7006

根据如下表格,并根据机器ip、端口分配情况,修改/export/data/redis/conf/700*.conf相应配置

配置项 配置值 说明
port 700* 设置redis的监听端口号
cluster-enabled yes 开启集群
cluster-config-file /export/data/redis-cluster/nodes-700*.conf 集群配置文件
cluster-node-timeout 15000 超时时长
daemonize yes 让redis以daemon进程运行
pidfile /export/data/redis/redis_700*.pid 设置redis的pid文件位置
dir /export/data/redis/700* 设置持久化文件的存储位置
logfile /export/data/redis-cluster-log/700*.log 设置集群日志持久化文件的存储位置
bind 192.168.33.6* 设置绑定地址,最好不要绑定127.0.0.1,否则集群启动不起来
appendonly yes 开启AOF功能

配置完后,将配置文件分发到另外两台机器

1
2
scp -r /export/servers/redis/700* bigdata@192.168.33.62:/export/data/redis/conf/
scp -r /export/servers/redis/700* bigdata@192.168.33.63:/export/data/redis/conf/

  1. 准备生产环境的启动脚本
    在/etc/init.d目录下,准备6个启动脚本,分别为: redis_7001, redis_7002, redis_7003, redis_7004, redis_7005, redis_7006,在每个启动脚本内,都修改对应的端口号。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    #!/bin/sh
    # chkconfig: 2345 90 10
    # description: Redis is a persistent key-value database
    #
    # Simple Redis init.d script conceived to work on Linux systems
    # as it does use of the /proc filesystem.

    #redis服务器监听的端口,根据实际情况修改
    REDISPORT=700*
    #EXEC=/usr/local/bin/redis-server
    #CLIEXEC=/usr/local/bin/redis-cli

    #PIDFILE=/var/run/redis_${REDISPORT}.pid
    #CONF="/etc/redis/${REDISPORT}.conf"

    #服务端所处位置,在make install后默认存放于`/usr/local/bin/redis-server`,如果未make install则需要修改该路径,下同。
    EXEC=/usr/local/bin/redis-server
    #客户端位置
    CLIEXEC=/usr/local/bin/redis-cli

    #Redis的PID文件位置
    PIDFILE=/export/data/redis/redis_${REDISPORT}.pid
    #配置文件位置,需要修改
    CONF="/export/data/redis/conf/${REDISPORT}.conf"

    case "$1" in
    start)
    if [ -f $PIDFILE ]
    then
    echo "$PIDFILE exists, process is already running or crashed"
    else
    echo "Starting Redis server..."
    $EXEC $CONF
    fi
    ;;
    stop)
    if [ ! -f $PIDFILE ]
    then
    echo "$PIDFILE does not exist, process is not running"
    else
    PID=$(cat $PIDFILE)
    echo "Stopping ..."
    $CLIEXEC -p $REDISPORT shutdown
    while [ -x /proc/${PID} ]
    do
    echo "Waiting for Redis to shutdown ..."
    sleep 1
    done
    echo "Redis stopped"
    fi
    ;;
    *)
    echo "Please use start or stop as first argument"
    ;;
    esac
  2. 在3台机器上,启动6个redis实例

    如果有启动redis实例,先停止实例

    1
    2
    3
    4
    5
    6
    /usr/local/bin/redis-cli -p 7001 shutdown
    /usr/local/bin/redis-cli -p 7002 shutdown
    /usr/local/bin/redis-cli -p 7003 shutdown
    /usr/local/bin/redis-cli -p 7004 shutdown
    /usr/local/bin/redis-cli -p 7005 shutdown
    /usr/local/bin/redis-cli -p 7006 shutdown

    清理集群状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    rm -rf /export/data/redis-cluster/*
    rm -rf /export/data/redis-cluster-log/*

    rm -rf /export/data/redis/7001/*
    rm -rf /export/data/redis/7002/*
    rm -rf /export/data/redis/7003/*
    rm -rf /export/data/redis/7004/*
    rm -rf /export/data/redis/7005/*
    rm -rf /export/data/redis/7006/*

    rm -rf /export/data/redis/redis_700*.pid

    #在192.168.33.61机器上执行redis-trib.rb create,所以要在清理192.168.33.61上的配置
    redis-cli -h 192.168.33.61 -p 7001 flushdb
    redis-cli -h 192.168.33.61 -p 7001 flushall
    redis-cli -h 192.168.33.61 -p 7001 cluster reset
    redis-cli -h 192.168.33.61 -p 7002 flushdb
    redis-cli -h 192.168.33.61 -p 7002 flushall
    redis-cli -h 192.168.33.61 -p 7002 cluster reset
    redis-cli -h 192.168.33.61 -p 7003 flushdb
    redis-cli -h 192.168.33.61 -p 7003 flushall
    redis-cli -h 192.168.33.61 -p 7003 cluster reset
    redis-cli -h 192.168.33.61 -p 7004 flushdb
    redis-cli -h 192.168.33.61 -p 7004 flushall
    redis-cli -h 192.168.33.61 -p 7004 cluster reset
    redis-cli -h 192.168.33.61 -p 7005 flushdb
    redis-cli -h 192.168.33.61 -p 7005 flushall
    redis-cli -h 192.168.33.61 -p 7005 cluster reset
    redis-cli -h 192.168.33.61 -p 7006 flushdb
    redis-cli -h 192.168.33.61 -p 7006 flushall
    redis-cli -h 192.168.33.61 -p 7006 cluster reset

    使用flushall和cluster reset命令,避免出现ERR Slot 0 is already busy (Redis::CommandError)错误

    192.168.33.61机器执行以下命令:

    1
    2
    /usr/local/bin/redis-server /export/data/redis/conf/7001.conf
    /usr/local/bin/redis-server /export/data/redis/conf/7002.conf

    192.168.33.62机器执行以下命令:

    1
    2
    /usr/local/bin/redis-server /export/data/redis/conf/7003.conf
    /usr/local/bin/redis-server /export/data/redis/conf/7004.conf

    192.168.33.63机器执行以下命令:

    1
    2
    /usr/local/bin/redis-server /export/data/redis/conf/7005.conf
    /usr/local/bin/redis-server /export/data/redis/conf/7006.conf

    查看redis实例启动日志:

    1
    cat /export/data/redis-cluster-log/700*
  3. 创建集群

    执行以下命令:

    1
    2
    cp /export/servers/redis/src/redis-trib.rb /usr/local/bin
    redis-trib.rb create --replicas 1 192.168.33.61:7001 192.168.33.61:7002 192.168.33.62:7003 192.168.33.62:7004 192.168.33.63:7005 192.168.33.63:7006

    –replicas 1: 每个master有1个slave

    如果出现以下错误:
    [ERR] Not all 16384 slots are covered by nodes,
    执行以下命令:

    1
    redis-trib.rb fix 192.168.33.61:7001

    查看集群状态:

    1
    redis-cli -h 192.168.33.61 -c -p 7001 cluster nodes
  4. 设置开机启动

    使用root用户执行
    192.168.33.61机器执行以下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #执行以下命令,设置开机服务自动启动
    cd /etc/init.d/
    chkconfig redis_7001 on
    chkconfig redis_7002 on

    #如果要关闭自动启动,执行以下命令
    cd /etc/init.d/
    chkconfig redis_7001 off
    chkconfig redis_7002 off

    192.168.33.62机器执行以下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #执行以下命令,设置开机服务自动启动
    cd /etc/init.d/
    chkconfig redis_7003 on
    chkconfig redis_7004 on

    #如果要关闭自动启动,执行以下命令
    cd /etc/init.d/
    chkconfig redis_7003 off
    chkconfig redis_7004 off

    192.168.33.63机器执行以下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #执行以下命令,设置开机服务自动启动
    cd /etc/init.d/
    chkconfig redis_7005 on
    chkconfig redis_7006 on

    #如果要关闭自动启动,执行以下命令
    cd /etc/init.d/
    chkconfig redis_7005 off
    chkconfig redis_7006 off

11. redis的RDB持久化配置以及数据恢复实验

11.1 如何配置RDB持久化机制

修改配置
/etc/init.d/redis.conf

1
save 60 1000

每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,snapshotting,快照。

save可以设置多个

11.2 RDB持久化机制的工作流程

  1. redis根据配置自己尝试去生成rdb快照文件
  2. fork一个子进程出来
  3. 子进程尝试将数据dump到临时的rdb快照文件中
  4. 完成rdb快照文件的生成之后,就替换之前的旧的快照文件

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


11.3 基于RDB持久化机制的数据恢复实验

  1. redis-cli SHUTDOWN这种方式去停掉redis,其实是一种安全退出的模式,redis在退出的时候会将内存中的数据
    立即生成一份完整的rdb快/var/redis/6379/dump.rdb

  2. 在redis中再保存几条新的数据,用kill -9粗暴杀死redis进程,模拟redis故障异常退出,导致内存数据丢失的场景
    这次就发现,redis进程异常被杀掉,数据没有进dump文件,几条最新的数据就丢失了

  3. 手动设置一个save检查点,save 5 1

  4. 写入几条数据,等待5秒钟,会发现自动进行了一次dump rdb快照,在dump.rdb中发现了数据
  5. 异常停掉redis进程,再重新启动redis,看刚才插入的数据还在

rdb的手动配置检查点,以及rdb快照的生成,包括数据的丢失和恢复,全都演示过了

3-redis高可用服务缓存架构-软件启动

Posted on 2100-01-01

1. 单机启动redis集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

#停止redis进程
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7001 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7002 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7003 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7004 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7005 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7006 shutdown

#启动redis集群
/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7001.conf
/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7002.conf
/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7003.conf
/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7004.conf
/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7005.conf
/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7006.conf

#查看redis集群启动日志
cat /export/data/test-env-redis/redis-cluster-log/700*



#清理redis集群
mkdir -p /export/data/test-env-redis/redis-cluster
mkdir -p /export/data/test-env-redis/redis-cluster-log
mkdir -p /export/data/test-env-redis/redis/7001
mkdir -p /export/data/test-env-redis/redis/7002
mkdir -p /export/data/test-env-redis/redis/7003
mkdir -p /export/data/test-env-redis/redis/7004
mkdir -p /export/data/test-env-redis/redis/7005
mkdir -p /export/data/test-env-redis/redis/7006


rm -rf /export/data/test-env-redis/redis-cluster/*
rm -rf /export/data/test-env-redis/redis-cluster-log/*

rm -rf /export/data/test-env-redis/redis/7001/*
rm -rf /export/data/test-env-redis/redis/7002/*
rm -rf /export/data/test-env-redis/redis/7003/*
rm -rf /export/data/test-env-redis/redis/7004/*
rm -rf /export/data/test-env-redis/redis/7005/*
rm -rf /export/data/test-env-redis/redis/7006/*
rm -rf /export/data/test-env-redis/redis/redis_700*.pid

redis-cli -h 192.168.33.61 -p 7001 flushdb
redis-cli -h 192.168.33.61 -p 7001 flushall
redis-cli -h 192.168.33.61 -p 7001 cluster reset
redis-cli -h 192.168.33.61 -p 7002 flushdb
redis-cli -h 192.168.33.61 -p 7002 flushall
redis-cli -h 192.168.33.61 -p 7002 cluster reset
redis-cli -h 192.168.33.61 -p 7003 flushdb
redis-cli -h 192.168.33.61 -p 7003 flushall
redis-cli -h 192.168.33.61 -p 7003 cluster reset
redis-cli -h 192.168.33.61 -p 7004 flushdb
redis-cli -h 192.168.33.61 -p 7004 flushall
redis-cli -h 192.168.33.61 -p 7004 cluster reset
redis-cli -h 192.168.33.61 -p 7005 flushdb
redis-cli -h 192.168.33.61 -p 7005 flushall
redis-cli -h 192.168.33.61 -p 7005 cluster reset
redis-cli -h 192.168.33.61 -p 7006 flushdb
redis-cli -h 192.168.33.61 -p 7006 flushall
redis-cli -h 192.168.33.61 -p 7006 cluster reset



#拷贝命令至bin目录
#cp /export/servers/redis/src/redis-trib.rb /usr/local/bin

#创建集群
redis-trib.rb create --replicas 1 192.168.33.61:7001 192.168.33.61:7002 192.168.33.61:7003 192.168.33.61:7004 192.168.33.61:7005 192.168.33.61:7006

storm集群安装并运行程序

Posted on 2099-12-31

安装依赖包

在Nimbus和worker机器上安装Java和Python依赖包

1
2
3
4
5
#需安装Java7以上版本
java -version

#需安装python2.7以上版本
python --version

解压storm安装包

1
2
3
4
tar -zxvf /home/bigdata/software/apache-storm-1.1.0.tar.gz -C /export/servers/
cd /export/servers/
rm -rf storm
ln -s apache-storm-1.1.0 storm

修改配置文件

1
2
cp /export/servers/storm/conf/storm.yaml /export/servers/storm/conf/storm.yaml.bak
vi /export/servers/storm/conf/storm.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#指定storm使用的zk集群
storm.zookeeper.servers:
- "bigdata01"
- "bigdata02"
- "bigdata03"

#指定storm本地目录
storm.local.dir: "/export/data/storm"

#用于配置主控节点的地址,可以配置多个
nimbus.seeds: ["192.168.33.61"]

#指定nimbus启动JVM最大可用内存大小
nimbus.childopts: "-Xmx1024m"

#指定supervisor启动JVM最大可用内存大小
supervisor.childopts: "-Xmx1024m"

#指定supervisor节点上,每个worker启动JVM最大可用内存大小
worker.childopts: "-Xmx768m"

#指定ui启动JVM最大可用内存大小,ui服务一般与nimbus同在一个节点上。
ui.childopts: "-Xmx768m"

#指定supervisor节点上,启动worker时对应的端口号,每个端口对应槽,每个槽位对应一个worker
#指定每个机器上可以启动多少个worker,一个端口号代表一个worker
supervisor.slots.ports:
- 6700
- 6701
- 6702
- 6703

设置环境变量

1
2
3
4
5
6
7
8
9
10
#使用root用户执行
vi /etc/profile

...
#Storm
export STORM_HOME=/export/servers/storm
export PATH=$PATH:$STORM_HOME/bin
...

source /etc/profile

分发安装包到其它台机器

1
2
3
4
5
6
scp -r /export/servers/apache-storm-1.1.0 bigdata02:/export/servers
scp -r /export/servers/apache-storm-1.1.0 bigdata03:/export/servers

#然后分别在各机器上创建软连接,并设置环境变量
cd /export/servers/
ln -s apache-storm-1.1.0 storm

设置环境变量

1
2
3
4
5
6
7
8
9
10
#使用root用户执行
vi /etc/profile

...
#Storm
export STORM_HOME=/export/servers/storm
export PATH=$PATH:$STORM_HOME/bin
...

source /etc/profile

启动storm集群、ui界面、运行程序

启动前,先保证zookeeper集群已经启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#在主节点启动nimbus
storm nimbus >/dev/null 2>&1 &

#在所有机器启动supervisor
storm supervisor >/dev/null 2>&1 &

#在主节点启动ui
storm ui >/dev/null 2>&1 &

#在supervisor启动
storm logviewer >/dev/null 2>&1 &

#运行程序
storm jar /home/bigdata/run-jar-dir/storm-0.0.1-SNAPSHOT.jar com.laungcisin.storm.WordCountTopology wordCountTopology

#kill topology
storm kill wordCountTopology

通过ui界面查看集群

访问 http://192.168.33.61:8080 ,即可看到storm的ui界面。

zookeeper-kafka集群的安装部署

Posted on 2099-12-31

zookeeper集群搭建

解压文件

1
2
3
tar -zxvf /home/bigdata/software/zookeeper-3.4.11.tar.gz -C /export/servers/
cd /export/servers/
ln -s zookeeper-3.4.11 zookeeper

修改配置

修改zookeeper数据目录和日志目录

1
2
3
4
5
6
7
8
9
cd /export/servers/zookeeper/conf
cp zoo_sample.cfg zoo.cfg
vi /export/servers/zookeeper/conf/zoo.cfg

#zookeeper数据目录
dataDir=/export/data/zookeeper/workdir/data

#zookeeper日志目录
dataLogDir=/export/data/zookeeper/workdir/log

设置zookeeper集群信息

1
2
3
4
5
6
7
8
vi /export/servers/zookeeper/conf/zoo.cfg

...
#设置集群信息,此处的bigdata0x可以用ip地址代替
server.1=bigdata01:2888:3888
server.2=bigdata02:2888:3888
server.3=bigdata03:2888:3888
...
1
2
3
4
5
6
7
#执行
rm -rf /export/data/zookeeper/

mkdir -p /export/data/zookeeper/workdir/data
mkdir -p /export/data/zookeeper/workdir/log

echo "1" > /export/data/zookeeper/workdir/data/myid

将zookeeper拷贝到其它机器上

1
2
3
4
5
6
7
8
9
10
11
12
13
#将zookeeper拷贝至bigdata02机器上
scp -r /export/servers/zookeeper-3.4.11 bigdata@bigdata02:/export/servers

#进入bigdata02机器执行以下命令
mkdir -p /export/data/zookeeper/workdir/data
echo "2" > /export/data/zookeeper/workdir/data/myid

#将zookeeper拷贝至bigdata03机器上
scp -r /export/servers/zookeeper-3.4.11 bigdata@bigdata03:/export/servers

#进入bigdata03机器执行以下命令
mkdir -p /export/data/zookeeper/workdir/data
echo "3" > /export/data/zookeeper/workdir/data/myid

配置环境变量

1
2
3
4
5
6
7
8
9
#切换成root用户执行,修改profile文件
vi /etc/profile
#Zookeeper
export ZOOKEEPER_HOME=/export/servers/zookeeper
export PATH=$PATH:${ZOOKEEPER_HOME}/bin


#使配置生效
source /etc/profile

zk命令

1
2
3
4
5
6
7
8
#启动时,切换成bigdata用户
su bigdata

#启动zookeeper
zkServer.sh start

#查看zookeeper状态
zkServer.sh status

kafka集群搭建

搭建kafka集群前,先保证zookeeper集群已搭建成功。

解压文件

1
2
3
tar -zxvf /home/bigdata/software/kafka_2.11-1.0.0.tgz -C /export/servers/
cd /export/servers/
ln -s kafka_2.11-1.0.0 kafka

修改配置

修改zookeeper数据目录和日志目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
cp /export/servers/kafka/config/server.properties /export/servers/kafka/config/server.properties.bak
vi /export/servers/kafka/config/server.properties

#根据实际情况修改
#当前机器在集群中的唯一标识,和zookeeper的myid性质一样
broker.id=0

#当前kafka对外提供服务的端口默认是9092
port=9092

#这个参数默认是关闭的,在0.8.1有个bug,DNS解析问题,失败率的问题。
host.name=192.168.33.61

#这个是borker进行网络处理的线程数
num.network.threads=3

#这个是borker进行I/O处理的线程数
num.io.threads=8

#消息存放的目录,这个目录可以配置为","逗号分割的表达式,
#上面的num.io.threads要大于这个目录的个数这个目录,
#如果配置多个目录,新创建的topic他把消息持久化的地方是,当前以逗号分割的目录中,那个分区数最少就放那一个
log.dirs=/export/data/kafka/logs

#发送缓冲区buffer大小,数据不是一下子就发送的,先回存储到缓冲区了到达一定的大小后在发送,能提高性能
socket.send.buffer.bytes=102400

#kafka接收缓冲区大小,当数据到达一定大小后在序列化到磁盘
socket.receive.buffer.bytes=102400

#这个参数是向kafka请求消息或者向kafka发送消息的请请求的最大数,这个值不能超过java的堆栈大小
socket.request.max.bytes=104857600

#默认的分区数,一个topic默认1个分区数
num.partitions=1

#默认消息的最大持久化时间,168小时,7天
log.retention.hours=168

#消息保存的最大值5M
message.max.byte=5242880


#kafka保存消息的副本数,如果一个副本失效了,另一个还可以继续提供服务
default.replication.factor=2

#取消息的最大直接数
replica.fetch.max.bytes=5242880

#这个参数是:因为kafka的消息是以追加的形式落地到文件,当超过这个值的时候,kafka会新起一个文件
log.segment.bytes=1073741824

#每隔300000毫秒去检查上面配置的log失效时间(log.retention.hours=168 ),
#到目录查看是否有过期的消息如果有,删除
log.retention.check.interval.ms=300000

#是否启用log压缩,一般不用启用,启用的话可以提高性能
log.cleaner.enable=false

#设置zookeeper的连接端口
zookeeper.connect=bigdata01:2181,bigdata02:2181,bigdata03:2181

#远程消费的话,配置此参数
listeners=PLAINTEXT://192.168.33.61:9092

分发安装包

1
2
3
4
5
6
7
#将kafka安装包分发到其它机器上
scp -r /export/servers/kafka_2.11-1.0.0 bigdata@bigdata02:/export/servers
scp -r /export/servers/kafka_2.11-1.0.0 bigdata@bigdata03:/export/servers

#然后分别在各机器上创建软连
cd /export/servers/
ln -s kafka_2.11-1.0.0 kafka

修改其它机器上的kafka配置

1
2
3
4
5
6
#修改其它机器的kafka配置信息
vi /export/servers/kafka/config/server.properties

...
将broker.id的值,修改成相应的数字
...

添加环境变量

1
2
3
4
5
6
7
8
9
#切换成root用户执行,修改profile文件
vi /etc/profile

#Kafka
export KAFKA_HOME=/export/servers/kafka
export PATH=$PATH:$KAFKA_HOME/bin

#使配置生效
source /etc/profile

kafka命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#依次在各节点上启动kafka
kafka-server-start.sh /export/servers/kafka/config/server.properties > /dev/null 2>&1 &

#停止kafka
kafka-server-stop.sh

#查看当前服务器中的所有topic
kafka-topics.sh --list --zookeeper bigdata01:2181

#创建topic
kafka-topics.sh --create --zookeeper bigdata01:2181 --replication-factor 3 --partitions 3 --topic test

#删除topic
kafka-topics.sh --delete --zookeeper bigdata01:2181 --topic test
需要server.properties中设置delete.topic.enable=true否则只是标记删除或者直接重启。

#通过shell命令发送消息
kafka-console-producer.sh --broker-list bigdata01:9092 --topic test


#通过shell消费消息
kafka-console-consumer.sh --zookeeper bigdata01:2181 --from-beginning --topic test

#查看消费位置
kafka-run-class.sh kafka.tools.ConsumerOffsetChecker --zookeeper bigdata01:2181 --group testGroup

#查看某个Topic的详情
kafka-topics.sh --topic test --describe --zookeeper bigdata01:2181

layui-table-行选中checkbox勾选-行变色

Posted on 2088-01-01 | In 前端

layui-table-行选中checkbox勾选-行变色

完整代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
layui.use([..., 'table'], function () {
var table = layui.table;
...

table.render({
...
});

//监听复选框事件,被选中的行高亮显示
table.on('checkbox(metadataTableFilter)', function (obj) {
if (obj.checked == true && obj.type == 'all') {
//点击全选
$('.layui-table-body table.layui-table tbody tr').addClass('layui-table-click');
} else if (obj.checked == false && obj.type == 'all') {
//点击全不选
$('.layui-table-body table.layui-table tbody tr').removeClass('layui-table-click');
} else if (obj.checked == true && obj.type == 'one') {
//点击单行
if (obj.checked == true) {
obj.tr.addClass('layui-table-click');
} else {
obj.tr.removeClass('layui-table-click');
}
} else if (obj.checked == false && obj.type == 'one') {
//点击全选之后点击单行
if (obj.tr.hasClass('layui-table-click')) {
obj.tr.removeClass('layui-table-click');
}
}
});

...
});

...
//单击table行,勾选checkbox事件
$(document).on("click", ".layui-table-body table.layui-table tbody tr", function () {
var index = $(this).attr('data-index');
var tableBox = $(this).parents('.layui-table-box');
//存在固定列
var tableDiv;
if (tableBox.find(".layui-table-fixed.layui-table-fixed-l").length > 0) {
tableDiv = tableBox.find(".layui-table-fixed.layui-table-fixed-l");
} else {
tableDiv = tableBox.find(".layui-table-body.layui-table-main");
}
var checkLength = tableDiv.find("tr[data-index=" + index + "]").find(
"td div.layui-form-checked").length;

var checkCell = tableDiv.find("tr[data-index=" + index + "]").find(
"td div.laytable-cell-checkbox div.layui-form-checkbox I");
if (checkCell.length > 0) {
checkCell.click();
}
});

$(document).on("click", "td div.laytable-cell-checkbox div.layui-form-checkbox", function (e) {
e.stopPropagation();
});

jQuery-Validate-layer表单校验

Posted on 2088-01-01 | In 前端

用jQuery Validate layer插件实现好看的表单校验

jQuery-Validate-layer表单校验效果

完整代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<html>
<head>
<title>valdate</title>
<meta charset="UTF-8">
<link rel="stylesheet" type="text/css" href="http://layui.hcwl520.com.cn/layui-v2.4.5/css/layui.css?v=201811010202"/>

</head>

<body style="padding-top: 20px">
<form class="layui-form" id="signupForm" action="">
<div class="layui-form-item">
<label class="layui-form-label">名字</label>
<div class="layui-input-inline">
<input type="text" id="firstname" name="firstname" autocomplete="off" placeholder="请输入名字" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">姓氏</label>
<div class="layui-input-inline">
<input type="text" id="lastname" name="lastname" autocomplete="off" placeholder="请输入姓氏" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">用户名</label>
<div class="layui-input-inline">
<input type="text" id="username" name="username" autocomplete="off" placeholder="请输入用户名" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">密码</label>
<div class="layui-input-inline">
<input type="password" id="password" name="password" autocomplete="off" placeholder="请输入密码" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">验证密码</label>
<div class="layui-input-inline">
<input type="password" id="confirm_password" name="confirm_password" autocomplete="off" placeholder="请输入验证密码" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">Email</label>
<div class="layui-input-inline">
<input type="text" id="email" name="email" autocomplete="off" placeholder="请输入Email" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-inline">
<button class="layui-btn" lay-submit="" lay-filter="demo1">立即提交</button>
<button type="reset" class="layui-btn layui-btn-primary">重置</button>
</div>
</div>

</form>

<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/jquery-validate/1.19.0/jquery.validate.min.js"></script>
<script src="https://cdn.bootcss.com/jquery-validate/1.19.0/localization/messages_zh.min.js"></script>
<script src="http://layui.hcwl520.com.cn/layui-v2.4.5/layui.js?v=201811010202"></script>
<style>
.layui-form-label {
width: 150px;
}
.layui-input {
width: 200px;
}
.layui-form-checkbox span {
height: 38px;
vertical-align: middle;
}
.error {
color: blue;
}
</style>
<script>
layui.use(['layer', 'form'], function () {
var layer = layui.layer,
form = layui.form;
});

$.validator.setDefaults({
submitHandler: function () {
layer.alert("提交事件!");
}
});

$(function(){
// 在键盘按下并释放及提交后验证提交表单
$("#signupForm").validate({
onfocusin: function (element) {
$(element).valid();
},
onfocusout: function (element) {
$(element).valid();
},
onclick: function (element) {
$(element).valid();
},
/*
onkeyup: function (element) {
$(element).valid();
},
*/
rules: {
firstname: "required",
lastname: "required",
username: {
required: true,
minlength: 2
},
password: {
required: true,
minlength: 5
},
confirm_password: {
required: true,
minlength: 5,
equalTo: "#password"
},
email: {
required: true,
email: true
}
},
messages: {
firstname: "请输入您的名字",
lastname: "请输入您的姓氏",
username: {
required: "请输入用户名",
minlength: "用户名不能少于两个字母"
},
password: {
required: "请输入密码",
minlength: "密码长度不能小于 5 个字母"
},
confirm_password: {
required: "请输入密码",
minlength: "密码长度不能小于 5 个字母",
equalTo: "两次密码输入不一致"
},
email: "请输入一个正确的邮箱"
},
//重写showErrors
showErrors: function (errorMap, errorList) {
$.each(errorList, function (i, v) {
//layer显示
layer.tips(v.message, v.element, {tips: [2, '#ee1212'], time: 1500});
return false;
});
},
/* 失去焦点时不验证 */
onfocusout: false
});
});
</script>

</body>
</html>

mybatis问题解决集合

Posted on 2066-01-01 | In mybatis

mybatis-foreach使用

前言:

后台传给mapper的字符串:"b, c",
mybatis需生成select * from table where ids in ('b', 'e')语句,来执行查询。

解决方法:

mybatis只需使用foreach语句,即可实现功能。

mybatis-xml配置:

1
2
3
4
5
6
7
<select id="getSimilarity" parameterType="java.lang.String" resultType="java.util.HashMap">
select * from table
where ids in
<foreach item="item" index="index" collection="ids.split(’,’)" open="(" separator="," close=")">
#{item}
</foreach>
</select>

控制台打印:

1
2
Preparing: select * from table where ids in ( ? , ?  ) 
Parameters: b(String), e(String)

安装hexo并部署到github-page上

Posted on 2019-05-08

Spring读取properties文件内容

Posted on 2019-04-28

76-83热点缓存问题及解决方案

Posted on 2019-01-28

76-瞬间的缓存热点数据问题

热数据 -> 热数据的统计 -> redis中缓存的预热 -> 避免新系统刚上线,或者是redis崩溃数据丢失后重启,redis中没有数据,redis冷启动 -> 大量流量直接到数据库

redis启动前,必须确保其中是有部分热数据的缓存的

热点缓存导致系统崩溃的问题

77-基于nginx+lua+storm的热点缓存的流量分发策略自动降级解决方案

  1. 在storm中,实时地计算出瞬间出现的热点数据

    有很多种算法,介绍一种比较简单的算法
    某个storm task,上面算出了1万个商品的访问次数,LRUMap
    计算频率高一些,每隔5秒去遍历一次LRUMap,将其中的访问次数进行排序,统计出排在95%的商品访问次数的平均值
    比如:
    1000
    999
    888
    777
    666
    50
    60
    80
    100
    120

    比如说,排在95%的商品,访问次数的平均值是100

    然后,从最前面开始,往后遍历,去找有没有瞬间出现的热点数据

    1000,是95%的平均值(100)的10倍,这个时候要设定一个阈值,比如说超出95%平均值的n倍,5倍

    我们就认为是瞬间出现的热点数据,判断其可能在短时间内继续扩大的访问量,甚至达到平均值几十倍,或者几百倍

    当遍历,发现说第一个商品的访问次数,小于平均值的5倍,就安全了,就break掉这个循环

    热点数据,热数据,不是一个概念

    有100个商品,前10个商品比较热,访问量在500左右,其他普通商品,访问量都在200左右,就说前10个商品是热数据

    预热的时候,将这些热数据加载到缓存中就可以了。

    热点数据:某个商品的访问量,瞬间超出了普通商品的10倍,或者100倍,1000倍。

  2. storm直接发送http请求到nginx上,nginx上用lua脚本去处理这个请求

    storm会将热点数据对应的productId,发送到流量分发nginx上面去,放在本地缓存中
    storm会将热点数据对应的完整的缓存数据,发送到所有的应用nginx服务器上去,直接放在本地缓存中

  3. 流量分发nginx的分发策略降级

    流量分发nginx,加一个逻辑,就是每次访问一个商品详情页的时候,如果发现它是个热点,那么立即做流量分发策略的降级

    hash策略,同一个productId的访问都对一个同一台应用nginx服务器

    降级成对这个热点商品,流量分发采取随机负载均衡发送到所有的后端应用nginx服务器上去

    瞬间将热点缓存数据的访问,从hash分发到一台nginx,变成了,负载均衡发送到多台nginx上去

    避免说大量的流量全部集中到一台机器,50万的访问量到一台nginx,5台应用nginx,每台就可以承载10万的访问量

  4. storm还需要保存下来上次识别出来的热点list

    下次去识别的时候,这次的热点list跟上次的热点list做一下diff,有的商品已经不是热点数据了

    热点数据的取消的逻辑:发送http请求到流量分发nginx上去,取消掉对应的热点数据,从nginx本地缓存中删除

热点缓存的解决方案

68-90缓存冷启动及缓存预热解决方案

Posted on 2019-01-16

缓存冷启动的问题

  1. 新系统第一次上线,此时在缓存里可能是没有数据的

  2. 系统在线上稳定运行着,但是突然间重要的redis缓存全盘崩溃了,而且不幸的是,数据全都无法找回来

缓存冷启动问题

缓存预热

缓存冷启动,redis启动后,一点数据都没有,直接就对外提供服务了,mysql就裸奔

解决方案:

  1. 提前给redis中灌入部分数据,再提供服务
  2. 肯定不可能将所有数据都写入redis,因为数据量太大了,第一耗费的时间太长了,第二根本redis容纳不下所有的数据
  3. 需要根据当天的具体访问情况,实时统计出访问频率较高的热数据
  4. 然后将访问频率较高的热数据写入redis中,肯定是热数据也比较多,我们也得多个服务并行读取数据去写,并行的分布式的缓存预热
  5. 然后将灌入了热数据的redis对外提供服务,这样就不至于冷启动,直接让数据库裸奔了

代码实现思路:

  1. nginx+lua将访问流量上报到kafka中

    要统计出来当前最新的实时的热数据有哪些,我们就得将商品详情页访问的请求对应的流量、日志,实时上报到kafka中

  2. storm从kafka中消费数据,实时统计出每个商品的访问次数,访问次数基于LRU内存数据结构的存储方案

    优先使用内存中的一个LRUMap去存放,优点是性能高而且没有外部依赖
    如果使用redis,要防止redis挂掉,导致数据丢失的情况。如果用mysql,扛不住高并发读写; 用hbase,hadoop生态系统,维护麻烦,太重了

    只要统计出最近一段时间访问最频繁的商品,然后对它们进行访问计数,同时维护出一个前N个访问最多的商品list即可。

    热数据:最近一段时间,比如最近1个小时,最近5分钟,1万个商品请求,统计出最近这段时间内每个商品的访问次数,排序,做出一个排名前N的list

    计算好每个task大致要存放的商品访问次数的数量,计算出大小

    然后构建一个LRUMap,apache commons collections有开源的实现,设定好map的最大大小,就会自动根据LRU算法去剔除多余的数据,保证内存使用限制

    即使有部分数据被干掉了,然后下次来重新开始计数,也没关系,因为如果它被LRU算法干掉,那么它就不是热数据,说明最近一段时间都很少访问了

  3. 每个storm task启动的时候,基于zookeeper分布式锁,将自己的id写入zookeeper同一个节点中

  4. 每个storm task负责完成自己这里的热数据的统计,每隔一段时间,就遍历一下这个map,然后维护一个前N个商品的list,更新这个list

  5. 写一个后台线程,每隔一段时间,比如1分钟,都将排名前N的热数据list,同步到zookeeper中去,存储到这个storm task对应的一个zNode中去

  6. 我们需要一个服务,比如说,代码可以跟缓存数据生产服务放一起,但是也可以放单独的服务

    服务可能部署了很多个实例

    每次服务启动的时候,就会去拿到一个storm task的列表,然后根据taskId,一个一个的去尝试获取taskId对应的zNode的zookeeper分布式锁

    如果能获取到分布式锁的话,那么就将那个storm task对应的热数据的list取出来
    然后将数据从mysql中查询出来,写入缓存中,进行缓存的预热,多个服务实例,分布式的并行的去做,基于zk分布式锁做了协调了,分布式并行缓存的预热


基于nginx+lua完成商品详情页访问流量实时上报kafka的开发

不在分发服务器做,在实际处理请求的机器上做,bigdata01,bigdata02

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#使用root用户安装
yum install -y unzip

#以下命令使用bigdata用户

#需要在nginx.conf中,http部分,加入resolver 8.8.8.8;
vi /export/servers/nginx/conf/nginx.conf
...
http {
resolver 8.8.8.8;
include mime.types;
default_type application/octet-stream;
...

#需要在kafka中加入 advertised.host.name = 192.168.33.6* ,重启三个kafka进程
vi /export/servers/kafka/config/server.properties
...
#每台机器根据本机ip修改
advertised.host.name = 192.168.33.61
...


cd /home/bigdata/software
wget https://github.com/doujiang24/lua-resty-kafka/archive/master.zip
chmod 777 /home/bigdata/software/master.zip
unzip /home/bigdata/software/master.zip
cp -rf /home/bigdata/software/lua-resty-kafka-master/lib/resty /home/bigdata/ngnix-test/lualib

#编辑lua脚本
vi /home/bigdata/ngnix-test/lua/product.lua

...
#注意不要重复引入相关lib
local cjson = require("cjson")
local producer = require("resty.kafka.producer")

local broker_list = {
{ host = "192.168.33.61", port = 9092 },
{ host = "192.168.33.62", port = 9092 },
{ host = "192.168.33.63", port = 9092 }
}

-- 将需要的数据放入json数据中
local log_json = {}
log_json["request_module"] = "product_detail_info"
log_json["headers"] = ngx.req.get_headers()
log_json["uri_args"] = ngx.req.get_uri_args()
log_json["body"] = ngx.req.read_body()
log_json["http_version"] = ngx.req.http_version()
log_json["method"] =ngx.req.get_method()
log_json["raw_reader"] = ngx.req.raw_header()
log_json["body_data"] = ngx.req.get_body_data()

-- json转string
local message = cjson.encode(log_json);

local productId = ngx.req.get_uri_args()["productId"]

-- 构建kafka producer
local async_producer = producer:new(broker_list, { producer_type = "async" })
-- 将数据发给kafka
local ok, err = async_producer:send("access-log", productId, message)

if not ok then
ngx.log(ngx.ERR, "kafka send err:", err)
return
end


#使用root启动nginx或重新加载nginx
/export/servers/nginx/sbin/nginx
/export/servers/nginx/sbin/nginx -s reload
...

两台机器上都这样做,才能统一上报流量到kafka

#创建access-log topic
kafka-topics.sh --zookeeper 192.168.33.61:2181,192.168.33.62:2181,192.168.33.63:2181 --topic access-log --replication-factor 1 --partitions 1 --create

#使用kafka消费命令消费数据,查看数据产生情况
kafka-console-consumer.sh --zookeeper 192.168.33.61:2181,192.168.33.62:2181,192.168.33.63:2181 --topic access-log --from-beginning

#使用浏览器产生数据

http://192.168.33.61/product?requestPath=product&productId=11&shopId=1

注意事项:

  1. kafka进程挂了,可能是虚拟机的问题,杀掉进程,重新启动一下

  2. 需要启动eshop-cache缓存服务,因为nginx中的本地缓存可能不在了

热启动方案注意点-1

基于storm-kafka完成商品访问次数实时统计拓扑的开发

nignx将访问日志通过kafka发送给storm,storm消费kafka消息

基于storm完成LRUMap中topn热门商品列表的算法讲解与编写

storm消费kafka消息,并统计结果,将结果放入LRUMap中,再写算法取TopN结果

基于storm-zookeeper完成热门商品列表的分段存储

  1. 将storm自己运行的task的 taskId 写入一个 zookeeper node 中,形成 taskId 的列表

    /taskId-list,111,222,333

  2. 然后每次都将自己的热门商品列表,写入自己的 taskId 对应的 zookeeper 节点

    <”/task-hot-product-list-taskId”, 热门商品列表>

  3. 然后这样的话,并行的预热程序才能从第一步中知道,有哪些 taskId

  4. 然后并行预热程序根据每个 taskId 去获取一个锁,然后再从对应的 zNode 中拿到热门商品列表

基于双重zookeeper分布式锁完成分布式并行缓存预热的代码开发

  1. 服务启动的时候,进行缓存预热

  2. 从zk中读取 taskId 列表

  3. 依次遍历每个 taskId,尝试获取分布式锁,如果获取不到,快速报错,不要等待,因为说明已经有其他服务实例在预热了

  4. 直接尝试获取下一个 taskId 的分布式锁

  5. 即使获取到了分布式锁,也要检查一下这个 taskId 的预热状态,如果已经被预热过了,就不再预热了

  6. 执行预热操作,遍历 productId 列表,查询数据,然后写ehcache和redis

  7. 预热完成后,设置 taskId 对应的预热状态

将缓存预热解决方案的代码运行后观察效果以及调试和修复所有的bug

61-67-Storm教程

Posted on 2019-01-08 | In storm

Storm到底是什么?

mysql、hadoop与storm

mysql:事务性系统,面临海量数据的尴尬
hadoop:离线批处理
storm:实时计算

我们能不能自己搞一套storm?

  1. 花费大量的时间在底层技术细节上:如何部署各种中间队列,节点间的通信,容错,资源调配,计算节点的迁移和部署,等等
  2. 花费大量的时间在系统的高可用上问题上:如何保证各种节点能够高可用稳定运行
  3. 花费大量的时间在系统扩容上:吞吐量需要扩容的时候,你需要花费大量的时间去增加节点,修改配置,测试,等等

storm的特点是什么?

  1. 支撑各种实时类的项目场景

    实时处理消息以及更新数据库,基于最基础的实时计算语义和API(实时数据处理领域);对实时的数据流持续的进行查询或计算,
    同时将最新的计算结果持续的推送给客户端展示,同样基于最基础的实时计算语义和API(实时数据分析领域);
    对耗时的查询进行并行化,基于DRPC,即分布式RPC调用,单表30天数据,并行化,每个进程查询一天数据,最后组装结果

  2. 高度的可伸缩性

    如果要扩容,直接加机器,调整storm计算作业的并行度就可以了,storm会自动部署更多的进程和线程到其他的机器上去,无缝快速扩容
    扩容起来,超方便

  3. 数据不丢失的保证

    storm的消息可靠机制开启后,可以保证一条数据都不丢
    数据不丢失,也不重复计算

  4. 超强的健壮性

    从历史经验来看,storm比hadoop、spark等大数据类系统,健壮的多的多,因为元数据全部放zookeeper,不在内存中,随便挂都不要紧
    特别的健壮,稳定性和可用性很高

  5. 使用的便捷性

    核心语义非常的简单,开发起来效率很高
    用起来很简单,开发API还是很简单的

mysql-hadoop与storm的关系


Storm集群架构与核心概念

Storm的集群架构

Nimbus,Supervisor,ZooKeeper,Worker,Executor,Task
storm集群架构

Storm的核心概念

Topology,Spout,Bolt,Tuple,Stream

  • 拓扑(Topology):务虚的一个概念
  • Spout:数据源的一个代码组件,就是我们可以实现一个spout接口,写一个java类,在这个spout代码中,我们可以自己尝试去数据源获取数据,比如说从kafka中消费数据
  • bolt:一个业务处理的代码组件,spout会将数据传送给bolt,各种bolt还可以串联成一个计算链条,java类实现了一个bolt接口
  • 一堆spout+bolt,就会组成一个topology,就是一个拓扑,实时计算作业,spout+bolt,一个拓扑涵盖数据源获取/生产+数据处理的所有的代码逻辑,topology
  • tuple:就是一条数据,每条数据都会被封装在tuple中,在多个spout和bolt之间传递
  • stream:就是一个流,务虚的一个概念,抽象的概念,源源不断过来的tuple,就组成了一条数据流
    storm核心概念

Storm的并行度和流分组

并行度和流分组

  • 并行度:Worker->Executor->Task,没错,是Task

  • 流分组:Task与Task之间的数据流向关系

  • Shuffle Grouping:随机发射,负载均衡
  • Fields Grouping:根据某一个,或者某些个fields,进行分组,那一个或者多个fields如果值完全相同的话,那么这些tuple,就会发送给下游bolt的其中固定的一个task
    你发射的每条数据是一个tuple,每个tuple中有多个field作为字段
    比如tuple,3个字段,name,age,salary
    {“name”: “tom”, “age”: 25, “salary”: 10000} -> tuple -> 3个field,name,age,salary
  • All Grouping:全分组,发射给下游的每个task
  • Global Grouping:全局id最小的
  • None Grouping 与 Shuffle Grouping相同
  • Direct Grouping:直接指定
  • Local or Shuffle Grouping:同一个executor中

storm并行度和流分组

57-分布式缓存重建并发冲突问题以及zookeeper分布式锁解决方案

Posted on 2018-12-28 | In redis

如果缓存服务在本地的ehcache中都读取不到数据,这个时候就意味着,需要重新到源头的服务中去拉取数据,拉取到数据之后,赶紧先给nginx的请求返回,同时将数据写入ehcache和redis中。

分布式重建缓存的并发冲突问题

重建缓存:

数据在所有的缓存中都不存在了(LRU算法清理了),就需要重新查询数据写入缓存,重建缓存。

分布式的重建缓存:

在不同的机器上,不同的服务实例中,去做上面的事情,就会出现多个机器分布式重建去读取相同的数据,然后写入缓存中。

分布式重建缓存的并发冲突问题

  1. 流量均匀分布到所有缓存服务实例上

    应用层nginx,是将请求流量均匀地打到各个缓存服务实例中的,可能咱们的eshop-cache那个服务,可能会部署多实例在不同的机器上

  2. 应用层nginx的hash,固定商品id,走固定的缓存服务实例

    分发层的nginx的lua脚本,有应用层nginx的地址列表,对每个商品id做一个hash,然后对应用nginx数量取模。

    将每个商品的请求固定分发到同一个应用层nginx上面去

    在应用层nginx里,发现自己本地lua shared dict缓存中没有数据的时候,就采取一样的方式,对product id取模,然后将请求固定分发到同一个缓存服务实例中去

    这样的话,就不会出现说多个缓存服务实例分布式的去更新那个缓存了

    留个作业,大家去做吧,这个东西,之前已经讲解过了,lua脚本几乎都是一模一样的,我们就不去做了,节省点时间

  3. 源信息服务发送的变更消息,需要按照商品id去分区,固定的商品变更走固定的kafka分区,也就是固定的一个缓存服务实例获取到

    缓存服务,是监听kafka topic的,一个缓存服务实例,作为一个kafka consumer,就消费topic中的一个partition

    所以你有多个缓存服务实例的话,每个缓存服务实例就消费一个kafka partition

    所以这里,一般来说,你的源头信息服务,在发送消息到kafka topic的时候,都需要按照product id去分区

    也就时说,同一个product id变更的消息一定是到同一个kafka partition中去的,也就是说同一个product id的变更消息,一定是同一个缓存服务实例消费到的

    我们也不去做了,其实很简单,kafka producer api,里面send message的时候,多加一个参数就可以了,product id传递进去,就可以了

  4. 问题是,自己写的简易的hash分发,与kafka的分区,可能并不一致!!!

    我们自己写的简易的hash分发策略,是按照crc32去取hash值,然后再取模的

    关键你又不知道你的kafka producer的hash策略是什么,很可能说跟我们的策略是不一样的

    那就可能导致说,数据变更的消息所到的缓存服务实例,跟我们的应用层nginx分发到的那个缓存服务实例也许就不在一台机器上了

    这样的话,在高并发,极端的情况下,可能就会出现冲突

  5. 分布式的缓存重建并发冲突问题发生了。。。

  6. 基于zookeeper分布式锁的解决方案

    分布式锁,如果你有多个机器在访问同一个共享资源,那么这个时候,如果你需要加个锁,让多个分布式的机器在访问共享资源的时候串行起来

    那么这个时候,那个锁,多个不同机器上的服务共享的锁,就是分布式锁

    分布式锁当然有很多种不同的实现方案,redis分布式锁,zookeeper分布式锁

    zk,做分布式协调这一块,还是很流行的,大数据应用里面,hadoop,storm,都是基于zk去做分布式协调

    zk分布式锁的解决并发冲突的方案

    1. 变更缓存重建以及空缓存请求重建,更新redis之前,都需要先获取对应商品id的分布式锁
    2. 拿到分布式锁之后,需要根据时间版本去比较一下,如果自己的版本新于redis中的版本,那么就更新,否则就不更新
    3. 如果拿不到分布式锁,那么就等待,不断轮询等待,直到自己获取到分布式的锁

多个缓存服务实例分布式重建的并发冲突问题

缓存更新和缓存重建在不同机器上的并发冲突问题

基于zookeeper分布式锁的冲突解决方案

54-基于nginx-lua-java完成多级缓存架构的核心业务逻辑

Posted on 2018-12-27 | In redis

分发层nginx,lua应用,会将商品id,商品店铺id,都转发到后端的应用nginx。

分发层ngnix配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
vi /home/bigdata/ngnix-test/ngnix-test.conf

location /product {
default_type 'text/html';
content_by_lua_file /home/bigdata/ngnix-test/lua/distribute.lua;
}

vi /home/bigdata/ngnix-test/lua/distribute.lua

local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]
local shopId = uri_args["shopId"]

local host = {"192.168.33.61", "192.168.33.62"}
local hash = ngx.crc32_long(productId)
hash = (hash % 2) + 1
backend = "http://"..host[hash]

local requestPath = uri_args["requestPath"]
requestPath = "/"..requestPath.."?productId="..productId.."&shopId="..shopId


local http = require("resty.http")
local httpc = http.new()

local resp, err = httpc:request_uri(backend, {
method = "GET",
path = requestPath
})

if not resp then
ngx.say("request error :", err)
return
end

ngx.say(resp.body)

httpc:close()

/export/servers/nginx/sbin/nginx -s reload

业务逻辑:

  1. 应用nginx的lua脚本接收到请求;
  2. 获取请求参数中的商品id,以及商品店铺id;
  3. 根据商品id和商品店铺id,在nginx本地缓存中尝试获取数据;
  4. 如果在nginx本地缓存中没有获取到数据,那么就到redis分布式缓存中获取数据,
    如果获取到了数据,还要设置到nginx本地缓存中;
  5. 如果缓存数据生产服务没有在redis分布式缓存中没有获取到数据,那么就在自己本地ehcache中获取数据,
    返回数据给nginx,也要设置到nginx本地缓存中;
  6. 如果ehcache本地缓存都没有数据,那么就需要去原始的服务中拉去数据,该服务会从mysql中查询,
    拉去到数据之后,返回给nginx,并重新设置到ehcache和redis中;
  7. nginx最终利用获取到的数据,动态渲染网页模板。
  8. 将渲染后的网页模板作为http响应,返回给分发层nginx

注意事项:

建议不要用nginx+lua直接去获取redis数据;
因为openresty没有太好的redis cluster的支持包,所以建议是发送http请求到缓存数据生产服务,由该服务提供一个http接口;
缓存数生产服务可以基于redis cluster api从redis中直接获取数据,并返回给nginx。
要使用发送http请求,需下载http相关lua脚本:

1
2
3
cd /home/bigdata/ngnix-test/lualib/resty
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua

下载相关lua脚本:

1
2
3
4
5
6
7
cd /home/bigdata/ngnix-test/lualib/resty
wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template.lua

mkdir -p /home/bigdata/ngnix-test/lualib/resty/html
cd /home/bigdata/ngnix-test/lualib/resty/html

wget https://raw.githubusercontent.com/bungle/lua-resty-template/master/lib/resty/template/html.lua

配置模板

在 /home/bigdata/ngnix-test/ngnix-test.conf 的 server 中配置模板位置

1
2
3
4
5
6
vi /home/bigdata/ngnix-test/ngnix-test.conf

...
set $template_location "/templates";
set $template_root "/home/bigdata/ngnix-test/templates";
...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mkdir -p /home/bigdata/ngnix-test/templates
vi /home/bigdata/ngnix-test/templates/product.html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>商品详情页</title>
</head>
<body>
商品id: {* productId *}<br/>
商品名称: {* productName *}<br/>
商品图片列表: {* productPictureList *}<br/>
商品规格: {* productSpecification *}<br/>
商品售后服务: {* productService *}<br/>
商品颜色: {* productColor *}<br/>
商品大小: {* productSize *}<br/>
店铺id: {* shopId *}<br/>
店铺名称: {* shopName *}<br/>
店铺评级: {* shopLevel *}<br/>
店铺好评率: {* shopGoodCommentRate *}<br/>
</body>
</html>

将渲染后的网页模板作为http响应,返回给分发层nginx

/export/servers/nginx/conf/nginx.conf 中添加设置:

1
2
3
vi /export/servers/nginx/conf/nginx.conf

lua_shared_dict my_cache 128m;

/home/bigdata/ngnix-test/ngnix-test.conf 中添加设置:

1
2
3
4
5
6
7
vi /home/bigdata/ngnix-test/ngnix-test.conf
...
location /product {
default_type 'text/html';
content_by_lua_file /home/bigdata/ngnix-test/lua/product.lua;
}
...

/home/bigdata/ngnix-test/lua/product.lua 脚本中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
vi /home/bigdata/ngnix-test/lua/product.lua

local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]
local shopId = uri_args["shopId"]

local cache_ngx = ngx.shared.my_cache

local productCacheKey = "product_info_"..productId
local shopCacheKey = "shop_info_"..shopId

local productCache = cache_ngx:get(productCacheKey)
local shopCache = cache_ngx:get(shopCacheKey)

if productCache == "" or productCache == nil then
local http = require("resty.http")
local httpc = http.new()

local resp, err = httpc:request_uri("http://192.168.230.10:8080",{
method = "GET",
path = "/getProductInfo?productId="..productId
})

productCache = resp.body
cache_ngx:set(productCacheKey, productCache, 10 * 60)
end

if shopCache == "" or shopCache == nil then
local http = require("resty.http")
local httpc = http.new()

local resp, err = httpc:request_uri("http://192.168.230.10:8080",{
method = "GET",
path = "/getShopInfo?shopId="..shopId
})

shopCache = resp.body
cache_ngx:set(shopCacheKey, shopCache, 10 * 60)
end

local cjson = require("cjson")
local productCacheJSON = cjson.decode(productCache)
local shopCacheJSON = cjson.decode(shopCache)

local context = {
productId = productCacheJSON.id,
productName = productCacheJSON.name,
productPrice = productCacheJSON.price,
productPictureList = productCacheJSON.pictureList,
productSpecification = productCacheJSON.specification,
productService = productCacheJSON.service,
productColor = productCacheJSON.color,
productSize = productCacheJSON.size,
shopId = shopCacheJSON.id,
shopName = shopCacheJSON.name,
shopLevel = shopCacheJSON.level,
shopGoodCommentRate = shopCacheJSON.goodCommentRate
}

local template = require("resty.template")
template.render("product.html", context)


在两台ngnix应用服务器,根据上面配置,重新部署。

1
2
3
4
5
6
scp /export/servers/nginx/conf/nginx.conf bigdata@bigdata02:/export/servers/nginx/conf/

scp -r /home/bigdata/ngnix-test/ bigdata@bigdata02:/home/bigdata/

/export/servers/nginx/sbin/nginx
/export/servers/nginx/sbin/nginx -s reload

第一次访问的时候,其实在nginx本地缓存中是取不到的,所以会发送http请求到后端的缓存服务里去获取,会从redis中获取

拿到数据以后,会放到nginx本地缓存里面去,过期时间是10分钟

然后将所有数据渲染到模板中,返回模板

以后再来访问的时候,就会直接从nginx本地缓存区获取数据了

缓存数据生产 -> 有数据变更 -> 主动更新两级缓存(ehcache+redis)-> 缓存维度化拆分

分发层nginx + 应用层nginx -> 自定义流量分发策略提高缓存命中率

nginx shared dict缓存 -> 缓存服务 -> redis -> ehcache -> 渲染html模板 -> 返回页面

还差最后一个很关键的要点,就是如果你的数据在nginx -> redis -> ehcache三级缓存都不在了,可能就是被LRU清理掉了

这个时候缓存服务会重新拉去数据,去更新到ehcache和redis中,这里我们还没讲解

分布式的缓存重建的并发问题

53-部署分发层nginx以及基于lua完成基于商品id的定向流量分发策略

Posted on 2018-12-27 | In redis

用bigdata01和bigdata02作为应用层nginx服务器,用bigdata03作为分发层nginx。

在bigdata03,也就是分发层nginx中,编写lua脚本,完成基于商品id的流量分发策略。

流量分发策略:

  1. 获取请求参数,比如productI;
  2. 对productId进行hash;
  3. hash值对应用服务器数量取模,获取到一个应用服务器;
  4. 利用http发送请求到应用层nginx;
  5. 获取响应后返回;

这个就是基于商品id的定向流量分发的策略,

lua脚本来编写和实现

我们作为一个流量分发的nginx,会发送http请求到后端的应用nginx上面去,所以要先引入lua http lib包

1
2
3
cd /home/bigdata/ngnix-test/lualib/resty/  
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http_headers.lua
wget https://raw.githubusercontent.com/pintsized/lua-resty-http/master/lib/resty/http.lua

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
vi /home/bigdata/ngnix-test/lua/ngnix-test.lua

local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]

local host = {"192.168.33.61", "192.168.33.62"}
local hash = ngx.crc32_long(productId)
hash = (hash % 2) + 1
backend = "http://"..host[hash]

local requestPath = uri_args["requestPath"]
requestPath = "/"..requestPath.."?productId="..productId


local http = require("resty.http")
local httpc = http.new()

local resp, err = httpc:request_uri(backend, {
method = "GET",
path = requestPath
})

if not resp then
ngx.say("request error :", err)
return
end

ngx.say(resp.body)

httpc:close()

1
/export/servers/nginx/sbin/nginx -s reload

访问 http://192.168.33.63/ngnix-test?requestPath=ngnix-test&productId=1

基于商品id的定向流量分发策略的lua脚本就开发完了,而且也测试过了

我们就可以看到,如果你请求的是固定的某一个商品,那么就一定会将流量打到固定的一个应用nginx上面去

52-基于OpenResty部署应用层nginx以及nginx-lua开发helloworld

Posted on 2018-12-26 | In redis

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

部署OpenResty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

#使用root用户安装
yum install -y readline-devel pcre-devel openssl-devel gcc

#使用bigdata用户安装
cd /home/bigdata/software
wget http://openresty.org/download/openresty-1.13.6.1.tar.gz
tar -zxvf /home/bigdata/software/openresty-1.13.6.1.tar.gz -C /export/servers/

cd /export/servers/openresty-1.13.6.1/bundle/LuaJIT-2.1-20171103/
make clean && make && make install
ln -sf luajit-2.1.0-alpha /usr/local/bin/luajit

cd /export/servers/openresty-1.13.6.1/bundle
wget https://github.com/FRiCKLE/ngx_cache_purge/archive/2.3.tar.gz
tar -xvf 2.3.tar.gz

cd /export/servers/openresty-1.13.6.1/bundle
wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/v0.3.0.tar.gz
tar -xvf v0.3.0.tar.gz

cd /export/servers/openresty-1.13.6.1
./configure --prefix=/export/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

make && make install

cd /export/servers/
ll
#显示有以下结果
/export/servers/luajit
/export/servers/lualib
/export/servers/nginx
/export/servers/nginx/sbin/nginx -V

#使用root启动nginx,否则会报错
#原因:the socket API bind() to a port less than 1024, such as 80 as your title mentioned, need root access.
/export/servers/nginx/sbin/nginx

nginx+lua开发的hello world

1
vi /export/servers/nginx/conf/nginx.conf

在http部分添加:

1
2
3
4
...
lua_package_path "/export/servers/lualib/?.lua;;";
lua_package_cpath "/export/servers/lualib/?.so;;";
...

/export/servers/nginx/conf下,创建一个lua.conf

1
2
3
4
5
6
7
cd /export/servers/nginx/conf
vi lua.conf

server {
listen 80;
server_name _;
}

在nginx.conf的http部分添加:

1
2
vi /export/servers/nginx/conf/nginx.conf
include lua.conf;

验证配置是否正确:

1
2
#使用root用户执行
/export/servers/nginx/sbin/nginx -t

在lua.conf的server部分添加:

1
2
3
4
5
6
7
8
vi /export/servers/nginx/conf/lua.conf
location /lua {
default_type 'text/html';
content_by_lua 'ngx.say("hello world")';
}

#使用root用户执行
/export/servers/nginx/sbin/nginx -t

重新nginx加载配置

1
/export/servers/nginx/sbin/nginx -s reload

访问 http://192.168.33.61/lua

1
2
3
4
cd /export/servers/nginx/conf/lua/
vi test.lua

ngx.say("hello world");

修改lua.conf

1
2
3
4
5
vi /export/servers/nginx/conf/lua.conf
location /lua {
default_type 'text/html';
content_by_lua_file conf/lua/test.lua;
}

查看异常日志

1
tail -f /export/servers/nginx/logs/error.log

工程化的nginx+lua项目结构

1
mkdir -p /home/bigdata/ngnix-test
1
2
3
4
5
6
7
#修改nginx.conf 把user 改成有权限的用户(当前用户)
#否则会报nginx_bug(1):failed to load external Lua file "xxxx.lua": cannot open xxxx.lua: Permission denied 错误
vi /export/servers/nginx/conf/nginx.conf

#user nobody;
# 改成 --> user [用户名].根据实际情况修改
user bigdata

项目工程结构

1
2
3
4
5
6
7
ngnix-test
ngnix-test.conf
lua
ngnix-test.lua
lualib
*.lua
*.so

放在/home/bigdata/ngnix-test目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
vi /export/servers/nginx/conf/nginx.conf

http {
include mime.types;
default_type application/octet-stream;

lua_package_path "/home/bigdata/ngnix-test/lualib/?.lua;;";
lua_package_cpath "/home/bigdata/ngnix-test/lualib/?.so;;";
include /home/bigdata/ngnix-test/ngnix-test.conf;
}

cd /home/bigdata/ngnix-test/
vi ngnix-test.conf

server {
listen 80;
server_name _;

location /ngnix-test {
default_type 'text/html';
content_by_lua_file /home/bigdata/ngnix-test/lua/ngnix-test.lua;
}
}

mkdir -p /home/bigdata/ngnix-test/lua
cd /home/bigdata/ngnix-test/lua
vi ngnix-test.lua

ngx.say("hello world");

mkdir -p /home/bigdata/ngnix-test/lualib
cp -r /export/servers/lualib/ /home/bigdata/ngnix-test/

#使用root用户执行
/export/servers/nginx/sbin/nginx -s reload

访问 http://192.168.33.61/ngnix-test

如法炮制,在其余机器上,也用OpenResty部署一个nginx

1
2
scp -r /home/bigdata/ngnix-test/ bigdata@192.168.33.63:/home/bigdata/
scp -r /export/servers/nginx/conf/nginx.conf bigdata@192.168.33.63:/export/servers/nginx/conf/nginx.conf

51-基于-分发层-应用层双层nginx架构提升缓存命中率方案分析

Posted on 2018-12-26 | In mysql

缓存命中率低

部署多个nginx,在里面都会放一些缓存,就默认情况下,此时缓存命中率是比较低的。

如何提升缓存命中率

分发层 + 应用层,双层nginx

  1. 分发层
    分发层nginx,负责流量分发的逻辑和策略,这个里面它可以根据你自己定义的一些规则,比如根据productId去进行hash,然后对后端的nginx数量取模。

将某一个商品的访问的请求,就固定路由到一个nginx后端服务器上去,保证说只会从redis中获取一次缓存数据,后面全都是走nginx本地缓存了。

  1. 应用层
    后端的nginx服务器,就称之为应用服务器; 最前端的nginx服务器,被称之为分发服务器。

看似很简单,其实很有用,在实际的生产环境中,可以大幅度提升你的nginx本地缓存这一层的命中率,大幅度减少redis后端的压力,提升性能。

缓存命中率低的原因

分发层-应用层双层nginx架构

50-基于kafka-ehcache-redis完成缓存数据生产服务的开发与测试

Posted on 2018-12-26 | In redis

kafka相关配置

1
2
3
4
vi /export/servers/kafka/config/server.properties

#远程消费的话,配置此参数
listeners=PLAINTEXT://192.168.33.61:9092

kafka测试命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#生成cache-message主题
kafka-topics.sh --create --zookeeper bigdata01:2181 --replication-factor 3 --partitions 3 --topic cache-message

#查看cache-message的详情
kafka-topics.sh --topic cache-message --describe --zookeeper bigdata01:2181

#生产数据
#注意事项,要远程消费数据,配置/export/servers/kafka/config/server.properties中listeners=PLAINTEXT://192.168.33.61:9092
kafka-console-producer.sh --broker-list bigdata01:9092,bigdata02:9092,bigdata03:9092 --topic cache-message
kafka-console-producer.sh --bootstrap-server 192.168.33.61:9092,192.168.33.62:9092,192.168.33.63:9092 --topic cache-message

#往console输入数据
{"serviceId": "productInfoService", "productId": 5}
{"serviceId": "shopInfoService", "shopId": 1}

#停止kafka
kafka-server-stop.sh

代码注意点

1
2
3
4
5
6
7
8
9
10
11
Properties props = new Properties();
// 确保 rebalance.max.retries * rebalance.backoff.ms > zookeeper.session.timeout.ms
props.put("zookeeper.connect", "192.168.33.61:2181,192.168.33.62:2181,192.168.33.63:2181");
props.put("group.id", "eshop-cache-group");
props.put("zookeeper.session.timeout.ms", "40000");
props.put("zookeeper.connection.timeout.ms", "40000");
props.put("zookeeper.sync.time.ms", "200");
props.put("rebalance.backoff.ms", "20000");
props.put("rebalance.max.retries", "10");
props.put("auto.commit.interval.ms", "1000");
return new ConsumerConfig(props);

49-zookeeper-kafka集群的安装部署以及如何简单使用的介绍

Posted on 2018-12-25 | In redis

zookeeper集群搭建

解压文件

1
2
3
tar -zxvf /home/bigdata/software/zookeeper-3.4.11.tar.gz -C /export/servers/
cd /export/servers/
ln -s zookeeper-3.4.11 zookeeper

修改配置

修改zookeeper数据目录和日志目录

1
2
3
4
5
6
7
8
9
cd /export/servers/zookeeper/conf
cp zoo_sample.cfg zoo.cfg
vi /export/servers/zookeeper/conf/zoo.cfg

#zookeeper数据目录
dataDir=/export/data/zookeeper/workdir/data

#zookeeper日志目录
dataLogDir=/export/data/zookeeper/workdir/log

设置zookeeper集群信息

1
2
3
4
5
6
7
8
vi /export/servers/zookeeper/conf/zoo.cfg

...
#设置集群信息,此处的bigdata0x可以用ip地址代替
server.1=bigdata01:2888:3888
server.2=bigdata02:2888:3888
server.3=bigdata03:2888:3888
...
1
2
3
4
5
6
7
#执行
rm -rf /export/data/zookeeper/

mkdir -p /export/data/zookeeper/workdir/data
mkdir -p /export/data/zookeeper/workdir/log

echo "1" > /export/data/zookeeper/workdir/data/myid

将zookeeper拷贝到其它机器上

1
2
3
4
5
6
7
8
9
10
11
12
13
#将zookeeper拷贝至bigdata02机器上
scp -r /export/servers/zookeeper-3.4.11 bigdata@bigdata02:/export/servers

#进入bigdata02机器执行以下命令
mkdir -p /export/data/zookeeper/workdir/data
echo "2" > /export/data/zookeeper/workdir/data/myid

#将zookeeper拷贝至bigdata03机器上
scp -r /export/servers/zookeeper-3.4.11 bigdata@bigdata03:/export/servers

#进入bigdata03机器执行以下命令
mkdir -p /export/data/zookeeper/workdir/data
echo "3" > /export/data/zookeeper/workdir/data/myid

配置环境变量

1
2
3
4
5
6
7
8
9
#切换成root用户执行,修改profile文件
vi /etc/profile
#Zookeeper
export ZOOKEEPER_HOME=/export/servers/zookeeper
export PATH=$PATH:${ZOOKEEPER_HOME}/bin


#使配置生效
source /etc/profile

zk命令

1
2
3
4
5
6
7
8
#启动时,切换成bigdata用户
su bigdata

#启动zookeeper
zkServer.sh start

#查看zookeeper状态
zkServer.sh status

kafka集群搭建

搭建kafka集群前,先保证zookeeper集群已搭建成功。

解压文件

1
2
3
tar -zxvf /home/bigdata/software/kafka_2.11-1.0.0.tgz -C /export/servers/
cd /export/servers/
ln -s kafka_2.11-1.0.0 kafka

修改配置

修改zookeeper数据目录和日志目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
cp /export/servers/kafka/config/server.properties /export/servers/kafka/config/server.properties.bak
vi /export/servers/kafka/config/server.properties

#根据实际情况修改
#当前机器在集群中的唯一标识,和zookeeper的myid性质一样
broker.id=0

#当前kafka对外提供服务的端口默认是9092
port=9092

#这个参数默认是关闭的,在0.8.1有个bug,DNS解析问题,失败率的问题。
host.name=192.168.33.61

#这个是borker进行网络处理的线程数
num.network.threads=3

#这个是borker进行I/O处理的线程数
num.io.threads=8

#消息存放的目录,这个目录可以配置为","逗号分割的表达式,
#上面的num.io.threads要大于这个目录的个数这个目录,
#如果配置多个目录,新创建的topic他把消息持久化的地方是,当前以逗号分割的目录中,那个分区数最少就放那一个
log.dirs=/export/data/kafka/logs

#发送缓冲区buffer大小,数据不是一下子就发送的,先回存储到缓冲区了到达一定的大小后在发送,能提高性能
socket.send.buffer.bytes=102400

#kafka接收缓冲区大小,当数据到达一定大小后在序列化到磁盘
socket.receive.buffer.bytes=102400

#这个参数是向kafka请求消息或者向kafka发送消息的请请求的最大数,这个值不能超过java的堆栈大小
socket.request.max.bytes=104857600

#默认的分区数,一个topic默认1个分区数
num.partitions=1

#默认消息的最大持久化时间,168小时,7天
log.retention.hours=168

#消息保存的最大值5M
message.max.byte=5242880


#kafka保存消息的副本数,如果一个副本失效了,另一个还可以继续提供服务
default.replication.factor=2

#取消息的最大直接数
replica.fetch.max.bytes=5242880

#这个参数是:因为kafka的消息是以追加的形式落地到文件,当超过这个值的时候,kafka会新起一个文件
log.segment.bytes=1073741824

#每隔300000毫秒去检查上面配置的log失效时间(log.retention.hours=168 ),
#到目录查看是否有过期的消息如果有,删除
log.retention.check.interval.ms=300000

#是否启用log压缩,一般不用启用,启用的话可以提高性能
log.cleaner.enable=false

#设置zookeeper的连接端口
zookeeper.connect=bigdata01:2181,bigdata02:2181,bigdata03:2181

#远程消费的话,配置此参数
listeners=PLAINTEXT://192.168.33.61:9092

分发安装包

1
2
3
4
5
6
7
#将kafka安装包分发到其它机器上
scp -r /export/servers/kafka_2.11-1.0.0 bigdata@bigdata02:/export/servers
scp -r /export/servers/kafka_2.11-1.0.0 bigdata@bigdata03:/export/servers

#然后分别在各机器上创建软连
cd /export/servers/
ln -s kafka_2.11-1.0.0 kafka

修改其它机器上的kafka配置

1
2
3
4
5
6
#修改其它机器的kafka配置信息
vi /export/servers/kafka/config/server.properties

...
将broker.id的值,修改成相应的数字
...

添加环境变量

1
2
3
4
5
6
7
8
9
#切换成root用户执行,修改profile文件
vi /etc/profile

#Kafka
export KAFKA_HOME=/export/servers/kafka
export PATH=$PATH:$KAFKA_HOME/bin

#使配置生效
source /etc/profile

kafka命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#依次在各节点上启动kafka
kafka-server-start.sh /export/servers/kafka/config/server.properties > /dev/null 2>&1 &

#停止kafka
kafka-server-stop.sh

#查看当前服务器中的所有topic
kafka-topics.sh --list --zookeeper bigdata01:2181

#创建topic
kafka-topics.sh --create --zookeeper bigdata01:2181 --replication-factor 3 --partitions 3 --topic test

#删除topic
kafka-topics.sh --delete --zookeeper bigdata01:2181 --topic test
需要server.properties中设置delete.topic.enable=true否则只是标记删除或者直接重启。

#通过shell命令发送消息
kafka-console-producer.sh --broker-list bigdata01:9092 --topic test


#通过shell消费消息
kafka-console-consumer.sh --zookeeper bigdata01:2181 --from-beginning --topic test

#查看消费位置
kafka-run-class.sh kafka.tools.ConsumerOffsetChecker --zookeeper bigdata01:2181 --group testGroup

#查看某个Topic的详情
kafka-topics.sh --topic test --describe --zookeeper bigdata01:2181

48-redis的LRU缓存清除算法讲解以及相关配置使用

Posted on 2018-12-25 | In redis

如果不断地将数据写入redis,然而redis的内存是有限的,每个redis实例最大一般也就是设置给10G。

那如果不断地写入数据,当数据写入的量超过了redis能承受的范围之后,该怎么玩儿呢???

redis是会在数据量超过一个最大的限度之后,就会将数据进行清理,从内存中清理掉一些数据。

只有清理掉一些数据之后,才能将新的数据写入内存中。

LRU算法概述

redis默认情况下使用LRU策略的,因为内存是有限的,但是如果不断地往redis里面写入数据,那肯定是没法将所有的数据存放在内存的。

所以redis默认情况下,当内存中写入的数据很满之后,就会使用LRU算法清理掉内存部分中的数据,腾出一些空间来,然后让新的数据写入redis缓存中。

LRU:Least Recently Used:最近最少使用算法

将最近一段时间内,最少使用的一些数据,给干掉。比如说有一个key,在最近1个小时内,只被访问了一次; 还有一个key在最近1个小时内,被访问了1万次。

这个时候比如你要将部分数据给清理掉,你会选择清理哪些数据啊?肯定是那个在最近小时内被访问了1万次的数据。

缓存清理设置

redis.conf配置文件设置。

maxmemory:设置redis用来存放数据的最大的内存大小,一旦超出这个内存大小之后,就会立即使用LRU算法清理掉部分数据。

如果用LRU,那么就是将最近最少使用的数据从缓存中清除出去。

对于64 bit的机器,如果maxmemory设置为0,那么就默认不限制内存的使用,直到耗尽机器中所有的内存为止; 但是对于32 bit的机器,有一个隐式的闲置就是3GB。

maxmemory-policy,可以设置内存达到最大闲置后,采取什么策略来处理。

  1. noeviction: 如果内存使用达到了maxmemory,client还要继续写入数据,那么就直接报错给客户端;
  2. allkeys-lru: 就是我们常说的LRU算法,移除掉最近最少使用的那些keys对应的数据;
  3. volatile-lru: 也是采取LRU算法,但是仅仅针对那些设置了指定存活时间(TTL)的key才会清理掉;
  4. allkeys-random: 随机选择一些key来删除掉;
  5. volatile-random: 随机选择一些设置了TTL的key来删除掉;
  6. volatile-ttl: 移除掉部分keys,选择那些TTL时间比较短的keys;

在redis里面,写入key-value对的时候,是可以设置TTL,存活时间,比如你设置了60s,那么一个key-value对,在60s之后就会自动被删除。

redis的使用,各种数据结构,list,set,等等

redis,给了这么多种乱七八糟的缓存清理的算法,其实真正常用的可能也就那么一两种,allkeys-lru是最常用的。

缓存清理的流程

  1. 客户端执行数据写入操作;
  2. redis server接收到写入操作之后,检查maxmemory的限制,如果超过了限制,那么就根据对应的policy清理掉部分数据;
  3. 写入操作完成执行;

redis的LRU近似算法

科普一个相对来说稍微高级一丢丢的知识点。

redis采取的是LRU近似算法,也就是对keys进行采样,然后在采样结果中进行数据清理

redis 3.0开始,在LRU近似算法中引入了pool机制,表现可以跟真正的LRU算法相当,但是还是有所差距的,不过这样可以减少内存的消耗

redis LRU算法,是采样之后再做LRU清理的,跟真正的、传统、全量的LRU算法是不太一样的

maxmemory-samples,比如5,可以设置采样的大小,如果设置为10,那么效果会更好,不过也会耗费更多的CPU资源

38-CentOS7安装部署MySQL数据库

Posted on 2018-12-03 | In mysql

Centos7 Yum方式安装Mysql7

安装命令:

1
2
3
4
5
6
7
wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
rpm -ivh mysql-community-release-el7-5.noarch.rpm

yum install -y mysql-server
service mysqld start
chkconfig mysqld on
yum install -y mysql-connector-java

37-高并发场景下的缓存+数据库双写不一致问题分析与解决方案设计

Posted on 2018-12-03 | In redis

数据库与缓存双写不一致问题及其解决方案

最初级的缓存不一致问题以及解决方案

问题:

先修改数据库,再删除缓存。如果删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据出现不一致。

最初级的数据库+缓存双写不一致问题

解决思路:

先删除缓存,再修改数据库。删除缓存成功了,如果修改数据库失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有了,所以读数据库中的旧数据,然后更新到缓存中。

最初级的数据库+缓存双写不一致问题的解决方案

比较复杂的数据不一致问题分析

问题分析:

数据发生了变化,先删除缓存,然后修改数据库中的数据。此时还在修改数据,一个请求过来,先去读缓存,发现缓存空了,然后去查询数据库,查到了修改前的旧数据,放到了缓存中。数据变更的程序完成了数据库的修改,完了,数据库和缓存中的数据不一样了。。。。

为什么上亿流量高并发场景下,缓存会出现这个问题?

只有在对一个数据进行并发读写,才可能会出现这种问题。其实你的系统并发量很低,特别是读并发很低,每天访问量就1万次,那么很少会出现上述描述的那种不一致的场景。但问题是,如果每天是上亿的流量,每秒并发读是几万,每秒只要有数据更新的请求,就可能会出现上述的数据库+缓存不一致的情况。系统并发量很大,出现问题是很多的。
读写并发的时候复杂的数据库+缓存双写不一致的场景

数据库与缓存更新与读取操作进行异步串行化

更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个jvm内部的队列中。

读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个jvm内部的队列中。

一个队列对应一个工作线程。

每个工作线程串行拿到对应的操作,然后一条一条的执行。

这样的话,一个数据变更的操作,先执行,删除缓存,然后再去更新数据库,但是还没完成更新;此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成。

这里有一个优化点,一个队列中,多个更新缓存请求串在一起是没意义的,因此可以做过滤,如果发现队列中已经有一个更新缓存的请求了,那么就不用再往队列中放更新请求操作,直接等待前面的更新操作请求完成即可。待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中。

如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回; 如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。

复杂的数据库+缓存双写一致保障方案

高并发场景下,该解决方案要注意的问题

  1. 读请求长时阻塞

    由于读请求进行了非常轻度的异步化,所以一定要注意读超时的问题,每个读请求必须在超时时间范围内返回。

    该解决方案,最大的风险点在于说,可能数据更新很频繁,导致队列中积压了大量更新操作在里面,然后读请求会发生大量的超时,最后导致大量的请求直接走数据库。

    务必通过一些模拟真实的测试,看看更新数据的频繁是怎样的。

    另外一点,因为一个队列中,可能会积压针对多个数据项的更新操作,因此需要根据自己的业务情况进行测试,可能需要部署多个服务,每个服务分摊一些数据的更新操作

    如果一个内存队列里居然会挤压100个商品的库存修改操作,每隔库存修改操作要耗费10ms区完成,那么最后一个商品的读请求,可能等待10 * 100 = 1000ms = 1s后,才能得到数据

    这个时候就导致读请求的长时阻塞

    一定要做根据实际业务系统的运行情况,去进行一些压力测试,和模拟线上环境,去看看最繁忙的时候,内存队列可能会挤压多少更新操作,可能会导致最后一个更新操作对应的读请求,会hang多少时间,如果读请求在200ms返回,如果你计算过后,哪怕是最繁忙的时候,积压10个更新操作,最多等待200ms,那还可以的

    如果一个内存队列可能积压的更新操作特别多,那么你就要加机器,让每个机器上部署的服务实例处理更少的数据,那么每个内存队列中积压的更新操作就会越少

    其实根据之前的项目经验,一般来说数据的写频率是很低的,因此实际上正常来说,在队列中积压的更新操作应该是很少的

    针对读高并发,读缓存架构的项目,一般写请求相对读来说,是非常非常少的,每秒的QPS能到几百就不错了

    一秒,500的写操作,5份,每200ms,就100个写操作

    单机器,20个内存队列,每个内存队列,可能就积压5个写操作,每个写操作性能测试后,一般在20ms左右就完成

    那么针对每个内存队列中的数据的读请求,也就最多hang一会儿,200ms以内肯定能返回了

    写QPS扩大10倍,但是经过刚才的测算,就知道,单机支撑写QPS几百没问题,那么就扩容机器,扩容10倍的机器,10台机器,每个机器20个队列,200个队列

    大部分的情况下,应该是这样的,大量的读请求过来,都是直接走缓存取到数据的

    少量情况下,可能遇到读跟数据更新冲突的情况,如上所述,那么此时更新操作如果先入队列,之后可能会瞬间来了对这个数据大量的读请求,但是因为做了去重的优化,所以也就一个更新缓存的操作跟在它后面

    等数据更新完了,读请求触发的缓存更新操作也完成,然后临时等待的读请求全部可以读到缓存中的数据

  2. 读请求并发量过高

    这里还必须做好压力测试,确保恰巧碰上上述情况的时候,还有一个风险,就是突然间大量读请求会在几十毫秒的延时hang在服务上,看服务能不能抗的住,需要多少机器才能抗住最大的极限情况的峰值

    但是因为并不是所有的数据都在同一时间更新,缓存也不会同一时间失效,所以每次可能也就是少数数据的缓存失效了,然后那些数据对应的读请求过来,并发量应该也不会特别大

    按1:99的比例计算读和写的请求,每秒5万的读QPS,可能只有500次更新操作

    如果一秒有500的写QPS,那么要测算好,可能写操作影响的数据有500条,这500条数据在缓存中失效后,可能导致多少读请求,发送读请求到库存服务来,要求更新缓存

    一般来说,1:1,1:2,1:3,每秒钟有1000个读请求,会hang在库存服务上,每个读请求最多hang多少时间,200ms就会返回

    在同一时间最多hang住的可能也就是单机200个读请求,同时hang住

    单机hang200个读请求,还是ok的

    1:20,每秒更新500条数据,这500秒数据对应的读请求,会有20 * 500 = 1万

    1万个读请求全部hang在库存服务上,就死定了

  3. 多服务实例部署的请求路由

    可能这个服务部署了多个实例,那么必须保证说,执行数据更新操作,以及执行缓存更新操作的请求,都通过nginx服务器路由到相同的服务实例上

机器级别的请求路由问题

  1. 热点商品的路由问题,导致请求的倾斜
    万一某个商品的读写请求特别高,全部打到相同的机器的相同的队列里面去了,可能造成某台机器的压力过大
    就是说,因为只有在商品数据更新的时候才会清空缓存,然后才会导致读写并发,所以更新频率不是太高的话,这个问题的影响并不是特别大
    但是的确可能某些机器的负载会高一些

36-Cache-Aside-Pattern缓存+数据库读写模式的分析

Posted on 2018-12-03 | In redis

cache-aside-pattern

最经典的缓存+数据库读写的模式,cache aside pattern

  1. Cache Aside Pattern

    1. 读的时候,先读缓存,缓存没有的话,那么就读数据库;然后取出数据后放入缓存,同时返回响应。

    2. 更新的时候,先删除缓存,然后再更新数据库。

  2. 为什么是删除缓存,而不是更新缓存呢?

原因很简单,很多时候,复杂点的缓存的场景,因为缓存有的时候,不简单是数据库中直接取出来的值

商品详情页的系统,修改库存,只是修改了某个表的某些字段,但是要真正把这个影响的最终的库存计算出来,可能还需要从其他表查询一些数据,然后进行一些复杂的运算,才能最终计算出

现在最新的库存是多少,然后才能将库存更新到缓存中去

比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据,并进行运算,才能计算出缓存最新的值的

更新缓存的代价是很高的

是不是说,每次修改数据库的时候,都一定要将其对应的缓存去更新一份?也许有的场景是这样的,但是对于比较复杂的缓存数据计算的场景,就不是这样了

如果你频繁修改的一个缓存涉及多个表,那么这个缓存会被频繁的更新,频繁的更新缓存

但是问题在于,这个缓存到底会不会被频繁访问到???

举个例子,一个缓存涉及的表的字段,在1分钟内就修改了20次,或者是100次,那么缓存更新20次,100次; 但是这个缓存在1分钟内就被读取了1次,有大量的冷数据

28法则,黄金法则,20%的数据,占用了80%的访问量

实际上,如果你只是删除缓存的话,那么1分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低

每次数据过来,就只是删除缓存,然后修改数据库,如果这个缓存,在1分钟内只是被访问了1次,那么只有那1次,缓存是要被重新计算的,用缓存才去算缓存

其实删除缓存,而不是更新缓存,就是一个lazy计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算

mybatis,hibernate,懒加载,思想

查询一个部门,部门带了一个员工的list,没有必要说每次查询部门,都里面的1000个员工的数据也同时查出来啊

80%的情况,查这个部门,就只是要访问这个部门的信息就可以了

先查部门,同时要访问里面的员工,那么这个时候只有在你要访问里面的员工的时候,才会去数据库里面查询1000个员工

35-亿级流量商品详情页的多级缓存架构以及架构中每一层的意义

Posted on 2018-11-30 | In redis

三级缓存架构图

1. 上亿流量的商品详情页系统的多级缓存架构

很多人以为,做个缓存,其实就是用一下redis,访问一下就可以了,简单的缓存。
做复杂的缓存,要支撑电商复杂的场景下的高并发的缓存,遇到的问题,非常非常之多,绝对不是说简单的访问一下redsi就可以了。

采用三级缓存:nginx本地缓存+redis分布式缓存+tomcat堆缓存的多级缓存架构

  • 时效性要求非常高的数据:库存。

一般来说,库存的显示,都是时效性要求会相对高一些,因为随着商品的不断的交易,库存会不断的变化。

当然,我们就希望当库存变化的时候,尽可能更快将库存显示到页面上去,而不是说等了很长时间,库存才反应到页面上去。

  • 时效性要求不高的数据:商品的基本信息(名称、颜色、版本、规格参数,等等)。

时效性要求不高的数据,就还好,比如说你现在改变了商品的名称,稍微晚个几分钟反应到商品页面上,也还能接受。

商品价格/库存等时效性要求高的数据,而且种类较少,采取相关的服务系统每次发生了变更的时候,直接采取数据库和redis缓存双写的方案,这样缓存的时效性最高。

商品基本信息等时效性不高的数据,而且种类繁多,来自多种不同的系统,采取MQ异步通知的方式,写一个数据生产服务,监听MQ消息,然后异步拉取服务的数据,更新tomcat jvm缓存+redis缓存。

nginx+lua脚本做页面动态生成的工作,每次请求过来,优先从nginx本地缓存中提取各种数据,结合页面模板,生成需要的页面。

如果nginx本地缓存过期了,那么就让nginx到redis中去拉取数据,更新到nginx本地。

如果redis中的数据被LRU算法清理掉了,那么就让nginx走http接口到后端的服务中拉取数据。数据生产服务中,先在本地tomcat里的jvm堆缓存中找,ehcache,如果数据也被LRU清理掉了,那么就重新发送请求到源头的服务中去拉取数据,然后再次更新tomcat堆内存缓存+redis缓存,并返回数据给nginx,nginx缓存到本地。

2. 多级缓存架构中每一层的意义

nginx本地缓存,抗的是热数据的高并发访问,一般来说,商品的购买总是有热点的,比如每天购买iphone、nike、海尔等知名品牌的东西的人,总是比较多的。

这些热数据,利用nginx本地缓存,由于经常被访问,所以可以被锁定在nginx的本地缓存内。

大量的热数据的访问,就是经常会访问的那些数据,就会被保留在nginx本地缓存内,那么对这些热数据的大量访问,就直接走nginx就可以了。

那么大量的访问,直接就可以走到nginx就行了,不需要走后续的各种网络开销了。

redis分布式大规模缓存,抗的是很高的离散访问,支撑海量的数据,高并发的访问,高可用的服务。

redis缓存最大量的数据,最完整的数据和缓存,1T+数据; 支撑高并发的访问,QPS最高到几十万; 可用性,非常好,提供非常稳定的服务。

nginx本地内存有限,也就能cache住部分热数据,除了各种iphone、nike等热数据,其他相对不那么热的数据,可能流量会经常走到redis那里。

利用redis cluster的多master写入,横向扩容,1T+以上海量数据支持,几十万的读写QPS,99.99%高可用性,那么就可以抗住大量的离散访问请求。

tomcat jvm堆内存缓存,主要是抗redis大规模灾难的,如果redis出现了大规模的宕机,导致nginx大量流量直接涌入数据生产服务,那么最后的tomcat堆内存缓存至少可以再抗一下,不至于让数据库直接裸奔。

同时tomcat jvm堆内存缓存,也可以抗住redis没有cache住的最后那少量的部分缓存。

28-在项目中重新搭建一套读写分离+高可用+多master的redis-cluster集群

Posted on 2018-11-28 | In redis

CentOS安装redis集群提示redis requires ruby version 2.2.2的解决方案

  1. 安装RVM

    1
    2
    3
    4
    5
    #具体RVM安装命令地址,参考:http://rvm.io/
    gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB

    curl -sSL https://get.rvm.io | bash -s stable
    source /usr/local/rvm/scripts/rvm
  2. 查看rvm库中已知的ruby版本

    1
    rvm list known
  3. 安装一个ruby版本

    1
    rvm install 2.5.1
  4. 使用一个ruby版本

    1
    rvm use 2.5.1
  5. 设置默认版本

    1
    rvm use 2.5.1 --default
  6. 卸载一个已知版本

    1
    2
    3
    rvm remove 1.8.1
    rvm remove 2.3.4
    rvm remove 2.4.1
  7. 查看ruby版本

    1
    ruby --version
  8. 安装redis

    1
    gem install redis

安装redis集群

集群配置3台机器,一台机器配置一主一从。
redis的配置文件:/export/data/redis/conf/700*.conf
redis的持久化文件夹:/export/data/redis/700*

3台机器:

192.168.33.61
192.168.33.62
192.168.33.63

  1. 创建目录
    在3台机器上执行如下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    mkdir -p /export/data/redis-cluster
    mkdir -p /export/data/redis-cluster-log
    mkdir -p /export/data/redis/7001
    mkdir -p /export/data/redis/7002
    mkdir -p /export/data/redis/7003
    mkdir -p /export/data/redis/7004
    mkdir -p /export/data/redis/7005
    mkdir -p /export/data/redis/7006

    /export/data/redis-cluster为集群目录
    /export/data/redis-cluster-log为集群日志目录

  2. 准备配置文件
    在1台机器上执行如下命令,准备好配置文件

    1
    2
    3
    4
    5
    6
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7001.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7002.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7003.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7004.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7005.conf
    cp -r /export/servers/redis/redis.conf /export/data/redis/conf/7006.conf
  3. 修改配置


机器端口分配情况:

机器ip 分配的端口号
192.168.33.61 7001 ~ 7002
192.168.33.62 7003 ~ 7004
192.168.33.63 7005 ~ 7006

根据如下表格,并根据机器ip、端口分配情况,修改/export/data/redis/conf/700*.conf相应配置

配置项 配置值 说明
port 700* 设置redis的监听端口号
cluster-enabled yes 开启集群
cluster-config-file /export/data/redis-cluster/nodes-700*.conf 集群配置文件
cluster-node-timeout 15000 超时时长
daemonize yes 让redis以daemon进程运行
pidfile /export/data/redis/redis_700*.pid 设置redis的pid文件位置
dir /export/data/redis/700* 设置持久化文件的存储位置
logfile /export/data/redis-cluster-log/700*.log 设置集群日志持久化文件的存储位置
bind 192.168.33.6* 设置绑定地址,最好不要绑定127.0.0.1,否则集群启动不起来
appendonly yes 开启AOF功能

配置完后,将配置文件分发到另外两台机器

1
2
scp -r /export/servers/redis/700* bigdata@192.168.33.62:/export/data/redis/conf/
scp -r /export/servers/redis/700* bigdata@192.168.33.63:/export/data/redis/conf/

  1. 准备生产环境的启动脚本
    在/etc/init.d目录下,准备6个启动脚本,分别为: redis_7001, redis_7002, redis_7003, redis_7004, redis_7005, redis_7006,在每个启动脚本内,都修改对应的端口号。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    #!/bin/sh
    # chkconfig: 2345 90 10
    # description: Redis is a persistent key-value database
    #
    # Simple Redis init.d script conceived to work on Linux systems
    # as it does use of the /proc filesystem.

    #redis服务器监听的端口,根据实际情况修改
    REDISPORT=700*
    #EXEC=/usr/local/bin/redis-server
    #CLIEXEC=/usr/local/bin/redis-cli

    #PIDFILE=/var/run/redis_${REDISPORT}.pid
    #CONF="/etc/redis/${REDISPORT}.conf"

    #服务端所处位置,在make install后默认存放于`/usr/local/bin/redis-server`,如果未make install则需要修改该路径,下同。
    EXEC=/usr/local/bin/redis-server
    #客户端位置
    CLIEXEC=/usr/local/bin/redis-cli

    #Redis的PID文件位置
    PIDFILE=/export/data/redis/redis_${REDISPORT}.pid
    #配置文件位置,需要修改
    CONF="/export/data/redis/conf/${REDISPORT}.conf"

    case "$1" in
    start)
    if [ -f $PIDFILE ]
    then
    echo "$PIDFILE exists, process is already running or crashed"
    else
    echo "Starting Redis server..."
    $EXEC $CONF
    fi
    ;;
    stop)
    if [ ! -f $PIDFILE ]
    then
    echo "$PIDFILE does not exist, process is not running"
    else
    PID=$(cat $PIDFILE)
    echo "Stopping ..."
    $CLIEXEC -p $REDISPORT shutdown
    while [ -x /proc/${PID} ]
    do
    echo "Waiting for Redis to shutdown ..."
    sleep 1
    done
    echo "Redis stopped"
    fi
    ;;
    *)
    echo "Please use start or stop as first argument"
    ;;
    esac
  2. 在3台机器上,启动6个redis实例

    如果有启动redis实例,先停止实例

    1
    2
    3
    4
    5
    6
    /usr/local/bin/redis-cli -p 7001 shutdown
    /usr/local/bin/redis-cli -p 7002 shutdown
    /usr/local/bin/redis-cli -p 7003 shutdown
    /usr/local/bin/redis-cli -p 7004 shutdown
    /usr/local/bin/redis-cli -p 7005 shutdown
    /usr/local/bin/redis-cli -p 7006 shutdown

    清理集群状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    rm -rf /export/data/redis-cluster/*
    rm -rf /export/data/redis-cluster-log/*

    rm -rf /export/data/redis/7001/*
    rm -rf /export/data/redis/7002/*
    rm -rf /export/data/redis/7003/*
    rm -rf /export/data/redis/7004/*
    rm -rf /export/data/redis/7005/*
    rm -rf /export/data/redis/7006/*

    rm -rf /export/data/redis/redis_700*.pid

    #在192.168.33.61机器上执行redis-trib.rb create,所以要在清理192.168.33.61上的配置
    redis-cli -h 192.168.33.61 -p 7001 flushdb
    redis-cli -h 192.168.33.61 -p 7001 flushall
    redis-cli -h 192.168.33.61 -p 7001 cluster reset
    redis-cli -h 192.168.33.61 -p 7002 flushdb
    redis-cli -h 192.168.33.61 -p 7002 flushall
    redis-cli -h 192.168.33.61 -p 7002 cluster reset
    redis-cli -h 192.168.33.61 -p 7003 flushdb
    redis-cli -h 192.168.33.61 -p 7003 flushall
    redis-cli -h 192.168.33.61 -p 7003 cluster reset
    redis-cli -h 192.168.33.61 -p 7004 flushdb
    redis-cli -h 192.168.33.61 -p 7004 flushall
    redis-cli -h 192.168.33.61 -p 7004 cluster reset
    redis-cli -h 192.168.33.61 -p 7005 flushdb
    redis-cli -h 192.168.33.61 -p 7005 flushall
    redis-cli -h 192.168.33.61 -p 7005 cluster reset
    redis-cli -h 192.168.33.61 -p 7006 flushdb
    redis-cli -h 192.168.33.61 -p 7006 flushall
    redis-cli -h 192.168.33.61 -p 7006 cluster reset

    使用flushall和cluster reset命令,避免出现ERR Slot 0 is already busy (Redis::CommandError)错误

    192.168.33.61机器执行以下命令:

    1
    2
    /usr/local/bin/redis-server /export/data/redis/conf/7001.conf
    /usr/local/bin/redis-server /export/data/redis/conf/7002.conf

    192.168.33.62机器执行以下命令:

    1
    2
    /usr/local/bin/redis-server /export/data/redis/conf/7003.conf
    /usr/local/bin/redis-server /export/data/redis/conf/7004.conf

    192.168.33.63机器执行以下命令:

    1
    2
    /usr/local/bin/redis-server /export/data/redis/conf/7005.conf
    /usr/local/bin/redis-server /export/data/redis/conf/7006.conf

    查看redis实例启动日志:

    1
    cat /export/data/redis-cluster-log/700*
  3. 创建集群

    执行以下命令:

    1
    2
    cp /export/servers/redis/src/redis-trib.rb /usr/local/bin
    redis-trib.rb create --replicas 1 192.168.33.61:7001 192.168.33.61:7002 192.168.33.62:7003 192.168.33.62:7004 192.168.33.63:7005 192.168.33.63:7006

    –replicas 1: 每个master有1个slave

    如果出现以下错误:
    [ERR] Not all 16384 slots are covered by nodes,
    执行以下命令:

    1
    redis-trib.rb fix 192.168.33.61:7001

    查看集群状态:

    1
    redis-cli -h 192.168.33.61 -c -p 7001 cluster nodes
  4. 设置开机启动

    使用root用户执行
    192.168.33.61机器执行以下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #执行以下命令,设置开机服务自动启动
    cd /etc/init.d/
    chkconfig redis_7001 on
    chkconfig redis_7002 on

    #如果要关闭自动启动,执行以下命令
    cd /etc/init.d/
    chkconfig redis_7001 off
    chkconfig redis_7002 off

    192.168.33.62机器执行以下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #执行以下命令,设置开机服务自动启动
    cd /etc/init.d/
    chkconfig redis_7003 on
    chkconfig redis_7004 on

    #如果要关闭自动启动,执行以下命令
    cd /etc/init.d/
    chkconfig redis_7003 off
    chkconfig redis_7004 off

    192.168.33.63机器执行以下命令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #执行以下命令,设置开机服务自动启动
    cd /etc/init.d/
    chkconfig redis_7005 on
    chkconfig redis_7006 on

    #如果要关闭自动启动,执行以下命令
    cd /etc/init.d/
    chkconfig redis_7005 off
    chkconfig redis_7006 off

test-env-redis集群搭建

Posted on 2018-10-27
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

/usr/local/bin/redis-cli -h 192.168.33.61 -p 7001 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7002 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7003 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7004 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7005 shutdown
/usr/local/bin/redis-cli -h 192.168.33.61 -p 7006 shutdown


mkdir -p /export/data/test-env-redis/redis-cluster
mkdir -p /export/data/test-env-redis/redis-cluster-log
mkdir -p /export/data/test-env-redis/redis/7001
mkdir -p /export/data/test-env-redis/redis/7002
mkdir -p /export/data/test-env-redis/redis/7003
mkdir -p /export/data/test-env-redis/redis/7004
mkdir -p /export/data/test-env-redis/redis/7005
mkdir -p /export/data/test-env-redis/redis/7006



rm -rf /export/data/test-env-redis/redis-cluster/*
rm -rf /export/data/test-env-redis/redis-cluster-log/*

rm -rf /export/data/test-env-redis/redis/7001/*
rm -rf /export/data/test-env-redis/redis/7002/*
rm -rf /export/data/test-env-redis/redis/7003/*
rm -rf /export/data/test-env-redis/redis/7004/*
rm -rf /export/data/test-env-redis/redis/7005/*
rm -rf /export/data/test-env-redis/redis/7006/*
rm -rf /export/data/test-env-redis/redis/redis_700*.pid


redis-cli -h 192.168.33.61 -p 7001 flushdb
redis-cli -h 192.168.33.61 -p 7001 flushall
redis-cli -h 192.168.33.61 -p 7001 cluster reset
redis-cli -h 192.168.33.61 -p 7002 flushdb
redis-cli -h 192.168.33.61 -p 7002 flushall
redis-cli -h 192.168.33.61 -p 7002 cluster reset
redis-cli -h 192.168.33.61 -p 7003 flushdb
redis-cli -h 192.168.33.61 -p 7003 flushall
redis-cli -h 192.168.33.61 -p 7003 cluster reset
redis-cli -h 192.168.33.61 -p 7004 flushdb
redis-cli -h 192.168.33.61 -p 7004 flushall
redis-cli -h 192.168.33.61 -p 7004 cluster reset
redis-cli -h 192.168.33.61 -p 7005 flushdb
redis-cli -h 192.168.33.61 -p 7005 flushall
redis-cli -h 192.168.33.61 -p 7005 cluster reset
redis-cli -h 192.168.33.61 -p 7006 flushdb
redis-cli -h 192.168.33.61 -p 7006 flushall
redis-cli -h 192.168.33.61 -p 7006 cluster reset






/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7001.conf
/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7002.conf

/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7003.conf
/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7004.conf

/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7005.conf
/usr/local/bin/redis-server /export/data/test-env-redis/redis/conf/7006.conf


cat /export/data/test-env-redis/redis-cluster-log/700*




#cp /export/servers/redis/src/redis-trib.rb /usr/local/bin
redis-trib.rb create --replicas 1 192.168.33.61:7001 192.168.33.61:7002 192.168.33.61:7003 192.168.33.61:7004 192.168.33.61:7005 192.168.33.61:7006

Springboot-mybatis-出现 com.mysql.cj.core.exceptions.InvalidConnectionAttributeException...

Posted on 2018-10-24
1
其实这个不是spring boot + mybatis的问题, 其实是为了使MySQL JDBC驱动程序的5.1.33版本与UTC时区配合使用,必须在连接字符串中明确指定serverTimezone。
1
jdbc:mysql://localhost:3306/lovewhf?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
1
在这里边明确指定serverTimezone

SpringBoot-Mybatis关于开启驼峰映射的设置

Posted on 2018-10-18
  1. 在application.properties文件中添加:

    1
    mybatis.configuration.map-underscore-to-camel-case=true
  2. 在mybatis的配置文件,如mybatis-config.xml中进行配置

    1
    2
    3
    4
    5
    6
    7
    <configuration>
    <!-- 开启驼峰映射 ,为自定义的SQL语句服务-->
    <!--设置启用数据库字段下划线映射到java对象的驼峰式命名属性,默认为false-->
    <settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    </configuration>

hexo命令

Posted on 2018-01-01
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#安装环境
hexo init laungcisin.github.io
cd laungcisin.github.io
npm install

npm audit fix

npm install hexo-cli -g
npm install hexo --save

su
npm install hexo-server
exit


git clone https://github.com/iissnan/hexo-theme-next themes/next

将Hexo博客 部署在Github Pages上

  1. 创建Github账号
  2. 创建Github仓库,Github仓库名为:<Github账号名称>.github.io
  3. 将本地Hexo博客推送到GithubPages

    1. 安装hexo-deployer-git插件
      npm install hexo-deployer-git --save
    2. 添加SSH key

      • 创建一个 SSH key 。在命令行(即Git Bash)输入以下命令, 回车三下即可
        cd ~/.ssh
        ssh-keygen -t rsa -C "laungcisin@qq.com"
      • 添加到 github。
        复制密钥文件内容(路径形如~/.ssh/id_rsa.pub),粘贴到New SSH Key即可。
        cat ~/.ssh/id_rsa.pub
      • 测试是否添加成功。在命令行(即Git Bash)依次输入以下命令,返回“You’ve successfully authenticated”即成功:

        1
        2
        ssh -T git@github.com
        yes
      • 如果报ssh_exchange_identification: read: Connection reset by peer,尝试用以下命令解决

        1
        2
        3
        4
        5
        6
        7
        8
        #编辑文件
        vi /etc/hosts.allow

        #追加内容
        sshd: ALL

        #重启ssh
        service sshd restart
    3. 修改_config.yml

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      #hexo显示图片功能
      #将post_asset_folder值改成true
      post_asset_folder: true
      npm install hexo-asset-image --save
      npm audit fix

      #创建文章使用hexo n命令
      hexo n "文章名"

      # 修改主题
      #theme: landscape
      theme: next

      # 添加github账号
      # Deployment
      ## Docs: https://hexo.io/docs/deployment.html
      deploy:
      type: git
      repository: git@github.com:<Github账号名称>/<Github账号名称 >.github.io.git
      branch: master
    4. 推送到GithubPages
      hexo clean && hexo g && hexo d

      1
      2
      3
      4
      5
      #本地运行
      hexo clean && hexo g && hexo s

      #浏览器查看
      http://192.168.33.61:4000/

1
2
3
4
5
6
7
8
9
10
11
#确保之前的步骤都能执行成功

#部署到github上
hexo clean && hexo g && hexo d

https://laungcisin.github.io/

yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel

git config --global user.email "laungcisin@qq.com"
git config --global user.name "laungcisin"

laungcisin

30 posts
5 categories
3 tags
© 2019 laungcisin
Powered by Hexo
|
Theme — NexT.Muse v5.1.4