一、Redis慢查询
1.1 Redis命令流程
1.2 慢查询配置:
可以通过以下命令配置慢查询时间阈值(慢查询值得是上图中执行命令的时间,不包含其他时间)
config set slowlog-log-slower-than 10000 //单位微秒
config rewrite //写入配置文件
- 时间阈值 ==0 所有查询时间都记录
- 时间阈值 < 0 都不记录
- 时间阈值 ==N 所有查询时间超过N微秒的都记录
多慢算慢查询?
一般配置到1000就可以
慢查询记录在哪里?
慢查询保存在内存的一个链表中
slowlog-max-len 128 //这个配置项是慢查询链表的长度,默认128
slowlog get 3 //查看最近3条慢查询记录
slowlog reset //清空慢查询
执行两条命令,并查看慢查询记录
127.0.0.1:6379> set shibng333 333
OK
127.0.0.1:6379> set shibing444 444
OK
127.0.0.1:6379> slowlog get 3
1) 1) (integer) 7 //命令序号
2) (integer) 1726739963 //执行的时间戳
3) (integer) 72 //执行耗时,单位微秒
4) 1) "set" //命令
2) "shibing444"
3) "444"
5) "127.0.0.1:38730" //节点地址
6) ""
二、Redis的PipeLine
Pipeline相当于一次性发送多条执行给Redis执行,相比普通命令执行方式减少了往返的网络时间。
Pipeline命令多长合适呢?
pipeline 内存输入输出缓冲区大小4k-8k。单个TCP报文大小1460B,超过这个大小会拆包,有额外的网络开销,所以尽量不要超过这个大小
实战
pipeline命令的使用
public class RedisPipeline {
@Autowired
private JedisPool jedisPool;
public List<Object> plGet(List<String> keys) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//pipe是将所有的命令组装成pipeline
Pipeline pipelined = jedis.pipelined();
pipelined.multi();//开启事务
//。。。。。等等命令
pipelined.exec();//提交事务
for(String key:keys){
pipelined.get(key);//不是仅仅是get方法,set方法还要很多很多方法pipeline都提供了支持
}
return pipelined.syncAndReturnAll();//这里只会向redis发送一次
} catch (Exception e) {
throw new RuntimeException("执行Pipeline获取失败!",e);
} finally {
jedis.close();
}
}
public void plSet(List<String> keys,List<String> values) {
if(keys.size()!=values.size()) {
throw new RuntimeException("key和value个数不匹配!");
}
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
Pipeline pipelined = jedis.pipelined();
for(int i=0;i<keys.size();i++){
pipelined.set(keys.get(i),values.get(i));
}
pipelined.sync();
} catch (Exception e) {
throw new RuntimeException("执行Pipeline设值失败!",e);
} finally {
jedis.close();
}
}
}
三、redis事物
redis事物有原子性,一致性,隔离性,特殊情况下支持持久性。不支持回滚。
但是Redis事务对回滚支持有问题,对于命令错误,可以支持回滚。但是命令没错,其他错误不支持回滚机制。所以不建议使用Redis事务。
Redis事务multi命令开始,exec提交事务。
如下 sadd命令的key和上一个重复了,但是上一个操作没有回滚,仍然是成功的:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set shi1900 apple
QUEUED
127.0.0.1:6379> sadd shi1900 grape
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get shi1900
"apple"
127.0.0.1:6379>
四、Redis Watch机制(乐观锁机制)
Watch实现了一个乐观锁的功能,如果一个key被watch,那么key被修改的时候,会将key当前的值和watch之前的值进行比较,相同就可以修改成功。否则会失败。
例:客户端1对key-watched进行了watch操作,然后开启事务执行命令给key-watched设置一个新值999,客户端2这时也给key-watched设置新值555成功,然后客户端1提交事务失败。
//客户端1:
127.0.0.1:6379> set key-watched 111
OK
127.0.0.1:6379> watch key-watched
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set key-watched 999 //执行该条命令后,在客户端2执行命令 set key-watched 555
QUEUED
127.0.0.1:6379> exec //提交失败
(nil)
127.0.0.1:6379> get key-watched //值已经被改成555
"555"
127.0.0.1:6379>
//客户端2:
127.0.0.1:6379> set key-watched 555
OK
五 Redis和Lua脚本
Redis可以支持使用Lua脚本执行命令,一个Lua脚本一次性执行完成,是个原子操作。
使用LUA脚本的好处
- 减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行
- 原子操作,Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。(Redis执行命令是单线程)
- 复用性,客户端发送的脚本会存储在Redis中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑
六、Lua脚本限流实战
在Redis维护一个计数器count,每次有请求进来,计数器count+1,当计数器的值超过限流值之后,请求会被拒绝,计数器设置一个超时时间,比如一分钟。计数器过期之后又可以接收请求。
代码实现:
@RequestMapping("/order")
public String killProduct(@RequestParam(required = true) String name) throws Exception{
//rateLimiter.tryAcquire(1); //调用
if(isAcquire.acquire("iphone",10,60)){//60秒只能进行10次
System.out.println("业务成功!");
return "恭喜("+name+"),抢到iphone!";
}else{
System.out.println("-----------业务被限流");
return "对不起,你被限流了!";
}
}
//判断限流方法---类似于RateLimiter
public boolean acquire(String limitKey,int limit,int expire) throws Exception{
//连接Redis
Jedis jedis = new Jedis("127.0.0.1",6379);
getRedisScript =new DefaultRedisScript<>();
getRedisScript.setResultType(Long.class);//脚本执行返回值 long
getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));
Long result = (Long)jedis.eval(getRedisScript.getScriptAsString(),
1,limitKey,String.valueOf(limit),String.valueOf(expire));
if(result ==0){
return false;
}
return true;
}
限流计数的Lua脚本:
afterval做流量计数,limit是流量限制次数,超过流量就会禁止
--java端送入三个参数(1个key,2个param )string
--limitKey(redi中key的值)
local key =KEYS[1];
--limit(次数)
local times = ARGV[1];
--expire(秒S)
local expire = ARGV[2];
--对key-value中的 value +1的操作 返回一个结果
local afterval= redis.call('incr',key);
if afterval ==1 then --第一次
redis.call('expire',key,tonumber(expire) ) --失效时间(1S) TLL 1S
return 1; --第一次不会进行限制
end
--不是第一次,进行判断
if afterval > tonumber(times) then
--限制了
return 0;
end
return 1;