Redis

Redis的优缺点

优点

  • redis是基于内存结构 性能极高(读110000次/秒,写81000次/秒)
  • redis基于键值对存储 但是支持多种数据类型
  • redis的所有操作都是原子性 可以通过lua脚本将多个操作和为一个原子操作(Redis的事务)
  • redis是基于单线程操作 但是其多路复用实现了高性能读写

缺点

  • 缓存数据与数据库数据必须通过两次操作才能保持数据的一致性
  • 使用缓存会存在缓存穿透、缓存击穿、缓存雪崩等问题 需要处理
  • redis可以作为数据库使用进行数据帝持久存储 存在数据丢失的风险

Redis基于Linux的安装和配置

安装

#下载包
[root@ljw software]# wget http://download.redis.io/releases/redis-5.0.5.tar.gz

#安装编译器
[root@ljw software]# yum -y install gcc

#解压redis安装包
[root@ljw servers]# tar -zxvf redis-5.0.5.tar.gz -C /export/servers/

#编译
[root@ljw redis-5.0.5]# make MALLOC=libc

#安装服务
[root@ljw redis-5.0.5]# make install

#启动服务(后台保持运行)
[root@ljw redis-5.0.5]# redis-server &

#启动命令行
[root@ljw redis-5.0.5]# redis-cli 

#默认配置启动redis
[root@ljw redis-5.0.5]# redis-server
#使用 redis.conf 配置文件启动 不默认启动(可同时启动多个端口)
[root@ljw redis-5.0.5]# redis-server redis.conf 


配置

常用reids配置
##	设置redis实例(服务)为守护模式,默认值为no,可以设置为yes
daemonize no/yes

##	设置当前redis实例启动之后自动保存进程id的文件路径
##	当启动一个redis服务时,redis.conf对应的pidfile项也会随之一起启动一个端口
##	如果多个服务同用一个pidfile配置项,后启动的服务会把前一个服务启动的端口覆盖
##	启动多个redis服务时 需要配置redis.conf的port和profile
pidfile /var/run/redis_6379.pid

## 设置reids实例的启动端口(默认6379)
port 6380

##	设置当前redis实例是否开启保护模式
protected-mode yes

##	允许访问实例的ip地址(protected-mode yes的前提下)
bind 127.0.0.1

##	设置连接密码
requirepass 123456

##	设置redis实例中数据库的个数(默认16个,编号0-15)
databases 16

##	设置最大并发数
maxclients 10000

##	设置客户端和redis建立连接的空闲时间(设置为0表示客户端不关闭就不关闭)
timeout 0

Redis的基本使用

1.Redis存储的数据结构

Redis是以键值对形式进行存储的,但是value支持多种数据类型

2.string常用指令

127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> get key1
"value1"

#	批量添加
127.0.0.1:6379> mset key2 value2 key3 value3

#	批量取值
127.0.0.1:6379> mget key1 key2 key3
1) "value1"
2) "value2"
3) "value3"

#	自增和自减
incr key 		##	在key对应的value上自增 +1
decr key 		##	在key对应的value上自减 -1
incrby key v	##	在key对应的value上自增 +v
decrby key v	##	在key对应的value上自减 -v

#	添加键值对,并设置过期时间(TTL)
setex key [time](seconds) value

#	设置值,如果key不存在泽添加成功,如果key存在则添加失败(不做修改操作)
setnx key value

#	在指定的key对应的value拼接字符串
append key value

#	获取key对应的字符串长度
strlen key

3.hash常用指令

##	向key对应的hash中添加键值对
hset key field value

##	向key对应的hash获取field对应的值
hget key field 

##	向key对应的hash结构中批量添加键值对
hmset key f1 v1 [f2 v2]

##	向key对应的hash批量获取field对应的值
hmset key f1 [f2]

## 再key对应的hash中的field对应的value上加v
hincrby key field v

## 获取key对应的hash中所有的键值对
hgetall key

##	获取key对应的hash中的所有的field
hkeys key

##	获取key对应的hash中的所有的value
hvals key

##	检查key对应的bash中是否右指定的field
hexists key field

##	获取key对应的hash中键值对的个数
hlen key

##	向key对应的hash结构中添加f-v 如果是field在hash中已经存在 则添加失败
hsetnx key field value

4.list常用指令

对于list数据结构,支持两侧存取数据
同侧存取:实现了堆栈结构,后进先出
异侧存取:实现了队列结构,先进先出
##	存储数据
lpush key value	#在key对应的列表的左侧添加数据value
rpush key value	#在key对应的列表的右侧添加数据value

##	获取数据
lpop key	#从key对应的列表的左侧取一个值
rpop key	#从key对应的列表的右侧取一个值

##	修改数据
lset key index value	#修改key对应的列表的索引位置的数据(索引从左往右,从0开始)

##	查看key对应的列表中索引从start到stop中所有的值
lrange key start stop

##	查看key对应的列表中index索引对应的值
lindex key index

## 获取key对应的列表中的元素的个数
llen key

##	从kye对应的列表中截取key在[start,stop]范围的值,不在次范围的数据一律被清除掉
ltrim key start stop

##	从k1右侧取出一个数据存放到k2的左侧
rpoplpush k1 k2

5.set常用指令

##	存储元素,在key对应的集合中添加元素 
sadd key v1 [v2 v3 v4...]

##	遍历key对应的集合中所有的元素
smembers key

##	随机从key对应的结合中获取一个值(出栈)
spop key

##	交集
sinter key1 key2

##	并集
sunion key1 key2

##	差集
sdiff key1 key2

##	从key对应的集合中移出指定的value
srem key value

##	检查key对应的结合中是否有指定的value
sismember key value

6.zset常用指令

zset 有序不可重复集合
##	存储数据(score存储位置必须是数值,可以是float类型,member元素不允许重复)
zadd key score member [score member...]

##	查看key对应的有序集合中索引[start,stop]数据--按照score值由小到大
##	[start,stop]不是指score,而是元素在有序集合中的索引
zrange key start stop

##	查看member元素在key对应的有序集合中的索引
zscore key member

##	获取key对应的zset中的元素的个数
zcard key

##	获取key对应的zset中,score在[min,max]范围内的member个数
zcount key min max

##	从key对应的zset中移除指定的member
zrem key6 member

##	查看key对应的有序集合中索引[start,stop]数据--按照score值从小到大
zrevrange key start stop

常用指令

key相关指令

##	查看redis中满足pattern规则的所有key(查看所有key)(keys *)
keys pattern

##	查看指定的key是否存在
exists key

##	删除指定的key-value对
del key

##	获取当前key的存活时间(如果没有设置过期返回-1,设置过期并且已经过期返回-2)
ttl key

##	设置键值对过期时间
expire key seconds
pexpire key milliseconds

##	取消键值对过期时间
persist key

db常用指令

redis的键值对是存储在数据库中的--db
redids中有默认的16个db 编号0-15
##	切换数据库
select index

##	将键值对从当前db移动到目标db
move key index

##	清空当前数据库数据
flushdb

##	清空所有数据库的k-v
flushall

##	查看当前db中k-v的个数
dbsize

##	获取最后一次持久化操作时间
lastsave

Redis的持久化

redis基于内存操作,但作为一个数据库也具备数据的持久化能力,但是为了实现高效的读取操作,并不会即时进行数据的持久化,而是按照一定的规则进行持久化操作的--持久化策略

redis提供了2种持久化策略

  • RDB(Redis DataBase)
  • AOF(Append Only File)

RDB

在满足特定的redis操作条件时,将内存中的数据以数据快照的形式存储到db文件中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7dHWb7Jo-1649410132050)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20220401210644089.png)]

  • 原理

    RDB是redis默认的持久化策略,当redis中的写操作达到指定的次数,同时距离上一次持久化达到指定的时间时,就会将redis内存中的数据生成数据快照,保存到指定的db文件中

  • 默认触发持久化条件

    • 900s 1次:当操作达到1次,900s就会进行持久化
    • 300s 10次:当操作达到10次,300s就会进行持久化
    • 60s 10000次:当操作达到10000次,60s就会进行持久化
  • 可以通过修改redis.conf来设置RDB持久化策略的触发条件

    #	RDB持久化策略开关
    rdbcompression yes
    
    #	配置redis的持久化策略
    save 900 1 
    save 300 10
    save 60 10000
    
    #	指定RDB数据存储的文件
    dbfilename dump.rdb
    
  • RDB持久化细节分析

    • 缺点
      • 如果redis出现故障,会存在数据丢失的风险,丢失上一次持久化之后的操作数据
      • RDB采用的是数据快照形式进行持久化,不适合实时性持久化
      • 如果数据量巨大,在RDB持久化过程中生成数据快照的子进程执行时间过长,会导致redis卡顿,因此时间周期设置不宜过短
    • 优点
      • 但是在数据量比较小的情况下,执行速度比较快
      • RDB是以数据快照的形式进行保存的,我们可以通过拷贝rdb文件轻松实现redis数据移植

AOF

Append Only File,当达到设定触发条件时,将redis执行的写操作指令存储到AOF文件中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ttvY2DN4-1649410132053)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20220401212328019.png)]

  • 原理

    Redis将每一个成功的写操作写入到aof文件中,当redis重启动时候执行aof文件中的指令恢复数据

  • 配置

    ##	开启AOF
    appendonly yes
    
    ##	设置触发条件(三选一)
    appendfsync always		##	只要进行成功的写操作后,就执行aof
    appendfsync everysec	##	每秒进行一次aof
    appendfsync no			##	让redis决定执行aof
    
    ##	设置aof文件路径
    appendfilename 'appendonly.aof'
    
  • AOF细节分析

    • 也可以通过拷贝aof文件进行redis数据移植
    • aof存储的指令,而且会对指令进行整理;而rdb是直接生成数据快照;数据量不大的时候RDB比较快。
    • aof是对指令文件进行增量更新,更适合实时性持久化
    • redis官方建议同事开启两种持久化策略,如果同时存在aof文件和rdb文件的情况下,aof优先

JAVA应用连接Redis

在普通Maven工程连接Redis

JAVA应用连接Redis 首先要将我们的Redis设置允许远程连接
  • 修改redis-6379.conf
##	关闭保护模式
protected-mode no

##	将bind注释(如果不注释,默认为bind 127.0.0.1 只能本机访问)
# bind 127.0.0.1

##	密码可以设置 也可以不设置
# requirepass 123456
  • 重启redis
redis-server redis-6379.conf
  • 阿里云安全组设置放行6379端口

  • 添加Jedis依赖和Gson依赖

 <dependencies>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>

    </dependencies>
  • 使用案例
public static void main(String[] args) {

        Product ad = new Product("101","ad钙",2.5);

        //1.连接redis
        Jedis jedis = new Jedis("120.25.179.232", 6379);

        //如果有设置密码
        //jedis.auth("123456")

        //2.操作
        String set = jedis.set(ad.getProductID(), new Gson().toJson(ad));
        System.out.println(set);

        //3.关闭
        jedis.close();

    }

在springboot工程连接Redis

Spring Data Redis, part of the larger Spring Data family, provides easy configuration and access to Redis from Spring applications. It offers both low-level and high-level abstractions for interacting with the store, freeing the user from infrastructural concerns.
Spring Data Redis依赖中 ,提供了链接redis的客户端
	RedisTemplate
	StringRedisTemplate

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vx7xahuw-1649410132053)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20220402012705498.png)]

  • 配置redis(application.yml)
spring:
  redis:
    database: 16
    host: 120.25.179.232
    port: 6379
  • 案例
直接在service层注入 RedisTemplate 或 StringRedisTemplate 就可以使用此对象完成reids操作

spring data redis

不同数据结构添加操作
//1.string(常用) set key value
stringRedisTemplate.boundValueOps(ad.getProductId()).set(s);

//2.hash(常用)
//stringRedisTemplate.boundHashOps("products").put(ad.getProductId(),s);

//3.List
//stringRedisTemplate.boundListOps("list").leftPush("aaa");

//4.set(无序)
//stringRedisTemplate.boundSetOps("s1").add("v1");

//5.zset(有序)
//stringRedisTemplate.boundZSetOps("z1").add("v1",1.2);
不同数据结构取值操作
//string
String s = stringRedisTemplate.boundValueOps(productId).get();

//hash
Object products = stringRedisTemplate.boundHashOps("products").get("103");

//list
String list = stringRedisTemplate.boundListOps("list").leftPop();
String list1 = stringRedisTemplate.boundListOps("list").rightPop();
String list2 = stringRedisTemplate.boundListOps("list").index(1);

//set
Set<String> s1 = stringRedisTemplate.boundSetOps("s1").members();

//zest
Set<String> z1 = stringRedisTemplate.boundZSetOps("z1").range(0, 5)
string类型的操作方法
//添加数据时指定过期时间300s   setex key 300 value
stringRedisTemplate.boundValueOps(ad.getProductId()).set(s,300);

//设置key过期时间 300L(L表示long类型数据)   expire key 300
stringRedisTemplate.boundValueOps(ad.getProductId()).expire(300L, TimeUnit.SECONDS);

//添加数据  setnx key value 是否存在
Boolean aBoolean = stringRedisTemplate.boundValueOps(ad.getProductId()).setIfAbsent(s);

使用Redis缓存数据库数据

Redis作为缓存的使用流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7nK7dhCW-1649410132054)(C:\Users\asus\AppData\Roaming\Typora\typora-user-images\image-20220405233646760.png)]

在项目中使用Reids缓存商品详情

在Service层添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在Mapper层配置配置文件
#由于Service层是依赖于Mapper层 所以可以配置在Mapper层
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://120.25.179.232:3306/legoforever?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
      username: root
      password: adminroot
  redis:
    port: 6379
    host: 120.25.179.232
    database: 0
json格式转list列表
 String productImgs = (String) stringRedisTemplate.boundHashOps("products").get(productId);
 JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, LgProductImg.class);
 List<LgProductImg> productImgs1=objectMapper.readValue(productImgs,javaType);
案例

说明:在电商系统中,为了提高商品详情的查询速度,减少对数据库的并发访问压力,我们可以使用redis来缓存商品详情,

@Autowired
    private StringRedisTemplate stringRedisTemplate;
    private ObjectMapper objectMapper= new ObjectMapper();
    @Override
    public ResultVO listRecommendProducts() {
        List<LgProductVO> productVOS = lgProductMapper.selectRecommendProducts();
        ResultVO resultVO = new ResultVO(ResStatus.OK, "success", productVOS);
        return  resultVO;
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public ResultVO getProductBasicInfo(String productId) {
        try {
            //  1.根据商品ID查询redis
            String productInfo = (String) stringRedisTemplate.boundHashOps("lgProducts").get(productId);
            //  2.如果redis查询到了商品信息,直接返回给控制器
            if (productInfo!=null){
                LgProduct lgProduct = objectMapper.readValue(productInfo, LgProduct.class);
                //从redis查询商品的图片
                String productImgs = (String) stringRedisTemplate.boundHashOps("lgProductImgs").get(productId);
                JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, LgProductImg.class);
                List<LgProductImg> productImgs1=objectMapper.readValue(productImgs,javaType);
                //从redis查询商品的套餐
                String productSkus = (String) stringRedisTemplate.boundHashOps("lgProductSkus").get(productId);
                JavaType javaType1 = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, LgProductSku.class);
                List<LgProductSku> productSkus1=objectMapper.readValue(productSkus,javaType1);
                //封装商品、图片、套餐
                HashMap<String, Object> basicInfo = new HashMap<>();
                basicInfo.put("lgProducts", lgProduct);
                basicInfo.put("lgProductImgs", productImgs1);
                basicInfo.put("lgProductSkus", productSkus1);
                return new ResultVO(ResStatus.OK,"success",basicInfo);
            }else {
                //  3.如果redis没有查询到了商品信息,查询数据库
                //1.商品基本信息
                Example example = new Example(LgProduct.class);
                Example.Criteria criteria = example.createCriteria();
                criteria.andEqualTo("lgProductId", productId);
                criteria.andEqualTo("lgProductStatus", 1);//状态为1代表上架商品
                List<LgProduct> lgProducts = lgProductMapper.selectByExample(example);
                if (lgProducts.size() > 0) {
                    //商品信息
                    LgProduct lgProduct = lgProducts.get(0);
                    String jsonStr = objectMapper.writeValueAsString(lgProduct);
                    stringRedisTemplate.boundHashOps("lgProducts").put(productId,jsonStr);

                    //商品图片
                    Example example1 = new Example(LgProductImg.class);
                    Example.Criteria criteria1 = example1.createCriteria();
                    criteria1.andEqualTo("lgProductImgItemId", productId);
                    List<LgProductImg> lgProductImgs = lgProductImgMapper.selectByExample(example1);
                    stringRedisTemplate.boundHashOps("lgProductImgs").put(productId,objectMapper.writeValueAsString(lgProductImgs));

                    //商品套餐
                    Example example2 = new Example(LgProductSku.class);
                    Example.Criteria criteria2 = example2.createCriteria();
                    criteria2.andEqualTo("lgProductSkuProductId", productId);
                    criteria2.andEqualTo("lgProductSkuStatus", 1);
                    List<LgProductSku> lgProductSkus = lgProductSkuMapper.selectByExample(example2);
                    stringRedisTemplate.boundHashOps("lgProductSkus").put(productId,objectMapper.writeValueAsString(lgProductSkus));

                    HashMap<String, Object> basicInfo = new HashMap<>();
                    basicInfo.put("lgProducts", lgProducts.get(0));
                    basicInfo.put("lgProductImgs", lgProductImgs);
                    basicInfo.put("lgProductSkus", lgProductSkus);
                    return new ResultVO(ResStatus.OK, "success", basicInfo);
                }
            }
        }catch (Exception e) {
        }
        return new ResultVO(ResStatus.NO, "查询的商品不存在", null);
    }

页面静态化

在电商系统中,为了提高商品详情的查询速度,减少对数据库的并发访问压力,我们可以使用redis来缓存商品详情,除此之外我们还可以使用页面静态化 技术来达到次目的

页面静态化:将数据库的每条数据结合模板生成单独的HTML文件进行存储(一条数据对应一个独立的HTML文件),当用户访问数据时,直接访问不同的静态HTML文件即可

使用Redis缓存平台页面数据

什么样的数据适合用缓存

因为缓存中的数据需要进行数据一致性的维护,当数据库数据发生变化后,要同步更新缓存中的数据

因此对于数据库的写操作交少,频繁查询数据库的数据更适合缓存

对于可能会发生修改,但是对于数据一致性要求不高的数据也适合缓存

使用Redis做缓存使用存在的问题

使用redis作为缓存存在高并发场景下可能出现缓存击穿、缓存穿透、缓存雪崩等问题

页面静态化

在电商系统中,为了提高商品详情的查询速度,减少对数据库的并发访问压力,我们可以使用redis来缓存商品详情,除此之外我们还可以使用页面静态化 技术来达到次目的

页面静态化:将数据库的每条数据结合模板生成单独的HTML文件进行存储(一条数据对应一个独立的HTML文件),当用户访问数据时,直接访问不同的静态HTML文件即可

使用Redis缓存平台页面数据

什么样的数据适合用缓存

因为缓存中的数据需要进行数据一致性的维护,当数据库数据发生变化后,要同步更新缓存中的数据

因此对于数据库的写操作交少,频繁查询数据库的数据更适合缓存

对于可能会发生修改,但是对于数据一致性要求不高的数据也适合缓存

使用Redis做缓存使用存在的问题

使用redis作为缓存存在高并发场景下可能出现缓存击穿、缓存穿透、缓存雪崩等问题

缓存击穿

未完待续

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值