redis常用命令
get/set/exists/del/keys/rename/dbsize/type/mget/mset/expire/setnx/incr/decr/append/flushall
redis的数据类型
String/Hash/List/Set/Zset/Hyperloglog/GeoHash/Streams/BitMap
redis原子性和事务
原子性和事务,在某些场景下支持,属于半支持
redis的单个命令是原子性的(比如get/set/mget/mset),要么成功要么失败,不存在并发干扰的问题。如果涉及到多个命令的时候,需要把多个命令作为一个不可分割的处理序列,redis提供了事务的功能,可以把一组命令一起执行。
事务特点:
1.按进入队列的顺序执行
2.不会受到其它客户端的请求的影响
3.事务不能嵌套,多个multi命令效果一样 事务用法:
1.multi:开启事务
2.exec:执行事务
3.discard:取消事务
4.watch:监视 事务可能遇到的问题:
1.在执行exec之前发生错误,队列都不会被执行。
2.在执行exec之后发生的错误,只有错误的不会被执行。redis事务无法保证原子性和数据一致性。同一个事务中,举个例子
先name jason值,再写个错误的指令,进行exec ,事务的原子性是支持的。
如果,先set name jason值,在写个incr name(报错了),进行exec,发现set成功了,事务的原子性没有支持。
RESP通信协议
package redis;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class RespTest {
private Socket socket;
private OutputStream write;
private InputStream read;
public RespTest(String host, int port) throws IOException {
socket = new Socket(host, port);
write = socket.getOutputStream();
read = socket.getInputStream();
}
/**
* 实现了set方法
* @param key
* @param val
* @throws IOException
*/
public void set(String key, String val) throws IOException {
StringBuffer sb = new StringBuffer();
// 代表3个参数(set key value)
sb.append("*3").append("\r\n");
// 第一个参数(set)的长度
sb.append("$3").append("\r\n");
// 第一个参数的内容
sb.append("SET").append("\r\n");
// 第二个参数key的长度(不定,动态获取)
sb.append("$").append(key.getBytes().length).append("\r\n");
// 第二个参数key的内容
sb.append(key).append("\r\n");
// 第三个参数value的长度(不定,动态获取)
sb.append("$").append(val.getBytes().length).append("\r\n");
// 第三个参数value的内容
sb.append(val).append("\r\n");
// 发送命令
write.write(sb.toString().getBytes());
byte[] bytes = new byte[1024];
// 接收响应
read.read(bytes);
System.out.println("-------------set-------------");
System.out.println(new String(bytes));
}
/**
* 实现了get方法
* @param key
* @throws IOException
*/
public void get(String key) throws IOException {
StringBuffer sb = new StringBuffer();
// 代表2个参数
sb.append("*2").append("\r\n");
// 第一个参数(get)的长度
sb.append("$3").append("\r\n");
// 第一个参数的内容
sb.append("GET").append("\r\n");
// 第二个参数key的长度
sb.append("$").append(key.getBytes().length).append("\r\n");
// 第二个参数内容
sb.append(key).append("\r\n");
write.write(sb.toString().getBytes());
byte[] bytes = new byte[1024];
read.read(bytes);
System.out.println("-------------get-------------");
System.out.println(new String(bytes));
}
public void passport(String password) throws IOException {
StringBuffer sb = new StringBuffer();
// 代表2个参数
sb.append("*2").append("\r\n");
// 第一个参数(get)的长度
sb.append("$4").append("\r\n");
// 第一个参数的内容
sb.append("AUTH").append("\r\n");
// 第二个参数key的长度
sb.append("$").append(password.getBytes().length).append("\r\n");
// 第二个参数内容
sb.append(password).append("\r\n");
write.write(sb.toString().getBytes());
byte[] bytes = new byte[1024];
read.read(bytes);
System.out.println("-------------get-------------");
System.out.println(new String(bytes));
}
public static void main(String[] args) throws IOException {
RespTest client = new RespTest("39.100.194.200", 6379);
client.passport("123456");
client.set("zj", "2673");
client.get("zj");
}
}
Pipeline
pipeline通过一个队列把所有的命令缓存起来,然后把多个命令在一次连接中发送给服务器,需要客户端和服务端都支持。
redis为什么这么快
-纯内存结构(KV结构的内存数据库,时间复杂度0(1))
-请求处理单线程(避免线程上下文切换,创建销毁和线程之间竞争的消耗,redis的瓶颈不在cpu,可能在带宽或机器内存)
-多路复用机制(同步非堵塞I/0)–epoch
数据一致性
xxxxx
过期策略
惰性删除 定时删除
淘汰机制
最大内存设置
1.参数设置(redis.conf的maxmemory)
2.动态修改(先get一下,config set maxmemory 2GB)
LRU(Least Recently Used):最近最少使用。判断最近被使用的时间,目前最远的数据优先被淘汰。
LFU(Least Frequently Used):最不常用,按照使用频率删除,4.0版本新增。
random:随机删除
如果没有设置ttl或者没有符合前提条件的key被淘汰,那么volatile-lru、volatile-random、volatile-ttl相当于noevication(不做内存回收)
动态修改淘汰策略(先get一下):
redis> config set maxmemory-policy volatile-lru
-建议使用volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的key
redis持久化机制
RDB是redis默认的持久化方案,如果RDB与AOF同时开启,默认加载AOF的配置文件
-如果不需要rdb方案,注释save或者配置成空字符串""
-自动触发(redis.conf)
配置规则触发
save 900 1 # 900 秒内至少有一个 key 被修改(包括添加)
save 300 10 # 400 秒内至少有 10 个 key 被修改
save 60 10000 # 60 秒内至少有 10000 个 key 被修改
shutdown触发,保证服务器正常关闭
flushall,rdb文件是空的,没什么意义
-手动触发
save:生成快照的时候会堵塞当前redis服务器,redis不能处理其他命令
bgsave:redis会在后台异步进行快照操作,可以同时响应客户端请求,redis进程会fork一个子进程,rdb持久化由子进程负责,完成自动结束
AOF
redis默认不开启,AOF采用日志的形式来记录每个写操作,并追加到文件中。都是RESP协议的命令
-AOF配置(redis.conf) 开关(默认关闭):appendonly no/文件名:appendfilename “appendonly.aof”
-AOF刷写配置方式 appendfsync
always:每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,但a速度也是最慢的,一般不 推荐使用
everysec:每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,是受推荐的方式
no:完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不被推荐
-文件越来越大怎么办 使用命令bgrewriteaof重写,aof文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一
条命令去代替之前记录这个键值对的多条命令,生 成 一个新的文件后去替换原来的aof文件. #重写触发机制
auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
-AOF重写过程中的命令 会放到一个AOF重写缓存中,等子进程重写完后再把缓存中的命令准写到aof文件中
热点数据发现
-redis的缓存淘汰机制,能够留下那些热点的key,不管是LRU和LFU。
-客户端Jedis的Connection类的sendCommand()里面,用一个HashMap进行key的统计
-代理层比如TwemProxy或者Codis
-服务端Redis有一个monitor的命令,可以监控到所有的Redis执行的命令。
-机器层面通过对TCP协议进行抓包,比如ELK的packetbeat插件
缓存雪崩 缓存击穿 缓存穿透
####################缓存雪崩
描述:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案: 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。 设置热点数据永远不过期。
####################缓存击穿
描述:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
解决方案:
设置热点数据永远不过期。加互斥锁
###################缓存穿透
描述:
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
布隆过滤器
位图的容量是基于元素的个数和误判率计算出来的。
从容器的角度来说:
1、如果布隆过滤器判断元素在集合中存在,不一定存在
2、如果布隆过滤器判断不存在,一定不存在 从元素的角度来说:
3、如果元素实际存在,布隆过滤器一定判断存在 4、如果元素实际不存在,布隆过滤器可能判断存在1Byte=8bit 1KB=1024Byte 1M=1024KB 1G=1024M