NoSQL四大分类
- kv键值对(Redis):
- 文档型数据库(MongoDB):
- 用于处理大量的文档!介于关系型数据库和非关系型数据库之间。
- 列存储数据库(HBase)
- 图关系型数据库(Neo4j):存放关系而非图片
Windows下载安装
1.下载地址:github.com/dmajkic/red…
2.得到安装包
3.解压到自己的环境中
4.开启Redis,双击运行服务即可
5.使用Redis客户端连接Redis
Linux下载安装
官网:redis.io/
用xftp工具将安装包上传到服务器上并且解压:
基本环境配置
yum install gcc-c
1.安装gcc-c环境,因为Redis是用c语言写的;
make && make install
2.编译并安装
Redis的默认安装路径:/usr/local/bin
3.将Redis安装目录中的配置文件复制到当前目录下
cp /opt/redis-5.0.9/redis.conf Redisconf/
之后就使用这个配置文件
4.设置开机自启动
将配置改成图中配置
5.通过制定的配置文件启动Redis
6.使用Redis客户端连接(本机省略主机名)
redis-cli -h -p 6379
7.关闭连接以及退出
8.开启远程连接
将配置中 bind 127.0.0.1 修改为 0.0.0.0
9.将6379这个端口开放
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --reload #重启防火墙
性能测试
redis-benchmark是一个压力测试工具
使用:redis-benchmark 命令参数!
简单测试:
redis-beanchmark -h localhost -p 6379 -c 100 -n 100000
基础知识
redis默认有16个数据库,默认使用第0个数据库
可以使用select 切换数据库
select 3 #切换数据库
keys * #查看数据库所有的key
flushdb #清空当前数据库
flushall #清空数据库
set key value # 设置key和value值
get key
move key [1] #移除某个key 1 代表当前数据库
exists key #查询是否存在某个key
expire key 时间 # 设定key在规定时间内过期
type key #查看当前数据类型
redis是单线程的,redis是基于内存的操作,CPU不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就用单线程。
因为redis是将所有的数据全部存在了内存中,所以使用单线程去操作效率是最高的,多线程(CPU的上下切换回消耗一定的时间)
五种基本数据类型
Redis-key
string
append key value #在原来值得后面追加一个值(如果不存在就相当于setkey)
strlen key #获取字符串的长度
incr key #增自增1
decr views #自减1
incrby key 10 #设置自增指定步长为10
getrange key index1 index2 #截取字符串从index1到index2
setrange key index value #将指定key值中指定的index处用value代替
setex (set with expire) #如果存在设置过期时间
setex key 时间 value #如果存在就设置一个key以及过期的时间
setnx(set if not exist) #如果不存在则设置
setnx key value #如果不存在就设置一个值
mset key1 v1 k2 v2 k3 v3 #批量设置key
mget k1 k2 k3 k4
msetnx #要么同时成功,同时失败,如果有一个存在就不会执行成功
#对象
set user:[id]{Filed}
set user:1{name:zhnagsan,age:3}#设置一个user:1 值为一个json字符串
mset user:1:name zhangsan user:1:age 2 #和上面一个效果
getset key value #先get到key再setkey,不存在则返回null,设置新值;存在则返回当前值,然后设置新值
==list==类型
lpush list value #在左边push一个value
rpush list value #在右边push一个value
lrange key start end #取start 到end 的值
lpop key #移除左边一个value
rpop key #移除右边
lindex key index #通过下表获取某个值
llen key #获取list长度
lrem key count value #移除列表中指定个数的指定value值
ltrim key start end #截取指定的长度
rpoplpush resource target #将源的最后一个移动到target的第一个
lset key index value #将列表中指定下标的值替换为指定的值
linsert key before/after value1 value #在指定值value1的前后插入一个值
==set==集合(值不能重复)
sadd myset hello #往set中添加指指
smembers key #查看set中所有值
sismember key value #判断是否存在value
scard key #获取key中的元素个数
srem key value #移除set中指定的值
srandmember myset #获取set中的随机值
srandmember key count #随机获取值set中的count个值
spop key #随机删除set集合中的key
smove source target value #将source中的value移动到target中
sdiff key1 key2 #返回key1中和key2中不同的值
sinter key1 key2 #返回key1 和key2 中相同的值
sunion key1 key2 #返回key1 和key2 全集
==hash==
hset key field value #设置一个hash值
hget key field #获取一个key中的一个filed值
hmget key filed1 field2 #获取多个字段值
hdel key field #删除hash指定的value值
hgetall key #获取hash所有的键值对
hlen key#查看当前hash有多少个value
hexists key filed#查看hash中是否存在指定字段
获取所有字段
hkeys key
获取所有值:
hvals key
自增:
hincreby key field 自增步长
hsetnx myhash field4 value #如果不存在就设置key中field的值
有序集合==Zset==
zadd key index value #在set集合中的指定位置添加一个value
zadd key index value index1 value1 #添加多个值
zrange key start end #查看指定范围的值
zrangebyscore key -inf +inf #从负无穷到正无穷排序
zrevrange key 0 -1 从大到小排序
zrangebyscore key -inf +inf withscores #排序并且带上值
zrem key value1 #移除value1
zcard key 获取key中元素个数
zcount key min max 区间中的值得个数
三种特殊数据类型
geospatial 地理位置
1.==geoadd==: 添加地理位置
geoadd china:city 166.40 39.90 beijing
2.==geopos==:查询地理位置
geopos china:city chongqing #获取指定城市的精度和纬度
3.==geodist==: 两人之间的距离
geodist china:key chongqing beijing 单位
单位:
km m ft(英尺) mi(英里)
附近的人?:获取所有人的定位!通过半径来查询
4.==georadius==:以给定的经纬度为中心,找出某一半径内的元素
georaius china:city 110 30 10000 km 【withdist,withcoord】#以110 30 位中心,10000km 为半径,在key中查找
georadius china:city 110 30 10000 km withdist count 3
#查找其中的三个城市
5.==georadiusbymember==
georadiusbymember china:city beijing 1000 km
#以城市名为中心,方圆10000km查找城市
6.==geohash==:将城市的经纬度转换为一个字符串
geohash china:city beijing chongqing
可以用zset操作geo
zrange china:city 0 -1 查询key中的所有值
zrem China:city 名称 移除指定名称的地理位置信息
Hyperloglog: 做基数统计的(重复只算一个)
网页uv统计(一个IP访问只记一次)
PFadd mykey a b c d e f g h i j k v #创建一组数据
pfcount key #获取基数数量
pfmerge target source1 source2 # 合并两组为组数据
如果允许容错,可以使用。
==Bitmps==: 位图
1.==位存储==
对于某些开关值,可以简单的运用0 和 1 来表示不同的状态,在这样的情况下,我们就可以用一个位来存储它,用 0 表示一种状态,用1 表示另外一种状态。
setbit key 名称 0 #设置名称的值为0
getbit key 名称 #获取对应的值
bitcount sign #获取1的个数
事务
-
redis单条命令是保存原子性的,但是事务是不保证原子性的。
-
一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行
-
redis事务没有隔离级别的概念
Redis 的事务
- 开启事务(multi)
- 命令入队()
- 执行事务(exec)
multi #开启事务
set k1 v1
set k2 v2
get k1
get k2
exec #执行事务
discard #取消事务(事务中的命令都不会执行)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
4) "v2"
放弃事务
discard
-
如果命令中出现编译型异常,事务中所有的命令都不会执行。
- 命令出错
-
如果事务中出现运行时异常,那么其他的命令依旧可以执行。错误命令会抛出异常。
- 编译出错(对字符串进行自增1)
监控 watch
悲观锁:
- 无论做什么都要加锁。
乐观锁
- 什么时候都不上锁。
Redis监控测试
watch money #开启监控money (在执行的时候会先去取money,和之前的比对,如果发生变动了就会提交失败)
set money 100
set out 0
multi #开启事务
decrby money 20 #减钱
incrby out 20
exec #执行事务 如果另外一个线程修改了
unwatch #关闭监控
使用watch可以当做redis的乐观锁操作
如果修改失败,关闭监视,获取最新值,再次开启事务就好
Jedis
什么是jedis? 是redis官方推荐你的java连接开发工具,使用java操作redis中间件!
1.导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
2.编码测试:
- 连接数据库(如果是远程连接,记得让防火墙开放端口)
- 操作命令
- 断开连接
整合springboot
在springboot2.0后jedis被替换成了lettuce?
- jedis 采用的是直连,多个线程操作的时候是不安全的,如果要避免不安全,就需要使用jedis pool连接池,类似BIO
- lettuce:采用的是netty,实例可以在多个线程中被共享,不存在线程不安全的情况,可以减少线程数据,类似NIO模式。
引入依赖(创建项目的时候选择即可)
配置文件:
spring:
redis: #配置redis
host: 123.57.220.238
port: 6379
传递对象的时候需要序列化(自定义template模板)
@Configuration
public class RedisConfig {
//固定模板
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// string的序列化配置
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用string序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用string的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//hash的value的序列化方式采用Jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
如果里面有中文字符,可以用 ./redis-cli --raw 显示中文信息(在bin目录下输入,然后获取)
对于redis的一些操作,我们可以给他封装成一个工具类,
Redis.conf详解
配置文件对大小写不敏感
include:将多个config文件合并起来
网络
通用配置
daemonize yes #后台运行默认为No
pidfile /var/run/redis_6379.pid #如果以后台形式运行,需要一个pid文件
logfile /usr/local/bin/log/redis.log #日志文件名
快照
持久化,在规定时间内,执行多少次操作,会持久化到rdb .aof
redis是内存数据库,如果没有持久化,name数据断电及失。
stop-writes-on-bgsave-error yes #持久化出错是否需要继续工作
rdbcompression yes #是否压缩rdb文件,需要消耗CPU资源
rdbchecksum yes #保存rdb文件的时候,进行错误的检查校验
dir #持久化文件保存路径
安全
设置redis密码。
config get requirepass #获取redis密码
config set requirepass '' #设置redis密码
auth 密码 #登录
限制 clients
maxclients 10000 #限制能连接上redis的客户端的数量
maxmemory <bytes> #redis配置最大的内存容量
maxmemory-policy noeviction #内存达到上限的策略
六种策略:
- noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错。
- allkeys-lru:在所有键中采用lru算法删除键,直到腾出足够内存为止。
- volatile-lru:在设置了过期时间的键中采用lru算法删除键,直到腾出足够内存为止。
- allkeys-random:在所有键中采用随机删除键,直到腾出足够内存为止。
- volatile-random:在设置了过期时间的键中随机删除键,直到腾出足够内存为止。
- volatile-ttl:在设置了过期时间的键空间中,具有更早过期时间的key优先移除。
APPEND ONLY模式 aof配置
appendonly no #默认不开启aof模式,默认使用rdb方式进行持久化
appendfilename 'appendon.aof' #持久化的文件的名字
appendfync always #每次修改都会同步,消耗性能
appendfsync everysec #每秒执行一次,可能会数据丢失
appendfsync no #不同步,这个时候操作系统自己同步数据,速度最快
Redis持久化
RDB
触发机制:
1.save的规则满足的情况下回触发rdb规则·
2.执行flushall命令,也会生成一个rdb文件
3.退出redis,也会产生rdb文件
备份就会生成一个rdb文件,放在启动命令目录下。
如何恢复rdb文件
1.将rdb文件放在我们redis启动目录就可以,redis会自动检查dump.rdb恢复其中数据
优点
1.适合大规模数据恢复
2.对数据完整性要求不高
缺点:需要一点的时间间隔操作!
fork进程的时候,会占用一定的内存空间!
AOF
追加文件!将所有命令都记录下来,恢复的时候就把所有命令全部执行一遍。
默认是不开启的,需要手动开启,将上图中的no 改为yes。重启redis就可以生效
如果aof文件有错位,这时候redis启动不起来,我们就需要修复这个aof文件。
redis给我们启动了一个工具:redis-check-aof ---fix 文件名.aof
重写规则
aof默认是文件无线追加
如果一个aof文件大于了64mb就会重新建一个文件来进行重写!
优点和缺点
appendonly no #默认不开启aof模式,默认使用rdb方式进行持久化
appendfilename 'appendon.aof' #持久化的文件的名字
appendfync always #每次修改都会同步,消耗性能
appendfsync everysec #每秒执行一次,可能会数据丢失
appendfsync no #不同步,这个时候操作系统自己同步数据,速度最快
优点:
- 每一次修改都同步,文件的完整性会更加好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点:
1.相对于文件来说aof远远大于rdb,修复的速度比rdb慢
2.aof运行
扩展
- rdb持久化方式能够在指定的时间间隔内对你的数据进行快照存储
- aof是一种追加协议来讲每次的写操作保存在文件的末尾,redis能对aof文件进行后台重写,使得aof文件的体积不至于过大。
- 只做缓存,如果过你只希望你的数据在服务器运行的时候存在,你也可以不使用持久化
- 同时开启两种持久化方式,默认会首先载入aof文件进行年数据恢复。因为aof文件更加完整。
发布订阅
redis发布订阅是一种消息通信模式:
测试
订阅端:
发送端:
接收端:
应用场景
1.实时消息系统
2.实时聊天
3.订阅,关注系统
redis主从复制
主从复制!读写分离,大部分情况都是进行读操作。
环境配置
info replication #查看当前库的信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:196b6fcccd83304082ae60d64c103ad7c3a1d40b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
复制三个配置文件,修改对应的信息
1.端口
2.pid名称
3.log文件名
4.dump.rdb文件名
配置完成后,通过进程查看!
一般情况下,只用配从机
127.0.0.1:6381> slaveof 127.0.0.1 6379 #寻找本机的6379端口作为主机
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1618669743
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a70ab68f69eafcb5218700807f527402f310a18c
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6381>
主机写,从机读。
主机如果宕机,从机依然连接到主机,但是没有写操作,主机恢复,从机仍然可以读
如果是使用命令行来配置的,这个是如果重启了,就会变回主机,只要变为从机立马可以获取值。
层层链路
如果主机断开,使用slaveof no one 让自己成为主节点。这个时候主节点恢复,需要重新配置
哨兵模式
以一个独立的哨兵进程,来向每个节点发起命令来检查节点是否活着,
测试:
1.配置哨兵配置文件
vim sentinel.conf
sentinel monitor name 127.0.0.1 6379 1
后面数字1代表,代表主机挂了,slave投票让一个slave代替成为master
用哨兵配置文件启动
如果主机回来了,也只能当从机!
优点:
1.哨兵集群,基于主从复制模式,所有的主从配置优点
缺点:
redis不好扩容,集群容量一旦达到上限,扩容很麻烦。
缓存穿透和雪崩
穿透
指访问不存在的缓存,造成每次都查询数据库
- 每次没查到就写入一个空值,设置过期时间,下次再来就直接返回空
- 禁用IP 限制ip访问
- 限流 每秒访问多少次
- 布隆过滤器过滤(它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。)
- 缓存空值,设置过期时间
雪崩
redis宕机(缓存大量失效)造成所有流量全部访问数据库,使得数据库压力过大而崩溃
- 工作之前,搭建redis集群,实现redis高可用(主从节点,多主多从,哨兵检测,投票选主)
- 工作的时候,采取某些限流或者采用降级组件以保证核心服务能使用
- 事后,redis持久化,一旦重启,自动加载数据,redis数据备份和恢复,快速魂村预热
击穿
单个key高并发下,造成数据库压力过大而崩溃
- 设置永不过期
- 实现互斥锁
- 多级缓存
springboot集成redis做缓存
==@EnableCaching // 开启Spring Redis Cache,使用注解驱动缓存机制==
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- spring-boot redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件
# Spring Redis Cache
# 设置缓存类型,这里使用Redis作为缓存服务器
spring.cache.type=REDIS
# 定义cache名称,用于在缓存注解中引用,多个名称可以使用逗号分隔
spring.cache.cache-names=redisCache
# 允许保存空值
spring.cache.redis.cache-null-values=true
# 自定义缓存前缀
#spring.cache.redis.key-prefix=
# 是否使用前缀
spring.cache.redis.use-key-prefix=false
# 设置缓存失效时间,0或者默认为永远不失效
spring.cache.redis.time-to-live= 600000
# 设置默认的隔离级别为读写提交
spring.datasource.tomcat.default-transaction-isolation=2
config
/**
* 自定义RedisCacheManager
*/
@Bean(name = "redisCacheManager")
public RedisCacheManager initRedisCacheManager() {
// 获取Redis加锁的写入器
RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory);
// 启动Redis缓存的默认设置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 设置JDK序列化器
config = config.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
// 自定义设置:禁用前缀
config = config.disableKeyPrefix();
// 设置失效时间
config = config.entryTtl(Duration.ofMinutes(10));
// 创建Redis缓存管理器
RedisCacheManager redisCacheManager = new RedisCacheManager(writer, config);
return redisCacheManager;
}
@CacheEnable
先从缓存中查询,查询不到,再进入方法,搭配@transactional注解,开启事务
#先到缓存redisCache中,查询缓存的键为redis_user_加上参数id 中的value,没有再进入方法
@Cacheable(value = "redisCache", key = "'redis_user_'+#id")
public User getUser(Long id) {
return userMapper.getUserById(id);
}
@CachePut
将方法执行的结果放入缓存
#将方法执行结果的id拼上字符串作为key,缓存到rediscache中,执行的条件是方法返回的结果不是空
@Transactional
@CachePut(value = "redisCache", condition = "#result != null ",key = "'redis_user_'+#result.id")
public User insertUser(User user) {
userMapper.insertUser(user);
System.out.println("After insert, User is: " + user);
return user;
}
@CacheEvict
删除缓存
#删除rediscache中,指定key的缓存,在方法执行前;也可以使用allEntity=true来表示删除所有缓存
@Transactional
@CacheEvict(value = "redisCache", key = "'redis_user_'+#id", beforeInvocation = false)
public int deleteUser(Long id) {
return userMapper.deleteUser(id);
}