• [技术干货] Redis 入门(附Python和Java操作redis教程)
    什么是MybatisRedis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。它通常被称为数据结构服务器,因为值(value)可以是 字符串(string)、哈希(Hash)、列表(list)、集合(sets)、有序集合(sorted sets)等类型。redis 与传统数据库的区别Redis与传统关系型数据库的区别如下:数据存储方式:Redis是基于内存的数据库,数据存储在内存中,读写速度快,但容量受限于内存大小。而关系型数据库通常将数据存储在磁盘中,读写速度较慢,但容量可以扩展到很大。数据结构:Redis支持多种数据结构,如字符串、哈希表、列表、集合和有序集合等,对程序员透明,且对原子性操作提供了更为复杂的数据结构。相比之下,关系型数据库通常只支持表格结构。应用场景:Redis主要用于缓存、队列、计数器等,而关系型数据库主要用于存储关系型数据。Python 操作redisPython操作Redis需要使用Redis模块,常用的Redis模块包括redis-py和redis-py-cluster。以下是使用redis-py模块操作Redis的基本步骤:安装redis-py模块:使用pip安装redis-py模块,命令为pip install redis。导入Redis模块:在Python脚本中导入Redis模块,import redis。连接Redis服务器:使用Redis模块中的Redis()函数创建Redis连接对象,指定Redis服务器的地址和端口号,例如r = redis.Redis(host='localhost', port=6379)。操作Redis数据库:使用Redis连接对象调用相应的方法操作Redis数据库,例如r.set('key', 'value')设置键值对,r.get('key')获取键对应的值。以下是一个简单的示例,演示如何使用Python操作Redis:import redis # 创建Redis连接对象 r = redis.Redis(host='localhost', port=6379) # 设置键值对 r.set('name', 'Alice') r.set('age', 25) # 获取键对应的值 name = r.get('name') age = r.get('age') # 输出获取的值 print('name:', name.decode('utf-8')) print('age:', age.decode('utf-8'))在上面的示例中,我们首先创建了一个Redis连接对象,然后设置了两个键值对,分别存储了一个人的姓名和年龄。接着使用get()方法获取键对应的值,并使用decode()方法将字节串解码为字符串。最后输出获取的值。Java 操作redis在Java中操作Redis,我们通常需要使用一个库,例如Jedis或Lettuce。以下是使用Jedis库进行基本操作的示例。首先,你需要在你的项目中添加Jedis的依赖。如果你使用Maven,你可以在你的pom.xml文件中添加以下依赖:<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.7.0</version> <!-- 版本可能会有所变化,取决于你查看此答案时的最新版本 --> </dependency>然后你可以使用以下Java代码进行基本的Redis操作:import redis.clients.jedis.Jedis; public class Main { public static void main(String[] args) { // 创建连接对象 Jedis jedis = new Jedis("localhost"); System.out.println("Connection to server successfully"); // 设置数据 jedis.set("foo", "bar"); System.out.println("Data set in Redis: " + jedis.get("foo")); // 获取存储的数据并输出 System.out.println("Stored data in Redis: " + jedis.get("foo")); } }这是一个非常基础的例子,它连接到了在本地运行的Redis服务器(这是默认设置),然后设置了一个键值对("foo", "bar"),并从Redis获取这个值然后打印出来。在实际应用中,你可能需要处理更复杂的情况,例如使用密码保护你的连接、从远程服务器获取数据、处理并发问题等。在这些情况下,你可能需要使用更高级的库,例如Redisson,它提供了更多的功能和更好的抽象。
  • [技术干货] Redis实战:Redis在Java中的基本使用-转载
     1、使用jedis操作redis 1.1、Jedis简介 Jedis 是 Java 语言开发的 Redis 客户端工具包,用于 Java 语言与 Redis 数据进行交互。 Jedis 在 github 官网地址:https://github.com/redis/jedis#readme Jedis 只是对 Redis 命令的封装,掌握 Redis 命令便可轻易上手 Jedis。 Jedis 遵循 RESP 协议规范开发,具有良好的通用性与可读性。  1.2、引入jedis的Maven依赖         <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->         <dependency>             <groupId>redis.clients</groupId>             <artifactId>jedis</artifactId>             <version>4.4.3</version>         </dependency> 1.2、获取连接 对于许多应用程序,需要连接到 Redis 时。最好使用连接池。可以像这样实例化一个 Jedis 连接池:  JedisPool pool = new JedisPool("localhost", 6379); 1 对于 JedisPool 实例,可以使用 try-With-resources 块获得连接并运行 Redis 命令(这种方式无须自己手动 close)。  try (Jedis jedis = pool.getResource()) {     // 运行单个 SET 命令   jedis.set("clientName", "Jedis"); } 1.3、使用实例 编写以下代码:      public static void main(String[] args) {         // Redis服务端IP和端口号         JedisPool pool = new JedisPool("127.0.0.1", 6379);         try (Jedis jedis = pool.getResource()) {             // 使用相关Redis的命令             // 选择第0个数据库进行操作             jedis.select(0);             // 向0号数据库写入,字符串数据             jedis.set("Java", "best");             jedis.set("PHP", "good");             // 查询是否写入             System.out.println(jedis.get("Java"));             System.out.println(jedis.get("PHP"));         }     } 运行测试用例:  看原文链接Jedis 实例实现了大多数 Redis 命令,这些命令可以在 https://www.javadoc.io/doc/redis.clients/jedis/latest/redis/clients/jedis/Jedis.htmlApI 中查询命令对应的方法。 2、对于JedisPooled的使用 2.1、使用JedisPooled 对每个命令使用 try-with-resources 块可能比较麻烦,因此我们可以考虑使用 JedisPooled。  JedisPooled jedis = new JedisPooled("localhost", 6379); 1 详细代码:      public static void main(String[] args) {         JedisPooled pool = new JedisPooled("127.0.0.1", 6379, null, null);         pool.set("Java", "best");         pool.set("PHP", "good");         System.out.println(pool.get("Java"));         System.out.println(pool.get("PHP"));     } 运行效果: 看原文链接 2.2、关于连接池 使用链接池是官方推荐的使用方式,通过连接池可以更好的使用 Jedis 的,我们可以通过 GenericObjectPoolConfig 对连接池进行相关配置,GenericObjectPoolConfig API 文档:https://commons.apache.org/proper/commons-pool/apidocs/org/apache/commons/pool2/impl/GenericObjectPoolConfig.html  通过 GenericObjectPoolConfig 对象对连接池进行配置,具体代码如下:      public static void main(String[] args) {         GenericObjectPoolConfig config = new JedisPoolConfig();         // 设置连接池中最多允许放100个Jedis对象         config.setMaxTotal(100);         // 设置连接池中最大允许空闲连接         config.setMaxIdle(100);         // 设置连接池中最小允许的连接数         config.setMinIdle(10);         // 借出连接的时候是否测试有效性,推荐false         config.setTestOnBorrow(false);         // 归还时是否测试,推荐false         config.setTestOnReturn(false);         // 创建时是否测试有效  开发的时候设置为false,实践运行的时候设置为true         config.setTestOnCreate(false);         // 当连接池内jedis无可用资源时,是否等待资源,true         config.setBlockWhenExhausted(true);         // 没有获取资源时最长等待1秒,1秒后没有还没有的话就报错         config.setMaxWaitMillis(1000);          JedisPool pool = new JedisPool(config, "127.0.0.1", 6379);          try (Jedis jedis = pool.getResource()) {             // 使用相关Redis的命令             // 选择第0个数据库进行操作             jedis.select(0);             // 向0号数据库写入,字符串数据             jedis.set("Java", "best");             jedis.set("PHP", "good");              // 查询是否写入             System.out.println(jedis.get("Java"));             System.out.println(jedis.get("PHP"));         }     } 运行效果: 看原文链接 3、SpringBoot下使用Redis 3.1、引入Maven依赖 首先,需要在 pom.xml 文件中添加 Redis 依赖:  <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-starter-data-redis</artifactId> 这个依赖包含了 Spring Data Redis,以及 Jedis 和 Lettuce 这两种 Redis 客户端的实现。  3.2、配置Redis连接 在 SpringBoot 项目中,可以通过在 application.properties 或 application.yml 文件中配置 Redis 连接信息。以下是一个示例:  spring:   data:     redis:       timeout: 3000       database: 0       password: password       port: 6379       host: localhost 其中,host 和 port 分别是 Redis 服务器的地址和端口号,password 是 Redis的密码(如果没有密码,可以不填),timeout 是 Redis 连接超时时间,jedis.pool 是连接池的相关设置。  3.3、创建RedisTemplate 使用 Spring Data Redis 操作 Redis,通常会使用 RedisTemplate 类。为了方便起见,我们可以创建一个工具类来管理 RedisTemplate 的创建和使用。以下是一个示例:  import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component;  import javax.annotation.Resource; import java.util.Set; import java.util.concurrent.TimeUnit;  @Component public class RedisUtils {      /**      * 如果使用 @Autowired 注解完成自动装配 那么      * RedisTemplate要么不指定泛型,要么泛型 为<Stirng,String> 或者<Object,Object>      * 如果你使用其他类型的 比如RedisTemplate<String,Object>      * 那么请使用 @Resource 注解      * */     @Resource     private RedisTemplate<String,Object> redisTemplate;      private Logger logger = LoggerFactory.getLogger(this.getClass());      /**      * 缓存value      *      * @param key -      * @param value -      * @param time -      * @return -      */     public boolean cacheValue(String key, Object value, long time) {         try {             ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();             valueOperations.set(key, value);             if (time > 0) {                 // 如果有设置超时时间的话                 redisTemplate.expire(key, time, TimeUnit.SECONDS);             }             return true;         } catch (Throwable e) {             logger.error("缓存[" + key + "]失败, value[" + value + "] " + e.getMessage());         }         return false;     }      /**      * 缓存value,没有设置超时时间      *      * @param key -      * @param value -      * @return -      */     public boolean cacheValue(String key, Object value) {         return cacheValue(key, value, -1);     }      /**      * 判断缓存是否存在      *      * @param key      * @return      */     public boolean containsKey(String key) {         try {             return redisTemplate.hasKey(key);         } catch (Throwable e) {             logger.error("判断缓存是否存在时失败key[" + key + "]", "err[" + e.getMessage() + "]");         }         return false;     }      /**      * 根据key,获取缓存      *      * @param key -      * @return -      */     public Object getValue(String key) {         try {             ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();             return valueOperations.get(key);         } catch (Throwable e) {             logger.error("获取缓存时失败key[" + key + "]", "err[" + e.getMessage() + "]");         }         return null;     }      /**      * 移除缓存      *      * @param key -      * @return -      */     public boolean removeValue(String key) {         try {             redisTemplate.delete(key);             return true;         } catch (Throwable e) {             logger.error("移除缓存时失败key[" + key + "]", "err[" + e.getMessage() + "]");         }         return false;     }      /**      * 根据前缀移除所有以传入前缀开头的key-value      *      * @param pattern -      * @return -      */     public boolean removeKeys(String pattern) {         try {             Set<String> keySet = redisTemplate.keys(pattern + "*");             redisTemplate.delete(keySet);             return true;         } catch (Throwable e) {             logger.error("移除key[" + pattern + "]前缀的缓存时失败", "err[" + e.getMessage() + "]");         }         return false;     }  } 在这个示例中,我们使用 @Resource 注解自动注入了一个 RedisTemplate<String, Object> 对象。然后,我们提供了三个方法来对 Redis 进行操作:cacheValue 方法用于缓存数据,getValue 方法用于获取缓存数据,removeValue 方法用于删除缓存数据。这些方法都是通过 redisTemplate 对象来实现的。  需要注意的是,在使用 RedisTemplate 时,需要指定键值对的类型。在这个示例中,我们指定了键的类型为 String,值的类型为 Object。  3.4、使用RedisTemplate 在上面的示例中,我们已经创建了一个 RedisTemplate 对象,并提供了一些方法来对 Redis 进行操作。现在,我们可以在 SpringBoot 项目中的任何地方使用这个工具类来进行缓存操作。以下是一个示例:  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController;  @RestController public class UserController {      @Autowired     private RedisUtils redisUtils;      @Autowired     private UserService userService;      @GetMapping("/users/{id}")     public User getUserById(@PathVariable Long id) {          String key = "user_" + id;         User user = (User) redisUtils.getValue(key);         if (user == null) {             user = userService.getUserById(id);             redisUtils.cacheValue(key, user);         }         return user;     } } 在这个示例中,我们创建了一个 UserController 类,用于处理 HTTP 请求。在 getUserById 方法中,我们首先构造了一个缓存的 key,然后使用 redisUtils.getValue 方法从 Redis 中获取缓存数据。如果缓存中没有数据,我们调用 userService.getUserById 方法从数据库中获取数据,并使用 redisUtils.cacheValue 方法将数据存入Redis缓存中。最后,返回获取到的数据。  通过这个示例,我们可以看到,在S pringBoot 项目中使用 Redis 作为缓存的流程。我们首先需要添加 Redis 依赖,然后在配置文件中配置 Redis 连接信息。接着,我们创建了一个 RedisUtil s工具类来管理 RedisTemplate 的创建和使用。最后,我们在控制器中使用 RedisUtils 来对 Redis 进行缓存操作。 ———————————————— 版权声明:本文为CSDN博主「栗筝i」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/weixin_45187434/article/details/132373648 
  • [技术干货] 拼多多社招面经:Redis是重点,讲一讲redis的内存模型【转】
    1、简单做一下自我介绍把,为什么这么快就想换工作。简单说下你简历中的项目。2、看你在项目中用了redis,我们先聊聊redis吧,常用的数据结构有哪几种,在你的项目中用过哪几种,以及在业务中使用的场景,redis的hash怎么实现的,rehash过程讲一下和JavaHashMap的rehash有什么区别?redis cluster有没有了解过,怎么做到高可用的?3redis集群和哨兵机制有什么区别?redis的持久化机制了解吗?你们在项目中是怎么做持久化的?遇到过redis的hotkey吗?怎么处理的?4redis是单线程的吗?单线程为什么还这么快?讲一讲redis的内存模型?5.我看你还用了RabbitMQ,简单说一下RabbitMQ的工作原理?如何保证消息的顺序执行?Kafka了解吗?和RabbitMQ有什么区别?你为啥不用kafka来做,当时怎么考虑的?6、我看你简历里说熟悉计算机网络,来聊一聊计算机网络吧。了不了解tcp/udp,简单说下两者的tcp和udp的区别?tcp为什么要三次握手和四次挥手?两次握手可以不?会有什么问题?tcp怎么保证有序传输的,讲下tcp的快速重传和拥塞机制,知不知道time_wait状态,这个状态出现在什么地方,有什么用?7、http与https有啥区别?https是怎么做到安全的?8、有没有了解过协程?说下线程和协程的区别?用过哪些linux命令?如查看内存使用、网络情况?9、你了解哪些设计模式啊。挑一个熟悉的讲讲?(除了单例模式)在项目中有用过设计模式吗?讲讲你怎么用的?简单说一下适配器模式和装饰器模式?10、你们数据库有没有用到分库分表,怎么做的?分库分表以后全局id怎么生成的?11、索引的常见实现方式有哪些,有哪些区别?MySQL的存储引擎有哪些,有哪些区别?InnoDB使用的是什么方式实现索引,怎么实现的?说下聚簇索引和非聚簇索引的区别?12、看你简历提到了raft算法,讲下raft算法的基本流程?raft算法里面如果出现脑裂怎么处理?有没有了解过paxos和zookeeper的zab算法,他们之前有啥区别?13、聊聊java基础吧,如果我是想一个人的姓名一样就认为他们equal,能现场写下我们怎么重写equals吗?如果两个对象,一个是cat,一个是dog,我们认为他们的name属性一样就一样,怎么重写equals14,还有点时间,写个题吧leetcode406. 根据身高重建队列假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。注意:总人数少于1100人。示例输入:[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]输出:[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]一:看你在项目中用了redis,我们先聊聊redis吧,常用的数据结构有哪几种,在你的项目中用过哪几种,以及在业务中使用的场景,redis的hash怎么实现的,rehash过程讲一下和JavaHashMap的rehash有什么区别?redis cluster有没有了解过,怎么做到高可用的?(1)常用的数据结构:字符串(String),散列/哈希(hash),列表(list),无序集合类型(set),有序集合类型(sorted set)(2)Redis的hash 实现:Redis散列/哈希是键值对的集合。Redis散列/哈希是字符串字段和字符串值之间的映射,但字段值只能是字符串,不支持其他类型。因此,他们用于表示对象。应用场景:存储用户的基本信息,等等、(3)redis cluster:redis最开始使用主从模式做集群,若master宕机需要手动配置slave转为master;后来为了高可用提出来哨兵模式,该模式下有一个哨兵监视master和slave,若master宕机可自动将slave转为master,但它也有一个问题,就是不能动态扩充;所以在3.x提出cluster集群模式。Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。其结构特点:1、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。2、节点的fail是通过集群中超过半数的节点检测失效时才生效。3、客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。4、redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value。5、Redis集群预分好16384个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。二.redis集群和哨兵机制有什么区别?redis的持久化机制了解吗?你们在项目中是怎么做持久化的?遇到过redis的hotkey吗?怎么处理的?(1)redis集群和哨兵机制有什么区别?谈到Redis服务器的高可用,如何保证备份的机器是原始服务器的完整备份呢?这时候就需要哨兵和复制。哨兵(Sentinel):可以管理多个Redis服务器,它提供了监控,提醒以及自动的故障转移的功能。复制(Replication):则是负责让一个Redis服务器可以配备多个备份的服务器。Redis正是利用这两个功能来保证Redis的高可用。哨兵是Redis集群架构中非常重要的一个组件,哨兵的出现主要是解决了主从复制出现故障时需要人为干预的问题。Redis哨兵主要功能(1)集群监控:负责监控Redis master和slave进程是否正常工作(2)消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员(3)故障转移:如果master node挂掉了,会自动转移到slave node上(4)配置中心:如果故障转移发生了,通知client客户端新的master地址Redis哨兵的高可用原理:当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。1.哨兵机制建立了多个哨兵节点(进程),共同监控数据节点的运行状况。2.同时哨兵节点之间也互相通信,交换对主从节点的监控状况。3.每隔1秒每个哨兵会向整个集群:Master主服务器+Slave从服务器+其他Sentinel(哨兵)进程,发送一次ping命令做一次心跳检测。这个就是哨兵用来判断节点是否正常的重要依据,涉及两个新的概念:主观下线和客观下线。主观下线:一个哨兵节点判定主节点down掉是主观下线。2.客观下线:只有半数哨兵节点都主观判定主节点down掉,此时多个哨兵节点交换主观判定结果,才会判定主节点客观下线。原理:基本上哪个哨兵节点最先判断出这个主节点客观下线,就会在各个哨兵节点中发起投票机制Raft算法(选举算法),最终被投为领导者的哨兵节点完成主从自动化切换的过程。Redis 复制Redis为了解决单点数据库问题,会把数据复制多个副本部署到其他节点上,通过复制,实现Redis的高可用性,实现对数据的冗余备份,保证数据和服务的高度可靠性。1.数据复制原理(执行步骤)①从数据库向主数据库发送sync(数据同步)命令。②主数据库接收同步命令后,会保存快照,创建一个RDB文件。③当主数据库执行完保持快照后,会向从数据库发送RDB文件,而从数据库会接收并载入该文件。④主数据库将缓冲区的所有写命令发给从服务器执行。⑤以上处理完之后,之后主数据库每执行一个写命令,都会将被执行的写命令发送给从数据库。注意:在Redis2.8之后,主从断开重连后会根据断开之前最新的命令偏移量进行增量复制Redis 主从复制、哨兵和集群这三个有什么区别主从复制是为了数据备份,哨兵是为了高可用,Redis主服务器挂了哨兵可以切换,集群则是因为单实例能力有限,搞多个分散压力,简短总结如下:主从模式:备份数据、负载均衡,一个Master可以有多个Slaves。sentinel发现master挂了后,就会从slave中重新选举一个master。cluster是为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器。sentinel着眼于高可用,Cluster提高并发量。1.主从模式:读写分离,备份,一个Master可以有多个Slaves。2.哨兵sentinel:监控,自动转移,哨兵发现主服务器挂了后,就会从slave中重新选举一个主服务器。3.集群:为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。4:redis是单线程的吗?单线程为什么还这么快?讲一讲redis的内存模型?Redis内存划分:Redis作为内存数据库,在内存中存储的内容主要是数据(键值对);通过前面的叙述可以知道,除了数据以外,Redis的其他部分也会占用内存。Redis的内存占用主要可以划分为以下几个部分:1、数据作为数据库,数据是最主要的部分;这部分占用的内存会统计在used_memory中。Redis使用键值对存储数据,其中的值(对象)包括5种类型,即字符串、哈希、列表、集合、有序集合。这5种类型是Redis对外提供的,实际上,在Redis内部,每种类型可能有2种或更多的内部编码实现;此外,Redis在存储对象时,并不是直接将数据扔进内存,而是会对对象进行各种包装:如redisObject、SDS等;这篇文章后面将重点介绍Redis中数据存储的细节。2、进程本身运行需要的内存Redis主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。这部分内存不是由jemalloc分配,因此不会统计在used_memory中。补充说明:除了主进程外,Redis创建的子进程运行也会占用内存,如Redis执行AOF、RDB重写时创建的子进程。当然,这部分内存不属于Redis进程,也不会统计在used_memory和used_memory_rss中。3、缓冲内存缓冲内存包括客户端缓冲区、复制积压缓冲区、AOF缓冲区等;其中,客户端缓冲存储客户端连接的输入输出缓冲;复制积压缓冲用于部分复制功能;AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory中。4、内存碎片内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统计在used_memory中。内存碎片的产生与对数据进行的操作、数据的特点等都有关;此外,与使用的内存分配器也有关系:如果内存分配器设计合理,可以尽可能的减少内存碎片的产生。后面将要说到的jemalloc便在控制内存碎片方面做的很好。如果Redis服务器中的内存碎片已经很大,可以通过安全重启的方式减小内存碎片:因为重启之后,Redis重新从备份文件中读取数据,在内存中进行重排,为每个数据重新选择合适的内存单元,减小内存碎片。5. 我看你还用了RabbitMQ,简单说一下RabbitMQ的工作原理?如何保证消息的顺序执行?Kafka了解吗?和RabbitMQ有什么区别?你为啥不用kafka来做,当时怎么考虑的?组成部分说明如下:Broker:消息队列服务进程,此进程包括两个部分:Exchange和Queue。Exchange:消息队列交换机,按一定的规则将消息路由转发到某个队列,对消息进行过虑。Queue:消息队列,存储消息的队列,消息到达队列并转发给指定的消费方。Producer:消息生产者,即生产方客户端,生产方客户端将消息发送到MQ。Consumer:消息消费者,即消费方客户端,接收MQ转发的消息。消息发布接收流程:-----发送消息-----1、生产者和Broker建立TCP连接。2、生产者和Broker建立通道。3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。4、Exchange将消息转发到指定的Queue(队列)----接收消息-----1、消费者和Broker建立TCP连接2、消费者和Broker建立通道3、消费者监听指定的Queue(队列)4、当有消息到达Queue时Broker默认将消息推送给消费者。5、消费者接收到消息。6、我看你简历里说熟悉计算机网络,来聊一聊计算机网络吧。了不了解tcp/udp,简单说下两者的区别?tcp为什么要三次握手和四次挥手?两次握手可以不?会有什么问题?tcp怎么保证有序传输的,讲下tcp的快速重传和拥塞机制,知不知道time_wait状态,这个状态出现在什么地方,有什么用?(1)区别:1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信5、TCP首部开销20字节;UDP的首部开销小,只有8个字节6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道两次握手为什么不可以?采用三次握手是为了防止失效的连接请求报文段突然又传送到主机B,因而产生错误。失效的连接请求报文段是指:主机A发出的连接请求没有收到主机B的确认,于是经过一段时间后,主机A又重新向主机B发送连接请求,且建立成功,顺序完成数据传输。考虑这样一种特殊情况,主机A第一次发送的连接请求并没有丢失,而是因为网络节点导致延迟达到主机B,主机B以为是主机A又发起的新连接,于是主机B同意连接,并向主机A发回确认,但是此时主机A根本不会理会,主机B就一直在等待主机A发送数据,导致主机B的资源浪费。为什么要四次挥手?TCP关闭链接四次握手原因在于tpc链接是全双工通道,需要双向关闭。client向server发送关闭请求,表示client不再发送数据,server响应。此时server端仍然可以向client发送数据,待server端发送数据结束后,就向client发送关闭请求,然后client确认。tcp怎么保证有序传输的:1)应用数据被分割成TCP认为最适合发送的数据块。2)超时重传:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。3)TCP给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。4)校验和:TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。5)TCP的接收端会丢弃重复的数据。6)流量控制:TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的我数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。7)拥塞控制:当网络拥塞时,减少数据的发送。time_wait状态,这个状态出现在什么地方,有什么用?1.为实现TCP全双工连接的可靠释放当服务器先关闭连接,如果不在一定时间内维护一个这样的TIME_WAIT状态,那么当被动关闭的一方的FIN到达时,服务器的TCP传输层会用RST包响应对方,这样被对方认为是有错误发生,事实上这只是正常的关闭连接工程,并没有异常2.为使过期的数据包在网络因过期而消失在这条连接上,客户端发送了数据给服务器,但是在服务器没有收到数据的时候服务器就断开了连接现在数据到了,服务器无法识别这是新连接还是上一条连接要传输的数据,一个处理不当就会导致诡异的情况发生下面讲讲大量的TIME_WAIT产生需要的条件:1.高并发2.服务器主动关闭连接如果服务器不主动关闭连接,那么TIME_WAIT就是客户端的事情了7、http与https有啥区别?https是怎么做到安全的?https安全的原因:https把http消息进行加密之后再传送,这样就算坏人拦截到了,得到消息之后也看不懂,这样就做到了安全,具体来说,https是通过对称加密和非对称加密和hash算法共同作用,来在性能和安全性上达到一个平衡,加密是会影响性能的,尤其是非对称加密,因为它的算法比较复杂,那么加密了就安全了吗?不是的,https除了对消息进行了加密以外还会对通信的对象进行身份验证。https并不是一种新的协议,而是使用了一种叫做TLS(Transport layer secure)的安全层,这个安全层提供了数据加密的支持,让http消息运行在这个安全层上,就达到了安全,而运行在这个安全层上的http就叫做https(http secure)8,还有点时间,写个题吧leetcode406. 根据身高重建队列假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。注意:总人数少于1100人。示例输入:[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]输出:[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]思路:第一步排序:people:[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]排序后:[[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]然后从数组people第一个元素开始,放入到数组result中,放入的位置就是离result开始位置偏移了元素第二个数字后的位置。如下:people: [7,0]插入到离开始位置偏移了0个距离的位置。result: [[7,0]]people: [7,1]插入到离开始位置偏移了1个距离的位置,即插入到[7,0]的后面。result: [[7,0], [7,1]]people: [6,1]插入到离开始位置偏移了1个距离的位置,即插入到[7,0]的后面。result: [[7,0], [6,1], [7,1]]people: [5,0]插入到离开始位置偏移了0个距离的位置,即插入到[7,0]的前面。result: [[5,0], [7,0], [6,1], [7,1]]people: [5,2]插入到离开始位置偏移了2个距离的位置,即插入到[7,0]的后面。result: [[5,0], [7,0], [5,2], [6,1], [7,1]]people: [4,4]插入到离开始位置偏移了4个距离的位置,即插入到[6,1]的后面。result: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]这种算法体现了元素第二个数字与其插入位置的关系,所以通过简单的一个for循环就可以搞定。
  • Redis常见面试题
    redis的特点:  redis本质上是一个key-value类型的内存数据库,整个数据库系统加载在内存当中操作,定期通过异步操作把数据库数据flash硬盘上进行保存。因为是纯内存操作,redis的性能非常出色,每秒可以处理超过10万次读写操作,是已知的最快的key-value数据库。redis出色的不仅仅是性能,还支持保存多种数据结构,并且单个value可以存储的最大限制是1GB,另外redis还可以对key-value设置过期时间  缺点是:数据库容易收到物理内存的限制,不能做海量数据的高性能读写,只适用于较小数据量的高性能操作何运算的场景。1.redis为什么这么快  基于内存  单线程减少上下文切换,同时保证原子性  使用多路I/O复用模型,非阻塞IO;   多种数据结构(hash、跳表等)2.为什么使用单线程  因为 Redis 是基于内存的操作,CPU 不会成为 Redis 的瓶颈,而最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。3.redis缓存三大问题  1.缓存穿透是指客户端请求的数据再缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。不断发起这样的请求,给数据库带来压力    缓存空对象、布隆过滤、 增强id的复杂度,避免被猜测id规律      做好数据的基础格式校验、加强用户权限校验、做好热点参数的限流  2.缓存雪崩是指在同一时间段大量的key同时失效或者redis服务器宕机,导致大量请求到达数据库,带来巨大压力。    给不同的key的TTL添加随机值    利用redis集群提高服务的可用性    给缓存业务添加降级限流策略    给业务添加多级缓存  3.缓存击穿问题也叫热点key问题,就是一个被高并发访问并且缓存重建业务比较复杂的key突然失效,无数的请求访问会在瞬间给数据库带来巨大的冲击    逻辑过期    互斥锁4.先删后写还是先写后删  先删除缓存再更新数据库 脏读问题:两个线程,一个更新操作,一个查询操作,当更新操作删除数据缓存后,这个时候查询操作没有找到缓存,就从数据库中找到旧数据,并将旧数据写入到缓存中,此时更新操作再把数据库中数据更新,导致数据不一致。脏读。  先更新数据库再删除缓存  脏读几率小:更新和查询,缓存失效,查询操作查询缓存没有,就找到数据库中的旧数据,此时,更新操作来了,删除缓存,并更新数据库,最后查询操作将查询的值缓存到缓存中,出现数据不一致问题。但是几率很小,往往数据库的读操作的速度远快于写操作。解决:1)缓存设置过期时间,实现最终一致性;2)使用 Cannel 等中间件监听 binlog 进行异步更新;3)通过 2PC 或 Paxos 协议保证一致性。5.redis如何保证原子性:需求:两个客户端同时对[key1]执行自增操作,不会相互影响操作:下面两个客户端并发操作会导致[key1]输出结果与预期不一致[客户端一]读取[key1],值为[1][客户端二]读取[key1],值为[1][客户端一]将[key1]自增1,值为[2][客户端二]将[key1]自增1,值为[2][客户端一]输出[key1],值为[2][客户端二]输出[key2],值为[2]解决思路[客户端一]、[客户端二]的R(读)、M(自增)、W(写)三个操作作为一个原子操作执行[客户端]对RMW整个操作过程加锁,加锁期间其它客户端不能对[key1]执行写操作Lua脚本思路一:单命令操作1. 概念    Redis 提供了 INCR/DECR/SETNX 命令,把RMW三个操作转变为一个原子操作    Redis 是使用单线程串行处理客户端的请求来操作命令,所以当 Redis 执行某个命令操作时,其他命令是无法执行的,这相当于命令操作是互斥执行的思路二:加锁1. 概念     加锁主要是将多客户端线程调用相同业务方法转换为串行化处理,比如多个客户端调用同一个方法对某个键自增(这里不考虑其它方法或业务会对该键同时执行自增操作)        调用SETNX命令对某个键进行加锁(如果获取锁则执行后续RMW操作,否则直接返回未获取锁提示)     执行RMW业务操作     调用DEL命令删除锁2. 加锁风险一     假如某个客户端在执行了SETNX命令加锁之后,在后面操作业务逻辑时发生了异常,没有执行 DEL 命令释放锁。     该锁就会一直被这个客户端持有,其它客户端无法拿到锁,导致其它客户端无法执行后续操作。     解决思路:给锁变量设置一个过期时间,到期自动释放锁SET key value [EX seconds | PX milliseconds] [NX]3. 加锁风险二     如果客户端 A 执行了 SETNX 命令加锁后,客户端 B 执行 DEL 命令释放锁,此时,客户端 A 的锁就被误释放了。如果客户端 C 正好也在申请加锁,则可以成功获得锁。     解决思路:加锁操作时给每个客户端设置一个唯一值(比如UUID),唯一值可以用来标识当前操作的客户端。                       在释放锁操作时,客户端判断当前锁变量的值是否和唯一标识相等,只有在相等的情况下,才能释放锁。(同一客户端线程中加锁、释放锁) SET lock_key unique_value NX PX 10000思路三:Lua脚本1. 概念多个操作写到一个 Lua 脚本中(Redis 会把整个 Lua 脚本作为一个整体执行,在执行的过程中不会被其他命令打断,从而保证了 Lua 脚本中操作的原子性)2. 需求限制所有客户端在一定时间范围内对某个方法(键)的访问次数。客户端 IP 作为 key,某个方法(键)的访问次数作为 value3. 脚本local current current = redis.call("incr",KEYS[1]) if tonumber(current) == 1 then redis.call("expire",KEYS[1],60) end4. 调用执行redis-cli --eval lua.script keys , args
  • Redis常见的数据类型以及应用场景
    Redis支持多种数据类型,每种数据类型都有其独特的特点和应用场景。以下是Redis常见的数据类型以及它们的应用场景:字符串(String):存储单个值或对象的序列化数据。应用场景:缓存、计数器、分布式锁等。哈希表(Hash):存储多个字段和值的散列数据结构,可以看作是一个关联数组。应用场景:存储用户信息、对象属性、配置项等。列表(List):有序的字符串集合,可以在两端进行插入和删除操作。应用场景:消息队列、最新动态、时间线等。集合(Set):无序的唯一字符串集合,支持交集、并集和差集等集合操作。应用场景:标签、点赞用户、共同好友等。有序集合(Sorted Set):类似于集合,但每个元素都有一个关联的分数,根据分数排序。应用场景:排行榜、Top N、优先级队列等。地理空间索引(GeoSpatial):存储地理位置信息的数据类型,支持距离计算和位置查询。应用场景:附近的人、地理位置搜索等。Redis的灵活数据类型和高性能特性使其在各种场景下都有广泛的应用。需要根据具体的业务需求来选择合适的数据类型,以提高系统的性能和可扩展性。
  • [技术干货] Redis中的IO多路复用
    在Redis中,也使用了I/O多路复用来实现高效的网络通信。Redis是一个高性能的键值存储数据库,其主要通过使用非阻塞I/O和I/O多路复用来处理并发连接,提高网络通信的性能。Redis使用了多个I/O多路复用模型,根据不同的操作系统平台和版本,可能采用select、epoll、kqueue等不同的机制。在Linux系统上,一般使用epoll来实现I/O多路复用。具体来说,Redis的I/O多路复用工作流程如下:客户端连接:当Redis服务器启动时,它会监听指定的端口,等待客户端连接。多路复用:一旦有客户端连接到服务器,Redis会使用I/O多路复用机制(如epoll)来同时监听多个客户端连接的读写事件。事件处理:当有I/O事件就绪时,Redis会根据事件类型执行相应的操作,如接收客户端发送的命令、读取数据、执行命令、将结果返回给客户端等。非阻塞处理:在处理客户端请求的过程中,Redis会尽可能地避免使用阻塞式I/O,而是使用非阻塞I/O来保证高效的并发处理。通过使用I/O多路复用,Redis能够高效地处理大量并发连接,而不会因为阻塞式I/O而导致性能下降。这使得Redis成为了一个优秀的高性能、高并发的数据库引擎,尤其适用于缓存和实时数据处理等场景。
  • [技术干货] Redis中的过期策略
    redis过期策略定时过期、惰性过期、定期过期问题:使用expire key 60,在key60s之后key就会过期,之后如何清除key定时过期每个设置过期时间的key都创建一个定时器,到时间就会对key进行清除。该策略可以立即清除过期key,对内存友好,但是需要消耗大量的cpu时间去清理过期数据,从而影响响应时间和吞吐量。惰性过期只有当访问一个key时,才会判断这个key是否过期,过期就清除。该策略可以节省cpu资源,但是对内存不够友好。极端情况下会出现大量的过期key没有被清理,占用内存。定期过期每隔一段时间,扫描一定数量数据库中的expires字典中的key,清除过期的key。该策略是前两种的一个折中策略,cpu和内存达到最优平衡。expires字典:当我们在 Redis 中设置一个键的过期时间时,该键会被添加到 expires 字典中,并在指定的过期时间后自动从数据库中删除。expires 字典以键为索引,值为键的过期时间戳。当 Redis 中的某个键过期时,Redis 服务器会在后台自动将其从数据库中删除。Redis 服务器使用定时器来监视 expires 字典中键的过期时间,定期检查键是否已过期。当键过期时,定时器会将其标记为即将删除,并在适当的时候释放该键所占用的内存空间。redis采用的了惰性过期和定期过期两种过期策略,定期过期。极端情况:定期过期漏掉了很多过期key,又没有走惰性过期。就会有很多过期key在内存中。就需要淘汰策略。内存淘汰机制Redis的内存淘汰策略是为了解决内存不足的问题,当Redis的内存使用达到上限时,需要根据事先设置的内存淘汰策略来选择一些键,使其被删除以释放内存空间。Redis提供了多种内存淘汰策略,可以根据实际需求进行配置。以下是Redis常见的内存淘汰策略:LRU(Least Recently Used,最近最少使用):淘汰最近最少被使用的键。当内存不足时,会优先删除最近最少使用的键来释放空间。LFU(Least Frequently Used,最不经常使用):淘汰最不经常被使用的键。当内存不足时,会优先删除访问频率最低的键来释放空间。TTL(Time To Live,生存时间):淘汰设置了过期时间的键。当键的过期时间到期时,会被自动删除。Random(随机):随机选择一些键进行删除。这种策略简单且随机性较高,但不保证删除的是最不重要的键。Allkeys-LRU:除了设置了过期时间的键外,其他键按照LRU策略进行淘汰。Allkeys-Random:除了设置了过期时间的键外,其他键按照随机策略进行淘汰。noeviction:在Redis中,默认的内存淘汰策略是noeviction,即当内存不足时,不会自动淘汰键,而是拒绝新写入的操作,直到有足够的内存空间。但为了保证数据存储的稳定性和可用性,一般建议配置合适的内存淘汰策略,避免因为内存不足而导致系统出现问题。可以通过配置Redis的maxmemory-policy参数来设置所需的内存淘汰策略,根据实际情况选择适合的策略来满足应用的需求。
  • [技术干货] MySQL数据同步到 Redis 缓存的几种方法
    将 MySQL 数据同步到 Redis 缓存时,可以采用以下几种常见的方法,并为每种方法提供一个示例:1.定时任务同步示例:使用 Node.js 编写一个定时任务程序,每隔一段时间将 MySQL 中的数据同步到 Redis 缓存中。const mysql = require('mysql');const redis = require('redis');// 创建 MySQL 连接const mysqlConnection = mysql.createConnection({ host: 'localhost', user: 'root', password: 'password', database: 'my_database',});// 创建 Redis 连接const redisClient = redis.createClient();// 从 MySQL 中获取数据并同步到 Redisfunction syncData() { mysqlConnection.query('SELECT * FROM my_table', (error, results) => { if (error) throw error; // 将数据同步到 Redis redisClient.set('my_data', JSON.stringify(results)); });}// 每隔一段时间执行同步任务setInterval(syncData, 60000); // 每分钟同步一次2.数据库触发器示例:在 MySQL 数据库中创建触发器,在数据变更时自动将变更的数据同步到 Redis 缓存。CREATE TRIGGER sync_to_redis AFTER INSERT ON my_tableFOR EACH ROWBEGIN SET @data = JSON_OBJECT( 'id', NEW.id, 'name', NEW.name, 'age', NEW.age -- 其他字段 ); SET @key = CONCAT('my_data:', NEW.id); -- 将数据同步到 Redis CALL redis_command('SET', @key, @data);END;3.双写模式示例:在应用层代码中对 MySQL 和 Redis 进行双写操作,保持数据的实时性和一致性。import pymysqlimport redis# 创建 MySQL 连接mysql_connection = pymysql.connect( host='localhost', user='root', password='password', database='my_database')# 创建 Redis 连接redis_client = redis.Redis()# 数据插入操作示例def insert_data(data): # 在 MySQL 中执行插入操作 with mysql_connection.cursor() as cursor: sql = 'INSERT INTO my_table (id, name, age) VALUES (%s, %s, %s)' cursor.execute(sql, (data['id'], data['name'], data['age'])) mysql_connection.commit() # 同步数据到 Redis redis_client.set(f"my_data:{data['id']}", json.dumps(data))# 调用示例data = {'id': 1, 'name': 'Alice', 'age': 25}insert_data(data)4.客户端代理示例:使用 Redis Proxy 工具将 MySQL 和 Redis 进行代理,并实现数据的同步和转发# 配置文件示例listen: - port: 6379 backend: mysql backend_config: host: localhost port: 3306 user: root password: password database: my_database - port: 6380 backend: redis backend_config: host: localhost port: 6379以上示例提供了不同的方法来将 MySQL 数据同步到 Redis 缓存中。根据实际需求和技术栈选择合适的方法,并根据具体情况进行配置和调整。
  • [技术干货] 本地缓存、Redis数据缓存策略
     需求看似简单,一取一传 当时是通过websocket获取服务端数据; 然后根据数据类别,将数据缓存到本地map中; 做了一个定时任务,通过ftp上传给第三方服务器; 当有并发时,map是不行的,数据会错乱,使用ConcurrentHashMap可以解决并发数据错乱问题。  现场网络很不稳定,FTP时好时坏; 做的是一个安全问题的实时监控系统,第三方数据要求还很严格,必须100%准确。 这矛盾怎么解决,无解了。  起初,是通过重启的方式解决的,哈哈,重启解决一切烦恼。  添加一个心跳功能,实时监控FTP服务的状态; 如果断了7秒以上,就采取报警功能,我记得设置的是火警的音乐,提示现场人员排查FTP网络; 如果断了1分钟以上,就将软件自动重启。 但是,又出现了一个新的问题,数据丢了。 因为用的是ConcurrentHashMap缓存数据,也就是本地缓存,你重启了,数据不就没了吗?兄弟。  到后来,才发现,当时做的真的是稀烂,本地缓存应该具有很多功能,当时这些,压根就没有。  超过最大限制有对应淘汰策略如LRU、LFU 过期时间淘汰如定时、懒式、定期 持久化 统计监控  下面从缓存、本地缓存、Redis缓存、Redis缓存策略几个维度,全方位、系统的学习一下缓存到底是个啥?  一、缓存 缓存就是把访问量较高的热点数据从传统的关系型数据库中加载到内存中,当用户再次访问热点数据时,是从内存中加载,减少了对数据库的访问量,解决了高并发场景下容易造成数据库宕机的问题。  缓存有哪些分类: 操作系统磁盘缓存,减少磁盘机械操作 数据库缓存,减少文件系统 I/O 应用程序缓存,减少对数据库的查询 Web 服务器缓存,减少应用程序服务器请求 客户端浏览器缓存,减少对网站的访问 本地缓存:在客户端本地的物理内存中划出一部分空间,来缓存客户端回写到服务器的数据。当本地回写缓存达到缓存阈值时,将数据写入到服务器中。  二、分析一下本地缓存的优势 数据缓存带来了诸多优势,其中两个核心优点是:  降低数据库压力:通过将常用的数据存储在快速访问的内存中,缓存有效地减轻了对后端数据库的压力。这意味着数据库可以更专注地处理复杂的查询和更新操作,而不必频繁地处理重复的读取请求。 提高响应速度:将数据存储在缓存中,使得系统能够更迅速地响应用户的请求。相比每次都从数据库中获取数据,缓存可以在毫秒级别内提供所需信息,从而极大地改善用户体验。  三、本地缓存解决方案? 上面介绍了ConcurrentHashMap,这里不再赘述。  1、基于Guava Cache实现本地缓存 Guava是Google团队开源的一款 Java 核心增强库,包含集合、并发、缓存、IO、反射等工具箱性能和稳定性上都有保障应用十分广泛。  Guava Cache支持很多特性:  支持最大容量限制 支持两种过期删除策略插入时间和访问时间 支持简单的统计功能 基于LRU算法实现 2、基于Caffeine实现本地缓存 Caffeine是基于java8实现的新一代缓存工具,缓存性能接近理论最优,可以看作是Guava Cache的增强版,功能上两者类似。  不同的是Caffeine采用了一种结合LRU、LFU优点的算法W-TinyLFU在性能上有明显的优越性。  3、基于Encache实现本地缓存 Encache是一个纯Java的进程内缓存框架具有快速、精干等特点。  同Caffeine和Guava Cache相比,Encache的功能更加丰富扩展性更强。  优点:  支持多种缓存淘汰算法包括LRU、LFU和FIFO 缓存支持堆内存储、堆外存储、磁盘存储支持持久化三种 支持多种集群方案解决数据共享问题 四、引入Redis 后来,因为一次事故,甲方被监管平台罚了100万,本质原因就是丢数据问题。  这可如何是好,我也是吓了一身冷汗,连夜想整改方案,最终的解决方案是,“引入Redis”。  Redis作为一款高性能、内存存储的缓存数据库,被广泛应用于缓存数据的场景。  用户第一次访问数据时,缓存中没有数据,要从数据库中获取数据,因为是从磁盘中拿数据读取数据的过程比较慢。 拿到数据后,将数据存储在缓存中; 用户第二次访问数据时,可以从缓存中直接获取,因为缓存是直接操作内存的,访问数据速度比较快。  下面将深入探讨Redis的数据缓存策略,重点解析LRU(最近最少使用)、LFU(最不经常使用)等算法,并分享如何通过性能优化来提升缓存系统的效率。  五、Redis数据缓存策略 1、为什么需要数据缓存策略 在现代应用中,数据缓存发挥着至关重要的作用。  通过将频繁访问的数据存储在内存中,我们能够避免不必要的数据库查询,从而显著提升系统的响应速度和吞吐量。  然而,随着应用规模和用户访问量的不断增加,有效的数据缓存策略变得尤为重要。  我们需要在性能和资源利用之间找到最佳平衡,以应对不同需求和挑战。  这进一步引出了一个关键问题:如何选择适合的数据缓存策略来满足不同的应用场景?  下图详细地说明了数据缓存的优势和选择适合的数据缓存策略的过程:   通过上图,我们深入探讨了数据缓存的优势,并展示了在选择合适的缓存策略时,我们如何在提升性能和资源利用之间找到最佳平衡。  选择适合的策略能够有效地降低数据库压力,并通过提高响应速度来提供更出色的用户体验。  2、Redis作为缓存的优势 Redis(Remote Dictionary Server)是一款强大的高性能开源内存数据库,不仅被广泛应用于缓存场景,还可用作队列、发布订阅系统等。作为缓存数据库,Redis拥有一系列突出的优势:  (1)高性能特点 Redis的数据存储在内存中,因此具备出色的读写性能。其高效的数据结构和优化的算法使得绝大多数情况下,读写操作能够在微秒级别内完成,满足了高并发应用的需求。  (2)多样性的缓存策略 Redis提供了多种数据缓存策略,使开发者可以根据业务特点选择合适的策略。这种灵活性允许我们根据数据的访问模式、使用频率以及其他因素来决定数据何时被清理或保留。  下图说明缓存策略的选择过程:   通过分析数据访问模式,根据数据的访问频率选择合适的缓存策略。根据实际情况不断地监控数据的访问情况,并优化缓存策略,在不同的场景中灵活应用这些策略。   六、LRU算法:最近最少使用 LRU(Least Recently Used)算法是一种经典的缓存替换策略,它的核心思想是优先淘汰最近最少使用的数据,以便为新数据腾出空间。在数据缓存场景中,LRU算法能够保留热门数据,从而提高缓存的命中率。  1、LRU算法原理解析 LRU算法的原理非常直观:当缓存空间满了,系统会优先淘汰最久未被访问的数据。这个策略的背后思想是,如果某个数据在最近一段时间内没有被访问,那么它在未来也可能不会被访问。这种替换策略有助于保持缓存中的数据是热数据,即最近被频繁访问的数据。   上图说明了LRU算法如何根据访问顺序来保留缓存中的数据。最近访问的数据会被保留在缓存中,而最早访问的数据会被优先替换。  示例代码如下,展示了如何通过继承LinkedHashMap来实现LRU缓存:  import java.util.LinkedHashMap; import java.util.Map;  class LRUCache<K, V> extends LinkedHashMap<K, V> {     private final int MAX_CAPACITY;      public LRUCache(int capacity) {         super(capacity, 0.75f, true);         MAX_CAPACITY = capacity;     }      @Override     protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {         return size() > MAX_CAPACITY;     } }  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 在这个示例中,我们创建了一个LRUCache类,继承自LinkedHashMap。通过重写removeEldestEntry方法,我们指定了当缓存大小超过一定阈值时,自动删除最久未被访问的数据。  2、redis中应用LRU算法 在Redis中,我们可以通过配置maxmemory-policy选项来启用LRU算法的缓存策略。当Redis的内存使用达到限制时,LRU算法将被用于淘汰部分数据,以便腾出空间给新数据。  以下是如何在Redis中启用LRU缓存策略的示例:  # 启用LRU缓存策略 CONFIG SET maxmemory-policy allkeys-lru 1 2 3、LRU算法的优点与限制  LRU(Least Recently Used)算法是一种常用的数据缓存策略,它在管理缓存数据时有一些明显的优点和一些限制。  优点 优点    描述 适用于热数据    LRU算法保留了最近最常访问的数据,因此非常适用于具有明显访问热点的场景。 简单有效    LRU算法的实现相对简单,不需要复杂的计算和维护。 限制 限制    描述 周期性访问    LRU算法可能会因为数据的周期性访问而导致不必要的数据替换,特别是在某些特殊业务场景中。 缓存污染    LRU算法容易受到突发的大量访问影响,可能导致缓存中的“热·数据被淘汰,从而影响缓存效果。 七、LFU算法:最不经常使用 LFU(Least Frequently Used)算法是一种与LRU相似的缓存替换策略,它的核心思想是优先淘汰最不经常使用的数据,以便为新数据腾出空间。在某些特定场景下,LFU算法能够更好地适应数据访问模式的变化。  1、LFU算法原理解析 LFU算法的原理与LRU算法类似,但不同之处在于LFU算法基于数据被访问的频率来做出替换决策,而不仅仅是访问的时间顺序。LFU算法维护了一个数据访问频率的记录,当需要淘汰数据时,会优先选择访问频率最低的数据。   上图说明了LFU算法如何根据数据的访问频率来保留缓存中的数据。频繁访问的数据会被保留,而不经常访问的数据会被优先替换。  2、在Redis中应用LFU算法 在Redis中,您可以通过配置maxmemory-policy选项来启用LFU算法的缓存策略。当Redis的内存使用达到限制时,LFU算法将用于淘汰部分数据,以便为新数据腾出空间。  以下是如何在Redis中启用LFU缓存策略的示例:  # 启用LFU缓存策略 CONFIG SET maxmemory-policy allkeys-lfu 1 2 3、LFU算法的优点与限制  LFU(Least Frequently Used)算法是一种另类的数据缓存策略,它在不同的场景下具有一些明显的优点和一些限制。  优点 优点    描述 适用于频繁刷新    LFU算法能够优先保留频繁被刷新的数据,适合某些周期性访问的场景。 对数据热度变化敏感    相比于LRU算法,LFU算法更能适应数据访问模式的变化,能够更好地反映数据的热度。 限制 限制    描述 计算复杂性    LFU算法需要维护数据的访问频率记录,这可能导致一定的计算复杂性,特别是在大规模数据场景下。 冷启动问题    对于刚开始访问的数据,由于没有足够的访问频率信息,LFU算法可能难以做出合适的替换决策。  八、其他数据缓存策略 1、Least Recently Used with Sampling(LRUS) 除了传统的LRU算法,还存在一种改进的版本,即LRUS(Least Recently Used with Sampling)算法。LRUS算法通过周期性的采样来记录数据的访问情况,从而更好地估计最近使用的数据,减少了LRU算法中的“冷启动·问题。  LRUS算法原理 LRUS算法引入了采样机制,通过周期性地记录一部分数据的访问情况,从而更准确地判断哪些数据是热数据,哪些是冷数据。与传统的LRU算法不同,LRUS算法能够更好地适应数据访问模式的变化,提高数据缓存的命中率。   上图LRUS算法通过周期性采样记录数据的访问情况,从而更精确地判断哪些数据应该被保留,哪些应该被替换。  2、Random Replacement(随机替换) 随机替换是一种简单但有效的缓存策略。与LRU和LFU不同,随机替换策略不考虑数据的访问时间或频率,而是随机选择要替换的数据。尽管这听起来不太智能,但在某些场景下,随机替换策略表现出意外的优势。  随机替换的原理 随机替换的核心思想是,每次需要替换数据时,从缓存中随机选择一条数据进行替换。虽然这种策略没有考虑数据的热度或频率,但在一些特殊情况下,随机替换能够避免特定数据被频繁淘汰,从而维持一定的数据多样性。   上图中,随机替换算法随机选择要替换的数据,从而在一些情况下维持了数据多样性。  九、性能优化与实际应用 1、数据缓存策略的性能考量 在选择和配置数据缓存策略时,性能是一个关键因素。不同的缓存策略适用于不同的业务场景,因此在做出决策时需要综合考虑多个因素。  (1)缓存大小与命中率的平衡 在配置缓存大小时,需要权衡缓存的总大小和实际存储的数据量。一个过小的缓存可能导致命中率降低,无法有效减轻数据库负载,而一个过大的缓存可能浪费内存资源。通常可以通过监控命中率和缓存利用率来优化缓存大小。  (2)数据访问模式的分析 分析业务的数据访问模式对于选择合适的缓存策略至关重要。例如,如果某些数据被频繁地访问,而另一些数据则很少被访问,那么选择适当的策略可以提高缓存的效果。对于频繁访问的热数据,可以选择LRU或者LFU策略,而对于较少访问的冷数据,可以考虑随机替换策略。  2、实际应用案例:电子商务网站 让我们通过一个实际的应用案例,来展示如何根据业务需求选择合适的缓存策略。考虑一个电子商务网站,用户经常访问商品列表、商品详情以及购物车等页面。针对这个场景,可以选择不同的缓存策略来优化性能。  (1)电子商务网站的缓存策略选择 商品列表页:由于商品列表页中的商品信息经常变动,可以选择LRU或者随机替换策略。这样可以保留最近的商品数据,提高页面加载速度。  // 使用LRU算法实现商品列表页缓存 LRUCache<String, List<Product>> productListCache = new LRUCache<>(1000); // 缓存容量1000  List<Product> cachedProductList = productListCache.get("productList"); if (cachedProductList == null) {     // 从数据库获取商品列表数据     List<Product> productList = database.getProductList();     productListCache.put("productList", productList);     cachedProductList = productList; } 1 2 3 4 5 6 7 8 9 10 商品详情页:商品详情页的数据相对稳定,适合选择LFU策略。这样可以保留频繁访问的商品详情数据,提高页面响应速度。  // 使用LFU算法实现商品详情页缓存 LFUCache<String, ProductDetails> productDetailsCache = new LFUCache<>(500); // 缓存容量500  ProductDetails cachedProductDetails = productDetailsCache.get("product123"); if (cachedProductDetails == null) {     // 从数据库获取商品详情数据     ProductDetails productDetails = database.getProductDetails("product123");     productDetailsCache.put("product123", productDetails);     cachedProductDetails = productDetails; } 1 2 3 4 5 6 7 8 9 10 购物车页:购物车页的数据与用户关联紧密,可以选择LRU或者LRUS策略。这样可以保留最近被访问的购物车数据,提供更好的用户体验。  // 使用LRUS算法实现购物车页缓存 LRUSCache<String, ShoppingCart> shoppingCartCache = new LRUSCache<>(200); // 缓存容量200  ShoppingCart cachedShoppingCart = shoppingCartCache.get("user123"); if (cachedShoppingCart == null) {     // 从数据库获取购物车数据     ShoppingCart shoppingCart = database.getShoppingCart("user123");     shoppingCartCache.put("user123", shoppingCart);     cachedShoppingCart = shoppingCart; } 1 2 3 4 5 6 7 8 9 10 (2)性能优化与实际应用改进 在实际应用中,通过合理配置缓存策略以及优化缓存大小,电子商务网站可以显著提升页面加载速度和用户体验。同时,通过监控数据访问模式的变化,还可以动态调整缓存策略,进一步优化性能。  十、总结与实践指导 1、Redis数据缓存策略的重要性 数据缓存不仅可以提升系统性能,还能降低后端数据库的压力,从而实现更快的响应时间和更好的用户体验。在现代高并发应用中,优化数据缓存策略已经成为系统设计中不可或缺的一环。  2、如何选择合适的缓存策略 在实际应用中,选择合适的缓存策略是至关重要的。根据不同的业务场景和数据访问模式,我们可以灵活地选择LRU、LFU、LRUS、随机替换等缓存策略。同时,还可以根据实际需要动态地调整缓存大小,以达到最佳的性能与资源利用率的平衡。  实践指导:  分析数据访问模式:在选择缓存策略之前,首先需要详细分析数据的访问模式。哪些数据被频繁访问?哪些数据变化较少?根据这些信息,选择适合的缓存策略。 选择合适的算法:根据业务需求,选择合适的缓存算法。LRU适用于保留最近访问的数据,LFU适用于保留最频繁访问的数据,而LRUS则更好地应对访问模式的变化。 监控与优化:缓存策略不是一成不变的,需要不断监控数据访问情况,优化缓存大小和策略。通过监控缓存的命中率和利用率,可以动态地做出调整。 灵活应用:不同的业务模块可能需要不同的缓存策略。根据实际情况,可以在系统中采用多种缓存策略,以最大程度地提升性能。 ———————————————— 原文链接:https://blog.csdn.net/guorui_java/article/details/132666743 
  • [技术干货] spring boot中使用redis计数器
    Redis是一个高性能的内存数据库,它支持多种数据结构,包括字符串、哈希表、列表、集合和有序集合等。其中,Redis的计数器是一种非常有用的数据结构,它可以用于记录某个事件的发生次数。在很多场景下,我们都需要对某个操作进行计数,比如统计网站访问量、用户活跃度等。Redis的计数器可以非常方便地实现这些功能。在Spring Boot中,我们可以使用Redis作为缓存来存储计数器的值。下面是一个示例代码,展示了如何在Spring Boot中使用Redis的计数器:首先,我们需要在pom.xml文件中添加Redis依赖项:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>然后,我们需要在application.properties文件中配置Redis连接信息:spring.redis.host=localhost spring.redis.port=6379接下来,我们可以创建一个Redis计数器服务类,用于操作Redis中的计数器:@Service public class RedisCounterService { @Autowired private StringRedisTemplate stringRedisTemplate; public void increment(String key) { stringRedisTemplate.opsForValue().increment(key); } public Long getCount(String key) { return stringRedisTemplate.opsForValue().getCount(key); } }最后,我们可以在Controller中使用Redis计数器服务类:@RestController public class CounterController { @Autowired private RedisCounterService redisCounterService; @GetMapping("/increment") public String increment(@RequestParam String key) { redisCounterService.increment(key); return "Counter incremented: " + key; } @GetMapping("/count") public Long count(@RequestParam String key) { return redisCounterService.getCount(key); } }在这个示例中,我们创建了一个Redis计数器服务类和一个Controller。在Controller中,我们通过调用Redis计数器服务类的increment方法和getCount方法来增加和获取计数器的值。当用户访问/increment和/count接口时,CounterController会分别调用这两个方法来操作Redis中的计数器。
  • [技术干货] Caffeine缓存的简单介绍【转】
    转自:https://baijiahao.baidu.com/s?id=1705731639018895075&wfr=spider&for=pc1、简介在本文中,我们将了解Caffeine,一个用于Java的高性能缓存库。缓存和Map之间的一个根本区别是缓存会清理存储的项目。一个清理策略会决定在某个给定时间哪些对象应该被删除,这个策略直接影响缓存的命中率——缓存库的一个关键特性。Caffeine使用Window TinyLfu清理策略,它提供了接近最佳的命中率。2、依赖我们需要将Caffeine依赖添加到我们的pom.xml中:<dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.5.5</version> </dependency>您可以在Maven Central上找到最新版本的Caffeine。3、写入缓存让我们关注Caffeine的三种缓存写入策略:手动、同步加载和异步加载。首先,让我们编写一个类,作为要存储在缓存中的值的类型:class DataObject { private final String data; private static int objectCounter = 0; // standard constructors/getters public static DataObject get(String data) { objectCounter++; return new DataObject(data); } }3.1、手动写入在此策略中,我们手动将值写入缓存并稍后读取它们。我们先初始化缓存:Cache<String, DataObject> cache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.MINUTES) .maximumSize(100) .build();现在,我们可以使用getIfPresent方法从缓存中获取一些值。如果缓存中不存在该值,则此方法将返回null:String key = "A"; DataObject dataObject = cache.getIfPresent(key); assertNull(dataObject);我们可以使用put方法手动写入缓存:cache.put(key, dataObject); dataObject = cache.getIfPresent(key); assertNotNull(dataObject);我们还可以使用get方法获取值,该方法接受一个函数和一个键作为参数。如果缓存中不存在该键,则此函数将用于提供兜底值,该值将在执行后写入缓存:dataObject = cache .get(key, k -> DataObject.get("Data for A")); assertNotNull(dataObject); assertEquals("Data for A", dataObject.getData());这个GET方法执行是原子性的。这意味着即使多个线程同时请求该值,执行只会进行一次。这就是为什么使用get比getIfPresent更好。有时我们需要手动使一些缓存的值失效:cache.invalidate(key); dataObject = cache.getIfPresent(key); assertNull(dataObject);3.2、同步加载这种加载缓存的方法需要一个Function,用于初始化写入值,类似于手动写入策略的get方法,让我们看看如何使用它。首先,我们需要初始化我们的缓存:LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(1, TimeUnit.MINUTES) .build(k -> DataObject.get("Data for " + k));现在我们可以使用get方法读取值:DataObject dataObject = cache.get(key); assertNotNull(dataObject); assertEquals("Data for " + key, dataObject.getData());我们还可以使用getAll方法获取一组值:Map<String, DataObject> dataObjectMap = cache.getAll(Arrays.asList("A", "B", "C")); assertEquals(3, dataObjectMap.size());值从传递给build方法的底层后端初始化Function中读取到,这样就可以使用缓存作为访问值的主要入口了。3.3、异步加载此策略的工作原理与前一个相同,但是会异步执行操作并返回一个CompletableFuture来保存实际的值:AsyncLoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumSize(100) .expireAfterWrite(1, TimeUnit.MINUTES) .buildAsync(k -> DataObject.get("Data for " + k));我们可以以相同的方式使用get和getAll方法,考虑到它们的返回是CompletableFuture:String key = "A"; cache.get(key).thenAccept(dataObject -> { assertNotNull(dataObject); assertEquals("Data for " + key, dataObject.getData()); }); cache.getAll(Arrays.asList("A", "B", "C")) .thenAccept(dataObjectMap -> assertEquals(3, dataObjectMap.size()));CompletableFuture具有很多有用的API,您可以在本文中阅读更多相关信息。4、缓存值的清理Caffeine有三种缓存值的清理策略:基于大小、基于时间和基于引用。4.1、基于大小的清理这种类型的清理设计为在超出缓存配置的大小限制时发生清理。有两种获取大小的方法——计算缓存中的对象数,或者获取它们的权重。让我们看看如何计算缓存中的对象数。缓存初始化时,其大小为零:LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumSize(1) .build(k -> DataObject.get("Data for " + k)); assertEquals(0, cache.estimatedSize());当我们添加一个值时,大小明显增加:cache.get("A"); assertEquals(1, cache.estimatedSize());我们可以将第二个值添加到缓存中,这会导致删除第一个值:cache.get("B"); cache.cleanUp(); assertEquals(1, cache.estimatedSize());值得一提的是,我们在获取缓存大小之前调用了cleanUp方法。这是因为缓存清理是异步执行的,该方法有助于等待清理完成。我们还可以传入一个weigher的Function来定义缓存大小的获取:LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumWeight(10) .weigher((k,v) -> 5) .build(k -> DataObject.get("Data for " + k)); assertEquals(0, cache.estimatedSize()); cache.get("A"); assertEquals(1, cache.estimatedSize()); cache.get("B"); assertEquals(2, cache.estimatedSize());当权重超过 10 时,这些值将从缓存中删除:cache.get("C"); cache.cleanUp(); assertEquals(2, cache.estimatedSize());4.2、基于时间的清理这种清理策略基于条目的过期时间,分为三种:访问后过期——自上次读取或写入以来,条目在经过某段时间后过期写入后过期——自上次写入以来,条目在经过某段时间后过期自定义策略——由Expiry的实现来为每个条目单独计算到期时间让我们使用expireAfterAccess方法配置访问后过期策略:LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .expireAfterAccess(5, TimeUnit.MINUTES) .build(k -> DataObject.get("Data for " + k));要配置写入后过期策略,我们使用expireAfterWrite方法:cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.SECONDS) .weakKeys() .weakValues() .build(k -> DataObject.get("Data for " + k));要初始化自定义策略,我们需要实现Expiry接口:cache = Caffeine.newBuilder().expireAfter(new Expiry<String, DataObject>() { @Override public long expireAfterCreate( String key, DataObject value, long currentTime) { return value.getData().length() * 1000; } @Override public long expireAfterUpdate( String key, DataObject value, long currentTime, long currentDuration) { return currentDuration; } @Override public long expireAfterRead( String key, DataObject value, long currentTime, long currentDuration) { return currentDuration; } }).build(k -> DataObject.get("Data for " + k));4.3、基于引用的清理我们可以配置我们的缓存,允许缓存的键或值或二者一起的垃圾收集。为此,我们需要为键和值配置WeakReference的使用,并且我们可以配置SoftReference仅用于值的垃圾收集。WeakReference的使用允许在没有对对象的任何强引用时对对象进行垃圾回收。SoftReference允许基于JVM的全局LRU(最近最少使用)策略对对象进行垃圾回收。可以在此处找到有关Java中引用的更多详细信息。我们使用Caffeine.weakKeys()、Caffeine.weakValues()和Caffeine.softValues()来启用每个选项:LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.SECONDS) .weakKeys() .weakValues() .build(k -> DataObject.get("Data for " + k)); cache = Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.SECONDS) .softValues() .build(k -> DataObject.get("Data for " + k));5、缓存刷新可以将缓存配置为在定义的时间段后自动刷新条目。让我们看看如何使用refreshAfterWrite方法做到这一点:Caffeine.newBuilder() .refreshAfterWrite(1, TimeUnit.MINUTES) .build(k -> DataObject.get("Data for " + k));在这里,我们应该明白expireAfter和refreshAfter的一个区别:当请求过期条目时,执行会阻塞,直到build函数计算出新值。但是如果该条目符合刷新条件,则缓存将返回一个旧值并异步重新加载该值。6、统计Caffeine提供了一种记录缓存使用统计信息的方法:LoadingCache<String, DataObject> cache = Caffeine.newBuilder() .maximumSize(100) .recordStats() .build(k -> DataObject.get("Data for " + k)); cache.get("A"); cache.get("A"); assertEquals(1, cache.stats().hitCount()); assertEquals(1, cache.stats().missCount());我们还可以创建一个StatsCounter的实现作为参数来传入recordStats。每次与统计相关的更改,这个实现对象都将被调用。7、结论在本文中,我们熟悉了Java的Caffeine缓存库。我们看到了如何配置和存入缓存,以及如何根据需要选择合适的过期或刷新策略。
  • [技术干货] 本地缓存与分布式缓存的优缺点、适用场景与实现分析【转】
    转自:https://www.cnblogs.com/wq-9/articles/16853403.html一、缓存的概念在服务端编程当中,缓存主要是指将数据库的数据加载到内存中,之后对该数据的访问都在内存中完成,从而减少了对数据库的访问,解决了高并发场景中数据库容易成为性能瓶颈的问题;以及基于内存的访问速度高于磁盘的访问速度的原理(数据库读取数据一般需要从磁盘读取),提高了数据的访问速度和程序性能。根据缓存是否与应用进程属于同一进程,可以将内存分为本地缓存和分布式缓存。本地缓存是在同一个进程内的内存空间中缓存数据,数据读写都是在同一个进程内完成;而分布式缓存是一个独立部署的进程并且一般都是与应用进程部署在不同的机器,故需要通过网络来完成分布式缓存数据读写操作的数据传输。二、本地缓存本地缓存的优缺点1. 访问速度快,但无法进行大数据存储本地缓存相对于分布式缓存的好处是,由于数据不需要跨网络传输,故性能更好,但是由于占用了应用进程的内存空间,如 Java 进程的 JVM 内存空间,故不能进行大数据量的数据存储。2. 集群的数据更新问题与此同时,本地缓存只支持被该应用进程访问,一般无法被其他应用进程访问,故在应用进程的集群部署当中,如果对应的数据库数据,存在数据更新,则需要同步更新不同部署节点的本地缓存的数据来包保证数据一致性,复杂度较高并且容易出错,如基于 Redis 的发布订阅机制来同步更新各个部署节点。3. 数据随应用进程的重启而丢失由于本地缓存的数据是存储在应用进程的内存空间的,所以当应用进程重启时,本地缓存的数据会丢失。所以对于需要持久化的数据,需要注意及时保存,否则可能会造成数据丢失。适用场景所以本地缓存一般适合于缓存只读数据,如统计类数据。或者每个部署节点独立的数据,如长连接服务中,每个部署节点由于都是维护了不同的连接,每个连接的数据都是独立的,并且随着连接的断开而删除。如果数据在集群的不同部署节点需要共享和保持一致,则需要使用分布式缓存来统一存储,实现应用集群的所有应用进程都在该统一的分布式缓存中进行数据存取即可。本地缓存的实现缓存一般是一种key-value的键值对数据结构,所以需要使用字典数据结构来实现,在 Java 编程中,常用的字典实现包括 HashMap 和 ConcurretHashMap。与此同时,本地缓存由于需要被不同的服务端线程并发读写,故需要保证线程安全。由于 HashMap 不是线程安全的,而 ConcurrentHashMap 是线程安全的,故一般会使用 ConcurrentHashMap 来作为 Java 编程中的本地缓存实现。除此之外,也有其他更加智能的本地缓存实现,如可以定时失效,访问重新加载等特性,典型实现包括 Google 的 guava 工具包的 Cache 实现,这些也是线程安全的。三、分布式缓存分布式缓存的优缺点1. 支持大数据量存储,不受应用进程重启影响分布式缓存由于是独立部署的进程,拥有自身独立的内存空间,不会受到应用进程重启的影响,在应用进程重启时,分布式缓存的数据依然存在。同时对于数据量而言,由于不需要占用应用进程的内存空间,并且一般支持以集群的方式拓展,故可以进行大数据量的数据缓存。2. 数据集中存储,保证数据一致性当应用进程采用集群方式部署时,集群的每个部署节点都通过一个统一的分布式缓存进行数据存取操作,故不存在本地缓存中的数据更新问题,保证了不同节点的应用进程的数据一致性问题。3. 数据读写分离,高性能,高可用分布式缓存一般支持数据副本机制,可以实现读写分离,故可以解决高并发场景中的数据读写性能问题。并且由于在多个缓存节点冗余存储数据,提高了缓存数据的可用性,避免某个缓存节点宕机导致数据不可用问题。4. 数据跨网络传输,性能低于本地缓存由于分布式缓存是独立部署的进程,并且一般都是与应用进程位于不同的机器,故需要通过网络来进行数据传输,这样相对于本地缓存的进程内部的数据读取操作,性能会较低。分布式缓存的实现分布式缓存的典型实现包括 MemCached 和 Redis。MemCachedMemCached 相对于本地缓存的主要差别是以独立进程方式存在,数据集中存储,数据不随应用程序的重启而丢失。而 key-value 键值对的 value 也是一个简单的对象类型,即 value 可以是任意格式的数据,如简单的数字、字符串、对象等,也可以是文件、图像、视频等复杂格式的数据,但是不支持数据结构的特性。所以 MemCached 进程相当于是在内存维护了一个非常大的哈希表来存储数据,对应的数据操作复杂度都是 O(1),即常量级别,这也是 MemCached 高性能的一个实现方式,键值对存取速度都非常快。RedisRedis 是在此基础上,更一步丰富了key-value 键值对的 value 的数据结构类型,即可以在 Redis 中完成 value 的相关数据操作,如 Set 集合去重、有序集合 ZSet 实现数据排序等,这样就不需要在应用程序额外进行这些操作,实现了开箱即用。并且 Redis 是单线程的,不存在并发数据读写的线程安全问题,以及更重要的是保证的数据读写操作的顺序性。除此之外,Redis 支持主从同步(读写分离)、集群分片拓展、数据持久化等特性,这也是 MemCached 不支持的。所以在高并发场景并且数据能够容忍极端情况下的少量丢失,或者说丢失后可以恢复,如通过日志或者重新计算等, Redis 也可以作为数据库来使用,提高高并发场景中的访问性能。
  • [技术干货] 面对本地缓存和分布式缓存,我们如何选择【转】
    对于应用系统来讲,我们经常将缓存划分为本地缓存和分布式缓存。本地缓存 :应用中的缓存组件,缓存组件和应用在同一进程中,缓存的读写非常快,没有网络开销。但各应用或集群的各节点都需要维护自己的单独缓存,无法共享缓存。分布式缓存:和应用分离的缓存组件或服务,与本地应用隔离,多个应用可直接共享缓存。1 缓存的本质我们常常会讲:“加了缓存,我们的系统就会更快” 。所谓的“更快”,本质上做到了如下两点:    减小 CPU 消耗    将原来需要实时计算的内容提前算好、把一些公用的数据进行复用,这可以减少 CPU 消耗,从而提升响应性能。    减小 I/O 消耗    将原来对网络、磁盘等较慢介质的读写访问变为对内存等较快介质的访问,从而提升响应性能。假如可以通过增强 CPU、I/O 本身的性能来满足需求的话,升级硬件往往是更好的解决方案,即使需要一些额外的投入成本,也通常要优于引入缓存后可能带来的风险。从开发角度来说,引入缓存会提高系统复杂度,因为你要考虑缓存的失效、更新、一致性等问题。从运维角度来说,缓存会掩盖掉一些缺陷,让问题在更久的时间以后,出现在距离发生现场更远的位置上。从安全角度来说,缓存可能泄漏某些保密数据,也是容易受到攻击的薄弱点。因此,缓存是把双刃剑。2 本地缓存 JDK MapJDK Map 经常用于缓存实现:    HashMap    HashMap 是一种基于哈希表的集合类,它提供了快速的插入、查找和删除操作。可以将键值对作为缓存项的存储方式,将键作为缓存项的唯一标识符,值作为缓存项的内容。    ConcurrentHashMap    ConcurrentHashMap 是线程安全的 HashMap,它在多线程环境下可以保证高效的并发读写操作。    LinkedHashMap    LinkedHashMap 是一种有序的 HashMap ,它保留了元素插入的顺序,可以按照插入顺序或者访问顺序进行遍历。    TreeMap    TreeMap 是一种基于红黑树的有序 Map,它可以按照键的顺序进行遍历。笔者曾经负责艺龙红包系统,红包活动就是存储在 ConcurrentHashMap 中 ,通过定时任务刷新缓存 。核心流程:1、红包系统启动后,初始化一个 ConcurrentHashMap 作为红包活动缓存 ;2、数据库查询所有的红包活动 , 并将活动信息存储在 Map 中 ;3、定时任务每隔 30 秒 ,执行缓存加载方法,刷新缓存。为什么红包系统会将红包活动信息存储在本地内存 ConcurrentHashMap 呢 ?    红包系统是高并发应用,快速将请求结果响应给前端,大大提升用户体验;    红包活动数量并不多,就算全部放入到 Map 里也不会产生内存溢出的问题;    定时任务刷新缓存并不会影响红包系统的业务。笔者见过很多单体应用都使用这种方案,该方案的特点是简洁易用,工程实现也容易 。3 本地缓存框架虽然使用 JDK Map 能快捷构建缓存,但缓存的功能还是比较孱弱的。因为现实场景里,我们可能需要给缓存添加缓存统计、过期失效、淘汰策略等功能。于是,本地缓存框架应运而生。流行的 Java 缓存框架包括: Ehcache , Google Guava , Caffine Cache 。下图展示了 Caffine 框架的使用示例。虽然本地缓存框架的功能很强大,但是本地缓存的缺陷依然明显。1、高并发的场景,应用重启之后,本地缓存就失效了,系统的负载就比较大,需要花较长的时间才能恢复;2、每个应用节点都会维护自己的单独缓存,缓存同步比较头疼。4 分布式缓存分布式缓存是指将缓存数据分布在多台机器上,以提高缓存容量和并发读写能力的缓存系统。分布式缓存通常由多台机器组成一个集群,每台机器上都运行着相同的缓存服务进程,缓存数据被均匀地分布在集群中的各个节点上。Redis 是分布式缓存的首选,甚至我们一提到缓存,很多后端工程师首先想到的就它。下图是神州专车订单的 Redis 集群架构 。将 Redis 集群拆分成四个分片,每个分片包含一主一从,主从可以切换。 应用 A 根据不同的缓存 key 访问不同的分片。与本地缓存相比,分布式缓存具有以下优点:1、容量和性能可扩展通过增加集群中的机器数量,可以扩展缓存的容量和并发读写能力。同时,缓存数据对于应用来讲都是共享的。2、高可用性由于数据被分布在多台机器上,即使其中一台机器故障,缓存服务也能继续提供服务。但是分布式缓存的缺点同样不容忽视。1、网络延迟分布式缓存通常需要通过网络通信来进行数据读写,可能会出现网络延迟等问题,相对于本地缓存而言,响应时间更长。2、复杂性分布式缓存需要考虑序列化、数据分片、缓存大小等问题,相对于本地缓存而言更加复杂。笔者曾经也认为无脑上缓存 ,系统就一定更快,但直到一次事故,对于分布式缓存的观念才彻底改变。2014年,同事开发了比分直播的系统,所有的请求都是从分布式缓存 Memcached 中获取后直接响应。常规情况下,从缓存中查询数据非常快,但在线用户稍微多一点,整个系统就会特别卡。通过 jstat 命令发现 GC 频率极高,几次请求就将新生代占满了,而且 CPU 的消耗都在 GC 线程上。初步判断是缓存值过大导致的,果不其然,缓存大小在 300k 到 500k 左右。解决过程还比较波折,分为两个步骤:    修改新生代大小,从原来的 2G 修改成 4G,并精简缓存数据大小 (从平均 300k 左右降为 80k 左右);把缓存拆成两个部分,第一部分是全量数据,第二部分是增量数据(数据量很小)。页面第一次请求拉取全量数据,当比分有变化的时候,通过 websocket 推送增量数据。经过这次优化,笔者理解到:缓存虽然可以提升整体速度,但是在高并发场景下,缓存对象大小依然是需要关注的点,稍不留神就会产生事故。另外我们也需要合理地控制读取策略,最大程度减少 GC 的频率 , 从而提升整体性能。5 多级缓存开源中国网站最开始完全是用本地缓存框架 Ehcache 。后来随着访问量的激增,出现了一个可怕的问题:“因为 Java 程序更新很频繁,每次更新的时候都要重启。一旦重启后,整个 Ehcache 缓存里的数据都被清掉。重启后若大量访问进来的话,开源中国的数据库基本上很快就会崩掉”。于是,开源中国开发了多级缓存框架 J2Cache,使用了多级缓存 Ehcache + Redis 。多级缓存有如下优势:    离用户越近,速度越快;减少分布式缓存查询频率,降低序列化和反序列化的 CPU 消耗;大幅度减少网络 IO 以及带宽消耗。本地缓存做为一级缓存,分布式缓存做为二级缓存,首先从一级缓存中查询,若能查询到数据则直接返回,否则从二级缓存中查询,若二级缓存中可以查询到数据,则回填到一级缓存中,并返回数据。若二级缓存也查询不到,则从数据源中查询,将结果分别回填到一级缓存,二级缓存中。2018年,笔者服务的一家电商公司需要进行 app 首页接口的性能优化。笔者花了大概两天的时间完成了整个方案,采取的是两级缓存模式,同时利用了 Guava 的惰性加载机制,整体架构如下图所示:缓存读取流程如下:1、业务网关刚启动时,本地缓存没有数据,读取 Redis 缓存,如果 Redis 缓存也没数据,则通过 RPC 调用导购服务读取数据,然后再将数据写入本地缓存和 Redis 中;若 Redis 缓存不为空,则将缓存数据写入本地缓存中。2、由于步骤1已经对本地缓存预热,后续请求直接读取本地缓存,返回给用户端。3、Guava 配置了 refresh 机制,每隔一段时间会调用自定义 LoadingCache 线程池(5个最大线程,5个核心线程)去导购服务同步数据到本地缓存和 Redis 中。优化后,性能表现很好,平均耗时在 5ms 左右。最开始我以为出现问题的几率很小,可是有一天晚上,突然发现 app 端首页显示的数据时而相同,时而不同。也就是说: 虽然 LoadingCache 线程一直在调用接口更新缓存信息,但是各个 服务器本地缓存中的数据并非完成一致。 说明了两个很重要的点:1、惰性加载仍然可能造成多台机器的数据不一致2、LoadingCache 线程池数量配置的不太合理, 导致了线程堆积最终,我们的解决方案是:1、惰性加载结合消息机制来更新缓存数据,也就是:当导购服务的配置发生变化时,通知业务网关重新拉取数据,更新缓存。2、适当调大 LoadigCache 的线程池参数,并在线程池埋点,监控线程池的使用情况,当线程繁忙时能发出告警,然后动态修改线程池参数。6 没有银弹没有银弹是 Fred Brooks 在 1987 年所发表的一篇关于软件工程的经典论文。论文强调真正的银弹并不存在,而所谓的银弹则是指没有任何一项技术或方法可以能让软件工程的生产力在十年内提高十倍。通俗来讲:在技术领域中没有一种通用的解决方案可以解决所有问题。技术本质上是为了解决问题而存在的,每个问题都有其独特的环境和限制条件,没有一种通用的技术或工具可以完美地解决所有问题。虽然技术不断发展和进步,但是对于复杂的问题,仍需要结合多种技术和方法,进行系统性的思考和综合性的解决方案设计,才能得到最优解决方案。回到文章开头的问题 ,如何说服技术老大用 Redis ?假如应用就是一个单体应用,缓存可以不共享,通过定时任务刷新缓存对业务没有影响,而且本地内存可以 Hold 住缓存的对象大小,那么你的技术老大的方案没有问题。假如应用业务比较复杂,需要使用缓存提升系统的性能,同时分布式缓存共享的特性对于研发来讲开发更加快捷,Redis 确实是个不错的选择,可以从研发成本、代码维护、人力模型等多个角度和技术老大提出自己的观点。总而言之,在技术领域中,没有银弹。我们需要不断探索和研究新的技术,但同时也需要认识到技术的局限性,不盲目追求所谓的“银弹”,而是结合具体问题和需求,选择最适合的解决方案。
  • [技术干货] Redis 常用命令介绍【转】
    转自:https://zhuanlan.zhihu.com/p/627662519#可以使用help命令查看各redis命令用法[root@Client-Ubuntu-1804-250:~]# redis-cli -a redis --no-auth-warning helpredis-cli 5.0.14To get help about Redis commands type: "help @<group>" to get a list of commands in <group> "help <command>" for help on <command> "help <tab>" to get a list of possible help topics "quit" to exitTo set redis-cli preferences: ":set hints" enable online hints ":set nohints" disable online hintsSet your preferences in ~/.redisclirc[root@Client-Ubuntu-1804-250:~]# redis-cli -a redis --no-auth-warning#查看info命令帮助127.0.0.1:6379> help info INFO [section] summary: Get information and statistics about the server since: 1.0.0 group: server#查看set命令帮助127.0.0.1:6379> help set SET key value [expiration EX seconds|PX milliseconds] [NX|XX] summary: Set the string value of a key since: 1.0.0 group: string127.0.0.1:6379>1、INFO查看当前节点运行状态info可以在INFO 后补充筛选内容#查看当前节点CPU信息127.0.0.1:6379> info CPU# CPUused_cpu_sys:0.508881used_cpu_user:0.848135used_cpu_sys_children:0.000000used_cpu_user_children:0.003046#查看当前节点Server信息127.0.0.1:6379> info Server# Serverredis_version:5.0.14redis_git_sha1:00000000redis_git_dirty:0redis_build_id:5d32a2d9ed5f67d5redis_mode:standaloneos:Linux 4.15.0-210-generic x86_64arch_bits:64multiplexing_api:epollatomicvar_api:atomic-builtingcc_version:7.5.0process_id:6069run_id:0e67fcd27ff6ae2589ff90ac2516bd52269f2965tcp_port:6379uptime_in_seconds:1358uptime_in_days:0hz:10configured_hz:10lru_clock:5746346executable:/app/redis/bin/redis-serverconfig_file:/app/redis/etc/redis_6379.conf2、SELECT切换数据库,类似于MySQL是 USE DATABASES;在Cluster模式下不支持多DB模式,会出现以下错误提示10.0.0.20:6379> info cluster# Clustercluster_enabled:110.0.0.20:6379> select 1(error) ERR SELECT is not allowed in cluster mode select3、KEYS查看当前数据库下所有的key(数据量大时会对数据库造成巨大压力,此命令慎用)此命令仅查询当前db下的数据127.0.0.1:6379> select 1OK127.0.0.1:6379[1]> keys *(empty list or set)127.0.0.1:6379[1]> info KeySpace# Keyspacedb0:keys=1000,expires=0,avg_ttl=0127.0.0.1:6379> help keys KEYS pattern summary: Find all keys matching the given pattern since: 1.0.0 group: generic#? 占位符127.0.0.1:6379> keys key_? 1) "key_8" 2) "key_5" 3) "key_4" 4) "key_9" 5) "key_0" 6) "key_2" 7) "key_1" 8) "key_3" 9) "key_6"10) "key_7"127.0.0.1:6379> keys key_2? 1) "key_27" 2) "key_28" 3) "key_22" 4) "key_26" 5) "key_21" 6) "key_20" 7) "key_25" 8) "key_23" 9) "key_24"10) "key_29"#* 通配#127.0.0.1:6379> keys key_9* 1) "key_995" 2) "key_957" 3) "key_988" 4) "key_98" 5) "key_990" 6) "key_959" 7) "key_949" 8) "key_984" 9) "key_999" 10) "key_950" 11) "key_976" 12) "key_912" 13) "key_991" 14) "key_944" 15) "key_987" 16) "key_954" 17) "key_981" 18) "key_951" 19) "key_998" 20) "key_916" 21) "key_986" 22) "key_933" 23) "key_996" 24) "key_918" 25) "key_942" 26) "key_906" 27) "key_925" 28) "key_965" 29) "key_964" 30) "key_917" 31) "key_9" 32) "key_95" 33) "key_994" 34) "key_94" 35) "key_961" 36) "key_901" 37) "key_962" 38) "key_909" 39) "key_971" 40) "key_966" 41) "key_968" 42) "key_913" 43) "key_960" 44) "key_937" 45) "key_977" 46) "key_90" 47) "key_926" 48) "key_93" 49) "key_955" 50) "key_970" 51) "key_939" 52) "key_908" 53) "key_929" 54) "key_972" 55) "key_902" 56) "key_948" 57) "key_956" 58) "key_930" 59) "key_978" 60) "key_99" 61) "key_924" 62) "key_915" 63) "key_943" 64) "key_932" 65) "key_928" 66) "key_914" 67) "key_982" 68) "key_905" 69) "key_974" 70) "key_979" 71) "key_940" 72) "key_941" 73) "key_958" 74) "key_997" 75) "key_993" 76) "key_922" 77) "key_92" 78) "key_980" 79) "key_934" 80) "key_911" 81) "key_946" 82) "key_923" 83) "key_945" 84) "key_992" 85) "key_969" 86) "key_921" 87) "key_910" 88) "key_936" 89) "key_983" 90) "key_903" 91) "key_989" 92) "key_931" 93) "key_919" 94) "key_953" 95) "key_947" 96) "key_96" 97) "key_935" 98) "key_91" 99) "key_907"100) "key_952"101) "key_927"102) "key_967"103) "key_904"104) "key_900"105) "key_938"106) "key_973"107) "key_97"108) "key_920"109) "key_985"110) "key_963"111) "key_975"#展示当前数据库内的所有key127.0.0.1:6379> keys * ...... 987) "key_172" 988) "key_64" 989) "key_79" 990) "key_801" 991) "key_502" 992) "key_144" 993) "key_514" 994) "key_615" 995) "key_197" 996) "key_84" 997) "key_681" 998) "key_572" 999) "key_656"1000) "key_805"4、BGSAVE手动触发后台执行save操作127.0.0.1:6379> 127.0.0.1:6379> help bgsave BGSAVE - summary: Asynchronously save the dataset to disk since: 1.0.0 group: server127.0.0.1:6379> bgsaveBackground saving started5、DBSIZE返回当前数据库下的所有key数量127.0.0.1:6379> help dbsize DBSIZE - summary: Return the number of keys in the selected database since: 1.0.0 group: server127.0.0.1:6379> dbsize(integer) 1000127.0.0.1:6379> select 1OK127.0.0.1:6379[1]> dbsize(integer) 06、FLUSHDB强制清空当前数据库中的所有key,不会影响其他db中的数据,谨慎使用127.0.0.1:6379> help flushdb FLUSHDB [ASYNC] summary: Remove all keys from the current database since: 1.0.0 group: server127.0.0.1:6379[1]> info KeySpace# Keyspacedb0:keys=1000,expires=0,avg_ttl=0db1:keys=4,expires=0,avg_ttl=0#清空db1中的所有键值,不会影响到db0中的数据127.0.0.1:6379[1]> flushdbOK127.0.0.1:6379[1]> info KeySpace# Keyspacedb0:keys=1000,expires=0,avg_ttl=0127.0.0.1:6379[1]>7、FLUSHALL强制清空当前redis节点上所有数据库中的所有key,即删除当前节点所有数据,谨慎使用,必要情况建议禁用此命令127.0.0.1:6379> info KeySpace# Keyspacedb0:keys=1000,expires=0,avg_ttl=0db1:keys=4,expires=0,avg_ttl=0127.0.0.1:6379> flushallOK127.0.0.1:6379> info KeySpace# Keyspace127.0.0.1:6379>8、SHUTDOWN时间复杂度:O(N) N为需要保存的数据库键数量SHUTDOWN执行过程:停止所有客户端连接如果至少有一个保存节点在等待,执行SAVE操作如果AOF开启,更新AOF内容关闭 Redis Server如果开启了持久化配置,SHUTDOWN命令可以保障服务器正常关闭而数据不丢失如果单纯执行SAVE之后,执行 QUIT 命令,则无法保证数据不丢失,SAVE执行完成后,执行 QUIT 命令的过程中,服务器可能依旧存在与客户端的访问连接,会造成这期间数据的丢失。127.0.0.1:6379> help shutdown SHUTDOWN [NOSAVE|SAVE] summary: Synchronously save the dataset to disk and then shut down the server since: 1.0.0 group: server127.0.0.1:6379> shutdownnot connected> not connected> not connected> exit[root@Client-Ubuntu-1804-250:~]# ps -aux | grep redis | grep ^grep[root@Client-Ubuntu-1804-250:~]# ss -ntlpState Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=867,fd=13)) LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=978,fd=3)) LISTEN 0 128 127.0.0.1:6010 0.0.0.0:* users:(("sshd",pid=1661,fd=10)) LISTEN 0 128 127.0.0.1:6011 0.0.0.0:* users:(("sshd",pid=1661,fd=15)) LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=978,fd=4)) LISTEN 0 128 [::1]:6010 [::]:* users:(("sshd",pid=1661,fd=9)) LISTEN 0 128 [::1]:6011 [::]:* users:(("sshd",pid=1661,fd=12))禁用Redis命令(rename配置)可通过配置文件中的 rename-comand <command> "" 对原有命令进行重命名(通常用于对高危命令进行管控)#在启用aof情况下,禁用或重命名命令可能会导致redis服务无法启动,是因为实例在之前执行被重命名的命令,导致加载aof时,命令执行失败6682:C 07 May 2023 23:20:57.411 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo6682:C 07 May 2023 23:20:57.411 # Redis version=5.0.14, bits=64, commit=00000000, modified=0, pid=6682, just started6682:C 07 May 2023 23:20:57.411 # Configuration loaded6682:C 07 May 2023 23:20:57.411 * supervised by systemd, will signal readiness6682:M 07 May 2023 23:20:57.417 * Running mode=standalone, port=6379.6682:M 07 May 2023 23:20:57.419 # Server initialized6682:M 07 May 2023 23:20:57.420 # Unknown command 'flushdb' reading the append only file解决方案:1、禁用aof选项2、修改配置重启服务之前,执行 bgrewriteaof 重新生成aof内容# Command renaming.## It is possible to change the name of dangerous commands in a shared# environment. For instance the CONFIG command may be renamed into something# hard to guess so that it will still be available for internal-use tools# but not available for general clients.## Example:## rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52## It is also possible to completely kill a command by renaming it into# an empty string:## rename-command CONFIG ""## Please note that changing the name of commands that are logged into the# AOF file or transmitted to replicas may cause problems.renmae-comand FLUSHALL ""rename-comand FULSHDB "REMOVE-THIS-DATABASE"127.0.0.1:6379> info keySpace# Keyspacedb0:keys=1000,expires=0,avg_ttl=0127.0.0.1:6379> flushall(error) ERR unknown command `flushall`, with args beginning with: 127.0.0.1:6379> flushdb(error) ERR unknown command `flushdb`, with args beginning with: 127.0.0.1:6379> info keySpace# Keyspacedb0:keys=1000,expires=0,avg_ttl=0127.0.0.1:6379> 127.0.0.1:6379> REMOVE-THIS-DATABASEOK127.0.0.1:6379> info keySpace# Keyspace127.0.0.1:6379>
  • [问题求助] 修改CC-Gateway作为服务端提供给远端CC-Gateway的鉴权密码
    【问题来源】 华夏银行【问题简要】 修改CC-Gateway作为服务端提供给远端CC-Gateway的鉴权密码 redis同步失败【问题类别】  CCGW【AICC解决方案版本】【必填】    AICC 22.100【问题现象描述】【必填】设置CTI Pool模式下ccgw 的redis同步,查看configproxy-rest 日志,有收到远程地址消息,但是显示redis 鉴权失败 ;想确认几个问题:1、使用 openas_encrypt_interactive.sh 生成的encryptedKey 和Password的时候 ,应该使用可逆还是不可逆 方式?2、加密时候输入的密码是否为ccgw 安装redis 的鉴权密码 ?下图报错的原因是?日志参考下图:a中心日志:b中心日志”