Redis

本文详细阐述了如何通过增加分布式缓存解决数据库并发问题,介绍了Redis的16个数据库和关键数据类型,包括字符串、散列、列表、集合和有序集合。此外,涵盖了Redis版本管理、操作指令、生命周期管理以及Java操作Redis的实战示例,涉及连接池、RedisTemplate和不同数据结构的操作演示。
摘要由CSDN通过智能技术生成

概述

通过增加分布式缓存进行解决并发读写数据库,数据库压力过大导致性能下降
Redis有16个数据库 0-15
在这里插入图片描述

数据类型

Redis数据库作为Key/value结构的数据存储系统,为了便于对数据进行管理,提供了多种数据类型,基于指定类型存储项目中产生的数据

常用数据类型

字符串 / 散列 / 列表 / 集合 / 有序集合

Redis版本说明

Redis次版本号(第一个小数点后的数字),偶数为稳定版本,奇数非稳定版本

Redis相关网址

Bootnb 相关:https://www.runoob.com/redis/redis-tutorial.html
Redis 官网:https://redis.io/
源码地址:https://github.com/redis/redis
Redis 在线测试:http://try.redis.io/
Redis 命令参考:http://doc.redisfans.com/

指令

登录Redis服务

//本地登录
redis-cli
//远程登录
redis-cli -h ip -p port -a password

查看Redis信息

info

关闭Redis

//默认持久化保存后关闭
shutdown
//不持久化关闭
shutdown nosave

查看所有Key

keys *

清除数据库数据

//清除当前数据库数据
flushdb
//清除所有数据库数据
flushall

设置Key生命周期

expire key seconds
expire key 10

关闭Key生命周期

Persist key

查看Key生命周期

ttl key
key不存在返回 -2
key没有设置生命周期返回 -1
key设置了生命周期返回具体秒数

Key/Value存储

set key value

get key

String类型操作

字符串长度支持512M,基于此类型可以实现博客的数字统计,将日志不断的追加到指定key,实现一个分布式自增Id,实现博客的点赞操作
自增/自增步长

//如果key不存在会默认创建并+1
incr key
incrby key 2

自减/自减步长

decr num
decrby num 2

追加

//向尾部追加值,如果键不存在则创建
append key value

value的长度

//返回value的长度,不存在返回0,如果是空串返回也是0
strlen key

设置/获取

//设置多个
mset key1 value1 key2 value2 key3 value3
//获得多个
mget key1 key2 key3

Hash类型

Redis散列类型相当于Java的HashMap,实现原理与HashMap一致,一般用于存取对象信息
设置/获取

hset key field value
hget key field
//多个
hmset key field1 value1 field2 value2
hmget key field1 field2
//获取所有
hgetall key

自增

hincrby key field  increment
hincrby key 属性 步长

判断属性是否存在

//没有则返回0  有则返回1
hexists key field

删除属性

hdel key value

只获取key或value

hkeys key
hvals key

List类型

Redis的List类型相当于 LinkedList 其原理是一个双向链表,支持正向/反向/便利操作,插入删除速度快,经常用来实现热销榜最新评论等
在这里插入图片描述

在头部添加字符串元素

lpush key "element"

在尾部添加字符串元素

rpush key "value"

从头部移除元素,并返回删除元素

//删除一个
lpop key

从尾部移除元素,并返回删除元素

//删除一个
rpop key

查看list元素

lrange key StrIndex EndIndex

删除元素

del key

特定位置之前或之后添加字符串元素

//在value1之前添加value2
linsert key before "value1" "value2"
//在value1之后添加value2
linsert key after "value1" "value2"

修改指定下标元素

lset key index "value"
lset key 0 "xxx"

删除count个指定value

count > 0 从头部开始删除
count < 0 从尾部开始删除
count = 0 全部删除
lrem key count "value"
lrem key 2 "xxx"

保留指定key的值范围内的数据

ltrim key StrIndex EndIndex
ltrim key 0 -1

返回key对应list的长度

llen key

返回指定下标的元素

lindex key index
lindex key 0

移动

//移除lis1尾部元素并添加到lis2的头部
rpoplpush lis1 lis2

set类型

Redis的Set 与Java的HashSet一致,集合成员都是唯一的不允许出现重复数据
添加元素

sadd key value

获取内容

smembers key

随机移除返回一个元素

spop key

获取个数,返回长度

scard key

移动一个元素到另一个集合

127.0.0.1:6379> sadd internet amoeba nginx redis
(integer) 3
127.0.0.1:6379> sadd bigdata hadopp spark rabbitmq
(integer) 3
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "nginx"
127.0.0.1:6379> smembers bigdata
1) "hadopp"
2) "spark"
3) "rabbitmq"
127.0.0.1:6379> smove bigdata internet rabbitmq
(integer) 1
127.0.0.1:6379> smembers internet
1) "amoeba"
2) "redis"
3) "rabbitmq"
4) "nginx"
127.0.0.1:6379> smembers bigdata
1) "hadopp"
2) "spark"
127.0.0.1:6379>

合并集合

sunino key key

Java操作Redis

配置文件说明

xxxx/conf/redis.conf文件

...
# 默认只允许本地链接,如果需要远程链接需要注释
bind 127.0.0.1
# 默认开启保护模式,需要将其关闭该为no
protected-mode yes

Jedis

导入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.5.2</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.6</version>
</dependency>
测试链接

测试是否链接,如果ping返回的值是PONG则表示链接成功

public class TestJedis {
    @Test
    public void TestConnection(){
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        jedis.auth("密码");
        String ping = jedis.ping();
        System.out.println(ping);
    }
}
字符串练习
@Test
    public void TestString() throws InterruptedException {
        //建立链接
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //存储数据
        jedis.set("user1","heJian1");
        jedis.set("user2","heJian2");
        jedis.set("age","1");
        //更新数据
        jedis.expire("user1",1); //设置有效时长
        jedis.incr("age");
        Thread.sleep(1000); //故意睡1秒
        //获取数据
        String user1 = jedis.get("user1");
        String user2 = jedis.get("user2");
        String age = jedis.get("age");
        System.out.println(user1+":-:-:"+user2+":-:-:"+age);
        //释放资源
        jedis.close();
    }
json数据练习
@Test
    public void TestJson(){
        //对象数据
        Map<String,Object> map = new HashMap();
        map.put("name","hejian");
        map.put("age","12");
        map.put("sex","男");
        //转换为Json
        Gson gson = new Gson();
        String jsonStr = gson.toJson(map);
        //建立链接
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //设置数据
        jedis.set("user",jsonStr);
        //获取数据
        String user = jedis.get("user");
        //转换数据 Json -Map
        Map<String,Object> map1 = gson.fromJson(user, Map.class);
        System.out.println(map1);
        //关闭资源
        jedis.close();
    }
hashMap数据练习
@Test
    public void TestHash(){
        //建立链接
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //手动存入数据
        jedis.hset("user","name","hejian");
        jedis.hset("user","age","18");
        //获取数据
        String name = jedis.hget("user", "name");
        String age = jedis.hget("user", "age");
        System.out.println(name);
        System.out.println(age);
        //存入Map
        Map<String,String> map = new HashMap<String, String>();
        map.put("cat","xiaohong");
        map.put("dog","wangwang");
        jedis.hset("animal",map);
        Map<String, String> animal = jedis.hgetAll("animal");
        System.out.println(animal);
        //关闭资源
        jedis.close();
    }
List类型操作
@Test
    public void TestList(){
        //建立链接
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //操作
        jedis.lpush("list1","A","B","C");
        int len = jedis.llen("list1").intValue();
        System.out.println(len);
        List<String> list1 = jedis.rpop("list1", len);
        System.out.println(list1);
        //阻塞式队列
        List<String> one = jedis.brpop(40, "list1");
        System.out.println(one);
        List<String> two = jedis.brpop(40, "list1");
        System.out.println(two);
        List<String> three = jedis.brpop(40, "list1");
        System.out.println(three);
        List<String> four = jedis.brpop(40, "list1");
        System.out.println(four);
        //释放资源
        jedis.close();
    }
Set类型操作
@Test
    public void Testset(){
        //建立链接
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        //操作数据
        jedis.sadd("user","1","1","2");
        Set<String> user = jedis.smembers("user");
        System.out.println(user);
        //关闭资源
        jedis.close();
    }
链接池

配置文件-链接池配置

spring:
  jedis: #连接池
      pool:
        max-idle: 8
        max-wait: 0
 @Test
    public void TestJedisPool(){
        JedisPoolConfig config = new JedisPoolConfig();
        //链接池最大连接数
        config.setMaxTotal(1000);
        //设置空闲连接数
        config.setMaxIdle(60);
        config.setMinIdle(20);
        //设置最大阻塞时间
        config.setMaxWaitMillis(500);

        JedisPool jedisPool = new JedisPool(config, "192.168.126.129", 6379);
        Jedis resource = jedisPool.getResource();
        resource.set("test","hj");
        String test = resource.get("test");
        System.out.println(test);
    }

Redis Template

依赖

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.3.2.RELEASE</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

配置文件

spring:
  redis:
    host: 192.168.126.129
    port: 6379

定制RedisTemp对象
RedisTemplate 默认采用JDK的序列化机制,如果不想使用JDK的序列化可以进行定制RedisTemp对象的序列化方式

package com.jt.redis.config;
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        //1.构建RedisTemplate对象
        RedisTemplate<Object,Object> redisTemplate=new RedisTemplate<>();
        //2.设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //3.定义序列化方式(在这里选择jackson)
        Jackson2JsonRedisSerializer redisSerializer= new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper=new ObjectMapper();
        //设置要序列化的域(属性)
        //any表示任意级别访问修饰符修饰的属性 private,public,protected
        objectMapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        //启动输入域检查(类不能是final修饰的)
        objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(),
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.PROPERTY);
        redisSerializer.setObjectMapper(objectMapper);
        //4.设置RedisTemplate的序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(redisSerializer);
        //spring规范中假如修改bean对象的默认特性,建议调用一下afterPropertiesSet()
        redisTemplate.afterPropertiesSet();
       return redisTemplate;
    }
}


package com.jt.redis.pojo;

import java.io.Serializable;

public class Blog implements Serializable {//{"id":10,"title":"redis"}
    private static final long serialVersionUID = -6721670401642138021L;
    private Integer id;
    private String title;
    public Blog(){
        System.out.println("Blog()");
    }
    public Blog(Integer id,String title){
        this.id=id;
        this.title=title;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
    @Override
    public String toString() {
        return "Blog{" +
                "id=" + id +
                ", title='" + title + '\'' +
                '}';
    }
}

@Test
void testJsonOper() throws JsonProcessingException {
    ValueOperations valueOperations = redisTemplate.opsForValue();
    Blog blog=new Blog(10,"study redis");
    valueOperations.set("blog",blog);//序列化
    blog=(Blog)valueOperations.get("blog");//反序列化
    System.out.println("blog="+blog);
}

StringRedisTemplate

依赖注入

 @Autowired
 private RedisTemplate redisTemplate;

测试链接

@Test
    void TestConnection(){
        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        String ping = connection.ping();
        System.out.println(ping);
    }

字符串操作

 @Test
    void TestRedisStringOper(){
        ValueOperations<String, String> svo = redisTemplate.opsForValue();
        svo.set("ip","192.168.126.129");
        //有效时长,及单位
        svo.set("port","6379",1, TimeUnit.SECONDS);
        svo.increment("port");
        String ip = svo.get("ip");
        String port = svo.get("port");
        System.out.println(ip);
        System.out.println(port);
    }

Set类型操作

 @Test
    void TestRedisSet(){
        SetOperations<String, String> SSO = redisTemplate.opsForSet();
        SSO.add("setKey","A","B","C","C");
        Set<String> setKey = SSO.members("setKey");
        System.out.println(setKey);
    }

List类型操作

@Test
    void TestList(){
        ListOperations<String, String> sslo = redisTemplate.opsForList();
        //存数据
        sslo.leftPush("listkey","100");
        sslo.leftPushAll("listkey","100","200","300");
        sslo.rightPush("listkey","800");
        //查数据
        List<String> listkey = sslo.range("listkey", 0, -1);
        System.out.println(listkey);
        //取数据
        String listkey1 = sslo.leftPop("listkey");
        System.out.println(listkey1);
        //查数据
        List<String> listkey2 = sslo.range("listkey", 0, -1);
        System.out.println(listkey2);
    }

Hash类型操作

 @Test
    void TestHash(){
        HashOperations<String, Object, Object> sooho = redisTemplate.opsForHash();
        Map<String,String> user = new HashMap();
        user.put("name","hejian3");
        user.put("age","16");
        sooho.putAll("user",user);
        sooho.put("user","sex","男");
        Object name = sooho.get("user", "name");
        System.out.println(name);

        Map<Object, Object> user1 = sooho.entries("user");
        System.out.println(user1);
    }

案例

package com.tedu;


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.Jedis;

import java.util.UUID;

@SpringBootTest
public class SingleSignOn {
    static String token;

     void doGetResource(){ 
         
        if(token==null){
            System.out.println("没有token,请登录");
            return;
        }
        Jedis jedis = new Jedis("192.168.126.129", 6379);
        String user = jedis.get(token);
        jedis.close();

        if(user==null){
            System.out.println("登录超时");
            return;
        }
        System.out.println("访问资源成功");
    }

    void doLogin(String name,String password){
        if("root".equals(name)&&"123".equals(password)){
            System.out.println("登录成功");
            String token = UUID.randomUUID().toString();
            Jedis jedis = new Jedis("192.168.126.129", 6379);
            jedis.set(token,name);
            jedis.expire(token,1);
            jedis.close();


            SingleSignOn.token = token;
            return;
        }
        System.out.println("账号密码不对");
    }

    @Test
     void test(){
         SingleSignOn s = new SingleSignOn();

        s.doGetResource();
        s.doLogin("root","123");
        s.doGetResource();

    }
}

数据持久化操作

为了解决Redis因故障(宕机/断电等等)导致不可用,从而数据丢失问题

持久化的方式

RDB

概述
Rdb方式可以通过手动(save-阻塞式 , bsave-异步)或周期性方式保存redis中的key/value的一种机制,Rdb方式一般为redis的默认数据持久化方式,系统启动时会自动开启这一种方式的持久化机制
配置

# 这里表示每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中完整的数据快照,这个操作也被称之为snapshotting(快照)。

save 60 1000

# 持久化 rdb文件遇到问题时,主进程是否接受写入,yes 表示停止写入,如果是no 表示redis继续提供服务。
stop-writes-on-bgsave-error yes
    
# 在进行快照镜像时,是否进行压缩。yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间。
rdbcompression yes
# 一个CRC64的校验就被放在了文件末尾,当存储或者加载rbd文件的时候会有一个10%左右的性能下降,为了达到性能的最大化,你可以关掉这个配置项。
rdbchecksum yes

# 快照的文件名
dbfilename dump.rdb

# 存放快照的目录
dir /var/lib/redis

模拟场景
场景一 :使用shutdown 关机
结果: 会保存数据(因为shutdown关机 是一种安全退出模式,在退出之前会将内存中的数据立即生成一份rdb快照)

场景二:kill线程
结果:不会保存

场景三:手动保存后再kill线程
结果:会保存

区别
Save:
执行一个同步保存操作,将当前Redis实例的所有数据快照以Rdb文件形式保存在磁盘

BGsave:
执行之后立即返回Ok,然后Redis会出一个新子进程用来负责将数据保存到磁盘

优点
1、 Rdb 会生成多个数据文件,每个数据文件都代表某一时刻的redis的数据,适合做冷备,可以将这种完整文件发送到远程云服务,按预定的备份策略来定期备份redis中的数据
2、Rdb 对Redis对外提供的读写服务影响非常小,可以让redis保持高性能,因为redis主进程只需要fork一个子进程,让子进程操作数据到磁盘
3、相对于AOF持久化机制,RDB数据文件来重启和恢复,速度更快

AOF

概述
通过记录操作日志的方式,记录redis数据的一种持久化机制,默认该机制是关闭的
配置

# 是否开启AOF,默认关闭
appendonly yes
# 指定 AOF 文件名
appendfilename appendonly.aof
# Redis支持三种刷写模式:
# appendfsync always #每次收到写命令就立即强制写入磁盘,类似MySQL的sync_binlog=1,是最安全的。但该模式下速度也是最慢的,一般不推荐使用。
appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做平衡,推荐该方式。
# appendfsync no     #完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不推荐。
    
#在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。
#设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes
no-appendfsync-on-rewrite yes
#当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。
auto-aof-rewrite-min-size 64mb

如果理解AOF方式的rewrite操作?
redis中的数据其实是有限的,很多数据可能会自动过期,可能会被用户删除,可以会被redis用缓存清除算法清理掉,也就是说redis的数据是不断淘汰旧的,只有常用的一部分才会保留在redis内存中,所以写日志还停留在AOF中,AOF日志文件就一个,所以会不断的膨胀
所以AOF会自动每个一段时间做rewrite操作,例如日志中有100w的操作而redis中只有10w条数据,AOF就会基于当前10w条数据构建一套新的日志,覆盖之前老的日志,确保AOF日志不会过大

优点
1、AOF可以更好的保证数据不丢失,一般AOF每隔一秒,通过后台线程执行一次fsync操作,最多丢失一秒的数据
2、AOF日志文件通过append-only模式写入,所以没有任何磁盘寻址开销,写入性能非常高,文件不易破损
3、AOF日志文件过大的时候,出现后台重写操作,也不会影响客户端的读写,因为在rewrite log时,会对其中的值导出压缩,创建一份需要恢复数据的最小日志,在创建日志文件的时候,老日志还是正常写入,当新的merge后的日志文件ready时在交换新老日志
4、AOF日志通过易读的方式进行记录,该特性非常适合做灾难恢复,比如不小心执行了flushall操作,只要这个时候还没有重写就可以拷贝AOF文件把 flushall操作删除在将AOF放回即可

备份方案

1、既要使用rdb也要使用aof ,rdb做冷备,aof保证数据丢失量达到最小

事务

https://blog.csdn.net/maitian_2008/article/details/119480944

主从 - 哨兵 - 集群

https://blog.csdn.net/maitian_2008/article/details/119482237

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值