1、前言

又到了金三银四的时候,大家都按耐不住内心的躁动,我在这里给大家分享下之前面试中遇到的一个知识点(zookeeper应用场景),希望对大家有些帮助。如有不足,欢迎大佬们指点指点。

2、zookeeper简介

ZooKeeper 是分布式应用程序的分布式开源协调服务。它公开了一组简单的api,分布式应用程序可以基于这些api实现更高级别的同步、配置维护、分组和命名服务。它被设计为易于编程,并使用一种数据模型,该模型以熟悉的文件系统目录树结构为风格。它在 Java 中运行,并具有 Java 和 C 的绑定。

众所周知,协调服务很难做好。它们特别容易出现竞争条件和死锁等错误。ZooKeeper背后的动机是减轻分布式应用程序从头开始实现协调服务的负担。

3、zookeeper应用场景

下面的代码都需要一个序列化类,所以放在最前面声明

  1. /**
  2. * @author admin
  3. */
  4. public class MyZkSerializer implements ZkSerializer {
  5. String charset = "UTF-8";
  6. @Override
  7. public Object deserialize(byte[] bytes) throws ZkMarshallingError {
  8. try {
  9. return new String(bytes, charset);
  10. } catch (UnsupportedEncodingException e) {
  11. throw new ZkMarshallingError(e);
  12. }
  13. }
  14. @Override
  15. public byte[] serialize(Object obj) throws ZkMarshallingError {
  16. try {
  17. return String.valueOf(obj).getBytes(charset);
  18. } catch (UnsupportedEncodingException e) {
  19. throw new ZkMarshallingError(e);
  20. }
  21. }
  22. }

3.1 配置中心

3.1.1 什么是配置中心呢?

假设咱们的项目部署在5台机子上形成一个集群,那么这5个实例在启动时读取的配置信息应该是一样的,同时一旦咱们的配置信息更改了,需要马上通知到这5个实例上并生效,这就是配置中心的功能。

3.1.2 zookeeper怎么实现配置中心呢?

必要条件

1、znode能存储数据

2、Watch能监听数据改变

实现方式

  1. 一个配置项对应一个zNode
  1. // 1 将单个配置放到zookeeper上
  2. public void putZk() {
  3. ZkClient client = new ZkClient("192.168.10.11:2181");
  4. client.setZkSerializer(new MyZkSerializer());
  5. String configPath = "/config1";
  6. String value = "1111111";
  7. if (client.exists(configPath)) {
  8. client.writeData(configPath, value);
  9. } else {
  10. client.createPersistent(configPath, value);
  11. }
  12. client.close();
  13. }
  1. // 需要配置的服务都从zk上取,并注册watch来实时获得配置更新
  2. public void getConfigFromZk() {
  3. ZkClient client = new ZkClient("192.168.10.11:2181");
  4. client.setZkSerializer(new MyZkSerializer());
  5. String configPath = "/config1";
  6. String value = client.readData(configPath);
  7. System.out.println("从zk读到配置config1的值为:" + value);
  8. // 监控配置的更新,基于watch实现发布订阅功能
  9. client.subscribeDataChanges(configPath, new IZkDataListener() {
  10. @Override
  11. public void handleDataDeleted(String dataPath) throws Exception {
  12. // TODO 配置删除业务处理
  13. }
  14. @Override
  15. public void handleDataChange(String dataPath, Object data) throws Exception {
  16. System.out.println("获得更新的配置值:" + data);
  17. }
  18. });
  19. // 这里只是为演示实时获取到配置值更新而加的等待。实际项目应用中根据具体场景写(可用阻塞方式)
  20. try {
  21. Thread.sleep(5 * 60 * 1000);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. }

  1. 一个配置文件对应一个zNode
  1. // 将配置文件的内容存放到zk节点上
  2. public void putConfigFile2ZK() throws IOException {
  3. File f = new File(this.getClass().getResource("/config.xml").getFile());
  4. FileInputStream fin = new FileInputStream(f);
  5. byte[] datas = new byte[(int) f.length()];
  6. fin.read(datas);
  7. fin.close();
  8. ZkClient client = new ZkClient("192.168.10.11:2181");
  9. client.setZkSerializer(new BytesPushThroughSerializer());
  10. String configPath = "/config2";
  11. if (client.exists(configPath)) {
  12. client.writeData(configPath, datas);
  13. } else {
  14. client.createPersistent(configPath, datas);
  15. }
  16. client.close();
  17. }

获取整个配置文件的方式跟步骤1类似,只不过需要解析对应的配置文件而已。

3.2 命名服务(注册中心)

3.2.1 什么是注册中心?

注册中心主要存储注册实例应用的名称和ip地址,供其他服务通过RPC来调用,其他服务只关心你的服务名是啥,而不必关心你的服务器地址对不对,有没有上线。

3.2.2 zookeeper怎么实现注册中心呢?

首先是服务发现问题,当一个实例启动后会向zookeeper创建一个临时节点,并存入自己的服务信息(包括应用名和ip等),其他服务通过zookeeper拿到该实例的注册信息即可调用。

一旦该服务宕机了或者主动下线,那么该临时节点则会被删除,其他服务通过watch监听到下线通知,也就不会在去调用该服务。

3.3 Master选举

3.3.1 什么是Master选举?

在一个主从部署的集群里,一般master实例负责所有请求的读写功能,其他slave实例同步master的数据,一旦master节点不可用了,那么就需要从他的slave实例中重新选举一个节点作为master实例。

3.3.2 zookeeper怎么实现Master选举呢?

首先是实例去竞争创建临时决定(Master节点),谁创建成功谁就是master,否则就是slave。

同时所有的实例都需要去servers节点(临时节点)注册自己的服务信息,方便通过该节点获取到所有在线的实例,有点类似注册中心的意思。

下面咱们通过代码来模拟一下master选举

  1. /**
  2. * @author yinfeng
  3. */
  4. public class Server {
  5. private final String cluster;
  6. private final String name;
  7. private final String address;
  8. private final String path, value;
  9. private String master;
  10. public Server(String cluster, String name, String address) {
  11. super();
  12. this.cluster = cluster;
  13. this.name = name;
  14. this.address = address;
  15. path = "/" + this.cluster + "Master";
  16. value = "name:" + name + " address:" + address;
  17. final ZkClient client = new ZkClient("192.168.10.11:2181");
  18. client.setZkSerializer(new MyZkSerializer());
  19. final Thread thread = new Thread(() -> {
  20. electionMaster(client);
  21. });
  22. thread.setDaemon(true);
  23. thread.start();
  24. }
  25. /**
  26. * 选举方法
  27. **/
  28. public void electionMaster(ZkClient client) {
  29. try {
  30. client.createEphemeral(path, value);
  31. master = client.readData(path);
  32. System.out.println(value + "创建节点成功,成为Master");
  33. } catch (ZkNodeExistsException e) {
  34. master = client.readData(path);
  35. System.out.println("Master为:" + master);
  36. }
  37. // 为阻塞自己等待而用
  38. final CountDownLatch cdl = new CountDownLatch(1);
  39. // 注册watcher
  40. IZkDataListener listener = new IZkDataListener() {
  41. @Override
  42. public void handleDataDeleted(String dataPath) throws Exception {
  43. System.out.println("-----监听到节点被删除");
  44. cdl.countDown();
  45. }
  46. @Override
  47. public void handleDataChange(String dataPath, Object data) throws Exception {
  48. }
  49. };
  50. client.subscribeDataChanges(path, listener);
  51. // 让自己阻塞
  52. if (client.exists(path)) {
  53. try {
  54. cdl.await();
  55. } catch (InterruptedException e1) {
  56. e1.printStackTrace();
  57. }
  58. }
  59. // 醒来后,取消watcher
  60. client.unsubscribeDataChanges(path, listener);
  61. // 递归调自己(下一次选举)
  62. electionMaster(client);
  63. }
  64. }

咱们通过启动多个服务来看看是否测试成功

  1. public static void main(String[] args) {
  2. // 测试时,依次开启多个Server实例java进程,然后停止获取的master的节点,看谁抢到Master
  3. Server s = new Server("cluster1", "server1", "192.168.1.11:8991");
  4. Server s1 = new Server("cluster1", "server2", "192.168.1.11:8992");
  5. Server s2 = new Server("cluster1", "server3", "192.168.1.11:8993");
  6. Server s3 = new Server("cluster1", "server4", "192.168.1.11:8994");
  7. try {
  8. Thread.sleep(100000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }



可以看到功能一切正常

3.4 分布式队列

3.4.1 什么是分布式队列?

队列的定义是先进先出,而在分布式环境下保证先进先出的队列就是分布式队列,有点类似于消息队列。

3.4.2 zookeeper怎么实现分布式队列?

由上图可知,zookeeper主要通过顺序节点来保证队列的先进先出。

3.5 分布式锁

3.5.1 什么是分布式锁?

分布式锁指的是控制分布式系统不同进程共同访问共享资源的一种锁的实现。 如果在不同的系统或同一个系统的不同主机之间共享和竞争某个临界资源,往往需要互斥来防止彼此干扰,避免出现脏数据或非业务数据,保证数据一致性。

3.5.2 zookeeper通过临时节点实现布式锁?

实现原理是zookeeper节点不可重名和watch的监听通知机制,使用临时节点主要是为了避免获取锁的节点由于异常原因无法释放锁而导致出现死锁情况。



竞争锁流程如下图:



代码实现如下

  1. /**
  2. * @author yinfeng
  3. */
  4. public class ZKDistributeLock implements Lock {
  5. private String lockPath;
  6. private ZkClient client;
  7. // 锁重入计数
  8. private ThreadLocal<Integer> reentrantCount = new ThreadLocal<>();
  9. public ZKDistributeLock(String lockPath) {
  10. super();
  11. this.lockPath = lockPath;
  12. client = new ZkClient("192.168.10.11:2181");
  13. client.setZkSerializer(new MyZkSerializer());
  14. }
  15. @Override
  16. public boolean tryLock() {
  17. // 锁重入不会阻塞
  18. if (this.reentrantCount.get() != null) {
  19. int count = this.reentrantCount.get();
  20. if (count > 0) {
  21. this.reentrantCount.set(++count);
  22. return true;
  23. }
  24. }
  25. // 创建节点
  26. try {
  27. client.createEphemeral(lockPath);
  28. this.reentrantCount.set(1);
  29. } catch (ZkNodeExistsException e) {
  30. return false;
  31. }
  32. return true;
  33. }
  34. @Override
  35. public void unlock() {
  36. // 重入释进行放锁处理
  37. if (this.reentrantCount.get() != null) {
  38. int count = this.reentrantCount.get();
  39. if (count > 1) {
  40. this.reentrantCount.set(--count);
  41. return;
  42. } else {
  43. this.reentrantCount.set(null);
  44. }
  45. }
  46. client.delete(lockPath);
  47. }
  48. @Override
  49. public void lock() {
  50. // 如果获取不到锁,阻塞等待
  51. if (!tryLock()) {
  52. // 没获得锁,阻塞自己
  53. waitForLock();
  54. // 再次尝试
  55. lock();
  56. }
  57. }
  58. private void waitForLock() {
  59. final CountDownLatch cdl = new CountDownLatch(1);
  60. IZkDataListener listener = new IZkDataListener() {
  61. @Override
  62. public void handleDataDeleted(String dataPath) throws Exception {
  63. System.out.println("----收到节点被删除了-------------");
  64. cdl.countDown();
  65. }
  66. @Override
  67. public void handleDataChange(String dataPath, Object data) throws Exception {
  68. }
  69. };
  70. client.subscribeDataChanges(lockPath, listener);
  71. // 阻塞自己
  72. if (this.client.exists(lockPath)) {
  73. try {
  74. cdl.await();
  75. } catch (InterruptedException e) {
  76. e.printStackTrace();
  77. }
  78. }
  79. // 取消注册
  80. client.unsubscribeDataChanges(lockPath, listener);
  81. }
  82. @Override
  83. public void lockInterruptibly() {
  84. }
  85. @Override
  86. public boolean tryLock(long time, TimeUnit unit) {
  87. return false;
  88. }
  89. @Override
  90. public Condition newCondition() {
  91. return null;
  92. }
  93. }

咱们在写个测试类试一下效果,通过多线程来模拟多实例竞争锁

  1. public static void main(String[] args) {
  2. // 并发数
  3. int currency = 50;
  4. // 循环屏障
  5. final CyclicBarrier cb = new CyclicBarrier(currency);
  6. // 多线程模拟高并发
  7. for (int i = 0; i < currency; i++) {
  8. new Thread(() -> {
  9. System.out.println(Thread.currentThread().getName() + "---------我准备好---------------");
  10. // 等待一起出发
  11. try {
  12. cb.await();
  13. } catch (InterruptedException | BrokenBarrierException e) {
  14. e.printStackTrace();
  15. }
  16. ZKDistributeLock lock = new ZKDistributeLock("/distLock11");
  17. try {
  18. lock.lock();
  19. System.out.println(Thread.currentThread().getName() + " 获得锁!");
  20. try {
  21. Thread.sleep(1000 * 2);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. } finally {
  26. lock.unlock();
  27. System.out.println(Thread.currentThread().getName() + " 释放锁!");
  28. }
  29. }
  30. ).start();
  31. }
  32. }

可以看到功能是正常的,但也有个很明显的问题,就是一旦释放锁之后所有的实例(线程)都会收到通知然后去重新竞争锁,当实例的数量达到一定程度之后,那么势必会对zookeeper造成很大的带宽和性能消耗,严重的话可能会把zookeeper集群搞挂了,这种情况也叫惊群效应,所以只通过顺序节点实现分布式锁还是有一定的问题的,下面咱们再来优化一下。

3.5.3 zookeeper通过临时顺序节点实现布式锁?

既然通过临时节点会造成惊群效应,那么咱们是否能将临时和顺序节点结合起来,通过最小的那个zNode节点来视为获得锁的标志呢?

答案是肯定能的,当释放锁时只通知他的下一个节点即可,完美的避免了惊群效应的发生。

原理图如下



流程图如下



接着咱们通过代码来实现吧

  1. /**
  2. * @author yinfeng
  3. */
  4. public class ZKDistributeImproveLock implements Lock {
  5. /**
  6. * 利用临时顺序节点来实现分布式锁
  7. * 获取锁:取排队号(创建自己的临时顺序节点),然后判断自己是否是最小号,如是,则获得锁;不是,则注册前一节点的watcher,阻塞等待
  8. * 释放锁:删除自己创建的临时顺序节点
  9. */
  10. private final String lockPath;
  11. private final ZkClient client;
  12. private ThreadLocal<String> currentPath = new ThreadLocal<>();
  13. private ThreadLocal<String> beforePath = new ThreadLocal<>();
  14. /**
  15. * 锁重入计数
  16. */
  17. private ThreadLocal<Integer> reentrantCount = new ThreadLocal<>();
  18. public ZKDistributeImproveLock(String lockPath) {
  19. super();
  20. this.lockPath = lockPath;
  21. client = new ZkClient("192.168.10.11:2181");
  22. client.setZkSerializer(new MyZkSerializer());
  23. if (!this.client.exists(lockPath)) {
  24. try {
  25. this.client.createPersistent(lockPath);
  26. } catch (ZkNodeExistsException ignored) {
  27. }
  28. }
  29. }
  30. @Override
  31. public boolean tryLock() {
  32. // 重入则直接返回获得锁成功
  33. if (this.reentrantCount.get() != null) {
  34. int count = this.reentrantCount.get();
  35. if (count > 0) {
  36. this.reentrantCount.set(++count);
  37. return true;
  38. }
  39. }
  40. if (this.currentPath.get() == null) {
  41. currentPath.set(this.client.createEphemeralSequential(lockPath + "/", "aaa"));
  42. }
  43. // 获得所有的子节点
  44. List<String> children = this.client.getChildren(lockPath);
  45. // 排序list
  46. Collections.sort(children);
  47. // 判断当前节点是否是最小的
  48. if (currentPath.get().equals(lockPath + "/" + children.get(0))) {
  49. this.reentrantCount.set(1);
  50. return true;
  51. } else {
  52. // 取到前一个
  53. // 得到字节的索引号
  54. int curIndex = children.indexOf(currentPath.get().substring(lockPath.length() + 1));
  55. beforePath.set(lockPath + "/" + children.get(curIndex - 1));
  56. }
  57. return false;
  58. }
  59. @Override
  60. public void lock() {
  61. if (!tryLock()) {
  62. // 阻塞等待
  63. waitForLock();
  64. // 再次尝试加锁
  65. lock();
  66. }
  67. }
  68. private void waitForLock() {
  69. final CountDownLatch cdl = new CountDownLatch(1);
  70. // 注册watcher
  71. IZkDataListener listener = new IZkDataListener() {
  72. @Override
  73. public void handleDataDeleted(String dataPath) throws Exception {
  74. System.out.println("-----监听到节点被删除");
  75. cdl.countDown();
  76. }
  77. @Override
  78. public void handleDataChange(String dataPath, Object data) throws Exception {
  79. }
  80. };
  81. client.subscribeDataChanges(this.beforePath.get(), listener);
  82. // 让自己阻塞
  83. if (this.client.exists(this.beforePath.get())) {
  84. try {
  85. cdl.await();
  86. } catch (InterruptedException e) {
  87. e.printStackTrace();
  88. }
  89. }
  90. // 醒来后,取消watcher
  91. client.unsubscribeDataChanges(this.beforePath.get(), listener);
  92. }
  93. @Override
  94. public void unlock() {
  95. // 重入的释放锁处理
  96. if (this.reentrantCount.get() != null) {
  97. int count = this.reentrantCount.get();
  98. if (count > 1) {
  99. this.reentrantCount.set(--count);
  100. return;
  101. } else {
  102. this.reentrantCount.set(null);
  103. }
  104. }
  105. // 删除节点
  106. this.client.delete(this.currentPath.get());
  107. }
  108. @Override
  109. public void lockInterruptibly() throws InterruptedException {
  110. }
  111. @Override
  112. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  113. return false;
  114. }
  115. @Override
  116. public Condition newCondition() {
  117. return null;
  118. }
  119. }

最后咱们再来测试一下

  1. public static void main(String[] args) {
  2. // 并发数
  3. int currency = 50;
  4. // 循环屏障
  5. final CyclicBarrier cb = new CyclicBarrier(currency);
  6. // 多线程模拟高并发
  7. for (int i = 0; i < currency; i++) {
  8. new Thread(() -> {
  9. System.out.println(Thread.currentThread().getName() + "---------我准备好---------------");
  10. // 等待一起出发
  11. try {
  12. cb.await();
  13. } catch (InterruptedException | BrokenBarrierException e) {
  14. e.printStackTrace();
  15. }
  16. ZKDistributeImproveLock lock = new ZKDistributeImproveLock("/distLock");
  17. try {
  18. lock.lock();
  19. System.out.println(Thread.currentThread().getName() + " 获得锁!");
  20. try {
  21. Thread.sleep(1000 * 2);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. } finally {
  26. lock.unlock();
  27. System.out.println(Thread.currentThread().getName() + " 释放锁!");
  28. }
  29. }).start();
  30. }
  31. }

可以看到功能是正常的,同时在释放锁的时候只通知了下一节点,没有出现惊群效应,非常完美。

4、总结

在微服务和分布式的时代,zookeeper作为协调服务的代表,在面试中很容易被问到,希望大家能掌握这方面的知识,提高自己的核心竞争力,在谈薪的时候拿到最高的那个区间。

最后,外出打工不易,希望各位兄弟找到自己心仪的工作,虎年发发发! 也希望兄弟们能关注、点赞、收藏、评论支持一波,非常感谢大家!

阿里一面,说说你了解zookeeper的应用场景有哪些?的更多相关文章

  1. ZooKeeper典型应用场景

    ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新.例 ...

  2. ZooKeeper典型应用场景一览

    原文地址:http://jm-blog.aliapp.com/?p=1232 ZooKeeper典型应用场景一览 数据发布与订阅(配置中心) 发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据 ...

  3. ZooKeeper典型应用场景(转)

    ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...

  4. ZooKeeper典型应用场景概览

    ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得ZooKeeper解决很多分布式问题.网上 ...

  5. 搞懂分布式技术6:Zookeeper典型应用场景及实践

    搞懂分布式技术6:Zookeeper典型应用场景及实践 一.ZooKeeper典型应用场景实践 ZooKeeper是一个高可用的分布式数据管理与系统协调框架.基于对Paxos算法的实现,使该框架保证了 ...

  6. ZOOKEEPER典型应用场景解析

    zookeeper实现了主动通知节点变化,原子创建节点,临时节点,按序创建节点等功能.通过以上功能的组合,zookeeper能够在分布式系统中组合出很多上层功能.下面就看几个常用到的场景,及使用方式和 ...

  7. ZooKeeper 典型应用场景-数据发布与订阅

    ZooKeeper 是一个高可用的分布式数据管理与系统协调框架.基于对 Paxos 算法的实现,使该框架保证了分布式环境中数据的强一致性,也正是基于这样的特性,使得 ZooKeeper 可以解决很多分 ...

  8. 面试题:4个zookeeper的应用场景,你知道几个?

    前言 现在聊的 topic 是分布式系统,面试官跟你聊完了 dubbo 相关的一些问题之后,已经确认你对分布式服务框架/RPC框架基本都有一些认知了.那么他可能开始要跟你聊分布式相关的其它问题了. 分 ...

  9. zookeeper系列(四)zookeeper的使用场景

    作者:leesf    掌控之中,才会成功:掌控之外,注定失败. 出处:http://www.cnblogs.com/leesf456/p/6036548.html感谢原著公开这么好的博文供大家学习 ...

  10. 阿里云提供全托管 ZooKeeper

    自 2010 年左右第一次引入以来,Apache ZooKeeper 目前在阿里巴巴集团内部已经有了将近 10 年的发展,使用的场景非常广泛,基于 ZooKeeper 强一致性的特点,被用在了分布式锁 ...

随机推荐

  1. Byobu安装与使用

    机子为Ubuntu18 Byobu安装 sudo apt-get install byobu Byobu安装后默认禁用,需要启用Byobu,之后每次登陆自动启用Byobu byobu-enable 还 ...

  2. Solution -「国家集训队」「洛谷 P2839」Middle

    \(\mathcal{Description}\)   Link.   给定序列 \(\{a_n\}\),\(q\) 组询问,给定 \(a<b<c<d\),求 \(l\le[a,b] ...

  3. c++ 程序编译后运行时的内存分配

    程序编译后运行时的内存分配 太好的文章了,看到不得不转,转自:http://blog.sina.com.cn/s/blog_5420e0000101a0w1.html 一.编译时与运行时的内存情况 1 ...

  4. Spring Boot数据访问之整合Mybatis

    在Mybatis整合Spring - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中谈到了Spring和Mybatis整合需要整合的点在哪些方面,需要将Mybatis中数据库连接池等相关对 ...

  5. tomcat 配置https证书 ssl

    修改tomcat-conf-server.xml,原配置文件是 <Connector connectionTimeout="20000" port="8080&qu ...

  6. Apache-log4j漏洞复现

    前言:昨天晚上当我还在睡梦中时,圈内爆出了核弹级的漏洞,今天我复现一下, 再开始前我们先建立一个maven项目,将pom.xml文件导入 <?xml version="1.0" ...

  7. sql server 数据字符串替换函数

    sql server 替换函数 replace 函数参数 REPLACE(string_expression, string_pattern, string_replacement) 1.string ...

  8. Ecma335、CLR、CLI、CTS、 IL、.net 以及他们之间的关系

    以上是个人对他们直接关系的理解:图片是原创 CLI 通用语言基础架构(Common Language Infrastructure), CLI是一个开放型的技术规范,它定义了一个语言无关的跨体系结构的 ...

  9. 【C#基础概念】编程语言:弱类型、强类型、动态类型、静态类型

    一.看图区别编程语言 一般来讲,看第一个图就够了 这图是引用的,有错误,Python是强类型,但是图片中却归为弱类型了. 业界堆静态和动态的区分达到共识. 但是堆强类型和弱类型语言还未达成共识.我个人 ...

  10. SQL Server 2005 - 让 SELECT 查詢結果额外增加递增序号

    /* 方法一*/SELECT 序號= (SELECT COUNT(客戶編號) FROM 客戶 AS LiMing                 WHERE LiMing.客戶編號<= Chan ...