Redis是一种基于键值对(key-value)的NoSQL数据库。
与很多键值对数据库不同的是,Redis中的值可以是由string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)等多种数据结构和算法组成,因此Redis可以满足很多的应用场景。
而且因为Redis会将所有数据都存放在内存中,所以它的读写性能非常惊人。
不仅如此,Redis还可以将内存的数据利用快照(RDB)和日志(AOF)的形式保存到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据不会“丢失”。
Redis的所有数据都是存放在内存中的,这是Redis速度快的最主要原因。
Redis是用C语言实现的,一般来说C语言实现的程序“距离”操作系统更近,执行速度相对会更快。
Redis使用了单线程架构,预防了多线程可能产生的竞争问题。
与很多键值对数据库不同的是,Redis中的值不仅可以是字符串,而且还可以是具体的数据结构,它主要提供了5种数据结构:字符串、哈希、列表、集合、有序集合。这样不仅能便于在许多应用场景的开发,同时也能够提高开发效率。
首先,Redis的源码很少。
其次,Redis使用单线程模型,这样不仅使得Redis服务端处理模型变得简单,而且也使得客户端开发变得简单。
最后,Redis不需要依赖于操作系统中的类库(例如Memcache需要依赖libevent这样的系统类库),Redis自己实现了事件处理的相关功能。
通常看,将数据放在内存中是不安全的,一旦发生断电或者机器故障,重要的数据可能就会丢失,因此Redis提供了两持久化方式:RDB和AOF,即可以用两种策略将内存的数据保存到硬盘中,这样就保证了数据的可持久性。
Redis提供了复制功能,实现了多个相同数据的Redis副本。
Redis从2.8版本正式提供了高可用实现Redis Sentinel(哨兵模式),它能够保证Redis节点的故障发现和故障自动转移。
Redis从3.0版本正式提供了分布式实现Redis Cluster(集群模式),它是Redis真正的分布式实现,提供了高可用、读写和容量的扩展性。
(1)缓存。合理地使用缓存不仅可以加快数据的访问速度,而且能够有效地降低后端数据源的压力。
(2)排行榜系统。Redis提供了列表和有序集合数据结构,合理地使用这些数据结构可以很方便地构建各种排行榜系统。
(3)计数器应用。Redis天然支持计数功能而且计数的性能也非常好。
(4)社交网络。赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能,由于社交网站访问量通常比较大,而且传统的关系型数据不太适合保存这种类型的数据,Redis提供的数据结构可以相对比较容易地实现这些功能。
(5)消息队列系统。消息队列系统可以说是一个大型网站的必备基础组件,因为其具有业务解耦、非实时业务削峰等特性。Redis提供了发布订阅功能和阻塞队列的功能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功能基本可以满足。
cd /opt/redis
导入redis-6.2.6.tar.gz
解压tar -zxvf redis-6.2.6.tar.gz
cd redis-6.2.6
make
make install PREFIX=/home/redistest/redis
cd /home/redistest/redis/bin
如果出现下面的结果则证明已安装成功
[root@localhost bin] ll
total 8424
-rwxr-xr-x. 1 root root 963456 Mar 1 10:00 redis-benchmark
lrwxrwxrwx. 1 root root 12 Mar 1 10:00 redis-check-aof -> redis-server
lrwxrwxrwx. 1 root root 12 Mar 1 10:00 redis-check-rdb -> redis-server
-rwxr-xr-x. 1 root root 1202888 Mar 1 10:00 redis-cli
lrwxrwxrwx. 1 root root 12 Mar 1 10:00 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 6452272 Mar 1 10:00 redis-server
mkdir -p /home/redistest/redis/run
mkdir -p /home/redistest/redis/log
mkdir -p /home/redistest/redis/data/7000
mkdir -p /home/redistest/redis/conf
cd /opt/redis/redis-6.2.6
cp redis.conf /home/redistest/redis/conf
cd /home/redistest/redis/conf
vi redis.conf
修改内容如下:
bind 127.0.0.1 注释掉该配置,表示允许任何ip地址远程访问
protected-mode no 关闭保护模式(否则远程无法连接)
port 7000 端口
daemonize yes 后台运行
pidfile /home/redistest/redis/run/redis_7000.pid pid存储目录
logfile /home/redistest/redis/log/redis_7000.log 日志存储目录
dir /home/redistest/redis/data/7000 数据存储目录,目录要提前创建好
appendonly no 持久化关闭
cluster-enabled yes 开启集群
cluster-config-file nodes-7000.conf 集群节点配置文件,这个文件是不能手动编辑的。确保每一个集群节点的配置文件不通
cluster-node-timeout 15000 集群节点的超时时间,单位:ms,超时后集群会认为该节点失败
masterauth yourpassword 设置密码(主要是针对master对应的slave节点设置的,在slave节点数据同步的时候用到。)
requirepass yourpassword 设置密码(对登录权限做限制,redis每个节点的requirepass可以是独立、不同的)
cluster-require-full-coverage no redis cluster需要16384个slot都正常的时候才能对外提供服务,换句话说,只要任何一个slot异常那么整个cluster不对外提供服务。 因此生产环境一般为no
cd /home/redistest/redis/bin
首先需要启动服务端
./redis-server ../conf/redis.conf
查看redis进程是否存在
ps -ef|grep redis
启动客户端
./redis-cli
[redistest@redistest-redis-02 redis]$ ll
总用量 0
drwxrwxr-x. 2 redistest redistest 185 2月 17 10:08 bin
drwxrwxr-x. 2 redistest redistest 52 2月 17 10:08 conf
drwxrwxr-x. 4 redistest redistest 30 2月 17 10:08 data
drwxrwxr-x. 2 redistest redistest 50 2月 17 10:09 log
drwxrwxr-x. 2 redistest redistest 50 2月 17 10:09 run
要求:/home/redistest/redis/data/下的 dump.rdb 和XXX.conf 文件需要删除,否则启动时会报错。
/home/redistest/redis/bin/ 下的启动关闭脚本,注意依据实际情况修改
[redistest@redistest-redis-03 bin]$ chmod 755 *
[redistest@redistest-redis-03 bin]$ ll
总用量 46796
-rwxr-xr-x. 1 redistest redistest 39 2月 17 09:55 cluster_shutdown.sh
-rwxr-xr-x. 1 redistest redistest 78 2月 17 09:55 cluster_start.sh
-rwxr-xr-x. 1 redistest redistest 4828320 2月 17 09:55 redis-benchmark
-rwxr-xr-x. 1 redistest redistest 9517728 2月 17 09:55 redis-check-aof
-rwxr-xr-x. 1 redistest redistest 9517728 2月 17 09:55 redis-check-rdb
-rwxr-xr-x. 1 redistest redistest 5002600 2月 17 09:55 redis-cli
-rwxr-xr-x. 1 redistest redistest 9517728 2月 17 09:55 redis-sentinel
-rwxr-xr-x. 1 redistest redistest 9517728 2月 17 09:55 redis-server
cd /home/redistest/redis/bin
首先需要启动服务端
./redis-server redis.conf
查看redis进程是否存在
ps -ef|grep redis
启动客户端
./redis-cli
Redis集群需要6台服务器进行配置,每天服务器配置的redis端口密码要求一致。
由于我们的机器有限,我们将采用一台机器多个端口的方式搭建我们的Redis集群。
若有6台服务器可以每台服务器的端口一致。
测试服务器只有3台,则每台服务器准备两个配置文件,分别使用不同的配置端口。
vi /home/redistest/redis/conf/redis_7000.conf
bind 127.0.0.1 注释掉该配置,表示允许任何ip地址远程访问
protected-mode no 关闭保护模式(否则远程无法连接)
port 7000 端口
daemonize yes 后台运行
pidfile /home/redistest/redis/run/redis_7000.pid pid存储目录
logfile /home/redistest/redis/log/redis_7000.log 日志存储目录
dir /home/redistest/redis/data/7000 数据存储目录,目录要提前创建好
appendonly no 持久化关闭
cluster-enabled yes 开启集群
cluster-config-file nodes-7000.conf 集群节点配置文件,这个文件是不能手动编辑的。确保每一个集群节点的配置文件不通
cluster-node-timeout 15000 集群节点的超时时间,单位:ms,超时后集群会认为该节点失败
masterauth yourpassword 设置密码(主要是针对master对应的slave节点设置的,在slave节点数据同步的时候用到。)
requirepass yourpassword 设置密码(对登录权限做限制,redis每个节点的requirepass可以是独立、不同的)
cluster-require-full-coverage no redis cluster需要16384个slot都正常的时候才能对外提供服务,换句话说,只要任何一个slot异常那么整个cluster不对外提供服务。 因此生产环境一般为no
由于我们的机器有限,我们将采用一台机器多个端口的方式搭建我们的Redis集群。
若有6台服务器可以每台服务器的端口一致。
复制配置文件:
cp /home/redistest/redis/conf/redis_7000.conf /home/redistest/redis/conf/redis_7001.conf
mkdir -p /home/redistest/redis/data/7001
vi /home/redistest/redis/conf/redis_7001.conf
打开redis_7001.conf文件,修改以下内容:
复制代码
bind 192.168.65.96
port 7001
pidfile /home/redistest/redis/run/redis_7001.pid
logfile /home/redistest/redis/log/redis_7001.log
dir /home/redistest/redis/data/7001 目录要提前创建好
cluster-config-file nodes-7001.conf
如果要复制多个端口,按上面的步骤重复操作,修改端口号即可。
cd /home/redistest/redis/bin
启动脚本:
vi cluster_start.sh
./redis-server ../conf/redis_7000.conf
./redis-server ../conf/redis_7001.conf
chmod +x cluster_start.sh
关闭脚本:
vi cluster_shutdown.sh
pgrep redis-server | xargs -exec kill -9
chmod +x cluster_shutdown.sh
启动&关闭Redis:
启动redis:
./cluster_start.sh
ps -ef|grep redis
root 26731 1 0 01:23 ? 00:00:00 redis-server 192.168.65.96:7000 [cluster]
root 26736 1 0 01:23 ? 00:00:00 redis-server 192.168.65.96:7001 [cluster]
root 26741 8648 0 01:23 pts/0 00:00:00 grep --color=auto redis
关闭redis:
./cluster_shutdown.sh
建立集群前需先启动各个节点的redis服务,并在其中一个redis服务器中执行以下指令建立集群。
创建集群
在redis3.0和4.0版本中,创建集群还是使用redis-trib.rb方式去创建,但是在5.0之后,可以直接使用redis-cli直接创建集群,本文redis版本为6.0,所以创建集群的方式为redis-cli方式直接创建。
用以下命令创建集群,–cluster-replicas 1 参数表示希望每个主服务器都有一个从服务器,这里则代表3主3从,前3个代表3个master,后3个代表3个slave。
通过该方式创建的带有从节点的机器不能够自己手动指定主节点,redis集群会尽量把主从服务器分配在不同机器上。
cd /home/redistest/redis/bin
./redis-cli --cluster create 192.168.65.96:7000 192.168.65.96:7001 192.168.65.97:7000 192.168.65.97:7001 192.168.65.98:7000 192.168.65.98:7001 --cluster-replicas 1
若有密码则需要
./redis-cli --cluster create 192.168.65.96:7000 192.168.65.96:7001 192.168.65.97:7000 192.168.65.97:7001 192.168.65.98:7000 192.168.65.98:7001 --cluster-replicas 1 -a passwd123
[redistest@redistest-redis-01 bin]$ ./redis-cli --cluster create 192.168.65.96:7000 192.168.65.96:7001 192.168.65.97:7000 192.168.65.97:7001 192.168.65.98:7000 192.168.65.98:7001 --cluster-replicas 1 -a passwd123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.65.97:7001 to 192.168.65.96:7000
Adding replica 192.168.65.98:7001 to 192.168.65.97:7000
Adding replica 192.168.65.96:7001 to 192.168.65.98:7000
M: 8abfa5e869df89e7942a1cd6cb8ca6cf6ab3b2bb 192.168.65.96:7000
slots:[0-5460] (5461 slots) master
S: 16bb724f2fe8d14d1a224926a68b680f9002f77a 192.168.65.96:7001
replicates e4408f2ca5ee8a41ab4aeb7b7608fad038a0edcd
M: 60a75223616b38b3625b3a1e231b47bbf3a5862f 192.168.65.97:7000
slots:[5461-10922] (5462 slots) master
S: 41ad1bee3effb6389b777d9c51cd8269180c7782 192.168.65.97:7001
replicates 8abfa5e869df89e7942a1cd6cb8ca6cf6ab3b2bb
M: e4408f2ca5ee8a41ab4aeb7b7608fad038a0edcd 192.168.65.98:7000
slots:[10923-16383] (5461 slots) master
S: 222037918bf27363743161573b521c41ff3a02b8 192.168.65.98:7001
replicates 60a75223616b38b3625b3a1e231b47bbf3a5862f
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
>>> Performing Cluster Check (using node 192.168.65.96:7000)
M: 8abfa5e869df89e7942a1cd6cb8ca6cf6ab3b2bb 192.168.65.96:7000
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 60a75223616b38b3625b3a1e231b47bbf3a5862f 192.168.65.97:7000
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
M: e4408f2ca5ee8a41ab4aeb7b7608fad038a0edcd 192.168.65.98:7000
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 41ad1bee3effb6389b777d9c51cd8269180c7782 192.168.65.97:7001
slots: (0 slots) slave
replicates 8abfa5e869df89e7942a1cd6cb8ca6cf6ab3b2bb
S: 222037918bf27363743161573b521c41ff3a02b8 192.168.65.98:7001
slots: (0 slots) slave
replicates 60a75223616b38b3625b3a1e231b47bbf3a5862f
S: 16bb724f2fe8d14d1a224926a68b680f9002f77a 192.168.65.96:7001
slots: (0 slots) slave
replicates e4408f2ca5ee8a41ab4aeb7b7608fad038a0edcd
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
备注:注意看M和S,对照下集群角色表
[redistest@redistest-redis-01 bin]$ ./redis-cli -c -h 192.168.65.96 -p 7000
192.168.65.96:7000> auth passwd123
OK
192.168.65.96:7000> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:1676
cluster_stats_messages_pong_sent:1755
cluster_stats_messages_sent:3431
cluster_stats_messages_ping_received:1750
cluster_stats_messages_pong_received:1676
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:3431
[redistest@redistest-redis-01 bin]$ ./redis-cli -c -h 192.168.65.96 -p 7000
192.168.65.96:7000> auth passwd123
OK
192.168.65.96:7000> cluster nodes
60a75223616b38b3625b3a1e231b47bbf3a5862f 192.168.65.97:7000@17000 master - 0 1645065658944 3 connected 5461-10922
8abfa5e869df89e7942a1cd6cb8ca6cf6ab3b2bb 192.168.65.96:7000@17000 myself,master - 0 1645065653000 1 connected 0-5460
e4408f2ca5ee8a41ab4aeb7b7608fad038a0edcd 192.168.65.98:7000@17000 master - 0 1645065656936 5 connected 10923-16383
41ad1bee3effb6389b777d9c51cd8269180c7782 192.168.65.97:7001@17001 slave 8abfa5e869df89e7942a1cd6cb8ca6cf6ab3b2bb 0 1645065656000 1 connected
222037918bf27363743161573b521c41ff3a02b8 192.168.65.98:7001@17001 slave 60a75223616b38b3625b3a1e231b47bbf3a5862f 0 1645065657940 3 connected
16bb724f2fe8d14d1a224926a68b680f9002f77a 192.168.65.96:7001@17001 slave e4408f2ca5ee8a41ab4aeb7b7608fad038a0edcd 0 1645065657000 5 connected
集群测试时密码在命令中输入,否则每次更换节点都要校验密码
[redistest@redistest-redis-01 bin]$ ./redis-cli -c -h 192.168.65.96 -p 7000 -a passwd123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.65.96:7000>
192.168.65.96:7000> set name nodel
-> Redirected to slot [5798] located at 192.168.65.97:7000
OK
192.168.65.97:7000> get name
"nodel"
cd /home/redistest/redis/bin
./cluster_shutdown.sh
java使用redis时,导入jedis-3.7.1.jar
当使用jedis连接池时,导入commons-pool2-2.11.1.jar
https://search.maven.org/artifact/redis.clients/jedis/3.7.1/jar
http://commons.apache.org/proper/commons-pool/download_pool.cgi
下载commons-pool2-2.11.1-bin.tar.gz,打开取出commons-pool2-2.11.1.jar,
将jedis-3.7.1.jar、commons-pool2-2.11.1.jar导入项目
右击“项目”→选择Properties,在弹出的对话框左侧列表中选择Java Build Path,选择Libraries:选择Add External
public class Demo01 {
public static void main(String[] args) {
//连接本地的 Redis 服务
Jedis jedis = new Jedis("127.0.0.1",6379);
//查看服务是否运行,打出pong表示OK
System.out.println("connection is OK==========>: "+jedis.ping());
}
}
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import java.util.HashSet;
import java.util.Set;
public class HelloJedisCluster {
public static void main(String[] args) {
Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
//Jedis Cluster will attempt to discover cluster nodes automatically
jedisClusterNodes.add(new HostAndPort("192.168.159.90", 6279));
jedisClusterNodes.add(new HostAndPort("192.168.159.91", 6279));
jedisClusterNodes.add(new HostAndPort("192.168.159.92", 6279));
jedisClusterNodes.add(new HostAndPort("192.168.159.90", 6280));
jedisClusterNodes.add(new HostAndPort("192.168.159.91", 6280));
jedisClusterNodes.add(new HostAndPort("192.168.159.92", 6280));
JedisCluster jc = new JedisCluster(jedisClusterNodes);
jc.set("foo", "bar");
String value = jc.get("foo");
System.out.println(value);
}
}
ip1=192.168.65.96
port1=7000
ip2=192.168.65.96
port2=7001
ip3=192.168.65.97
port3=7000
ip4=192.168.65.97
port4=7001
ip5=192.168.65.98
port5=7000
ip6=192.168.65.98
port6=7001
connectionTImeout=5000
soTimeout=3000
maxAttempts=10
passwd=passwd123
package test;
import java.util.HashSet;
import java.util.ResourceBundle;
import java.util.Set;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
/**
* @author test
*/
public class Cluster {
private static JedisCluster jediscluster;
/**
* connectionTImeout :连接超时 soTimeout 读写超时 maxAttempts:重试次数 password:集群密码
*/
int connectionTimeout;
int soTimeout;
int maxAttempts;
String passwd;
public JedisCluster getJedisCluster() {
// 添加集群的服务节点Set集合 nodes 所有redis cluster节点信息
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
// 添加集群节点
try {
// 从类路径下加载文件redisClusterConfig.properties
ResourceBundle resource = ResourceBundle.getBundle("redisClusterConfig");
String ip1 = resource.getString("ip1");
int port1 = Integer.parseInt(resource.getString("port1"));
String ip2 = resource.getString("ip2");
int port2 = Integer.parseInt(resource.getString("port2"));
String ip3 = resource.getString("ip3");
int port3 = Integer.parseInt(resource.getString("port3"));
connectionTimeout = Integer.parseInt(resource.getString("connectionTImeout"));
soTimeout = Integer.parseInt(resource.getString("soTimeout"));
maxAttempts = Integer.parseInt(resource.getString("maxAttempts"));
passwd = resource.getString("passwd");
System.out.println(ip1 + ":" + port1);
System.out.println(ip2 + ":" + port2);
System.out.println(ip3 + ":" + port3);
nodes.add(new HostAndPort(ip1, port1));
nodes.add(new HostAndPort(ip2, port2));
nodes.add(new HostAndPort(ip3, port3));
} catch (Exception e) {
System.out.println("无法读取系统配置文件redisClusterConfig.properties,可能该文件不存在");
}
// jedis连接池配置
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大连接数,默认8个
jedisPoolConfig.setMaxTotal(8);
// 最大空闲连接数,默认8个
jedisPoolConfig.setMaxIdle(8);
// 最小空闲连接数,默认0
jedisPoolConfig.setMinIdle(0);
// 获取连接时的最大等待毫秒数
jedisPoolConfig.setMaxWaitMillis(2);
// 对拿到的connection 进行validateObject 校验
jedisPoolConfig.setTestOnBorrow(false);
// nodes 所有redis节点信息
//jediscluster= new JedisCluster(nodes,jedisPoolConfig); --没有密码
jediscluster = new JedisCluster(nodes, connectionTimeout, soTimeout, maxAttempts, passwd, jedisPoolConfig);
// 返回集群连接
return jediscluster;
}
}
package test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import redis.clients.jedis.JedisCluster;
public class test2 {
public static void main(String[] args) {
Cluster cl = new Cluster();
JedisCluster js = cl.getJedisCluster();
int expireSecond = 11;
System.out.println(js.get("k6"));
js.getSet("ddsfs", "sdfsdfs");
//js.expireAt("ddsfs", 5);
System.out.println(js.get("ddsfs"));
}
}
package com.redis;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.SortingParams;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 单机环境Redis操作:一台Redis服务器
*/
public class Standalone {
private static Jedis jedis;
static {
jedis = new Jedis("192.168.56.180", 6379);
jedis.auth("123456"); // 之前我在redis配置中配置了权限密码,这里需要设置
}
/**
* 测试key - value 的数据
*
* @throws InterruptedException
*/
@Test
public void testKey() throws InterruptedException {
System.out.println("判断某个键是否存在:" + jedis.exists("username"));
System.out.println("新增<'username','wukong'>的键值对:" + jedis.set("username", "wukong"));
System.out.println("设置键username的过期时间为5s:" + jedis.expire("username", 5));
TimeUnit.SECONDS.sleep(2);
System.out.println("查看键username的剩余生存时间:" + jedis.ttl("username"));
TimeUnit.SECONDS.sleep(2);
System.out.println("查看键username的剩余生存时间:" + jedis.ttl("username"));
TimeUnit.SECONDS.sleep(2);
System.out.println("判断某个键是否存在:" + jedis.exists("username"));
}
/***
* 字符串操作
* memcached和redis同样有append的操作,但是memcached有prepend的操作,redis中并没有。
* @throws InterruptedException
*/
@Test
public void testString() throws InterruptedException {
System.out.println("===========增加数据===========");
System.out.println(jedis.set("key1", "value1"));
System.out.println(jedis.set("key2", "value2"));
System.out.println(jedis.set("key3", "value3"));
System.out.println("删除键key2:" + jedis.del("key2"));
System.out.println("获取键key2:" + jedis.get("key2"));
System.out.println("修改key1:" + jedis.set("key1", "value1Changed"));
System.out.println("获取key1的值:" + jedis.get("key1"));
System.out.println("在key3后面加入值:" + jedis.append("key3", "End"));
System.out.println("key3的值:" + jedis.get("key3"));
System.out.println("===========新增键值对防止覆盖原先值==============");
System.out.println(jedis.setnx("key1", "value1"));
System.out.println(jedis.setnx("key2", "value2"));
System.out.println(jedis.setnx("key2", "value2-new"));
System.out.println(jedis.get("key1"));
System.out.println(jedis.get("key2"));
System.out.println("===========新增键值对并设置有效时间=============");
System.out.println(jedis.setex("key3", 2, "value3"));
System.out.println(jedis.get("key3"));
TimeUnit.SECONDS.sleep(3);
System.out.println(jedis.get("key3"));
System.out.println("===========获取原值,更新为新值==========");//GETSET is an atomic set this value and return the old value command.
System.out.println(jedis.getSet("key2", "key2GetSet"));
System.out.println(jedis.get("key2"));
System.out.println("获得key2的值的字串:" + jedis.getrange("key2", 2, 4));
}
/***
* 整数和浮点数
*/
@Test
public void testNumber() {
jedis.set("key1", "1");
jedis.set("key2", "2");
jedis.set("key3", "2.3");
System.out.println("key1的值:" + jedis.get("key1"));
System.out.println("key2的值:" + jedis.get("key2"));
System.out.println("key3的值:" + jedis.get("key3"));
System.out.println("key1的值加1:" + jedis.incr("key1"));
System.out.println("获取key1的值:" + jedis.get("key1"));
System.out.println("key2的值减1:" + jedis.decr("key2"));
System.out.println("获取key2的值:" + jedis.get("key2"));
System.out.println("将key1的值加上整数5:" + jedis.incrBy("key1", 5));
System.out.println("获取key1的值:" + jedis.get("key1"));
System.out.println("将key2的值减去整数5:" + jedis.decrBy("key2", 5));
System.out.println("获取key2的值:" + jedis.get("key2"));
}
/***
* 列表
*/
@Test
public void testList() {
System.out.println("===========添加一个list===========");
jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");
jedis.lpush("collections", "HashSet");
jedis.lpush("collections", "TreeSet");
jedis.lpush("collections", "TreeMap");
System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));//-1代表倒数第一个元素,-2代表倒数第二个元素
System.out.println("collections区间0-3的元素:" + jedis.lrange("collections", 0, 3));
System.out.println("===============================");
// 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈
System.out.println("删除指定元素个数:" + jedis.lrem("collections", 2, "HashMap"));
System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
System.out.println("删除下表0-3区间之外的元素:" + jedis.ltrim("collections", 0, 3));
System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
System.out.println("collections列表出栈(左端):" + jedis.lpop("collections"));
System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
System.out.println("collections添加元素,从列表右端,与lpush相对应:" + jedis.rpush("collections", "EnumMap"));
System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
System.out.println("collections列表出栈(右端):" + jedis.rpop("collections"));
System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
System.out.println("修改collections指定下标1的内容:" + jedis.lset("collections", 1, "LinkedArrayList"));
System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
System.out.println("===============================");
System.out.println("collections的长度:" + jedis.llen("collections"));
System.out.println("获取collections下标为2的元素:" + jedis.lindex("collections", 2));
System.out.println("===============================");
jedis.lpush("sortedList", "3", "6", "2", "0", "7", "4");
System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0, -1));
System.out.println(jedis.sort("sortedList"));
System.out.println("sortedList排序后:" + jedis.lrange("sortedList", 0, -1));
}
/***
* set集合
*/
@Test
public void testSet() {
System.out.println("============向集合中添加元素============");
System.out.println(jedis.sadd("eleSet", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
System.out.println(jedis.sadd("eleSet", "e6"));
System.out.println(jedis.sadd("eleSet", "e6"));
System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
System.out.println("删除一个元素e0:" + jedis.srem("eleSet", "e0"));
System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
System.out.println("删除两个元素e7和e6:" + jedis.srem("eleSet", "e7", "e6"));
System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
System.out.println("随机的移除集合中的一个元素:" + jedis.spop("eleSet"));
System.out.println("随机的移除集合中的一个元素:" + jedis.spop("eleSet"));
System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
System.out.println("eleSet中包含元素的个数:" + jedis.scard("eleSet"));
System.out.println("e3是否在eleSet中:" + jedis.sismember("eleSet", "e3"));
System.out.println("e1是否在eleSet中:" + jedis.sismember("eleSet", "e1"));
System.out.println("e1是否在eleSet中:" + jedis.sismember("eleSet", "e5"));
System.out.println("=================================");
System.out.println(jedis.sadd("eleSet1", "e1", "e2", "e4", "e3", "e0", "e8", "e7", "e5"));
System.out.println(jedis.sadd("eleSet2", "e1", "e2", "e4", "e3", "e0", "e8"));
System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
System.out.println("eleSet3中的元素:" + jedis.smembers("eleSet3"));
System.out.println("============集合运算=================");
System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1"));
System.out.println("eleSet2中的元素:" + jedis.smembers("eleSet2"));
}
/***
* 散列
*/
@Test
public void testHash() {
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");
map.put("key4", "value4");
jedis.hmset("hash", map);
jedis.hset("hash", "key5", "value5");
System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));//return Map<String,String>
System.out.println("散列hash的所有键为:" + jedis.hkeys("hash"));//return Set<String>
System.out.println("散列hash的所有值为:" + jedis.hvals("hash"));//return List<String>
System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:" + jedis.hincrBy("hash", "key6", 6));
System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:" + jedis.hincrBy("hash", "key6", 3));
System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
System.out.println("删除一个或者多个键值对:" + jedis.hdel("hash", "key2"));
System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
System.out.println("散列hash中键值对的个数:" + jedis.hlen("hash"));
System.out.println("判断hash中是否存在key2:" + jedis.hexists("hash", "key2"));
System.out.println("判断hash中是否存在key3:" + jedis.hexists("hash", "key3"));
System.out.println("获取hash中的值:" + jedis.hmget("hash", "key3"));
System.out.println("获取hash中的值:" + jedis.hmget("hash", "key3", "key4"));
}
/**
* 有序集合
*/
@Test
public void testSortedSet() {
Map<String, Double> map = new HashMap<String, Double>();
map.put("key2", 1.2);
map.put("key3", 4.0);
map.put("key4", 5.0);
map.put("key5", 0.2);
System.out.println(jedis.zadd("zset", 3, "key1"));
System.out.println(jedis.zadd("zset", map));
System.out.println("zset中的所有元素:" + jedis.zrange("zset", 0, -1));
System.out.println("zset中的所有元素:" + jedis.zrangeWithScores("zset", 0, -1));
System.out.println("zset中的所有元素:" + jedis.zrangeByScore("zset", 0, 100));
System.out.println("zset中的所有元素:" + jedis.zrangeByScoreWithScores("zset", 0, 100));
System.out.println("zset中key2的分值:" + jedis.zscore("zset", "key2"));
System.out.println("zset中key2的排名:" + jedis.zrank("zset", "key2"));
System.out.println("删除zset中的元素key3:" + jedis.zrem("zset", "key3"));
System.out.println("zset中的所有元素:" + jedis.zrange("zset", 0, -1));
System.out.println("zset中元素的个数:" + jedis.zcard("zset"));
System.out.println("zset中分值在1-4之间的元素的个数:" + jedis.zcount("zset", 1, 4));
System.out.println("key2的分值加上5:" + jedis.zincrby("zset", 5, "key2"));
System.out.println("key3的分值加上4:" + jedis.zincrby("zset", 4, "key3"));
System.out.println("zset中的所有元素:" + jedis.zrange("zset", 0, -1));
}
/**
* 排序
*/
@Test
public void testSort() {
jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");
System.out.println("collections的内容:" + jedis.lrange("collections", 0, -1));
SortingParams sortingParameters = new SortingParams();
System.out.println(jedis.sort("collections", sortingParameters.alpha()));
System.out.println("===============================");
jedis.lpush("sortedList", "3", "6", "2", "0", "7", "4");
System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0, -1));
System.out.println("升序:" + jedis.sort("sortedList", sortingParameters.asc()));
System.out.println("升序:" + jedis.sort("sortedList", sortingParameters.desc()));
}
}
[root@localhost src] redis-cli --cluster create --cluster-replicas 1 192.168.14.172:7001 192.168.14.172:7002 192.168.14.172:7003 192.168.14.172:7004 192.168.14.172:7005 192.168.14.172:7006
Could not connect to Redis at 192.168.14.172:7001: Connection refused
是因为这两个配置没有改。
bind 127.0.0.1 注释掉该配置,表示允许任何ip地址远程访问
protected-mode no 关闭保护模式(否则远程无法连接)
[root@localhost src] redis-cli --cluster create --cluster-replicas 1 192.168.14.172:7001 192.168.14.172:7002 192.168.14.172:7003 192.168.14.172:7004 192.168.14.172:7005 192.168.14.172:7006
Could not connect to Redis at 192.168.14.172:7001: Connection refused
[root@localhost src] redis-cli --cluster create --cluster-replicas 1 192.168.14.172:7001 192.168.14.172:7002 192.168.14.172:7003 192.168.14.172:7004 192.168.14.172:7005 192.168.14.172:7006
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 192.168.14.172:7005 to 192.168.14.172:7001
Adding replica 192.168.14.172:7006 to 192.168.14.172:7002
Adding replica 192.168.14.172:7004 to 192.168.14.172:7003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 5147817d673d78118bae6cc8c152aeb554fdc3f0 192.168.14.172:7001
slots:[0-5460] (5461 slots) master
M: 5147817d673d78118bae6cc8c152aeb554fdc3f0 192.168.14.172:7002
slots:[5461-10922] (5462 slots) master
M: 5147817d673d78118bae6cc8c152aeb554fdc3f0 192.168.14.172:7003
slots:[10923-16383] (5461 slots) master
S: 5147817d673d78118bae6cc8c152aeb554fdc3f0 192.168.14.172:7004
replicates 5147817d673d78118bae6cc8c152aeb554fdc3f0
S: 5147817d673d78118bae6cc8c152aeb554fdc3f0 192.168.14.172:7005
replicates 5147817d673d78118bae6cc8c152aeb554fdc3f0
S: 5147817d673d78118bae6cc8c152aeb554fdc3f0 192.168.14.172:7006
replicates 5147817d673d78118bae6cc8c152aeb554fdc3f0
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
....................................................
一直提示Waiting for the cluster to join…
进入各个节点的redis的rdb文件保存位置
删除rdb持久化文件和nodes.conf文件
[redistest@redistest-redis-01 bin]$ ./redis-cli --cluster create 192.168.65.96:7000 192.168.65.96:7001 192.168.65.97:7000 192.168.65.97:7001 192.168.65.98:7000 192.168.65.98:7001 --cluster-replicas 1
[ERR] Node 192.168.65.96:7000 NOAUTH Authentication required.
解决:在命令后面加上 -a passwd123
./redis-cli --cluster create 192.168.65.96:7000 192.168.65.96:7001 192.168.65.97:7000 192.168.65.97:7001 192.168.65.98:7000 192.168.65.98:7001 --cluster-replicas 1 -a passwd123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
有warning提示,没有关系,是因为密码明文。
Jedis在调用Redis时,如果出现了读写超时后,会出现下面的异常:
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: Read timed out
造成该异常的原因也有以下几种:
1、读写超时设置的过短。
2、命令本身就比较慢。
3、客户端与服务端网络不正常。
4、Redis自身发生阻塞。
Jedis在调用Redis时,如果出现了读写超时后,会出现下面的异常:
redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
造成该异常的原因也有以下几种:
1、连接超时设置的过短。
2、Redis发生阻塞,造成tcp-backlog已满,造成新的连接失败。
3、客户端与服务端网络不正常。
Jedis在调用Redis时,如果出现客户端数据流异常,会出现下面的异常。
redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
造成这个异常原因可能有如下几种:
1.输出缓冲区满。例如将普通客户端的输出缓冲区设置为1M 1M 60:
config set client-output-buffer-limit "normal 1048576 1048576 60 slave 268435456 67108864 60 pubsub 33554432 8388608 60"
如果使用get命令获取一个bigkey(例如3M),就会出现这个异常。
2.长时间闲置连接被服务端主动断开,可以查询timeout配置的设置以及自身连接池配置是否需要做空闲检测。
3.不正常并发读写:Jedis对象同时被多个线程并发操作,可能会出现上述异常。
这种情况一般是因为启动 redis-cli 时没有设置集群模式所导致。
启动时使用 -c 参数来启动集群模式,命令如下:
./redis-cli -h 192.24.54.1 -p 6379 -a '123456' -c
192.24.54.1:6379> get name
-> Redirected to slot [5798] located at 192.24.54.2:6379
"ceshi"
这个是因为redis客户端开启了密码,需要进行认证才能进入
Jedis调用Redis执行写操作时,如果Redis的使用内存大于maxmemory的设置,会收到下面的异常,此时应该调整maxmemory并找到造成内存增长的原因(maxmemory之前章节已经介绍了)
redis.clients.jedis.exceptions.JedisDataException: OOM command not allowed when used memory > 'maxmemory'.
如果客户端连接数超过了maxclients,新申请的连接就会出现如下异常:
redis.clients.jedis.exceptions.JedisDataException: ERR max number of clients reached
此时新的客户端连接执行任何命令,返回结果都是如下:
127.0.0.1:6379> get hello
(error) ERR max number of clients reached
这个问题可能会比较棘手,因为此时无法执行Redis命令,一般来说可以从两个方面进行着手。
1.客户端:如果maxclients参数不是很小的话,应用方的客户端连接数基本不会超过maxclients,通常来看是由于应用方对于Redis客户端使用不当造成的。此时如果应用方是分布式结构的话,可以通过下线部分应用节点(例如占用连接较多的节点),使得Redis的连接数先降下来。从而让绝大部分节点可以正常运行,此时在再通过查找程序bug或者调整maxclients进行问题的修复。
2.服务端:如果此时客户端无法处理,而当前Redis为高可用模式(例如Redis Sentinel和Redis Cluster),可以考虑将当前Redis做故障转移。
此问题不存在确定的解决方式,但是无论从哪个方面进行处理,故障的快速恢复极为重要,当然更为重要的是找到问题的所在,否则一段时间后客户端连接数依然会超过maxclients。
Redis在业务系统中作为缓存角色起着重要作用,如果结构规划不合理、命令使用不规范,除了增大运维难度,还会对业务系统带来性能和可用性问题。为避免出现此类问题,特制定本使用规范,开发、运维、测试及其他相关团队在使用Redis过程中均需要按照规范化、流程化的使用规则进行操作。
本规范按照使用方作为维度分为开发侧与运维侧,并且以【强制】、【建议】、【参考】3个级别进行标注。其中【强制】级别需严格遵守,否则危害极大;【建议】级别建议遵守,可提升性能、便于运维;【参考】级别根据业务特点参考实施。
高危命令包括keys、flushdb、flushall。由于Redis对读写请求都为单线程工作,如果执行命令涉及大量操作或耗时较长,都会阻塞主线程导致其它请求无法正常进行。
Redis作为内存数据库,要考虑数据丢失风险,程序在发生Redis数据丢失时要可进行重加载。
对于数据的聚合操作应由客户端完成,尽量避免让Redis进行SUNIONSTORE、SDIFFSTORE、SINTERSTORE等聚合命令,这些操作会消耗大量CPU资源,导致主线程繁忙。如果不可避免要执行这类命令,要保证N尽量小(N<300),有遍历需求可使用 hscan、sscan、zscan作为替代。
随着业务发展,集合类型数据元素会越来越多,使用HGETALL、SINTER、LRANGE、ZRANGE等时间复杂度O(N)的命令都会对底层数据结构进行全量扫描 ,导致阻塞 Redis 主线程。如果业务场景需要获取全量数据建议使用HSCAN、SSCAN等命令分批返回集合数据,或者将大集合按照不同维度拆成多个集合。
Redis为部分命令提供了命令的阻塞版本,如BLPOP、BLMOVE、BLPOPPUSH、BLMPOP,这些命令在列表没有元素的场景下会持续阻塞客户端。
频繁的短连接会导致 Redis耗费大量时间在连接的创建和断开上,并且TCP三次握手和四次挥手也会增加访问延迟。在使用长连接时需要注意设置合理的参数,连接数不能过多,长时间不操作 Redis 时要有释放连接资源的机制。
一个好的key命名应由业务名为前缀、英文半角冒号进行分隔、数据类型作为结尾,如user:uid:1:string。禁止使用单双引号、换行符及其它转义字符。好的命名可加强数据可读性,便于进行问题定位和寻找数据。
在见名知意的前提下尽量简短,建议不超过128字节,避免因为大量key名过长占用额外的内存.
由于Redis 是单线程执行读写指令(即便Redis 6依然如此),如果对bigkey 进行读写操作都会阻塞主线程,影响Redis的性能。string类型建议控制在10K以内,hash、list、set、zset类型元素个数建议不要过万。如果业务需要,建议通过压缩方式减小数据大小。
把Redis仅当作缓存使用,建议根据业务需求尽可能对每一个Key都设置过期时间,避免无效数据堆积占用大量内存。在进行过期时间设置时要注意集中过期问题,将过期时间打散,避免缓存雪崩。
单个实例包含的Key数量建议控制在1千万内,单实例的键个数过大,可能导致过期键回收不及时。
如果考虑业务拆分建议部署多个实例进行隔离。在一个连接上操作多个db时,会因为SELECT切换带来额外消耗。并且Redis Cluster只支持db0,如果后期进行架构调整,会增大迁移成本。
Redis提供了丰富的数据类型,需要根据业务场景选择合适的类型。Redis自身为了节约内存资源,一种数据类型可能对应多种数据结构,如Hash类型在元素数量比较少时采用压缩列表(ziplist)存储,在存储数据较多时会转换为哈希表。建议在不确定复杂数据类型优于 String时,避免使用复杂数据类型。
Redis作为高性能缓存应该尽量多保存业务热点数据,对于低频冷数据可以使用 MySQL、MongoDB 等基于磁盘的存储方式,节约内存成本,提升性能。
通过熔断机制防止缓存服务异常引发数据库甚至整个业务系统连锁反应。
为了减少熔断影响还可以进一步配置请求限流,避免大量并发请求压力传递到数据库层。
对于读写缓存或者只读缓存设计不同的数据一致性验证机制,避免缓存与数据库信息不一致。
可以采用重命名方式将高危命令进行修改,限制客户端直接使用,降低主线程阻塞风险。
对于无法避免的BigKey,采用异步处理,可以在最大程度上避免对主线程的影响。对于FLUSHDB和FLUSHALL操作也需要通过 ASYNC异步执行。
建议按照业务线部署Redis,可以避免业务相互影响以及单实例膨胀问题,并且在发生故障时降低影响面,且有助于快速恢复。
做好服务器基础资源(CPU、内存、带宽、磁盘等)与实例运行时重要指标监控(节点状态、主从同步、内存占用、慢日志、key数量等),触发阈值有报警机制。
根据业务预估数据量为每个实例配置合理的max memory,为系统预留至少30%最大可用内存。避免Redis内存持续膨胀,过大的实例在主从全量同步及RDB备份时都会增加运维难度。
把Redis仅作为缓存使用,对于丢失数据不敏感的业务可以关闭数据持久化功能,避免持久化大量数据时产生的磁盘IO影响Redis性能。
结合业务特点设置过期数据淘汰策略,避免内存中存放大量无效数据。
不同业务线应配置不同的认证密码,提升安全性。
随着monitor命令执行时间增长,数据会不断积压在输出缓冲区,最终导致输出缓冲区溢出,引起服务崩溃。建议仅在定位Redis性能问题时才使用该命令。