精华内容
下载资源
问答
  • Redis5.0最近被作者突然放出来了,增加了很多新的特色功能。而Redis5.0最大的新特性就是多出了一个数据结构Stream,它是一个新的强大的支持多播的可持久化的消息队列,作者坦言Redis Stream狠狠地借鉴了Kafka的设计...

    Redis5.0最近被作者突然放出来了,增加了很多新的特色功能。而Redis5.0最大的新特性就是多出了一个数据结构Stream,它是一个新的强大的支持多播的可持久化的消息队列,作者坦言Redis Stream狠狠地借鉴了Kafka的设计。

    Redis Stream的结构如上图所示,它有一个消息链表,将所有加入的消息都串起来,每个消息都有一个唯一的ID和对应的内容。消息是持久化的,Redis重启后,内容还在。

    每个Stream都有唯一的名称,它就是Redis的key,在我们首次使用xadd指令追加消息时自动创建。

    每个Stream都可以挂多个消费组,每个消费组会有个游标last_delivered_id在Stream数组之上往前移动,表示当前消费组已经消费到哪条消息了。每个消费组都有一个Stream内唯一的名称,消费组不会自动创建,它需要单独的指令xgroup create进行创建,需要指定从Stream的某个消息ID开始消费,这个ID用来初始化last_delivered_id变量。

    每个消费组(Consumer Group)的状态都是独立的,相互不受影响。也就是说同一份Stream内部的消息会被每个消费组都消费到。

    同一个消费组(Consumer Group)可以挂接多个消费者(Consumer),这些消费者之间是竞争关系,任意一个消费者读取了消息都会使游标last_delivered_id往前移动。每个消费者者有一个组内唯一名称。

    消费者(Consumer)内部会有个状态变量pending_ids,它记录了当前已经被客户端读取的消息,但是还没有ack。如果客户端没有ack,这个变量里面的消息ID会越来越多,一旦某个消息被ack,它就开始减少。这个pending_ids变量在Redis官方被称之为PEL,也就是Pending Entries List,这是一个很核心的数据结构,它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢失了没处理。

    消息ID

    消息ID的形式是timestampInMillis-sequence,例如1527846880572-5,它表示当前的消息在毫米时间戳1527846880572时产生,并且是该毫秒内产生的第5条消息。消息ID可以由服务器自动生成,也可以由客户端自己指定,但是形式必须是整数-整数,而且必须是后面加入的消息的ID要大于前面的消息ID。

    消息内容

    消息内容就是键值对,形如hash结构的键值对,这没什么特别之处。

    增删改查xadd 追加消息

    xdel 删除消息,这里的删除仅仅是设置了标志位,不影响消息总长度

    xrange 获取消息列表,会自动过滤已经删除的消息

    xlen 消息长度

    del 删除Stream

    # *号表示服务器自动生成ID,后面顺序跟着一堆key/value

    127.0.0.1:6379> xadd codehole * name laoqian age 30 # 名字叫laoqian,年龄30岁

    1527849609889-0 # 生成的消息ID

    127.0.0.1:6379> xadd codehole * name xiaoyu age 29

    1527849629172-0

    127.0.0.1:6379> xadd codehole * name xiaoqian age 1

    1527849637634-0

    127.0.0.1:6379> xlen codehole

    (integer) 3

    127.0.0.1:6379> xrange codehole - + # -表示最小值, +表示最大值

    127.0.0.1:6379> xrange codehole - +

    1) 1) 1527849609889-0

    2) 1) "name"

    2) "laoqian"

    3) "age"

    4) "30"

    2) 1) 1527849629172-0

    2) 1) "name"

    2) "xiaoyu"

    3) "age"

    4) "29"

    3) 1) 1527849637634-0

    2) 1) "name"

    2) "xiaoqian"

    3) "age"

    4) "1"

    127.0.0.1:6379> xrange codehole 1527849629172-0 + # 指定最小消息ID的列表

    1) 1) 1527849629172-0

    2) 1) "name"

    2) "xiaoyu"

    3) "age"

    4) "29"

    2) 1) 1527849637634-0

    2) 1) "name"

    2) "xiaoqian"

    3) "age"

    4) "1"

    127.0.0.1:6379> xrange codehole - 1527849629172-0 # 指定最大消息ID的列表

    1) 1) 1527849609889-0

    2) 1) "name"

    2) "laoqian"

    3) "age"

    4) "30"

    2) 1) 1527849629172-0

    2) 1) "name"

    2) "xiaoyu"

    3) "age"

    4) "29"

    127.0.0.1:6379> xdel codehole 1527849609889-0

    (integer) 1

    127.0.0.1:6379> xlen codehole # 长度不受影响

    (integer) 3

    127.0.0.1:6379> xrange codehole - + # 被删除的消息没了

    1) 1) 1527849629172-0

    2) 1) "name"

    2) "xiaoyu"

    3) "age"

    4) "29"

    2) 1) 1527849637634-0

    2) 1) "name"

    2) "xiaoqian"

    3) "age"

    4) "1"

    127.0.0.1:6379> del codehole # 删除整个Stream

    (integer) 1

    独立消费

    我们可以在不定义消费组的情况下进行Stream消息的独立消费,当Stream没有新消息时,甚至可以阻塞等待。Redis设计了一个单独的消费指令xread,可以将Stream当成普通的消息队列(list)来使用。使用xread时,我们可以完全忽略消费组(Consumer Group)的存在,就好比Stream就是一个普通的列表(list)。

    # 从Stream头部读取两条消息

    127.0.0.1:6379> xread count 2 streams codehole 0-0

    1) 1) "codehole"

    2) 1) 1) 1527851486781-0

    2) 1) "name"

    2) "laoqian"

    3) "age"

    4) "30"

    2) 1) 1527851493405-0

    2) 1) "name"

    2) "yurui"

    3) "age"

    4) "29"

    # 从Stream尾部读取一条消息,毫无疑问,这里不会返回任何消息

    127.0.0.1:6379> xread count 1 streams codehole $

    (nil)

    # 从尾部阻塞等待新消息到来,下面的指令会堵住,直到新消息到来

    127.0.0.1:6379> xread block 0 count 1 streams codehole $

    # 我们从新打开一个窗口,在这个窗口往Stream里塞消息

    127.0.0.1:6379> xadd codehole * name youming age 60

    1527852774092-0

    # 再切换到前面的窗口,我们可以看到阻塞解除了,返回了新的消息内容

    # 而且还显示了一个等待时间,这里我们等待了93s

    127.0.0.1:6379> xread block 0 count 1 streams codehole $

    1) 1) "codehole"

    2) 1) 1) 1527852774092-0

    2) 1) "name"

    2) "youming"

    3) "age"

    4) "60"

    (93.11s)

    客户端如果想要使用xread进行顺序消费,一定要记住当前消费到哪里了,也就是返回的消息ID。下次继续调用xread时,将上次返回的最后一个消息ID作为参数传递进去,就可以继续消费后续的消息。

    block 0表示永远阻塞,直到消息到来,block 1000表示阻塞1s,如果1s内没有任何消息到来,就返回nil

    127.0.0.1:6379> xread block 1000 count 1 streams codehole $

    (nil)

    (1.07s)

    创建消费组

    Stream通过xgroup create指令创建消费组(Consumer Group),需要传递起始消息ID参数用来初始化last_delivered_id变量。

    127.0.0.1:6379> xgroup create codehole cg1 0-0 # 表示从头开始消费

    OK

    # $表示从尾部开始消费,只接受新消息,当前Stream消息会全部忽略

    127.0.0.1:6379> xgroup create codehole cg2 $

    OK

    127.0.0.1:6379> xinfo stream codehole # 获取Stream信息

    1) length

    2) (integer) 3 # 共3个消息

    3) radix-tree-keys

    4) (integer) 1

    5) radix-tree-nodes

    6) (integer) 2

    7) groups

    8) (integer) 2 # 两个消费组

    9) first-entry # 第一个消息

    10) 1) 1527851486781-0

    2) 1) "name"

    2) "laoqian"

    3) "age"

    4) "30"

    11) last-entry # 最后一个消息

    12) 1) 1527851498956-0

    2) 1) "name"

    2) "xiaoqian"

    3) "age"

    4) "1"

    127.0.0.1:6379> xinfo groups codehole # 获取Stream的消费组信息

    1) 1) name

    2) "cg1"

    3) consumers

    4) (integer) 0 # 该消费组还没有消费者

    5) pending

    6) (integer) 0 # 该消费组没有正在处理的消息

    2) 1) name

    2) "cg2"

    3) consumers # 该消费组还没有消费者

    4) (integer) 0

    5) pending

    6) (integer) 0 # 该消费组没有正在处理的消息

    消费

    Stream提供了xreadgroup指令可以进行消费组的组内消费,需要提供消费组名称、消费者名称和起始消息ID。它同xread一样,也可以阻塞等待新消息。读到新消息后,对应的消息ID就会进入消费者的PEL(正在处理的消息)结构里,客户端处理完毕后使用xack指令通知服务器,本条消息已经处理完毕,该消息ID就会从PEL中移除。

    # >号表示从当前消费组的last_delivered_id后面开始读

    # 每当消费者读取一条消息,last_delivered_id变量就会前进

    127.0.0.1:6379> xreadgroup GROUP cg1 c1 count 1 streams codehole >

    1) 1) "codehole"

    2) 1) 1) 1527851486781-0

    2) 1) "name"

    2) "laoqian"

    3) "age"

    4) "30"

    127.0.0.1:6379> xreadgroup GROUP cg1 c1 count 1 streams codehole >

    1) 1) "codehole"

    2) 1) 1) 1527851493405-0

    2) 1) "name"

    2) "yurui"

    3) "age"

    4) "29"

    127.0.0.1:6379> xreadgroup GROUP cg1 c1 count 2 streams codehole >

    1) 1) "codehole"

    2) 1) 1) 1527851498956-0

    2) 1) "name"

    2) "xiaoqian"

    3) "age"

    4) "1"

    2) 1) 1527852774092-0

    2) 1) "name"

    2) "youming"

    3) "age"

    4) "60"

    # 再继续读取,就没有新消息了

    127.0.0.1:6379> xreadgroup GROUP cg1 c1 count 1 streams codehole >

    (nil)

    # 那就阻塞等待吧

    127.0.0.1:6379> xreadgroup GROUP cg1 c1 block 0 count 1 streams codehole >

    # 开启另一个窗口,往里塞消息

    127.0.0.1:6379> xadd codehole * name lanying age 61

    1527854062442-0

    # 回到前一个窗口,发现阻塞解除,收到新消息了

    127.0.0.1:6379> xreadgroup GROUP cg1 c1 block 0 count 1 streams codehole >

    1) 1) "codehole"

    2) 1) 1) 1527854062442-0

    2) 1) "name"

    2) "lanying"

    3) "age"

    4) "61"

    (36.54s)

    127.0.0.1:6379> xinfo groups codehole # 观察消费组信息

    1) 1) name

    2) "cg1"

    3) consumers

    4) (integer) 1 # 一个消费者

    5) pending

    6) (integer) 5 # 共5条正在处理的信息还有没有ack

    2) 1) name

    2) "cg2"

    3) consumers

    4) (integer) 0 # 消费组cg2没有任何变化,因为前面我们一直在操纵cg1

    5) pending

    6) (integer) 0

    # 如果同一个消费组有多个消费者,我们可以通过xinfo consumers指令观察每个消费者的状态

    127.0.0.1:6379> xinfo consumers codehole cg1 # 目前还有1个消费者

    1) 1) name

    2) "c1"

    3) pending

    4) (integer) 5 # 共5条待处理消息

    5) idle

    6) (integer) 418715 # 空闲了多长时间ms没有读取消息了

    # 接下来我们ack一条消息

    127.0.0.1:6379> xack codehole cg1 1527851486781-0

    (integer) 1

    127.0.0.1:6379> xinfo consumers codehole cg1

    1) 1) name

    2) "c1"

    3) pending

    4) (integer) 4 # 变成了5条

    5) idle

    6) (integer) 668504

    # 下面ack所有消息

    127.0.0.1:6379> xack codehole cg1 1527851493405-0 1527851498956-0 1527852774092-0 1527854062442-0

    (integer) 4

    127.0.0.1:6379> xinfo consumers codehole cg1

    1) 1) name

    2) "c1"

    3) pending

    4) (integer) 0 # pel空了

    5) idle

    6) (integer) 745505

    Stream消息太多怎么办

    读者很容易想到,要是消息积累太多,Stream的链表岂不是很长,内容会不会爆掉就是个问题了。xdel指令又不会删除消息,它只是给消息做了个标志位。

    Redis自然考虑到了这一点,所以它提供了一个定长Stream功能。在xadd的指令提供一个定长长度maxlen,就可以将老的消息干掉,确保最多不超过指定长度。

    127.0.0.1:6379> xlen codehole

    (integer) 5

    127.0.0.1:6379> xadd codehole maxlen 3 * name xiaorui age 1

    1527855160273-0

    127.0.0.1:6379> xlen codehole

    (integer) 3

    我们看到Stream的长度被砍掉了。

    消息如果忘记ACK会怎样

    Stream在每个消费者结构中保存了正在处理中的消息ID列表PEL,如果消费者收到了消息处理完了但是没有回复ack,就会导致PEL列表不断增长,如果有很多消费组的话,那么这个PEL占用的内存就会放大。

    PEL如何避免消息丢失

    在客户端消费者读取Stream消息时,Redis服务器将消息回复给客户端的过程中,客户端突然断开了连接,消息就丢失了。但是PEL里已经保存了发出去的消息ID。待客户端重新连上之后,可以再次收到PEL中的消息ID列表。不过此时xreadgroup的起始消息ID不能为参数>,而必须是任意有效的消息ID,一般将参数设为0-0,表示读取所有的PEL消息以及自last_delivered_id之后的新消息。

    结论

    Stream的消费模型借鉴了kafka的消费分组的概念,它弥补了Redis Pub/Sub不能持久化消息的缺陷。但是它又不同于kafka,kafka的消息可以分partition,而Stream不行。如果非要分parition的话,得在客户端做,提供不同的Stream名称,对消息进行hash取模来选择往哪个Stream里塞。如果读者稍微研究过Redis作者的另一个开源项目Disque的话,这极可能是作者意识到Disque项目的活跃程度不够,所以将Disque的内容移植到了Redis里面。这只是本人的猜测,未必是作者的初衷。如果读者有什么不同的想法,可以在评论区一起参与讨论。

    展开全文
  • 目录一、亿级数据过滤和布隆过滤器1.布隆过滤器2.布隆过滤器代码实现1)自己简单模拟实现2)...持久化简介1)持久化发生了什么 | 从内存到磁盘2)如何尽可能保证持久化的安全2.Redis 中的两种持久化方式1)方式一:快照①

    一、亿级数据过滤和布隆过滤器

    1.布隆过滤器

    布隆过滤器已在这篇文章中做过详细解读,感兴趣的同学可以点击这里跳转》》》》》

    2.布隆过滤器代码实现

    1)自己简单模拟实现

    根据上面的基础理论,我们很容易就可以自己实现一个用于 简单模拟 的布隆过滤器数据结构:

    public static class BloomFilter { 
    	private byte[] data; 
    	
    	public BloomFilter(int initSize) { 
    		this.data = new byte[initSize * 2]; // 默认创建大小 * 2 的空间 
    	}
    
    	public void add(int key) { 
    		int location1 = Math.abs(hash1(key) % data.length); 
    		int location2 = Math.abs(hash2(key) % data.length); 
    		int location3 = Math.abs(hash3(key) % data.length); 
    		
    		data[location1] = data[location2] = data[location3] = 1; 
    	}
    	
    	public boolean contains(int key) { 
    		int location1 = Math.abs(hash1(key) % data.length); 
    		int location2 = Math.abs(hash2(key) % data.length); 
    		int location3 = Math.abs(hash3(key) % data.length); 
    		
    		return data[location1] * data[location2] * data[location3] == 1; 
    	}
    	
    	private int hash1(Integer key) { 
    		return key.hashCode(); 
    	}
    	
    	private int hash2(Integer key) { 
    		int hashCode = key.hashCode(); 
    		return hashCode ^ (hashCode >>> 3); 
    	}
    	
    	private int hash3(Integer key) { 
    		int hashCode = key.hashCode(); 
    		return hashCode ^ (hashCode >>> 16); 
    	} 
    }
    

    这里很简单,内部仅维护了一个 byte 类型的 data 数组,实际上 byte 仍然占有一个字节之多,可以优化成 bit来代替,这里也仅仅是用于方便模拟。另外我也创建了三个不同的hash函数,其实也就是借鉴HashMap哈希抖动的办法,分别使用自身的 hash和右移不同位数相异或的结果。并且提供了基础的 addcontains 方法。

    下面我们来简单测试一下这个布隆过滤器的效果如何:

    public static void main(String[] args) { 
    	Random random = new Random(); 
    	// 假设我们的数据有 1 百万 
    	int size = 1_000_000; 
    	// 用一个数据结构保存一下所有实际存在的值 
    	LinkedList<Integer> existentNumbers = new LinkedList<>(); 
    	BloomFilter bloomFilter = new BloomFilter(size); 
    	
    	for (int i = 0; i < size; i++) { 
    		int randomKey = random.nextInt(); 
    		existentNumbers.add(randomKey); 
    		bloomFilter.add(randomKey); 
    	}
    	
    	// 验证已存在的数是否都存在 
    	AtomicInteger count = new AtomicInteger(); 
    	AtomicInteger finalCount = count; 
    	existentNumbers.forEach(number -> { 
    		if (bloomFilter.contains(number)) {
    			finalCount.incrementAndGet(); 
    		} 
    	}); 
    	System.out.printf("实际的数据量: %d, 判断存在的数据量: %d \n", size, count.get()); 
    	
    	// 验证10个不存在的数 
    	count = new AtomicInteger(); 
    	while (count.get() < 10) { 
    		int key = random.nextInt(); 
    		if (existentNumbers.contains(key)) { 
    			continue; 
    		} else { 
    			// 这里一定是不存在的数 
    			System.out.println(bloomFilter.contains(key)); 
    			count.incrementAndGet(); 
    		} 
    	} 
    }
    

    输出如下:

    实际的数据量: 1000000, 判断存在的数据量: 1000000 
    false 
    true 
    false 
    true 
    true 
    true 
    false 
    false 
    true 
    false
    

    这就是前面说到的,当布隆过滤器说某个值 存在时,这个值 可能不存在,当它说某个值不存在时,那就 肯定不存在,并且还有一定的误判率…

    2)手动实现参考

    当然上面的版本特别 low,不过主体思想是不差的,这里也给出一个好一些的版本用作自己实现测试的参考:

    import java.util.BitSet; 
    
    public class MyBloomFilter { 
    
    	/**
    		* 位数组的大小 
    	*/ 
    	private static final int DEFAULT_SIZE = 2 << 24; 
    	/**
    		* 通过这个数组可以创建 6 个不同的哈希函数 
    		*/ 
    	private static final int[] SEEDS = new int[]{3, 13, 46, 71, 91, 134}; 
    
    	/**
    		* 位数组。数组中的元素只能是 0 或者 1
    		*/ 
    	private BitSet bits = new BitSet(DEFAULT_SIZE); 
    	
    	/**
    		* 存放包含 hash 函数的类的数组 
    		*/ 
    	private SimpleHash[] func = new SimpleHash[SEEDS.length]; 
    	
    	/**
    		* 初始化多个包含 hash 函数的类的数组,每个类中的 hash 函数都不一样 
    		*/ 
    	public MyBloomFilter() { 
    		// 初始化多个不同的 Hash 函数 
    		for (int i = 0; i < SEEDS.length; i++) { 
    			func[i] = new SimpleHash(DEFAULT_SIZE, SEEDS[i]); 
    		} 
    	}
    	
    	/**
    		* 添加元素到位数组 
    		*/ 
    	public void add(Object value) { 
    		for (SimpleHash f : func) { 
    			bits.set(f.hash(value), true); 
    		} 
    	}
    	
    	/**
    		* 判断指定元素是否存在于位数组 
    		*/ 
    	public boolean contains(Object value) { 
    		boolean ret = true; 
    		for (SimpleHash f : func) { 
    			ret = ret && bits.get(f.hash(value)); 
    		}
    		return ret; 
    	}
    	
    	/**
    		* 静态内部类。用于 hash 操作! 
    		*/ 
    	public static class SimpleHash { 
    		private int cap; 
    		private int seed; 
    		public SimpleHash(int cap, int seed) { 
    			this.cap = cap; 
    			this.seed = seed; 
    		}
    		
    		/**
    			* 计算 hash 值 
    			*/ 
    		public int hash(Object value) { 
    			int h; 
    			return (value == null) ? 0 : Math.abs(seed * (cap - 1) & ((h = 
    value.hashCode()) ^ (h >>> 16)));
    		} 
    	} 
    }
    

    3)使用 Google 开源的 Guava 中自带的布隆过滤器

    自己实现的目的主要是为了让自己搞懂布隆过滤器的原理,Guava 中布隆过滤器的实现算是比较权威的,所以实际项目中我们不需要手动实现一个布隆过滤器。

    首先我们需要在项目中引入 Guava 的依赖:

    <dependency> 
    	<groupId>com.google.guava</groupId> 
    	<artifactId>guava</artifactId> 
    	<version>28.0-jre</version> 
    </dependency>
    

    实际使用如下:
    我们创建了一个最多存放 最多 1500 个整数的布隆过滤器,并且我们可以容忍误判的概率为百分之(0.01)

    // 创建布隆过滤器对象 
    BloomFilter<Integer> filter = BloomFilter.create( 
    	Funnels.integerFunnel(), 
    	1500, 
    	0.01); 
    // 判断指定元素是否存在 
    System.out.println(filter.mightContain(1)); 
    System.out.println(filter.mightContain(2));
    // 将元素添加进布隆过滤器 
    filter.put(1); 
    filter.put(2); 
    System.out.println(filter.mightContain(1)); 
    System.out.println(filter.mightContain(2));
    

    在我们的示例中,当 mightContain()方法返回 true 时,我们可以 99% 确定该元素在过滤器中,当过滤器返回 false 时,我们可以 100% 确定该元素不存在于过滤器中。

    Guava 提供的布隆过滤器的实现还是很不错的 (想要详细了解的可以看一下它的源码实现),但是它有一个重大的缺陷就是只能单机使用 (另外,容量扩展也不容易),而现在互联网一般都是分布式的场景。为了解决这个问题,我们就需要用到 Redis 中的布隆过滤器了。


    小插曲:
    更多阿里、腾讯、美团、京东等一线互联网大厂Java面试真题;包含:基础、并发、锁、JVM、设计模式、数据结构、反射/IO、数据库、Redis、Spring、消息队列、分布式、Zookeeper、Dubbo、Mybatis、Maven、面经等。
    更多Java程序员技术进阶小技巧;例如高效学习(如何学习和阅读代码、面对枯燥和量大的知识)高效沟通(沟通方式及技巧、沟通技术)
    更多Java大牛分享的一些职业生涯分享文档


    请点击这里添加》》》》》》》》》社群,免费获取


    比你优秀的对手在学习,你的仇人在磨刀,你的闺蜜在减肥,隔壁老王在练腰, 我们必须不断学习,否则我们将被学习者超越!
    趁年轻,使劲拼,给未来的自己一个交代!

    二、GeoHash查找附近的人

    像微信 “附近的人”,美团 “附近的餐厅”,支付宝共享单车 “附近的车” 是怎么设计实现的呢?

    1.使用数据库实现查找附近的人

    我们都知道,地球上的任何一个位置都可以使用二维的 经纬度 来表示,经度范围 [-180, 180],纬度范围 [-90, 90],纬度正负以赤道为界,北正南负,经度正负以本初子午线 (英国格林尼治天文台) 为界,东正西负。比如说,北京人民英雄纪念碑的经纬度坐标就是 (39.904610, 116.397724),都是正数,因为中国位于东北半球。

    所以,当我们使用数据库存储了所有人的 经纬度 信息之后,我们就可以基于当前的坐标节点,来划分出一个矩形的范围,来得知附近的人,如下图:

    所以,我们很容易写出下列的伪 SQL 语句:

    SELECT id FROM positions WHERE x0 - r < x < x0 + r AND y0 - r < y < y0 + r
    

    如果我们还想进一步地知道与每个坐标元素的距离并排序的话,就需要一定的计算。

    当两个坐标元素的距离不是很远的时候,我们就可以简单利用 勾股定理 就能够得出他们之间的 距离。不过需要注意的是,地球不是一个标准的球体,经纬度的密度不一样 的,所以我们使用勾股定理计算平方之后再求和时,需要按照一定的系数 加权 再进行求和。当然,如果不准求精确的话,加权也不必了。

    参考下方 参考资料 2 我们能够差不多能写出如下优化之后的 SQL 语句来:(仅供参考)

    SELECT
    	* 
    FROM
    	users_location 
    WHERE
    	latitude > '.$lat.' - 1 
    	AND latitude < '.$lat.' + 1 
    	AND longitude > '.$lon.' - 1 
    	AND longitude < '.$lon.' + 1 
    ORDER BY 
    	ACOS(SIN( ( '.$lat.' * 3.1415 ) / 180 ) * SIN( ( latitude * 3.1415 ) / 180 ) + COS( ( '.$lat.' * 3.1415 ) / 180 ) * 
    	COS( ( latitude * 3.1415 ) / 180 ) * COS( ( '.$lon.' * 3.1415 ) / 180 - ( longitude * 3.1415 ) / 180 ) ) * 6380 ASC LIMIT 10 ';
    

    为了满足高性能的矩形区域算法,数据表也需要把经纬度坐标加上 双向复合索引 (x, y),这样可以满足最大优化查询性能。

    2.GeoHash 算法简述

    这是业界比较通用的,用于 地理位置距离排序 的一个算法,Redis 也采用了这样的算法。GeoHash算法将 二维的经纬度 数据映射到 一维 的整数,这样所有的元素都将在挂载到一条线上,距离靠近的二维坐标映射到一维后的点之间距离也会很接近。当我们想要计算 「附近的人时」,首先将目标位置映射到这条线上,然后在这个一维的线上获取附近的点就行了。

    它的核心思想就是把整个地球看成是一个 二维的平面,然后把这个平面不断地等分成一个一个小的方格,每一个 坐标元素都位于其中的 唯一一个方格 中,等分之后的 方格越小,那么坐标也就越精确,类似下图:

    经过划分的地球,我们需要对其进行编码:

    经过这样顺序的编码之后,如果你仔细观察一会儿,你就会发现一些规律:

    • 横着的所有编码中,第 2 位和第 4 位都是一样的,例如第一排第一个 0101 和第二个 0111 ,他们的第 2 位和第 4 位都是 1
    • 竖着的所有编码中,第 1 位和第 3 位是递增的,例如第一排第一个 0101 ,如果单独把第 1 位和第 3 位拎出来的话,那就是 00 ,同理看第一排第二个0111,同样的方法第 1 位和第 3 位拎出来是 01 ,刚好是 00 递增一个;

    通过这样的规律我们就把每一个小方块儿进行了一定顺序的编码,这样做的 好处 是显而易见的:每一个元素坐标既能够被 唯一标识 在这张被编码的地图上,也不至于 暴露特别的具体的位置,因为区域是共享的,我可以告诉你我就在公园附近,但是在具体的哪个地方你就无从得知了。

    总之,我们通过上面的思想,能够把任意坐标变成一串二进制的编码了,类似于11010010110001000100这样 (注意经度和维度是交替出现的哦…),通过这个整数我们就可以还原出元素的坐标,整数越长,还原出来的坐标值的损失程序就越小。对于 “附近的人” 这个功能来说,损失的一点经度可以忽略不计。

    最后就是一个 Base32 (0~9, a~z, 去掉 a/i/l/o 四个字母) 的编码操作,让它变成一个字符串,例如上面那一串儿就变成了 wx4g0ec1

    在 Redis 中,经纬度使用 52 位的整数进行编码,放进了 zset 里面,zset的 value 是元素的 keyscoreGeoHash52 位整数值。zset 的score虽然是浮点数,但是对于 52 位的整数值来说,它可以无损存储。

    3.在Redis中使用Geo

    下方内容引自 参考资料 1 - 《Redis 深度历险》

    在使用 Redis 进行 Geo 查询 时,我们要时刻想到它的内部结构实际上只是一个 zset(skiplist)。通过zset 的 score 排序就可以得到坐标附近的其他元素 (实际情况要复杂一些,不过这样理解足够了),通过将 score 还原成坐标值就可以得到元素的原始坐标了。

    Redis 提供的 Geo 指令只有 6 个,很容易就可以掌握。

    1)增加

    geoadd指令携带集合名称以及多个经纬度名称三元组,注意这里可以加入多个三元组。

    127.0.0.1:6379> geoadd company 116.48105 39.996794 juejin 
    (integer) 1 
    127.0.0.1:6379> geoadd company 116.514203 39.905409 ireader 
    (integer) 1 
    127.0.0.1:6379> geoadd company 116.489033 40.007669 meituan 
    (integer) 1 
    127.0.0.1:6379> geoadd company 116.562108 39.787602 jd 116.334255 40.027400 
    xiaomi 
    (integer) 2
    

    不过很奇怪… Redis 没有直接提供 Geo 的删除指令,但是我们可以通过 zset 相关的指令来操作 Geo 数据,所以元素删除可以使用 zrem 指令即可。

    2)距离

    geodist指令可以用来计算两个元素之间的距离,携带集合名称、2 个名称和距离单位。

    127.0.0.1:6379> geodist company juejin ireader km 
    "10.5501" 
    127.0.0.1:6379> geodist company juejin meituan km 
    "1.3878" 
    127.0.0.1:6379> geodist company juejin jd km 
    "24.2739" 
    127.0.0.1:6379> geodist company juejin xiaomi km 
    "12.9606" 
    127.0.0.1:6379> geodist company juejin juejin km 
    "0.0000"
    

    我们可以看到掘金离美团最近,因为它们都在望京。距离单位可以是 m 、 km 、 ml 、 ft ,分别代表米、千米、英里和尺。

    3)获取元素位置

    geopos指令可以获取集合中任意元素的经纬度坐标,可以一次获取多个。

    127.0.0.1:6379> geopos company juejin 
    1) 1) "116.48104995489120483" 
    	2) "39.99679348858259686" 
    127.0.0.1:6379> geopos company ireader 
    1) 1) "116.5142020583152771" 
    	2) "39.90540918662494363" 
    127.0.0.1:6379> geopos company juejin ireader 
    1) 1) "116.48104995489120483" 
    	2) "39.99679348858259686" 
    2) 1) "116.5142020583152771" 
    	2) "39.90540918662494363"
    

    我们观察到获取的经纬度坐标和 geoadd 进去的坐标有轻微的误差,原因是 Geohash 对二维坐标进行的一维映射是有损的,通过映射再还原回来的值会出现较小的差别。对于 「附近的人」 这种功能来说,这点误差根本不是事。

    4)获取元素的 hash 值

    geohash 可以获取元素的经纬度编码字符串,上面已经提到,它是 base32编码。 你可以使用这个编码值去 http://geohash.org/${hash} 中进行直接定位,它是 Geohash 的标准编码值。

    127.0.0.1:6379> geohash company ireader 
    1) "wx4g52e1ce0" 
    127.0.0.1:6379> geohash company juejin 
    1) "wx4gd94yjn0"
    

    让我们打开地址 http://geohash.org/wx4g52e1ce0 ,观察地图指向的位置是否正确:

    很好,就是这个位置,非常准确。

    5)附近的公司

    georadiusbymember指令是最为关键的指令,它可以用来查询指定元素附近的其它元素,它的参数非常复杂。

    # 范围 20 公里以内最多 3 个元素按距离正排,它不会排除自身 
    127.0.0.1:6379> georadiusbymember company ireader 20 km count 3 asc 
    1) "ireader" 
    2) "juejin" 
    3) "meituan" 
    # 范围 20 公里以内最多 3 个元素按距离倒排
    127.0.0.1:6379> georadiusbymember company ireader 20 km count 3 desc 
    1) "jd" 
    2) "meituan" 
    3) "juejin" 
    # 三个可选参数 withcoord withdist withhash 用来携带附加参数 
    # withdist 很有用,它可以用来显示距离 
    127.0.0.1:6379> georadiusbymember company ireader 20 km withcoord withdist withhash count 3 asc 
    1) 1) "ireader" 
    	2) "0.0000" 
    	3) (integer) 4069886008361398 
    	4) 1) "116.5142020583152771" 
    	2) "39.90540918662494363" 
    2) 1) "juejin" 
    	2) "10.5501" 
    	3) (integer) 4069887154388167 
    	4) 1) "116.48104995489120483" 
    	2) "39.99679348858259686" 
    3) 1) "meituan" 
    	2) "11.5748" 
    	3) (integer) 4069887179083478 
    	4) 1) "116.48903220891952515" 
    	2) "40.00766997707732031"
    

    除了georadiusbymember指令根据元素查询附近的元素,Redis 还提供了根据坐标值来查询附近的元素,这个指令更加有用,它可以根据用户的定位来计算「附近的车」,「附近的餐馆」等。它的参数和georadiusbymember基本一致,除了将目标元素改成经纬度坐标值:

    127.0.0.1:6379> georadius company 116.514202 39.905409 20 km withdist count 3 asc 
    1) 1) "ireader" 
    	2) "0.0000" 
    2) 1) "juejin" 
    	2) "10.5501" 
    3) 1) "meituan" 
    	2) "11.5748"
    

    6)注意事项

    在一个地图应用中,车的数据、餐馆的数据、人的数据可能会有百万千万条,如果使用 RedisGeo数据结构,它们将 全部放在一个 zset 集合中。在 Redis 的集群环境中,集合可能会从一个节点迁移到另一个节点,如果单个 key 的数据过大,会对集群的迁移工作造成较大的影响,在集群环境中单个 key对应的数据量不宜超过 1M,否则会导致集群迁移出现卡顿现象,影响线上服务的正常运行。

    所以,这里建议 Geo 的数据使用 单独的 Redis 实例部署,不使用集群环境。

    如果数据量过亿甚至更大,就需要对 Geo 数据进行拆分,按国家拆分、按省拆分,按市拆分,在人口特大城市甚至可以按区拆分。这样就可以显著降低单个 zset 集合的大小。


    小插曲:
    更多阿里、腾讯、美团、京东等一线互联网大厂Java面试真题;包含:基础、并发、锁、JVM、设计模式、数据结构、反射/IO、数据库、Redis、Spring、消息队列、分布式、Zookeeper、Dubbo、Mybatis、Maven、面经等。
    更多Java程序员技术进阶小技巧;例如高效学习(如何学习和阅读代码、面对枯燥和量大的知识)高效沟通(沟通方式及技巧、沟通技术)
    更多Java大牛分享的一些职业生涯分享文档


    请点击这里添加》》》》》》》》》社群,免费获取


    比你优秀的对手在学习,你的仇人在磨刀,你的闺蜜在减肥,隔壁老王在练腰, 我们必须不断学习,否则我们将被学习者超越!
    趁年轻,使劲拼,给未来的自己一个交代!

    三、持久化

    1.持久化简介

    Redis 的数据 全部存储内存 中,如果 突然宕机,数据就会全部丢失,因此必须有一套机制来保证Redis的数据不会因为故障而丢失,这种机制就是 Redis 的 持久化机制,它会将内存中的数据库状态保存到磁盘中。

    1)持久化发生了什么 | 从内存到磁盘

    我们来稍微考虑一下 Redis 作为一个 “内存数据库” 要做的关于持久化的事情。通常来说,从客户端发起请求开始,到服务器真实地写入磁盘,需要发生如下几件事情:

    详细版 的文字描述大概就是下面这样:

    1. 客户端向数据库 发送写命令 (数据在客户端的内存中)
    2. 数据库 接收 到客户端的 写请求 (数据在服务器的内存中)
    3. 数据库 调用系统 API 将数据写入磁盘 (数据在内核缓冲区中)
    4. 操作系统将 写缓冲区 传输到 磁盘控控制器 (数据在磁盘缓存中)
    5. 操作系统的磁盘控制器将数据 写入实际的物理媒介 中 (数据在磁盘中)

    注意: 上面的过程其实是 极度精简 的,在实际的操作系统中,缓存缓冲区会比这多得多…

    2)如何尽可能保证持久化的安全

    如果我们故障仅仅涉及到 软件层面 (该进程被管理员终止或程序崩溃) 并且没有接触到内核,那么在 上 述步骤 3 成功返回之后,我们就认为成功了。即使进程崩溃,操作系统仍然会帮助我们把数据正确地写入磁盘。

    如果我们考虑 停电/ 火灾更具灾难性 的事情,那么只有在完成了第 5 步之后,才是安全的。

    所以我们可以总结得出数据安全最重要的阶段是:步骤三、四、五,即:

    • 数据库软件调用写操作将用户空间的缓冲区转移到内核缓冲区的频率是多少?
    • 内核多久从缓冲区取数据刷新到磁盘控制器?
    • 磁盘控制器多久把数据写入物理媒介一次?
    • 注意: 如果真的发生灾难性的事件,我们可以从上图的过程中看到,任何一步都可能被意外打断丢失,所以只能 尽可能地保证 数据的安全,这对于所有数据库来说都是一样的。

    我们从 第三步 开始。Linux 系统提供了清晰、易用的用于操作文件的 POSIX file API20 多年过去,仍然还有很多人对于这一套 API 的设计津津乐道,我想其中一个原因就是因为你光从API 的命名就能够很清晰地知道这一套 API 的用途:

    int open(const char *path, int oflag, .../*,mode_t mode */); 
    int close (int filedes);int remove( const char *fname ); 
    ssize_t write(int fildes, const void *buf, size_t nbyte); 
    ssize_t read(int fildes, void *buf, size_t nbyte);
    

    所以,我们有很好的可用的 API 来完成 第三步,但是对于成功返回之前,我们对系统调用花费的时间没有太多的控制权。

    然后我们来说说 第四步。我们知道,除了早期对电脑特别了解那帮人 (操作系统就这帮人搞的),实际的物理硬件都不是我们能够 直接操作 的,都是通过 操作系统调用 来达到目的的。为了防止过慢的 I/O 操作拖慢整个系统的运行,操作系统层面做了很多的努力,譬如说 上述第四步 提到的 写缓冲区,并不是所有的写操作都会被立即写入磁盘,而是要先经过一个缓冲区,默认情况下,Linux 将在 30 秒 后实际提交写入。

    但是很明显,30 秒 并不是 Redis 能够承受的,这意味着,如果发生故障,那么最近 30 秒内写入的所有数据都可能会丢失。幸好 PROSIX API 提供了另一个解决方案:fsync ,该命令会 强制 内核将 缓冲区 写入 磁盘,但这是一个非常消耗性能的操作,每次调用都会 阻塞等待 直到设备报告 IO 完成,所以一般在生产环境的服务器中,Redis 通常是每隔 1s 左右执行一次 fsync 操作。

    到目前为止,我们了解到了如何控制 第三步第四步 ,但是对于第五步,我们 完全无法控制。也许一些内核实现将试图告诉驱动实际提交物理介质上的数据,或者控制器可能会为了提高速度而重新排序写操作,不会尽快将数据真正写到磁盘上,而是会等待几个多毫秒。这完全是我们无法控制的。

    2.Redis 中的两种持久化方式

    1)方式一:快照

    Redis 快照 是最简单的 Redis 持久性模式。当满足特定条件时,它将生成数据集的时间点快照,例如,如果先前的快照是在2分钟前创建的,并且现在已经至少有 100 次新写入,则将创建一个新的快照。此条件可以由用户配置 Redis 实例来控制,也可以在运行时修改而无需重新启动服务器。快照作为包含整个数据集的单个 .rdb 文件生成。

    但我们知道,Redis 是一个 单线程 的程序,这意味着,我们不仅仅要响应用户的请求,还需要进行内存快照。而后者要求 Redis 必须进行 IO 操作,这会严重拖累服务器的性能。

    还有一个重要的问题是,我们在 持久化的同时内存数据结构 还可能在 变化,比如一个大型的 hash字典正在持久化,结果一个请求过来把它删除了,可是这才刚持久化结束,咋办?

    ①、使用系统多进程 COW(Copy On Write) 机制 | fork 函数

    操作系统多进程 COW(Copy On Write) 机制 拯救了我们。Redis 在持久化时会调用 glibc 的函数fork产生一个子进程,简单理解也就是基于当前进程 复制 了一个进程,主进程和子进程会共享内存里面的代码块和数据段:

    这里多说一点,为什么 fork 成功调用后会有两个返回值呢? 因为子进程在复制时复制了父进程的堆栈段,所以两个进程都停留在了 fork 函数中 (都在同一个地方往下继续"同时"执行),等待返回,所以一次在父进程中返回子进程的 pid另一次在子进程中返回零系统资源不够时返回负数。 (伪代码如下)

    pid = os.fork() 
    if pid > 0: 
    	handle_client_request() # 父进程继续处理客户端请求 
    if pid == 0: 
    	handle_snapshot_write() # 子进程处理快照写磁盘 
    if pid < 0: 
    	# fork error
    

    所以 快照持久化 可以完全交给 子进程 来处理,父进程 则继续 处理客户端请求子进程 做数据持久化,它 不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中。但是父进程 不一样,它必须持续服务客户端请求,然后对 内存数据结构进行不间断的修改

    这个时候就会使用操作系统的 COW 机制来进行 数据段页面 的分离。数据段是由很多操作系统的页面组合而成,当父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后 对这个复制的页面进行修改。这时 子进程 相应的页面是 没有变化的,还是进程产生时那一瞬间的数据。

    子进程因为数据没有变化,它能看到的内存里的数据在进程产生的一瞬间就凝固了,再也不会改变,这也是为什么 Redis 的持久化 叫「快照」的原因。接下来子进程就可以非常安心的遍历数据了进行序列化写磁盘了。

    2)方式二:AOF

    快照不是很持久。如果运行 Redis 的计算机停止运行,电源线出现故障或者您 kill -9 的实例意外发生,则写入 Redis 的最新数据将丢失。尽管这对于某些应用程序可能不是什么大问题,但有些使用案例具有充分的耐用性,在这些情况下,快照并不是可行的选择。

    AOF(Append Only File - 仅追加文件) 它的工作方式非常简单:每次执行 修改内存 中数据集的写操作时,都会 记录 该操作。假设 AOF 日志记录了自 Redis 实例创建以来 所有的修改性指令序列,那么就可以通过对一个空的 Redis 实例 顺序执行所有的指令,也就是 「重放」,来恢复 Redis 当前实例的内存数据结构的状态。

    为了展示 AOF 在实际中的工作方式,我们来做一个简单的实验:

    ./redis-server --appendonly yes # 设置一个新实例为 AOF 模式
    

    然后我们执行一些写操作:

    redis 127.0.0.1:6379> set key1 Hello 
    OK
    redis 127.0.0.1:6379> append key1 " World!" 
    (integer) 12 
    redis 127.0.0.1:6379> del key1 
    (integer) 1 
    redis 127.0.0.1:6379> del non_existing_key 
    (integer) 0
    

    前三个操作实际上修改了数据集,第四个操作没有修改,因为没有指定名称的键。这是 AOF 日志保存的文本:

    $ cat appendonly.aof 
    *2
    $6
    SELECT 
    $1
    0
    *3
    $3
    set 
    $4
    key1 
    $5
    Hello 
    *3
    $6
    append 
    $4
    key1 
    $7
    World! 
    *2
    $3
    del 
    $4
    key1
    

    如您所见,最后的那一条 DEL` 指令不见了,因为它没有对数据集进行任何修改。

    就是这么简单。当 Redis 收到客户端修改指令后,会先进行参数校验、逻辑处理,如果没问题,就 立即将该指令文本 存储 到 AOF 日志中,也就是说,先执行指令再将日志存盘。这一点不同于 MySQLLevelDBHBase 等存储引擎,如果我们先存储日志再做逻辑处理,这样就可以保证即使宕机了,我们仍然可以通过之前保存的日志恢复到之前的数据状态,但是 Redis 为什么没有这么做呢

    Emmm… 没找到特别满意的答案,引用一条来自知乎上的回答吧:

    • @缘于专注 - 我甚至觉得没有什么特别的原因。仅仅是因为,由于AOF文件会比较大,为了避免写入无效指令(错误指令),必须先做指令检查?如何检查,只能先执行了。因为语法级别检查并不能保证指令的有效性,比如删除一个不存在的key。而MySQL这种是因为它本身就维护了所有的表的信息,所以可以语法检查后过滤掉大部分无效指令直接记录日志,然后再执行。

    ①、AOF 重写

    Redis 在长期运行的过程中,AOF 的日志会越变越长。如果实例宕机重启,重放整个 AOF 日志会非常耗时,导致长时间 Redis 无法对外提供服务。所以需要对 AOF 日志 “瘦身”

    Redis 提供了 bgrewriteaof 指令用于对 AOF 日志进行瘦身。其 原理 就是 开辟一个子进程 对内存进行 遍历 转换成一系列 Redis 的操作指令,序列化到一个新的 AOF 日志文件 中。序列化完毕后再将操作期间发生的 增量 AOF 日志 追加到这个新的 AOF 日志文件中,追加完毕后就立即替代旧的 AOF 日志文件了,瘦身工作就完成了。

    ②、fsync

    AOF 日志是以文件的形式存在的,当程序对 AOF 日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷回到磁盘的。

    就像我们 上方第四步 描述的那样,我们需要借助glibc 提供的fsync(int fd)函数来讲指定的文件内容强制从内核缓存刷到磁盘。但 “强制开车” 仍然是一个很消耗资源的一个过程,需要 “节制”!通常来说,生产环境的服务器,Redis 每隔 1s 左右执行一次 fsync 操作就可以了。

    Redis 同样也提供了另外两种策略,一个是 永不 fsync ,来让操作系统来决定合适同步磁盘,很不安全,另一个是 来一个指令就 fsync一次,非常慢。但是在生产环境基本不会使用,了解一下即可。

    3)Redis 4.0 混合持久化

    重启 Redis 时,我们很少使用 rdb 来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重放,但是重放 AOF 日志性能相对 rdb 来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很长的时间。

    Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自持久化开始到持久化结束 的这段时间发生的增量 AOF 日志,通常这部分 AOF 日志很小:

    于是在 Redis 重启的时候,可以先加载 rdb 的内容,然后再重放增量 AOF 日志就可以完全替代之前的AOF全量文件重放,重启效率因此大幅得到提升。


    参考资料:《Java中高级核心知识全面解析》限量100份,有一些人已经通过我之前的文章获取了哦!
    名额有限先到先得!!!
    有想要获取这份学习资料的同学可以点击这里免费获取》》》》》》》

    展开全文
  • 有好的,有坏的,有快的,有慢的,有短暂的,有持久的 更具马斯洛的金字塔层次需求,最下面的是优先本满足,同时需求实现的难易程度,也是成指数增大 x轴是需求难易程度,y轴是幸福感的强弱 如果主动降低y...

    针对这个话题,也有相关比较理论的书籍论述过

    最近看了李叔同的《佛学.杂记》,有感记录

    增加幸福感唯一方式就是满足自己需求,但是满足自己需求方式有很多。

    有好的,有坏的,有快的,有慢的,有短暂的,有持久的

    更具马斯洛的金字塔层次需求,最下面的是优先本满足,同时需求实现的难易程度,也是成指数增大

     

    x轴是需求难易程度,y轴是幸福感的强弱

    如果主动降低y轴,可以增加幸福感

    举一个例子,深呼吸,憋气,是在不能忍受,然后缓慢呼出【为什么要缓慢呼出,延迟你的满足感,增加满足感的时间效果】

    通过这样就可以使大脑愉悦,从而产生满足感,解压

    呼吸是人的基本需求之一,通过这个是主动降低自己的y轴,暂时降低自身的需求,但是跨域y轴是自己能力之内,所以可以

    轻松的增加自己幸福感

    通过这个例子说明,我们就可以解释生活中一些现象

    三分寒,七分饱;

    还有一个说法是,为什么婴儿饿了,要3分钟以后给他喂奶,这也是这个原理

    省吃俭用是或有必要?

    李叔同的师傅印光大师,每次吃饭,饭碗里面都一粒米都不剩,吃完后,用水冲碗,然后把水给喝了

    按照印光大师的说法是,一个人能有多大的福气才能浪费粮食,要“惜”

    冲表面上是是教人节约,但是从本质来讲,是叫自己约束自己的欲望

    主动降低自己的y轴,

    同时生活在现在物质极大满足的时代,我们生活中大部分物资都是我们不需要的

    及时需要,也是只用一两次,也就不用了,即使闲置哪里,自己心里面,也会产生负罪感

    由此引出了一个捐物===善

    通过这样的方式约束自己欲望【在一定范围内】。

    可以将更多时间投入到自我的修炼中既最求自我实现

    欲望是无处不在的,李叔同还举一个例子,

    他父亲对于方寸白纸的珍惜,这对他产生的巨大影响【李叔同是一个全才,在艺术的各个领域都有很大的造诣】

    “方寸”有多大,通过举这个例子,更事说明“惜”,相信大家对于自身欲望的约束有了一定的看法

     

    这里谈一下个人对“惜”这个字的理解

    人生在世,没有一丝一毫是多余的

     

     

     

    展开全文
  • 8、Service 之间通信 微服务架构的应用由若干 service 组成。比如有运行 httpd 的 web 前端,有提供缓存的 memcached,有存放数据的 mysql,每一层都是 ...但明显的缺点是把 memcached 和 mysql 也暴露到外网,增加

    8、Service 之间通信

    微服务架构的应用由若干 service 组成。比如有运行 httpd 的 web 前端,有提供缓存的 memcached,有存放数据的 mysql,每一层都是 swarm 的一个 service,每个 service 运行了若干容器。在这样的架构中,service 之间是必然要通信的

    服务发现

    一种实现方法是将所有 service 都 publish 出去,然后通过 routing mesh 访问。但明显的缺点是把 memcached 和 mysql 也暴露到外网,增加了安全隐患。
    如果不 publish,那么 swarm 就要提供一种机制,能够:
    1、让 service 通过简单的方法访问到其他 service。
    2、当 service 副本的 IP 发生变化时,不会影响访问该 service 的其他 service。
    3、当 service 的副本数发生变化时,不会影响访问该 service 的其他 service。
    这其实就是服务发现(service discovery)。Docker Swarm 原生就提供了这项功能,通过服务发现,service 的使用者不需要知道 service 运行在哪里,IP 是多少,有多少个副本,就能与 service 通信

    《实践部署》

    1、创建 overlay 网络

    要使用服务发现,需要相互通信的 service 必须属于同一个 overlay 网络,所以我们先得创建一个新的 overlay 网络。只在swarm-manager上创建

    直接使用 ingress 行不行?

    很遗憾,目前 ingress 没有提供服务发现,必须创建自己的 overlay 网络。

    2、部署 service 到 overlay网络

    部署一个 web 服务,并将其挂载到新创建的 overlay 网络。

    [root@swarm-manager ~]# docker service create --name my_web --replicas=3 --network myapp_net httpd
    ytsfzosj6c1jxom7scsltncrf
    overall progress: 3 out of 3 tasks 
    1/3: running   [==================================================>] 
    2/3: running   [==================================================>] 
    3/3: running   [==================================================>] 
    verify: Service converged 
    [root@swarm-manager ~]# 

    部署一个 util 服务用于测试,挂载到同一个 overlay 网络。

    [root@swarm-manager ~]# docker service create --name util --network myapp_net busybox:1.28.3 sleep 10000000
    eiknvmsrrmhkdipxsy2d45hi8
    overall progress: 1 out of 1 tasks 
    1/1: running   
    verify: Service converged 
    [root@swarm-manager ~]# 

    sleep 10000000 的作用是保持 busybox 容器处于运行的状态,我们才能够进入到容器中访问 service my_web

    新版busybox镜像的nslookup命令报错,使用旧版本即可,如1.28.3版

    2、验证

    通过docker service ps util 确认 util 所在的节点为 swarm-worker1。

    [root@swarm-manager ~]# docker service ps util
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE           ERROR               PORTS
    uzub4he2axfb        util.1              busybox:1.28.3      swarm-worker1       Running             Running 6 minutes ago                       
    [root@swarm-manager ~]# 

    登录到 swarm-worker1,在容器 util.1 中 ping 服务 my_web

    [root@swarm-worker1 ~]# docker ps
    CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS               NAMES
    42ce32a9c9f9        busybox:1.28.3      "sleep 10000000"     9 minutes ago       Up 9 minutes                            util.1.uzub4he2axfbvnxaxuwpv9mqr
    941a47d7b8f6        httpd:latest        "httpd-foreground"   14 minutes ago      Up 14 minutes       80/tcp              my_web.2.sgu8fdgnovg7c1xqwukya8cqs
    [root@swarm-worker1 ~]# docker exec  util.1.uzub4he2axfbvnxaxuwpv9mqr ping -c 3 my_web
    PING my_web (10.0.1.18): 56 data bytes
    64 bytes from 10.0.1.18: seq=0 ttl=64 time=0.181 ms
    64 bytes from 10.0.1.18: seq=1 ttl=64 time=0.063 ms
    64 bytes from 10.0.1.18: seq=2 ttl=64 time=0.060 ms
    
    --- my_web ping statistics ---
    3 packets transmitted, 3 packets received, 0% packet loss
    round-trip min/avg/max = 0.060/0.101/0.181 ms
    [root@swarm-worker1 ~]# 

    10.0.1.18 是 my_web service 的 VIP(Virtual IP),swarm 会将对 VIP 的访问负载均衡到每一个副本。

    在工具类service中ping之前创建的web service,是可以ping通的,但解析到的IP并不是副本的IP地址,而是该service的VIP,该VIP具有负载均衡的功能

    我们可以执行下面的命令查看每个副本的 IP。

    [root@swarm-worker1 ~]# docker exec  util.1.uzub4he2axfbvnxaxuwpv9mqr  nslookup tasks.my_web
    Server:    127.0.0.11
    Address 1: 127.0.0.11
    
    Name:      tasks.my_web
    Address 1: 10.0.1.19 my_web.3.jecmwxbyha7wx7iv9akr8z9ku.myapp_net
    Address 2: 10.0.1.20 my_web.1.jqqahg7wkjrqr2q0j3b942jjt.myapp_net
    Address 3: 10.0.1.21 my_web.2.sgu8fdgnovg7c1xqwukya8cqs.myapp_net
    

    10.0.1.1910.0.1.2010.0.1.21 才是各个副本自己的 IP。不过对于服务的使用者(这里是 util.1),根本不需要知道 my_web副本的 IP,也不需要知道 my_web 的 VIP,只需直接用 service 的名字 my_web 就能访问服务。

    9、如何滚动更新 Service

    部署了多个副本的服务,如何滚动更新每一个副本。

    滚动更新降低了应用更新的风险,如果某个副本更新失败,整个更新将暂停,其他副本则可以继续提供服务。
    同时在更新的过程中,总是有副本在运行的,因此也保证了业务的连续性。

    下面我们将部署三副本的httpd服务,httpd:2.4.35  升级到 httpd:2.4.37,以及回滚原来的版本

    1、创建Service 镜像 httpd:2.4.35,副本数3

    [root@localhost ~]# docker service create --name httpd_2435 --replicas 3 httpd:2.4.35 
    k8y1twyg0j8kbb9iv1eiou1kl
    overall progress: 3 out of 3 tasks 
    1/3: running   
    2/3: running   
    3/3: running   
    verify: Service converged 
    [root@localhost ~]# 

    2、更新Service 镜像到 httpd:2.4.37  --image 指定新的镜像。

    [root@swarm-manager ~]# docker service update --image httpd:2.4.37 httpd_2435
    httpd_2435
    overall progress: 3 out of 3 tasks 
    1/3: running   
    2/3: running   
    3/3: running   
    verify: Service converged 
    [root@swarm-manager ~]# 

    Swarm 将按照如下步骤执行滚动更新:
    1、停止第一个副本。
    2、调度任务,选择 worker node。
    3、在 worker 上用新的镜像启动副本。
    4、如果副本(容器)运行成功,继续更新下一个副本;如果失败,暂停整个更新过程

    3、docker service ps 查看更新结果。三个副本都已经更新到 httpd:2.2.32。

    [root@swarm-manager ~]# docker service ps httpd_2435
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                 ERROR               PORTS
    vgtlj9p3wm4f        httpd_2435.1        httpd:2.4.37        swarm-worker2       Running             Running 20 seconds ago                            
    7ie69runo0fk         \_ httpd_2435.1    httpd:2.4.35        swarm-worker2       Shutdown            Shutdown 54 seconds ago                           
    nygpgm3rcktb        httpd_2435.2        httpd:2.4.37        swarm-worker1       Running             Running 57 seconds ago                            
    ifacs8pjc20s         \_ httpd_2435.2    httpd:2.4.35        swarm-worker1       Shutdown            Shutdown about a minute ago                       
    208o4cq1sbyt        httpd_2435.3        httpd:2.4.37        swarm-worker1       Running             Running 56 seconds ago                            
    227v6kftxy0y         \_ httpd_2435.3    httpd:2.4.35        swarm-worker2       Shutdown            Shutdown 56 seconds ago                           
    [root@swarm-manager ~]# 

    4、回滚到之前的版本  --rollback 快速恢复到更新之前的状态

    [root@swarm-manager ~]# docker service update --rollback httpd_2435
    httpd_2435
    rollback: manually requested rollback 
    overall progress: rolling back update: 3 out of 3 tasks 
    1/3: running   
    2/3: running   
    3/3: running   
    verify: Service converged 
    [root@swarm-manager ~]# 

    5、回滚到之前的版本后,新开了一个 httpd:2.4.25,而不是使用之前的

    [root@swarm-manager ~]# docker service ps httpd_2435
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE             ERROR               PORTS
    luu1kh08bt4z        httpd_2435.1        httpd:2.4.35        swarm-worker2       Running             Running 54 seconds ago                        
    vgtlj9p3wm4f         \_ httpd_2435.1    httpd:2.4.37        swarm-worker2       Shutdown            Shutdown 55 seconds ago                       
    7ie69runo0fk         \_ httpd_2435.1    httpd:2.4.35        swarm-worker2       Shutdown            Shutdown 29 minutes ago                       
    idp4k4e1mqj9        httpd_2435.2        httpd:2.4.35        swarm-worker1       Running             Running 52 seconds ago                        
    nygpgm3rcktb         \_ httpd_2435.2    httpd:2.4.37        swarm-worker1       Shutdown            Shutdown 53 seconds ago                       
    ifacs8pjc20s         \_ httpd_2435.2    httpd:2.4.35        swarm-worker1       Shutdown            Shutdown 29 minutes ago                       
    pldxqcs7ccn3        httpd_2435.3        httpd:2.4.35        swarm-worker2       Running             Running 56 seconds ago                        
    208o4cq1sbyt         \_ httpd_2435.3    httpd:2.4.37        swarm-worker1       Shutdown            Shutdown 57 seconds ago                       
    227v6kftxy0y         \_ httpd_2435.3    httpd:2.4.35        swarm-worker2       Shutdown            Shutdown 29 minutes ago                       
    [root@swarm-manager ~]# 

    默认配置下,Swarm 一次只更新一个副本,并且两个副本之间没有等待时间。我们可以通过 --update-parallelism 设置并行更新的副本数目,通过 --update-delay 指定滚动更新的间隔时间。

    下面的例子中,我们有20个副本,更新并发数 4 ,更新延时 10s

    docker service update --image httpd:2.2 --update-parallelism 4 --update-delay 10s httpd_2435

    ps:弹性伸缩时,即增减副本数并不受此限制,会以最快的速度完成伸缩 

    10、Swarm 如何管理数据

    service 的容器副本会 scale up/down ,会 failover,会在不同的主机上创建和销毁,这就引出一个问题,如果Service有要管理的数据,那么这些数据应该如何存放呢?
    选项一:打包在容器里
        显然不行,除非数据不会发生变化,否则,如何在多个副本间保持同步呢
    选项二:数据放在Docker主机的本地目录中,通过 volume 映射到容器中
        位于同一个主机的副本倒是可以共享这个volume,但是不同主机中的副本该如何同步呢? 
    选项三:利用 Docker 的 volume driver ,由外部的storage provider 管理和提供 volume,所有Docker 主机的volume将挂载到各个副本。
        这是目前最好的方案了,volume 不依赖docker主机和容器,生命周期由storage provider 管理,volume 的高可用和数据有效性也全权由provider负责,Docker只管使用。

    下面我们以 nfs 来实践第三种方案
     
    swarm-worker1                nfs-client                                          192.168.1.109
    swarm-worker2                nfs-client                                         192.168.1.108
    swarm-manager               nfs-client + nfs-server(/var/nfs) 192.168.1.107

    1、安装和配置 nfs-server

    [root@swarm-manager ~]# yum install -y  nfs-utils   
    #安装nfs服务
    [root@swarm-manager ~]# yum install -y rpcbind
    #安装rpc服务
    [root@swarm-manager ~]# mkdir /var/nfs
    [root@swarm-manager ~]# cat /etc/exports
    /var/nfs * (rw,sync,no_root_squash)
    [root@swarm-manager ~]# systemctl start rpcbind 
    [root@swarm-manager ~]# systemctl enable rpcbind 
    [root@swarm-manager ~]# systemctl start nfs-server
    [root@swarm-manager ~]# systemctl enable  nfs-server
    Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.
    [root@swarm-manager ~]# showmount -e 192.168.1.107
    Export list for 192.168.1.107:
    /var/nfs *
    [root@swarm-manager ~]# 
    

    2、安装和配置 nfs-client (需要在每台host都创建一遍 volume)

    [root@swarm-manager ~]# docker volume create --driver local --opt type=nfs --opt o=addr=10.9.1.240,rw --opt device=:/var/nfs volume-nfs
    volume-nfs
    [root@swarm-manager ~]# docker volume ls
    DRIVER              VOLUME NAME
    local               volume-nfs
    [root@swarm-manager ~]# 
    [root@swarm-worker1 ~]# yum -y install nfs-utils rpcbind
    [root@swarm-worker1 ~]# showmount -e 192.168.1.107
    Export list for 192.168.1.107:
    /var/nfs *
    [root@swarm-worker1 ~]# docker volume create --driver local --opt type=nfs --opt o=addr=192.168.1.107,rw --opt device=:/var/nfs volume-nfs
    volume-nfs
    [root@swarm-worker1 ~]# docker volume ls
    DRIVER              VOLUME NAME
    local               volume-nfs
    
    [root@swarm-worker2 ~]# yum -y install nfs-utils rpcbind
    [root@swarm-worker2 ~]# showmount -e 192.168.1.107
    Export list for 192.168.1.107:
    /var/nfs *
    [root@swarm-worker2 ~]# docker volume create --driver local --opt type=nfs --opt o=addr=192.168.1.107,rw --opt device=:/var/nfs volume-nfs
    volume-nfs
    [root@swarm-worker2 ~]# docker volume ls
    DRIVER              VOLUME NAME
    local               volume-nfs
    

    3、创建Service 并挂载nfs 的volume,并验证

    [root@swarm-manager ~]# docker service create --name my_web --publish 80:80 --mount type=volume,source=volume-nfs,volume-nocopy=true,destination=/usr/local/apache2/htdocs --replicas 2   httpd
    dhrf6dkql4kwzwfc65u47xopl
    overall progress: 2 out of 2 tasks 
    1/2: running   
    2/2: running   
    verify: Service converged 

    如果遇到报错:

    # 加一个参数volume-nocopy=true 

    4、更新共享目录里/var/nfs/下的html文件,并curl输入

    [root@swarm-manager ~]# cat /var/nfs/index.html
    docker swarm nfs volume test
    [root@swarm-manager ~]# curl http://192.168.1.107
    docker swarm nfs volume test
    [root@swarm-manager ~]# curl http://192.168.1.108
    docker swarm nfs volume test
    [root@swarm-manager ~]# curl http://192.168.1.109
    docker swarm nfs volume test

     5、查看服务

    [root@swarm-manager ~]# docker service ps my_web
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE            ERROR               PORTS
    v4envw9v96qj        my_web.1            httpd:latest        swarm-worker1       Running             Running 17 minutes ago                       
    ma2wnu0eodn4        my_web.2            httpd:latest        swarm-worker2       Running             Running 17 minutes ago                       
    [root@swarm-manager ~]# 
    

    6、查看每个副本容器的信息

    [root@swarm-worker1 ~]# docker exec -it my_web.1.v4envw9v96qj12xx0isv3ezcy cat /usr/local/apache2/htdocs/index.html
    docker swarm nfs volume test
    [root@swarm-worker1 ~]# docker volume inspect volume-nfs
    [
        {
            "CreatedAt": "2020-05-13T02:06:38-04:00",
            "Driver": "local",
            "Labels": {},
            "Mountpoint": "/var/lib/docker/volumes/volume-nfs/_data",
            "Name": "volume-nfs",
            "Options": {
                "device": ":/var/nfs",
                "o": "addr=192.168.1.107,rw",
                "type": "nfs"
            },
            "Scope": "local"
        }
    ]
    [root@swarm-worker1 ~]# docker inspect  my_web.1.v4envw9v96qj12xx0isv3ezcy | jq .[0].Mounts
    -bash: jq: command not found
    #这里是因为容器里没有jq命令实际上输出是下面的信息
    [
      {
        "Type": "volume",
        "Name": "volume-nfs",
        "Source": "/var/lib/docker/volumes/volume-nfs/_data",
        "Destination": "/usr/local/apache2/htdocs",
        "Driver": "local",
        "Mode": "z",
        "RW": true,
        "Propagation": ""
      }
    ]
    

    7、成功将 nfs 的volume挂载到 Service上,验证 Failover时,验证 Swarm 数据持久性

    ①Scale Up   增加副本,并验证数据是否能够同步到新启动的容器上

    [root@swarm-manager ~]# docker service update --replicas 4 my_web
    my_web
    overall progress: 4 out of 4 tasks 
    1/4: running   
    2/4: running   
    3/4: running   
    4/4: running   
    verify: Service converged 
    [root@swarm-manager ~]# docker service ps my_web
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE                ERROR               PORTS
    68g07ulol72e        my_web.1            httpd:latest        swarm-worker1       Running             Running 26 minutes ago                                                    
    ma2wnu0eodn4        my_web.2            httpd:latest        swarm-worker2       Running             Running 50 minutes ago                           
    jwlr705r68iq        my_web.3            httpd:latest        swarm-worker2       Running             Running about a minute ago                       
    gj0pdh1suybq        my_web.4            httpd:latest        swarm-worker1       Running             Running about a minute ago                       
    
    

    ②验证新的副本

    [root@swarm-worker1 ~]# docker inspect  my_web.4.gj0pdh1suybqmpml8nrkit99j | jq .[0].Mounts
    [
      {
        "Type": "volume",
        "Name": "volume-nfs",
        "Source": "/var/lib/docker/volumes/volume-nfs/_data",
        "Destination": "/usr/local/apache2/htdocs",
        "Driver": "local",
        "Mode": "z",
        "RW": true,
        "Propagation": ""
      }
    ]
    [root@swarm-worker1 ~]#  docker exec  my_web.4.gj0pdh1suybqmpml8nrkit99j cat /usr/local/apache2/htdocs/index.html
    docker swarm nfs volume test
    [root@swarm-worker1 ~]# 
    

    ③更新Volume内容,并进行验证

    [root@swarm-manager ~]# echo "add test str" >> /var/nfs/index.html
    [root@swarm-manager ~]# cat /var/nfs/index.html
    docker swarm nfs volume test
    add test str
    [root@swarm-manager ~]# curl http://192.168.1.107
    docker swarm nfs volume test
    add test str
    [root@swarm-manager ~]# curl http://192.168.1.108
    docker swarm nfs volume test
    add test str
    [root@swarm-manager ~]# curl http://192.168.1.109
    docker swarm nfs volume test
    add test str
    [root@swarm-manager ~]# 
    

    ④验证每个副本

    [root@swarm-worker1 ~]#  docker exec  my_web.4.gj0pdh1suybqmpml8nrkit99j cat /usr/local/apache2/htdocs/index.html
    docker swarm nfs volume test
    add test str
    [root@swarm-worker1 ~]#  docker exec my_web.1.68g07ulol72ef8xl72tb3raeg cat /usr/local/apache2/htdocs/index.html
    docker swarm nfs volume test
    add test str
    [root@swarm-worker1 ~]# 
    

    ⑤Failover 验证,warm-work1关机

    关机前状态

    [root@swarm-manager ~]# docker service ps my_web
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE               ERROR               PORTS
    68g07ulol72e        my_web.1            httpd:latest        swarm-worker1       Running             Running 55 minutes ago                                                
    ma2wnu0eodn4        my_web.2            httpd:latest        swarm-worker2       Running             Running about an hour ago                       
    jwlr705r68iq        my_web.3            httpd:latest        swarm-worker2       Running             Running 30 minutes ago                          
    gj0pdh1suybq        my_web.4            httpd:latest        swarm-worker1       Running             Running 30 minutes ago                          
    

    关闭warm-work1

    [root@swarm-manager ~]# docker service ps my_web
    ID                  NAME                IMAGE               NODE                DESIRED STATE       CURRENT STATE               ERROR               PORTS
    bm5ndipc2fym        my_web.1            httpd:latest        swarm-worker2       Ready               Ready 4 seconds ago                                                       
    v4envw9v96qj         \_ my_web.1        httpd:latest        swarm-worker1       Shutdown            Complete 58 minutes ago                         
    ma2wnu0eodn4        my_web.2            httpd:latest        swarm-worker2       Running             Running about an hour ago                       
    jwlr705r68iq        my_web.3            httpd:latest        swarm-worker2       Running             Running 33 minutes ago                          
    a2a0ut21mxm4        my_web.4            httpd:latest        swarm-worker2       Ready               Ready 4 seconds ago                             
    gj0pdh1suybq         \_ my_web.4        httpd:latest        swarm-worker1       Shutdown            Running 17 seconds ago                          
    

    验证warm-work2上的容器

    [root@swarm-worker2 ~]# docker ps
    CONTAINER ID        IMAGE               COMMAND              CREATED              STATUS              PORTS               NAMES
    cd49394e4337        httpd:latest        "httpd-foreground"   2 minutes ago        Up 2 minutes        80/tcp              my_web.4.a2a0ut21mxm42i02w2kwbc2fg
    a228218a0656        httpd:latest        "httpd-foreground"   2 minutes ago        Up 2 minutes        80/tcp              my_web.1.bm5ndipc2fymnanu9113vxrup
    28efe52c66f8        httpd:latest        "httpd-foreground"   35 minutes ago       Up 35 minutes       80/tcp              my_web.3.jwlr705r68iq9cd6rguqaqlf7
    9540958f0b7d        httpd:latest        "httpd-foreground"   About an hour ago    Up About an hour    80/tcp              my_web.2.ma2wnu0eodn4iuspzwp7hdi9z
    [root@swarm-worker2 ~]# docker exec  my_web.1.bm5ndipc2fymnanu9113vxrup cat /usr/local/apache2/htdocs/index.html
    docker swarm nfs volume test
    add test str
    [root@swarm-worker2 ~]# docker exec   my_web.2.ma2wnu0eodn4iuspzwp7hdi9z cat /usr/local/apache2/htdocs/index.html
    docker swarm nfs volume test
    add test str
    [root@swarm-worker2 ~]# docker exec    my_web.4.a2a0ut21mxm42i02w2kwbc2fg cat /usr/local/apache2/htdocs/index.html
    docker swarm nfs volume test
    add test str
    [root@swarm-worker2 ~]# 
    
    [root@swarm-manager ~]# curl http://192.168.1.109    #    因为swarm-work1 关机了,所有swarm-work1 的ip也访问不了了
    curl: (7) Failed to connect to 192.168.1.109 port 80: No route to host
    [root@swarm-manager ~]# curl http://192.168.1.108
    docker swarm nfs volume test
    add test str
    [root@swarm-manager ~]# curl http://192.168.1.107
    docker swarm nfs volume test
    add test str

     

    展开全文
  • 前者终究是个缓存,不可能永久保存数据(LRU机制),支持分布式,后者除了缓存的同时也支持把数据持久化到磁盘等,redis要自己去实现分布式缓存(貌似最新版本的已集成),自己去实现一致性hash。因为不知道你们的应用...
  • 最近在看 webpack 如何持久化缓存的内容,发现其中还是有一些坑点的,正好有时间就将它们整理总结一下,读完本文你大致能够明白: <ol><li>什么是持久化缓存,为什么做持久化缓存?...
  • 好多童鞋都喜欢用长短时记忆(LSTM)模型来预测时序变量,但是训练好的模型如何进行持久化操作呢? Keras提供了一种将模型持久化的方法,简单地说就是使用如下语句: model.save('path/name.h5') 其原理是使用...
  • 如何才能让sentinel像nacos那样,持久化保存配置信息呢? 持久化配置 sentinel针对配置数据的持久化保存有很多种方案,如mysql、redis、文本格式等。此处采取官方推荐的nacos实现持久化配置保存操作。 配置操作以...
  • RDD持久化:操作RDD时如何准确保存结果,cache和persist,checkpoint。 Spark广播:构建算法时,对降低网络传输的数据量,提高内存使用效率,加快程序运行速度很重要。 Spark累加器:全局的指针步减变量,只能增加...
  • 同时,HTTP是基于报文的,因此如何确定报文长度也是协议中比较重要的一点。 Persistent Connections持久连接 ... 在使用持久连接前,HTTP协议规定为获取每个URL资源都需要使用单独的一个TCP连接,这增加了...
  • ActiveMQ具体就不介绍了,直接介绍如何讲ActiveMQ持久化到本地数据库,以SQL Server 2008 R2为例1.下载ActiveMQ后直接解压,我下载的是apache-activemq-5.14.5-bin.zip。 2.打开conf下的activemq.xml,我要配置的是...
  • 如何解决分布式事务

    2020-01-23 15:06:36
    事务有四个特性:原子性、一致性、隔离性、持久性。...但同时也增加了系统复杂度,每个子业务系统都涉及数据库操作,如何解决分布式事务是一个绕不开的话题。 什么是分布式事务,一句话概括:分布式事务...
  • ActiveMQ具体就不介绍了,直接介绍如何讲ActiveMQ持久化到本地数据库,以SQL Server 2008 R2为例1.下载ActiveMQ后直接解压,我下载的是apache-activemq-5.14.5-bin.zip。2.打开conf下的activemq.xml,我要配置的是将...
  • 本地数据库的创建和管理是大多数移动应用工程中的核心组件之一,一般会通过直接使用 SQLite 或 Jetpack Room 持久化库来完成。开发者们也在不断地寻求着更好的方式,使其能够在运行中的应用中直接检查和调试数据库。...
  • 场景, 网站用户的个性化配置较多, 而且容易发生增加和改变, 如何在数据库保存是个问题. 为了避免数据库表字段经常变更, 我们设计在表增加一个varbinary(max)字段存储个性化配置. 个性化的配置则存储到dictionary<...
  • 随着微服务数量不断增加,流量进一步暴增,硬件资源有点不堪重负,那么,如何实现更好的限流熔断降级等流量防护措施,这个课题就摆在了掌门人的面前。由于 Spring Cloud 体系已经演进到第二代,第一代的 Hystrix 限...
  • Spark3.0版本--chapter2.7--RDD持久

    千次阅读 2021-02-24 22:10:24
    如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新...
  • 我们仍然在学习如何利用其在扩展性,易于维护和构建等方面的优势。当然我们也必须承担微服务增加的成本,比如从SOA架构的迁移,编排,备份,以及对技能提升的需求等等。一个典型的微服务架构可能是这样的:Touchbase...
  • 意义:直接使用本地磁盘目录进行存储,增加读写性能 Local Persistent Volume设计难点: 1)如何将本地磁盘抽象成PV 问题:不应该把一个宿主机上的目录当作 PV 使用,因为缺乏io隔离,并且有可能被应用写满,造成...
  • 我们仍然在学习如何利用其在扩展性,易于维护和构建等方面的优势。当然我们也必须承担微服务增加的成本,比如从SOA架构的迁移,编排,备份,以及对技能提升的需求等等。一个典型的微服务架构可能是这样的:Touchbase...
  • 千山万水之Hibernate(一)——对象持久

    千次阅读 热门讨论 2015-03-31 17:36:14
    在学习完三层后,随着学习和工作,逐渐体会到了软件分层的重要性,特别是在面向对象大行其道的今天,我们开发项目,不得不考虑我们的系统该如何分层。三层算是给我们的理论思想给予了基本的指导,但到实际应用之前,...
  • 我在使用Spring Cloud 整合Sentines做限流时,想把规则数据存到Redis中,Sentines官方文档是支持持久化到Redis的,但是我按照官方文档继承AbstractDataSource接口后,不知道该如何调用这个类: 我重写的类如下: ...
  • 随着微服务数量不断增加,流量进一步暴增,硬件资源有点不堪重负,那么,如何实现更好的限流熔断降级等流量防护措施,这个课题就摆在了掌门人的面前。由于Spring Cloud体系已经演进到第二代,第一代的Hystrix限流...
  •   一般情况下,nginx或者web server(不包含MySQL)自身都是不需要保存数据的,对于 web server,数据会保存在专门做持久化的节点上。所以这些节点可以随意扩容或者缩容,只要简单的增加或减少副本的数量就可以。...
  • 随着微服务数量不断增加,流量进一步暴增,硬件资源有点不堪重负,那么,如何实现更好的限流熔断降级等流量防护措施,这个课题就摆在了掌门人的面前。由于 Spring Cloud 体系已经演进到第二代,第一代的 Hystrix 限...
  • 如何学好框架?

    2016-05-02 12:34:00
    1.为什么学习框架? 提高开发效率,框架就是别人写好的工具类,我们需要遵循其规则进行操作。 2.目前我们学习哪些框架?... 2)CRUD是指在做计算处理时的增加(Create)、读取(Retrieve)(重新得到数据)、更新(Updat...
  • 消息队列如何保证消息能百分百成功被消费  目前常用的消息队列有很多种,如...我们可以再增加一个机制,增加一个确认机制: 流程解释: 1)订单服务生产者再投递消息之前,先把消息持久化到Redis或DB中,建议redi...
  • 数据持久化保证了系统在发生宕机或者重启之后数据不会丢失,增加了系统的可靠性和减少了系统不可用的时间(省去了手动恢复数据的过程); 主从数据同步(主从复制) 主从数据同步可以将数据存储至多台服务器,这样...
  • 上一篇文章我们聊了 HTTP/1.1 的发展史,虽然 HTTP/1.1 已经做了大量的优化,但是依然存在很多性能瓶颈,依然不能满足我们日... 增加持久连接; 浏览器为每个域名最多同时维护 6 个 TCP 持久连接; 使用 CD

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 216
精华内容 86
关键字:

如何增加持久