load balancing with mod_proxy_balancer

1.

$ a2enmod proxy

$ a2enmod proxy_balancer # provides three algorithms for scheduling requests [ default = “byrequest” algorithm ]

$ a2enmod proxy_http # for HTTP-aware proxying of requests.
$ a2enmod status # for getting the status of the backend servers.

$ a2enmod rewrite # require by mod_proxy to do reverse proxying ( Client thinks the proxy server is serving its request, but in fact the proxy is forwarding the request to a different backend server )

2. config loadbalancing

$ cp /etc/apache2/sites-available/default /etc/apache2/sites-available/default_orig

edit /etc/apache2/sites-available/default

NameVirtualHost *
<VirtualHost *>
ServerName localhost
ServerAlias localhost
DocumentRoot /var/www/
ProxyRequests Off
# This is sets up access for proxy forwading.
# This configuration allows all request to be forwarded.
<Proxy *>
Order deny,allow
Allow from all
</Proxy>
# If the url includes /balancer-manager,
# The server will not proxy the request
ProxyPass /balancer-manager !
# This maintains session across the different backend servers
ProxyPass / balancer://mycluster/ stickysession=BALANCEID nofailover=On
# The following allows every request to be forwared to the given servers
ProxyPassReverse / http://192.41.170.42/
ProxyPassReverse / http://192.41.170.41/
# Next the backend web servers and the scheduling algorithm are defined
<Proxy balancer://mycluster>
BalancerMember http://192.41.170.42 route=http1
BalancerMember http://192.41.170.41 route=http2
ProxySet lbmethod=byrequests
</Proxy>
# Balancer-manager Web app allows configuration of routes
<Location /balancer-manager>
SetHandler balancer-manager
Order deny,allow
Allow from all
</Location>
</VirtualHost>

ref : http://www.howtoforge.com/load_balancing_apache_mod_proxy_balancer


http://httpd.apache.org/docs/2.2/mod/mod_proxy.html for more on reverse proxying and the necessary URL rewriting

Advertisements

load balancing with pound

1.

$ apt-get install pound

2. edit /etc/pound/pound.cfg

ListenHTTP
Address 127.0.0.1  # ip เครื่องเรา
Port    80 # port เครื่องเรา

## allow PUT and DELETE also (by default only GET, POST and HEAD)?:
xHTTP           0   # 0 คือ ทั้งหมด

Service
BackEnd
Address 127.0.0.1  # address ที่ให้ redirect ไป ( ใส่เปง DNS ก็ได้ เช่น google.com )
Port    80
End
End
End

NOTE : Service ใส่ลงไปหลายๆอันก็ได้นะ

หรือ อาจจะใช้ matching urls ให้ static file วิ่งไปอีกเครื่องก็ได้

Service
    URL ".*.(jpg|gif|png|js|css|jpeg|html|txt)"
    BackEnd
        Address 127.0.0.1
        Port    8000
    End
End

หรือ เอา

checkURL “.*.(jpg|gif|png|js|css|jpeg|html|txt)”

ไปใส่ไว้นอก Service ก็ได้

3. edit /etc/default/pound

startup=1
4.

$ sudo /etc/init.d/pound start

ตัวอย่าง shooting_star observer ที่หามานาน

หลังจาก ประสบปัญหาว่า จะรู้ว่า user online และ offline จากห้องแชทได้ยังไง

ในที่สุดก็ไปเจอมาแล้ว ที่นี่ หรือว่า ที่นี่

โค้ดเป็นตามนี้

#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require File.dirname(__FILE__) + '/../config/environment'
require 'drb/drb'

class ChatObserver
include DRb::DRbUndumped
def name; 'simple_chat/chatroom observer' end
def enter(params)
puts "ChatObserver:"
puts params.inspect
end
def leave(params)
puts "ChatObserver:"
puts params.inspect
end
end

Meteor::shooter.observe('<strong>chat1</strong>', ChatObserver.new) # สมมติว่า channel ชื่อ chat1 ละกัน [ ของในตัวอย่างมันเป็น simple_chat/chatroom ตัวหลังเดาว่าเป็น tag (ไม่ชัวร์นะ) ]
puts 'ChatObserver installed successfully.'
puts '[ PRESS ENTER KEY TO EXIT ]'
gets

นอกจากนี้ยังมีการเขียน observer ที่มีประโยชน์อยู่ในเทสของเวอร์ชั่น 3.2.0

อยู่ใน test/lib/shooting_star_test.rb

require File.join(File.dirname(__FILE__), '../test_helper')
require 'shooting_star'
require 'socket'
require 'thread'

$command_line = 'echo "testing"'

class ShootingStarTest &lt; Test::Unit::TestCase
class TestObserver
include DRb::DRbUndumped
def name; 'test-observer' end
def enter(params) @params = params end
def params; @params end
end

def setup
@config = ShootingStar.configure :silent =&gt; true,
:pid_file =&gt; 'tmp/pids/shooting_star.test.pid',
:log_file =&gt; 'log/shooting_star.test.log',
:server =&gt; {:host =&gt; '127.0.0.1', :port =&gt; 8081},
:shooter =&gt; {:uri =&gt; 'druby://127.0.0.1:7124'}
mutex = Mutex.new
mutex.lock
@thread = Thread.new{ShootingStar.start{mutex.unlock}}
mutex.lock
@query = "sig=0123456789&amp;execute=http://127.0.0.1:4001/meteor/strike"
@query2 = "sig=1123456789&amp;execute=http://127.0.0.1:4001/meteor/strike"
end

def teardown
ShootingStar.stop
File.rm_f(@config.pid_file)
File.rm_f @config.log_file
@thread.join
end

def test_connection_with_invalid_method
client = TCPSocket.open('127.0.0.1', 8081)
assert_not_nil client
send(client, "GET", "test/channel", @query)
assert client.read.empty?
end

def test_connection
client = TCPSocket.open('127.0.0.1', 8081)
send(client, "POST", "test/channel", @query)
assert_not_nil result = client.read
assert_not_nil result.index('xhr.getResponseHeader')
assert_not_nil result.index('test\/channel')
client.close

mutex = Mutex.new
mutex.lock
Thread.new do
client = TCPSocket.open('127.0.0.1', 8081)
send(client, "POST", "test/channel", "#{@query}&amp;__t__=c")
mutex.unlock
end
mutex.lock
shooter = DRbObject.new_with_uri('druby://127.0.0.1:7124')
assert_not_nil shooter
shooter.shoot("test/channel", 12, [])
assert_not_nil result = client.read
assert_not_nil result.index('meteor/strike/12')
end

def test_multi_user_communication
client1 = TCPSocket.open('127.0.0.1', 8081)
client2 = TCPSocket.open('127.0.0.1', 8081)
assert_not_nil client1
assert_not_nil client2
shooter = DRbObject.new_with_uri('druby://127.0.0.1:7124')
assert_not_nil shooter
observer = TestObserver.new
assert_not_nil observer
shooter.observe('test/channel', observer)
mutex = Mutex.new
mutex.lock
assert_nil observer.params
Thread.new do
send(client1, "POST", "test/channel", "#{@query}&amp;__t__=c")
mutex.unlock
end
mutex.lock
assert_nil observer.params
Thread.new do
send(client2, "POST", "test/channel", "#{@query2}&amp;__t__=c")
mutex.unlock
end
mutex.lock
assert_not_nil observer.params
assert_equal :enter, observer.params[:event]
assert_not_nil result1 = client1.read
assert_not_nil result1.index('meteor/strike/event-')
shooter.shoot("test/channel", 12, [])
assert_not_nil result2 = client2.read
assert_not_nil result2.index('meteor/strike/12')
end

def test_xmlsocket_server
client = TCPSocket.open('127.0.0.1', 8081)
client.write("&lt;policy-file-request/&gt;")
assert_not_nil client.read.index('allow-access-from')
end

def test_shooter_exists
shooter = ShootingStar.shooter
assert_not_nil shooter
end

def test_c10k_problem
bin = File.join(RAILS_ROOT, 'bin/test_c10k_problem')
src = File.join(File.dirname(__FILE__), 'test_c10k_problem.c')
if !File.exist?(bin) || File.mtime(src) &gt; File.mtime(bin)
system "gcc #{src} -o #{bin}"
end
system bin
end

private
def send(client, method, path, body)
client.write "#{method} /#{path} HTTP/1.1\n\r" +
"Host: #{@config.server.host}:#{@config.server.port}\n\r" +
"Keep-Alive: 300\n\r" +
"Content-length: #{body.length}\n\r" +
"Connection: keep-alive\n\r\n\r#{body}"
end
end

rails on mysql

หลังจากลง mysql-server, mysql-client, libmysqlclient15-dev

$ gem install mysql
$ mysql -u root -p
GRANT ALL PRIVILEGES ON *.* TO 'dsin'@'localhost'

1. dump data from .sql using rake

lib/tasks/sample_data.rake

# Provides task to load sample data.

require 'active_record'
require 'active_record/fixtures'

namespace :db do
DATA_DIRECTORY = "#{RAILS_ROOT}/lib/tasks/sample_data"
namespace :sample_data do

desc "Load sample data"
task :load =&gt; :environment do |t|
chdir DATA_DIRECTORY
system 'mysql lectures_development &lt; lectures.sql'
end
end
end
$ rake db:sample_data:load

2. ลง dojo-release-1.2.1.tar.gz ไปที่ public/javascript

/layouts/application.html.erb —

<%= stylesheet_link_tag "/javascripts/dojox/grid/resources/Grid.css" %>
<%= stylesheet_link_tag "/javascripts/dojox/grid/resources/tundraGrid.css" %>
<%= stylesheet_link_tag "/javascripts/dijit/themes/tundra/tundra" %>
<%= stylesheet_link_tag "/javascripts/dojo/resources/dojo.css" %>
<%= javascript_include_tag 'dojo/dojo', :djConfig => 'isDebug:false, parseOnLoad:true' %>

3. ถ้า javascript turn off จะไม่ทำใน noscript

<noscript>

<table> ... </table>

</noscript>

4.

dojo.data.ItemFileWriteStore

structure -> HTML table

—————————————-

<script type=”text/javascript”>
dojo.require(“dojo.data.ItemFileWriteStore”);

dojo.require(“dojox.grid.DataGrid”);

dojo.require(“dojox.grid.cells”);

var gridStore = new dojo.data.ItemFileWriteStore({
url: ‘/courses/<%= @course.id %>/lectures.json’,   # ajax ไปเอาข้อมูล JSON มาจาก URL นี้ แล้วโหลดเข้า structure ( Model )
id: ‘gridData’
});
var grid = null;

////////////////////////////////////////////////////////////////////////////

function saveGrid(){
gridStore.save();
}


// extend dojo.data.ItemFileWriteStore

dojo.declare(“CustomItemFileWriteStore”, dojo.data.ItemFileWriteStore, {
_saveCustom: saveCustom,
_sendUpdate: sendUpdate,
_formEncode: formEncode,
});


var saveCustom = function (saveCompleteCallback, saveFailedCallback) {
var success = true;
if (this.isDirty()) {
if (!this._isEmpty(this._pending._newItems)) {
success = this._sendCreate(this._pending._newItems);
}
if (success && !this._isEmpty(this._pending._deletedItems)) {
success = this._sendDelete(this._pending._deletedItems);
}
if (success && !this._isEmpty(this._pending._modifiedItems)) {
for (identity in this._pending._modifiedItems) {
var modifications = null;
// identity is the ORIGINAL value; we need the modified items too
if (this._itemsByIdentity) {
modifications = this._itemsByIdentity[identity];
} else {
modifications = this._arrayOfAllItems[identity];
}
success = this._sendUpdate(identity,modifications);
}
}
}
if(success) {
saveCompleteCallback();
} else {
saveFailedCallback();
}
}


function formEncode(item) {
var id = ‘lecture[id]=’ + item.id;
var dates = ‘&lecture[lecture_dates]=’ + escape(item.lecture_dates);
var number = ‘&lecture[lecture_number]=’ + escape(item.lecture_number);
var topics = ‘&lecture[topics]=’ + escape(item.topics);
var readings = ‘&lecture[readings]=’ + escape(item.readings);
var auth = ‘&’ + window._auth_token_name + ‘=’ + window._auth_token;
return id + dates + number + topics + readings + auth;
}


var sendUpdate = function(origItem, newItem){
var putData = this._formEncode(newItem);
return dojo.rawXhrPut({
preventCache: true,
url: “<%= course_lectures_path( @course ) %>/” + origItem + ‘.json’,
handleAs: “text”,
putData: putData,
sync: true,
timeout: 10000,
load: function(response, ioArgs){
return true;
},
error: function(response, ioArgs){
var errors = eval( ‘(‘ + response.responseText + ‘)’ );
alert(‘Error:’ + errors.base);
return false;
}
});
}

var gridStore = new CustomItemFileWriteStore({
url: ‘/courses/<%= @course.id %>/lectures.json’,   

id: ‘gridData’

});

///////////////////////////////////////////////////////////////////////////

dojo.addOnLoad( function() {
// Add style to the gridContainerOuter
var gridContainerOuterNode = dojo.byId(“gridContainerOuter”);
gridContainerOuterNode.setAttribute(“style”, “background:maroon; padding:0.5em; width:50em;”);
// Add style to the gridContainerInner
var gridContainerInnerNode = dojo.byId(“gridContainerInner”);
gridContainerInnerNode.setAttribute(“style”, “background:#fff; height:350px;”);
// Add style to the gridNode
var gridNode = dojo.byId(“gridNode”);
gridNode.setAttribute(“style”, “border: 1px solid #333;”);
// Create the column layout for the grid
var layout = [
{name: “Lecture number”, field: “lecture_number”, width: “3em”, editable: true, type: dojox.grid.cells.Editor },
{name: “Dates”,          field: “lecture_dates”,  width: “5em”, editable: true , type: dojox.grid.cells.Editor },
{name: “Topics”,         field: “topics”,         width: “35%”, editable: true , type: dojox.grid.cells.Editor },
{name: “Readings”,       field: “readings”,       width: “35%”, editable: true , type: dojox.grid.cells.Editor },
];

// editable: true — double click on cell and can edit

//  type: dojox.grid.cells.Editor — rich-text html editor

// ( dojo.require(“dojox.grid.cells”); )


// Create the grid
grid = new dojox.grid.DataGrid({
query: { id: ‘*’ },   #
store: gridStore, #
structure: layout
}, ‘gridNode‘);
grid.startup();

// Create the save button , callback to saveGrid()
saveButton = new dijit.form.Button({label:’Save changes’, onClick:saveGrid});
dojo.byId(‘scheduleGrid’).appendChild(saveButton.domNode);


});
</script>
<div id=”scheduleGrid”>
<div id=”gridContainerOuter”>
<div id=”gridContainerInner”>
<div id=”gridNode“></div>
</div>
</div>
</div>

5. extend ActiveRecord::Errors

application.rb —

class ActiveRecord::Errors
def to_json
@errors.to_json
end
end

how to update gem

หลังจากเจอปัญหา

Rails requires RubyGems >= 1.1.1 (you have 0.9.4). Please `gem update –system` and try again.

$ sudo gem update --system

$ sudo gem install -v=2.1.1 rails

$ gedit /usr/bin/gem
require 'rubygems'

require 'rubygems/gem_runner'  ## -- add this line in

ใน environment.rb แก้

RAILS_GEM_VERSION = '0.9.4'

nested resources rails

แก้ใน routes.rb

เพิ่มจาก

map.resources :courses

เป็น

map.resources :courses do |courses|
courses.resources :lectures
end

ทำให้เราสามารถเรียกได้จาก

http://127.0.0.1:3005/courses/1/lectures

ใช้ params[:course_id] ได้เลย

def index
@course = Course.find(params[:course_id])
@lectures = Lecture.find(:all)

respond_to do |format|
format.html # index.html.erb
format.xml  { render :xml => @lectures }
end
end

def show
@course = Course.find(params[:course_id])
@lecture = @course.lectures.find(params[:id]) # เปลี่ยนจาก Lecture.find(params[:id])

respond_to do |format|
format.html # show.html.erb
format.xml  { render :xml => @lecture }
format.json  { render :json => @lecture }
end
end

# GET /lectures/new
# GET /lectures/new.xml

def new
@course = Course.find(params[:course_id])

@lecture = Lecture.new

respond_to do |format|
format.html # new.html.erb
format.xml  { render :xml => @lecture }
format.json  { render :json => @lecture }
end
end

# GET /lectures/1/edit
def edit
@course = Course.find(params[:course_id])
@lecture = @course.lectures.find(params[:id]) # เปลี่ยนจาก Lecture.find(params[:id])
end

def create
@course = Course.find( params[:course_id])
@lecture = Lecture.new(params[:lecture])
@lecture.course_id = @course.id

respond_to do |format|
if @lecture.save
flash[:notice] = ‘Lecture was successfully created.’
format.html { redirect_to([@course, @lecture]) } # @lecture
format.xml  { render :xml => @lecture, :status => :created, :location => @lecture }
format.json  { render :json => @lecture, :status => :created, :location => @lecture }
else
format.html { render :action => “new” }
format.xml  { render :xml => @lecture.errors, :status => :unprocessable_entity }
format.json  { render :json => @lecture.errors, :status => :unprocessable_entity }
end
end
end

def update
@course = Course.find(params[:course_id])
@lecture = @course.lecture.find(params[:id]) # เปลี่ยนจาก @lecture = Lecture.find(params[:id])
@lecture.course_id = @course.id

respond_to do |format|
if @lecture.update_attributes(params[:lecture])
flash[:notice] = ‘Lecture was successfully updated.’
format.html { redirect_to([@course, @lecture]) } # @lecture
format.xml  { head :ok }
format.json  { head :ok }
else
format.html { render :action => “edit” }
format.xml  { render :xml => @lecture.errors, :status => :unprocessable_entity }
format.json  { render :json => @lecture.errors, :status => :unprocessable_entity }
end
end
end

def destroy
@course = Course.find(params[:course_id])
@lecture = Lecture.find(params[:id])
@lecture.destroy

respond_to do |format|
format.html { redirect_to(course_lectures_url(@course)) } แก้จาก # lectures_url
format.xml  { head :ok }
end
end

————————————————————————————-

view/index.html

<h1>Listing lectures for <%= @course.name + “, ” + @course.semester + “, ” + @course.year.to_s %></h1>

<table>
<tr>
<th>Lecture number</th>
<th>Date</th>
<th>Topics</th>
<th>Readings</th>

# ลบ <th>Course</th> ทิ้งไป
</tr>

<% for lecture in @lectures %>
<tr>
<td><%=h lecture.lecture_number %></td>
<td><%=h lecture.date %></td>
<td><%=h lecture.topics %></td>
<td><%=h lecture.readings %></td>
# ลบ <td><%=h lecture.course_id %></td>

<td><%= link_to ‘Show’, [@course, lecture] %></td> # แก้จาก lecture
<td><%= link_to ‘Edit’, edit_course_lecture_path(@course, lecture) %></td> # แก้จาก edit_lecture_path(lecture)
<td><%= link_to ‘Destroy’, [@course, lecture], :confirm => ‘Are you sure?’, :method => :delete %></td>
</tr>
<% end %>
</table>

<br />

<%= link_to ‘New lecture’, new_course_lecture_path(@course) %> # แก้จาก new_lecture_path

————————————————————————————-

มี blanket อยู่สองอันคือ

redirect_to([@course, @lecture])

<% form_for([@course, @lecture]) do |f| %>

มีที่ต้องระวังคือ

course_lectures_url(@course)

def destroy
format.html { redirect_to(course_lectures_url(@course)) }