Nodejs模拟并发,尝试的两种解决方案
一、准备数据库表
创建商品库存表 db_stock ,插入一条数据
DROP TABLE IF EXISTS `db_stock`;
CREATE TABLE `db_stock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`goods_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品id',
`inventory_total` int(11) NULL DEFAULT NULL COMMENT '总库存',
`inventory_remain` int(11) NULL DEFAULT NULL COMMENT '剩余库存',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '记录实时库存表' ROW_FORMAT = Dynamic; -- ----------------------------
-- Records of db_stock
-- ----------------------------
INSERT INTO `db_stock` VALUES (1, 'goods_01', 100, 100); SET FOREIGN_KEY_CHECKS = 1;
创建出库信息表 db_checkout
DROP TABLE IF EXISTS `db_checkout`;
CREATE TABLE `db_checkout` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`goods_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '商品id',
`opt_num` int(11) NULL DEFAULT NULL COMMENT '操作数量',
`inventory_remain` int(11) NULL DEFAULT NULL COMMENT '剩余库存',
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '出库记录表' ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
查看数据库
mysql> select * from db_stock;
+----+----------+-----------------+------------------+
| id | goods_id | inventory_total | inventory_remain |
+----+----------+-----------------+------------------+
| 1 | goods_01 | 100 | 100 |
+----+----------+-----------------+------------------+
1 row in set (0.00 sec) mysql> select * from db_checkout;
Empty set (0.00 sec) mysql>
二、准备Nodejs接口(基于Koa2)
接口说明:
url dm/testConcurrentOrder
method get
data initDB //首次初始化数据
goods_id //商品id
opt_num //出库数量
router.js
const router = require('koa-router')();
const dm_model = require("../models/dm_model"); router.preffix("/dm"); /**
* 测试并发
* testConcurrentOrder
*/
router.get('/testConcurrentOrder', async function (ctx) {
let reqParams = ctx.reqParams;
try {
ctx.body = await dm_model.testConcurrentOrder(reqParams);
} catch (e) {
ctx.body = {
success: false,
msg: `出库异常: ${e.toString()}`,
}
}
}); module.exports = router;
model.js
let db = require("../utils/mysql_util").db;
let moment = require("moment"); let model = {};
/**
* 测试并发
* @param reqParams
* @returns {Promise<{msg: string, success: boolean}>}
*/
model.testConcurrentOrder = async function (reqParams) {
let initDB = !!reqParams.initDB || 0;// 是否初始化db let goods_id = reqParams.goods_id || "goods_01";// 商品id
let opt_num = reqParams.opt_num || 1;// 出库数量 if (initDB) {
// 清除数据
await db.query(`TRUNCATE db_stock`);
await db.query(`TRUNCATE db_checkout`);
// 插入库存数据
await db.query(`INSERT INTO db_stock(goods_id, inventory_total, inventory_remain) VALUES ('${goods_id}', 100, 100)`); return {
success: true,
msg: '初始化DB成功',
}
} else { let tran = await db.beginTransaction();
// 查询剩余库存
let querySql = `SELECT
t.inventory_total,
t.inventory_remain
FROM
db_stock t
WHERE
1 = 1
AND t.goods_id = ?`; try {
let result = {}; let queryResult = await tran.queryOne(querySql, [goods_id]);
if (!queryResult) {
result = {
success: false,
msg: `无法匹配商品id:${goods_id}`
}
} else {
let inventory_remain = queryResult.inventory_remain - 1; // 新增出库记录
let checkoutSql = `INSERT INTO db_checkout(goods_id, opt_num, inventory_remain, create_time) VALUES (?, ?, ?, ?)`;
await tran.query(checkoutSql, [goods_id, opt_num, inventory_remain, new Date()]); // 更新库存信息
let updateStockSql = `UPDATE db_stock t SET inventory_remain = ? WHERE t.goods_id = ?`;
// 更新库存
await tran.query(updateStockSql, [inventory_remain, goods_id]);
result = {
success: true,
msg: '出库成功',
}
}
await tran.commit();
return result;
} catch (e) {
await tran.rollback();
throw e;
}
} }; module.exports = model;
三、并发测试
总共请求20次,20个并发同时请求
C:\WINDOWS\system32>ab.exe -n -c http://localhost:3000/dm/testConcurrentOrder
This is ApacheBench, Version 2.3 <$Revision: $>
Copyright Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient).....done Server Software:
Server Hostname: localhost
Server Port: Document Path: /dm/testConcurrentOrder
Document Length: bytes Concurrency Level:
Time taken for tests: 24.284 seconds
Complete requests:
Failed requests:
Total transferred: bytes
HTML transferred: bytes
Requests per second: 0.82 [#/sec] (mean)
Time per request: 24284.077 [ms] (mean)
Time per request: 1214.204 [ms] (mean, across all concurrent requests)
Transfer rate: 0.30 [Kbytes/sec] received Connection Times (ms)
min mean[+/-sd] median max
Connect: 0.4
Processing: 4696.9
Waiting: 4696.2
Total: 4696.9 Percentage of the requests served within a certain time (ms)
%
%
%
%
%
%
%
%
% (longest request) C:\WINDOWS\system32>
查看并发请求后,数据库表的信息
mysql> select * from db_stock;
+----+----------+-----------------+------------------+
| id | goods_id | inventory_total | inventory_remain |
+----+----------+-----------------+------------------+
| 1 | goods_01 | 100 | 97 |
+----+----------+-----------------+------------------+
1 row in set (0.00 sec) mysql> select * from db_checkout;
+----+----------+---------+------------------+---------------------+
| id | goods_id | opt_num | inventory_remain | create_time |
+----+----------+---------+------------------+---------------------+
| 61 | goods_01 | 1 | 99 | 2019-10-09 17:31:01 |
| 62 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 63 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 64 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 65 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 66 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 67 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 68 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 69 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 70 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 71 | goods_01 | 1 | 98 | 2019-10-09 17:31:01 |
| 72 | goods_01 | 1 | 97 | 2019-10-09 17:31:01 |
| 73 | goods_01 | 1 | 97 | 2019-10-09 17:31:01 |
| 74 | goods_01 | 1 | 97 | 2019-10-09 17:31:01 |
| 75 | goods_01 | 1 | 97 | 2019-10-09 17:31:01 |
| 76 | goods_01 | 1 | 97 | 2019-10-09 17:31:01 |
| 77 | goods_01 | 1 | 97 | 2019-10-09 17:31:01 |
| 78 | goods_01 | 1 | 97 | 2019-10-09 17:31:01 |
| 79 | goods_01 | 1 | 97 | 2019-10-09 17:31:01 |
| 80 | goods_01 | 1 | 97 | 2019-10-09 17:31:01 |
+----+----------+---------+------------------+---------------------+
20 rows in set (0.00 sec) mysql>
结果:并发请求20次,由于代码和数据库没有控制并发,导致数据错误。
四:解决方案
1、加行锁
在查询剩余库存的时候调整sql为
// 查询剩余库存
let querySql = `SELECT
t.inventory_total,
t.inventory_remain
FROM
db_stock t
WHERE
1 = 1
AND t.goods_id = ? for update`;
这个是基于数据库的行锁实现,对于单节点Mysql实现没有难度,测试结果就不放了。
2、使用redis分布式锁(基于Redlock的实现)
调整model.js
let db = require("../utils/mysql_util").db;
let moment = require("moment");
let {async_redis_client, redlock} = require("../utils/redis_util"); let model = {}; /**
* 测试并发
* @param reqParams
* @returns {Promise<{msg: string, success: boolean}>}
*/
model.testConcurrentOrder = async function (reqParams) { // 之前本人有个误区,设置资源锁的key为 `goods_${inventory_remain}`,但是仍然有写入错误的数据。
// 思考了一下,觉得是很多请求同时进来,从加锁->释放锁相当于进入队列,前一个操作完成后释放了锁,后一个才要开始加锁。
// 假如有100个请求同时进来,其中50个请求在加锁之前查询到的库存都是100,提交事务的出库信息都是剩余库存99,那么有49条数据都会形成脏数据。
// 所以这里必须设置资源锁的key一致,只要有一个资源还没有释放锁,其他资源就不允许进行查询库存的操作,问题解决。 // 上锁的资源
let resource_lock_key = `redislock_goods`;
// 上锁资源5s,操作完成或者到时间后自动释放
let resource_ttl = 5 * 1000; // 开始加锁
let lock = await redlock.lock(resource_lock_key, resource_ttl);
console.log(resource_lock_key, lock.value); console.log(moment().format('YYYY-MM-DD HH:mm:ss:SSS'));
let initDB = !!reqParams.initDB || 0;// 是否初始化db let goods_id = reqParams.goods_id || "goods_01";// 商品id
let opt_num = reqParams.opt_num || 1;// 出库数量 let result = {}; if (initDB) {
// 清除数据
await db.query(`TRUNCATE db_stock`);
await db.query(`TRUNCATE db_checkout`);
// 插入库存数据
await db.query(`INSERT INTO db_stock(goods_id, inventory_total, inventory_remain) VALUES ('${goods_id}', 100, 100)`); result = {
success: true,
msg: '初始化DB成功',
};
} else { let querySql = `SELECT
t.inventory_total,
t.inventory_remain
FROM
db_stock t
WHERE
1 = 1
AND t.goods_id = ?`; let queryResult = await db.queryOne(querySql, [goods_id]);
if (!queryResult) {
result = {
success: false,
msg: `无法匹配商品id:${goods_id}`
}
} else { // 当前库存
let inventory_remain = queryResult.inventory_remain;
// 等待设置的新库存
let inventory_remain_new = inventory_remain - 1; let tran = await db.beginTransaction(); try {
// 新增出库记录
let checkoutSql = `INSERT INTO db_checkout(goods_id, opt_num, inventory_remain, create_time) VALUES (?, ?, ?, ?)`;
await tran.query(checkoutSql, [goods_id, opt_num, inventory_remain_new, new Date()]); // 更新库存信息
let updateStockSql = `UPDATE db_stock t SET inventory_remain = ? WHERE t.goods_id = ?`;
// 更新库存
await tran.query(updateStockSql, [inventory_remain_new, goods_id]); await tran.commit(); result = {
success: true,
msg: '出库成功',
}
} catch (e) {
await tran.rollback(); // 抛出异常之前,及时释放资源锁
await lock.unlock(); throw e;
}
}
} if (lock) {
// 及时释放资源锁
await lock.unlock();
}
return result;
}; module.exports = model;
附上redis_util.js的代码
// Redis单节点
let date_util = require("../utils/date_util");
let RedLock = require("redlock");
// redis
let redis = require("redis");
// async_redis
let async_redis = require("async-redis"); // redis_option
let redis_option = {
//redis服务器的ip地址
host: '127.0.0.1',
//redis服务器的端口
port: '6379',
// 授权密码,修改redis.windows.conf requirepass 1234,密码错误报错: NOAUTH Authentication required
password: '1234',
// redis select $index,默认0
db: '0',
// reply number => string 默认null
string_numbers: null,
// replay strings => buffer,默认false
return_buffers: false,
// 是否检测缓冲区,默认false
detect_buffers: false,
// 长链接,默认true
socket_keepalive: true,
// 长链接延迟时间,默认0
socket_initialdelay: 0,
// 禁用ready检查,默认false
no_ready_check: false,
// 启用离线命令队列,默认true
enable_offline_queue: true,
// // @Deprecated 重连最大延迟,默认null
// retry_max_delay: null,
// // @Deprecated 连接超时时间,默认60*60*1000,默认1h
// connect_timeout: 60 * 60 * 1000,
// // @Deprecated 最大连接次数,默认0,设置为1将阻止任何重新连接尝试
// max_attempts: 0,
// 默认false, 如果设置为true,则在重新建立连接后,将重试连接丢失时未执行的所有命令。如果使用状态更改命令(例如incr),请小心使用此命令。这在使用阻塞命令时特别有用。
retry_unfulfilled_commands: false,
retry_strategy: function (options) {
if (options.error && options.error.code === 'ECONNREFUSED') {
// 拒绝连接
let errMsg = 'The server refused the connection';
console.log(date_util.format(), 'retry_strategy', errMsg);
return new Error(errMsg);
}
// 默认 60*60*1000
if (options.total_retry_time > 60 * 60 * 1000) {
// 超过最大重试时间
let errMsg = 'Retry time exhausted';
console.log(date_util.format(), 'retry_strategy', errMsg);
return new Error(errMsg);
}
// 默认10
if (options.attempt > 10) {
// 超过最大重试次数
let errMsg = 'maximum connection attempts exceeded';
console.log(date_util.format(), 'retry_strategy', errMsg);
return new Error(errMsg);
}
// reconnect after
return Math.min(options.attempt * 1000, 3000);
}
}; // redis_client
let redis_client = redis.createClient(redis_option); // async_redis_client
let async_redis_client = async_redis.createClient(redis_option); redis_client.on("error", function (err) {
console.log(date_util.format(date_util.pattern().ymdhmsSSS), err.toString());
});
async_redis_client.on("error", function (err) {
console.log(date_util.format(date_util.pattern().ymdhmsSSS), err.toString());
}); // NOTE: 这里只能使用redis_client
let redlock = new RedLock([redis_client]);
redlock.on('clientError', function (err) {
console.error(date_util.format(date_util.pattern().ymdhmsSSS), 'A redis error has occurred:', err);
}); module.exports = {
redis_client,
async_redis_client,
redlock
};
ab测试
C:\WINDOWS\system32>ab.exe -n -c http://localhost:3000/dm/testConcurrentOrder
This is ApacheBench, Version 2.3 <$Revision: $>
Copyright Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient).....done Server Software:
Server Hostname: localhost
Server Port: Document Path: /dm/testConcurrentOrder
Document Length: bytes Concurrency Level:
Time taken for tests: 1.867 seconds
Complete requests:
Failed requests:
Total transferred: bytes
HTML transferred: bytes
Requests per second: 50.88 [#/sec] (mean)
Time per request: 1867.041 [ms] (mean)
Time per request: 19.653 [ms] (mean, across all concurrent requests)
Transfer rate: 9.34 [Kbytes/sec] received Connection Times (ms)
min mean[+/-sd] median max
Connect: 0.2
Processing: 437.8
Waiting: 437.9
Total: 437.8 Percentage of the requests served within a certain time (ms)
%
%
%
%
%
%
%
%
% (longest request) C:\WINDOWS\system32>
Mysql查询结果:
mysql> select * from db_stock;
+----+----------+-----------------+------------------+
| id | goods_id | inventory_total | inventory_remain |
+----+----------+-----------------+------------------+
| 1 | goods_01 | 100 | 5 |
+----+----------+-----------------+------------------+
1 row in set (0.00 sec) mysql> select * from db_checkout;
+----+----------+---------+------------------+---------------------+
| id | goods_id | opt_num | inventory_remain | create_time |
+----+----------+---------+------------------+---------------------+
| 1 | goods_01 | 1 | 99 | 2019-10-10 18:36:15 |
| 2 | goods_01 | 1 | 98 | 2019-10-10 18:36:15 |
| 3 | goods_01 | 1 | 97 | 2019-10-10 18:36:15 |
| 4 | goods_01 | 1 | 96 | 2019-10-10 18:36:15 |
| 5 | goods_01 | 1 | 95 | 2019-10-10 18:36:15 |
| 6 | goods_01 | 1 | 94 | 2019-10-10 18:36:15 |
| 7 | goods_01 | 1 | 93 | 2019-10-10 18:36:15 |
| 8 | goods_01 | 1 | 92 | 2019-10-10 18:36:15 |
| 9 | goods_01 | 1 | 91 | 2019-10-10 18:36:15 |
| 10 | goods_01 | 1 | 90 | 2019-10-10 18:36:15 |
| 11 | goods_01 | 1 | 89 | 2019-10-10 18:36:15 |
| 12 | goods_01 | 1 | 88 | 2019-10-10 18:36:15 |
| 13 | goods_01 | 1 | 87 | 2019-10-10 18:36:15 |
| 14 | goods_01 | 1 | 86 | 2019-10-10 18:36:15 |
| 15 | goods_01 | 1 | 85 | 2019-10-10 18:36:15 |
| 16 | goods_01 | 1 | 84 | 2019-10-10 18:36:15 |
| 17 | goods_01 | 1 | 83 | 2019-10-10 18:36:15 |
| 18 | goods_01 | 1 | 82 | 2019-10-10 18:36:15 |
| 19 | goods_01 | 1 | 81 | 2019-10-10 18:36:15 |
| 20 | goods_01 | 1 | 80 | 2019-10-10 18:36:15 |
| 21 | goods_01 | 1 | 79 | 2019-10-10 18:36:15 |
| 22 | goods_01 | 1 | 78 | 2019-10-10 18:36:15 |
| 23 | goods_01 | 1 | 77 | 2019-10-10 18:36:15 |
| 24 | goods_01 | 1 | 76 | 2019-10-10 18:36:15 |
| 25 | goods_01 | 1 | 75 | 2019-10-10 18:36:15 |
| 26 | goods_01 | 1 | 74 | 2019-10-10 18:36:15 |
| 27 | goods_01 | 1 | 73 | 2019-10-10 18:36:15 |
| 28 | goods_01 | 1 | 72 | 2019-10-10 18:36:15 |
| 29 | goods_01 | 1 | 71 | 2019-10-10 18:36:15 |
| 30 | goods_01 | 1 | 70 | 2019-10-10 18:36:15 |
| 31 | goods_01 | 1 | 69 | 2019-10-10 18:36:15 |
| 32 | goods_01 | 1 | 68 | 2019-10-10 18:36:15 |
| 33 | goods_01 | 1 | 67 | 2019-10-10 18:36:15 |
| 34 | goods_01 | 1 | 66 | 2019-10-10 18:36:15 |
| 35 | goods_01 | 1 | 65 | 2019-10-10 18:36:15 |
| 36 | goods_01 | 1 | 64 | 2019-10-10 18:36:15 |
| 37 | goods_01 | 1 | 63 | 2019-10-10 18:36:15 |
| 38 | goods_01 | 1 | 62 | 2019-10-10 18:36:15 |
| 39 | goods_01 | 1 | 61 | 2019-10-10 18:36:15 |
| 40 | goods_01 | 1 | 60 | 2019-10-10 18:36:15 |
| 41 | goods_01 | 1 | 59 | 2019-10-10 18:36:15 |
| 42 | goods_01 | 1 | 58 | 2019-10-10 18:36:15 |
| 43 | goods_01 | 1 | 57 | 2019-10-10 18:36:15 |
| 44 | goods_01 | 1 | 56 | 2019-10-10 18:36:15 |
| 45 | goods_01 | 1 | 55 | 2019-10-10 18:36:15 |
| 46 | goods_01 | 1 | 54 | 2019-10-10 18:36:16 |
| 47 | goods_01 | 1 | 53 | 2019-10-10 18:36:16 |
| 48 | goods_01 | 1 | 52 | 2019-10-10 18:36:16 |
| 49 | goods_01 | 1 | 51 | 2019-10-10 18:36:16 |
| 50 | goods_01 | 1 | 50 | 2019-10-10 18:36:16 |
| 51 | goods_01 | 1 | 49 | 2019-10-10 18:36:16 |
| 52 | goods_01 | 1 | 48 | 2019-10-10 18:36:16 |
| 53 | goods_01 | 1 | 47 | 2019-10-10 18:36:16 |
| 54 | goods_01 | 1 | 46 | 2019-10-10 18:36:16 |
| 55 | goods_01 | 1 | 45 | 2019-10-10 18:36:16 |
| 56 | goods_01 | 1 | 44 | 2019-10-10 18:36:16 |
| 57 | goods_01 | 1 | 43 | 2019-10-10 18:36:16 |
| 58 | goods_01 | 1 | 42 | 2019-10-10 18:36:16 |
| 59 | goods_01 | 1 | 41 | 2019-10-10 18:36:16 |
| 60 | goods_01 | 1 | 40 | 2019-10-10 18:36:16 |
| 61 | goods_01 | 1 | 39 | 2019-10-10 18:36:16 |
| 62 | goods_01 | 1 | 38 | 2019-10-10 18:36:16 |
| 63 | goods_01 | 1 | 37 | 2019-10-10 18:36:16 |
| 64 | goods_01 | 1 | 36 | 2019-10-10 18:36:16 |
| 65 | goods_01 | 1 | 35 | 2019-10-10 18:36:16 |
| 66 | goods_01 | 1 | 34 | 2019-10-10 18:36:16 |
| 67 | goods_01 | 1 | 33 | 2019-10-10 18:36:16 |
| 68 | goods_01 | 1 | 32 | 2019-10-10 18:36:16 |
| 69 | goods_01 | 1 | 31 | 2019-10-10 18:36:16 |
| 70 | goods_01 | 1 | 30 | 2019-10-10 18:36:16 |
| 71 | goods_01 | 1 | 29 | 2019-10-10 18:36:16 |
| 72 | goods_01 | 1 | 28 | 2019-10-10 18:36:16 |
| 73 | goods_01 | 1 | 27 | 2019-10-10 18:36:16 |
| 74 | goods_01 | 1 | 26 | 2019-10-10 18:36:16 |
| 75 | goods_01 | 1 | 25 | 2019-10-10 18:36:16 |
| 76 | goods_01 | 1 | 24 | 2019-10-10 18:36:16 |
| 77 | goods_01 | 1 | 23 | 2019-10-10 18:36:16 |
| 78 | goods_01 | 1 | 22 | 2019-10-10 18:36:16 |
| 79 | goods_01 | 1 | 21 | 2019-10-10 18:36:16 |
| 80 | goods_01 | 1 | 20 | 2019-10-10 18:36:16 |
| 81 | goods_01 | 1 | 19 | 2019-10-10 18:36:16 |
| 82 | goods_01 | 1 | 18 | 2019-10-10 18:36:16 |
| 83 | goods_01 | 1 | 17 | 2019-10-10 18:36:16 |
| 84 | goods_01 | 1 | 16 | 2019-10-10 18:36:16 |
| 85 | goods_01 | 1 | 15 | 2019-10-10 18:36:16 |
| 86 | goods_01 | 1 | 14 | 2019-10-10 18:36:16 |
| 87 | goods_01 | 1 | 13 | 2019-10-10 18:36:16 |
| 88 | goods_01 | 1 | 12 | 2019-10-10 18:36:16 |
| 89 | goods_01 | 1 | 11 | 2019-10-10 18:36:16 |
| 90 | goods_01 | 1 | 10 | 2019-10-10 18:36:16 |
| 91 | goods_01 | 1 | 9 | 2019-10-10 18:36:16 |
| 92 | goods_01 | 1 | 8 | 2019-10-10 18:36:16 |
| 93 | goods_01 | 1 | 7 | 2019-10-10 18:36:16 |
| 94 | goods_01 | 1 | 6 | 2019-10-10 18:36:16 |
| 95 | goods_01 | 1 | 5 | 2019-10-10 18:36:16 |
+----+----------+---------+------------------+---------------------+
95 rows in set (0.00 sec) mysql>
完美!
Nodejs模拟并发,尝试的两种解决方案的更多相关文章
- Android 的保活的两种解决方案
原文链接:http://blog.csdn.net/pan861190079/article/details/72773549 详细的阐述了 Android 的保活的两种解决方案 —— 由panhao ...
- PHP中实现MySQL嵌套事务的两种解决方案
PHP中实现MySQL嵌套事务的两种解决方案 一.问题起源 在MySQL的官方文档中有明确的说明不支持嵌套事务: Transactions cannot be nested. This is a co ...
- javascript文件夹选择框的两种解决方案
javascript文件夹选择框的两种解决方案 解决方案1:调用windows 的shell,但会有安全问题. * browseFolder.js * 该文件定义了BrowseFolder()函数,它 ...
- USB设备(移动硬盘、鼠标)掉电掉驱动的两种解决方案
症状: 当你发现"移动硬盘图标"经常无故消失,又自己出现时. 你可以把这个现象称之为"掉电" or "掉驱动". 遇到这种情况,相当不爽. ...
- [ACM_模拟] POJ1068 Parencodings (两种括号编码转化 规律 模拟)
Description Let S = s1 s2...s2n be a well-formed string of parentheses. S can be encoded in two diff ...
- 转:前端页面a标签嵌套a标签效果的两种解决方案
这是由工作中的一个小改动需求得到的这个解决方案的:那个需求是这样的,如图: 需求原来是球队名字没有点击功能的,而蓝色方框两队之间的比赛点击的时候会跳转到比赛文字直播页面.现在需要要求点击球队名字要 ...
- Ajax保留浏览器历史的两种解决方案(Hash&Pjax)
总是在github down点东西,github整个界面做的不错,体验也很好~对于其中的源代码滑动的特效最为喜欢了~刚开始以为这个只是普通的ajax请求效果,但是发现这个特效能够导致浏览器地址栏跟随变 ...
- Go -- 并发编程的两种限速方法
引子 golang提供了goroutine快速实现并发编程,在实际环境中,如果goroutine中的代码要消耗大量资源时(CPU.内存.带宽等),我们就需要对程序限速,以防止goroutine将资源耗 ...
- 前端页面a标签嵌套a标签效果的两种解决方案
这是由工作中的一个小改动需求得到的这个解决方案的:那个需求是这样的,如图: 需求原来是球队名字没有点击功能的,而蓝色方框两队之间的比赛点击的时候会跳转到比赛文字直播页面.现在需要要求点击球队名字要跳转 ...
随机推荐
- Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第四章:Direct 3D初始化
原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第四章:Direct 3D初始化 学习目标 对Direct 3D编程在 ...
- Python操作pymysql写入数据库时的错误
错误一 InternalError: (pymysql.err.InternalError) (1366, "Incorrect string value: '\\xE6\\xAD\\xA3 ...
- SDUT-2772_数据结构实验之串一:KMP简单应用
数据结构实验之串一:KMP简单应用 Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 给定两个字符串string1和str ...
- Linux服务部署:nginx服务 nfs服务
nginx服务 源码安装: yum install gcc-* glibc-* openssl openssl-devel pcre pcre-devel zlib zlib-devel -ylsta ...
- 9-1进程,进程池和socketserver
一 进程: # 什么是进程 : 运行中的程序,计算机中最小的资源分配单位# 程序开始执行就会产生一个主进程# python中主进程里面启动一个进程 —— 子进程# 同时主进程也被称为父进程# 父子进程 ...
- list extend 和 append
append 一次追加一个列表 extend 一次追加所有的元素 单个的形式加入
- python元组和range
1.元组 1)元组介绍 元组: 俗称不可变的列表.⼜被成为只读列表, 元组也是python的基本数据类型之⼀, ⽤⼩括号括起来, ⾥⾯可以放任何数据类型的数据, 查询可以. 循环也可以. 切片也可以. ...
- redis 清除缓存
- 2018-6-24-WPF-使用RPC调用其他进程
title author date CreateTime categories WPF 使用RPC调用其他进程 lindexi 2018-06-24 14:41:29 +0800 2018-2-13 ...
- 装机必备 Windows 操作系统ISO镜像资源
小编今天使用VMware虚拟机软件搭建Win7系统时,开始一直不成功总是出现:Start booting from CD...Directory "EZBOOT" not foun ...