一,redis概述
1.基本介绍
1.1redis介绍
现在市面上用得非常多的一款内存数据库 Redis是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语言编写的key-value存储系统(区别于MySQL的二维表格的形式存储。)。和Memcache类似,但很大程度补偿了Memcache的不足。和Memcache一样,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失。所以Memcache的应用场景适用于缓存无需持久化的数据。而Redis不同的是它会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化
1.2使用场景
1.取最新N个数据的操作
2.排行榜应用,取TOP N操作
3.需要精准设定过期时间的应用
4.计数器应用
5.Uniq操作,获取某段时间所有数据排重值
6.实时系统,反垃圾系统
7.Pub/Sub构建实时消息系统
8.构建队列系统
9.缓存
1.3特点
高效性:Redis读取的速度是110000次/s,写的速度是81000次/s
原子性:Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)
稳定性:持久化,主从复制(集群)
其他特性:支持过期时间,支持事务,消息订阅
2.redis环境安装
2.1 下载redis安装包
cd /export/softwares
wget http://download.redis.io/releases/redis-3.2.8.tar.gz
2.2 解压
cd /export/softwares
tar -zxvf redis-3.2.8.tar.gz -C …/servers/
2.3安装C程序运行环境
yum -y install gcc-c++
2.4 安装较新版的tcl
方式一:在线下载tcl安装压缩包
cd /export/softwares
wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz
解压tcl
tar -zxvf tcl8.6.1-src.tar.gz -C …/servers/
进入指定目录
cd …/servers/tcl8.6.1/unix/
./configure
make && make install
方式二 在线安装tcl
yum -y install tcl
2.5 编译redis
cd /export/servers/redis-3.2.8/
make MALLOC=libc 或者使用命令 make 进行编译
make test && make install
2.6修改redis配置文件
cd /export/servers/redis-3.2.8/
mkdir -p /export/servers/redis-3.2.8/logs
mkdir -p /export/servers/redis-3.2.8/redisdata
vim redis.conf
bind node01
daemonize yes
pidfile /var/run/redis_6379.pid
logfile "/export/servers/redis-3.2.8/logs/redis.log"
dir /export/servers/redis-3.2.8/redisdata
2.7启动redis(node01)
cd /export/servers/redis-3.2.8/src
redis-server …/redis.conf
2.8连接redis客户端
cd /export/servers/redis-3.2.8/src
redis-cli -h node01
二,redis的数据类型
redis当中一共支持五种数据类型
- string
- list
- set
- hash
- zset
2.1redis当中字符串string的操作
常用命令
- set
- get
- setnx
- setex
- 是否会在到达过期时间自动删除
- 不会,他会在请求该key的时候进行判断,如果已经超时,就进行删除。
- 是否会在到达过期时间自动删除
- incr
- incrby
- decr
- decrby
2.2redis当中字符串hash的操作
map
Map person= new HashMap<String,String>()
map.put(name,wukong)
map.put(age,500)
map.put(sex,man)
map.put(hobby,xiangjiao)
person.get(name)
Hset person name wukong
Hset person age 500
-
一般用来存储对象,user(
id 1,
name tom,
age 13)
-
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
-
常用命令
- hset user id 1
- hset user name tom
- hset user age 13
- hexists
- hget
- hkeys
- hvls
- hincrby
- hdel 删除一个或多个哈希表字段
- hset user id 1
2.3redis当中字符串list的操作
List list = new ArrayList()
list.put(“1”)
list.put(“2”)
list.put(“3”)
list.put(“4”)
for(Stirng l : list){
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WE1yIgpl-1571546729417)(C:\Users\星星stars\AppData\Roaming\Typora\typora-user-images\1571484195183.png)]
- Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
- 常用用法
- lpush
- lpop
- lrange key 0 -1
- rpush
- rpop
- lindex(lpush 和 rpush角标的异同)
2.4redis当中字符串set的操作
Set set = new HashSet()
set.add(1)
map.put(1,obj)
set set = map.keyset
-
redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
-
常用命令
-
sadd
-
smembers
-
scard
-
sdiff
-
sinter
-
sismember
-
2.5redis当中字符串zset的操作
redis 127.0.0.1:6379> ZADD myzset 1 redis
(integer) 1
redis 127.0.0.1:6379> ZADD myzset 2 mongodb
(integer) 1
redis 127.0.0.1:6379> ZADD myzset 3 mysql
(integer) 1
redis 127.0.0.1:6379> ZADD myzset 3 mysql
(integer) 0
redis 127.0.0.1:6379> ZADD myzset 4 mysql
(integer) 0
redis 127.0.0.1:6379> ZRANGE myzset 0 10 WITHSCORES
----------------------------------------------------------------
mysql redis mongodb
----------------------------------------------------------------
ZCARD key
获取成员数
ZSCORE key member
返回成员的分数值
ZINCRBY key increment member
对指定成员的分数加上增量 increment
ZRANGE key start stop [WITHSCORES]
通过索引区间返回指定区间内的成员,返回正序排序
ZRANK key member
返回指定成员的索引
ZREM key member [member ...]
移除一个或多个成员
ZREVRANGE key start stop [WITHSCORES]
通过索引区间返回指定区间内的成员,成员按分数值递减(从大到小)排序
ZREVRANK key member
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序
三,redis的javaAPI操作
1.string,hash,list,set的测试代码
package cn.itcast.jedis;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class jedisoperate {
private JedisPool jedisPool;
@BeforeTest
public void connectJedis(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setMinIdle(5);
jedisPoolConfig.setMaxWaitMillis(5000);
jedisPoolConfig.setMaxTotal(50);
jedisPool = new JedisPool(jedisPoolConfig, "node01", 6379);
}
/**
* 添加string类型数据
*/
@Test
public void stringOperate(){
//获取连接池对象
Jedis resource = jedisPool.getResource();
//1.添加
resource.set("str1","helloworld");
//2.查询
String value = resource.get("str1");
System.out.println("key为str1的value:"+value);
//3.修改
resource.set("str1","beauty");
//4.删除
resource.del("str1");
String result = resource.get("str1");
System.out.println("删除key以后:"+result);
//5.实现增长
resource.set("nums","5");
Long nums = resource.incrBy("nums", 5);
System.out.println("nums自定义增长以后的值是:"+nums);
String nums1 = resource.get("nums");
System.out.println("nums自定义增长以后的值是:"+nums);
//6.关闭资源
resource.close();
}
/*测试hash类型:键唯一,键值可以重复*/
@Test
public void hashOperatition(){
//获取资源对象
Jedis resource = jedisPool.getResource();
//1.添加数据
resource.hset("hsetkey","mapkey1","mapvalue1");
resource.hset("hsetkey","mapkey2","mapvalue2");
resource.hset("hsetkey","mapkey2","mapvalue3");
resource.hset("hsetkey","mapkey3","mapvalue3");
//2.获取所有数据
Map<String, String> hgetAll = resource.hgetAll("hsetkey");
System.out.println("获取所有map集合中所有的键值对:"+hgetAll);
//获取所有键
Set<String> keySet = hgetAll.keySet();
//遍历所有键
for (String key : keySet) {
//根据键获取键值
System.out.println(hgetAll.get(key));
}
//3.修改数据
resource.hset("hsetkey","mapkey1","mapvalue4");
Map<String, String> hsetkey2 = resource.hgetAll("hsetkey");
Set<String> keySet1 = hsetkey2.keySet();
for (String key1 : keySet1) {
System.out.println("修改后获取的键以及键值:");
System.out.println(key1+hsetkey2.get(key1));
}
//4.删除数据
resource.del("hsetkey");
Set<String> keys = resource.keys("hsetkey");
for (String v : keys) {
System.out.println(v);
}
resource.close();
}
/**
* 操作list类型的数据,集合值可重复
*/
@Test
public void setOperate(){
//获取jedis资源对象
Jedis resource = jedisPool.getResource();
//1.从左边增加数据
resource.lpush("listkey","lv1","lv2","lv3");
List<String> lrange = resource.lrange("listkey", 0, -1);
System.out.println("listkey的左增集合值为:"+lrange);
//2.从右边增加数据
resource.rpush("listkey","lv7","lv8");
List<String> rlist = resource.lrange("listkey", 0, -1);
System.out.println("listkey的右增集合值为:"+rlist);
//3.获取所有值
for (String v : lrange) {
System.out.println("左增数据值为:"+v);
}
for (String vr : rlist) {
System.out.println("右增数据值为:"+vr);
}
resource.close();
}
/**
* set类型数据操作,集合值唯一
*/
@Test
public void setOperatition(){
//获取jedispool资源对象
Jedis resource = jedisPool.getResource();
//1.添加数据
resource.sadd("setkey","sv1","sv2","sv3","sv3");
//2.查询数据
Set<String> setkey = resource.smembers("setkey");
System.out.println("setkey集合查询到值:"+setkey);
//3.移除掉一个数据
resource.srem("setkye","sv1");
//关闭资源
resource.close();
}
@AfterTest
public void closeJedis(){
jedisPool.close();
}
}
四,redis的持久化
1.方案一RDB
修改配置文件
cd /export/servers/redis-3.2.8/
vim redis.conf
-
保存快照
-
在redis当中,提供了两种数据持久化的方式,分别为RDB以及AOF,且redis默认开启的数据持久化方式为RDB方式。
-
Redis会定期保存数据快照至一个rbd文件中,并在启动时自动加载rdb文件,恢复之前保存的数据。可以在配置文件中配置Redis进行快照保存的时机:save [seconds][changes] redis.conf
-
意为在[seconds]秒内如果发生了[changes]次数据修改,则进行一次RDB快照保存
-
rdb的缺点
- 1、 快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据。
- 2、 如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力。
2.方案二:AOF
-
预写日志
-
采用AOF持久方式时,Redis会把每一个写请求都记录在一个日志文件里。在Redis重启时,会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。AOF默认是关闭的,如要开启,进行如下配置:
appendonly yes
- appendfsync no:不进行fsync,将flush文件的时机交给OS决定,速度最快
- appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
- appendfsync everysec:折中的做法,交由后台线程每秒fsync一次
-
aof配置
cd /export/servers/redis-3.2.8 vim redis.conf appendonly yes # appendfsync always appendfsync everysec # appendfsync no
-
aof的缺点
- aof文件通常比rdb文件更大
- 性能消耗比rdb高
- 数据恢复速度比rdb慢
五,redis的架构
1,redis的主从复制结构
-
mysql 也可以搭建主从架构,读写分离。 主库专门用来写数据(inodb,支持事务),从数据库专门用来读数据(myisam,不支持事务,速度快)。解决请求的负载均衡,扩大并发量。(数据的副本)
-
在Redis中,用户可以通过执行SLAVEOF命令或者设置slaveof选项,让一个服务器去复制(replicate)另一个服务器,我们称呼被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave)
-
实现node01作为主节点,node02与node03作为从节点
-
步骤
- node02、node03安装redis(修改配置文件 slaveof node01 6379)
- 分别启动三台redis,启动成功便可以实现redis的主从复制,node01可以读写操作,node02与node03只支持读取操作。
2.redis的Sentinel架构
Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。
第一步:执行以下命令修改redis哨兵配置文件sentinel.conf
cd /export/servers/redis-3.2.8
vim sentinel.conf
#修改bind配置,每台机器修改为自己对应的主机名
bind node01
#配置sentinel服务后台运行
daemonize yes
#修改三台机器监控的主节点,现在主节点是node01服务器
sentinel monitor mymaster node01 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass <master-name> <password>
第二步:三台机器启动哨兵服务
cd /export/servers/redis-3.2.8
src/redis-sentinel sentinel.conf
第三步:杀死第一台的redis服务
kill -9 redis服务进程号
第四步:redis的sentinel模式代码开发连接
/*测试哨兵模式下代码开发sentinel*/
@Test
public void sentinelTest(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10);
jedisPoolConfig.setMaxIdle(5);
jedisPoolConfig.setMinIdle(5);
//(String masterName, Set<String> sentinels,
// final GenericObjectPoolConfig poolConfig
HashSet<String> sentinels = new HashSet<>(Arrays.asList("node01:26379", "node02:26379", "node03:26379"));
//创建哨兵连接池
JedisSentinelPool jedisSentinelPool1 = new JedisSentinelPool("mymaster", sentinels, jedisPoolConfig);
//获取客户端
Jedis resource = jedisSentinelPool1.getResource();
//执行两个命令
resource.set("mykey","sentinelvalue");
String result = resource.get("mykey");
System.out.println("sentinel下获取的值:"+result);
//关闭客户端
resource.close();
}
六,redis集群
1.集群介绍
Redis 集群是一个提供在多个Redis节点之间共享数据的程序集。
Redis 集群并不支持同时处理多个键的 Redis 命令,因为这需要在多个节点间移动数据,这样会降低redis集群的性能,在高负载的情况下可能会导致不可预料的错误。Redis 集群通过分区来提供一定程度的可用性,即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
2.Redis 集群的优势:
1.缓存永不宕机:启动集群,永远让集群的一部分起作用。主节点失效了子节点能迅速改变角色成为主节点,整个集群的部分节点失败或者不可达的情况下能够继续处理命令;
2.迅速恢复数据:持久化数据,能在宕机后迅速解决数据丢失的问题;
3.Redis可以使用所有机器的内存,变相扩展性能;
4.使Redis的计算能力通过简单地增加服务器得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长;
5.Redis集群没有中心节点,不会因为某个节点成为整个集群的性能瓶颈;
3.集群的环境搭建
由于redis集群当中最少需要三个主节点,每个主节点,最少需要一个对应的从节点,所以搭建redis集群最少需要三主三从的配置,所以redis集群最少需要6台redis的实例,我们这里使用三台机器,每台服务器上面运行两个redis的实例。我们这里使用node01服务器,通过配置不同的端口,实现redis集群的环境搭建
第一步:下载安装配置redis以及下载依赖环境同上
-
128G 256G
-
3.x之前,redis官方没有集群概念,codis 专门用来搭建redis集群 (豌豆荚)
- 横向扩展内存空间的问题。
-
Redis 集群是一个提供在多个Redis节点之间共享数据的程序集。Redis 集群通过分区来提供一定程度的可用性,即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
-
集群搭建
- set name tom
- set age 11
- name crc16 10 % 3 = 1 node01
- age crc16 11 % 3 = 2 node02
第二步:创建redis不同实例的配置文件
创建文件夹,并将redis的配置文件拷贝到以下这些目录
cd /export/servers/redis-3.2.8/
mkdir clusters
mkdir -p /export/servers/redis-3.2.8/clusters/7001
mkdir -p /export/servers/redis-3.2.8/clusters/7002
mkdir -p /export/servers/redis-3.2.8/clusters/7003
mkdir -p /export/servers/redis-3.2.8/clusters/7004
mkdir -p /export/servers/redis-3.2.8/clusters/7005
mkdir -p /export/servers/redis-3.2.8/clusters/7006
第三步:修改redis的六个配置文件
cd /export/servers/redis-3.2.8/
mkdir -p /export/servers/redis-3.2.8/redisdata/7001
mkdir -p /export/servers/redis-3.2.8/redisdata/7002
mkdir -p /export/servers/redis-3.2.8/redisdata/7003
mkdir -p /export/servers/redis-3.2.8/redisdata/7004
mkdir -p /export/servers/redis-3.2.8/redisdata/7005
mkdir -p /export/servers/redis-3.2.8/redisdata/7006
复制配置文件到对应端口
cd /export/servers/redis-3.2.8/redis.conf
cp redis.conf /export/servers/redis-3.2.8/clusters/7001
例如第一台的修改
vim /export/servers/redis-3.2.8/clusters/7001/redis.conf
bind node01
port 7002
cluster-enabled yes
cluster-config-file nodes-7002.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
pidfile /var/run/redis_7002.pid
logfile "/export/redis-3.2.8/logs/7001.log"
dir /export/servers/redis-3.2.8/redisdata/7001
复制第一台的配置文件到其他集群
cd /export/servers/redis-3.2.8/clusters/7001
\cp -rf redis.conf /export/servers/redis-3.2.8/clusters/7002
\cp -rf redis.conf /export/servers/redis-3.2.8/clusters/7003
\cp -rf redis.conf /export/servers/redis-3.2.8/clusters/7004
\cp -rf redis.conf /export/servers/redis-3.2.8/clusters/7005
\cp -rf redis.conf /export/servers/redis-3.2.8/clusters/7006
第四步:启动redis进程
cd /export/redis-3.2.8
src/redis-server clusters/7001/redis.conf
src/redis-server clusters/7002/redis.conf
src/redis-server clusters/7003/redis.conf
src/redis-server clusters/7004/redis.conf
src/redis-server clusters/7005/redis.conf
src/redis-server clusters/7006/redis.conf
第五步:安装ruby运行环境
node01下安装ruby
yum install ruby
yum install rubygems
gem install redis
如果报错如下
[root@node01 redis-3.2.8]# gem install redis
ERROR: Error installing redis:
redis requires Ruby version >= 2.3.0.
You have new mail in /var/spool/mail/root
这时,需要升级Ruby版本 node01执行以下命令升级ruby版本
cd /export/servers/redis-3.2.8
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://get.rvm.io | bash -s stable
或者
curl -sSL https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer | bash
source /etc/profile.d/rvm.sh
rvm list known
rvm install 2.4.1
第六步:创建redis集群
cd /export/servers/redis-3.2.8
gem install redis
src/redis-trib.rb create --replicas 1 192.168.150.110:7001 192.168.150.110:7002 192.168.150.110:7003 192.168.150.110:7004 192.168.150.110:7005 192.168.150.110:7006
第七步:连接redis客户端
cd /export/servers/redis-3.2.8
src/redis-cli -h node01 -c -p 7001
src/redis-cli -h node01 -c -p 7002
src/redis-cli -h node01 -c -p 7003
src/redis-cli -h node01 -c -p 7004
src/redis-cli -h node01 -c -p 7005
src/redis-cli -h node01 -c -p 7006
4.集群管理
添加一个新节点作为主节点
启动新节点的redis服务,然后添加到集群当中去
启动服务
src/redis-server clusters/7007/redis.conf
src/redis-trib.rb add-node 192.168.150.110:7007 192.168.150.110:7001
添加一个新节点作为副本
src/redis-server clusters/7008/redis.conf
src/redis-trib.rb add-node --slave 192.168.150.110:7008 192.168.150.110:7001
删除一个节点 命令格式
src/redis-trib del-node 127.0.0.1:7000 `<node-id>`
src/redis-trib.rb del-node 192.168.150.110:7008 7c7b7f68bc56bf24cbb36b599d2e2d97b26c5540
重新分片
./redis-trib.rb reshard node01:7001
./redis-trib.rb reshard --from <node-id> --to <node-id> --slots <number of slots> --yes <host>:<port>
5.JavaAPI操作redis
@Test
public void jedisClusterTest() throws IOException {
Set<HostAndPort> portSet = new HashSet<HostAndPort>();
portSet.add(new HostAndPort("node01",7001));
portSet.add(new HostAndPort("node01",7002));
portSet.add(new HostAndPort("node01",7003));
portSet.add(new HostAndPort("node01",7004));
portSet.add(new HostAndPort("node01",7005));
portSet.add(new HostAndPort("node01",7006));
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setMaxWaitMillis(3000);
jedisPoolConfig.setMaxTotal(30);
jedisPoolConfig.setMinIdle(5);
JedisCluster jedisCluster = new JedisCluster(portSet, jedisPoolConfig);
jedisCluster.set("cluster","clustervalue");
jedisCluster.close();
}
ublic void jedisClusterTest() throws IOException {
Set portSet = new HashSet();
portSet.add(new HostAndPort(“node01”,7001));
portSet.add(new HostAndPort(“node01”,7002));
portSet.add(new HostAndPort(“node01”,7003));
portSet.add(new HostAndPort(“node01”,7004));
portSet.add(new HostAndPort(“node01”,7005));
portSet.add(new HostAndPort(“node01”,7006));
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setMaxWaitMillis(3000);
jedisPoolConfig.setMaxTotal(30);
jedisPoolConfig.setMinIdle(5);
JedisCluster jedisCluster = new JedisCluster(portSet, jedisPoolConfig);
jedisCluster.set("cluster","clustervalue");
jedisCluster.close();
}