存储服务
一、基本概念
数据库与缓存是服务端开发人员的必学知识点.
1. 什么是数据库?
"数据库"是一种信息记录、存取的虚拟标记地点的集合统称. 比如现实生活中, 我们经常会用到文件柜、书桌等等数据存取容器.
在对容器进行数据存取的时候, 我们会为每一层打上一个标签表示一种分类项. 而这种在数据库中划分子分类形成了表的概念. 这就是我们通常所说的结构化数据库.
由于通常数据表之间可能会存在依赖关系, 某一(或者多)层通常可能会用于同一种用途. 这种用途将一层划分为索引表, 二层划分为分类表, 三层划分为数据表.
实现这种功能与依赖关系的数据库, 我们称之为: 关系型数据库. 它可以定义一套规范并且建立数据存取模型, 这样方便维护一整套结构化的数据信息.
每当我们需要对数据进行结构化操作(查询、增加、删除、修改)的时候, 需要在计算机中用一种通俗易懂的语言表达方式来进行助记. 这种结构化查询语言称之为SQL
.
2. 什么是缓存?
我们通常将数据存储完毕后, 能通过指定或特定的一(多)种方式对数据进行操作. 在项目开发的初期, 这并没有太大的问题.
但是随着数据量的不断增大, 在数据库的内存中已经放不下这么多数据. 我们的数据逐渐无法被加载到内存中: 只会在使用的时候才会进行(随机)读取. 而这会加大磁盘I/O
.
我们知道通常磁盘的读写速度基本上会比内存读写慢几个数量级(即使是SSD), 大量请求可能瞬间将磁盘IO
占满并出现数据库的CPU利用率
低、内存频繁进行修改/置换等问题.
为了解决这些问题, 出现了很多解决方案: 读、写分离、分表分库等等. 虽然有了这些方案, 但是也同样回引来新的问题: 主从同步
、分布式事务
等问题.
"缓存"则是近十年兴起的概念, 它的本质是一份数据结构化存储在内存中的副本. 高级的缓存我们也可以将其称之为内存数据库或NOSQL
(非关系型)数据库.
"缓存"也是一种"另类"解决数据库问题点一种手段! 它通过丰富的数据结构扩展了数据模型的组合能力, 通过简单的使用方法与高效的连接方式提供更好数据操作方式.
"缓存"将查询、更新较为频繁的热数据组成一个集合加载进内存中, 较少使用的冷数据序列化到磁盘内部. 高效利用内存的同时, 根据变化的情况合理更新、删除缓存.
这样的方式配合数据库的读、写分离与数据分区将数据合理的从一个数据集副本分散到多个数据集副本, 有效的减少性能问题点产生并且提升了整个业务系统的横向扩展能.
二、 DB库
在使用下面的方法之前, 请先确保已经导入库: local DB = require "DB"
.
1. DB:new
函数原型: DB:new(opts)
opts
是一个table
类型的连接配置表, 内部的一些参数决定如何连接到MySQL
:
host
- MySQL主机名或IP地址(string类型).port
- MySQL端口号(int类型).charset
- MySQL字符集设置.database
- MySQL指定数据库的名称.username
- MySQL用户账户(string类型).password
- MySQL用户密码(string类型).max
- MySQL的最大连接池大小(int类型).这个方法返回一个新创建db对象. (创建的对象并不代表已经连接成功, 您需要主动进行连接.)
2. DB:connect
函数原型: DB:connect()
开始连接MySQL
. 连接成功返回true
, 否则将会持续进行连接并且输出连接失败原因的日志.
需要注意的是: 这个方法只需要在对象创建完成之后调用一次即可.
3. DB:query
函数原型: DB:query(SQL)
数据库查询语句调用方法, SQL为string类型的的一个标准SQL语句.
返回值为ret
与err
. 查询成功ret为一个结果集数组, 在发生错误时为nil
, err
为错误信息.
4. 简单的使用示例
这份示例带来你可以从这里中找到.
local LOG = require "logging"
-- local cf = require "cf"
local DB = require "DB"
local db = DB:new {
host = 'localhost',
port = 3306,
database = 'test',
username = 'root',
password = '123456789',
max = 1,
}
local ok = db:connect()
if not ok then
return LOG:DEBUG("连接mysql失败")
end
LOG:DEBUG("连接成功")
--[[
/* 这里有一份测试表创建语句, 你可以使用这份代码创建一个表用来测试 */
SET NAMES utf8;
SET FOREIGN_KEY_CHECKS = 0;
CREATE DATABASE IF NOT EXISTS `test` DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
CREATE TABLE IF NOT EXISTS `test`.`user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`user` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`passwd` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SET FOREIGN_KEY_CHECKS = 1;
--]]
-- 测试普通数据库语句
-- cf.fork(function ( ... )
local ret, err = db:query("show variables like 'wait_timeout'")
LOG:DEBUG(ret, err)
-- end)
-- 测试预编译语句
-- cf.fork(function ( ... )
local rkey = db:prepare([[SELECT version() AS version]])
local ret, err = db:execute(rkey)
LOG:DEBUG(ret, err)
-- end)
三、Cache库
在使用下面的API之前, 请先确保已经导入库: local Cache = require "Cache"
.
1. Cache:new
函数原型: Cache:new(opts)
opts
是一个table
类型的连接配置表, 内部的一些参数决定如何连接到Redis
:
host
- Redis主机名或IP地址(string类型).port
- Redis主机端口号(int类型).auth
- Redis主机设置的密码, 默认为:nil.db
- Redis的数据库设置.max
= 最大连接池大小(int类型).此方法返回一个新创建的
Cache
对象. (创建的对象并不代表已经连接成功, 您需要主动进行连接.)
2. Cache:connect
函数原型: Cache:connect()
开始连接Redis
. 连接成功返回true
, 否则将会持续进行连接并且输出连接失败原因的日志.
需要注意的是: 这个方法只需要在对象创建完成之后调用一次即可.
3. 简单的使用示例
-- 测试Cache库
local Cache = require "Cache"
local cf = require "cf"
local Log = require("logging"):new()
local opt = {
host = "localhost",
port = 6379,
auth = nil,
db = 1,
max = 1,
}
cf.fork(function ( ... )
local Cache = Cache:new(opt)
local ok, err = Cache:connect()
if not ok then
return print(err)
end
-- 测试 GET/SET/DEL 命令示例
local ok, ret = Cache:set("test", 1)
Log:DEBUG(ok, ret)
local ok, ret = Cache:get("test")
Log:DEBUG(ok, ret)
local ok, ret = Cache:del("test")
Log:DEBUG(ok, ret)
-- 测试哈希表命令示例
local ok, ret = Cache:hmset("website", "google", "www.google.com", "baidu", "www.baidu.com")
Log:DEBUG(ok, ret)
local ok, ret = Cache:hlen("website")
Log:DEBUG(ok, ret)
local ok, ret = Cache:hkeys("website")
Log:DEBUG(ok, ret)
local ok, ret = Cache:hgetall("website")
Log:DEBUG(ok, ret)
local ok, ret = Cache:hmget("website", "google", "baidu")
Log:DEBUG(ok, ret)
local ok, ret = Cache:hdel("website", "google", "baidu")
Log:DEBUG(ok, ret)
-- 测试列表命令示例
local ok, ret = Cache:lpush("language", "lua", "python", "C", "C++")
Log:DEBUG(ok, ret)
local ok, ret = Cache:rpush("language", "golang", "java", "ruby", "javascript")
Log:DEBUG(ok, ret)
local ok, ret = Cache:lrange("language", 0, -1)
Log:DEBUG(ok, ret)
local ok, ret = Cache:ltrim("language" , -1, 0)
Log:DEBUG(ok, ret)
local ok, ret = Cache:llen("language")
Log:DEBUG(ok, ret)
-- 测试有序集合命令示例
local ok, ret = Cache:smembers("bbs")
Log:DEBUG(ok, ret)
local ok, ret = Cache:sadd("bbs", "discuz.cn", "group.google.com", "oschina.net", "csdn.net")
Log:DEBUG(ok, ret)
local ok, ret = Cache:sismember("bbs", "oschina.net")
Log:DEBUG(ok, ret)
local ok, ret = Cache:srem("bbs", "discuz.cn", "group.google.com", "oschina.net", "csdn.net")
Log:DEBUG(ok, ret)
local ok, ret = Cache:sismember("bbs", "oschina.net")
Log:DEBUG(ok, ret)
local ok, ret = Cache:sadd("book1", "宝宝的C++", "宝宝的HTML", "宝宝的CSS", "C程序设计")
local ok, ret = Cache:sadd("book2", "宝宝的C++", "宝宝的HTML", "宝宝的CSS", "C++从入门到放弃")
local ok, ret = Cache:sadd("book3", "宝宝的C++", "宝宝的HTML", "宝宝的CSS", "MySQL从入门到删库跑路")
local ok, ret = Cache:sdiff("book1", "book2", "book3")
Log:DEBUG(ok, ret)
local ok, ret = Cache:del("book1", "book2", "book3")
Log:DEBUG(ok, ret)
-- 测试有序集合命令示例
local ok, ret = Cache:zadd("scores", 10, "admin", 20, "Candy", 30, "QQ", 40, "Guest")
Log:DEBUG(ok, ret)
local ok, ret = Cache:zrange("scores", 0, -1)
-- local ok, ret = Cache:zrange("scores", 0, -1, "WITHSCORES")
Log:DEBUG(ok, ret)
local ok, ret = Cache:zcount("scores", 10, 100)
Log:DEBUG(ok, ret)
local ok, ret = Cache:zscore("scores", "QQ")
Log:DEBUG(ok, ret)
local ok, ret = Cache:zrank("scores", "Candy")
Log:DEBUG(ok, ret)
local ok, ret = Cache:zrem("scores", "admin", "Candy", "QQ", "Guest")
Log:DEBUG(ok, ret)
local ok, ret = Cache:del("scores")
Log:DEBUG(ok, ret)
-- 脚本支持
local ok, ret = Cache:script_load("return 10086")
Log:DEBUG(ok, ret)
local sha = ret
local ok, ret = Cache:script_exists(sha)
Log:DEBUG(ok, ret)
local ok, ret = Cache:evalsha(sha, 0)
Log:DEBUG(ok, ret)
local ok, ret = Cache:script_flush()
Log:DEBUG(ok, ret)
-- 其它一些特殊方法支持
-- type, move, rename, keys, randomkey等等
Log:DEBUG(Cache:count())
-- 管道命令支持
local ok, ret = Cache:pipeline {
{"HMSET", "USER_INFO", "name", "Candy", "email", '869646063@qq.com', 'phone', '13000000000'},
{"HGET", "USER_INFO", "email"},
{"HGET", "USER_INFO", "phone"},
}
Log:DEBUG(ok, ret)
end)
四、一些建议
下面的话也许你并不能理解! 但是回过头来凝望后发现, 原来正确的道路就在起点...
1. 数据库
数据库索引越多并不是越好, 正确的索引才能对性能优化起到一定的帮助;
数据库的硬件配置达到一定程度后, 硬件配置提升已经变得微乎其微;
数据库的连表查询最好事先建立合理的字段, 否则后期优化会非常难受;
数据库在大多数场景并没有性能问题, 因为最大的问题是你不了解如何正确使用它;
2. 缓存
缓存并不是
万能灵药
, 它在更加会增加的心智负担
;设计合理的
缓存机制
就要设计合理的更新机制
;内部的
数据结构
不可滥用, 更有多方面的因素
要慎重考虑;
五、继续学习
本章节我们知道了DB
库与Cache
库的使用方法, 下一章节我们将继续学习如何利用httpc
库请求第三方接口.