修订日期 | 内容 |
---|---|
2021-3-27 | 初稿 |
redis-学习-由入门到精通、常见的面试题目、一篇就够了
安装运行
下载
官方下载地址:http://download.redis.io/releases/
github下载:https://github.com/redis/redis/tags
Windows版本:https://github.com/microsoftarchive/redis/tags
- 官网网速你懂的,建议在github下载
登陆
redis-cli -h ip地址 -p 端口 -a 密码
如果是集群需要加上 -c
可以后输入密码
使用
auth 密码
安装(linux系统)
以本人下载的redis-6.0.0.tar.gz
为例
tar -zxvf redis-6.0.0.tar.gz
cd redis-6.0.0/
make
make 后查看由没有报错信息,本人有如下报错:
MAKE hiredis
cd hiredis && make static
make[3]: Entering directory `/usr/local/redis-6.0.0/deps/hiredis'
cc -std=c99 -pedantic -c -O3 -fPIC -Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers -g -ggdb net.c
make[3]: cc: Command not found
make[3]: *** [net.o] Error 127
make[3]: Leaving directory `/usr/local/redis-6.0.0/deps/hiredis'
make[2]: *** [hiredis] Error 2
make[2]: Leaving directory `/usr/local/redis-6.0.0/deps'
make[1]: [persist-settings] Error 2 (ignored)
CC adlist.o
/bin/sh: cc: command not found
make[1]: *** [adlist.o] Error 127
make[1]: Leaving directory `/usr/local/redis-6.0.0/src'
make: *** [all] Error 2
这是因为缺少gcc编译工具导致,需要安装
yum install gcc
# 查看版本 (默认4.8.5)
gcc -v
#redis6需要4.9以上版本,所以需要升级gcc版本
yum install centos-release-scl scl-utils-build
#安装gcc8
yum install -y devtoolset-8-toolchain
# 生效 后再次查看
scl enable devtoolset-8 bash
gcc -v
再次进入安装redis 并测试
make
#make成功后进入src/目录 查询会有./redis-server的文件,运行
./redis-server &
# 进入客户端
./redis-cli
127.0.0.1:6379> set redis redis6.0.0
OK
127.0.0.1:6379> get redis
"redis6.0.0"
# 成功
基本数据结构(五种)
一.String
String 是最基本的数据类型,实际运用最为广泛,采用key-value形式。
基本操作
127.0.0.1:6379> set user admin
OK
127.0.0.1:6379> get user
"admin"
二.Hash
Hash是一个键对应多个属性,如:{user:{name:‘admin’,role:‘superadmin’}} 这样的数据,实际应用比较多。
基本操作
127.0.0.1:6379> hmset user username admin role superadmin
OK
127.0.0.1:6379> hmget user username
# 实际可以根据id存储用户相关的信息:如
127.0.0.1:6379> hmset user:1 username admin role superadmin
OK
127.0.0.1:6379> hmget user:1 username
1) "admin"
127.0.0.1:6379> hmget user:1 username role
1) "admin"
2) "superadmin"
三、List
简单的有序字符串列表,用来存储如:{‘user’:[‘admin’,‘manager’,‘system’]},类似这样的结构,可以不断的添加key中的value
基本操作
127.0.0.1:6379> lpush user admin superadmin
(integer) 2
127.0.0.1:6379> lpush user system
(integer) 3
127.0.0.1:6379> lrange user 0 10
1) "system"
2) "superadmin"
3) "admin"
四、Set
Set与List结构相似,通过hash表实现的无序集合,value不能重复。添加、删除、查找效率都比List高。
基本操作
127.0.0.1:6379> sadd user admin superadmin
(integer) 2
# 添加重复的value无效
127.0.0.1:6379> sadd user admin superadmin
(integer) 0
127.0.0.1:6379> sadd user system
(integer) 1
127.0.0.1:6379> SMEMBERS user
1) "admin"
2) "system"
3) "superadmin"
五、zset(sorted set)
Zset与set相比是有序且能快速访问的集合,zset中每一个value都带有一个分数score
基本操作
127.0.0.1:6379> del user
(integer) 1
127.0.0.1:6379> zadd user 1 admin
(integer) 1
127.0.0.1:6379> zadd user 1 admin
(integer) 0
127.0.0.1:6379> zadd user 2 superadmin
(integer) 1
127.0.0.1:6379> zadd user 3 system
(integer) 1
127.0.0.1:6379> ZRANGE user 1 2
1) "superadmin"
2) "system"
什么是缓存雪崩?如何解决
在了解缓存雪崩之前我们先了解下缓存的过期时间。
缓存失效(过期)
# 设置10秒过期
127.0.0.1:6379> set user admin ex 10
OK
# 查看剩余有效时间
127.0.0.1:6379> ttl user
(integer) 7
127.0.0.1:6379> get user
"admin"
127.0.0.1:6379> ttl user
(integer) -2
# 过期后无法获取
127.0.0.1:6379> get user
(nil)
缓存雪崩
一般在redis会结合数据库一起使用,在redis中查询不到数据时,或缓存服务宕机会查询数据库,并将查到的数据放入redis中。
试想一下,当大批量的缓存在同一时间失效,恰好此时大量的用户请求查询不到缓存,都进入数据库,导致数据库服务宕机,这种现象称之为缓存雪崩。
解决方案
- 错峰设置缓存过期时间
- redis服务问题可以设置集群,增加服务器
缓存穿透
当大量的请求请求一定不存在的数据时,则可能会有大量的请求频繁的查询数据库,这就是缓存穿透
解决方案
- 使用布隆过滤器,将所有数据放入布隆过滤器中(类似一个大的HashMap),如过查询在布隆过滤器中查询不到数据库时不需要查询数据库
- 粗暴的将一个空数据也放入缓存中,无论改数据是否真的不存在还是系统出现异常,尽可能设置一个较短的过期时间,该方式可能会产生两个问题:
- redis中可能会存在大量的空key
- 可能会出现缓存中判定为空的数据实际上数据库中已经存在
缓存击穿
缓存击穿是指一个非常热点的key出现过期时,大量的请求访问数据库时造成的宕机。
解决方案(2种)
- 设置key永不过期
- 使用分布式锁,在过期时只让一个线程去查数据库
内存淘汰策略
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(常用)
- volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
- volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
持久化
Redis分布式锁
原理:利用redis作为分布式锁,主要利用的redis指令
setnx key value
:setnx的全称 set if not exists ,当key不存在时才插入成功,已存在则会插入失败
以实现扣减库存为例做如下设计
方案一
public String testLock(){
// 1. 尝试加锁
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "");
// 2.加锁失败直接返回
if(!success){
return "加锁失败";
}
try{
//3.加锁成功,处理业务逻辑
String num = stringRedisTemplate.opsForValue().get(STOCK_KEY);
logger.info("加锁成功,查询到剩余库存数量:{}",num);
if(Integer.valueOf(num) > 0){
stringRedisTemplate.opsForValue().decrement(STOCK_KEY);
logger.info("扣减库存成功");
} else {
logger.info("产品已卖完了");
}
} catch (Exception e){
logger.error("加锁业务逻辑处理异常",e);
} finally {
//4.处理完业务逻辑后释放锁
stringRedisTemplate.delete(LOCK_KEY);
}
return null;
}
上面的方案一存在什么问题呢?
假设在执行try至catch的内容时出现宕机或关闭机器,则不会执行到stringRedisTemplate.delete(LOCK_KEY);
下次重启完服务后则redis中一直存在这个KEY,导致所有线程均不能加锁成功,即出现死锁的现象,如何解决呢?对key 设置一个过期时间行不行呢?看方案二
方案二
//方案二与方案一的基础上将
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "");
//更改为 设置一个过期时间
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "",10, TimeUnit.SECONDS);
那么方案二是否完美了呢?其实也会出现如下问题:
1.如果线程①处理业务实际超过设置的超时时间,线程①自动释放锁,线程②进入处理业务逻辑的过程中,线程①执行了第四步中删除key的代码,等于线程①释放了线程②的锁,则会导致线程③进入
2.如果处理业务实际超过设置的超时时间以后,redis中的key失效,也就会释放锁,那么其他线程加锁成功就会导致库存重复扣减的问题
如果解决呢?
解决问题一
方案三
以方案一为基础做如下修改:大致方案,仅删除自己加的锁,避免自己的锁过期,删除其他线程刚加的锁
// 步骤1.做如下改动
String uuid = UUID.randomUUID().toString();
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_KEY, uuid ,10, TimeUnit.SECONDS);
//步骤4修改如下
String lockValue = stringRedisTemplate.opsForValue().get(LOCK_KEY);
//仅删除自己加的锁,避免自己的锁过期,删除其他线程刚加的锁
if(uuid.equals(lockValue)){
stringRedisTemplate.delete(LOCK_KEY);
}
完整代码
public String testLock(){
String uuid = UUID.randomUUID().toString();
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(LOCK_KEY, uuid ,10, TimeUnit.SECONDS);
if(!success){
logger.error("加锁失败");
return "failed";
}
try{
String num = stringRedisTemplate.opsForValue().get(STOCK_KEY);
logger.info("加锁成功,查询到剩余库存数量:{}",num);
if(Integer.valueOf(num) > 0){
stringRedisTemplate.opsForValue().decrement(STOCK_KEY);
logger.info("扣减库存成功");
} else {
logger.info("产品已卖完了");
}
} catch (Exception e){
logger.error("加锁业务逻辑处理异常",e);
} finally {
String lockValue = stringRedisTemplate.opsForValue().get(LOCK_KEY);
if(uuid.equals(lockValue)){
stringRedisTemplate.delete(LOCK_KEY);
}
}
return null;
}
那方案二中的第二个问题还是没有解决,提供如下解决思路:
在设置过期时间比如10秒,则开启一个定时任务每3秒中执行一次看是否处理完成,如果未处理完则延长超时时间的3秒(1 /3可以跟进实际业务自己定义)。
上述的一些问题,其他第三方框架已经提供了完整的解决方案,如redission,比较推荐使用,避免重复造车轮。
第三方分布式锁框架redisson
一:引入pom文件
<dependency>
2 <groupId>org.redisson</groupId>
3 <artifactId>redisson</artifactId>
4 <version>3.11.1</version>
5 </dependency>
1.创建锁
RLock rlock = redisson.getLock(LOCK_KEY);
2.加锁 下面的方法为阻塞式方法,redisson提供了非常多的锁种类,可以跟进实际业务情况自己选择
rlock.lock(30,TimeUnit.SECONDS);//阻塞30秒
// 处理业务逻辑
3.释放锁
finally{
rlock.unlock();
}