此笔记部分根据狂神说redis部分视频记录,感谢狂神说!下面为视频路径:https://www.bilibili.com/video/BV1S54y1R7SB?t=951&p=35
no only sql 不仅仅是sql
一般称之为非关系型数据库
web2.0存在一些问题:(3高)
1.高并发
2.大数据
3.高扩展,高可用
分类:
key/value的格式
文档型格式
列类型
图
redis是使用c语言开发的一个高性能键值对的数据库,可以用作数据库、缓存和消息中间件
支持的数据类型(五大基础类型,三种特殊类型)
String(★) 字符串
hash(理解) 散列
list 列表
set 集合
sortedSet(zset) 有序集合
geospatial 地理空间
hyperloglog 范围查询
bitmaps
默认16个数据库,默认0号
select 3 #切换数据库
DBSIZE #查看数据库的数据量
keys * #查看数据库锁有的key
flushall #清空所有数据库
flushdb #清空当前数据库
redis是单线程的(6.0以后支持多线程),官方表示redis是基于内存操作的,redis的瓶颈是根据计算机的内存和网络宽带。
1.下载redis
2.上传到linux
3.安装redis
mkdir /usr/local/redis
mv /root/redis.tar /usr/local/redis
cd /usr/local/redis
tar -xvf redis.tar
4.编译redis 依赖 gcc
yum install gcc-c++
make
5.安装redis
make PREFIX=/usr/local/redis install
6.配置
复制一个redis.conf 到bin目录下
启动服务器的方式1:
前台启动的方式:
cd /usr/local/redis/bin
./redis-server redis.conf
后台的方式:
配置一下redis.conf
修改:daemonize yes
保存退出
启动客户端
简单的方式:
./redis-cli #连接本地端口号为 6379的服务器
推荐的方式
./redis-cli -h 连接ip -p 端口号
redis的停止
方式1:通过kill -9 进程号(不推荐)
方式2:通过客户端发送命令
./redis-cli -h ip -p port shutdown
性能测试:redis-benchmark.exe
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
赋值:格式: set key value
例如:set username tom
取值:格式: get key
例如: get username
先获取再设置:getset key value,如果不存在值,返回nil
例如: getset username jack
删:del key
例如: del d
了解
对于数字类型 自增和自减
incr key ++
decr key –
增加或减少指定的数量
incrby key int
decrby key int
拼接字符串,如果当前key不存在,就相当于set key value
append key value
字符串范围
getrange key1 0 3 设置key1的值
getrange key1 0 -1 相当于get key1
setrange key2 1 xx 替换指定位置开始的字符串
setex(set with expire)设置过期时间
setex key3 30 hello
setnx(set if not exist)如果不存在设置,在分布式锁中使用
setnx mykey redis
批量设置
mset key1 v1 key2 v2 key3 v3
mget key1 key2
msetnx k4 v4 key1 v1 mset是原子性的操作,要么一起成功,要么一起失败
user:{id}:{filed}
mset user:1:name shangsan user:1:age 2
mget user:1:name user:1:age
赋值:
左边:lpush key value value2 value3
右边:rpush key value value2 value3
取值:
左边:lpop key
右边:rpop key
获取所有元素
lrange key 0 -1
获取元素的个数
llen key
扩展:
lpushx key value :若有则添加 若没有则不添加
rpushx key value :若有则添加 若没有则不添加
lrem key count value:从左边移除count个value
若count>0 :从左边移除count个value
若count<0 :从右边移除count个value
若count=0 :从右边移除所有的value
lset key index value
设置链表中指定索引的元素值 0 代表是第一个
-1代表的是最后一个
Redis单条命令是保存原子性的,但是事务不保证原子性。redis事务没有隔离级别的概念,redis事务的本质是一组命令的集合,一个事务中的所有命令都会被序列化,在发起命令后,才会按照顺序执行。一致性、顺序性、排他性
简介:
命令:
127.0.0.1:6379[3]> multi
OK
127.0.0.1:6379[3]> set k1 v1
QUEUED
127.0.0.1:6379[3]> set k2 v2
QUEUED
127.0.0.1:6379[3]> get k2
QUEUED
127.0.0.1:6379[3]> set k3 v3
QUEUED
127.0.0.1:6379[3]> exec
1) OK
2) OK
3) "v2"
4) OK
编译型异常(命令出错),所有事务都不会被执行
运行时异常(如:0/1),其他的命令可以正常执行,错误命令抛出异常
监控:
#正常
127.0.0.1:6379[3]> set money 100
OK
127.0.0.1:6379[3]> set out 0
OK
127.0.0.1:6379[3]> watch money
OK
127.0.0.1:6379[3]> multi
OK
127.0.0.1:6379[3]> decrby money 20
QUEUED
127.0.0.1:6379[3]> incrby out 20
QUEUED
127.0.0.1:6379[3]> exec
1) (integer) 80
2) (integer) 20
#多线程修改时,使用watch可当作乐观锁操作
127.0.0.1:6379[3]> watch money
OK
127.0.0.1:6379[3]> multi
OK
127.0.0.1:6379[3]> decrby money 10
QUEUED
127.0.0.1:6379[3]> incrby out 10
QUEUED
#在执行前,另外一个线程修改了值,会导致执行失败
127.0.0.1:6379[3]> exec
(nil)
#解决办法
127.0.0.1:6379[3]> unwatch #先解锁,再获取锁
OK
127.0.0.1:6379[3]> watch money
OK
geoadd 将指定的地理空间位置(纬度【-180度到180度】、经度【-85.05112878度到85.05112878度】、名称)添加到指定的key
中,两极无法添加。一般会下载城市数据,直接通过java程序一次性导入。
127.0.0.1:6379[3]> geoadd china:city 120.15 30.28 hangzhou
(integer) 1
127.0.0.1:6379[3]> geoadd china:city 113.62 34.75 zhengzhou
geopos获取当前定位
127.0.0.1:6379[3]> geopos china:city beijing tianjin
1) 1) "116.39999896287918"
2) "39.900000091670925"
2) 1) "117.19999998807907"
2) "39.120000488192183"
两个位置之间的直线距离
127.0.0.1:6379[3]> geodist china:city beijing tianjin km
"110.6313"
georadius:以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
127.0.0.1:6379[3]> georadius china:city 110 30 1000 km
1) "hangzhou"
2) "zhengzhou"
georadiusbymember:指定成员的位置被用作查询的中心。
127.0.0.1:6379[3]> georadiusbymember china:city beijing 1000 km
1) "zhengzhou"
2) "tianjin"
3) "beijing"
geohash:返回一个或多个位置元素的 Geohash (11位)表示。
底层原理:zset
127.0.0.1:6379[3]> zrange china:city 0 -1
1) "hangzhou"
2) "zhengzhou"
3) "tianjin"
4) "beijing"
127.0.0.1:6379[3]> zrem china:city tianjin
(integer) 1
127.0.0.1:6379[3]> zrange china:city 0 -1
1) "hangzhou"
2) "zhengzhou"
3) "beijing"
基数:不重复的元素,允许容错
127.0.0.1:6379[3]> pfadd mykey a d b d d s k #创建
(integer) 1
127.0.0.1:6379[3]> pfcount mykey #统计数量
(integer) 5
127.0.0.1:6379[3]> pfadd mykey2 k i y g p w v
(integer) 1
127.0.0.1:6379[3]> pfmerge mykey3 mykey mykey2 #合并,并集
OK
127.0.0.1:6379[3]> pfcount mykey3
位运算,可用作打卡、签到等场景
127.0.0.1:6379[3]> setbit sign 0 0 #设置
(integer) 0
127.0.0.1:6379[3]> setbit sign 1 1
(integer) 0
127.0.0.1:6379[3]> setbit sign 2 1
(integer) 0
127.0.0.1:6379[3]> getbit sign 2 #获取某个index的值
(integer) 1
127.0.0.1:6379[3]> bitcount sign #统计记录
(integer) 2
Jedis是官方推荐的java连接开发工具,
导入对应的依赖
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
编码测试
连接数据库
操作命令
断开连接
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println(jedis.ping());
}
API与命令相似
例:事务
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "kuangshen");
Transaction multi = jedis.multi();
String s = jsonObject.toJSONString();
try {
multi.set("user1", s);
multi.set("user2", s);
int i = 1 / 0;
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
springboot操作数据:spring-data, jpa, mongodb, redis
SpringBoot2.x以后,jedis被改成了lettuce
jedis:采用的直连,多个线程操作不安全,若要避免,则要使用jedis pool连接池,更像Bio模式
lettuce:采用netty,可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数量,更像Nio模式
源码
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")//不存在就生效
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
//默认的RedisTemplate没有过多的设置,redis对象需要序列化
//两个泛型都是Object,需要强制转换
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean //由于string类型是最长使用的类型,所以单独提取出来的
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
配置连接
spring.redis.host=127.0.0.1
spring.redis.port=6379
测试及编码
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//opsForValue操作string
//opsForList list
//opsForHyperLogLog HyperLogLog
//除了基本的操作,常用的方法都可以直接通过RedisTemplate操作,比如事务
//清空数据库通过connection操作
/*RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();*/
redisTemplate.opsForValue().set("mykey", "关注狂神说Java的公众号");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
序列化配置,默认为JdkSerializationRedisSerializer
。自定义序列化配置网上查询。
@Nullable
private RedisSerializer keySerializer = null;
@Nullable
private RedisSerializer valueSerializer = null;
@Nullable
private RedisSerializer hashKeySerializer = null;
@Nullable
private RedisSerializer hashValueSerializer = null;
若不序列化,会报错SerializationException
配置文件redis.windows.conf
大小写不敏感
包含,INCLUDES,
NETWORK
,网络
bind 127.0.0.1 #绑定IP、端口号
port 6379
protected-mode yes #以守护线程的方式运行,默认为no
GENERAL
,
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)生产环境使用
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" #日志文件位置名
databases 16 #数据库数量
SNAPSHOTTING
,快照
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb, .aof
redis是内存数据库,若没有持久化,就会断电即失
#持久化规则,如果xxxs内,至少有xxx个key进行修该,进行持久化操作
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes #持久化出差后,是否还进行工作
rdbcompression yes #是否压缩rdb文件,需要消耗CPU资源
rdbchecksum yes #保存rdb检查
dir ./ #文件保存目录
REPLICATION
配置主从复制,具体查看八
SECURITY
,安全,redis密码两种方式,配置文件修该或下述方式。
127.0.0.1:6379> config get requirepass #查看密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" #设置密码
OK
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 #登录
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
LIMITS
,服务端限制
# maxclients 10000
# maxmemory
# maxmemory-policy noeviction 内存达到上限后的策略
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key according to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys-random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
APPEND ONLY MODE
,aof配置
appendonly no #默认不开启,使用rdb方式
appendfilename "appendonly.aof"
# appendfsync always #每次修改后同步,消耗性能
appendfsync everysec #每秒执行一次
# appendfsync no #不执行同步
redis是内存数据库,若没有持久化,就会断电即失。在主从复制中,rdb是在从机上备用的,aof几乎不使用
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。redis会单独创建(fork)一个子进程来进行持久化,会将数据写入到一个临时rdb文件中,待持久化过程结束后,再用这个临时文件替换上次持久化好的文件,成为正式的rdb文件,子线程退出。缺点是最后一次持久化后的数据可能丢失。
文件默认是dump.rdb
触发机制:
恢复rdb文件:只需将rdb文件放在redis启动目录即可。
将所有数据都记录下来,在恢复时会重新把所有命令都执行一遍。以日志的形式来记录每个写操作。只需要将appendonly no #默认不开启,使用rdb方式
改为yes,然后重启即可生效。
文件默认是appendonly.aof
,若aof文件被破坏,则会启动失败。可使用redis-check-aof
进行修复,会直接将错误命令删除
redis-check-aof --fix appendonly.aof
当同时开启两种持久化方式,重启时会优先载入aof文件来恢复原始数据。因为rdb只用作后备,所以一般只用save 900 1
即可。
是指将一台redis服务器的数据,复制到其他redis服务器,前者称为master(主节点),后者称为从节点(slave/follower),数据的复制是单向的,只能从主节点到从节点,主节点只能写,从节点只能读。默认情况下,每台主节点都是主节点,且一个主节点可以有多个从节点,一个从节点只能有一个主节点。一个集群至少有三个服务器,一主二从。
作用:
环境配置:
只配置从库,不配置主库。
127.0.0.1:6379> info replication
# Replication 查看当前的信息
role:master
connected_slaves:0 #从机
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
#复制redis的配置文件,对端口号、pidfile、logfile、dbfilename进行更改
cp redis.conf redis79.conf
cp redis.conf redis80.conf
cp redis.conf redis81.conf
#更改完成后用配置文件启动,并用ps -ef|grep redis查看启动线程
redis-server kconfig/redis79.config
redis-server kconfig/redis80.config
redis-server kconfig/redis81.config
一主二从
slaveof 127.0.0.1 6379 #在从机上配置从机的主机地址
info replication #查看主机信息
上述命令行配置的主从复制时暂时的,真实的主从配置应该在配置文件中配置
# slaveof
# masterauth
细节
主机可以写,从机只能读,主机中的所有数据都会被从机保存。若主机shutdown,从机依旧是从机,主机重新启动后,从机会自动连接上主机,能获取到主机写入的新主机。
如果使用命令行配置主从复制,当redis重新启动后,会默认恢复成主机,所以,当从机shutdown后,需要重新配置。
复制原理
slave启动成功连接到master后,会发送sync同步命令,master接到命令,会启动存盘进程,将整个数据文件传送给slave,并完成一次同步。
只要重新连接到主机,一次全量复制将被执行。
主机–>从机/主机–>从机方式(层层链路)
配置完成之后中间的redis依旧是从节点,当主节点shutdown后,需要进行如下配置
slaveof no one #让自己变成主机
如果第一个主节点恢复了,就需要重新配置。哨兵模式不需要配置上述步骤。
redis2.8+提供了Sentinel(哨兵)架构。能够后台监控主机是否故障,如果发生故障,根据投票数自动将从库转换为主库
。通过发送命令,等待redis服务器响应,从而监控多个redis实例。使用多个哨兵进行监控,形成多哨兵模式。
如果主服务器宕机,哨兵1先检测到这个结果,系统不会马上进行投票(主观下线),当其他哨兵也检查到并数量达到一定后,哨兵就会发起投票,进行故障转移,切换成功之后,就会通过发布订阅模式,实现票数最多的从机切换主机(客观下线)。
配置sentinel.config
#主机名称 端口号 宕机时,从机投票
sentinel monitor myredis 127.0.0.1 1
#启动哨兵
redis-sentinel kconfig/sentinel-conf
如果主机恢复之后,只能归并到新的主机下,当作从机。
服务的高可用问题
概念
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案
布隆过滤器
是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先校验,不符合丢弃,从而避免了底层存储系统的查询压力。
缓存空对象
当存储层不命中后,即使返回空对象也会被存储起来,同时设置一个过期时间,之后访问这个数据会直接从缓存中获取,保护后端数据源。
概念
缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
解决方案
设置热点数据永不过期
加互斥锁
分布式锁,使用分布式锁,保证每个key同时只有一个线程去查询后端服务,其他的线程没有获得分布式锁的权限,因此只需要等待即可。这种方式把高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
概念
缓存雪崩是指,缓存层出现了错误,不能正常工作了(redis宕机,缓存集中过期失效等)。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
解决方案
redis高可用
多设置几台redis,搭建redis集群。(异地多活)
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
sh形式存储,在控制层先校验,不符合丢弃,从而避免了底层存储系统的查询压力。
缓存空对象
当存储层不命中后,即使返回空对象也会被存储起来,同时设置一个过期时间,之后访问这个数据会直接从缓存中获取,保护后端数据源。