JAVA程序员分级,你属于哪一种?

  • 初级—初

    掌握java基础,熟悉常用类库。理解java web中的servlet,jsp,并了解常用的框架对java web的封装原理,能够借助框架完成增删改查功能。理解数据库在web开发中的地位。

  • 初级—中

    理解java中较为高级的特性,如反射,动态代理,JVM,内存模型,多线程等等。熟练使用框架,对框架中遇到的bug,能够借助日志和搜索引擎分析出问题的原因。在团队中,能够独立完成普通后台业务功能的开发。了解数据库的高级特性,如索引,存储引擎等等。

  • 初级—高

    理解java分布式架构,微服务架构,了解其与集中式架构的区别,并能保证分布式代码质量。熟练使用各个中间件如redis,mq,zookeeper等等,并了解其工作原理和使用场景。能够在中级或高级程序员的带领之下,完成非核心功能的研发。能够关注开源,并且具有阅读源码的能力。

  • 中级

    具备一定的项目开发经验(3年之上一线互联网产品研发经验),拥有线上bug的处理能力,JVM调优能力,以及完成核心业务功能的开发。并且带领团队的新人,能够按能力分配任务。

  • 高级

    团队的核心人物,把控整个项目的质量,包括代码漏洞和规范问题。具有5年以上项目开发经验,2年以上架构搭建的经验,能够根据业务选择不同的架构类型;根据团队组成,分配不同的任务。具有将自己的知识分享出去的能力,带领初级程序员走向中级,中级程序员走向高级的能力。

分享到

drools用户指南----Cross Products

Cross Products

之前提到“Cross Products”一词,其实就是一个join操作(译者注:可以理解为笛卡尔积)。想象一下,火灾报警示例的数据与以下规则结合使用,其中没有字段约束:

1
2
3
4
5
6
7
rule "Show Sprinklers" when
$room : Room()
$sprinkler : Sprinkler()
then
System.out.println( "room:" + $room.getName() +
" sprinkler:" + $sprinkler.getRoom().getName() );
end

在SQL术语中,这就像是执行了select * from Room, Sprinkler,Sprinkler 表中的每一行将与Room表中的每一行相连接,从而产生以下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
room:office sprinkler:office
room:office sprinkler:kitchen
room:office sprinkler:livingroom
room:office sprinkler:bedroom
room:kitchen sprinkler:office
room:kitchen sprinkler:kitchen
room:kitchen sprinkler:livingroom
room:kitchen sprinkler:bedroom
room:livingroom sprinkler:office
room:livingroom sprinkler:kitchen
room:livingroom sprinkler:livingroom
room:livingroom sprinkler:bedroom
room:bedroom sprinkler:office
room:bedroom sprinkler:kitchen
room:bedroom sprinkler:livingroom
room:bedroom sprinkler:bedroom

这些连接结果显然会变得巨大,它们必然包含冗余数据。 cross products的大小通常是新规则引擎产品性能问题的根源。 从这可以看出,我们希望约束cross products,这便是用可变约束(the variable constraint)完成的。

1
2
3
4
5
6
7
8
rule
when
$room : Room()
$sprinkler : Sprinkler( room == $room )
then
System.out.println( "room:" + $room.getName() +
" sprinkler:" + $sprinkler.getRoom().getName() );
end

这就使得筛选结果只有寥寥几行, 这就为每一个Room筛选出了正确的Sprinkler. 在sql中(实际上是HQL) 这样的查询约等于select * from Room, Sprinkler where Room == Sprinkler.room.

分享到

drools用户指南----Methods vs Rules

Methods vs Rules

人们经常混淆方法和规则,初学者经常会问:“我如何理解规则的含义?“ 在最后一节之后,你会对规则的使用得心应手,答案也变得显而易见的,但在这之前,先让我们总结一下方法判断和规则的差异。

1
2
3
4
5
public void helloWorld(Person person) {
if ( person.getName().equals( "Chuck" ) ) {
System.out.println( "Hello Chuck" );
}
}
  1. 方法是被直接调用的
  2. 需要传递具体的实例
  3. 一个调用导致一次执行(One call results in a single execution)。
1
2
3
4
5
rule "Hello World" when
Person( name == "Chuck" )
then
System.out.println( "Hello Chuck" );
end
  1. 只要将其插入引擎,就可以通过匹配任何数据执行规则。
  2. 规则永远无法被直接调用,而只能触发
  3. 无法将特定的实例传递给规则
  4. 根据匹配,一个规则可能会触发一次或多次,或根本不被触发。
分享到

drools用户指南----stateless session(无状态会话)的使用

stateless session 无状态会话

Drools规则引擎中有如此多的用例和诸多功能,它变得令人难以置信。不过不用担心,复杂性是分层的,你可以用简单的用例来逐步了解drools。

无状态会话,不使用推理,形成最简单的用例。无状态会话可以被称为函数传递一些数据,然后再接收一些结果。无状态会话的一些常见用例有以下但不限于:

  1. 验证
    这个人有资格获得抵押吗?
  2. 计算
    计算抵押保费。
  3. 路由和过滤
    将传入的邮件(如电子邮件)过滤到文件夹中。
    将传入的邮件发送到目的地。

所以让我们从使用驾驶执照应用程序的一个非常简单的例子开始吧。

1
2
3
4
5
6
public class Applicant {
private String name;
private int age;
private boolean valid;
// getter and setter methods here
}

现在我们有了我们的数据模型,我们可以写出我们的第一个规则。我们假设应用程序使用规则来拒绝不符合规则的申请。由于这是一个简单的验证用例,我们将添加一条规则来取消任何18岁以下的申请人的资格。

1
2
3
4
5
6
7
8
package com.company.license

rule "Is of valid age"
when
$a : Applicant( age < 18 )
then
$a.setValid( false );
end

查看更多

分享到

drools用户指南----stateful session(有状态会话)的使用

stateful session 有状态会话

有状态会话长期存在,并允许随着时间的推移进行迭代更改。 有状态会话的一些常见用例包括但不限于:

  1. 监测
    半自动买入股票市场监控与分析。
  2. 诊断
    故障查找,医疗诊断
  3. 物流
    包裹跟踪和送货配置
  4. 合规
    验证市场交易的合法性。

与无状态会话相反,必须先调用dispose()方法,以确保没有内存泄漏,因为KieBase包含创建状态知识会话时的引用。 由于状态知识会话是最常用的会话类型,所以它只是在KIE API中命名为KieSession。 KieSession还支持BatchExecutor接口,如StatelessKieSession,唯一的区别是FireAllRules命令在有状态会话结束时不被自动调用。

我们举例说明了用于提高火灾报警器的监控用例。 只使用四个类,我们假设Room代表房子里的房间,每个Room都有一个喷头Sprinkler。 如果在房间里发生火灾,我们用一个Fire实例来表示,用Alarm代表警报 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Room {
private String name
// getter and setter methods here
}

public class Sprinkler {
private Room room;
private boolean on;
// getter and setter methods here
}

public class Fire {
private Room room;
// getter and setter methods here
}

public class Alarm {
}

在上一节无状态会话中介绍了插入和匹配数据的概念。 这个例子假设每个对象类型的都是单个实例被插入的,因此只使用了字面约束。 然而,房子有许多房间,因此rules必须表达实体类之间的关系,例如在某个房间内的喷洒器。 这最好通过使用绑定变量作为模式中的约束来完成。 这种“加入”过程产生了所谓的“cross products”,这在下一节中将会介绍。

查看更多

分享到

Zuul性能测试

环境准备

采用三台阿里云服务器作为测试
10.19.52.8 部署网关应用-gateway
10.19.52.9, 10.19.52.10 部署用于测试的业务系统
这里写图片描述

压测工具准备

选用ab作为压力测试的工具,为了方便起见,直接将ab工具安装在10.19.52.8这台机
测试命令如下:

1
ab -n 10000 -c 100 http://10.19.52.8:8080/hello/testOK?access_token=e0345712-c30d-4bf8-ae61-8cae1ec38c52

其中-n表示请求数,-c表示并发数,上面一条命令也就意味着,100个用户并发对http://10.19.52.8/hello/testOK累计发送了10000次请求。

服务器,网关配置

由于我们使用的tomcat容器,关于tomcat的一点知识总结如下:

查看更多

分享到

springcloud----Zuul动态路由

前言

Zuul 是Netflix 提供的一个开源组件,致力于在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。也有很多公司使用它来作为网关的重要组成部分,碰巧今年公司的架构组决定自研一个网关产品,集动态路由,动态权限,限流配额等功能为一体,为其他部门的项目提供统一的外网调用管理,最终形成产品(这方面阿里其实已经有成熟的网关产品了,但是不太适用于个性化的配置,也没有集成权限和限流降级)。

不过这里并不想介绍整个网关的架构,而是想着重于讨论其中的一个关键点,并且也是经常在交流群中听人说起的:动态路由怎么做?

再阐释什么是动态路由之前,需要介绍一下架构的设计。

传统互联网架构图

这里写图片描述
上图是没有网关参与的一个最典型的互联网架构(本文中统一使用book代表应用实例,即真正提供服务的一个业务系统)

加入eureka的架构图

这里写图片描述
book注册到eureka注册中心中,zuul本身也连接着同一个eureka,可以拉取book众多实例的列表。服务中心的注册发现一直是值得推崇的一种方式,但是不适用与网关产品。因为我们的网关是面向众多的其他部门已有或是异构架构的系统,不应该强求其他系统都使用eureka,这样是有侵入性的设计。

最终架构图

这里写图片描述
要强调的一点是,gateway最终也会部署多个实例,达到分布式的效果,在架构图中没有画出,请大家自行脑补。

本博客的示例使用最后一章架构图为例,带来动态路由的实现方式,会有具体的代码。

查看更多

分享到

分布式限流

前言

最近正在为本科论文的事感到心烦,一方面是在调研期间,发现大部分的本科论文都是以MVC为架构,如果是使用了java作为开发语言则又是千篇一律的在使用SSH,二方面是自己想就微服务,分布式方面写一篇论文,讲述一些技术点的实现,和一些中间件的使用,看到如八股文般的模板格式..不免让人望文生怯。退一步,投入模板化ssh-web项目的怀抱,落入俗套,可以省去自己不少时间,因为在外实习,琐事并不少;进一步,需要投入大量时间精力去研究,而且不成体系,没有论文参考。

突然觉得写博客,比写论文爽多了,可以写自己想写的,记录自己最真实的想法。可能会逐渐将之前博客维护的自己的一些想法,纳入到本科论文中去。

经典限流算法

说回正题,补上之前分布式限流的实现。先介绍一些现有的限流方案。

核心的算法主要就是四种:
A类:计数器法,滑动窗口法
B类:令牌桶法,漏桶法

这里的四种算法通常都是在应用级别讨论的,这里不重复介绍这四种算法的实现思路了,只不过我人为的将他们分成了A,B两类。

  • A类算法,是否决式限流。即如果系统设定限流方案是1分钟允许100次调用,那么真实请求1分钟调用200次的话,意味着超出的100次调用,得到的是空结果或者调用频繁异常。

  • B类算法,是阻塞式限流。即如果系统设定限流方案是1分钟允许100次调用,那么真实请求1分钟调用200次的话,意味着超出的100次调用,会均匀安排到下一分钟返回。(当然B类算法,也可以立即返回失败,也可以达到否决式限流的效果)

B类算法,如Guava包提供的RateLimiter,内部其实就是一个阻塞队列,达到阻塞限流的效果。然后分布式场景下,有一些思路悄悄的发生了变化。多个模块之间不能保证相互阻塞,共享的变量也不在一片内存空间中。为了使用阻塞限流的算法,我们不得不将统计流量放到redis一类的共享内存中,如果操作是一系列复合的操作,我们还不能使用redis自带的CAS操作(CAS操作只能保证单个操作的原子性)或者使用中间件级别的队列来阻塞操作,显示加分布式锁的开销又是非常的巨大。最终选择放弃阻塞式限流,而在分布式场景下,仅仅使用redis+lua脚本的方式来达到分布式-否决式限流的效果。redis执行lua脚本是一个单线程的行为,所以不需要显示加锁,这可以说避免了加锁导致的线程切换开销。

锁的演变

下面记录一下这个设计的演变过程。

  • 单体式应用中显示加锁
    首先还是回到单体应用中对共享变量进行+1的例子。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    Integer count = 0;

    //sychronized锁
    public synchronized void synchronizedIncrement(){
    count++;
    }

    //juc中的lock
    Lock lock = new ReentrantLock();

    public void incrementByLock(){
    lock.lock();
    try{
    count++;
    }finally {
    lock.unlock();
    }

    }

用synchronized或者lock同步的方式进行统计,当单位时间内到达限定次数后否决执行。限制:单体应用下有效,分布式场景失效,显示加锁,开销大。

  • 单体式应用中CAS操作
1
2
3
4
5
public AtomicInteger atomicInteger = new AtomicInteger(0);

public increamt(){
atomicInteger.incrementAndGet();
}

虽然没有显示加锁,但是CAS操作有一定的局限性,限流中不仅要对计数器进行+1,而且还要记录时间段,所以复合操作,还是无法避免加锁。

  • 分布式应用中显示加锁
1
2
3
4
5
6
7
8
9
10
11
RedisDistributeLock lock = new RedisDistributeLock();

public void incrementByLock(){
lock.lock();
try{
count++;
}finally {
lock.unlock();
}

}

分布式阻塞锁的实现,可以参考我之前的博客。虽然能达到多个模块之间的同步,但还是开销过大。不得已时才会考虑使用。

  • redis+lua脚本限流(最终方案)
1
2
3
4
5
6
7
8
9
10
11
12
13
local key = KEYS[1] --限流KEY(一秒一个)
local limit = tonumber(ARGV[1]) --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
redis.call("INCRBY", key,"1") -- 如果不需要统计真是访问量可以不加这行
return 0
else --请求数+1,并设置2秒过期
redis.call("INCRBY", key,"1")
if tonumber(ARGV[2]) > -1 then
redis.call("expire", key,tonumber(ARGV[2])) --时间窗口最大时间后销毁键
end
return 1
end

lua脚本返回值比较奇怪,用java客户端接受返回值,只能使用Long,没有去深究。这个脚本只需要传入key(url+时间戳/预设时间窗口大小),便可以实现限流。
这里也贴下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
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
85
86
87
88
89
90
91
92
93
94
95
package sinosoftgz.apiGateway.utils;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.util.Assert;

import java.util.Arrays;

/**
* Created by xujingfeng on 2017/3/13.
* <p>
* 基于redis lua脚本的线程安全的计数器限流方案
* </p>
*/
public class RedisRateLimiter {

/**
* 限流访问的url
*/
private String url;

/**
* 单位时间的大小,最大值为 Long.MAX_VALUE - 1,以秒为单位
*/
final Long timeUnit;

/**
* 单位时间窗口内允许的访问次数
*/
final Integer limit;

/**
* 需要传入一个lua script,莫名其妙redisTemplate返回值永远是个Long
*/
private RedisScript<Long> redisScript;

private RedisTemplate redisTemplate;

/**
* 配置键是否会过期,
* true:可以用来做接口流量统计,用定时器去删除
* false:过期自动删除,时间窗口过小的话会导致键过多
*/
private boolean isDurable = false;

public void setRedisScript(RedisScript<Long> redisScript) {
this.redisScript = redisScript;
}

public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public boolean isDurable() {
return isDurable;
}

public void setDurable(boolean durable) {
isDurable = durable;
}

public RedisRateLimiter(Integer limit, Long timeUnit) {
this.timeUnit = timeUnit;
Assert.isTrue(timeUnit < Long.MAX_VALUE - 1);
this.limit = limit;
}

public RedisRateLimiter(Integer limit, Long timeUnit, boolean isDurable) {
this(limit, timeUnit);
this.isDurable = isDurable;
}

public boolean acquire() {
return this.acquire(this.url);
}

public boolean acquire(String url) {
StringBuffer key = new StringBuffer();
key.append("rateLimiter").append(":")
.append(url).append(":")
.append(System.currentTimeMillis() / 1000 / timeUnit);
Integer expire = limit + 1;
String convertExpire = isDurable ? "-1" : expire.toString();
return redisTemplate.execute(redisScript, Arrays.asList(key.toString()), limit.toString(), convertExpire).equals(1l);
}

}

由此可以见,分布式场景下,一个小小的统计次数的需求,如果真想在分布式下做到最完善,需要花很大的精力。

分享到

DevOps的八荣八耻

前言

被群里的好友安利了一发,周日跑去参加了一个技术讲座《云上开发与运维最佳实践》,听完两个人的演讲之后才发现主题竟然是讲运维,好在有一个人干货不少,在此记录下所得。简单追溯了一下这个DevOps才发现并不是一个新的概念,早在2010年就能看到有相关的人在追捧这个概念了。DevOps 就是开发(Development)和运维(Operations)这两个领域的合并。(如果没错的话,DevOps还包括产品管理、QA、winces 甚至销售等领域)。这种理念和现如今流行的微服务架构以及分布式特性的相关理念不谋而合。这篇文章主要就是转载记录了当时又拍云运维总监的演讲稿。

DevOps的八荣八耻

DevOps这个思想提出来已经五六年了,一直都是呼声很高,落地很难,为什么呢?这可能与各个公司的业务情况和技术发展路线有或多或少的关系,比如说创业的最早技术合伙人是运维出身或者技术出身,但是水平不高,为了公司持续发展,引入新鲜血液时,就会存在技术的先进性跟解决遗留烂摊子的矛盾。又或者业务本身偏向于用户,导致技术被边缘化,产品又没有好的架构,限制了快速发展等;所以,DevOps的推进一定要自上而下,凭借挑战自我,颠覆传统的勇气才能去落实。

以可配置为荣,以硬编码为耻

  img

△ 以可配置为荣,以硬编码为耻

hardcoding一时爽,真正要做改动时,需要定位代码,做出调整,甚至可能会破坏功能。以下可以说是配置的一个进化史

• 本地配置,程序⽣生成 (txt/ini/cfg)
• 集中配置, 动态⽣生成(Yaml/Json)
• 环境变量量(代码⽆无侵⼊入&语⾔言⽆无关性)
• 服务⾃自动发现,⾃自动注册(zookeeper/consul)

以互备为荣,以单点为耻

  img

  △ 以互备为荣,以单点为耻

互容互备一直是优良架构的设计重点。

又拍云早期做架构设计,使用了LVS+Keeplived+VRRP做转换,这样可以方便负载均衡,动态升级,隔离故障。现在的又拍云第二代,已经在部分大节点使用OSPF和Quagga做等价路由的负载均衡和冗余保障。

Nginx可以加Haproxy或LVS做负载均衡。MySQL可以做主从切换,或者是MMM的高可用成熟解决方案。我们的消息队列之前用rabbitmq做,现在主要是redis和kafka集群化,其中kafka已经迁到了Mesos容器平台里。

服务的自动发现、注册,我们可以使用consul、etcd、doozer(Heroku公司产品),还有zookeeper。主要区别是算法不一样,zookeeper用的是paxos算法,而consul用的是raft算法。目前看来consul比较流行,因为consul的自动发现和自动注册更加容易使用。etcd主要是CoreOS在主推,CoreOS本身就是一个滚动发布的针对分布式部署的操作系统,大家可以去关注一下它。还有一个是hadoop和elk,大数据平台的可扩展性是标配,很容易互备。

上面是举了一些常见互备的软件组件的造型,那我们如何是设计一个无单点的架构呢?主要掌握以下几点:

1.无状态

无状态意味着没有竞争,很容易做负载均衡,负载均衡的方式有很多种,F5,LVS,Haproxy,总能找到一种适合你的方式。

2.无共享

以前我们很喜欢用内存来保持临时信息,如进程间的交换,这种方式虽然效率很高,但是对程序的扩展性没什么好处,尤其是现在的互联网体量,光靠单机或者高性能机器是明显玩不转的。所以我们现在就需要使用类似消息队列的组件,把数据共享出去,利用多台机器把负载给承担下来。

3.松耦合/异步处理

以前我们用Gearman这样的任务框架。大家可以把任务丢进任务池里,生成多个消费者去取任务。当我的消费不够用时,可以平滑增加我的work资源,让他从更快的去拿任务。运维平台这边以python/celery的组合使用更多。

4.分布式/集群协作

像Hadoop这样的天生大数据/数据仓库解决方案,由于先前设计比较成熟,一般都是通过很多台机器扩容来实现map/reduce的扩展计算能力。

以随时重启为荣,以不能迁移为耻

img

△ 以随时重启为荣,以不能迁移为耻

关于这个点,我们讲三个方面:

1.Pet到Cow观念的转变

以前我们说机器是pet,也就是宠物模式,然后花了几万块钱去买的服务器,当宝一般供奉。但事实上却并不是这样,任何电子设备、服务器只要一上线,便开始了一个衰老的过程,你根本不知道在运行过程中会发生什么事,比如说质量差的电容会老化爆浆,电子元器件在机房的恶劣环境里会加速损坏,这些变化都是我们无法参与控制的,所以无论我们怎么努力,都无法保障机器有多么的牢靠。

谷歌指出的Cow模式就是指农场模式。就是要把机器发生故障当做常态,打个比方,比如说这头牛死了,那我就不要了,因为我有很多这样的牛,或者是再拉一头新的牛。这就是我们软件开发和运维需要做的转变,去适应这种变化。

2.OpenStack虚拟机的编排

虚拟化是个好东西,通过OpenStack我们很容易就可以做出一些存储或者迁移的操作,但是在实施的过程中,也是一波三折的。

又拍云从2014年开始在内部推动OpenStack,当然我们也踩过OpenStack网络的坑,那时候我们用双千兆的卡做内网通讯,因为使用OpenStack实现虚拟化后,一切都变成了文件,在网络上传输的话,对网络的压力会非常大,结果就导致部分服务响应缓慢(因为本身就是实验性质,所以在硬件上没有足够投入,内测时也没有推广,所以影响不大)。

2015年又拍云再上的OpenStack,全部都用双万兆的网卡做bonding,交换机也是做了端口聚合和堆叠。目前来说,只有云存储没有上线,其它云处理,云网络的使用还是能够满足要求。

3.Docker的导入导出

Docker是更轻量级的资源隔离和复用技术,从2016年开始,又拍云同时也在尝试使用Mesos/Docker来实现云处理的业务迁移。

以整体交付为荣,以部分交付为耻

  img

  △ 以整体交付为荣,以部分交付为耻

以往开发运维要安装一个机器,首先要去申请采购,购买完了还要等待运输,在运输中要花去一天的时间,之后还需要配交换机和网络。在这个过程中你会发现,简单的给开发配台机器,光上架就涉及到运维的很多环节,更不要说系统安装,优化,软件配置等剩余工作了,所以大多数情况下你只能做到部分交付。

要如何解决这些问题?通过OpenStack可以做到云计算、云网络、云存储这三块搭建完成之后,进行整体交付。

根据一些经验总结,在整个云平台当中,云存储的坑最多,云计算、云网络相对来说比较成熟。现在云计算的硬件基本上是基于英特尔CPU的虚拟化技术来硬件指令穿透的,损耗大概2%~5%,这是可以接受的。至于云网络,刚才胡凯(B站运维总监)提到内网包转发效率,我做过一个测试,在OpenStack的内网中,如果MTU默认是1500,万兆网卡的转发率大概为6.7xxGbps。后来我在优化的过程中,也翻查一些文档,看到的数据是可以达到9.5xxGbps,通过不断的摸索,对比测试后发现,如果把内网的MTU搞成大包,如9000时,万兆网卡的存储量直接达到了9.72Gbps左右的。不过,这个MTU需要提前在宿主机上调整好,需要重启生效。所以,这个问题发现得越早越好,这样就可以做到统一调度,分配资源。

Docker的好处是可以做到Build、Shipand Run,一气呵成。无论是对开发,测试,还是运维来说,Docker都是同一份Dockerfile清单,所以使用Docker在公司里的推动就很顺畅。虽然OpenStack也可以一站式交付,整体交付,使用时非常方便。但是对开发来说,他还是拿到一台机器,还是需要去安装软件环境,配置,上线,运行,除了得到机器快一些,对上线服务没有什么大的帮助,所以又拍云现在的Openstack集群一般对内申请开发测试用,外网生产环境还是以Docker容器化部署为主,这也是大家都喜闻乐见的方式,但前提是开发那边能够适应编写Dockerfile(目前是我在内部推动这种变革,如新的项目就强制要求用docker)。

以无状态为荣,以有状态为耻

  img

  △ 以无状态为荣,以有状态为耻

有状态的服务真的很麻烦,无论是存在数据库、磁盘开销,还有各种锁等资源的竞争,横向扩展也很差,不能重启,也不能互备。所以,有姿态的服务对于扩展原则来说,就是一场恶梦。如果是说我们解决这个问题,那就要使用解耦和负载均衡的方法去解决问题。

1.使用可靠的中间件

中间件其实最早出现在金融公司、证券公司,后来随着互联网行业不断壮大以后,就用一些高可靠性的号称工业级的消息队列出现,如RabbitMQ,一出来以后,就把中间件拉下神坛。随着中间件民用化,互联网蓬勃发展,是可以把一些服务变成无状态,方便扩展。

2.公共资源池

我们可以通过各种云,容器云、弹性云,做计算单元的弹性扩展。

3.能够被计算

如果你不想存状态,那也可以被计算,比如说Ceph存储,它的创新在于每个数据块都是可计算出来的,这就类似无状态的,每次都算,反正现在的cpu都这么强悍了,所以,无状态是一个命题,在做架构的时候,你脑海里一定要有这个意念,然后再看你用什么样的方式开动脑筋,预先的跟开发,运维沟通好,把应用拆分成一种无状态的最佳组合。

以标准化为荣,以特殊化为耻

  img

△ 以标准化为荣,以特殊化为耻

在标准化方面,我们在这几个方面改良:

1.统一输入输出

统一入口是我加入又拍云后做的第一件事情,我们用一个统一的文本,到现在也在用,然后推送到所有的边缘,服务器上面的组件,要用到的参数,都能从配置里读出来。代码管理方面我们也使用git,git wiki,批量部署我们用ansible(早在2012年,我做了一些比较后,就在公司里推行ansible,看来还是很明智的决定)。

2.统一的流程管理

运维中使用python最多,所以我们使用了yaml和playbook。又拍云有自己的跳板机,通过VPN登陆,目前我们也在试用一个带有审计功能的堡垒机,可以把每个人的操作录制下来,然后再去回放观察,改进我们的工作流程。

3.抽象底层设计和复用组件

如果是开发者的话,就会写很多的复用函数,对于优秀的运维人员来说,也要有优秀的抽象业务的能力,也要去做一些重复工作的复用准备,如频繁的,繁琐易出错的手工操作抽象成若干运维的脚本化。

最后是巧妙的利用虚拟化、容器服务、server-less微服务,这些服务是可以被备份,还原的,可以保持一个相对稳定的状态,我们要拒绝多的特殊管理操作。香农-信息熵理论里说,变量的不确定性越大,熵就越大,把它搞清楚所需要的信息量也就越大。理论上来说,如果是一个孤立的系统,他就会变得越来越乱。

以自动化工具为荣,以手动和人肉为耻

img

  △ 以自动化工具为荣,以手动和人肉为耻

又拍云早期,用的是bash、sed、awk,因为我之前有搞嵌入式的背景和经验,对一个十几兆的嵌入式系统来说,上面是不可能有python/perl/nodejs等环境。所以我们把服务器批量安装,部署,上线,做成了嵌入式的系统后,只要点亮以后,运行一个硬件检测的程序,会把机器的CPU、内存、硬盘大小等都打印出来,供货商截图给我看,这个机器是否合格。合格的机器可以直接发到机房去,在机器到了机房通上网线以后会有一个ansibleplaybook的推动。

自从用了这种方法以后,我们在公司里面基本上没有见到服务器,一般直接产线上检测通过后发到机房。然后又拍云的运维人员就可以连上去远程管理,在过去的三年里我们服务器平均每年翻了三倍,节点翻了六倍多,但是人手并没有增加。

关于tgz、rpm、pkg的打包部署,我们用的是tgz的打包及docker镜像。优势在于,又拍云自有CDN网络,软件通过推动到CDN网络下可以加速下发。

关于集成测试、自动测试的发布,像ELK集中日志的分析、大数据的分析,我们现在使用ELK以后,只要有基础的运维技术知识便可看懂,不需要高深的运维知识和脚本编辑知识,大多数人都可以完成这份工作,好处就是你多了好多眼睛帮你一起来发现问题,定位问题。

最后是不要图形,不要交互,不要终端。一旦有了图形以后,很难实现自动化。原则就是,不要手工hack,最好是用程序生成程序的方式去完成这个步骤。

以无人值守为荣,以人工介入为耻

  img

  △ 以无人值守为荣,以人工介入为耻

运维部门要做的事情有三件:

1.运维自动化

要有一定的业务抽象能力,要有标准化的流程。没有好的自动化,就很难把运维的工作效率提升了,只要做好这些,就可以节省时间,从容应对业务增长。而且运维自动化的另一个好处就是运维不会因为人的喜怒哀乐而受到影响稳定性,比如说我今天心情不好,你让我装一台机器我还可以忍,你让我装十台一百台就不行了。但如果公司有了运维自动化的流程,这个事情就可以避免,因为谁做都一样。

2.监控要常态

2016年年初,又拍云特别成立大数据分析部门,我们把日志做了采样收集和过滤,通过大数据平台做日志的同构数据分析,重点关注4xx/5xx/2xx比例,响应时间分析如100毫秒、200毫秒、500毫秒,还有区域性的速率分布,讲真,这真是一个好东西。

3.性能可视化

数据的有效展示。现在ELK对我们的帮助很大,从监控图上来看相关的数据指标,一目了然。这里就不反复赘述了。

DevOps的本质

最后,我们谈一谈DevOps的本质。

  1. 弹性

    像亚马逊推云时,那个单词叫elastic,意思是,你要能够扩展,如横向扩展;你要能负载均衡,如果你是基于openstack/docker资源池,你的资源就可以复用,可以编排回滚。比如说OpenStack有模板,我打一个镜像包,稍微重了一点,Docker的就轻一点,Docker可以做一个滚动发布,可以保留原来的程序、原来的容器,你可以做快速切换,这也是一种变化的弹性。

  2. 无关性

    如果是虚拟化资源,一切都可以在模板里面设置,可以把底层的硬件、系统、网络抚平差异,比如说不管物理磁盘是1T(市面上缺货)/4T/6T的盘,都可以划分100G容量,所以当把一切变成按需申请的服务,无论是开发还是运维,工作都会比较简单,因为它的无关性。

  3. 不可变的基础设施

    这个对传统运维可能是一种打击,因为基础镜像可能已经做的足够安全,足够完美,足够精干,不需要基础运维过多的人工参与。但我认为恰恰能帮助传统运维减轻工作量,反而有更多的精力去迎接虚拟化、容器化,SDN的挑战,掌握了新技能后,就可以随取随用。

分享到

java并发实践--ConcurrentHashMap与CAS

前言

最近在做接口限流时涉及到了一个有意思问题,牵扯出了关于concurrentHashMap的一些用法,以及CAS的一些概念。限流算法很多,我主要就以最简单的计数器法来做引。先抽象化一下需求:统计每个接口访问的次数。一个接口对应一个url,也就是一个字符串,每调用一次对其进行加一处理。可能出现的问题主要有三个:

  1. 多线程访问,需要选择合适的并发容器
  2. 分布式下多个实例统计接口流量需要共享内存
  3. 流量统计应该尽可能不损耗服务器性能

但这次的博客并不是想描述怎么去实现接口限流,而是主要想描述一下遇到的问题,所以,第二点暂时不考虑,即不使用redis。

说到并发的字符串统计,立即让人联想到的数据结构便是ConcurrentHashpMap<String,Long> urlCounter;

查看更多

分享到