当前位置: 首页 > news >正文

Redis队列自研组件

     背景

        年初的时候设计实践过一个课题:SpringBoot+Redis实现不重复消费的队列,并用它开发了一个年夜饭下单和制作的服务。不知道大家还有没有印象。完成这个课题后,我兴致勃勃的把它运用到了项目里面,可谁曾想,运行不久后运维人员就找到了我,说我的功能有问题,而且问题很大。具体表现在用户下单之后,很久都没有收到订单完成的消息,后台服务里的日志也是残缺的,只查到了开始制作年夜饭的日志,没有年夜饭制作过程和完成的日志。

        为此,我花了大量的时间和精力去分析程序,定位问题,最终发现,是运维人员在系统上线时将生产服务和UAT服务的redis服务地址给配成了同一个,这就导致生产上的订单进入Redis队列后,被UAT服务给消费了,而UAT和生产又是不同的数据库,自然导致UAT上通过队列中的主键在数据库中找不到相关数据,从而消费失败的结果。而这些失败的信息全都记录在了UAT的服务器上,生产服务器中自然很难分析和定位到问题。

        要解决这个问题其实很简单,只需要把channelTopic改为从配置文件中获取,然后生产、UAT环境配置不同的字符串即可。

        但实际上真的只做这些就够了吗?如果下次再发生不可预料的问题,我还要花那么多的时间吭哧吭哧的去看日志,调程序,定位问题吗?答案是否定的!

      原服务整改

        结合生产环境产生的问题,痛定思痛,我决定对原来的服务进行一轮大整改,优化服务的可维护性,可测试性。在我的构想中,新的服务需要有以下功能:

        1、保证原有的“年夜饭”功能稳定正常的运行。

        2、可以查询哪些订单还未开始处理

        3、可以查询哪些订单已经处理,以及处理结果。

        4、可以清空N天以前的处理成功的订单。

        5、可以清空待处理的订单

        6、对于已经处理但处理失败的订单,可以一键重新处理

        7、待处理订单插队

        我的设想是,通过redisTemplate.opsForZSet()方法创建两个新队列:待办队列和已办队列,在下单时,插入一条数据到待办队列,在处理任务时,从待办队列中删除该数据,在处理完成后,插入一条数据到已办队列,这样,通过查询待办队列,已办队列,就可以知道哪些任务还在排队,哪些任务已经完成了。

        以下是我的程序整改过程,老粉可以对比上一篇博客看看两者的不同之处。

#以下是application.yml配置server:port: 19200servlet:context-path: /leiximax-http-header-size: 102400
spring:redis:database: 9host: 127.0.0.1port: 6379password:jedis:pool:max-active: 8max-wait: -1max-idle: 8min-idle: 0
leixi:redis-queue-key: NEW_YEAR_DINNER_DEV
//以下是Java程序代码:
/*** Redis配置** @author leixiyueqi* @since 2024/06/15 22:00*/
@Configuration
public class RedisConfig {@Value("${leixi.redis-queue-key}")private String REDIS_QUEUE_KEY ;@Beanpublic ChannelTopic topic() {return new ChannelTopic(REDIS_QUEUE_KEY+ Constant.WORKING_QUEUE_SUFFIX);}@Beanpublic MessageListenerAdapter messageListenerAdapter(DinnerListener listener) {return new MessageListenerAdapter(listener);}@Beanpublic RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory,MessageListenerAdapter messageListenerAdapter,ChannelTopic topic) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(redisConnectionFactory);container.addMessageListener(messageListenerAdapter, topic);return container;}
}/*** 订单处理控制层* * @author leixiyueqi* @since 2024/06/15 22:00*/
@RestController
public class DinnerController {private int i = 0;@Autowiredprivate DinnerService service;@Value("${leixi.redis-queue-key}")private String REDIS_QUEUE_KEY ;@GetMapping("/orderDinner")public Object orderDinner() {OrderEntity entity = new OrderEntity();entity.setOrderCode("Order" + (++i));entity.setCustomerName("第"+i+"位客户");return service.orderNewYearEveDinner(entity);}@Autowiredprivate RedisTemplate<String, String> redisTemplate;@GetMapping("/getPendingOrder")public Object getPendingOrder() {return redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, 0, -1);}@GetMapping("/cleanPendingOrder")public Object cleanPendingOrder() {Set<String> set = redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, 0, -1);if(set.size() > 0) {redisTemplate.opsForZSet().remove(REDIS_QUEUE_KEY + Constant.PENDING_SUFFIX, set.toArray());}return "待处理订单已被清空!";}@GetMapping("/getHandledOrder")public Object getHandledOrder() {return redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, 0, -1);}@GetMapping("/cleanOldSucceedOrder")public Object cleanHandledOrder(@RequestParam("day") Integer day) {Set<String> set = redisTemplate.opsForZSet().rangeByScore(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, 0, Constant.getScoreByDate() - day);set.forEach(s -> {JSONObject obj = JSONObject.parseObject(s);if (obj.getString("result").equals("SUCCESS")) {redisTemplate.opsForZSet().remove(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, s);}});return redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, 0, System.currentTimeMillis());}/*** 这里还有最后一个问题, 把已办里的错误的信息摘除出来,重新走请求。并且反馈哪些信息重新走了请求。*/@GetMapping("/restartFailedOrder")public Object restartFailedOrder() {Set<String> set = redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, 0, -1);StringBuilder sb = new StringBuilder();set.forEach(s -> {JSONObject obj = JSONObject.parseObject(s);if (!obj.getString("result").equals("SUCCESS")) {redisTemplate.opsForZSet().remove(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, s);OrderEntity entity = JSON.parseObject(obj.getString("msg"), OrderEntity.class);service.orderNewYearEveDinner(entity);sb.append(entity.getOrderCode()).append(",");}});return "以下订单号被重启: "+ sb;}@GetMapping("/cutInLineJob")public Object cutInLineJob(@RequestParam("orderCode")  String orderCode) {Set<String> set = redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, 0, -1);for (String s : set) {OrderEntity obj = JSONObject.parseObject(s, OrderEntity.class);if (obj.getOrderCode().equals(orderCode)) {CompletableFuture.runAsync(() -> {service.doListenerWork(s);});return "订单 " + orderCode + " 插队成功!";}}return " 插队失败,该订单已经在制作了!";}
}/**** @author leixiyueqi* @since 2024/06/15 22:00*/
@Data
public class OrderEntity implements Serializable {/*** 客户姓名*/private String customerName;/*** 订单号*/private String orderCode;/*** 菜单*/List<String> menus;/*** 出餐状态*/private String dinnerState;/*** 做饭开始时间*/private String dinnerStartTime;/*** 做饭结束时间*/private String dinnerEndTime;/*** 备注*/private String remark;
}/*** 监听类*  * @author leixiyueqi* @since 2024/06/15 22:00*/
@Component
public class DinnerListener implements MessageListener {@Autowiredprivate DinnerService service;private final Object lock = new Object();@Overridepublic void onMessage(Message message, byte[] pattern)  {synchronized (lock) {service.doListenerWork(message.toString());}}
}/**** @author leixiyueqi* @since 2024/06/15 22:00*/
@Slf4j
@Service
public class DinnerService {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Value("${leixi.redis-queue-key}")private String REDIS_QUEUE_KEY ;/*** 年夜饭下单** @param req 订单信息* @return*/public Object orderNewYearEveDinner(OrderEntity req) {// 存储订单信息saveOrder(req);// 异步开始做菜redisTemplate.delete(JSON.toJSONString(req));redisTemplate.opsForZSet().add(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, JSON.toJSONString(req), Constant.getScoreByDate());redisTemplate.convertAndSend(REDIS_QUEUE_KEY+ Constant.WORKING_QUEUE_SUFFIX, JSON.toJSONString(req));return "您已成功下单,订单号为"+ req.getOrderCode()+",后厨正在准备预制菜!";}/*** 这里模拟的是做年夜饭的过程方法,该方法用时较长,整个过程需要10秒,但是,这个过程中存在多种意外,该方法可能失败** @param req 订单信息*/public void doNewYearEveDinner(OrderEntity req) throws Exception {System.out.println("开始做订单 " + req.getOrderCode() + " 的年夜饭");Thread.sleep(10000);// 这里写个方法模拟报错的场景int i = new Random().nextInt(6) + 1;if (i ==4) {throw new Exception("厨师跑了");}if (i ==5) {throw new Exception("食物跑了");}if (i ==6) {throw new Exception("厨房着火了");}System.out.println("订单 " + req.getOrderCode() + " 的年夜饭已经完成");}private void saveOrder(OrderEntity req) {//这里假设做的是订单入库操作System.out.println("订单 " + req.getOrderCode() + " 已经入库, 做饭开始时间为 "+ new Date());}/*** 根据订单编号修改订单信息** @param orderCode 订单编号* @param dinnerStatus* @param remark*/public void updateOrder(String orderCode, String dinnerStatus, String remark) {// 根据订单编号修改订单的出餐结束时间,出餐状态,备注等信息。System.out.println("更新订单 "+ orderCode +" 信息,做饭结束时间为 "+ new Date() + ", 出餐状态为"+ dinnerStatus +", 备注为 " +remark);}public void doListenerWork(String message) {Boolean flag = redisTemplate.opsForValue().setIfAbsent(message, "1", 1, TimeUnit.DAYS);// 加锁失败,已有消费端在此时对此消息进行处理,这里不再做处理if (!flag) {return;}redisTemplate.opsForZSet().remove(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, message);OrderEntity param = CastUtils.cast(JSON.parseObject(message, OrderEntity.class));JSONObject obj = new JSONObject();obj.put("msg", message);try {obj.put("server", InetAddress.getLocalHost().getHostAddress());this.doNewYearEveDinner(param);this.updateOrder(param.getOrderCode(), "SUCCESS", "成功");obj.put("result", "SUCCESS");}catch (Exception e) {e.printStackTrace();this.updateOrder(param.getOrderCode(), "FAIL", e.getMessage());obj.put("result", "FAIL");obj.put("desc", e.getMessage());}finally {obj.put("endTime", new Date());redisTemplate.opsForZSet().add(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, obj.toJSONString(), Constant.getScoreByDate());}}/*** 静态工具类* * @author leixiyueqi* @since 2024/06/15 22:00*/
public class Constant {// 工作队列后缀public static final String WORKING_QUEUE_SUFFIX = "_QUEUE";//待处理工作队列后缀public static final String PENDING_SUFFIX = "_PENDING";// 已处理工作后缀public static final String HANDLED_SUFFIX = "_HANDLED";//一天的毫秒数private static final Integer ONE_DAY_MINI = 86400000;/*** 根据当前日期计算队列的分数** @return*/public static Integer getScoreByDate() {return (int)System.currentTimeMillis()/ONE_DAY_MINI;}
}

      接口测试

        1、年夜饭下单

        

        2、查询待处理订单

        

        3、查询已处理订单

        

        4、清空已处理且成功的订单

        

        5、清空待处理订单

        

        6、一键重启处理失败的订单

        

        7、订单插队

        

      组件化封装

        完成了以上测试,基本上我想要的功能都已经实现了。但是仔细想了下,上述的功能里除了第一个下单接口是跟业务相关的,剩下的所有接口都是业务无关的。如果我们公司主营业务变了,从年夜饭变成中秋做月饼,端午包棕子,本服务中的大部分代码都可以在调整之后复用。那么,为什么我不整理出一个与业务无关的Redis队列工具出来呢,这样可以极大的提升代码的可复用性。后面有新的业务时,直接引入这个工具包,完善业务部分即可。

        以下是我在反思之后,对代码的整改(只包含有整改或新增的代码)

/*** 消息承载类** @author leixiyueqi* @since 2024/06/15 22:00*/
@Data
public class RedisQueueMsg<T> implements Serializable {/*** 消息Id*/private String id;/*** 服务名*/private String serverName;/*** 数据体*/private T data;
}package com.leixi.queue.pojo;import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** 任务处理结果封装类** @author leixiyueqi* @since 2024/06/15 22:00*/
@Data
public class RedisResultVo implements Serializable {private String status;private Object data;private String desc;private Date startTime;private Date endTime;private String server;public RedisResultVo() {this.startTime = new Date();}public RedisResultVo(Object data) {this.data = data;this.startTime = new Date();}
}/*** 抽象的业务处理服务** @author leixiyueqi* @since 2024/06/15 22:00*/
public abstract class QueueBusiBasicService {/*** 处理任务的方法** @param obj 业务类*/public abstract void handle(Object obj);/*** 处理失败的回调方法** @param obj 业务类* @param e*/public abstract void callBack(Object obj, Exception e);
}/*** Redis队列的服务层** @author leixiyueqi* @since 2024/06/15 22:00*/
@Slf4j
@Service
public class QueueCommonService {@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Autowiredprivate Map<String, QueueBusiBasicService> serviceMap;@Value("${leixi.redis-queue-key}")private String REDIS_QUEUE_KEY ;/*** 插入消息到队列** @param obj 业务对象* @param serverName  服务名* @return*/public RedisQueueMsg sendMessage(Object obj, String serverName) {RedisQueueMsg msg = new RedisQueueMsg();msg.setId(IdUtil.fastSimpleUUID());msg.setServerName(serverName);msg.setData(obj);redisTemplate.delete(JSON.toJSONString(msg));redisTemplate.opsForZSet().add(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, JSON.toJSONString(msg), Constant.getScoreByDate());redisTemplate.convertAndSend(REDIS_QUEUE_KEY+ Constant.WORKING_QUEUE_SUFFIX, JSON.toJSONString(msg));return msg;}/*** 处理队列中的工作** @param message*/public void handle(String message) {Boolean flag = redisTemplate.opsForValue().setIfAbsent(message, "1", 1, TimeUnit.DAYS);// 加锁失败,已有消费端在此时对此消息进行处理,这里不再做处理if (!flag) {return;}redisTemplate.opsForZSet().remove(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, message);RedisQueueMsg param = CastUtils.cast(JSON.parseObject(message, RedisQueueMsg.class));RedisResultVo result = new RedisResultVo(param);try {result.setServer(InetAddress.getLocalHost().getHostAddress());serviceMap.get(param.getServerName()).handle(param.getData());result.setStatus(Constant.SUCCESS);}catch (Exception e) {e.printStackTrace();serviceMap.get(param.getServerName()).callBack(param.getData(), e);result.setStatus(Constant.FAIL);result.setDesc(e.getMessage());}finally {result.setEndTime(new Date());redisTemplate.opsForZSet().add(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, JSON.toJSONString(result), Constant.getScoreByDate());}}/*** 查询待处理任务** @return*/public Object getPendingTask() {return redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, 0, -1);}/*** 清理待处理任务** @return*/public Object cleanPendingTask() {Set<String> set = redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, 0, -1);if(set.size() > 0) {redisTemplate.opsForZSet().remove(REDIS_QUEUE_KEY + Constant.PENDING_SUFFIX, set.toArray());}return "待处理任务已被清空!";}/*** 查询已处理任务** @return*/public Object getHandledTask() {return redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, 0, -1);}/*** 清理某天前的处理任务** @param day 天数* @return*/public Object cleanHandledTask(Integer day) {Set<String> set = redisTemplate.opsForZSet().rangeByScore(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, 0, Constant.getScoreByDate() - day);set.forEach(s -> {RedisResultVo obj = JSONObject.parseObject(s, RedisResultVo.class);if (obj.getStatus().equals(Constant.SUCCESS)) {redisTemplate.opsForZSet().remove(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, s);}});return redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, 0, System.currentTimeMillis());}/*** 重新处理已处理任务*/public String restartFailedTask() {Set<String> set = redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, 0, -1);StringBuilder sb = new StringBuilder();set.forEach(s -> {RedisResultVo obj = JSONObject.parseObject(s, RedisResultVo.class);if (!obj.getStatus().equals(Constant.SUCCESS)) {redisTemplate.opsForZSet().remove(REDIS_QUEUE_KEY+Constant.HANDLED_SUFFIX, s);RedisQueueMsg msg = JSON.parseObject(JSON.toJSONString(obj.getData()), RedisQueueMsg.class); ;sendMessage(msg.getData(), msg.getServerName());sb.append(msg.getId()).append(",");}});return "以下任务被重启: "+ sb;}/*** 任务插队** @param msgId 要插队的消息ID*/public RedisQueueMsg cutInLineTask(String msgId) {Set<String> set = redisTemplate.opsForZSet().range(REDIS_QUEUE_KEY+ Constant.PENDING_SUFFIX, 0, -1);for (String s : set) {RedisQueueMsg msg = JSONObject.parseObject(s, RedisQueueMsg.class);if (msg.getId().equals(msgId)) {CompletableFuture.runAsync(() -> {this.handle(s);});return msg;}}throw new RuntimeException("未找到相关任务,该项任务已经在执行了!");}
}/*** 业务服务类,继承抽象类务类,实现业务逻辑** @author leixiyueqi* @since 2024/06/15 22:00*/
@Service(Constant.DINNER_SERVER)
public class QueueDinnerService extends QueueBusiBasicService {@Autowiredprivate QueueCommonService queueCommonService;/*** 年夜饭下单** @param entity 订单信息* @return*/public Object orderNewYearEveDinner(OrderEntity entity) {// 存储订单信息saveOrder(entity);queueCommonService.sendMessage(entity, Constant.DINNER_SERVER);// 异步开始做菜return "您已成功下单,订单号为"+ entity.getOrderCode()+",后厨正在准备预制菜!";}/*** 这里模拟的是做年夜饭的过程方法,该方法用时较长,整个过程需要10秒,但是,这个过程中存在多种意外,该方法可能失败** @param req 订单信息*/private void doNewYearEveDinner(OrderEntity req) throws Exception {System.out.println("开始做订单 " + req.getOrderCode() + " 的年夜饭");Thread.sleep(10000);int i = new Random().nextInt(6) + 1;if (i ==4) {throw new Exception("厨师跑了");}if (i ==5) {throw new Exception("食物跑了");}if (i ==6) {throw new Exception("厨房着火了");}System.out.println("订单 " + req.getOrderCode() + " 的年夜饭已经完成");}/*** 保存订单信息** @param req*/private void saveOrder(OrderEntity req) {//这里假设做的是订单入库操作System.out.println("订单 " + req.getOrderCode() + " 已经入库, 做饭开始时间为 "+ new Date());}/*** 根据订单编号修改订单信息** @param orderCode 订单编号* @param dinnerStatus* @param remark*/private void updateOrder(String orderCode, String dinnerStatus, String remark) {// 根据订单编号修改订单的出餐结束时间,出餐状态,备注等信息。System.out.println("更新订单 "+ orderCode +" 信息,做饭结束时间为 "+ new Date() + ", 出餐状态为"+ dinnerStatus +", 备注为 " +remark);}/*** 处理订单** @param obj 业务类*/@Override@SneakyThrowspublic void handle(Object obj) {OrderEntity entity = JSON.parseObject(JSON.toJSONString(obj), OrderEntity.class);doNewYearEveDinner(entity);updateOrder(entity.getOrderCode(), Constant.SUCCESS, "出餐成功");}@Overridepublic void callBack(Object obj, Exception e) {OrderEntity entity = JSON.parseObject(JSON.toJSONString(obj), OrderEntity.class);System.out.println("更新订单 "+ entity.getOrderCode() +" 信息,做饭结束时间为 "+ new Date() + ", 出餐状态为FAIL, 原因为 " +e.getMessage());}
}@Component
public class RedisQueueListener implements MessageListener {@Autowiredprivate QueueCommonService service;private final Object lock = new Object();@Overridepublic void onMessage(Message message, byte[] pattern)  {synchronized (lock) {service.handle(message.toString());}}
}/*** 业务控制层** @author leixiyueqi* @since 2024/06/15 22:00*/
@RestController
@RequestMapping("/dinner")
public class DinnerController {@Autowiredprivate QueueDinnerService dinnerService;private int i = 0;@GetMapping("/orderDinner")public Object orderDinner() {OrderEntity entity = new OrderEntity();entity.setOrderCode("Order" + (++i));entity.setCustomerName("第"+i+"位客户");return dinnerService.orderNewYearEveDinner(entity);}}/*** Redis工具控制器** @author leixiyueqi* @since 2024/06/15 22:00*/
@RestController
@RequestMapping("/redisQueue")
public class RedisQueueController {@Autowiredprivate QueueCommonService service;@GetMapping("/getPendingTask")public Object getPendingTask() {return service.getPendingTask();}@GetMapping("/cleanPendingTask")public Object cleanPendingTask() {return service.cleanPendingTask();}@GetMapping("/getHandledTask")public Object getHandledTask() {return service.getHandledTask();}@GetMapping("/cleanOldSucceedTask")public Object cleanHandledTask(@RequestParam("day") Integer day) {return service.cleanHandledTask(day);}/*** 这里还有最后一个问题, 把已办里的错误的信息摘除出来,重新走请求。并且反馈哪些信息重新走了请求。*/@GetMapping("/restartFailedTask")public Object restartFailedTask() {service.restartFailedTask();return "重启失败的服务成功";}@GetMapping("/cutInLineTask")public Object cutInLineTask(@RequestParam("msgId")  String msgId) {RedisQueueMsg msg = service.cutInLineTask(msgId);return "任务 "+ JSON.toJSONString(msg) + "插队成功!";}}

        组件化的调整就是把属于Redis队列的操作与业务类操作完全分开,这样,以后有别的业务需要引入组件处理时,只需要写个业务服务继承QueueBusiBasicService即可,最大限度的复用了队列的这套机制和代码。

        注意,本组件有它特定的适用场景:处理任务的频度不高,每次处理任务用时较长,而且任务有一定的小概率失败,失败之后重新处理不会影响最终处理结果。

        完成这个工具研发后,我结合之前在网上查到的Redis队列的一些案例,发现用别的方案可以更简单的去实现我要的效果,比如直接用Redis队列详解(springboot实战)里的方案,仅仅是因为不想在代码里写 while(true) 这种不是优雅的代码,再加上用Listener的方式长时间没对消息进行消费时,消息会丢失,因此才额外花费了这么多的功夫来打补丁。果然方向选错了,工作量会成倍的增加,希望看到本文的读者能引以为戒,不要自误啊。

相关文章:

Redis队列自研组件

背景 年初的时候设计实践过一个课题&#xff1a;SpringBootRedis实现不重复消费的队列&#xff0c;并用它开发了一个年夜饭下单和制作的服务。不知道大家还有没有印象。完成这个课题后&#xff0c;我兴致勃勃的把它运用到了项目里面&#xff0c;可谁曾想&#xff0c;运行不久后…...

ArchLinux挑战安装(ZFS、Wayland、KDE、xero)

目录 0. 前言&#xff1a; 1. 先期准备 1.1 引导ArchLinx光盘。 1.2 禁用 reflector 服务 1.3 防止网卡禁用 1.4 wifi设置 1.5 测试网络是否连接 1.6 更新系统时间 1.7 更换源 1.8 下载ZFS模块 1.9 加载ZFS模块 2. 磁盘处理 2.1 查看磁盘分区 2.2 清除与整个磁盘…...

纯css写一个动态圣诞老人

效果预览 在这篇文章中&#xff0c;我们将学习如何使用CSS来创建一个生动的圣诞老人动画。通过CSS的魔力&#xff0c;我们可以让圣诞老人在网页上摇摆&#xff0c;仿佛在向我们招手庆祝圣诞节和新年。 实现思路 实现这个效果的关键在于CSS的keyframes动画规则以及各种CSS属性…...

百度Apollo的PublicRoadPlanner一些移植Ros2-foxy的思路(持续更新)

如今的PublicRoadPlanner就是之前耳熟能详的EM planner 计划 —— ROS2与CARLA联合仿真 结构化场景: 规划算法:EM-planner 控制算法:MPC和PID 非结构化场景: 规划算法采用Hybrid A* (1)小车模型搭建(计划参考Github上Hybrid上的黑车,比较炫酷) (2)车辆里程计: 位…...

Linux内存管理(七十三):cgroup v2 简介

版本基于: Linux-6.6 约定: 芯片架构:ARM64内存架构:UMACONFIG_ARM64_VA_BITS:39CONFIG_ARM64_PAGE_SHIFT:12CONFIG_PGTABLE_LEVELS :31. cgroup 简介 术语: cgroup:control group 的缩写,永不大写(never capitalized); 单数形式的 cgroup 用于指定整个特性,也用…...

c++习题01-ljc的暑期兼职

目录 一&#xff0c;题目描述 二&#xff0c;思路 三&#xff0c;伪代码 四&#xff0c;流程图 五&#xff0c;代码 一&#xff0c;题目描述 二&#xff0c;思路 1&#xff0c;根据题目要求需要声明4个变量&#xff1a;a,b,c,d ;牛奶价格a&#xff0c;活动要求b&…...

有哪些方法可以恢复ios15不小心删除的照片?

ios15怎么恢复删除的照片&#xff1f;在手机相册里意外删除了重要的照片&#xff1f;别担心&#xff01;本文将为你介绍如何在iOS 15系统中恢复已删除的照片。无需专业知识&#xff0c;只需要按照以下步骤操作&#xff0c;你就能轻松找回宝贵的回忆。 一、从iCloud云端恢复删除…...

nacos漏洞汇总

1 nacos介绍 1.1 nacos是啥 Alibaba Nacos是阿里巴巴推出来的一个新开源项目&#xff0c;是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。致力于帮助发现、配置和管理微服务。Nacos提供了一组简单易用的特性集&#xff0c;可以快速实现动态服务发现、服…...

React Antd ProTable 如何设置类似于Excel的筛选框

React Antd ProTable 如何设置类似于Excel的筛选框 目标&#xff1a;在web页面的table表格中完成类似于EXCEL的Filter筛选功能。 示例图&#xff1a;点击标题列上方的漏斗状图标&#xff0c;即可对数据进行筛选。 ProTable 前景提要 ProTable API中有说明&#xff0c;是有…...

句法分析概述

第1关&#xff1a;句法分析概述 任务描述 本关任务&#xff1a;通过对句法分析基本概念的学习&#xff0c;完成相应的选择题。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 句法分析的基础概念&#xff1b; 句法分析的数据集和评测方法。 句法分析简介…...

简单了解css的基本使用

CSS 一、基础认知 1、CSS引入方式 1.1、内嵌式&#xff08;CSS写在style标签中&#xff09; style标签虽然可以写在页面的任意位置&#xff0c;但是通常约定写在head标签中 2.2、外联式&#xff08;CSS写在一个单独的.css文件中&#xff09; 需要通过link标签在网页中引入…...

构建网络图 (JavaScript)

前序&#xff1a;在工作中难免有一些千奇百怪的需求&#xff0c;如果你遇到构建网络图&#xff0c;或者学习应对未来&#xff0c;请看这边文章&#xff0c;本文以代码为主。 网络图是数据可视化中实用而有效的工具&#xff0c;特别适用于说明复杂系统内的关系和连接。这些图表…...

洛谷U389682 最大公约数合并

这道题最后有一个性质没有想出来&#xff0c;感觉还是有一点遗憾。 性质一、贪心是不对的 8 11 11 16虽然第一次选择8和16合并是最优的&#xff0c;但是如果合并两次的话8 11 11是最优的。 性质二 、有1的情况就是前k1个&#xff0c;也就是说&#xff0c;很多情况下取前k1都…...

video_多个m3u文件合并成一个m3u文件

主要是用#EXT-X-DISCONTINUITY进行拼接,用简单的例子说明: 第一个文件: #EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:69 #EXT-X-MEDIA-SEQUENCE:1001 #EXTINF:60.000000, xmt202406_11001.ts #EXTINF:60.000000, xmt202406_11002.ts #EXTINF:60.000000, xmt202406_11…...

x264 码率控制 MBtree 原理:i_propagate_cost计算过程

x264 码率控制 MBtree 原理 关于x264 码率控制中 MBtree 算法的原理具体可以参考:x264 码率控制MBtree原理。 i_propagate_cost介绍 该值在 frame.h 中 x264_frame_t结构体中声明。该值是一个 uint16_t型指针变量,在 MBtree 算法中用来存储每个宏块的传播代价。在*frame_ne…...

C语言基础笔记(全)

一、数据类型 数据的输入输出 1.数据类型 常量变量 1.1 数据类型 1.2 常量 程序运行中值不发生变化的量&#xff0c;常量又可分为整型、实型(也称浮点型)、字符型和字符串型 1.3 变量 变量代表内存中具有特定属性的存储单元&#xff0c;用来存放数据&#xff0c;即变量的值&a…...

通过注释语句,简化实体类的定义(省略get/set/toString的方法)

引用Java的lombok库&#xff0c;减少模板代码&#xff0c;如getters、setters、构造函数、toString、equals和hashCode方法等 import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;Data NoArgsConstructor AllArgsConstructorData&#xf…...

springboot框架使用Netty依赖中解码器的作用及实现详解

在项目开发 有需求 需要跟硬件通信 也没有mqtt 作为桥接 也不能http 请求 api 所以也不能 json字符串这么爽传输 所以要用tcp 请求 进行数据交互 数据还是16进制的 写法 有帧头 什么的 对于这种物联网的这种对接 我的理解就是 我们做的工作就像翻译 把这些看不懂的 字节流 变成…...

Python爬虫实战之爬取京东商品数据

在数字化时代&#xff0c;数据如同黄金般珍贵&#xff0c;而电商数据&#xff0c;尤其是像京东这样的大型电商平台上的信息&#xff0c;更是商家、市场分析师和数据科学家眼中的瑰宝。本文将带您走进Python爬虫的世界&#xff0c;探索如何高效、合法地采集京东商品数据&#xf…...

浅析Resource Quota中limits计算机制

前言 在生产环境中&#xff0c;通常需要通过配置资源配额&#xff08;Resource Quota&#xff09;来限制一个命名空间&#xff08;namespace&#xff09;能使用的资源量。在资源紧张的情况下&#xff0c;常常需要调整工作负载&#xff08;workload&#xff09;的请求值&#xf…...

《数据结构与算法基础 by王卓老师》学习笔记——1.4算法与算法分析

一、算法 1.1算法的研究内容 1.2算法的定义 1.3算法的描述 以下是算法的自然语言描述 以下是算法的传统流程图表示 以下是NS流程图表示 1.4算法和程序的区别与联系 1.5算法的五个特性 1.6算法设计的要求 Robustness也称为鲁棒性 二、算法分析 2.1算法时间效率的度量 2.1.1事…...

运维团队如何加强安全设备监控与日志管理

随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显&#xff0c;安全设备的监控和日志管理成为了运维团队不可或缺的工作内容。本文将结合运维行业的实际需求&#xff0c;探讨如何加强安全设备监控与日志管理&#xff0c;以提升系统的安全性和稳定性。 一、安全设备监控…...

仓库管理系统13--物资设置

1、添加窗体 2、设计UI界面 注意这个下拉框的绑定&#xff0c;你看到的选项是由displaymember决定&#xff0c;当你选择了哪个选项时&#xff0c;后台绑定这个选项的ID <UserControl x:Class"West.StoreMgr.View.GoodsView"xmlns"http://schemas.microsoft…...

机器人控制系列教程之URDF文件语法介绍

前两期推文&#xff1a;机器人控制系列教程之动力学建模(1)、机器人控制系列教程之动力学建模(2)&#xff0c;我们主要从数学的角度介绍了机器人的动力学建模的方式&#xff0c;随着机器人技术的不断发展&#xff0c;机器人建模成为了机器人系统设计中的一项关键任务。URDF&…...

Arathi Basin (AB) PVP15

Arathi Basin &#xff08;AB&#xff09; PVP15 阿拉希盆地&#xff0c;PVP&#xff0c;15人战场...

Ubuntu/Linux SSH 端口转发

文章目录 Ubuntu/Linux SSH 端口转发概述本地端口转发场景一场景二 参考资料 Ubuntu/Linux SSH 端口转发 概述 SSH, Secure Shell 是一种在网络上用于安全远程登录到另一台机器的工具。除了远程登录以外&#xff0c;ssh 的端口转发是它的另一项强大功能。通过 ssh 端口转发功…...

flask的locked_cached_property

下面是一个关于 locked_cached_property 装饰器的详细教程。这个装饰器将一个方法转换为一个惰性属性&#xff0c;在第一次访问时计算其值&#xff0c;并在随后的访问中缓存该值。同时&#xff0c;它在多线程环境中是线程安全的。 教程&#xff1a;理解和使用 locked_cached_p…...

OSI七层模型TCP/IP四层面试高频考点

OSI七层模型&TCP/IP四层&面试高频考点 1 OSI七层模型 1. 物理层&#xff1a;透明地传输比特流 在物理媒介上传输原始比特流&#xff0c;定义了连接主机的硬件设备和传输媒介的规范。它确保比特流能够在网络中准确地传输&#xff0c;例如通过以太网、光纤和无线电波等媒…...

Swagger2及常用校验注释说明

Api(value "后台用户管理") RestController RequestMapping("bossuser") public class BossUserController {ApiOperation(value "测试接口")PostMapping("test")public String testUser(Valid RequestBody TestUser user) {LOG.inf…...

【项目实训】各种反爬策略及爬虫困难点总结

在这里&#xff0c;我总结了本次项目的数据收集过程中遇到的反爬虫策略以及一些爬虫过程中容易出现问题的地方。 user-agent 简单的设置user-agent头部为浏览器即可&#xff1a; 爬取标签中带href属性的网页 对于显示岗位列表的页面&#xff0c;通常检查其源代码就会发现&…...

能量智慧流转:全面升级储能电站的智能网关解决方案

监控系统是电化学储能电站的关键组成部分&#xff0c;储能电站也需要相应的监控系统&#xff0c;通过监控系统对储能设备的状态进行监测&#xff0c;实时感知储能设备的健康状态&#xff0c;控制储能设备的充放电功率和时机等&#xff0c; 一个好的监控系统可以实现储能电站安全…...

【金融研究】6月,对冲基金狂卖美国科技股 短期乐观,长期悲观?“油价最大空头”花旗:明年跌到60

科技股新高的背后&#xff0c;是对冲基金与散户投资者的分歧&#xff0c;对冲基金正在向散户投资者出售创纪录数量的科技/半导体/美股“七姐妹”股票。 对冲基金狂卖美国科技股 在五大明星科技股&#xff08;苹果、亚马逊、微软、英伟达、谷歌&#xff09;轮番创下历史新高的…...

GroundingDINO1.5突破开放式物体检测界限:介绍与应用

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…...

centos编译内核ko模块

1、make报错 make: * /lib/modules/4.14.0-49.el7a.aarch64/build: 没有那个文件或目录。 停止。 [rootlocalhost 4.14.0-49.el7a.aarch64]# pwd /lib/modules/4.14.0-49.el7a.aarch64 [rootlocalhost 4.14.0-49.el7a.aarch64]# ll 总用量 1744 lrwxrwxrwx. 1 root root …...

Android13 WMS窗口层级树

1&#xff0c;认识层级树 可以通过dumpsys activity containers 看到 WMS 层级树的结构 ACTIVITY MANAGER CONTAINERS (dumpsys activity containers) ROOT typeundefined modefullscreen override-modeundefined requested-bounds[0,0][0,0] bounds[0,0][1440,2960]#0 Displa…...

计算机毕业设计Python+LSTM+Tensorflow股票分析预测 基金分析预测 股票爬虫 大数据毕业设计 深度学习 机器学习 数据可视化 人工智能

基于TensorFlow-LSTM的股票预测系统开题报告 一、研究背景与意义 随着信息技术的飞速发展&#xff0c;股票市场作为现代经济活动的重要组成部分&#xff0c;其价格波动受到广泛关注。投资者们迫切希望通过科学的方法预测股票价格&#xff0c;以优化投资决策&#xff0c;实现利…...

仓库管理系统14--仓库设置

1、添加窗体 <UserControl x:Class"West.StoreMgr.View.StoreView"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc"http://schemas.openxmlformats.…...

Python 算法交易实验73 QTV200第二步: 数据清洗并写入ClickHouse

说明 先检查一下昨天启动的worker是否正常工作&#xff0c;然后做一些简单的清洗&#xff0c;存入clickhouse。 内容 1 检查数据 from Basefuncs import * # 将一般字符串转为UCS 名称 def dt_str2ucs_blockname(some_dt_str):some_dt_str1 some_dt_str.replace(-,.).re…...

记录:有趣的C#多元运算符 ? : 表达式写法

有时候用 if //...Whatre you she wanna go else if //...do do do else //...and i know something just like this... 感觉代码太多了怎么优雅的、高端的替换&#xff1f; 看个高端的栗子菊&#xff1a; LedCOM["parity"] ledData[4] "N" ? …...

华宽通中标长沙市政务共性能力建设项目,助力智慧政务建设新飞跃

在数字化浪潮的推动下&#xff0c;长沙市政府正积极拥抱智慧城市建设&#xff0c;以科技力量提升政务服务效能。华宽通凭借其卓越的技术实力与丰富的项目经验&#xff0c;成功中标长沙市政务共性能力建设项目&#xff0c;这无疑是对华宽通在智慧城市领域实力的高度认可。 华宽…...

[面试题]计算机网络

[面试题]Java【基础】[面试题]Java【虚拟机】[面试题]Java【并发】[面试题]Java【集合】[面试题]MySQL[面试题]Maven[面试题]Spring Boot[面试题]Spring Cloud[面试题]Spring MVC[面试题]Spring[面试题]MyBatis[面试题]Nginx[面试题]缓存[面试题]Redis[面试题]消息队列[面试题]…...

企业级低代码开发效率变革赋能业务增长

企业级低代码开发已经成为当今软件开发领域的一大趋势&#xff0c;它为企业带来了前所未有的效率变革&#xff0c;从而赋能业务增长。本文将围绕这一主题&#xff0c;深入探讨低代码开发的概念、优势以及如何在企业级应用中实现高效的低代码开发&#xff0c;以助力我国企业实现…...

2024最新总结:1500页金三银四面试宝典 记录35轮大厂面试(都是面试重点)

学习是你这个职业一辈子的事 手里有个 1 2 3&#xff0c;不要想着去怼别人的 4 5 6&#xff0c;因为还有你不知道的 7 8 9。保持空瓶心态从 0 开始才能学到 10 全。 毕竟也是跳槽高峰期&#xff0c;我还是为大家准备了这份1500页金三银四宝典&#xff0c;记录的都是真实大厂面…...

使用Spring Boot和Thymeleaf构建动态Web页面

使用Spring Boot和Thymeleaf构建动态Web页面 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天&#xff0c;我们将探讨如何利用Spring Boot和Thymeleaf构建动…...

扫盲之webSocket

介绍 webSocket 是一种协议&#xff0c;设计用于在客户端和服务器之间提供低延迟、全双工、和长期运行的连接。 全双工&#xff1a;通信的两个参与方可以同时发送和接收数据&#xff0c;不需要等待对方的响应或传输完成 websocket就是为了解决实时通信的问题 建立webSocke…...

一些硬件知识(十二)

1、请说明一下滤波磁珠和滤波电感的区别。 因此磁珠通常用于模数地的连接。 磁珠由导线穿过铁氧体组成&#xff0c;直流电阻很小&#xff0c;在低频时阻抗也很小&#xff0c;对直流信号几乎没有影响。 在高频&#xff08;几十兆赫兹以上&#xff09;时磁珠阻抗比较大&#xff0…...

Adobe Acrobat编辑器最新版下载安装 Adobe Acrobat版本齐全!

功能强大&#xff0c;Adobe Acrobat无疑是PDF文档处理领域的翘楚。这款软件集多种PDF文档处理功能于一身&#xff0c;不仅使得用户可以轻松地编辑PDF文档&#xff0c;更能轻松应对转换和合并等多种需求。 在编辑功能上&#xff0c;Adobe Acrobat的表现尤为出色。无论是添加文字…...

k8s如何使用 HPA 实现自动扩展

使用Horizontal Pod Autoscaler (HPA) 实验目标&#xff1a; 学习如何使用 HPA 实现自动扩展。 实验步骤&#xff1a; 创建一个 Deployment&#xff0c;并设置 CPU 或内存的资源请求。创建一个 HPA&#xff0c;设置扩展策略。生成负载&#xff0c;观察 HPA 如何自动扩展 Pod…...

Hi3861 OpenHarmony嵌入式应用入门--0.96寸液晶屏 iic驱动ssd1306

使用iic驱动ssd1306&#xff0c;代码来源hihope\hispark_pegasus\demo\12_ssd1306 本样例提供了一个HarmonyOS IoT硬件接口的SSD1306 OLED屏驱动库&#xff0c;其功能如下&#xff1a; 内置了128*64 bit的内存缓冲区&#xff0c;支持全屏刷新;优化了屏幕刷新速率&#xff0c;…...

代码随想录训练营第二十二天 77组合

第一题&#xff1a; 原题链接&#xff1a;77. 组合 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 经典的回溯模板题&#xff1a; 终止条件&#xff0c;当中间变量用来存储单个结果的大小等于k&#xff0c;则将中间变量存放到结果数组中。 一个for循环横向遍历…...