开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

PHP7中的异常与错误处理

发表于 2017-12-06

PHP 中的 Exception, Error, Throwable

  • PHP 中将代码自身异常(一般是环境或者语法非法所致)称作错误 Error,将运行中出现的逻辑错误称为异常 Exception
  • 错误是没法通过代码处理的,而异常则可以通过 try/catch 来处理
  • PHP7 中出现了 Throwable 接口,该接口由 Error 和 Exception 实现,用户不能直接实现 Throwable 接口,而只能通过继承 Exception 来实现接口

PHP7 异常处理机制

过去的 PHP,处理致命错误几乎是不可能的。致命错误不会调用由 set_error_handler() 设置的处理方式,而是简单的停止脚本的执行。

在 PHP7 中,当致命错误和可捕获的错误(E_ERROR 和 E_RECOVERABLE_ERROR)发生时会抛出异常,而不是直接停止脚本的运行。对于某些情况,比如内存溢出,致命错误则仍然像之前一样直接停止脚本执行。在 PHP7 中,一个未捕获的异常也会是一个致命错误。这意味着在 PHP5.x 中致命错误抛出的异常未捕获,在 PHP7 中也是致命错误。

注意:其他级别的错误如 warning 和 notice,和之前一样不会抛出异常,只有 fatal 和 recoverable 级别的错误会抛出异常。

从 fatal 和 recoverable 级别错误抛出的异常并非继承自 Exception 类。这种分离是为了防止现有 PHP5.x 的用于停止脚本运行的代码也捕获到错误抛出的异常。fatal 和 recoverable 级别的错误抛出的异常是一个全新分离出来的类 Error 类的实例。跟其他异常一样,Error 类异常也能被捕获和处理,同样允许在
finally 之类的块结构中运行。

Throwable

为了统一两个异常分支,Exception 和 Error 都实现了一个全新的接口:Throwable

PHP7 中新的异常结构如下:

1
2
3
4
5
6
7
8
9
复制代码interface Throwable
|- Exception implements Throwable
|- ...
|- Error implements Throwable
|- TypeError extends Error
|- ParseError extends Error
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error

如果在 PHP7 的代码中定义了 Throwable 类,它将会是如下这样:

1
2
3
4
5
6
7
8
9
10
11
复制代码interface Throwable
{
public function getMessage(): string;
public function getCode(): int;
public function getFile(): string;
public function getLine(): int;
public function getTrace(): array;
public function getTraceAsString(): string;
public function getPrevious(): Throwable;
public function __toString(): string;
}

这个接口看起来很熟悉。Throwable 规定的方法跟 Exception 几乎是一样的。唯一不同的是 Throwable::getPrevious() 返回的是 Throwable 的实例而不是 Exception 的。Exception 和 Error 的构造函数跟之前 Exception 一样,可以接受任何 Throwable 的实例。
Throwable 可以用于 try/catch 块中捕获 Exception 和 Error 对象(或是任何未来可能的异常类型)。记住捕获更多特定类型的异常并且对之做相应的处理是更好的实践。然而在某种情况下我们想捕获任何类型的异常(比如日志或框架中错误处理)。在 PHP7 中,要捕获所有的应该使用 Throwable 而不是 Exception。

1
2
3
4
5
复制代码try {
// Code that may throw an Exception or Error.
} catch (Throwable $t) {
// Handle exception
}

用户定义的类不能实现 Throwable 接口。做出这个决定一定程度上是为了预测性和一致性——只有 Exception 和 Error 的对象可以被抛出。此外,异常需要携带对象在追溯堆栈中创建位置的信息,而用户定义的对象不会自动的有参数来存储这些信息。
Throwable 可以被继承从而创建特定的包接口或者添加额外的方法。一个继承自 Throwable 的接口只能被
Exception 或 Error 的子类来实现。

1
2
3
复制代码interface MyPackageThrowable extends Throwable {}
class MyPackageException extends Exception implements MyPackageThrowable {}
throw new MyPackageException();

Error

事实上,PHP5.x 中所有的错误都是 fatal 或 recoverable 级别的错误,在 PHP7 中都能抛出一个 Error 实例。跟其他任何异常一样,Error 对象可以使用 try/catch 块来捕获。

1
2
3
4
5
6
复制代码$var = 1;
try {
$var->method(); // Throws an Error object in PHP 7.
} catch (Error $e) {
// Handle error
}

通常情况下,之前的致命错误都会抛出一个基本的 Error 类实例,但某些错误会抛出一个更具体的 Error 子类:TypeError、ParseError 以及 AssertionError。

TypeError

当函数参数或返回值不符合声明的类型时,TypeError 的实例会被抛出。

1
2
3
4
5
6
7
8
9
10
复制代码function add(int $left, int $right)
{
return $left + $right;
}
try {
$value = add('left', 'right');
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
//Argument 1 passed to add() must be of the type integer, string given

ParseError

当 include/require 文件或 eval() 代码存在语法错误时,ParseError 会被抛出。

1
2
3
4
5
复制代码try {
require 'file-with-parse-error.php';
} catch (ParseError $e) {
echo $e->getMessage(), "\n";
}

ArithmeticError

ArithmeticError 在两种情况下会被抛出。一是位移操作负数位。二是调用 intdiv() 时分子是 PHP_INT_MIN 且分母是 -1 (这个使用除法运算符的表达式:PHP_INT_MIN / -1,结果是浮点型)。

1
2
3
4
5
复制代码try {
$value = 1 << -1;
catch (ArithmeticError $e) {
echo $e->getMessage();//Bit shift by negative number
}

DevisionByZeroError

当 intdiv() 的分母是 0 或者取模操作 (%) 中分母是 0 时,DivisionByZeroError 会被抛出。注意在除法运算符 (/) 中使用 0 作除数(也即xxx/0这样写)时只会触发一个 warning,这时候若分子非零结果是 INF,若分子是 0 结果是 NaN。

1
2
3
4
5
复制代码try {
$value = 1 % 0;
} catch (DivisionByZeroError $e) {
echo $e->getMessage();//Modulo by zero
}

AssertionError

当 assert() 的条件不满足时,AssertionError 会被抛出。

1
2
3
4
5
复制代码ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);
$test = 1;
assert($test === 0);
//Fatal error: Uncaught AssertionError: assert($test === 0)

只有断言启用并且是设置 ini 配置的 zend.assertions = 1 和 assert.exception = 1 时,assert() 才会执行并抛 AssertionError。

在你的代码中使用 Error

用户可以通过继承 Error 来创建符合自己层级要求的 Error 类。这就形成了一个问题:什么情况下应该抛出 Exception,什么情况下应该抛出 Error。

Error 应该用来表示需要程序员关注的代码问题。从 PHP 引擎抛出的 Error 对象属于这些分类,通常都是代码级别的错误,比如传递了错误类型的参数给一个函数或者解析一个文件发生错误。Exception 则应该用于在运行时能安全的处理,并且另一个动作能继续执行的情况。

由于 Error 对象不应该在运行时被处理,因此捕获 Error 对象也应该是不频繁的。一般来说,Error 对象仅被捕获用于日志记录、执行必要的清理以及展示错误信息给用户。

编写代码支持 PHP5.x 和 PHP7 的异常

为了在同样的代码中捕获任何 PHP5.x 和 PHP7 的异常,可以使用多个 catch,先捕获 Throwable,然后是 Exception。当 PHP5.x 不再需要支持时,捕获 Exception 的 catch 块可以移除。

1
2
3
4
5
6
7
复制代码try {
// Code that may throw an Exception or Error.
} catch (Throwable $t) {
// Executed only in PHP 7, will not match in PHP 5.x
} catch (Exception $e) {
// Executed only in PHP 5.x, will not be reached in PHP 7
}

不幸的是,处理异常的函数中的类型声明不容易确定。当 Exception 用于函数参数类型声明时,如果函数调用时候能用 Error 的实例,这个类型声明就要去掉。当 PHP5.x 不需要被支持时,类型声明则可以还原为 Throwable。

原文链接:Throwable Exceptions and Errors in PHP 7

  • 本文作者: novnan
  • 本文链接: novnan.github.io/PHP/throwab…
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

用python写通用restful api service

发表于 2017-12-06

一直在用node.js做后端,要逐步涉猎大数据范围,注定绕不过python,因此决定把一些成熟的东西用python来重写,一是开拓思路、通过比较来深入学习python;二是有目标,有动力,希望能持之以恒的坚持下去。

项目介绍

用python语言来写一个restful api service,数据库使用mysql。因为只做后端微服务,并且ORM的实现方式,采用自动生成SQL的方式来完成,因此选择了轻量级的flask作为web框架。如此选择,主要目的是针对中小规模的网络应用,能充分利用关系数据库的种种优势,来实现丰富的现代互联网应用。

restful api

restful api 的概念就不介绍了。这里说一下我们实现协议形式:

1
2
3
4
复制代码[GET]/rs/user/{id}/key1/value1/key2/value2/.../keyn/valuen         
[POST]/rs/user[/{id}]
[PUT]/rs/user/{id}
[DELETE]/rs/user/{id}/key1/value1/key2/value2/.../keyn/valuen

说明:

  • rs为资源标识;
  • 第二节,user,会被解析为数据库表名;
  • 查询时,id为空或0时,id会被忽略,即为列表查询;
  • 新建和修改,除接收form表单外,url中的id参数也会被合并到参数集合中;
  • 删除同查询。

让flask支持正则表达式

flask默认路由不支持正则表达式,而我需要截取完整的URL自己来解析,经查询,按以下步骤很容易完成任务。

  • 使用werkzeug库 :from werkzeug.routing import BaseConverter
  • 定义转换器:
1
2
3
4
复制代码class RegexConverter(BaseConverter):
def __init__(self, map, *args):
self.map = map
self.regex = args[0]
  • 注册转换器 : app.url_map.converters[‘regex’] = RegexConverter
  • 用正则来截取url : @app.route(‘/rs/<regex(“.*“):query_url>’, methods=[‘PUT’, ‘DELETE’, ‘POST’, ‘GET’])

几点疑问:

  1. 正则(.*)理论上应该是匹配任何除回车的所有字符,但不知道为什么,在这里不识别问号(?)
  2. 我用request.data来取表单数据,为何request.form取不到?
  3. ‘/rs/<regex(“.*”):query_url>’后若加个反斜杠(’/rs/<regex(“.*”):query_url>/‘),request.data就取不到数据,为什么?

解析json数据

解析json数据很容易,但我需要对客户端送上来的数据进行校验,下面是用异常处理又只解析一次的解决方案。

1
2
3
4
5
6
复制代码def check_json_format(raw_msg):
try:
js = json.loads(raw_msg, encoding='utf-8')
except ValueError:
return False, {}
return True, js

URL解析

按既定协议解析URL,提取表名,为生成sql组合参数集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
复制代码@app.route('/rs/<regex(".*"):query_url>', methods=['PUT', 'DELETE', 'POST', 'GET'])
def rs(query_url):
(flag, params) = check_json_format(request.data)

urls = query_url.split('/')
url_len = len(urls)
if url_len < 1 or url_len > 2 and url_len % 2 == 1:
return "The params is wrong."

ps = {}
for i, al in enumerate(urls):
if i == 0:
table = al
elif i == 1:
idd = al
elif i > 1 and i % 2 == 0:
tmp = al
else:
ps[tmp] = al

ps['table'] = table
if url_len > 1:
ps['id'] = idd
if request.method == 'POST' or request.method == 'PUT':
params = dict(params, **{'table': ps.get('table'), 'id': ps.get('id')})
if request.method == 'GET' or request.method == 'DELETE':
params = ps
return jsonify(params)

pycharm项目配置

配置好Run/Debug Configurations才能在IDE中运行并单步调试,可以很熟悉flask框架的运行原理。

  • Script path : /usr/local/bin/flask
  • Parameters : run
  • 环境变量
+ FLASK\_APP = index.py
+ LC\_ALL = en\_US.utf-8
+ LANG = en\_US.utf-8

本以为配置完上面三条就能运行了,因为在终端模拟器上就已经能正常运行。结果在IDE中出现了一堆莫名的错误,仔细看,大概是编码配置的问题。经搜索,还需要配置后面两个环境变量才能正常运行,大概原因是python版本2与3之间的区别。

完整代码

1
2
3
4
复制代码git clone https://github.com/zhoutk/pyrest.git
cd rest
export FLASK_APP=index.py
flask run

小结

今天利用flask完成了web基础架构,能够正确解析URL,提取客户端提交的数据,按请求的不同方式来组合我们需要的数据。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go语言中实现基于 event-loop 网络处理

发表于 2017-12-06

我们知道, Go语言为并发编程提供了简洁的编程方式, 你可以以”同步”的编程风格来并发执行代码, 比如使用go关键字新开一个goroutine。 对于网络编程,Go标准库和运行时内部采用 epoll/kqueue/IoCompletionPort来实现基于 event-loop的网络异步处理,但是通过netpoll的方式对外提供同步的访问。具体代码可以参考 runtime/netpoll、
net和internal/poll。

Package poll supports non-blocking I/O on file descriptors with polling.
This supports I/O operations that block only a goroutine, not a thread.
This is used by the net and os packages.
It uses a poller built into the runtime, with support
from the
runtime scheduler.

当然,我们平常不会设计到这些封装的细节,正常使用net包就很方便的开发网络程序了, 但是,如果我们想自己实现基于epoll的 event-loop网络程序呢?

基于epoll的简单程序

man epoll可以查看epoll的相关介绍。下面这个例子来自tevino, 采用edge-triggered方式处理事件。

它采用 syscall.Socket、syscall.SetNonblock、syscall.Bind、syscall.Listen系统调用来监听端口,然后采用syscall.EpollCreate1、syscall.EpollCtl、syscall.EpollWait来关联这个监听的file descriptor,
一旦有新的连接的事件过来,使用syscall.Accept接收连接请求,并对这个连接file descriptor调用syscall.EpollCtl监听数据事件。一旦连接有数据ready, 调用syscall.Read读数据,调用syscall.Write写数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
复制代码package main
import (
"fmt"
"net"
"os"
"syscall"
)
const (
EPOLLET = 1 << 31
MaxEpollEvents = 32
)
func echo(fd int) {
defer syscall.Close(fd)
var buf [32 * 1024]byte
for {
nbytes, e := syscall.Read(fd, buf[:])
if nbytes > 0 {
fmt.Printf(">>> %s", buf)
syscall.Write(fd, buf[:nbytes])
fmt.Printf("<<< %s", buf)
}
if e != nil {
break
}
}
}
func main() {
var event syscall.EpollEvent
var events [MaxEpollEvents]syscall.EpollEvent
fd, err := syscall.Socket(syscall.AF_INET, syscall.O_NONBLOCK|syscall.SOCK_STREAM, 0)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer syscall.Close(fd)
if err = syscall.SetNonblock(fd, true); err != nil {
fmt.Println("setnonblock1: ", err)
os.Exit(1)
}
addr := syscall.SockaddrInet4{Port: 2000}
copy(addr.Addr[:], net.ParseIP("0.0.0.0").To4())
syscall.Bind(fd, &addr)
syscall.Listen(fd, 10)
epfd, e := syscall.EpollCreate1(0)
if e != nil {
fmt.Println("epoll_create1: ", e)
os.Exit(1)
}
defer syscall.Close(epfd)
event.Events = syscall.EPOLLIN
event.Fd = int32(fd)
if e = syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, fd, &event); e != nil {
fmt.Println("epoll_ctl: ", e)
os.Exit(1)
}
for {
nevents, e := syscall.EpollWait(epfd, events[:], -1)
if e != nil {
fmt.Println("epoll_wait: ", e)
break
}
for ev := 0; ev < nevents; ev++ {
if int(events[ev].Fd) == fd {
connFd, _, err := syscall.Accept(fd)
if err != nil {
fmt.Println("accept: ", err)
continue
}
syscall.SetNonblock(fd, true)
event.Events = syscall.EPOLLIN | EPOLLET
event.Fd = int32(connFd)
if err := syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, connFd, &event); err != nil {
fmt.Print("epoll_ctl: ", connFd, err)
os.Exit(1)
}
} else {
go echo(int(events[ev].Fd))
}
}
}
}

上面的基于epoll只是一个简单的event-loop处理原型,而且在有些平台下(MAC OS)也不能执行,事件的处理也很粗糙,如果你想实现一个完整的event-loop的网络程序, 可以参考下节的库。

evio

evio是一个性能很高的event-loop网络库,代码简单,功能强大。它直接使用 epoll和kqueue系统调用,除了Go标准net库提供了另外一种思路, 类似libuv和libevent。

这个库实现redis和haproxy等同的包处理机制,但并不想完全替代标准的net包。对于一个需要长时间运行的请求(大于1毫秒), 比如数据库访问、身份验证等,建议还是使用Go net/http库。

你可能知道, 由很多基于event-loop的程序, 比如Nginx、Haproxy、redis、memcached等,性能都非常不错,而且它们都是单线程运行的,非常快。

这个库还有一个好处, 你可以在一个event-loop中处理多个network binding。

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
复制代码package main
import "github.com/tidwall/evio"
func main() {
var events evio.Events
events.Data = func(id int, in []byte) (out []byte, action evio.Action) {
out = in
return
}
if err := evio.Serve(events, "tcp://localhost:5000", "tcp://192.168.0.10:5001", "tcp://192.168.0.10:5002","unix://socket"); err != nil {
panic(err.Error())
}
}

作者对性能做了对比,性能非常不错。

简单的echo例子简单的echo例子
http对比http对比
pipeline 为1pipeline
为1
pipeline 为8pipeline
为8

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

中小型研发团队架构实践:任务调度Job

发表于 2017-12-06

一、Job 简介

Job 类似于数据库中的作业,多用于实现定时执行任务。适用场景主要包括定时轮询数据库同步、定时处理数据、定时邮件通知等。

我们的 Job 分为操作系统级别定时任务 WinJob 和 HttpJob,其中,WinJob 使用开源的任务调度框架 Quartz.NET+ ZooKeeper 实现,HttpJob 的服务端是自主开发实现的,可以直接定时调用你的计划任务如微服务。下面分别予以介绍。

二、WinJob

WinJob 使用 Quartz.NET+ZooKeeper 来实现,Quartz.NET 实现调度,ZooKeeper 使用 MasterElection 来实现高可用,解决单点问题。ZooKeeper 后继有文章单独介绍,这里重点介绍 Quartz.NET 框架的使用。

Quartz.NET 是一个全功能的开源任务调度框架,通过简单的配置就可以实现强大的任务调度功能,使得开发人员不用过多关注任务的调度,只用关注项目的业务逻辑。使用任务调度框架的价值:

  1. 提高开发效率:开发人员只需要编写业务代码,而具体的任务调度只需要通过配置就可以实现。
  2. 提高软件的可靠性:同一应用多个任务之间可以很好的隔离起来,互不影响。
  3. 降低开发人员成本和开发复杂度:开发人员不需要对线程、Timer 很了解,就能实现一个强大的执行计划应用。
  4. 容易迁移:只需实现 Quartz.IJob 接口即可,调用一次业务逻辑的入口即可。
  5. 容易扩展:新业务只需增加配置即可。

基于 Quartz.NET 实现 Job 调度的方法:

在后端服务声明实例化一个调度器,在启动服务的时候启动调度器,相应的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码__Wed Dec 06 2017 10:14:23 GMT+0800 (CST)____Wed Dec 06 2017 10:14:23 GMT+0800 (CST)__/// <summary>         
/// 当前调度服务的调度器
/// </summary>
public IScheduler CurrentSched
{
get; private set;
}
public JobService()
{
InitializeComponent();

StdSchedulerFactory schedulerFactory = new StdSchedulerFactory();
CurrentSched = schedulerFactory.GetScheduler();
}
protected override void OnStart(string[] args)
{
CurrentSched.Start();
logger.Info("调度服务成功启动!");
}__Wed Dec 06 2017 10:14:23 GMT+0800 (CST)____Wed Dec 06 2017 10:14:23 GMT+0800 (CST)__

创建相应的任务和触发器,之后把任务和关联的触发器加入之前声明的调度器 CurrentSched,相应的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码__Wed Dec 06 2017 10:14:23 GMT+0800 (CST)____Wed Dec 06 2017 10:14:23 GMT+0800 (CST)__/// <summary>         
/// 演示一个任务多触发器的使用
/// </summary>
private static void JobWithManyTriggerDemo() {
IJobDetail simpleJob = JobBuilder.Create<SimpleJob>().WithIdentity("任务名称", "任务组名").Build(); // 创建一个 simpleJob 任务
ITrigger simpleTrigger = TriggerBuilder.Create().WithIdentity("触发器名称 3", "触发器组名").StartNow()
.WithSimpleSchedule(x => x.WithIntervalInSeconds(5).RepeatForever()).Build(); // 创建一个简单触发器,每隔 5 秒执行一次

CurrentSched.ScheduleJob(simpleJob, simpleTrigger); // 把 simpleJob 任务、简单触发器加入调度器

ITrigger cronTrigger = TriggerBuilder.Create().WithIdentity("触发器名称 4", "触发器组名").StartNow()
.WithCronSchedule("/10 * * ? * *").ForJob(simpleJob).Build(); // 创建一个为任务“simpleJob”服务的 Cron 触发器,每隔 10 秒执行一次

CurrentSched.ScheduleJob(cronTrigger); // 把 Cron 触发器加入调度器
}__Wed Dec 06 2017 10:14:23 GMT+0800 (CST)____Wed Dec 06 2017 10:14:23 GMT+0800 (CST)__

在业务逻辑层继承 IJob 接口,并实现 Execute 方法,在该方法内实现需要调度的业务逻辑,相应的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码__Wed Dec 06 2017 10:14:23 GMT+0800 (CST)____Wed Dec 06 2017 10:14:23 GMT+0800 (CST)__/// <summary>     
/// 简单任务
/// </summary>
public class SimpleJob : IJob
{
ILog logger = LogManager.GetLogger(typeof(SimpleJob));
public void Execute(IJobExecutionContext context)
{
Console.WriteLine("简单的任务演示!" + DateTime.Now.ToString("HH:mm:ss"));
logger.Info("简单的任务演示!" + DateTime.Now.ToString("HH:mm:ss"));
// 业务逻辑处理
Thread.Sleep(2000);
}
}__Wed Dec 06 2017 10:14:23 GMT+0800 (CST)____Wed Dec 06 2017 10:14:23 GMT+0800 (CST)__

三、HttpJob

通过自主开发的 JobServer,结合自主开发的 Job 集中式管理平台,可以实现满足绝大部分场景的 Job 调度。 这种 Job 调度使用方式使你只需关注实现业务系统的业务逻辑部分即可,无需在业务系统中额外关注如何使用 Quartz.NET。

3.1、HttpJob 的服务端实现

JobServer 实现的主要逻辑:

  1. 借助 Quartz,可实现多个线程(如 10 个线程)同时调用多个 HttpJob;
  2. 实现了 Get、Post、Head 三种方式的请求;
  3. 借助 ZooKeeper的 MasterElection 来实现高可用,实现自动主备切换;
  4. 记录日志,方便追踪。

3.2、HttpJob 的后台——Job 集中式管理平台

在集中式 Job 管理平台中,配置相应的 Job 信息。配置完 Job 信息后,JobServer 获取到这些 Job 信息后,就能够定时执行这些 Job。要配置的 Job 信息包括 Job 的任务名称、任务组名、请求地址、请求类型、开始时间、触发器类型、次数、间隔时间 (s)、Cron-Like 表达式以及状态。

其中请求地址就是 JobServer 实际定时调用的任务的 http 地址,例如 HttpJobDemo 的 WebForm1.aspx 这个任务的运行地址 http://localhost:10786/WebForm1.aspx。

)

3.3、采用 HttpJob 进行任务调度的优势和约束

采用 HttpJob 的优势:

  1. 高可用:借助网站集群巧妙地解决 Job 服务的单点问题。
  2. 方便发布:不用重启 Job 服务。
  3. 减少依赖,易学易用,不用关注线程、Windows 服务方面的知识。
  4. 数据分片,可以采用 URL 来取模 + 多个 HttpJob。

采用 HttpJob 的约束:

  1. 由于请求 HttpJob 的最长响应时间是 30 秒,所以 Job 运行时间一旦超过 30 秒,则建议为 Job 先创建异步线程,立即返回。
  2. Job 调度的频率最少间隔时间是 1 分钟,因为通过 HttpJob 通知并不是件高效的事情。
  3. 为了安全应建立专业的 Job 集群,一般两台即可,外部不可访问,SLB 采用简单轮询方案。
  4. 新增及修改 Job 配置,10 分钟生效。

四、Cron 表达式

Cron 表达式格式:秒 分 时 日 月 周 年(可选)。要遵守的规范请见下表:

)

五、Demo 下载及更多资料

  • WinJobDemo 下载地址:github.com/das2017/Qua…
  • HttpJobDemo 下载地址:github.com/das2017/Htt…
  • Quartz.NET 官网:www.quartz-scheduler.net/
  • Quartz.NET 开源网址:github.com/quartznet/q…

本系列文章涉及内容清单如下(并不按这顺序发布),其中有感兴趣的,欢迎关注:

  • 开篇:中小型研发团队架构实践三要点
  • 缓存 Redis
  • 消息队列 RabbitMQ:如何用好消息队列RabbitMQ?
  • 集中式日志 ELK
  • 任务调度 Job
  • 应用监控 Metrics:应用监控怎么做?
  • 微服务框架 MSA
  • 搜索利器 Solr
  • 分布式协调器 ZooKeeper
  • 小工具:
  • Dapper.NET/EmitMapper/AutoMapper/Autofac/NuGet
  • 发布工具 Jenkins
  • 总体架构设计:电商如何做企业总体架构?
  • 单个项目架构设计
  • 统一应用分层:如何规范公司所有应用分层?
  • 调试工具 WinDbg
  • 单点登录
  • 企业支付网关
  • 结篇

作者介绍

张辉清,10 多年的 IT 老兵,先后担任携程架构师、古大集团首席架构、中青易游 CTO 等职务,主导过两家公司的技术架构升级改造工作。现关注架构与工程效率,技术与业务的匹配与融合,技术价值与创新。

杨丽,拥有多年互联网应用系统研发经验,曾就职于古大集团,现任职中青易游的系统架构师,主要负责公司研发中心业务系统的架构设计以及新技术积累和培训。现阶段主要关注开源软件、软件架构、微服务以及大数据。

感谢雨多田光对本文的审校。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

每个程序员都需要知道一些游戏网络知识

发表于 2017-12-06

作为一个程序员,你有没有想象过多人游戏是如何实现的?

在外行人看来游戏很神奇:两个或者更多的玩家在网络上分享共同的经历,就像他们真实的存在于相同的虚拟的世界一样。游戏看起来犹如一个巨大的魔术,奇妙而又刺激,但作为一个开发人员我们知道,真实的情况和我们所看到的并不一样,那只是一种错觉。你感受到的共享现实,实际上是在那个时刻内,由你自己的独特视角和位置所感知的近似情况。

一、Peer-to-Peer 帧同步

最初的游戏是通过peer-to-peer来联网的,每个计算机通过网状拓扑的结构的彼此连接并交换信息。你仍然可以看到这种模型存在于RTS游戏中,而且基于某些原因它还很有趣,也许是因为它是大多数人认为游戏网络工作方式的第一种方式。

处理游戏信息的基本思想就是把游戏的数据抽象并转换成一系列命令消息,当处理每个转换的时候就直接演变为游戏的状态。比如:移动单位、攻击物体、建造建筑。这一切都需要在线的每个玩家机器,从一个初始化命令开始之后,都运行完全相同的命令和转换数据。

当然了,这只是一个过于简单的解释,同时也隐去了很多细节,不过我们通过这个基本的思路可以知道RTS游戏的网络是如何工作的。如果你想知道更多网络模型,请点击:1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond.

这些看起来是如此简单和优雅,但不幸的它们有几个因素限制者我们。

第一个限制,要保证游戏状态完全确定一致的是异常困难,特别是保持每台机器上每个转换输出都保持相同。比如,一个单位在两台机器上有略微不同的路径,在一台机器上早一些到达并开始了战斗,结果反败为胜,而在另一台机器上,由于稍微晚一些到达而失败。就像一只蝴蝶扇动了翅膀,然后在世界的另一边导致了飓风的出现,随着时间的推移,一个微小的区别就会导致两边完全的不同步。

第二个限制,为了保证游戏的所有玩家输出一致,这就需要等到所有玩家的当前回合数据都到达之后才可以模拟播放这一回合动作。这就意味着游戏中的每一个玩家都需要等待网络延迟最高的那个玩家。RTS游戏通常代表性地通过立即提供音频反馈与(或是)播放吟唱(过渡)动画来掩盖这段延迟,但是最终真正影响游戏的动作要在这段延迟过去之后才能进行。

第三个限制,因为游戏中状态改变的同步是通过发送命令信息来同步的。所以为了游戏中玩家状态都一致,需要所有的玩家都要从相同的初始状态来开始游戏。这意味着每个玩家必须在开始游戏之前先加入房间然后一起开始游戏,尽管理论上也可以支持让某些玩家晚些加入游戏,但是在一场进行中的游戏中获得一个完全确定的起始点的难度相当大,所以这种情况并不常见。

尽管有这些因素限制困扰者我们,不过这个模型还是很适合RTS游戏的,并且它仍然存在于今天的游戏当中,例如“Command and Conquer”、“Age of Empires”与“Starcraft”等。原因就是在RTS游戏中,里面包含了上千多的单位,这些单位都有自己的状态需要同步,而且他们数据量都太大了,很难用来在玩家之间交换。别无选择,我们只能通过这些游戏状态改变的命令来同步。

所以以上这些就是 peer-to-peer 帧同步的网路游戏模型的介绍了,对于其他类型的游戏,最先进的技术已经开始出现了。让我们现在从Doom, Quake 以及 Unreal经典游戏中开始一起观察动作游戏的技术演化。

二、客户端/服务器(c/s架构)

在动作游戏的时代,以上帧同步的限制在Doom 游戏中变得更加明显,尽管在局域网中体验还不错,但在对于互联网的用户来说它体验太糟糕了:

尽管可以使用一个猫(调制解调器)把两个Doom 机器通过互联网连接在一起,但他们一起游戏会异常缓慢。范围从无法游戏(例如:14.4Kbps PPP 连接)到稍微可以玩(例如 :28.8Kbps 猫运行一个被SLIP驱动压缩的数据)之间游戏联机都异常缓慢。由于这些连接方式只是边际效用,本文将仅关注直接的网络连接。

这个问题是因为Doom网络部分本来就是只为局域网而设计的,并且使用了前面介绍的peer-to-peer 帧同步模型。每一回合每个玩家的输入的信息(比如关键按键等)都与其他人进行同步通知,并且任何玩家在播放这一帧动画之前,必须得等到所有其他玩家的关键按键信息都被接收到,才可以去模拟播放。

也就是说,在你可以转身(转换),移动或者射击之前,你必须等待延迟最大的猫(调制调解器)玩家的输入。只是想想上述那个人所写的“这些连接方式只是边际效用”就会让人咬牙切齿和沮丧了。

为了改变这种现状,只能在局域网以及大学网络和大型企业才能获得良好连接而进行游戏,是需要改变这种网络模型了。在1996年,这变成了现实并被实现了,John Carmack当时 发布雷神之锤,他采用客户端/服务器(C/S)架构代替了P2P模型。

如今游戏中的玩家可以不必再运行相同的代码以及直接相互通信,每个玩家的机器是都是一个“客户端”,他们都通过一台叫做“服务器”的机器进行通信交互。游戏的最终状态确定不再依赖于每台客户端机器来共同确认,而是由服务器来确定最终结果。每个客户端如同一个哑终端,用来展示一个近似值的表演,真是的游戏状态是运行于服务器之上。

在一个纯粹的c/s架构中,你不必在本地运行游戏代码,而是把一些例如按键、鼠标移动,点击等输入信息发送到服务器。服务器会在游戏世界中更新你的玩家状态,然后再封包一个包含你角色信息以及临近玩家数据的包回复给你的客户端。所有的客户端在每个消息更新的间隙做一个插值预测,以改善在每个状态更新期间,物体可以平滑的移动,如此,你就有一个可以联网的客户端/服务器架构的游戏了。

这已经是向前迈出了极大的一步。游戏的体验依赖于客户端和服务器的连接,而不是游戏中延迟最大的那个玩家。如此可以支持玩家在游戏中自由的进入和退出,同时由于客户端/服务器降低了平均每位玩家的带宽,从而可以增加更多的在线玩家。

但是这里仍然有一些问题存在于 c/s 架构中:

我记得我交代了所有从DOO到Quake中关于网络的决策,但是重要的是我正在使用错误的假设来做一个好的网络游戏。我原先设计的目标是网络延迟<200ms。人们通过一个好的网络供应商连接互联网,从而可以获得一个好的游戏体验。但事与愿违,世界上99%的用户使用猫(调制调解器)通过 slip或者ppp 进行连接,而他们常常都会通过槽糕而又拥挤的ISP。这会带来最低300+ms 的 网络延迟。一个消息要经过,客户端>用户猫>ISP猫>服务器>ISP猫>用户猫>客户端。上帝,这太逊了。

OK,我做了一个错误的设定。我在家里使用T1 宽带,所以我只是不了解在PPP网络下的生活。我现在就解决它。

这个问题当然是延迟。

接下来John在他发布QuakeWorld的时候将改变这个行业。

三、客户端预测(Client-Side Prediction)

在原来的Quake游戏中,你会感觉到电脑与服务器之间的延迟。比如,你按键向前移动,在你真正移动之前,你需要等到数据包发送服务器然后再回复到你的客户端,你才可以真正的移动。按键开火,在你的射击之前同样需要相同的等待。

如果你玩过任何FPS游戏,比如:Modern Warfar,你会发现并没有延迟发生。那么fps游戏是如何做到在多人情况下,你的动作看起来并没有延迟?

这个问题被分为两个部分来解决。第一个部分是客户端移动预测,这事John Carmack 为 QuakeWorld游戏多开发的,后来被合并到了Tim Sweeney的虚幻网络模块。第二个部分就是延迟补偿,它是有Valve公司的Yahn Bernier在Counterstrike所开发。那么在这个章节,我们把焦点放在第一部分——隐藏用户移动的延迟。

当写到关于他即将发布的QuakeWorld计划的时候,John Carmack 讲到:

我现在允许客户端可以预测用户的移动,直到服务器的权威信息回复之前。这是一个重大的结构变更。客户端需要知道关于对象的硬度、摩擦力、重力等一系列基础属性。我很伤心的看到,客户端仅作为一个终端存在将会离开,但作为一个实用主义者,我必须超越这种理想情怀。

那么现在我们为了消除延迟,客户端需要运行更多的代码。它现在不再是一个只把输入发送给服务器然后再把返回信息进行插入的哑终端。现在客户端的机器可以运行一部分游戏代码,它可以在本地预测你的角色移动并且可以即时响应你的输入。

现在当你即刻按键向前,你的游戏会立刻向前移动,不会再去等待数据往返一次客户端和服务器之间才来回应你的操作。

这种方式的难点不在于预测,这种预测工作,就像正常的游戏代码一样 —— 根据玩家的输入,及时地更新游戏角色的状态。而难点在于,当客户端和服务器对于玩家角色所做的事情(动作)核检不一致的时候,客户端如何基于服务器信息进行更正。

现在你会想,hey,如果代码运行在客户端——为何不以客户端的信息为准?客户端可以自己的为角色模拟运行代码,并且只需要在每次发送数据包时告知服务器这些信息。如果每个客户端都对服务器发送相同的信息,告诉服务器“这是我现在的位置信息”,那么将会带来这样的问题。客户端会很容易被黑客攻击并控制,这样在RPG游戏中,一个作弊便可以立即躲避对方技能击中,或者当你射击的时候瞬间移动到你的身后。

所以在FPS游戏中,尽快每个玩家的客户端可以预测他们自己的角色进行操作移动,但最终每个玩家的角色状态绝对以服务器为准。就像Tim Sweeney 在所写的文章The Unreal Networking Architecture中描述的一样:“服务器才是主人”。

这就是有趣的地方。如果客户端和服务器产生了不一致,客户端必须基于服务器的信息为准并更新,但是由于客户端和服务器之前有延迟,服务器的修正必然是过去的动作。比如,如果信息从客户端到服务器耗时100ms,然后返回又耗时100ms,那么任何服务器的的修正都是客户端200ms之前的行为动作,这个时间正好是客户端预测角色移动的时间。

如果客户端每个动作都会被服务器修正,那么你将会看客户端被拉回了原先的位置,如此客户端将做不了任何预先预测的运算。那么我们如何解决这个问题,依然可以保持客户端提前预测?

解决方案就是在客户端创建一个buffer,然后用来循环保持角色的状态以及本来玩家的输入。当客户端收到了服务器的更正信息时候,它首先丢弃掉buffer里面比(服务器回复的)更正状态要老的状态信息,然后基于(更正的)正确的状态重放存储在buffer里面的输入信息,重发的这些输入信息的范围是从正确状态到当前预测时间之间。如此实际上,客户端只是看似无形中“倒带和重放”当地玩家角色运动的最后n帧,同时保持世界其他地方没有变化。

这种方法可以让玩家感觉在控制游戏的时候没有延迟,同时也改善了客户端和服务器之间代码运行的一致性——在同等输入的情况下保持一致的结果。当然了,修正的情况很少发生,Tim Sweeney 如此描述:

…对于客户端和服务器最好的是:所有情况下,服务器都是权威的。几乎所有的时间,客户端模拟的和服务器的数据都是一致,所以客户端的位置很少被修正。只有的少数罕见的情况下,例如一个玩家被火箭击中,或者和一个敌人(怪物)碰撞上,那么客户端本地的情况有可能需要被修正。

也就是说,只有当玩家的角色被一些外部事件影响玩家的输入,并且这些不能被客户端预测时,玩家的位置(行为)需要被服务器修正。当然,如果玩家试图作弊,必然会被服务器修正。

这是一篇翻译文章,主要针对游戏的网络设计,目前主流的网络游戏实现方案都有讲解,如果对英文更感兴趣,请查看文章尾部链接,你若是觉得有翻译不妥的地方,欢迎留言指正。原文地址:点此看原文


更新游戏开发专题请关注我的公众号

大码候,关注个人成长和游戏研发,致力于推进国内游戏技术社区的进步。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何处理Express异常?

发表于 2017-12-06

译者按:根据墨菲定律:“有可能出错的事情,就会出错”。那么,既然代码必然会出错,我们就应该处理好异常。

  • 原文: How to handle errors in Express
  • 译者:Fundebug

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

处理异常是编程非常重要的一点。我们的程序依赖于第三方服务、数据库以及我们的用户,一切都不可预料。数据库可能会宕机,第三方服务可能会崩溃,用户可能会使用错误的参数调用我们的接口。

为了处理各种复杂的情况,我们必须处理好代码异常,下面是代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
stylus复制代码

app.get('/users/:id', (req, res) => {
const userId = req.params.id
if (!userId) {
return res.sendStatus(400).json({
error: 'Missing id'
})
}
Users.get(userId, (err, user) => {
if (err) {
return res.sendStatus(500).json(err)
}
res.send(users)
})
})

代码中处理了异常,但是存在问题:

  • 在多处代码处理异常
  • 没有使用Express的异常处理模块来统一处理异常

接下来,我们来一步步优化代码异常处理。

Express异常处理中间件

所有Express的路由处理函数都有第三个参数next,它可以用来调用下一个中间件,也可以将错误传递给错误处理中间件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
moonscript复制代码

app.get('/users/:id', (req, res, next) => {
const userId = req.params.id
if (!userId) {
const error = new Error('missing id')
error.httpStatusCode = 400
return next(error)
}
Users.get(userId, (err, user) => {
if (err) {
err.httpStatusCode = 500
return next(err)
}
res.send(users)
})
})

使用next(err),Express就知道出错了,并把这个错误传递给错误处理模块。为了处理这些错误,需要添加一个中间件,它有4个参数:

1
2
3
4
5
6
stata复制代码

app.use((err, req, res, next) => {
// log the error...
res.sendStatus(err.httpStatusCode).json(err)
})

这样,我们就可以使用中间件统一处理错误了。但是,现在的代码有些重复:创建错误,指定HTTP状态码,使用next(err)…

Fundebug是全栈JavaScript错误监控平台,支持各种前端和后端框架,可以帮助您第一时间发现BUG!

boom

boom是一个兼容HTTP的错误对象,他提供了一些标准的HTTP错误,比如400(参数错误)等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
moonscript复制代码

const boom = require('boom')
app.get('/users/:id', (req, res, next) => {
const userId = req.params.id
if (!userId) {
return next(boom.badRequest('missing id'))
}
Users.get(userId, (err, user) => {
if (err) {
return next(boom.badImplementation(err))
}
res.send(users)
})
})

错误处理中间件需要稍作修改:

1
2
3
4
5
6
7
8
9
10
stata复制代码

app.use((err, req, res, next) => {
if (err.isServer) {
// log the error...
// probably you don't want to log unauthorized access
// or do you?
}
return res.status(err.output.statusCode).json(err.output.payload);
})

Async/Await错误处理

使用Async/Await之后,可以这样处理Express异常:

  • 将中间件使用Promise封装起来,使用catch统一处理异常
  • 在中间件中,直接抛出异常就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
zephir复制代码

const boom = require('boom');
// wrapper for our async route handlers
// probably you want to move it to a new file
const asyncMiddleware = fn => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch((err) => {
if (!err.isBoom) {
return next(boom.badImplementation(err));
}
next(err);
});
};
// the async route handler
app.get('/users/:id', asyncMiddleware(async (req, res) => {
const userId = req.params.id
if (!userId) {
throw boom.badRequest('missing id')
}
const user = await Users.get(userId)
res.json(user)
}))

参考

  • 验证HTTP请求参数可以使用joi模块
  • 打印日志可以使用winston或者pino模块

您的用户遇到BUG了吗?

体验Demo 免费使用

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

分布式消息队列实现概要 前言 正文 总结

发表于 2017-12-06

前言

消息队列允许应用之间通过发消息的方式异步通讯,简单来说,发送者和消费者的生产效率通常是不一致的,那么我们就需要一种抽象模型去解耦,因此这里就可以引入消息队列,将任务暂时写入消息中间件,待消费者慢慢处理。消息中间件目前已经有了很多选择,例如RocketMQ、Kafka、Pulsar等等,Message queue带来很多便利的同时,也引入了一些技术上的复杂性,就像一个黑盒子一样,如果不能理解其原理,如果碰到了问题查起来也很蛋疼,今天我们就来看看如何着手实现一个简单的消息队列

正文

首先我们看看Kafka以及RocketMQ的包结构,看看一个分布式消息队列究竟需要哪些组件
image
image

存储层

消息队列最核心的组件之一就是存储层,消息如何落地、如何读取,这里的技术选型是比较重要的一点,例如RocketMQ以及Kafka都是选择存储到本机,也就是本地文件系统,而Pulsar则是选择存储到分布式文件系统bookKeeper中,当然也有一些选择了分布式KV系统甚至是数据库,例如Redis自身也是支持publish/consume模型的,具体的选择哪一种实现方式只要还是看自己的业务场景,例如如果可靠性要求较高但对性能并不那么敏感的场景可以选择数据库作为存储介质。
选择本地文件系统去实现一个分布式消息队列相对来说是这几种最复杂的,不仅仅需要自己实现文件的IO细节,对于复制、一致性(当出现网络异常或者系统异常宕机时如何根据日志恢复系统的状态)也都需要自己实现,而这每一部分都需要相当一部分精力去研究,我们这次只是先首先一个比较简单的原型,对于这个方案之后有时间会搞。
基于分布式KV的方案相对来说也是不错的方案,性能很不错,而且接口也比较人性化,但是可靠性差了一点,对于类似交易、缓存同步这种对可靠性要求比较高的场景来说不那么使用。
image

基于数据库的方式性能上会有很大的损失,DB的数据结构本质上就不适合去实现消息队列。这次我们选择利用分布式文件系统作为存储介质,泪如HDFS、Apache BookKeeper等,我们分析一下Message queue的场景,单线程写-多线程读,这里需要引入topic分区的概念,一般如果某些topic比较活跃,吞吐量比较高,那么我们可以将消息分区,实现思路一般是将topic再从细粒度切分为子topic,并将每个子topic分布到不同的broker上,从而实现性能的线性提升,也就是说这里的单线程写具体指的是单个分区,多线程读相对来说比较容易理解,而HDFS正好适合这个场景,而且我们也不用去管replica、写分片、刷盘策略等等,减少了很多实现的复杂性,BookKeeper在这方面是不错的选择。
image

客户端API实现

对于使用者而言,接触到的更多的是客户端暴漏的API,而客户端和服务器端Broker也需要一种方式通讯,对于RocketMQ以及Kafka都是选择实现了自定义的协议,消息队列的如果想要达到极高的吞吐量,实现一种高性能的网络通讯框架是相当重要的一环,RocketMQ是基于Netty之上构建的,而Kafka是直接基于NIO实现的,相对来说要复杂一点,如果看过源码的话会有所了解,Kafka客户端提交之后是先放到一个本地队列,然后根据broker、topic、分区信息等合并提交到服务器端,而Pulsar印象中是基于Protocol
buffer实现的,这样相对自定义协议很多好处,首先如果协议后期实现过程有变动的话,如何兼容老的协议等这些细节已经由Protocol buffer帮你解决了,另外很重要的一点是,Protocol buffer可以帮你生成各个不同语言的API,如果是自定义协议这个又要费相当的精力去实现。

一致性

对于消息队列的场景,每条消息都是一旦落盘之后,就不再支持更新操作,对于读取也都是顺序读,consumer抓取到的消息也都是已经落盘的或者已经commit的记录,因此一致性在消息队列中相对来说还是比较容易实现的。

高可用

首先就存储层来说,我们的技术选型就已经决定了本质上就是高可用的,因为BookKeeper本身就支持指定复制到几个slave以及ack的机制,例如需要写入到所有的分区才向客户端返回成功,而对于broker端,因为我们的消息队列是存储和计算分离的,也就是说broker本身是无状态的,当producer/consumer连接的broker宕机或者网络超时的断开连接时,可以直接由另一个broker接着提供服务,当然这里还有很多细节问题,但是复杂性相对RocketMQ等已经降低了很多。

消费者进度存储

我们知道消息存在三种语义: at most once、at least once、exactly once,那么消费者offset的存储于同步机制就一定程度上决定了我们具体是什么语义,例如发送端,如果发送失败不重试的话就是 at most once,如果发送失败选择一定次数的重试,那么就是at least once,这里就可能造成消息重复落盘从而造成重复消费,例如说消息实际已经落盘但是发送提交响应的过程出现了网络异常,就会出现这种情况,而exactly once的场景就会比较复杂一点。我们回到offset的场景,RocketMQ以及Kafka默认都是定时去同步当前的消费进度,那么这个消费进度存储到哪里又是一个问题。
RocketMQ的方式是存储到本地文件系统中,Kafka在0.8版本之前是选择存储到了Zookeeper中,后面改成存储到另外一个topic中,那么这两种方式有什么优缺点呢:

  1. 性能/横向扩展: Zookeeper是一个一致性系统,它保留的API也都是基于key/value的格式,ZK本质上是不支持大量写的,同时ZK不支持横向扩展,因为每个节点都会同步所有的transaction 并保持整个数据集,实际上ZK是基于单个日志写并同步复制到其他节点的分布式系统。ZK的吞吐量据我测试差不多1W/S写左右,但是假如说我们有几十上百万个topic,每秒同步一次消费进度,这个时候ZK已经完全不能满足需要,而且并不能横向扩展,只能通过分片的方式解决,而这又引入了一个代理层
  2. 实现的复杂性: 基于本地文件系统性能虽然可观,但是和消息存储同理,需要考虑很多实现的细节

因此我们这里参考Kafka最新的实现,我们选择将消费进度也存储到BookKeeper中,这样就可以支持大量的写,而且支持线性扩展,BK也会将小的log合并存储到一个文件中,避免了性能被一些不活跃的topic所影响。

总结

本文简单讲解了实现一个分布式消息队列所需要考虑的一些方面,例如一致性、高可用、消费语义、通讯模型等等,但实际去写一个Message queue 所需要的远远不止这些,建议先从源码阅读开始,先理清整体的架构、脉络,再去研究细节、看代码,最好将每个项目的源码在IDE中实际的去打断点、调试,一步一步的了解到从发送消息到接收到消息的这一整个过程都发生了什么。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java泛型之类型擦除 Java泛型之类型擦除 类型擦除 参

发表于 2017-12-06

Java泛型之类型擦除

2017-12-05 Java 泛型,
类型擦除 9 评论 字数统计: 1,214(字) 阅读时长: 6(分)
类型擦除
====

学过C++模板的,在使用Java泛型的时候,会感觉到有点不疑问,例如:(1)无法定义一个泛型数组、无法调用泛型参数对象中对应的方法(当然,通过extends关键字是可以做到,只是比较麻烦);(2)ArrayList和ArrayList
在运行时的类型是相同的。Java中的泛型有这些问题,是它的实现机制决定的,即“类型擦除”。

  1. 类型擦除的定义:编译通过后,准备进入JVM运行时,就不再有类型参数的概念,换句话说:每定义一个泛型类型,JVM会自动提供一个对应的原生类;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
复制代码public class Holder4<T> {

private T a;
private T b;
private T c;

public Holder4(T a, T b, T c) {
this.a = a;
this.b = b;
this.c = c;
}

public T getA() {
return a;
}

public T getB() {
return b;
}

public T getC() {
return c;
}

public void setA(T a) {
this.a = a;
}

public void setB(T b) {
this.b = b;
}

public void setC(T c) {
this.c = c;
}


public static void main(String[] args) {
Holder4<Automobile> holder4 = new Holder4<>(new Automobile(),new Automobile(), new Automobile());

Automobile a = holder4.getA(); //编译器帮忙转型,不需要显式转型
Automobile b = holder4.getB();
Automobile c = holder4.getC();
}
}

在Java中,每定义一个泛型类型,就会自动提供一个对应的原始类型,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
复制代码public class Holder4Raw {

private Object a;
private Object b;
private Object c;

public Holder4Raw(Object a, Object b, Object c) {
this.a = a;
this.b = b;
this.c = c;
}

public Object getA() {
return a;
}

public Object getB() {
return b;
}

public Object getC() {
return c;
}

public void setA(Object a) {
this.a = a;
}

public void setB(Object b) {
this.b = b;
}

public void setC(Object c) {
this.c = c;
}

public static void main(String[] args) {
Holder4Raw holder4Raw = new Holder4Raw(new Automobile(),new Automobile(), new Automobile());

Automobile a = (Automobile) holder4Raw.getA(); //显示的转型
Automobile b = (Automobile) holder4Raw.getB();
Automobile c = (Automobile) holder4Raw.getC();
}
}
  1. 为什么选择这种实现机制?
* 在Java诞生10年后,才想实现类似于C++模板的概念,即泛型;
* Java的类库是Java生态中非常宝贵的财富,必须保证向后兼容(即现有的代码和类文件依旧合法)和迁移兼容(泛化的代码和非泛化的代码可互相调用)基于上面这两个背景和考虑,Java设计者采取了“类型擦除”这种折中的实现方式。
  1. Java泛型依赖编译器实现,只存在于编译期,JVM中没有泛型的概念;那么,编译器做了什么工作呢?(1)set方法是编译期检查;(2)get方法的返回值进行转型,编译器插入了一个checkcast语句。

我们通过字节码进行观察,可以看出:(1)Holder4和Holder4Raw两个类的字节码完全相同;(2)在main函数的33、41和49行就是编译器插入的checkcast语句;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
复制代码public class org.java.learn.generics.Holder4<T> {
public org.java.learn.generics.Holder4(T, T, T);
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield #2 // Field a:Ljava/lang/Object;
9: aload_0
10: aload_2
11: putfield #3 // Field b:Ljava/lang/Object;
14: aload_0
15: aload_3
16: putfield #4 // Field c:Ljava/lang/Object;
19: return

public T getA();
Code:
0: aload_0
1: getfield #2 // Field a:Ljava/lang/Object;
4: areturn

public T getB();
Code:
0: aload_0
1: getfield #3 // Field b:Ljava/lang/Object;
4: areturn

public T getC();
Code:
0: aload_0
1: getfield #4 // Field c:Ljava/lang/Object;
4: areturn

public void setA(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field a:Ljava/lang/Object;
5: return

public void setB(T);
Code:
0: aload_0
1: aload_1
2: putfield #3 // Field b:Ljava/lang/Object;
5: return

public void setC(T);
Code:
0: aload_0
1: aload_1
2: putfield #4 // Field c:Ljava/lang/Object;
5: return

public static void main(java.lang.String[]);
Code:
0: new #5 // class org/java/learn/generics/Holder4
3: dup
4: new #6 // class org/java/learn/generics/Automobile
7: dup
8: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
11: new #6 // class org/java/learn/generics/Automobile
14: dup
15: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
18: new #6 // class org/java/learn/generics/Automobile
21: dup
22: invokespecial #7 // Method org/java/learn/generics/Automobile."<init>":()V
25: invokespecial #8 // Method "<init>":(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V
28: astore_1
29: aload_1
30: invokevirtual #9 // Method getA:()Ljava/lang/Object;
33: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
36: astore_2
37: aload_1
38: invokevirtual #10 // Method getB:()Ljava/lang/Object;
41: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
44: astore_3
45: aload_1
46: invokevirtual #11 // Method getC:()Ljava/lang/Object;
49: checkcast #6 // class org/java/learn/generics/Automobile,get方法的转型
52: astore 4
54: return
}

参考资料

  1. 《Java编程思想》
  2. 《Effective Java》
  3. 《Java核心技术》

阿杜Java Developer

Thoughts, stories and ideas.

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

几种分布式调用链监控组件的实践与比较(二)比较

发表于 2017-12-06

引言:最近在调研与选型分布式调用链监控组件。选了主要的三种APM组件进行了实践与比较。本来打算一篇文章写完的,篇幅太长,打算分两篇。距离第一篇已经有近一个月时间了,主要最近工作比较忙,更新很慢。本文将会讲下几种APM选型的比较与性能测试。

  1. 前文回顾

上一篇文章主要讲了三种分布式调用链监控组件的实践。问题的背景由微服务架构展开,微服务的好处已经不用多说,而微服务的多了之后,千头万绪,下面这张图经常被用到。

mcd

微服务调用链

系统的复杂度因此提升。系统越复杂,越难解决问题,例如系统失败或者性能问题。在三层架构中找到解决方案还不是太难,仅仅需要分析3个组件比如web服务器,应用服务器和数据库,而服务器数量也不多。但是,如果问题发生在n层架构中,就需要调查大量的组件和服务器。另一个问题是仅仅分析单个组件很难看到大局;当发生一个低可见度的问题时,系统复杂度越高,就需要更长的时间来查找原因。最糟糕的是,某些情况下我们甚至可能无法查找出来。

上面其实已经提到存在的故障定位问题,基于微服务体系之下构建的业务系统存在的问题基本上分为三类:

  • 故障定位难,一个简单操作,其背后可能是由十几个微服务共同完成的,这些微服务也由不同的团队去负责。一旦出现问题,最坏情况下我们也许需要这十几个团队一起来解决问题。
  • 链路梳理难,应用没有形成应用拓扑,不知道自己的服务下游会影响其他哪些人。
  • 资源浪费多,容量预估难。对于一些服务,其消耗的cpm和memory可能连10%不到,远远没有充分利用物理机。这其实和容量预估关联,过大或者过小估算峰值的机器容量,都是浪费。

APM主要的目的就是解决上面所说的这四个问题,主要的手段是通过收集、存储、分析、分布式系统中的调用事件数据,协助开发运营人员进行故障诊断、容量预估、性能瓶颈定位以及调用链路梳理。第一篇其实已经讲过链路监控组件的需求:

  • 代码的侵入性
  • 探针的性能消耗
  • 全面的调用链路数据分析
  • 可扩展性

这边列一下pinpoint在其wiki中提到的几点:

  • 分布式事务跟踪,跟踪跨分布式应用的消息
  • 自动检测应用拓扑,帮助你搞清楚应用的架构
  • 水平扩展以便支持大规模服务器集群
  • 提供代码级别的可见性以便轻松定位失败点和瓶颈
  • 使用字节码增强技术,添加新功能而无需修改代码

下面我们沿着这些需求,看一下这几种分布式调用链监控组件。

  1. AMP比较

上面列了需求,但是不够通用,笔者将需要对比的项提炼出来:

  1. 探针的性能

主要是agent对服务的吞吐量、CPU和内存的影响。微服务的规模和动态性使得数据收集的成本大幅度提高。
2. collector的可扩展性

能够水平扩展以便支持大规模服务器集群。
3. 全面的调用链路数据分析

提供代码级别的可见性以便轻松定位失败点和瓶颈。
4. 对于开发透明,容易开关

添加新功能而无需修改代码,容易启用或者禁用。
5. 完整的调用链应用拓扑

自动检测应用拓扑,帮助你搞清楚应用的架构

笔者根据主要的需求,提炼出如上五点。

2.1 探针的性能

笔者其实也是比较关注探针的性能,毕竟APM定位还是工具,如果启用了链路监控组建后,直接导致吞吐量降低过半,那也是不能接受的。笔者对skywalking、zipkin、pinpoint进行了压测,并与基线(未使用探针)的情况进行了对比。

选用了一个常见的基于Spring的应用程序,他包含Spring Boot, Spring MVC,redis客户端,mysql。 监控这个应用程序,每个trace,探针会抓取5个span(1 Tomcat, 1 SpringMVC, 2 Jedis, 1 Mysql)。这边基本和skywalkingtest的测试应用差不多。

模拟了三种并发用户:500,750,1000。使用jmeter测试,每个线程发送30个请求,设置思考时间为10ms。使用的采样率为1,即100%,这边与产线可能有差别。pinpoint默认的采样率为20,即50%,通过设置agent的配置文件改为100%。zipkin默认也是1。组合起来,一共有12种。下面看下汇总表。

ac

性能对比

从上表可以看出,在三种链路监控组件中,skywalking的探针对吞吐量的影响最小,zipkin的吞吐量居中。pinpoint的探针对吞吐量的影响较为明显,在500并发用户时,测试服务的吞吐量从1385降低到774,影响很大。然后再看下CPU和memory的影响,笔者是在内部服务器进行的压测,对CPU和memory的影响都差不多在10%之内。

2.2 collector的可扩展性

collector的可扩展性,使得能够水平扩展以便支持大规模服务器集群。

  • zipkin

在前一篇文章中,我们开发了zipkin-Server(其实就是提供的开箱即用包),zipkin-agent与zipkin-Server通过http或者mq进行通信,http通信会对正常的访问造成影响,所以还是推荐基于mq异步方式通信,zipkin-Server通过订阅具体的topic进行消费。这个当然是可以扩展的,多个zipkin-Server实例进行异步消费mq中的监控信息。

zk

zipkin

  • skywalking

skywalking的collector支持两种部署方式:单机和集群模式。collector与agent之间的通信使用了gRPC。

  • pinpoint

同样,pinpoint也是支持集群和单机部署的。pinpoint agent通过thrift通信框架,发送链路信息到collector。

2.3 全面的调用链路数据分析

全面的调用链路数据分析,提供代码级别的可见性以便轻松定位失败点和瓶颈。

  • zipkin

zipkininfo

zipkin链路调用分析

zipkin的链路监控粒度相对没有那么细,从上图可以看到调用链中具体到接口级别,再进一步的调用信息并未涉及。

  • skywalking

swinfo

skywalking链路调用分析

skywalking 还支持20+的中间件、框架、类库,比如主流的dubbo、Okhttp,还有DB和消息中间件。上图skywalking链路调用分析截取的比较简单,网关调用user服务,由于支持众多的中间件,所以skywalking链路调用分析比zipkin完备些。

  • pinpoint

ppinfo

pinpoint链路调用分析

pinpoint应该是这三种APM组件中,数据分析最为完备的组件。提供代码级别的可见性以便轻松定位失败点和瓶颈,上图可以看到对于执行的sql语句,都进行了记录。还可以配置报警规则等,设置每个应用对应的负责人,根据配置的规则报警,支持的中间件和框架也比较完备。

2.4 对于开发透明,容易开关

对于开发透明,容易开关,添加新功能而无需修改代码,容易启用或者禁用。我们期望功能可以不修改代码就工作并希望得到代码级别的可见性。

对于这一点,Zipkin 使用修改过的类库和它自己的容器(Finagle)来提供分布式事务跟踪的功能。但是,它要求在需要时修改代码。skywalking和pinpoint都是基于字节码增强的方式,开发人员不需要修改代码,并且可以收集到更多精确的数据因为有字节码中的更多信息。

2.5 完整的调用链应用拓扑

自动检测应用拓扑,帮助你搞清楚应用的架构。

ppreal

pinpoint链路拓扑

skyreal

skywalking链路拓扑

zipkinreal

zipkin dependency

上面三幅图,分别展示了APM组件各自的调用拓扑,都能实现完整的调用链应用拓扑。相对来说,pinpoint界面显示的更加丰富,具体到调用的DB名,zipkin的拓扑局限于服务于服务之间。

  1. 总结

本文讲了三种分布式调用链监控组件的比较,主要从五方面着手,笔者对每一项都进了对比。至于具体选用哪款组件,大家可以根据实际的业务需求和场景进行选型,上面比较的数据仅供参考。这三款都是开源项目,一般公司都对针对实际情况进行一些二次开发,比如增加一些组件的支持、对接现存的大数据平台等等。

最后,看了eagleEye的相关介绍,想提下监控系统如何从被动报警转化为主动发现,其实和AIOps很密切。链路监控数据量很大,尽管可以通过压缩比来降低传输的数据量,但是我们真的需要存储每一条链路吗?是不是只需要识别每一个链路当中出现异常的情况。时序指标当中的异常点,那个时间点我们要识别出来。识别完了之后,对异常进行关联,定位出最后的问题。当然这个涉及到业务和应用系统层面,很复杂,但笔者认为是后面AIOps的大趋势。

推荐阅读

几种分布式调用链监控组件的实践与比较(一)实践

订阅最新文章,欢迎关注我的公众号

微信公众号


参考

  1. Technical Overview Of Pinpoint
  2. 阿里微服务之殇及分布式链路追踪技术原理

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

几种分布式调用链监控组件的实践与比较(二)比较

发表于 2017-12-05

引言:最近在调研与选型分布式调用链监控组件。选了主要的三种APM组件进行了实践与比较。本来打算一篇文章写完的,篇幅太长,打算分两篇。距离第一篇已经有近一个月时间了,主要最近工作比较忙,更新很慢。本文将会讲下几种APM选型的比较与性能测试。

  1. 前文回顾

上一篇文章主要讲了三种分布式调用链监控组件的实践。问题的背景由微服务架构展开,微服务的好处已经不用多说,而微服务的多了之后,千头万绪,下面这张图经常被用到。

mcd

微服务调用链

系统的复杂度因此提升。系统越复杂,越难解决问题,例如系统失败或者性能问题。在三层架构中找到解决方案还不是太难,仅仅需要分析3个组件比如web服务器,应用服务器和数据库,而服务器数量也不多。但是,如果问题发生在n层架构中,就需要调查大量的组件和服务器。另一个问题是仅仅分析单个组件很难看到大局;当发生一个低可见度的问题时,系统复杂度越高,就需要更长的时间来查找原因。最糟糕的是,某些情况下我们甚至可能无法查找出来。

上面其实已经提到存在的故障定位问题,基于微服务体系之下构建的业务系统存在的问题基本上分为三类:

  • 故障定位难,一个简单操作,其背后可能是由十几个微服务共同完成的,这些微服务也由不同的团队去负责。一旦出现问题,最坏情况下我们也许需要这十几个团队一起来解决问题。
  • 链路梳理难,应用没有形成应用拓扑,不知道自己的服务下游会影响其他哪些人。
  • 资源浪费多,容量预估难。对于一些服务,其消耗的cpm和memory可能连10%不到,远远没有充分利用物理机。这其实和容量预估关联,过大或者过小估算峰值的机器容量,都是浪费。

APM主要的目的就是解决上面所说的这四个问题,主要的手段是通过收集、存储、分析、分布式系统中的调用事件数据,协助开发运营人员进行故障诊断、容量预估、性能瓶颈定位以及调用链路梳理。第一篇其实已经讲过链路监控组件的需求:

  • 代码的侵入性
  • 探针的性能消耗
  • 全面的调用链路数据分析
  • 可扩展性

这边列一下pinpoint在其wiki中提到的几点:

  • 分布式事务跟踪,跟踪跨分布式应用的消息
  • 自动检测应用拓扑,帮助你搞清楚应用的架构
  • 水平扩展以便支持大规模服务器集群
  • 提供代码级别的可见性以便轻松定位失败点和瓶颈
  • 使用字节码增强技术,添加新功能而无需修改代码

下面我们沿着这些需求,看一下这几种分布式调用链监控组件。

  1. AMP比较

上面列了需求,但是不够通用,笔者将需要对比的项提炼出来:

  1. 探针的性能
    主要是agent对服务的吞吐量、CPU和内存的影响。微服务的规模和动态性使得数据收集的成本大幅度提高。
  2. collector的可扩展性
    能够水平扩展以便支持大规模服务器集群。
  3. 全面的调用链路数据分析
    提供代码级别的可见性以便轻松定位失败点和瓶颈。
  4. 对于开发透明,容易开关
    添加新功能而无需修改代码,容易启用或者禁用。
  5. 完整的调用链应用拓扑
    自动检测应用拓扑,帮助你搞清楚应用的架构

笔者根据主要的需求,提炼出如上五点。

2.1 探针的性能

笔者其实也是比较关注探针的性能,毕竟APM定位还是工具,如果启用了链路监控组建后,直接导致吞吐量降低过半,那也是不能接受的。笔者对skywalking、zipkin、pinpoint进行了压测,并与基线(未使用探针)的情况进行了对比。

选用了一个常见的基于Spring的应用程序,他包含Spring Boot, Spring MVC,redis客户端,mysql。 监控这个应用程序,每个trace,探针会抓取5个span(1 Tomcat, 1 SpringMVC, 2 Jedis, 1 Mysql)。这边基本和skywalkingtest的测试应用差不多。

模拟了三种并发用户:500,750,1000。使用jmeter测试,每个线程发送30个请求,设置思考时间为10ms。使用的采样率为1,即100%,这边与产线可能有差别。pinpoint默认的采样率为20,即50%,通过设置agent的配置文件改为100%。zipkin默认也是1。组合起来,一共有12种。下面看下汇总表。

ac

性能对比

从上表可以看出,在三种链路监控组件中,skywalking的探针对吞吐量的影响最小,zipkin的吞吐量居中。pinpoint的探针对吞吐量的影响较为明显,在500并发用户时,测试服务的吞吐量从1385降低到774,影响很大。然后再看下CPU和memory的影响,笔者是在内部服务器进行的压测,对CPU和memory的影响都差不多在10%之内。

2.2 collector的可扩展性

collector的可扩展性,使得能够水平扩展以便支持大规模服务器集群。

  • zipkin
    在前一篇文章中,我们开发了zipkin-Server(其实就是提供的开箱即用包),zipkin-agent与zipkin-Server通过http或者mq进行通信,http通信会对正常的访问造成影响,所以还是推荐基于mq异步方式通信,zipkin-Server通过订阅具体的topic进行消费。这个当然是可以扩展的,多个zipkin-Server实例进行异步消费mq中的监控信息。

zk

zipkin

  • skywalking
    skywalking的collector支持两种部署方式:单机和集群模式。collector与agent之间的通信使用了gRPC。
  • pinpoint
    同样,pinpoint也是支持集群和单机部署的。pinpoint agent通过thrift通信框架,发送链路信息到collector。

2.3 全面的调用链路数据分析

全面的调用链路数据分析,提供代码级别的可见性以便轻松定位失败点和瓶颈。

  • zipkin

zipkininfo

zipkin链路调用分析

zipkin的链路监控粒度相对没有那么细,从上图可以看到调用链中具体到接口级别,再进一步的调用信息并未涉及。

  • skywalking

swinfo

skywalking链路调用分析

skywalking 还支持20+的中间件、框架、类库,比如主流的dubbo、Okhttp,还有DB和消息中间件。上图skywalking链路调用分析截取的比较简单,网关调用user服务,由于支持众多的中间件,所以skywalking链路调用分析比zipkin完备些。

  • pinpoint

ppinfo

pinpoint链路调用分析

pinpoint应该是这三种APM组件中,数据分析最为完备的组件。提供代码级别的可见性以便轻松定位失败点和瓶颈,上图可以看到对于执行的sql语句,都进行了记录。还可以配置报警规则等,设置每个应用对应的负责人,根据配置的规则报警,支持的中间件和框架也比较完备。

2.4 对于开发透明,容易开关

对于开发透明,容易开关,添加新功能而无需修改代码,容易启用或者禁用。我们期望功能可以不修改代码就工作并希望得到代码级别的可见性。

对于这一点,Zipkin 使用修改过的类库和它自己的容器(Finagle)来提供分布式事务跟踪的功能。但是,它要求在需要时修改代码。skywalking和pinpoint都是基于字节码增强的方式,开发人员不需要修改代码,并且可以收集到更多精确的数据因为有字节码中的更多信息。

2.5 完整的调用链应用拓扑

自动检测应用拓扑,帮助你搞清楚应用的架构。

ppreal

pinpoint链路拓扑

skyreal

skywalking链路拓扑

zipkinreal

zipkin dependency

上面三幅图,分别展示了APM组件各自的调用拓扑,都能实现完整的调用链应用拓扑。相对来说,pinpoint界面显示的更加丰富,具体到调用的DB名,zipkin的拓扑局限于服务于服务之间。

  1. 总结

本文讲了三种分布式调用链监控组件的比较,主要从五方面着手,笔者对每一项都进了对比。至于具体选用哪款组件,大家可以根据实际的业务需求和场景进行选型,上面比较的数据仅供参考。这三款都是开源项目,一般公司都对针对实际情况进行一些二次开发,比如增加一些组件的支持、对接现存的大数据平台等等。

最后,看了eagleEye的相关介绍,想提下监控系统如何从被动报警转化为主动发现,其实和AIOps很密切。链路监控数据量很大,尽管可以通过压缩比来降低传输的数据量,但是我们真的需要存储每一条链路吗?是不是只需要识别每一个链路当中出现异常的情况。时序指标当中的异常点,那个时间点我们要识别出来。识别完了之后,对异常进行关联,定位出最后的问题。当然这个涉及到业务和应用系统层面,很复杂,但笔者认为是后面AIOps的大趋势。

推荐阅读

几种分布式调用链监控组件的实践与比较(一)实践


参考

  1. Technical Overview Of Pinpoint

  2. 阿里微服务之殇及分布式链路追踪技术原理

    aoho wechat 欢迎您扫一扫上面的微信公众号,aoho求索,订阅我的博客! 坚持原创技术分享,您的支持将鼓励我继续创作!

    赏
    aoho WeChat Pay
    微信打赏

    aoho Alipay
    支付宝打赏

  • 本文作者: aoho
  • 本文链接: blueskykong.com/2017/12/05/…
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…916917918…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%