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事务的顺序:
-
开启事务
-
命令入队
-
执行事务
eg:
multi #开启事务
set k1 v1
set k2 v2
set k3 v3 #添加三个命令到对列
exec #真正开始执行事务
如果需要取消事务
#在入队命令的时候
multi
set k1 v1
discard #取消事务
4.2 redis事务错误类型
- 编译型异常(代码有问题, 这时, 事务对列中的命令都不会被执行
比如, k5就没有值, 说明getset命令错了, 其他命令也都没有执行
- 运行时异常(代码没问题, 运行出错, 这时, 事务对列中这条命令会出错, 但其他命令依然会被执行)
比如, 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等工具包, 会更加方便。