【Redis】Redis 的学习教程(六)Redis 的缓存问题

在服务端中,数据库通常是业务上的瓶颈,为了提高并发量和响应速度,我们通常会采用 Redis 来作为缓存,让尽量多的数据走 Redis 查询,不直接访问数据库。

同时 Redis 在使用过程中(高并发场景下)也会出现各种各样的问题,面对这些问题我们该如何处理:

  • 缓存穿透
  • 缓存击穿
  • 缓存雪崩
  • 缓存污染
  • 数据一致性

1. 缓存穿透

缓存穿透:当缓存和数据中都没有对应记录,但是客户端却一直在查询,导致所有的查询压力全部给到了数据库。

比如:黑客攻击系统,不断的去查询系统中不存在的用户,查询时先走缓存,缓存中没有,再去查数据库;或者电商系统中,用户搜索某类商品,但是这类商品再系统中根本不存在,这次的搜索应该直接返回空

解决方案:

  1. 网关层增加校验,进行用户鉴权,黑名单控制,接口流量控制
  2. 对于同一类查询,如果缓存和数据库都没有获取到数据,那么可用用一个空缓存记录下来,设置过期时间(如:5s),下次遇到同类查询,直接取出缓存中的空数据返回即可

比如:查询一个用户:先查询缓存中是否存在该用户,如果存在则直接返回。否则,再查询数据库,并将查询结果进行缓存

@GetMapping("/queryById")
public User queryById(Integer id) {String userKey = "user:" + id;Object obj = redisUtil.get(userKey);if (Objects.nonNull(obj)) {return (User)obj;}User user = userService.getById(id);if (Objects.isNull(user)) {throw new RuntimeException("该用户不存在");}redisUtil.set(userKey, user);return user;
}

如果项目的并发量不大,这样写的话几乎没啥问题。

如果项目的并发量很大,那么这就存在一个隐藏问题:如果在访问了一个不存在的用户(这个用户已经在后台可能是被删除),那么就会导致所有的请求全部需要到数据库中进行查询,从而给数据库造成压力,甚至造成宕机

解决方案:缓存空对象

针对缓存穿透问题缓存空对象可以有效避免所产生的影响,当查询一条不存在的数据时,在缓存中存储一个空对象并设置一个过期时间(设置过期时间是为了避免出现数据库中存在了数据但是缓存中仍然是空数据现象),这样可以避免所有请求全部查询数据库的情况

@GetMapping("/queryById")
public User queryById(Integer id) {String key = "user::" + id;Object obj = redisUtil.get(userKey);if (Objects.nonNull(obj)) {return (User)obj;}User user = userService.getById(id);if (Objects.isNull(user)) {// 缓存空对象redisUtil.set(userKey, "", 5L);} else {redisUtil.set(userKey, user);}return user;
}

缺点:在于无论数据存不存在都需要查询一次数据库,并且 Redis 中存储了大量的空数据。

这个时候可以采用布隆过滤器来解决

  1. 使用布隆过滤器,布隆过滤器可以用来判断某个元素是否存在于集合中,利用布隆过滤器可以过滤掉一大部分无效请求

布隆过滤器(Bloom Filter)是一种数据结构,用于快速检查一个元素是否属于某个集合中。它可以快速判断一个元素是否在一个大型集合中,且判断速度很快且不占用太多内存空间

布隆过滤器的主要原理:

使用一组哈希函数,将元素映射成一组位数组中的索引位置。当要检查一个元素是否在集合中时,将该元素进行哈希处理,然后查看哈希值对应的位数组的值是否为1。如果哈希值对应的位数组的值都为1,那么这个元素可能在集合中,否则这个元素肯定不在集合中。由于哈希函数的映射可能会发生冲突,因此布隆过滤器可能会出现误判

布隆过滤器的实现:

在使用布隆过滤器时有两个核心参数,分别是预估的数据量size以及期望的误判率fpp,这两个参数我们可以根据自己的业务场景和数据量进行自主设置

在实现布隆过滤器时,有两个核心问题,分别是 hash 函数的选取个数 n 、确定 bit 数组的大小 len:

  1. 根据预估数据量 size 和误判率 fpp,可以计算出 bit 数组的大小 len
    在这里插入图片描述

  2. 根据预估数据量 size 和 bit 数组的长度大小 len,可以计算出所需要的 hash 函数个数 n
    在这里插入图片描述

1. 单机版布隆过滤器

目前单机版的布隆过滤器实现方式有很多:Guava 提供的 BloomFilter,Hutool 工具包中提供的 BitMapBloomFilter 等

这里以 Guava 为例,引入依赖:

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>21.0</version>
</dependency>

布隆过滤器工具类:闯将布隆过滤器

public class BloomFilterUtil {public static BloomFilter<Integer> localBloomFilter =  BloomFilter.create(Funnels.integerFunnel(),10000L,0.01);}

将需要筛选的数据同步到过滤器中

// 单机版布隆过滤器数据初始化
@PostConstruct
public void initUserDataLocal(){List<User> users = userService.lambdaQuery().select(User::getId).list();if(!CollectionUtils.isEmpty(users)){users.stream().map(User::getId).forEach(id -> BloomFilterUtil.localBloomFilter.put(id));}
}

使用布隆过滤器:

@GetMapping("/queryById")
public User queryById(Integer id) {boolean mightContain = BloomFilterUtil.localBloomFilter.mightContain(id);//是否有可能存在于布隆过滤器中if(!mightContain) {log.info("==== select from bloomFilter , data not available ====");return null;}String userKey = "user:" + id;// ...
}

2. 自定义分布式版布隆过滤器

自定义分布式布隆过滤器的存储依赖于 Redis 的 Bitmap 数据结构来实现,另外还需要定义四个参数,分别为预估数据量 size,误判率 fpp,数组大小 bitNum 以及 hash 函数个数 hashNum。其中,预估数据量和误判率需要配置在 yml 文件中。

application.yml

bloom:filter:size: 10000fpp: 0.01

布隆过滤器工具类:

@Component
public class BloomFilterUtil {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 预估数据量*/@Value("${bloom.filter.size}")private long size;/*** 误判率*/@Value("${bloom.filter.fpp}")private double fpp;/*** 数组大小len*/private long bitNum;/*** hash函数个数size*/private int hashNum;@PostConstructprivate void initBloom() {this.bitNum = getNumOfBits(size, fpp);this.hashNum = getNumOfHashFun(size, bitNum);//借助 Redis 的 Bitmap 来实现二进制数组redisTemplate.opsForValue().setBit("bloom::filter", bitNum, false);}/*** 计算bit数组大小* * @author zzc* @date 2023/8/30 15:15 * @param size* @param fpp * @return long*/private long getNumOfBits(long size, double fpp) {return (long) (-size * Math.log(fpp) / (Math.log(2) * Math.log(2)));}/*** 计算所需的hash个数* * @author zzc* @date 2023/8/30 15:15 * @param size* @param numOfBits * @return int*/private int getNumOfHashFun(long size, long numOfBits) {return Math.max(1, (int) Math.round((double) numOfBits / size * Math.log(2)));}/*** 向自定义布隆过滤器中添加元素** @author zzc* @date 2023/8/30 15:17* @param key*/public void putBloomFilterRedis(String key) {long hash64 = HashUtil.metroHash64(key.getBytes());int hash1 = (int) hash64;int hash2 = (int) (hash64 >>> 32);for (int i = 1; i <= hashNum; i++) {/***   上面不是说,要使用n个hash函数吗??为啥这里直接用一个动态变量取乘积了呢???*  不用担心,请看《Less Hashing, Same Performance: Building a Better Bloom Filter》,*  里面论述了这种操作不会影响布隆过滤器的性能,毕竟hash的代价还是很大的,这算是个有效的优化手段吧:*    A standard technique from the hashing literature is to use two hash*    functions h(x) and h(x) to simulate additional hash functions of the form g(x) = h(x) + ih(x) .*/int combinedHash = hash1 + i * hash2;if (combinedHash < 0) {//如果为负数,则取反(保证结果为正数)combinedHash = ~combinedHash;}// 计算出数组下标,并将下标值置为1int bitIdx = (int) (combinedHash % bitNum);redisTemplate.opsForValue().setBit("bloom::filter", bitIdx, true);}}/*** 判断自定义布隆过滤器中元素是否有可能存在** @author zzc* @date 2023/8/30 15:16* @param key* @return boolean*/public boolean existBloomFilterRedis(String key) {long hash64 = HashUtil.metroHash64(key.getBytes());int hash1 = (int) hash64;int hash2 = (int) (hash64 >>> 32);for (int i = 1; i <= hashNum; i++) {int combinedHash = hash1 + i * hash2;if (combinedHash < 0) {combinedHash = ~combinedHash;}int bitIdx = (int) (combinedHash % bitNum);//判断下标值是否为1,如果不为1直接返回falseBoolean bit = redisTemplate.opsForValue().getBit("bloom::filter", bitIdx);if (!bit) {return false;}}return true;}}

使用布隆过滤器:

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate BloomFilterUtil bloomFilterUtil;@GetMapping("/queryById")public User queryById(Integer id) {String key = "user::" + id;boolean mightContain = bloomFilterUtil.existBloomFilterRedis(key);//是否有可能存在于布隆过滤器中if(!mightContain) {log.info("==== select from bloomFilter , data not available ====");return null;}Object obj = redisUtil.get(key);// ...}/*** 单机版布隆过滤器数据初始化* * @author zzc* @date 2023/8/30 14:44  */@PostConstructpublic void initUserDataLocal(){List<User> users = userService.lambdaQuery().select(User::getId).list();if (!CollectionUtils.isEmpty(users)) {users.stream().map(user -> "user::" + user.getId()).forEach(id -> bloomFilterUtil.putBloomFilterRedis(id));}}}

不存在的数据成功被拦截掉了,避免再去查询数据库,即使存在一定的误判率,也几乎不会有啥影响,最多就是查询一次数据库

虽然布隆过滤器可以有效的解决缓存穿透问题,并且实现的算法查找效率也很快。但是,也存在一定的缺点,由于存在 hash 冲突的原因,一方面存在一定的误判率(某个在过滤器中并不存在的 key,但是通过 hash 计算出来的下标值都为 1)。另一方面,删除比较困难(如果将一个数组位置为0,那么这个位置有可能也代表其他 key 的值,会影响到其他的 key)

2. 缓存击穿

缓存击穿:缓存中某个热点数据失效,在高并发情况下,所有用户的请求全部都打到数据库上,短时间造成数据库压力过大

解决方案:

  1. 接口限流、熔断
  2. 热点数据不设置过期时间:适用于不严格要求缓存一致性的场景
  3. 互斥锁,当第一个用户请求到时,如果缓存中没有,其他用户的请求先锁住,第一个用户查询数据库后立即缓存到 Redis,然后释放锁,这时候其他用户就可以直接查询缓存

如果是单机部署的环境下可以使用 synchronized 或 lock 来处理,保证同时只能有一个线程来查询数据库,其他线程可以等待数据缓存成功后在被唤醒,从而直接查询缓存即可。如果是分布式部署,可以采用分布式锁来实现互斥

互斥锁工具类:

@Component
public class RedisLockUtil {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 模拟互斥锁* @author zzc* @date 2023/8/30 16:29 * @param key* @param value* @param exp * @return boolean*/public boolean tryLock(String key, String value, long exp) {Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, exp, TimeUnit.SECONDS);if (Boolean.TRUE.equals(absent)) {return true;}// 如果线程没有获取锁,则在此处循环获取return tryLock(key, value, exp); }/*** 释放锁* * @author zzc* @date 2023/8/30 16:29 * @param key* @param value */public void unLock(String key, String value) {Object obj = redisTemplate.opsForValue().get(key);if (Objects.nonNull(obj) && (StrUtil.equals((String) obj, value))) {// 避免锁被其他线程误删redisTemplate.delete(key);}}}

使用互斥锁:在查询数据库前进行加锁,读取完成后在释放锁

@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate BloomFilterUtil bloomFilterUtil;@Autowiredprivate RedisLockUtil redisLockUtil;@GetMapping("/queryById")public User queryById(Integer id) {String key = "user::" + id;boolean mightContain = bloomFilterUtil.existBloomFilterRedis(key);//是否有可能存在于布隆过滤器中if(!mightContain) {log.info("==== select from bloomFilter , data not available ====");return null;}Object obj = redisUtil.get(key);if (Objects.nonNull(obj)) {log.info("==== select from cache ====");return (User)obj;}// 给锁加个标识,避免误删String s = UUID.randomUUID().toString();String lockKey = key + "::lock";//尝试加锁boolean lock = redisLockUtil.tryLock(lockKey, s, 60);User user = null;if (lock) {try {// 如果加锁成功,先再次查询缓存,有可能上一个线程查询并添加到缓存了obj = redisUtil.get(key);if (Objects.nonNull(obj)) {log.info("==== select from cache ====");return (User)obj;}log.info("==== select from db ====");user = userService.getById(id);if (Objects.nonNull(user)) {redisUtil.set(key, user, 5);}} finally {// 解锁redisLockUtil.unLock(lockKey, s);}}return user;}/*** 单机版布隆过滤器数据初始化* * @author zzc* @date 2023/8/30 14:44  */@PostConstructpublic void initUserDataLocal(){List<User> users = userService.lambdaQuery().select(User::getId).list();if (!CollectionUtils.isEmpty(users)) {users.stream().map(user -> "user::" + user.getId()).forEach(id -> bloomFilterUtil.putBloomFilterRedis(id));}}}

3. 缓存雪崩

缓存雪崩:对热点数据设置了相同的过期时间,在同一时间这些热点数据key大批量发生过期,请求全部转发到数据库,从而导致数据库压力骤增,甚至宕机

与缓存击穿不同,击穿是指一个 key 过期,雪崩是指很多 key 同时过期。

解决方案:

  1. 缓存过期时间设置随机的过期时间
if (Objects.nonNull(user)) {//生成一个随机数int randomInt = RandomUtil.randomInt(2, 10);redisUtil.set(key, user, 5 + randomInt);
}
  1. 缓存过期时间不设置过期时间:在更新数据库数据时,同时也需要更新缓存数据。适用于不严格要求缓存一致性的场景
  2. 搭建高可用集群:缓存服务故障时,也会触发缓存雪崩,为了避免因服务故障而发生的雪崩,推荐使用高可用的服务集群,这样即使发生故障,也可以进行故障转移

4. 缓存污染

缓存污染:由于历史原因,缓存中有很多 key 没有设置过期时间,导致很多 key 其实已经没有用了,但是一直存放在 redis 中,时间久了,redis 内存就被占满了

解决方案:

  1. 缓存尽量设置过期时间
  2. 设置缓存淘汰策略为最近最少使用的原则,然后将这些数据删除

5. 数据一致性

通常情况下,使用缓存的直接目的是为了提高系统的查询效率,减轻数据库的压力。一般情况下使用缓存是下面这几步骤:

  1. 查询缓存,数据是否存在
  2. 如果数据存在,直接返回
  3. 如果数据不存在,再查询数据库
  4. 如果数据库中数据存在,那么将该数据存入缓存并返回。如果不存在,返回空

这么搞好像看上去并没有啥问题,那么会有一个细节问题:当一条数据存入缓存后,立刻又被修改了,那么这个时候缓存该如何更新呢。不更新肯定不行,这样导致了缓存中的数据与数据库中的数据不一致。

一般情况下对于缓存更新有以下情况:

  • 先更新缓存,再更新数据库
  • 先更新数据库,再更新缓存
  • 先删除缓存,再更新数据库
  • 先更新数据库,再删除缓存

1、先更新缓存,再更新数据库

先更新缓存,再更新数据库:如果业务执行正常,不出现网络等问题,这么操作不会有啥问题,两边都可以更新成功。

但是,如果缓存更新成功了,但是当更新数据库时或者在更新数据库之前出现了异常,导致数据库无法更新。这种情况下,缓存中的数据变成了一条实际不存在的假数据。

2、先更新数据库,再更新缓存

这种情况跟上面情况基本一致。如果失败,会导致数据库中是最新的数据,缓存中是旧数据。

还有一种极端情况:在高并发情况下容易出现数据覆盖的现象:A 线程更新完数据库后,在要执行更新缓存的操作时,线程被阻塞了,这个时候线程 B 更新了数据库并成功更新了缓存,当 B 执行完成后线程A继续向下执行,那么最终线程 B 的数据会被覆盖。

3、先删除缓存,再更新数据库

先删除缓存,再更新数据库这种情况,如果并发量不大用起来不会有啥问题。但是在并发场景下会有这样的问题:线程 A 在删除缓存后,在写入数据库前发生了阻塞。这时线程 B 查询了这条数据,发现缓存中不存在,继而向数据库发起查询请求,并将查询结果缓存到了 Redis。当线程 B 执行完成后,线程 A 继续向下执行更新了数据库,那么这时缓存中的数据为旧数据,与数据库中的值不一致

4、先更新数据库,再删除缓存

先更新数据库,再删除缓存也并不是绝对安全的。在高并发场景下,如果线程 A 发起读请求:查询一条在缓存中不存在的数据(这条数据有可能过期被删除了),查询数据库后在要将查询结果缓存到 Redis 时发生了阻塞。这个时候线程 B 发起了更新请求:先更新了数据库,再次删除了缓存。当线程 B 执行成功后,线程 A 继续向下执行,将查询结果缓存到了 Redis 中,那么此时缓存中的数据与数据库中的数据发生了不一致。

解决数据不一致方案

延时双删

延时双删:延时双删,即在写数据库之前删除一次,写完数据库后,再删除一次,在第二次删除时,并不是立即删除,而是等待一定时间在做删除

这个延时的功能可以使用 mq 来实现,这里为了省事,偷个懒,本地测试使用的延时队列来模拟 mq 达到延时效果。

1、定义一个队列元素对象 DoubleDeleteTask

@Data
public class DoubleDeleteTask implements Delayed {/*** 需要删除的key*/private String key;/*** 需要延迟的时间 毫秒*/private long time;public DoubleDeleteTask(String key, long time) {this.key = key;this.time = time;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(time - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed o) {return Long.compare(time, ((DoubleDeleteTask) o).time);}}

2、定义一个队列并交给 Spring 管理:

@Configuration
public class DoubleDeleteQueueConfig {@Bean(name = "doubleDeleteQueue")public DelayQueue<DoubleDeleteTask> doubleDeleteQueue() {return new DelayQueue<>();}}

3、设置一个独立线程,特意用来处理延时的任务:

@Slf4j
@Component
public class DoubleDeleteTaskRunner implements CommandLineRunner {@Resourceprivate DelayQueue<DoubleDeleteTask> doubleDeleteQueue;@Resourceprivate RedisTemplate<String, Object> redisTemplate;/*** 失败重试次数*/private static final int RETRY_COUNT = 3; @Overridepublic void run(String... args) {Runnable runnable = () -> {try{while (true) {DoubleDeleteTask doubleDeleteTask = doubleDeleteQueue.take();String key = doubleDeleteTask.getKey();try {redisTemplate.delete(key);log.info("====延时删除key:{}====", key);} catch (Exception e) {int count = 1;for (int i = 1; i <= RETRY_COUNT; i++) {if (count < RETRY_COUNT) {log.info("====延时删除key:{},失败重试次数:{}====", key, count);Boolean aBoolean = redisTemplate.delete(key);if (aBoolean) {break;} else {count++;}} else {break;}}}}} catch (Exception e) {e.printStackTrace();}};new Thread(runnable, "double-delete-task").start();}
}

如果数据删除失败,可以自定义重试次数以保证数据的一致性,但是也会带来一定的性能影响,如果在实际项目中,建议还是以异步的方式来实现重试。

4、使用延时队列,处理延时双删:

 @Autowired
private DelayQueue<DoubleDeleteTask> doubleDeleteTask;@PostMapping("/update")
public String update(@RequestBody User user) {String key = "user::" + user.getId();// 更新缓存redisUtil.set(key, JSON.toJSONString(user), 5);// 更新数据库userService.updateById(user);// 延迟删除缓存doubleDeleteTask.add(new DoubleDeleteTask(key, 2000L));return "update";
}

最后

在高并发的场景下,使用 Reids 还是存在很多坑的,稍不注意就会出现缓存穿透,缓存雪崩等情况,严重的话可以直接造成服务宕机。所以,在以后的开发中需要注意(如果项目没啥并发量的话,可以不用考虑)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/114358.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ansible-palybook学习

目录 一.playbook介绍二.playbook格式1.书写格式2.notify介绍 一.playbook介绍 playbook 是 ansible 用于配置&#xff0c;部署&#xff0c;和管理被控节点的剧本。通过 playbook 的详细描述&#xff0c;执行其中的一系列 tasks &#xff0c;可以让远端主机达到预期的状态。pl…

uniapp项目实战系列(3):底部导航栏与头部导航栏的配置

目录 系列往期文章&#xff08;点击跳转&#xff09;uniapp项目实战系列(1)&#xff1a;导入数据库&#xff0c;启动后端服务&#xff0c;开启代码托管&#xff08;点击跳转&#xff09;uniapp项目实战系列(2)&#xff1a;新建项目&#xff0c;项目搭建&#xff0c;微信开发工具…

Mac性能优化:深入了解WindowServer及其影响

文章目录 Mac性能优化:深入了解WindowServer及其影响WindowServer是什么?WindowServer为什么会占用那么多CPU?如何检查WindowServer是否使用了过多的CPU使用率?如何减少WindowServer的CPU使用率?Mac性能优化:深入了解WindowServer及其影响 大家好!今天我们来聊聊Mac上的…

【OJ比赛日历】快周末了,不来一场比赛吗? #09.03-09.09 #12场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-09-03&#xff08;周日&#xff09; #5场比赛2023-09-04…

函数(个人学习笔记黑马学习)

1、函数定义 #include <iostream> using namespace std;int add(int num1, int num2) {int sum num1 num2;return sum; }int main() {system("pause");return 0; } 2、函数的调用 #include <iostream> using namespace std;int add(int num1, int num2…

分布式锁实现一. 利用Mysql数据库update锁

文章目录 分布式锁1、什么是分布式锁&#xff1a;2、分布式锁应该具备哪些条件&#xff1a; 基于数据库的分布式锁代码传送代码运行 分布式锁 1、什么是分布式锁&#xff1a; 分布式锁&#xff0c;即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资源访问的问题…

常见的数据结构之队列

一、介绍 队列(Queue)是一种常见的数据结构,用于存储和管理一系列数据元素,其中元素按照 先进先出(First-In-First-Out,简称FIFO)的原则进行插入和删除。 队列可以类比为现实生活中排队等候的场景,例如在超市收银台排队购物的顾客队列。 二、队列的基本操作 2.1 出…

PHP8的箭头函数-PHP8知识详解

php 7.4 引入了箭头函数&#xff08;Arrow Functions&#xff09;&#xff0c;并在 PHP 8 中得到了进一步改进和扩展。 箭头函数是一种更简洁的匿名函数形式&#xff0c;它们提供了一种更便捷的方式来定义轻量级的、单行的回调函数。 箭头函数的语法如下&#xff1a; fn (参…

Docker拉取RocketMQ及可视化界面

本文介绍Docker拉取RocketMQ及可视化界面操作步骤 Linux下安装Docker请参考&#xff1a;Linux安装Docker 文章目录 安装namesrv创建挂载目录授权相关权限拉取镜像运行容器查看运行情况 安装Broker创建挂载目录及配置文件目录授权相关权限创建配置文件运行容器查看运行情况 安装…

2023年8月随笔之有顾忌了

1. 回头看 日更坚持了243天。 读《发布&#xff01;设计与部署稳定的分布式系统》终于更新完成 选读《SQL经典实例》也更新完成 读《高性能MySQL&#xff08;第4版&#xff09;》开更&#xff0c;但目前暂缓 读《SQL学习指南&#xff08;第3版&#xff09;》开更并持续更新…

KaiwuDB 助力能源企业实现 4 大价值提升

行业背景 近年来&#xff0c;随着能源行业数字化的不断推进&#xff0c;智能电网、可再生能源发电、分布式发电、微电网等技术蓬勃发展。越来越多的能源企业意识到数据管理与价值挖掘对储能及能源利用有着重大意义&#xff0c;并开始探索一套有效的数据库解决方案以应对分布式…

Redis 的混合持久化

RDB 相比于 AOF&#xff0c;数据恢复的速度更快&#xff0c;因为是二进制数据&#xff0c;直接加载进内存即可&#xff0c;但是 RDB 的频率不好把握。 如果频率太低&#xff0c;在两次快照期间服务器发生宕机&#xff0c;可能会丢失较多的数据如果频率太高&#xff0c;频繁写入…

Apipost:API文档、调试、Mock与测试的一体化协作平台

随着数字化转型的加速&#xff0c;API&#xff08;应用程序接口&#xff09;已经成为企业间沟通和数据交换的关键。而在API开发和管理过程中&#xff0c;API文档、调试、Mock和测试的协作显得尤为重要。Apipost正是这样一款一体化协作平台&#xff0c;旨在解决这些问题&#xf…

Linux以系统服务的方式启动Kafka(其他服务同理)

最终效果&#xff1a; 先回顾命令行的启动方式&#xff1a; kafka的启动 进入kafka的安装目录 1、首先启动zookeeper服务&#xff1a; bin/zookeeper-server-start.sh config/zookeeper.properties2、再启动kafka bin/kafka-server-start.sh config/server.properties &…

【附安装包】Substance3D 2022安装教程

软件下载 软件&#xff1a;Substance3D版本&#xff1a;2022语言&#xff1a;简体中文大小&#xff1a;4.0G安装环境&#xff1a;Win11/Win10&#xff08;1809版本以上&#xff09;硬件要求&#xff1a;CPU2.0GHz 内存4G(或更高&#xff0c;不支持7代以下CPU&#xff09;下载通…

智己 LS6 用实力和你卷,最强 800v ?

2023 成都车展期间&#xff0c;智己 LS6 正式公布预售价格&#xff0c;新车预售价为 23-30 万元。新车会在 10 月份进行上市&#xff0c;11 月正式交付。 此前我们对智己 LS6 做过非常详细的静态体验&#xff0c;感兴趣的可点击此链接了解。 造型方面&#xff0c;新车前脸相比…

农产品小程序商城搭建宝典

在当今的电子商务时代&#xff0c;农产品小程序商城已经成为了一种新型的电商模式&#xff0c;为许多农产品的生产和销售带来了新的机遇。但是&#xff0c;如何搭建一个功能完善、用户体验优秀的农产品小程序商城呢&#xff1f;下面&#xff0c;我们就来探讨一下。 首先&#x…

材料科学顶刊IF:29.4 |工程手段 干预细菌铁死亡

8月1日&#xff0c;凌恩生物客户四川大学邓怡及白丁等在《Advanced Materials》发表题为Engineered bio-heterojunction confers extra- and intracellular bacterial ferroptosis and hunger-triggered cell protection for diabetic wound repair的研究论文。该研究报道了一种…

ceph源码阅读 erasure-code

1、ceph纠删码 纠删码(Erasure Code)是比较流行的数据冗余的存储方法&#xff0c;将原始数据分成k个数据块(data chunk)&#xff0c;通过k个数据块计算出m个校验块(coding chunk)。把nkm个数据块保存在不同的节点&#xff0c;通过n中的任意k个块还原出原始数据。EC包含编码和解…

通过HTTP进行并发的数据抓取

在进行大规模数据抓取时&#xff0c;如何提高效率和稳定性是关键问题。本文将介绍一种可操作的方案——使用HTTP代理来实现并发的网页抓取&#xff0c;并帮助您加速数据抓取过程。 1. 选择合适的HTTP代理服务供应商 - 寻找信誉良好、稳定可靠且具备较快响应时间的HTTP代理服务…