开不了口

开不了口 查看完整档案

呼伦贝尔编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

开不了口 发布了文章 · 2月2日

《java与模式》之建造模式

在阎宏博士的《JAVA与模式》一书中开头是这样描述建造(Builder)模式的:

建造模式是对象的创建模式。建造模式可以将一个产品的内部表象(internal representation)与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。

产品的内部表象

一个产品常有不同的组成成分作为产品的零件,这些零件有可能是对象,也有可能不是对象,它们通常又叫做产品的内部表象(internal representation)。不同的产品可以有不同的内部表象,也就是不同的零件。使用建造模式可以使客户端不需要知道所生成的产品有哪些零件,每个产品的对应零件彼此有何不同,是怎么建造出来的,以及怎么组成产品。

对象性质的建造

有些情况下,一个对象会有一些重要的性质,在它们没有恰当的值之前,对象不能作为一个完整的产品使用。比如,一个电子邮件有发件人地址、收件人地址、主题、内容、附录等部分,而在最起码的收件人地址得到赋值之前,这个电子邮件不能发送。

有些情况下,一个对象的一些性质必须按照某个顺序赋值才有意义。在某个性质没有赋值之前,另一个性质则无法赋值。这些情况使得性质本身的建造涉及到复杂的商业逻辑。这时候,此对象相当于一个有待建造的产品,而对象的这些性质相当于产品的零件,建造产品的过程是建造零件的过程。由于建造零件的过程很复杂,因此,这些零件的建造过程往往被“外部化”到另一个称做建造者的对象里,建造者对象返还给客户端的是一个全部零件都建造完毕的产品对象。

建造模式利用一个导演者对象和具体建造者对象一个个地建造出所有的零件,从而建造出完整的产品对象。建造者模式将产品的结构和产品的零件的建造过程对客户端隐藏起来,把对建造过程进行指挥的责任和具体建造者零件的责任分割开来,达到责任划分和封装的目的。

建造模式的结构

在这个示意性的系统里,最终产品Product只有两个零件,即part1和part2。相应的建造方法也有两个:buildPart1()和buildPart2()、同时可以看出本模式涉及到四个角色,它们分别是:

抽象建造者(Builder)角色:给 出一个抽象接口,以规范产品对象的各个组成成分的建造。一般而言,此接口独立于应用程序的商业逻辑。模式中直接创建产品对象的是具体建造者 (ConcreteBuilder)角色。具体建造者类必须实现这个接口所要求的两种方法:一种是建造方法(buildPart1和 buildPart2),另一种是返还结构方法(retrieveResult)。一般来说,产品所包含的零件数目与建造方法的数目相符。换言之,有多少 零件,就有多少相应的建造方法。

具体建造者(ConcreteBuilder)角色:担任这个角色的是与应用程序紧密相关的一些类,它们在应用程序调用下创建产品的实例。这个角色要完成的任务包括:1.实现抽象建造者Builder所声明的接口,给出一步一步地完成创建产品实例的操作。2.在建造过程完成后,提供产品的实例。

导演者(Director)角色:担任这个角色的类调用具体建造者角色以创建产品对象。应当指出的是,导演者角色并没有产品类的具体知识,真正拥有产品类的具体知识的是具体建造者角色。

产品(Product)角色:产品便是建造中的复杂对象。一般来说,一个系统中会有多于一个的产品类,而且这些产品类并不一定有共同的接口,而完全可以是不相关联的。

导演者角色是与客户端打交道的角色。导演者将客户端创建产品的请求划分为对各个零件的建造请求,再将这些请求委派给具体建造者角色。具体建造者角色是做具体建造工作的,但是却不为客户端所知。

一般来说,每有一个产品类,就有一个相应的具体建造者类。这些产品应当有一样数目的零件,而每有一个零件就相应地在所有的建造者角色里有一个建造方法。

源代码

产品类Product

public class Product { /**

  • 定义一些关于产品的操作 */ private String part1; private String part2; public String getPart1() { return part1;

} public void setPart1(String part1) { this.part1 = part1;
} public String getPart2() { return part2;
} public void setPart2(String part2) { this.part2 = part2;
}
}

抽象建造者类Builder

public interface Builder { public void buildPart1(); public void buildPart2(); public Product retrieveResult();
}

具体建造者类ConcreteBuilder

public class ConcreteBuilder implements Builder { private Product product = new Product(); /**

  • 产品零件建造方法1

*/ @Override public void buildPart1() { //构建产品的第一个零件 product.setPart1("编号:9527");
} /**

  • 产品零件建造方法2

*/ @Override public void buildPart2() { //构建产品的第二个零件 product.setPart2("名称:XXX");
} /**

  • 产品返还方法 */ @Override public Product retrieveResult() { return product;

}
}

导演者类Director

public class Director { /**

  • 持有当前需要使用的建造器对象 / private Builder builder; /*
  • 构造方法,传入建造器对象 @param builder 建造器对象 / public Director(Builder builder){ this.builder = builder;

} /**

  • 产品构造方法,负责调用各个零件建造方法 */ public void construct(){

builder.buildPart1();
builder.buildPart2();
}
}

客户端类Client

public class Client { public static void main(String[]args){
Builder builder = new ConcreteBuilder();
Director director = new Director(builder);
director.construct();
Product product = builder.retrieveResult();
System.out.println(product.getPart1());
System.out.println(product.getPart2());
}
}

时序图

客户端负责创建导演者和具体建造者对象。然后,客户端把具体建造者对象交给导演者,导演者操作具体建造者,开始创建产品。当产品完成后,建造者把产品返还给客户端。

把创建具体建造者对象的任务交给客户端而不是导演者对象,是为了将导演者对象与具体建造者对象的耦合变成动态的,从而使导演者对象可以操纵数个具体建造者对象中的任何一个。

使用场景

假设有一个电子杂志系统,定期地向用户的电子邮件信箱发送电子杂志。用户可以通过网页订阅电子杂志,也可以通过网页结束订阅。当客户开始订阅时,系统发送一个电子邮件表示欢迎,当客户结束订阅时,系统发送一个电子邮件表示欢送。本例子就是这个系统负责发送“欢迎”和“欢送”邮件的模块。

在本例中,产品类就是发给某个客户的“欢迎”和“欢送”邮件,如下图所示。

虽然在这个例子里面各个产品类均有一个共同的接口,但这仅仅是本例子特有的,并不代表建造模式的特点。建造模式可以应用到具有完全不同接口的产品类上。大多数情况下是不知道最终构建出来的产品是什么样的,所以在标准的建造模式里面,一般是不需要对产品定义抽象接口的,因为最终构造的产品千差万别,给这些产品定义公共接口几乎是没有意义的。

这个系统含有客户端(Client)、导演者(Director)、抽象建造者(Builder)、具体建造者(WelcomeBuilder和GoodbyeBuilder)、产品(WelcomeMessage和GoodbyeMessage)等角色。

源代码

抽象类AutoMessage源代码,send()操作仅仅是示意性的,并没有给出任何发送电子邮件的代码。

public abstract class AutoMessage { //收件人地址 private String to; //发件人地址 private String from; //标题 private String subject; //内容 private String body; //发送日期 private Date sendDate; public void send(){
System.out.println("收件人地址:" + to);
System.out.println("发件人地址:" + from);
System.out.println("标题:" + subject);
System.out.println("内容:" + body);
System.out.println("发送日期:" + sendDate);
} public String getTo() { return to;
} public void setTo(String to) { this.to = to;
} public String getFrom() { return from;
} public void setFrom(String from) { this.from = from;
} public String getSubject() { return subject;
} public void setSubject(String subject) { this.subject = subject;
} public String getBody() { return body;
} public void setBody(String body) { this.body = body;
} public Date getSendDate() { return sendDate;
} public void setSendDate(Date sendDate) { this.sendDate = sendDate;
}

}

具体产品类WelcomeMessage

public class WelcomeMessage extends AutoMessage { /**

  • 构造子 */ public WelcomeMessage(){

System.out.println("发送欢迎信息");
}
}

具体产品类GoodbyeMessage

public class GoodbyeMessage extends AutoMessage{ /**

  • 构造子 */ public GoodbyeMessage(){

System.out.println("发送欢送信息");
}
}

抽象建造者类

public abstract class Builder { protected AutoMessage msg; //标题零件的建造方法 public abstract void buildSubject(); //内容零件的建造方法 public abstract void buildBody(); //收件人零件的建造方法 public void buildTo(String to){
msg.setTo(to);
} //发件人零件的建造方法 public void buildFrom(String from){
msg.setFrom(from);
} //发送时间零件的建造方法 public void buildSendDate(){
msg.setSendDate(new Date());
} /**

  • 邮件产品完成后,用此方法发送邮件 此方法相当于产品返还方法 / public void sendMessage(){

msg.send();
}
}

具体建造者WelcomeBuilder

public class WelcomeBuilder extends Builder { public WelcomeBuilder(){
msg = new WelcomeMessage();
}
@Override public void buildBody() { // TODO Auto-generated method stub msg.setBody("欢迎内容");
}
@Override public void buildSubject() { // TODO Auto-generated method stub msg.setSubject("欢迎标题");
}
}

具体建造者GoodbyeBuilder

public class GoodbyeBuilder extends Builder { public GoodbyeBuilder(){
msg = new GoodbyeMessage();
}
@Override public void buildBody() { // TODO Auto-generated method stub msg.setBody("欢送内容");
}
@Override public void buildSubject() { // TODO Auto-generated method stub msg.setSubject("欢送标题");
}
}

导演者Director,这个类提供一个construct()方法,此方法调用建造者的建造方法,包括buildTo()、buildFrom()、buildSubject()、buildBody()、buildSendDate()等,从而一部分一部分地建造出产品对象,既AutoMessage对象。

public class Director {
Builder builder; /**

  • 构造子 */ public Director(Builder builder){ this.builder = builder;

} /**

  • 产品构造方法,负责调用各零件的建造方法 */ public void construct(String toAddress , String fromAddress){ this.builder.buildTo(toAddress); this.builder.buildFrom(fromAddress); this.builder.buildSubject(); this.builder.buildBody(); this.builder.buildSendDate(); this.builder.sendMessage();

}
}

客户端Client

public class Client { public static void main(String[] args) { // TODO Auto-generated method stub Builder builder = new WelcomeBuilder();
Director director = new Director(builder);
director.construct("toAddress@126.com", "fromAddress@126.com");

}
}

建造模式分成两个很重要的部分:

  1. 一个部分是Builder接口,这里是定义了如何构建各个部件,也就是知道每个部件功能如何实现,以及如何装配这些部件到产品中去;
  2. 另外一个部分是Director,Director是知道如何组合来构建产品,也就是说Director负责整体的构建算法,而且通常是分步骤地来执行。

不管如何变化,建造模式都存在这么两个部分,一个部分是部件构造和产品装配,另一个部分是整体构建的算法。认识这点是很重要的,因为在源码交易建造模式中,强调的是固定整体构建的算法,而灵活扩展和切换部件的具体构造和产品装配的方式。

再直白点说,建造模式的重心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用。具体的构造实现可以很方便地扩展和切换,从而可以灵活地组合来构造出不同的产品对象。

使用建造模式构建复杂对象

考虑这样一个实际应用,要创建一个保险合同的对象,里面很多属性的值都有约束,要求创建出来的对象是满足这些约束规则的。约束规则比如:保险合同通常情况下可以和个人签订,也可以和某个公司签订,但是一份保险合同不能同时与个人和公司签订。这个对象里有很多类似这样的约束,采用建造模式来构建复杂的对象,通常会对建造模式进行一定的简化,因为目标明确,就是创建某个复杂对象,因此做适当简化会使程序更简洁。大致简化如下:

●  由于是用Builder模式来创建某个对象,因此就没有必要再定义一个Builder接口,直接提供一个具体的建造者类就可以了。

●  对于创建一个复杂的对象,可能会有很多种不同的选择和步骤,干脆去掉“导演者”,把导演者的功能和Client的功能合并起来,也就是说,Client这个时候就相当于导演者,它来指导构建器类去构建需要的复杂对象。

保险合同类

/**

  • 保险合同对象 / public class InsuranceContract { //保险合同编号 private String contractId; /*
  • 被保险人员的名称,同一份保险合同,要么跟人员签订,要么跟公司签订 也就是说,“被保险人员”和“被保险公司”这两个属性,不可能同时有值 / private String personName; //被保险公司的名称 private String companyName; //保险开始生效日期 private long beginDate; //保险失效日期,一定会大于保险开始生效日期 private long endDate; //其他数据 private String otherData; //私有构造方法 private InsuranceContract(ConcreteBuilder builder){ this.contractId = builder.contractId; this.personName = builder.personName; this.companyName = builder.companyName; this.beginDate = builder.beginDate; this.endDate = builder.endDate; this.otherData = builder.otherData;

} /**

  • 保险合同的一些操作 */ public void someOperation(){

System.out.println("当前正在操作的保险合同编号为【"+this.contractId+"】");
} public static class ConcreteBuilder{ private String contractId; private String personName; private String companyName; private long beginDate; private long endDate; private String otherData; /**

  • 构造方法,传入必须要有的参数 @param contractId 保险合同编号 @param beginDate 保险合同开始生效日期 @param endDate 保险合同失效日期 / public ConcreteBuilder(String contractId,long beginDate,long endDate){ this.contractId = contractId; this.beginDate = beginDate; this.endDate = endDate;

} //被保险人员的名称 public ConcreteBuilder setPersonName(String personName) { this.personName = personName; return this;
} //被保险公司的名称 public ConcreteBuilder setCompanyName(String companyName) { this.companyName = companyName; return this;
} //其他数据 public ConcreteBuilder setOtherData(String otherData) { this.otherData = otherData; return this;
} /**

  • 构建真正的对象并返回 @return 构建的保险合同对象 / public InsuranceContract build(){ if(contractId == null || contractId.trim().length()==0){ throw new IllegalArgumentException("合同编号不能为空");

} boolean signPerson = (personName != null && personName.trim().length() > 0); boolean signCompany = (companyName != null && companyName.trim().length() > 0); if(signPerson && signCompany){ throw new IllegalArgumentException("一份保险合同不能同时与个人和公司签订");
} if(signPerson == false && signCompany == false){ throw new IllegalArgumentException("一份保险合同不能没有签订对象");
} if(beginDate <= 0 ){ throw new IllegalArgumentException("一份保险合同必须有开始生效的日期");
} if(endDate <=0){ throw new IllegalArgumentException("一份保险合同必须有失效的日期");
} if(endDate < beginDate){ throw new IllegalArgumentException("一份保险合同的失效日期必须大于生效日期");
} return new InsuranceContract(this);
}
}
}

客户端类

public class Client { public static void main(String[]args){ //创建构建器对象 InsuranceContract.ConcreteBuilder builder = new InsuranceContract.ConcreteBuilder("9527", 123L, 456L); //设置需要的数据,然后构建保险合同对象 InsuranceContract contract =
builder.setPersonName("小明").setOtherData("test").build(); //操作保险合同对象的方法 contract.someOperation();
}
}

在本例中将具体建造者合并到了产品对象中,并将产品对象的构造函数私有化,防止客户端不使用构建器来构建产品对象,而是直接去使用new来构建产品对象所导致的问题。另外,这个构建器的功能就是为了创建被构建的对象,完全可以不用单独一个类。

在什么情况下使用建造模式

  1. 需要生成的产品对象有复杂的内部结构,每一个内部成分本身可以是对象,也可以仅仅是一个对象(即产品对象)的一个组成部分。
  2. 需要生成的产品对象的属性相互依赖。建造模式可以强制实行一种分步骤进行的建造过程,因此,如果产品对象的一个属性必须在另一个属性被赋值之后才可以被赋值,使用建造模式是一个很好的设计思想。
  3. 在对象创建过程中会使用到系统中的其他一些对象,这些对象在产品对象的创建过程中不易得到。
查看原文

赞 0 收藏 0 评论 2

开不了口 发布了文章 · 2月2日

Flink实战-订单支付和对账情况监控(分别使用CEP和ProcessFunction来实现)

在电商网站中,订单的支付作为直接与钱挂钩的一环,在业务流程中非常重要。对于订单而言,为了正确控制业务流程,也为了增加用户的支付意愿,网站一般会设置一个支付失效时间,超过一段时间没支付的订单就会被取消。另外,对于订单的支付,还应该保证最终支付的正确性,可以通过第三方支付平台的交易数据来做一个实时对账

第一个实现的效果,实时获取订单数据,分析订单的支付情况,分别实时统计支付成功的和15分钟后支付超时的情况

新建一个maven项目,这是基础依赖,如果之前引入了,就不用加了

<properties>

 <maven.compiler.source>`8`</maven.compiler.source>
 <maven.compiler.target>`8`</maven.compiler.target>
 <flink.version>`1.10.1`</flink.version>
 <scala.binary.version>`2.12`</scala.binary.version>
 <kafka.version>`2.2.0`</kafka.version>
 </properties>
 <dependencies>
 <dependency>
 <groupId>`org.apache.flink`</groupId>
 <artifactId>`flink-scala_${scala.binary.version}`</artifactId>
 <version>`${flink.version}`</version>
 </dependency>
 <dependency>
 <groupId>`org.apache.flink`</groupId>
 <artifactId>`flink-streaming-scala_${scala.binary.version}`</artifactId>
 <version>`${flink.version}`</version>
 </dependency>
 <dependency>
 <groupId>`org.apache.kafka`</groupId>
 <artifactId>`kafka_${scala.binary.version}`</artifactId>
 <version>`${kafka.version}`</version>
 </dependency>
 <dependency>
 <groupId>`org.apache.flink`</groupId>
 <artifactId>`flink-connector-kafka_${scala.binary.version}`</artifactId>
 <version>`${flink.version}`</version>
 </dependency>
 <dependency>
 <groupId>`cn.hutool`</groupId>
 <artifactId>`hutool-all`</artifactId>
 <version>`5.5.6`</version>
 </dependency>
 <dependency>
 <groupId>`org.apache.flink`</groupId>
 <artifactId>`flink-table-planner-blink_2.12`</artifactId>
 <version>`1.10.1`</version>
 </dependency>
 </dependencies>

这个场景需要用到cep,所以再加入cep依赖

<dependencies>

 <dependency>
 <groupId>`org.apache.flink`</groupId>
 <artifactId>`flink-cep-scala_${scala.binary.version}`</artifactId>
 <version>`${flink.version}`</version>
 </dependency>
 </dependencies>

准备数据源文件src/main/resources/OrderLog.csv:

`1234,`**create**`,,`1611047605

1235,create,,1611047606

1236,create,,1611047606

1234,pay,akdb3833,1611047616

把java目录改为scala,新建com.mafei.orderPayMonitor.OrderTimeoutMonitor.scala 的object

/*

*

* @author mafei

* @date 2021/1/31

*/

package com.mafei.orderPayMonitor
import org.apache.flink.cep.{PatternSelectFunction, PatternTimeoutFunction}
import org.apache.flink.cep.scala.CEP
import org.apache.flink.cep.scala.pattern.Pattern
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.scala.{OutputTag, StreamExecutionEnvironment, createTypeInformation}
import org.apache.flink.streaming.api.windowing.time.Time
import java.util

/**

* _定义输入样例类类型,_

*

* @param orderId _订单id_

* @param eventType _事件类别: 创建订单create还是支付订单pay_

* @param txId _支付流水号_

* @param ts _时间_

*/

caseclassOrderEvent(orderId: Long, eventType:String,txId: String, ts: Long)

/**

* _定义输出样例类类型,_

*/

caseclassOrderResult(orderId: Long, resultMsg: String)

object OrderTimeoutMonitor {
 def main(args: Array[String]): Unit = {
 val env = StreamExecutionEnvironment.getExecutionEnvironment
 `env.setParallelism(`1`)`
 env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
 _// 1__、从文件中读取数据_
 `val resource = getClass.getResource(`"/OrderLog.csv"`)`
 val orderEvnetStream = env.readTextFile(resource.getPath)
 .map(d=>{
 `val arr = d.split(`","`)`
 `OrderEvent(arr(`0`).toLong,arr(`1`),arr(`2`), arr(`3`).toLong)` _//__把数据读出来转换成想要的样例类类型_
 `}).assignAscendingTimestamps(_.ts *` 1000`L)` _//__指定ts字段_
 `.keyBy(_.orderId)` _//__按照订单id分组_
 _/**_

* 2__、定义事件-匹配模式

* _定义15分钟内能发现订单创建和支付_

*/

 val orderPayPattern = Pattern
 `.begin[OrderEvent](`"create"`).where(_.eventType ==` "create"`)` _//__先出现一个订单创建的事件_
 `.followedBy(`"pay"`).where(_.eventType ==` "pay"`)` _//__后边再出来一个支付事件_
 `.within(Time.minutes(`15`))` _//__定义在15分钟以内,触发这2个事件_
 _// 3__、将pattern应用到流里面,进行模式检测_
 val patternStream = CEP.pattern(orderEvnetStream, orderPayPattern)
 _//4__、定义一个侧输出流标签,用于处理超时事件_
 `val orderTimeoutTag = new OutputTag[OrderResult](`"orderTimeout"`)`
 _// 5__、调用select 方法,提取并处理匹配的成功字符事件以及超时事件_
 val resultStream = patternStream.select(
 orderTimeoutTag,
 new OrderTimeoutSelect(),
 new OrderPaySelect()
 )
 `resultStream.print(`"pay"`)`
 resultStream.getSideOutput(orderTimeoutTag).print()
 `env.execute(`" order timeout monitor"`)`
 }
}

//__获取超时之后定义的事件还没触发的情况,也就是订单支付超时了。

classOrderTimeoutSelect() extends PatternTimeoutFunction[OrderEvent, OrderResult]{

 override def timeout(map: util.Map[String, util.List[OrderEvent]], l: Long): OrderResult = {
 `val timeoutOrderId = map.get(`"create"`).iterator().next().orderId`
 `OrderResult(timeoutOrderId,` "超时了。。。。超时时间:"`+l)`
 }
}

classOrderPaySelect() extends PatternSelectFunction[OrderEvent, OrderResult]{

 override def select(map: util.Map[String, util.List[OrderEvent]]): OrderResult = {
 `val orderTs = map.get(`"create"`).iterator().next().ts`
 `val paydTs = map.get(`"pay"`).iterator().next().ts`
 `val payedOrderId = map.get(`"pay"`).iterator().next().orderId`
 `OrderResult(payedOrderId,` "订单支付成功,下单时间:"`+orderTs+`" 支付时间:"`+paydTs)`
 }
}

用ProcessFunction来实现上面的场景
csv还可以用上面的数据,新建一个scala的object src/main/scala/com/mafei/orderPayMonitor/OrderTimeoutMonitorWithProcessFunction.scala

/*
 *
 * @author mafei
 * @date 2021/1/31
*/
package com.mafei.orderPayMonitor
import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.KeyedProcessFunction
import org.apache.flink.streaming.api.scala.{OutputTag, StreamExecutionEnvironment, createTypeInformation}
import org.apache.flink.util.Collector
object OrderTimeoutMonitorWithProcessFunction {
 def main(args: Array[String]): Unit = {

val env = StreamExecutionEnvironment.getExecutionEnvironment

env.setParallelism(1)

env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

// 1、从文件中读取数据

val resource = getClass.getResource("/OrderLog.csv")

val orderEventStream = env.readTextFile(resource.getPath)

.map(d=>{

val arr = d.split(",")

OrderEvent(arr(0).toLong,arr(1),arr(2), arr(3).toLong) //把数据读出来转换成想要的样例类类型

}).assignAscendingTimestamps(_.ts * 1000L) //指定ts字段

.keyBy(_.orderId) //按照订单id分组

val resultStream = orderEventStream

.process(new OrderPayMatchProcess())

resultStream.print("支付成功的: ")

resultStream.getSideOutput(new OutputTag[OrderResult]).print("订单超时事件")

env.execute("订单支付监控with ProcessFunction")

 }
}
class OrderPayMatchProcess() extends KeyedProcessFunction[Long, OrderEvent, OrderResult]{
 // 
先定义状态标识,标识create、payed、是否已经出现,以及对应的时间戳
 `lazy val isCreateOrderState: ValueState[`Boolean`] = getRuntimeContext.getState(new ValueStateDescriptor[`Boolean`](`"isCreateOrderState", classOf[Boolean]`))`
 `lazy val isPayedOrderState: ValueState[`Boolean`] = getRuntimeContext.getState(new ValueStateDescriptor[`Boolean`](`"isPayedOrderState", classOf[Boolean]`))`
 `lazy val timerTsState : ValueState[`Long`] = getRuntimeContext.getState(new ValueStateDescriptor[`Long`](`"timerTsState", classOf[Long]`))`
 // 
定义一个侧输出流,捕获timeout的订单信息
 `val orderTimeoutOutputTag = new OutputTag[`OrderResult`](`"timeout"`)`
 override def onTimer(timestamp: Long, ctx: KeyedProcessFunction[Long, OrderEvent, OrderResult]#OnTimerContext, out: Collector[OrderResult]): Unit = {

//到这里,肯定不会出现订单创建和支付同时存在的情况,因为会在processElement处理掉

//如果只有订单创建

if (isCreateOrderState.value()){

ctx.output(orderTimeoutOutputTag,OrderResult(ctx.getCurrentKey,"订单没支付或超时"))

}else if(isPayedOrderState.value()){

ctx.output(orderTimeoutOutputTag, OrderResult(ctx.getCurrentKey,"只有支付,没看到订单提交"))

}

isCreateOrderState.clear()

isPayedOrderState.clear()

timerTsState.clear()

 }
 override def processElement(i: OrderEvent, context: KeyedProcessFunction[Long, OrderEvent, OrderResult]#Context, collector: Collector[OrderResult]): Unit = {

/**

  • 判断当前事件类型,是create还是pay
  • 分几种情况:
  • 1、判断create和pay都来了
  • 要看有没有超时,没有超时就正常输出
  • 超时了输出到侧输出流
  • 2、create或者pay有一个没来
  • 注册一个定时器等着,然后等定时器触发后再输出

*

*/

val isCreate = isCreateOrderState.value()

val isPayed = isPayedOrderState.value()

val timerTs = timerTsState.value()

// 1、create来了

if (i.eventType == "create"){

// 1.1 如果已经支付过了,那是正常支付完成,输出匹配成功的结果

if (isPayed){

isCreateOrderState.clear()

isPayedOrderState.clear()

timerTsState.clear()

context.timerService().deleteEventTimeTimer(timerTs)

collector.collect(OrderResult(context.getCurrentKey,"支付成功"))

}else{ //如果没有支付过,那注册一个定时器,等待15分钟后触发

context.timerService().registerEventTimeTimer(i.ts)

timerTsState.update(i.ts 1000L + 9001000L)

isCreateOrderState.update(true)

}

}

else if(i.eventType == "pay"){ //如果当前事件是支付事件

if(isCreate){ //判读订单创建事件已经发生

if(i.ts * 1000L < timerTs){ // 创建订单到支付的时间在超时时间内,代表正常支付

collector.collect(OrderResult(context.getCurrentKey,"支付成功"))

}else{

context.output(orderTimeoutOutputTag, OrderResult(context.getCurrentKey,"已经支付,但是没有找到订单超时了"))

}

isCreateOrderState.clear()

isPayedOrderState.clear()

timerTsState.clear()

context.timerService().deleteEventTimeTimer(timerTs)

}else{ //如果没看到订单创建的事件,那就注册一个定时器等着

context.timerService().registerEventTimeTimer(i.ts)

isPayedOrderState.update(true)

timerTsState.update(i.ts)

}

}

 }
`}`

上面实现了监测用户支付的情况,实际中还需要对支付后的账单跟第三方支付平台做一个实时对账功能

会涉及到2条源码交易数据流(支付和账单)的合流计算

这里模拟账单,所以需要准备一个数据ReceiptLog.csv

akdb3833,alipay,1611047619
`akdb3832,wechat,1611049617`

上代码: src/main/scala/com/mafei/orderPayMonitor/TxMatch.scala

/*
 *
 * @author mafei
 * @date 2021/1/31
*/
package com.mafei.orderPayMonitor
import com.mafei.orderPayMonitor.OrderTimeoutMonitor.getClass
import org.apache.flink.api.common.state.{ValueState, ValueStateDescriptor}
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.co.CoProcessFunction
import org.apache.flink.streaming.api.scala.{OutputTag, StreamExecutionEnvironment, createTypeInformation}
import org.apache.flink.util.Collector
case class ReceiptEvent(orderId: String, payChannel:String, ts: Long)
object TxMatch {
 def main(args: Array[String]): Unit = {

val env = StreamExecutionEnvironment.getExecutionEnvironment

env.setParallelism(1)

env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

// 1、从订单文件中读取数据

val resource = getClass.getResource("/OrderLog.csv")

val orderEventStream = env.readTextFile(resource.getPath)

.map(d=>{

val arr = d.split(",")

OrderEvent(arr(0).toLong,arr(1),arr(2), arr(3).toLong) //把数据读出来转换成想要的样例类类型

}).assignAscendingTimestamps(_.ts * 1000L) //指定ts字段

.filter(_.eventType=="pay")

.keyBy(_.txId) //按照交易id分组

// 2、从账单中读取数据

val receiptResource = getClass.getResource("/ReceiptLog.csv")

val receiptEventStream = env.readTextFile(receiptResource.getPath)

.map(d=>{

val arr = d.split(",")

ReceiptEvent(arr(0),arr(1),arr(2).toLong) //把数据读出来转换成想要的样例类类型

}).assignAscendingTimestamps(_.ts * 1000L) //指定ts字段

.keyBy(_.orderId) //按照订单id分组

// 3、合并两条流,进行处理

val resultStream = orderEventStream.connect(receiptEventStream)

.process(new TxPayMatchResult())

resultStream.print("match: ")

resultStream.getSideOutput(new OutputTag[OrderEvent]).print("unmatched-pay")

resultStream.getSideOutput(new OutputTag[ReceiptEvent]).print("unmatched-receipt")

env.execute()

 }
}
class TxPayMatchResult() extends CoProcessFunction[OrderEvent,ReceiptEvent,(OrderEvent,)]{
 `lazy val orderEventState: ValueState[`OrderEvent`] = getRuntimeContext.getState(new ValueStateDescriptor[`OrderEvent`]
 `lazy val receiptEventState: ValueState[`ReceiptEvent`] = getRuntimeContext.getState(new ValueStateDescriptor[`ReceiptEvent`](`"payEvent", classOf[ReceiptEvent]`))`
 // 
定义自定义侧输出流
 `val unmatchedOrderEventTag = new OutputTag[`OrderEvent`](`"unmatched-pay"`)`
 `val unmatchedReceiptEventTag = new OutputTag[`ReceiptEvent`](`"receipt"`)`
 override def processElement1(in1: OrderEvent, context: CoProcessFunction[OrderEvent, ReceiptEvent, (OrderEvent, ReceiptEvent)]#Context, collector: Collector[(OrderEvent, ReceiptEvent)]): Unit = {

//判断支付账单来了

val receiptEvent = receiptEventState.value()

if(receiptEvent != null){

//如果账单已经过来了,那直接输出

collector.collect((in1,receiptEvent))

orderEventState.clear()

receiptEventState.clear()

}else{

//如果没来,那就注册一个定时器,等待10秒钟

context.timerService().registerEventTimeTimer(in1.ts*1000L + 10000L)

orderEventState.update(in1)

}

 }
 override def processElement2(in2: ReceiptEvent, context: CoProcessFunction[OrderEvent, ReceiptEvent, (OrderEvent, ReceiptEvent)]#Context, collector: Collector[(OrderEvent, ReceiptEvent)]): Unit = {

//判断支付事件来了

val orderEvent = orderEventState.value()

if(orderEvent != null){

//如果账单已经过来了,那直接输出

collector.collect((orderEvent,in2))

orderEventState.clear()

receiptEventState.clear()

}else{

//如果没来,那就注册一个定时器,等待2秒钟

context.timerService().registerEventTimeTimer(in2.ts*1000L + 2000L)

receiptEventState.update(in2)

}

 }
 override def onTimer(timestamp: Long, ctx: CoProcessFunction[OrderEvent, ReceiptEvent, (OrderEvent, ReceiptEvent)]#OnTimerContext, out: Collector[(OrderEvent, ReceiptEvent)]): Unit = {

if(orderEventState.value() != null){

ctx.output(unmatchedOrderEventTag, orderEventState.value())

}

else if(receiptEventState.value() != null){

ctx.output(unmatchedReceiptEventTag, receiptEventState.value())

}

orderEventState.clear()

receiptEventState.clear()

 }
`}`

第二种, 使用join来实现这个效果
这种方式优点是跟方便了,做了一层封装,缺点也很明显如果要实现一些复杂情况如没匹配中的也输出之类的就不行了,具体看实际场景需要

/*

*

* @author mafei

* @date 2021/1/31

*/

package com.mafei.orderPayMonitor
import com.mafei.orderPayMonitor.TxMatch.getClass
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.co.ProcessJoinFunction
import org.apache.flink.streaming.api.scala.{StreamExecutionEnvironment, createTypeInformation}
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.util.Collector
object TxMatchWithJoin {
 def main(args: Array[String]): Unit = {
 val env = StreamExecutionEnvironment.getExecutionEnvironment
 `env.setParallelism(`1`)`
 env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
 _// 1__、从订单文件中读取数据_
 `val resource = getClass.getResource(`"/OrderLog.csv"`)`
 val orderEventStream = env.readTextFile(resource.getPath)
 .map(d=>{
 `val arr = d.split(`","`)`
 `OrderEvent(arr(`0`).toLong,arr(`1`),arr(`2`), arr(`3`).toLong)` _//__把数据读出来转换成想要的样例类类型_
 `}).assignAscendingTimestamps(_.ts *` 1000`L)` _//__指定ts字段_
 `.filter(_.eventType==`"pay"`)`
 `.keyBy(_.txId)` _//__按照交易id分组_
 _// 2__、从账单中读取数据_
 `val receiptResource = getClass.getResource(`"/ReceiptLog.csv"`)`
 val receiptEventStream = env.readTextFile(receiptResource.getPath)
 .map(d=>{
 `val arr = d.split(`","`)`
 `ReceiptEvent(arr(`0`),arr(`1`),arr(`2`).toLong)` _//__把数据读出来转换成想要的样例类类型_
 `}).assignAscendingTimestamps(_.ts *` 1000`L)` _//__指定ts字段_
 `.keyBy(_.orderId)` _//__按照订单id分组_
 val resultStream = orderEventStream.intervalJoin(receiptEventStream)
 `.between(Time.seconds(`-3`), Time.seconds(`5`))`
 .process(new TxMatchWithJoinResult())
 resultStream.print()
 env.execute()
 }
}

classTxMatchWithJoinResult() extends ProcessJoinFunction[OrderEvent, ReceiptEvent,(OrderEvent,ReceiptEvent)]{

 `override def processElement(in1: OrderEvent, in2: ReceiptEvent, context: ProcessJoinFunction[OrderEvent, ReceiptEvent, (OrderEvent, ReceiptEvent)]`**#Context, collector: Collector[(OrderEvent, ReceiptEvent)]): Unit = {**
 collector.collect((in1,in2))
 }
`}`
查看原文

赞 0 收藏 0 评论 0

开不了口 发布了文章 · 2月1日

php开发memcached

一、memcached 简介

memcached是高性能的分布式内存缓存服务器。一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展

性。它可以应对任意多个连接,使用非阻塞的网络IO。由于它的工作机制是在内存中开辟一块空间,然后建立一个HashTable,Memcached自管理这些HashTable。

二、memcached 安装(参考” Linux下的Memcache安装”)

首先是下载 memcached 了,目前最新版本是 1.4.0,直接从官方网站即可下载到 memcached-1[1].4.0.tar.gz。除此之外,memcached 用到了 libevent,我yum install libevent libevent-devel

接下来是将 memcached-1[1].4.0.tar.gz 解开包、编译、安装:

tar -xzf memcached-1.1.12.tar.gz

cd memcached-1.1.12

./configure

make

make install

三、运行 memcached 守护程序

运行 memcached 守护程序很简单,只需一个命令行即可,不需要修改任何配置文件(也没有配置文件给你修改):

/usr/local/bin/memcached -d -m 128 -l 192.168.x.y -p 11211 -u www

参数解释:

-d 以守护程序(daemon)方式运行 memcached;

-m 设置 memcached 可以使用的内存大小,单位为 M;

-l 设置监听的 IP 地址,如果是本机的话,通常可以不设置此参数;(一般是不写)

-p 设置监听的端口,默认为 11211,所以也可以不设置此参数;

-u 指定用户,如果当前为 root 的话,需要使用此参数指定用户。

当然,还有其它参数可以用,man memcached 一下就可以看到了。

四、memcached 的工作原理

首先memcached 是以守护程序方式运行于一个或多个服务器中,随时接受客户端的连接操作,客户端可以由各种语言编写,目前已知的客户端 API 包括 Perl/PHP/Python/Ruby/Java/C#/C 等等。PHP 等客户端在与 memcached 服务建立连接之后,接下来的事情就是存取对象了,每个被存取的对象都有一个唯一的标识符 key,存取操作均通过这个 key 进行,保存到 memcached 中的对象实际上是放置内存中的,并不是保存在 cache 文件中的,这也是为什么 memcached 能够如此高效快速的原因。注意,这些对象并不是持久的,服务停止之后,里边的数据就会丢失。

五、PHP 如何作为 memcached 客户端

有两种方法可以使 PHP 作为 memcached 客户端,调用 memcached 的服务进行对象存取操作。

第一种,PHP 有一个叫做 memcache 的扩展,Linux 下需编译安装memcache,Window 下则在 php.ini 中去掉 php_memcache.dll 前边的注释符,使其可用。

参考张宴文章中安装nginx的一部分

编译安装PHP5扩展模块

tar zxvf memcache-2.2.5.tgz <----下载 (这是一个php扩展包资源库)
cd memcache-2.2.5/
/usr/local/webserver/php/bin/phpize
./configure --with-php-config=/usr/local/webserver/php/bin/php-config
make
make install
cd ../

这样,php.ini文件中添加了extension = "memcache.so"

例子(PHP的Memcache):

(1)

<?php

$mem = new Memcache;

$mem->connect("192.168.x.y", 11211)or die ("Could not connect");

$mem->set('key', 'This is a test!', 0, 60);

$val = $mem->get('key');

echo $val;

?>

(2)

< ?php
//连接
$mem = new Memcache;
$mem->connect("192.168.0.200", 12000);
//保存数据
$mem->set('key1', 'This is first value', 0, 60);
$val = $mem->get('key1');
echo "Get key1 value: " . $val ."
";
//替换数据
$mem->replace('key1', 'This is replace value', 0, 60);
$val = $mem->get('key1');
echo "Get key1 value: " . $val . "
";
//保存数组
$arr = array('aaa', 'bbb', 'ccc', 'ddd');
$mem->set('key2', $arr, 0, 60);
$val2 = $mem->get('key2');
echo "Get key2 value: ";
print_r($val2);
echo "
";
//删除数据
$mem->delete('key1');
$val = $mem->get('key1');
echo "Get key1 value: " . $val . "
";
//清除所有数据
$mem->flush();
$val2 = $mem->get('key2');
echo "Get key2 value: ";
print_r($val2);
echo "
";
//关闭连接
$mem->close();
?>

如果正常的话,浏览器将输出:
Get key1 value: This is first value
Get key1 value: This is replace value
Get key2 value: Array ( [0] => aaa [1] => bbb [2] => ccc [3] => ddd )
Get key1 value:
Get key2 value:

程序代码分析

初始化一个Memcache的对象:
$mem = new Memcache;

连接到我们的Memcache服务器端,第一个参数是服务器的IP地址,也可以是主机名,第二个参数是Memcache的开放的端口:
$mem->connect("192.168.0.200",12000);

保存一个数据到Memcache服务器上,第一个参数是数据的key,用来定位一个数据,第二个参数是需要保存的数据内容,这里是一个字符串,第三 个参数是一个标记,一般设置为0或者MEMCACHE_COMPRESSED就行了,第四个参数是数据的有效期,就是说数据在这个时间内是有效的,如果过 去这个时间,那么会被Memcache服务器端清除掉这个数据,单位是秒,如果设置为0,则是永远有效,我们这里设置了60,就是一分钟有效时间:
$mem->set(‘key1‘,‘This is first value’, 0, 60);

从Memcache服务器端获取一条数据,它只有一个参数,就是需要获取数据的key,我们这里是上一步设置的key1,现在获取这个数据后输出输出:
$val = $mem->get(’key1′);
echo "Get key1 value: " . $val;

现在是使用replace方法来替换掉上面key1的值,replace方法的参数跟set是一样的,不过第一个参数key1是必须是要替换数据内容的key,最后输出了:
$mem->replace(‘key1′,‘This is replace value’, 0, 60);
$val = $mem->get(‘key1′);
echo "Get key1 value: " . $val;

同样的,Memcache也是可以保存数组的,下面是在Memcache上面保存了一个数组,然后获取回来并输出
$arr = array(‘aaa’,‘bbb’, ‘ccc’,‘ddd’);
$mem->set(‘key2′,$arr, 0, 60);
$val2 = $mem->get(‘key2′);
print_r($val2);

现在删除一个数据,使用delte接口,参数就是一个key,然后就能够把Memcache服务器这个key的数据删除,最后输出的时候没有结果
$mem->delete(‘key1′);
$val = $mem->get(‘key1′);
echo "Get key1 value: " . $val . "
";

最后我们把所有的保存在Memcache服务器上的数据都清除,会发现数据都没有了,最后输出key2的数据为空,最后关闭连接

(3)数据

  
uid    username  password                        gender
751490a629862338d03f56ad2c48d494cd4b73b3ba64dca      0
751489hldhoxbh  25d55ad283aa400af464c76d713c07ad      0
751488wuhan0088ba2206cbae7c1bfe33a54ff161943bab      0
751487anrron    b206e95a4384298962649e58dc7b39d4      0
751486hldjxlkx  9c98df872d24244696c393a1d26ab749      0
7514851394afjh  25d55ad283aa400af464c76d713c07ad      0
751484yesi808  32baeaa3c422413843b015919c0be999      0
751483IDC010pw  25f9e794323b453885f5181f1b624d0b      0
751482ebay360v  a36b9e764318d31b4810d7d18096e6e7      0
751481ppgqsvgv  9c98df872d24244696c393a1d26ab749      0

10 rows in set (0.00 sec)

<?php

#数据库连接信息

$host="192.168.0.71";

$user="askwan";

$passwd="passwd";

$db="pwbbs";

$query="select UID,username,password,gender from pw_members order by uid desc limit 10;"

#我这里选用了MD5方式加密SQL做为key ,也可用其他加密方式,如Base64等

$m_key=md5($query);

$mem=new Memcache();

$mem->connect("192.168.0.72",11211);

if(!$result=$mem->get($m_key)){

echo "这是从数据库读出来的结果!";

$connection=mysql_connect($host,$user,$passwd) or die ("Unable to conect!" );

mysql_select_db($db);

$result=mysql_query($query) or die("Error query!".mysql_error());

while($row=mysql_fetch_row($result)){

$memdata[]=$row;

}

$mem->add($m_key,$memdata);

mysql_free_result($result);

mysql_close($connection);

}

else{

echo "这是从Memcached Server读出来的结果!n";

}

$result=$mem->get($m_key);

#显示获取的数据

echo "<table cellpadding=10 border=2>";

echo "<tr>";

echo "<th>Uid</th>";

echo "<th>Username</th>";

echo "<th>PassWord</th>";

echo "<th>Gender</th>";

echo "</tr>";

for($i=0;$i<10;$i++){

echo "<tr>";

for($j=0;$j<4;$j++){

echo "<td>".$result[$i][$j]."</td>";

}

echo "</tr>";

}

?>

第二种,可以避开扩展、重新编译所带来的麻烦,那就是直接使用 php-memcached-client.php,但效率会比扩展库稍差一些。

首先 下载 memcached-client.php, 在下载了 memcached-client.php 之后,就可以通过这个文件中的类“memcached”对 memcached 服务进行操作了。其实代码调用非常简单,主要会用到的方法有 add()、get()、replace() 和 delete(),方法说明如下:

add ($key, $val, $exp = 0)
往 memcached 中写入对象,$key 是对象的唯一标识符,$val 是写入的对象数据,$exp 为过期时间,单位为秒,默认为不限时间;

get ($key)
从 memcached 中获取对象数据,通过对象的唯一标识符 $key 获取;

replace ($key, $value, $exp=0)
使用 $value 替换 memcached 中标识符为 $key 的对象内容,参数与 add() 方法一样,只有 $key 对象存在的情况下才会起作用;

delete ($key, $time = 0)
删除 memcached 中标识符为 $key 的对象,$time 为可选参数,表示删除之前需要等待多长时间。

下面是一段简单的测试代码,代码中对标识符为 'mykey' 的对象数据进行存取操作:

<?php

// 包含 memcached 类文件

require_once('memcached-client.php');

// 选项设置

$options = array(

'servers' => array('192.168.10.215:11211'), //memcached 服务的地址、端口,可用多个数组元素表示多个 memcached 服务

'debug' => false,  //是否打开 debug

'compress_threshold' => 10240,  //超过多少字节的数据时进行压缩

'persistant' => false  //是否使用持久连接

);

// 创建 memcached 对象实例

$mc = new memcached($options);

// 设置此脚本使用的唯一标识符

$key = 'mykey';

// 往 memcached 中写入对象

$mc->add($key, 'some random strings');

$val = $mc->get($key);

echo "
".str_pad('$mc->add() ', 60, '_')."
";

var_dump($val);

// 替换已写入的对象数据值

$mc->replace($key, array('some'=>'haha', 'array'=>'xxx'));

$val = $mc->get($key);

echo "
".str_pad('$mc->replace() ', 60, '_')."
";

var_dump($val);

// 删除 memcached 中的对象

$mc->delete($key);

$val = $mc->get($key);

echo "
".str_pad('$mc->delete() ', 60, '_')."
";

var_dump($val);

?>

是不是很简单,在实际应用中,通常会把数据库查询的结果集保存到 memcached 中,下次访问时直接从 memcached 中获取,而不再做数据库查询操作,这样可以在很大程度上减轻数据库的负担。通常会将 SQL 语句 md5() 之后的值作为唯一标识符 key。下边是一个利用 memcached 来缓存数据库查询结果集的示例(此代码片段紧接上边的示例代码):

<?php

$sql = 'SELECT * FROM users';

$key = md5($sql);   //memcached 对象标识符

if ( !($datas = $mc->get($key)) ) {

// 在 memcached 中未获取到缓存数据,则使用数据库查询获取记录集。

echo "
".str_pad('Read datas from MySQL.', 60, '_')."
";

$conn = mysql_connect('localhost', 'test', 'test');

mysql_select_db('test');

$result = mysql_query($sql);

while ($row = mysql_fetch_object($result))

$datas[] = $row;

// 将数据库中获取到的结果集数据保存到 memcached 中,以供下次访问时使用。

$mc->add($key, $datas);

} else {

echo "
".str_pad('Read datas from memcached.', 60, '
')."n";

}

var_dump($datas);

?>

可以看出,使用 memcached 之后,可以减少数据库连接、查询操作,数据库负载下来了,脚本的运行速度也提高了。

Memcache的使用
使用Memcache的网站一般流量都是比较大的,为了缓解数据库的压力,让Memcache作为一个缓存区域,把部分信息保存在内存中,在前端能够迅速 的进行存取。那么一般的焦点就是集中在如何分担数据库压力和进行分布式,毕竟单台Memcache的内存容量的有限的。我这里简单提出我的个人看法,未经 实践,权当参考。

分布式应用
Memcache本来支持分布式,我们客户端稍加改造,更好的支持。我们的key可以适当进行有规律的封装,比如以user为主的网站来说,每个用户都有 User ID,那么可以按照固定的ID来进行提取和存取,比如1开头的用户保存在第一台Memcache服务器上,以2开头的用户的数据保存在第二胎 Mecache服务器上,存取数据都先按照User ID来进行相应的转换和存取。

但是这个有缺点,就是需要对User ID进行判断,如果业务不一致,或者其他类型的应用,可能不是那么合适,那么可以根据自己的实际业务来进行考虑,或者去想更合适的方法。

减少数据库压力
这个算是比较重要的,所有的数据基本上都是保存在数据库当中的,每次频繁的存取数据库,导致数据库性能极具下降,无法同时服务更多的用户,比如 MySQL,特别频繁的锁表,那么让Memcache来分担站长博客数据库的压力吧。我们需要一种改动比较小,并且能够不会大规模改变前端的方式来进行改变目前的 架构。

我考虑的一种简单方法:
后端的数据库操作模块,把所有的Select操作提取出来(update/delete/insert不管),然后把对应的SQL进行相应的hash算法 计算得出一个hash数据key(比如MD5或者SHA),然后把这个key去Memcache中查找数据,如果这个数据不存在,说明还没写入到缓存中, 那么从数据库把数据提取出来,一个是数组类格式,然后把数据在set到Memcache中,key就是这个SQL的hash值,然后相应的设置一个失效时 间,比如一个小时,那么一个小时中的数据都是从缓存中提取的,有效减少数据库的压力。缺点是数据不实时,当数据做了修改以后,无法实时到前端显示,并且还 有可能对内存占用比较大,毕竟每次select出来的数据数量可能比较巨大,这个是需要考虑的因素。

Memcache的安全
我们上面的Memcache服务器端都是直接通过客户端连接后直接操作,没有任何的验证过程,这样如果服务器是直接暴露在互联网上的话是比较危险,轻则数 据泄露被其他无关人员查看,重则服务器被入侵,因为Mecache是以root权限运行的,况且里面可能存在一些我们未知的bug或者是缓冲区溢出的情 况,这些都是我们未知的,所以危险性是可以预见的。为了安全起见,我做两点建议,能够稍微的防止黑客的入侵或者数据的泄露。

内网访问
最好把两台服务器之间的访问是内网形态的,一般是Web服务器跟Memcache服务器之间。普遍的服务器都是有两块网卡,一块指向互联网,一块指向内 网,那么就让Web服务器通过内网的网卡来访问Memcache服务器,我们Memcache的服务器上启动的时候就监听内网的IP地址和端口,内网间的 访问能够有效阻止其他非法的访问。
memcached -d -m 1024 -u root -l 192.168.0.200 -p 11211 -c 1024 -P /tmp/memcached.pid
Memcache服务器端设置监听通过内网的192.168.0.200的ip的11211端口,占用1024MB内存,并且允许最大1024个并发连接

设置防火墙
防火墙是简单有效的方式,如果却是两台服务器都是挂在网的,并且需要通过外网IP来访问Memcache的话,那么可以考虑使用防火墙或者代理程序来过滤非法访问。
一般我们在Linux下可以使用iptables或者FreeBSD下的ipfw来指定一些规则防止一些非法的访问,比如我们可以设置只允许我们的Web服务器来访问我们Memcache服务器,同时阻止其他的访问。
iptables -F
iptables -P INPUT DROP
iptables -A INPUT -p tcp -s 192.168.0.2 –dport 11211 -j ACCEPT
iptables -A INPUT -p udp -s 192.168.0.2 –dport 11211 -j ACCEPT
上面的iptables规则就是只允许192.168.0.2这台Web服务器对Memcache服务器的访问,能够有效的阻止一些非法访问,相应的也可以增加一些其他的规则来加强安全性,这个可以根据自己的需要来做。

参考(1)

Linux下的Memcache安装

最近在研究怎么让Discuz!去应用Memcache去做一些事情,记录下Memcache安装的过程。

Linux下Memcache服务器端的安装
服务器端主要是安装memcache服务器端,目前的最新版本是 memcached-1.3.0 。
1.2.2.tar.gz
另外,Memcache用到了libevent这个库用于Socket的处理,所以还需要安装libevent,libevent的最新版本是libevent-1.3。(如果你的系统已经安装了libevent,可以不用安装)
用wget指令直接下载这两个东西.下载回源文件后。
1.先安装libevent。这个东西在配置时需要指定一个安装路径,即./configure –prefix=/usr;然后make;然后make install;
2.再安装memcached,只是需要在配置时需要指定libevent的安装路径即./configure –with-libevent=/usr;然后make;然后make install;
这样就完成了Linux下Memcache服务器端的安装。详细的方法如下:

1.分别把memcached和libevent下载回来,放到 /tmp 目录下:
cd /tmp
1.2.0.tar.gz

2.先安装libevent:
tar zxvf libevent-1.2.tar.gz
cd libevent-1.2
./configure –prefix=/usr
make
make install

3.测试libevent是否安装成功:
ls -al /usr/lib | grep libevent
lrwxrwxrwx 1 root root 21 11?? 12 17:38 libevent-1.2.so.1 -> libevent-1.2.so.1.0.3
-rwxr-xr-x 1 root root 263546 11?? 12 17:38 libevent-1.2.so.1.0.3
-rw-r–r– 1 root root 454156 11?? 12 17:38 libevent.a
-rwxr-xr-x 1 root root 811 11?? 12 17:38 libevent.la
lrwxrwxrwx 1 root root 21 11?? 12 17:38 libevent.so -> libevent-1.2.so.1.0.3
还不错,都安装上了。

4.安装memcached,同时需要安装中指定libevent的安装位置:
cd /tmp
tar zxvf memcached-1.2.0.tar.gz
cd memcached-1.2.0
./configure –with-libevent=/usr
make
make install
如果中间出现报错,请仔细检查错误信息,按照错误信息来配置或者增加相应的库或者路径。
安装完成后会把memcached放到 /usr/local/bin/memcached ,

5.测试是否成功安装memcached:
ls -al /usr/local/bin/mem*
-rwxr-xr-x 1 root root 137986 11?? 12 17:39 /usr/local/bin/memcached
-rwxr-xr-x 1 root root 140179 11?? 12 17:39 /usr/local/bin/memcached-debug

安装Memcache的PHP扩展
1.选择相应想要下载的memcache版本。
2.安装PHP的memcache扩展

tar vxzf memcache-2.2.1.tgz
cd memcache-2.2.1
/usr/local/php/bin/phpize
./configure –enable-memcache –with-php-config=/usr/local/php/bin/php-config –with-zlib-dir
make
make install

3.上述安装完后会有类似这样的提示:

Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-2007xxxx/

4.把php.ini中的extension_dir = “./”修改为

extension_dir = “/usr/local/php/lib/php/extensions/no-debug-non-zts-2007xxxx/”

5.添加一行来载入memcache扩展:extension=memcache.so

memcached的基本设置
1.启动Memcache的服务器端:
/usr/local/bin/memcached -d -m 10 -u root -l 192.168.0.200 -p 12000 -c 256 -P /tmp/memcached.pid

-d选项是启动一个守护进程,
-m是分配给Memcache使用的内存数量,单位是MB,我这里是10MB,
-u是运行Memcache的用户,我这里是root,
-l是监听的服务器IP地址,如果有多个地址的话,我这里指定了服务器的IP地址192.168.0.200,
-p是设置Memcache监听的端口,我这里设置了12000,最好是1024以上的端口,
-c选项是最大运行的并发连接数,默认是1024,我这里设置了256,按照你服务器的负载量来设定,
-P是设置保存Memcache的pid文件,我这里是保存在 /tmp/memcached.pid,

2.如果要结束Memcache进程,执行:

kill cat /tmp/memcached.pid

也可以启动多个守护进程,不过端口不能重复。

3.重启apache,service httpd restart

Memcache环境测试
运行下面的php文件,如果有输出This is a test!,就表示环境搭建成功。开始领略Memcache的魅力把!
< ?php
$mem = new Memcache;
$mem->connect(”127.0.0.1″, 11211);
$mem->set(’key’, ‘This is a test!’, 0, 60);
$val = $mem->get(’key’);
echo $val;
?>

查看原文

赞 0 收藏 0 评论 0

开不了口 发布了文章 · 2月1日

大话PHP之性能

1缘起

关于PHP,很多人的直观感觉是PHP是一种灵活的脚本语言,库类丰富,使用简单,安全,非常适合WEB开发,但性能低下。PHP的性能是否真的就如同大家的感觉一样的差呢?本文就是围绕这么一个话题来进行探讨的。从源码、应用场景、基准性能、对比分析等几个方面深入分析PHP之性能问题,通过真实的性能数据来说话,最终找出影响PHP模块性能的关键因素。

2从原理分析PHP性能

从原理分析PHP的性能,主要从以下几个方面:内存管理、变量、函数、运行机制、网络模型来进行分析。

2.1内存管理

类似Nginx的内存管理方式,PHP在内部也是基于内存池,并且引入内存池的生命周期概念。在内存池方面,PHP对PHP脚本和扩展的所有内存相关操作都进行了托管。对大内存和小内存的管理采用了不同的实现方式和优化。在内存分配和回收的生命周期内,PHP采用一次初始化申请+动态扩容+内存标识回收机制,并且在每次请求结束后直接对内存池进行重新mask。

2.2变量

总所周知,PHP是一种弱变量类型的语言,所以在PHP内部,所有的PHP变量都对应成一种类型Zval,其中具体定义如下:

在变量方面,PHP做了大量的优化工作,比如说Reference counting和copy on writer机制。这样能够保证内存使用上的优化,并且减少内存拷贝次数(请参考http://blog.xiuwz.com/2011/11...)。在数组方面,PHP内部采用高效的hashtable来实现。

2.3函数

在PHP内部,所有的PHP函数都回转化成内部的一个函数指针。比如说扩展中函数

ZEND_FUNCTION ( my_function );//类似function my_function(){}

在内部展开后就会是一个函数

void zif_my_function ( INTERNAL_FUNCTION_PARAMETERS );

void zif_my_function(

int ht,

zval * return_value,

zval * this_ptr,

int return_value_used,

zend_executor_globals * executor_globals

);

从这个角度来看,PHP函数在内部也是对应一个函数指针。

2.4运行机制

在话说PHP性能的时候,很多人都会说“C/C++是编译型,JAVA是半编译型,PHP是解释型”。也就是说PHP是先动态解析再代码运行的,所以从这个角度来看,PHP性能必然很差。

的确,从PHP脚本运行来输出,的确是一个动态解析再代码运行的过程。

PHP的运行阶段也分成三个阶段:
●Parse。语法分析阶段。
● Compile。编译产出opcode中间码。
● Execute。运行,动态运行进行输出。

通过上图也可以看出,其实在PHP内部本身也是存在编译的过程。事实上,在标准的生产环境中,也都基本上利用了这个特点,比如说opcode cache工具apc、eacc、xcache等等。基于opcode cache,能到做到“PHP脚本编译一次,多次运行”的效果。从这点上,PHP就和JAVA的半编译机制非常类似。

所以,从运行机制上来看,PHP的运行模式和JAVA是非常类似的,都是先产生中间码,然后运行在不同虚拟机上。

2.5动态运行

从上面的几个分析来看,PHP在内存管理、变量、函数、运行机制等几个方面都做了大量的工作,所以从原理来看,PHP不应该存在性能问题,性能至少也应该和JAVA比较接近

但为什么还有很多人感觉PHP慢呢?尤其是一些计算量的性能对比上,总发现PHP处理的性能相对比较低效。这个时候就不得不谈PHP动态语言的特性所带来的性能问题了,由于PHP是动态运行时,所以所有的变量、函数、对象调用、作用域实现等等都是在执行阶段中才确定的。这个从根本上决定了PHP性能中很难改变的一些东西:C/C++等能够在静态编译阶段确定的变量、函数,在PHP中需要在动态运行中确定,也就决定了PHP中间码不能直接运行而需要运行在Zend Engine

说到PHP变量的具体实现,又不得不说一个东西了:hashtable。Hashtable可以说在PHP灵魂之一,在PHP内部广泛用到,包含变量符号栈、函数符号栈等等都是基于hashtable的。

以PHP变量为例来说明下PHP的动态运行特点,比如说代码:

  1. <?php
  2. $var = “hello, blog.xiuwz.com”;
  3. ?>

该代码的执行结果就是在变量符号栈(是一个hashtable)中新增一个项

当要使用到该变量时候,就去变量符合栈中去查找(也就是变量调用对出了一个hash查找的过程)。

同样对于函数调用也基本上类似有一个函数符号栈(hashtable)。

其实关于动态运行的变量查找特点,在PHP的运行机制中也能看出一些。

可以看出,PHP代码在compile之后,产出的了类符号表、函数符号表、和OPCODE。在真正执行的时候,zend Engine会根据op code去对应的符号表中进行查找,处理。

从某种程度上,在这种问题的上,很难找到解决方案。因为这是由于PHP语言的动态特性所决定的。但是在国内外也有不少的人在寻找解决方案。因为通过这样,能够从根本上完全的优化PHP。典型的列子有facebook的hiphop。

但所有的这种编译优化方案,都基本上是牺牲了PHP动态运行的特性。当然可以在具体的编译优化中去对动态特性做一些折中,但很难做到完完全全的兼容。

2.6网络模型

目前采用PHP的方式,比较理想和通用的模式是采用fastcgi(PHP-FPM)。Php-fpm在网络模型上比较类似nginx,采用了多进程Master+多worker的模式。Php-fpm本身是基于libevent中的epoll模型。从网络模型来看,该方式也不会和其他网络模型存在性能差异。

2.7结论

从上面分析来看,在基础的内存管理、变量、函数、运行机制、网络模型方面,PHP本身并不会存在明显的性能差异,但由于PHP的动态运行特性,决定了PHP和其他的编译型语言相比,所有的变量查找、函数运行等等都会多一些hash查找的CPU开销和额外的内存开销,至于这种开销具体有多大,可以通过后续的基准性能和对比分析得出。

因此,也可以大体看出PHP不太适合的一些场景:大量计算性任务、大数据量的运算、内存要求很严格的应用场景。如果要实现这些功能,也建议通过扩展的方式实现,然后再提供钩子函数给PHP调用。这样可以减低内部计算的变量、函数等系列开销。

3基准性能

对于PHP基准性能,目前缺少标准的数据。大多数同学都存在感性的认识,有人认为800QPS就是PHP的极限了。此外,对于框架的性能和框架对性能的影响很没有响应的权威数字。

本章节的目的是给出一个基准的参考性能指标,通过数据给大家一个直观的了解。

具体的基准性能有以下几个方面:

1、 裸PHP性能。完成基本的功能。

2、 裸框架的性能。只做最简单的路由分发,只走通核心功能。

3、 标准模块的基准性能。所谓标准模块的基准性能,是指一个具有完整服务模块功能的基准性能。

3.1环境说明

测试环境:

Uname -a

Linux db-forum-test17.db01.baidu.com 2.6.9_5-7-0-0 #1 SMP Wed Aug 12 17:35:51 CST 2009 x86_64 x86_64 x86_64 GNU/Linux

Red Hat Enterprise Linux AS release 4 (Nahant Update 3)

8  Intel(R) Xeon(R) CPU           E5520  @ 2.27GHz

软件相关:

Nginx:

nginx version: nginx/0.8.54  built by gcc 3.4.5 20051201 (Red Hat 3.4.5-2)

Php5:(采用php-fpm)

PHP 5.2.8 (cli) (built: Mar  6 2011 17:16:18)

Copyright (c) 1997-2008 The PHP Group

Zend Engine v2.2.0, Copyright (c) 1998-2008 Zend Technologies

with eAccelerator v0.9.5.3, Copyright (c) 2004-2006 eAccelerator, by eAccelerator

bingo2:

PHP框架。

其他说明:

目标机器的部署方式:

脚本。

测试压力机器和目标机器独立部署。

3.2裸PHP性能

最简单的PHP脚本。

  1. <?php
  2. require_once ‘./actions/indexAction.php’;
  3. $objAction = new indexAction();
  4. $objAction->init();
  5. $objAction->execute();
  6. ?>

Acitons/indexAction.php里面的代码如下

  1. <?php
  2. class indexAction
  3. {
  4. public function execute()
  5. {
  6. echo ‘hello, world!’;
  7. }
  8. }
  9. ?>

3.3裸PHP框架性能

为了和3.2的对比,基于bingo2框架实现了类似的功能。代码如下

  1. <?php
  2. require_once ‘Bingo/Controller/Front.php’;
  3. $objFrontController = Bingo_Controller_Front::getInstance(array(
  4. ‘actionDir’ => ‘./actions’,
  5. ));
  6. $objFrontController->dispatch();
  7. ?>

从该测试结果可以看出:框架虽然有一定的消耗,但对整体的性能来说影响是非常小的

3.4标准PHP模块的基准性能

所谓标准PHP模块,是指一个PHP模块所必须要具体的基本功能:

●路由分发。

●自动加载。

●LOG初始化&Notice日志打印。所以的UI请求都一条标准的日志。

●错误处理。

●时间校正。

●自动计算每个阶段耗时开销。

●编码识别&编码转化。

●标准配置文件的解析和调用

采用bingo2的代码自动生成工具产生标准的测试PHP模块:test。

3.5结论

从测试数据的结论来看,PHP本身的性能还是可以的。基准性能完全能够达到几千甚至上W的QPS。至于为什么在大多数的PHP模块中表现不佳,其实这个时候更应该去找出站长博客系统的瓶颈点,而不是简单的说OK,PHP不行,那我们换C来搞吧。(下一个章节,会通过一些例子来对比,采用C来处理不见得有特别的优势)

通过基准数据,可以得出以下几个具体的结论:

1、 PHP本身性能也很不错。简单功能下能够达到5000QPS(50CPU IDLE),极限也能过W。

2、 PHP框架本身对性能影响非常有限。尤其是在有一定业务逻辑和数据交互的情况下,几乎可以忽略。

3、 一个标准的PHP模块,基准性能能够达到2000QPS(80 cpu idle)。

4PHP与C性能对比分析

很多时候,大家发现PHP模块性能不行的时候,就来一句“ok,我们采用C重写吧”。在公司内,采用C/C++来写业务逻辑模块的现象到处都有,在前几年甚至几乎全部都是采用C来写。那时候大家写的真是一个痛苦:调试难、敏捷不要谈。

那么,本章节要谈论的一个话题就是:C写的业务逻辑和PHP写的业务逻辑模块进行性能对比,采用真实的数据来说话。

4.1前提

为什么要特别说出这个前提呢?因为在理想情况下,一个功能采用PHP实现,该性能铁定不可能比理想的C写出来好。这个前提需要特别注意。

但为什么还要对比呢?因为在现实情况下,能写出非常优秀的C程序,并且在频繁修改的情况下还能做到完全高性能的又有几个呢?并且在现实的应用中C实现的性能是否真的全都都比PHP要好好几倍呢?这些目前都没有确切的数据来论证。

所以,本章节的对比是基于现实中的情况来进行的,并采用真实数据来说话。

4.2 真实业务模块PHP模块 VS C模块

4.2.1业务模块介绍

一个真实的案列,该业务模块的流量高达数十亿。

该业务模块功能非常简单,上层是web server,下游是各个数据模块。都是基于socket进行数据交互。该业务模块的主要工作模型是:响应web server的请求,根据请求从各个后端数据模块读取相应数据,并根据数据产出最终的HTML页面返回给web服务器

为了方便后续介绍,定义CUI表示用C实现的模块,PHPUI表示用PHP实现的模块。

4.2.2C/C++模块的性能数据结果

09年,该模块重构选择了一个新的C/C++框架。当时重构的时候,该模块连接的后端数据模块规模5-7

基于C/C++的模块,最终测试数据数据分成两个部分:

一、性能对比测试。

基于当时线上压力,进行真实数据的性能测试。所以当时只测试一个压力数据如下:

压力:210QPS

CPU(IDLE):84.18

二、极限性能测试1。

该测试模型是:CUI只连接一个核心数据模块,其他数据模块完全关闭。

三、极限性能测试2。

该测试模型是:CUI连接后端一个核心数据模块,3个数据模块,其他数据模块不连接。

4.2.3 PHP实现模块的性能测试数据

到11年,基于09年的CUI基本上达到了代码不看维护的地步。而且这个时候,CUI的极限性能已经不到600QPS(主要原因是随着项目的发展,后端数据模块的数目增加到14个)。据此,决定采用PHP方案来重写整个模块,并产出最终的pbui模块。

性能测试结果分成两种:

1、PHPUI连接一个核心模块。

2、PHPUI连接后端所有模块(14个)。

4.2.4数据对比结论

由于PHPUI和CUI的业务逻辑和测试方法都不完全相同,所以抽取了部分大体能对比的点进行整理。具体对比数据如下:

从上面的对比数据来看,在真实的业务项目中,PHPUI的性能并不会比CUI。这个不是简简单单一个模块来验证的,在部门里面,我们有不少模块都是从C/C++迁移到PHP,从迁移的结果来看,并没有存在质的性能下降,大部分模块迁移后性能指标都是非常接近的。

这个时候就需要思考为什么会这样了?细分来说有两个问题:

1、 为什么在真实业务项目中,PHPUI的性能并不会比CUI差太多?

2、 为什么基准的PHP性能这么高,80CPU的情况下2000QPS,但到了真实的PHP模块中只能是200QPS?

其实这两个问题,也可以归结成一种原因:在真实业务项目中,影响性能更多的不是说采用了什么语言,而是其业务相关的部分,比如说socket交互次数,比如说字符串处理,也比如说网络交互包大小

OK。那么接下来的关键是找出影响性能的关键因素。

4.2.5影响PHP模块性能的关键因素

从前面分析,我们得出,影响前端PHP模块性能的关键因素不是语言本身(是否是PHP/JAVA/C都不重要)。那么到底影响PHP业务模块性能的关键因素在哪里呢?CPU耗时是统计一个项目性能的关键点之一,考虑到系统中都打印出了系列日志。通过分析日志中请求的耗时分布可以大体上看出关键点。

在我们系统中,CPU耗时重点打印出以下几个方面:

1、 请求总时间。

2、 请求关键函数的性能,其中所有的socket交互都有耗时计算。

3、 模版渲染也是好事的一个关键点。

在前面分析中,我们基本上判定socket和字符串处理是一个关键点之一,通过数据我们来验证下。抽取一个模块指定数目的日志,进行综合分析得出以下数据:

通过这个可以看出,在一个业务模块中,影响最大的是socket数据交互,其次是大量的字符串处理。具体细分来说是以下几个因素:socket交互次数、socket交互包大小、socket交互响应时间、字符串处理。

4.2.6结论

通过上述分析,可以得出以下结论:在前端业务模块中,PHP语言本身不会成为性能瓶颈。因为影响性能的几个关键因数是:

● 网络交互数目。

● 网络交互数据大小,包含数据打包解包开销。

● 网络交互响应时间。

● 大量的字符串处理。

5最终结论

通过上述三个章节的具体分析,可以得出以下结论:

1、从PHP实现原理来看,PHP属于半编译型语言,并且在各个方面都进行了大量的优化工作,本身不会存在明显的性能问题。但由于动态语言的特性,决定了PHP需要运行在Zend Engine虚拟机上,并且在变量查找、函数调用、作用域切换等各个方面需要一些额外开销。

2、从PHP的基准性能来看,PHP本身不会存在明显的资源消耗,单机QPS能够轻松过W, PHP框架本身也不会对业务系统的性能带来关键性的影响。

3、从真实的应用场景来看,基于C语言实现的模块不见得比基于PHP实现的模块性能高效很多。因为在真实的应用场景中,更多的性能开销在于网络数据交互和字符串处理。语言方面微小的性能差异不会成为瓶颈。

据此,可以推出:基于C语言实现的大部分业务系统都可以考虑迁移到PHP上来,一方面能够快速开发,另外一方面性能也不会存在问题。

查看原文

赞 0 收藏 0 评论 0

开不了口 发布了文章 · 1月31日

DBA日常工作

1.引言

本指南旨在简要地列出Sybase ASE系统管理员(DBA)所需的日常维护工作。一般说来,在完成这些操作后,所管理的ASE数据库可以长期安全可靠地运行。本指南着重的是what to do,而不是how to do,也即是说本指南并不会详细地介绍如何进行这些日常工作,但会给出相应的参考手册。我们认为作为一个合格的数据库管理员,应及早发现可能导致的问题,而不是等到出现问题时才来解决。根据Sybase技术支持人员的经验,在出现问题时,因时间急迫,数据库管理员所采取的一些紧急措施往往容易导致更严重的问题。因此,本手册着重介绍的是一些事前的预防、检查措施,而不是事后的处理。
考虑到目前仅ASE12.5.0有中文手册,因此,本指南所指出的参考的手册和章节均使用英文版手册的名称和章节。
另外,需要注意的是,本指南只是 guide,实际生产环境差异甚大,请灵活掌握。如 2.10 和 2.11 节就不适合 7*24 运行的系统。同时,前期发布的本指南的 PDF 版本不再更新。
本指南的撰写过程中,得到了Sybase广州办事处胡道军、周海涛工程师的大力支持和指正。SybaseBBS的朋友们也无私地贡献出他们宝贵的经验和意见。在此真诚地表示谢意。

1.1.适合的读者

本指南所面向的读者主要是Sybase ASE 数据库管理员,应具有Sybase ASE数据库基本知识,能独立进行数据库的基本操作。
本指南可以为那些希望制订适合所在组织的ASE数据库维护制度的管理人员提供参考。

1.2.约定

本手册遵从以下字体和风格约定:

元素

示例

书名、章节名

__System Administration Guide Volume 2__Developing a Backup and Recovery Plan

定期频度

每周一次

存贮过程或、命令

sp_addserver targetservername

2.日常维护工作

2.1.定期备份master

Master库是ASE最核心的系统库,它记录了所有数据库的物理和逻辑信息。因此其备份工作独立成节。
建议master数据库的备份频度为每周一次。同时,在进行任何系统表操作之前和之后,应事先/立即备份master库。如: disk init 、 sp_addumpdevice 、 sp_dropdevice 、磁盘镜像命令、 sp_addsegment 、 sp_dropsegment 或 sp_extendsegment 等。
Master数据库的备份可以采用在服务停止后,直接复制master.dat文件的方式进行。
有关备份master数据库的详细信息,请参考Sybase手册之 __System Administration Guide Volume 2__ 中的 Developing a Backup and Recovery Plan 一章。

2.2.定期备份用户数据库

对于数据库维护而言,定期备份是十分重要的工作。ASE管理员应制定合理的备份策略,定期进行数据库备份( dump database )和日志备份( dump transaction )。建议数据库备份的频度至少为每周一次、日志备份的频度至少为每日一次。可根据应用的实际情况将日志备份调整为每半天一次或每小时一次,以尽可能地降低意外导致的损失。
需要注意的事,除了定期备份外,当发生以下操作之前和之后,也应及时进行数据库备份:

  • 数据库版本升级;
  • 创建新索引;
  • 无日志记录操作,如无记录的writetext、永久表上的select into、快速批量复制(bcp)到一个没有触发器或索引的表等;
  • dump transaction with truncate_only 或 dump transaction with no_log 。

    同时,建议在进行了任何DDL操作后,立即备份数据库。
    对于小容量并使用文件系统文件为设备的数据库,可以采用直接备份设备文件的方式进行。采用此种方式备份,必须准确地记录设备文件所在的目录。
    有关备份用户数据库的详细信息,请参考Sybase手册之 __System Administration Guide Volume 2__ 中的 Developing a Backup and Recovery Plan 一章。

2.3.定期检查最早活动事务

最早活动事务(the oldest active transaction)是指一个数据库中的最早未完成(未提交或未回滚)的事务。它将导致日志空间逐渐减少,持续时间越长,日志空间越少。由于事务的瞬间性,通常并不会存在被记录下来的最早活动事务。但一些特殊情况可能会导致最早活动事务出现。比如,在一个大事务处理过程中,网络出现故障。
在master数据库中,系统表syslogshold为每个数据库记录了最早活动事务(如果存在的话)以及复制服务的截断点(如果存在的话),也就是说在该表中,每个数据库可能存在0、1或2条记录。
可以通过查询syslogshold表获取最早活动事务的情况。建议检查频度为每周一次。
有关备份用户数据库的详细信息,请参考Sybase手册之 __System Administration Guide Volume 2__ 中的 Backing Up and Restoring User Databases____Managing Free Space with Thresholds 章节、 __Reference Manual: Procedures__ 中的 System Procedures 一章以及 __Reference Manual: Tables__ 中的 System Tables 一章。

2.4.定期检查数据库日志空间

ASE数据库采取的是先记日志的机制。每当用户执行修改数据库的操作时,ASE会自动地将变化写入日志中。一条SQL语句所产生的所有变化都被记录到日志后,它们才被写到数据页在缓冲区的拷贝中。日志对于数据库的数据安全性、完整性至关重要。如果当日志空间满了再来处理,有可能会造成一定的损失。因此,需要定期检查数据库日志空间。
可以通过 sp_spaceused syslogs 查看日志空间。有关该存贮过程的详细说明,请参考 __System Administration Guide Volume 2__ 中的 Managing Free Space with Thresholds 一章和 __Reference Manual: Procedures__ 中的 System Procedures 一章。
管理员应根据应用类型、业务量以及日志空间的大小来制订检查的频度。建议至少每周一次 。

2.5.定期检查数据库剩余空间

通常在设计时,数据库的容量比当前容量大很多。然而,随着时间的流逝、数据量的不断增加 ,数据库剩余空间逐渐减少。建议检查的频度至少为每月一次。
可以通过sp_helpdb查看数据库的使用情况,有关该存贮过程的详细说明,请参考 __Reference Manual: Procedures__ 中的 System Procedures 一章。

2.6.定期查看(错误)日志

实际上,定期查看日志是任何系统的管理员都必须养成的良好习惯。日志(包含备份服务的日志)详细记录了数据库的运行过程情况,任何异常也会在日志中体现。查看日志并不需要多少时间,通常2-5分钟就足够了。将此项工作定期化,管理员就可以大致掌握数据库的运行状况,并及时分析异常并做出正确的响应。有鉴于此,强烈建议日志查看的频度为每日一次。
同时,在数据库发生任何异常时,请首先查看日志。
如何阅读日志,请参考Sybase ASE手册之 __System Administration Guide Volume 1__ 中的 Diagnosing System Problems 一章。

2.7.定期检查数据库软件更新

虽然用户都希望能有一个没有Bug的软件,然而遗憾的是:任何软件都存在BUG,ASE自然也不会例外。因此,及时获取补丁并更新,是非常重要的工作。
强烈建议:

  • ASE管理员应至少每月查看一次Sybase官方网站的EBF包发布情况;建议在打补丁或更新前,管理员应认真阅读Targeted CR-List,分析并权衡更新可能对现有应用可能带来的影响。
  • 只要可能,管理员也应认真阅读Target CR-List,了解当前ASE版本存在哪些问题,从而采取相应的措施,避免潜在的损失。

2.8.定期更新统计信息

ASE查询优化器依靠统计信息来生成查询计划,统计信息的正确与否,直接决定了SQL的执行速度。一个真实的例子是:一个应用系统运行一段时间后,性能急骤下降。监控过程中发现,一些查询SQL的SARG明明建有索引,但域名交易查询计划显示并未使用索引,而是全表扫描。在更新统计信息后,系统速度恢复正常。
建议根据表的更新程度,采取不同的频度执行此项工作。在ASE15之前,只能凭经验来估计需要更新的频度。而自版本15开始,ASE引入了一个 datachange 函数,可以获取表的更新程度,从而更灵活地更新统计信息。
需要注意的是,更新统计信息是极消耗系统资源的,因此应尽可能避免在业务时间内执行此项工作。同时,强烈建议不要使用update all模式,对于大表而言,update all将是一个灾难。同时对于大数据量的表,应使用采样更新。建议的采样率为10%到20%。
如何更新统计信息以及为哪些列增加统计信息,请参考Sybase ASE手册之 __Performance and Tuning:Monitoring and Analyzing__Using Statistics to Improve Performance 一章以及 __Reference Manual: procedures__
由于 ASE 15的优化器可利用组合索引的非前导列,因此可适当增加 update index statistics 的执行频度。

2.9.定期进行性能检查

使用sp_sysmon存贮过程(所有ASE版本),定期检查数据库运行性能。也可以使用MDA(也称mon表,要求ASE版本为12.5.0.3以上),或者配合相关工具,如DB X-ray、Spotlight、Sybase DB Expert等。
有关 sp_sysmon 存贮过程的详细信息,请参考Sybase ASE手册之 __Reference Manual: procedures__
有关MDA的详细信息,请参考Sybase ASE手册之 __Performance and Tuning: Monitoring and Analyzing__ 中的 Monitoring Tables 一节,

建议的频度为每周一次,尤其是在业务高峰期。

2.10.定期检查数据库完整性

DBCC(database consistency checker)提供了检查数据库逻辑和物理完整性的命令。其主要功能是:

  • 使用checkstorage或checktable和checkdb检查页级和行级上的页链和数据指针;
  • 使用checkstorage、checkalloc、checkverify、tablealloc和indexalloc检查分配页。

    需要注意的是,DBCC需要消耗大量的资源,因此应尽可能地避免在业务时间内执行此项工作。建议定期DBCC的频度为每季度一次。另外,一旦发现硬件存贮可能存在问题,应尽快进行DBCC检查。
    关于DBCC的详细信息,请参考Sybase手册之 __System Administration Guide Volume 2__Checking Database Consistency 一章。

2.11.定期重新组织表空间

数据库运行一段时间后,频繁的表更新活动最终可能会导致空间利用不充分以及性能的降低。因此需要定期的重新组织表空间。
需要注意的是,重新组织表空间需要足够的空余空间,建议应保证1.5倍表原有空间以上。同时,重组表空间需要大量的资源,因此应尽可能地避免在业务时间内执行此项工作。
建议定期重新组织表空间的频度为每半年一次。

2.11.1.APL

对于有聚集索引(Clustered Index)的APL表,可删除该聚集索引,并重建;
对于没有聚集索引的APL表,可选择一列创建聚集索引,然后删除。
关于如何创建和删除聚集索引的信息,请参考Sybase手册之 __Performance and Tuning: Basics__ 中的 How Indexes Work 一章。

2.11.2.DOL

自11.9.2开始,ASE引入了DOL表。与传统的APL表相比,DOL表的存贮发生了较大的变化。使用 Reorg 命令可以重组表空间的使用并提高性能。
关于reorg命令的使用,请参考Sybase手册之 __System Administration Guide Volume 2__ 中的 Using the reorg Command 一章。

3.如何自动化

第2章所介绍的操作,一些操作是可以进行自动化的,如定期备份数据库、定期更新统计信息、定期检查数据库完整性、定期重新组织表空间等等。需要注意的是:自动化的操作一定要输出执行结果。执行完后,管理员必须要查看所记录的结果文档。

3.1.使用操作系统的任务调度

采用OS支持的脚本语言(如perl、bash、BAT)编写相应的操作脚本,使用操作系统的crontab(UNIX和类UNIX系统)或任务调度(Windows系统)周期性执行,适合于所有版本的ASE数据库。缺点是需要将数据库密码以明文方式写在脚本中。
另外,通过 xp 服务也可以调用系统程序,请自行参考相关手册。

3.2.使用ASE的任务调度

自12.5.1(UNIX)/12.5.2(Windows)开始,ASE引入了任务调度(Job Scheduler)。通过JS,可以完成以前只能有OS的任务调度才能完成的功能。需要注意的是:使用JS功能,应尽可能地更新ASE版本,建议ASE版本至少为12.5.3ESD4。
有关JS的配置和使用信息,本文不再描述。请参考本文的WORD版本(注:该WORD版本不再更新),或Sybase手册之 __Job Scheduler User’s Guide__

附录AOracle DBA的任务(部份)

以下内容摘自 Oracle 10G Administration I Study Guide ,第26页

  • 选择运行数据库软件的服务器硬件
  • 安装和配置Oracle 10g
  • 创建数据库
  • 创建和管理应用所需的表和其它对象
  • 创建和管理用数据库用户
  • 建立可靠的备份和恢复过程
  • 监视和优化数据库性能
查看原文

赞 0 收藏 0 评论 0

开不了口 发布了文章 · 1月31日

DBA的40条军规

1、涉及业务上的修改/删除数据,在得到业务方、CTO的邮件批准后方可执行,执行前提前做好备份,必要时可逆。


2、所有上线需求必须走工单系统,口头通知视为无效。


3、在对大表做表结构变更时,如修改字段属性会造成锁表,并会造成从库延迟,从而影响线上业务,必须在凌晨0:00 后业务低峰期执行,另统一用工具 pt-online-schema-change 避免锁表且降低延迟执行时间。

使用范例:

#pt-online-schema-change --alter="add index IX_id_no(id_no)"

--no-check-replication-filters --recursion-method=none --user=dba

--password=123456 D=test,t=t1 --execute

对于MongoDB创建索引要在后台创建,避免锁表。

使用范例:

db.t1.createIndex({idCardNum:1},{background:1})


4、所有线上业务库均必须搭建MHA高可用架构,避免单点问题。


5、给业务方开权限时,密码要用MD5加密,至少16位。权限如没有特殊要求,均为select查询权限,并做库表级限制。


6、删除默认空密码账号。

deletefrom mysql.user whereuser='' andpassword='';

flushprivileges;


7、汇总库开启Audit审计日志功能,出现问题时方可追溯。


行为规范

8、禁止一个MySQL实例存放多个业务数据库,会造成业务耦合性过高,一旦出现问题会殃及池鱼,增加了定位故障问题的难度。通常采用多实例解决,一个实例一个业务库,互不干扰。


9、禁止在主库上执行后台管理和统计类的功能查询,这种复杂类的SQL会造成CPU的升高,进而会影响业务。


10、批量清洗数据,需要开发和DBA共同进行审查,应避开业务高峰期时段执行,并在执行过程中观察服务状态。


11、促销活动等应提前与DBA当面沟通,进行流量评估,比如提前一周增加机器内存或扩展架构,防止DB出现性能瓶颈。


12、禁止在线上做数据库压力测试。


基本规范

13、禁止在数据库中存储明文密码。


14、使用InnoDB存储引擎。

  • 支持事务,行级锁,更好的恢复性,高并发下性能更好。
  • InnoDB表避免使用COUNT(*)操作,因内部没有计数器,需要一行一行累加计算,计数统计实时要求较强可以使用memcache或者Redis。
    • *

15、表字符集统一使用UTF8。

  • 不会产生乱码风险。
    • *

16、所有表和字段都需要添加中文注释。

  • 方便他人、方便自己。
    • *

17、不在数据库中存储图片、文件等大数据。

  • 图片、文件更适合于GFS分布式文件系统,数据库里存放超链接即可。
    • *

18、避免使用存储过程、视图、触发器、事件。

MySQL是OLTP应用,最擅长简单的增、删、改、查操作,但对逻辑计算分析类的应用,并不适合,所以这部分的需求最好通过程序上实现。


19、避免使用外键,外键用来保护参照完整性,可在业务端实现。

  • 外键会导致父表和子表之间耦合,十分影响SQL性能,出现过多的锁等待,甚至会造成死锁。
    • *

20、对事务一致性要求不高的业务,如日志表等,优先选择存入MongoDB。

  • 其自身支持的sharding分片功能,增强了横向扩展的能力,开发不用过多调整业务代码。
    • *

库表设计规范

21、表必须有主键,例如自增主键。

  • 这样可以保证数据行是按照顺序写入,对于SAS传统机械式硬盘写入性能更好,根据主键做关联查询的性能也会更好,并且还方便了数据仓库抽取数据。从性能的角度来说,使用UUID作为主键是个最不好的方法,它会使插入变得随机。
    • *

22、禁止使用分区表。

  • 分区表的好处是对于开发来说,不用修改代码,通过后端DB的设置,比如对于时间字段做拆分,就可以轻松实现表的拆分。但这里面涉及一个问题,查询的字段必须是分区键,否则会遍历所有的分区表,并不会带来性能上的提升。此外,分区表在物理结构上仍旧是一张表,此时我们更改表结构,一样不会带来性能上的提升。所以应采用切表的形式做拆分,如程序上需要对历史数据做查询,可通过union all的方式关联查询。另外随着时间的推移,历史数据表不再需要,只需在从库上dump出来,即便捷地迁移至备份机上。

字段设计规范

23、用DECIMAL代替FLOAT和DOUBLE存储精确浮点数。
浮点数的缺点是会引起精度问题,请看下面一个例子:

mysql> CREATE TABLE t3 (c1 float(10,2),c2 decimal(10,2));

Query OK, 0 rows affected (0.05 sec)

mysql> insert into t3 values (999998.02, 999998.02);

Query OK, 1 row affected (0.01 sec)

mysql> select * from t3;

| c1 | c2 |

| 999998.00 | 999998.02 |

1 row inset (0.00 sec)

可以看到c1列的值由999998.02变成了999998.00,这就是float浮点数类型的不精确性造成的。因此对货币等对精度敏感的数据,应该用定点数表示或存储。


24、使用TINYINT来代替ENUM类型。

采用enum枚举类型,会存在扩展的问题,例如用户在线状态,如果此时增加了:5表示请勿打扰、6表示开会中、7表示隐身对好友可见,那么增加新的ENUM值要做DDL修改表结构操作了。


25、字段长度尽量按实际需要进行分配,不要随意分配一个很大的容量。

选择字段的一般原则是保小不保大,能用占用字节少的字段就不用大字段。比如主键,强烈建议用int整型,不用uuid,为什么?省空间啊。空间是什么?空间就是效率!按4个字节和按32个字节定位一条记录,谁快谁慢太明显了。涉及几个表做join时,效果就更明显了。更小的字段类型域名交易占用的内存就更少,占用的磁盘空间和磁盘I/O也会更少,而且还会占用更少的带宽。

有不少开发人员在设计表字段时,只要是针对数值类型的全部用int,但这不一定合适,就比如用户的年龄,一般来说,年龄大都在1~100岁之间,长度只有3,那么用int就不适合了,可以用tinyint代替。又比如用户在线状态,0表示离线、1表示在线、2表示离开、3表示忙碌、4表示隐身等,其实类似这样的情况,用int都是没有必要的,浪费空间,采用tinyint完全可以满足需要,int占用的是4字节,而tinyint才占用1个字节。

int整型有符号(signed)最大值是2147483647,而无符号(unsigned)最大值是4294967295,如果你的需求没有存储负数,那么建议改成有符号(unsigned),可以增加int存储范围。

int(10)和int(1)没有什么区别,10和1仅是宽度而已,在设置了zerofill扩展属性的时候有用,例:

root@localhost(test)10:39>create table test(id int(10) zerofill,id2 int(1));

Query OK, 0 rows affected (0.13 sec)

root@localhost(test)10:39>insert into test values(1,1);

Query OK, 1 row affected (0.04 sec)

root@localhost(test)10:56>insert into test values(1000000000,1000000000);

Query OK, 1 row affected (0.05 sec)

root@localhost(test)10:56>select * from test;

| id | id2 |

| 0000000001 | 1 |

| 1000000000 | 1000000000 |

2 rows in set (0.01 sec)


26、字段定义为NOT NULL要提供默认值。
从应用层角度来看,可以减少程序判断代码,比如你要查询一条记录,如果没默认值,你是不是得先判断该字段对应变量是否被设置,如果没有,你得通过java把该变量置为''或者0,如果设了默认值,判断条件可直接略过。

NULL值很难进行查询优化,它会使索引统计更加复杂,还需要MySQL内部进行特殊处理。


27、尽可能不使用TEXT、BLOB类型。

  • 增加存储空间的占用,读取速度慢。

索引规范

28、索引不是越多越好,按实际需要进行创建。

  • 索引是一把双刃剑,它可以提高查询效率但也会降低插入和更新的速度并占用磁盘空间。适当的索引对应用的性能至关重要,而且在MySQL中使用索引它的速度是极快的。遗憾的是,索引也有相关的开销。每次向表中写入时(如INSERT、UPDATEH或DELETE),如果带有一个或多个索引,那么MySQL也要更新各个索引,这样索引就增加了对各个表的写入操作的开销。只有当某列被用于WHERE子句时,才能享受到索引的性能提升的好处。如果不使用索引,它就没有价值,而且会带来维护上的开销。
    • *

29、查询的字段必须创建索引。

  • 如:1、SELECT、UPDATE、DELETE语句的WHERE条件列;2、多表JOIN的字段。
    • *

30、不在索引列进行数学运算和函数运算。
无法使用索引,导致全表扫描。
例:SELECT * FROM t WHERE YEAR(d) >= 2016;
由于MySQL不像Oracle那样支持函数索引,即使d字段有索引,也会直接全表扫描。
应改为----->
SELECT * FROM t WHERE d >= '2016-01-01';


31、不在低基数列上建立索引,例如‘性别’。
有时候,进行全表浏览要比必须读取索引和数据表更快,尤其是当索引包含的是平均分布的数据集是更是如此。对此典型的例子是性别,它有两个均匀分布的值(男和女)。通过性别需要读取大概一半的行。在种情况下进行全表扫描浏览要更快。


32、不使用%前导的查询,如like ‘%xxx’。
无法使用索引,导致全表扫描。

低效查询

SELECT * FROM t WHEREnameLIKE '%de%';

----->

高效查询

SELECT * FROM t WHEREnameLIKE 'de%';


33、不使用反向查询,如 not in / not like。
无法使用索引,导致全表扫描。


34、避免冗余或重复索引。
联合索引IX_a_b_c(a,b,c) 相当于 (a) 、(a,b) 、(a,b,c),那么索引 (a) 、(a,b) 就是多余的。


SQL设计规范

35、不使用SELECT ,只获取必要的字段。*
消耗CPU和IO、消耗网络带宽;
无法使用覆盖索引。


36、用IN来替换OR。

低效查询

SELECT * FROM t WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30;

----->

高效查询

SELECT * FROM t WHERE LOC_IN IN (10,20,30);


37、避免数据类型不一致。

SELECT * FROM t WHEREid = '19';

----->

SELECT * FROM t WHEREid = 19;


38、减少与数据库的交互次数。

INSERTINTO t (id, name) VALUES(1,'Bea');

INSERTINTO t (id, name) VALUES(2,'Belle');

INSERTINTO t (id, name) VALUES(3,'Bernice');

----->

INSERTINTO t (id, name) VALUES(1,'Bea'), (2,'Belle'),(3,'Bernice');

Updatewhereidin (1,2,3,4);

Altertable tbl_name addcolumn col1, addcolumn col2;


39、拒绝大SQL,拆分成小SQL。

低效查询

SELECT * FROM tag

JOIN tag_post ON tag_post.tag_id = tag.id

JOIN post ON tag_post.post_id = post.id

WHERE tag.tag = 'mysql';

可以分解成下面这些查询来代替

----->

高效查询

SELECT * FROM tag WHERE tag = 'mysql'

SELECT * FROM tag_post WHERE tag_id = 1234

SELECT * FROM post WHERE post_id in (123, 456, 567, 9098, 8904);


40、禁止使用order by rand()

SELECT * FROM t1 WHERE 1=1 ORDERBYRAND() LIMIT 4;

---->

SELECTFROM t1 WHEREid >= CEIL(RAND()1000) LIMIT 4;

查看原文

赞 0 收藏 0 评论 0

开不了口 发布了文章 · 1月30日

Javascript DOM封装方法汇总

一:在页面上增加类和删除类的方法:

增加类:传两个参数 节点和类名 方法如下:

  1. function addClass(obj,className){
  2. obj.className+=" " +className;
  3. return obj;
  4. }

删除类 也是传两个参数 节点和类名 然后获取该节点的所有类名并且用正则表达式 用空格把他们隔开 然后一个for循环 判断当前的任何一个类名 是不是和我当前的传的参数类名 是不是相同 如果是相同的话 就删掉 最后返回该对象!代下:

  1. function delClass(obj,className){
  2.         var s = obj.className.split(/s+/);//正则把类名分开
  3. for(var i=0;i<s.length;i++){
  4. if(s[i]==className){
  5. delete s[i];
  6. }
  7. }
  8. obj.className = s.join(" ");
  9. return obj;
  10. }

二:简单的用try{}catch(e){}语句写个收藏夹:

代码如下:

  1. <a href="javascript:void(0)" onclick="addFav()">aaaaa
  2. <script>
  3.         //把相应网址添加到收藏夹里面去
  4.         //IE的方式是:window.external.addFavorite(sUrl,sTitle);
  5.         //火狐的方式是:window.sidebar.addPanel(sTitle,sUrl,"");
  6.         //IE和火狐分别实现了自己的添加到收藏夹的方式 其中address是页面的网址 name是页面的标题
  7.         //可以写个简单的函数来判断
  8. function addFav(){
  9. try{
  10.                 window.sidebar.addPanel("百度","http://www.baidu.com",""); //火狐的
  11. }catch(e){
  12. try{
  13.                     window.external.addFavorite("http://www.baidu.com","百度"); //IE的
  14. }catch(e){
  15. }
  16. }
  17. }
  18. </script>

三 跨游览器事件:

  1. function addEvent(obj,evt,fn,userCape){
  2. if(obj.addEventListener){
  3. obj.addEventListener(evt,fn,false);
  4. }else{
  5. obj.attachEvent("on"+evt,function(){
  6. fn.call(obj);
  7. });
  8. }
  9. }
  10. function delEvent(obj,evt,fn,userCape){
  11. if(obj.removeEventListener){
  12. obj.removeEventListener(evt,fn,false);
  13. }else{
  14. obj.detachEvent("on"+evt,fn);
  15. }
  16. }
  17. function fixEvt(evt){
  18. evt = evt || window.event;
  19.         if(!evt.target){  // IE下
  20. evt.target = evt.srcElement;
  21. evt.layerX = evt.offsetX;
  22. evt.layerY = evt.offsetY;
  23. evt.stopPropagation = function(){
  24. this.cancelBubble = true;
  25. }
  26. }
  27. return evt;
  28. }
  29. </script>

特别注意上面的attachEvent这个方法 千万不要写成这样的 attachEvent(obj,"on"+evt,fn),如果写成这样的话 那么函数fn(如果你用this的话 那么函数内的this就指向与window)所以这也是个IEbug 因为我们是想要该函数this指向与当前实例化对象 所以我们要改成这样的方式:attachEvent(obj,"on"+evt,function(){fn.call(obj)});用当前的函数调用该对象 那么该函数的指针就指向与该对象!这个地方要注意点!虽然改了后 this也是指向与对象的!但是用这个函数也要注意一个地方 如果用attachEvent(obj,"on"+evt,function(){fn.call(obj)}); 这个方法在IE下 注册三个同样的函数 三个同样的事件的话 那么在IE下 你点击一下元素的话 他们会同时触发三个事件 而在火狐下只会触发一个事件 所以用这个函数在IE下 也并不是说没有bug 但是用这个函数注册事件的话 一般情况下是没有什么问题的 但是这个也是我们要注意的地方!如果要修改下这个bug话 也并不是说不能改 只是要写个更复杂的函数来判断下 就是在页面上判断当前函数及事件是不是在页面上已经注册了 如果注册了 那么我们就返回!但是这个函数比较复杂 所以也没有去研究!不过用上面的那种方式一般情况下足够了!

四:滚动的文字:

在有的网站上我们经常看到title有文字滚动的效果:其实这个用js来实现也是一件非常简单的事情!其实这个和在页面上实现滚动是一样的道理 首先我们用document.title就可以获取该对象内容 接着我们只做两件事情 第一用字符串分开 第二把第一个文字放到最后去

代码如下:

  1. <button id="oStart">开始</button>
  2.     <button id="oEnd">结束</button>
  3. <!--  <script>
  4. function $(id){
  5. return document.getElementById(id);
  6. }
  7. var start = $("oStart"),
  8. end = $("oEnd");
  9.         var flag; //用flag有个问题 就是不能清除缓存 当我点击停止时候 等一段时间后 再点击开始 文字会滚动的很快
  10. addEvent(start,'click',scroll);
  11. addEvent(end,'click',stop);
  12. function scroll(){
  13. flag = true;
  14. var s = document.title.split("");
  15. setInterval(function(){
  16. if(flag){
  17. s.push(s.shift());
  18. document.title = s.join("");
  19. }
  20. },300);
  21.             this.disabled = true; //点击后 让按钮成为不可以点击的状态
  22.             end.disabled = false; //让结束按钮成为可以点击的状态
  23. }
  24. function stop(){
  25. flag = false;
  26. start.disabled = false;
  27. this.disabled = true;
  28. }
  29. function addEvent(obj,evt,fn,userCape){
  30. if(obj.addEventListener){
  31. obj.addEventListener(evt,fn,false);
  32. }else{
  33. obj.attachEvent("on" + evt,function(){
  34. fn.call(obj);
  35. })
  36. }
  37. }
  38. </script> -->

上面代码 在页面上给了两个按钮 当我点击开始时候 文字就开始滚动 当点击结束时候 文字就停止滚动!然后点击后 在相应的按钮上设置成不可点击状态!上面的函数用了一个变量flag 判断如果是true的话 用setInterval执行这个函数 但是用flag有个bug 就是不能清除网站监控游览器的缓存 就是当我点击停止按钮后 再点击开始时候 文字会滚动的很快!这样的效果并不是我们想要的!

所以我们可以接着继续写代码:

  1. function $(id){
  2. return document.getElementById(id);
  3. }
  4. var start = $("oStart"),
  5. end = $("oEnd");
  6. var t;
  7. addEvent(start,'click',scroll);
  8. addEvent(end,'click',stop);
  9. function scroll(){
  10. var s = document.title.split("");
  11. clearInterval(t);
  12. t = setInterval(function(){
  13. s.push(s.shift());
  14. document.title = s.join("");
  15. },300);
  16. }
  17. function stop(){
  18.             clearInterval(t); //用这种方式就可以清除缓存 但是还存在另一个问题 就是说假如我点击多次开始按钮时候 它还会执行上面的代码setInterval这段代码
  19. // 而现在我执行结束按钮时候 结束不掉 !所以我们要做他们执行setIvterval之前要clearInterval清除一次
  20. }
  21. function addEvent(obj,evt,fn,userCape){
  22. if(obj.addEventListener){
  23. obj.addEventListener(evt,fn,false);
  24. }else{
  25. obj.attachEvent("on" + evt,function(){
  26. fn.call(obj);
  27. })
  28. }
  29. }
  30. </script>

代码如上所示!

下面我们就在文档中写个简单的文字滚动 当然是用面向对象的方式来写个函数 代码如下:

  1. <!-- 上面实现标题文字滚动没有多大意思 下面实现段落或者div中的文字滚动-->
  2. <p id="op">aaaaaa</p>
  3. <script>
  4. function $(id){
  5. return document.getElementById(id);
  6. }
  7. obj = $("op");
  8. var oStart = $("oStart");
  9. var oEnd = $("oEnd");
  10. function addEvent(obj,evt,fn,userCape){
  11. if(obj.addEventListener){
  12. obj.addEventListener(evt,fn,false);
  13. }else{
  14. obj.attachEvent("on" + evt,function(){
  15. fn.call(obj);
  16. })
  17. }
  18. }
  19. var test;
  20. /* function scrollText(text,fn,t){
  21. test = text.split("");
  22. setInterval(function(){
  23. test.push(test.shift());
  24. fn(test.join(""));
  25. },t)
  26.     } */  //这种方式可以 但是如果我想要和上面一样 能有控制按钮 那么我们现在可以使用下面面向对象方法来解决
  27. function ScrollText(s,fn,t){
  28. this.s = s.split("");
  29. this.fn = fn;
  30. tthis.t = t || 500;
  31. }
  32. ScrollText.prototype = {
  33. start : function(){
  34. clearInterval(this.interval);
  35. var s = this.s,fn = this.fn;
  36. this.interval = setInterval(function(){
  37. s.push(s.shift());
  38. fn(s.join(""));
  39. },this.t);
  40. },
  41. stop : function(){
  42. clearInterval(this.interval);
  43. }
  44. }
  45.     var sss = new ScrollText("要滚动的文字",function(g){
  46. obj.innerHTML = g;
  47. },1000);
  48. oStart.onclick = function(){
  49. sss.start();
  50. };
  51. oEnd.onclick = function(){
  52. sss.stop()
  53. };

上面就是用了个简单的面向对象的方式写了个简单的函数!

五:获取类名封装(getElementsByClassName)

在用js时候 我们经常要用到获取类名的方法!但是我们原审js是没有这个方法的 当然不用js框架情况下 那么我们可以简单的用函数封装一个!但是封装这个函数之前 我们要考虑先写个简单的函数 就是hasClass()这个方法 这个方法就是判断当前的页面有没有这个类 这个方法也是为获取类名做好准备的 首先我们肯定要判断当前的页面有没有这个类名 如果有的话 我就获取它!下面是hasClass()方法

  1. //检查有没有类
  2. function hasClass(node,className){
  3.         var names = node.className.split(/s+/);//正则表达式所有的类名用空格分开
  4.         //遍历这个类名
  5. for(var i=0;i<names.length;i++){
  6. if(names[i]==className)
  7. return true;
  8. }
  9. return false;
  10. }

下面是获取类名的代码:

  1. function getElementsByClassName(className,context){
  2. context = context || document;
  3. if(context.getElementsByClassName){
  4. return context.getElementsByClassName(className);
  5. }
  6.     var nodes = context.getElementsByTagName("*"),ret=[];//获取页面上的所有节点
  7.     for(var i=0;i<nodes.length;i++){   //遍历所有的节点
  8. if(hasClass(nodes[i],className)) ret.push(nodes[i]);
  9. }
  10. return ret;
  11. }

获取类名的代码 和我那个相比简单很多 只是传了两个参数 一个是类名 还有一个当前的上下文!默认情况下文档document !代码也比较简洁!

下面是所有代码一起贴上来吧!

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>无标题文档</title>
  6. <style>
  7. </style>
  8. </head>
  9. <body>
  10. <div class="oDiv">
  11. <p>aaa</p>
  12. <div></div>
  13. </div>
  14. <script>
  15. function getElementsByClassName(className,context){
  16. contextcontext = context || document;
  17. if(context.getElementsByClassName){
  18. return context.getElementsByClassName(className);
  19. }
  20.         var nodes = context.getElementsByTagName("*"),ret=[];//获取页面上的所有节点
  21.         for(var i=0;i<nodes.length;i++){   //遍历所有的节点
  22. if(hasClass(nodes[i],className)) ret.push(nodes[i]);
  23. }
  24. return ret;
  25. }
  26. //检查有没有类
  27. function hasClass(node,className){
  28.         var names = node.className.split(/s+/);//正则表达式所有的类名用空格分开
  29.         //遍历这个类名
  30. for(var i=0;i<names.length;i++){
  31. if(names[i]==className)
  32. return true;
  33. }
  34. return false;
  35. }
  36. //获取元素的第一个子节点 js本来是有获取第一个子节点的方法 但是获取时候会把空白 其他字符也当作节点 此函数就是为了解决这样的方法
  37. function firstNode(node){
  38. if(node.firstChild){
  39. if(node.firstChild.nodeType==1){
  40. return node.firstChild;
  41. }else{
  42. var n = node.firstChild;
  43. while(n.nextSibling){
  44. if(n.nextSibling.nodeType==1) return n.nextSibling;
  45. nn = n.nextSibling;
  46. }
  47. return null;
  48. }
  49. return null;
  50. }
  51. }
  52.     function next(node){ //返回node的下一个兄弟元素
  53. if(node.nextSibling){
  54. if(node.nextSibling.nodeType==1){
  55. return node.nextSibling;
  56. }else{
  57. var n = node.nextSibling;
  58. while(n.nextSibling){
  59. if(n.nextSibling.nodeType==1) return n.nextSibling;
  60. nn = n.nextSibling;
  61. }
  62. return null;
  63. }
  64. return null;
  65. }
  66. }
  67. </script>
  68. <script>
  69. var ss = getElementsByClassName("oDiv")[0];
  70. var kk = ss.childNodes;
  71. var h = firstNode(ss);
  72. alert(h.tagName)
  73. </script>
  74. </body>
  75. </html>

javascript判断浏览器是不是IE6

  1. if(window.XMLHttpRequest){ //Mozilla, Safari, IE7
  2. if(!window.ActiveXObject){ // Mozilla, Safari,
  3. alert('Mozilla, Safari');
  4. }else{
  5. alert('IE7');
  6. }
  7. }else {
  8. alert('IE6');
  9. }
查看原文

赞 0 收藏 0 评论 0

开不了口 发布了文章 · 1月29日

一篇文章带你了解JavaScript函数

一个JavaScript函数是一个为执行特定任务而设计的代码块,JavaScript函数当调用时被执行。

一、函数语法

一个JavaScript函数使用function关键字定义,后面跟一个函数名称,后面跟一对括号()。

函数名可以包含字母、数字、下划线和美元符号(与变量相同的规则)。

圆括号可以包括参数名称以逗号分隔: (parameter1, parameter2, ...)。

函数要执行的代码,被放置在括号内: {}。

语法:

  1. functionname(parameter1, parameter2, parameter3) {
  2. code to be executed
  3. }

函数形式参数是函数定义中列出的名称,实际参数是函数调用时接收的实际值。

在函数中,实际参数(形式参数)表现为局部变量。

例:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.   <meta charset="UTF-8">
  5.   <title>项目</title>
  6. </head>
  7. <body>
  8. <h1>JavaScript 函数</h1>
  9.   <p>调用函数完成计算,并返回结果:</p>
  10.   <p id="demo"></p>
  11. <script>
  12. function myFunction(p1, p2) {
  13. return p1  p2; / 函数返回p1和p2的乘积*/
  14. }
  15.     document.getElementById("demo").innerHTML = myFunction(4, 3);
  16. </script>
  17. </body>
  18. </html>

注:

在其他编程语言中,函数与过程或子程序非常相似。

1.函数调用

当某些情况下调用函数时,函数内的代码将执行。

  1. //当事件发生(例如:当用户单击按钮)。
  2. //当从JavaScript代码中调用时。
  3. //自动执行 (自己调用)。

2.函数返回

当JavaScript达到一个return语句,函数将停止执行。

如果从一个语句调用了函数,JavaScript将“回归”到调用语句后继续执行代码。

函数通常计算返回值. 返回值返回给调用者。

例:

  1. var x = myFunction(4, 3);        // 函数被调用,返回值赋值给x
  2. function myFunction(a, b) {
  3. return a * b;                // 函数返回a和b的乘积
  4. }

完整代码:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.   <meta charset="UTF-8">
  5.   <title>项目</title>
  6. </head>
  7. <body>
  8. <h1>JavaScript 函数</h1>
  9.   <p>此示例调用一个函数,该函数执行计算并返回结果:</p>
  10.   <p id="demo"></p>
  11. <script>
  12. function myFunction(a, b) {
  13. return a * b;
  14. }
  15.     document.getElementById("demo").innerHTML = myFunction(4, 3);
  16. </script>
  17. </body>
  18. </html>

二、为什么使用函数?

您可以重用代码:定义代码一次,并多次使用它。

您可以使用同一代码多次使用不同的参数,产生不同的结果。

  1. function toCelsius(fahrenheit) {
  2. return (5/9) * (fahrenheit-32);
  3. }
  4. document.getElementById("demo").innerHTML = toCelsius(77);

1.使用()操作符调用函数

使用上面的例子,toCelsius指函数对象,而toCelsius()指作用的结果。

  1. function toCelsius(fahrenheit) {
  2. return (5/9) * (fahrenheit-32);
  3. }
  4. document.getElementById("demo").innerHTML = toCelsius;

2.用作变量值的函数

函数可以像变量一样使用,在所有类型的公式、赋值和计算中都可以使用。

  1. var x = toCelsius(77);
  2. var text = "The temperature is " + x + " Celsius";

三、总结

文章主要从俩个方面介绍了JavaScript函数的应用,从函数的语法,怎么去调用函数,函数的返回值,以及在编程中为什么需要函数的加入等等,都做了详细讲解。通过实践案例的操作,运行效果的展示,能够让读者更好的理解。

查看原文

赞 0 收藏 0 评论 0

开不了口 发布了文章 · 1月28日

展望2021丨驱动企业数字化转型的十大技术趋势

艰难的2020年过去了。疫情使全球经济陷入几乎停滞状态,越来越多的企业在2021年将把IT战略重点聚焦于如何将数据转化为业务智能,如何重构后疫情时代平台化业务“底座”,如何布局“新基建”,打造智能物联生态环境,如何保证IT架构的平滑演进与持续升级,以及如何构建创新驱动新动能。

新的一年,在数字化转型的进程中,我们仍会遇到困难和挑战,会经历一个不断迭代的过程,需要快速适应、积极探索、持续演进,看清方向谋求长足发展。那么在接下来的2021年,哪些领域,那些技术或将驱动企业数字化转型呢?笔者简单梳理了十大或将影响企业数字化转型的热点技术,希望对您有所帮助。

混合云成为新常态

疫情加速企业向混合云演进。来自德勤的数据显示,全球85%的企业选择混合云作为“理想IT”形态,其中61%的用户表示跨不同云的应用可移动性是平滑上混合云最重要的评估要素。

数字化转型正在推动中国云计算进一步演进升级。中国企业级用户的云计算部署和应用正在从早期的企业级云计算1.0向2.0演进。同时,未来两年,应用创新正在加速中国企业级用户对混合云的需求从初期通过VPN实现数据互联互通,以及基于API实现混合云的资源管理,向企业级一致性工作负载双向迁移和智能管理演进。

企业业务向数字化转型的过程中,往往需要针对不同的应用选择不同的资源,或私有云,或公有云,甚至多种不同的云。因此,保证多云和混合云的企业级一致性以及工作负载双向迁移是企业对云计算应用的核心诉求。

边缘计算不“边缘”

预测显示,到2025年,全球物联网设备数量将达到416亿台,所产生的数据量将达到79.4ZB。同时有预测显示,到2023年,企业将有超过50%的新应用部署于边缘端;到2024年,边缘应用程序的数量将增加800%。

人与机器、机器与机器间的数据交互和数据分析以及建模、推理,将使应用和数据的“重心”快速移向边缘端。边缘计算具有低延迟、高弹性、保证业务合规以及有效降低数据管理成本等优势。

随着“新基建”的逐渐落地,5G、工业互联网、人工智能、物联网将得到快速发展和应用,将极大地刺激中国市场对边缘计算需求的快速增长,加速边缘计算在制造业、交通运输、医疗卫生、农业、智能家居以及虚拟现实应用等行业的迅速发展。

人工智能持续火热

根据IDC预测,2019-2024年全球人工智能年复合增长率为17.1%,到2024年人工智能全球收入将达到3000亿美元。

在中国,AI产业近年来发展迅猛,有相关预测表明,中国AI市场规模的年复合增长率将超过40%。中桥最新调研数据显示,有42.8%的受访企业表示未来两年将为AI做IT方面投入,同时,2021年,AI将成为中国企业用户IT重点战略之一。

数字化、智能化已经成为不可阻挡的趋势,受到今年新冠疫情的影响,这一趋势正在以更快的速度席卷而来。

5G真的来了

经过几年的炒作和承诺,我们将看到5G的扩散,随之而来的将是围绕通信基础设施、远程管理模式和连接的范式转变,这将影响到服务器的外形尺寸和功能。随着企业开发更多的边缘基础设施来处理数据的产生和涌入,5G将使客户需要重新评估其边缘连接和基础设施管理产品,以利用5G功能。

5G的新标准在21世纪开始确立,我们将克服在云IT和一般IT方面最大的问题,那就是分散的连接。解决了连接问题,能够释放出巨大的价值,包括如何做混合IT、如何在家工作、如何做数字医疗、如何数字化转型,以及如何做任何企业想要实现的事情。

异构计算大势所趋

当前,全球进入以数据为关键生产要素的数字经济时代,数字化应用的蓬勃发展促使数据资源发生新变化,数据体量爆发式增长、数据结构多样性演变、数据资源泛在分布以及每比特数据应用成本不断下降。数据要素变化引发计算技术演进,异构、极致、泛在、协同、绿色、普惠成为数字经济时代“新计算”特征,为全球计算产业带来发展新机。

处理器的格局正在发生变化,它正在成为一个收购、专业化和供应商差异化整合的环境。我们看到很多大厂都在进行收购,为各自的产品组合提供CPU、DPU和加速器。真正的赢家将能够利用他们的硅产品和软件库组合,形成针对工作负载的集成产品“配方”,帮助终端客户优化业务成果。

IT基础设施垂直化

未来,同质化的IT基础设施将被淘汰。随着客户从基础设施中寻找最有效的成果,行业将继续看到更多垂直化和专业化的产品,软件生态系统的实现和特定领域的加速器,可满足独特的性能和功能要求,并针对特定的业务成果进行优化。

安全无小事

围绕数据访问和数据保护的安全性从未如此具有挑战性,因此客户需要他们的安全性信心能够被量化,以衡量基础设施的可信度并识别数字风险。客户需要分析产品的起源和功能、新的安全技术,以及在监管趋严的环境背景下对特定的数字威胁进行细分,以便从边缘到核心到云,制定他们对IT信任的衡量标准。

远程办公新常态

2020年突如其来的疫情使之前发展平缓的远程办公得到爆发性增长,持续的疫情,使远程办公成为办公服务的常态。

后疫情时期,远程办公将进入到一个新的发展阶段,向平台化和智能化方向发展。有数据显示,疫情后,混合远程桌面办公平台将成为协作办公新常态。同时,多数企业认为,未来他们需要的是具有安全性、平台化、智能化、多功能和个性化等特性的协作办公平台。

智能存储释放数据价值

在数字经济时代,数据成为业务创新、产业生态协作、业务合规的核心资源。随着5G、工业互联网、人工智能、物联网等新兴技术的快速发展,如何将数据转化为企业业务价值,这是每一个企业都面临的挑战。

为应对企业级用户的不同需求,将人工智能技术引入企业级存储中已是势在必行。构建智能化的存储平台,智能整合和管理存储资源,智能判断并提供不同应用所需要的存储资源,并能细粒度化,实现数据的动态迁移,满足企业多云环境下的需求,帮助企业在数字化转型中,释放数据价值。

数字平台成为数字化转型的助推器

数字平台的建设和应用,能够促进企业内和企业间以及外部的资源流动与能力互补。未来,能够建立竞争优势的企业必将是技术驱动的,特别是以“大智移云”为代表的 ICT 数字技术驱动的。

在此基础上,企业搭建跨边界共享的数字平台是大势所趋。数字平台更是企业数字化转型和创新的助推器,促进数据、技术、资源、市场等全要素的互联和资源配置优化,驱动企业数字化转型的深入。

写在最后,数字化转型是未来十年的商业巨变和机会,我们正处在一个数字经济快速发展的时代,数字化转型已成为产业变革的主旋律,抓住数字化转型的关键时机对于企业存亡兴衰有着至关重要的意义,2021企业数字化转型,将开启新的征程

查看原文

赞 0 收藏 0 评论 0

开不了口 发布了文章 · 1月28日

网络安全趋势预测:勒索软件威胁最大,安全支出将飙升

展望2021年,疫情并没有退散,远程办公还将影响未来办公方式。在这个背景下,我们结合各个知名组织对未来安全的预测,进行汇总、总结以及评价。

2020年疫情这次黑天鹅事件加速了数字化转型进程。在这个背景下,远程办公、数字化转型让全球网络犯罪激增400%,全球网络安全市场趋向深度防御和快速检测/响应。此外,网络安全正在迎来一场来自灵魂深处的变革,威胁情报、AI助推自动化、智能化的下一代安全运营。

展望2021年,疫情并没有退散,远程办公还将影响未来办公方式。在这个背景下,我们结合各个知名组织对未来安全的预测,进行汇总、总结以及评价,涵盖安全的各个领域,诸如全球网络安全支出、内容安全管理市场、电子邮件安全趋势、全球虚拟专用网市场规模发展、视频监控趋势,还有专业组织对2021年安全市场的预测。

2021年CISO调查预测

据Proofpoint报告,53%的CISO和CSO报告他们的组织在2020年遭受了至少一次重大网络攻击,其中14%遭受了多次攻击。遗憾的是,2021年这种趋势并不会下降,有64%的人表示担心其组织在2021年有遭受攻击的风险,更令人担忧的是,仍然有28%的受访者认为2021年网络攻击不会造成大麻烦。

2021年,随着云计算的快速普及,勒索软件越来越多地将以云存储为目标,以最大程度地发挥影响力并增加杠杆作用以提高利润、扩大企业数据泄露规模和风险。

Proofpoint的调查显示,有46%的CSO/CISO认为勒索软件是未来两年对其业务最大的网络安全威胁。其次是云账户入侵(39%)、内部威胁(33%)和网络钓鱼(30%)。

内容安全管理市场规模

据Frost&Sullivan的预测,到2024年,安全内容管理(SCM)市场的复合年增长率将达到11.4%,其中Web和电子邮件安全总收入将达到22亿美元。从这里可以看出恶意电子邮件和Web链接仍然是最流行的攻击媒介,这类威胁包括更高级和更复杂的网络钓鱼电子邮件、企业电子邮件泄露和恶意内容。

随着组织寻求在云迁移过程中能更好地兼容并同时节省成本,厂商正在将多种功能添加到核心功能中,传统的Web和电子邮件安全产品形态正在发生改变。大型企业通常需要专用的Web和电子邮件安全性才能有效地检测、预防和补救威胁。这些财力雄厚的公司通常会优先考虑绩效,并会选择独立的解决方案或同类最佳的选择。

网络安全支出将飙升

据Canalys的最新报告,随着全球经济从大流行中缓慢复苏,2021年全球网络安全市场将增长10%,达到600亿美元,即使在最坏的情况下,2021年网络安全支出也将增长6.6%。

虽然新冠病毒大流行对全球经济产生超出预期的巨大冲击,中小企业以及酒店、零售和运输等行业受到了沉重打击,但是网络安全市场已被证明对这场大流行引起的全球经济危机具有显着的复原力。

Canalys报告对2021年增长最快的网络安全细分领域的预测如下:

· 网络和电子邮件安全性(12.5%)

· 漏洞和安全分析(11%)

· 数据安全性(6.6%)

· 网络安全性(8%)

欺骗式防御技术将爆发

根据IDG的《安全优先研究》,为应对快速增长的安全威胁,2021年企业将会积极测试、评估和实施以下六种热门技术:

· 零信任(40%)

· 欺骗技术(32%)

· 身份验证解决方案(32%)

· 访问控制(27%)

· 应用程序监视(25%)

· 基于云的安全服务(22%)

除了零信任、身份验证、访问控制、云安全、应用监控等霸榜技术之外,欺骗技术的排名蹿升幅度之大令人印象深刻。接近三分之一(32%)的受访企业表示正在积极研究欺骗技术——现代化的蜜罐技术,基于经典的手动部署的蜜罐,提供诱惑入侵者的诱饵密码列表、假数据库或假访问权限。

欺骗式防御技术将传统的蜜罐流程自动化,可以根据对真实网络区域和数据的扫描生成诱饵。它们通常作为模拟网络部署,这些模拟网络与真实网络在同一远程桌面基础结构上运行。当入侵者试图进入真实网络时,他们被导向虚假网络并立即通知安全管理人员。

电子邮件安全2021趋势

随着域名价格持续走低,账户接管变得更加容易,云计算的应用范围不断扩大,2021年电子邮件攻击的威胁也将持续增长。Darktrace电子邮件安全产品总监Dan Fein给出了五个邮件安全预测:

(1) 供应链欺诈将超过CEO欺诈

2021年供应链欺诈将超过首席执行官(高级管理层)欺诈,保护C级别的高级管理者是很多企业网络安全安全负责人的首要任务。

(2) 基于MX记录的电子邮件安全解决方案和第三方网关将被淘汰

2021年电子邮件安全的第二个风险不是来自攻击者的威胁,而是企业的电子邮件安全工具(或确切地说,其部署方式)自身的风险。

(3) 网络钓鱼攻击的生命周期将持续减缩短

与大多数攻击类似,电子邮件攻击的生命周期也在不断缩短,欺诈电子邮件的平均寿命从2018年3月的2.1天已经下降到2020年的12小时。

(4) 邮件攻击将更具针对性

“大规模的邮件攻击活动”正在被“针对性攻击”取代,一次性的钓鱼网站域名非常适合这种网络犯罪业务模型趋势。

(5) 一些攻击者选择电子邮件欺诈而不是勒索软件

2021年,对于那些扩展远程办公(更依赖云服务而不是集中的本地基础架构)的企业来说,勒索软件也许并不是攻击者最佳的选择。通过电子邮件发送的欺诈性发票可能会在谋求经济利益的网络犯罪分子中大受欢迎。Fein认为,欺诈电子邮件可能会更安静,更有利可图。

虚拟专用网市场规模预测

据Valuates的最新报告,2019年全球虚拟专用网市场价值254.1亿美元,预计到2027年将达到755.9亿美元,从2020年到2027年的复合年增长率为14.7%。分析认为,推动虚拟专用网市场规模增长主要因素包括:数据安全问题增加、高级复杂网络威胁增加以及企业内移动和无线设备使用量激增。

此外,北美有望占据最大的虚拟专用网市场份额,北美的主导地位归因于电信和BFSI等行业和垂直行业的需求快速增长。欧洲市场在虚拟专用网市场中也占有重要份额,并且预计将稳定增长。与此同时,随着虚拟专用网解决方案和服务在中国、印度和其他亚洲国家中的采用率不断提高,预计亚太地区将在预测期内实现最高复合年增长率。

2021视频监控趋势

Eagle Eye Networks发布报告,指出2021年视频监控和相关安全市场将呈现四大趋势。

· 客户要求云服务:视频监控领域向云服务的转变是大势所趋,可以节省大量成本、提高数据安全性、远程访问和维护、灵活的存储和保留、提高可扩展性、增强稳定性和灾难恢复。

· 合规要求在不断发展:随着视频监控成为各行各业和各大洲越来越广泛采用的工具,越来越多的行业将视频用于法规遵从性目的,对视频监控的使用进行监管变得越来越普遍。

· IT部门加大视频监控投入力度:IT管理者不仅参与了视频管理系统,而且实际上也是视频管理系统的拥有者。

· 对开放,集成系统的需求:开放和互联的生态系统使企业和开发人员可以在单个视频管理系统(VMS)平台上集成任意数量的应用程序。

写在最后

2021年网络安全市场将迎来新的趋势,每个细分领域安全问题得到彰显,全球网络安全支出将持续飙升,与此同时,企业对网络安全的重视程度将大幅度提升。

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 1 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 1月10日
个人主页被 1.2k 人浏览