Redis

  • 一、Redis相关介绍
  • 二、如何保持session会话
  • 三、nginx+tomcat+redis实现负载均衡、session共享
  • 四、Redis基本操作命令
  • 五、Redis持久化的实现方法
  • 六、Redis主从服务器的搭建
  • 七、Redis集群的搭建

一、Redis相关介绍

1.数据库分类

关系型:mysql、oracle、sqlserver、db2、postgresql

非关系型:redis、mongo、ES

2.Redis介绍
Redis是一个key-value存储系统。和Memcached类似,但是它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set–有序集合)和hash(哈希类型)。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现主从同步。
Redis是一个高性能的key-value数据库。Redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++/C#,PHP,JavaScript,Perl,Object-C,Python,Ruby等客户端,使用很方便。
3.Redis与Memcached的区别

(1)Redis不仅支持简单的k/v类型数据,同时还提供list,set,zset,hash等数据结构的存储。

(2)Redis支持数据的备份,即master-slave模式的数据备份。

(3)Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启时可以再次加载进行使用。

注意:在Redis中,并不是所有的数据都一直存储在内存中的。
这是和Memcached相比一个最大的区别。Redis只会缓存所有的key的信息,如果Redis发现内存的使用量超过了某一个阀值,将触发swap的操作,Redis根据“swappability = age*log(size_in_memory)”计算出哪些key对应的value需要swap到磁盘。然后再将这些key对应的value持久化到磁盘中,同时在内存中清除。这种特性使得Redis可以保持超过其机器本身内存大小的数据。当然,机器本身的内存必须要能够保持所有的key,因为这些数据是不会进行swap操作的。
当从Redis中读取数据时,如果读取的key对应的value不在内存中,那么Redis就需要从swap文件中加载相应数据,然后再返回给请求方。
4.Redis的主要特性

(1)速度快
   c语言编写
   代码简洁优雅
   单线程架构 

(2)支持多种数据结构
   字符串、哈希、列表、集合、有序集合

(3)丰富的功能
   天然计数器
   键过期功能
   消息队列

(4)支持客户端语言
   php、java、python等

(5)数据持久化
   所有的数据都运行在内存中
   支持2种格式持久化数据:AOF&RDB

(6)自带多种高可用架构
   主从
   哨兵
   集群

5.memcached和Redis的比较

(1)网络IO模型
Memcached是多线程,非阻塞IO复用的网络模型,分为监听主线程和worker子线程,监听线程监听网络连接,接受请求后,将连接描述字pipe传递给worker线程,进行读写IO,网络层使用libevent封装的事件库,多线程模型可以发挥多核作用。
Redis使用单线程的IO复用模型,自己封装了一个简单的AeEvent事件处理框架,主要实现了epoll、kqueue和select,对于单纯只有IO操作来说,单线程可以将速度优势发挥到最大,但是Redis也提供了一些简单的计算功能,比如排序、聚合等,对于这些操作,单线程模型实际会严重影响整体吞吐量,CPU计算过程中,整个IO调度都是被阻塞住的。

(2)内存管理方面
Memcached使用预分配的内存池的方式,使用slab和大小不同的chunk来管理内存,value根据大小选择合适的chunk存储。
Redis使用现场申请内存的方式来存储数据。

(3)存储方式及其它方面
Memcached基本只支持简单的key-value存储,不支持持久化和复制等功能。
Redis除key/value之外,还支持list、set、sortedset、hash等众多数据结构。

6.Redis应用场景

1.缓存--键过期时间
(1)把session会话存在redis,过期删除;
(2)缓存用户信息,缓存Mysql部分数据,用户先访问redis,没有再访问mysql,然后
回写给redis;
(3)商城优惠卷过期时间;

2.排行榜--列表&有序集合
(1)热度/点击数排行榜
(2)直播间礼物积分排行

3.计数器-天然支持计数器
(1)帖子浏览数
(2)视频播放数
(3)评论数
(4)点赞/踩

4.社交网络-集合
(1)粉丝
(2)共同好友 
(3)兴趣爱好
(4)标签

5.消息队列-发布订阅
(1)配合ELK缓存收集来的日志

二、如何保持session会话

为了使web能适应大规模的访问,需要实现应用的集群部署。集群最有效的方案就是负载均衡,而实现负载均衡用户每一个请求都有可能被分配到不固定的服务器上,这样首先要解决session的统一来保证无论用户的请求被转发到哪个服务器上都能保证用户的正常使用,即需要实现session的共享机制。
在集群系统下实现session统一有如下几种方案:
1.请求精确定位:sessionsticky
例如基于访问ip的hash策略,即当前用户的请求都集中定位到一台服务器中,这样单台服务器保存了用户的session登录信息,如果宕机,则等同于单点部署,会丢失,会话不复制。
2.session复制共享:sessionreplication
如tomcat自带session共享,主要是指集群环境下,多台应用服务器之间同步session,使session保持一致,对外透明。如果其中一台服务器发生故障,根据负载均衡的原理,调度器会遍历寻找可用节点,分发请求,由于session已同步,故能保证用户的session信息不会丢失,会话复制。
此方案的不足之处:必须在同一种中间件之间完成(如:tomcat-tomcat之间)。
session复制带来的性能损失会快速增加,特别是当session中保存了较大的对象,而且对象变化较快时,性能下降更加显著,会消耗系统性能。这种特性使得web应用的水平扩展受到了限制。
Session内容通过广播同步给成员,会造成网络流量瓶颈,即便是内网瓶颈。在大并发下表现并
不好。
3.基于cache DB缓存的session共享
基于memcache/redis缓存的session共享
即使用cacheDB存取session信息,应用服务器接受新请求将session信息保存在cache DB中,当应用服务器发生故障时,调度器会遍历寻找可用节点,分发请求,当应用服务器发现session不在本机内存时,则去cache DB中查找,如果找到则复制到本机,这样实现session共享和高可用。

三、nginx+tomcat+redis实现负载均衡、session共享

1.环境准备

主机

IP

NGINX

192.168.229.187

Tomcat1

192.168.229.209

Tomcat2

192.168.229.210

MySQL

192.168.229.215

Redis

192.168.229.220

2.架构图

Redis 当数据库使用 redis存储数据库表_redis


nginx做为反向代理,实现静动分离,将客户动态请求根据权重随机分配给两台tomcat服务器,redis做为两台tomcat的共享session数据服务器,mysql做为两台tomcat的后端数据库。

3.NGINX安装配置

使用nginx作为Tomcat的负载平衡器,Tomcat的会话Session数据存储在Redis,能够实现零宕机的7x24效果。将会话存储在Redis中,Nginx就不必配置成stick粘贴某个Tomcat方式,这样才能真正实现后台多个Tomcat负载平衡。

(1)安装zlib-devel、pcre-devel等依赖包

[root@www ~]# yum -y install gcc gcc-c++ make libtool zlib zlib-devel pcre pcre-devel openssl openssl-devel

(2)创建nginx程序用户

[root@www ~]# useradd -s /sbin/nologin www

(3)编译安装nginx

[root@www ~]# tar zxf nginx-1.10.2.tar.gz 
[root@www ~]# cd nginx-1.10.2/
[root@www nginx-1.10.2]# ./configure --prefix=/usr/local/nginx1.10 --user=www --group=www --with-http_stub_status_module --with-http_realip_module --with-http_ssl_module --with-http_gzip_static_module --with-pcre --with-http_flv_module
[root@www nginx-1.10.2]# make && make install

(4)优化nginx程序的执行路径

[root@www nginx-1.10.2]# ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/
[root@www nginx-1.10.2]# nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful

(5)编写nginx服务脚本

[root@www ~]# vim /etc/init.d/nginx
#!/bin/bash
# nginx Startup script for the Nginx HTTP Server
# chkconfig: 35 85 15
# pidfile: /usr/local/nginx/logs/nginx.pid
# config: /usr/local/nginx/conf/nginx.conf
nginxd=/usr/local/nginx/sbin/nginx
nginx_config=/usr/local/nginx/conf/nginx.conf
nginx_pid=/usr/local/nginx/logs/nginx.pid
RETVAL=0
prog="nginx"
# Source function library.
. /etc/rc.d/init.d/functions
# Start nginx daemons functions.
start() {
if [ -f $nginx_pid ] ; then
echo "nginx already running...."
exit 1
fi
echo -n "Starting $prog: "
$nginxd -c ${nginx_config}
RETVAL=$?
[ $RETVAL = 0 ] && touch /var/lock/subsys/nginx
}
# Stop nginx daemons functions.
stop() {
echo -n "Stopping $prog: "
$nginxd -s stop
RETVAL=$?
[ $RETVAL = 0 ] &&rm -f /var/lock/subsys/nginx
}
# reloadnginx service functions.
reload() {
echo -n "Reloading $prog: "
$nginxd -s reload
}
# statusngnx service functions
status() {
if [ -f $nginx_pid ] ; then
echo "$prog is running"
else
echo "$prog is stop"
fi
}
case "$1" in
start)
start
;;
stop)
stop
;;
reload)
reload
;;
restart)
stop
start
;;
status)
status
;;
*)
echo "Usage: $prog {start|stop|restart|reload|status}"
exit 1
;;
esac
[root@www ~]# chmod +x /etc/init.d/nginx
[root@www ~]# chkconfig --add nginx
[root@www ~]# chkconfig nginx on
[root@www ~]# systemctl daemon-reload

(6)配置nginx反向代理
反向代理+负载均衡+健康探测

[root@www ~]# vim /usr/local/nginx/conf/nginx.conf
user www www;
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
worker_rlimit_nofile 10240;
pid logs/nginx.pid;
events {
use epoll;
worker_connections 4096;
}
http {
include mime.types;
default_type application/octet-stream;
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;
server_tokens off;
sendfile on;
tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#Compression Settings
gzip on;
gzip_comp_level 6;
gzip_http_version 1.1;
gzip_proxied any;
gzip_min_length 1k;
gzip_buffers 16 8k;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;
#end gzip
# http_proxy Settings
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 75;
proxy_send_timeout 75;
proxy_read_timeout 75;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
#load balance Settings
upstream backend_tomcat {
server 192.168.229.209:8080 weight=1 max_fails=2 fail_timeout=10s;
server 192.168.229.210:8080 weight=1 max_fails=2 fail_timeout=10s;
}
#virtual host Settings
server {
listen 80;
server_name www.benet.com;
charset utf-8;
location / {
root html;
index index.jsp index.html index.htm;
}
location ~* \.(jsp|do)$ {
proxy_pass http://backend_tomcat;
proxy_redirect 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;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
}
location /nginx_status {
stub_status on;
access_log off;
allow 192.168.229.0/24;
deny all;
}
}
}
[root@www ~]# service nginx restart

4.安装部署Tomcat应用程序
在安装tomcat之前必须先安装JDK,JDK的全称是java development kit,是sun公司免费提供的java语言的软件开发工具包,其中包含java虚拟机(JVM),编写好的java源程序经过编译可形成java字节码,只要安装了JDK,就可以利用JVM解释这些字节码文件,从而保证了java的跨平台性。
安装JDK,配置java环境。

[root@tomcat1 ~]# yum -y install java-devel
[root@tomcat2 ~]# yum -y install java-devel

在tomcat1和tomcat2节点安装配置tomcat

[root@tomcat1 ~]# tar zxf apache-tomcat-8.5.16.tar.gz
[root@tomcat1 ~]# mv apache-tomcat-8.5.16 /usr/local/tomcat8

配置tomcat1和Tomcat2的环境变量

[root@tomcat-1 ~]# vim /etc/profile
export CATALINA_HOME=/usr/local/tomcat8
export PATH=$CATALINA_HOME/bin:$PATH
[root@tomcat-1 ~]# source /etc/profile

查看tomcat的版本信息

[root@tomcat1 ~]# catalina.sh version
Using CATALINA_BASE:   /usr/local/tomcat8
Using CATALINA_HOME:   /usr/local/tomcat8
Using CATALINA_TMPDIR: /usr/local/tomcat8/temp
Using JRE_HOME:        /usr
Using CLASSPATH:       /usr/local/tomcat8/bin/bootstrap.jar:/usr/local/tomcat8/bin/tomcat-juli.jar
Server version: Apache Tomcat/8.5.16
Server built:   Jun 21 2017 17:01:09 UTC
Server number:  8.5.16.0
OS Name:        Linux
OS Version:     3.10.0-1127.el7.x86_64
Architecture:   amd64
JVM Version:    1.8.0_282-b08
JVM Vendor:     Red Hat, Inc.

启动Tomcat1和Tomcat2

[root@tomcat1 ~]# startup.sh
Using CATALINA_BASE:   /usr/local/tomcat8
Using CATALINA_HOME:   /usr/local/tomcat8
Using CATALINA_TMPDIR: /usr/local/tomcat8/temp
Using JRE_HOME:        /usr
Using CLASSPATH:       /usr/local/tomcat8/bin/bootstrap.jar:/usr/local/tomcat8/bin/tomcat-juli.jar
Tomcat started.

Tomcat默认运行在8080端口,运行netstat命令查看8080端口监听的信息。

[root@tomcat1 ~]# netstat -anput | grep java
tcp6       0      0 :::8009                 :::*                    LISTEN      51881/java          
tcp6       0      0 :::8080                 :::*                    LISTEN      51881/java          
tcp6       0      0 127.0.0.1:8005          :::*                    LISTEN      51881/java

浏览器分别对tomcat1和tomcat2访问测试。

Redis 当数据库使用 redis存储数据库表_经验分享_02


如果想关闭tomcat则运行shutdown.sh命令。

可以看到访文成功,说明tomcat安装完成,下面修改配置文件。

[root@tomcat1 ~]# vim /usr/local/tomcat8/conf/server.xml
#设置默认虚拟主机,并增加jvmRoute
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">
#修改默认虚拟主机,并将网站文件路径指向/web/webapp1,在host段增加context段
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Context docBase="/web/webapp1" path="" reloadable="true"/>
</Host>

增加文档目录与测试文件

[root@tomcat1 ~]# mkdir -p /web/webapp1
[root@tomcat1 ~]# cd /web/webapp1/
[root@ tomcat1 webapp1]# vim index.jsp
<%@page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
<title>tomcat1</title>
</head>
<body>
<h1><font color="red">Session serviced by tomcat</font></h1>
<table aligh="center" border="1">
<tr>
<td>Session ID</td>
<td><%=session.getId() %></td>
<% session.setAttribute("abc","abc");%>
</tr>
<tr>
<td>Created on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
<html>

停止tomcat运行,检查配置文件并启动tomcat。

[root@tomcat1 ~]# shutdown.sh
[root@tomcat1 ~]# netstat -anput | grep java
[root@tomcat1 ~]# catalina.sh configtest
[root@tomcat1 webapp1]# startup.sh
[root@tomcat1 webapp1]# netstat -anput | grep java
tcp6       0      0 :::8009                 :::*                    LISTEN      66495/java          
tcp6       0      0 :::8080                 :::*                    LISTEN      66495/java          
tcp6       0      0 127.0.0.1:8005          :::*                    LISTEN      66495/java

Tomcat2节点与tomcat1节点配置基本类似,只是jvmRoute不同,另外为了区分由哪个节点提供访问,测试页标题也不同(生产环境两个tomcat服务器提供的网页内容是相同的)。其他的配置都相同。

[root@tomcat2 ~]# vim /usr/local/tomcat8/conf/server.xml
#设置默认虚拟主机,并增加jvmRoute
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat2">
#修改默认虚拟主机,并将网站文件路径指向/web/webapp1,在host段增加context段
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Context docBase="/web/webapp1" path="" reloadable="true"/>
</Host>
[root@tomcat2 ~]# mkdir -p /web/webapp1
[root@tomcat2 ~]# cd /web/webapp1/
[root@ tomcat2 webapp1]# vim index.jsp
<%@page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<html>
<head>
<title>tomcat2</title>
</head>
<body>
<h1><font color="red">Session serviced by tomcat</font></h1>
<table aligh="center" border="1">
<tr>
<td>Session ID</td>
<td><%=session.getId() %></td>
<% session.setAttribute("abc","abc");%>
</tr>
<tr>
<td>Created on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
<html>
[root@tomcat2 ~]# shutdown.sh
[root@tomcat2 ~]# netstat -anput | grep java
[root@tomcat2 ~]# catalina.sh configtest
[root@tomcat2 webapp1]# startup.sh
[root@tomcat2 webapp1]# netstat -anput | grep java
tcp6       0      0 :::8009                 :::*                    LISTEN      66495/java          
tcp6       0      0 :::8080                 :::*                    LISTEN      66495/java          
tcp6       0      0 127.0.0.1:8005          :::*                    LISTEN      66495/java

用浏览器访问nginx主机,验证负载均衡。

第一次访问结果

Redis 当数据库使用 redis存储数据库表_经验分享_03


第二次访问结果

Redis 当数据库使用 redis存储数据库表_centos_04


验证健康检查的方法可以关掉一台tomcat主机,用客户端浏览器测试访问。

从上面的结果能看出两次访问,nginx把访问请求分别分发给了后端的tomcat1和tomcat2,客户端的访问请求实现了负载均衡,但session id并不一样。所以,到这里准备工作就全部完成,下面配置tomcat通过redis实现会话保持。

5.安装Redis

(1)准备安装和数据目录

[root@redis1 ~]# mkdir -p /data/soft
[root@redis1 ~]# mkdir -p /opt/redis_cluster/redis_6379/{conf,logs,pid}

(2)下载redis安装包

[root@redis1 ~]# cd /data/soft
[root@redis1 soft]# wget http://download.redis.io/releases/redis-5.0.7.tar.gz

(3)解压redis到/opt/redis_cluster/

[root@redis1 soft]# tar xf redis-5.0.7.tar.gz -C /opt/redis_cluster/
[root@redis1 soft]# ln -s /opt/redis_cluster/redis-5.0.7 /opt/redis_cluster/redis

(4)切换目录安装redis

[root@redis1 soft]# cd /opt/redis_cluster/redis
[root@redis1 redis]# yum -y install gcc gcc-c++
[root@redis1 redis]# make && make install

(5)编写配置文件

[root@redis1 redis]# vim /opt/redis_cluster/redis_6379/conf/6379.conf
添加:
bind 127.0.0.1 192.168.229.177
port 6379
daemonize yes
pidfile /opt/redis_cluster/redis_6379/pid/redis_6379.pid
logfile /opt/redis_cluster/redis_6379/logs/redis_6379.log
databases 16
dbfilename redis.rdb
dir /opt/redis_cluster/redis_6379

(6)启动当前redis服务

[root@redis1 redis]# redis-server /opt/redis_cluster/redis_6379/conf/6379.conf

6.配置tomcat session redis同步
下载tomcat-redis-session-manager相应的jar包,主要有三个:

tomcat-redis-session-manage-tomcat7.jar
jedis-2.5.2.jar
commons-pool2-2.2.jar

下载完成后拷贝到$TOMCAT_HOME/lib中

[root@tomcat1 ~]# vim /usr/local/tomcat7/conf/context.xml
...
<Context>
<!-- Default set of monitored resources -->
<WatchedResource>WEB-INF/web.xml</WatchedResource>
<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->
<!-- Uncomment this to enable Comet connection tacking (provides events
on session expiration as well as webapp lifecycle) -->
<!--
<Valve className="org.apache.catalina.valves.CometConnectionManagerValve" />
-->
添加内容如下
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" />
<Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager"
host="192.168.229.221"
password="asd123"
port="6379"
database="0"
maxInactiveInterval="60" />
</Context>

说明:

maxInactiveInterval="60":session的失效时间

重启tomcat服务

[root@tomcat1 ~]# shutdown.sh 
[root@tomcat1 ~]# startup.sh

tomcat2执行和tomcat1相同的操作。
通过浏览器访问http://192.168.229.187/index.jsp测试页
第一次访问和第二次访问分别访问了不同的tomcat,但是得到的session却是相同的,说明达到了集群的目的。
注意:从Tomcat6开始默认开启了Session持久化设置,测试时可以关闭本地Session持久化,其实也很简单,在Tomcat的conf目录下的context.xml文件中,取消注释下面那段配置即可:
修改前:

<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<!--
<Manager pathname="" />
-->

修改后:

<!-- Uncomment this to disable session persistence across Tomcat restarts -->
<Manager pathname="" />

重启tomcat服务
查看redis:

[root@redis ~]# redis-cli -h 192.168.229.221 -p 6379 -a asd123
192.168.229.221:6379> keys *
1) "6C3F950BE6413AD2E0EF00F930881224.tomcat-1.tomcat-1"
2) "name"
3) "7A6D5D4C5B1EA52C4E9EED1C5523FEB5.tomcat-2.tomcat-2"
4) "32C35EEA064884F065E93CB00C690662.tomcat-1.tomcat-1"

7.tomcat连接数据库

mysql> grant all on *.* to javauser@'192.168.229.%' identified by 'javapasswd';
Query OK, 0 rows affected, 1 warning (0.02 sec)

mysql> create database javatest;
Query OK, 1 row affected (0.01 sec)

mysql> use javatest;
Database changed
mysql> create table testdata(id int not null auto_increment primary key,foo varchar(25),bar int);
Query OK, 0 rows affected (0.02 sec)

插入些数据

mysql> insert into testdata(foo,bar) values ('hello','123456'),('ok','654321');
Query OK, 2 rows affected (0.04 sec)
Records: 2 Duplicates: 0 Warnings: 0

mysql> select * from testdata;
+----+-------+--------+
| id | foo   | bar    |
+----+-------+--------+
| 1  | hello | 123456 |
| 2  | ok    | 654321 |
+----+-------+--------+
2 rows in set (0.00 sec)

配置tomcat服务器连接mysql数据库
下载mysql-connector-java-5.1.22-bin.jar并复制到$CATALINA_HOME/lib目录下

[root@tomcat1 ~]# cp mysql-connector-java-5.1.22-bin.jar /usr/local/tomcat8/lib/
[root@tomcat1 ~]# ls /usr/local/tomcat7/lib/mysql-connector-java-5.1.22-bin.jar  
/usr/local/tomcat8/lib/mysql-connector-java-5.1.22-bin.jar
context configuration
configure the JNDI datasource in tomcat by adding a declaration for your resource to your context
[root@tomcat1 ~]# vim /usr/local/tomcat8/conf/context.xml
在<Context>中添加如下内容:
<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="javauser" password="javapass" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://192.168.229.220:3306/javatest"/>

保存修改并退出

[root@tomcat1 ~]# mkdir /web/webapp1/WEB-INF
[root@tomcat1 ~]# vim /web/webapp1/WEB-INF/web.xml
添加内容如下:
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<description>MySQL Test App</description>
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/TestDB</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>

保存修改并退出,重启tomcat服务

[root@tomcat1 ~]# shutdown.sh
[root@tomcat1 ~]# startup.sh

tomcat2进行和tomcat1相同的操作
测试代码

[root@tomcat2 webapp1]# vim test.jsp
<%@ page language="java" import="java.sql.*" pageEncoding="GB2312"%>
<html>
<head>
<title>MySQL</title>
</head>
<body>
connect MySQL<br>
<%
String driverClass="com.mysql.jdbc.Driver";
String url="jdbc:mysql://192.168.229.221:3306/javatest";
String username = "javauser"; 
String password = "javapasswd"; 
Class.forName(driverClass); 
Connection conn=DriverManager.getConnection(url, username, password); 
Statement stmt=conn.createStatement();
ResultSetrs = stmt.executeQuery("select * from testdata"); 
while(rs.next()){
out.println("<br>foo:"+rs.getString(2)+"bar:"+rs.getString(3));
}
rs.close();
stmt.close();
conn.close();
%>
</body></html>

通过浏览器访问http://192.168.229.187/test.jsp测试页

Redis 当数据库使用 redis存储数据库表_Redis 当数据库使用_05


注:以上配置可以参考tomcat docs。

Redis 当数据库使用 redis存储数据库表_Redis 当数据库使用_06


Redis 当数据库使用 redis存储数据库表_linux_07

四、Redis基本操作命令

1.全局命令
登入Redis的命令为

[root@redis1 redis]# redis-cli
KEYS *  #列出所有键值名,但在企业环境禁止使用(键值全部显示过多会造成服务器崩溃)

DBSIZE  #查看有多少键值数

EXISTS  #查看键值是否存在

EXPIRE k2 20  #设置K2过期时间为20秒,20秒后k2自动取消

PERSIST k2  #取消k2的过期时间

TTL k2  #查看k2的生命周期

TYPE  #查看数据类型

2.字符串类型:string

SET	k3 3  #创建键值

GET	k3  #查看键值

DEL	k2  #删除键值 

INCR k3  #键值k3为整数,递增加1

INCRBY k3 10  #递增k3的量值10 

MSET k4 4 k5 5 k6 6 k7 7  #批量创建键值

MGET k4 k5 k6 k7  #批量查看键值

3.列表:list

RPUSH list1 1 2 3 4  #创建列表list1,值为1 2 3 4

RPUSH list1 5 6 7 8	 #在list1右侧添加5 6 7 8

LPUSH list1 0  #在list1左侧添加0

LRANGE list1 0  -1  #查看list1所有值

RPOP list1  #删除右侧最后一个值

LPOP list1  #删除左侧第一个值

LTRIM list1 0 2  #仅保留前3位,其他值删除(截取前三位保留)

4.哈希:hash

HMSET user:1000 username lisi age 17 job it  #创建hash键值user:1000 

HGET user:1000 username  #查看键值中username参数

HGET user:1000 age  #查看键值中age参数

HGET user:1000 job  #查看键值中job参数

HMSET user:1000 tel 18866668888  #添加值tel

5.集合:set

SADD set1 1 2 3  #创建集合set1

SMEMBERS set1  #查看集合set1

SADD set1 1 4  #为集合set1添加值1 4 ,但集合特性是去除重复,所以1无法再添加

SREM set1 1 4  #删除集合的值1 4

SADD set2 1 4 5  #创建第二个集合set2

SDIFF set1 set2  #求差集

SINTER set1 set2  #求合集(交集)

SUNION set1 set2  #求并集
五、Redis持久化的实现方法

1.redis持久化

RDB:生成时间点快照,保存于硬盘。
	优点:速度快,适合做备份,能做主从复制,单开子进程进行rdb操作不影响主业务;
	缺点:会有部分数据丢失;
		
AOF:记录所有写操作命令,通过再次执行这些命令还原数据。
	优点:最大程度保证数据不丢失;
	缺点:日志记录量太大;

2.RDB配置

[root@redis1 redis]# vim /opt/redis_cluster/redis_6379/conf/6379.conf	
添加:
save 900 1  #在900秒(15分钟)后至少有1个key发生变化则dump内存快照;
save 300 10  #在300秒(5分钟)后至少有10个key发生变化,则dump内存快照;
save 60 10000  #在60秒(1分钟)后至少有10000个key发生变化,则dump内存快照;
保存退出
[root@redis1 redis]# redis-cli  #登录redis
[root@redis1 redis]# bgsave  #rdb保存命令

3.AOF配置

[root@redis1 redis]# vim /opt/redis_cluster/redis_6379/conf/6379.conf
添加:
appendonly yes  #启用AOF持久化
appendfilename "redis.aof"  #指定AOF文件名
appendfsync everysec  #每秒同步一次

4.重启redis

[root@redis1 redis]# redis-cli shutdown
[root@redis1 redis]# redis-server /opt/redis_cluster/redis_6379/conf/6379.conf
六、Redis主从服务器的搭建

环境准备

主服务器:192.168.229.161

从服务器:192.168.229.167

1.redis主从复制
为解决单点故障把数据复制到一个或多个副本服务器(从服务器),实现故障恢复和负载均衡。
2.开启第二台服务器,安装redis
(1)把第一台服务器的redis安装目录scp到第二台服务器上

[root@redis1 redis]# scp -rp /opt/redis_cluster/ root@192.168.229.167:/opt

(2)在第二台服务器上,make install安装redis

[root@redis2 ~]# cd /opt/redis_cluster/redis
[root@redis2 redis]# make install
[root@redis2 redis]# vim /opt/redis_cluster/redis_6379/conf/6379.conf 
修改:
bind 127.0.0.1 192.168.229.167
末尾添加:
slaveof 192.168.229.161 6379

(3)启动服务

[root@redis2 redis]# redis-server /opt/redis_cluster/redis_6379/conf/6379.conf

(4)主服务器上新建键值,测试从服务器自动同步
(5)从服务器在同步过程中,只能复制主数据库的数据,不能手动添加修改数据;
如果从服务器非要修改数据,需要断开同步:

[root@redis2 redis]# redis-cli slaveof no one

如需再次连接:

[root@redis2 redis]# redis-cli slaveof 192.168.229.161 6379

如果主服务器出现问题,则只需要把从服务器的配置文件slaveof那一行去掉就可以变成主服务器。

七、Redis集群的搭建

1.redis集群简介
Redis Cluster是redis的分布式解决方案,在3.0版本正式推出。
当遇到单机、内存、并发、流量等瓶颈时,可以采用Cluster架构方案达到负载均衡的目的。
Redis Cluster之前的分布式方案有两种

(1)客户端分区方案:
优点:分区逻辑可控;
缺点:需要自己处理数据路由、高可用和故障转移等。

(2)代理方案:
优点:简化客户端分布式逻辑和升级维护便利;
缺点:加重架构部署和性能消耗。
官方提供的Redis Cluster集群方案,很好的解决了集群方面的问题。

2.数据分布
分布式数据库首先要解决把整个数据库集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整体数据的一个子集,需要关注的是数据分片规则,Redis Cluster采用哈希分片规则。

3.目录规划

#redis安装目录
/opt/redis_cluster/redis_{PORT}/{conf,logs,pid}

#redis数据目录
/data/redis_cluster/redis_{PORT}/redis_{PORT}.rdb

#redis运维脚本
/root/scripts/redis_shell.sh

4.集群拓扑

redis1 192.168.229.160
redis2 192.168.229.161
redis3 192.168.229.167

5.手动搭建部署集群
思路:
先部署一台服务器上的2个集群节点,
把文件发送到其他主机修改IP地址,启动服务。
具体操作如下
(1)redis1操作

[root@redis1 ~]# mkdir -p /opt/redis_cluster/redis_{6380,6381}/{conf,logs,pid}
[root@redis1 ~]# mkdir –p /data/redis_cluster/redis_{6380,6381}

(2)安装redis

[root@redis1 ~]# wget http://download.redis.io/releases/redis-5.0.7.tar.gz
[root@redis1 ~]# tar xf redis-5.0.7.tar.gz -C /opt/redis_cluster/
[root@redis1 ~]# ln -s /opt/redis_cluster/redis-5.0.7 /opt/redis_cluster/redis
[root@redis1 ~]# cd /opt/redis_cluster/redis
[root@redis1 redis]# yum -y install gcc gcc-c++
[root@redis1 redis]# make && make install
[root@redis1 redis]# vim /opt/redis_cluster/redis_6380/conf/redis_6380.conf
添加:
bind 192.168.229.160
port 6380
daemonize yes
pidfile "/opt/redis_cluster/redis_6380/pid/redis_6380.pid"
logfile "/opt/redis_cluster/redis_6380/logs/redis_6380.log"
dbfilename "redis_6380.rdb"
dir "/data/redis_cluster/redis_6380/"
cluster-enabled yes
cluster-config-file nodes_6380.conf
cluster-node-timeout 15000
[root@redis1 redis]# cd /opt/redis_cluster/
[root@redis1 redis_cluster]# cp redis_6380/conf/redis_6380.conf redis_6381/conf/redis_6381.conf
[root@redis1 redis_cluster]# sed -i 's/6380/6381/g' redis_6381/conf/redis_6381.conf 
[root@redis1 redis_cluster]# redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf
[root@redis1 redis_cluster]# redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf

(3)复制redis1的安装和数据目录到redis2、redis3

[root@redis1 redis_cluster]# scp -rp /opt/redis_cluster/ root@192.168.229.161:/opt
[root@redis1 redis_cluster]# scp -rp /opt/redis_cluster/ root@192.168.229.167:/opt

(4)redis2操作:

[root@redis2 ~]# cd /opt/redis_cluster/redis
[root@redis2 redis]# make install 
[root@redis2 redis]# find /opt/redis_cluster/redis_638* -type f -name "*.conf" |xargs sed -i "s/160/161/g"
[root@redis2 redis]# mkdir –p /data/redis_cluster/redis_{6380,6381}
[root@redis2 redis]# redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf
[root@redis2 redis]# redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf

(5)redis3操作

[root@redis3 ~]# cd /opt/redis_cluster/redis
[root@redis3 redis]# make install
[root@redis3 redis]# find /opt/redis_cluster/redis_638* -type f -name "*.conf" |xargs sed -i 's/160/167/g'
[root@redis3 redis]# mkdir –p /data/redis_cluster/redis_{6380,6381}
[root@redis3 redis]# redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf
[root@redis3 redis]# redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf

6.手动配置节点发现
(1)当把所有节点都启动后查看进程会有cluster的字样,但是登录后执行CLUSTER NODES命令会发现只有每个节点自己的ID,目前集群内的节点还没有互相发现,所以搭建redis集群我们第一步要做的就是让集群内的节点互相发现。

在执行节点发现命令之前我们先查看一下集群的数据目录会发现有生成集群的配置文件;

[root@redis1 redis_6380]# cat /data/redis_cluster/redis_6380/nodes_6380.conf
[root@redis1 redis_6380]# redis-cli -h 192.168.229.160 -p 6380
192.168.229.160:6380> cluster nodes

查看后发现只有自己的节点内容,等节点全部发现后会把所发现的节点ID写入这个文件;

集群模式的Redis除了原有的配置文件之外又加了一份集群配置文件.当集群内节点
信息发生变化,如添加节点,节点下线,故障转移等.节点会自动保存集群状态到配置文件;
需要注意的是,Redis自动维护集群配置文件,不需要手动修改,防止节点重启时产生错乱。

节点发现使用命令:

CLUSTER MEET {IP} {PORT}

提示:在集群内任意一台机器执行此命令就可以。
可以编写登录脚本

[root@redis1 redis]# vim redis_shell.sh
#!/bin/bash

USAG(){
    echo "sh $0 {start|stop|restart|login|ps|tail} PORT"
}
if [ "$#" = 1 ]
then
    REDIS_PORT='6379'
elif 
    [ "$#" = 2 -a -z "$(echo "$2"|sed 's#[0-9]##g')" ]
then
    REDIS_PORT="$2"
else
    USAG
    exit 0
fi

REDIS_IP=$(hostname -I|awk '{print $1}')
PATH_DIR=/opt/redis_cluster/redis_${REDIS_PORT}/
PATH_CONF=/opt/redis_cluster/redis_${REDIS_PORT}/conf/redis_${REDIS_PORT}.conf
PATH_LOG=/opt/redis_cluster/redis_${REDIS_PORT}/logs/redis_${REDIS_PORT}.log

CMD_START(){
    redis-server ${PATH_CONF}
}

CMD_SHUTDOWN(){
    redis-cli -c -h ${REDIS_IP} -p ${REDIS_PORT} shutdown
}

CMD_LOGIN(){
    redis-cli -c -h ${REDIS_IP} -p ${REDIS_PORT}
}

CMD_PS(){
    ps -ef|grep redis
}

CMD_TAIL(){
    tail -f ${PATH_LOG}
}

case $1 in
    start)
        CMD_START
        CMD_PS
        ;;
    stop)
        CMD_SHUTDOWN
        CMD_PS
        ;;
    restart)
        CMD_START
        CMD_SHUTDOWN
        CMD_PS
        ;;
    login)
        CMD_LOGIN
        ;;
    ps)
        CMD_PS
        ;;
    tail)
        CMD_TAIL
        ;;
    *)
        USAG
esac
[root@redis1 redis]# chmod +x redis_shell.sh
[root@redis1 redis]# sh redis_shell.sh login 6380
192.168.1.102:6380> CLUSTER MEET 192.168.229.160 6381
OK
192.168.1.102:6380> CLUSTER MEET 192.168.229.161 6380
OK
192.168.1.102:6380> CLUSTER MEET 192.168.229.167 6380
OK
192.168.1.102:6380> CLUSTER MEET 192.168.229.161 6381
OK
192.168.1.102:6380> CLUSTER MEET 192.168.229.167 6381
OK
192.168.229.160:6380> CLUSTER NODES
098d20bfacb5c8d926aa46455fe11942de083643 192.168.229.161:6380@16380 master - 0 1595914581766 2 connected
38d0a92b60d00ab1ce1149d32aaef0fbb29bb6ab 192.168.229.167:6380@16380 master - 0 1595914579000 5 connected
391ddc472dfed2cc65846f9ef55ddc14a9f02e88 192.168.229.167:6381@16381 master - 0 1595914579742 4 connected
c23e23467e6e45d3bd783f0bbbf784252292400e 192.168.229.161:6381@16381 master - 0 1595914580000 3 connected
cb94deff8ec6d686172f012e064ab98c9f0edfc3 192.168.229.160:6380@16380 myself,master - 0 1595914581000 0 connected
7eb1d0435165f2657a722022887fd1c453c36d3d 192.168.229.160:6381@16381 master - 0 1595914580753 1 connected

节点都发现完毕后我们再次查看集群配置文件;
可以看到,发现到的节点的ID也被写入到了集群的配置文件里。
(2)Redis Cluster通讯流程
在分布式存储中需要提供维护节点元数据信息的机制,所谓元数据是指:节点负责哪些数据,是否出现故障灯状态信息,redis集群采用Gossip(流言)协议,Gossip协议工作原理就是节点彼此不断交换信息,一段时间后所有的节点都会知道集群完整信息,这种方式类似流言传播。
通信过程

1.集群中的每一个节点都会单独开辟一个tcp通道,用于节点之间彼此通信,防火墙放行(端口号+10000);

2.每个节点在固定周期内通过特定规则选择结构节点发送ping消息;

3.接收到ping消息的节点用pong消息作为响应。集群中每个节点通过一定规则挑选要通信的节点,每个节点可能知道全部节点,也可能仅知道部分节点,只要这些节点彼此可以正常通信,最终他们会打成一致的状态,当节点出现故障,新节点加入,主从角色变化等,它能够给不断的ping/pong消息,从而达到同步目的。

通讯消息类型

Gossip
	Gossip协议职责就是信息交换,信息交换的载体就是节点间彼此发送Gossip消息;
常见Gossip消息分为:ping、pong、meet、fail等;

meet
	meet消息:用于通知新节点加入,消息发送者通知接受者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行ping、pong消息交换;

ping
	ping消息:集群内交换最频繁的消息,集群内每个节点每秒想多个其他节点发送ping消息,用于检测节点是否在线和交换彼此信息;

pong
	Pong消息:当接收到ping、meet消息时,作为相应消息回复给发送方确认消息正常通信,节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新;

fail
	fail消息:当节点判定集群内另一个节点下线时,回向集群内广播一个fail消息,其他节点收到fail消息之后把对应节点更新为下线状态;

(3)Redis Cluster手动分配槽位
虽然节点之间已经互相发现了,但是此时集群还是不可用的状态,因为并没有给节点分配槽位,而且必须是所有的槽位都分配完毕后整个集群才是可用的状态.反之,也就是说只要有一个槽位没有分配,那么整个集群就是不可用的.
测试命令

[root@redis1 redis]# sh redis_shell.sh login 6380
192.168.229.160:6380> set k1 1
(error) CLUSTERDOWN Hash slot not served
192.168.229.160:6380> cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:0
cluster_current_epoch:5
cluster_my_epoch:0
cluster_stats_messages_ping_sent:940
cluster_stats_messages_pong_sent:969
cluster_stats_messages_meet_sent:5
cluster_stats_messages_sent:1914
cluster_stats_messages_ping_received:969
cluster_stats_messages_pong_received:945
cluster_stats_messages_received:1914

前面说了,我们虽然有6个节点,但是真正负责数据写入的只有3个节点,其他3个节点只是作为主节点的从节点,也就是说,只需要分配期中三个节点的槽位就可以了。
分配槽位的方法:
分配槽位需要在每个主节点上来配置,此时有2种方法执行:
分别登录到每个主节点的客户端来执行命令
在其中一台机器上用redis客户端远程登录到其他机器的主节点上执行命令
每个节点执行命令:

[root@redis1 redis]# redis-cli -h 192.168.229.160 -p 6380 cluster addslots {0..5461}
OK
[root@redis1 redis]# redis-cli -h 192.168.229.161 -p 6380 cluster addslots {5462..10922}
OK
[root@redis1 redis]# redis-cli -h 192.168.229.167 -p 6380 cluster addslots {10923..16383}
OK

分配完所有槽位之后我们再查看一下集群的节点状态和集群状态,可以看到三个节点都分配了槽位,而且集群的状态是OK的。
(4)手动配置集群高可用
虽然这时候集群是可用的了,但是整个集群只要有一台机器坏掉了,那么整个集群都是不可用了,所以这时候需要用到其他三个节点分别作为现在三个主节点的从节点,以应对集群主节点故障时可以进行自动切换以保证集群持续可用。
注意:

1.不要让复制节点复制本机器的主节点, 因为如果那样的话机器挂了集群还是不可用状态, 所以复制节点要复制其他服务器的主节点。

2.使用redis-trid工具自动分配的时候会出现复制节点和主节点在同一台机器上的情况,需要注意。

(5)测试集群
这一次我们采用在一台机器上使用redis客户端远程操作集群其他节点
注意:

1.需要执行命令的是每个服务器的从节点

2.注意主从的ID不要搞混了

执行命令

[root@redis1 redis]# redis-cli -h 192.168.229.160 -p 6381 cluster replicate 098d20bfacb5c8d926aa46455fe11942de083643  (第二台Redis的6380ID)
OK
[root@redis1 redis]# redis-cli -h 192.168.229.161 -p 6381 cluster replicate 38d0a92b60d00ab1ce1149d32aaef0fbb29bb6ab  (第三台Redis的6380ID)
OK
[root@redis1 redis]# redis-cli -h 192.168.229.167 -p 6381 cluster replicate cb94deff8ec6d686172f012e064ab98c9f0edfc3  (第一台Redis的6380ID)
OK

(6)Redis Cluster测试集群
我们使用常规插入redis数据的方式往集群里写入数据看看会发生什么

[root@redis1 redis]# redis-cli -h 192.168.229.160 -p 6380 set k1 1
(error) MOVED 12706 192.168.229.167:6380

结果提示error, 但是给出了集群另一个节点的地址
那么这条数据到底有没有写入呢? 我们登录这两个节点分别查看

[root@redis1 redis]# redis-cli -h 192.168.229.167 -p 6380 get k1
(nil)

结果没有,这是因为使用集群后由于数据被分片了,所以并不是说在那台机器上写入数据就会在哪台机器的节点上写入,集群的数据写入和读取就涉及到了另外一个概念,ASK路由。
(7)Redis Cluster ASK路由介绍
在集群模式下,Redis接受任何键相关命令时首先会计算键对应的槽,再根据槽找出所对应的节点,如果节点是自身,则处理键命令;否则回复MOVED重定向错误,通知客户端请求正确的节点,这个过程称为Mover重定向。
知道了ask路由后,我们使用-c选项批量插入一些数据

[root@redis1 redis]# vim input_key.sh 
#!/bin/bash
for i in $(seq 1 1000)
do
    redis-cli -c -h 192.168.229.160 -p 6380 set k_${i} v_${i} && echo "set k_${i} is ok"
done

写入后我们同样使用-c选项来读取刚才插入的键值,然后查看下redis会不会帮我们路由到正确的节点上。

[root@redis1 redis]# redis-cli -c -h 192.168.229.160 -p 6380
192.168.229.160:6380> get k_1
"v_1"
192.168.229.160:6380> get k_100
-> Redirected to slot [5541] located at 192.168.229.161:6380
"v_100"

(8)模拟故障转移
至此,我们已经手动的把一个redis高可用的集群部署完毕了, 但是还没有模拟过故障
这里我们就模拟故障,停掉期中一台主机的redis节点,然后查看一下集群的变化,我们使用暴力的kill -9(真实环境禁止使用,建议使用kill或pkill)杀掉,redis2上的redis集群节点,然后观察节点状态;理想情况应该是redis1上的6381从节点升级为主节点,在redis1上查看集群节点状态。
虽然我们已经测试了故障切换的功能,但是节点修复后还是需要重新上线,所以这里测试节点重新上线后的操作,重新启动redis2的6380,然后观察日志。
观察redis1上的日志,这时假如我们想让修复后的节点重新上线,可以在想变成主库的从库执行CLUSTER FAILOVER命令,这里我们在redis2的6380上执行。
操作命令
中止redis2的服务:

[root@redis2 redis]# pkill redis-server

查看日志:

[root@redis1 redis]# sh redis_shell.sh tail 6380

再启动redis2的服务

[root@redis2 redis]# redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf
[root@redis2 redis]# redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf

redis2重回master:

[root@redis2 redis]# redis-cli -h 192.168.229.161 -p 6380 CLUSTER FAILOVER

7.使用工具自动搭建部署Redis Cluster
手动搭建集群便于理解集群创建的流程和细节,不过手动搭建集群需要很多步骤,当集群节点众多时,必然会加大搭建集群的复杂度和运维成本,因此官方提供了redis-trib.rb的工具方便我们快速搭建集群。redis-trib.rb是采用Ruby实现的redis集群管理工具,内部通过Cluster相关命令帮我们简化集群创建、检查、槽迁移和均衡等常见运维操作,使用前要安装ruby依赖环境。
前提准备
停掉所有的节点,然后清空数据,恢复成一个全新的集群,所有机器执行命令:

[root@redis1 redis]# pkill redis
[root@redis1 redis]# rm -rf /data/redis_cluster/redis_6380/*
[root@redis1 redis]# rm -rf /data/redis_cluster/redis_6381/*

全部清空之后启动所有的节点,所有机器执行:

[root@redis1 redis]# redis-server /opt/redis_cluster/redis_6380/conf/redis_6380.conf
[root@redis1 redis]# redis-server /opt/redis_cluster/redis_6381/conf/redis_6381.conf

(1)安装命令:注意新版本redis不需安装,直接采用步骤(2)

yum makecache fast
yum install rubygems
gem sources --remove https://rubygems.org/
gem sources -a http://mirrors.aliyun.com/rubygems/
gem update –system
gem install redis -v 3.3.5

redis1执行创建集群命令

cd /opt/redis_cluster/redis/src/
./redis-trib.rb create --replicas 1 192.168.229.160:6380 192.168.229.161:6380 192.168.229.167:6380 192.168.229.160:6381 192.168.229.161:6381 192.168.229.167:6381

检查集群完整性

./redis-trib.rb check 192.168.229.160:6380

(2)或直接使用redis-cli命令
创建群集选择yes进行创建

[root@redis1 redis]# redis-cli --cluster create --cluster-replicas 1 192.168.229.160:6380 192.168.229.161:6380 192.168.229.167:6380 192.168.229.160:6381 192.168.229.161:6381 192.168.229.167:6381

检查完整性:

[root@redis1 redis]# redis-cli --cluster check 192.168.229.160:6380

8.工具扩容节点
Redis集群的扩容操作可分为以下几个步骤

1)准备新节点

2)加入集群

3)迁移槽和数据

扩容流程
我们在redis1上创建2个新节点;

[root@redis1 redis]# mkdir -p /opt/redis_cluster/redis_{6390,6391}/{conf,logs,pid}
[root@redis1 redis]# mkdir -p /data/redis_cluster/redis_{6390,6391}
[root@redis1 redis]# cd /opt/redis_cluster/
[root@redis1 redis_cluster]# cp redis_6380/conf/redis_6380.conf redis_6390/conf/redis_6390.conf
[root@redis1 redis_cluster]# cp redis_6380/conf/redis_6380.conf redis_6391/conf/redis_6391.conf
[root@redis1 redis_cluster]# sed -i 's/6380/6390/g' redis_6390/conf/redis_6390.conf
[root@redis1 redis_cluster]# sed -i 's/6380/6391/g' redis_6391/conf/redis_6391.conf

启动节点

[root@redis1 redis]# bash redis_shell.sh start 6390
[root@redis1 redis]# bash redis_shell.sh start 6391

发现节点

[root@redis1 redis]# redis-cli -c -h 192.168.229.160 -p 6380 cluster meet 192.168.229.160 6390
OK
[root@redis1 redis]# redis-cli -c -h 192.168.229.160 -p 6380 cluster meet 192.168.229.160 6391
OK

在redis1上使用工具扩容;注意新版本使用步骤(2)
(1)旧版本3…
cd /opt/redis_cluster/redis/src/
./redis-trib.rb add-node 192.168.229.160:6390 192.168.229.160:6380
./redis-trib.rb add-node 192.168.229.160:6391 192.168.229.160:6380
./redis-trib.rb reshard 192.168.229.160:6380

(2)直接使用redis-cli命令完成

[root@redis1 redis]# redis-cli --cluster add-node 192.168.229.160:6390 192.168.229.160:6380
[root@redis1 redis]# redis-cli --cluster add-node 192.168.229.160:6391 192.168.229.160:6380
[root@redis1 redis]# redis-cli --cluster reshard 192.168.229.160:6380

打印出进群每个节点信息后,reshard命令需要确认迁移的槽数量,这里我们输入4096个:
How many slots do you want to move (from 1 to 16384)? 4096
输入6390的节点ID作为目标节点,也就是要扩容的节点,目标节点只能指定一个
What is the receiving node ID? 6390的ID号
之后输入源节点的ID,这里分别输入每个主节点的6380的ID最后输入done,或者直接输入all
Source node #1:all
迁移完成后命令会自动退出,这时候我们查看一下集群的状态

旧版本
[root@redis1 redis]# ./redis-trib.rb rebalance 192.168.229.160:6380

新版本
[root@redis1 redis]# redis-cli --cluster rebalance 192.168.229.160:6380

9.工具收缩节点
流程说明

1)首先需要确定下线节点是否有负责的槽;如果是,需要把槽迁移到其他节点,保证节点下线后整个集群槽节点映射的完整性.

2)当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记该节点后可以正常关闭.

这里我们准备将刚才新添加的节点下线,也就是6390和6391
收缩和扩容迁移的方向相反,6390变为源节点,其他节点变为目标节点,源节点把自己负责的4096个槽均匀的迁移到其他节点上;由于redis-trib.rb reshard命令只能有一个目标节点,因此需要执行3次reshard命令,分别迁移1365,1365,1366个槽。
操作命令

[root@redis1 redis]# cd /opt/redis_cluster/redis/src/
[root@redis1 src]# ./redis-trib.rb reshard 192.168.229.160:6380
How many slots do you want to move (from 1 to 16384)? 1365
输入6380的id
输入6390的id
done

忘记节点
由于我们的集群是做了高可用的,所以当主节点下线的时候从节点也会顶上,所以最好我们先下线从节点,然后在下线主节点;

[root@redis1 redis]# cd /opt/redis_cluster/redis/src/
[root@redis1 src]# ./redis-trib.rb del-node 192.168.229.160:6391 ID
[root@redis1 src]# ./redis-trib.rb del-node 192.168.229.160:6390 ID

10.Redis集群常用命令

集群(cluster)
CLUSTER INFO 打印集群的信息
CLUSTER NODES 列出集群当前已知的所有节点(node),以及这些节点的相关信息。 

节点(node)
CLUSTER MEET <ip> <port> 将ip和port所指定的节点添加到集群当中,让它成为集群的一份子。
CLUSTER FORGET <node_id> 从集群中移除node_id指定的节点。
CLUSTER REPLICATE <node_id> 将当前节点设置为node_id指定的节点的从节点。
CLUSTER SAVECONFIG 将节点的配置文件保存到硬盘里面。

槽(slot)
CLUSTER ADDSLOTS <slot> [slot ...] 将一个或多个槽(slot)指派(assign)给当前节点。
CLUSTER DELSLOTS <slot> [slot ...] 移除一个或多个槽对当前节点的指派。
CLUSTER FLUSHSLOTS 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点。
CLUSTER SETSLOT <slot> NODE <node_id> 将槽slot指派给node_id指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽,然后再进行指派。
CLUSTER SETSLOT <slot> MIGRATING <node_id> 将本节点的槽slot迁移到node_id指定的节点中。
CLUSTER SETSLOT <slot> IMPORTING <node_id> 从node_id指定的节点中导入槽slot到本节点。
CLUSTER SETSLOT <slot> STABLE 取消对槽slot的导入(import)或者迁移(migrate)。

键 (key)
CLUSTER KEYSLOT <key> 计算键key应该被放置在哪个槽上。
CLUSTER COUNTKEYSINSLOT <slot> 返回槽slot目前包含的键值对数量。CLUSTER GETKEYSINSLOT <slot> <count> 返回count个slot槽中的键。

11.Redis运维工具
(1)运维脚本

[root@redis1 ~]# cat redis_shell.sh 
#!/bin/bash

USAG(){
    echo "sh $0 {start|stop|restart|login|ps|tail} PORT"
}
if [ "$#" = 1 ]
then
    REDIS_PORT='6379'
elif 
    [ "$#" = 2 -a -z "$(echo "$2"|sed 's#[0-9]##g')" ]
then
    REDIS_PORT="$2"
else
    USAG
    exit 0
fi

REDIS_IP=$(hostname -I|awk '{print $1}')
PATH_DIR=/opt/redis_cluster/redis_${REDIS_PORT}/
PATH_CONF=/opt/redis_cluster/redis_${REDIS_PORT}/conf/redis_${REDIS_PORT}.conf
PATH_LOG=/opt/redis_cluster/redis_${REDIS_PORT}/logs/redis_${REDIS_PORT}.log

CMD_START(){
    redis-server ${PATH_CONF}
}

CMD_SHUTDOWN(){
    redis-cli -c -h ${REDIS_IP} -p ${REDIS_PORT} shutdown
}

CMD_LOGIN(){
    redis-cli -c -h ${REDIS_IP} -p ${REDIS_PORT}
}

CMD_PS(){
    ps -ef|grep redis
}

CMD_TAIL(){
    tail -f ${PATH_LOG}
}

case $1 in
    start)
        CMD_START
        CMD_PS
        ;;
    stop)
        CMD_SHUTDOWN
        CMD_PS
        ;;
    restart)
        CMD_START
        CMD_SHUTDOWN
        CMD_PS
        ;;
    login)
        CMD_LOGIN
        ;;
    ps)
        CMD_PS
        ;;
    tail)
        CMD_TAIL
        ;;
    *)
        USAG
esac

(2)数据导入导出工具
需求背景
刚切换到redis集群的时候肯定会面临数据导入的问题,所以这里推荐使用redis-migrate-tool工具来导入单节点数据到集群里;
 安装工具

[root@redis1 ~]# cd /opt/redis_cluster/
[root@redis1 redis_cluster]# yum -y install git
[root@redis1 redis_cluster]# git clone https://github.com/vipshop/redis-migrate-tool.git
[root@redis1 redis_cluster]# cd redis-migrate-tool/
[root@redis1 redis-migrate-tool]# yum -y install install autoconf automake libtool
[root@redis1 redis-migrate-tool]# autoreconf -fvi
[root@redis1 redis-migrate-tool]# ./configure
[root@redis1 redis-migrate-tool]# make && make install

创建配置文件

[root@redis1 redis-migrate-tool]# vim redis_6380_to_6381.conf    
[source]
type: single
servers:
- 192.168.229.160:6380
 
[target]
type: redis cluster
servers:
- 192.168.229.160:6381 
 
[common]
listen: 0.0.0.0:8888
source_safe: true

生成测试数据

[root@redis1 ~]# cat input_key.sh 
#!/bin/bash
for i in $(seq 1 1000)
do
    redis-cli -c -h 192.168.229.160 -p 6380 set k_${i} v_${i} && echo "set k_${i} is ok"
done

执行导入命令

[root@redis1 ~]# redis-migrate-tool -c redis_6380_to_6381.conf

数据校验

[root@redis1 ~]# redis-migrate-tool -c redis_6380_to_6381.conf -C redis_check

(3)分析键值大小
需求背景
redis的内存使用太大键值太多,不知道哪些键值占用的容量比较大,而且在线分析会影响性能。
安装工具

[root@redis1 ~]# yum install python-pip gcc python-devel 
[root@redis1 ~]# cd /opt/
[root@redis1 opt]# git clone https://github.com/sripathikrishnan/redis-rdb-tools
[root@redis1 opt]# cd redis-rdb-tools
[root@redis1 redis-rdb-tools]# python setup.py install

使用方法

[root@redis1 redis-rdb-tools]# cd /data/redis_cluster/redis_6380/
[root@redis1 redis_6380]# rdb -c memory redis_6380.rdb -f redis_6380.rdb.csv

分析rdb并导出

[root@redis1 redis_6380]# awk -F ',' '{print $4,$2,$3,$1}' redis_6380.rdb.csv |sort > 6380.txt

(4)监控过期键
需求背景
因为开发重复提交,导致电商网站优惠卷过期时间失效
问题分析
如果一个键已经设置了过期时间,这时候在set这个键,过期时间就会取消
解决思路
如何在不影响机器性能的前提下批量获取需要监控键过期时间

1.Keys * 查出来匹配的键名。然后循环读取ttl时间

2.scan * 范围查询键名。然后循环读取ttl时间

Keys 重操作,会影响服务器性能,除非是不提供服务的从节点
Scan 负担小,但是需要去多次才能取完,需要写脚本

脚本内容:

[root@redis1 redis_6380]# vim 01get_key.sh 
#!/bin/bash
key_num=0
> key_name.log
for line in $(cat key_list.txt)
do
    while true
    do
        scan_num=$(redis-cli -h 192.168.229.160 -p 6380 SCAN ${key_num} match ${line}\* count 1000|awk 'NR==1{print $0}')
        key_name=$(redis-cli -h 192.168.229.160 -p 6380 SCAN ${key_num} match ${line}\* count 1000|awk 'NR>1{print $0}')
        echo ${key_name}|xargs -n 1 >> key_name.log
        ((key_num=scan_num))
        if [ ${key_num} == 0 ]
           then
           break
        fi
    done
done