Redis
文章目录
NoSql概述
现在是属于大数据时代,大数据一般的数据库无法进行分析处理
- 单机MySQL,问题:
- 数据量如果太大,一个机器放不下
- 数据的索引(B+Tree),一个机器内存也放不下
- 访问量太大(读写混合),一个服务器承受不了
- Memcached(缓存) + MySQL+ 垂直拆分(读写分离)
网站80%的情况都是在读,每次都要去查询数据库,十分麻烦。因此使用缓存可以减轻压力(读压力)
发展过程:优化数据结构和索引–>文件缓存(IO)–>Memcached(当时最热门的技术!)
- 分库分表 + 水平拆分
早些年MyISAM:表锁,十分影响效率
Innodb:行锁
后面使用分库分表解决写的压力
mysql集群
- 如今时代
MySQL等关系型数据库不够用了,数据量很多,变化很快
如果使用MySQL存储一些比较大的文件,博客,图片等!数据库表很大,效率就低了!
为什么要用NoSQL
用户个人信息,社交网络,地理位置,用户自己产生的数据,用户日志等爆发
这时候就要使用NoSQL
什么是NoSQL
Not Only SQL
泛指非关系型数据库
NoSQL特点
- 方便扩展(数据之间没有关系,很好扩展)
- 大数据量高性能(redis一秒写8万次,读11万次,细粒度的缓存,性能会比较高!)
- 数据类型多样(不需要实现设计数据库,随取随用)
- NoSQL:
- NoSQL没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- CAP定理和BASE
- 高性能,高可用,高可扩
3V和3高
大数据时代的3V:
- 海量Volume
- 多样Variety
- 实时Velocity
大数据时代的3高:
- 高并发
- 高可拓(随时水平拆分)
- 高性能
- 商品的基本信息:
- 名称、价格、商品信息
- 关系型数据库解决
- 商品的描述、评论(文字比较多)
- 文档型数据库,MongoDB
- 图片
- 分布式文件系统 FastDFS
- 淘宝自己的 TFS
- Gooale的 GFS
- Hadoop HDFS
- 阿里云的 oss
- 商品的关键字(搜索)
- 搜索引擎 solr elasticsearch
- ISearch:多隆
- 商品热门的波段信息
- 内存数据库
- Redis ,Tair, Memache
- 商品的交易,外部的支付接口
- 第三方应用
NoSQL的四大分类
KV键值对:
- 新浪:Redis
- 美团:Redis+Tair
- 阿里,百度:Redis+Memecache
文档型数据库(bson格式和json一样):
- MongoDB(一般必须要掌握)
- Mongodb是一个基于分布式文件存储的数据库,主要用来处理大量的文档
- MongoDB一个介于关系型数据库和非关系型数据库中中间的产品!MongoDB是非关系型数据库中最像关系型数据库的!
- ConthDB
列存储数据库
- HBase
- 分布式文件系统
图关系数据库
- 不是存图形,而是存关系,比如:朋友圈,广告推荐
Redis入门
是什么?
Remote Dictionary Server,远程字典服务,key-value数据库,免费开源,C语言编写
能干吗?
- 内存存储、持久化、内存中是断电即失(rdb、aof)
- 效率高,可用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器,计数器
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
Linux安装:
宝塔面板无脑安装,然后进入/home/www/server/redis目录下,
执行redis/src/redis-server redis.conf开启服务(如果被关闭了就要开启)
执行redis-cli命令进入redis命令行
**退出Redis:**exit
也可以使用shutdown 关闭服务器
Redis基础知识
127.0.0.1:6379> SELECT 3 # 选择数据库(默认有16个)
OK
127.0.0.1:6379[3]> SELECT 0
OK
127.0.0.1:6379> DBSIZE # 查看当前数据库有多少条数据
(integer) 1
127.0.0.1:6379>
127.0.0.1:6379> FLUSHDB # 清空当前数据库
OK
127.0.0.1:6379> KEYS * # 查看当前数据库所有的key
(empty array)
127.0.0.1:6379> SET name sen # 添加键值对
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> GET name # 根据key获取value
"sen"
127.0.0.1:6379> KEYS *
1) "name"
2) "age"
127.0.0.1:6379> FLUSHALL # 清空所有数据库的数据
OK
Redis是单线程的
Redis是基于内存操作,cpu不是Redis的性能瓶颈,瓶颈是根据机器的内存和网络带宽
Redis为什么单线程还这么快?
Redis是将所有的数据全部放在内存中
Redis五大数据类型
Redis-Key
127.0.0.1:6379> SET name sen
OK
127.0.0.1:6379> EXISTS name
(integer) 1 # 存在返回1,否则返回0
127.0.0.1:6379> MOVE name 1 # 移动这个key,后面跟着的1代表当前数据库
(integer) 1
127.0.0.1:6379> SET name sen
OK
127.0.0.1:6379> EXPIRE name 10 # 设置过期时间,10s
(integer) 1
127.0.0.1:6379> ttl name # 查看还剩多久过期
(integer) 5
127.0.0.1:6379> ttl name
(integer) 3
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 0
127.0.0.1:6379> GET name
(nil)
127.0.0.1:6379> SET age 18
OK
127.0.0.1:6379> TYPE age # 查看key类型
string
Strings
127.0.0.1:6379> SET name hello
OK
127.0.0.1:6379> APPEND name ,world # 追加值
(integer) 11
127.0.0.1:6379> get name
"hello,world"
127.0.0.1:6379> APPEND age 18 # 如果key不存在,就相当于SET
(integer) 2
127.0.0.1:6379> GET age
"18"
127.0.0.1:6379> STRLEN name # 获取长度
(integer) 11
增加减少:
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> INCR views # 增1
(integer) 1
127.0.0.1:6379> INCR views
(integer) 2
127.0.0.1:6379> GET views
"2"
127.0.0.1:6379> DECR views # 减1
(integer) 1
127.0.0.1:6379> DECR views
(integer) 0
127.0.0.1:6379> DECR views
(integer) -1
127.0.0.1:6379> INCRBY views 10 # 设置步长
(integer) 9
127.0.0.1:6379> INCRBY views 10
(integer) 19
127.0.0.1:6379> DECRBY views 5
(integer) 14
获取部分字符串
127.0.0.1:6379> set key1 hello,world
OK
127.0.0.1:6379> GETRANGE key1 0 5 # 闭区间[0,5]
"hello,"
127.0.0.1:6379> GETRANGE key1 0 -1 # 全部
"hello,world"
127.0.0.1:6379> SETRANGE key1 1 xxxx # 替换指定开始的字符串
(integer) 11
127.0.0.1:6379> GET key1
"hxxxx,world"
条件设置
127.0.0.1:6379> SETEX key2 30 hello # 设置key2的值30s过期
OK
127.0.0.1:6379> SETNX key redis # key不存在才设置,返回1表示设置成功
(integer) 1
127.0.0.1:6379> SETNX key mongoGB # 返回0表示设置失败
(integer) 0
127.0.0.1:6379> GET key
"redis"
批量设置和获取, mset, mget
127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> MGET k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> MSETNX k1 hello k4 v4 # 原子性操作,要不全部成功,要么全都不成功
(integer) 0
key还可以用:分割,如(user: id: field)
127.0.0.1:6379> set user:1:name sen
OK
127.0.0.1:6379> set user:1:age 18
OK
127.0.0.1:6379> get user:1:age
"18"
127.0.0.1:6379> get user:1:name
"sen"
getset
127.0.0.1:6379> getset db redis # 不存在返回空,再设置
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongoDB # 存在返回先前的值再设置
"redis"
127.0.0.1:6379> get db
"mongoDB"
Lists
Redis的list可作为栈、队列、阻塞队列
所有命令大多都是以 L 开头的
左插入LPUSH 和 右插入RPUSH ,左弹出LPOP和右弹出RPOP,查看指定索引的值LINDEX, 查看列表长度LLEN
127.0.0.1:6379> LPUSH list one # 从左边插入
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1 # 查看列表所有的的元素
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1
1) "three"
2) "two"
127.0.0.1:6379> RPUSH list zero # 从右边插入
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "zero"
127.0.0.1:6379> LPOP list # 弹出左边第一个值
"three"
127.0.0.1:6379> RPOP list # 弹出右边第一个值
"zero"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> LINDEX 0
(error) ERR wrong number of arguments for 'lindex' command
127.0.0.1:6379> LINDEX list 0 # 查看指定索引的值
"two"
127.0.0.1:6379> LINDEX list 1
"one"
127.0.0.1:6379> LLEN list
(integer) 2
移动指定的值LREM
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lpush list three two
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "three"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> LREM list 2 two # 移出指定的值,中间的count为数量
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "one"
127.0.0.1:6379> LREM list 1 three
(integer) 1
只保留指定范围的元素LTRIM(Ltrim)
127.0.0.1:6379> rpush mylist one two three four
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> LTRIM mylist 1 2
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "two"
2) "three"
RpopLpush
Lset替换指定下标的值
Linsert插入
sets
set的指令基本都是以S开头
127.0.0.1:6379> SADD myset hello # 添加值
(integer) 1
127.0.0.1:6379> SADD myset hello
(integer) 0
127.0.0.1:6379> SADD myset name age sex
(integer) 3
127.0.0.1:6379> SMEMBERS myset # 查看所有的值
1) "name"
2) "hello"
3) "sex"
4) "age"
127.0.0.1:6379> SISMEMBER myset hello # 查看某个值是否在set里面,1为true,0为false
(integer) 1
127.0.0.1:6379> SISMEMBER myset chen
(integer) 0
127.0.0.1:6379> SCARD myset # 查看set元素个数
(integer) 4
127.0.0.1:6379> SREM myset hello # 移除指定元素
(integer) 1
127.0.0.1:6379> SRANDMEMBER myset 1 # 随机抽出指定个数元素
1) "sex"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "name"
2) "age"
127.0.0.1:6379> SPOP myset 1 # 随机删除一个元素
1) "age"
127.0.0.1:6379> SMEMBERS myset
1) "name"
2) "sex"
smove 将一个集合中的指定元素移动另一个元素中
127.0.0.1:6379> SADD set1 a b c d
(integer) 4
127.0.0.1:6379> sadd set2 c e
(integer) 2
127.0.0.1:6379> smove set1 set2 d
(integer) 1
127.0.0.1:6379> SMEMBERS set2
1) "c"
2) "e"
3) "d"
交并集合
127.0.0.1:6379> SDIFF set1 set2 # 差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER set1 set2 # 交集
1) "c"
127.0.0.1:6379> SUNION set1 set2 # 并集
1) "a"
2) "c"
3) "e"
4) "b"
5) "d"
Hashes
指令大多以H开头
127.0.0.1:6379> hset myhash field1 hello # 设置一个map
(integer) 1
127.0.0.1:6379> hmset myhash field2 redis field3 mongoDB # 设置多个map
(integer) 2
127.0.0.1:6379> HGETALL myhash # 查询所有的map
1) "field1"
2) "hello"
3) "field2"
4) "redis"
5) "field3"
6) "mongoDB"
127.0.0.1:6379> HGET myhash field1 #查询指定map
"hello"
127.0.0.1:6379> HMGET myhash field2 field3 # 查询多个map
1) "redis"
2) "mongoDB"
127.0.0.1:6379> HDEL myhash field1 # 删除指定的map(以map的key删除)
(integer) 1
127.0.0.1:6379> HLEN myhash # 获取hash的map数量
(integer) 2
127.0.0.1:6379> HEXISTS myhash field2 # 查看某个map是否存在
(integer) 1
127.0.0.1:6379> HKEYS myhash # 查看所有的key
1) "field2"
2) "field3"
127.0.0.1:6379> HVALS myhash
1) "redis"
2) "mongoDB"
用处:存储用户信息之类的,经常变动的信息。适合存储对象
127.0.0.1:6379> HSET user:1 name sen age 18 sex man
(integer) 3
127.0.0.1:6379> HGETALL user:1
1) "name"
2) "sen"
3) "age"
4) "18"
5) "sex"
6) "man"
Zsets(有序集合)
指令大多以Z开头
127.0.0.1:6379> ZADD salary 2500 xiaoming # 中间的数字是score,根据这个score来排序
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaohong 1000 sen
(integer) 2
127.0.0.1:6379> ZRANGE salary 0 -1 # 正序
1) "sen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> ZREVRANGE salary 0 -1 # 倒叙
1) "xiaohong"
2) "xiaoming"
3) "sen"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "sen"
2) "xiaoming"
3) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "sen"
2) "1000"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 3000 withscores
1) "sen"
2) "1000"
3) "xiaoming"
4) "2500"
127.0.0.1:6379> ZCARD salary # 获取元素个数
(integer) 3
127.0.0.1:6379> ZCOUNT salary 1000 3000 # 获取指定区间范围的元素数量
(integer) 2
三种特殊数据类型
Geospatial地理空间
存储纬度经度相关信息
Hyperloglog
计数统计的算法
Bitmaps
位存储
只有两个状态的,都可以使用Bitmaps
事务
要么同时成功,要么同时失败,原子性!
Redis单条命令保持原子性,但是Redis事务不保证原子性!
Redis事物本质:一组命令的集合!一个事务中的所有命令都会被序列化,按照顺序执行(一次性,顺序性,排他性,没有原子性,没有隔离级别的概念!)
---------- 队列 set set set 队列 ----------
Redis事务的操作:
- 开启事务(multi)
- 命令入队
- 执行事务(exec) 取消事务(discard)
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name chen
QUEUED
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> set sex man
QUEUED
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK
4) "chen"
如果是代码有问题,事务中所有命令都不会执行
如果是执行过程中发现一条命令有错(如让字符串+1),跳过这条命令,其他命令依旧执行(没有原子性)
监视(Watch)
悲观锁
很悲观,认为什么时候都会出现问题,无论做什么都会加锁
乐观锁
很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据
1.获取version
2.更新的时候比较version
Redis 使用watch监控变量当作乐观锁使用,如果编写事务过程中变量被其他线程修改了,那么当提交事务时会失败,需要unwatch这个变量,再watch变量,重写编写事务再提交
Jedis
1.导入依赖
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
</dependencies>
2.测试
package com.sen;
import redis.clients.jedis.Jedis;
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("47.113.205.64", 6379);
System.out.println(jedis.ping());
String name = jedis.get("name");
System.out.println(name);
}
}
jedis的API和前面的指令一样
Spingboot整合
Springdata: JDBC、JPA、MongoDB、Redis
1.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置连接
spring.redis.port=6379
spring.redis.host=47.113.205.64
spring.redis.database=0
3.测试
@SpringBootTest
class RedisApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("name", "chen");
Object name = redisTemplate.opsForValue().get("name");
System.out.println(name);
}
}
自定义redisTemplate,添加序列化配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// Json序列化配置
Jackson2JsonRedisSerializer objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectJackson2JsonRedisSerializer.setObjectMapper(om);
// String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setValueSerializer(objectJackson2JsonRedisSerializer);
template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.afterPropertiesSet();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
Redis持久化(重点)
Redis是内存数据库
- RDB
- AOF
Redis实务操作
Redis订阅发布
先订阅
127.0.0.1:6379> SUBSCRIBE bilibili # 订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "bilibili"
3) (integer) 1
后发布
127.0.0.1:6379> PUBLISH bilibili hello # 往指定频道发布消息
(integer) 1
订阅者自动接收到信息
127.0.0.1:6379> SUBSCRIBE bilibili
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "bilibili"
3) (integer) 1
1) "message"
2) "bilibili" # 来自哪个频道的消息
3) "hello"
原理:
-
通过subscribe订阅某个频道后,redis-server里维护了一个字典,字典的key就是一个个频道,而字典的值是一条链表,链表中保存了所有订阅这个频道的所有用户。subscribe命令就是将用户添加到指定频道的链表中
-
通过publish命令发布消息,redis-server使用给定的频道作为key,使用这个可以找到对应的链表,将消息发布给链表中的所有订阅者
使用场景:
- 实时消息系统
- 实时聊天(频道当作聊天室)
- 订阅、关注系统
复杂的场景使用专业的MQ中间件做