1、Nosql的优势
(1)使用nosql解决cpu与内存压力
(2)使用nosql解决I/O压力
2、Nosql数据库的概述
(1)NoSql= Not Only SQL
(2)采用key-value模式存储
(3)不遵循SQL标准
(4)性能远超过SQL
3、使用场景
(1)数据的高并发读写
(2)海量数据读写
(3)数据可扩展性
4、不适用场景
(1)需要事务的支持
(2)基于sql的结构化查询存储,需要即席查询
5、 Redis概述
(1)开源的key-value系统
(2)支持String、List、Set、zset、hash等数据类型
(3)数据库支持push/pop/add/remove操作
(3)支持不同方式的排序
(4)可写入内存也可以持久化
(5)主从同步功能
1、官网下载:放入liunx对应目录内
2、使用yum安装gcc编译环境
yum -y install gcc gcc-c++ kernel-devel //安装gcc、c++编译器以及内核文件
测试gcc版本,gcc --version
或者 gcc -v
3、解压缩:tar zxvf redis-6.2.5.tar.gz
(我的放在新建的soft目录下了)
4、进入redis-6.2.5 目录执行make命令
cd /soft/redis-6.2.5
make
cd /usr/local/bin
ll
cp -r redis.conf /opt/
(2)修改参数配置,将 daemonize no
改为 daemonize yes
,让服务支持在后台启动
[root@localhost redis-6.2.4]# cd /opt/
[root@localhost opt]# vi redis.conf
[root@localhost bin]# cd /usr/local/bin/
[root@localhost bin]# redis-server /opt/redis.conf
[root@localhost bin]# ps -ef|grep redis
(1)redis-cli shutdown
(进入终端shutdown也可以)
(2)kill -9 xxx
(进程)
(一)Redis key操作
(二)Redis字符串(String)
简介:字符串,一个key对应一个value,是二进制安全的,是Redis最基本数据类型,value最多512M,底层为动态字符串,ArrayList
设置多个key-value(当key都不存在时设置成功):msetnx
msetnx k11 v11 k12 v12 k13 v13
msetnx k1 v11 k4 v4
(三)Redis列表(List)
简介:单键多值的字符串列表,可以按照插入顺序排序,底层为双向链表(zipList(数据少时)->quickList(数据多时))
在key对应的value前面/后面插入new value:linset before/after
linsert k1 before "v3" "v31"
linsert k1 after "v2" "v21"
将列表key下标为index的值替换成value:lset
lset k1 1 "new31"
(四)Redis集合(Set)
Redis Set是String类型的无序集合,它的底层其实是一个value为null的hash表,value自动排重且无序
取两个集合的交集/并集/差集(key1中存在,key2中不存在):sinter/sunoin/sdiff
inter k2 k3
sunion k2 k3
sdiff k2 k3
(五)Redis哈希(Hash)
hset user:1001 id 1
hset user:1001 name zhangsan
hget user:1001 id
hget user:1001 name
hmset user:1002 id 2 name lisi age 30
hexists user:1002 id
hexists user:1002 name
hexists user:1002 gender
hkeys user:1002
hvals user:1002
hincrby user:1002 age 2
hsetnx user:1002 age 40
hsetnx user:1002 gender 1
(六)Redis有序集合(Zset)
clear
zadd topn 200 java 300 c++ 400 mysql 500 php
之间:zrange,自动按照score排序,[withscores]
可以返回评分zrange topn 0 -1
zrange topn 0 -1 withscores
zrangebyscore
[withscores]
[limit offset count]
zrangebyscore topn 300 500 withscores
zrevngebyscore [withscores][limit offset count]
zincrby
zincrby topn 50 java
zrem
zrem topn php
zcount
zcount topn 200 300
zrank
zrank topn c++
1、units单位:
只支持bytes,支持bit,不区分大小写
2、INCLUDES:
# foobared 取消注释,设置对应的密码信息
1、发布与订阅:
(1)发送者:pub发送消息
(2)订阅者:sub接受消息
redis客户端可以订阅任意数量的频道
2、发布订阅流程
(1)客户端可以订阅频道
(2)当这个频道发布消息后,消息就会发送给订阅的客户端
3、发布订阅的命令行实现
subscribe channel1
publish channel1 hello
1、Bitmaps
setbit
getbit
getbit users:20210101 1
bitcount[begin][end]
bitcount users:20210101
bitcount users:20210101 1 5
bitop and/or/not/xor
bitop and unique:users:and:20201104_03 unique:users:20201103 unique:users:20201104
bitcount unique:users:and:20201104_03
bitop or unique:users:or:20201104_03 unique:users:20201103 unique:users:20201104
bitcount unique:users:or:20201104_03
2、HyperLogLog
pdadd[element]
,1成功0失败pfadd program "java"
pfadd program "php"
pfadd program "java"
pfadd program "c++" "mysql"
pfcount
pfmerge k100 k1 program
3、Geospatial
geoadd[]...
geoos[member]...
geopos china:city shanghai
geopos china:city beijing
geodist<单位>
geodist china:city beijing shanghai km
georadiusradius m|km|ft|mi
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
3、jedis连接redis测试(Maven)
package cn.tedu.jedis;
import redis.clients.jedis.Jedis;
/**
* @author archibald
* @date 2021/9/8
* @apiNote
*/
public class jedisDemo1 {
public static void main(String[] args) {
//创建Jedis对象,需要修改redis.conf的bind(注释)与protected-mode(no)配置
Jedis jedis =new Jedis("192.168.170.107",6379);
//测试
String value = jedis.ping();
System.out.println(value);
}
}
4、Jedis-API:操作key
//操作key
@Test
public void demo1(){
//创建Jedis对象,需要修改redis.conf的bind(注释)与protected-mode(no)配置
Jedis jedis =new Jedis("192.168.170.107",6379);
//清空redis
jedis.flushDB();
//添加数据
jedis.set("k1","v1");
jedis.set("k2","v2");
jedis.set("k3","v3");
//查询所有key值
Set<String> keys = jedis.keys("*");
for(String key:keys){
System.out.println(key);
}
//根据key获取value
String value = jedis.get("k1");
System.out.println("k1对应的value为:"+value);
}
//操作String
@Test
public void demo2(){
//创建Jedis对象,需要修改redis.conf的bind(注释)与protected-mode(no)配置
Jedis jedis =new Jedis("192.168.170.107",6379);
//清空redis
jedis.flushDB();
//添加多个数据
jedis.mset("str1","v1","str2","v2","str3","v3");
//查询所有key值
System.out.println(jedis.mget("str1","str2","str3"));
}
//操作List
@Test
public void demo3(){
//创建Jedis对象,需要修改redis.conf的bind(注释)与protected-mode(no)配置
Jedis jedis =new Jedis("192.168.170.107",6379);
//清空redis
jedis.flushDB();
//添加数据
jedis.lpush("k1","lucy","mary","jack");
//查询数据
List<String> value = jedis.lrange("k1", 0, -1);
System.out.println(value);
}
//操作set
@Test
public void demo4(){
//创建Jedis对象,需要修改redis.conf的bind(注释)与protected-mode(no)配置
Jedis jedis =new Jedis("192.168.170.107",6379);
//清空redis
jedis.flushDB();
//添加数据
jedis.sadd("name","luck","mary","jack");
//查询数据
Set<String> names = jedis.smembers("name");
System.out.println(names);
}
//操作set
@Test
public void demo5(){
//创建Jedis对象,需要修改redis.conf的bind(注释)与protected-mode(no)配置
Jedis jedis =new Jedis("192.168.170.107",6379);
//清空redis
jedis.flushDB();
//添加数据
jedis.sadd("orders","order1");
jedis.sadd("orders","order2");
jedis.sadd("orders","order3");
jedis.sadd("orders","order4");
//查询数据
Set<String> orders1 = jedis.smembers("orders");
System.out.println(orders1);
//删除后再查询
jedis.srem("orders","order1");
Set<String> orders2 = jedis.smembers("orders");
System.out.println(orders2);
}
//操作Hash
@Test
public void demo6(){
//创建Jedis对象,需要修改redis.conf的bind(注释)与protected-mode(no)配置
Jedis jedis =new Jedis("192.168.170.107",6379);
//清空redis
jedis.flushDB();
//添加数据
jedis.hset("users","age","20");
//查询数据
String hget = jedis.hget("users", "age");
System.out.println(hget);
}
//操作Zset
@Test
public void demo7(){
//创建Jedis对象,需要修改redis.conf的bind(注释)与protected-mode(no)配置
Jedis jedis =new Jedis("192.168.170.107",6379);
//清空redis
jedis.flushDB();
//添加数据
jedis.zadd("china",100d,"shanghai");
//查询数据
Set<String> china = jedis.zrange("china", 0, -1);
System.out.println(china);
}
1、idea创建springboot工程
2、pom文件引入springboot-redis的两个依赖
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--spring2.X集合redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.10.4</version>
</dependency>
3、springboot配置文件中配置redis相关内容
文件位置为resources下面的application.properties
# Redis服务器地址
spring.redis.host=192.168.170.107
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# Redis数据库索引(默认为0)
spring.redis.database=0
# 连接超时时间(毫秒)
spring.redis.timeout=1800000
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
4、创建redis配置类:
package cn.tedu.redis_springboot.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* @author archibald
* @date 2021/9/8
* @apiNote
*/
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
5、编写RedisTestControll添加测试方法:
package cn.tedu.redis_springboot.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author archibald
* @date 2021/9/8
* @apiNote
*/
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String testRedis(){
//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
6、启动类启动Springboot类:RedisSpringbootApplication
浏览器访问验证:http://localhost:8080/redisTest
,显示结果:
1、Redis事务定义
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序的执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队
2、Multi、Exec、discard
基本概念
输入Multi命令开始:输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队
multi
set key1 value1
set key2 value2
exec
multi
set a1 v1
set a2 v2
discard
multi
set b1 v1
set b2 v2
set b3
exec
multi
set c1 v1
incr c1
set c2 v2
exec
4、事务冲突的问题
场景:多个人同时使用一个账户,参加双十一抢购,购买不同的商品,未加事务会产生冲突。
悲观锁
每次拿数据适都认为别人会修改,所以每次在拿数据适都会上锁,这样别人想拿数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁,都是操作前上锁。
乐观锁
每次拿数据的适合都认为别人不会修改,所以不会上锁,但是在更新的适合会判断一下在此期间别人有没有取更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用check-and-set机制实现事务的。
WATCH key[key …]
含义: 在执行multi之前,先执行wath key1[key2] 可以监视一个或多个key,如果在事务执行之前这些key被其他命令所改动,那么事务讲被打断。
UNWATCH:
取消命令对所有key的监视。
5、Redis事务三特性:
单独的隔离操作:
事务种的所有名历经都会序列化、按顺利执行,事务在执行的过程中,不会被其他客户端发送来的命令所打断
没有隔离级别的概念:
队列中的命令没有提交之前不会实际被执行,因为事务提交前任何执行都不会被实际执行
不保证原子性
事务中如果有一条命令执行失败,其中的命令任然会被执行,没有回滚
1、简介
两种持久化方式
(1)RDB(Redis DataBase):内存中数据直接写入文件中
(2)AOF(Append Of File):以追加的行为把内容写入文件中
2、RDB:
概念:
指定的时间间隔内讲内存中的数据集快照写入磁盘,它恢复时可以将快照文件直接读到内存里
RDB持久化流程:
Redis会单独创建fork一个子进程来进行持久化,先将数据写入一个临时文件中,待持久化过程结束后,再用这个临时文件替换上次持久化的文件,RDB方式比AOF文件更加高效,缺点是最后一次持久化的数据可能丢失。
Fork:
写时复制技术:新进程的所有数据(变量、环境变量、程序计数器等)数值都有原进程一致。
redis.conf配置内RDB相关配置(SNAPSHOTTING内配置)
rdb文件名:dbfilename dump.rdb
文件产生的路径,默认值(启动程序的位置):dir ./
dbfilename dump.rdb
dir ./
快照的时间间隔:
# save 3600 1
# save 300 100
# save 60 10000
设置手动持久化或自动持久化
save Vs bgsave
:建议设置自动持久化
# save ""
Redis无法写入磁盘的化,直接关掉Redis的写操作,默认yes
stop-writes-on-bgsave-error yes
是否进行文件压缩:rdbcompre
,默认yes
rdbcompression yes
检查数据的完整性:rdbchecksum yes,默认yes
rdbchecksum yes
RDB恢复备份文件
将持久化的备份文件恢复,重新redis,数据恢复
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> shutdown
not connected> exit
[root@localhost bin]# /usr/local/bin/redis-server /opt/redis.conf
[root@localhost bin]# /usr/local/bin/redis-cli
127.0.0.1:6379> keys *
1) "program"
2) "k100"
3) "k1"
4) "china:city"
[root@localhost bin]# ll
total 18884
-rw-r--r--. 1 root root 92 Jun 14 09:57 dump.rdb
-rw-r--r--. 1 root root 302 Jun 14 09:54 dump.rdb.bak
-rwxr-xr-x. 1 root root 4829592 May 20 06:16 redis-benchmark
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-aof -> redis-server
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-rdb -> redis-server
-rwxr-xr-x. 1 root root 5002840 May 20 06:16 redis-cli
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 9486688 May 20 06:16 redis-server
[root@localhost bin]#
[root@localhost bin]# rm -rf dump.rdb
[root@localhost bin]# mv dump.rdb.bak dump.rdb
3、AOF:
概念:
以日志形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数。
优点:备份机制更稳健,丢失数据概率更低,通过操作AOF文件可以处理误操作
缺点:比RDB占用更多磁盘,恢复备份速度慢,每次读写都同步的话有性能压力
AOF持久化流程:
客户端在请求写命令时会被append追加到AOF缓冲区内
AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中
AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量
redis.conf关于AOF相关配置:
开启AOF默认:,默认不开启no,开启需要修改为yes
appendonly no
AOF生成文件名:默认为appendonly.aof,生成的路径同RDB
appendfilename "appendonly.aof"
保存文件生成,同时开启AOF与RDB时,系统会使用AOF保存数据:
AOF使用与恢复
执行操作命令:appendonly.aof文件追加了内容:
127.0.0.1:6379> set k11 v11
OK
127.0.0.1:6379> set k12 v12
OK
127.0.0.1:6379> set k13 v13
OK
-rw-r--r--. 1 root root 0 Jun 14 11:29 appendonly.aof
-rw-r--r--. 1 root root 302 Jun 14 09:54 dump.rdb
-rwxr-xr-x. 1 root root 4829592 May 20 06:16 redis-benchmark
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-aof -> redis-server
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-rdb -> redis-server
-rwxr-xr-x. 1 root root 5002840 May 20 06:16 redis-cli
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 9486688 May 20 06:16 redis-server
[root@localhost bin]#
[root@localhost bin]#
[root@localhost bin]#
[root@localhost bin]# ll
total 18884
-rw-r--r--. 1 root root 116 Jun 14 11:33 appendonly.aof
-rw-r--r--. 1 root root 302 Jun 14 09:54 dump.rdb
-rwxr-xr-x. 1 root root 4829592 May 20 06:16 redis-benchmark
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-aof -> redis-server
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-check-rdb -> redis-server
-rwxr-xr-x. 1 root root 5002840 May 20 06:16 redis-cli
lrwxrwxrwx. 1 root root 12 May 20 06:16 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 9486688 May 20 06:16 redis-server
127.0.0.1:6379> keys *
1) "k12"
2) "k11"
3) "k13"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> shutdown
not connected> exit
[root@localhost bin]# redis-server /opt/redis.conf
[root@localhost bin]# redis-cli
127.0.0.1:6379> keys *
1) "k11"
2) "k13"
3) "k12"
[root@localhost bin]# cp -r appendonly.aof appendonly.aof.bak
[root@localhost bin]# rm -rf appendonly.aof
[root@localhost bin]# mv appendonly.aof.bak appendonly.aof
异常恢复:
当AOF文件损坏,可通过如下命令执行文件修复:
redis-check-aof --fix appendonly.aof
AOF同步频率设置
设置始终同步:appendfsync always
,性能较差,但是数据完整性好
Rewrite压缩
当AOF文件大小超过所设定的阈值时(>=64M*2),Redis会启动AOF的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof开启此功能。
使用fork子进程将原来文件重写,把rdb的快照已二进制形式附在新的aof头部,作为已有的历史数据据,替换原有的流水账操作。
1、概念:
主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
2、优势:
(1)读写分离,性能扩展
(2)容灾快速恢复
3、主从复制的实现:
创建/myredis文件夹
复制redis.conf配置文件到文件夹中
配置一主两从,创建三个配置文件
redis6379.conf
redis6380.conf
redis6381.conf
在三个配置文件中写入内容
配置先关闭AOF或改名,配置redis6379、redis6380、redis6381配置文件
include /myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
include /myredis/redis.conf
pidfile /var/run/redis_6380.pid
port 6380
dbfilename dump6380.rdb
include /myredis/redis.conf
pidfile /var/run/redis_6381.pid
port 6381
dbfilename dump6381.rdb
[root@localhost myredis]# redis-server redis6379.conf
[root@localhost myredis]# redis-server redis6380.conf
[root@localhost myredis]# redis-server redis6381.conf
[root@localhost myredis]# ps -ef | grep redis
root 3906 1 0 11:53 ? 00:00:08 redis-server *:6379
root 4024 1 0 13:07 ? 00:00:00 redis-server *:6380
root 4030 1 0 13:07 ? 00:00:00 redis-server *:6381
root 4036 3743 0 13:07 pts/3 00:00:00 grep --color=auto redis
[root@localhost myredis]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:cc2e7d7d4336c03f7e4333a94a56f4bc9fdbf464
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
slaveof
:成为某个实例的从服务器:
在6380与6381上执行
127.0.0.1:6380> slaveof 127.0.0.1 6379
127.0.0.1:6381> slaveof 127.0.0.1 6379
4、test主从测试:
场景:主服务6379做写操作,查看从服务器,且从服务器不能做写操作
5、常用三招:
一主两从:
从服务器挂掉后,重启会变成主服务,需要重新加入,数据会从主服务器重新复制一份
主服务器挂掉后,从服务器还是从服务器,主服务器重启后还是主服务器
薪火相传:
反客为主:
当一个master宕机后,可以让一台slave升为master,其后面的slave不用做任何修改
slaveof no one
6、主从复制原理
(1)当从服务器连上主服务器之后,从服务器向主服务器发送进行数据同步消息
(2)主服务器接到从服务器发送过来同步消息,把主服务器数据进行持久化,生成RDB文件,把RDB文件发送给从服务器,从服务器拿到RDB进行读取
(3)每次主服务器进行写操作之后,和从服务器进行数同步
7、哨兵模式(sentinel)
含义:
反客为主的自动版,能否后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
启动哨兵模式:
如一主二从的场景下,在myredis文件夹下建立sentinel.conf,配置哨兵模式,启动哨兵
sentinel monitor mymaster 127.0.0.1 6379 1
mymaster为监控对象别名,1为至少多少个哨兵同意迁移的数量
[root@localhost myredis]# redis-sentinel sentinel.conf
配置哨兵的优先级:
redis.conf配置文件中:slave-priority 100
,值越小优先级越高。
当优先级相同时选举偏移量最大的
当偏移量一样的时选举runid最小的(随机)
1、集群概念:
Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数量的1/N。
Redis集群通过分区(partition)来提供一定程序的可用性(avaliability):即使集群中有一部分节点失效或者无法通讯,集群也可以继续处理命令请求。
Redis集群的优势:实现扩容、分摊压力、无中心配置相对简单
Redis集群的不足:多键操作不被支持、多键事务不支持(lua脚本不支持)、技术出现较晚,已有redis服务迁移到集群复杂度较高
2、redis集群搭建:
[root@localhost myredis]# vi redis6379.conf
include /myredis/redis.conf
pidfile "/var/run/redis_6379.pid"
port 6379
dbfilename "dump6379.rdb"
#开启集群模式
cluster-enabled yes
#设置节点的名字
cluster-config-file nodes-6379.conf
#超时切换时间
cluster-node-timeout 15000
同样方式复制并修改(VI命令替换操作:%s/6379/6380):
[root@localhost myredis]# cp redis6379.conf redis6380.conf
[root@localhost myredis]# ll
total 104
-rw-r--r--. 1 root root 244 Jun 27 17:42 redis6379.conf
-rw-r--r--. 1 root root 244 Jun 27 17:44 redis6380.conf
-rw-r--r--. 1 root root 93721 Jun 27 17:38 redis.conf
-rw-r--r--. 1 root root 392 Jun 27 15:45 sentinel.conf
[root@localhost myredis]# cp redis6379.conf redis6381.conf
[root@localhost myredis]# cp redis6379.conf redis6389.conf
[root@localhost myredis]# cp redis6379.conf redis6390.conf
[root@localhost myredis]# cp redis6379.conf redis6391.conf
[root@localhost myredis]# vi redis6380.conf
[root@localhost myredis]# vi redis6381.conf
[root@localhost myredis]# vi redis6381.conf
[root@localhost myredis]# vi redis6389.conf
[root@localhost myredis]# vi redis6390.conf
[root@localhost myredis]# vi redis6391.conf
[root@localhost myredis]# redis-server redis6379.conf
[root@localhost myredis]# redis-server redis6380.conf
[root@localhost myredis]# redis-server redis6381.conf
[root@localhost myredis]# redis-server redis6389.conf
[root@localhost myredis]# redis-server redis6390.conf
[root@localhost myredis]# redis-server redis6391.conf
[root@localhost myredis]# ps -ef |grep redis
root 1596 1 0 15:33 ? 00:00:11 redis-server *:6380
root 1603 1 0 15:33 ? 00:00:10 redis-server *:6381
root 1644 1 0 15:42 ? 00:00:18 redis-sentinel *:26379 [sentinel]
root 1661 1 0 15:52 ? 00:00:09 redis-server *:6379
root 1926 1 0 17:53 ? 00:00:00 redis-server *:6389 [cluster]
root 1932 1 0 17:53 ? 00:00:00 redis-server *:6390 [cluster]
root 1938 1 0 17:53 ? 00:00:00 redis-server *:6391 [cluster]
[root@localhost myredis]# cd /opt/redis-6.2.4/src/
[root@localhost src]# redis-cli --cluster create --cluster-replicas 1 192.168.170.107:6379 192.168.170.107:6380 192.168.170.107:6381 192.168.170.107:6389 192.168.170.107:6390 192.168.170.107:6391
[ERR] Node 192.168.170.107:6379 is not configured as a cluster node.错误需要将redis.conf下的
cluster-enabled yes
的注释打开
配置6379、6380、6381为master,6389、6390、6391为slaver,yes确认
[root@localhost src]# redis-cli -c -p 6379
127.0.0.1:6379> cluster nodes
3、redis集群分配原则:
分配原则:尽量保证每个主数据运行在不同的IP地址,每个主库和从库不在一个IP地址上
选项 --cluster-replicas 1表示我们希望为集群中的每个主节点创建一个从节点。
4、slots(插槽):
一个Redis集群包含16384个插槽(hash slot),数据库中每个键都属于这16384个插槽的其中之一。
集群使用公式CRC16(key)%16384来计算键key属于哪个槽,其中CRC176(key)语句用于计算键key和CRC16校验和。
集群中的每个节点负责处理一部分插槽。
添加数据,即往插槽内添加数据
添加多个数据时会报错
如要插入多条数据,需要分组操作
计算key对应的插槽值:cluster keyslot k1
计算对应插槽中的数值数量(只能看到属于自己集群的插槽):cluster countkeysinslot 12706
返回操作中n个数值:cluster getkeysinslot 449 1
5、故障恢复
(1)使6379集群shutdown,6380从机替换变为主机
cluster-require-full-coverage为yes,那么某一段插槽主从挂掉,整个集群都挂掉
cluster-require-full-coverage为no,那么某一段插槽主从挂掉,该段集群的插槽不能提供服务,其他插槽依然可以提供服务
6、集群的jedis开发
package cn.tedu.jedis;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
/**
* 演示redis集群操作
*/
public class RedisClusterDemo {
public static void main(String[] args) {
//创建对象
HostAndPort hostAndPort = new HostAndPort("192.168.170.107", 6379);
JedisCluster jedisCluster = new JedisCluster(hostAndPort);
//进行操作
jedisCluster.set("b1","value1");
String value = jedisCluster.get("b1");
System.out.println(value);
//关闭jedis连接
jedisCluster.close();
}
}
查看运行结果:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
value1
现象:
应用服务器压力变大
redis命中率降低
一直查询数据库
造成原因:
redis查询不到数据库
出现很多非正常url访问
解决方案:
对空值进行缓存:缓存空结果null值
设置访问白名单:使用bitmaps类型定义一个可以访问的名单,每次访问时进行拦截
布隆过滤器:(Bloom Filter)1970年布隆提出的,它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器用于检索一个元素是否在一个集合,但是也存在误识别的情况
进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,设置黑名单
现象:
数据库的访问压力瞬时增加、redis里面没有出现大量key过期、redis正常运行
造成原因:
redis某个key过期了,大量访问使用这个key
解决方案:
预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis内,加大这些热门数据key的时长
实时调整:现场监控哪些数据热门,实时调整key的过期时长
使用锁的方式:设置排它锁:在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,释放锁。若其他线程发现获取锁失败,则睡眠一段时间后重试
现象:
数据库压力变大、服务器崩溃
造成原因:
在极少的时间段,查询大量key的集中过期情况
解决方案:
构建多级缓存架构:nginx缓存+redis缓存+其他缓存(ehcache等)
使用锁或队列:用加锁或者队列保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量并发请求落到底层存储系统上,不适用于高并发情况
设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台更新实际key的缓存
将缓存失效实际分散开:可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,很难引发集体失效的事件
4、分布式锁:
解决问题:
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式多线程,多进程并且分布在不同机器上,这将使原单机部署的情况下并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
分布式锁主流的实现方案
基于数据库实现分布式锁
基于缓存Redis等
基于Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
性能:redis最高
可靠性:zookeeper最高
这里介绍的是基于redis实现的分布式锁
实现方案:使用redis实现分布式锁
使用setnx实现分布式锁:
192.168.37.8:6381> setnx users 10
(integer) 1
192.168.37.8:6381> expire users 10
(integer) 1
192.168.37.8:6381> ttl users
为防止上锁后redis机器故障,使用set nx ex上锁同时设置过期时间:(原子操作)
java代码实现分布式锁
springboot编写的代码如下:
package cn.tedu.redis_springboot.controller;
import io.netty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("testLock")
public void testLock(){
//1获取锁,sentne,并设置锁的过期时间3s
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",3, TimeUnit.SECONDS);
//2获取锁成功、查询num的值
if(lock){
Object value = redisTemplate.opsForValue().get("num");
//2.1判断numb为空return
if(StringUtils.isEmpty(value)){
return;
}
//2.2有值就转成int
int num = Integer.parseInt(value+"");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num",++num);
//2.4释放锁,del
redisTemplate.delete("lock");
}
else {
//3获取锁失败,每隔0.1秒再获取
try{
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@GetMapping
public String testRedis(){
//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
运行,并在管理台测试,首先建立key->num赋值为0
127.0.0.1:6379> set num "0"
OK
127.0.0.1:6379> get num
"0"
另一个窗口通过ab压力测试工具进行测试,1000个请求,100个请求并发,并且触发分布式锁
ab -n 1000 -c 100 http://192.168.31.12:8080/redisTest/testLock
127.0.0.1:6379> get num
"1000"
解决释放错锁的问题(防误删)
第一步:通过uuid表示不同的操作
set lock uuid nx ex 10
第二部:释放锁时候,首先判断当前uuid和要适当锁uuid是否一样
改造测试代码如下:
package cn.tedu.redis_springboot.controller;
import io.netty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1获取锁,sentne,并设置锁的过期时间3s
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,3, TimeUnit.SECONDS);
//2获取锁成功、查询num的值
if(lock){
Object value = redisTemplate.opsForValue().get("num");
//2.1判断numb为空return
if(StringUtils.isEmpty(value)){
return;
}
//2.2有值就转成int
int num = Integer.parseInt(value+"");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num",++num);
//2.4释放锁,del
//判断比较uuid值是否一样
Object lockUuid = redisTemplate.opsForValue().get("lock");
if(lockUuid.equals(uuid)){
redisTemplate.delete("lock");
}
}
else {
//3获取锁失败,每隔0.1秒再获取
try{
Thread.sleep(100);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@GetMapping
public String testRedis(){
//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
解决删除操作非原子性问题:
场景:当比较uuid一样,当a删除操作的时候,正要删除还没有删除时,锁到了过期时间自动释放,此时b上了这把锁,会导致a把b的锁删除掉。
可以通过定义lua脚本优化代码:
package cn.tedu.redis_springboot.controller;
import io.netty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping("testLock")
public void testLock(){
//1声音一个uuid,讲作为一个value放入我们的key对应的值中
String uuid = UUID.randomUUID().toString();
//2定义一个锁:lua脚本可以使用同一把锁,来实现删除!
String skuId = "25";
String locKey= "lock" + skuId;
//3取锁,sentne,并设置锁的过期时间3s
Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey,uuid,3, TimeUnit.SECONDS);
//2获取锁成功、查询num的值
if(lock){
Object value = redisTemplate.opsForValue().get("num");
//2.1判断numb为空return
if(StringUtils.isEmpty(value)){
return;
}
//2.2有值就转成int
int num = Integer.parseInt(value+"");
//2.3把redis的num加1
redisTemplate.opsForValue().set("num",String.valueOf(++num));
/*使用lua脚本来锁*/
//定义lua脚本
String script = "if redis.call('get',KEY[1]) == ARGV[1] then return redis.call('del',KEY[1]) else return 0 end";
//使用redis执行lua脚本
DefaultRedisScript<Long> redisScript= new DefaultRedisScript<>();
redisScript.setScriptText(script);
//设置一下返回类型为Long
//因为删除的时候,返回为0,给其封装为数据类型,如果不封装那么默认返回String
//那么返回字符串与0会发成错误
redisScript.setResultType(Long.class);
//第一个要是script脚本,第二个需要判断的key,第三个就是key对应的值
redisTemplate.execute(redisScript, Arrays.asList(locKey),uuid);
}
else {
//3获取锁失败,每隔0.1秒再获取
try{
Thread.sleep(1000);
testLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@GetMapping
public String testRedis(){
//设置值到redis
redisTemplate.opsForValue().set("name","lucy");
//从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
总结-分布式锁可用性需要同事满足四个条件:
互斥性: 在任意时刻,只有一个客户端能持有锁。
不发生死锁: 即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
解铃还须系铃人: 加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
加锁和解锁必须具有原子性。
1、ACL(访问控制列表):
简介
Access Control List:Redis6提供ACL功能对用户进行更细粒度的权限控制。
命令
使用acl list展现用户权限列表
127.0.0.1:6379> acl list
使用acl cat查看添加权限的指令类别
查看当前acl用户:
127.0.0.1:6379> acl whoami
添加acl用户:(可用,包含密码,可操作包含cached的key,只能get命令操作)
127.0.0.1:6379> acl setuser mary on >password ~cached:* +get
切换用户,进行测试:
127.0.0.1:6379> auth mary password
2、IO多线程
简介:
Redis6加入了多线程:值得是客户端交互部分的网络IO交互处理模板多线程,而非执行命令多线程,Redis6执行命令依然是单线程的。
原理架构:
Redis的多线程部分只是用户处理网路数据的读写和协议解析,执行命令依然是单线程的,因为是需要控制key、lua、事务。
3、工具支持cluster
Redis5之前的版本搭建集合需要单独安装ruby环境,Redis5讲redis-trib.rb的功能集成到了redis-cli,另外官方redis-benchmark工具开始支持cluster模式,通过多线程的方式对多个分片进行压测。