Загадки и мифы SPF |
SPF (Sender Policy Framework), полное название можно перевести как "Основы политики отправителя для авторизации использования домена в Email" — протокол, посредством которого домен электронной почты может указать, какие хосты Интернет авторизованы использовать этот домен в командах SMTP HELO и MAIL FROM. Публикация политики SPF не требует никакого дополнительного софта и поэтому чрезвычайно проста: достаточно добавить в зону DNS запись типа TXT, содержащую политику, пример записи есть в конце статьи. Для работы с SPF есть многочисленные мануалы и даже онлайн-конструкторы.
Первая версия стандарта SPF принята более 10 лет назад. За это время были созданы многочисленные реализации, выработаны практики применения и появилась свежая версия стандарта. Но самое удивительное, что почему-то именно SPF, более чем любой другой стандарт, оброс за 10 лет невероятным количеством мифов и заблуждений, которые кочуют из статьи в статью и с завидной регулярностью выскакивают в обсуждениях и ответах на вопросы на форумах. А протокол, казалось бы, такой простой: внедрение занимает всего пару минут. Давайте попробуем вспомнить и разобрать наиболее частые заблуждения.
TL;DR — рекомендации в конце.
На самом деле: SPF никак не защищает видимый пользователю адрес отправителя.
Объяснение: SPF вообще не работает с содержимым письма, которое видит пользователь, в частности с адресом отправителя. SPF авторизует и проверяет адреса на уровне почтового транспорта (SMTP) между двумя MTA (envelope-from, RFC5321.MailFrom aka Return-Path). Эти адреса не видны пользователю и могут отличаться от видимых пользователю адресов из заголовка From письма (RFC5322.From). Таким образом, письмо с поддельным отправителем во From запросто может пройти SPF-авторизацию.
На самом деле: скорей всего, изменений в плане безопасности и спама вы не заметите.
Объяснение: SPF изначально альтруистический протокол и сам по себе не дает преимуществ тому, кто публикует SPF-политику. Теоретически, внедрение вами SPF могло бы защитить кого-то другого от поддельных писем с вашего домена. Но на практике даже это не так, потому что результаты SPF редко используются напрямую (об этом ниже). Боле того, даже если бы все домены публиковали SPF, а все получатели запрещали получение писем без SPF-авторизации, это вряд ли привело бы к снижению уровня спама.
SPF не защищает от подделки отправителя или спама напрямую, тем не менее, он активно используется и весьма полезен и в системах спам-фильтрации и для защиты от поддельных писем, т.к. позволяет привязать письмо к определенному домену и его репутации.
На самом деле: Всё зависит от типа письма и пути, по которому оно доставляется.
Объяснение: SPF сам по себе не влияет на доставляемость писем обычным потоком, и отрицательно влияет при неправильном внедрении или на непрямых потоках писем (indirect flow), когда пользователь получает письма не от того сервера, с которого письмо было отправлено, например на перенаправленные письма. Но системы спам-фильтрации и репутационные классификаторы учитывают наличие SPF, что в целом, на основном потоке писем, дает положительный результат. Если, конечно, вы не рассылаете спам.
На самом деле: SPF авторизует почтовый сервер, отправляющий письмо от имени домена.
Объяснение: Во-первых, SPF работает только на уровне доменов, а не для отдельных адресов электронной почты. Во-вторых, даже если вы являетесь легальным пользователем почты определенного домена, SPF не позволяет вам отправить письмо из любого места. Чтобы письмо прошло SPF-валидацию, вы должны отправлять только через авторизованный сервер. В-третьих, если вы авторизовали сервер по SPF (например, разрешили отправку писем от вашего домена через какого-либо ESP или хостинг-провайдера) и он не реализует дополнительных ограничений, то все пользователи данного сервера могут рассылать письма от имени вашего домена. Всё это следует учитывать при внедрении SPF и аутентификации писем в целом.
На самом деле: SPF-авторизация или ее отсутствие в общем случае не влияет кардинально на доставку писем.
Объяснение: стандарт SPF является только стандартом авторизации и в явном виде указывает, что действия, применяемые к письмам, не прошедшим авторизацию, находятся за пределами стандарта и определяются локальной политикой получателя. Отказ в получении таких писем приводит к проблемам с письмами, идущими через непрямые маршруты доставки, например, перенаправления или списки рассылки, и этот факт должен учитываться в локальной политике. На практике, строгий отказ из-за сбоя SPF-авторизации не рекомендуется к использованию и возможен только при публикации доменом политики -all
(hardfail) в отсутствии других средств фильтрации. В большинстве случаев, SPF-авторизация используется как один из факторов в весовых системах. Причем вес этого фактора очень небольшой, потому что нарушение SPF-авторизации обычно не является сколь-либо достоверным признаком спама — многие спам-письма проходят SPF-авторизацию, а вполне легальные — нет, и вряд ли эта ситуация когда-нибудь кардинально изменится. При таком использовании, разницы между "-all" и "~all" нет.
Факт наличия SPF-авторизации важен не столько для доставки письма и принятия решения о том, является ли оно спамом, сколько для подтверждения адреса отправителя и связи с доменом, что позволяет для письма использовать не репутацию IP, а репутацию домена.
На принятие решения о действии над письмом, не прошедшим авторизацию, гораздо больше влияет политика DMARC. Политика DMARC позволяет отбросить (или поместить в карантин) все письма, не прошедшие авторизацию или их процент.
-all
(hardfail), это безопасней чем ?all
или ~all
На самом деле: на практике -all
никак не влияет ни на чью безопасность, зато негативно влияет на доставляемость писем.
Объяснение: -all
приводит к блокировке писем, отправленных через непрямые маршруты теми немногими получателями, которые используют результат SPF напрямую и блокируют письма. При этом на большую часть спама и поддельных писем эта политика существенного влияния не окажет. На текущий момент наиболее разумной политикой считается ~all
(softfail), она используется практически всеми крупными доменам, даже теми, у которых очень высокие требованиями к безопасности (paypal.com, например). -all
можно использовать для доменов, с которых не производится отправки легальных писем. Для DMARC -
, ~
и ?
являются эквивалентными.
На самом деле: необходимо также прописать SPF для доменов, используемых в HELO почтовых серверов, и желательно прописать блокирующую политику для неиспользуемых для отправки почты A-записей и вайлдкарда.
Объяснение: в некоторых случаях, в частности, при доставке NDR (сообщение о невозможности доставки), DSN (сообщение подтверждения доставки) и некоторых автоответах, адрес отправителя в SMTP-конверте (envelope-from) является пустым. В таком случае SPF проверяет имя хоста из команды HELO/EHLO. Необходимо проверить, какое имя используется в данной команде (например, заглянув в конфигурацию сервера или отправив письмо на публичный сервер и посмотрев заголовки) и прописать для него SPF. Спамерам совершенно не обязательно слать спам с тех же доменов, с которых вы отправляете письма, они могут отправлять от имени любого хоста, имеющего A- или MX-запись. Поэтому, если вы публикуете SPF из альтруистических соображений, то надо добавлять SPF для всех таких записей, и желательно еще wildcard (*) для несуществующих записей.
На самом деле: надо добавлять запись типа TXT.
Объяснение: в текущей версии стандарта SPF (RFC 7208) записи типа SPF являются deprecated и не должны больше использоваться.
На самом деле: по возможности, следует максимально сократить SPF-запись и использовать в ней только адреса сетей через IP4/IP6.
Объяснение: на разрешение SPF-политики отведен лимит в 10 DNS-запросов. Его превышение приведет к постоянной ошибке политики (permerror). Кроме того, DNS является ненадежной службой, и для каждого запроса есть вероятность сбоя (temperror), которая возрастает с количеством запросов. Каждая дополнительная запись a
или include
требует дополнительного DNS-запроса, для include
также необходимо запросить всё, что указано в include-записи. mx
требует запроса MX-записей и дополнительного запроса A-записи для каждого MX-сервера. ptr
требует дополнительного запроса, к тому же в принципе является небезопасной. Только адреса сетей, перечисленные через IP4
/IP6
, не требуют дополнительных DNS-запросов.
На самом деле: как и для большинства DNS-записей, лучше иметь TTL в диапазоне от 1 часа до 1 суток, заблаговременно снижая его при внедрении или планируемых изменениях и повышая при стабильно работающей политике.
Объяснение: более высокий TTL снижает вероятность ошибок DNS и, как следствие, temperror SPF, но повышает время реакции при необходимости внесения изменений в SPF-запись.
+all
На самом деле: политика с явным +all
или неявным правилом, разрешающим рассылку от имени домена с любых IP-адресов, негативно скажется на доставке почты.
Объяснение: такая политика не несет смысловой нагрузки и часто используется спамерами, чтобы обеспечить SPF-аутентификацию на спам-письмах, рассылаемых через ботнеты. Поэтому домен, публикующий подобную политику, имеет очень большие шансы попасть под блокировку.
На самом деле: SPF необходим.
Объяснение: SPF — это один из механизмов авторизации отправителя в электронной почте и способ идентификации домена в репутационных системах. В настоящее время крупные провайдеры почтовых сервисов постепенно начинают требовать наличие авторизации писем, и на письма, не имеющие авторизации, могут накладываться «штрафные санкции» по их доставке или отображению пользователю. Кроме того, на письма, не прошедшие SPF-авторизации, могут не возвращаться автоответы и отчеты о доставке или невозможности доставки. Причина в том, что эти категории ответов, как правило, идут именно на адрес SMTP-конверта и требуют, чтобы он был авторизован. Поэтому SPF необходим даже в том случае, если все письма авторизованы DKIM. Также SPF совершенно необходим в IPv6-сетях и облачных сервисах: в таких сетях практически невозможно использовать репутацию IP-адреса и письма с адресов, не авторизованных SPF, как правило, не принимаются. Одна из основных задач SPF, определенная в стандарте — как раз использование репутации доменного имени вместо репутации IP.
На самом деле: необходимы также DKIM и DMARC.
Объяснение: DKIM необходим для прохождения писем через различные пересылки. DMARC необходим для защиты адреса отправителя от подделок. Кроме того, DMARC позволяет получать отчеты о нарушениях политики SPF.
На самом деле: запись должна быть ровно одна.
Объяснение: это требование стандарта. Если записей будет более одной, это приведет к постоянной ошибке (permerror). Если необходимо объединить несколько SPF-записей, опубликуйте запись с несколькими include
.
На самом деле: надо использовать v=spf1
.
Объяснение: spf2.0 не существует. Публикация записи spf2.0 может приводить к непредсказуемым результатам. spf2.0 никогда не существовал и не был стандартом, но отсылка на него есть в экспериментальном стандарте RFC 4406 (Sender ID), который писался в предположении, что такой стандарт будет принят, ведь его принятие обсуждалось на тот момент. Sender ID, который должен был решить проблему подмены адресов, не стал общепринятым стандартом и от него следует отказаться в пользу DMARC. Даже в том случае, если вы решаете использовать Sender ID и опубликовать spf2.0 запись, она не заменит записи spf1.
Я практически закончил писать эту статью, когда меня перехватила служба поддержки пользователей и настоятельно (с угрозой применения грубой силы) рекомендовала напомнить о следующих нюансах SPF, с которыми им чаще всего приходится сталкиваться при разрешении проблем:
all
или redirect
. После этих директив ничего идти не должно.all
или redirect
могут встретиться в политике ровно один раз, они заменяют друг друга (то есть в одной политике не может быть all
и redirect
одновременно).include
не заменяет директивы all
или redirect
. include
может встретиться в политике несколько раз, при этом политику всё равно следует завершать директивами all
или redirect
. Политика, включаемая через include
или redirect
, также должна быть валидной политикой, оканчивающейся на директиву all
или redirect
. При этом для include
безразлично, какое правило (-all
, ~all
, ?all
) используется для all
во включаемой политике, а для redirect
разница есть.include
используется с двоеточием (include:example.com
), директива redirect
— со знаком равенства (redirect=example.com
).v=spf1
в DNS.IP4
/IP6
. Располагайте их в начале политики, чтобы избежать лишних DNS-запросов. Минимизируйте использование include
, не используйте без необходимости a
, без крайней и непреодолимой необходимости не используйте mx
и никогда не используйте ptr
.~all
для доменов, с которых реально отправляются письма, -all
— для неиспользуемых доменов и записей.Пример политики SPF: @ IN TXT "v=spf1 ip4:1.2.3.0/24 include:_spf.myesp.example.com ~all"
Метки: author z3apa3a спам и антиспам dns блог компании mail.ru group spf электронная почта рассылки |
[Перевод] Коннектор Azure Container Instances для Kubernetes |
$ az group create -n aci-test -l westus
{
"id": "/subscriptions//resourceGroups/aci-test",
"location": "westus",
"managedBy": null,
"name": "aci-test",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null
}
$ az account list -o table
Name CloudName SubscriptionId State IsDefault
----------------------------------------------- ----------- ------------------------------------ ------- -----------
Pay-As-You-Go AzureCloud 12345678-9012-3456-7890-123456789012 Enabled True
$ az ad sp create-for-rbac --role=Contributor --scopes /subscriptions//
{
"appId": "",
"displayName": "azure-cli-2017-07-19-19-13-19",
"name": "http://azure-cli-2017-07-19-19-13-19",
"password": "",
"tenant": ""
}
$ az provider list -o table | grep ContainerInstance
Microsoft.ContainerInstance NotRegistered
$ az provider register -n Microsoft.ContainerInstance
$ az provider list -o table | grep ContainerInstance
Microsoft.ContainerInstance Registered
$ kubectl create -f examples/aci-connector.yaml
deployment "aci-connector" created
$ kubectl get nodes -w
NAME STATUS AGE VERSION
aci-connector Ready 3s 1.6.6
k8s-agentpool1-31868821-0 Ready 5d v1.7.0
k8s-agentpool1-31868821-1 Ready 5d v1.7.0
k8s-agentpool1-31868821-2 Ready 5d v1.7.0
k8s-master-31868821-0 Ready,SchedulingDisabled 5d v1.7.0
$ helm install --name my-release ./charts/aci-connector
$ helm install --name my-release --set env.azureClientId=YOUR-AZURECLIENTID,env.azureClientKey=YOUR-AZURECLIENTKEY,env.azureTenantId=YOUR-AZURETENANTID,env.azureSubscriptionId=YOUR-AZURESUBSCRIPTIONID,env.aciResourceGroup=YOUR-ACIRESOURCEGROUP,env.aciRegion=YOUR-ACI-REGION ./charts/aci-connector
$ kubectl create -f examples/nginx-pod.yaml
pod "nginx" created
$ kubectl get po -w -o wide
NAME READY STATUS RESTARTS AGE IP NODE
aci-connector-3396840456-v75q2 1/1 Running 0 44s 10.244.2.21 k8s-agentpool1-31868821-2
nginx 1/1 Running 0 31s 13.88.27.150 aci-connector
$ kubectl create -f examples/nginx-pod-tolerations.yaml
$ kubectl set image deploy/aci-connector aci-connector=microsoft/aci-connector-k8s:canary
Метки: author stasus saas / s+s microsoft azure блог компании microsoft microsoft kubernetes k8s azure container instances azure |
[Перевод] Коннектор Azure Container Instances для Kubernetes |
$ az group create -n aci-test -l westus
{
"id": "/subscriptions//resourceGroups/aci-test",
"location": "westus",
"managedBy": null,
"name": "aci-test",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null
}
$ az account list -o table
Name CloudName SubscriptionId State IsDefault
----------------------------------------------- ----------- ------------------------------------ ------- -----------
Pay-As-You-Go AzureCloud 12345678-9012-3456-7890-123456789012 Enabled True
$ az ad sp create-for-rbac --role=Contributor --scopes /subscriptions//
{
"appId": "",
"displayName": "azure-cli-2017-07-19-19-13-19",
"name": "http://azure-cli-2017-07-19-19-13-19",
"password": "",
"tenant": ""
}
$ az provider list -o table | grep ContainerInstance
Microsoft.ContainerInstance NotRegistered
$ az provider register -n Microsoft.ContainerInstance
$ az provider list -o table | grep ContainerInstance
Microsoft.ContainerInstance Registered
$ kubectl create -f examples/aci-connector.yaml
deployment "aci-connector" created
$ kubectl get nodes -w
NAME STATUS AGE VERSION
aci-connector Ready 3s 1.6.6
k8s-agentpool1-31868821-0 Ready 5d v1.7.0
k8s-agentpool1-31868821-1 Ready 5d v1.7.0
k8s-agentpool1-31868821-2 Ready 5d v1.7.0
k8s-master-31868821-0 Ready,SchedulingDisabled 5d v1.7.0
$ helm install --name my-release ./charts/aci-connector
$ helm install --name my-release --set env.azureClientId=YOUR-AZURECLIENTID,env.azureClientKey=YOUR-AZURECLIENTKEY,env.azureTenantId=YOUR-AZURETENANTID,env.azureSubscriptionId=YOUR-AZURESUBSCRIPTIONID,env.aciResourceGroup=YOUR-ACIRESOURCEGROUP,env.aciRegion=YOUR-ACI-REGION ./charts/aci-connector
$ kubectl create -f examples/nginx-pod.yaml
pod "nginx" created
$ kubectl get po -w -o wide
NAME READY STATUS RESTARTS AGE IP NODE
aci-connector-3396840456-v75q2 1/1 Running 0 44s 10.244.2.21 k8s-agentpool1-31868821-2
nginx 1/1 Running 0 31s 13.88.27.150 aci-connector
$ kubectl create -f examples/nginx-pod-tolerations.yaml
$ kubectl set image deploy/aci-connector aci-connector=microsoft/aci-connector-k8s:canary
Метки: author stasus saas / s+s microsoft azure блог компании microsoft microsoft kubernetes k8s azure container instances azure |
Начальник, что мне делать для того, чтобы получать больше денег |
Метки: author digore управление разработкой управление персоналом деньги зарплатные ожидания управление людьми |
Начальник, что мне делать для того, чтобы получать больше денег |
Метки: author digore управление разработкой управление персоналом деньги зарплатные ожидания управление людьми |
Я б в программеры пошёл, пусть меня научат |
#include
int main()
{
int i, fact=1, n;
cin>>n;
for (i=1; i<=n; i++)
{
fact=fact*i;
}
cout << fact;
return 0;
}
math.h
. Для целей обучения — полезный, весёлый и поучительный урок. Для целей работы — трата сил, времени и размножение костылей. Многое придумано до нас — достаточно взять, подключить и научиться использовать.
|
Я б в программеры пошёл, пусть меня научат |
#include
int main()
{
int i, fact=1, n;
cin>>n;
for (i=1; i<=n; i++)
{
fact=fact*i;
}
cout << fact;
return 0;
}
math.h
. Для целей обучения — полезный, весёлый и поучительный урок. Для целей работы — трата сил, времени и размножение костылей. Многое придумано до нас — достаточно взять, подключить и научиться использовать.
|
Вторая версия Монитора качества воздуха |
Инструкцию о том как прошивать контроллер можно посмотреть тут.
Электрическая схема:
Монтажная плата:
Метки: author Migrator разработка для интернета вещей программирование микроконтроллеров lua со2 экология климат в офисе |
Вторая версия Монитора качества воздуха |
Инструкцию о том как прошивать контроллер можно посмотреть тут.
Электрическая схема:
Монтажная плата:
Метки: author Migrator разработка для интернета вещей программирование микроконтроллеров lua со2 экология климат в офисе |
Используем PubNub: эмоциональный говорящий чат своими руками |
public class HomeController : Controller
{
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Main(LoginDTO loginDTO)
{
String chatChannel = ConfigurationHelper.ChatChannel;
String textToSpeechChannel = ConfigurationHelper.TextToSpeechChannel;
String authKey = loginDTO.Username + DateTime.Now.Ticks.ToString();
var chatManager = new ChatManager();
if (loginDTO.ReadAccessOnly)
{
chatManager.GrantUserReadAccessToChannel(authKey, chatChannel);
}
else
{
chatManager.GrantUserReadWriteAccessToChannel(authKey, chatChannel);
}
chatManager.GrantUserReadWriteAccessToChannel(authKey, textToSpeechChannel);
var authDTO = new AuthDTO()
{
PublishKey = ConfigurationHelper.PubNubPublishKey,
SubscribeKey = ConfigurationHelper.PubNubSubscribeKey,
AuthKey = authKey,
Username = loginDTO.Username,
ChatChannel = chatChannel,
TextToSpeechChannel = textToSpeechChannel
};
return View(authDTO);
}
}
public class ChatManager
{
private const String PRESENCE_CHANNEL_SUFFIX = "-pnpres";
private Pubnub pubnub;
public ChatManager()
{
var pnConfiguration = new PNConfiguration();
pnConfiguration.PublishKey = ConfigurationHelper.PubNubPublishKey;
pnConfiguration.SubscribeKey = ConfigurationHelper.PubNubSubscribeKey;
pnConfiguration.SecretKey = ConfigurationHelper.PubNubSecretKey;
pnConfiguration.Secure = true;
pubnub = new Pubnub(pnConfiguration);
}
public void ForbidPublicAccessToChannel(String channel)
{
pubnub.Grant()
.Channels(new String[] { channel })
.Read(false)
.Write(false)
.Async(new AccessGrantResult());
}
public void GrantUserReadAccessToChannel(String userAuthKey, String channel)
{
pubnub.Grant()
.Channels(new String[] { channel, channel + PRESENCE_CHANNEL_SUFFIX })
.AuthKeys(new String[] { userAuthKey })
.Read(true)
.Write(false)
.Async(new AccessGrantResult());
}
public void GrantUserReadWriteAccessToChannel(String userAuthKey, String channel)
{
pubnub.Grant()
.Channels(new String[] { channel, channel + PRESENCE_CHANNEL_SUFFIX })
.AuthKeys(new String[] { userAuthKey })
.Read(true)
.Write(true)
.Async(new AccessGrantResult());
}
}
var pubnub;
var chatChannel;
var textToSpeechChannel;
var username;
function init(publishKey, subscribeKey, authKey, username, chatChannel, textToSpeechChannel) {
pubnub = new PubNub({
publishKey: publishKey,
subscribeKey: subscribeKey,
authKey: authKey,
uuid: username
});
this.username = username;
this.chatChannel = chatChannel;
this.textToSpeechChannel = textToSpeechChannel;
addListener();
subscribe();
}
function subscribe() {
pubnub.subscribe({
channels: [chatChannel, textToSpeechChannel],
withPresence: true
});
}
function addListener() {
pubnub.addListener({
status: function (statusEvent) {
if (statusEvent.category === "PNConnectedCategory") {
getOnlineUsers();
}
},
message: function (message) {
if (message.channel === chatChannel) {
var jsonMessage = JSON.parse(message.message);
var chat = document.getElementById("chat");
if (chat.value !== "") {
chat.value = chat.value + "\n";
chat.scrollTop = chat.scrollHeight;
}
chat.value = chat.value + jsonMessage.Username + ": " +
jsonMessage.Message;
}
else if (message.channel === textToSpeechChannel) {
if (message.publisher !== username) {
var audio = new Audio(message.message.speech);
audio.play();
}
}
},
presence: function (presenceEvent) {
if (presenceEvent.channel === chatChannel) {
if (presenceEvent.action === 'join') {
if (!UserIsOnTheList(presenceEvent.uuid)) {
AddUserToList(presenceEvent.uuid);
}
PutStatusToChat(presenceEvent.uuid,
"joins the channel");
}
else if (presenceEvent.action === 'timeout') {
if (UserIsOnTheList(presenceEvent.uuid)) {
RemoveUserFromList(presenceEvent.uuid);
}
PutStatusToChat(presenceEvent.uuid,
"was disconnected due to timeout");
}
}
}
});
}
function publish(message) {
var jsonMessage = {
"Username": username,
"Message": message
};
var publishConfig = {
channel: chatChannel,
message: JSON.stringify(jsonMessage)
};
pubnub.publish(publishConfig);
var emotedText = '';
var selectedEmotion = iconSelect.getSelectedValue();
if (selectedEmotion !== "") {
emotedText += '';
}
emotedText += message;
if (selectedEmotion !== "") {
emotedText += '';
}
emotedText += ' ';
jsonMessage = {
"text": emotedText
};
publishConfig = {
channel: textToSpeechChannel,
message: jsonMessage
};
pubnub.publish(publishConfig);
}
Метки: author Cromathaar системы обмена сообщениями программирование javascript .net pubnub iaas |
Используем PubNub: эмоциональный говорящий чат своими руками |
public class HomeController : Controller
{
public ActionResult Login()
{
return View();
}
[HttpPost]
public ActionResult Main(LoginDTO loginDTO)
{
String chatChannel = ConfigurationHelper.ChatChannel;
String textToSpeechChannel = ConfigurationHelper.TextToSpeechChannel;
String authKey = loginDTO.Username + DateTime.Now.Ticks.ToString();
var chatManager = new ChatManager();
if (loginDTO.ReadAccessOnly)
{
chatManager.GrantUserReadAccessToChannel(authKey, chatChannel);
}
else
{
chatManager.GrantUserReadWriteAccessToChannel(authKey, chatChannel);
}
chatManager.GrantUserReadWriteAccessToChannel(authKey, textToSpeechChannel);
var authDTO = new AuthDTO()
{
PublishKey = ConfigurationHelper.PubNubPublishKey,
SubscribeKey = ConfigurationHelper.PubNubSubscribeKey,
AuthKey = authKey,
Username = loginDTO.Username,
ChatChannel = chatChannel,
TextToSpeechChannel = textToSpeechChannel
};
return View(authDTO);
}
}
public class ChatManager
{
private const String PRESENCE_CHANNEL_SUFFIX = "-pnpres";
private Pubnub pubnub;
public ChatManager()
{
var pnConfiguration = new PNConfiguration();
pnConfiguration.PublishKey = ConfigurationHelper.PubNubPublishKey;
pnConfiguration.SubscribeKey = ConfigurationHelper.PubNubSubscribeKey;
pnConfiguration.SecretKey = ConfigurationHelper.PubNubSecretKey;
pnConfiguration.Secure = true;
pubnub = new Pubnub(pnConfiguration);
}
public void ForbidPublicAccessToChannel(String channel)
{
pubnub.Grant()
.Channels(new String[] { channel })
.Read(false)
.Write(false)
.Async(new AccessGrantResult());
}
public void GrantUserReadAccessToChannel(String userAuthKey, String channel)
{
pubnub.Grant()
.Channels(new String[] { channel, channel + PRESENCE_CHANNEL_SUFFIX })
.AuthKeys(new String[] { userAuthKey })
.Read(true)
.Write(false)
.Async(new AccessGrantResult());
}
public void GrantUserReadWriteAccessToChannel(String userAuthKey, String channel)
{
pubnub.Grant()
.Channels(new String[] { channel, channel + PRESENCE_CHANNEL_SUFFIX })
.AuthKeys(new String[] { userAuthKey })
.Read(true)
.Write(true)
.Async(new AccessGrantResult());
}
}
var pubnub;
var chatChannel;
var textToSpeechChannel;
var username;
function init(publishKey, subscribeKey, authKey, username, chatChannel, textToSpeechChannel) {
pubnub = new PubNub({
publishKey: publishKey,
subscribeKey: subscribeKey,
authKey: authKey,
uuid: username
});
this.username = username;
this.chatChannel = chatChannel;
this.textToSpeechChannel = textToSpeechChannel;
addListener();
subscribe();
}
function subscribe() {
pubnub.subscribe({
channels: [chatChannel, textToSpeechChannel],
withPresence: true
});
}
function addListener() {
pubnub.addListener({
status: function (statusEvent) {
if (statusEvent.category === "PNConnectedCategory") {
getOnlineUsers();
}
},
message: function (message) {
if (message.channel === chatChannel) {
var jsonMessage = JSON.parse(message.message);
var chat = document.getElementById("chat");
if (chat.value !== "") {
chat.value = chat.value + "\n";
chat.scrollTop = chat.scrollHeight;
}
chat.value = chat.value + jsonMessage.Username + ": " +
jsonMessage.Message;
}
else if (message.channel === textToSpeechChannel) {
if (message.publisher !== username) {
var audio = new Audio(message.message.speech);
audio.play();
}
}
},
presence: function (presenceEvent) {
if (presenceEvent.channel === chatChannel) {
if (presenceEvent.action === 'join') {
if (!UserIsOnTheList(presenceEvent.uuid)) {
AddUserToList(presenceEvent.uuid);
}
PutStatusToChat(presenceEvent.uuid,
"joins the channel");
}
else if (presenceEvent.action === 'timeout') {
if (UserIsOnTheList(presenceEvent.uuid)) {
RemoveUserFromList(presenceEvent.uuid);
}
PutStatusToChat(presenceEvent.uuid,
"was disconnected due to timeout");
}
}
}
});
}
function publish(message) {
var jsonMessage = {
"Username": username,
"Message": message
};
var publishConfig = {
channel: chatChannel,
message: JSON.stringify(jsonMessage)
};
pubnub.publish(publishConfig);
var emotedText = '';
var selectedEmotion = iconSelect.getSelectedValue();
if (selectedEmotion !== "") {
emotedText += '';
}
emotedText += message;
if (selectedEmotion !== "") {
emotedText += '';
}
emotedText += ' ';
jsonMessage = {
"text": emotedText
};
publishConfig = {
channel: textToSpeechChannel,
message: jsonMessage
};
pubnub.publish(publishConfig);
}
Метки: author Cromathaar системы обмена сообщениями программирование javascript .net pubnub iaas |
Как попасть в Технопарк Университета ИТМО: большое интервью |
В последнее время стало приходить больше компаний, развивающихся в области Life Science, и нам очень интересно с ними сотрудничать. У нас уже размещаются компании, создающие решения в области биофармацевтики, биомедицинских технологий и сервисов, биоинформационных технологий, медицинского приборостроения и здравоохранения, и в ближайшее время мы планируем расширить присутствие таких компаний.
— Олеся Баранюк, Технопарк Университета ИТМО
На момент становления резидентом наш проект находился на начальной стадии. В принципе, была только базовая идея и ничего больше.
— Дмитрий Павлов, коммерческий директор VISmart (компании-создателя платформы Ontodia)
[На момент получения статуса резидента проект находился на стадии:] MVP [минимально жизнеспособный продукт], 1 внедрение, 1 контракт на подходе.
—Олег Шахов директор по развитию бизнеса компании «Оптимальное движение» (разрабатывает проект odgAssist)
[На момент появления в Технопарке] мы находились на стадии активной разработки программного обеспечения, хотя наша команда разработчиков состояла только из трех программистов. Также мы активно искали подходящее решение для самого устройства (процессор, оптику, сенсоры и так далее) и производителя, который сможет это устройство сделать. На тот момент у нас уже была запущена кампания на Indiцegogo.
— Александр Морено, технический директор Orbi
[Состояние на момент получения статуса резидента:] вывод пилотной разработки на рынок.
— Александр Павлов, директор компании Parseq Lab
Мы подумали, что хорошо быть рядом к центру компетенций в той области, в которой мы решили развивать свой продукт. Пока что нам кажется, что наше решение стать резидентом Технопарка было верным.
— Дмитрий Павлов, VISmart
Мы искали место, где можно заниматься инновационной деятельностью. Нам нужна была инфраструктура для запуска проекта. И нам были нужны не только стол и стул, но также возможность взаимодействовать с другими инновационными предприятиями, получить доступ к интересным событиям и мероприятиям и много чего другого.
Сначала мы определились с тем, что нам нужен технопарк. Всё-таки стартовать с поддержкой немного проще. Затем мы сделали мониторинг 4 ведущих технопарков в Санкт-Петербурге. В результате Технопарк ИТМО оказался в безоговорочных лидерах. Кроме того, у основателей уже имелись контакты с резидентами, что было для нас очень удобно. Также нам понравилась атмосфера технопарка и его представители
— Олег Шахов, odgAssist
Год назад, когда проект дошел до определенной стадии, встала необходимость расширения и поиска подходящего места, для дальнейшей работы над разработкой.
Найдя в интернете Технопарк ИТМО, узнав все условия и преимущества размещения в нем, а так же обнаружив, что среди его резидентов уже есть те, кто работают в области 360 видео, мы поняли, что это именно то место, где мы сможем наиболее эффективно развивать свой проект.
— Александр Морено, Orbi
Прежде всего, [для нас важна была] готовность руководства идти навстречу при решении вопросов размещения. Хорошая инфраструктура и месторасположение Технопарка.
— Александр Павлов, Parseq Lab
Довольно часто к нам обращаются крупные корпорации с просьбой разместить в Технопарке свои R&D-центры, чтобы иметь возможность находиться в эпицентре инновационной активности, сотрудничать с научными подразделениями Университета и резидентами технопарка, а также постепенно формировать портфель инновационных продуктов.
— Олеся Баранюк, Технопарк Университета ИТМО
Мы пришли в Технопарк с несколькими проектами на разных стадиях готовности, из которых уцелело только 50%. Нам кажется, что в большей степени мы заинтересовали Технопарк как команда.
В этой связи, я бы рекомендовал кандидатам в резиденты обратить внимание на представление своей команды. Направление деятельности может смениться, но команда — это величина, по моему мнению, более постоянная и важная, чем кажущийся сильным проект.
— Дмитрий Павлов, VISmart
Про наш проект могу так сказать: у нас были рекомендации от резидентов, мы подготовились к встрече. Не думаю, что сложно попасть в технопарк если у вас годный проект, есть MVP (мне кажется, это важно) и реалистичный план. И да – если вы делаете инновацию, а не интернет-магазин или рекламное агентство.
Мы прилично оделись на встречу, рассказали всю нашу историю, были честны, показали себя профессионалами, реалистами. Показали, что болеем своим делом.
— Олег Шахов, odgAssist
Мы разрабатываем инновационное устройство, находящееся действительно на переднем крае современных технологий в области камеростроения (например используемые нами процессор и объективы только появились на рынке, буквально год назад ничего подобного в принципе не существовало). Сама область 360 видео новая и очень активно развивается.
Плюс основным направлением нашей деятельности на базе Технопарка является именно разработка программного обеспечения, что явно близко как самому Технопраку так и Университету ИТМО в целом. Мы выражали готовность активно участвовать в жизни Технопарка, что также очень важно.
Совет — думаю, что на встрече важно сделать акцент на то, насколько ваш проект подходит по своей направленности Технопарку. Если среди резидентов уже присутствуют компании, с которыми вы могли бы плодотворно сотрудничать, это будет очень хорошим аргументом. Ну и важно быть готовым активно участвовать в жизни Технопарка.
Что касается потенциальных инвесторов, то наличие в их портфеле компаний, с которыми вы смогли бы плодотворно сотрудничать, также будет серьезным плюсом. Однако в первую очередь инвесторы, особенно на самых ранних стадиях, оценивают не только проект, но и вас самих. Важно презентовать не только идею — этого мало. Нужно презентовать команду, и убедить их [руководство Технопарка и/или инвесторов] что вы — именно те люди, которые лучше всех смогут данный проект реализовать.
— Александр Морено, Orbi
У нас есть несколько компаний, которые создают решения в области ДНК-диагностики тяжелых наследственных заболеваний; решения для идентификации личности на основе технологии высокопроизводительного секвенирования; занимаются вопросами создания инфраструктуры для анализа и хранения геномных данных; разработкой геннотерапии, направленной на победу над старостью, онкологией и даже ВИЧ.
Область для Университета не профильная, но, тем не менее, в Университете развивается такое направление как Биоинформатика в рамках Лаборатории и Института Биоинформатики [кстати, об этом направлении мы рассказывали в одном из наших материалов], и наличие резидентов с уникальными компетенциями в этой области значительно увеличивает исследовательские возможности студентов, аспирантов и сотрудников научных подразделений.
— Олеся Баранюк, Технопарк Университета ИТМО
В настоящий момент у нас сложился довольно интересный кейс взаимодействия резидентов Технопарка, сотрудников Университета и сотрудника Венского экономического Университета. Резидент Технопарка, компания VISmart занимается разработкой программного обеспечения в области семантического веба. Она довольно плотно сотрудничают с Международной лабораторией Университета ИТМО «Интеллектуальные методы обработки информации и семантические технологии» (несколько сотрудников VISmart также являются сотрудниками международной лаборатории и наоборот).
Сервис, над которым работала команда VISmart, очень заинтересовал ассистент-профессора Венского экономического университета Герхарда Вольгенаннта, что привело его вначале в компанию VISmart, а потом и в Университет ИТМО. В результате он стал участником программы ITMO Fellowship, влился в команду, в настоящий момент работает над усовершенствованием семантических технологий в области голосового управления устройствами.
— Олеся Баранюк, Технопарк Университета ИТМО
Да, наши разработки связаны с Университетом ИТМО. Дело в том, что наш продукт Ontodia ориентирован в том числе на поддержку стандартов semantic web, выпущенных w3c, а именно RDF, OWL, SPARQL.
В России, по нашим сведениям, есть только одно сильное научное подразделение, которое учит применять эти стандарты и ведет серьезные научно-исследовательские проекты в этой сфере — это Международная лаборатория Университета ИТМО “Интеллектуальные методы обработки информации и семантические технологии”, с которой у нас установлены тесные связи. Двое наших сотрудников является аспирантами ИТМО.
— Дмитрий Павлов, VISmart
Возможность совместного использования оборудования лабораторий Университета или других резидентов Технопарка является опцией, которой довольно часто пользуются наши резиденты. Они обращаются к нам со своим запросом, мы их представляем нашим коллегам из научных подразделений, в случае, если есть взаимная заинтересованность в сотрудничестве, рождаются совместные научные и бизнес-проекты.
— Олеся Баранюк, Технопарк Университета ИТМО
Для нас наиболее полезными оказались следующие услуги: возможность пользоваться конференц-залом Технопарка, возможности ФабЛаб. Нам удобно и приятно иметь помещения в здании Технопарка, потому что авторитет ИТМО помогает выглядеть убедительными, особенно на ранних этапах.
Но самое главное все же — это помощь инновационных менеджеров. Они знакомят нас с потенциальными клиентами, содействуют в продвижении и популяризации проекта, от них можно получить помощь практически в любом вопросе.
— Дмитрий Павлов, VISmart
Технопарк активно помогает находить интересные программы и конкурсы для стартапов. Например, Технопарк поспособствовал нашему участию в конкурсе проектов Polar Bear Pitching в Оулу (Финляндия), где мы заняли второе место. Это был интересный опыт, участие в котором дало возможность стать нам более узнаваемыми в мире, ведь на мероприятии присутствовали представители средств массовой информации со всего мира: CNN, BBC, Al Jazeera и др., многие вели он-лайн трансляцию, ведь не каждый день можно увидеть предпринимателя, вещающего о конкурентных преимуществах из ледяной проруби в феврале при температуре –25.
Также для нас очень полезна возможность общаться с другими компаниями, работающими в области 360 видео. Очень удобно, что в технопарке есть ФабЛаб, где много различных инструментов и оборудования, что позволяет, например, быстро напечатать на 3D принтере прототипы, детали и так далее.
— Александр Морено, Orbi
Куча крутых событий на которые нас зовут, крутой PR, участие в мероприятиях, бешеный нетворкинг, помощь в взаимодействии с различными инструментами гос. поддержки, трэкшн, добрый совет, тёплые слова поддержки.
— Олег Шахов, odgAssist
Попробуйте найти клиента. Пусть это будет не совсем профильный клиент. Но попробуйте начать зарабатывать как можно раньше. Нам на начальном этапе очень сильно помог грант от Фонда Содействия Инновациям
— Дмитрий Павлов, VISmart
Проект должен выполнять задачи, для которых он создаётся. Бизнес должен приносить деньги. То, что вы делаете, может приносить деньги? А что можно сделать, чтобы приносило больше?
Любыми способами (легальными) добейтесь первых продаж. Вы должны понимать, что вы делаете, за какие деньги, для кого и зачем (какую проблему решаете). Если всё ок, то сделайте SWOT-анализ своей идеи/бизнеса. Не стесняйтесь pivot’а и закрытия проекта.
Не взлетело – посмотрите, почему. Если ценности нет — что ж, так бывает. Если нет скорости, фокуса или execution – поработайте над собой, возможно, вам ещё рано делать стартап. И делайте CustDev, много CustDev’a, чтобы ваш продукт был кому-то нужен.
— Олег Шахов, odgAssist
Универсальных советов давать не буду. Просто приведу пример того, с чем мы столкнулись сами. Как правило, особенно когда делаешь не просто ПО, а устройство, то приходится взаимодействовать с большим количеством партнеров (фабрики, производители процессоров и других электронных компонентов и так далее).
Очень важно налаживать с ними не только деловые, но и личные отношения. Несмотря на то, что сейчас практически все можно обсудить удаленно, по почте, на звонке и так далее, не следует пренебрегать личной встречей. Во-первых, это помогает намного быстрее решить множество вопросов, а во-вторых, делает ваше партнерство более надежным.
— Александр Морено, Orbi
Метки: author itmo управление проектами развитие стартапа блог компании университет итмо университет итмо технопарк университета итмо стартапы |
Как попасть в Технопарк Университета ИТМО: большое интервью |
В последнее время стало приходить больше компаний, развивающихся в области Life Science, и нам очень интересно с ними сотрудничать. У нас уже размещаются компании, создающие решения в области биофармацевтики, биомедицинских технологий и сервисов, биоинформационных технологий, медицинского приборостроения и здравоохранения, и в ближайшее время мы планируем расширить присутствие таких компаний.
— Олеся Баранюк, Технопарк Университета ИТМО
На момент становления резидентом наш проект находился на начальной стадии. В принципе, была только базовая идея и ничего больше.
— Дмитрий Павлов, коммерческий директор VISmart (компании-создателя платформы Ontodia)
[На момент получения статуса резидента проект находился на стадии:] MVP [минимально жизнеспособный продукт], 1 внедрение, 1 контракт на подходе.
—Олег Шахов директор по развитию бизнеса компании «Оптимальное движение» (разрабатывает проект odgAssist)
[На момент появления в Технопарке] мы находились на стадии активной разработки программного обеспечения, хотя наша команда разработчиков состояла только из трех программистов. Также мы активно искали подходящее решение для самого устройства (процессор, оптику, сенсоры и так далее) и производителя, который сможет это устройство сделать. На тот момент у нас уже была запущена кампания на Indiцegogo.
— Александр Морено, технический директор Orbi
[Состояние на момент получения статуса резидента:] вывод пилотной разработки на рынок.
— Александр Павлов, директор компании Parseq Lab
Мы подумали, что хорошо быть рядом к центру компетенций в той области, в которой мы решили развивать свой продукт. Пока что нам кажется, что наше решение стать резидентом Технопарка было верным.
— Дмитрий Павлов, VISmart
Мы искали место, где можно заниматься инновационной деятельностью. Нам нужна была инфраструктура для запуска проекта. И нам были нужны не только стол и стул, но также возможность взаимодействовать с другими инновационными предприятиями, получить доступ к интересным событиям и мероприятиям и много чего другого.
Сначала мы определились с тем, что нам нужен технопарк. Всё-таки стартовать с поддержкой немного проще. Затем мы сделали мониторинг 4 ведущих технопарков в Санкт-Петербурге. В результате Технопарк ИТМО оказался в безоговорочных лидерах. Кроме того, у основателей уже имелись контакты с резидентами, что было для нас очень удобно. Также нам понравилась атмосфера технопарка и его представители
— Олег Шахов, odgAssist
Год назад, когда проект дошел до определенной стадии, встала необходимость расширения и поиска подходящего места, для дальнейшей работы над разработкой.
Найдя в интернете Технопарк ИТМО, узнав все условия и преимущества размещения в нем, а так же обнаружив, что среди его резидентов уже есть те, кто работают в области 360 видео, мы поняли, что это именно то место, где мы сможем наиболее эффективно развивать свой проект.
— Александр Морено, Orbi
Прежде всего, [для нас важна была] готовность руководства идти навстречу при решении вопросов размещения. Хорошая инфраструктура и месторасположение Технопарка.
— Александр Павлов, Parseq Lab
Довольно часто к нам обращаются крупные корпорации с просьбой разместить в Технопарке свои R&D-центры, чтобы иметь возможность находиться в эпицентре инновационной активности, сотрудничать с научными подразделениями Университета и резидентами технопарка, а также постепенно формировать портфель инновационных продуктов.
— Олеся Баранюк, Технопарк Университета ИТМО
Мы пришли в Технопарк с несколькими проектами на разных стадиях готовности, из которых уцелело только 50%. Нам кажется, что в большей степени мы заинтересовали Технопарк как команда.
В этой связи, я бы рекомендовал кандидатам в резиденты обратить внимание на представление своей команды. Направление деятельности может смениться, но команда — это величина, по моему мнению, более постоянная и важная, чем кажущийся сильным проект.
— Дмитрий Павлов, VISmart
Про наш проект могу так сказать: у нас были рекомендации от резидентов, мы подготовились к встрече. Не думаю, что сложно попасть в технопарк если у вас годный проект, есть MVP (мне кажется, это важно) и реалистичный план. И да – если вы делаете инновацию, а не интернет-магазин или рекламное агентство.
Мы прилично оделись на встречу, рассказали всю нашу историю, были честны, показали себя профессионалами, реалистами. Показали, что болеем своим делом.
— Олег Шахов, odgAssist
Мы разрабатываем инновационное устройство, находящееся действительно на переднем крае современных технологий в области камеростроения (например используемые нами процессор и объективы только появились на рынке, буквально год назад ничего подобного в принципе не существовало). Сама область 360 видео новая и очень активно развивается.
Плюс основным направлением нашей деятельности на базе Технопарка является именно разработка программного обеспечения, что явно близко как самому Технопраку так и Университету ИТМО в целом. Мы выражали готовность активно участвовать в жизни Технопарка, что также очень важно.
Совет — думаю, что на встрече важно сделать акцент на то, насколько ваш проект подходит по своей направленности Технопарку. Если среди резидентов уже присутствуют компании, с которыми вы могли бы плодотворно сотрудничать, это будет очень хорошим аргументом. Ну и важно быть готовым активно участвовать в жизни Технопарка.
Что касается потенциальных инвесторов, то наличие в их портфеле компаний, с которыми вы смогли бы плодотворно сотрудничать, также будет серьезным плюсом. Однако в первую очередь инвесторы, особенно на самых ранних стадиях, оценивают не только проект, но и вас самих. Важно презентовать не только идею — этого мало. Нужно презентовать команду, и убедить их [руководство Технопарка и/или инвесторов] что вы — именно те люди, которые лучше всех смогут данный проект реализовать.
— Александр Морено, Orbi
У нас есть несколько компаний, которые создают решения в области ДНК-диагностики тяжелых наследственных заболеваний; решения для идентификации личности на основе технологии высокопроизводительного секвенирования; занимаются вопросами создания инфраструктуры для анализа и хранения геномных данных; разработкой геннотерапии, направленной на победу над старостью, онкологией и даже ВИЧ.
Область для Университета не профильная, но, тем не менее, в Университете развивается такое направление как Биоинформатика в рамках Лаборатории и Института Биоинформатики [кстати, об этом направлении мы рассказывали в одном из наших материалов], и наличие резидентов с уникальными компетенциями в этой области значительно увеличивает исследовательские возможности студентов, аспирантов и сотрудников научных подразделений.
— Олеся Баранюк, Технопарк Университета ИТМО
В настоящий момент у нас сложился довольно интересный кейс взаимодействия резидентов Технопарка, сотрудников Университета и сотрудника Венского экономического Университета. Резидент Технопарка, компания VISmart занимается разработкой программного обеспечения в области семантического веба. Она довольно плотно сотрудничают с Международной лабораторией Университета ИТМО «Интеллектуальные методы обработки информации и семантические технологии» (несколько сотрудников VISmart также являются сотрудниками международной лаборатории и наоборот).
Сервис, над которым работала команда VISmart, очень заинтересовал ассистент-профессора Венского экономического университета Герхарда Вольгенаннта, что привело его вначале в компанию VISmart, а потом и в Университет ИТМО. В результате он стал участником программы ITMO Fellowship, влился в команду, в настоящий момент работает над усовершенствованием семантических технологий в области голосового управления устройствами.
— Олеся Баранюк, Технопарк Университета ИТМО
Да, наши разработки связаны с Университетом ИТМО. Дело в том, что наш продукт Ontodia ориентирован в том числе на поддержку стандартов semantic web, выпущенных w3c, а именно RDF, OWL, SPARQL.
В России, по нашим сведениям, есть только одно сильное научное подразделение, которое учит применять эти стандарты и ведет серьезные научно-исследовательские проекты в этой сфере — это Международная лаборатория Университета ИТМО “Интеллектуальные методы обработки информации и семантические технологии”, с которой у нас установлены тесные связи. Двое наших сотрудников является аспирантами ИТМО.
— Дмитрий Павлов, VISmart
Возможность совместного использования оборудования лабораторий Университета или других резидентов Технопарка является опцией, которой довольно часто пользуются наши резиденты. Они обращаются к нам со своим запросом, мы их представляем нашим коллегам из научных подразделений, в случае, если есть взаимная заинтересованность в сотрудничестве, рождаются совместные научные и бизнес-проекты.
— Олеся Баранюк, Технопарк Университета ИТМО
Для нас наиболее полезными оказались следующие услуги: возможность пользоваться конференц-залом Технопарка, возможности ФабЛаб. Нам удобно и приятно иметь помещения в здании Технопарка, потому что авторитет ИТМО помогает выглядеть убедительными, особенно на ранних этапах.
Но самое главное все же — это помощь инновационных менеджеров. Они знакомят нас с потенциальными клиентами, содействуют в продвижении и популяризации проекта, от них можно получить помощь практически в любом вопросе.
— Дмитрий Павлов, VISmart
Технопарк активно помогает находить интересные программы и конкурсы для стартапов. Например, Технопарк поспособствовал нашему участию в конкурсе проектов Polar Bear Pitching в Оулу (Финляндия), где мы заняли второе место. Это был интересный опыт, участие в котором дало возможность стать нам более узнаваемыми в мире, ведь на мероприятии присутствовали представители средств массовой информации со всего мира: CNN, BBC, Al Jazeera и др., многие вели он-лайн трансляцию, ведь не каждый день можно увидеть предпринимателя, вещающего о конкурентных преимуществах из ледяной проруби в феврале при температуре –25.
Также для нас очень полезна возможность общаться с другими компаниями, работающими в области 360 видео. Очень удобно, что в технопарке есть ФабЛаб, где много различных инструментов и оборудования, что позволяет, например, быстро напечатать на 3D принтере прототипы, детали и так далее.
— Александр Морено, Orbi
Куча крутых событий на которые нас зовут, крутой PR, участие в мероприятиях, бешеный нетворкинг, помощь в взаимодействии с различными инструментами гос. поддержки, трэкшн, добрый совет, тёплые слова поддержки.
— Олег Шахов, odgAssist
Попробуйте найти клиента. Пусть это будет не совсем профильный клиент. Но попробуйте начать зарабатывать как можно раньше. Нам на начальном этапе очень сильно помог грант от Фонда Содействия Инновациям
— Дмитрий Павлов, VISmart
Проект должен выполнять задачи, для которых он создаётся. Бизнес должен приносить деньги. То, что вы делаете, может приносить деньги? А что можно сделать, чтобы приносило больше?
Любыми способами (легальными) добейтесь первых продаж. Вы должны понимать, что вы делаете, за какие деньги, для кого и зачем (какую проблему решаете). Если всё ок, то сделайте SWOT-анализ своей идеи/бизнеса. Не стесняйтесь pivot’а и закрытия проекта.
Не взлетело – посмотрите, почему. Если ценности нет — что ж, так бывает. Если нет скорости, фокуса или execution – поработайте над собой, возможно, вам ещё рано делать стартап. И делайте CustDev, много CustDev’a, чтобы ваш продукт был кому-то нужен.
— Олег Шахов, odgAssist
Универсальных советов давать не буду. Просто приведу пример того, с чем мы столкнулись сами. Как правило, особенно когда делаешь не просто ПО, а устройство, то приходится взаимодействовать с большим количеством партнеров (фабрики, производители процессоров и других электронных компонентов и так далее).
Очень важно налаживать с ними не только деловые, но и личные отношения. Несмотря на то, что сейчас практически все можно обсудить удаленно, по почте, на звонке и так далее, не следует пренебрегать личной встречей. Во-первых, это помогает намного быстрее решить множество вопросов, а во-вторых, делает ваше партнерство более надежным.
— Александр Морено, Orbi
Метки: author itmo управление проектами развитие стартапа блог компании университет итмо университет итмо технопарк университета итмо стартапы |
Без заголовка |
Метки: author kudzev разработка веб-сайтов javascript html css блог компании 2гис frontfest codefest гис iot spotify microsoft clojurescript |
Без заголовка |
Метки: author kudzev разработка веб-сайтов javascript html css блог компании 2гис frontfest codefest гис iot spotify microsoft clojurescript |
[recovery mode] Переезды в облако: 5 разных историй |
Метки: author SZinkevich серверное администрирование виртуализация it- инфраструктура блог компании крок крок |
Как мы отмечали 256 день года и рисовали пиксели через API |
13 сентября в Контуре отмечали День программиста. В самом большом офисе разработки играли в Pac-Man и пытались съесть 280 коробок с пиццей. Одновременно полторы тысячи человек рисовали пиксели в онлайне. В этом посте четыре разработчика рассказывают, как делали праздник.
День программиста у нас отмечает вся компания, а не только разработчики. Поэтому была нужна идея для онлайновой игры, в которой могут участвовать все желающие. Я вспомнил, что в апреле прошёл Reddit Place — социальный эксперимент по коллективному рисованию на холсте 1000x1000 пикселей, в котором участвовал миллион человек.
Я решил, что надо сделать свой Place, с таймлапсом и API.
На Reddit миллион человек рисовал на холсте размером один мегапиксель. Каждый мог закрасить не больше одного пикселя раз в 5–20 минут. Если сделать праздничный холст 256x256 пикселей (в 15 раз меньше) и учесть, что у нас не миллион сотрудников (а в 200 раз меньше), то задержку между пикселями тоже должна быть примерно в 10 раз меньше.
Поэтому для нашего поля 256x256 пикселей я выбрал задержку от 2:56 до 0:32. А после этого рассказал об идее коллегам, которые согласились помочь.
Я сразу поняла, что на фронте будет нужен холст, палитра и зум. Но дизайнеры (Владимир dzekh и Юлия krasilnikovayu) оказались хитрее и придумали ещё перемотку, статистику, лидерборд и скриншоты.
Кстати, сначала в палитре было меньше цветов, но потом ребята добавили коричневый, чтобы не ограничивать ничьи творческие порывы.
Тем временем я, как современный фронтендер, рефлекторно начала думать о том, чтобы настроить Webpack, Babel и Autoprefixer. А когда очнулась, узнала, что бэкенд-разработчик уже всё сделал. И оно даже работало. Криво-косо, но работало: точки на canvas ставились, зум зумился. Я отпилила от прекрасного дизайна все ненужное и красивенько сверстала.
Остались две проблемы: Edge и Safari.
В Safari и правда все тормозило со страшной силой. Сначала обнаружила, что canvas не вынесен в отдельный композитный слой. Поэтому браузер при каждом обновлении холста перерисовывал весь документ. Добавила канвасу transition: translateZ(0)
, и все стало тормозить быстрее. Потом отрефакторила остальной бакендерский код, избавилась ещё от десятка перерисовок. Интерфейс полетел на первой космической.
Об IE я сразу не заботилась, потому что знала, что игроки будут пользоваться нормальными браузерами. Беда пришла от старшего брата. Если просишь Edge нарисовать квадрат, он категорически отказывается. Говорит: «Но плавные переходы лучше!» — и размывает весь рисунок.
Такая же проблема была у ребят из Reddit. Сначала я решила её с помощью CSS-свойства image-rendering
и флага CanvasRenderingContext2D.imageSmoothingEnabled
. Но перед запуском оказалось, что Edge косячит при общении с сервером через вебсокеты. Поэтому я и его объявила ненормальным браузером.
Горжусь, что трижды пыталась принести в код React, Webpack, Babel, LESS и Autoprefixer, но смогла победить себя. В итоге всё написано на чистом ES6+ и CSS, но с модными гридами, вебсокетами и fetch-ем.
Я не хотел писать всё с нуля, поэтому поискал готовое. Оригинальный Place лежит на Github, но там слишком много кода. Я взял простой клон под NodeJS и прошёлся по нему напильником. Именно поэтому, когда за дело взялась Вероника, интерфейс уже как-то работал. Вообще, есть уйма клонов, выбирайте для себя любой.
В коде выбранного клона пришлось пофиксить уязвимости и добавить недостающее: таймер, расширенную палитру, перемотку в онлайне, сбор статистики, рисование через вебсокеты вместо REST-запросов, вход через Паспорт (наш внутренний аутентификатор).
Архитектура была такая: пользователь ставил пиксель в браузере, браузер отправлял сообщение через вебсокет на сервер, сервер отправлял сообщение об изменении холста в очередь (Apache Kafka). Потом серверы забирали данные из очереди и отправляли всем клиентам. Выше оригинальная схема от автора клона, на которой клиенты ещё общаются с сервером с помощью REST-запросов.
Мы планировали сделать перемотку, поэтому я решил кэшировать снэпшоты холста с версиями. При старте сервер должен был взять последний снэпшот из кэша, а не строить его с нуля. Тот же снэпшот получал клиент при подключении к серверу, отчего загрузка холста была почти мгновенной. Можно было отправить клиенту снэпшот любой версии и таким образом организовать перемотку. Новый снэпшот сохранялся после изменения каждых ста пикселей.
Примерно через сутки после начала игры случился инцидент. Я исправил баг и перезапустил сервер. А пользователи увидели, что часть нарисованных точек пропала.
Дело было в том, что при подключении к серверу клиент запрашивал конкретную версию холста. Поэтому после старта сервер взял из кэша не последний снэпшот, а какую-то старую версию, которую запросил один из клиентов. Пользователи не увидели свежие пиксели и начали рисовать по-новой, а сервер продолжил складывать данные в очередь.
Когда я понял, в чём дело, я почистил кэш. Сервер заново загрузил данные из очереди и сгенерировал актуальную версию холста. Получился забавный эффект: новые рисунки пользователей наложились на предыдущие, так как вернулось всё потерянное и добавились новые изменения. Фикс был быстрый, поэтому ничего не испортилось:
В общем, не зря говорят, что инвалидация кэшей — это одна из двух сложнейших задач в компьютерных науках.
Забавно, что я, пока чинил кэш, случайно выключил аутентификацию. Тут же нашёлся коллега-кулхакер, который закрасил скриптом несколько тысяч пикселей за пару минут. Я выкатил фикс, но зелёная полоса осталась:
Ещё я хотел после окончания игры сделать хороший таймлапс и посчитать статистику. Для этого я решил завести базу данных и сохранять в ней для каждой нарисованной точки координаты, цвет, дату и время, а также идентификатор пользователя.
Сервер был под NodeJS, поэтому я выбрал LokiJS. Эту базу хвалили за простоту и скорость работы, потому что все данные хранятся в памяти и автоматически записываются на диск через заданные интервалы времени. Для моей задачи подходило.
Я настроил сохранение раз в 1 минуту. Протестировал локально, в том числе под нагрузкой — всё работало как часы. А на боевой площадке происходило что-то паранормальное. Данные сохранялись на диск не по расписанию, а по собственному желанию. Например, в течение нескольких часов не сохранялись ни разу. За три дня я так и не нашёл причины этого поведения. В итоге, много статистики потерялось при перезапусках сервера.
Однако я был готов к этому с самого начала, потому что таймлапс уж очень был нужен. Каждую минуту я сохранял холст в виде картинки в файл. Получилось несколько гигабайт картинок, зато видео с таймлапсом было записано и озвучено парой команд:
$ ffmpeg -pattern_type glob \
-i "*.png" \
-c:v libx264 \
-vf format=yuv420p \
timelapse.mp4
$ ffmpeg -i timelapse.mp4 \
-i sci-fi.mp3 \
256.mp4
После начала игры все быстро поняли, что один в поле не воин. Началась самоорганизация в Стаффе, нашей внутренней соцсети:
Я тоже в этом поучаствовал:
Но даже с командой было неинтересно рисовать масштабную картинку. Я понял это после первых семи пикселей радуги. Не радовало даже, что коллеги сделали кучу полезных инструментов:
Я ждал от Дня программиста большего. И дождался — на второй день Игорь опубликовал в Стаффе такой фрагмент кода и стал раздавать желающим API-ключи:
Это было уже что-то!
Для разминки я написал бота, который рисовал заданную картинку. Это было не очень весело. Потом придумал создавать картинку алгоритмически. Получился бот, который загнул идеально круглую радугу. Но это тоже было скучно.
Я понял, что нескучный бот должен не просто рисовать пиксели, а взаимодействовать с окружающим миром и чужим творчеством. Но нужно было избежать вандализма, потому что бот — это сила, а с силой должна идти ответственность.
Можно было нарисовать часы с текущим временем. Или движущуюся картинку, которая ползёт по холсту и затирает чужие рисунки… чтобы потом их восстановить. И тут я придумал сюжет, который объединил эти две идеи.
Часы стали таймером обратного отсчёта, движущаяся картинка — взлетающей ракетой. К тому же, ракету очень удобно рисовать — сначала на пиксель удлиняешь верхнюю часть, потом на пиксель укорачиваешь нижнюю. Это не только хорошо смотрится, но и экономит пиксели, ведь задержку при рисовании через API никто не отменял.
Это должен был быть самый медленный полёт ракеты в истории человечества. С текущей задержкой за пару часов я мог сдвинуть ракету всего на несколько пикселей. Нужно было либо уменьшать ракету, либо двигать её скачками, либо смириться с тем, что лететь она будет сутки. Поделился муками выбора с Игорем, а он со словами «Твори добро!» внезапно отсыпал без малого 50 ключей для API. С таким количеством ключей ракета могла достичь скорости один пиксель в секунду!
Осталось немного: выбрать дизайн ракеты и написать весь код. Я отбросил мультяшные ракеты и выбрал ракету-носитель «Восток». Сразу стало понятно, что полёт ракеты должен заканчиваться выводом на орбиту корабля Восток-1.
Почему «Восток»? Потому что прямо сейчас куча инженеров из Контура занимается секретным проектом с кодовым названием Vostok. Я хотел, чтобы парням было приятно.
Я настроил бота, запустил таймер обратного отсчёта, позвал зрителей через Стафф. Ракета взлетела. И тут я понял, как нелепо выглядит ракета в космосе с неотделёнными разгонными блоками и первой ступенью. Чудом нашёл 10 свободных минут, чтобы добавить отделение ступени и перезапустить бота. Так что это был не только самый медленный полет ракеты в истории человечества, но и первый полёт ракеты, в середине которого поменяли её конструкцию.
Было приятно наблюдать, как коллеги стирали копию ракеты, из-за бага оставшуюся на стартовом столе. Пририсовывают однопиксельного человечка в окошко ракеты. Переделывают слово «поехали» в «понаехали». Вообще, радовало, что все вели себя культурно, несмотря на отсутствие правил. Даже когда место на холсте закончилось:
Кстати, без NSFW-контента не обошлось. Кто-то из нарисованного моим первым нескучным ботом слова TRON упорно делал слово PRON.
Ваня потом рассказал, что 13 сентября на холсте одновременно рисовало 1630 человек и десяток ботов, то есть примерно треть всех работников компании. В среднем к серверам было подключено 440 клиентов, а в дневные часы — 840.
В итоге у нас получилась такая картинка:
И такой таймлапс. Моя ракета взлетает на 27 секунде:
А вы программируете по праздникам и для праздников? Расскажите нам в комментариях.
P. S. Если интересно, о чём мы не рассказываем на Хабре, подписывайтесь на наш канал в Телеграме.
Метки: author green_hippo разработка игр программирование визуализация данных canvas блог компании скб контур день программиста reddit космос восток |
Наш рецепт отказоустойчивого VPN-сервера на базе tinc, OpenVPN, Linux |
ep1
, ep2
и ep3
). Кроме того, в сети присутствовал гипервизор с сервисами клиента (hpv1
). На всех машинах установили Ubuntu Server 16.04.$ sudo apt-get update && sudo apt-get install tinс
l2vpnnet
. Создаем структуру каталогов:$ sudo mkdir -p /etc/tinc/l2vpnnet/hosts
/etc/tinc/l2vpnnet
создаем файл tinc.conf
и наполняем его следующим содержимым:# Имя текущей машины
Name = ep1
# Тип сети, в нашем случае — L2
Mode = switch
# Интерфейс, который мы будем использовать
Interface = tap0
# По умолчанию используется протокол UDP
Port = 655
# Записываем имена всех остальных хостов, к которым мы будем подключаться
ConnectTo = ep2
ConnectTo = ep3
ConnectTo = hpv1
/etc/tinc/l2vpnnet/ep1
и вносим в него параметры:# Публичный адрес и порт
Address = 100.101.102.103 655
# Используемые алгоритмы шифрования и аутентификации
Cipher = aes-128-cbc
Digest = sha1
# Для уменьшения задержек рекомендуем также выключать сжатие
Compression = 0
$ cd /etc/tinc/l2vpnnet && sudo tincd -n l2vpnnet -K2048
Generating 2048 bits keys:
............................................+++ p
.................................+++ q
Done.
Please enter a file to save private RSA key to [/etc/tinc/l2vpnnet/rsa_key.priv]:
Please enter a file to save public RSA key to [/etc/tinc/l2vpnnet/hosts/ep1]:
/etc/tinc/l2vpnnet/hosts/ep1|ep2|ep3|hpv1
) необходимо разместить у всех участников сети в каталоге /etc/tinc/l2vpnnet/hosts
./etc/tinc/nets.boot
, чтобы tinc запускал VPN к нашей сети автоматически при загрузке:$ sudo cat nets.boot
#This file contains all names of the networks to be started
#on system startup.
l2vpnnet
/etc/network/interfaces
описание параметров устройства tap0
:# Устройство запускается автоматически при старте системы
auto tap0
# Указываем режим конфигурации manual, так как IP мы назначим уже на bridge
iface tap0 inet manual
# Создание устройства перед запуском tinc
pre-up ip tuntap add dev $IFACE mode tap
# ... и его удаление после остановки
post-down ip tuntap del dev $IFACE mode tap
# Собственно, запуск tinc с настроенной нами сетью
tinc-net l2vpnnet
10.10.10.0/24
. Настроим bridge-интерфейс и назначим ему IP — для этого внесем в /etc/network/interfaces
такую информацию:auto br0
iface br0 inet static
# Естественно, IP должен быть разным для хостов
address 10.10.10.1
netmask 255.255.255.0
# Указываем, что в бридже наш интерфейс tinc vpn
bridge_ports tap0
# Отключаем протокол spanning tree для bridge-интерфейса
bridge_stp off
# Максимальное время ожидания готовности моста
bridge_maxwait 5
# Отключаем задержку при форвардинге
bridge_fd 0
$ sudo ifup tap0 && sudo ifup br0
$ ping -c3 10.10.10.2
PING 10.10.10.2 (10.10.10.2) 56(84) bytes of data.
64 bytes from 10.10.10.2: icmp_seq=1 ttl=64 time=3.99 ms
64 bytes from 10.10.10.2: icmp_seq=2 ttl=64 time=1.19 ms
64 bytes from 10.10.10.2: icmp_seq=3 ttl=64 time=1.07 ms
--- 10.10.10.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 1.075/2.087/3.994/1.349 ms
$ sudo apt-get update && sudo apt-get install openvpn easy-rsa
vpn.compa.ny. IN A 100.101.102.103
vpn.compa.ny. IN A 50.51.52.53
vpn.compa.ny. IN A 1.1.1.1
Node 1 10.10.10.100-10.10.10.129
Node 2 10.10.10.130-10.10.10.159
Node 2 10.10.10.160-10.10.10.189
$ cd /etc/openvpn
$ sudo -s
# make-cadir ca
# mkdir keys
# chmod 700 keys
# exit
vars
, установив следующие значения:# Каталог с easy-rsa
export EASY_RSA="`pwd`"
# Путь к openssl, pkcs11-tool, grep
export OPENSSL="openssl"
export PKCS11TOOL="pkcs11-tool"
export GREP="grep"
# Конфиг openssl
export KEY_CONFIG=`$EASY_RSA/whichopensslcnf $EASY_RSA`
# Каталог с ключами
export KEY_DIR="$EASY_RSA/keys"
export PKCS11_MODULE_PATH="dummy"
export PKCS11_PIN="dummy"
# Размер ключа
export KEY_SIZE=2048
# CA-ключ будет жить 10 лет
export CA_EXPIRE=3650
# Описываем нашу организацию: страна, регион,
# город, наименование, e-mail и подразделение
export KEY_COUNTRY="RU"
export KEY_PROVINCE="Magadan region"
export KEY_CITY="Susuman"
export KEY_ORG="Company"
export KEY_EMAIL="info@compa.ny"
export KEY_OU="IT"
export KEY_NAME="UnbreakableVPN"
# . vars
# ./clean-all
# ./build-ca
Generating a 2048 bit RSA private key
..........................+++
.+++
writing new private key to 'ca.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [RU]:
State or Province Name (full name) [Magadan region]:
Locality Name (eg, city) [Susuman]:
Organization Name (eg, company) [Company]:
Organizational Unit Name (eg, section) [IT]:
Common Name (eg, your name or your server's hostname) [Company CA]:
Name [UnbreakableVPN]:
Email Address [info@compa.ny]:
# ./build-dh
Generating DH parameters, 2048 bit long safe prime, generator 2
This is going to take a long time
…
# ./build-key-server server
# openvpn --genkey --secret keys/ta.key
# ./build-key testuser
# ./revoke-full testuser
# cd keys
# mkdir /etc/openvpn/.keys
# cp ca.crt server.crt server.key dh2048.pem ta.key crl.pem /etc/openvpn/.keys
# exit
/etc/openvpn/server.conf
:# Устанавливаем подробность ведения журнала
verb 4
# Порт и протокол подключения
port 1194
proto tcp-server
# Режим и способ аутентификации
mode server
tls-server
# Определяем MTU
tun-mtu 1500
# Определяем имя и тип интерфейса, который будет обслуживать клиентов
dev ovpn-clients
dev-type tap
# Указываем, что TA-ключ используется в режиме сервера
key-direction 0
# Описываем ключевую информацию
cert /etc/openvpn/.keys/server.crt
key /etc/openvpn/.keys/server.key
dh /etc/openvpn/.keys/dh2048.pem
tls-auth /etc/openvpn/.keys/ta.key
crl-verify /etc/openvpn/.keys/crl.pem
# Определяем протоколы аутентификации и шифрования
auth sha1
cipher AES-128-CBC
# Опция, указывающая, что устройство будет создаваться единожды
# на все время работы сервера
persist-tun
# Указываем тип топологии и пул
topology subnet
server-bridge 10.10.10.1 255.255.255.0 10.10.10.100 10.10.10.129
# Указываем маршрут по умолчанию через туннель и определяем
# внутренние DNS
push "redirect-gateway autolocal"
push "dhcp-option DNS 10.10.10.200"
push "dhcp-option DNS 10.20.20.200"
# Проверяем доступность подключенного клиента раз в 10 секунд,
# таймаут подключения — 2 минуты
keepalive 10 120
# То самое ограничение в 30 клиентов
max-clients 30
# Локальные привилегии демона openvpn
user nobody
group nogroup
# Позволяет удаленному клиенту подключаться с любого IP и порта
float
# Путь к файлу журнала
log /var/log/openvpn-server.log
/etc/network/interfaces
:auto ovpn-clients
iface ovpn-clients inet manual
pre-up ip tuntap add dev $IFACE mode tap
post-up systemctl start openvpn@server.service
pre-down systemctl stop openvpn@server.service
post-down ip tuntap del dev $IFACE mode tap
br0
: ...
netmask 255.255.255.0
bridge_ports tap0
bridge_ports ovpn_clients
bridge_stp off
...
$ sudo ifup ovpn-clients && sudo ifdown br0 && sudo ifup br0
$ sudo -s
# cd /etc/openvpn/ca
# ./build-key PetrovIvan
# exit
$ vim PetrovInan.ovpn
# Указываем тип подключения, тип устройства и протокол
client
dev tap
proto tcp
# Определяем MTU такой же, как и на сервере
tun-mtu 1500
# Указываем узел и порт подключения
remote vpn.compa.ny 1194
# Отказываемся от постоянного прослушивания порта
nobind
# Опция, которая позволяет не перечитывать ключи для каждого соединения
persist-key
persist-tun
# Корректируем MSS
mssfix
# Указываем, что будем использовать TA как TLS-клиент
key-direction 1
ns-cert-type server
remote-cert-tls server
auth sha1
cipher AES-128-CBC
verb 4
keepalive 10 40
### Сюда вставляем содержимое ca.crt
### Сюда вставляем содержимое ta.key
### Сюда вставляем содержимое PetrovIvan.crt
### Сюда вставляем содержимое PetrovIvan.key
$ ./revoke-all PetrovIvan
crl.pem
и выполняем:$ sudo service openvpn reload
server.conf
отсутствует опция persist-key
. Это позволяет обновить ключевую информацию во время выполнения reload
— иначе бы для этого потребовался рестарт демона.reload
для OpenVPN мы используем Chef. Очевидно, для этой цели подойдут любые другие средства автоматического развертывания конфигураций (Ansible, Puppet…) или даже простой shell-скрипт.
Метки: author gserge системное администрирование сетевые технологии настройка linux блог компании флант vpn openvpn tinc linux |
Разбираемся с новым sync.Map в Go 1.9 |
Одним из нововведений в Go 1.9 было добавление в стандартную библиотеку нового типа sync.Map, и если вы ещё не разобрались что это и для чего он нужен, то эта статья для вас.
Для тех, кому интересен только вывод, TL;DR:
если у вас высоконагруженная (и 100нс решают) система с большим количеством ядер процессора (32+), вы можете захотеть использовать sync.Map вместо стандартного map+sync.RWMutex. В остальных случаях, sync.Map особо не нужен.
Если же интересны подробности, то давайте начнем с основ.
Если вы работаете с данными в формате "ключ"-"значение", то всё что вам нужно это встроенный тип map
(карта). Хорошее введение, как пользоваться map есть в Effective Go и блог-посте "Go Maps in Action".
map
— это generic структура данных, в которой ключом может быть любой тип, кроме слайсов и функций, а значением — вообще любой тип. По сути это хорошо оптимизированная хеш-таблица. Если вам интересно внутреннее устройство map — на прошлом GopherCon был очень хороший доклад на эту тему.
Вспомним как пользоваться map:
// инициализация
m := make(map[string]int)
// запись
m["habr"] = 42
// чтение
val := m["habr"]
// чтение с comma,ok
val, ok := m["habr"] // ok равен true, если ключ найден
// итерация
for k, v := range m { ... }
// удаление
delete(m, "habr")
Во время итерации значения в map могут изменяться.
Go, как известно, является языком созданным для написания concurrent программ — программ, который эффективно работают на мультипроцессорных системах. Но тип map не безопасен для параллельного доступа. Тоесть для чтения, конечно, безопасен — 1000 горутин могут читать из map без опасений, но вот параллельно в неё ещё и писать — уже нет. До Go 1.8 конкурентный доступ (чтение и запись из разных горутин) могли привести к неопределенности, а после Go 1.8 эта ситуация стала явно выбрасывать панику с сообщением "concurrent map writes".
Решение делать или нет map потокобезопасным было непростым, но было принято не делать — эта безопасность не даётся бесплатно. Там где она не нужна, дополнительные средства синхронизации вроде мьютексов будут излишне замедлять программу, а там где она нужна — не составляет труда реализовать эту безопасность с помощью sync.Mutex.
В текущей реализации map очень быстр:
Такой компромисс между скоростью и потокобезопасностью, при этом оставляя возможность иметь и первый и второй вариант. Либо у вас сверхбыстрый map безо всяких мьютексов, либо чуть медленнее, но безопасен для параллельного доступа. Важно тут понимать, что в Go использование переменной параллельно несколькими горутинами — это не далеко единственный способ писать concurrent-программы, поэтому кейс этот не такой частый, как может показаться вначале.
Давайте, посмотрим, как это делается.
Реализация потокобезопасной map очень проста — создаём новую структуру данных и встраиваем в неё мьютекс. Структуру можно назвать как угодно — хоть MyMap, но есть смысл дать ей осмысленное имя — скорее всего вы решаете какую-то конкретную задачу.
type Counters struct {
sync.Mutex
m map[string]int
}
Мьютекс никак инициализировать не нужно, его "нулевое значение" это разлоченный мьютекс, готовый к использованию, а карту таки нужно, поэтому будет удобно (но не обязательно) создать функцию-конструктор:
func NewCounters() *Counters {
return &Counters{
m: make(map[string]int),
}
}
Теперь у переменной типа Counters будет метод Lock()
и Unlock()
, но если мы хотим упростить себе жизнь и использовать этот тип из других пакетов, то будет также удобно сделать функции обёртки вроде Load()
и Store()
. В таком случае мьютекс можно не встраивать, а просто сделать "приватным" полем:
type Counters struct {
mx sync.Mutex
m map[string]int
}
func (c *Counters) Load(key string) (int, bool) {
c.mx.Lock()
defer c.mx.Unlock()
val, ok := c.m[key]
return val, ok
}
func (c *Counters) Store(key string, value int) {
c.mx.Lock()
defer c.mx.Unlock()
c.m[key] = value
}
Тут нужно обратить внимание на два момента:
defer
имеет небольшой оверхед (порядка 50-100 наносекунд), поэтому если у вас код для высоконагруженной системы и 100 наносекунд имеют значение, то вам может быть выгодней не использовать defer
Get()
и Store()
должны быть определены для указателя на Counters
, а не на Counters
(тоесть не func (c Counters) Load(key string) int { ... }
, потому что в таком случае значение ресивера (c
) копируется, вместе с чем скопируется и мьютекс в нашей структуре, что лишает всю затею смысла и приводит к проблемам.Вы также можете, если нужно, определить методы Delete()
и Range()
, чтобы защищать мьютексом map во время удаления и итерации по ней.
Кстати, обратите внимание, я намеренно пишу "если нужно", потому что вы всегда решаете конкретную задачу и в каждом конкретном случае у вас могут разные профили использования. Если вам не нужен Range()
— не тратьте время на его реализацию. Когда нужно будет — всегда сможете добавить. Keep it simple.
Теперь мы можем легко использовать нашу безопасную структуру данных:
counters := NewCounters()
counters.Store("habr", 42)
v, ok := counters.Load("habr")
В зависимости, опять же, от конкретной задачи, можно делать любые удобные вам методы. Например, для счётчиков удобно делать увеличение значения. С обычной map мы бы делали что-то вроде:
counters["habr"]++
а для нашей структуры можем сделать отдельный метод:
func (c *Counters) Inc(key string) {
c.mx.Lock()
defer c.mx.Unlock()
c.m[key]++
}
...
counters.Inc("habr")
Но часто у работы с данными в формате "ключ"-"значение", паттерн доступа неравномерный — либо частая запись, и редкое чтение, либо наоборот. Типичный случай — обновление происходит редко, а итерация (range) по всем значениям — часто. Чтение, как мы помним, из map — безопасно, но сейчас мы будем лочиться на каждой операции чтения, теряя без особой выгоды время на ожидание разблокировки.
В стандартной библиотеке для решения этой ситуации есть тип sync.RWMutex. Помимо Lock()/Unlock()
, у RWMutex есть отдельные аналогичные методы только для чтения — RLock()/RUnlock()
. Если метод нуждается только в чтении — он использует RLock()
, который не заблокирует другие операции чтения, но заблокирует операцию записи и наоборот. Давай обновим наш код:
type Counters struct {
mx sync.RWMutex
m map[string]int
}
...
func (c *Counters) Load(key string) (int, bool) {
c.mx.RLock()
defer c.mx.RUnlock()
val, ok := c.m[key]
return val, ok
}
Решения map+sync.RWMutex
являются почти стандартом для map, которые должны использоваться из разных горутин. Они очень быстрые.
До тех пор, пока у вас не появляется 64 ядра и большое количество одновременных чтений.
Если посмотреть на код sync.RWMutex, то можно увидеть, что при блокировке на чтение, каждая горутина должна обновить поле readerCount
— простой счётчик. Это делается атомарно с помощью функции из пакета sync/atomic atomic.AddInt32(). Эти функции оптимизированы под архитектуру конкретного процессора и реализованы на ассемблере.
Когда каждое ядро процессора обновляет счётчик, оно сбрасывает кеш для этого адреса в памяти для всех остальных ядер и объявляет, что владеет актуальным значением для этого адреса. Следующее ядро, прежде чем обновить счётчик, должно сначала вычитать это значение из кеша другого ядра.
На современном железе передача между L2 кешем занимает что-то около 40 наносекунд. Это немного, но когда много ядер одновременно пытаются обновить счётчик, каждое из них становится в очередь и ждёт эту инвалидацию и вычитывание из кеша. Операция, которая должна укладываться в константное время внезапно становится O(N) по количеству ядер. Эта проблема называется cache contention.
В прошлом году в issue-трекере Go была создана issue #17973 на эту проблему RWMutex. Бенчмарк ниже показывает почти 8-кратное увеличение времени на RLock()/RUnlock() на 64-ядерной машине по мере увеличения количества горутин активно "читающих" (использующих RLock/RUnlock):
А это бенчмарк на одном и том же количестве горутин (256) по мере увеличения количества ядер:
Как видим, очевидная линейная зависимость от количества задействованных ядер процессора.
В стандартной библиотеке map-ы используются довольно много где, в том числе в таких пакетах как encoding/json
, reflect
или expvars
и описанная проблема может приводить к не очень очевидным замедлениям в более высокоуровневом коде, который и не использует напрямую map+RWMutex, а, например, использует reflect.
Собственно, для решения этой проблемы — cache contention в стандартной библиотеке и был добавлен sync.Map.
Итак, ещё раз сделаю акцент — sync.Map решает совершенно конкретную проблему cache contention в стандартной библиотеке для таких случаев, когда ключи в map стабильны (не обновляются часто) и происходит намного больше чтений, чем записей.
Если вы совершенно чётко не идентифицировали в своей программе узкое место из-за cache contention в map+RWMutex, то, вероятнее всего, никакой выгоды от sync.Map
вы не получите, и возможно даже слегка потеряете в скорости.
Ну а если все таки это ваш случай, то давайте посмотрим, как использовать API sync.Map. И использовать его на удивление просто — практически 1-в-1 наш код раньше:
// counters := NewCounters() <--
var counters sync.Map
Запись:
counters.Store("habr", 42)
Чтение:
v, ok := counters.Load("habr")
Удаление:
counters.Delete("habr")
При чтении из sync.Map вам, вероятно также потребуется приведение к нужному типу:
v, ok := counters.Load("habr")
if ok {
val = v.(int)
}
Кроме того, есть ещё метод LoadAndStore(), который возвращает существующее значение, и если его нет, то сохраняет новое, и Range(), которое принимает аргументом функцию для каждого шага итерации:
v2, ok := counters.LoadOrStore("habr2", 13)
counters.Range(func(k, v interface{}) bool {
fmt.Println("key:", k, ", val:", v)
return true // if false, Range stops
})
API обусловлен исключительно паттернами использования в стандартной библиотеке. Сейчас sync.Map
используется в пакетах encoding/{gob/xml/json}, mime, archive/zip, reflect, expvars, net/rpc.
По производительности sync.Map гарантирует константное время доступа к map вне зависимости от количества одновременных чтений и количества ядер. До 4 ядер, sync.Map
при большом количестве параллельных чтений, может быть существенно медленнее, но после начинает выигрывать у map+RWMutex:
Резюмируя — sync.Map
это не универсальная реализация неблокирующей map-структуры на все случаи жизни. Это реализация для конкретного паттерна использования для, преимущественно, стандартной библиотеки. Если ваш паттерн с этим совпадает и вы совершенно чётко знаете, что узкое место в вашей программе это cache contention на map+sync.RWMutex
— смело используйте sync.Map. В противном случае, sync.Map
вам вряд ли поможет.
Если же вам просто лень писать map+RWMutex обертку и высокая производительность совершенно не критична, но нужна потокобезопасная map, то sync.Map
также может быть неплохим вариантом. Но не ожидайте от sync.Map слишком многого для всех случаев.
Так же для вашего случая могут больше подходить други реализации hash-таблиц, например на lock-free алгоритмах. Подобные пакеты были давно, и единственная причина, почему sync.Map находится в стандартной библиотеке — этого его активное использование другими пакетами из стандратной библиотеки.
Метки: author divan0 go mutex rwmutex map sync cache contention |
Eggs Datacenter: как Emercoin позволил реализовать идею распределённого дата-центра на блокчейне |
Блокчейн — это и база хранения информации, и гарант её неизменности. Одним из вариантов применения блокчейна стала запись и хранение в нем важной информации в виде параметров «имя-значение» (NVS). Реализованное в блокчейне Emercoin, это решение позволяет локально сохранять данные публичных SSH-ключей, SSL-сертификатов и DNS-записей, не доверяя конкретному центру. Блокчейн Emercoin позволяет базирующимся на нём проектам быть и децентрализованными, и защищеннёми одновременно. Такова сила криптографии.
Метки: author EmercoinBlog хранилища данных хранение данных хостинг it- инфраструктура блог компании emercoin emercoin eggs datacenter egs ico |