redis可以很好的解决nginx反向代理时的session共享的问题。
keys * : 查看当前库中所有的key。
exists key : 查看某个key是否存在。
type key : 查看key是什么类型。
del key : 删除对应的key。
unlink key : 删除对应的key。(仅将key从keyspac元数据中删除,真正的删除会在后续的异步操作中)
expire key : 设置key的过期时间。(expire key 10 表示该key会在十秒内过期,单位为S)
ttl key : 查看key 还有多久过期。(-2表示已经过期,-1表示永不过期)
select : 切换数据库。
dbsize : 查看当前数据库的key数量。
flushdb : 清空当前库。
flushall : 清空全部数据库。
get key : 查找对应的key的value值。
append key value : 在对应的key的valu后追加value值,并放回长度。
strlen key : 查询key值对应的value的长度。
setnx key value : 只有在key值不存在时才会执行set指令。
incr key : 对key的value值加1。(需要保证value为数字类型)
decr key : 对key的value值减1。(需要保证value为数字类型)
incrby/decrby key 步长:自定义key的value值的加减步长。
mset key1 value1 key2 value2... : 同时设置多个key-value。
mget key1 key2... : 同时查询多个value值。
msetnx key1 value1 key2 value2 ... : 在key不存在时添加key-value。
getrange key <开始索引> <停止位置> : 获取对应长度的value值。
setrange key <插入位置> value : 在key对应的value中的对应插入位置插入value值。
setex key <过期时间> value : 设置key-values时设置过气时间。
getset key value : 将旧值替换为新的key-value值。
底层就是双向链表。
它是quickList。
在数据较少时,其会创建一个连续的空间存放数据,我们将其称为zipList,当数据较大时就会创建多个zipList,然后以链表的形式将它们串起来。(其是符合双向链表的)
lpush/rpush key value1 value2 value3... : 在左边或右边插入多个value值。(符合头插法)
lrange key <开始位置> <停止位置> : 从左边的开始位置开始查询链表中的对应长度的数据。
lpop/rpop key 从左边或右边吐出一个value值。
rpoplpush key1 key2 : 从key1链表的右边吐出一个value,将此value插入key2的左边。
lindex key <索引下标> : 通过索引下标查找key链表中对应的值。(从左向右)
llen key : 获取key链表的长度。
linsert before/after key value newvalue : 在key链表中的value值的后面或前面插入新的value。
lrem key
lset
sadd key value1 value2 value3 .... : 添加一个key-set的数据。
smembers key1 : 查询key对set中的所有值。
simember key value : 查询key对应的集合中是否存在value值,如果存在就放回1,否则返回0。
scard key : 返回key对应的集合的长度。
srem key value1 value2 .... : 删除key对应集合中的value值。
spop key : 从key对应的集合中随机吐出一个value值。
srandmember key
smove oldkey newkey value : 将value值从oldkey对应的set转移到newkey对应的set中。
sinter key1 key2 ... : 查询key1对应set和key2对应set的交集。
sunion key1 key2... : 查询key1对应的set和key2对应的set的并集。
sdiff key1 key2 ... : 查询key1对应的set和key2对应的set的差集。(key1中存在但key2不存在)
可以将value值理解成C语言中的结构体。(map里套map)
hset key field value .... : 添加一个hash表。
hget key field ...:查询key对应的hash表中对应的value值。
hmset key field value ... : 对key对应的hash表添加多条数据。
hexists key field : 查询在key对应的hash表中是否存在field。
hkeys key : 查询在key对应的hash表中所有的field。
hvals key : 查询在key对应的hash表中所有的value。
hincrby key field increment : 对key对应的hash表的field加increment。
hsetnx key field value : 对key对应的hash表添加field-value,且在不存在时添加。
它是一个没有重复元素的有序集合。
结构类型
跳跃表:
优化链表为跳表。
zadd key score1 value1 ..... : 添加zset集合。
zrange key
zincrby key increment value : 对key对应的集合的score加increment。
zrem key value : 删除key对应的集合的value值。
zcounnt key min max : 统计集合,在对应范围内的元素个数。
zrank key value : 查询value在集合中的排名。(从0开始)
setbit key
getbit key
bitcount key (开始位置) (结束位置): 统计key对应的bitmap中value为1的数量。
bitop and(or/not/xor) destkey key1 key2 ... : 将key1和key2的复合操作结果存放到destkey对应的bitmaps中。
其特点: 可以极大减少存储空间。(在存在大量数据时其特点最为突出)
作用:用于解决基数问题。
pfadd key value1 value2 ... : 添加指定的元素到key对应的HyperLoglog中。(且在添加重复数据时不会进行添加,此特点可以很好的解决基数问题)
pfcount key : 查询key对应的HyperLoglog中基数的数量。
pfmerge
geoadd key <经度> <纬度> 名称 ... : 对key对应的geo添加一条数据。
geopos key 名称 :从key对应的geo中查询名称对应的经纬度。
geodist key 名称1 名称2 (单位) : 从key对应的geo中查询名称1和名称2的直线距离。
georadius key <经度> <纬度> radius (单位) : 以给定的经纬度为中心,找出某一半径内的元素。
订阅指令:
SUBSCRIBE <对应的频道>
发布指令:
publish <对应的频道> <对应的内容>
例子:
redis.clients
jedis
3.3.0
该对象创建需要对应的ip地址和redis的端口号。
可通过jedis.ping()查看链接的状态。
jedis中的指令和redis中是相同的。
jedis实例-手机验证码:
对应例子代码为下:
import redis.clients.jedis.Jedis;
import java.util.Random;
public class verifyCode {
private static Jedis jedis = new Jedis("47.104.194.172", 6379);
//生成验证码
//在点击发送验证码时执行
public static String createVerifyCode(int length, String phoneNumber){
Random random = new Random();
String result = "";
for(int i = 0; i < length; i++)
result += random.nextInt(10);
//验证码对应的key
String verifyKey = "verify" + phoneNumber;
//将验证码插入数据库
jedis.set(verifyKey, result);
//设置周期
jedis.expire(verifyKey, 5);
System.out.println(result);
return result;
}
//判断输入的验证码是否正确
public static String searchVerifyCode(String phoneNumber, String inputCode){
String verifyKey = "verify" + phoneNumber;
String verifyCode = jedis.get(verifyKey);
System.out.println(verifyCode);
if(verifyCode == null){
return "验证次数已用光!";
}
if(verifyCode.equals(inputCode))
return "成功";
return "错误";
}
//判断手机号的属性
//手机号验证
public static void searchPhone(String phoneNumber, int length){
String key = "usr:" + phoneNumber;
String value = "";
String count = jedis.get(key);
if(count == null){
value = "1";
jedis.set(key, value);
}else if(Integer.valueOf(count) < 3){
jedis.incrBy(key, 1);
}else{
return ;
}
createVerifyCode(length, phoneNumber);
}
public static void main(String[] args) {
System.out.println(jedis.ping());
}
}
redis-springBoot
对应依赖:
org.springframework.boot
spring-boot-starter-data-redis
application配置文件中的配置:
spring:
redis:
#Redis 服务器的地址
host: 11.111.111.111
#Redis的端口
port: 6379
#Redis默认的数据库索引(默认为0)
database: 0
#连接超时时间(毫秒)
connect-timeout: 1800000
#连接池最大的连接数(使用负值表示没有限制)
lettuce:
pool:
max-active: 20
#最大阻塞等待时间(负数表示没有限制)
max-wait: -1
#连接池中最大空闲连接数
max-idle: 5
#连接池中最大空闲连接数
min-idle: 0
通过自动装配RedisTemplate,完成对应的数据库操作。
事务定义:Redis事务是一个单独的隔离操作,事务中的所有指令都会序列化,按顺序地执行。事务在执行的过程中,不会被其他客户端发来的命令请求所打断。
Redis事务的主要作用是串联多个命令防止别的命令插队。
multi :将多条语句组队。
Exec : 按顺序执行队伍中的语句。(类似mysql中的提交)
discard : 停止执行队伍中的语句。(类似mysql中的回滚)
对应例子:
组队执行
放弃组队:
事务错误情况处理:
1.组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
2.在执行阶段某个命令报出错误,则只有报错的命令不会执行,而其他的命令都会执行,不会回滚。
事务冲突问题:
事务冲突解决方案:
1.乐观锁
定义:每次进行操作时添加版本号,通过判断版本号来解决事务冲突问题。
优点:乐观锁适用于多读的应用类型,这样可以提高吞吐量,Redis就是利用这种check-and-set机制实现事务的。
乐观锁的使用:
watch key1 key2 .... :在执行multi之前,先执行watch key1 key2 ...,可以监视一个或多个key,如果在事务执行之前这个或这些key被其他命令所改动,那么事务就会被打断。
unwatch : 取消watch命令对所有key的监视。
2.悲观锁
定义 : 每次在操作时就会先进行上锁。
缺点: 效率低下,无法实现多人同时操作。
1.单独的隔离操作
事务中的所有命令都会被序列化,按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的请求所打断。
2.没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行。
3.不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚。
超卖问题解决
解决方法:使用乐观锁解决超卖问题。
通过watch监听对应的库存key和秒杀成功的用户组key,然后追加事务操作。
连接超时问题解决
使用理解池解决:在application文件中配置连接池的连接超时的参数。
库存遗留问题
因为乐观锁的机制,在高并发时,假设有100个人同时抢,但最后只会有一个成功,其他的事务会全部取消。
使用LUA解决超卖问题
RDB(Redis DataBase) 方式
定义:在指定的时间间隔内将内存中的数据集快照写入磁盘。(redis默认就存在RDB操作)
先将数据集存入temp文件的原因: 如果直接将redis内存存入dump .rdb中的话,当在传输过程中如果突然断电就是导致存入的数据集不完整,所以先将数据集存入temp文件就是为了保证数据的完整性和数据的一致性。
优点:
1. 适合大规模的数据恢复。
2.对数据完整性和一致性要求不高更适合使用。
3.节省磁盘空间。
4.恢复速度快。
RDB的缺点:
1.Fork的是时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑。
2.虽然redis在fork时使用了写时拷贝技术,但是如果数据庞大时还会比较消耗性能。
3.在备份周期在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改内容。
RDB备份
所谓的备份就是将dump.rdb 复制一份,并将对其更改名字,在有使用备份时将dump.rdb删除,并将备份的文件改名为dump.rdb。
stop-writes-on-bgsave-error : 当硬盘满的时候就关闭redis的操作。
rebcompression : 是否将持久化的文件进行压缩。
rdbchecksum : 检查数据集是否完整。(它会有大约10%的性能消耗,如果希望获取最大的性能提升,可以关闭此功能)
Save
格式: save 秒钟 写操作次数
默认是一分钟内改了1万次, 或5分钟内改了10次,或配置复合的快照触发条件。
Save和bgsave
save: save时只管保存,其他不管,全部堵塞,手动保存,不建议。
bgsave : redis会在后台异步进行快照操作,快照同时还可以响应客户端的请求。
可以通过lastsave命令获取最后一次成执行快照的时间。
AOF和 RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)。
AOP持久化流程:
1. 客户端的请求写命令会被append追加到AOF缓冲区。
2.AOF缓冲区根据AOF持久化策略[always, everysec, no]将操作sync同步到磁盘的AOF文件中。
3.AOP文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量。
4. redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的。
AOP的优点:
1.备份机制更稳定,丢失数据概率更低。
2.可读的日志文本,通过操作AOF稳健,可以处理误操作。
AOF缺点:
1.比起RDB占用更多的磁盘空间。
2.恢复备份速度慢。
3.每次读写都同步的话,有一定的性能压力。
AOF的恢复
AOF的恢复 : 执行过程和RDB的备份过程一模一样。
AOF异常恢复: 如果遇到AOF文件损坏,通过redis-check-aof--fix 对appendonly.aof进行恢复。
AOP同步频率设置:
appendfsync always
始终同步,每次redis的写入都会立刻记录到日志,性能较差但数据完整性比较好。
appendfsync everysec
每秒同步,每秒记录日志一次,如果宕机,本秒的数据可能会丢失。
appendfsync no
redis不主动进行同步,把同步时机交给操作系统。
Rewrite压缩
通过一个指令将多个指令包含在一起。
符合一主多从机制。
在从表中不能做写操作。
作用:
读写分离,性能扩展。
容灾分离快速恢复。
info replication : 查看redis的信息。
通过更改redis.conf文件中的端口号,来启动多个redis。
配从不配主
slaveof <主机的ip> <主机的端口号>
设置密码了需要在从机配置文件中修改 masterauth 选项
常用三招
一主二仆:
当从服务器挂掉以后,需要重新加入主从关系中,在此过程中,从服务器会将主服务器中的数据全部复制到从服务器中。
当主服务器挂掉时,从服务器还是会记住主服务器的状态和地址。(主服务器在重启的时候还会记录从服务器的数据)
薪火相传:
反客为主:
复制原理:
在第一次从服务器连接到主服务器时(全量复制),从服务器会主动找主服务器获取rdb文件并进行读取,而在后续会由主服务器对从服务器进行数据的同步(增量复制)。
新建sentinel.conf文件,名字绝对不能错。
在此文件中编写配置:
sentinel monitor <哨兵的名字> <对应ip> n
n为至少有多少个哨兵同意迁移的数量。
当主机挂掉了,从机选举中产生新的主机。
且当主机再次重启时,此主机会变为从机。
在reids6中的配置文件中配置权重的参数为下:
replica-priority的直越小,优先级就越高。
偏移量: 从机与主机数据的同步值。
设置集群
开启daemonize yes
进入redis/src中,执行下面指令:
redis-cli --cluster-replicas 1(1代表一个主机有一个从机) <对应的ip加端口号> <对应的ip加端口号> <对应的ip加端口号> ...
在redis中通过cluster nodes 查看集群信息。
查询集群中的值
cluster getkeysinslot
Jedis操作redis集群
SpringBoot自动使用的lettuce客户端操作redis,可以在配置文件直接添加两个配置就能做集群。
缓存穿透
解决方案1:
对空值进行缓存 : 如果查询的结果为空(不管数据是否存在),我们仍然把此空值存入redis中,设置结果的过期时间会很短,最长不会超过五分钟。
解决方案2:
设置可以访问的名单(白名单): 使用bitmaps类型定义一个可以访问的名单, 名单id作为bitmaps的偏移量,每次访问和bitmaps里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。(每次访问之前都要访问bitmaps,所以效率不会很高)
解决方案3:
采用布隆过滤器:(布隆过滤器(bloom Filter))是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)
解决方案4:
进行实时监测:当发现redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。
缓存击穿
缓存击穿是缓存中某些数据过期了,但是访问该数据的量特别大期,缓存击穿是该数据在数据库和缓存中都没有,导致每次请求都要缓存,数据库都查询一遍,拉跨数据库。
某个key过期了,但是这个期间大量访问使用这个key。
解决方案1:
预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长。
解决方案2:
事实调整:现场监控哪些热门,实时调整key的过期时长。
解决方案3:
使用锁:
(1)就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。
(2)先使用缓存工具的某些带成功操作的返回值(比如redis的SETNX),去set一个mutex key。
(3)当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key。
(4)当操作返回失败时,证明有线程再load db, 当前线程睡眠一段时间再重试整个get缓存的方法。
缺点:效率低下。
缓存雪崩
缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案1:
构建多级缓存框架:nginx缓存 + redis缓存 + 其他缓存(ehcache等)
解决方案2:
使用锁或队列:用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层储存系统上,不适用高并发情况。
解决方案3:
设置过期标志更新缓存:记录缓存数据是否过期(设置提前量), 如过期回触发通知另外的线程再后台去更新实际key的缓存。
解决方案4:
将缓存失效时间分散开:比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每个缓存的过期时间重复率就会降低,就很难引发集体失效的事件。
分布式锁应用的方面:单纯的Java API 并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
在集群中能够共享的锁。
redis命令
EX second : 设置键的过期时间为second秒。SET key value EX second 效果等同于SETEX key second value。
setnx key value :对key-value上锁。(只有当锁被释放时才可以进行操作,可以提供删除对应的key-value就等于释放)
我们可以提供对应key设置有效时间来防止死锁问题。
分布式锁原子性冲突问题
在对key设置有效时间时突然断电,因为redis的原子性这时候就会出现为设置key有效时间的问题。
解决此问题方法: 上锁时候同时设置过期时间。
指令: set key value nx ex
误删问题
acl list : 查看用户信息。
acl cat : 查看添加权限指令类别。
设置用户名,密码,ACL权限,并启用的用户。
切换用户。
auth 用户名 password
RedisTemplate的使用
对应依赖:
org.springframework.boot
spring-boot-starter-data-redis
propries的配置
spring:
redis:
host: xx.xxx.xxx.xxx
port: 6379
password: xxxxxx
测试redisTemplate
@SpringBootTest
class BlogApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.boundValueOps("string1").set("how");
Object string1 = redisTemplate.opsForValue().get("string1");
System.out.println(string1);
}
}
result: