[ Redis 1 ] 数据类型和操作

发布时间 2023-10-13 00:31:25作者: Roy2048

Redis_1 类型和操作


1. NoSql

1.1 历史

  • 单机MySql时代:

最后的问题: 数据量太大, 数据库索引太大导致内存放不下, 访问量太大导致服务器承受不了

  • Memcached缓存+ MySql + 垂直拆分(读写分离) (主要解决读的问题)

3个数据库, 只有2用来写内容,写完之后同步给1和3, 把1or3 的内容放到cache里面, 读取的时候直接从cache中读内容。cache的内容太多, 所以采用memcached技术

  • 分库分表+ 水平拆分+ MySql集群

  • 多种格式的文件需要保存(定位,音乐etc)

    图行数据库, BSON数据库...

1.2 NoSQL

not only sql 泛指非关系型数据库, 传统的关系型数据库很难对付超大规模的高并发。

  • 方便扩展(数据之间没有关系

  • 大数据量高性能(1s 写8w次, 11w读

  • 数据类型多样型(出了基本类型, 还有位图, 等)

  • RDBMS和NoSQL区别

1.3 NoSQL分类

  • KV键值对: redis

  • 文档型数据库: MongoDB, 基于分布式文件存储的数据库, 主要用来处理大量的文档

    MongoDB是一个介于关系型数据库和非关系型数据中中间的产品

  • 列存储数据库: HBase

  • 图关系型数据库(放的是关系, 比如朋友圈社交网络等)

2. Redis

Remote dictionary service 远程字典服务

支持网络, 可基于内存也可基于持久化的日志型key-value数据库

2.1 启动

redis下的文件:

redis.conf中, 6之前的版本需要把daemonize yes这个改成yes, 这样就可以在后台启动(守护线程)

如果是自己下载的redis, 需要redis-server redis.conf来启动服务

然后用redis-cli -h -p 6379连接redis(-h 指定服务器主机名, 默认127.0.0.1

ping #ping是否联通
set keyName value #存一个key-value
get keyName #取一个value
keys * #查看所有的key
shutdown #关闭redis服务

测试redis性能: 用redis-benchmark

redis-benchmark -h localhost -p 6379 -c 100 -n 100000

-c 指定并发数, -n指定总请求数(可以去菜鸟看具体文档)

2.2 redis基础

redis默认有16个database (看redis.conf配置), 默认使用第0个数据库

select 1 # 选择第一个数据库
DBSIZE #查看数据库大小
flushdb # 清空当前数据库
flushall #清空所有数据库

redis是单线程的,cpu不是redis的性能瓶颈, 瓶颈是内存和网络带宽;redis6有多线程, 但多线程不涉及数据操作;

redis使用单线程还很快的原因: redis把所有的数据都放在内存中, 使用单线程去操作是最快的, 因为多线程在内存中切换上下文时比较耗时;如果多次读写都是在一个cpu和内存的组合下操作, 就是最快的。

3. 基本数据类型(5种)

redis可以做数据库, 缓存和消息中间件MQ

3.1 key的常用命令

keys *
EXISTS keyName#返回1存在, 返回0不存在
move keyName 1 #移动这个key到database1
del keyName #删除key
#---设置某个key过期时间
EXPIRE keyName 10 #默认单位为second
ttl keyName #查看剩余有效时间 time to live

3.2 String类型

append keyName "value2" #给某个key拼接字符串,如果key不存在,则相当于新建key
StrLen keyName #返回value的长度

set test-key 0
incr test-key #让test-key自增1
decr test-key #让test-key自减1
incrBy test-key 10 #让test-key自增, 步长为10
decrBy test-key 5 #让test-key自减, 步长为10
getRange key1 0 3 #截取[0, 3], [0, -1]表示全部
setRange key1 offset value #从offset开始, 替换成value
setex keyName seconds value # (set with expire)设置一个key的过期时间
setnx key value #set if not exist 如果key不存在则设置
#eg: 我们首次设置 setnx key3 “value”, 这时候key3是value字符串,并返回1表示设置成功
#再次setnx key3 “value change”, 这时候返回0, 表示设置失败, get key3还是value字符串
#批量设置
mset k1 v1 k2 v2 ...
mget k1 k2 k3
# 如果这时候已经有key1-3了, 执行
msetnx k1 v1 k4 v4 #因为k1已经存在, 所以k4也不会被创建, 因为mset是原子性操作

msetnx是原子性操作, 要么一起成功, 要么一起失败

getset key1 value1 #返回key1的当前值, 并set为命令中的值
# 不存在返回nil, 存在返回原来的值 value0
# 下次get的时候, 拿到的值是value1

3.3 List类型

list操作有L 头插, 和R 尾插, 如果用LPush进去的, LRange就是后面push进去的在最前面

LPush listName value1 #给key为listName的list push一个值
LRange listName start stop #获取list的一部分(1, -1仍然代表全部)
# 如果依次LPush 1,2,3, 则LRange出来的顺序是 3,2,1
RPush listName value "mostRight" #放在最后边
LPop listName
RPop listName #移除头一个或者尾一个元素
LIndex listName 1 #通过下标, 获取list的某个元素
LLen listName #返回list的长度
Lrem listName count value #删除值为value的count个值:
Lrem listName 3 "value1" #删除3个值为value1的元素
LTrim listName start stop #截取start和stop范围中的元素, 修改的是原list
LTrim LRange的区别: range只是从原list上取出数据, 原list不变, trim是对原list进行修剪
#组合操作: 
RpopLpush source desitination #把sourceListrpop的元素, 放到destinationlpush里
Lset listName index value #通过下标index来set value,⚠️, 当index下表不存在时, 不可以set值
eg: listName的len是1, 要lset listName 4 value4, 会报错out of range

3.4 Set类型

都是s开头的命令(看文档吧, 累了不想记了)

sadd, 添加

sMembers, 查看

sIsmember 看在不在这个set里

3.5 Hash类型

就是一个map, 里面是key-value

3.6 ZSet有序set

4. 事务(redis的事务类似于批处理

redis单条命令是保证原子性的, 但事务不保证原子性

也就是说,Redis事务本质是一组命令的集合, 一个事物中所有的命令都会被序列化, 在事务执行的过程中, 都会按照顺序执行。

一次性, 顺序性, 排他性的执行一对列的命令

-----对列 set1, set2, set3 执行 ----

所有的命令在事务中, 并没有直接被执行, 只有发起执行命令的时候才会被执行(Exec

4.1 redis事务开启和取消

redis事务的顺序:

  1. 开启事务

  2. 命令入队

  3. 执行事务

eg:

multi #开启事务
set k1 v1
set k2 v2
set k3 v3 #添加三个命令到对列
exec #真正开始执行事务

如果需要取消事务

#在入队命令的时候
multi
set k1 v1
discard #取消事务

4.2 redis事务错误类型

  1. 编译型异常(代码有问题, 这时, 事务对列中的命令都不会被执行

比如, k5就没有值, 说明getset命令错了, 其他命令也都没有执行

  1. 运行时异常(代码没问题, 运行出错, 这时, 事务对列中这条命令会出错, 但其他命令依然会被执行)

比如, k1是个字符串, 在事务中incr k1, 这条会报错, 但set k2 v2是执行了的, 因为exec之后可以get k2成功

4.3 redis实现锁

使用watch监视资源即可实现乐观锁

悲观: 啥都加锁; 乐观锁: 不上锁, 更新数据的时候去判断, 在此期间有没有人修改这个数据(java的实现方式: 获取的时候顺便获取version, 更新的时候去比较version有没有变化, 没变化就是没人修改,有人修改的话就修改失败)

案例:

set money 100
set out 0
#有钱100块, 花出去0元
#首先用乐观锁监视money, 然后开启一个事务, money减少10, out增加10;
#但是, 用另一个redis连接修改money为1000
#redis-cli 1
watch money
multi
decrby money 10
incrby out 10
#redis 2
set money 1000
#redis 1
exec
#这时候, 在redis1这个连接上执行事务, 会返回nil, 即没有执行成功, 因为money已经被修改了

这次执行事务失败后, 需要先unwatch money(其实无论事务失败还是成功, redis都会自动解锁), 再watch money, 然后开启事务-命令入对列-执行事务。

5. Jedis

是java连接redis的操作中间件(类似数据库的JDBC, 比较底层, springboot中用redisTemplate)

Jedis jedis = new Jedis("host", "port");//然后所有的命令都是jedis.的方法

6. SpringBoot整合redis

springboot中, 所有和数据有关的jpa/jdbc/redis/mongodb等, 都是在data下面的

依赖spring-boot-stater-data-redis

springboot2之后, redis的底层不再使用jedis, 而是lettuce

  • jedis采用直连, 多个线程操作不安全, 如果想避免不安全需要采用jedis pool连接池(BIO模式)

  • lettuce采用netty, 实例可以再多个线程中进行共享,不存在线程不安全的情况(NIO模式)

    Redis的两个类:

  • 不存在自己写的redisTemplate的时候有效(自己可以去定义redisTemplate)

    • 因为redis底层使用了netty, 所以需要序列化, 而默认的都是object
  • StringRedisTemplate是最常用的, 所以提出单独的一个

public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }

6.1 操作

注入redisTemplate之后

//redisTempLate
//opsForVaLue 操作字符串类似String
//opsForList 操作ist类似list
// 先选择数据类型, 然后写命令
redisTemplate.opsForxxx().命令;
//而常用的删除, 事务等, 可以直接使用, 不用指定数据类型
redisTemplate.del();

6.2 自定义redisTemplate

因为默认的template使用的序列化方式是jdk序列化(可以看源码, 在LettuceConnectionxxx类中), 所以, 如果是程序中保存的key在命令行中用keys *查看会乱码, 因此需要自定义redisTemplate。

(新版已经帮你序列化了, 这里只是原理讲解)

自己写一个redisConfiguration,

@Configuration
public class RedisConfig {
    @Bean //抄下来,可以自定义为String-Object
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        //重点,可以set不同的序列化方式, 可以查看方法中的参数类型的实现类
        template.setKeySerializer(stringRedisSerializer);
        return template;
    }
}

实际开发中, 都不会使用opsForValue之类的东西, 都是使用自己定义的RedisUtils等工具包, 会更加方便。