Redis概述
redis是典型的nosql数据库,nosql就是 非关系型数据库,和Java里的 Map<String,Object>相似,所以我们可以使用redis来做缓存
nosql的特点:
- 方便扩展,数据之间没有关系,很好扩展
- 大数据量高性能,Nosql的缓存记录是一种细粒度的缓存,性能高
- 数据类型多样,不需要事先设计数据库
- 传统的RDBMS(关系型数据库) 和Nosql
redis: Remote Dictionary Server
-
内存存储,持久化(使用RDB和AOF(append only file)两种方式)
-
效率高,可以用于高速缓存
redis有五大数据类型和三大特殊数据类型(geospatial,hyperloglog,bitmaps
)
redis是单线程的,使用了reactor模式的IO
为何redis是单线程?
redis是基于内存操作的,速度很快,所以redis的瓶颈不是CPU ,而是机器的内存和网络带宽,所以能使用单线程就使用单线程
redis使用单线程也很快?
- 运行速度是 CPU>内存>硬盘
- 多线程需要有CPU的上下文切换,不一定有单线程效率高
- redis将所有数据都放在内存中,所以使用单线程去操作,效率会是最高,用多线程时,CPU会有上下文切换,耗时较长。对于内存系统来说,如果没有上下文切换,效率就是最高的,多次读写都是在一个CPU上,所以redis使用单线程
命令:
启动: 去点击redis目录下的 redis-server
启动redis服务器,点击 redis-cli
启动redis客户端来连接服务器
redis默认有16个数据库(0-15)默认使用0号数据库 ,我们可以使用 select index
来切换数据库
select index
切换数据库dbsize
:获取当前数据库的键值对
的个数keys *
得到当前数据库的所有键值对flushdb
清空当前数据库flushall
清空所有数据库move key index
把某个 键值对 移动到 index数据库中去,那么当前数据库的键值对就会消失expire key seconds
设置过期时间pexpire key milliseconds
设置过期时间,以毫秒为单位(后面设置的过期时间会覆盖前面设置的)ttl key
查看还有多久过期,返回值是 秒pttl key
查看还有多久过期 ,返回值是 毫秒expireat key timestamp
设置过期时间,前面是设置的是剩余的存活时间,这个设置的是存活到某个时间pexpireat key timestamp
time
获取当前时间戳,可用于与上面 配置type key
获取类型
五大数据类型:
String
set key value
append key value
当key为 null的时候相当于 set,不然就是追加在 key的value 后面strlen key
获取字符串长度,这是在String的对象里有记录字符串长度的属性,所以时间复杂度为 O(1)setnx key value
set if not exist 不存在才设置,设置失败的话返回 0mset key1 value1 key2 value2 ...
multiple set ,批量设置mget key1 key2 key3
List
list链表里的value值是可以重复的
lpush key value1 value2 value3
插入数据lrange key start stop
取数据,下标从0开始,到stop结束(包括stop),然后stop为-1的时候是取出start 后面所有
rpop key
弹出链表尾值,链表中就会少一个值lpop key
,弹出链表头的值
llen key
得到链表的长度lrem key count value
在链表里面删除值为 value ,的元素,只删除count个,从链表头开始删 (为何是从链表头开始查,因为redis对链表的掌控都是使用 链表头的,所以遍历自然也是从链表头开始遍历,那么查到了就删,所以可以说是从链表头开始删)
ltrim key start end
把当前的链表切割成 list[start]–>list[end] 这部分rpoplpush src dst
从src链表中使用rpop
取出一个值,使用lpush
来插入到dst链表中去lset key index value
把链表中的index
这个位置的元素改成 value,也就是根据索引修改值linsert key before|after pivot value
(pivot是中心,也就是要查找的值),(list的值可以重复,所以就是找到离链表头最近的那个进行操作,原理上面有说过)
Set
list是有序的,set是无序的,所以前面list能使用索引进行操作,但是set就不可以
set是无序不重复的集合
sadd key value
插入smembers key
得到所有的成员sismember key value
判断set是否存在 value这个值scard key
查询set的数量srem key value
删除set里的valuespop key
随机删除smove src dst value
将src里的value移动到dst里去,dst可以不存在,移动的时候创建,src这个set就会丢失valuesrandmember key count
随机取count个元素sdiff key1 key2
取两个set的差集sinter key1 key2
取两个set的交集sunion key1 key2
取两个set的并集
Hash
类似于Java里的Map
hset key field value
,只能一对一对地赋值hmset key field1 value1 field2 value2
多对赋值hget key field
取出hash集合里的field对应的value值hmget key field1 field2 field3
一次性取多个值hgetall key
取出全部的 键值对hkeys key
取出所有键值对的 keyhvals key
取出所有键值对的 value
Zset
按照score
进行排序的set,可以叫做有序不重复集合
zadd key score1 member1 score2 member2
插入值zrange key start stop
查看值,stop可以是 -1zrevrange key start stop
倒序查看zrangebyscore key min max
按照分值查看值,这里min和max的值可以是-int
(表示最小整数),+int
(表示最大整数)zrem key value
删除
三大特殊类型
geospatial
这个就是地图
geoadd key longitude latitude member
添加值,longitude指的是经度,latitude是纬度geopos key value
查看geodist key member1 member2 [unit]
根据两个元素的经纬度计算两地的距离,unit可以修改单位,默认是m,可以改成 km
hyperloglog
用于基数统计
基数 :每个重复的元素只计算一次
pfadd key element
插入pfmerge dst src
将src合并到dst中去pfcount key
计算基数
BitMap
类似于Map ,但是bitmap的值只能设置 0/1 ,所有只存在两种状态的结构都可以使用bitmaps
setbit key offset value
设置值getbit key offset
查看值
事务
redis事务: 一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中会按照顺序执行
Redis会将一个事务中的所有命令序列化,然后按顺序执行。Redis不可能在一个Redis事务的执行过程中插入执行另一个客户端发出的请求。这样便能保证Redis将这些命令作为一个单独的隔离操作执行。
在一个Redis事务中,Redis要么执行其中的所有命令,要么什么都不执行。因此,Redis事务能够保证原子性。
redis事务的特性: 一次性、顺序性、排他性
使用事务:
- 标记事务块的开始
multi
- 命令入队,redis会将命令逐个放入队列中
- 执行事务
exec
,在一个事务中执行所有先前放入队列的命令,返回值是一个数组,分别是每一条命令的返回值 - 丢弃命令队列
discard
,清除所有放入队列的命令
事务错误:
事务执行时会产生两种错误:
- 一个命令可能会在被放入队列时失败。因此,事务有可能在调用EXEC命令之前就发生错误。例如,这个命令可能会有语法错误(参数的数量错误、命令名称错误,等等),或者可能会有某些临界条件(例如:如果使用maxmemory指令,为Redis服务器配置内存限制,那么就可能会有内存溢出条件)。
- 在调用EXEC命令之后,事务中的某个命令可能会执行失败。例如,我们对某个键执行了错误类型的操作(例如,对一个字符串(String)类型的键执行列表(List)类型的操作)。
对于第一种,服务器会记住事务积累命令期间发生的错误。然后,Redis会拒绝执行这个事务,在运行EXEC命令之后,便会返回一个错误消息。最后,Redis会自动丢弃这个事务。
对于第二种,也就是在调用EXEC命令之后发生的事务错误,Redis不会进行任何特殊处理:在事务运行期间,即使某个命令运行失败,所有其他的命令也将会继续执行。
redis为何不支持回滚
只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。
事务回滚并不能解决任何程序错误。例如,如果某个查询会将一个键的值递增2,而不是1,或者递增错误的键,那么事务回滚机制是没有办法解决这些程序问题的。请注意,没有人能解决程序员自己的错误,这种错误可能会导致Redis命令执行失败。正因为这些程序错误不大可能会进入生产环境,所以发Redis时选用更加简单和快速的方法,没有实现错误回滚的功能。
锁、监视器
- 悲观锁 : 认为什么时候都会出问题,无论什么时候才会加锁
- 乐观锁:很乐观,认为什么时候都不会出问题,所以不会上锁,更新数值的时候才判断在此期间是否有人修改过此数据 ,方法是使用版本
获取version
---->更新的时候比较version
redis可以使用 CAS(检查再设置,compare and swap
)实现乐观锁,用的是 watch
命令:
- 开启两个客户端 cli1 和 cli2
- 在 cli2 使用
watch key
给某个变量上锁,然后开启事务 - 在cli1 使用
set key
修改这个变量 - 在cli2的事务里
get key
查看变量和set key
修改变量 - cli2的事务提交
exec
,发现返回值为nil,也就是事务允许失败
我们在 watch key给这个key上锁,拿到一个version,我们上锁之后在事务执行期间在第二个客户端执行了set,修改了version,当我们事务去执行的时候就会发现version版本不同,执行失败。
要拿到最新的版本需要 unwatch–> watch
Java操作redis
jedis是redis官方推荐的Java连接开发工具
依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
但是我们在使用spring boot的时候,依赖则是:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring-boot-starter-data-redis
使用的是 lettuce的方式连接redis
- jedis: 采用的是直连,多个线程操作时不安全的,如果想要避免不安全的,使用jedis pool连接池!像BIO模式
- lettuce: 采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据了,更像NIO模式
使用:
- 导入
spring-boot-starter-data-redis
依赖 - 配置文件里配置
spring.redis.host
,spring.redis.port
- 自动注入 RedisTemplate
查看里面的方法,可以发现几乎全部都是RedisSerializer类型,所以我们在向redis设置值的时候,所有对象都需要进行序列化,否则会报错
序列化方法:
- 使用
ObjectMapper
类的writeValueAsString(Object value)
- 对象实现
Serializable
接口
对reids的操作方法:
- 使用redis的opsforXXXXX().set()来进行设置值的操作
- 使用opsForXXXXX().get() 来进行取值的操作