предисловие
Демонстрация проекта: Сервер деактивирован...
Гитхаб:server | внешний интерфейс
Почему нарисованная доска:v2ex
Будучи фронтендом, вы всегда намеренно или ненамеренно будете вступать в контакт с NodeJS, намеренно или ненамеренно читать документацию и намеренно или ненамеренно замечать фреймворк, но когда нам действительно нужно эффективно использовать его в нашей работе, большинство время у нас еще есть, чтобы вздохнуть «на бумаге Приходи и чувствуй себя мелким». Итак, неделю назад я решил сделать практическую попытку, надеясь интегрировать знания, которые я случайно получил в прошлом, и, наконец, решил переписать предыдущую демо-версию блокнота и добавить серверную часть.
стек технологий
- [vue + vuex + vue-router] рендеринг страницы + обмен данными + скачок маршрутизации
- [axios] Использование HTTP-запросов в качестве обещаний
- [стилус] Предварительная обработка CSS
- [element-ui] библиотека пользовательского интерфейса
- Пакет [Webpack] над этими вещами
- [Socket.io] Пуш в реальном времени
- [pm2] Развертывание службы узла
- [nginx] Развернуть службу доступа к статическим ресурсам (HTTPS), прокси-запросы
- [letSencrypt] Создать бесплатный сертификат HTTPS
Webpack также указан, потому что этот проект является проектомluwuer.com
Модуль , который требует веб-пакета для независимой упаковки.
node-canvas
Установить
node-canvas - самая сложная зависимость, с которой я когда-либо сталкивался для установки, поэтому я вообще не хочу ее устанавливать под Windows.Его функции зависят от многих пакетов, которых нет по умолчанию под системой, и вы также можете увидеть много тегов проблем на Github — это помощь в установке. В качестве примера возьмем чистую версию CentOS 7. Перед ее установкой необходимо установить следующие зависимости.Стоит отметить, что команды, представленные в документации npm, не имеют cairo 。
# centos 前置条件
sudo yum install gcc-c++ cairo cairo-devel pango-devel libjpeg-turbo-devel giflib-devel
# 安装本体
yarn add canvas -D
Также есть неизвестная яма.Если предварительные условия готовы, тело установки все еще зависает на этапе извлечения пакета (ошибки не сообщается), в это время вам нужно обновить npm отдельно.
Пример использования
Основы использования легко понять, обратившись к документации.В следующем примере сначала получаются пиксельные данные для генерации ImageData, а затем исторические данные рисуются на холсте с помощью putImageData.
const {
createCanvas,
createImageData
} = require('canvas')
const canvas = createCanvas(canvasWidth, canvasHeight)
const ctx = canvas.getContext('2d')
// 初始化
const init = callback => {
Dot.queryDots().then(data => {
let imgData = new createImageData(
Uint8ClampedArray.from(data),
canvasWidth,
canvasHeight
)
// 移除 Smooth
ctx.mozImageSmoothingEnabled = false
ctx.webkitImageSmoothingEnabled = false
ctx.msImageSmoothingEnabled = false
ctx.imageSmoothingEnabled = false
ctx.putImageData(imgData, 0, 0, 0, 0, canvasWidth, canvasHeight)
successLog('canvas render complete !')
callback()
})
}
Socket.io
В дизайне этого проекта есть два места, где необходимо использовать push: одно — это информация о других пользователях, а другое — сообщения чата, отправляемые всеми пользователями.
client
// socket.io init
// transports: [ 'websocket' ]
window.socket = io.connect(window.location.origin.replace(/https/, 'wss'))
// 接收图片
window.socket.on('dataUrl', data => {
this.imageObject.src = data.url
this.loadInfo.push('渲染图像...')
this.init()
})
// 接收其他用户建点
window.socket.on('newDot', data => {
this.saveDot(
{
x: data.index % this.width,
y: Math.floor(data.index / this.width),
color: data.color
},
false
)
})
// 接收所有人的最新推送消息
window.socket.on('newChat', data => {
if (this.msgs.length === 50) {
this.msgs.shift()
}
this.msgs.push(data)
})
server /bin/www
let http = require('http');
let io = require('socket.io')
let server = http.createServer(app.callback())
let ws = io.listen(server)
server.listen(port)
ws.on('connection', socket => {
// 建立连接的 client 加入房间 chatroom ,为了下方可以广播
socket.join('chatroom')
socket.emit('dataUrl', {
url: cv.getDataUrl()
})
socket.on('saveDot', async data => {
// 推送给其他用户,即广播
socket.broadcast.to('chatroom').emit('newDot', data)
saveDotHandle(data)
})
socket.on('newChat', async data => {
// 推送给所有用户
ws.sockets.emit('newChat', data)
newChatHandle(data)
})
})
letsencrypt
Подать заявку на сертификат
# 获得程序
git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt
# 自动生成证书(环境安装完毕后会有两次确认),证书目录 /etc/letsencrypt/live/{输入的第一个域名} 我这里是 /etc/letsencrypt/live/www.luwuer.com/
./letsencrypt-auto certonly --standalone --email html6@foxmail.com -d www.luwuer.com -d luwuer.com
авто-обновление
# 进入定时任务编辑
crontab -e
# 提交申请,我这里设置每两月一次,过期时间为三月
* * * */2 * cd /root/certificate/letsencrypt && ./letsencrypt-auto certonly --renew
nginx
yum install -y nginx
/etc/nginx/config.d/https.conf
server {
# 使用 HTTP/2,需要 Nginx1.9.7 以上版本
listen 443 ssl http2 default_server;
# 开启HSTS,并设置有效期为“6307200秒”(6个月),包括子域名(根据情况可删掉),预加载到浏览器缓存(根据情况可删掉)
add_header Strict-Transport-Security "max-age=6307200; preload";
# add_header Strict-Transport-Security "max-age=6307200; includeSubdomains; preload";
# 禁止被嵌入框架
add_header X-Frame-Options DENY;
# 防止在IE9、Chrome和Safari中的MIME类型混淆攻击
add_header X-Content-Type-Options nosniff;
# ssl 证书
ssl_certificate /etc/letsencrypt/live/www.luwuer.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.luwuer.com/privkey.pem;
# OCSP Stapling 证书
ssl_trusted_certificate /etc/letsencrypt/live/www.luwuer.com/chain.pem;
# OCSP Stapling 开启,OCSP是用于在线查询证书吊销情况的服务,使用OCSP Stapling能将证书有效状态的信息缓存到服务器,提高TLS握手速度
ssl_stapling_verify on;
#OCSP Stapling 验证开启
ssl_stapling on;
#用于查询OCSP服务器的DNS
resolver 8.8.8.8 8.8.4.4 valid=300s;
# DH-Key交换密钥文件位置
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# 指定协议 TLS
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# 加密套件,这里用了CloudFlare's Internet facing SSL cipher configuration
ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
# 由服务器协商最佳的加密算法
ssl_prefer_server_ciphers on;
server_name ~^(\w+\.)?(luwuer\.com)$; # $1 = 'blog.' || 'img.' || '' || 'www.' ; $2 = 'luwuer.com'
set $pre $1;
if ($pre = 'www.') {
set $pre '';
}
set $next $2;
root /root/apps/$pre$next;
location / {
try_files $uri $uri/ /index.html;
index index.html;
}
location ^~ /api/ {
proxy_pass http://43.226.147.135:3000/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# socket代理配置
location /socket.io/ {
proxy_pass http://43.226.147.135:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# location /weibo/ {
# proxy_pass https://api.weibo.com/;
# }
include /etc/nginx/utils/cache.conf;
}
server {
listen 80;
server_name www.luwuer.com;
rewrite ^(.*)$ https://$server_name$request_uri;
}
приложение
Процесс мышления структуры хранения базы данных
Во-первых, спрос заключается в том, что Sketchpad может рисовать в натуральную величину.{ width: 1024px, height: 512px }
-
- Хранение цветов в шестнадцатеричном формате
- Все данные холста хранятся как один фрагмент данных.
Хотя структура 2 выглядит немного глупо, я сначала думал о такой структуре, и мне было непонятно, что самая трудоемкая часть выборки данных — это не запрос, а ввод-вывод.
Затем я проверил, был 1.1 и 1.2 из этих двух структур, то прямое отрицание структуры 2, потому что в тестировании я обнаружил, что IO потребляет более 98% от общего объема трудоемкого времени, и не может получить абсолютную структуру 2, несомненно, потому что выгодных преимуществ единых данных.
- 1.1
- Размер хранения 10 м
- Получить все данные 8000+ мс
- Полный запрос таблицы 150 мс (findOne и поиск результатов сравнения)
- Остальное занимает 20 мс (findOne и поиск результатов сравнения)
- 1.2
- Размер хранилища 10M
- Получить все данные 7500+мс
- Полный запрос таблицы
- Оставшееся время
Структура 2. Если данные не берутся в миллисекундах, это смертный приговор, потому что одно изменение пикселя в этой структуре должно хранить все данные изображения.
Честно говоря, этот результат теста сделал меня немного неприемлемым, я спросил несколько бэкендов, я знал, почему производительность такая низкая и есть ли решение, но результата не было. Что еще страшнее, так это то, что тест проводился на моем настольном компьютере с процессором i7.Когда я разместил тестовую среду на одноядерном сервере, время, необходимое для получения полных данных таблицы, было умножено на 10. К счастью, пока вы долго думаете над вопросом, даже если вы просто долго думаете об этом, вы всегда можете вырваться из какого-то необъяснимого вдохновения. Я подумал об одном из ключевыхДанные могут быть извлечены и помещены в память только при запуске службы, а база данных и копии данных в памяти изменяются синхронно при изменении пикселей.
const mongoose = require('mongoose')
let schema = new mongoose.Schema({
index: {
type: Number,
index: true
},
r: Number,
g: Number,
b: Number
}, {
collection: 'dots'
})
index
заменятьx & y
и удалитьrgba
серединаa
В дополненном коде может значительно минимизировать размер хранения коллекции
top
Просмотр информации об использовании оборудования), он также намеренно арендовал новый сервер, хотите использовать группу друзей, чтобы напомнить "распределенные". Затем последовал период времени, я просматриваю данные и обнаружил, что программа всегда занимает сотни тысяч и несколько сотен секунд (фиксированное число), Бен внезапно рухнет, поэтому невиновность карты ЦП.
PS: К счастью, раньше не было распределенного опыта, иначе, если вы пойдете в темноте, вы все равно можете подумать, что это проблема процессора.
Мыслительный процесс передачи данных
Как уже упоминалось выше, цветной массив длиной 1,572,864 занимает 1,5 м памяти, и я думаю, что этот размер также используется во время передачи данных. Сначала я подумал, я должен сжать эти данные (не GZIP), но поскольку я не могу, я думал о альтернативе. Ранее, чтобы избежать высокого потребления IO, при выборе данных, копия данных будет храниться в памяти. Я думал, что могу генерировать эти данные, сращивание (структура 1.1 имеет гораздо меньше расход CPU)ImageData
пройти сноваctx.putImageData
Рисование на холсте, это второй ключКартина на холсте на сервере копия данных.
Тогда все в порядке, вы можете пройтиctx.toDataURL || fs.writeFile('{path}', canvas.toBuffer('image/jpeg')
Отправляем данные клиенту в виде картинок, алгоритм самой картинки помогает нам сжимать данные, чтобы нам самим не возиться с ними. На самом деле степень сжатия очень впечатляет: когда почти все цвета на чертежной доске повторяются на ранней стадии, 1,5 млн данных могут быть сжаты даже менее чем до 10 КБ, и, по оценкам, она должна быть в пределах 300 КБ в более поздняя стадия.
Учитывая, что DataURL удобнее, здесь я использую DataURL для передачи данных изображения.
трудовая книжка
- День 1 Реконструкция внешнего интерфейса пиксельной монтажной области, чтобы решить проблему увеличения, когда изображение слишком велико.
- День 2 Работа с внутренней логикой из-за ограничений ввода-вывода базы данных, пробовать разные структуры хранения, но производительность не идеальна
- На 3 день я продолжил изучение проблемы, и наконец решил синхронизировать работу холста на стороне сервера, вместо того, чтобы только хранить его в библиотеке, но процесс пока не наладился, т.к. я проспал полдня.
- На 4-й день 1-ядерный сервер 1G дал сбой при доступе к базе данных для извлечения фрагментов данных 50 Вт.Посоветовавшись с друзьями, я случайно обнаружил актуальную проблему, и было решение (иногда новый сервер был оснащен набором сред, но из-за решения проблем снова устарело)
- День 5 добавляет функции объявления, пользователя, чата и запроса исторической информации о пикселях.
- День 6/7 решил проблему https socket.io.После двух дней ночевки, наконец, выяснилось, что это была проблема ускорения CDN, которая чуть не взлетела до небес.
День 4 сказал, что реальная проблема, я, вероятно, могу найти в NodeJS ограничение размера переменной или ограничить количество объектов, потому что после того, как я преобразовал массив [объект] длины 50 Вт в массив [число] длины 200 Вт, проблема исчезнет, узнайте конкретные причины Гангстер Пожалуйста, держите.
Запись скопирована из Дневника приехала, День 6/7 действительно самые трудные два дня, на самом деле, код не так с самого начала, проблема в том, что они стреляют в ускорении CDN облака, ужасно, что я не думал Чулприт он виновник. На самом деле повторите тест через два дня, потому что это ничего не делают, у меня есть два сомнения CDN. Впервые я положил DNS-сервер на IP, но результаты теста все еще дают, а затем возобновило ускорение. Второй раз был в седьмом дне пяти утра, когда голова очень трудно принять прямое расширение CDN, остановилось, думая, что невозможно удалить окончательный тестовый сертификат CDN HTTPS с HTTP-адресом. Затем я понял, что после того, как я пингу доменное имя было изменено, чтобы определить аналитический (около 10 минут спустя модифицировать анализ), пространство доменного имени будет переанализировать CDN (повторил эту причину, я не знаю почему, ALI Облачное имя доменного имени), первый тест должен быть допущен, имеется по этой причине, не дольше дольше. Я намерен возобновить после решения теста ускорения CDN, но никогда не узнал, что конфигурация вызывает проблему, поэтому в конце концов я не смог ускорить восстановление.