среда, 11 января 2012 г.

Оптимизация процессинга в Windows Azure. Часть 3.

В предыдущем посте я описал подход, позволяющий существенно сократить количество вызовов к azure storage, который может сэкономить много денег. Но тем не менее ваши воркеры продолжают поедать ваши деньги.

А нужны ли вообще воркеры?

Оказывается не нужны. Если у вас небольшое приложение и вы используете очереди для надежной (reliable) асинхронной обработки, причем сама обработка не требует больших вычислительных затрат, то вам и не нужны воркеры. Можете использовать пару методов ToObserver\ToObservable из предыдущего поста, а для оповещений обычный Subject<Unit>.

Отказываться  от очередей в данном случае не надо, так как при масштабировании работу подхватят воркеры.

Scale Down

Как вы уже могли догадаться возможность масштабировать “вниз” в облаке не менее важна, чем масштабирование “вверх”. С учетом всех ранее перечисленных подходов можно любое приложение развернуть на одном Extra Small Instance в Windows Azure за $30 и тысячей транзакций хранилища (меньше $0.01) в месяц, если к нему будет мало обращений. Это уже сопоставимо с ценой shared-хостинга.

На этом история scale down заканчивается и начинается история…

Scale Up\Out

Сразу же рекомендую посмотреть на Autoscale Application Block (кодовое имя WASABi) из комплекта Enterprise Library. Ссылка на Enterprise Library 5.0 Windows Azure Integration Pack. Этот модуль позволяет задавать правила в соответствии с которыми будет изменяться количество экземпляров ролей в вашем приложении.

Но количество ролей позволяет выдерживать вычислительную нагрузку, хотя в большинстве веб-приложений хранилище становится узким местом.

К сожалению Windows Azure тут не исключение. В блоге Windows Azure Storage описаны scalability targets. Вы можете обнаружить очень интересные сведения о том что максимальное количество сообщений очереди, обрабатываемых в секунду – 500 (по другим сведениям это количество транзакций в секунду). Это очень-очень  мало. И надо не забывать что это предельное значение, на практике его достигнуть будет непросто. Кроме того латентность очереди может достигать 100ms.

Первое что необходимо чтобы избежать высокой латентности на маленьких сообщениях в очереди - установить ServicePointManager.UseNagleAlgorithm значение false.

Следующая проблема – максимальный размер сообщения в очереди – 8KB, так как для передачи используется Base64 кодировка, то реально данных можно передать около 6KB, кстати строки по-умолчанию не кодируются. Добрые люди уже придумали как решать такую проблему: http://msdn.microsoft.com/en-us/library/windowsazure/hh690942(v=VS.103).aspx

Масштабирование воркеров

Как вы думаете что будет если взять “наивную” реализацию воркера, как в первом посте и запустить на Extra Large Instance, насколько быстрее будет работать?

На самом деле вообще не будет быстрее. С этой точки зрения большое количество маленьких воркеров лучше чем один большой. Хотя тоже не лучший вариант по словам представителей Microsoft. С другой стороны куча маленьких воркеров будут пинать Azure Storage гораздо чаще, что несомненно отразится на ценнике. Того же можно добиться если запустить вручную несколько потоков с наивным циклом в воркере, развернутом на Medium instance или более крутой машине.

Чтобы этого избежать надо использовать метод CloudQueue.GetMessages. Пример ниже показывает кусок кода для итератора, который потом обрабатывается Rx.

while (true)
{
    var msgsObs = getMessages(32).ToListObservable();
    yield return msgsObs;
    var msgs = msgsObs[0];

    var hasMessages = false;
    foreach (var msg in msgs)
    {
        hasMessages = true;
        idleCount = 0;

        result.OnNext(msg);
    }

    if (!hasMessages)
    {
        var delay = CalulateDelay(idleCount++, MinimumIdleIntervalMs, MaximumIdleIntervalMs, 100);
        if (delay.TotalMilliseconds >= MaximumIdleIntervalMs)
        {
            yield break;
        }

        yield return Observable.Timer(delay).ToListObservable();
    }
}

Обратите внимание что вызов OnNext должен быть упорядочен, чтобы не возникало Race Condition. Это требование указано в Rx Design Guidelines, и если вы его не читали, то крайне рекомендую это сделать.

Кроме того удаление сообщения из очереди в таком коде возлагается на внешний код.

Пример:

from m in queue.ToObservable(notifications)
from _1 in Observable.Start(() => /*work*/, Scheduler.TaskPool)
from _2 in queue.DeleteMessageAsync(m)
select Unit.Default;

Само это выражение не приводит ни к какому эффекту. Для него надо выполнить Subscribe чтобы запустить вычисления. Тогда будет использоваться TaskPool, который довольно эффективно распределяет вычисления по процессорам. Если вычисления длительные (более 10ms - 100ms), то лучше использовать Scheduler.NewThread. Если же у вас IO-bound код, то лучше будет использовать Scheduler.ThreadPool.

Подходы, описанные выше помогут выжать максимум из очереди Windows Azure, оптимально расходуя ресурсы виртуальных машин при этом. Но что делать когда код упрется в ограничение количества сообщений в секунду. Ни добавление воркеров, ни увеличение толщины инстансов не поможет.  В таком случае может помочь секционирование.

Вместо одной очереди вы создаете N очередей. При добавлении сообщения в очередь выбираете случайную. Считываете сразу из всех. Надо как-то разбираться из какой очереди пришло сообщение. Реализация такого нетривиальна и уже есть первый подобный проект на codeplex: http://partitioncloudqueue.codeplex.com/.

Но Rx как всегда рулит и с его помощью очень просто сделать такой partitioning.

На клиенте:

List<CloudQueue> queues = /*...*/;
var observers = queues.Select(q => q.ToObserver(/*notifier*/))
                      .ToList();

var rnd = new Random();
var partitionedObserver = Observer.Create<CloudQueueMessage>(
        m => observers[rnd.Next(observers.Count)].OnNext(m),
        e => observers.ForEach(obs => obs.OnError(e)),
        () => observers.ForEach(obs => obs.OnCompleted())
    );
partitionedObserver.OnNext(new CloudQueueMessage(/*message*/));

На сервере:

IObservable<Unit> ProcessMessages(CloudQueue queue, /*notifier*/, /*scheduler*/)
{
    return from m in queue.ToObservable(/*notifier*/)
           from _1 in Observable.Start(/*action*/, /*scheduler*/)
           from _2 in queue.DeleteMessageAsync(m)
           select Unit.Default;
}

/*.....*/

List<CloudQueue> queues = /*...*/;
queues.Select(q => ProcessMessages(q,/*notifier*/, /*scheduler*/))
      .Merge()
      .Subscribe();

Другой подход, позволяющий решить проблему ограничения на количество сообщений – пакетная передача. Вместо создания множества очередей, вы записываете множество сообщений в один пакет и предаете его. Для этих целей можно использовать CloudBlockBlob. Можно отдельными блоками загружать отдельные сообщения, а потом получить список блоков из блоба. В сообщении при этом передавать только url блоба.

Заключение

Все описанные выше способы помогут вам более эффективно реализовывать процессинг в Windows Azure. Для тех кто дочитал до сюда – сюрприз. Весь код с примерами использования есть на codeplex, а также библиотека для работы с очередями доступна в NuGet.

вторник, 10 января 2012 г.

Оптимизация процессинга в Windows Azure. Часть 2.

В первой части я показал сколько стоит использование воркер-ролей и очередей в Windows Azure и что с этим можно сделать.

Довольной хороший подход – адаптировать интервал опроса новых сообщений и отключать опрос в случае их отсутствия продолжительное время. Но после выключения надо как-то включать.

Для этого был создан extension-метод:

public static IObservable<CloudQueueMessage> ToObservable<T>(
                                                        this CloudQueue queue, 
                                                        IObservable<T> haveMoreMessages)

Этот метод возвращает сообщения очереди в виде IObservable коллекции. Включение опроса осуществляется появлением элемента в последовательности haveMoreMessages.

Теперь о том как реализовать последовательность haveMoreMessages.

Самый дешевый вариант взаимодействия между экземплярами ролей это internal wcf communication. Для того чтобы работать с WCF необходимо определить контракты.

[ServiceContract]
public interface IQueueNotifier
{
    [OperationContract(IsOneWay = true)]
    void MessageAdded(string queueName);

    [OperationContract(IsOneWay = true)]
    void NoMoreMessages(string queueName);
}

Контракт содержит всего два метода оповещения о новом сообщении в очереди и об окончании сообщений.

Реализация тоже тривиальна:

public class QueueNotifier : IQueueNotifier
{
    private ISubject<string> moreMessages = new Subject<string>();
    private ISubject<string> queueCompleted = new Subject<string>();

    public IObservable<string> MoreMessages
    {
        get
        {
            return this.moreMessages;
        }
    }

    public IObservable<string> QueueCompleted
    {
        get
        {
            return this.queueCompleted;
        }
    }

    public void MessageAdded(string queueName)
    {
        moreMessages.OnNext(queueName);
    }

    public void NoMoreMessages(string queueName)
    {
        queueCompleted.OnNext(queueName);
    }
}

Далее комбинируя два потока получаем IObservable<Unit> пригодный для метода, описанного в начале поста.

public static IObservable<Unit> GetQueueNotifications(this QueueNotifier service, string queueName)
{
    return Observable.Create<Unit>(obs =>
    {
        var sub1 = service.MoreMessages
                          .Where(q => q == queueName)
                          .Subscribe(q => obs.OnNext(Unit.Default));

        var sub2 = service.QueueCompleted
                          .Where(q => q == queueName)
                          .Subscribe(q => obs.OnCompleted());

        return new CompositeDisposable(sub1, sub2);
    });
}

Теперь захостив QueueNotifier в воркере можно передавать ему оповещения из других ролей.

Клиентская сторона

Чтобы отправлять оповещения нужно создать ChannelFactory<IQueueNotifier> и получить экземпляр прокси на клиенте.

Далее надо получить IObserver:

public static IObserver<Unit> CreateQueueNotifierObserver(this IQueueNotifier proxy, string queueName)
{
    return Observer.Create<Unit>(
                _ => proxy.MessageAdded(queueName),
                _ => proxy.NoMoreMessages(queueName),
                () => proxy.NoMoreMessages(queueName)
            );
}

Надо помнить что экземпляров воркера может быть много и у вас получится по одному наблюдателю на каждый инстанс воркера. При этом не надо передавать оповещение каждому воркеру, достаточно передать оповещение одному (случайному). В случае высокой нагрузки оповещения будут распределяться равномерно межу воркерами и никто их них не будет “спать”. В случае низкой нагрузки просыпаться будет только один воркер, экономя деньги.

public static IObserver<Unit> CombineObservers(List<IObserver<Unit>> notifiers)
{
    var rnd = new Random();

    return Observer.Create<Unit>(
            u => notifiers[rnd.Next(notifiers.Count)].OnNext(u),
            e => notifiers.ForEach(obs => obs.OnError(e)),
            () => notifiers.ForEach(obs => obs.OnCompleted())
        );
}

Обратите внимание что OnCompleted рассылается всем воркерам, чтобы можно было остановить обработку сообщений.

Остается только скомбинировать отправку сообщения в очередь с отправкой оповещения.

public static IObserver<CloudQueueMessage> ToObserver(this CloudQueue queue, IObserver<Unit> notifier)
{
    var addMessage = Observable.FromAsyncPattern<CloudQueueMessage>(queue.BeginAddMessage, queue.EndAddMessage);

    return Observer.Create<CloudQueueMessage>(
            m => addMessage(m).Subscribe(notifier.OnNext, notifier.OnError),
            notifier.OnError,
            notifier.OnCompleted);
}

Таким образом получается достигнуть того что воркеры не обращаются постоянно к Azure Storage, экономя деньги и ресурсы виртуальных машин. При этом мы получили на клиенте и сервере очень простые интерфейсы, позволяющие тем не менее выполнять сложные действия  с ними.

В следующей части дальнейшая оптимизация, библиотека и пример приложения.

понедельник, 9 января 2012 г.

Оптимизация процессинга в Windows Azure. Часть 1.

Для тех кто не в курсе: Windows Azure – “облачная” платформа Microsoft. Создавая приложения, работающие “в облаке”, у вас есть возможность разделять систему на “роли”. Бывают веб-роли, которые представляют из себя обычные веб-приложения,  бывают также worker-роли (далее воркеры), предназначенные для вычислений.

Для увеличения масштабируемости приложения используется очереди. Сообщения в очередях обрабатываются воркерами, а ставят сообщения чаще всего веб-роли или другие воркеры. Таким образом можно разбить какие-либо длительные операции на небольшие и обрабатывать их асинхронно на любом количестве узлов, так как очереди в Windows Azure специально проектировали для сценария множественных потребителей.

Типовой код для воркера Windows Azure на C# такой:

while (true)
{
    var msg = queue.GetMessage();
    if (msg != null)
    {
        //do some work
        queue.DeleteMessage(msg);
    }
    else
    {
        Thread.Sleep(10000);
    }

    Trace.WriteLine("Working", "Information");
}

Как вы думаете сколько стоит этот воркер. В смысле реальных денег потребляемых таким приложением, развернутым на Windows Azure.

Для этого надо посмотреть цены: https://www.windowsazure.com/en-us/pricing/details/.

Если задеплоить такую роль в одном small экземпляре, то получится $2,88 в день/$86,4 в месяц/~2600 рублей в месяц. Так? А вот и нет…

Есть еще “скрытая” стоимость такой архитектуры, заключается она в том что транзакции к хранилищу тоже оплачиваются https://www.windowsazure.com/en-us/pricing/details/#storage. Всего  $0.01 за 10,000 транзакций. Каждая транзакция – это один запрос к azure storage.

Код выше выполняет один запрос каждые 10 секунд даже если нету никаких сообщений в очереди.
Стоимость такого кода получается 60*60*24*30/(10 * 1000) = $25,92 в месяц. вместе со стоимостью compute hours это выходит $112,32 в месяц. И это даже если код не выполняет никакой работы!

Кроме того SLA гарантирует работоспособность роли 99,95% только при наличии минимум двух инстансов, так что для устойчивости надо еще умножить цену на 2. Итого $250 в месяц.

Вывод

Архитектура, которую предлагает Microsoft для масштабирования довольно дорого стоит. Используйте код из примеров очень осторожно, он может увести ваш проект в большой минус.

Что делать?

Вариант первый – использовать service bus, в нем тоже есть очереди, но API позволяет в одной транзакции ожидать сообщения, а не сразу null возвращать при его отсутствии.

Вариант второй – использовать адаптивную подстройку интервала опроса очереди и выключать опрос в случае отсутствия сообщений.

Второй вариант кажется хорошей идеей так как позволяет масштабировать подход как “вниз”, так и “вверх”. Но тут возникает вопрос, а если мы прекратим прием сообщений, то как его потом возобновить? Видимо надо передать сообщение… Приходим снова к той же проблеме.

Но сигнал к “пробуждению” читателя сообщений можно передавать по более дешевому каналу, например через wcf internal endpoint.

Реализация

Чтобы абстрагироваться от всех деталей с сообщениями, таймаутами и каналами удобно использовать библиотеку Rx. Я использую Experimental версию так как в ней собрано много нужных комбинаторов.

Для начала надо вписать код в концепцию Rx. Длительные операции, вроде вызовов методов Cloud Storage и тайматуов сделать в виде IObservable.

public static IObservable<CloudQueueMessage> ObserveMessages(this CloudQueue queue)
{
    return Observable.Create<CloudQueueMessage>(obs => Iterator(obs, queue));
}

private static IEnumerable<IObservable<object>> Iterator(
                                                    IObserver<CloudQueueMessage> result, 
                                                    CloudQueue queue)
{
    //Observable queue.GetMessage
    var getMessage = Observable.FromAsyncPattern<CloudQueueMessage>(
                                        queue.BeginGetMessage,
                                        queue.EndGetMessage);
    //Observable queue.DeleteMessage
    var deleteMessage = Observable.FromAsyncPattern<CloudQueueMessage>(
                                        queue.BeginDeleteMessage,
                                        queue.EndDeleteMessage);

    while (true)
    {
        //var msg = queue.GetMessage();               
        var msgObs = getMessage().ToListObservable();
        yield return msgObs;
        var msg = msgObs[0];

        if (msg != null)
        {
            //do some work
            result.OnNext(msg);

            //queue.DeleteMessage(msg);                     
            yield return deleteMessage(msg).ToListObservable();
        }
        else
        {
            //Thread.Sleep(10000);
            //Same pattern as above
            yield return Observable.Timer(TimeSpan.FromSeconds(10))
                                   .ToListObservable();
        }

        Trace.WriteLine("Working", "Information");
    }
}

Теперь надо немного изменить код, сделав таймаут адаптивным.

Функция вычисления таймаута:

private static TimeSpan CalulateDelay(int idleCount, int minimumIdleIntervalMs,  int maximumIdleIntervalMs, int deltaBackoffMs)
{
    // Calculate a new sleep interval value that will follow a random exponential back-off curve.
    int delta = (int)((Math.Pow(2.0, (double)idleCount) - 1.0) * (new Random()).Next((int)(deltaBackoffMs * 0.8), (int)(deltaBackoffMs * 1.2)));
    int interval = Math.Min(minimumIdleIntervalMs + delta, maximumIdleIntervalMs);

    // Pass the calculated interval to the dequeue task to enable it to enter into a sleep state for the specified duration.
    return TimeSpan.FromMilliseconds((double)interval);            
}

Честно украдена отсюда.

Сам код воркера:

var idleCount = 0;
while (true)
{
    var msgObs = getMessage().ToListObservable();
    yield return msgObs;
    var msg = msgObs[0];

    if (msg != null)
    {
        idleCount = 0;

        //do some work
        result.OnNext(msg);

        yield return deleteMessage(msg).ToListObservable();
    }
    else
    {
        var delay = 
                CalulateDelay(idleCount++, 
                              MinimumIdleIntervalMs, 
                              MaximumIdleIntervalMs, 
                              100);
        if (delay.TotalMilliseconds >= MaximumIdleIntervalMs)
        {
            yield break;
        }

        yield return Observable.Timer(delay).ToListObservable();
    }
}

Выключать цикл опроса сообщений мы научились, теперь попробуем научиться его включать. Будем считать что “внешний раздражитель”, который будет будить цикл выборки сообщений, выглядит как IObservable<T>.

public static IObservable<CloudQueueMessage> ObserveMessages<T>(
                                                this CloudQueue queue, 
                                                IObservable<T> haveMoreMessages)
{
    var iterator = Observable.Create<CloudQueueMessage>(
                                  obs => Iterator(obs, queue));
    IDisposable subscription = null;

    return Observable.Create<CloudQueueMessage>(
        obs => haveMoreMessages.Subscribe(
            _ =>
            {
                if (subscription == null)
                {
                    subscription = iterator.Subscribe(
                                                obs.OnNext, 
                                                obs.OnError, 
                                                () => subscription = null);
                }
            }, 
            () => subscription.Dispose() ));
}

Код получился запутанный, но при некоторой сноровке читается очень хорошо.

На сегодня все. В следующей части я расскажу как сделать  пробуждение воркеров по сигналу и какими еще способами можно оптимизировать стоимость решения для Windows Azure.

понедельник, 12 декабря 2011 г.

Применимость DDD

До сих пор не утихают холивары на тему DDD\rich vs anemic. С одной стороны апологеты DDD (domain driven design, дизайн на основе предметной области) твердят о том как это круто, с другой стороны говоря что оно не везде подходит. На вопрос где же оно подходит обычно затрудняются ответить или отвечают “for compex domain”, причем примеров применения такого встретить непросто.

понедельник, 21 ноября 2011 г.

В поисках неподвижной точки

Наверное все знают что в C# 3.0 лямбда выражения, которые позволяют записывать анонимные функции (то есть функции без имени).
Например:

seq.Select(x => x * x);

Выражение x => x*x является функцией одного аргумента и возвращает значение квадрата числа.

А теперь попробуем  записать функцию факториала:

Func<int, int> f = x => x > 1 ? x * f(x - 1) : 1;

Компилятор C# такое выражение не компилирует. Ругается на неинициализированную переменную f в правой части. Кстати roslyn такое прожевывает нормально. Тем не менее код выше не является выражением, его нельзя передать параметром в функцию.

Попробуем превратить его в выражение.

fact = f => x => x > 1 ? x * f(x - 1) : 1;

Тип выражения справа получится Func<Func<int,int>, Func<int,int>>, параметром передается рекурсивный вызов, чтобы его сформировать надо снова подставить рекурсивный вызов в функцию.  Получится что-то вроде бесконечного вызова
fact(fact(fact(fact(…. но реально число вызовов конечно.

Немного теории

Для функций f которая принимает аргумент и возвращают значения из одного и того же множества (на C# это записывается как Func<T,T>, а в математике T –> T) может существовать “неподвижная точка” x, для которой f(x) = x. Чтобы находить неподвижные точки можно построить комбинатор g:(T->T)->T, такой что g(f) = x и f(x) = x. Функция g называется комбинатором неподвижной точки.

В лямбда исчислении есть теоремы доказывающие существование неподвижных точек у некоторых функций и формулы  комбинаторов. Не все формулы можно перенести в типизированные языки.

Краткая формула для рекурсивного комбинатора неподвижной точки Y выглядит как Y(g) = g(Y(g)). Если выполнить подстановку то получится g(g(Y(g))), выполняя подстановку бесконечное число раз получим как раз то что нам нужно для факториала.

Вернемся к практике

Попробуем написать на C#

static T Y<T>(Func<T, T> f)
{
    return f(Y(f));
}

Но язык C# использует энергичные вычисления и Y-комбинатор сразу попытается посчитать бесконечную рекурсию. Что приведет к StackOverflowException.

Ленивость вычислений как всегда вводится через лямбды.

static Func<T, T> Y<T>(Func<Func<T, T>, Func<T, T>> f)
{
    return x => f(Y(f))(x);
}

После этого вполне можно написать следующий код:

var fact = Y<int>(f => x => x > 1 ? x * f(x - 1) : 1);

Или например

seq.Select(Y<int>(f => x => x > 1 ? x * f(x - 1) : 1));

Таким образом получили возможность записывать анонимную рекурсию в виде выражения.

Заключение

Знание фундаментальной теории очень помогает писать программы и зачастую дает возможность улучшить их крайне неожиданными способами. Изучение таких тем никогда не будет лишним багажом.

понедельник, 24 октября 2011 г.

Октябрьская встреча Russian SharePoint User Group

Ссылка на анонс на сайте RUSUG: http://rusug.net/News/Lists/Posts/Post.aspx?ID=64

Один из докладов на встрече, про использование поиска в приложениях SharePoint, буду читать я. Чтобы больше удовлетворить интерес и потребности аудитории предлагаю выбрать интересующие темы из списка ниже:

  1. Настройка поиска SharePoint Server
  2. Out-of-box возможности поиска
  3. Кастомизация out-of-box функциональности
  4. Использование серверного API поиска
  5. Использование клиентского API поиска в sandboxed решениях
  6. Архитектура поиска SharePoint Server

Все темы в пределах одного доклада не смогу рассказать, но 3-4 из них вполне могу пройти достаточно подробно.

Пишите пожелания в комментах. Приходите на встречу. Но даже если вы не сможете прийти на встречу, то будет видеозапись докладов и ваши пожелания будут учтены.

понедельник, 17 октября 2011 г.

Почему вам нужен SharePoint

Последнее время на различных конференциях я слышу один и тот же вопрос:

А зачем мне нужен SharePoint?

Вопрос банальный, частота его задавания связана с тем что очень много крупных и не очень компаний получают SharePoint вместе с различными пакетами программ Microsoft. Но вразумительных ответов на этот вопрос я пока не слышал.

Ниже “краткий” ответ, может быть вы найдете что-нибудь для себя.

  • Если вы руководитель или ИТ-директор компании более 5 человек и
    • Используете  MS Office
    • Отправляете документы по электронной почте
    • Использует расшаренные папки для хранения документов в электронном виде
    • У вас есть процессы согласования и утверждения документов
    • Храните данные в Excel или Access
    • Хотите создать базу знаний
    • Хотите развернуть helpdesk
    • Пользуетесь средствами средствами Microsoft BI
    • Хотите отображать данные из разных источников в одном месте
    • Хотите развернуть корпоративный портал для сотрудников

    если одно из вышеперечисленного верно, то вам однозначно нужен SharePoint. Он поможет вам создать единое хранилище документов и табличных данных с богатыми возможностями отображения и поиска. SharePoint позволяет создавать решения без среды разработки и написания кода. Продвинутые пользователи самостоятельно смогут создавать и улучшать решения в SharePoint.

  • Если вы менеджер проектов, то вам должны быть знакомы продукты MS Project и Project Server. Последний является надстройкой над SharePoint.
    Но даже без Projet Server вы можете:
    • Создавать отдельные сайты для проектов несколькими кликами мыши, где можно будет размещать и согласовывать документы
    • Хранить и отображать на портале списки задач из Microsoft Project
    • Отслеживать риски и проблемы
    • При необходимости вывести создать на портале SharePoint интерфейс к другим системам управления проектами
    • Получать сводку по вашим проектами
    • Получать отчеты и KPI на портале

  • Если вы архитектор или ведущий разработчик и разрабатываете корпоративный софт, то вам нужен SharePoint потому что:
    • он включает в себя возможности управления документами
    • он имеет надежную систему разграничения доступа
    • он интегрируется с MS Office
    • он позволяет искать по всему содержимому
    • он поддерживает длительные рабочие процессы, которые могут продолжаться больше чем время непрерывной работы серверов
    • он имеет модульный пользовательский интерфейс
    • он позволяет интегрироваться с другими системами
    • он поддерживает масштабируемость всех своих компонент
    • он почти весь функционал SharePoint поддается расширению и кастомизации
    • содержит систему установки и удаления приложений, гораздо проще, чем написание инсталляторов вручную

  • Если вы IT-специалист, то вам обязательно нужен SharePoint. Он вам позволит:
    • Собирать в одном месте данные из различных систем
    • Отображать таблицы, графики, отчеты, KPI на портале
    • Автоматизировать процессы процессы организации с помощью простых инструментов
    • Размещать веб-контент не имея навыков веб-разработки
    • Управлять множеством сервисов со сложной топологией с помощью простого графического интерфейса
    • Заскриптовать любые действия с помощью PowerShell

  • Если вы рядовой .NET разработчик, то вы сможете в SharePoint:
    • Применить уже имеющиеся навыки
      • для разработки интерфейса
      • для создания решений по интеграции с другими системами
      • для создания рабочих процессов
    • Изучив платформу более детально вы сможете создавать любые решения и превратитесь из рядового разработчика в высокооплачиваемого специалиста :)

  • Если вы веб-разработчик, то ваши навыки будут очень востребованы в среде SharePoint
    • для брендинга портала, это сейчас очень востребованная тема
    • для разработки макетов веб-страниц для размещения контента
      (html + css + js)
    • для создания представлений данных и результатов поиска
      (xslt + html + css + js)
    • для приложений на javascript или silverlight, большая часть функциональности SharePoint доступна на клиентской стороне

 

Несмотря на богатые возможности примеров успешных внедрений не так много, как хотелось бы. Это связано с тем что платформа SharePoint сложна, а специалистов не хватает. По большей части не хватает именно разработчиков, которые хорошо владеют функционалом и могут собрать из него решение.

Если у вас будут возникать вопросы по SharePoint , то присоединяйтесь с сообществу http://area51.stackexchange.com/proposals/35899/sharepoint-in-russian, поддержите его развитие и вы сможете получать много полезной информации.