Redis

一、简介

1.sql和nosql的y比较

SQLNoSQL
数据结构结构化非结构化
数据关联关联无关联
查询方式sql查询非sql
事务特征ACIDBASE
存储方式磁盘内存
扩展性垂直水平
使用场景

1)数据结构稳定

2)对安全性和一致性要求较高

1)结构不固定

2)对一致性,安全性要求不高

3)对性能要求高

2.redis特征

  • 键值(key-value)型,value支持多种数据结构,功能丰富
  • 单线程,每个命令具有原子性
  • 低延迟,速度快(基于内存,IO多路复用,良好的编码)
  • 支持数据持久化
  • 支持主从集群,分片集群
  • 支持多种语言客户端

3.redis的安装(基于Linux)

1)安装redis依赖

yum install -y gcc tcl

2) 上传 redis-6.2.7.tar.gz压缩包到linux目录中,一般放到/usr/local/src下,进到该目录下进行解压

tar -zxvf  redis-6.2.7.tar.gz

3)进入解压后的redis-6.2.7的目录里

cd redis-6.2.7/

4)运行编译命令

make && make install 

如果没有出错,就安装成功了,默认安装在/usr/local/bin目录下

  • redis-cli:redis提供的命令行客户端
  • redis-server:redis的服务端启动脚本
  • redis-sentinel:redis的哨兵启动脚本 

5)redis的启动

  • 正常启动      redis-server

 

这种属于前台启动,会阻塞整个会话窗口,窗口关闭或者按下CTRL+c,则服务停止,不建议使用 

  • 指定配置启动

如果要让redis以后台形式启动,则必须修改redis的配置文件,在安装包下,名字叫做redis.conf

vim命令打开redis.conf文件进行修改

1.允许访问的地址,默认是本地ip,只能本人访问,修改为0.0.0.0即可任意ip访问,

2.bind 0.0.0.0

3.守护进程,修改为yes后即可后台运行

        daemonize yes

4.密码

        requirepass 123456

5.logfile "redis.log"    日志文件

 启动redis:

cd /usr/local/src/redis-6.2.7        进入安装目录

redis-server redis.conf

ps -ef | grep redis     查看redis是否后台运行

kill -9 进程id       杀死该进程,关闭redis

redis-cli -u redis密码 shutdown    也可以关闭服务

 4.Redis客户端

1)命令行客户端

redis安装后自带该客户端,即redis-cli命令,打开方式:

redis-cli [option] [commends]

-h  要连接的ip地址

-p  要运行的端口

a   指定访问密码

2)图形化客户端

3)多语言客户端

如java里面的redis客户端,名为:jedis

二、Redis数据结构

1.通用命令

  • KEYS:查看符合模板的所有key

  • DEL:删除一个指定的key

  • EXISTS:判断key是否存在

  • EXPIRE:给一个key设置有效期,有效期到之后,key会自动删除

  • TTL:查看一个key的有效期

 

 当返回  -2 时,代表该key已经被自动删除

              -1时,代表该key永久保存,不会被删除

2.String命令

需要区分不同的类型的数据时  可以在key上加上层级,比如一个id可以设置为:

项目名:业务名:类型:id   也可以自定义类型  ,用于区分

3.Hash类型

hash类型是一个散列,其value是一个无序字典

 

4.List类型

有序

5.Set类型 

无序,元素不可重复

6.Sortedset 

可排序,元素不重复

三、Redis的java客户端

1.Jedis 

2.spring data Redis

 导入依赖之后,去配置文件进行配置,配置如下:

 之后就可以运用redistemplate模板进行操作,用法和jdbcTemplate类似,template是由spring boot自动装配的,所以可以直接注入

 

 设置一个city之后,进入redis查看   发现key是一个类似乱码的东西

 

 这是应为使用redistemplate之后,会对key进行一个序列化

需要创建一个类进行redis序列化的改变

四、Redis实用

1.缓存的更新策略

第一种:内存淘汰

这种机制是利用redis自身去维护的,不需要人为的干预,就是当redis感觉内存不足的时候,会随机清除一部分的缓存来腾出空间,但是正基于这种不确定性,当部分数据,其数据库早已做出了更改,但是这部分缓存却迟迟没有被清除,这样就会造成数据的不一致性。所以这种方案维护成本低,但是一致性方面比较差。

第二种:超时剔除

这种机制是通过给缓存中的每个数据设置一个过期时间,这样的话等到期之后,那些缓存就会自动清除掉,这样下次查询的时候就会更新缓存了。但是如果在这没过期的时间内,数据库发生了更改,那么缓存中的还是旧数据依然会造成数据不一致的情况。这种只需要加一个TTL过期时间,开发维护成本比较低,但是一致性方面比价一般。

第三种:主动更新

这种机制是通过我们在数据库发生变化的时候,主动去更新缓存。维护成本比较高,但是一致性也随之变高了。

注:主动更新可以搭配设置过期时间一起使用,设置过期时间作为兜底机制,效果更好。

主动更新有三种企业级方案:

(1)当数据库发生变化的时候,我们编写代码去更新redis中的缓存

(2)维护一个服务,保证数据库更新的同时更新缓存,在这个服务里实现

(3)当我们数据库变化的时候,开启一个新的独立的线程去异步更新缓存

当然在实际中第一种方案更好一点,但是会出现一个问题,当数据库发生变化的时候,我们是去更新缓存还是删除缓存呢?我们可以想想,当数据库改变的时候,只是部分数据改变,但是对于更新数据库数据的操作,我们本来就不经过缓存而是直接去进行数据库进行操作的,所以说同时更新缓存是无效的。所以我们在数据库发生变化的时候,删掉缓存,不仅减少内存消耗,等再次查询的时候还能更新缓存,一致性也得到了保证。而且就是先操作数据库在操作缓存。

2.缓存穿透

缓存穿透是指客户端请求的数据在缓存和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

常见的两种解决方案:

(1)缓存空对象

        就是当客户端第一次访问一个缓存和数据库中都没有的数据的时候,会将这个key缓存起来,key设为空,等下次在访问,缓存中已经存在,就不会去访问数据库了,尽管是空,这样就避免过多的访问数据库,造成数据库压力过大了。

        优点:实现简单,代码易于维护

        缺点:任何进来的数据都会缓存一个空对象进去,造成内存的浪费。可能造成短暂的不一致

(2)布隆过滤器

布隆过滤器简单来说就是一个byte数组,里面存储的二进制位,分别对有缓存中的数据,1代表缓存中存在,0代表缓存中不存在,客户端访问的数据,会先通过不同过滤器,布隆过滤器会先判断数据库中是否存在这个数据,不存在则直接拒绝,存在才会放行继续访问缓存等操作。

        优点:内存占用少,没有多余的key

        缺点:实现复杂,有误判的可能

 

 2.缓存雪崩

缓存雪崩是指在同一时段内,大量的缓存key同时失效,或者redis服务宕机了,导致大量的请求访问到了数据库,给数据库带来巨大的压力。

 解决方案:

(1)设置key的TTL过期时间为随机数

(2)建立redis集群提高服务的高可用性

(3)利用降级限流来避免过多请求

(4)采用多级缓存机制

3.缓存击穿

缓存击穿也可以看做是热点key问题,是指一些高热的key失效了,同一时间很多请求同时访问这个key,造成数据库压力过大。

解决方案:

(1)互斥锁:

就是加锁,每一个想要更新缓存的操作,都要先争夺锁,持有锁的才可以进行缓存的更新,没有获得锁的就等待重试。

简易版代码:

//获取锁
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
        return flag;
    }

    //释放锁
    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }


try {
            //先尝试获取锁
            boolean flag = tryLock(LOCK_SHOP_KEY + id);
            if(!flag){
                Thread.sleep(1000);
                return queryById(id);
            }
            //不存在,就去查询数据库
            shopById = this.getById(id);
            //如果数据库也不存在,缓存空对象,避免缓存穿透
            if(shopById == null){
                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
                return Result.fail("店铺不存在");
            }
            //如果数据库存在,返回,并且加入缓存
            String Json = JSONUtil.toJsonStr(shopById);
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,Json,CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            new RuntimeException(e);
        } finally {
            unLock(LOCK_SHOP_KEY+id);
        }

(2)逻辑过期

不设置TTL过期时间,在缓存加入一个逻辑过期时间,当查询缓存的时候,判断是否逻辑过期,如果过期,去获取锁,获得锁之后,开启一个新的线程去负责查询数据库,重建缓存的操作,比更新逻辑过期时间。避免主线程等待,主线程直接返回旧数据,新线程重建好之后释放锁,在没有释放之前,其他访问如果也是过期同样返回旧的数据,知道重建完成之后,才返回新的数据,

 两者比较:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员Mapy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值