p0po's blog

排行榜技术记录

排行榜一般都是异步任务跑出结果,将结果放入缓存,比如redis,然后将缓存中的结果展示给用户。
一般来说,大家都能想到这个方案,但是很多在展示的排行榜都忽略了一个要素,那就是排行榜生成时间,
对于异步生成的排行榜来说,这个至关重要,因为从严谨性考虑,应该给用户交代清楚,这个结果的有效时间范围。

排行榜需求是统计用户投资前100名。

开始为了完成功能:

在数据量少的时候,直接SQL统计结果出来,放到REDIS中,很简单,也很有效。

但随着数据量的增加,一个SQL肯定会越来越慢:

于是想到分页查询,在内存中统计结果,然后将结果放入到REDIS中,效果也还不错。

随着数据量的增加,每次统计IO越来越大,时间越来越长:

于是想到将查询到结果缓存到本地内存中,缓存中没有才去数据库查询,然后将其缓存到本地,即减少了和数据库的交互次数,又减少了每次统计时间,可以继续运转了。

随着数据量的增加,本地内存开始出现溢出告警,对于java程序来说,经常出现FULL GC:

于是将每个用户的投资总额放到redis,标记每次查询的时间,下次增量查询计算相关用户的投资总额,并改写用户在redis中的投资总额,
将每个用户的投资总额和排行榜中的最小值做比较,如果大于最小值,则放入排行榜,删除原来的最小值。

随着数据量的增加,没有出现什么大的问题,但上述方案存在隐患,后续验证后会补上。

Hello World

欢迎来到 P0PO 的博客 这是我的技术博客,长期有效.

JODA-TIME 文档

为什么选择joda-time?因为jdk的时间类比较简陋,先看几个joda-time官网的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public boolean isAfterPayDay(DateTime datetime) {
if (datetime.getMonthOfYear() == 2) { // February is month 2!!
return datetime.getDayOfMonth() > 26;
}
return datetime.getDayOfMonth() > 28;
}

public Days daysToNewYear(LocalDate fromDate) {
LocalDate newYear = fromDate.plusYears(1).withDayOfYear(1);
return Days.daysBetween(fromDate, newYear);
}

public boolean isRentalOverdue(DateTime datetimeRented) {
Period rentalPeriod = new Period().withDays(2).withHours(12);
return datetimeRented.plus(rentalPeriod).isBeforeNow();
}

public String getBirthMonthText(LocalDate dateOfBirth) {
return dateOfBirth.monthOfYear().getAsText(Locale.ENGLISH);
}

接下来看一下joda-time的主要功能:

  • LocalDate - date without time(没有时间的日期)
  • LocalTime - time without date (没有日期的时间)
  • Instant - an instantaneous point on the time-line (时间轴上的时间点)
  • DateTime - full date and time with time-zone (没有时区的日期时间)
  • DateTimeZone - a better time-zone (好用的时区)
  • Duration and Period - amounts of time (时间段)
  • Interval - the time between two instants (两个时间点之间的时间)
  • A comprehensive and flexible formatter-parser (灵活全面的时间格式化工具)

如何构建?
Each date-time class provides a variety of constructors. These include the Object constructor. This allows you to construct an instance from a variety of different objects: For example, a DateTime can be constructed from the following objects:
每个date-time类都提供多个构造方法。这些包含类构造方法。用这些方法能够从不同的类构造date-time类,例如:

  • Date - a JDK instant
  • Calendar - a JDK calendar
  • String - in ISO8601 format
  • Long - in milliseconds
  • any Joda-Time date-time class

(ISO8601 格式:合并表示时,要在时间前面加一大写字母T,如要表示北京时间2004年5月3日下午5点30分8秒,可以写成2004-05-03T17:30:08+08:00或20040503T173008+08。)

日期和时间(Date and Time)

The use of an Object constructor is a little unusual, but it is used because the list of types that can be converted is extensible. The main advantage is that converting from a JDK Date or Calendar to a Joda-Time class is easy - simply pass the JDK class into the constructor. For example, this code converts a java.util.Date to a DateTime:
例如,将Date转化为DateTime

1
2
java.util.Date juDate = new Date();
DateTime dt = new DateTime(juDate);

Each date-time class provides simple easy methods to access the date-time fields. For example, to access the month and year you can use:
每个类都提供了简单易用的方法获取时间字段。例如,可以用如下方法获取年和月:

1
2
3
DateTime dt = new DateTime();
int month = dt.getMonthOfYear(); // where January is 1 and December is 12
int year = dt.getYear();

All the main date-time classes are immutable, like String, and cannot be changed after creation.
However, simple methods have been provided to alter field values in a newly created object. For example, to set the year, or add 2 hours you can use:
所有主要的时间类都是不可变的,类似于String类,一旦创建就不能改变。但是可以通过类提供的简单方法修改字段值。
例如,可以设置年份或者加2个月:

1
2
3
DateTime dt = new DateTime();
DateTime year2000 = dt.withYear(2000);
DateTime twoHoursLater = dt.plusHours(2);

In addition to the basic get methods, each date-time class provides property methods for each field.
These provide access to the full wealth of Joda-Time functionality. For example, to access details about a month or year:
获取年和月的的详情:

1
2
3
4
5
DateTime dt = new DateTime();
String monthName = dt.monthOfYear().getAsText();
String frenchShortName = dt.monthOfYear().getAsShortText(Locale.FRENCH);
boolean isLeapYear = dt.year().isLeap();
DateTime rounded = dt.dayOfMonth().roundFloorCopy();

P2P订单系统设计笔记

定义

  • R系统:请求发起方
  • S系统:请求接受方

R–>S 发起一个同步网络交易过程

  • R生成唯一下单号存储(R如果不是系统,而是用户不用此步)
  • R携带唯一的下单号发起交易请求
  • S接收唯一下单号防重校验
  • S防重校验通过生成订单号存储起来,返给R,发送MQ消息(下单号,订单号,状态…)
  • S防重校验通过失败将查询到的订单号,返给R
  • R接收到S的返回结果更新数据
  • S如果接收不到结果,用同一个下单号重试
  • S对于MQ的处理要根据下单号防重

对于网络请求,有明确结果是最理想的情况,可能出现的异常通常有两大类:

  • R网络请求没有达到S
  • R网络请求达到S,S的返回结果没有到达R
  • S的返回结果超出预期

服务可用性指标

线上服务可用性评估标准

服务可用性百分比

引用一句我的签名“概率是时间之后的第五个维度”,时间我们尚且不能控制,何况概率这种事呢?

服务可用性,是衡量一个系统稳定性的标准,大家通常用几个9做为系统稳定性的单位,比如通常说的4个9:

如果衡量时长为一天,那么允许故障时长为8.64秒,

如果衡量时长为一个月,那么允许故障时长为1.752分钟,

如果衡量时长为一年,那么允许故障时长为52.26分钟.

我们可以看出,时间越长对系统的考验越大,因为随着时间增长发生故障的概率会增大,因此长时间提供稳定服务就成了一个系统比较困难的指标。

我们可以简单列举几个常见的故障,比如系统受到ddos攻击,需要切换域名到其他ip,这个大概需要多久呢?

最快15分钟生效,整个互联网同步至少要一多个小时吧。

如果没有应对措施,一年来一次你就达不到4个9了。

再比如,系统上线要停止服务?停一次如果花2分钟,那这个月你就达不到4个9了。

由于流量激增,某个服务挂了,停几十分钟,又要与4个9无缘了。

4个9好难,5个9在国内的公司中是不敢奢求了,我们暂时把目标放低点,先踏踏实实做好3个9,从基础架构和基础设施开始。