redis学习
redis通用命令:
help :获取帮助信息
help @group:获取复杂数据结构的操作命令
help exists:查看exists用法
key的层级名称:
便于区分同类型不同意义的字段
key: 项目名:业务名:类型:id value:json字符串
redis六种基本数据结构
-
String
set:添加或者修改已经存在的string类型的键值对 get:根据key获取string类型的value mset:批量添加多个string类型的键值对 mget:根据多个key获取多个string类型的value incr:让一个value为整型的key自增1 incrby:让一个value整型的key自增并指定步长 incrbyfloat:让一个浮点类型的数字自增并指定步长 setnx:添加一个string类型的键值对,前提是这个key不存在,否则不执行 setex:添加一个string类型的键值对,并指定有效期
-
hash
hset key field value:添加或者修改hash类型key的field的值
hget key field:获取一个hash类型key的field的值
hmset:批量添加多个hash类型key的field的值
hmget:批量获取多个hash类型key的field的值
hgetall:获取一个hash类型的key中的所有field和value
hkeys:获取一个hash类型的key中的所有field
hvals:获取一个hash类型中key中所有的value
hincrby:让一个hash类型key的字段值自增并指定步长
hsetnx:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
-
list
朋友圈点赞列表或者评论列表 lpush key element:向列表左侧插入一个或多个元素 lpop key:移除或返回列表左侧的第一个元素 lrange key star end:返回一段角标范围内的所有元素 Rpush key element:向列表右侧插入一个或多个元素 rpop key:移除并返回列表右侧的第一个元素 Blpop和brpop:与lpop和rpop类似,只不过在没有元素时等待指定时间,而不是直接返回nil
-
set
集合操作:共同好友共同关注 sadd key member:向set中添加一个元素 srem key member:移除set中的指定元素 scard key:返回set中的元素的个数 sismember key member:判断一个元素是否存在set中 smembers:获取set中的所有元素 sinter key1 key2:求key1与key2的交集 sdiff key1 key2:求key1与key2的差集 sunion key1 key2:求key1与key2的并集
-
sortedset
zadd key score member:添加一个或多个元素到sorted set,如果已经存在则更新score值 zrem key member:删除sortedset中的一个指定元素 zscore key member:获取sortedset中的指定元素的score值 zrank key member:获取sortedset中指定元素的排名 zcard key:获取sortedset中元素的个数 zcount key min max:统计score值在给定范围内的所有元素的个数 zincrby key increment member:让sortedset中指定元素自增,步长为指定的increment值 zrange key min max:按照score排序后,获取指定排名范围内的元素 zdiff zinter zunion:差集,交集,并集
redis的java客户端
jedis:已redis命令作为方法名,学习成本低。但是jedis实例是线程不安全的,多线程环境下需要基于连接池来使用 lettuce:lettuce是基于netty实现的,支持同步,异步和响应式编程方式,并且是线程安全的,支持redis的哨兵模式,集群模式和管道模式 redisson:redisson是一个基于redis实现的分布式,可伸缩的java数据结构集合,包含了map,queue,lock,semaphore,atomiclong等强大功能 redisTemplate:引入spring-boot-starter-data-reids依赖 commons-pool2连接池依赖 编写yml配置文件 redisTemplate:序列化默认jdk序列化可读性差,内存占用大,必须改变序列化方式自定义序列化配置文件 反序列化:将json字符串转化为java对象,默认的会带有@class StringRedisTemplate类,它的key和value的序列化方式默认是string方式。省去了我们自定义RedisTemplate的过程
缓存:
缓存穿透:客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库
缓存雪崩:同一时段内大量的缓存key同时失效或者redis服务宕机,导致大量请求到达数据库,带来巨大压力
解决方案:1.给不同的key的ttl添加随机值 2.利用redis集群提高服务的可用性
3.给缓存业务添加降级限流策略 4.给业务添加多级缓存
缓存击穿:热点key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效,无数的请求访问会在瞬间给数据库带来巨大冲击
解决方案:加互斥锁,逻辑过期
基于redis的分布式锁
不同业务有不同的锁
trylock(time){
}
unlock(){
String script = "if(redis.call('get',KEY[1])==ARGV[1] then return redis.call('del',KEY[1]) end return 0)";//解决释放锁原子性一致问题
}
@RestController
public class DlockController {
@Resource
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/lock/{order}/{username}")
public String update(@PathVariable ("order") String order,@PathVariable("username") String username){
//标识线程,防止误删
String uuid = UUID.randomUUID().toString();
//锁的key,多个进程访问共享资源要竞争同一把锁标识,即形成互斥
String lockKey = "lock:"+order; //order表示对某类业务加锁
lock(uuid,lockKey,username);
return "";
}
private void lock(String uuid,String lockKey,String username) {
//lua脚本 解锁原子性
String script = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
//获取锁
Boolean nativeLock = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 2000,TimeUnit.SECONDS);
System.out.println(username+"加锁状态:"+nativeLock);
if(nativeLock){
//加锁成功
try{
//作业业务相关
System.out.println(username+"正在修改数据");
TimeUnit.SECONDS.sleep(10L);
}catch (Exception e){
}finally {
//解锁
Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockKey), uuid);
System.out.println(username+"解锁状态:"+result);
}
}else{
//自旋操作
try{
System.out.println("加锁失败,休眠5秒...");
TimeUnit.MILLISECONDS.sleep(5000);
}catch (InterruptedException e){
}
lock(uuid,lockKey,username);
}
}
}
synchronized:
本地锁只能锁当前进程,只要是同一把锁,就能锁住需要这个锁的所有进程
1.synchronized(this):springboot所有的组件在容器中都是单例的,每个this代表当前容器的实例。而在分布式情况下一个服务可能部署在多个容器上,所以会有多个容器的实例即this.
本地缓存
缓存使用:
1.那些数据适合放入缓存
即时性,数据一致性要求不高的
访问量大且更新频率不高的数据
分布式缓存:
分布式缓存:通过缓存的中间件集中配置缓存,避免了调用了不同的服务器而导致数据不一致
本地缓存:在多个本地缓存的数据不能保证数据的一致性问题