Redis基础(一)

一:前言

        在现代应用开发中,数据的高效存储和快速检索是至关重要的。而Redis作为一种开源的内存数据库,以其快速、可靠和灵活的性能而受到广泛关注和应用。 

二:什么是Redis

        Redis(Remote Dictionary Server的缩写)是一种先进的键值存储系统,常用于构建缓存、消息队列、实时统计等应用,是一个开源的使用ANSI C语言编写、支持网络、可基于内存也可持久化的日志型、Key-Value(NoSQL)数据库。

三:Redis的安装

(1)Linux的安装

安装c编译器

yum -y install gcc

下载redis

cd  /usr/local
wget http://download.redis.io/releases/redis-3.2.5.tar.gz

解压redis

tar -xvf redis-3.2.5.tar.gz
mv redis-3.2.5 redis

编译redis

cd redis
make

配置redis

修改redis.conf

# bind 127.0.0.1  去掉绑定本机IP,让其它机器访问
protected mode no 关闭保护模式

启动redis服务器 切换到src中

./redis-server

启动redis客户端

./redis-cli

(2)Windows安装

从官网下载redis的windows版本 Releases · tporadowski/redis · GitHub. 解压后,双击redis-server.exe,就完成了Redis启动

 

四:Redis的应用场景和特点

(1)Redis的应用场景

主要应用场景有:1、缓存

                             2、分布式锁

                             3、分布式Session

                             4、消息队列

                             5、订阅和发布

(2)Redis的特点 

高性能储存在内存中、读写速度快,适用于对延迟应用高的场景

支持多种数据类型                                                 

如字符串(String)、列表(list)、哈希表(hash)、集合(set)、有序集合(zset)等,适用于解决不同类型的问题
事务支持支持事务的操作,可以将多个命令打包成一个事务,保证事务的原子性
数据持久化可以将数据保存到磁盘上面去,可以保证数据的持久性
丰富特性支持发布订阅、通知、过期策略等丰富的特性,可以满足各种应用需求。
单线程没有并发问题、但是在Redis5.0后引入了多线程来提高性能
分布式在理论上来看,可以无限的扩展。可以实现高可用性和扩展性

五: Redis的数据类型

        Redis的几种数据类型有:字符串(String)、哈希(hash )、列表(list)、集合 (set)、有序集合(zset )

 (1)字符串(String)

        字符串是最基本的数据类型,是一种key-value结构,一个key对应一个value。redis的string可以包含任何数据,可以是各种格式的字符、编码后的图片或者序列化的对象,但是它们最大只能储存512MB

        应用场景:1、缓存

                          2、分布式ID

                          3、分布式锁

                          4、全局Session

                          5、全局计数器

设置值

set 键 值
set name zhagnsan
set name zhagnsan EX 60  //EX是过期时间,单位是秒
set 键 值 NX                   //NX键如果不存在就设置成功,存在就失败

读取值  

get 键
get name

删除键  

del 键名

 递增/递减(值必须为数字)

incr 键  递增1
decr 键  递减1
incrby 键 递增量
decrby 键 递减量

//设置值    EX 是过期时间,单位是秒;NX键如果不存在就设置成功,存在就失败  
127.0.0.1:6379> set name tangqi
OK
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> set sex nan EX 5
OK
127.0.0.1:6379> get name     //读取值
"tangqi"
127.0.0.1:6379> get age
"20"
127.0.0.1:6379> get sex
(nil)
127.0.0.1:6379> del sex      //删除键
(integer) 0
127.0.0.1:6379> get sex
(nil)  
127.0.0.1:6379> incr age          //递增为1  (必须是数字)
(integer) 21
127.0.0.1:6379> decr age          //递减为1
(integer) 20
127.0.0.1:6379> incrby age 10     //age-键  10-需要的递增量
(integer) 30
127.0.0.1:6379> decrby age 5      //age-键  10-需要的递减量
(integer) 25

  (2)哈希(hash)

        哈希是一个键值对的集合,可以将多个字段和值存储在一个键下面,就是一个key下面有多个key-value。和String对比的话,hash可以灵活的读写对象的部分属性。

        应用场景:保存对象的多个属性

设置对象属性

student是对象名称,name和age是属性名称

 hmset student name zhangsan age 20

 读取对象属性

hmget student name

读取对象所有属性  

hgetall student 

删除对象属性  

hdel student age

//设置对象的属性   student-对象的名称   name、age是属性的名称
127.0.0.1:6379> hmset student name tangqi age 20
OK
127.0.0.1:6379> hmget student name   //读取对象的属性
1) "tangqi"
127.0.0.1:6379> hgetall student      //读取所有对象
1) "name"
2) "tangqi"
3) "age"
4) "20"
127.0.0.1:6379> hdel student age   //删除对象属性
(integer) 1
127.0.0.1:6379> hgetall student
1) "name"
2) "tangqi"

 (3)列表(list)

        Redis列表是简单的字符串列表、按照插入顺序排序,list采用链表结构保存多个数据,是有序的、可以重复的。

        应用场景:1、模拟数组、栈、队列等数据结构

                          2、线型结构,如粉丝、点赞列表

                          3、消息队列

左边入栈  

lpush students zhangsan
lpush students lisi
lpush students wangwu

左边出栈  

lpop students 

右边入栈  

rpush students zhangsan 

右边出栈  

 rpop students

读取列表,0和2是开始和结束位置(-1 代表全部)  

lrange students 0 2 

//左边入栈
127.0.0.1:6379> lpush students tangqi
(integer) 1
127.0.0.1:6379> lpush students lisi            
(integer) 2
127.0.0.1:6379> lpush students zhangsan        
(integer) 3
127.0.0.1:6379> lpop students                  //左边出栈
"zhangsan"
127.0.0.1:6379> rpush students tangqi          //右边出栈
(integer) 3
127.0.0.1:6379> rpop students                  //右边入栈
"tangqi"
127.0.0.1:6379> lrange students 0 2            //读取列表,0和2是开始和结束位置(-1 代表全部)
1) "lisi"
2) "tangqi"

(4)集合(set)

        Redis的set是无序的、不可重复的集合。

        应用场景:1、点赞数(避免复杂)

                          2、社交关系(共同关注、可能认识)

添加数据  

sadd students zhangsan
sadd students lisi
sadd students wangwu 

读取数据  

smembers students 

是否是其中元素  

sismember students zhangsan 

取交集  

sinter key1 key2 ... 

取差集  

sdiff key1 key2 ... 

取并集  

sunion key1 key2 ... 

//添加数据
127.0.0.1:6379> sadd stu zhangsan
(integer) 1
127.0.0.1:6379> sadd stu lisi
(integer) 1
127.0.0.1:6379> sadd stu wangwu
(integer) 1
127.0.0.1:6379> smembers stu            //读取stu数据
1) "wangwu"
2) "lisi"
3) "zhangsan"
127.0.0.1:6379> sismember stu zhangsan   //是否是其中元素
(integer) 1
127.0.0.1:6379> sismember stu zhangs     //里面没有这个元素
(integer) 0
127.0.0.1:6379> sadd stus tangqi        //添加另一个数据
(integer) 1
127.0.0.1:6379> sadd stus zhangsan
(integer) 1
127.0.0.1:6379> smembers stus           //读取stus数据
1) "zhangsan"
2) "tangqi"
127.0.0.1:6379> sinter stu stus         //取交集
1) "zhangsan"
127.0.0.1:6379> sdiff stu stus          //取差集
1) "lisi"
2) "wangwu"
127.0.0.1:6379> sunion stus stu         //取并集
1) "lisi"
2) "wangwu"
3) "tangqi"
4) "zhangsan"

 (5)有序集合(zset)

        Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。是有序的、不可重复的集合。zset的成员是唯一的,但分数(score)却可以重复

        应用场景:排行榜

添加数据,要添加一个score数字,按score排序  

zadd key score value 

读取数据

        1、zrangebyscore ,start和end是score最小和最大值 zrevrangebyscore 反向读取zrangebyscore

zrangebyscore key start end  

        2、zrange ,start和end是开始和结束位置 zrevrange 反向读取zrange  

zrange key start end 

//添加数据,要添加一个score数字,按score排序
zadd key score value


127.0.0.1:6379> zadd stu 0 tangqi
(integer) 1
127.0.0.1:6379> zadd stu 0 zhangsan
(integer) 1
127.0.0.1:6379> zadd stu 0 lisi
(integer) 1

//读取数据:
    1、zrangebyscore ,start和end是score最小和最大值。zrevrangebyscore 反向读取zrangebyscore
        zrangebyscore key start end 

127.0.0.1:6379> zrangebyscore stu 0 100 
1) "lisi"
2) "tangqi"
3) "zhangsan"
    
    2、zrange ,start和end是开始和结束位置。zrevrange 反向读取zrange 
        zrange key start end 

127.0.0.1:6379> zrange stu 0 100
1) "lisi"
2) "tangqi"
3) "zhangsan"

六:Redis的事务 

(1)Redis事务的介绍

        Redis提供的事务是将多个命令打包,然后一次性、按照先进先出的顺序(FIFO)有序的执行。在执行过程中不会被打断(在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中),当事务队列中的所以命令都被执行(无论成功还是失败)完毕之后,事务才会结束。

(2)事务的相关命令

  • multi:该命令用于开启一个事务。在执行该命令后,后续的命令会被加入到事务队列中
  • exec:该命令用于提交事务。执行exec命令后,Redis会执行事务队列中的所有命令。
  • discard:该命令用于放弃当前事务,清空事务队列。
  • watch:该命令用于监视一个或多个键,在事务执行期间,如果监视的键被其他客户端修改,事务会被放弃。

(3)事务的操作 

        案例1:开启事务,正常执行,提交事务

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 hello
QUEUED
127.0.0.1:6379> set k2 world
QUEUED
127.0.0.1:6379> set k3 redis
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
127.0.0.1:6379> get k1
"hello"

        案例2:开启事务,放弃事务,事务中的操作没有执行 

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 java
QUEUED
127.0.0.1:6379> set k2 redis
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> get k1
"hello"
127.0.0.1:6379> 

        案例3: 开始事务,出现了语法错误,提交事务后,操作都没有执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 good
QUEUED
127.0.0.1:6379> set k2 student
QUEUED
127.0.0.1:6379> setxxxx
(error) ERR unknown command `setxxxx`, with args beginning with: 
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1
"hello"
127.0.0.1:6379> 

        案例4: 开始事务,出现数值错误(k1是字符串不能递增),提交事务后,其它的操作可以执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> set k2 999
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
127.0.0.1:6379> get k2
"999"
127.0.0.1:6379> 

        案例5: 监视score键,没有其它客户端修改,事务正常执行

127.0.0.1:6379> set score 100
OK
127.0.0.1:6379> watch score
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set score 200
QUEUED
127.0.0.1:6379> incrby score 100
QUEUED
127.0.0.1:6379> exec
1) OK
2) (integer) 300

        案例6:监视score键,提交前打开另一个客户端修改score,提交事务后incrby score没有执行

七:Redis的开发

(1)编程式缓存

通过SpringBoot整合Redis的方式来实现缓存商品

1)导入Redis依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

 2)添加配置文件

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-wait=100ms
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.min-idle=10

 3)配置类 配置RedisTemplate

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        // 配置序列化器
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson序列化器
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

 4)使用RedisTemplate

(1)常用的方法:

  • ValueOperations opsForValue() 获得string类型的操作对象
  • HashOperations opsForHash 获得hash类型的操作对象

(2)ValueOperations操作对象 :

  • set(key,value) 保存key-value
  • Object get(key) 读取value

 使用缓存的步骤:

  1. 先查询缓存

  2. 如果查到直接返回

  3. 如果查不到,查询数据库

  4. 数据库查到,保存缓存中

  5. 数据库查不到返回null

 

@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    public static final String PREFIX = "Student-";

    //注入RedisTemplate对象
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public Student getStudentById(Long id) {
        //获得字符串操作对象
        ValueOperations<String, Object> ops = redisTemplate.opsForValue();
        //先查询Redis
        Student stu = (Student) ops.get(PREFIX + id);
        if (stu == null) {
            synchronized (this) {
                System.out.println("进入了同步锁");
                //获得字符串操作对象
                ValueOperations<String, Object> ops = redisTemplate.opsForValue();
                //先查询Redis
                stu = (Student) ops.get(PREFIX + id);
                //如果Redis缓存存在数据,就直接返回
                if (stu != null) {
                    System.out.println("Redis查到,返回" + stu);
                    return stu;
                }
                //如果Redis没有查到数据,就查询MySQL
                stu = studentMapper.selectById(id);
                //MySQL查询到数据,就保存到Redis
                if (stu != null) {
                    System.out.println("MySQL查到,返回" + stu);
                    ops.set(PREFIX + id, stu);
                    return stu;
                }else {
                    //MySQL没有查询到数据,就在Redis保存空对象
                    System.out.println("MySQL没有查询到数据");
                    Student student = new Student();
                    ops.set(PREFIX + id,student,5, TimeUnit.SECONDS);
                }
            }
        } else {
            System.out.println("没有执行同步锁");
        }
        return stu;
    }
}

(2)声明式缓存

编程式缓存使用复杂,代码侵入性高,推荐使用声明式缓存,通过注解来实现热点数据缓存。

 1)在启动类上添加注解

//启动缓存
@EnableCaching

 2)Redis的配置类

@Configuration
public class RedisConfig {

    @Bean
    public RedisCacheConfiguration provideRedisCacheConfiguration(){
        //加载默认配置
        RedisCacheConfiguration conf = RedisCacheConfiguration.defaultCacheConfig();
        //返回Jackson序列化器
        return conf.serializeValuesWith(
                RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()));
    }
}

 3)缓存相关注解

  • @CacheConfig 使用在Service类上,可以配置缓存名称,如: @CacheConfig(cacheNames = "books")

  • @Cacheable 使用在查询方法上,让方法优先查询缓存

  • @CachePut 使用在更新和添加方法上,数据库更新和插入数据后同时保存到缓存里

  • @CacheEvict 使用在删除方法上,数据库删除后同时删除缓存

    注意:缓存的实体类必须实现序列化接口

 4)案例 :Reids缓存学生

@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    //分页查询学生时进行缓存,缓存名称是Student-Page,键是分类current
    @Cacheable(cacheNames = "Student-Page", key = "T(String).valueOf(#current)")
    @Override
    public Page<Student> selectStudentPage(Long current, Long size) {
        return studentMapper.selectStudentPage(new Page<>(current, size));
    }

    //缓存单个学生
    @Cacheable(cacheNames = "Student",key = "T(String).valueOf(#id)")
    @Override
    public Student getStudentById(Long id) {
        return studentMapper.selectById(id);
    }

    //删除学生时删除缓存
    @CacheEvict(cacheNames = "Student",key = "T(String).valueOf(#id)")
    @Override
    public void removeStudentById(Long id) {
        studentMapper.deleteById(id);
    }

    //修改学生时更新缓存
    @CachePut(cacheNames = "Student",key = "T(String).valueOf(#student.stuId)")
    @Override
    public Student updateStudentById(Student student) {
        studentMapper.updateById(student);
        return student;
    }

    //添加时缓存
    @CachePut(cacheNames = "Student",key = "T(String).valueOf(#student.stuId)")
    @Override
    public Student addStudent(Student student) {
        studentMapper.insert(student);
        return student;
    }
}

a、组合注解 @Cacing

        同时完成多个查询、更新、删除的操作

@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {

    @Autowired
    private StudentMapper studentMapper;

    @Cacheable(cacheNames = "Student-Page", key = "T(String).valueOf(#current)")
    @Override
    public Page<Student> selectStudentPage(Long current, Long size) {
        return studentMapper.selectStudentPage(new Page<>(current, size));
    }

    @Cacheable(cacheNames = "Student",key = "T(String).valueOf(#id)")
    @Override
    public Student getStudentById(Long id) {
        return studentMapper.selectById(id);
    }

    //使用组合注解,删除一个学生时,将所有分页缓存删掉
    @Caching(
            evict = {@CacheEvict(cacheNames = "Student", key = "T(String).valueOf(#id)"),
                    @CacheEvict(cacheNames = "Student-Page", allEntries = true)}
    )
    @Override
    public void removeStudentById(Long id) {
        studentMapper.deleteById(id);
    }

    @Caching(
            put = @CachePut(cacheNames = "Student", key = "T(String).valueOf(#student.stuId)"),
            evict = @CacheEvict(cacheNames = "Student-Page", allEntries = true)
    )
    @Override
    public Student updateStudentById(Student student) {
        studentMapper.updateById(student);
        return student;
    }

    @Caching(
            put = @CachePut(cacheNames = "Student", key = "T(String).valueOf(#student.stuId)"),
            evict = @CacheEvict(cacheNames = "Student-Page", allEntries = true)
    )
    @Override
    public Student addStudent(Student student) {
        studentMapper.insert(student);
        return student;
    }
}

八:Redis的不足

  • 内存限制:Redis的数据存储是基于内存的,存储容量受到内存大小的限制。

  • 单线程模型:Redis的早期版本是单线程模型,虽然现在的Redis引入了多线程来提高性能,但对于特定场景的高并发写入操作仍然可能存在性能瓶颈。

  • 查询能力有限:Redis主要以键值对存储和各种数据结构为基础,查询功能相对有限。

九:总结

        Redis的每种类型都具有不同的特点和应用场景。合理选择和使用数据类型,可以最大程度地发挥Redis的性能和功能。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值