一、Redis的简介
1.1 NoSql的简介
1. NoSql 是Not-Only Sql的简写,泛指非关系型数据库
2. 关系型数据库不太适合存储非结构化的大数据(现在的非结构化的数据占比90%),所以提出了一个新的数据库解决方案,来存储这样的数据。
3. NoSql的分类
- 键值对模型的NoSQL:Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
应用场景:内容缓存,主要用于处理大量数据的高访问负载
优势:快速查询
劣势:存储的数据缺少结构化
- 列式模型的NoSQL:Cassandra, HBase, Riak
应用场景:分布式的文件系统
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限
- 文档模型的NoSQL:CouchDB、MongoDB
应用场景:Web应用
优势:数据结构要求不严格
劣势:查询性能不高,而且缺乏统一的查询语法
- 图模型的NoSQL:Neo4J、InfoGrid、Infinite Graph
应用场景:社交网络
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
1.2 Redis的简介
1. Redis是C语言开发的
2. 是开源的
3. 基于内存的,(可以提供持久化保存)
4. 高性能的键值对模型的数据库
5. 可以用于消息缓存
6. 提供了多种数据类型的支持,比如字符串类型,散列类型,列表类型,集合类型,有序集合类型
1.3 Redis的应用场景
- 缓存(数据查询、短连接、新闻内容、商品内容等等)。(**最多使用**)
- 分布式集群架构中的session分离。
- 聊天室的在线好友列表。
- 任务队列。(秒杀、抢购、12306等等)lpush
- 应用排行榜。sortedSet
- 网站访问统计。
- 数据过期处理(可以精确到毫秒)。
1.4 Redis的特性
- redis数据访问速度快(数据在内存中)
- redis有数据持久化机制(持久化机制有两种:1、定期将内存数据dump到磁盘;2、aof(append only file)持久化机制——用记日志的方式记录每一条数据更新操作,一旦出现灾难事件,可以通过日志重放来恢复整个数据库)
- redis支持集群模式(容量可以线性扩展)
- redis相比其他缓存工具(ehcache/memcached),有一个鲜明的优势:支持丰富的数据结构
二、Redis的安装
2.1 下载与环境需求
官网地址:https://redis.io/
下载地址:http://download.redis.io/releases/redis-4.0.14.tar.gz
环境需求:
- Linux:centOS7.7
- VMware:14
- C语言环境:gcc-c++
2.2 安装步骤
步骤1)上传,解压源码包到/usr/local下,更名
[root@mei01 ~]# tar -zxvf redis-4.0.14.tar.gz -C /usr/local
[root@mei01 ~]# cd /usr/local
[root@mei01 local]# mv redis-4.0.14 redis
注意,源码包的安装,编译时产生的路径在源码包下,所以不要轻易删除源码包
步骤2)验证是否安装了c语言环境
[root@mei01 local]# yum install gcc-c++
步骤3)进入redis的目录下,进行编译
[root@mei01 local]# cd redis
[root@mei01 redis]# make
步骤4)安装redis,生成bin目录
[root@mei01 redis]# make install PREFIX=/usr/local/redis
[root@mei01 redis]# ll
bin
步骤5) 配置环境变量
[root@mei01 redis]# vim /etc/profile
....省略...
#redis env
export REDIS_HOME=/usr/local/redis
export PATH=$REDIS_HOME/bin:$PATH
[root@mei01 redis]# source /etc/profile
步骤6)验证版本号
[root@mei01 redis]# redis-cli --version
redis-cli 4.0.14
[root@mei01 redis]# redis-cli --help 查看参数
2.3 redis的启动
两种方式,分为前端启动,和后端启动
2.3.1 前端启动
作为前台进程启动,但是启动后,当前窗口什么都不能干,如果窗口退出,或者是ctrl+c,那么redis服务就会停掉
[root@mei01 redis]# redis-server
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oclzYCEy-1615728140364)(ClassNotes.assets/image-20201203102318831.png)]
2.3.2 后台启动
1)修改redis.conf文件
找到以下内容,大约在136行左右,,将daemonize修改为yes
################################# GENERAL #####################################
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize yes
2)启动服务,并使用配置文件
[root@mei01 redis]# redis-server ./redis.conf
2.4 关闭服务
第一种:正常关闭
# 查看是否开启了服务
[root@mei01 redis]# ps -ef | grep redis
root 41510 1 0 10:29 ? 00:00:00 redis-server 127.0.0.1:6379
root 41648 1849 0 10:29 pts/0 00:00:00 grep --color=auto redis
#正常关闭
[root@mei01 redis]# redis-cli shutdown
#再次查看
[root@mei01 redis]# ps -ef | grep redis
root 42445 1849 0 10:31 pts/0 00:00:00 grep --color=auto redis
第二种:使用kill杀死
#查看进程
[root@mei01 redis]# ps -ef | grep redis
root 42855 1 0 10:32 ? 00:00:00 redis-server 127.0.0.1:6379
root 42908 1849 0 10:32 pts/0 00:00:00 grep --color=auto redis
#杀死进程
[root@mei01 redis]# kill -9 42855
#再次查看
[root@mei01 redis]# ps -ef | grep redis
root 43042 1849 0 10:33 pts/0 00:00:00 grep --color=auto redis
三、Redis的客户端连接
3.1 客户端脚本
# 使用脚本连接redis服务
[root@mei01 redis]# redis-cli
127.0.0.1:6379> set user1 gaoyuanyuan <= 设置一个键值对
OK
127.0.0.1:6379> set user2 linzhiling <= 设置一个键值对
OK
127.0.0.1:6379> get user1 <= 获取键user1的value
"gaoyuanyuan"
127.0.0.1:6379> get user2 <= 获取键user2的value
"linzhiling"
127.0.0.1:6379> keys * <= 查看所有的key
1) "user1"
2) "user2"
127.0.0.1:6379>
3.2 Redis桌面管理工具
第一步:安装redis桌面管理工具
第二步:创建连接(注意事项:redis.conf下的bind 要设置成主机名,而不是127.0.0.1)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eiRw7Zc7-1615728140366)(ClassNotes.assets/%E5%88%9B%E5%BB%BA%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%BF%9E%E6%8E%A5.png)]
界面如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uafTCfMH-1615728140367)(ClassNotes.assets/%E7%99%BB%E5%BD%95%E5%90%8E%E7%95%8C%E9%9D%A2.png)]
3.3 java客户端
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.0.0</version>
</dependency>
3.3.1 简介
Redis不仅是使用命令来操作,现在基本上主流的语言都有客户端支持,比如java、C、C#、C++、php、Node.js、Go等。
在官方网站里列一些Java的客户端,有Jedis、Redisson、Jredis、JDBC-Redis、等其中官方推荐使用Jedis和Redisson。 在企业中用的最多的就是Jedis,下面我们就重点学习下Jedis。
Jedis同样也是托管在github上,地址:httpsRedis.assets//github.com/xetorthio/jedis
3.3.2 单线程连接
package com.qf.redis.day01
import redis.clients.jedis.Jedis
object _01JedisDemo01 {
def main(args: Array[String]): Unit = {
//使用java的redis客户端API jedis, 要连接linux上的redis服务,主要要修改redis.conf下的bind为主机名或者而是Ip
val jedis = new Jedis("mei01", 6379)
//设置键值对
val rs: String = jedis.set("user4", "libai")
//覆盖
jedis.set("user4","dufu")
//追加:如果key不存在就创建,存在就追加到value后
jedis.append("user4","wangwei")
println(rs)
println(jedis.get("user1"))
println(jedis.get("user2"))
println(jedis.get("user4"))
//释放资源
jedis.close()
}
}
3.3.3 线程池连接
package com.qf.redis.day01
import java.util
import redis.clients.jedis.{Jedis, JedisPool}
object _02JedisPoolDemo01 {
def main(args: Array[String]): Unit = {
//获取连接redis的连接池对象
val pool = new JedisPool("mei01", 6379)
//从连接池中获取一个具体的连接对象
val jedis: Jedis = pool.getResource
//验证密码
jedis.auth("123456")
//设置一个键值对
val str: String = jedis.mset("hobby", "book", "movie","阿甘正传")
println(str)
//获取hobbys的value信息
val strings: util.List[String] = jedis.mget("hobbys","movie")
println(strings)
pool.close()
}
}
3.3.4 密码策略
服务端可以设置密码,客户端想要连接时,需要使用密码
1)修改redis.conf
找到安全模块,将requirepass解开注释,设置自己的密码
################################## SECURITY ###################################
# Require clients to issue AUTH <PASSWORD> before processing any other
# commands. This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#
requirepass 123456
2)重启服务
[root@mei01 redis]# redis-server ./redis.conf
3)客户端连接服务
[root@mei01 redis]# redis-cli -h mei01 -a 123456
Warning: Using a password with '-a' option on the command line interface may not be safe.
四、Redis的数据类型
4.1 字符串类型
Redis 字符串数据类型的相关命令用于管理 redis 字符串值
4.1.1 命令行演示
[root@mei01 redis]# redis-cli -h mei01 -a 123456
Warning: Using a password with '-a' option on the command line interface may not be safe.
mei01:6379> set a b
OK
mei01:6379>
mei01:6379> set u1 gyy
OK
mei01:6379> set u2 cls
OK
mei01:6379> get u1
"gyy"
mei01:6379> get u2
"cls"
mei01:6379> mget u1 u2
1) "gyy"
2) "cls"
mei01:6379> set num 10
OK
mei01:6379> get num
"10"
mei01:6379> incr num
(integer) 11
mei01:6379> get num
"11"
mei01:6379> incrby num 3
(integer) 14
mei01:6379> get num
"14"
mei01:6379> decr num
(integer) 13
mei01:6379> get num
"13"
mei01:6379> decrby num 10
(integer) 3
mei01:6379> mset u11 xiaohong u12 xiaoming
OK
mei01:6379> mget u11 u12
1) "xiaohong"
2) "xiaoming"
mei01:6379>
4.1.2 API演示
package com.qf.redis.day01
import java.util
import redis.clients.jedis.{Jedis, JedisPool}
object _03StringTypeDemo {
def main(args: Array[String]): Unit = {
//获取连接redis的连接池对象
val pool = new JedisPool("mei01", 6379)
//从连接池中获取一个具体的连接对象
val jedis: Jedis = pool.getResource
//验证密码
jedis.auth("123456")
//设置一个键值对
val str1:String = jedis.set("score","100")
println(jedis.get("score")) //获取并打印
//自增: value必须是数字
jedis.incr("score")
println("自增后:"+jedis.get("score")) //获取并打印
//自增,设置步长
jedis.incrBy("score",5)
println("自增5个长度后:"+jedis.get("score")) //获取并打印
//自减
jedis.decr("score")
println("自减后:"+jedis.get("score")) //获取并打印
//自减,设置步长
jedis.decrBy("score",6)
println("自减6个长度后:"+jedis.get("score")) //获取并打印
//设置多对kv
jedis.mset("hobby", "book", "movie","阿甘正传")
//获取多个k的value值,返回的是一个集合
val strings: util.List[String] = jedis.mget("hobbys","movie")
println(strings)
pool.close()
}
}
4.2 Hash类型
Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
4.2.1 命令行演示
# 针对一个对象t1 设置一个键值对 username liumin
mei01:6379> hset t1 username liumin
(integer) 1 <==1表示新增一对kv
# 获取t1的username的value
mei01:6379> hget t1 username
"liumin"
# 这种写法是错误的
mei01:6379> hget t1
(error) ERR wrong number of arguments for 'hget' command
# 针对一个t2对象设置三对KV
mei01:6379> hset t2 username gaoyuanyuan age 38 height 168
(integer) 3
# 针对一个t3对象设置三对KV
mei01:6379> hmset t3 username gaoyuanyuan age 38 height 168
OK
# 获取三个K的V
mei01:6379> hmget t2 username age height
1) "gaoyuanyuan"
2) "38"
3) "168"
#自增值,步长是2
mei01:6379> hincrby t2 age 2
(integer) 40
mei01:6379> hincrby t2 age 2
(integer) 42
mei01:6379> hget t2 age
"42"
# 删除k
mei01:6379> hdel t1
(error) ERR wrong number of arguments for 'hdel' command
mei01:6379> hdel t1 username
(integer) 0
mei01:6379> hdel t2 username age
(integer) 2
4.2.2 API演示
package com.qf.redis.day01
import java.util
import redis.clients.jedis.{Jedis, JedisPool}
object _03StringTypeDemo {
def main(args: Array[String]): Unit = {
//获取连接redis的连接池对象
val pool = new JedisPool("mei01", 6379)
//从连接池中获取一个具体的连接对象
val jedis: Jedis = pool.getResource
//验证密码
jedis.auth("123456")
//设置一个键值对
val str1:String = jedis.set("score","100")
println(jedis.get("score")) //获取并打印
//自增: value必须是数字
jedis.incr("score")
println("自增后:"+jedis.get("score")) //获取并打印
//自增,设置步长
jedis.incrBy("score",5)
println("自增5个长度后:"+jedis.get("score")) //获取并打印
//自减
jedis.decr("score")
println("自减后:"+jedis.get("score")) //获取并打印
//自减,设置步长
jedis.decrBy("score",6)
println("自减6个长度后:"+jedis.get("score")) //获取并打印
//设置多对kv
jedis.mset("hobby", "book", "movie","阿甘正传")
//获取多个k的value值,返回的是一个集合
val strings: util.List[String] = jedis.mget("hobbys","movie")
println(strings)
pool.close()
}
}
4.3 List类型
4.3.1 说明
Redis使用了LinkedList来实现数据类型List。可以使用其来实现队列和栈两种结构
如果使用的命令是lpush,底层使用的就是栈的数据结构, 先进后出(FILO)
如果使用的命令是rpush,底层使用的则是队列的数据结构, 先进先出(FIFO)
命令如下:
lpush
rpush
lrange
llen
lrem
lindex
linsert
lpop
rpop
lset
等
参考官网命令集:http://www.redis.cn/commands.html#list
4.3.2 API演示
package com.qf.redis.day01
import java.util
import java.util.UUID
import redis.clients.jedis.Jedis
object _05ListTypeDemo {
def main(args: Array[String]): Unit = {
//使用java的redis客户端API jedis, 要连接linux上的redis服务,主要要修改redis.conf下的bind为主机名或者而是Ip
val jedis = new Jedis("mei01", 6379)
jedis.auth("123456")
// for(i<- 0 to 20){
// val rid: String = UUID.randomUUID().toString
// jedis.lpush("list1",rid)
// }
//取出列表里的元素
// val strings: util.List[String] = jedis.lrange("list1", 0, -1)
// import scala.collection.JavaConversions._
// for(i<- strings){
// println(i)
// }
import scala.collection.JavaConversions._
//从list1里弹出,放入另一个列表list2中
while (jedis.llen("list1")>0) {
val str: String = jedis.rpoplpush("list1", "list2")
println(str)
}
//遍历list2里的元素
val strings: util.List[String] = jedis.lrange("list2", 0, -1)
for(i<- strings){
println(i)
}
//释放资源
jedis.close()
}
}
4.4 Set类型
4.4.1 说明
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
4.4.2 命令集
参考官网:http://www.redis.cn/commands.html#set
4.4.3 API的演示
package com.qf.redis.day01
import java.util
import redis.clients.jedis.Jedis
object _06SetTypeDemo {
def main(args: Array[String]): Unit = {
//使用java的redis客户端API jedis, 要连接linux上的redis服务,主要要修改redis.conf下的bind为主机名或者而是Ip
val jedis = new Jedis("mei01", 6379)
jedis.auth("123456")
jedis.sadd("heros","superman","盖伦","daomei","毕加索")
//获取
val strings: util.Set[String] = jedis.smembers("heros")
import scala.collection.JavaConversions._
for(i<- strings){
println(i)
}
//删除 刀妹
println(jedis.srem("heros", "daomei"))
println("-----------删除后--------")
for(i<- jedis.smembers("heros")){
println(i)
}
//释放资源
jedis.close()
}
}
4.5 SortedSet类型
Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
4.5.1 命令集
参考:http://www.redis.cn/commands.html#sorted_set
4.5.2应用案例演示:
1)需求:
求商品销售量的排行榜的前10名
2)分析:
根据商品销售量对商品进行排行显示,定义sorted set集合,商品销售量为元素的分数。
定义商品销售排行榜key:itemsRedis.assetssellsort
写入商品销售量,如商品编号1001的销量是9,商品编号1002的销量是10,参考下面:
ZADD itemsRedis.assetssellsort 9 1001 10 1002 9 1003 8 1004 7 1005 11 1006 2 1007 3 1008 3 1009 4 1010 5 1011
3)如果某一个商品销售增加了,比如编号1001的销量加1
ZINCRBY itemsRedis.assetssellsort 1 1001
4)商品销量前10名:
ZREVRANGE itemsRedis.assetssellsort 0 9 withscores
4.5.3 API演示
package com.qf.redis.day01
import java.util
import redis.clients.jedis.{Jedis, Tuple}
object _07SortedSetTypeDemo {
def main(args: Array[String]): Unit = {
//使用java的redis客户端API jedis, 要连接linux上的redis服务,主要要修改redis.conf下的bind为主机名或者而是Ip
val jedis = new Jedis("mei01", 6379)
jedis.auth("123456")
//添加几个商品销售量,销售量作为分数
jedis.zadd("rank",100,"毛衣1")
jedis.zadd("rank",110,"毛衣2")
jedis.zadd("rank",90,"毛衣3")
jedis.zadd("rank",120,"毛衣4")
jedis.zadd("rank",100,"毛衣5")
jedis.zadd("rank",80,"毛衣6")
jedis.zadd("rank",70,"毛衣7")
jedis.zadd("rank",88,"毛衣8")
jedis.zadd("rank",50,"毛衣9")
jedis.zadd("rank",200,"毛衣10")
jedis.zadd("rank",250,"毛衣11")
//获取销售量的前10名
import scala.collection.JavaConversions._
val strings: util.Set[String] = jedis.zrevrange("rank", 0, 9)
for(i<-strings){
println(i)
}
///获取销售量的前10名,并显示分数值(销售量)
val tuples: util.Set[Tuple] = jedis.zrevrangeWithScores("rank",0,9)
for(i<-tuples){
println(i)
}
jedis.close()
}
}
五、Redis的key操作(keys命令组)
1. keys :查看满足模式的所有的key
2. del: 删除
3. dump: 序列化key的值
4. type: 查看key的类型
5. restore: 反序列化
6. exists:判断key是否存在,存在返回1,不存在返回0
7. move:移动一个key到另一个数据库中
8. rename:重命名一个key
9. ttl key: 查看key的剩余存活时间
10. EXPIRE key seconds:设置key的生存时间(单位:秒)key在多少秒后会自动删除
11. PERSIST key:清除生存时间, 将之前设置的生产时间取消,变成默认值 -1,-1表示内存中永久有效
12. PEXPIRE key milliseconds:生存时间设置单位为:毫秒
六、GEO命令组
用于索引地理空间位置或者是距离等
1. GEOADD key longitude latitude member [longitude latitude member ...]
添加一个或多个地理为止到指定的key中
2. GEODIST key member1 member2 [unit]
求指定的key中两个地址位置的距离
m 表示单位为米。 默认是m
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
3. GEOPOS key member [member ...]
返回某一个地理位置的经纬度
4. GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
返回key中距离某一个地址位置指定半径范围以内的所有的元素。
5. GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count]
返回key中距离某一个地址位置指定半径范围以内的所有的元素。
七、redis的持久化
redis虽然是基于内存的,但是也会将数据支持化到磁盘上的,以保证数据的安全性(万一宕机了,岂不是损失大了)。而redis的持久化方式有两种,一种是RDB方式,一种是AOF方式
7.1 RDB方式
RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。
RDB是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置:
################################ SNAPSHOTTING ################################
#
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
#
# Note: you can disable saving completely by commenting out all "save" lines.
#
# It is also possible to remove all the previously configured save
# points by adding a save directive with a single empty string argument
# like in the following example:
#
# save ""
save 900 1
save 300 10
save 60 10000
save开头的一行就是持久化配置,可以配置多个条件(每行配置一个条件),每个条件之间是“或”的关系,“save 900 1”表示15分钟(900秒钟)内至少1个键被更改则进行快照,“save 300 10”表示5分钟(300秒)内至少10个键被更改则进行快照。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-41uC0MJS-1615728140368)(ClassNotes.assets/rdb%E6%96%B9%E5%BC%8F%E6%8C%81%E4%B9%85%E5%8C%96.png)]
redis服务启动时:
Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。
问题总结:
通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。
7.2 AOF方式
aof是默认不开启的,需要手动设置,设置也非常简单,只需要将对应的no改为yes即可。相应的文件就会保存到和dump.rdb同一个目录下了。
注意:aof是实时保存的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E9jvNoo3-1615728140369)(ClassNotes.assets/%E5%BC%80%E5%90%AFaof%E6%96%B9%E5%BC%8F.png)]
如果rdb方式和aof方式同时使用的话,那么默认从aof文件中加载数据。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wIiqXXOF-1615728140370)(ClassNotes.assets/aof%E6%96%87%E4%BB%B6%E5%86%85%E5%AE%B9.png)]
八、redis的集群搭建
8.1 说明
在一台机器上模拟3个redis实例。 一个端口即一个实例
redis集群需要ruby环境,因为要用到ruby脚本。 那就需要安装ruby
安装指令:yum install ruby rubygems -y
版本兼容问题:每一个redis版本都对ruby版本要求:
比如 redis4.0.0要求ruby的版本大于2.2.2
redis4.2.5要求ruby的版本大于等于2.3.0
redis3.0.6对ruby的版本没有要求
8.2 搭建步骤:
我选择的是redis-3.0.6.tar.gz和redis-3.0.0.gem插件
步骤1)上传,解压:
[root@mei01 ~]# tar -zxvf redis-3.0.6.tar.gz -C /usr/local/
步骤2)编译和安装
[root@mei01 ~]# cd /usr/local/redis-3.0.6
[root@mei01 redis-3.0.6]# make
[root@mei01 redis-3.0.6]# make install PREFIX=/usr/local/redis-3.0.6
步骤3)为了维护三个实例,而创建三个目录,目录对应实例的端口号
[root@mei01 redis-3.0.6]# mkdir -p /usr/local/redis-3.0.6/cluster/{7001,7002,7003}
步骤4)复制redis.conf到7001里,并修改相关信息
[root@mei01 redis-3.0.6]# cp redis.conf ./cluster/7001
找到以下内容,并修改配置文件中选项
bind mei01
port 7001
daemonize yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
步骤5)将7001下修改好的redis.conf拷贝到7002,7003下,并修改端口号
步骤6)启动三个实例
注意:要进入实例的目录下启动,因为会产生相关的文件到启动目录下。
[root@mei01 redis-3.0.6]# cd cluster/7001
[root@mei01 7001]# ../../bin/redis-server ./redis.conf
[root@mei01 7001]# cd ../7002
[root@mei01 7002]# ../../bin/redis-server ./redis.conf
[root@mei01 7002]# cd ../7003
[root@mei01 7003]# ../../bin/redis-server ./redis.conf
小贴士:编写一个启动集群的脚本
步骤7)创建三个实例的关联。形成可以通信的集群
1. 安装gem-redis插件
[root@mei01 7003]# gem install -l ~/redis-3.0.0.gem
2. 为了方便,将src下的ruby脚本拷贝到/usr/local/bin下,不需要配置环境变量
[root@mei01 7003]# cp /usr/local/redis-3.0.6/src/redis-trib.rb /usr/local/bin/redis-tri.rb
3. 创建关联
[root@mei01 7003]# redis-trib.rb create --replicas 0 192.168.10.99:7001 192.168.10.99:7002 192.168.10.99:7003 --?
会遇到以下内容,回答yes即可
Can I set the above configuration? (type 'yes' to accept): yes
注意:创建时,要使用ip,不要使用主机名。
步骤8)测试:使用脚本连接集群
[root@mei01 7003]# /usr/local/redis-3.0.6/bin/redis-cli -h mei01 -p 7001 -c
mei01:7001> set a 1
-> Redirected to slot [15495] located at 192.168.10.101:7002
OK
192.168.10.101:7002> set b 2
-> Redirected to slot [3300] located at 192.168.10.101:7003
OK
192.168.10.101:7003> set c 3
-> Redirected to slot [7365] located at 192.168.10.101:7001
OK
注意:可以看到,每存一个kv,就会切换集群节点。达到负载均衡效果
8.3 集群的架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wAJ59ZA9-1615728140371)(ClassNotes.assets/redis%E9%9B%86%E7%BE%A4%E6%9E%B6%E6%9E%84%E5%9B%BE.png)]
1、 集群通信是通过“ping-pong”机制进行通信;
2、 客户端不需要将所有的节点都连接上,只需要连接其中一个节点即可。
3、 集群中存储数据是存储到一个个的槽中,集群中槽的个数是固定的:16384,槽的编号是【0-16383】。在集群中存储数据时,会根据key进行计算,计算出一个结果,然后将这个结果和16384取余,余数就是这个key将要存储的槽的编号。
注意:槽的编号之间不能断开。
槽的计算会将数据保存的很平均,不会产生一个槽满一个槽空的情况。
8.4 集群的容错
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TvoKMXyr-1615728140371)(…/…/day01/%25E6%2596%2587%25E6%25A1%25A3/Redis.assets/%25E6%258A%2595%25E7%25A5%25A8%25E5%25AE%25B9%25E9%2594%2599%25E6%259C%25BA%25E5%2588%25B6.png)]
什么时候整个集群不可用(cluster_stateRedis.assetsfail)?
第一种情况:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态…
第二种情况:如果集群超过半数以上master挂掉,无论是否有slave集群进入fail状态
8.5 API
package com.qf.redis.day01
import java.util
import redis.clients.jedis.{HostAndPort, JedisCluster}
object _08ClusterConnectDemo {
def main(args: Array[String]): Unit = {
//创建一个集群,存储集群的实例
val sets = new util.HashSet[HostAndPort]()
sets.add(new HostAndPort("mei01",7001))
sets.add(new HostAndPort("mei01",7002))
sets.add(new HostAndPort("mei01",7003))
//创建JedisCluster对象, 就是一个客户端
val cluster = new JedisCluster(sets)
//设置kv对象
cluster.set("user1","zhangdasan")
cluster.hset("user2","username","zhangxiaosan")
cluster.lpush("scores","10","20","30")
cluster.sadd("names","xiaozhang","xiaohei","xiaohong")
cluster.close()
}
}
[外链图片转存中…(img-TvoKMXyr-1615728140371)]
什么时候整个集群不可用(cluster_stateRedis.assetsfail)?
第一种情况:如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完成时进入fail状态…
第二种情况:如果集群超过半数以上master挂掉,无论是否有slave集群进入fail状态
8.5 API
package com.qf.redis.day01
import java.util
import redis.clients.jedis.{HostAndPort, JedisCluster}
object _08ClusterConnectDemo {
def main(args: Array[String]): Unit = {
//创建一个集群,存储集群的实例
val sets = new util.HashSet[HostAndPort]()
sets.add(new HostAndPort("mei01",7001))
sets.add(new HostAndPort("mei01",7002))
sets.add(new HostAndPort("mei01",7003))
//创建JedisCluster对象, 就是一个客户端
val cluster = new JedisCluster(sets)
//设置kv对象
cluster.set("user1","zhangdasan")
cluster.hset("user2","username","zhangxiaosan")
cluster.lpush("scores","10","20","30")
cluster.sadd("names","xiaozhang","xiaohei","xiaohong")
cluster.close()
}
}