
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. (我的放在新建的soft目录下了)
4、进入redis-6.2.5 目录执行make命令
cd /soft/redis-6.2.5
make
5、执行安装make install
6、验证安装成功
cd /usr/local/bin
ll
7、相关软件介绍:
8、前台启动(不推荐)
9、后台启动
(1)复制配置文件
cp -f /opt/
(2)修改参数配置,将 daemonize no 改为 daemonize yes ,让服务支持在后台启动
[root@localhost redis-6.2.4]# cd /opt/
[root@localhost opt]# f
(3)启动redis
[root@localhost bin]# cd /usr/local/bin/
[root@localhost bin]# redis-server /f
[root@localhost bin]# ps -ef|grep redis
(4)使用redis-cli测试
10、redis关闭
(1)redis-cli shutdown(进入终端shutdown也可以)
(2)kill -9 xxx(进程)
(一)Redis key操作
查看所有key:keys *
添加 key value:set
判断key是否存在 exists
查看key的类型:type
删除key数据:del
选择非阻塞删除:unlink(异步删除)
设置key的过期时间(秒):expire
查看ttl过期时间(秒):ttl(-1永久不过期,-2已经过期)
切换数据库:select
查看当前数据库的key数量:dbsize
清空当前库内数据 flushdb (慎用)
通杀所有库内数据 flushall (慎用)
(二)Redis字符串(String)
简介:字符串,一个key对应一个value,是二进制安全的,是Redis最基本数据类型,value最多512M,底层为动态字符串,ArrayList
设置值,相同key值覆盖:set
set k1 v100
获取值:get
get k1
追加值:append,返回总长度
append k1 abcd
获取值的长度:strlen
strlen k1
当key存在时操作:setnx,设置成功返回1,设置失败返回0
setnx k1 v1
将数字类型值+1/-1:incr/decr,原子性操作,不受多线程机制打断。
incr k3
decr k3
将key存储的数字值递增x/递减x:incrby/decrby
incrby k3 10
decrby k3 5
同时设置一个或多个key-value键值对:mset
mset k1 v1 k2 v2 k3 v3
同时获取一个或多个value:mget
mget k1 k2 k3
设置多个key-value(当key都不存在时设置成功):msetnx
msetnx k11 v11 k12 v12 k13 v13
msetnx k1 v11 k4 v4
获取范围的值(开始-结束):getrange
getrange name 0 3
设置范围的值(开始位置-覆盖):setrange,返回总长度
setrange name 3 abc
设置key的同时设置过期时间:setex
setex age 20 value30
以新值换旧值(显示旧值):getset
getset name jack
(三)Redis列表(List)
简介:单键多值的字符串列表,可以按照插入顺序排序,底层为双向链表(zipList(数据少时)->quickList(数据多时))
从左边/右边插入一个或多个值:lpush/rpush,返回数组长度
按照索引下标(范围)获取元素,从左到右(0表示左边第一个,-1表示右边第一个):lrange
从左边或右边取出一个值:lpop/rpop
从k1列表右边吐出一个值,插入到v2列表的左边:rpoplpush
按照索引下标(单值)获取元素(从左到右):lindex
获取列表的长度:llen
llen k1
在key对应的value前面/后面插入new value:linset before/after
linsert k1 before "v3" "v31"
linsert k1 after "v2" "v21"
从左边删除n个对应的value:lrem
lrem k1 2 "new11"
将列表key下标为index的值替换成value:lset
lset k1 1 "new31"
(四)Redis集合(Set)
Redis Set是String类型的无序集合,它的底层其实是一个value为null的hash表,value自动排重且无序
将一个或多个元素加入到集合key中:sadd,已经存在元素将忽略
sadd k1 v1 v2 v3
取出集合中的所有值:smembers
smembers k1
判断key集合中是否包含对应的value:sismember,1有0无
sismember k1 v1
返回集合中元素个数:scard
scard k1
从集合中删除某一个或多个元素:srem
srem k1 v1
随机从该集合吐出一个元素:spop
spop k1
随机从集合中取出n个值,不会从集合中删除:srandmember
srandmember k1 2
把集合中的一个值从一个集合移动到另一个集合:smove
smove k1 k2 v3
取两个集合的交集/并集/差集(key1中存在,key2中不存在):sinter/sunoin/sdiff
inter k2 k3
sunion k2 k3
sdiff k2 k3
(五)Redis哈希(Hash)
hset user:1001 id 1hset user:1001 name zhangsanhget user:1001 idhget user:1001 namehmset user:1002 id 2 name lisi age 30hexists user:1002 idhexists user:1002 namehexists user:1002 genderhkeys user:1002hvals user:1002hincrby user:1002 age 2hsetnx user:1002 age 40hsetnx user:1002 gender 1(六)Redis有序集合(Zset)
clearzadd topn 200 java 300 c++ 400 mysql 500 php<start><stop>之间:zrange,自动按照score排序,[withscores]可以返回评分zrange topn 0 -1zrange topn 0 -1 withscoreszrangebyscore <key> <min> <max> [withscores] [limit offset count]zrangebyscore topn 300 500 withscoreszrevngebyscore <key><max><min>[withscores][limit offset count]zincrby<key><increment><value>zincrby topn 50 javazrem<key><value>zrem topn phpzcount<key><min><max>zcount topn 200 300zrank<key><value>zrank topn c++1、units单位:
只支持bytes,支持bit,不区分大小写
2、INCLUDES:
包含其他的配置文件
3、NETWORK:网络相关配置
# foobared 取消注释,设置对应的密码信息1、发布与订阅:
(1)发送者:pub发送消息
(2)订阅者:sub接受消息
redis客户端可以订阅任意数量的频道
2、发布订阅流程
(1)客户端可以订阅频道
(2)当这个频道发布消息后,消息就会发送给订阅的客户端
3、发布订阅的命令行实现
subscribe channel1publish channel1 hello1、Bitmaps
setbit<key><offset><value>getbit<key><offset>getbit users:20210101 1bitcount[begin][end]bitcount users:20210101bitcount users:20210101 1 5bitop and/or/not/xorbitop and unique:users:and:20201104_03 unique:users:20201103 unique:users:20201104bitcount unique:users:and:20201104_03bitop or unique:users:or:20201104_03 unique:users:20201103 unique:users:20201104bitcount unique:users:or:20201104_032、HyperLogLog
pdadd<key><element>[element],1成功0失败pfadd program "java"pfadd program "php"pfadd program "java"pfadd program "c++" "mysql"pfcount<key>pfmerge k100 k1 program3、Geospatial
geoadd<key><longitude><latitude><member>[<longitude><latitude><member>]...geoos<key><member>[member]...geopos china:city shanghaigeopos china:city beijinggeodist<key><member2><member2><单位>geodist china:city beijing shanghai kmgeoradius<key><longitude><latitude>radius m|km|ft|mi1、idea建立maven工程
2、引入相关依赖:
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.2.0</version>
</dependency>
3、jedis连接redis测试(Maven)
du.jedis;import redis.clients.jedis.Jedis;/*** @author archibald* @date 2021/9/8* @apiNote*/
public class jedisDemo1 {public static void main(String[] args) {//创建Jedis对象,需要修改f的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@Testpublic void demo1(){//创建Jedis对象,需要修改f的bind(注释)与protected-mode(no)配置Jedis jedis =new Jedis("192.168.170.107",6379);//清空redisjedis.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获取valueString value = ("k1");System.out.println("k1对应的value为:"+value);}
查看运行结果:
5、Jedis-API:操作String
//操作String@Testpublic void demo2(){//创建Jedis对象,需要修改f的bind(注释)与protected-mode(no)配置Jedis jedis =new Jedis("192.168.170.107",6379);//清空redisjedis.flushDB();//添加多个数据jedis.mset("str1","v1","str2","v2","str3","v3");//查询所有key值System.out.("str1","str2","str3"));}
查看运行结果:
6、Jedis-API:操作List
//操作List@Testpublic void demo3(){//创建Jedis对象,需要修改f的bind(注释)与protected-mode(no)配置Jedis jedis =new Jedis("192.168.170.107",6379);//清空redisjedis.flushDB();//添加数据jedis.lpush("k1","lucy","mary","jack");//查询数据List<String> value = jedis.lrange("k1", 0, -1);System.out.println(value);}
查看运行结果:
7、Jedis-API:操作set
//操作set@Testpublic void demo4(){//创建Jedis对象,需要修改f的bind(注释)与protected-mode(no)配置Jedis jedis =new Jedis("192.168.170.107",6379);//清空redisjedis.flushDB();//添加数据jedis.sadd("name","luck","mary","jack");//查询数据Set<String> names = jedis.smembers("name");System.out.println(names);}
查看运行结果:
8、Jedis-API:操作set
//操作set@Testpublic void demo5(){//创建Jedis对象,需要修改f的bind(注释)与protected-mode(no)配置Jedis jedis =new Jedis("192.168.170.107",6379);//清空redisjedis.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);}
查看运行结果:
9、Jedis-API:操作Hash
//操作Hash@Testpublic void demo6(){//创建Jedis对象,需要修改f的bind(注释)与protected-mode(no)配置Jedis jedis =new Jedis("192.168.170.107",6379);//清空redisjedis.flushDB();//添加数据jedis.hset("users","age","20");//查询数据String hget = jedis.hget("users", "age");System.out.println(hget);}
查看运行结果:
10、Jedis-API:操作Zset
//操作Zset@Testpublic void demo7(){//创建Jedis对象,需要修改f的bind(注释)与protected-mode(no)配置Jedis jedis =new Jedis("192.168.170.107",6379);//清空redisjedis.flushDB();//添加数据jedis.zadd("china",100d,"shanghai");//查询数据Set<String> china = ange("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.apachemons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version>
</dependency>
<dependency><groupId&disson</groupId><artifactId>redisson</artifactId><version>2.10.4</version>
</dependency>
3、springboot配置文件中配置redis相关内容
文件位置为resources下面的application.properties
# Redis服务器地址
dis.host=192.168.170.107
# Redis服务器连接端口
dis.port=6379
# Redis服务器连接密码(默认为空)
dis.password=
# Redis数据库索引(默认为0)
dis.database=0
# 连接超时时间(毫秒)
dis.timeout=1800000
# 连接池最大连接数(使用负值表示没有限制)
dis.jedis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
dis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
dis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
dis.jedis.pool.min-idle=0
4、创建redis配置类:
du.fig;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 t.annotation.Bean;
import t.annotation.Configuration;
import org.dis.cache.RedisCacheConfiguration;
import org.dis.cache.RedisCacheManager;
import org.tion.RedisConnectionFactory;
import org.RedisTemplate;
import org.dis.serializer.Jackson2JsonRedisSerializer;
import org.dis.serializer.RedisSerializationContext;
import org.dis.serializer.RedisSerializer;
import org.dis.serializer.StringRedisSerializer;import java.time.Duration;/*** @author archibald* @date 2021/9/8* @apiNote*/@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic 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;}@Beanpublic 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添加测试方法:
du.ller;import org.springframework.beans.factory.annotation.Autowired;
import org.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 {@Autowiredprivate RedisTemplate redisTemplate;@GetMappingpublic String testRedis(){//设置值到redisredisTemplate.opsForValue().set("name","lucy");//从redis获取值String name = (String)redisTemplate.opsForValue().get("name");return name;}
}
6、启动类启动Springboot类:RedisSpringbootApplication
显示运行结果:
浏览器访问验证:localhost:8080/redisTest,显示结果:
1、Redis事务定义
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序的执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队
2、Multi、Exec、discard
基本概念
输入Multi命令开始:输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队
正常场景:
异常场景2种:
3、事务命令演示
multiset key1 value1set key2 value2execmultiset a1 v1set a2 v2discardmultiset b1 v1set b2 v2set b3execmultiset c1 v1incr c1set c2 v2exec4、事务冲突的问题
场景:多个人同时使用一个账户,参加双十一抢购,购买不同的商品,未加事务会产生冲突。
悲观锁
每次拿数据适都认为别人会修改,所以每次在拿数据适都会上锁,这样别人想拿数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁,读锁,写锁,都是操作前上锁。
乐观锁
每次拿数据的适合都认为别人不会修改,所以不会上锁,但是在更新的适合会判断一下在此期间别人有没有取更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用check-and-set机制实现事务的。
WATCH key[key …]
含义: 在执行multi之前,先执行wath key1[key2] 可以监视一个或多个key,如果在事务执行之前这些key被其他命令所改动,那么事务讲被打断。
举例: 同时打开两个客户端,都watch 同一个key,然后第一个窗口exec,第二个窗口再执行exec,被乐观锁住:
UNWATCH:
取消命令对所有key的监视。
5、Redis事务三特性:
单独的隔离操作:
事务种的所有名历经都会序列化、按顺利执行,事务在执行的过程中,不会被其他客户端发送来的命令所打断
没有隔离级别的概念:
队列中的命令没有提交之前不会实际被执行,因为事务提交前任何执行都不会被实际执行
不保证原子性
事务中如果有一条命令执行失败,其中的命令任然会被执行,没有回滚
1、简介
两种持久化方式
(1)RDB(Redis DataBase):内存中数据直接写入文件中
(2)AOF(Append Of File):以追加的行为把内容写入文件中
2、RDB:
概念:
指定的时间间隔内讲内存中的数据集快照写入磁盘,它恢复时可以将快照文件直接读到内存里
RDB持久化流程:
Redis会单独创建fork一个子进程来进行持久化,先将数据写入一个临时文件中,待持久化过程结束后,再用这个临时文件替换上次持久化的文件,RDB方式比AOF文件更加高效,缺点是最后一次持久化的数据可能丢失。
Fork:
写时复制技术:新进程的所有数据(变量、环境变量、程序计数器等)数值都有原进程一致。
dbfilename dump.rdbdir ./dbfilename dump.rdbdir ./# save 3600 1# save 300 100# save 60 10000save Vs bgsave:建议设置自动持久化# save ""stop-writes-on-bgsave-error yesrdbcompre,默认yesrdbcompression yesrdbchecksum 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 /f
[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服务重启时,会重新load加载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
AOF备份恢复
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 /f
[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,性能较差,但是数据完整性好
每秒同步:appendfsync everysec
把同步时机交给操作系统:appendfsync no
Rewrite压缩
当AOF文件大小超过所设定的阈值时(>=64M*2),Redis会启动AOF的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof开启此功能。
使用fork子进程将原来文件重写,把rdb的快照已二进制形式附在新的aof头部,作为已有的历史数据据,替换原有的流水账操作。
no-appendfsync-on-rewrite no
1、概念:
主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
2、优势:
(1)读写分离,性能扩展
(2)容灾快速恢复
3、主从复制的实现:
创建/myredis文件夹
复制f配置文件到文件夹中
配置一主两从,创建三个配置文件
f
f
f
在三个配置文件中写入内容
配置先关闭AOF或改名,配置redis6379、redis6380、redis6381配置文件
include /f
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
include /f
pidfile /var/run/redis_6380.pid
port 6380
dbfilename dump6380.rdb
include /f
pidfile /var/run/redis_6381.pid
port 6381
dbfilename dump6381.rdb
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[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 <ip><port>:成为某个实例的从服务器:
在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文件夹下建立f,配置哨兵模式,启动哨兵
sentinel monitor mymaster 127.0.0.1 6379 1
mymaster为监控对象别名,1为至少多少个哨兵同意迁移的数量
[root@localhost myredis]# f
哨兵默认端口为26379
当主机挂掉,从服务器选举成为主服务器:
再次启动原来的主服务器,变为从服务器
配置哨兵的优先级:
当偏移量一样的时选举runid最小的(随机)
1、集群概念:
Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数量的1/N。
Redis集群通过分区(partition)来提供一定程序的可用性(avaliability):即使集群中有一部分节点失效或者无法通讯,集群也可以继续处理命令请求。
Redis集群的优势:实现扩容、分摊压力、无中心配置相对简单
Redis集群的不足:多键操作不被支持、多键事务不支持(lua脚本不支持)、技术出现较晚,已有redis服务迁移到集群复杂度较高
2、redis集群搭建:
[root@localhost myredis]# f include /f
pidfile "/var/run/redis_6379.pid"
port 6379
dbfilename "dump6379.rdb"
#开启集群模式
cluster-enabled yes
#设置节点的名字
f
#超时切换时间
cluster-node-timeout 15000
同样方式复制并修改(VI命令替换操作:%s/6379/6380):
[root@localhost myredis]# f
[root@localhost myredis]# ll
total 104
-rw-r--r--. 1 root root 244 Jun 27 17:f
-rw-r--r--. 1 root root 244 Jun 27 17:f
-rw-r--r--. 1 root root 93721 Jun 27 17:f
-rw-r--r--. 1 root root 392 Jun 27 15:f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[root@localhost myredis]# f
[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.错误需要将f下的
cluster-enabled yes的注释打开
配置6379、6380、6381为master,6389、6390、6391为slaver,yes确认
配置完成:
5) 连接集群并查看:
[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从机替换变为主机
(2)主-从均挂掉的情况
cluster-require-full-coverage为yes,那么某一段插槽主从挂掉,整个集群都挂掉
cluster-require-full-coverage为no,那么某一段插槽主从挂掉,该段集群的插槽不能提供服务,其他插槽依然可以提供服务
6、集群的jedis开发
du.jedis;import org.apachemons.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 = ("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 .html#StaticLoggerBinder for further details.
value1
1、缓存穿透
现象:
应用服务器压力变大
redis命中率降低
一直查询数据库
造成原因:
redis查询不到数据库
出现很多非正常url访问
解决方案:
对空值进行缓存:缓存空结果null值
设置访问白名单:使用bitmaps类型定义一个可以访问的名单,每次访问时进行拦截
布隆过滤器:(Bloom Filter)1970年布隆提出的,它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器用于检索一个元素是否在一个集合,但是也存在误识别的情况
进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,设置黑名单
2、缓存击穿:
现象:
数据库的访问压力瞬时增加、redis里面没有出现大量key过期、redis正常运行
造成原因:
redis某个key过期了,大量访问使用这个key
解决方案:
预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis内,加大这些热门数据key的时长
实时调整:现场监控哪些数据热门,实时调整key的过期时长
使用锁的方式:设置排它锁:在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,释放锁。若其他线程发现获取锁失败,则睡眠一段时间后重试
3、缓存雪崩
现象:
数据库压力变大、服务器崩溃
造成原因:
在极少的时间段,查询大量key的集中过期情况
解决方案:
构建多级缓存架构:nginx缓存+redis缓存+其他缓存(ehcache等)
使用锁或队列:用加锁或者队列保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量并发请求落到底层存储系统上,不适用于高并发情况
设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台更新实际key的缓存
将缓存失效实际分散开:可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,很难引发集体失效的事件
4、分布式锁:
解决问题:
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式多线程,多进程并且分布在不同机器上,这将使原单机部署的情况下并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
分布式锁主流的实现方案
基于数据库实现分布式锁
基于缓存Redis等
基于Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
性能:redis最高
可靠性:zookeeper最高
这里介绍的是基于redis实现的分布式锁
实现方案:使用redis实现分布式锁
使用setnx实现分布式锁:
127.0.0.1:6379> setnx users 10
删除key释放setnx分布式锁:
192.168.37.8:6381> del users
使用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上锁同时设置过期时间:(原子操作)
set users 10 nx ex 12
java代码实现分布式锁
springboot编写的代码如下:
du.ller;import ioty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.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 urrent.TimeUnit;@RestController
@RequestMapping("/redisTest")
public class RedisTestController {@Autowiredprivate RedisTemplate redisTemplate;@GetMapping("testLock")public void testLock(){//1获取锁,sentne,并设置锁的过期时间3sBoolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111",3, TimeUnit.SECONDS);//2获取锁成功、查询num的值if(lock){Object value = redisTemplate.opsForValue().get("num");//2.1判断numb为空returnif(StringUtils.isEmpty(value)){return;}//2.2有值就转成intint num = Integer.parseInt(value+"");//2.3把redis的num加1redisTemplate.opsForValue().set("num",++num);//2.4释放锁,delredisTemplate.delete("lock");}else {//3获取锁失败,每隔0.1秒再获取try{Thread.sleep(100);testLock();} catch (InterruptedException e) {e.printStackTrace();}}}@GetMappingpublic String testRedis(){//设置值到redisredisTemplate.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 192.168.31.12:8080/redisTest/testLock
查看num,值累加到1000
127.0.0.1:6379> get num
"1000"
解决释放错锁的问题(防误删)
第一步:通过uuid表示不同的操作
set lock uuid nx ex 10
第二部:释放锁时候,首先判断当前uuid和要适当锁uuid是否一样
改造测试代码如下:
du.ller;import ioty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.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 urrent.TimeUnit;@RestController
@RequestMapping("/redisTest")
public class RedisTestController {@Autowiredprivate RedisTemplate redisTemplate;@GetMapping("testLock")public void testLock(){String uuid = UUID.randomUUID().toString();//1获取锁,sentne,并设置锁的过期时间3sBoolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,3, TimeUnit.SECONDS);//2获取锁成功、查询num的值if(lock){Object value = redisTemplate.opsForValue().get("num");//2.1判断numb为空returnif(StringUtils.isEmpty(value)){return;}//2.2有值就转成intint num = Integer.parseInt(value+"");//2.3把redis的num加1redisTemplate.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();}}}@GetMappingpublic String testRedis(){//设置值到redisredisTemplate.opsForValue().set("name","lucy");//从redis获取值String name = (String)redisTemplate.opsForValue().get("name");return name;}
}
解决删除操作非原子性问题:
场景:当比较uuid一样,当a删除操作的时候,正要删除还没有删除时,锁到了过期时间自动释放,此时b上了这把锁,会导致a把b的锁删除掉。
可以通过定义lua脚本优化代码:
du.ller;import ioty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.RedisTemplate;
import org.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 urrent.TimeUnit;@RestController
@RequestMapping("/redisTest")
public class RedisTestController {@Autowiredprivate 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,并设置锁的过期时间3sBoolean lock = redisTemplate.opsForValue().setIfAbsent(locKey,uuid,3, TimeUnit.SECONDS);//2获取锁成功、查询num的值if(lock){Object value = redisTemplate.opsForValue().get("num");//2.1判断numb为空returnif(StringUtils.isEmpty(value)){return;}//2.2有值就转成intint num = Integer.parseInt(value+"");//2.3把redis的num加1redisTemplate.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对应的值ute(redisScript, Arrays.asList(locKey),uuid);}else {//3获取锁失败,每隔0.1秒再获取try{Thread.sleep(1000);testLock();} catch (InterruptedException e) {e.printStackTrace();}}}@GetMappingpublic String testRedis(){//设置值到redisredisTemplate.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、事务。
多线程默认是不开启的,需要配置文件中配置
io-threads 4
3、工具支持cluster
Redis5之前的版本搭建集合需要单独安装ruby环境,Redis5讲redis-trib.rb的功能集成到了redis-cli,另外官方redis-benchmark工具开始支持cluster模式,通过多线程的方式对多个分片进行压测。
本文发布于:2024-02-02 07:43:07,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170683099042352.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
| 留言与评论(共有 0 条评论) |