MongoDB – bắt đầu với C# Driver phiên bản 2.0.1

Ở phần 1 minh dùng C# Driver 1.10, trong ví dụ này mình dùng version 2.0.1

– Về cơ bản phiên bản này có sự cải tiến đáng kể và hỗ trợ .NET 4.5 mạnh mẽ hơn

Sau đây là đoạn trong phần 1 nhưng viết theo phong cách version 2.0.1

using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MongoDBLab
{
public class Entity
{
public ObjectId Id { get; set; }

public string Name { get; set; }
}

class Program
{
static void Main(string[] args)
{
Task task = new Task(TestAsync);
task.Start();
task.Wait();

Console.WriteLine(“Press any key to stop.”);
Console.ReadKey();
}

/// <summary>
/// Dùng Driver 2.0.1
/// </summary>
static async void TestAsync()
{
// Lấy tham chiếu tới đối tượng client
var connectionString = “mongodb://localhost”;
var client = new MongoClient(connectionString);

// Lấy 1 tham chiếu tới đối tượng database
var database = client.GetDatabase(“test”);

// Lấy 1 tham chiếu tới đố tượng Collection
var collection = database.GetCollection<Entity>(“entities”);

// Thêm 1 đối tượng
Entity entity = new Entity() { Name = “entity 1” };
await collection.InsertOneAsync(entity);
var id = entity.Id;
var query = Builders<Entity>.Filter.Eq(e => e.Id, id);

var update = Builders<Entity>.Update.Set(e => e.Name, “XXX_YYY_ZZZ”);
await collection.UpdateOneAsync(query, update);
}
}
}

Advertisements

MongoDB – bắt đầu với C# Driver – phần 1

Hôm nay rỗi thời gian làm 1 bài lab về mongoDB theo hướng dẫn trên trang chủ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using MongoDB.Driver.Builders;
using MongoDB.Driver.GridFS;
using MongoDB.Driver.Linq;
using MongoDB.Driver;
using MongoDB.Bson;

namespace MongoDBLab
{
public class Entity
{
public ObjectId Id { get; set; }

public string Name { get; set; }
}

class Program
{
static void Main(string[] args)
{
// Lấy tham chiếu tới đối tượng client
var connectionString = “mongodb://localhost”;
var client = new MongoClient(connectionString);

// Lấy 1 tham chiếu tới đối tượng Server
var server = client.GetServer();

// Lấy 1 tham chiếu tới đối tượng database
var database = server.GetDatabase(“test”);

// Lấy 1 tham chiếu tới đố tượng Collection
var collection = database.GetCollection<Entity>(“entities”);

// Thêm 1 đối tượng
Entity entity = new Entity() { Name = “entity 1” };
collection.Insert(entity);
var id = entity.Id;
var query = Query<Entity>.EQ(e => e.Id, id);

var update = Update<Entity>.Set(e => e.Name, “XXX”);
collection.Update(query, update);

collection.Remove(query);
Console.WriteLine(“Press any key to stop.”);
Console.ReadKey();
}
}
}

LINK: http://mongodb.github.io/mongo-csharp-driver/1.10/getting_started/

Creating a Data Repository using Dapper: 10 Years of .Net Compressed into Weeks #12

This post looks at writing a repository implementation using the Micro-ORM named Dapper. This post is part of a blog series ASP.Net 10 Years On. Even though this is part of a series I have tried to make each post standalone.

Update

I’ve improved upon this implementation. See this post for details.

Original

In the previous post we compared options for our repository implementation and we concluded that a Micro-ORM was best suited for our requirements. We decided on Dapper as our tool. To recap, we chose a Micro-ORM as we wanted to balance automation and control over SQL queries in the application. We’re happy for the nontrivial stuff to be automated but we want control over any SQL that has a JOIN or more.

To kick off our repository we start with a base repository interface. All domain level repository interfaces will implement this interface as it will offer the convention we seek in making all repositories uniform:

public interface IRepository<T> where T : EntityBase, IAggregateRoot
{
    void Add(T item);
    void Remove(T item);
    void Update(T item);
    T FindByID(Guid id);
    IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
    IEnumerable<T> FindAll();
}

Here is how the solution structure looks in Visual Studio. The IRepository is situated in the .Infrastructure project, since it is generic but the ICompetitionRepository can be found in the .Model project since it’s domain specific:

Screen Shot 2012 12 13 at 23 25 00

Next we move to the .Dapper project and add a concrete implementation of the IRepository interface. In our requirements for the repository we desired convention. All concrete implementations of the domain specific interfaces such asICompetitionRepository will inherit from this base repository class. This provides the convention that all domain entities that implement IAggregateRoot have CRUD operations and the ID property will always be a GUID.

public abstract class Repository<T> : IRepository<T> where T : EntityBase, IAggregateRoot
{
    private readonly string _tableName;

    internal IDbConnection Connection
    {
        get
        {
            return new SqlConnection(ConfigurationManager.ConnectionStrings["SmsQuizConnection"].ConnectionString);
        }
    }

    public Repository(string tableName)
    {
        _tableName = tableName;
    }

 internal virtual dynamic Mapping(T item)
    {
        return item;
    }

    public virtual void Add(T item)
    {
        using (IDbConnection cn = Connection)
        {
            var parameters = (object)Mapping(item);
            cn.Open();
            item.ID = cn.Insert<Guid>(_tableName, parameters);
        }
    }

    public virtual void Update(T item)
    {
        using (IDbConnection cn = Connection)
        {
            var parameters = (object)Mapping(item);
            cn.Open();
            cn.Update(_tableName, parameters);
        }
    }

    public virtual void Remove(T item)
    {
        using (IDbConnection cn = Connection)
        {
            cn.Open();
            cn.Execute("DELETE FROM " + _tableName + " WHERE ID=@ID", new { ID = item.ID });
        }
    }

    public virtual T FindByID(Guid id)
    {
        T item = default(T);

        using (IDbConnection cn = Connection)
        {
            cn.Open();
            item = cn.Query<T>("SELECT * FROM " + _tableName + " WHERE ID=@ID", new { ID = id }).SingleOrDefault();
        }

        return item;
    }

    public virtual IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
    {
        IEnumerable<T> items = null;

        // extract the dynamic sql query and parameters from predicate
        QueryResult result = DynamicQuery.GetDynamicQuery(_tableName, predicate);

        using (IDbConnection cn = Connection)
        {
            cn.Open();
            items = cn.Query<T>(result.Sql, (object)result.Param);
        }

        return items;
    }

    public virtual IEnumerable<T> FindAll()
    {
        IEnumerable<T> items = null;

        using (IDbConnection cn = Connection)
        {
            cn.Open();
            items = cn.Query<T>("SELECT * FROM " + _tableName);
        }

        return items;
    }
}

The CompetitionRepository implementation will look as below. Assuming that a domain entities properties are primitive types and are named the same as the corresponding database table our code will “just work” and the CRUD operations will be implemented by way of convention:

public sealed class Competitionepository : Repository<Competition>, ICompetitionRepository
{
    public CompetitionRepository() : base("Competitions") { }
}

Note how all the CRUD methods are virtual, so in the event we want to take control of a method we can simply override the method.

Some additional methods have been added to provide additional automation/convention of repetitive tasks. The additional features are:

  • An extension method for Dapper that dynamically generates the INSERT SQL query.
  • An extension method for Dapper that dynamically generates the UPDATE SQL query.
  • A utility method that will dynamically generate simple and ONLY simple lambda expressions into SQL.

Here is the code for the first two extension methods for Dapper:

public static class DapperExtensions
{
    public static T Insert<T>(this IDbConnection cnn, string tableName, dynamic param)
    {
        IEnumerable<T> result = SqlMapper.Query<T>(cnn, DynamicQuery.GetInsertQuery(tableName, param), param);
        return result.First();
    }

    public static void Update(this IDbConnection cnn, string tableName, dynamic param)
    {
        SqlMapper.Execute(cnn, DynamicQuery.GetUpdateQuery(tableName, param), param);
    }
}

The three add-ons we discussed all point to the static methods that begin with DynamicQuery.[SomeMethod]. The static methods available in this class are:

  1. DynamicQuery.GetInsertQuery();
  2. DynamicQuery.GetUpdateQuery();
  3. DynamicQuery.GetDynamicQuery();

These methods do as their name suggests and generate a SQL query based on the property names of the dynamic ‘param’ variable passed by value to the method. The 3rd method GetDynamicQuery() accepts a lambda expression that will be converted into a simple SQL query. The dynamic query generator will ONLY work for basic queries with no JOINS. Any SQL query that has a JOIN or more must be hand crafted.

I wrote these utilities to accompany this blog series as a learning exercise so they are not production ready or performance tested (yet!), but with that said you can find the source code here.

Why not just use an ORM?

Most of the functionality described so far you also get when using an ORM. So why go to all this trouble to get the same thing? We really start to see the benefit of this implementation when things become a little more complex.

Mapping Properties when Saving

For example, the User domain entity looks as follows:

public sealed class User : EntityBase, IAggregateRoot
{
    public string Username { get; set; }
    public EncryptedString Password { get; set; }
}

The slight difference when inserting or updating this entity to the database is the property type named Password which has a data type of EncryptedString. This type is a custom value object in our domain model that provides string encryption. Since this is not a primitive type we need some way of mapping the correct property value to the database table column. In the database we have a Users table with three columns:

  • ID
  • Username
  • Password

In this instance we need to map the value of the Password property to the table column Password. Even though they are named the same the property accessor to get the encrypted password value is not the same as if it were a string or int data type. We can perform such a mapping by simply overriding the virtual Mapping method:

public sealed class UserRepository : Repository<User>, IUserRepository
{
    public UserRepository() : base("Users") { }

    internal override dynamic Mapping(User item)
    {
        return new
        {
            ID = item.ID,
            Username = item.Username,
            Password = item.Password.EncryptedValue
        };
    }
}

Mapping Properties when Retrieving Data

As we’ve discussed, if our entity matches the database table structure like-for-like then everything just works. But what happens when this is not the case? What if a single domain entity is made up of data across three database tables?

Following up our UserRepository example we need to set the value of the EncryptedString property type as follows:

public override User FindByID(Guid id)
{
    User item = null;

    using (IDbConnection cn = Connection)
    {
        cn.Open();
        var result = cn.Query("SELECT * FROM Users WHERE ID=@ID", new { ID = id }).SingleOrDefault();

        if (result != null)
        {
            item = new User();
            item.ID = result.ID;
            item.Username = result.Username;
            /* The custom mapping */
            item.Password = new EncryptedString(result.Password);
        }
    }

    return item;
}

Whenever we want to take control we can use the full range of methods from the Dapper documentation to meet our requirements. If we need to add any very custom code that goes beyond what Dapper offers we can just code this manually if we choose to.

The key point is that we never have to fight a framework when we need explicit control. We use convention to make the simple cases “just work” so we can focus on getting the more complex stuff right.

Isn’t it bad to use Dynamic?

The use of the dynamic type in C# will often polarise developers into love or hate categories. There are arguments relating to performance and that the use of dynamic is more error prone since possible runtime errors will not be picked up by the compiler at compile time in the same way static types are.

Performance

The performance benchmarks from the Dapper documentation indicate that using dynamic in the data access code doesn’t result in a performance hit.

For more detail on how dynamic works there is a great answer to a question at Stackoverflow.com.

Lack of static typing

In our examples we used dynamic to map properties such as:

return new
{
    ID = item.ID,
    Username = item.Username,
    Password = item.Password.EncryptedValue
};

I would contest that this is little different from manually mapping properties from a data reader object such as the following line of code:

item.Username = reader["Username"].ToString();

In either example if I make a spelling mistake I will get a runtime error regardless!

Future move to NoSql?

In the requirements there was a possibility moving the database to a cloud computing/NoSQL platform. In order to future proof the repository design I wrote a simple NoSql flavour version of the base repository just to double check my interface design makes sense before it becomes too late to change. Here is how the code looks:

public abstract class Repository<T> : IRepository<T> where T : EntityBase, IAggregateRoot
{
    private Dictionary<Guid, T> _db = new Dictionary<Guid,T>();

    public void Add(T item)
    {
        item.ID = Guid.NewGuid();
        _db.Add(item.ID, item);
    }

    public void Remove(T item)
    {
        _db.Remove(item.ID);
    }

    public void Update(T item)
    {
        _db[item.ID] = item;
    }

    public T FindByID(System.Guid id)
    {
        if (_db.ContainsKey(id))
            return _db[id];

        return default(T);
    }

    public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
    {
        return _db.Values.Where(predicate.Compile()).AsQueryable();
    }

    public IEnumerable<T> FindAll()
    {
        return _db.Values.AsQueryable();
    }
}

This is an oversimplification of how NoSQL instances work but it helps in sounding out how the repository interface works when dealing with a key-value pair. I run the repository unit test I wrote for the Dapper implementation of theCompetitionRepository to verify this proof of concept.

LINK: http://www.bradoncode.com/blog/2012/12/creating-data-repository-using-dapper.html

Tổng quan MongoDb

1. NoSQL

1.1. Khái niệm

NoSQL là một khái niệm chỉ về một lớp các hệ cơ sở dữ liệu không sử dụng mô hình quan hệ. (RDBMS). RDBMS vốn tồn tại khá nhiều nhược điểm như có hiệu năng không tốt nếu kết nối dữ liệu nhiều bảng lại hay khi dữ liệu trong một bảng là rất lớn.

1Hình 1 – Carlo Strozzi

NoSQL ra đời năm 1998 bởi Carlo Strozzi khi ông lập mới một hệ cơ sở dữ liệu quan hệ mã nguồn mở nhanh và nhẹ không liên quan đến SQL Vào năm 2009, Eric Evans, nhân viên của Rackspace giới thiệu lại thuật ngữ NoSQL khi Johan Oskarsson của Last.fm muốn tổ chức một hội thảo về cơ sở dữ liệu nguồn mở phân tán. Thuật ngữ NoSQL đánh dấu bước phát triển của thế hệ CSDL mới: phân tán (distributed) + không ràng buộc (non-relational).

1.2. Đặc điểm

  • NoSQL lưu trữ dữ liệu của mình theo dạng cặp giá trị “key – value”. Sử dụng số lượng lớn các node để lưu trữ thông tin – Mô hình phân tán dưới sự kiểm soát phần mềm
  • Chấp nhận dữ liệu bị trùng lặp do một số node sẽ lưu cùng thông tin giống nhau
  • Một truy vấn sẽ được gửi tới nhiều máy cùng lúc, do đó khi một máy nào đó không phục vụ được sẽ không ảnh hưởng lắm đến chất lượng trả về kết quả
  • Phi quan hệ – không có ràng buộc nào cho việc nhất quán dữ liệu
  • Tính nhất quán không theo thời gian thực: Sau mỗi thay đổi CSDL, không cần tác động ngay đến tất cả các CSDL liên quan mà được lan truyền theo thời gian.

1.3. Các dạng NoSQL cơ bản

Key – value data stores: Dữ liệu lưu dưới dạng cặp key – value. Giá trị được truy xuất thông qua key.

  • Ví dụ : Redis, Dynomite, Project Voldemort.
  • Thường cho: Content caching Applications
  • Ưu điểm: Tìm kiếm rất nhanh
  • Nhược điểm: Lưu dữ liệu không theo khuôn dạng (schema) nhất định

Column-based – Tabular: Cơ sở dữ liệu tổ chức dưới dạng các bảng. Gần giống với mô hình RDBMS. Tuy nhiên, Chúng lưu dữ liệu bởi các cột chứ không phải bằng các dòng. Nó khá thích hợp với để hiển thị bằng các phần mềm quản lý kho dữ liệu

  • Ví dụ : Apache Hbase, Apache Cassandra, Hypertable
  • Thường cho: các hệ phân tán file
  • Ưu điểm: Tìm kiếm nhanh, Phân tán dữ liệu tốt
  • Nhược điểm: Hỗ trợ được với rất ít phần mềm

Document-based: Dữ liệu (bán cấu trúc hay semi-structured) được lưu trữ và tổ chức dưới dạng một tập hợp các document. Các document này linh hoạt, mỗi document có một tập nhiều trường.

  • Ví dụ : Apache CouchDB và MongoDB
  • Thường cho: Web applications
  • Ưu điểm: Dùng khi dữ liệu nguồn không được mô tả đầy đủ
  • Nhược điểm: Hiệu năng truy vấn, Không có cú pháp chuẩn cho câu truy vấn dữ liệu

Graph-based data-stores: Những CSDL này áp dụng lý thuyết đồ thị trong khoa học máy tính để lưu trữ và truy xuất dữ liệu. Chúng tập trung vào tính rời rạc giữa các phần dữ liệu. Các phần tử đơn vị dữ liệu được biểu thị như một nút và liên kết với các thành phần khác bằng các cạnh.

  • Ví dụ : Neo4j, InfiniteGraph, DEX
  • Thường cho: Social networking, Hệ trợ giúp
  • Ưu điểm: Ứng dụng các thuật toán trên đồ thị như Đường đi ngắn nhất, liên thông,…
  • Nhược điểm: Phải duyệt nội bộ đồ thị, để trả lời lại các truy vấn. Không dễ để phân tán

2. MongoDB

2.1. Khái quát

MongoDB (bắt nguồn từ “humongous”) là một hệ cơ sở dữ liệu NoSQL mã nguồn mở.logo

Hình 2 – Logo của MongoDB

Thay cho việc lưu trữ dữ liệu vào các bảng có quan hệ với nhau như truyền thống, MongoDB lưu các dữ liệu cấu trúc dưới dạng giống với JSON(JavaScript Object Notation) và gọi tên là BSON. Dự án được bắt đầu triển khai vào tháng 10 năm 2007 bởi 10gen trong khi công ty này đang xây dựng một nền tảng như là dịch vụ (Platform as a Service) giống như Google App Engine. Phải đến năm 2009, dự án này được tách độc lập. Hệ thống có thể chạy trên Windows, Linux, OS X và Solaris. Nó được một số tổ chức sử dụng trong thực tế như:

  • Caigslist : Công ty làm việc trong lịch vực môi giới quảng cáo trên các website khác (giống adMicro của Việt Nam). MongoDB giúp cho công ty này quản lý hàng tỉ các bản ghi quảng cáo thuận tiện và nhanh chóng.
  • Foursquare là một mạng xã hội gắn các thông tin địa lý. Công ty này cần lưu dữ liệu của rất rất nhiều vị trí của các địa điểm như quán cafe, nhà hàng, điểm giải trí, lịch sử, … và ghi lại những nơi mà người sử dụng đã đi qua.
  • CERN : Trung tâm nghiên cứu năng lượng nguyên tử của Châu Âu, sử dụng MongoDB để lưu trữ lại các kết quả, dữ liệu thí nghiệm của mình. Đây là một lượng dữ liệu khổng lồ sẽ dùng để sử dụng trong tương lai.
  • MTV Networks, Disney Interactive Media Group, bit.ly, The New York Times, The Guardian, SourceForge, Barclays, …

2.2. Tại sao lại chọn MongoDB?

MongoDB có những ưu điểm sau đây

  • Dễ học, có một số nét khá giống với CSDL quan hệ – Quản lý bằng command line hoặc bằng GUI như RockMongo hoặc phpMoAdmin
  • Linh động, không cần phải định nghĩa cấu trúc dữ liệu trước khi tiến hành lưu trữ nó -> rất tốt khi ta cần làm việc với các dạng dữ liệu không có cấu trúc.
  • Khả năng mở rộng tốt (distributed horizontally), khả năng cân bằng tải cao, tích hợp các công nghệ quản lý dữ liệu vẫn tốt khi kích thước và thông lượng trao đổi dữ liệu tăng.
  • Miễn phí

2.3. Kiến trúc tổng quát

Một MongoDB Server sẽ chứa nhiều database. Mỗi database lại chứa một hoặc nhiều colection. Đây là một tập các documnents, về mặt logic thì chúng gần tương tự như các table trong CSDL quan hệ. Tuy nhiên, điểm hay ở đây là ta không cần phải định nghĩa trước cấu trúc của dữ liệu trước khi thao tác thêm, sửa dữ liệu… Một document là một đơn vị dữ liệu – một bản ghi (không lớn hơn 16MB). Mỗi chúng lại chứa một tập các trước hoặc các cặp key – value. Key là một chuỗi ký tự, dùng để truy xuất giá trị dạng : string, integer, double, … Dưới đây là một ví dụ về MongoDB document

{
_id : ObjectId("4db31fa0ba3aba54146d851a"),
username : "joegunchy",
email : "joe@mysite.org",
age : 26,
is_admin : true,
created : "Sun Apr 24 2011 01:52:58 GMT+0700 (BDST)"
}

Cấu trúc có vẻ khá giống JSON, tuy nhiên, khi lưu trữ document này ra database, MongoDB sẽ serialize dữ liệu thành một dạng mã hóa nhị phân đặc biệt – BSON. Ưu điểm của BSON là hiệu quả hơn các dạng format trung gian như XML hay JSON cả hệ tiêu thụ bộ nhớ lẫn hiệu năng xử lý. BSON hỗ trợ toàn bộ dạng dữ liệu mà JSON hỗ trợ (string, integer, double, Boolean, array, object, null) và thêm một số dạng dữ liệu đặc biệt như regular expression, object ID, date, binary, code.dbms

Hình 3 – So sánh giữa RDBMS và MongoDB

com

Hình 4 – So sánh Table với Collection

2.4. Cơ chế hoạt động chi tiết của MongoDB

2.4.1. Sharding

2.1.1.1. Sharding là gì?

Sharding là cơ chế tự động của MongoDB dùng để chia tách một dữ liệu kích thước lớn cho rất nhiều server (thường gọi là cluster). Sharding được thiết kế để phục vụ 3 mục điêu cơ bản sau

  • Làm cho cluster “trong suốt” với người dùng: Để hoàn thành nhiệm vụ này, MongoDB sử dụng một quá trình routing đặc biết gọi là mongos. Mongos đứng trước cluster, đóng vai trò điều phối việc truy cập đến shard nào, trả dữ liệu từ shard nào ra. Nó chuyển tiếp các request tới các server khác có tài nguyên hoặc đến cluster đằng sau nó. Sau đó lắp ráp lại, và gửi các response lại về cho các client. Do đó, các client không cần biết rằng chúng đang giao tiếp với cluster nào thật sự mà chỉ biết rằng mình đang kết nối tới một server bình thường. Đây gọi là tính “trong suốt” với người sử dụng

mongos

Hình 5 – Mongos

  • Làm cho cluster luôn sẵn sàng để đọc hoặc ghi: Một cluster còn tồn tại phải đảm bảo được rằng nó luôn sẵn sàng. Mỗi phần con trong cluster sẽ có ít nhất một vài tiến trình phục vụ dự bị trên máy khác.
  • Làm cho cluster phát triển “tự nhiên”: Bất cứ khi nào người dùng cần thêm dung lượng, họ có thể thêm một cách dễ dàng Mỗi cluster khi được quản lý lại “thể hiện” như một node riêng lẻ và dễ dàng config.

2.1.1.2. Cơ chế của Sharding

2.1.1.2.1. Cơ chế chia tách dữ liệu

Một shard là một hoặc nhiều server trong một cluster chịu trách nhiệm với một tập các dữ liệu lưu trữ. Ví dụ, nếu ta có một cluster chứa 1.000.000 documents – 1.000.000 tài khoản website, thì một shard chứa khoảng 200.000 tài khoản. Nếu shard có chứa nhiều hơn một server, thì mỗi server đó sẽ lưu một bản copy giống hệt nhau của một tập con dữ liệu. Điều này có nghĩa là một shard cũng là một bộ các bản sao dữ liệurep

Hình 6 – Các bản sao dữ liệu trong một shard

Để đồng đều phân phối dữ liệu vào các shard, MongoDB lại di chuyển một tập con dữ liệu từ shard này sang shard khác. Việc chọn lựa tập con nào để chuyển đi lại phụ thuộc vào khóa mà ta chọn. Ví dụ, chúng ta sẽ chọn cách thức chia một collection của các user dựa vào trường username và sử dụng khoảng chia giống trong Toán học ‘[‘ , ‘]’ , ‘(‘ , ‘)’.

2.1.1.2.2. Cơ chế phân tán dữ liệu
2.1.1.2.2.1. Một khoảng cho một shard

Đây là cách đơn giản nhất để phân tán dữ liệu ra cho các shard. Mỗi shard sẽ đảm nhận một tập con dữ liệu. Giả sử có 4 shard và 4 khoảng chia dữ liệu, ta sẽ chia như sau:shard

Hình 7 – 4 khoảng cho 4 shard [a,f), [f,n), [n,t), [t,{)

Chia như thế này là rất dễ hiểu nhưng nó trở nên rất phức tạp cho các hệ thống lớn. Giả sử rằng có một lượng lớn đăng ký username có chữ cái bắt đầu thuộc [a,f), điều này làm cho shard 1 trở nên lớn, giải quyết đơn giản bằng cách điều chỉnh cho shard 1 chỉ lấy từ [a,c) và shard 2 lấy nhiều hơn, từ [c,n)shard2

Hình 8 – Giới hạn lại khoảng cho shard 1 sang shard 2

Mọi thứ có vẻ ok, nhưng nếu shard 2 trở nên overload thì lại giải quyết thế nào? Giả sử rằng shard 1 và shard 2 chứa 500GB dữ liệu, shard 3 và shard 4 mỗi cái chứa 300GB dữ liệu. Giải quyết vấn đề này như sau:shard3

Hình 9 – Chuyển dữ liệu sang shard tiếp theo

  • Chuyển 100GB từ shard 1 sang shard 2
  • Chuyển 200GB từ shard 2 sang shard 3
  • Chuyển 100GB từ shard 3 sáng shard 4

Mỗi shard đều chứa 400GB dữ liệu, có vẻ ổn. Tổng cộng, đã chuyển 400GB dữ liệu qua mạng, quá lớn!!! Cách khác, thêm mới một shard thì sao? Giả sử có 4 shard, mỗi shard hiện đang có 500GB dữ liệu, thêm vào shard 5 và chia dữ liệu.shard4

Hình 10 – Thêm mới shard rồi chia đều dữ liệu

  • Chuyển 400GB từ shard 4 sang shard 5
  • Chuyển 300GB từ shard 3 sang shard 4
  • Chuyển 200GB từ shard 2 sang shard 3
  • Chuyển 100GB từ shard 1 sang shard 2

Mỗi shard đều chứa 400GB dữ liệu, vẫn ổn. Tổng cộng, đã chuyển 1TB dữ liệu qua mạng, quá lớn!!! Rõ ràng là cách thức chia mỗi khoảng trên một shard này làm cho vấn đề trao đổi dữ liệu, xử lý trong hệ thống lớn trở nên quá đắt đỏ và tốn kém. Do đó, MongoDB không phân tán dữ liệu theo cách thô thiển như thế này, thay vào đó là nhiều khoảng trên nhiều shard.

2.1.1.2.2.2. Nhiều khoảng trên nhiều shard

Giả sử tình huống cần giải quyết giống với hình 8, shard 1 và shard 2, mỗi shard có 500GB dữ liệu còn shard 3 và shard 4 mỗi shard có 300GB dữ liệu. Lúc này, ta sẽ cho phép mỗi shard chứa nhiều phần của khoảng chia. Chia dữ liệu trong shard 1 thành hai khoảng :

  • 400GB cho [a,d)
  • 100GB cho [d,f)

Chia dữ liệu trong shard 2 thành hai khoảng :

  • 400GB cho [f,j)
  • 100GB cho [j,n)

Lúc này, ta di chuyển:

  • 100GB [d,f) từ shard 1 sang shard 4
  • 100GB [j,n) từ shard 2 sang shard 3

Vậy, kết luận cả 4 shard đều chứa 400GB dữ liệu và chỉ phải trao đổi qua mạng 200GB dữ liệu. Điều này chấp nhận được S

Hình 11 – Cho phép đa, không liên tiếp các khoảng cho phép ta lấy ra và di chuyển dữ liệu đi bất cứ đâu

Với ví dụ của hình 9, nếu ta thêm vào một shard mới, ta chỉ lấy đi 100GB dữ liệu trên mỗi 4 shard cũ để cho vào shard 5 mới, lúc này các shard đã lưu dữ liệu như nhau và thông lượng trao đổi dữ liệu qua mạng ở mức tốt nhất 400GBS11

Hình 12 – Hớt 100GB của mỗi shard ném vào shard 5

Đây là cách mà MongoDB phân tán dữ liệu giữa các shard với nhau, nếu có shard nào mất cân bằng, dữ liệu sẽ được điều chỉnh để phục hồi lại trạng thái cân bằng mới giữa các shard.

2.1.1.2.3. Cơ chế tách, tạo đoạn của Sharding

Khi ta quyết định phân tán dữ liệu, ta phải chọn ra một khóa được sử dụng để tách các khoảng với nhau (ở trên ta chọn username). Khóa này được gọi là shard key có thể là bất kỳ hoặc tập bất kỳ trường nào. Giả sử rằng collection của chúng ta có các document như sau (không kể ra_ids):DOCUMENTSNếu ta chọn Age là shard key và khoảng là [15,26) -> Kết quả làd

2.1.1.2.3. Cơ chế Sharding các Collection

Lần đầu tiên khi shard một Collection – bảng, MongoDB chỉ tảo ra một chunk cho bảng này sẵn sàng cho việc thêm mới dữ liệu. Chunk này sẽ có khoảng là (-∞,∞), với giá trị -∞ tương đương bởi $minKey và ∞ tương đương với $maxKey. Dĩ nhiên nếu dữ liệu trong bảng là đủ lớn, MongoDB sẽ tự động chia thành nhiều chunk hơn để lưu dữ liệu. Khi dữ liệu lớn dần, MongoDB tự động tách chunk như sau: chia đôi khoảng hiện tại ra theo khoảng của dữ liệu chọn làm key. Ví dụ trên, giả sử nó chọn chia đôi thành

  • Một chunk chứa tuổi nhỏ hơn 15 (-∞,15)
  • Một chunk chứa tuổi lớn hơn hoặc bằng 15 [15, ∞)

Nếu tiếp tục có nhiều dữ liệu được thêm vào chunk [15, ∞), nó sẽ bị chia tiếp, chẳng hạn [15, 26) và [26, ∞). Hiện tại ta có 3 chunk: (-∞,15), [15, 26) và [26, ∞) và cứ như vậy. Yêu cầu đặt ra:

  • Việc chia chunk phải liên tiếp
  • Một chunk chỉ tương đương với một khoảng và ngược lại
  • Mỗi một document chỉ thuộc một và chỉ một chunk

chunk

Hình 13 – Chia một chunk ra thành hai

Một số chú ý

  • Việc chia chunk này phụ thuộc tất cả vào shard key do vậy MongoDB không cho phép ta thêm dữ liệu mà rỗng hoặc không có shard key (ít nhất phải để null). Ta cũng không thể đổi được giá trị trường shard key, muốn vậy, bắt buộc phải thay đổi từ phía client, sau đó xóa, và thêm lại document muốn đổi shard key.
  • Nếu ta thêm nhiều kiểu dữ liệu vào nội dung trường age ở trên, ví dụ như một mảng, boolean, số, string, null, … MongoDB vẫn chấp nhận và dùng quy tắc chia chunk cho sharding theo thứ tự sau:

null < numbers < strings < objects < arrays < binary data < ObjectIds < booleans < dates < regular expressions

  • Ở các hệ thống thực tế một chunk thường chỉ được 200MB dữ liệu (còn mặc định là 64 MB) Một chunk là đơn vị logic. Các document trong một chunk cũng không nhất thiết phải nối tiếp nhau trong đĩa cứng vật lý. Chúng vẫn rải rác ngẫu nhiên trong collection. Tuy chỉ có một document thuộc về chunk thứ i,  nếu và chỉ nếu shard key của nó thuộc khoảng chia của chunk thứ i.

2.4.2. Balancing – Cân bằng tải

Nếu có nhiều shard đang sẵn sàng và có thêm chứa thêm dữ liệu, MongoDB sẽ tiến hành chuyển dữ liệu từ các shard khác sang để cân bằng tải. Cách thức tiến hành là di chuyển các chunk từ shard này sang shard khác một cách tự động. Balancing cũng có thể bị tắt hoặc bật nếu admin muốn Balancing cũng không được đảm bảo ngay tức thì, chúng ta hãy xem ví dụ dưới đâys2

Hình 14 – Nếu cần bằng real time, sẽ có những tài nguyên bị di chuyển lãng phí

2.4.3. Cấu hình cho một Cluster

2.4.3.1. Chọn Shard Key

2.4.3.1.1 Low-Cardinality Shard Key

Đây là việc chọn khóa một cách trực quan như ví dụ “Tôi đang có 4 Shard, Tôi chọn một trường nào đó làm Shard key để sao cho chia được ok trường đó thành 4 khoảng”. Cụ thể hơn, ta có 7 khoảng chia vào 7 shard như sau:

  • (-∞, “Antarctica”)
  • [“Antarctica”, “Asia”)
  • [“Asia”, “Australia”)
  • [“Australia”, “Europe”)
  • [“Europe”, “North America”)
  • [“North America”, “South America”)
  • [“South America”, ∞)

continent

Hình 15 – Một Shard cho một Châu lục

Điều này mới đầu rất ổn. Nhưng để ý, nếu về lâu dài, dữ liệu trong châu Á chẳng hạn tự nhiên lên cao hơn các châu khác thì ta không biết sẽ phân chia lại khoảng các shard như thế nào để cân bằng vì đã chọn một shard – một châu lục rồi. Cách giải quyết tình thế là ở đây lấy thêm một trường nữa làm Shard Key. Shard key có hai trường sẽ cho phép chia thành nhiều khoảng hơn. Nhưng kết lại, đây nói chung không phải là cách chọn Shard Key tốt.

2.4.3.1.2. Ascending Shard Key

Như chúng ta đã biết, đọc dữ liệu từ RAM nhanh hơn nhiều so với đọc dữ liệu từ thiết bị lưu trữ ngoại vi. Theo nghiên cứu cho thấy, ở hầu hết các phần mềm hiện nay, ta thường làm việc với dữ liệu gần đây hơn là những dữ liệu lâu cũ; vì vậy, ta muốn dùng một shard key nào đó mà phân chia được dữ liệu gần đây để tiện việc đọc ra. Đa phần mọi người sẽ nghĩ ngay đến việc dùng tem thời gian (timestamp) hay trường ObjectId nhưng điều này không mang lại kết quả như mong đợi, ta cùng xem ví dụ sau: Giả sử ta đang quản lý dịch vụ like của facebook, mỗi document sẽ lưu hai trường

  • Ai là người gửi
  • Khi nào nó được gửi

Vấn đề như sau: Bắt đầu bằng một shard (-∞, ∞) – và dùng shard key là timestamp, dữ liệu thêm mới sẽ vào shard này và đến một lúc nào đó dữ liệu tăng lên sẽ chia thành hai shard (-∞, 1000) và [1000, ∞). Dữ liệu thêm mới sẽ nhảy vào shard [1000, ∞). Đến một lúc nào đó, shard này lại chia thành hai [1000, 3000) và [3000, ∞). Dữ liệu thêm mới lại chỉ vào shard [3000, ∞) . . . Tuy có ưu điểm là khi làm việc, ta thường làm việc với shard cuối cùng – chứa nhiều dữ liệu gần đây nhưng nhược điểm lại lấn át bởi vì dữ liệu thêm mới chỉ thêm vào shard cuối cùng và quan trọng là shard đó luôn luôn phải chia ra. Điều này sinh ra các dòng dữ liệu chuyển qua lại lãng phí giữa các shard.

2.4.3.1.3. Random Shard Key

Để tránh nhược điểm trên, ta lại chọn cách thức chọn shard key mới: ngấu nhiên. Mới đầu làm việc tốt nhưng dữ liệu tăng lên, hệ thống hoạt động càng ngày càng chậm đi. Giả sử ta muốn lưu ảnh của một website vào CSDL, Mỗi document gồm có:

  • Định dạng nhị phân của ảnh
  • Hash MD5 của ảnh
  • Một chú thích ảnh
  • Ngày lưu bức ảnh được chụp
  • Ai chụp nó

Tiến hành chọn trường Hash MD5 để làm shard key. Càng ngày, dữ liệu lớn dần lên và chúng ta có một loạt các chunk phân bố đồng đều trên các shard. Giả sử rằng, Shard 2 đang có nhiều hơn Shard 1 10 chunk và phải cân bằng. MongoDB lúc này sẽ load ngẫu nhiên 5 chunk để chuyển. Do chúng phân bố ngẫu nhiên nên việc lấy dữ liệu này không thông qua đánh index. Một lượng lớn công sức sẽ phải bỏ ra để chuyển dữ liệu (qua RAM, qua IO,…). Chính vì để ngẫu nhiên, không đánh index nên việc truy vấn dữ liệu diễn ra chậm

2.4.3.1.4. Good Shard Key

Rút cuộc, câu chuyện của ta là phải chọn một shard key sao cho nó đảm bảo tính chia nhỏ được của khoảng dữ liệu nhưng không nhỏ tới mức biến nó thành nhược điểm. Kết hợp ascending key + search key Đa phần chúng ta chỉ làm việc với các dữ liệu gần đây, nên việc chia khoảng bằng date/time là hợp lý và phân bố khá đều. Chúng ta áp dụng điều này bằng cách sử dụng cặp shard key – một loại key khác (như ascending key dạng thô, search key) Giả sử rằng ta đang có một phần mềm phân tích và cần dữ liệu của tháng gần đây nhất. Ta shard trên 2 trương {month, user}.

  • Khởi đầu có 2 chunk ((-∞,-∞), (∞,∞)).
  • Khi dữ liệu nhiều lên, chia chunk thành hai ví dụ như ((-∞, -∞),(“2011-04”, “susan”))and [(“2011-04”, “susan”), (∞, ∞))
  • Tiếp tục, dữ liệu tăng lên, các chunk tiếp theo được sinh ra trong 04 – 2011 sẽ được chuyển sang shard khác, MongoDB sẽ dần dần cân bằng tải các cluster. Sang tháng 5, ta tạo ra một chunk mới – khoảng 05 – 2011. Đến tháng 06 – 2011, ta không cần đến dữ liệu của tháng 04 nữa, dữ liệu này có thể cất đi. Nhưng khi muốn xem lại lịch sử hoạt động, chúng ta không cần chia hoặc di chuyển bất kể dữ liệu gì hết (điểm yếu của random shard key)

3. Thử nghiệm

Ta sẽ thử nghiệm về việc tự động chia chunk giữa các shard như sau:

Bước chuẩn bị : 

Ở máy chủ: tạo thư mục /data/db và /data/configdb Ở máy con(chứa các shard con): tạo thư mục /data/db

a. Máy chủ: Khởi động mongoDB Server ở máy chủ chế độ chờ config với lệnh

mongod –configsvr

b. Máy chủ: Thiết lập config cho máy chủ mongos để chia các chunk (192.168.1.2 đang là ip của máy chủ) Ta thiết lập –chunkSize 1 (chunk chỉ lớn 1MB) để thấy việc chuyển chunk cho nhanh (nếu không có, mặc định là 64MB) với lệnh

mongos –configdb 192.168.1.2 [–chunkSize 1]

c. Các máy con: Khởi tạo các shard ở máy con

mongod –shardsvr

d. Máy chủ chạy

mongo localhost:27017/admin

> db.runCommand({addshard:”192.168.x.x:27018″})

> db.runCommand({enablesharding:”test”})

> db.runCommand({shardcollection: “test.table1”, key: {_id: 1}});

Máy chủ lần lượt add các shard vừa tạo (cổng mặc định của shard là 27018). Áp dụng sharding trên db “test”, collection “table1”, key trên trường “_id” (_id: 1 – 1 nghĩa là on – chọn trường này làm shard key) (có thể chọn nhiều trường cùng làm shard key như đã trình bày ở trên)

LINK: http://bigsonata.com/mongodb/

Money Lover : Thành công nhờ theo sát bước chân người dùng

Trong bối cảnh nhà nhà làm ứng dụng, người người làm ứng dụng thì thành công của Money Lover – một trong top các ứng dụng quản lý tài chính cá nhân trên thế giới của một nhà sáng lập Việt Nam là những kinh nghiệm quý báu cho các nhà phát triển ứng dụng độc lập trong nước.

Cũng như nhiều nhà phát triển ứng dụng độc lập khác tại Việt Nam, anh Ngô Xuân Huy bắt đầu với nền tảng về Công nghệ thông tin. Vào thời điểm làm Money Lover, tuy có nhiều kinh nghiệm lập trình nhưng kinh nghiệm về thiết kế giao diện hay làm cách nào để tiếp thị một ứng dụng tốt đều không phải là điểm mạnh của Huy. Mặc dù vậy, Money Lover đã đạt 600,000 lượt tải trên kho ứng dụng Google Play sau 1,5 năm và khoảng hơn 40,000 lượt tải trên iOS sau một năm. Đâu là bí quyết thành công của Huy ?

moneylover3

Muốn tạo ra sản phẩm, trước hết phải là người dùng

Huy cho rằng, mình phải là người dùng, trước khi mình là người tạo ra sản phẩm. Money Lover được sinh ra với mục đích ban đầu là phục vụ nhu cầu của chính tác giả.

Gặp vấn đề trong việc quản lý tài chính cá nhân, Huy thử dùng các ứng dụng khác nhau để giải quyết vấn đề của mình. Tuy nhiên, anh không thể dùng được các ứng dụng đang có trên thị trường, do hầu hết các ứng dụng này đều được kết nối với thẻ tín dụng, hình thức giao dịch hàng ngày ở nước ngoài nhưng lại chưa phổ biến tại Việt Nam. Vì vậy, mong muốn tạo ra một ứng dụng dùng để ghi lại những khoản chi tiêu nhỏ nhất và phù hợp với thị trường Việt Nam ra đời.

Triết lý phải là người dùng của chính sản phẩm của mình, luôn được áp dụng trong mọi sản phẩm của Huy. Anh cho biết, mỗi lần có ý tưởng về sản phẩm mới, anh luôn tìm hiểu trên thị trường, đã có sản phẩm nào tương tự với ý tưởng của mình chưa ? Nếu có rồi thì điểm mạnh, điểm yếu của từng sản phẩm là gì ? Mình có làm nó tốt hơn được không ?

“Người tạo nên sản phẩm phải hiểu rõ sản phẩm của mình, hiểu rõ các sản phẩm tương tự chứ không nên sao chép tính năng mà không rõ bản chất”, Huy nói.

Tuy không có kinh nghiệm về thiết kế giao diện cho ứng dụng nhưng với kinh nghiệm lập trình và dùng thử nhiều sản phẩm trong quá trình làm founder (nhà sáng lập) hai diễn đàn game4v.vn và pes.vn,  Huy  hiểu rõ làm thế nào để người dùng dễ sử dụng và cảm thấy thoải mái khi dùng Money Lover đó là “giao diện có thể xấu nhưng phải dễ dùng”.

Một tuần sau khi ra đời, Money Lover được giới thiệu lên XDA, diễn đàn hàng đầu cho dân lập trình Android và nhận được nhiều ý kiến phản hồi từ người dùng trên toàn thế giới. Không bỏ qua bất cứ ý kiến nào, Huy trả lời hết các lời bình luận góp ý và mail anh nhận được.

moneylover1

Nhờ các ý kiến đóng góp từ người dùng, anh liên tục thử nghiệm, kể cả những thử nghiệm nhỏ như vị trí đặt thanh menu, để mang lại sự thoải mái và tiện dụng cho người dùng. Ngoài các ý kiến đóng góp về sản phẩm, anh Huy còn nhận được sự giúp đỡ của người dùng khi giúp anh thiết kế các biểu tượng phù hợp với văn hóa, tiền tệ của đất nước họ, giúp Money Lover có hơn 35 ngôn ngữ khác nhau.

moneylover2

Vì vậy, từng có thời điểm gần 1000 lượt gỡ bỏ ứng dụng trên 2000 lượt tải mỗi ngày nhưng do luôn hướng đến người dùng nên Money Lover dần xây dựng được những khách hàng trung thành. Nhờ vậy mà đầu năm 2013, Money Lover đạt lượng người dùng trả tiền khá cao là 2%, ước tính mang lại doanh thu khoảng 35,000 đô-la Mỹ sau 1,5 năm ra đời.

Tiếp tục phục vụ người dùng ngay cả khi sản phẩm đã hoàn thiện

Hiểu rõ điểm yếu của sản phẩm như Money Lover là bắt buộc người phải nhập dữ liệu hàng ngày, Huy đang cố gắng kết nối với các merchant (bên bán) để họ cùng tham gia vào hệ thống của Money Lover, giúp tự động ghi lại chi tiêu khi người dùng thanh toán tiền khi mua sắm ở phía bên bán.

Tự nhận mình là người không giỏi về thiết kế cũng như tiếp thị nhưng Money Lover đã thành công bằng việc đặt người dùng là trung tâm của sản phẩm. Trong mọi giai đoạn của sản phẩm, từ quá trình lên ý tưởng, lập trình đến khâu hoàn thiện, nhà sáng lập Money Lover luôn lắng nghe để hiểu mong muốn của người dùng từ đó mang lại một sản phẩm đáp ứng đúng nhu cầu của họ với những trải nghiệm tốt nhất.

LINK: http://www.action.vn/money-lover-thanh-cong-nho-theo-sat-buoc-chan-nguoi-dung-2.html

Câu chuyện khởi nghiệp – Hành trình từ TimViecNhanh.com đến MuaBanNhanh.com

Khi làn sóng khởi nghiệp nhân rộng kéo theo nhiều câu chuyện gọi vốn thành công của một số công ty công nghệ tiêu biểu; song ít ai biết rằng có một số rất ít công ty trong số đông ấy từ lâu đã bắt đầu hành trình của mình trên đôi chân của chính mình, họ vẫn tăng trưởng và có lợi nhuận tốt sau nhiều năm phát triển.

Hành trình từ TimViecNhanh.com đến MuaBanNhanh.com là một câu chuyện dài thú vị của những người sáng lập “không thích sống nhờ tiền của nhà đầu tư”.

TimViecNhanh.com – Sự lựa chọn liều lĩnh

Ngày 01/06/2005, website việc làm Vietnamworks.com được quỹ đầu tư mạo hiểm IDG Việt Nam đầu tư, ước tính thương vụ đầu tư lúc đó khoảng 2 triệu đô-la. Năm 2007, quỹ đầu tư DFJ VinaCapital đầu tư vào VON chủ sở hữu KiemViec.com, HRVietnam, Yume.vn và Timnhanh.com với số tiền tương đương khoảng 2 triệu đô-la. Cũng trong năm 2007, vào tháng 5 TimViecNhanh.com ra đời. Không lâu sau đó, vào tháng 08 năm 2008, http://www.TimViecNhanh.com chính thức vượt mặt Kiemviec về lượng truy cập tại Việt Nam.

TimViecNhanh.com là một sự lựa chọn liều lĩnh của hai nhà sáng lập “cứng đầu” lần đầu thử sức với lĩnh vực Internet: Trương Võ Tuấn và Lâm Quang Vinh. Được tiếp xúc cá nhân với Võ Tuấn và Quang Vinh từ những ngày đầu tiên thành lập công ty, tác giả bài viết vẫn còn nhớ như in câu nói của Võ Tuấn vào thời điểm đó: “Ngày trước, người ta cần 30 – 40 năm để có thể xây dựng sự nghiệp và trở nên giàu có trong lĩnh vực bất động sản; nhưng ngày nay với tốc độ phát triển của công nghệ, chắc chắn khoảng thời gian ấy sẽ được rút ngắn ít nhiều, có thể chỉ từ 5 – 10 năm để tạo lập doanh nghiệp có nguồn thu bền vững”.

Nhận thấy Vietnamworks và Kiemviec đang tập trung vào phân khúc nhân sự cao cấp – đây cũng là phân khúc mang đến doanh thu tốt cho những công ty ấy khi đó, TimViecNhanh.com tự tách mình và lựa chọn một phân khúc khác biệt: từ sinh viên mới ra trường cho đến quản lý cấp trung. Mọi việc không hề dễ dàng bởi vì một khi lựa chọn phân khúc này, TimViecNhanh.com đã lựa chọn cho mình một con đường hẹp hơn về doanh thu, bắt buộc phải liên tục cải tổ bộ máy hoạt động sao cho nhẹ nhàng nhất để tối ưu hoá chi phí.

Những năm đầu tiên trôi qua, tất cả tin đăng tuyển dụng đều theo đúng tiêu chí của TimViecNhanh là miễn phí dành cho doanh nghiệp. Không phí phạm ngân sách marketing bằng nhiều hoạt động quảng cáo đơn thuần, TimViecNhanh tập trung vào tối ưu hoá nội dung trên các công cụ tìm kiếm, xây dựng cộng đồng dành cho sinh viên, cung cấp kiến thức dành cho nhóm đối tượng sinh viên đi làm bán thời gian và những người chưa có nhiều kinh nghiệm làm việc. Trong khi hai đối thủ của mình vẫn mải mê đánh chiến với nhau ở thị trường nhân sự cấp cao khó nhằng, văn phòng được xây dựng đẹp đẽ và hoành tráng, thì tại TimViecNhanh vẫn khiêm tốn với một đội ngũ nhân sự dưới 20 người nhưng luôn làm việc ở cường độ tập trung cao nhất, phục vụ người dùng không kể ngày đêm, mang đến sự hài lòng cao nhất với người dùng.

Khi bắt đầu đưa mô hình kinh doanh để thương mại hoá TimViecNhanh, Võ Tuấn và Quang Vinh cũng phải thử và chấp nhận sai ở nhiều lần khác nhau. Cuối cùng, cả hai cùng kiếm ra được cách thu tiền ít nhưng thu nhiều trên số đông thay vì thu tiền nhiều chỉ ở vài khách hàng ít ỏi và cách thức kinh doanh này bắt đầu phát huy tác dụng, TimViecNhanh chính thức có lãi sau 02 năm hoạt động và mức độ tăng trưởng hơn 200% mỗi năm. Từ thời điểm TimViecNhanh chính thức được xây dựng và phát triển cho đến giai đoạn có lợi nhuận, TimViecNhanh chưa từng có bất kỳ nhà đầu tư nào tham gia vào công ty của mình.

Vào cuối năm 2013, Công ty Cổ phần 24H nhận thấy tiềm năng và sự phát triển ấn tượng của TimViecNhanh.com đã chính thức mua phần lớn cổ phần và sát nhập trang Việc Làm 24H vào Công ty Cổ phần Tìm Việc Nhanh theo mô hình quản lý tinh gọn như hai nhà sáng lập đã kiến trúc từ ban đầu. Việt Nam có 93 triệu dân tính đến hết năm 2014, dân số Việt Nam là đặc thù dân số trẻ, vì vậy các nhu cầu cơ bản như học hành – việc làm và y tế là những nhu cầu thiết yếu hàng đầu. Sự liều lĩnh của TimViecNhanh ban đầu lại là một sự liều lĩnh đáng giá của cả hai nhà sáng lập – lần – đầu – khởi – nghiệp.

Tiếp tục cuộc phiêu lưu với MuaBanNhanh.com

Sau khi bán phần lớn công ty của mình, Võ Tuấn và Quang Vinh dành nhiều thời gian cho các hoạt động xã hội từ thiện bên cạnh công việc. Thỉnh thoảng nhìn lại TimViecNhanh, cả hai đều khiêm tốn nhìn nhận: do bản thân mình may mắn hơn người khác thôi. Dự định rằng sẽ chỉ tiếp tục duy trì công việc ổn định hiện tại và tham gia các hội nhóm từ thiện, thế nhưng Võ Tuấn và Quang Vinh đều cảm thấy thiếu một cái gì đó trong cuộc sống của mình: một động lực lớn lao để thoát khỏi bẫy nhàm chán và tự mãn trong sự nghiệp.

https://MuaBanNhanh.com là một ý tưởng khởi nghiệp bất chợt đến khi cả hai quyết định sẽ tiếp tục cuộc phiêu lưu của mình trong thế giới công nghệ. Làn sóng di động rộ lên trong vài năm trở lại đây đã thực sự ảnh hưởng sâu sắc đến cuộc sống xung quanh, từ giải trí, học hành cho đến thương mại, buôn bán; từ doanh nghiệp lớn hay cửa hàng tạp hoá nhỏ lẻ đến cá nhân, bất kỳ ai, dù ở bất kỳ đâu cũng không thể nào thoát khỏi vòng vây của các thiết bị di động. Mua bán trên nền tảng di động, một ý tưởng không hề tồi, nhưng làm sao để khác biệt với các đối thủ mạnh hiện nay trên thị trường, lại là một bài toán khó khăn.

Xác định cuộc chơi lần này khác với câu chuyện của chính mình cách đây 08 năm về trước, khi thị trường của ngày xưa vẫn còn nhiều cơ hội bỏ ngỏ, trong khi đó, hiện nay đã có nhiều tay chơi có nguồn vốn lớn trong và ngoài nước. Cả hai vẫn chưa quyết định mời một số nhà đầu tư trong nước tham gia cùng mình cho đến khi mô hình kinh doanh sáng rõ trước mắt và lộ trình phát triển được vẽ ra chi tiết, hai nhà sáng lập mới mở cửa chào đón nhà đầu tư khi đã có sản phẩm và cách thương mại hoá sản phẩm cụ thể.

18-8-201529-6976-1439952248

Thật khó để nói trước được tương lai, thế nhưng khi nhìn lại quá khứ khởi nghiệp của hai kẻ “cứng đầu” này, có thể thấy rằng điều quan trọng nhất trong quá trình xây dựng công ty và lập nghiệp đó chính là không nên phụ thuộc vào người khác quá nhiều ở giai đoạn ban đầu. Theo Võ Tuấn và Quang Vinh, cả hai đều không thích việc xem nguồn tiền đầu tư từ nhà đầu tư là “doanh thu” của mình, mà phải luôn tìm thấy được mô hình kinh doanh trước khi bắt đầu xây dựng sản phẩm.

Để là người độc lập tài chính thì phải xác định mô hình kinh doanh đúng đắn và quan trọng hơn hết là dám thử – dám sửa và dám làm lại, không nhất thiết phải đi theo người dẫn đầu, chính mình sẽ tự tìm ra được một con đường khác biệt khi bản thân đủ hiểu sâu sắc bản chất của vấn đề. Ngày nay, các nhóm khởi nghiệp mới non trẻ thường bắt đầu với “ý tưởng sáng tạo” hay “ý tưởng độc quyền”, hoặc thậm chí là “vì bản thân yêu thích”, thế nên gặp lúng túng khi chuyển từ ý tưởng ban đầu sang mô hình kinh doanh bền vững.

LINK: http://www.action.vn/cau-chuyen-khoi-nghiep-hanh-trinh-tu-timviecnhanh-com-den-muabannhanh-com.html

5 Website nhắn tin SMS miễn phí tới điện thoại

Hiện nay nhắn tin SMS trở nên quá phổ biến và không thể thiếu đối với chúng ta. Mỗi ngày chúng ta sẽ phải tri ra hàng chục nghìn để nhắn tin SMS cho bạn bè ! Mỗi tin nhắn gửi đi chúng ta phải tri trả rất đắt từ 300 – 1000 VNĐ cho mỗi tin nhắn. Sau đây TCN sẽ liệt kê ra 5 website nhắn tin miễn phí sẽ phần nào giúp giảm chi phí SMS cho người dùng.

5 Website nhắn tin SMS miễn phí tới điện thoại

 

1. SlideSMS (slidesms.com)

Website cung cấp cho bạn ứng dụng gửi tin nhắn SMS không giới hạn .Bạn có thể gửi tin nhắn cho hơn 200 quốc gia trên thế giới và có thể gửi tin nhắn tối đa 300 ký tự, ngoài ra hệ thống còn cho bạn đính kèm thêm hình ảnh .

2. Way2SMS (way2sms.com)

Way2SMS là dịch vụ nổi tiếng cho bạn gửi SMS liên tục trong khoảng thời gian là 10s. Gửi dễ dàng, dễ quản lý liên lạc đây là một số tính năng được cung cấp bởi oher Way2SMS. Không chỉ vậy, bạn có thể nhận được thông báo E-mail miễn phí đến hộp thư điện thoại di động và cũng có thể Chat trên hệ thống Gtalk của bạn và bạn bè sử dụng yahoo . Nhưng những chức năng mở rộng trên hệ thống chỉ giới hạn trên Quốc gia Ấn độ.

3. SMScity (en.smscity.com)

SMScity bạn có thể gửi tin nhắn SMS quốc tế miễn phí. Không chỉ có tin nhắn SMS bạn có thể gửi tin nhắn miễn phí kèm theo Hình ảnh. Sử dụng SMScitybạn có thể gửi tin nhắn miễn phí đến 22 quốc gia khác nhau. Bạn cũng có thểgửi tin nhắn SMS thông qua dịch vụ MSN của bạn và cung cấp SMScity.

4. SMS440 (sms440.com)

Trang web này khác với tất cả những website được đề cập bởi nó có một hệ thống quản lý SMS cực kỳ hiệu quả

  • Nó cho phép bạn gửi tin nhắn ký tự dài tối đa 440 ký tự.
  • Bạn có thể gửi tin nhắn SMS trong nhiều khác ngôn ngữ khác ngoài Tiếng Anh.
  • Bạn cũng có thể sắp xếp tin nhắn SMS cho bất kỳ ngày và thời gian sắp tới.
  • Cho phép bạn tạo danh bạn điện thoại.
  • cũng có thể gửi tin nhắn nhóm.


5. Afreesms (afreesms.com)

Đây là một dịch vụ SMS miễn phí mà tôi khá ưa thích vì nó có thể gửi tin nhắn trên toàn thế giới. Thao tác đơn giản, dễ sử dụng, tốc độ gửi tin nhanh. Đây cũng là lựa chọn hàng đầu cho các bạn muốn gửi SMS miễn phí trên toàn thế giới .

LINK: https://www.trangcongnghe.com/thu-thuat/1204-5-website-nhan-tin-sms-mien-phi-toi-dien-thoai.html