在RPC场景中因为重试或者没有实现幂等机制而导致的重复数据问题,必须引起大家重视,有可能会造成例如一次购买创建多笔订单,一条通知信息被发送多次等问题,这是技术人员必须面对和解决的问题。
有人可能会说:当调用失败时程序并没有显示重试,为什么还会产生重复数据问题呢?这是因为即使没有显示重试,RPC框架在集群容错机制中自动进行了重试,这个问题必须引起关注。
本文我们以DUBBO框架为例分析为什么重试,怎么做重试,怎么做幂等三个问题。
如果简单对一个RPC交互过程进行分类,我们可以分为三类:响应成功、响应失败、没有响应。
对于响应成功和响应失败这两种情况,消费者很好处理。因为响应信息明确,所以只要根据响应信息,继续处理成功或者失败逻辑即可。但是没有响应这种场景比较难处理,这是因为没有响应可能包含以下情况:
(1) 生产者根本没有接收到请求
(2) 生产者接收到请求并且已处理成功,但是消费者没有接收到响应
(3) 生产者接收到请求并且已处理失败,但是消费者没有接收到响应
假设你是一名RPC框架设计者,究竟是选择重试还是放弃调用呢?其实最终如何选择取决于业务特性,有的业务本身就具有幂等性,但是有的业务不能允许重试否则会造成重复数据。
那么谁对业务特性最熟悉呢?答案是消费者,因为消费者作为调用方肯定最熟悉自身业务,所以RPC框架只要提供一些策略供消费者选择即可。
DUBBO作为一款优秀RPC框架,提供了如下集群容错策略供消费者选择:
Failover: 故障转移
Failfast: 快速失败
Failsafe: 安全失败
Failback: 异步重试
Forking: 并行调用
Broadcast:广播调用
故障转移策略。作为默认策略当消费发生异常时通过负载均衡策略再选择一个生产者节点进行调用,直到达到重试次数
快速失败策略。消费者只消费一次服务,当发生异常时则直接抛出
安全失败策略。消费者只消费一次服务,如果消费失败则包装一个空结果,不抛出异常
异步重试策略。当消费发生异常时返回一个空结果,失败请求将会进行异步重试。如果重试超过最大重试次数还不成功,放弃重试并不抛出异常
并行调用策略。消费者通过线程池并发调用多个生产者,只要有一个成功就算成功
广播调用策略。消费者遍历调用所有生产者节点,任何一个出现异常则抛出异常
Failover故障转移策略作为默认策略,当消费发生异常时通过负载均衡策略再选择一个生产者节点进行调用,直到达到重试次数。即使业务代码没有显示重试,也有可能多次执行消费逻辑从而造成重复数据:
public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {
public FailoverClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 所有生产者Invokers
List<Invoker<T>> copyInvokers = invokers;
checkInvokers(copyInvokers, invocation);
String methodName = RpcUtils.getMethodName(invocation);
// 获取重试次数
int len = getUrl().getMethodParameter(methodName, Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
RpcException le = null;
// 已经调用过的生产者
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size());
Set<String> providers = new HashSet<String>(len);
// 重试直到达到最大次数
for (int i = 0; i < len; i++) {
if (i > 0) {
// 如果当前实例被销毁则抛出异常
checkWhetherDestroyed();
// 根据路由策略选出可用生产者Invokers
copyInvokers = list(invocation);
// 重新检查
checkInvokers(copyInvokers, invocation);
}
// 负载均衡选择一个生产者Invoker
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {
// 服务消费发起远程调用
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + methodName + " in the service " + getInterface().getName() + " was successful by the provider " + invoker.getUrl().getAddress() + ", but there have been failed providers " + providers + " (" + providers.size() + "/" + copyInvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le);
}
// 有结果则返回
return result;
} catch (RpcException e) {
// 业务异常直接抛出
if (e.isBiz()) {
throw e;
}
le = e;
} catch (Throwable e) {
// RpcException不抛出继续重试
le = new RpcException(e.getMessage(), e);
} finally {
// 保存已经访问过的生产者
providers.add(invoker.getUrl().getAddress());
}
}
throw new RpcException(le.getCode(), "Failed to invoke the method " + methodName + " in the service " + getInterface().getName() + ". Tried " + len + " times of the providers " + providers + " (" + providers.size() + "/" + copyInvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le.getCause() != null ? le.getCause() : le);
}
}
消费者调用生产者节点A发生RpcException异常时(例如超时异常),在未达到最大重试次数之前,消费者会通过负载均衡策略再次选择其它生产者节点消费。试想如果生产者节点A其实已经处理成功了,但是没有及时将成功结果返回给消费者,那么再次重试可能就会造成重复数据问题。
快速失败策略。消费者只消费一次服务,当发生异常时则直接抛出,不会进行重试:
public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> {
public FailfastClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
// 检查生产者Invokers是否合法
checkInvokers(invokers, invocation);
// 负载均衡选择一个生产者Invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
try {
// 服务消费发起远程调用
return invoker.invoke(invocation);
} catch (Throwable e) {
// 服务消费失败不重试直接抛出异常
if (e instanceof RpcException && ((RpcException) e).isBiz()) {
throw (RpcException) e;
}
throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0,
"Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName()
+ " select from all providers " + invokers + " for service " + getInterface().getName()
+ " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost()
+ " use dubbo version " + Version.getVersion()
+ ", but no luck to perform the invocation. Last error is: " + e.getMessage(),
e.getCause() != null ? e.getCause() : e);
}
}
}
安全失败策略。消费者只消费一次服务,如果消费失败则包装一个空结果,不抛出异常,不会进行重试:
public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class);
public FailsafeClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
// 检查生产者Invokers是否合法
checkInvokers(invokers, invocation);
// 负载均衡选择一个生产者Invoker
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
// 服务消费发起远程调用
return invoker.invoke(invocation);
} catch (Throwable e) {
// 消费失败包装为一个空结果对象
logger.error("Failsafe ignore exception: " + e.getMessage(), e);
return new RpcResult();
}
}
}
异步重试策略。当消费发生异常时返回一个空结果,失败请求将会进行异步重试。如果重试超过最大重试次数还不成功,放弃重试并不抛出异常:
public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class);
private static final long RETRY_FAILED_PERIOD = 5;
private final int retries;
private final int failbackTasks;
private volatile Timer failTimer;
public FailbackClusterInvoker(Directory<T> directory) {
super(directory);
int retriesConfig = getUrl().getParameter(Constants.RETRIES_KEY, Constants.DEFAULT_FAILBACK_TIMES);
if (retriesConfig <= 0) {
retriesConfig = Constants.DEFAULT_FAILBACK_TIMES;
}
int failbackTasksConfig = getUrl().getParameter(Constants.FAIL_BACK_TASKS_KEY, Constants.DEFAULT_FAILBACK_TASKS);
if (failbackTasksConfig <= 0) {
failbackTasksConfig = Constants.DEFAULT_FAILBACK_TASKS;
}
retries = retriesConfig;
failbackTasks = failbackTasksConfig;
}
private void addFailed(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker) {
if (failTimer == null) {
synchronized (this) {
if (failTimer == null) {
// 创建定时器
failTimer = new HashedWheelTimer(new NamedThreadFactory("failback-cluster-timer", true), 1, TimeUnit.SECONDS, 32, failbackTasks);
}
}
}
// 构造定时任务
RetryTimerTask retryTimerTask = new RetryTimerTask(loadbalance, invocation, invokers, lastInvoker, retries, RETRY_FAILED_PERIOD);
try {
// 定时任务放入定时器等待执行
failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS);
} catch (Throwable e) {
logger.error("Failback background works error,invocation->" + invocation + ", exception: " + e.getMessage());
}
}
@Override
protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
Invoker<T> invoker = null;
try {
// 检查生产者Invokers是否合法
checkInvokers(invokers, invocation);
// 负责均衡选择一个生产者Invoker
invoker = select(loadbalance, invocation, invokers, null);
// 消费服务发起远程调用
return invoker.invoke(invocation);
} catch (Throwable e) {
logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: " + e.getMessage() + ", ", e);
// 如果服务消费失败则记录失败请求
addFailed(loadbalance, invocation, invokers, invoker);
// 返回空结果
return new RpcResult();
}
}
@Override
public void destroy() {
super.destroy();
if (failTimer != null) {
failTimer.stop();
}
}
/**
* RetryTimerTask
*/
private class RetryTimerTask implements TimerTask {
private final Invocation invocation;
private final LoadBalance loadbalance;
private final List<Invoker<T>> invokers;
private final int retries;
private final long tick;
private Invoker<T> lastInvoker;
private int retryTimes = 0;
RetryTimerTask(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, Invoker<T> lastInvoker, int retries, long tick) {
this.loadbalance = loadbalance;
this.invocation = invocation;
this.invokers = invokers;
this.retries = retries;
this.tick = tick;
this.lastInvoker = lastInvoker;
}
@Override
public void run(Timeout timeout) {
try {
// 负载均衡选择一个生产者Invoker
Invoker<T> retryInvoker = select(loadbalance, invocation, invokers, Collections.singletonList(lastInvoker));
lastInvoker = retryInvoker;
// 服务消费发起远程调用
retryInvoker.invoke(invocation);
} catch (Throwable e) {
logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
// 超出最大重试次数记录日志不抛出异常
if ((++retryTimes) >= retries) {
logger.error("Failed retry times exceed threshold (" + retries + "), We have to abandon, invocation->" + invocation);
} else {
// 未超出最大重试次数重新放入定时器
rePut(timeout);
}
}
}
private void rePut(Timeout timeout) {
if (timeout == null) {
return;
}
Timer timer = timeout.timer();
if (timer.isStop() || timeout.isCancelled()) {
return;
}
timer.newTimeout(timeout.task(), tick, TimeUnit.SECONDS);
}
}
}
并行调用策略。消费者通过线程池并发调用多个生产者,只要有一个成功就算成功:
public class ForkingClusterInvoker<T> extends AbstractClusterInvoker<T> {
private final ExecutorService executor = Executors.newCachedThreadPool(new NamedInternalThreadFactory("forking-cluster-timer", true));
public ForkingClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
try {
checkInvokers(invokers, invocation);
final List<Invoker<T>> selected;
// 获取配置参数
final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS);
final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
// 获取并行执行的Invoker列表
if (forks <= 0 || forks >= invokers.size()) {
selected = invokers;
} else {
selected = new ArrayList<>();
for (int i = 0; i < forks; i++) {
// 选择生产者
Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
// 防止重复增加Invoker
if (!selected.contains(invoker)) {
selected.add(invoker);
}
}
}
RpcContext.getContext().setInvokers((List) selected);
final AtomicInteger count = new AtomicInteger();
final BlockingQueue<Object> ref = new LinkedBlockingQueue<>();
for (final Invoker<T> invoker : selected) {
// 在线程池中并发执行
executor.execute(new Runnable() {
@Override
public void run() {
try {
// 执行消费逻辑
Result result = invoker.invoke(invocation);
// 存储消费结果
ref.offer(result);
} catch (Throwable e) {
// 如果异常次数大于等于forks参数值说明全部调用失败,则把异常放入队列
int value = count.incrementAndGet();
if (value >= selected.size()) {
ref.offer(e);
}
}
}
});
}
try {
// 从队列获取结果
Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
// 如果异常类型表示全部调用失败则抛出异常
if (ret instanceof Throwable) {
Throwable e = (Throwable) ret;
throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e);
}
return (Result) ret;
} catch (InterruptedException e) {
throw new RpcException("Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e);
}
} finally {
RpcContext.getContext().clearAttachments();
}
}
}
广播调用策略。消费者遍历调用所有生产者节点,任何一个出现异常则抛出异常:
public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class);
public BroadcastClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
checkInvokers(invokers, invocation);
RpcContext.getContext().setInvokers((List) invokers);
RpcException exception = null;
Result result = null;
// 遍历调用所有生产者节点
for (Invoker<T> invoker : invokers) {
try {
// 执行消费逻辑
result = invoker.invoke(invocation);
} catch (RpcException e) {
exception = e;
logger.warn(e.getMessage(), e);
} catch (Throwable e) {
exception = new RpcException(e.getMessage(), e);
logger.warn(e.getMessage(), e);
}
}
// 任何一个出现异常则抛出异常
if (exception != null) {
throw exception;
}
return result;
}
}
经过上述分析我们知道,RPC框架自带的重试机制可能会造成数据重复问题,那么在使用中必须考虑幂等性。幂等性是指一次操作与多次操作产生结果相同,并不会因为多次操作而产生不一致性。常见幂等方案有取消重试、幂等表、数据库锁、状态机。
取消重试有两种方法,第一是设置重试次数为零,第二是选择不重试的集群容错策略。
<!-- 设置重试次数为零 -->
<dubbo:reference id="helloService" interface="com.java.front.dubbo.demo.provider.HelloService" retries="0" />
<!-- 选择集群容错方案 -->
<dubbo:reference id="helloService" interface="com.java.front.dubbo.demo.provider.HelloService" cluster="failfast" />
假设用户支付成功后,支付系统将支付成功消息,发送至消息队列。物流系统订阅到这个消息,准备为这笔订单创建物流单。
但是消息队列可能会重复推送,物流系统有可能接收到多次这条消息。我们希望达到效果是:无论接收到多少条重复消息,只能创建一笔物流单。
解决方案是幂等表方案。新建一张幂等表,该表就是用来做幂等,无其它业务意义,有一个字段名为key建有唯一索引,这个字段是幂等标准。
物流系统订阅到消息后,首先尝试插入幂等表,订单编号作为key字段。如果成功则继续创建物流单,如果订单编号已经存在则违反唯一性原则,无法插入成功,说明已经进行过业务处理,丢弃消息。
这张表数据量会比较大,我们可以通过定时任务对数据进行归档,例如只保留7天数据,其它数据存入归档表。
还有一种广义幂等表就是我们可以用Redis替代数据库,在创建物流单之前,我们可以检查Redis是否存在该订单编号数据,同时可以为这类数据设置7天过期时间。
物流单创建成功后会发送消息,订单系统订阅到消息后更新状态为完成,假设变更是将订单状态0更新至状态1。订单系统也可能收到多条消息,可能在状态已经被更新至状态1之后,依然收到物流单创建成功消息。
解决方案是状态机方案。首先绘制状态机图,分析状态流转形态。例如经过分析状态1已经是最终态,那么即使接收到物流单创建成功消息也不再处理,丢弃消息。
数据库锁又可以分为悲观锁和乐观锁两种类型,悲观锁是在获取数据时加锁:
select * from table where col='xxx' for update
乐观锁是在更新时加锁,第一步首先查出数据,数据包含version字段。第二步进行更新操作,如果此时记录已经被修改则version字段已经发生变化,无法更新成功:
update table set xxx,
version = #{version} + 1
where id = #{id}
and version = #{version}
本文首先分析了为什么重试这个问题,因为对于RPC交互无响应场景,重试是一种重要选择。然后分析了DUBBO提供的六种集群容错策略,Failover作为默认策略提供了重试机制,在业务代码没有显示重试情况下,仍有可能发起多次调用,这必须引起重视。最后我们分析了几种常用幂等方案,希望本文对大家有所帮助。
你好,我是征哥,之前分享过微软的文本转语音服务,已经听不出是机器了,很多人惊叹于它的强大,希望能把自己的文字转成语音,做为视频或文章的配音,今天就来分享如何白嫖微软的文本转语音。其实很多大厂这样的服务都是需要注册才能试用,有的还限制试用时长,而注册通常要绑定信用卡,非常麻烦,微软这个不需要注册就可以试用,因此才有白嫖的机会。1.打开这个链接,快速体验https://azure.microsoft.com/en-us/services/cognitive-services/text-to-speech/?ocid=AID3027325#features 复制2.录制电脑播放的声音上面的那个链接,我们可以输入文本,点击播放按钮就可以听到效果,还有很多角色、感情、音色可以选择。现在的问题就是如何录制电脑播放的声音,如果你在安静的地方,也可以用手机录制,但效果可能不太好,最好的就是让电脑自己录制自己播放的声音,这样播放的时候就和自己听到的效果完全一样。录制Windows播放的声音不需要安装任何软件。Windows自带的录音机虽然看起来不专业,但足以满足我们本文的需求:录制电脑播放的声音。录制之
java中编写GUI有两中工具包,分别为AWT、Swing。 Swing是AWT的拓展,Swing具有比AWT丰富的组件和方法。 AWT和Swing都能跨平台使用;AWT会随着不同的系统平台,UI样式会有所变化,Swing则不会,设计完毕后在所有平台下样式一致。importjava.awt.*; importjavax.swing.*;复制一个awt示例下面是一个窗口示例importjava.awt.*; publicclassMyFrameextendsFrame{ publicMyFrame(){ super("测试"); setSize(400,200); setVisible(true); } publicstaticvoidMain(Stringargs[]){ newMyFrame(); } }复制 创建一个窗口,要继承Frame,同时会继承Frame中的一些方法; Frame中:super方法可以设置窗口标题;setSize设置窗口大小,(width,hight)setVisible设置是否显示窗口,true显示,false隐藏setLayout(ne
什么是罚单元罚单元可以用来使结构位移强制满足某个或某一组线性约束。它非实际单元,但它的所有行为都与实际单元相同。如图所示的结构对象,中间的铰接点不能看作拥有两个自由度的一个节点。因为连续梁的挠度函数在铰接点这里虽然连续但不可导,即节点两边,不同单元的转角是不一样的。所以铰接点要建立两个节点。这样一来自由度1和自由度3对应的位移必须相等,就需要建立约束关系u_1=u_3或者u_1-u_3=0罚单元应用以上述连续梁为例。将约束关系u_1-u_3=0写成矩阵形式: \begin{bmatrix} 1&-1\\ \end{bmatrix} \begin{bmatrix} u_1\\ u_3\\ \end{bmatrix} =0两边再乘以矩阵\begin{bmatrix}1&-1\\\end{bmatrix}^T,得到\begin{bmatrix} 1&-1\\ -1&1\\ \end{bmatrix} \begin{bmatrix} u_1\\ u_3\\ \end{bmatrix} = \begin{bmatrix} 0\\ 0\\ \end{bmatrix}
标题:CRLF:AutomaticCalibrationandRefinementbasedonLineFeatureforLiDARandCamerainRoadScenes作者:TaoMa∗,ZhizhengLiu∗,GuohangYan,andYikangLi编译:点云PCL来源:arXiv2021本文仅做学术分享,如有侵权,请联系删除。欢迎各位加入免费知识星球,获取PDF论文,欢迎转发朋友圈。内容如有错误欢迎评论留言,未经允许请勿转载!公众号致力于分享点云处理,SLAM,三维视觉,高精地图相关的文章与技术,欢迎各位加入我们,一起每交流一起进步,有兴趣的可联系微信:920177957。本文来自点云PCL博主的分享,未经作者允许请勿转载,欢迎各位同学积极分享和交流。摘要对于自动驾驶车辆,激光雷达和相机的精确标定是多传感器感知系统的前提。然而,现有的标定技术要么需要复杂的标定板,要么需要预先进行初始标定,这严重阻碍了其在大规模自主车辆部署中的应用。为了解决这些问题,我们提出了一种新的方法来标定道路场景中激光雷达和摄像机的外部参数。该方法在图像和点云中引入了静态直线形状物体(如车道和标
io多线程以前的redis是单线程模型,其实就是多路复用机制,知道多路复用的来一波6,我们在架构师课程中讲过,那么netty也有,看过老师相关课程的也应该知道。这里不多说了。Redis6开始有了IO读写多线程,只不过执行用户的命令和早期版本也是一样的,都是单线程执行,所以线程安全。我们先来看一下老版本的单线程: 首先读取客户端的命令,读取后执行命令,然后回写给客户端,这个就是一组命令的执行,由于单线程安全,他们会一组一组的去进行执行。他们的读写命令以及执行命令都是在一个线程中执行的,这个线程在redis6中称之为主线程。在这里我们可以回顾一下netty的reactor的线程模型,也就是多路复用。 你可以把这个单线程理解为是一个人,他是酒吧会所的接待员,在门口接待了以后,然后还要领到里面去一个一个的招待他们。我们可以打开redis.conf配置文件,看一下: 默认情况下,是按照老版本的样子,如果要使用多线程,那么开启即可,这里的io-threads就是设置多线程的数量,开启多线程后,整体的性能要比单线程要更高。io-threads设置的数量最大不建议超过8,提升的空间不大。另外线程
初识Pipepipe,中文翻译为管道,是Unix/Linux系统中一种比较常用的IPC(InterProcessCommunication)。下面这组shell命令,估计大部分人都用过或者见过。ls|wc-l复制ls和wc分别是两个独立的进程。shell会将ls的输出结果作为wc的输入结果,然后再由wc把处理结果投放到终端上。[^1]Usingapipetoconnecttwoprocesses上图很生动地描绘出这组命令的工作流程。pipe就像一根圆管,ls的输出内容流入到圆管的一端(标准输出)。随后,内容一直流到圆管的另一端(标准输入)由wc接收。这跟圆管只接受bytestream,也就是说消息内容是无边界的。创建Pipe#include<unistd.h> intpipe(int[2]pfd);复制我们需要向pipe()传入一个大小为2的数组,与此同时内核会维护一个临时的buffer,也就是看不见摸不着的管道。随后,内核会返回管道的读端和写端的文件描述符,它们分别存放于参数数组的第0个元素和第1个元素。Pipe的读写之所以叫IPC,顾名思义,管道就是用来让两个或者多个
github代码下载一、JDBC是什么?JDBCAPI属于JavaAPIJDBC用于以下几种功能:连接到数据库、执行SQL语句二、SpringBoot中如何使用JDBC2.1创建SpringBootProject时引入JDBCAPI依赖和MySQLDriver依赖,以及SpringWeb依赖(测试时用到) 可以在POM中找到引入的JDBC依赖和mysql依赖: JDBC依赖:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>复制MySql驱动依赖:<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scop
版权声明:本文为博主原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/weixin_42449444/article/details/89817797题目描述:编写一个程序,输入一个字符串(长度不超过80),然后统计出该字符串当中包含有多少个单词。例如:字符串“thisisabook”当中包含有4个单词。输入格式:输入一个字符串,由若干个单词组成,单词之间用一个空格隔开。输出格式:输出一个整数,即单词的个数。输入样例:thisisabook复制输出样例:4复制解题思路:这题跟【蓝桥杯】ADV-105不同单词个数统计类似,一个是统计单词总数、一个是统计不同单词的个数。先将字符串str放入字符串流ss中,然后从字符串流ss中依次输入word,用cnt来统计单词总数输出即可。AC代码:#include<bits/stdc++.h> usingnamespacestd; intmain() { stringstr; getline(cin,str); stringstreamss; ss<<
前言《MyBatis+SpringMVC开发指南(一)》《MyBatis+SpringMVC开发指南(二)》本系列的前面2篇文章,已经为大家介绍了MyBatis;从本篇博客开始将为大家介绍SpringMVC开发的那些事!关于SpringMVC这块将会涵盖SpringMVC的框架原理、基础知识、SpringMVC和MyBatis整合、基于注解的开发、参数绑定、JSON数据交互、RESTful、拦截器等方面。从SpringMVC框架原理进行发散这里,博主将会以自己理解的SpringMVC的框架原理流程图,进行思维发散,把SpringMVC说透!为了帮助你理解SpringMVC,建议你参考博主的《写出我的第一个框架:迷你版SpringMVC》SpringMVC框架原理图前端控制器(DispatcherServlet):在图中,你也能看到,它的交互是最多的;实际上,它负责接收请求、响应结果,相当于转发器。由于它的存在,减少了其他组件之间的耦合度。 处理器映射器(HandlerMapping):说白了,就是指明了URL到处理器的映射关系。 处理器适配器(HandlerAdapter)+处理器(
今天读到朋友圈评论:“中国最充满前途的年轻人们都聚在被称为“创业导师”的中年男人们周围,一起彻夜不休地燃烧生命,只为了在一轮又一轮如何送外卖、洗车、搭讪陌生人、借高利贷、联接电饭煲和冰箱的挑战赛中搏出更好的名次...” 而大洋对岸那边很多巨头公司的创始人,他们骨子里并不是商人,而是geek。热衷于创造新奇的事物,热衷于解决难题,是geek的天性。科技,在这一刻,非常残忍地拉开了国与国之间的差距。JudgementDayBonJovi-NewJersey今天将迎来了人机大战的最后一局,除了结果,也许大家还会好奇,就是坐在李世石对面的那个人是谁,本文就来揭晓他的身世,看完就想到那句歌词:成功的背后总是堆积着高高的寂寞。笔者也想多做一些贡献,除了这篇,今天推送的[AlphaGo系列]包括:爱钱进首席数据科学家李文哲,Facebook人工智能研究所田渊栋,喜欢折腾的王垠,Airbnb集美貌与智慧于一身的朱赟,发表的人工智能和机器学习评论,风格各异,对应科普,深入,跃迁和八卦。欢迎大家订阅文末公众号“董老师在硅谷”(donglaoshi-123),接下来会有更多知识系列。担任AlphaGo电
刘永峰 腾讯云高级产品经理 看过《超能陆战队》的朋友可能仍然对于电影中的男主角介绍和演示自己发明的微型机器人的场景记忆犹新。 “它”看起来只是一跟带有磁性的小小的金属部件。但是它是一个独立的个体,自己能够独立的大脑,同时,和同伴之间有相互的接口你行链接。能够通讯。能够随意的组合成任意功能的物体。 通过类比,我们很容易由硬件领域想到软件领域。譬如软件系统的架构,一直都是伴随着几种主流的模式,集中式,分布式以及最近才开始流行起来的“微服务”。 滨田宏发明的微型机器人,其实和微服务的思想很类似。一个微小的服务实体,有对外的接口与外部通讯。彼此之间能够快速组合成新的服务。其架构松散耦合。 接下来的内容,会和大家一起分享微服务的特点以及所拥有的一些神奇的魔法。 什么是微服务? 微服务,至少我目前也没有找到一个很精确的标准化解释。所以我们首先从字面上来理解。既然是服务,那一定是一个能够实现某个功能的实体。 光有功能,是不能成为一下服务的,因为还需要有途径和外部交互。让外部的实体能够获取服务。譬如web服务,通过http协议和浏览器或者app进行交互。所以微服务,一般来说,
记一次阿里Excel工具easyexcel的导入简单使用 maven的配置 <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>1.1.2-beat1</version> </dependency> 复制 ExcelData类继承BaseRowModel类 publicclassXlsMonitorDataextendsBaseRowModel{ @ExcelProperty(value="采样日期",index=1) privateStringtime; } 复制 导入功能的使用 直接使用EasyExcel读取数据,注意的是这里的invoke和doAfterAllAnalysed需要重写,因为是观察者模式,这两个函数再取数据时候会被调用, ZnfkHsjcMapperznfkHsjcMapper=SpringContextHolder.getBean
1xx(临时响应)表示临时响应并需要请求者继续执行操作的状态代码。 100(继续)请求者应当继续提出请求。服务器返回此代码表示已收到请求的第一部分,正在等待其余部分;101(切换协议)请求者已要求服务器切换协议,服务器已确认并准备切换。 2xx(成功) 表示服务器成功处理了请求的状态代码。 200(成功)服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。如果针对您的robots.txt文件显示此状态,则表示Googlebot已成功检索到该文件;201(已创建)请求成功并且服务器创建了新的资源;202(已接受)服务器已接受请求,但尚未处理;203(非授权信息)服务器已成功处理了请求,但返回的信息可能来自另一来源;204(无内容)服务器成功处理了请求,但没有返回任何内容;205(重置内容)服务器成功处理了请求,但没有返回任何内容;206(部分内容)服务器成功处理了部分GET请求。 3xx(重定向)要完成请求,需要进一步操作。通常,这些状态代码用来重定向。Google建议您在每次请求中使用重定向不要超过5次。您可以使用网站管理员工具查看一
1.使用场景 假设我们有训练好的模型A,B,C,我们希望使用A,B,C中的部分或者全部变量,合成为一个模型D,用于初始化或其他目的,就需要融合多个模型的方法 2.如何实现 我们可以先声明模型D,再创建多个Saver实例,分别从模型A,B,C的保存文件(checkpoint文件)中读取所需的变量值,来达成这一目的,下面是示例代码: 首先创建一个只包含w1,w2两个变量的模型,初始化后保存: 1deftrain_model1(): 2w1=tf.get_variable("w1",shape=[3,1],initializer=tf.truncated_normal_initializer(),trainable=True) 3w2=tf.get_variable("w2",shape=[3,1],initializer=tf.truncated_normal_initializer(),trainable=True) 4x=tf.placeholder(tf.float32,shape=[None,3],name="x") 5a1=tf.matmul(x,w1) 6inp
原题链接 日常写水题 将所有女生看作\(-1\),然后对整个序列进行前缀和操作(设前缀和数组为\(S[]\))。那么若\(S[x]=S[y]\),就表明区间\((x,y]\)之间的男女人数是相同的。 因此我们扫一遍前缀和数组,开一个桶记录某个值下的最小下标(即上面提到的区间左端点\(x\)),而当下一次扫到该值时,就拿当前的下标去与桶里记录的最小下标作差。 那么答案就是对每次作差取\(\max\)。 另外,因为前缀和会有负数,所以要将桶的下标平移下,并注意下前缀和为\(0\)的情况。 #include<cstdio> usingnamespacestd; constintN=1e5+10; inta[N],v[N<<1]; inlineintre() { intx=0; charc=getchar(); boolp=0; for(;c<'0'||c>'9';c=getchar()) p|=c=='-'; for(;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0'; ret
项目开发中前后端数据交互常会使用id作为主键索引,通常id数值都不大,使用number类型就可以表示处理,但对于一些分布式id或其他情况,id数值太大且超过了JS的最大处理数(Math.pow(2, 53)= 9007199254740992)时就会存在精度问题:Math.pow(2,53)+1= 9007199254740992; 实际例子: 当后端返回了这样一个id数值的数据,可以看到此数值已经超过了JS的最大处理数,丢失了精度,前端此时拿到的id值是错误的,此时涉及id的前后端数据交互,前端传输的参数id为1528669910682108000,后端无法根据此id找到对应的数据或者找出了其他数据导致异常 可以通过浏览器控制台Network的Preview和Response查看差异。Response中是原始响应数据,这里的id是正确的为1528669910682107904。而Preview中是浏览器接收到Response,通过JS转化为JavaScript对象形式,并格式化层级结构,以便查看,此时经过了JS处理,id数值精度丢失,看到的就是1528
1、setup模块获取主机信息ansibleweb01-msetup-a"filter=ansible_all_ipv4_addresses"常用参数 ansible_all_ipv4_addresses:仅显示ipv4的信息。 ansible_devices:仅显示磁盘设备信息。 ansible_distribution:显示是什么系统,例:centos,suse等。 ansible_distribution_major_version:显示是系统主版本。 ansible_distribution_version:仅显示系统版本。 ansible_machine:显示系统类型,例:32位,还是64位。 ansible_eth0:仅显示eth0的信息。 ansible_hostname:仅显示主机名(不准确)&nbs
valmap1=Map("scala"->1,"java"->2,"C"->3) valmap2=Map(("scala",1),("java",2),("C"->3)) map1.getOrElse("C#",-1)//如果没有要取的值,这个方法不会报错,而是返回-1 复制 元组 valt=("scala",100L,3.14,("spark",1)) t._1//访问元组中对应的值-scala valt,(a,b,c,d)=("scala",100L,3.14,("spark",1)) //a就是对应scala 复制 zip方法 scala>valarr1=Array("aaa","bbb","ccc") arr1:Array[String]=Array(aaa,bbb,ccc) scala>valarr2=Array(1,2,3,4) arr2:Array[Int]=Array(1,2,3,4) scala>arr1ziparr2 res62:Array[(String,Int)]=Array((a
1.什么是延迟发货 拼多多对延迟发货的定义 延迟发货是指商家未在发货时限内完成发货,即商家未在发货时限内上传已成交订单对应的真实物流单号至拼多多后台,依据《拼多多发货规则》及相应的技术标准,该订单将被自动标识为延迟发货订单。 发货时限自订单成交时起算,各类商品默认发货时限如下: 延迟发货的扣罚 商家发生延迟发货的,拼多多平台有权从商家账户余额(包括货款、保证金等,下同)中扣除对应的消费者赔付金,并将同等金额以50年有效期的无门槛现金券形式发放给延迟发货订单所对应的消费者,赔付标准如下: 需注意,未发货是指商家未在发货时限内上传已成交商品订单对应的真实物流单号至拼多多后台。 拼多多平台按照上述标准对商家延迟发货行为作出处理的同时,将视情况对商家账户余额采取限制提现措施,直至全部延迟发货订单完成真实发货或者退款成功。 2.什么是虚假发货 拼多多对虚假发货的定义 商家上传商品物流单号(即做了发货的动作)后,如果24小时内(普通商品)、或72小时内(直邮、直供商品),该物流单号在相应的物流公司官网没有物流信息的,会被判定为虚假发货 虚假发货的扣罚 商家对虚假发货订单消费者进行赔付,
1、何为观察者模式? 观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监听者,类似监听器的存在,一旦被观察/监听的目标发生的情况,就会被监听者发现,这么想来目标发生情况到观察者知道情况,其实是由目标将情况发送到观察者的。 观察者模式多用于实现订阅功能的场景,例如微博的订阅,当我们订阅了某个人的微博账号,当这个人发布了新的消息,就会通知我们。 2、观察者模式解决的问题? 解决主体对象与观察者之间功能耦合。 3、实现观察者步骤: (1)创建一个观察者: 把观察者或者消息系统看作一个对象,那么他应该包含两个方法,一个是接受消息,一个是向中转站发送相应消息。 首先,我们把需要的把观察者对象创建出来,有一个消息容器和三个方法,分别是,订阅消息的方法,取消订阅消息的方法,发送订阅消息的方法。 varObserver=(function(){ //防止消息队列暴露而被篡改,所以将消息容器作为静态私有变量保存 var__messsages={}; return{ //注册信息接口 regist:function(){}, //发布信息的接口 fire:function(){}, /
python中的多线程是一个非常重要的知识点,但python默认是单任务,所以今天简单的介绍一下多线程。 什么是线程: 线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。 线程是进程中的一个实体,是CPU调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。 一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行 主线程:当⼀个程序启动时,就有⼀个进程被操作系统(OS)创建,与此同时⼀个线程也⽴刻运⾏,该线程通常叫做程序的主线程 主线程的重要性: 是产生其他子线程的线程 通常它必须最后完成执行各种关闭动作 子线程:可以看做是程序执⾏的⼀条分⽀,当⼦线程启动后会和主线程⼀起同时执⾏ 使用threading.Thread(target=函数名,args=(参数列表,元组))创建子线程对象 那究竟什么是多线程呢,通过下面的例子很容易就搞懂了。 假如你要烧水和拖地还有玩游戏,一共三个任务,如果是单线程来处理这三个任务的话,先烧水