王筝的博客
ruby学习

How to Install Redis on Mac OSX

$ mkdir redis && cd redis
$ curl -O http://download.redis.io/redis-stable.tar.gz
$ tar xzvf redis-stable.tar.gz
$ cd redis-stable
$ make
$ make test
$ sudo make install

使用下面的命令启动Redis Server。Now to start Redis server, you can run this command.

$ redis-server

To Test if Redis server is running run this command.

1$ redis-cli ping

If it replies “PONG”, then it’s good to go!

To set a value run the following commandsShell

$ redis-cliredis> set foo bar

OK

redis> get foo”bar”

rspec new expectation syntax
using the new rspec syntax for expectations instead the old `should`
you can write this in the new syntax like

“`it { is_expected.to validate_numericality_of(:years_lived) }“`

https://github.com/thoughtbot/shoulda-matchers#should-vs-is_expectedto

More info about http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/

 

http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

 

 

The most interesting addition to Ruby 2.3.0 is the Safe Navigation Operator(&.). A similar operator has been present in C# and Groovy for a long time with a slightly different syntax – ?.. So what does it do?

Scenario

Imagine you have an account that has an owner and you want to get the owner’s address. If you want to be safe and not risk a nil error, you would write something like the following:

if account && account.owner && account.owner.address
...
end

This is really verbose and annoying to type. ActiveSupport includes the try method which has a similar behaviour (but with few key differences that will be discussed later):

if account.try(:owner).try(:address)
...
end

It accomplishes the same thing – it either returns the address or nil if some value along the chain is nil. The first example may also return false if, for example, the owner is set to false.

Using the safe navigation operator (&.)

We can rewrite the previous example using the safe navigation operator:

account&.owner&.address

The syntax is a bit awkward but I guess we will have to deal with it because it does make the code more compact.

More examples

Let’s compare all three approaches in more detail.

account = Account.new(owner: nil) # account without an owner

account.owner.address
# => NoMethodError: undefined method `address' for nil:NilClass

account && account.owner && account.owner.address
# => nil

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => nil

No surprises so far. What if owner is false (unlikely but not impossible in the exciting world of shitty code)?

account = Account.new(owner: false)

account.owner.address
# => NoMethodError: undefined method `address' for false:FalseClass `

account && account.owner && account.owner.address
# => false

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => undefined method `address' for false:FalseClass`

Here comes the first surprise – the &. syntax only skips nil but recognizes false! It is not exactly equivalent to the s1 && s1.s2 && s1.s2.s3 syntax.

What if the owner is present but doesn’t respond to address?

account = Account.new(owner: Object.new)

account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>

account && account.owner && account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`

account.try(:owner).try(:address)
# => nil

account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`

Oops, the try method doesn’t check if the receiver responds to the given method. This is why it’s always better to use the stricter version of try – try!:

account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`

Pitfalls

nil.nil?

# => true

nil?.nil?
# => false

nil&.nil?
# => nil

As Joeri Samson pointed out in the comments, this section is actually wrong – I mistakenly used ?. instead of &.. But I still think that the last example is confusing and nil&.nil? should return true.

Array#dig and Hash#dig

The #dig method is, in my opinion, the most useful feature in this version. No longer do we have to write abominations like the following:

address = params[:account].try(:[], :owner).try(:[], :address)

# or

address = params[:account].fetch(:owner) { {} }.fetch(:address)

We can now simply use Hash#dig and accomplish the same thing:

address = params.dig(:account, :owner, :address)

Final words

I really dislike dealing with nil values in dynamic languages (check my previous posts) and think the addition of the safe operator and the dig methods are really neat.

 

http://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/

 

Yes
Implements a hash where keys :foo and "foo" are considered to be the same.
enum provider_name: {
trade_me: ‘Trade Me’,
facebook: ‘Facebook’,
google: ‘Google’
}
irb(main):010:0> provider = ‘google’
=> “google”
irb(main):011:0> provider.to_s
=> “google”
irb(main):012:0> provider.to_sym
=> :google
irb(main):013:0> ThirdPartyIdentity.provider_names[provider.to_sym]
=> “Google”
irb(main):014:0> ThirdPartyIdentity.provider_names[provider.to_s]
=> “Google”
https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html
rgb = ActiveSupport::HashWithIndifferentAccess.new

rgb[:black] = '#000000'
rgb[:black]  # => '#000000'
rgb['black'] # => '#000000'

rgb['white'] = '#FFFFFF'
rgb[:white]  # => '#FFFFFF'
rgb['white'] # => '#FFFFFF'

Internally symbols are mapped to strings when used as keys in the entire writing interface (calling []=merge, etc). This mapping belongs to the public interface. For example, given:

hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)

You are guaranteed that the key is returned as a string:

hash.keys # => ["a"]

Technically other types of keys are accepted:

hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
hash[0] = 0
hash # => {"a"=>1, 0=>0}

but this class is intended for use cases where strings or symbols are the expected keys and it is convenient to understand both as the same. For example the params hash in Ruby on Rails.

Note that core extensions define Hash#with_indifferent_access:

rgb = { black: '#000000', white: '#FFFFFF' }.with_indifferent_access

which may be handy.

To access this class outside of Rails, require the core extension with:

require "active_support/core_ext/hash/indifferent_access"

which will, in turn, require this file.

 

the time difference in months can be treated as:
`((Time.current – updated_at)/1.month.seconds).to_i`
This will give you:

“`
2.4.5 :013 > ((Time.current – 1.month.ago)/1.month.seconds).to_i
=> 1
2.4.5 :014 > ((Time.current – 10.month.ago)/1.month.seconds).to_i
=> 10
2.4.5 :015 > ((Time.current – 0.month.ago)/1.month.seconds).to_i
=> 0
“`
Also there is a good gem more human style like https://github.com/tmlee/time_difference a bit old but does the job.

prefer `Time.current` because this guy decides if it wants to be a `Time.zone.now` or a `Time.now` depending on the project settings.
This is also valid for `Date.current` and `DateTime.current`
a great explanation here https://nandovieira.com/working-with-dates-on-ruby-on-rails

 

https://github.com/rest-client/rest-client

1) Add new gem to Gemfile, then bundle install

gem ‘rest-client’, ‘~> 2.0.2’

 

2) Use rails console to test new gem

irb(main):001:0> url = “http://www.weather.com.cn/data/sk/101190408.html”

=> “http://www.weather.com.cn/data/sk/101190408.html”

irb(main):002:0> safeurl = URI.encode(url.strip)

=> “http://www.weather.com.cn/data/sk/101190408.html”

irb(main):003:0> response = RestClient.get(safeurl)

=> <RestClient::Response 200 “{\”weatherin…”>

irb(main):004:0> url_hash = JSON.parse(response.body)

=> {“weatherinfo”=>{“city”=>”太仓”, “cityid”=>”101190408”, “temp”=>”22.8”, “WD”=>”东风”, “WS”=>”小于3级”, “SD”=>”81%”, “AP”=>”1005.5hPa”, “njd”=>”暂无实况”, “WSE”=>”<3”, “time”=>”17:55”, “sm”=>”3.2”, “isRadar”=>”0”, “Radar”=>””}}

irb(main):005:0> response.code

=> 200

MySQL binlog的格式有三种:
  1. 基于SQL语句的复制(statement-based replication, SBR)
  2. 基于行的复制(row-based replication, RBR)
  3. 混合模式复制(mixed-based replication, MBR)
相应地,binlog的格式也有三种:STATEMENT,ROW,MIXED。它主要用于mysql的复制技术。
idb基本上都是供的基于行的复制。
  1. 基于SQL语句的复制(statement-based replication, SBR), (1) 优点: 历史悠久,技术成熟。 产生的binlog文件较小,比较节省空间。 binlog中包含了所有数据库更改信息,可以据此来审核数据库的安全等情况。 binlog可以用于实时的还原,而不仅仅用于复制。 主从版本可以不一样,从服务器版本可以比主服务器版本高。 (2) 缺点: 不是所有的UPDATE语句都能被复制,尤其是包含不确定操作的时候。 调用具有不确定因素的 UDF 时复制也可能出问题 使用以下函数的语句也无法被复制:
  • LOAD_FILE()
  • UUID()
  • USER()
  • FOUND_ROWS()
  • SYSDATE() (除非启动时启用了 –sysdate-is-now 选项) INSERT … SELECT 会产生比 RBR 更多的行级锁
2.基于行的复制(row-based replication, RBR), (1)优点: 任何情况都可以被复制,这对复制来说是最安全可靠的 多数情况下,从服务器上的表如果有主键的话,复制就会快了很多 复制以下几种语句时的行锁更少:
  • INSERT … SELECT
  • 包含 AUTO_INCREMENT 字段的 INSERT
  • 没有附带条件或者并没有修改很多记录的 UPDATE 或 DELETE 语句 执行 INSERT,UPDATE,DELETE 语句时锁更少 从服务器上采用多线程来执行复制成为可能。
(2)缺点: binlog 文件太大 复杂的回滚时 binlog 中会包含大量的数据 主服务器上执行 UPDATE 语句时,所有发生变化的记录都会写到 binlog 中,而 SBR 只会写一次,这会导致频繁发生 binlog 的并发写问题 UDF 产生的大 BLOB 值会导致复制变慢 无法从 binlog 中看到都复制了写什么语句,无法进行审计。
  1. 混合模式复制(mixed-based replication, MBR)。
是上面两种方式的折中,对于能用
对应的,binlog的格式也有三种:STATEMENT,ROW,MIXED。

 

内存分配机制

mc内存分配机制简介
memcached默认情况下采用了名为Slab Allocator的机制分配、管理内存,Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,以完全解决内存碎片问题。
先来解释一下与Slab Allocator存储有关的几个术语:
Page:分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk。 Chunk:用于缓存记录的内存空间。 Slab Class:特定大小的chunk的组。
Growth Factor:增长因数,默认为1.25(较早的版本固定为2)
mc启动后,会根据这个factor,计算出从1M逐步递减的不同的slab,如factor=1.25时:
slab class   1: chunk size     88 perslab 11915  
slab class   2: chunk size    112 perslab  9362  
slab class   3: chunk size    144 perslab  7281  
slab class   4: chunk size    184 perslab  5698  
slab class   5: chunk size    232 perslab  4519  
slab class   6: chunk size    296 perslab  3542  
slab class   7: chunk size    376 perslab  2788  
slab class   8: chunk size    472 perslab  2221  
slab class   9: chunk size    592 perslab  1771  
slab class  10: chunk size    744 perslab  1409
第一列数据(slab class),为slab的编号;
第二列数据是chunk的大小,跟slab class是一一对应的关系,可以通俗的理解为slab就是存放一组相同大小chunk的集合,只不过这个集合是固定的(1M),
第三列数据,表示每种不同slab中的page可以存放的chunk个数,实际上等于1MB/ (chunk size),例如slab1中的chunk size是88B,那么这种slab中每个page中可以存放的chunk个数为 1MB / 88B ,约等于11915
很显然,slab的chunk size越大,其中的每个page包含的chunk数量就越少
如图所示:
新入对象时,会根据自身大小来匹配slab列表,比如100KB的对象,根据最小空间损失原则,会被放入到slab2(size:112B)对应的page下,如下图
这时,如果slab2下的page中有尚可以使用的chunk(即空闲的chunk或者过期的chunk),slab2会优先使用这些chunk,在没有chunk可用的情况下,mc会去内存中再申请一个page,然后切分成chunk,然后使用;需要注意的是,根据 Slab Allocator算法, 该实例中的100KB对象,是永远没有机会存放到其他slab(如slab3,slab4等等),即便是其他slab中有大量的可用chunk,细心的朋友会发现,这种机制很有可能会导致内存浪费严重,mc命中率降低等问题,对,这种问题真的存在,这也正是这种机制的缺点,下面会进行详细的分析和探讨
mc数据删除机制简介:
首先我们知道,缓存在mc中的数据,不会主动从内存中消失,也就是说mc不会监视记录是否过期,而是在client端执行get方法时才去检查时间戳是否过期(这样做的目的就是避免在过期监视上耗费cpu资源,以提高mc的响应能力);每次有新对象加入时,mc会优先将对象置于已超时的同一规格chunk中,然而,即使如此,内存仍然会发生追加新记录时空间不足的情况,那么,当mc内存耗完后,又是怎样处理新入的数据呢?mc有两种处理策略,一种是默认的LRU(Least Recently Used),指删除近段时间最少使用同规格chunk,再将对象塞入),另一种策略是存满即不可再存,除非有过期的对象,否则会报错

一致性哈希原理

synchronized

This version is the fewest lines of code, largely because the synchronization mechanism used is built into the language and runtime. But the programmer has to remember to avoid a couple of common bugs: The wait() must be inside a while instead of an if, and notifyAll() must be used instead of notify() because there are two different logical conditions being awaited.
   public class SafeBox<V> {
     private V value;

     public synchronized V get() throws InterruptedException {
       while (value == null) {
         wait();
       }
       V result = value;
       value = null;
       notifyAll();
       return result;
     }

     public synchronized void set(V newValue) throws InterruptedException {
       while (value != null) {
         wait();
       }
       value = newValue;
       notifyAll();
     }
   }

ReentrantLock

This version is much more verbose than the synchronized version, and still suffers from the need for the programmer to remember to use while instead of if. However, one advantage is that we can introduce two separate Condition objects, which allows us to use signal() instead of signalAll(), which may be a performance benefit.

   public class SafeBox<V> {
     private final ReentrantLock lock = new ReentrantLock();
     private final Condition valuePresent = lock.newCondition();
     private final Condition valueAbsent = lock.newCondition();
     private V value;

     public V get() throws InterruptedException {
       lock.lock();
       try {
         while (value == null) {
           valuePresent.await();
         }
         V result = value;
         value = null;
         valueAbsent.signal();
         return result;
       } finally {
         lock.unlock();
       }
     }

     public void set(V newValue) throws InterruptedException {
       lock.lock();
       try {
         while (value != null) {
           valueAbsent.await();
         }
         value = newValue;
         valuePresent.signal();
       } finally {
         lock.unlock();
       }
     }
   }

 

 

背景

现实场景

  1. 单个节点的容量达到上限,无法继续单点增加内存,如何解决?
  2. 单个节点支撑的QPS达到上限,如何解决?

初步方案

增加N个缓存节点,为了保证缓存数据的均匀,一般情况会采用对key值hash,然后取模的方式,然后根据结果,确认数据落到哪台节点上:如下:

hash(key)%N

很好,这个的确解决了上面的问题,实现了初步的分布式存储,数据均匀分散到了各个节点上,流量请求也均匀的分散到了各个节点。

方案问题

  1. 某台服务器突然宕机。缓存服务器从N变为N-1台。 hash(key)%(N-1)
  2. 缓存容量达到上限或者请求处理达到上限,需要增加缓存服务器,假定增加1台,则缓存服务器从N变为N+1。hash(key)%(N+1)

增加或者删除缓存服务器的时候,意味着大部分的缓存都会失效。这个是比较致命的一点,缓存失效,如果业务为缓存不命中,查询DB的话,会导致一瞬间DB的压力陡增。可能会导致整个服务不可用。

目标

增删机器时,希望大部分key依旧在原有的缓存服务器上保持不变。举例来说:key1,key2,key3原先再Cache1机器上,现在增加一台缓存服务器,希望key1,key2,key3依旧在Cache1机器上,而不是在Cache2机器上。

原理

基本概念

一致性hash算法是希望在增删节点的时候,让尽可能多的数据不失效。

判断hash算法好坏的四个标准:

  • 平衡性:平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。
  • 单调性:单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。
  • 分散性:在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。
  • 负载:负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同 的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

 

使用常见的hash算法可以把一个key值哈希到一个具有2^32个桶的空间中。也可以理解成,将key值哈希到 [0, 2^32] 的一个数字空间中。 我们假设这个是个首尾连接的环形空间。

一致性Hash算法

  1. 构造hash环空间
  2. 把节点(服务器)映射到hash环
  3. 把数据对象映射到hash环
  4. 把数据对象映射到节点

 

把数据用hash函数(如MD5,CRC32),映射到一个很大的空间里,如图所示。数据的存储时,先得到一个hash值,对应到这个环中的每个位置,如k1对应到了图中所示的位置,然后沿顺时针找到一个机器节点B,将k1存储到B这个节点中。

如果B节点宕机了,则B上的数据就会落到C节点上,也就是说只有C节点受到影响,也就意味着解决了最开始的方案中可能的雪崩问题。如下图所示:

上面的简单的一致性hash的方案在某些情况下但依旧存在问题:一个节点宕机之后,数据需要落到距离他最近的节点上,会导致下个节点的压力突然增大,可能导致雪崩,整个服务挂掉。

  1. 之前请求到B上的流量转嫁到了C上,会导致C的流量增加,如果之前B上存在热点数据,则可能导致C扛不住压力挂掉。
  2. 之前存储到B上的key值转义到了C,会导致C的内容占用量增加,可能存在瓶颈。

当上面两个压力发生的时候,可能导致C节点也宕机了。那么压力便会传递到D上,又出现了类似滚雪球的情况,服务压力出现了雪崩,导致整个服务不可用。

虚拟节点

图中的A1、A2、B1、B2、C1、C2、D1、D2都是虚拟节点,机器A负载存储A1、A2的数据,机器B负载存储B1、B2的数据,机器C负载存储C1、C2的数据。由于这些虚拟节点数量很多,均匀分布,因此不会造成雪崩现象。

虚拟节点的 hash 计算可以采用对应节点的 IP 地址加数字后缀的方式。例如假设 cache A 的 IP 地址为202.168.14.241 。

引入虚拟节点前,计算 cache A 的 hash 值:

Hash(“202.168.14.241”);

引入虚拟节点后,计算虚拟节点cache A1 和 cache A2 的 hash 值:

Hash(“202.168.14.241#1”);  // cache A1

Hash(“202.168.14.241#2”);  // cache A2

实际节点的N个虚拟节点尽量随机分布在整数增加cache,就能尽量保到新cache点的key来自于不同的cache从而保证负载均衡.

hash(key) -> 虚拟节点 -> 真实节点

JAVA实现

public class Shard<S> { // S类封装了机器节点的信息 ,如name、password、ip、port等

    private TreeMap<Long, S> nodes; // 虚拟节点
    private List<S> shards; // 真实机器节点
    private final int NODE_NUM = 100; // 每个机器节点关联的虚拟节点个数

    public Shard(List<S> shards) {
        super();
        this.shards = shards;
        init();
    }

    private void init() { // 初始化一致性hash环
        nodes = new TreeMap<Long, S>();
        for (int i = 0; i != shards.size(); ++i) { // 每个真实机器节点都需要关联虚拟节点
            final S shardInfo = shards.get(i);

            for (int n = 0; n < NODE_NUM; n++)
                // 一个真实机器节点关联NODE_NUM个虚拟节点
                nodes.put(hash("SHARD-" + i + "-NODE-" + n), shardInfo);

        }
    }

    public S getShardInfo(String key) {
        SortedMap<Long, S> tail = nodes.tailMap(hash(key)); // 沿环的顺时针找到一个虚拟节点
        if (tail.size() == 0) {
            return nodes.get(nodes.firstKey());
        }
        return tail.get(tail.firstKey()); // 返回该虚拟节点对应的真实机器节点的信息
    }

    /**
     *  MurMurHash算法,是非加密HASH算法,性能很高,
     *  比传统的CRC32,MD5,SHA-1(这两个算法都是加密HASH算法,复杂度本身就很高,带来的性能上的损害也不可避免)
     *  等HASH算法要快很多,而且据说这个算法的碰撞率很低.
     *  http://murmurhash.googlepages.com/
     */
    private Long hash(String key) {

        ByteBuffer buf = ByteBuffer.wrap(key.getBytes());
        int seed = 0x1234ABCD;

        ByteOrder byteOrder = buf.order();
        buf.order(ByteOrder.LITTLE_ENDIAN);

        long m = 0xc6a4a7935bd1e995L;
        int r = 47;

        long h = seed ^ (buf.remaining() * m);

        long k;
        while (buf.remaining() >= 8) {
            k = buf.getLong();

            k *= m;
            k ^= k >>> r;
            k *= m;

            h ^= k;
            h *= m;
        }

        if (buf.remaining() > 0) {
            ByteBuffer finish = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN);
            finish.put(buf).rewind();
            h ^= finish.getLong();
            h *= m;
        }

        h ^= h >>> r;
        h *= m;
        h ^= h >>> r;

        buf.order(byteOrder);
        return h;
    }

}

 

应用

  1. memcache、redis服务器等缓存服务器的负载均衡(分布式cache);
  2. MySQL的分布式集群(分布式DB);
  3. 大量session的共享存储(分布式文件,或session服务器等)。

twemproxy 是 twitter 开源的一个轻量级的后端代理,兼容 redis/memcache 协议,可用以管理 redis/memcache 集群。

twemproxy 内部有实现一致性哈希算法,对于客户端而言,twemproxy 相当于是缓存数据库的入口,它无需知道后端的部署是怎样的。twemproxy 会检测与每个节点的连接是否健康,出现异常的节点会被剔除;待一段时间后,twemproxy 会再次尝试连接被剔除的节点。

通常,一个 Redis 节点池可以分由多个 twemproxy 管理,少数 twemproxy 负责写,多数负责读。twemproxy 可以实时获取节点池内的所有 Redis 节点的状态,但其对故障修复的支持还有待提高。解决的方法是可以借助 redis sentinel 来实现自动的主从切换,当主机 down 掉后,sentinel 会自动将从机配置为主机。而 twemproxy 可以定时向 redis sentinel 拉取信息,从而替换出现异常的节点。