Redis是一个开源的,基于内存且支持持久化,高性能且基于key-value存储的Nosql数据库,用作数据库、缓存、消息代理和流媒体引擎。
应用场景:做为k-v数据库,其存储价值不如同类MongoDB。做为消息队列,不如Kafka,本职还是做为高速缓存,其“缓存”的性质远大于其“数据存储“的性质。
默认端口:6379
Redis是基于k-v存储的,它的key类型只能是String类型,包括空字符串也是一个合格的key,而Value值类型则可以是多种多样了。
Key取值原则: 键值不需要太长,太长会消耗内存,且在数据中查找这类键值的计算成本较高;键值不宜过短,过短则可读性较差。
官方最新公布支持的值类型如下,除了我们熟知的五种基本数据类型外,还有一堆陌生的数据类型,大家感兴趣的可以通过官网了解,本文我们重点介绍常用的五种数据结构,String,List,Hash,Set 和 Zset (即Sorted sets)。各个数据结构的具体说明看 “CRUD操作“模块。
Redis集群搭建有三种方式,感觉和MongoDB类似。
1,主从模式(master/slave)
一个master可以有多个slaves,slaves同步master节点的数据。默认配置下,master节点可以进行读和写,slave节点只能进行读操作,写操作被禁止。
缺点:master节点挂了以后,redis就不能对外提供写服务了,因为剩下的slave不能成为master。
2,sentinel哨兵模式
sentinel的中文含义是哨兵、守卫,是一个独立的进程。也就是说既然主从模式中,当master节点挂了以后,slave节点不能主动选举一个master节点出来,那么我就安排一个或多个sentinel来做这件事,当sentinel发现master节点挂了以后,sentinel就会从slave中重新选举一个master。
sentinel模式基本可以满足一般生产的需求,具备高可用性。但是当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片,将数据存储到多个Redis实例中。
3,cluster模式(分片+副本模式)
cluster模式的出现是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机器实现的redis分布式存储,也就是说每台redis节点上存储不同的内容。
每台节点(master)又可以引入多个slave,保证集群的高可用性,所以如果配置两个副本三个分片的话,就需要六个Redis实例。
因为Redis的数据是根据一定规则分配到cluster的不同机器的,当数据量扩增时,可以很方便的新增机器进行扩容。
这种模式适合数据量巨大的缓存要求,当数据量不是很大使用sentinel即可。
Redis-Cluster采用无中心结构,客户端与redis节点直连,不需要中间代理层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
Redis集群有以下几个重要的特征:
(1)、Redis集群的分片特征在于将空间拆分为16384个槽位solt,某一个节点负责其中一些槽位,key对16384取余分配到不同的solt。
(2)、Redis集群提供一定程度的可用性,可以在某个节点宕机(其下没有slave,或者所有slave全部宕机)或者不可达的情况继续处理命令。
(3)、Redis集群不存在中心节点或代理节点,集群的其中一个最重要的设计目标是达到线性可扩展性。
先介绍两个脚本命令:redis-server 和 redis-cli
1,redis-server命令用于启动redis服务
#启动redis
redis-server redis.conf
#查看版本
redis-server -v
2,redis-cli 命令用于连接redis服务端,进入命令行模式
#连接 (-p指定端口,-a指定密码,-c 集群模式)
redis-cli -h 172.28.226.5 -p 6379 -a $password -c
#注意:在Redis6.0之前的版本中,如上面指定的是默认用户default的密码,redis以前的版本也只支持单用户访问,也就是没有用户名这个概念。
redis提供了非常丰富的命令,下面仅介绍常用的命令,其他命令大家可以查看 https://redis.io/commands/ ,搜索各个命令更详细的用法。此外,也可以通过" help command " 查看命令的用法。
1,运维命令
#测试连接是否可用,返回pang则表示连接可用
redis> PING
"PONG"
#查看redis统计信息
redis> info
#列出所有连接
redis> client list
#杀死某个连接
redis> client kill 127.0.0.1:43501
2,基本操作命令
dbsize #显示数据库中所有key的数量
keys * #显示所有的key
exists mykey #判断该键是否存在,存在返回1,不存在返回0
flushdb #清空数据库
del mykey #删除该键值
type key #查看key的值类型
help command #帮助命令,查看命令的用法,如help get
Sting
字符串类型,最简单最常用的值类型。在命令行中,key和value都可以不带引号。
#设值,取值,删值基本操作,name就是我们设置的key值,注意以下所有命令command后紧跟的字符串都是key值。
set name shengr
get name
del name
#对已经存在的key设置过期时间,单位秒
expire name 60
List
列表类型,类比java中数组,ArrayList。
列表中的元素是字符串类型,元素的顺序和插入的顺序一致。列表的头尾增删速度快,中间增删速度慢,正常使用过程中增删元素是常态,列表中元素可以重复出现;列表的索引,从左至右,从0开始;从右至左,从-1开始。
# lpush 元素依次压入列表头部,一次可以压入一个或多个元素
lpush words a b c d
# rpush 元素依次插入到列表末位
rpush words e f g
# lrange 获取下标从start到end的元素,下标从0开始并且包含end处的值。另外,下标可以是负数,表示倒数第几个值,-1即表示最后一位。
lrange words 0 -1
# lpop 从列表首位移除一个值
lpop words
# rpop 从列表末尾移除一个值
rpop words
# 获取List长度
llen words
# 通过下标获取值
lindex words 1
# 通过下标设置
lset words 0 a
Hash
哈希类型,类比java中的HashMap,所以一个key对应的是一个map类型的值(一组k-v对)。
Hash是由field和关联的value组成的map键值对,而field和value都是字符串类型。
# hset 依次设置map中键值对,一次可以设置一个或多个
hset user name shengr age 12 city sz
# hget 获取map中某个field上的值
hget user name
# hgetAll 获取map中的所有field值
hgetall user
#hdel 删除map中的一个或多个指定fileld,不存在的字段忽略
hdel user age city
# hexists 判断字段是否存在,存在返回1,不存在返回0
hexists user age
# hkeys 获取map上的所有field:
hkeys user
# hvals 获取map上的所有value:
hvals user
# hlen 获取map中field个数:
hlen user
Set
集合类型,类比java中的HashSet,成员唯一,无序,元素是字符串类型。
# sadd 往集合中加入元素,重复的元素无法插入
sadd chars a b b c
# smembers 取出所有元素
smembers chars
# srem 从集合中删除元素
srem chars a
# scard 获取集合中元素个数
scard chars
Zset
有序的集合类型,类比java中的TreeSet,成员唯一,有序,元素是字符串类型,每一个元素都关联着一个浮点数分值Score,并按照分值从小到大的顺序排列集合中的元素(分值可以相同,分值也可以为负数)。
# zadd 往集合中加入带分值的元素
zadd letters 1 a 3.1 b 4 c
# zrange 根据下标获取集合中的元素,正序
zrange letters 0 -1
# zrem 从集合中删除元素
zrem letters a
# zcard 返回元素个数
zcard letters
# zscore 返回某个元素的分值
zscore letters b
# zincrby 给某个元素的分值加上一个值,减去一个值使用负数即可
zincrby letters 2 b
Jedis
老牌redis客户端,简单易用,提供了比较全面的Redis命令支持,也是我们下面重点介绍的。Jedis中的方法调用的是底层的Redis API,也即Jedis中的Java方法基本和Redis的API保持着一致,了解Redis的API,也就能更熟练的使用Jedis。官方地址:https://github.com/redis/jedis
注意点:jedis使用阻塞的 I/O,且其方法调用都是同步的,不支持异步。Jedis 客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
Lettuce
英文直译为“生菜”,用于线程安全同步、异步和响应使用的高级Java Redis客户端。支持集群、哨兵、流水线和编解码器。官网地址: https://lettuce.io/
Redisson
在Redis的基础上实现了Java驻内存数据网格功能(In-Memory Data Grid),是一个高级的分布式协调Redis客户端,提供了一系列的分布式的 Java 对象。Jedis、Lettuce客户端更侧重对 Reids 数据库的 CRUD(增删改查)操作,而 Redisson API 侧重于分布式开发。官网地址:https://redisson.org/
Jedis客户端访问模式介绍:
Jedis客户端同时支持单机模式、分片模式、集群模式的访问模式,通过构建 Jedis类对象 实现单机模式下的数据访问,通过构建 ShardedJedis类对象 实现分片模式的数据访问,通过构建 JedisCluster类对象 实现集群模式下的数据访问。其中ShardedJedis是Redis没有集群功能之前客户端实现的一个数据分布式方案,现在我们用JedisCluster替代即可。
通过ip和端口构建Jedis类对象,如果redis配置了密码,需要使用auth方法进行认证。
Jedis jedis = new Jedis("172.28.226.5",6379);
jedis.auth("$pwd");
//1,设置k-v值
jedis.set("name","shengr");
//2,获取k对应v值
String username=jedis.get("name");
System.out.println(username);
//3,设置值的同时指定过期时间,方法为setex,第二参数指定过期时间,单位秒
jedis.setex("flag",30,"1");
//4,删除key
jedis.del("flag");
//5,关闭连接
jedis.close();
//lpush 元素依次插入到列表首位
jedis.lpush("words","a","b");
//rpush 元素依次插入到列表末位
jedis.rpush("words","c","d","e");
//lrange 获取下标从start到end的元素,下标从0开始并且包含end处的值。另外,下标可以是负数,表示倒数第几个值,-1即表示最后一位。
List<String> words = jedis.lrange("words", 0, -1);
System.out.println(words);
//lpop 从列表首位弹出一个值
String word1 = jedis.lpop("words");
//rpop 从列表末位弹出一个值
String word2 = jedis.rpop("words");
System.out.println(word1+"--"+word2);
一个key对应一组 field-value 值
//设值
jedis.hset("user","name","shengr");
jedis.hset("user","age","12");
jedis.hset("user","city","sz");
//获取单个值
String name=jedis.hget("user","name");
System.out.println(name);
//遍历所有值
Map<String,String> user = jedis.hgetAll("user");
for (String key : user.keySet()) {
String value = user.get(key);
System.out.println(key+":"+value);
}
//删除key中的一个或多个指定fileld,不存在的字段忽略
jedis.hdel("user","city","sex");
集合中元素唯一,添加重复的元素无效
//设值
jedis.sadd("distinctWords","a","b","a");
//获取所有值
Set<String> distinctWords = jedis.smembers("distinctWords");
System.out.println(distinctWords);
有序的Set类型,通过score指定排名分值,元素按照此分值从小到大排序
jedis.zadd("sortedset",3,"ding");
jedis.zadd("sortedset",5,"seng");
jedis.zadd("sortedset",4,"ming");
Set<String> sortedset = jedis.zrange("sortedset", 0, -1);
System.out.println(sortedset);
完整运行代码如下:
public class JedisTest { private static Jedis jedis; //通过静态代码块进行初始化值 static { jedis = new Jedis("172.28.226.5",6379); jedis.auth("$pwd"); } //1,String类型 public static void op01(){ //1,设置k-v值 jedis.set("name","shengr"); //2,获取k对应v值 String username=jedis.get("name"); System.out.println(username); //3,设置值的同时指定过期时间,方法为setex,第二参数指定过期时间,单位秒 jedis.setex("flag",30,"1"); //4,删除key jedis.del("flag"); //5,关闭连接 jedis.close(); } //2,List类型 public static void op02(){ //lpush 元素依次插入到列表首位 jedis.lpush("words","a","b"); //rpush 元素依次插入到列表末位 jedis.rpush("words","c","d","e"); //lrange 获取下标从start到end的元素,下标从0开始并且包含end处的值。另外,下标可以是负数,表示倒数第几个值,-1即表示最后一位。 List<String> words = jedis.lrange("words", 0, -1); System.out.println(words); //lpop 从列表首位弹出一个值 String word1 = jedis.lpop("words"); //rpop 从列表末位弹出一个值 String word2 = jedis.rpop("words"); System.out.println(word1+"--"+word2); } //3,Hash类型,同一个key对应一组k-v值 public static void op03(){ //设值 jedis.hset("user","name","shengr"); jedis.hset("user","age","12"); jedis.hset("user","city","sz"); //获取单个值 String name=jedis.hget("user","name"); System.out.println(name); //遍历所有值 Map<String,String> user = jedis.hgetAll("user"); for (String key : user.keySet()) { String value = user.get(key); System.out.println(key+":"+value); } //删除key中的一个或多个指定fileld,不存在的字段忽略 jedis.hdel("user","city","sex"); } //4-1,Set类型:集合中元素唯一 public static void op04(){ jedis.sadd("distinctWords","a","b","a"); Set<String> distinctWords = jedis.smembers("distinctWords"); System.out.println(distinctWords); } //4-2,有序Set类型: score指定排名分值,元素按照此分值从小到大排序 public static void op05(){ jedis.zadd("sortedset",3,"ding"); jedis.zadd("sortedset",5,"seng"); jedis.zadd("sortedset",4,"ming"); Set<String> sortedset = jedis.zrange("sortedset", 0, -1); System.out.println(sortedset); } public static void main(String[] args) { op01(); op02(); op03(); op04(); op05(); } }
集群模式访问,我们需要构建的是JedisCluster对象,而不是Jedis对象。虽然对象换了,但是访问以上各种数据类型的方法和Jedis对象完全一致。我们这里只需要关注如何创建一个JedisCluster的连接对象即可。
jedis的连接池叫JedisPool,在创建连接池后我们可以从连接池中获取连接,客户端连接Redis使用的是TCP协议,直连的方式每次需要建立TCP连接,而连接池的方式是可以预先初始化好Jedis连接,所以每次只需要从Jedis连接池借用即可,而借用和归还操作是在本地进行的,只有少量的并发同步开销,远远小于新建TCP连接的开销。
连接池配置属性(通过JedisPoolConfig对象去设置属性):
maxTotal
连接池容量,即可用的最大连接数。
maxIdle
最大空闲连接数,默认值是8。
maxWaitMillis
等待可用连接的最大时间,单位是毫秒,默认值为-1,表示永不超时。
testOnReturn
布尔值,当该属性为true时,在调用borrowObject方法从连接池获取连接前,会调用validateObject方法进行校验。若校验失败,连接会从连接池中移除并销毁。同时会尝试重新借一个新的连接对象。
testOnBorrow
布尔值,当该属性为true时,在调用returnObject方法归还连接到连接池时,会调用validateObject方法,校验当前连接,校验不通过,则销毁该连接。
通过构造方法创建,标准的包含全参的构造方法参数如下:
HostAndPort
HostAndPort表示集群单个节点的ip和端口信息,集群的多个节点放到Set集合中即可。
connectionTimeout
连接超时时间,单位毫秒
soTimeout
读写数据的超时时间,单位毫秒
maxAttempts
最大重连次数
password
密码
jectPoolConfig
封装了连接池属性的JedisPoolConfig对象
完整运行代码如下:
public class JedisClusterTest { public static JedisCluster getJedisCluster() { //1设置集群节点的ip和端口信息,集群有多个节点需要添加多个 Set<HostAndPort> hostAndPorts = new HashSet<HostAndPort>(); HostAndPort hostAndPort = new HostAndPort("172.28.226.5",6379); hostAndPorts.add(hostAndPort); //2设置密码 String password = "$pwd"; //3连接超时时间 int connectTimeout = 5000; //4读写超时时间 int soTimeout = 5000; //5最大重连次数 int maxAttempts = 3; //6连接池参数 JedisPoolConfig jedisPoolConfig = getJedisPoolConfig(); JedisCluster jedisCluster = new JedisCluster(hostAndPorts, connectTimeout, soTimeout, maxAttempts, password, jedisPoolConfig); //注意:JedisCluster最好设置成单例,且一般不需要执行close操作,而是由连接池管理。 return jedisCluster; } private static JedisPoolConfig getJedisPoolConfig() { //1,pool容量,即最大连接数 int maxTotal = 1024; //2,pool最大空闲连接数,默认值是8 int maxIdle = 200; //3,等待可用连接的最大时间,单位是毫秒,默认值为-1,表示永不超时。 int maxWaitMillis = 10000; //4,在返回一个连接对象时,是否进行有效性检查 boolean testOnReturn = true; //5,借用一个连接对象时,是否进行有效性检查 boolean testOnBorrow = true; JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(maxTotal); jedisPoolConfig.setMaxIdle(maxIdle); jedisPoolConfig.setMaxWaitMillis(maxWaitMillis); jedisPoolConfig.setTestOnReturn(testOnReturn); jedisPoolConfig.setTestOnBorrow(testOnBorrow); return jedisPoolConfig; } public static void main(String[] args) { JedisCluster jc=getJedisCluster(); jc.set("myname","shengr"); System.out.println(jc.get("myname")); } }
Exception in thread "main" redis.clients.jedis.exceptions.JedisMovedDataException: MOVED 14315 172.24.226.7:6379
at redis.clients.jedis.Protocol.processError(Protocol.java:115)
at redis.clients.jedis.Protocol.process(Protocol.java:161)
at redis.clients.jedis.Protocol.read(Protocol.java:215)
at redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:340)
at redis.clients.jedis.Connection.getStatusCodeReply(Connection.java:239)
at redis.clients.jedis.Jedis.set(Jedis.java:121)
at redisClient.JedisTest.op01(JedisTest.java:24)
at redisClient.JedisTest.main(JedisTest.java:52)
问题原因 : MOVED表示当前是Redis集群模式。您正在集群模式下使用Jedis类对象访问,而 Jedis类对象 单机模式下的数据访问方式。
解决方案: 将连接对象从 Jedis 换成 JedisCluster 就可以了。