redis基础操作,应用

通用命令:
keys : 查看符合模版的所有key
del: 删除指定的key
exists: 判断key是否存在
expire: 给key设置有效期
ttl: 查看key的剩余时间

String命令:
String类型的value有三种形式: String ,int , float
set:添加/修改String类型的键值对.如果key不存在是添加,存在是修改
get: 根据key获取value值
mset: 批量添加多个String类型的键值对
mget: 根据多个key获取多个String类型的value
incr: 让一个整型的key自增1
incrby:让一个整型key自增并指定步长,如 incrby score 2 ,让score自增2
incrbyfloat: 让一个浮点型自增并指定步长
setnx: 添加一个string类型的键值对,如果key存在就不执行
setex: 添加一个String类型的键值对并指定有效期

Hash命令:
hash结构的value也是 key,value形式的,和String类型的value是json串类似,但String类型的json
串不能改某个字段,想改的话只能改整个value,hash结构可以改某个value字段的值.
hash类型的结构是 key: 多对{field:value}
hset key field value :添加/修改某个hash类型某一个属性的值
hmset : 添加/修改某个hash类型多个属性的值
hget key field: 获取某个hash类型某个field值
hmget: 获取某个hash类型的多个field值
hgetall key : 获取某个key的所有field,value对
hkeys key: 获取某个key的所有field
hvals key : 获取某个key的所有value值
hincrby key field 步长 : 让一个hash类型的某个field增长指定的步长
hsetnx: 和String类型一样

List命令:
lpush key element: 向列表左侧插入一个活多个命令
lpop key : 移除并返回左侧第一个元素, 没有则返回nil
rpush key element: 向列表key的右侧插入一个或多个元素
rpop key: 移除并返回列表右侧第一个元素
lrange key star end : 返回下标从star开始到end结束的所有元素,下标从0开始
blpop/brpop : 和lpop/rpop类似,区别是没有元素时指定等待时间,而不是返回nil

Set命令:
sadd key value1,value2...: 向 key的set中添加一个或多个元素
srem key value1,value2...: 移除元素
scard key : 返回key中的元素个数
sismember key member: 判断member是否是key中的元素
smembers key :获取key中所有元素
sinsert key1 key2 : 求key1key2交集
sdiff key1 key2 : 求key1 key2 差集
sunion key1 key2 : 并集

SortedSet的常见命令有:
ZADD key score member:添加一个或多个元素到sorted set ,如果已经存在则更新其score值
ZREM key member:删除sorted set中的一个指定元素
ZSCORE key member : 获取sorted set中的指定元素的score值
ZRANK key member:获取sorted set 中的指定元素的排名
ZCARD key:获取sorted set中的元素个数
ZCOUNT key min max:统计score值在给定范围内的所有元素的个数
ZINCRBY key increment member:让sorted set中的指定元素自增,步长为指定的increment值
ZRANGE key min max:按照score排序后,获取指定排名范围内的元素
ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素
ZDIFF、ZINTER、ZUNION:求差集、交集、并集

redis应用:

一: 给接口加redis缓存

复制代码
 public String redisCache(String id){//先查redis
        String userJson = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isNotBlank(userJson)){
            //如果redis中有,直接返回
            User user = JSONUtil.toBean(userJson,User.class);
            return user.toString();
        }// redis中不存在,去数据库中查询
        JSONObject user =  userMapper.selectUserById(id);
        if(user==null){return "数据库中没有";
        }
        //数据库中存在则写入redis并设置30分钟过期时间
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(user), Long.parseLong("30"), TimeUnit.MINUTES);
        return user.toString();
    }
复制代码

二 :当数据库更新后如何保证redis和数据库的数据一致?

解决方案: 更新数据库后删除redis, 先更新数据库还是先删除redis?如果先删缓存再更新数据库的话,当一个线程删除缓存还没更新完
数据库这段时间内有另一个线程查询,会发现缓存中没有查到,会将数据库中还没更新的数据写到redis,这种情况发生概率很大,因为
数据库的更新操作比较费时,而查询缓存操作很快.如果先更新数据库再删除缓存,唯一可能出现不一致的情况是当缓存失效时一个
线程查缓存没查到再去查数据库时其他线程更新数据库并删缓存,第二个线程完成后第一个线程再将查到的旧数据写入缓存,这种情况
发生概率比较小,而且如果发生这种情况,当缓存过期后再次同步又会保持一致,先更新再删除这个整体方法上加@Transactional只能
保证数据库出现异常后回滚,redis不会回滚

三: 缓存穿透: 请求的数据在redis和数据库里都不存在,导致一直在查数据库
解决办法:缓存null值(value值),布隆过滤,增加key的复杂度并做好格式校验,限流

复制代码
 public String redisCache(String id){
        //缓存null值(value值)解决缓存穿透
        String key="user:"+id;
        //先查redis
        String userJson = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isNotBlank(userJson)){
            //如果redis中有,直接返回
            User user = JSONUtil.toBean(userJson,User.class);
            return user.toString();
        }
        if(userJson!=null){
            //userJson 在这里一定是空字符串,是防止缓存穿透加的
            return "用户不存在";
        }
        // redis中不存在,去数据库中查询
        JSONObject user =  userMapper.selectUserById(id);
        if(user==null){
            //将空值写入redis防止缓存穿透
            stringRedisTemplate.opsForValue().set(key,"", Long.parseLong("30"), TimeUnit.MINUTES);
            return "数据库中没有";
        }
        //数据库中存在则写入redis并设置30分钟过期时间
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(user), Long.parseLong("30"), TimeUnit.MINUTES);
        return user.toString();
    }
复制代码

四: 缓存雪崩-大量缓存同时失效或redis服务不可用
解决办法: 缓存生效-给缓存设置过期时间时后面加一个随机数,为了防止大量key同时过期,redis集群

五: 缓存击穿-缓存由于各种原因失效后,重建缓存的过程较长(多表联查),重建期间缓存无法保护数据库的情况,是否考虑缓存击穿问题,
要看缓存重建耗时长不长,如果很短时间就能重建缓存,那就不用考虑击穿问题.
解决方案: 1 互斥锁:线程发现缓存没有后,先加锁再查数据库重建缓存,其他线程拿不到锁就不会查数据库,缺点是在第一个线程重建
缓存期间其他线程查不到数据,优点是 实现简单,保证一致性
2 逻辑过期: 缓存中的数据不设置ttl,也就是永不过期,添加一个时间戳字段,保存过期时间,这个就是逻辑过期时间,在java代码里判断
当前是否过期,如果过期就另开一个线程去重建缓存,重建缓存这段时间其他线程返回redis当前数据,也就是旧数据,重建完成后进来的
线程返回redis的新数据,有点是线程无需等待,性能好,缺点是不保证一致性,实现复杂,保证可用性

六 分布式全局唯一ID解决方案:
1 UUID:缺点是字符串格式长度太长,不能递增,是乱序的,插入数据库的性能太差
2 snowflake:缺点是依赖每台机器的时间钟,可能造成不是递增的情况,不过问题不大
3 数据库自增主键:并发性能不好,扩展太困难
4 redis自增:完美

复制代码
 public Long nextId(String keyWord){
        Long BEGIN_TIMESTAMP = LocalDateTime.of(2022,1,1,0,0,0).toEpochSecond(ZoneOffset.UTC);
        //时间戳
        LocalDateTime now = LocalDateTime.now();
        Long newSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timeStamp = newSecond-BEGIN_TIMESTAMP;
        //生成序列号
        //获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyy:yy:dd"));
        //自增长,redis根据key生成一个唯一id,keyWord根据业务传不同的值,比如订单模块的id就传 order,用户模块的id就传user,date是每天不同的,
        //因为long是64位,返回值long的低32位是redis自增位,如果每天都用同一个key的话,超过2的32次方后会生成重复的id,date每天变化,只要当天不超过2的32次方个id就不会重复
        long count = stringRedisTemplate.opsForValue().increment("icr:"+keyWord+":"+date);
        //返回格式: 最高位符号位(0),31位时间戳,32位redis自增位
        return timeStamp << 32 |count;
    }
复制代码

七 分布式锁:
为什么需要分布式锁: syncronized锁只能在单体应用下有效,一旦应用多节点部署,也就是分布式,syncronized锁就会失效,因为
syncronized锁是基于同一进程下的多个线程有共享内存加锁的,多节点部署意味着有多个jvm,多个进程,就没有共享内存了.
分布式和微服务的区别: 分布式是从部署角度,应用多节点部署,微服务是从业务角度,用户微服务,订单微服务.
如何实现分布式锁:只要能保证多个jvm进程之间可见并且互斥就可以用来做分布式锁,所以 Mysql(事务之间互斥),redis(setnx命令的互斥性)都可以做分布式锁
redis的setnx命令互斥说明: 多个线程同时执行 setnx key value 命令,只能有一个执行成功,执行del key 后其他线程再执行
setnx key value 命令,del命令执行前可能发生redis服务宕机,所以要设置过期时间,要保证set命令的互斥性和过期性具有原子性,要用
set key value NX EX 10 (NX是互斥,EX是过期时间10秒),对应java:
StringRedisTemplate.opsForValue().setIfAbsent(key,value,10,TimeUtil.SECONDS),value是uuid+threadId,不能光用threadid,
因为不同的jvm肯能有相同的threadid,当前逻辑有一个bug,如果第一个获取锁的线程出现异常导致在过期时间内没有执行del操作,
redis会在到期时间后自动删除这个锁,如果后面的线程获得锁后,第一个线程执行了del,那释放的就是第二个线程的锁.所有线程在执行
del操作时要判断当前线程的uuid+threadid是否和redis中要删除的value一致,一致才能删除.这样还有一个bug,如果判断当前线程的
key和redis中要删除的key一致后,执行del操作前,如果程序发生长时间阻塞,导致key过期自动删除,又会出现和前面一样的问题,所以
要保证判断一致和删除操作具有原子性,办法是将这两行代码放到lua脚本里,用stringRedisTemplate.execute()调lua脚本.

posted @   杨吃羊  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!
历史上的今天:
2021-04-13 springsecurity认证总结
点击右上角即可分享
微信分享提示