【学习笔记】Redis入门第1-n天(库存)

196 阅读17分钟

NoSQL四大分类

  • kv键值对(Redis):
  • 文档型数据库(MongoDB):
    • 用于处理大量的文档!介于关系型数据库和非关系型数据库之间。
  • 列存储数据库(HBase)
  • 图关系型数据库(Neo4j):存放关系而非图片

Windows下载安装

1.下载地址:github.com/dmajkic/red…

2.得到安装包

3.解压到自己的环境中

4.开启Redis,双击运行服务即可

5.使用Redis客户端连接Redis

Linux下载安装

官网:redis.io/

用xftp工具将安装包上传到服务器上并且解压:

image-20210415210759606

基本环境配置

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

image-20210415214307844

6.使用Redis客户端连接(本机省略主机名)

redis-cli -h -p 6379

image-20210415214744814

7.关闭连接以及退出

image-20210415214950739

8.开启远程连接

将配置中 bind 127.0.0.1 修改为 0.0.0.0

image-20210417145556050

9.将6379这个端口开放

firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --reload  #重启防火墙

性能测试

redis-benchmark是一个压力测试工具

使用:redis-benchmark 命令参数!

20200526094341953

简单测试:

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.编码测试:

  • 连接数据库(如果是远程连接,记得让防火墙开放端口)
  • 操作命令
  • 断开连接

image-20210417145952424

image-20210417150010184

整合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详解

配置文件对大小写不敏感

image-20210417195721310

include:将多个config文件合并起来

image-20210417195647632

网络

image-20210417195837669

通用配置

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

image-20210417204008687

触发机制:

1.save的规则满足的情况下回触发rdb规则·

2.执行flushall命令,也会生成一个rdb文件

3.退出redis,也会产生rdb文件

备份就会生成一个rdb文件,放在启动命令目录下。

如何恢复rdb文件

1.将rdb文件放在我们redis启动目录就可以,redis会自动检查dump.rdb恢复其中数据

优点

1.适合大规模数据恢复

2.对数据完整性要求不高

缺点:需要一点的时间间隔操作!

fork进程的时候,会占用一定的内存空间!

AOF

追加文件!将所有命令都记录下来,恢复的时候就把所有命令全部执行一遍。

image-20210417211644124

默认是不开启的,需要手动开启,将上图中的no 改为yes。重启redis就可以生效

如果aof文件有错位,这时候redis启动不起来,我们就需要修复这个aof文件。

redis给我们启动了一个工具:redis-check-aof ---fix 文件名.aof

重写规则

image-20210417212651756

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发布订阅是一种消息通信模式:

image-20210417214108862

测试

订阅端:

image-20210417214450875

发送端:

image-20210417214523560

接收端:

image-20210417214536306

应用场景

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文件名

配置完成后,通过进程查看!

image-20210417222529994

一般情况下,只用配从机

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);
    }