AWS Lambda最佳实践之利用容器重用来提高函数性能
AWS Lambda最佳实践:
- 利用容器重用来提高函数性能。 确保您的代码检索到的外部化配置或依赖关系在初次执行后在本地存储和引用。限制变量/对象在每次调用时的重新初始化,而是使用静态初始化/构造函数、全局/静态变量以及单例。保持运行并重复使用连接 (HTTP,数据库等),它们在上次调用时建立。
初学Lambda时,被Lambda自动扩展功能误导,一直以为Lambda执行时,会启动一个实例去处理一个请求(类似多线程),示意图:
其实不然,Lambda启动一个实例可以处理多个请求,在一个实例处理请求数达到一定数量时,就会提前进行扩展,如图:
因此,容器重用是提高Lambda函数性能的有效方法。
实验目的:
验证容器重用对于Lambda函数性能的影响。
实验语言和工具:
JDK1.8,Maven,AWS Lambda
实验内容:
以Lambda函数查询RDS数据库为例,首先测试当每次查询数据库都新建一个数据库连接时,获取数据库连接的时间和整个函数运行时间,再测试只有第一次查询数据库时,才新建数据库连接的情况。测试次数20。
实验详情:
1、每次查询都新建数据库连接的service代码:
/**
* 每次操作都新建数据库连接
*
* @return 操作结果
*/
public String getConnectEverytime() {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
logger.error("load driver failed.", e);
}
Connection conn;
List<Object> list = new ArrayList<>();
try {
long t1 = System.currentTimeMillis();
conn = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
long t2 = System.currentTimeMillis();
logger.info("get connection cost:" + (t2 - t1));
logger.info("get connection success.");
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(SQL_QUERY);
while (resultSet.next()) {
Map<Integer, String> map = new HashMap<>();
map.put(resultSet.getInt(1), resultSet.getString(2));
list.add(map);
}
resultSet.close();
statement.close();
conn.close();
} catch (Exception e) {
logger.error(e);
}
return JSONObject.toJSONString(list);
}
2、只有第一次查询才新建数据库连接的service代码:
/**
* 第一次操作新建数据库连接
*
* @return 操作结果
*/
public String getConnectFirstTime() {
Connection conn;
List<Object> list = new ArrayList<>();
try {
long t1 = System.currentTimeMillis();
conn = DBHelper.getInstance().getConnection();
long t2 = System.currentTimeMillis();
logger.info("get connection cost:" + (t2 - t1));
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery(SQL_QUERY);
while (resultSet.next()) {
Map<Integer, String> map = new HashMap<>();
map.put(resultSet.getInt(1), resultSet.getString(2));
list.add(map);
}
resultSet.close();
statement.close();
conn = null;
} catch (Exception e) {
logger.error(e);
}
return JSONObject.toJSONString(list);
}
DBHelper.java
/**
* 数据库连接管理类,单例(静态内部类模式)
*
* @author zhangzhentao1995@163.com
* 2017-11-28
*/
public class DBHelper {
private static final Logger logger = LogManager.getLogger(DBHelper.class);
private Connection connection;
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://";
private static final String USER_NAME = "";
private static final String PASSWORD = "";
private static class LazyHolder {
private static final DBHelper helper = new DBHelper();
}
private DBHelper() {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
logger.error("load driver failed.", e);
}
try {
connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
logger.info("get connection success.");
} catch (Exception e) {
logger.error("get connection failed.", e);
}
}
public static final DBHelper getInstance() {
return LazyHolder.helper;
}
public Connection getConnection() {
return connection;
}
public void setConnection(Connection connection) {
this.connection = connection;
}
}
实验结果:
首次查询时,两种方法都进行了新建数据库连接操作,所以耗时差不多,但之后的操作中,第二种方法获取数据库连接的耗时为0ms,平均耗时比第一种方法减少30%。
结论:
使用单例模式在AWS Lambda函数中进行资源重用处理的方法具有可行性,对Lambda函数的性能提高效果显著。
实现容器重用时,也可以使用一些依赖注入的框架,但是首选启动时可以更快速加载的框架,而不是更复杂的框架。
项目Github:https://github.com/zhangzhentao1995org/AwsLambdaBestPracticeReusing.git