Redis高并发缓存设计问题与性能优化

Redis高并发缓存设计问题与性能优化

  • 缓存设计典型问题
    • 缓存穿透
    • 缓存失效(击穿)
    • 缓存雪崩
    • 热点缓存key重建优化
    • 缓存与数据库双写不一致
  • 开发规范与性能优化
    • 一、键值设计
      • 1. key名设计
      • 2. value设计
      • big key的危害:
        • 1.导致redis阻塞
        • 2.网络拥塞
        • 3. 过期删除
      • big key的产生:
      • 如何优化big key
        • 1. 拆
        • 2. 如果bigkey不可避免 ,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要 hmget ,而不是hgetall) ,删除也是一样 ,尽量使用优雅的方式来处理。
    • 二、命令使用
      • 1.【推荐】 O(N)命令关注N的数量
      • 2.【推荐】:禁用命令
      • 3.【推荐】合理使用select
      • 4.【推荐】使用批量操作提高效率
      • 5.【建议】 Redis事务功能较弱 ,不建议过多使用 ,可以用lua替代

缓存设计典型问题

缓存穿透

缓存穿透是指查询一个根本不存在的数据 , 缓存层和存储层都不会命中 , 通常出于容错的考虑 , 如果从存储
层查不到数据则不写入缓存层。
缓存穿透将导致不存在的数据每次请求都要到存储层去查询 , 失去了缓存保护后端存储的意义。
造成缓存穿透的基本原因有两个:
第一 , 自身业务代码或者数据出现问题。
第二 , 一些恶意攻击、 爬虫等造成大量空命中。
缓存穿透问题解决方案:
1、缓存空对象

1 String get(String key) {
2 // 从缓存中获取数据
3 String cacheValue = cache.get(key);
4 // 缓存为空
5 if (StringUtils.isBlank(cacheValue)) {
6 // 从存储中获取
7 String storageValue = storage.get(key);
8 cache.set(key, storageValue);
9 // 如果存储数据为空, 需要设置一个过期时间(300秒)
10 if (storageValue == null) {
11 cache.expire(key, 60 * 5);
12 }
13 return storageValue;
14 } else {
15 // 缓存非空
16 return cacheValue;
17 }
18 }

2、布隆过滤器
对于恶意攻击 ,向服务器请求大量不存在的数据造成的缓存穿透 ,还可以用布隆过滤器先做一次过滤 ,对于不 存在的数据布隆过滤器一般都能够过滤掉 ,不让请求再往后端发送。当布隆过滤器说某个值存在时 ,这个值可 能不存在;当它说不存在时 ,那就肯定不存在。


向布隆过滤器询问 key 是否存在时 ,跟 add 一样 ,也会把 hash 的几个位置都算出来 ,看看位数组中这几个 位置是否都为 1 ,只要有一个位为 0 ,那么说明布隆过滤器中这个key 不存在。如果都是 1 ,这并不能说明这 个 key 就一定存在 ,只是极有可能存在 ,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位 数组比较稀疏 ,这个概率就会很大 ,如果这个位数组比较拥挤 ,这个概率就会降低。
这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景 , 代码维护较为 复杂 , 但是缓存空间占用很少
布隆过滤器就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得 比较均匀。
向布隆过滤器中添加 key 时 ,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度 进行取模运算得到一个位置 ,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就 完成了 add 操作。

可以用redisson实现布隆过滤器 ,引入依赖:

1
2 org.redisson 3 redisson
4 3.6.5
5

示例伪代码:
1 package com.redisson;
2
3 import org.redisson.Redisson;
4 import org.redisson.api.RBloomFilter;
5 import org.redisson.api.RedissonClient;
6 import org.redisson.config.Config;
7
8 public class RedissonBloomFilter {
9
10 public static void main(String[] args) {
11 Config config = new Config();
12 config.useSingleServer().setAddress(“redis://localhost:6379”);
13 //构造Redisson
14 RedissonClient redisson = Redisson.create(config);
15
16 RBloomFilter bloomFilter = redisson.getBloomFilter(“nameList”);
17 //初始化布隆过滤器:预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的bit数组大小

18 bloomFilter.tryInit(100000000L,0.03);
19 //将zhuge插入到布隆过滤器中
20 bloomFilter.add(“zhuge”);
21
22 //判断下面号码是否在布隆过滤器中
23 System.out.println(bloomFilter.contains(“guojia”));//false
24 System.out.println(bloomFilter.contains(“baiqi”));//false
25 System.out.println(bloomFilter.contains(“zhuge”));//true
26 }
27 }

使用布隆过滤器需要把所有数据提前放入布隆过滤器 ,并且在增加数据时也要往布隆过滤器里放 ,布隆过滤器 缓存过滤伪代码:
1 //初始化布隆过滤器
2 RBloomFilter bloomFilter = redisson.getBloomFilter(“nameList”);
3 //初始化布隆过滤器:预计元素为100000000L,误差率为3%
4 bloomFilter.tryInit(100000000L,0.03);
5
6 //把所有数据存入布隆过滤器
7 void init(){
8 for (String key: keys) {
9 bloomFilter.put(key);
10 }
11 }
12
13 String get(String key) {
14 // 从布隆过滤器这一级缓存判断下key是否存在
15 Boolean exist = bloomFilter.contains(key);
16 if(!exist){
17 return “”;
18 }
19 // 从缓存中获取数据
20 String cacheValue = cache.get(key);
21 // 缓存为空
22 if (StringUtils.isBlank(cacheValue)) {
23 // 从存储中获取
24 String storageValue = storage.get(key);
25 cache.set(key, storageValue);
26 // 如果存储数据为空, 需要设置一个过期时间(300秒)
27 if (storageValue == null) {
28 cache.expire(key, 60 * 5);
29 }
30 return storageValue;
31 } else {
32 // 缓存非空
33 return cacheValue;
34 }
35 }

注意:布隆过滤器不能删除数据 ,如果要删除得重新初始化数据。

缓存失效(击穿)

由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库 ,可能会造成数据库瞬间压力过大 甚至挂掉 ,对于这种情况我们在批量增加缓存时最好将这一批数据的缓存过期时间设置为一个时间段内的不同 时间。
示例伪代码:

1 String get(String key) {
2 // 从缓存中获取数据
3 String cacheValue = cache.get(key);
4 // 缓存为空
5 if (StringUtils.isBlank(cacheValue)) {
6 // 从存储中获取
7 String storageValue = storage.get(key);
8 cache.set(key, storageValue);
9 //设置一个过期时间(300到600之间的一个随机数)
10 int expireTime = new Random().nextInt(300) + 300;
11 if (storageValue == null) {
12 cache.expire(key, expireTime);
13 }
14 return storageValue;
15 } else {
16 // 缓存非空
17 return cacheValue;
18 }
19 }

缓存雪崩

缓存雪崩指的是缓存层支撑不住或宕掉后 , 流量会像奔逃的野牛一样 , 打向后端存储层。
由于缓存层承载着大量请求 , 有效地保护了存储层 , 但是如果缓存层由于某些原因不能提供服务(比如超大并 发过来 ,缓存层支撑不住 ,或者由于缓存设计不好 ,类似大量请求访问bigkey ,导致缓存能支撑的并发急剧下
降) , 于是大量请求都会打到存储层 , 存储层的调用量会暴增 , 造成存储层也会级联宕机的情况。 预防和解决缓存雪崩问题 , 可以从以下三个方面进行着手。
1) 保证缓存层服务高可用性 ,比如使用Redis Sentinel或Redis Cluster。
2) 依赖隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix限流降级组件。
比如服务降级 ,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据(例如电商商 品属性 ,用户信息等)时 ,暂时停止从缓存中查询这些数据 ,而是直接返回预定义的默认降级信息、空值或是 错误提示信息;当业务应用访问的是核心数据(例如电商商品库存)时 ,仍然允许查询缓存 ,如果缓存缺失 , 也可以继续通过数据库读取。
3) 提前演练。 在项目上线前 , 演练缓存层宕掉后 , 应用以及后端的负载情况以及可能出现的问题 , 在此基 础上做一些预案设定。

热点缓存key重建优化

开发人员使用“缓存+过期时间”的策略既可以加速数据读写 , 又保证数据的定期更新 , 这种模式基本能够
满足绝大部分需求。 但是有两个问题如果同时出现 , 可能就会对应用造成致命的危害:
当前key是一个热点key(例如一个热门的娱乐新闻) ,并发量非常大。
重建缓存不能在短时间完成 , 可能是一个复杂计算 , 例如复杂的SQL、 多次IO、 多个依赖等。 在缓存失效的瞬间 , 有大量线程来重建缓存 , 造成后端负载加大 , 甚至可能会让应用崩溃。
要解决这个问题主要就是要避免大量线程同时重建缓存。
我们可以利用互斥锁来解决 ,此方法只允许一个线程重建缓存 , 其他线程等待重建缓存的线程执行完 , 重新
从缓存获取数据即可。
示例伪代码:

1 String get(String key) {
2 // 从Redis中获取数据
3 String value = redis.get(key);
4 // 如果value为空, 则开始重构缓存
5 if (value == null) {
6 // 只允许一个线程重建缓存, 使用nx, 并设置过期时间ex
7 String mutexKey = “mutext🔑” + key;
8 if (redis.set(mutexKey, “1”, “ex 180”, “nx”)) {
9 // 从数据源获取数据
10 value = db.get(key);
11 // 回写Redis, 并设置过期时间
12 redis.setex(key, timeout, value);
13 // 删除key_mutex
14 redis.delete(mutexKey);
15 }// 其他线程休息50毫秒后重试
16 else {
17 Thread.sleep(50);
18 get(key);
19 }
20 }
21 return value;
22 }

缓存与数据库双写不一致

在大并发下 ,同时操作数据库与缓存会存在数据不一致性问题
1、双写不一致情况


2、读写并发不一致

解决方案:
1、对于并发几率很小的数据(如个人维度的订单数据、用户数据等) ,这种几乎不用考虑这个问题 ,很少会发生 缓存不一致 ,可以给缓存数据加上过期时间 ,每隔一段时间触发读的主动更新即可。
2、就算并发很高 ,如果业务上能容忍短时间的缓存数据不一致(如商品名称 ,商品分类菜单等) ,缓存加上过期 时间依然可以解决大部分业务对于缓存的要求。
3、如果不能容忍缓存数据不一致 ,可以通过加读写锁保证并发读写或写写的时候按顺序排好队 ,读读的时候 相当于无锁
4、也可以用阿里开源的canal通过监听数据库的bin log日志及时的去修改缓存 ,但是引入了新的中间件 ,增加 了系统的复杂度。


以业务名(或数据库名)为前缀(防止key冲突) ,用冒号分隔 ,比如业务名:表名:id


总结:
以上我们针对的都是读多写少的情况加入缓存提高性能 ,如果写多读多的情况又不能容忍缓存数据不一致 ,那 就没必要加缓存了 ,可以直接操作数据库。当然 ,如果数据库抗不住压力 ,还可以把缓存作为数据读写的主存 储 ,异步将数据同步到数据库 ,数据库只是作为数据的备份。
放入缓存的数据应该是对实时性、一致性要求不是很高的数据。切记不要为了用缓存 ,同时又要保证绝对的一 致性做大量的过度设计和控制 ,增加系统复杂性!

开发规范与性能优化

一、键值设计

1. key名设计

(1)【建议】: 可读性和可管理性

1 trade:order:1

(2)【建议】:简洁性
保证语义的前提下 ,控制key的长度 ,当key较多时, 内存占用也不容忽视 ,例如:

1 user:{uid}:friends:messages:{mid} 简化为 u:{uid}🇫🇷m:{mid}
(3)【强制】:不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符

2. value设计

(1)【强制】:拒绝bigkey(防止网卡流量、慢查询)
在Redis中 ,一个字符串最大512MB ,一个二级数据结构(例如hash、list、set、zset)可以存 储大约40亿个(2 ^32-1)个元素 ,但实际中如果下面两种情况 ,我就会认为它是bigkey。
1. 字符串类型:它的big体现在单个value值很大 ,一般认为超过10KB就是bigkey。 2. 非字符串类型:哈希、列表、集合、有序集合 ,它们的big体现在元素个数太多。
一般来说 ,string类型控制在10KB以内 ,hash、list、set、zset元素个数不要超过5000。 反例:一个包含200万个元素的list。

非字符串的bigkey ,不要使用del删除 ,使用hscan、sscan、zscan方式渐进式删除 ,同时要注 意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期 ,会触发del操作 , 造成阻塞)

big key的危害:

1.导致redis阻塞

2.网络拥塞

bigkey也就意味着每次获取要产生的网络流量较大 ,假设一个bigkey为1MB ,客户端每秒访问 量为1000 ,那么每秒产生1000MB的流量 ,对于普通的千兆网卡(按照字节算是128MB/s)的服务 器来说简直是灭顶之灾 ,而且一般服务器会采用单机多实例的方式来部署 ,也就是说一个bigkey
可能会对其他实例也造成影响 ,其后果不堪设想。

3. 过期删除

有个bigkey ,它安分守己(只执行简单的命令 ,例如hget、lpop、zscore等) ,但它设置了过 期时间 ,当它过期后 ,会被删除 ,如果没有使用Redis 4.0的过期异步删除(lazyfree-lazy-
expire yes) ,就会存在阻塞Redis的可能性。

big key的产生:

一般来说 ,bigkey的产生都是由于程序设计不当 ,或者对于数据规模预料不清楚造成的 ,来看几
个例子:
(1) 社交类:粉丝列表 ,如果某些明星或者大v不精心设计下 ,必是bigkey。
(2) 统计类:例如按天存储某项功能或者网站的用户集合 ,除非没几个人用 ,否则必是bigkey。 (3) 缓存类:将数据从数据库load出来序列化放到Redis里 ,这个方式非常常用 ,但有两个地方 需要注意 ,第一 ,是不是有必要把所有字段都缓存;第二 ,有没有相关关联的数据 ,有的同学为 了图方便把相关数据都存一个key下 ,产生bigkey。

如何优化big key

1. 拆

big list: list1、list2、…listN
big hash:可以讲数据分段存储 ,比如一个大的key ,假设存了1百万的用户数据 ,可以拆分成 200个key ,每个key下面存放5000个用户数据

2. 如果bigkey不可避免 ,也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要 hmget ,而不是hgetall) ,删除也是一样 ,尽量使用优雅的方式来处理。

(2)【推荐】:选择适合的数据类型。
例如:实体类型(要合理控制和使用数据结构 ,但也要注意节省内存和性能之间的平衡) 反例:

1 set user:1:name tom
2 set user:1:age 19
3 set user:1:favor football

但要注意控制一次批量操作的元素个数(例如500以内 ,实际也和元素字节数有关)。 注意两者不同:

正例:

1 hmset user:1 name tom age 19 favor football
3.【推荐】:控制key的生命周期 ,redis不是垃圾桶。
建议使用expire设置过期时间(条件允许可以打散过期时间 ,防止集中过期)。

二、命令使用

1.【推荐】 O(N)命令关注N的数量

例如hgetall、l range、smembers、zrange、s inter等并非不能使用 ,但是需要明确N的值。有 遍历的需求可以使用hscan、sscan、zscan代替。

2.【推荐】:禁用命令

禁止线上使用keys、flushall、flushdb等 ,通过redis的rename机制禁掉命令 ,或者使用scan的 方式渐进式处理。

3.【推荐】合理使用select

redis的多数据库较弱 ,使用数字进行区分 ,很多客户端支持较差 ,同时多业务用多数据库实际 还是单线程处理 ,会有干扰。

4.【推荐】使用批量操作提高效率

1 原生命令:例如mget、mset。
2 非原生命令:可以使用pipeline提高效率。

1 1. 原生命令是原子操作,pipeline是非原子操作。
2 2. pipeline可以打包不同的命令,原生命令做不到
3 3. pipeline需要客户端和服务端同时支持。

5.【建议】 Redis事务功能较弱 ,不建议过多使用 ,可以用lua替代

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

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

相关文章

电脑完全重装教程——原版系统镜像安装

注意事项 本教程会清除所有个人文件 请谨慎操作 请谨慎操作 请谨慎操作 前言 本教程是以系统安装U盘为介质进行系统重装操作,照着流程操作会清除整个硬盘里的文件,请考虑清楚哦~ 有些小伙伴可能随便在百度上找个WinPE作为启动盘就直接…

AI日报:2024年人工智能对各行业初创企业的影响

欢迎订阅专栏 《AI日报》 获取人工智能邻域最新资讯 文章目录 2024年人工智能对初创企业的影响具体行业医疗金融服务运输与物流等 新趋势 2024年人工智能对初创企业的影响 2023年见证了人工智能在各个行业的快速采用和创新。随着我们步入2024年,人工智能初创公司正…

反序列化版本漏洞

laravel5.7反序列化漏洞 <?phpnamespace Illuminate\Foundation\Testing {class PendingCommand{public $test;protected $app;protected $command;protected $parameters;public function __construct($test, $app, $command, $parameters){$this->test $test; …

5 分钟体验下一代轻量级日志分析系统Loki

公众号「架构成长指南」&#xff0c;专注于生产实践、云原生、分布式系统、大数据技术分享 Grafana Loki 是什么 Loki是由Grafana Labs开源的一个水平可扩展、高可用性&#xff0c;多租户的日志聚合系统的日志聚合系统。它的设计初衷是为了解决在大规模分布式系统中&#xff0…

matlab实践(十一):导弹追踪

1.题目 a9.94,x062.06 2.方程 我们有&#xff1a; ( d x d t ) 2 ( d y d t ) 2 w 2 (\frac{\mathrm d\mathrm x}{\mathrm d\mathrm t})^2(\frac{\mathrm d\mathrm y}{\mathrm d\mathrm t})^2\mathrm w^2 (dtdx​)2(dtdy​)2w2 还有导弹始终指向船 ( d x d t d y d t ) …

C语言:字符串字面量及其保存位置

相关阅读 C语言https://blog.csdn.net/weixin_45791458/category_12423166.html?spm1001.2014.3001.5482 虽然C语言中不存在字符串类型&#xff0c;但依然可以通过数组或指针的方式保存字符串&#xff0c;但字符串字面量却没有想象的这么简单&#xff0c;本文就将对此进行讨论…

MLX vs MPS vs CUDA:苹果新机器学习框架的基准测试

如果你是一个Mac用户和一个深度学习爱好者&#xff0c;你可能希望在某些时候Mac可以处理一些重型模型。苹果刚刚发布了MLX&#xff0c;一个在苹果芯片上高效运行机器学习模型的框架。 最近在PyTorch 1.12中引入MPS后端已经是一个大胆的步骤&#xff0c;但随着MLX的宣布&#x…

【EasyExcel实践】万能导出,一个接口导出多张表以及任意字段(可指定字段顺序)-简化升级版

文章目录 前言正文一、项目简介二、核心代码2.1 pom.xml 依赖配置2.2 ExcelHeadMapFactory2.3 ExcelDataLinkedHashMap2.4 自定义注解 ExcelExportBean2.5 自定义注解 ExcelColumnTitle2.6 建造器接口 Builder2.7 表格工具类 ExcelUtils2.8 GsonUtil2.9 模版类 ExportDynamicCo…

Opencv中的滤波器

一副图像通过滤波器得到另一张图像&#xff0c;其中滤波器又称为卷积核&#xff0c;滤波的过程称之为卷积。 这就是一个卷积的过程&#xff0c;通过一个卷积核得到另一张图片&#xff0c;明显发现新的到的图片边缘部分更加清晰了&#xff08;锐化&#xff09;。 上图就是一个卷…

java8实战 lambda表达式、函数式接口、方法引用双冒号(中)

前言 书接上文&#xff0c;上一篇博客讲到了lambda表达式的应用场景&#xff0c;本篇接着将java8实战第三章的总结。建议读者先看第一篇博客 其他函数式接口例子 上一篇有讲到Java API也有其他的函数式接口&#xff0c;书里也举了2个例子&#xff0c;一个是java.util.functi…

Ubuntu系统如何安装和卸载CUDA和CUDNN

背景 最近在学习PaddlePaddle在各个显卡驱动版本的安装和使用&#xff0c;所以同时也学习如何在Ubuntu安装和卸载CUDA和CUDNN&#xff0c;在学习过程中&#xff0c;顺便记录学习过程。在供大家学习的同时&#xff0c;也在加强自己的记忆。本文章以卸载CUDA 8.0 和 CUDNN 7.05 …

Docker 编译OpenHarmony 4.0 release

一、背景介绍 1.1、环境配置 编译环境&#xff1a;Ubuntu 20.04OpenHarmony版本&#xff1a;4.0 release平台设备&#xff1a;RK3568 OpenHarmony 3.2更新至OpenHarmony 4.0后&#xff0c;公司服务器无法编译通过&#xff0c;总是在最后几十个文件时报错,错误码4000&#xf…

Linux一行命令配置jdk环境

使用方法&#xff1a; 压缩包上传 到/opt, 更换命令中对应的jdk包名即可。 注意点&#xff1a;jdk-8u151-linux-x64.tar.gz 解压后名字是jdk1.8.0_151 sudo tar -zxvf jdk-8u151-linux-x64.tar.gz -C /opt && echo export JAVA_HOME/opt/jdk1.8.0_151 | sudo tee -a …

Diffusion扩散模型学习:图片高斯加噪

高斯分布即正态分布&#xff1b;图片高斯加噪即把图片矩阵每个值和一个高斯分布的矩阵上的对应值相加 1、高斯分布 np.random.normal 一维&#xff1a; import numpy as np import matplotlib.pyplot as pltdef generate_gaussian_noise(mean, std_dev, size):noise np.ran…

【CentOS 7.9 分区】挂载硬盘为LVM操作实例

LVM与标准分区有何区别&#xff0c;如何选择 目录 1 小系统使用LVM的益处&#xff1a;2 大系统使用LVM的益处&#xff1a;3 优点&#xff1a;CentOS 7.9 挂载硬盘为LVM操作实例查看硬盘情况格式化硬盘创建PV创建VG创建LV创建文件系统并挂载自动挂载添加&#xff1a;注意用空格间…

VSCode SSH 连接提示: spawn UNKNOWN

随笔记录 目录 1. 背景介绍 2. 确认问题 : ssh -V 3. 解决问题 3.1 确认本地 ssh.exe 路径 3.2 修改vscode Remote.ssh:Path 3.2.1 设置 Reomte.ssh:Path - 方法一 3.2.2 设置 Reomte.ssh:Path - 方法二 1. 背景介绍 windows 系统vscode ssh remote CentOS7&#xff…

【零基础入门Docker】什么是Dockerfile Syntax

✍面向读者&#xff1a;所有人 ✍所属专栏&#xff1a;零基础入门Docker专栏https://blog.csdn.net/arthas777/category_12455882.html 目录 编写Dockerfile和Format的语法 2. MAINTAINER 3. RUN 4. ADD 6. ENTRYPOINT 7. CMD 8. EXPOSE 9. VOLUME 11. USER 12. ARG …

.NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式

之前写过使用自定义返回类的方式来统一接口数据返回格式&#xff0c;.Net Core webapi RestFul 统一接口数据返回格式-CSDN博客 但是这存在一个问题&#xff0c;不是所有接口会按照定义的数据格式返回&#xff0c;除非每个接口都返回我们自定义的类&#xff0c;这种实现起来不…

Adobe InDesign各版本安装指南

下载链接 https://pan.baidu.com/s/1VWGKDUijTTETU9sVWFjCtg?pwd0531 #2024版本 1.鼠标右击【InCopy2024(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 InCopy2024(64bit)】。 2.打开解压后的文件夹&#xff0c;鼠标右击【Setup…

DevOps系列文章 : 使用dpkg命令打deb包

创建一个打包的目录&#xff0c;类似rpmbuild&#xff0c;这里创建了目录deb_build mkdir deb_build目标 我有一个hello的二进制文件hello和源码hello.c, 准备安装到/opt/helloworld目录中 步骤 在deb_build目录创建一个文件夹用于存放我的安装文件 mkdir helloworld在he…