Redis
1、简介
优点:
1、负载均衡后保证数据之间的共享,将数据放入缓存中
2、缓解对数据库io的读写压力
3、操作都是原子性
4、定期数据写入磁盘
5、主从同步
6、数据类型:string,list,set,zset(有序集合),hash
7、支持不同方式的排序
2、C语言环境安装
yum gcc install
一路Yes就可以了
3、Redis安装
1、解压文件
tar -zxvf xxxxxxx.tar.gz
2、进入解压后文件夹编译
直接输入 make即可
如果出现错误
1、查看gcc是否安装成功
2、使用
//先执行make distclean 在make一次
3、安装
make install
安装成功后会在/usr/local/bin 生成文件
4、启动
1、前台启动
redis-server
2、后台启动
1、先到解压后的redis文件目录下复制一份 redis.conf 到一个你想到的路径下
cp redis.conf /XXX/redis.conf
2、找到daemonize no 改为yes
vim redis.conf
3、回到启动环境执行以下代码
redis-server /XXX/redis.conf
3、查看进程
ps -ef | grep redis
5、连接
redis-cli
ping
结果为pong为连接成功
//退出如下
exit;
6、关闭
1、用户连接后使用以下语言
shut down
2、指定端口关闭
ps -ef | grep redis //找到进程
kill -9 XXXXX //xxx为他的进程号
7、底层介绍
1、单线程+多路io复用技术
2、默认端口号6379
默认有16个数据库【从0到15】
select + index即可访问 密码都相同
8、五大基本类型
8.1、key value 操作
key * //查看所有key
set key vuale //插入
type key //判断key是什么类型
exists key //判断key是否存在
del key //删除key
unlink key //选择非阻塞删除
expire key 10 //设置key过期时间 单位秒
ttl key //查看还有多少秒过期 -1 表示永不过期 -2表示已经过期
dbsize //查看当前数据库有多少key
flushdb //清空当前库
flushall //清空所有
8.2 、unlink key
先进行对key删除,真正对value的删除会在后续异步进行操作
8.3、String数据类型
1、二进制安全(只要能序列化就能进行存储)
2、value最大可以存512M
基本操作:
get key1 //取值
set 相同的key //覆盖操作
append key XXX //返回value长度 和Stringbuffer.append一样
strlen k1 //获取key长度
setnx //只有key不存在才能赋值 不能覆盖
incr k1 //只要是数字就加1 返回操作后的值
decr k1 //只要是数字就减1 返回操作后的值
incrby k1 10 //自定义增加多少减少一样的
mset k1 vlaue k2 value //可以添加多个字符串
mget k1 k2 k3 //获取多个字符串
msetnx //可以设置多个 有一个重复整个语句都不会执行
mget //获取多个值
getrange k1 0 3 //获取k1对应字符串下标从的0到3之间是一个闭区间
setrange k1 2 ab //从k1对应的字符串下标2开始往后的2个字符为ab
setex k1 时间 value //注入数据库时就设定存活时间以及值 单位秒
getset k1 name //将获取和覆盖操作结合获取新的值覆盖旧的值
3、incr decr原子性操作与他是单线程有关
8.4、String的数据结构 Value的存储类型
简单动态字符串 类似javaArrayList
小于1M时每次扩容是之前的一倍,大于1M每次只会扩容1M最大512M
8.4、List(单键多值,双向链表:读慢写快)
lpush/rpuch k1 x x x x //从左右插入数据
lrange k1 0 -1 //读取所有的值
lpop/rpop k1 //从左边取值 从右边取值
lrange k1 0 -1 //从左往右下标从0开始取值 -1为取全部
rpoplpush k1 k2 //从k1的右边取一个值放到k2的左边
lindex k1 0 //从左边去取下标为0的数字
llen //获取列表的长度
linset k1 before|after value ele //从左往右在k1的链表里面“value”的值前或者后插入一个ele元素
lrem k1 2 ele //从左往右删除遍历到的第一个和第二个为ele vlaue
lset k1 1 ele //从左往右将下标索引为1的value替换为ele
8.5、List的数据结构QuickList
1、当元素很少的时候,会使用一块很少的内存存储,这个结构是ziplis,就是压缩列表
2、当元素多的时候会将多个ziplist 构建一个大链表 Quicklist
8.6、集合Set(自动排重,String类型的无序集合复杂度低O(1) )
sadd k1 x x x x //创建一个名为k1的无序集合
smembers k1 //取出k1所有的值
sismember k1 v1 //判断v1是不是k1的值
scard k1 //获取k1的长度
srem k1 v1 v2 //删除k1里的 v1 v2
spop k1 //随机取出一个值
srandmember k1 3 //随机从集合中取出3个值不会影响元数据
smove k1 k2 ele //将ele从k1移到k2中去
sinter k1 k2 //取交集
sunion k1 k2 //取并集
sdiff k1 k2 //取差集 包含k1不包含k2
8.7、SET数据类型(字典类似与Java:HashSet)
8.8、Hash(是一个String类型的field和value的键值对集合【映射表】java:Map 适合存储对象)
hset key filed value filed value //设置key为最外层键,里面添加字段filed对应的值vlaue
hget key filed value //获取值
hmset key filed value //同hset
hexists key filed //查看值是否存在
hkeys key //查看所有键
hvals key //查看所有的值
hincrby key filed value 3 //给值加3
hsetnx key filed value //添加值存在就不添加
8.9、Hash数据结构
1、数量少数时就用ziplist
2、数量多hashtable
8.10、Zset数据类型(有序的字典,每个value有一个对应的score以score来进行排序 )
zadd k1 score1 v1 score2 v1 score3 v3 //创建
zrange k1 0 -1 //取出所有内容 从小到大
zrange k1 0 -1 withscores //取出所有内容和score 从小到大
zrangebyscore k1 300 500 //取出score在300到500之间的数
zrevrangebyscore k1 //降序取
zincrby k1 50 value //给score加50
zrem k1 vlaue //删除元素
zcount k1 200 300 //计数200到300
zrank k1 value //获取元素排在第几位下标从0开始
8.11、ZSet数据机构
1、Zset也是Hash ,filed就是value值,value就是score
2、跳跃表
9、配置文件
9.1、设置单位
9.2、引入其他路径配置文件(类似于spring@import)
9.3、设置允许进入的ip地址(bind)
9.4、设置开启保护模式(只能本机访问,远程不能访问)
9.5、端口号
9.6、网络协议(backlog总和等于完成三次握手和未三次握手的数量)
9.7、TimeOut(设置超时连接0为永不超时 单位秒)
9.8、和timeOut相同 300秒检测如果操作就断开链接
9.9、设置进程号
9.10、日志级别
1、debug 开发环境能看到更详细信息
2、verbose 显示有用的信息
3、notice显示有用重要
9.10、日志输出文件路径
9.11、设置数据库大小
9.12、设置最大连接数
10、发布和订阅
类似于B站up猪和订阅者之间的关系
subscribe channel1 //订阅频道1
publish channel1 message //发布信息
11、Redis6的新数据类型
1、Bitmaps(进行位操作,偏移量大会使执行过慢)
setbit users:20210101 1 1 //设置第1位的二进制位数为1
getbit users:20210101 1 //取第一位的值
bitcount users:20210101 //获取设置key里的为1的数量
bitcount users:20210101 0 5 //获取0-5设置key里的为1的数量 -1为最后一位-2位倒数第二位
bitop and|or|xor result k1 k2 //对k1 k2 进行位运算并返回结果
12、HyperLogLog(去重集合;优点:比hsah,set内存利用率更高适合做页面访问统计)
pfadd k1 v1 v2 v3 //添加
pfcount k1 //计数
pfcount k1 v1 //计数 v1出现次数
pfmeger result k1 k2 //将k1 和k2中基数的数量进行相加到result中
13、GeoSpatial(用于存储地理位置)
geoadd china:city 112.23 31.23 shanghai //添加可以添加多个
有效经度-180到180纬度-85.05 到85.05
geopos key value //查询经纬度
geodist key shanghai beijin km //查询上海到背景的距离 有米千米 尺和英尺
georadius key 20 20 100 km //查询经纬度为20 20 100千米以内的 元素
14、Jedis
14.1、导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
14.2、操作(jedis中的方法和redis操作中一致)
public static void main(String[] args) {
Jedis jedis=new Jedis("192.168.10.130",6379);
//添加
jedis.set("k1","lucy");
//获取
System.out.println(jedis.get("k1"));
//测试
String ping = jedis.ping();
System.out.println(ping);
//获取所有的key
Set<String> keys = jedis.keys("*");
}
15、Springboot整合
15.1、springboot启动器依赖里内置redis,只需要导入一个连接池依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
spring2.x以后Redis的底层jedis换成了lettcuce
Jedis:直连多个线程操作不安全,想要连接就需要连接池! Bio模式
lettuce:采用netty ,实例可以再多个线程中进行共享,不存在线程不安全的情况! NIO模式
15.2、重写RedisTemplate覆盖原来配置类
@SpringBootTest
class SpringRedisApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//操作字符串
redisTemplate.opsForValue().set("k1","helloRedis");
redisTemplate.opsForValue().get("k1");
//操作list
// redisTemplate.opsForList();
//操作set
//操作数据库 获取连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushAll();
// connection.flushDb();
}
}
16、Redis事务和锁
Redis是一个单独隔离操作:事务中所有所有命令都会序列化并顺序执行,事务在执行过程中,不会被其他客户端送来的请求打断。
串联多个命令,防止插队。
16.1、Multi、Exec、Discard
1、先组队列
2、再执行
3、如果想打算就discard
multi //开启队列
set v1 k1
exec //执行
discard //放弃
16.2、组队的时候失败执行都不会成功
16.3、执行的时候失败,就执行的语句不会成功
16.4、事务悲观锁和乐观锁
16.5、Watch key(乐观锁适合多读的情况)
在执行multi之前,如果在事务执行之前有key发生改动,那么事务将会打断
先开启监听
watch k1
//2个客户端都开启队列并对k1进行操作
谁先提交 watch版本号就提高
//后面的事务就无法提交成功
16.6、Redis事务三特性
1、不保证原子性:队列中有错误所有操作都不会执行
2、没有隔离级别概念,提交任何指令前都不会被实际执行
3、单独隔离操作,事务执行过程中不会被其他事务打断
17、秒杀案例
1、并发测试工具httpd-tools
yum install httpd-tools
2、可能需要传参创建一个文件按以下格式填写传参数据
pid=123&
pid=123&name=123&
3、
ab -n 1000 -c 100 -p XXX enctype值 //XXX指的是上述文件名
请求路径
http://192.168.81.1:8080/getMx
SrpingBoot下的解决(1、连接超时问题(池化技术)2、超卖问题使用Redis事务乐观锁来解决):
package com.huang.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Random;
@Controller
@SuppressWarnings("all")
public class RedisController {
@Autowired
StringRedisTemplate redisTemplate;
static String productKey="MxProduct:0101";
static String userKey="Mx:USer";
@GetMapping("/getMx")
@ResponseBody
public String MxRedis(){
SessionCallback<List> sessionCallback=new SessionCallback<List>() {
@Override
public List execute(RedisOperations operations) throws DataAccessException {
Random random=new Random();
String user=new String("");
redisTemplate.watch(productKey);
for (int i = 0; i <6 ; i++) {
user=user+String.valueOf(random.nextInt(10));
}
if (redisTemplate.opsForSet().isMember(userKey,user)){
System.out.println("你已经秒杀过了!!");
return null;
}
if(Integer.parseInt(redisTemplate.opsForValue().get(productKey))<=0){
System.out.println("名额已经被抢光了!!!");
return null;
}
operations.multi();
operations.opsForSet().add(userKey, String.valueOf(user));
operations.opsForValue().decrement(productKey);
List exec =redisTemplate.exec();
return exec;
}
};
redisTemplate.multi();
List execute = redisTemplate.execute(sessionCallback);
if (execute==null || execute.size()==0){
System.out.println("秒杀失败");
}else {
System.out.println("秒杀成功");
}
return "秒杀成功!";
}
}
17.2、库存遗留问题
Lua脚本来解决,或者使用IBMMQ消息队列解决。
18、Redis持久化操作
18.1、RDB(在指定的时间间隔内将内存中的数集快照写入磁盘)
Redis数据存储位置:dump.rdb
过程(利用写时复制):Redis会单独创建一个子进程Fork,将数据写入一个临时文件,在指定的时间间隔内满足条件的数据写入将临时文件时间间隔结束将数据写入磁盘,超时则会等到写入条件满足立即执行写入操作。主进程效率提高不进行IO操作。最后一次持久化操作肯出现问题!
1、配置文件(redis.conf):
redis持久化的文件目录
磁盘满时不再写入
持久化文件是否压缩,检测文件完整性
Save配置
关于RDB的备份:
写个脚本定时复制一份dump.rdb,将文件名改为改为dump.rdb,放置到指定目录下即可。
18.2、AoF
开启AOF
AOF和RDB同时开启时,AOF优先级高于RDB。
AOF文件异常恢复
redis-check-aof --fix
AOF同步频率设置
appendfsync always //每次操作都更新日志,确保数据的完整性
appendfsync everysec //每秒
appendfsync no //同步时机交操作系统
压缩重写机制(条件:文件大于设置基值的2倍才会触发该机制,重写过程与RDB建立进程相似)
19、主从复制
1、读写分离
2、容灾快速回复(从机出错能切换到其他从机)
如何配置
1、创建一个/myredis文件(需要关闭AOF)
2、将配置文件复制到该文件目录下。
3、利用include创建多个启动配置文件,配置信息如下:
include /myredis/redis.conf
pidfile /var/run/redis6379.pid
port 6379
db filename dump6379.rdb
4、根据不同配置文件启动所有不同端口的redis服务。
redis-cli -p 6379
info replication 查看该端口redis服务的信息
5、配置从机
slaveof ip地址 端口号
宕机相关问题
从机宕机后再重启,角色会变为默认的Master。需要重新赋予新角色。
主机宕机后再重启,对整个内容没有影响。
给从机配置从机
slaveof ip地址 端口号
配置主机后选机
slaveof no one //如果主机宕机从机中使用代码
哨兵模式
1、创建一个conf文件在配置监听对象,并设哨兵同意数量
sentinel.conf内容如下:
sentinel monitor mymaster 127.0.0.1 6379 1
2、启动哨兵服务
redis-sentinel sentinel.conf
3、设计从机优先级(修改配置文件)
1、设置优先级 replica-priorrity 100 <!--值越小优先级越高-->
2、选择偏移量大的 数据同步最高
3、选择runid最小的
20、集群
**无中心化集群:**每个redis服务器都可以是请求入口,如果请求操作不是本服务器,会将请求转发给对应操作的服务器。
集群配置文件:
include /myredis/redis.conf
pidfile /var/run/redis6379.pid
port 6379
db filename dump6379.rdb
cluster-enabled yes //开集群
cluster-config-file nodes-6379.conf //集群节点命名
cluster-node-timeout 15000 //设置节点超时时间
修改好配置文件之后,开启每个redis服务,并进行集群操作。
到redis安装目录
cd /opt/redis-6.2.7/src
集群命令:
redis-cli --cluster create --cluster-replicas 1 192.168.10.130:6379 192.168.10.130:6380 192.168.10.130:6381 192.168.10.130:6389 192.168.10.130:6390 192.168.10.130:6391
集群如何连接:
redis-cli -c -p 6379
cluster node //查看集群信息
最简单的集群(1 ):
分为三个主机
slot分配:0-5460 5461-10922 10923-16283
关于集群的多个插入值:
例如: mset name{user} lucy age{user} 20 //这些键同属于user组
计算集群键的值:
cluster keyslot k1 //12706
查看插槽里的值:
cluster countkeysinslot 12706 //只能查看该端口号redis的插槽值
返回插槽里想要数量的键:
cluster getkeysinslot 12706 10 //只能查看该端口号redis的插槽键
如果节点挂了如何处理:
1、返回配置文件目录
cd /myredis
2、启动对应redis
redis-server redisXXXX.conf即可
失败的话可以使用以下方式
redis-cli -a 123456 --cluster add-node 192.168.10.103:6378 192.168.10.103:6377 --cluster-slave --cluster-master-id 66f3cbc063a84ff3c209fa2db7104e4666d82e9c
6377是主机 6378是从机 66f3cbc063a84ff3c209fa2db7104e4666d82e9c是主机节点值
-a 输入密码
删除节点:
进入redis操作界面输入 cluster forget 66f3cbc063a84ff3c209fa2db7104e4666d82e9c 即可
如果主从都挂了:
配置文件设置:
cluster-require-full-coverage yes //整个集群全挂点no则单个挂掉不关闭集群
21、springboot项目中集群操作
22、缓存穿透
原因:
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
1、对用户进行鉴权、对请求参数进行校验,不合理直接过滤。
2、对查询不到的数据也放到缓存,value为空,设置一定的过期时间。(不太常用,因为如果是随机key就不起作用,且占缓存)
3、使用布隆过滤器,快速判断key是否在数据库中存在,不存在直接返回。(最有效)
第1种是最常用的策略,第2种不太常用,因为如果是随机key就不起作用,且占缓存,第三种最简单有效。实际使用中,可以1、3相结合。
23、缓存击穿
原因: 因为缓存服务挂掉或者热点缓存失效,所有请求都去查数据库,导致数据库连接不够或者数据库处理不过来,从而导致整个系统不可用。
解决方案:
1、 预先设置热门数据:数据预注入
2、实时调整:监控热门数据,实时更新key
3、 当从缓存数据过期,重新从数据库加载数据到缓存的过程上互斥锁。
public String get(String key) throws Exception {
String value = redis.get(key);
// 缓存过期
if (value == null) {
// 设置有效期,防止del操作失败时,缓存过期一直不能重新加载缓存
if (redis.setnx(key_mutex, 1, 60)) {
// 从数据库加载缓存
value = database.get(key);
redis.set(key, value, expire_time);
redis.del(key_mutex);
} else {
// 其他线程已经在加载缓存,等待并重新获取即可
sleep(50);
get(key);
}
}
return value;
}
24、雪崩
原因: 如在高并发的情况下,缓存同一时刻失效(如缓存挂了,或者设置了相同过期时间),所有请求会读数据库,容易导致数据库负载瞬间上升,乃至崩掉。如果重启数据库,立马又会被新的请求压崩。
1、在较少的时间内,查询大量key都过期的情况
解决方案:
1、缓存的失效时间设置为随机值,避免同时失效。
2、redis搭建高可用,主从+哨兵,redis cluster。
3、服务限流、降级,避免数据库被瞬间压崩。
4、构建多级缓存(出现数据一致性问题)
第1种只能防止因缓存同时过期导致的缓存失效,第2种可以有效避免单台缓存挂掉的情况。第3种是通过提高服务的高可用,来避免缓存失效带来的影响,是辅助措施。
25、分布式锁
实现分布锁的方式:
1、锁redis 2、锁数据库 3、基于zookeeper
基于Redis的分布式锁
1、setnx //只有del后才能set 配合过期时间使用 不推荐
2、set key value nx ex 10 //对Value追加uuid保证锁的唯一性
3、利用lua脚本处理
1、构建多级缓存架构:Nginx+redis+ehache
2、使用锁和队列:应用管程法或者互斥锁,不适用于高并发情况
3、设置过期标志更新缓存:数据快过期的时候,通知其他线程后台更新key的缓存
4、将缓存失效时间分散开:比如插入key比上个key
26、Redis6.0新功能
ACL
acl list //查看redis用户权限信息
acl cat //查看acl相关的命令
acl setuser xxx on >password ~cached:* +get //添加用户开启设置密码并切使其只能对开头为cached:的key进行get操作
acl whoami //查看当前操作的用户
author username password //切换用户
IO多线程
数据获取开启多线程获取,执行还是单线程。
io-thread-do-read yes //开启多线程
io-thread 4 //设置线程数量
工具支持Cluster
内置ruby
其他
1、RESP3新的通信协议:优化客户端与服务端的通信
2、Client side caching :基于RESP3协议在客户端对用户经常访问的数据进行缓存。减少TCP交互
3、Proxy集群代理模式
4、Module API :面向多系统开发
过期导致的缓存失效,第2种可以有效避免单台缓存挂掉的情况。第3种是通过提高服务的高可用,来避免缓存失效带来的影响,是辅助措施。
25、分布式锁
实现分布锁的方式:
1、锁redis 2、锁数据库 3、基于zookeeper
基于Redis的分布式锁
1、setnx //只有del后才能set 配合过期时间使用 不推荐
2、set key value nx ex 10 //对Value追加uuid保证锁的唯一性
3、利用lua脚本处理
1、构建多级缓存架构:Nginx+redis+ehache
2、使用锁和队列:应用管程法或者互斥锁,不适用于高并发情况
3、设置过期标志更新缓存:数据快过期的时候,通知其他线程后台更新key的缓存
4、将缓存失效时间分散开:比如插入key比上个key
26、Redis6.0新功能
ACL
acl list //查看redis用户权限信息
acl cat //查看acl相关的命令
acl setuser xxx on >password ~cached:* +get //添加用户开启设置密码并切使其只能对开头为cached:的key进行get操作
acl whoami //查看当前操作的用户
author username password //切换用户
IO多线程
数据获取开启多线程获取,执行还是单线程。
io-thread-do-read yes //开启多线程
io-thread 4 //设置线程数量
工具支持Cluster
内置ruby
其他
1、RESP3新的通信协议:优化客户端与服务端的通信
2、Client side caching :基于RESP3协议在客户端对用户经常访问的数据进行缓存。减少TCP交互
3、Proxy集群代理模式
4、Module API :面向多系统开发