基于zookeeper和quartz实现分布式定时调度
yuyutoo 2024-10-16 15:46 7 浏览 0 评论
利用zookeeper的特性,来控制quartz实现分布式调度,保证quartz的单点运行,同时解除quartz自身分布式部署对数据库的依赖,保证同一时刻只有一个quartz应用在执行任务。
实现方式
利用zk的分布式独占锁,控制quartz应用执行节点,让拿到独占锁的quartz应用执行调度,没有拿到独占锁的quartz处理等待状态。
类图
核心代码
public class TriggerBean { /** * 标识 */ private String key; /** * 所属组 */ private String group; /** * 描述 */ private String description; /** * 启动时间 */ private String startTime; /** * 结束时间 */ private String endTime; /** * 优先级 */ private Integer priority; /** * 日历名称 */ private String calendarName; /** * 失火指令(参数0,1,2) * MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1 * MISFIRE_INSTRUCTION_SMART_POLICY = 0 (默认) * MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1 * MISFIRE_INSTRUCTION_DO_NOTHING = 2 */ private Integer misfireInstruction; /** * 任务代理类 */ private JobDetailProxyBean jobDetail; public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getGroup() { return group; } public void setGroup(String group) { this.group = group; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getStartTime() { return startTime; } public void setStartTime(String startTime) { this.startTime = startTime; } public String getEndTime() { return endTime; } public void setEndTime(String endTime) { this.endTime = endTime; } public Integer getPriority() { return priority; } public void setPriority(Integer priority) { this.priority = priority; } public String getCalendarName() { return calendarName; } public void setCalendarName(String calendarName) { this.calendarName = calendarName; } public Integer getMisfireInstruction() { return misfireInstruction; } public void setMisfireInstruction(Integer misfireInstruction) { this.misfireInstruction = misfireInstruction; } public JobDetailProxyBean getJobDetail() { return jobDetail; } public void setJobDetail(JobDetailProxyBean jobDetail) { this.jobDetail = jobDetail; } } public class CronTriggerBean extends TriggerBean { /** * CRON表达式 */ private String cronExpression; public String getCronExpression() { return cronExpression; } public void setCronExpression(String cronExpression) { this.cronExpression = cronExpression; } } public class SimpleTriggerBean extends TriggerBean { /** * 时间间隔(秒) */ private Integer interval; /** * 重复次数(默认:-1为无限循环) */ private Integer repeatCount; public Integer getInterval() { return interval; } public void setInterval(Integer interval) { this.interval = interval; } public Integer getRepeatCount() { return repeatCount; } public void setRepeatCount(Integer repeatCount) { this.repeatCount = repeatCount; } } public class SchedulerFactoryBean implements InitializingBean { protected static Logger logger = Logger.getLogger(SchedulerFactoryBean.class); /** * 触发器列表 */ private List<Object> triggers; /** * zooKeeper工厂 */ private ZookeeperFactory zooKeeperFactory; /** * Spring初始化方法 * @throws SchedulerException */ public void afterPropertiesSet() throws SchedulerException { this.initSchedulerFactory(); } /** * 初始化调度器工厂 * @throws SchedulerException */ public void initSchedulerFactory() throws SchedulerException { //初始化StdSchedulerFactory StdSchedulerFactory schedulerFactory = SchedulerUtils.initStdSchedulerFactory(); //获取调度器 Scheduler scheduler = schedulerFactory.getScheduler(); //装载调度器 for(Object triggerObject : this.getTriggers()){ if(triggerObject instanceof CronTriggerBean){ CronTriggerBean cronTriggerBean = (CronTriggerBean)triggerObject; //获取任务代理类对象 JobDetailProxyBean jobDetailProxyBean = cronTriggerBean.getJobDetail(); //装配任务 JobDetail jobDetail = SchedulerUtils.assemblyJobDetail(jobDetailProxyBean); //设置zooKeeper连接工厂 jobDetail.getJobDataMap().put("zooKeeperFactory",this.getZooKeeperFactory()); //装配触发器 CronTrigger cronTrigger = SchedulerUtils.assemblyCronTrigger(cronTriggerBean); scheduler.scheduleJob(jobDetail, cronTrigger); // System.out.println("CronTriggerBean"); }else{ SimpleTriggerBean simpleTriggerBean = (SimpleTriggerBean)triggerObject; //获取任务代理类对象 JobDetailProxyBean jobDetailProxyBean = simpleTriggerBean.getJobDetail(); //装配任务 JobDetail jobDetail = SchedulerUtils.assemblyJobDetail(jobDetailProxyBean); //设置zooKeeper连接工厂 jobDetail.getJobDataMap().put("zooKeeperFactory",this.getZooKeeperFactory()); //装配触发器 SimpleTrigger simpleTrigger = SchedulerUtils.assemblySimpleTrigger(simpleTriggerBean); scheduler.scheduleJob(jobDetail, simpleTrigger); // System.out.println("SimpleTriggerBean"); } } scheduler.start(); logger.info("调度器已启动"); } public List<Object> getTriggers() { return triggers; } public void setTriggers(List<Object> triggers) { this.triggers = triggers; } public ZookeeperFactory getZooKeeperFactory() { return zooKeeperFactory; } public void setZooKeeperFactory(ZookeeperFactory zooKeeperFactory) { this.zooKeeperFactory = zooKeeperFactory; } }
package com.ab.scheduling.quartz; import com.ab.scheduling.quartz.constant.Constant; import org.apache.curator.RetryPolicy; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.ExponentialBackoffRetry; import org.apache.log4j.Logger; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.ZooDefs; import org.springframework.beans.factory.InitializingBean; import java.util.Collections; import java.util.List; /** * Zookeeper 工厂类 * Date: 14-4-2 * Time: 下午4:03 */ public class ZookeeperFactory implements InitializingBean{ public static Logger logger = Logger.getLogger(ZookeeperFactory.class); /** * zookeeper服务地址 */ private String hosts; /** * 回话的超时时间(毫秒) */ private Integer sessionTimeOut; /** * 连接的超时时间(毫秒) */ private Integer connectionTimeOut; /** * 命名空间 **/ private String nameSpace; /** * zookeeper管理对象 */ private CuratorFramework zkTools; /** * 独享队列节点 */ private String monopolyQueueNode; /** * 连接状态 */ private String connectionState; /** * 会话ID */ private long sessionId; /** * Spring初始化方法 */ public void afterPropertiesSet(){ this.connection(); this.addListener(); } /** * 连接 */ public void connection(){ RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, Integer.MAX_VALUE); zkTools = CuratorFrameworkFactory .builder() .connectString(hosts) .namespace(nameSpace) .retryPolicy(retryPolicy) .connectionTimeoutMs(connectionTimeOut == null ? 30000 : connectionTimeOut) .sessionTimeoutMs(sessionTimeOut == null ? 30000 : sessionTimeOut) .build(); zkTools.start(); } /** * 连接状态监听 */ public void addListener(){ zkTools.getConnectionStateListenable().addListener(new ConnectionStateListener() { public void stateChanged(CuratorFramework client, ConnectionState newState) { if (newState.equals(ConnectionState.CONNECTED)) { logger.info("连接"); connectionState = "CONNECTED"; try { sessionId = zkTools.getZookeeperClient().getZooKeeper().getSessionId(); registerMonopolyQueue(); } catch (Exception e) { logger.error("注册独占队列失败"); } } if (newState.equals(ConnectionState.RECONNECTED)) { logger.info("重新连接"); connectionState = "CONNECTED"; try { if(sessionId != zkTools.getZookeeperClient().getZooKeeper().getSessionId()) { registerMonopolyQueue(); } } catch (Exception e) { logger.error("注册独占队列失败"); } } if (newState.equals(ConnectionState.LOST)) { logger.info("丢失"); connectionState = "LOST"; } if (newState.equals(ConnectionState.SUSPENDED)) { logger.info("暂停"); connectionState = "SUSPENDED"; } if (newState.equals(ConnectionState.READ_ONLY)) { logger.info("只读"); connectionState = "READ_ONLY"; } } }); } /** * 注册独占队列 */ private void registerMonopolyQueue() throws Exception { if(zkTools.checkExists().watched().forPath(Constant.MONOPOLY) == null){ zkTools.create() .withMode(CreateMode.PERSISTENT) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) .forPath(Constant.MONOPOLY); logger.info("创建独享锁队列节点成功!"); } if(monopolyQueueNode == null || (monopolyQueueNode != null && zkTools.checkExists().forPath(monopolyQueueNode)==null)) { monopolyQueueNode = zkTools.create() .withMode(CreateMode.EPHEMERAL_SEQUENTIAL) .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) .forPath(Constant.MONOPOLY + Constant.SEPARATOR + Constant.QUEUE_NODE); logger.info("成功加入独享锁队列"); } } /** * 获得独占锁的执行权限 * @return 执行权限标识 * @throws KeeperException * @throws InterruptedException */ public boolean getMonopolyLock() throws Exception { boolean flag = false; if(connectionState != null && (connectionState.equals("CONNECTED") || connectionState.equals("RECONNECTED"))){ List<String> nodes = zkTools.getChildren().watched().forPath(Constant.MONOPOLY); if(nodes.size() > 0){ Collections.sort(nodes); //判断当前应用是否在队列的第一位 if((Constant.SEPARATOR + Constant.MONOPOLY + Constant.SEPARATOR + nodes.get(0)).equals(monopolyQueueNode)){ flag = true; } } } return flag; } /** * 关闭连接 */ public void close(){ if(zkTools != null){ zkTools.close(); zkTools = null; } } public String getHosts() { return hosts; } public void setHosts(String hosts) { this.hosts = hosts; } public Integer getSessionTimeOut() { return sessionTimeOut; } public void setSessionTimeOut(Integer sessionTimeOut) { this.sessionTimeOut = sessionTimeOut; } public Integer getConnectionTimeOut() { return connectionTimeOut; } public void setConnectionTimeOut(Integer connectionTimeOut) { this.connectionTimeOut = connectionTimeOut; } public String getNameSpace() { return nameSpace; } public void setNameSpace(String nameSpace) { this.nameSpace = nameSpace; } }
package com.ab.scheduling.quartz.common; import com.ab.scheduling.quartz.JobDetailProxyBean; import com.ab.scheduling.quartz.CronTriggerBean; import com.ab.scheduling.quartz.SimpleTriggerBean; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.simpl.SimpleThreadPool; import java.util.Properties; /** * Quartz调度工具类 * Date: 14-5-15 * Time: 下午6:10 */ public class SchedulerUtils { protected static Logger logger = Logger.getLogger(SchedulerUtils.class); /** * 初始化StdSchedulerFactory * @return StdSchedulerFactory */ public static StdSchedulerFactory initStdSchedulerFactory() { StdSchedulerFactory schedulerFactory = null; try{ schedulerFactory = (StdSchedulerFactory) Class.forName(StdSchedulerFactory.class.getName()).newInstance(); Properties mergedProps = new Properties(); // 设置Quartz线程池设置 mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName()); mergedProps.setProperty("org.quartz.threadPool.threadCount", Integer.toString(10)); schedulerFactory.initialize(mergedProps); } catch (Exception e){ logger.error("初始化StdSchedulerFactory失败"); logger.error(e); } return schedulerFactory; } /** * 装配任务 * @param jobDetail 任务代理类 * @return JobDetail */ public static JobDetail assemblyJobDetail(JobDetailProxyBean jobDetail){ JobBuilder jobBuilder = JobBuilder.newJob(jobDetail.getClass()); //设置JobDetail身份标识与所属组 String key = jobDetail.getKey(); if(StringUtils.isNotBlank(key)){ jobBuilder = jobBuilder.withIdentity(key, jobDetail.getGroup()); }else{ jobBuilder = jobBuilder.withIdentity(IdentityUtils.generatorUUID("JOB"), jobDetail.getGroup()); } //设置任务描述 if(StringUtils.isNotBlank(jobDetail.getDescription())){ jobBuilder = jobBuilder.withDescription(jobDetail.getDescription()); } //设置JobDetail数据参数 JobDataMap jobDataMap = new JobDataMap(); jobDataMap.put("targetObject",jobDetail.getTargetObject()); //目标对象 jobDataMap.put("targetMethod",jobDetail.getTargetMethod()); //目标方法 jobDataMap.put("mode", jobDetail.getMode()); //运行模式 jobBuilder = jobBuilder.usingJobData(jobDataMap); return jobBuilder.build(); } /** * 装配表达式触发器 * @param cronTriggerBean 表达式触发器 * @return 表达式触发器 */ public static CronTrigger assemblyCronTrigger(CronTriggerBean cronTriggerBean){ TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger(); //设置触发器身份标识与所属组 String key = cronTriggerBean.getKey(); if(StringUtils.isNotBlank(key)){ triggerBuilder = triggerBuilder.withIdentity(key, cronTriggerBean.getGroup()); }else{ triggerBuilder = triggerBuilder.withIdentity(IdentityUtils.generatorUUID("CronTrigger"), cronTriggerBean.getGroup()); } //设置描述 if(StringUtils.isNotBlank(cronTriggerBean.getDescription())){ triggerBuilder = triggerBuilder.withDescription(cronTriggerBean.getDescription()); } //设置启动时间 if(StringUtils.isNotBlank(cronTriggerBean.getStartTime())){ triggerBuilder = triggerBuilder.startAt(DateUtils.StringToDate(cronTriggerBean.getStartTime(), "yyyy-MM-dd HH:mm:ss")); }else{ triggerBuilder = triggerBuilder.startNow(); //当启动时间为空默认立即启动调度器 } //设置结束时间 if(StringUtils.isNotBlank(cronTriggerBean.getEndTime())){ triggerBuilder = triggerBuilder.endAt(DateUtils.StringToDate(cronTriggerBean.getEndTime(), "yyyy-MM-dd HH:mm:ss")); } //设置优先级 if(cronTriggerBean.getPriority() != null){ triggerBuilder = triggerBuilder.withPriority(cronTriggerBean.getPriority()); } //设置Cron表达式(不允许为空)与集火指令 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cronTriggerBean.getCronExpression()); if(cronTriggerBean.getMisfireInstruction() != null){ if(cronTriggerBean.getMisfireInstruction() == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { cronScheduleBuilder = cronScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); } if(cronTriggerBean.getMisfireInstruction() == CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW) { cronScheduleBuilder = cronScheduleBuilder.withMisfireHandlingInstructionFireAndProceed(); } if(cronTriggerBean.getMisfireInstruction() == CronTrigger.MISFIRE_INSTRUCTION_DO_NOTHING) { cronScheduleBuilder = cronScheduleBuilder.withMisfireHandlingInstructionDoNothing(); } } triggerBuilder = triggerBuilder.withSchedule(cronScheduleBuilder); return (CronTrigger)triggerBuilder.build(); } /** * 装配简单触发器 * @param simpleTriggerBean 简单触发器 * @return 简单触发器 */ public static SimpleTrigger assemblySimpleTrigger(SimpleTriggerBean simpleTriggerBean){ TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger(); //设置触发器身份标识与所属组 String key = simpleTriggerBean.getKey(); if(StringUtils.isNotBlank(key)){ triggerBuilder = triggerBuilder.withIdentity(key, simpleTriggerBean.getGroup()); }else{ triggerBuilder = triggerBuilder.withIdentity(IdentityUtils.generatorUUID("SimpleTrigger"), simpleTriggerBean.getGroup()); } //设置描述 if(StringUtils.isNotBlank(simpleTriggerBean.getDescription())){ triggerBuilder = triggerBuilder.withDescription(simpleTriggerBean.getDescription()); } //设置启动时间 if(StringUtils.isNotBlank(simpleTriggerBean.getStartTime())){ triggerBuilder = triggerBuilder.startAt(DateUtils.StringToDate(simpleTriggerBean.getStartTime(), "yyyy-MM-dd HH:mm:ss")); }else{ triggerBuilder = triggerBuilder.startNow(); //当启动时间为空默认立即启动调度器 } //设置结束时间 if(StringUtils.isNotBlank(simpleTriggerBean.getEndTime())){ triggerBuilder = triggerBuilder.endAt(DateUtils.StringToDate(simpleTriggerBean.getEndTime(), "yyyy-MM-dd HH:mm:ss")); } //设置优先级 if(simpleTriggerBean.getPriority() != null){ triggerBuilder = triggerBuilder.withPriority(simpleTriggerBean.getPriority()); } //设置简单触发器 时间间隔(不允许为空)、执行次数(默认为-1)与集火指令 SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(20).withRepeatCount(-1); simpleScheduleBuilder = simpleScheduleBuilder.withIntervalInSeconds(simpleTriggerBean.getInterval()); if(simpleTriggerBean.getRepeatCount() != null){ simpleScheduleBuilder = simpleScheduleBuilder.withRepeatCount(simpleTriggerBean.getRepeatCount()); }else{ simpleScheduleBuilder = simpleScheduleBuilder.withRepeatCount(-1); } if(simpleTriggerBean.getMisfireInstruction() != null){ if(simpleTriggerBean.getMisfireInstruction() == Trigger.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionIgnoreMisfires(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionFireNow(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionNextWithExistingCount(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionNextWithRemainingCount(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionNowWithExistingCount(); } if(simpleTriggerBean.getMisfireInstruction() == SimpleTrigger.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT) { simpleScheduleBuilder = simpleScheduleBuilder.withMisfireHandlingInstructionNowWithRemainingCount(); } } triggerBuilder = triggerBuilder.withSchedule(simpleScheduleBuilder); return (SimpleTrigger)triggerBuilder.build(); } }
spring配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName"> <!--定时任务实现类--> <bean id="test1" class="com.jd.scheduling.quartz.test.Test1"/> <!--任务代理--> <bean id="jobDetail1" class="com.ab.scheduling.quartz.JobDetailProxyBean"> <property name="targetObject" ref="test1"/> <property name="targetMethod" value="test"/> </bean> <!--触发器--> <bean id="cronTrigger" class="com.ab.scheduling.quartz.CronTriggerBean"> <property name="jobDetail" ref="jobDetail1"/> <property name="cronExpression" value="0/10 * * * * ?"/> </bean> <!--zk配置--> <bean id="zooKeeperFactory" class="com.ab.scheduling.quartz.ZookeeperFactory"> <property name="hosts" value="127.0.0.1:2181"/> <property name="sessionTimeOut" value="15000"/> <property name="nameSpace" value="zk-scheduling"/> </bean> <!--调度工厂--> <bean id="schdulerFactory" autowire="no" class="com.ab.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="cronTrigger" /> </list> </property> <property name="zooKeeperFactory" ref="zooKeeperFactory"/> </bean> </beans>
相关推荐
- pdf,word,ppt,rar,mp4等等文档在线预览
-
背景:移动端的智能化已经被大多数人接受了,但是有时一些文件格式要在移动端打开,需要安装特定的软件才行,这个就是很多人不喜欢的,要看个文档还要下载安装一个app,实在麻烦,那能不能直接在线就预览文件呢具...
- Qt/C++音视频开发69-保存监控pcm音频数据到mp4文件/监控录像
-
一、前言用ffmpeg做音视频保存到mp4文件,都会遇到一个问题,尤其是在视频监控行业,就是监控摄像头设置的音频是PCM/G711A/G711U,解码后对应的格式是pcm_s16be/pcm_alaw...
- 全能下载神器文件蜈蚣体验(全能文件王)
-
文件蜈蚣是一款开源免费的软件,在GitHub上公布了所有源代码,自身非常绿色环保,没有流氓后台也没广告,和莫名弹窗的同行相比,可算得上是一股清流。文件蜈蚣的使用很简单,解压后运行一次其中的exe,完成...
- 支持HLS和mp4在线播放的源码(hls支持的视频编码格式)
-
今天安利的一套在线视频播放源码,它不是安卓端,也不是PC端。你只需要部署一下这个单页面源码即可。使用php+mysql+nginx即可。任何版本都能运行。HLSDOWNLOAD网页打开服务器地址:1...
- 大模型微调知识与实践分享(模具微调结构)
-
一、微调相关知识介绍...
- IOS遇到的几个H5坑、h5键盘弹起遮挡输入框的处理
-
一、IOS遇到的几个H5坑1、ios端兼容input光标高度 问题描述:input输入框光标,在安卓手机上显示没有问题,但是在苹果手机上当点击输入的时候,光标的高度和父盒子的高度一样。例如下图,左...
- 实用技巧:如何在win10中安装没有管理员权限的软件
-
通常,我们可能会遇到需要在Windows10电脑上安装软件,但在该电脑上没有管理员权限的情况,由于不是管理员,所以无权在在电脑上安装软件,这让人非常苦恼。事实上,这是微软专门设计的一个安全功能,它的...
- 基于ENVI的Landsat 7遥感影像处理与多种大气校正方法对比
-
1数据导入与辐射定标关于数据的下载,网络中相关资源很多,这里不再赘述。...
- 在 Python 中为无服务器应用设计安全租户隔离
-
软件即服务(SaaS)已经成为当今一种非常普遍的软件交付方式。虽然这方便了用户访问,而且消除了用户的运营开销,但这也改变了以前的模式,将实现SLA以及现代云原生组织所期望的所有安全和数据隐私要求的...
- 基于JFinal的后台业务框架通用模块
-
jcbase是基于JFinal2.x的后台业务框架通用模块,包括系统权限模块、APP版本管理、日志管理、数据字典等使用的技术要点后端使用JFinal2.x前端页面是基于acev1.3模板改造的,更方便...
- PHOTOSHOP图层技巧(掌握photoshop合并图层技巧)
-
你会图层吗?不会?喔,那你肯定不会PHOTOSHOP。为什么那么说呢?因为图层可以说是PHOTOSHOP的核心,几乎PHOTOSHOP所有的应用都是基于图层的,很多强劲的图像处理功能也是图层所提供的,...
- Cadence Allegro背钻设置详细介绍教程
-
CadenceAllegro背钻设置详细介绍教程...
- Pt中间层显著降低PEM水电解电子传输阻抗
-
在质子交换膜水电解(PEMWE)中,阳极OER的Ir基催化剂成本高昂,成为制约产业化的重要瓶颈。虽然非晶态IrOx具有高OER活性,但其电导率较差、与多孔钛PTL之间接触不良,往往导致催化剂层利用率低...
- GIMP 教程:制作 Duotone 双色调效果
-
今天我们学习如何使用GIMP这款强大的开源图像编辑器,制作流行的Duotone(双色调)效果。Duotone效果的核心原理,是将图像的色调信息映射到两种主要颜色上。通常,一种颜色用于图像的亮部...
- CAD打印的时候线条没了?原来是这些设置出了错
-
每当我们辛辛苦苦绘制完一张图纸之后,打印出图的时候总会出现各种各样的问题,不知道大家有没有遇到这种情况:在预览的时候还一切正常,但是打印出来之后就会发现很多线条都会不见了或者部分缺失。那么到底是怎么一...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)